{"record":{"id":"01e9ac4823d87d8a","repo":"koala73/worldmonitor","slug":"company-monitoring-access-denied-01e9ac","errorCode":null,"errorMessage":"COMPANY_MONITORING_ACCESS_DENIED","messagePattern":"COMPANY_MONITORING_ACCESS_DENIED","errorType":"exception","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/companyMonitoring/accounts.ts","lineNumber":352,"sourceCode":" * Provisioning delegates to the same state machine the reaper uses, so a\n * terminal tombstone still refuses to yield an active account: the sync returns\n * the terminal row untouched and the re-resolve below rejects it.\n */\nexport async function ensureActiveAccount(\n  ctx: MutationCtx,\n  ownerUserId: string,\n  knownEntitlement?: Doc<\"entitlements\"> | null,\n) {\n  const existing = await activeAccountForOwner(ctx, ownerUserId, knownEntitlement);\n  if (existing) return existing;\n  await syncCompanyMonitoringAccountFromEntitlement(ctx, ownerUserId);\n  return activeAccountForOwner(ctx, ownerUserId, knownEntitlement);\n}\n\n/** `requireActiveAccount` for the entry points that may provision. */\nexport async function requireProvisionedAccount(ctx: MutationCtx, ownerUserId: string) {\n  const account = await ensureActiveAccount(ctx, ownerUserId);\n  if (!account) throw new ConvexError(\"COMPANY_MONITORING_ACCESS_DENIED\");\n  return account;\n}\n\nasync function terminalize(\n  ctx: MutationCtx,\n  ownerUserId: string,\n  terminalReason: \"owner_deleted\" | \"account_deleted\",\n  existing?: Doc<\"companyMonitoringAccounts\"> | null,\n) {\n  const ownerFence = await companyMonitoringOwnerFenceCandidates(ownerUserId);\n  const ownerFenceHash = ownerFence.current;\n  const match = await findAccountByOwnerFence(ctx, ownerFence);\n  if (existing && match && existing._id !== match.account._id) {\n    throw new ConvexError(\"ACCOUNT_OWNER_FENCE_CONFLICT\");\n  }\n  const account = existing ?? match?.account ?? null;\n  const now = Date.now();\n  if (!account) {","sourceCodeStart":334,"sourceCodeEnd":370,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/companyMonitoring/accounts.ts#L334-L370","documentation":"Thrown by requireProvisionedAccount (accounts.ts:350-353) after ensureActiveAccount fails to find or provision an active account for the ownerUserId. Unlike requireActiveAccount, this path first attempts to provision (sync from entitlement) and only throws if provisioning also yields no active account. This is the entry-point guard for mutations that are allowed to create the account on first use.","triggerScenarios":"The user has no active entitlement (free plan, expired, features.tier === 0). The user's entitlement validUntil is in the past. The entitlement row exists but the account cannot be provisioned because canonical.active is false. The userId is an anonymous browser UUID (ANON_ID_V4_REGEX) which is deliberately skipped.","commonSituations":"A free-tier user invokes a provisioning-eligible Company Monitoring mutation. A user's subscription expired between the UI check and the backend call. The entitlement sync has not yet propagated. An anonymous (pre-claim) user attempts the action.","solutions":["Confirm the user has an active paid entitlement (planKey !== 'free', features.tier > 0, validUntil >= now) before invoking the mutation.","If the user is anonymous (browser UUID), they must complete claimSubscription to bind a real owner before Company Monitoring is available.","If the entitlement exists but provisioning fails, inspect the entitlements row and canonicalEntitlement output for the userId.","Gate the client UI on entitlement status so the mutation is never called for ineligible users."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before calling a provisioning-eligible Company Monitoring endpoint:\nconst entitled = entitlement && entitlement.planKey !== \"free\" && entitlement.features.tier > 0 && entitlement.validUntil >= Date.now();\nif (!entitled) {\n  // show paywall; do not call the backend\n}","typeGuard":null,"tryCatchPattern":"try {\n  await ctx.runMutation(internal.companyMonitoring.someProvisioningEndpoint, args);\n} catch (err) {\n  if (err instanceof ConvexError && err.message === \"COMPANY_MONITORING_ACCESS_DENIED\") {\n    // user not entitled or anonymous — surface paywall/claim flow\n    throw new Error(\"Company Monitoring requires an active PRO subscription.\");\n  }\n  throw err;\n}","preventionTips":["Ensure anonymous (browser-UUID) users complete claimSubscription before exposing Company Monitoring.","Gate the client UI on entitlement so provisioning endpoints are never called for ineligible users.","If provisioning fails for an entitled user, inspect the entitlements row and canonicalEntitlement output."],"tags":["convex","entitlement","authorization","company-monitoring","access-control","provisioning"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}