Problem
The current Usage → Limits identity layer is forced to infer subscription identity from display-ish fields:
That works for some cases, but it cannot represent the actual invariant: which logical subscription/workspace/quota bucket does this snapshot belong to?
Real failures already exist:
Existing stronger identities
The provider edges already expose stronger IDs:
- Codex/CLIProxy ID tokens carry
chatgpt_account_id.
- Claude keeps
oauthAccount.organizationUuid in .claude.json; reset-credit redemption already reads that UUID.
- Hub auth records already have stable per-account IDs.
The shared contracts currently discard these before packages/shared/src/usageLimits.ts.
Proposed contract
Add one optional stable logical identity to both native and hub account snapshots:
ServerProviderAuth {
...
accountId?: string
}
UsageLimitSourceAccount {
...
accountId?: string
}
Semantics:
accountId identifies the logical subscription/workspace/quota source for grouping.
- It is provider-scoped; collectors key on
driver + accountId.
- It is not a display label and should not be rendered raw by default.
- Email, plan and organization name remain display/fallback metadata.
- Missing
accountId keeps today's conservative fallback behavior.
Provider mappings:
- Codex →
chatgpt_account_id when present.
- Claude →
organizationUuid when present.
- Hub adapters → the same provider-native identity when available.
Grouping precedence
For account dedupe/merge:
driver + accountId when both observations provide it;
- provider-specific fallback such as email + org/plan when stable identity is missing;
- environment/source instance identity when equivalence cannot be established.
An unknown ID must never be treated as matching a known ID merely because email/plan happens to match when multiple known accounts exist.
Why this shape
This is the same separation Tokenomics now uses with measurement_source.logical_source_id: observer identity, human labels and logical quota identity are different concepts.
It also lets #10845 and #13089 remain useful incremental fixes without making plan strings or organization display names the long-term identity contract.
Acceptance
Related
Problem
The current Usage → Limits identity layer is forced to infer subscription identity from display-ish fields:
auth.email+auth.label;email+plan;That works for some cases, but it cannot represent the actual invariant: which logical subscription/workspace/quota bucket does this snapshot belong to?
Real failures already exist:
Existing stronger identities
The provider edges already expose stronger IDs:
chatgpt_account_id.oauthAccount.organizationUuidin.claude.json; reset-credit redemption already reads that UUID.The shared contracts currently discard these before
packages/shared/src/usageLimits.ts.Proposed contract
Add one optional stable logical identity to both native and hub account snapshots:
Semantics:
accountIdidentifies the logical subscription/workspace/quota source for grouping.driver + accountId.accountIdkeeps today's conservative fallback behavior.Provider mappings:
chatgpt_account_idwhen present.organizationUuidwhen present.Grouping precedence
For account dedupe/merge:
driver + accountIdwhen both observations provide it;An unknown ID must never be treated as matching a known ID merely because email/plan happens to match when multiple known accounts exist.
Why this shape
This is the same separation Tokenomics now uses with
measurement_source.logical_source_id: observer identity, human labels and logical quota identity are different concepts.It also lets #10845 and #13089 remain useful incremental fixes without making plan strings or organization display names the long-term identity contract.
Acceptance
chatgpt_account_id;organizationUuid;Related