koala73/worldmonitor · error · ConvexError

ACCOUNT_OWNER_FENCE_CONFLICT

ACCOUNT_OWNER_FENCE_CONFLICT

Error message

ACCOUNT_OWNER_FENCE_CONFLICT

What it means

Account-lookup fence conflict raised via findAccountByOwnerFence: the owner-fence hash candidate resolution produced a conflicting or ambiguous match among companyMonitoringAccounts rows. The owner fence hash binds an account to exactly one owner identity, so a conflict means the account-to-owner mapping is inconsistent — a fence-hash collision or a stale duplicate account.

Solutions

  1. Inspect all companyMonitoringAccounts rows matching the ownerFenceHash candidates for duplicates or stale rows
  2. If a stale duplicate exists, let the purge/reaper flow terminalize it rather than manually patching fences
  3. Verify the owner-fence hash computation inputs (owner identity fields) have not changed format
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at convex/companyMonitoring/accounts.ts:122 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21). Data as JSON: /api/errors/d4ad12d1779ddb4c. Report an issue: GitHub.

Appendix: source

Thrown at convex/companyMonitoring/accounts.ts:122

    Math.max(0, delayMs),
    internal.companyMonitoring.accounts.advanceAccountPurge,
    { ownerFenceHash, purgeGeneration },
  );
}

async function findAccountByOwnerFence(
  ctx: MutationCtx,
  ownerFence: CompanyMonitoringOwnerFenceCandidates,
): Promise<{ account: Doc<"companyMonitoringAccounts">; matchedHash: string } | null> {
  let match: { account: Doc<"companyMonitoringAccounts">; matchedHash: string } | null = null;
  for (const ownerFenceHash of ownerFence.all) {
    const account = await ctx.db
      .query("companyMonitoringAccounts")
      .withIndex("by_ownerFenceHash", (q) => q.eq("ownerFenceHash", ownerFenceHash))
      .unique();
    if (!account) continue;
    if (match && match.account._id !== account._id) {
      throw new ConvexError("ACCOUNT_OWNER_FENCE_CONFLICT");
    }
    match = { account, matchedHash: ownerFenceHash };
  }
  return match;
}

async function findAccountByOwnerUserId(
  ctx: MutationCtx,
  ownerUserId: string,
): Promise<Doc<"companyMonitoringAccounts"> | null> {
  const accounts = await ctx.db
    .query("companyMonitoringAccounts")
    .withIndex("by_ownerUserId", (q) => q.eq("ownerUserId", ownerUserId))
    .take(2);
  if (accounts.length > 1) throw new ConvexError("ACCOUNT_OWNER_FENCE_CONFLICT");
  return accounts[0] ?? null;
}

View on GitHub (pinned to eeab0a219f)