fix(ai): require member identity within a shared org for report routing, overlays, and coverage

Codex review round 7 on e90a72bdd flagged that broker usage-report
matching, header-overlay keying, and omp-usage coverage all treated a
matching organization as a sufficient match. Two Team members share the
org id while drawing on per-user pools, so the first same-org report
(or a lone sibling report) was handed to the wrong member. The org is
now a gate: within the same-org subset the member's own base identity
(account/email/project) must still match, with org-only entities (no
base identifiers) matching on the org alone when unambiguous. The
overlay merger (findMatchingReportIndex) had the identical same-org
flaw and receives the symmetric fix. Org-presence-mismatch semantics
are unchanged: org-scoped vs org-less stays fall-through/unreported,
and both-org-less keeps the legacy base-identity fallback.
This commit is contained in:
chan1103
2026-07-11 22:49:12 +09:00
parent 45203a1b56
commit e095af3be0
6 changed files with 210 additions and 30 deletions
+23 -10
View File
@@ -303,20 +303,33 @@ export function collectUnreportedAccounts(
// org-scoped account is covered only by its own org's report, and an
// org-less legacy account is never covered by an org-attributed sibling
// report — its own fetch failing must surface as "no usage data". The
// email/account fallback below applies only when both sides are
// org-less.
// shared org is a GATE, not a match: two Team members share the org id
// while drawing on per-user pools, so coverage also requires the
// account's own base identity inside the same-org subset (an org-only
// account, with no base identifiers, is covered by any same-org
// report). The email/account fallback below applies only when both
// sides are org-less.
const accountOrg = account.orgId?.toLowerCase();
const reportedOrgs = new Set<string>();
for (const report of providerReports) {
const metaOrg = report.metadata?.orgId;
if (typeof metaOrg === "string" && metaOrg) reportedOrgs.add(metaOrg.toLowerCase());
}
if (accountOrg || reportedOrgs.size > 0) {
return !(accountOrg !== undefined && reportedOrgs.has(accountOrg));
}
const ids = [account.email, account.accountId, account.projectId]
.filter((value): value is string => typeof value === "string" && value.length > 0)
.map(value => value.toLowerCase());
const sameOrgReports: UsageReport[] = [];
let sawReportOrg = false;
for (const report of providerReports) {
const metaOrg = report.metadata?.orgId;
if (typeof metaOrg === "string" && metaOrg) {
sawReportOrg = true;
if (accountOrg !== undefined && metaOrg.toLowerCase() === accountOrg) sameOrgReports.push(report);
}
}
if (accountOrg || sawReportOrg) {
if (!accountOrg || sameOrgReports.length === 0) return true;
if (ids.length === 0) return false;
return !sameOrgReports.some(report => {
const identifiers = reportIdentifiers(report);
return ids.some(id => identifiers.has(id));
});
}
if (ids.length === 0) return false;
const reported = new Set<string>();
let anyIdentified = false;