Addresses the Codex review round on c3fcb4fa7:
- Active-account matching (/logout preselection, /usage 'in use by this
session' marker) treated a shared org as sufficient: two Anthropic Team
seats in one org (same orgId, per-user pools, distinct email/account)
matched each other's rows and reports. The org is now a gate that
qualifies the base identity — mismatched org presence or different orgs
still never match (round-3 semantics unchanged), a shared org falls
through to the account/email/project checks, and only an org-only
active identity (no base identifiers recovered) matches on the org
alone.
- An org-only credential row keyed org:<id> (stored when login recovered
neither email nor account) was never claimed by a later same-org login
that does recover the identity, duplicating one subscription across two
rows. matchesReplacementCredential now upgrades and re-keys such a row
in place; the upgrade stays one-way — an org-only incoming key still
only claims rows via exact key equality.
Regression tests: same-org different-member rows/reports stay unmatched
while the member's own row matches; org-only identities match same-org
rows on the org alone; org-only row upgraded in place on identity
recovery with the one-way direction preserved.
Addresses the second review round (internal re-review + Codex on c38840482):
- Active-account matching (logout preselection, /usage in-use marker) is
org-decisive when EITHER side carries an org: a legacy bare-email active
row no longer flags org-scoped siblings via the shared email (reverse of
the previous fix). Both-org-less keeps the email/account fallback, so
providers without orgs are unaffected.
- Status-line usage context key includes orgId, so rotating between two
same-email subscriptions invalidates the cached quota immediately
instead of showing the previous org's numbers for the cache TTL.
- CredentialHealthResult carries orgId/orgName and auth-gateway check
labels rows with the org, so a failing row names the subscription.
- getOAuthAccountIdentity preserves org-only identities; the login
success message renders them.
- ACP /usage account-id fallback labels get the org suffix too.
- Regression tests for both matching directions (marker + logout).
Addresses Codex review on #5170:
- OAuthAccess/OAuthAccessFailure and every resolution site now carry
orgId/orgName; dry-balance bench keys and labels are org-qualified so
two same-email subscriptions stay two benchmark targets.
- When the active identity is org-scoped, logout active-marking and the
/usage in-use marker match ONLY the same org — an org-less legacy row
or pre-upgrade report can no longer be flagged active via the shared
email, so the logout preselection cannot land on the wrong row.
- Regression tests: org-scoped active vs sibling org and vs legacy
bare-email row (logout + in-use marker), org-suffixed logout labels.
- Extracted `limitMatchesActiveAccount`/`reportMatchesActiveAccount` into `slash-commands/helpers/active-oauth-account.ts` as the single definition of the report-to-account matching rules, including projectId matching against `limit.scope.projectId`/metadata.
- Dropped the duplicated `ActiveAccountIdentity`/`OAuthAccessResolver` shims, `as unknown` session casts, the dead `getOAuthAccountId` fallback, and the email-vs-scope-accountId comparison from `command-controller.ts` and `usage-report.ts`.
- Replaced the async per-provider `resolveActiveAccountsForReports` map with one synchronous typed `authStorage.getOAuthAccountIdentity()` call per render, gated to the session's current provider.
- Re-exported `OAuthAccountIdentity` from `session/auth-storage.ts` and added `active-oauth-account.test.ts` covering the matching rules.