fix(ai): org-qualify account/project fallback identities; org-decisive routing on either side
Addresses the fourth review round (Codex no-email finding on d37e3992c, confirmed and scoped by internal review): - resolveProviderCredentialIdentityKey: the anthropic org qualifier now rides on whichever base identity exists (email > account > project), not only email. The account UUID is identical across the orgs of one login account, so the bare account fallback let a second subscription replace the first whenever the email could not be recovered (token response omits it AND bootstrap fails). Org-only credentials key on the org alone instead of losing identity entirely. - matchesReplacementCredential: the one-way legacy claim strips a trailing |org: from ANY anthropic base key (account/project included); only anthropic keys carry the qualifier, so other providers are unaffected. - Usage-report dedupe falls back to the org-qualified account for no-email anthropic reports instead of returning no identifiers. - Broker report/overlay routing (matchUsageReport/findMatchingReportIndex) is org-decisive on EITHER side: an org-less legacy credential no longer receives an org-attributed sibling's pool via the lone-candidate or email/account fallback, and an org-less overlay only merges into org-less reports. - omp usage unreported-account attribution follows the same either-side rule, so a legacy row whose fetch failed surfaces as 'no usage data' instead of being hidden by a sibling's report. - Regression tests: no-email identity coexistence/replace/claim, no-email report dedupe, org-less broker routing, either-side unreported attribution.
This commit is contained in:
@@ -139,6 +139,30 @@ describe("collectUnreportedAccounts", () => {
|
||||
const unreported = collectUnreportedAccounts(anonymous, accounts);
|
||||
expect(unreported).toEqual([{ provider: "cerebras", type: "api_key" }]);
|
||||
});
|
||||
|
||||
it("attributes org-decisively when either side carries an org", () => {
|
||||
const shared = "shared@example.test";
|
||||
const orgAccounts: UsageAccountIdentity[] = [
|
||||
{ provider: "anthropic", type: "oauth", email: shared, orgId: "org-team" },
|
||||
{ provider: "anthropic", type: "oauth", email: shared, orgId: "org-max" },
|
||||
{ provider: "anthropic", type: "oauth", email: shared },
|
||||
];
|
||||
const teamReport = {
|
||||
...makeReport("anthropic", shared, []),
|
||||
metadata: { email: shared, orgId: "org-team" },
|
||||
};
|
||||
// Only the Team org reported: Max and the org-less legacy row must both
|
||||
// surface as unreported despite the shared email.
|
||||
const unreported = collectUnreportedAccounts([teamReport], orgAccounts);
|
||||
expect(unreported).toEqual([
|
||||
{ provider: "anthropic", type: "oauth", email: shared, orgId: "org-max" },
|
||||
{ provider: "anthropic", type: "oauth", email: shared },
|
||||
]);
|
||||
// Both sides org-less: the email fallback still covers the account.
|
||||
const orglessReport = { ...makeReport("anthropic", shared, []), metadata: { email: shared } };
|
||||
const orglessAccounts: UsageAccountIdentity[] = [{ provider: "anthropic", type: "oauth", email: shared }];
|
||||
expect(collectUnreportedAccounts([orglessReport], orglessAccounts)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatUsageBreakdown", () => {
|
||||
|
||||
Reference in New Issue
Block a user