{"record":{"id":"29e9d1f0ed074b0f","repo":"koala73/worldmonitor","slug":"already-revoked","errorCode":"ALREADY_REVOKED","errorMessage":"ALREADY_REVOKED","messagePattern":"ALREADY_REVOKED","errorType":"error_code","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/apiKeys.ts","lineNumber":183,"sourceCode":"      revokedAt: k.revokedAt,\n      scopes: k.scopes,\n      companyMonitoringAccountId: k.companyMonitoringAccountId,\n    }));\n  },\n});\n\n/** Revoke a key owned by the current user. */\nexport const revokeApiKey = mutation({\n  args: { keyId: v.id(\"userApiKeys\") },\n  handler: async (ctx, args) => {\n    const userId = await requireUserId(ctx);\n    const key = await ctx.db.get(args.keyId);\n\n    if (!key || key.userId !== userId) {\n      throw new ConvexError(\"NOT_FOUND\");\n    }\n    if (key.revokedAt) {\n      throw new ConvexError(\"ALREADY_REVOKED\");\n    }\n\n    await ctx.db.patch(args.keyId, { revokedAt: Date.now() });\n    return { ok: true, keyHash: key.keyHash };\n  },\n});\n\n// ---------------------------------------------------------------------------\n// Internal (service-to-service) — called from HTTP actions / middleware\n// ---------------------------------------------------------------------------\n\n/**\n * Look up an API key by its SHA-256 hash.\n * Returns the key row (with userId) if found and not revoked, else null.\n * Used by the edge gateway to validate incoming API keys.\n */\nexport const validateKeyByHash = internalQuery({\n  args: { keyHash: v.string() },","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/convex/apiKeys.ts#L165-L201","documentation":"A ConvexError thrown by the revokeApiKey mutation when the target userApiKeys document exists, belongs to the calling user, but already has a non-null revokedAt timestamp. It fires when a client attempts to revoke an already-revoked API key, guarding against double-revocation and overwriting the original revocation time. It is a sentinel guard; the input at fault is the keyId of a previously revoked key. Clients should treat this key as already inactive rather than retrying.","triggerScenarios":"Thrown at convex/apiKeys.ts:183 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Treat the error as success — the desired end state (revoked) already holds","Have callers check revokedAt before calling revoke, or catch ALREADY_REVOKED and return a no-op success"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}