Skip to content

Commit b731b4c

Browse files
committed
Centralize Codex dashboard ownership into CodexDashboardAuthority
Introduces a single policy engine that evaluates dashboard ownership (attach/displayOnly/failClosed) with allowed effects and cleanup sets, replacing six scattered ownership-sensitive consumers across app and CLI paths. Guards against circular evidence by filtering dashboard-derived data from ownership proof. Adds credits provenance tracking and wires authority decisions through the app refresh, CLI live fetch, and CLI cache restore paths. Includes comprehensive test coverage with worked example parity tests proving app/CLI agreement.
1 parent 6aacc91 commit b731b4c

25 files changed

Lines changed: 3850 additions & 822 deletions

Sources/CodexBar/Providers/Codex/UsageStore+CodexAccountState.swift

Lines changed: 92 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ extension UsageStore {
3434
phaseDidChange?(.credits)
3535

3636
if self.settings.codexCookieSource.isEnabled {
37-
let expectedGuard = self.currentCodexAccountScopedRefreshGuard()
37+
let expectedGuard = self.currentCodexOpenAIWebRefreshGuard()
3838
await self.refreshOpenAIDashboardIfNeeded(
3939
force: true,
4040
expectedGuard: expectedGuard,
@@ -76,6 +76,7 @@ extension UsageStore {
7676
self.lastCreditsError = nil
7777
self.lastCreditsSnapshot = nil
7878
self.lastCreditsSnapshotAccountKey = nil
79+
self.lastCreditsSource = .none
7980
self.creditsFailureStreak = 0
8081

8182
self.clearCodexOpenAIWebStateForAccountTransition(targetEmail: self.codexAccountEmailForOpenAIDashboard())
@@ -179,31 +180,106 @@ extension UsageStore {
179180
return currentGuard.identity == expectedGuard.identity
180181
}
181182

182-
func shouldApplyOpenAIDashboardResult(
183+
func shouldApplyOpenAIDashboardRefreshGuard(
183184
expectedGuard: CodexAccountScopedRefreshGuard,
184-
dashboardAccountEmail: String?) -> Bool
185+
routingTargetEmail: String?) -> Bool
185186
{
187+
let normalizedRoutingTargetEmail = CodexIdentityResolver.normalizeEmail(routingTargetEmail)
188+
let currentGuard = self.currentCodexOpenAIWebRefreshGuard()
189+
guard currentGuard.source == expectedGuard.source else { return false }
190+
186191
if expectedGuard.identity != .unresolved {
187-
let currentGuard = self.currentCodexAccountScopedRefreshGuard()
188-
guard currentGuard.source == expectedGuard.source else { return false }
189192
return currentGuard.identity == expectedGuard.identity
190193
}
191194

192-
let currentGuard = self.currentCodexOpenAIWebRefreshGuard()
193-
guard currentGuard.source == expectedGuard.source else { return false }
194195
guard case .liveSystem = expectedGuard.source else { return false }
195196
guard currentGuard.identity == .unresolved else { return false }
196-
let dashboardIdentity = CodexIdentityResolver.resolve(accountId: nil, email: dashboardAccountEmail)
197-
guard dashboardIdentity != .unresolved else { return false }
198-
let currentTargetIdentity = CodexIdentityResolver.resolve(
199-
accountId: nil,
200-
email: self.currentCodexOpenAIWebTargetEmail(
197+
return CodexIdentityResolver.normalizeEmail(
198+
self.currentCodexOpenAIWebTargetEmail(
201199
allowCurrentSnapshotFallback: true,
202-
allowLastKnownLiveFallback: false))
203-
if currentTargetIdentity != .unresolved {
204-
return dashboardIdentity == currentTargetIdentity
200+
allowLastKnownLiveFallback: false)) == normalizedRoutingTargetEmail
201+
}
202+
203+
func shouldApplyOpenAIWebNonSuccessResult(
204+
expectedGuard: CodexAccountScopedRefreshGuard,
205+
routingTargetEmail: String?) -> Bool
206+
{
207+
self.shouldApplyOpenAIDashboardRefreshGuard(
208+
expectedGuard: expectedGuard,
209+
routingTargetEmail: routingTargetEmail)
210+
}
211+
212+
func codexDashboardKnownOwnerCandidates() -> [CodexDashboardKnownOwnerCandidate] {
213+
let snapshot = self.settings.codexAccountReconciliationSnapshot
214+
var candidates = snapshot.storedAccounts.map { account in
215+
CodexDashboardKnownOwnerCandidate(
216+
identity: snapshot.runtimeIdentity(for: account),
217+
normalizedEmail: CodexIdentityResolver.normalizeEmail(snapshot.runtimeEmail(for: account)))
205218
}
206-
return true
219+
220+
if let liveSystemAccount = snapshot.liveSystemAccount {
221+
candidates.append(CodexDashboardKnownOwnerCandidate(
222+
identity: snapshot.runtimeIdentity(for: liveSystemAccount),
223+
normalizedEmail: CodexIdentityResolver.normalizeEmail(liveSystemAccount.email)))
224+
}
225+
226+
return candidates
227+
}
228+
229+
func trustedCurrentCodexUsageEmailForDashboardAuthority() -> String? {
230+
guard let sourceLabel = self.lastSourceLabels[.codex], sourceLabel != "openai-web" else {
231+
return nil
232+
}
233+
return CodexIdentityResolver.normalizeEmail(self.snapshots[.codex]?.accountEmail(for: .codex))
234+
}
235+
236+
func currentCodexDashboardExpectedScopedEmail() -> String? {
237+
switch self.settings.codexResolvedActiveSource {
238+
case .liveSystem:
239+
CodexIdentityResolver.normalizeEmail(
240+
self.settings.codexAccountReconciliationSnapshot.liveSystemAccount?.email)
241+
case .managedAccount:
242+
CodexIdentityResolver.normalizeEmail(self.currentManagedCodexRuntimeEmail())
243+
}
244+
}
245+
246+
func makeCodexDashboardAuthorityInput(
247+
dashboard: OpenAIDashboardSnapshot,
248+
sourceKind: CodexDashboardSourceKind,
249+
routingTargetEmail: String?) -> CodexDashboardAuthorityInput
250+
{
251+
let source = self.settings.codexResolvedActiveSource
252+
return CodexDashboardAuthorityInput(
253+
sourceKind: sourceKind,
254+
proof: CodexDashboardOwnershipProofContext(
255+
currentIdentity: self.currentCodexOpenAIWebIdentity(source: source),
256+
expectedScopedEmail: self.currentCodexDashboardExpectedScopedEmail(),
257+
trustedCurrentUsageEmail: self.trustedCurrentCodexUsageEmailForDashboardAuthority(),
258+
dashboardSignedInEmail: dashboard.signedInEmail,
259+
knownOwners: self.codexDashboardKnownOwnerCandidates()),
260+
routing: CodexDashboardRoutingHints(
261+
targetEmail: CodexIdentityResolver.normalizeEmail(routingTargetEmail),
262+
lastKnownDashboardRoutingEmail: CodexIdentityResolver.normalizeEmail(
263+
self.lastKnownLiveSystemCodexEmail)))
264+
}
265+
266+
func evaluateCodexDashboardAuthority(
267+
dashboard: OpenAIDashboardSnapshot,
268+
sourceKind: CodexDashboardSourceKind,
269+
routingTargetEmail: String?) -> (input: CodexDashboardAuthorityInput, decision: CodexDashboardAuthorityDecision)
270+
{
271+
let input = self.makeCodexDashboardAuthorityInput(
272+
dashboard: dashboard,
273+
sourceKind: sourceKind,
274+
routingTargetEmail: routingTargetEmail)
275+
return (input, CodexDashboardAuthority.evaluate(input))
276+
}
277+
278+
func codexDashboardAttachmentEmail(from input: CodexDashboardAuthorityInput) -> String? {
279+
CodexIdentityResolver.normalizeEmail(
280+
input.proof.expectedScopedEmail ??
281+
input.proof.trustedCurrentUsageEmail ??
282+
input.proof.dashboardSignedInEmail)
207283
}
208284

209285
func rememberLiveSystemCodexEmailIfNeeded(_ email: String?) {

Sources/CodexBar/Providers/Codex/UsageStore+CodexRefresh.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ extension UsageStore {
3636
self.lastCreditsError = nil
3737
self.lastCreditsSnapshot = credits
3838
self.lastCreditsSnapshotAccountKey = expectedGuard.accountKey
39+
self.lastCreditsSource = .api
3940
self.creditsFailureStreak = 0
4041
self.lastCodexAccountScopedRefreshGuard = expectedGuard
4142
}
@@ -69,6 +70,7 @@ extension UsageStore {
6970
self.lastCodexAccountScopedRefreshGuard = expectedGuard
7071
} else {
7172
self.credits = nil
73+
self.lastCreditsSource = .none
7274
self.lastCreditsError = "Codex credits are still loading; will retry shortly."
7375
}
7476
}
@@ -89,6 +91,7 @@ extension UsageStore {
8991
} else {
9092
self.lastCreditsError = message
9193
self.credits = nil
94+
self.lastCreditsSource = .none
9295
}
9396
}
9497
}

Sources/CodexBar/UsageStore+HistoricalPace.swift

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,26 +66,36 @@ extension UsageStore {
6666
self.setCodexHistoricalDataset(nil, accountKey: nil)
6767
return
6868
}
69-
let ownership = self.codexHistoricalOwnershipContext(dashboard: self.openAIDashboard)
69+
let ownership = self.codexHistoricalOwnershipContext()
7070
let dataset = await self.historicalUsageHistoryStore.loadCodexDataset(
7171
canonicalAccountKey: ownership.canonicalKey,
7272
canonicalEmailHashKey: ownership.canonicalEmailHashKey,
7373
legacyEmailHash: ownership.legacyEmailHash,
7474
hasAdjacentMultiAccountVeto: ownership.hasAdjacentMultiAccountVeto)
7575
self.setCodexHistoricalDataset(dataset, accountKey: ownership.canonicalKey)
7676
if let dashboard = self.openAIDashboard {
77-
self.backfillCodexHistoricalFromDashboardIfNeeded(dashboard)
77+
let authority = self.evaluateCodexDashboardAuthority(
78+
dashboard: dashboard,
79+
sourceKind: .liveWeb,
80+
routingTargetEmail: self.lastOpenAIDashboardTargetEmail)
81+
self.backfillCodexHistoricalFromDashboardIfNeeded(
82+
dashboard,
83+
authorityDecision: authority.decision,
84+
attachedAccountEmail: self.codexDashboardAttachmentEmail(from: authority.input))
7885
}
7986
}
8087

81-
func backfillCodexHistoricalFromDashboardIfNeeded(_ dashboard: OpenAIDashboardSnapshot) {
88+
func backfillCodexHistoricalFromDashboardIfNeeded(
89+
_ dashboard: OpenAIDashboardSnapshot,
90+
authorityDecision: CodexDashboardAuthorityDecision,
91+
attachedAccountEmail: String?)
92+
{
8293
guard self.settings.historicalTrackingEnabled else { return }
94+
guard authorityDecision.allowedEffects.contains(.historicalBackfill) else { return }
8395
guard !dashboard.usageBreakdown.isEmpty else { return }
8496

8597
let codexSnapshot = self.snapshots[.codex]
86-
let ownership = self.codexHistoricalOwnershipContext(
87-
preferredEmail: codexSnapshot?.accountEmail(for: .codex),
88-
dashboard: dashboard)
98+
let ownership = self.codexHistoricalOwnershipContext(preferredEmail: attachedAccountEmail)
8999
let referenceWindow: RateWindow
90100
let calibrationAt: Date
91101
if let dashboardWeekly = dashboard.secondaryLimit {
@@ -126,8 +136,7 @@ extension UsageStore {
126136
}
127137

128138
private func codexHistoricalOwnershipContext(
129-
preferredEmail: String? = nil,
130-
dashboard: OpenAIDashboardSnapshot? = nil) -> CodexHistoricalOwnershipContext
139+
preferredEmail: String? = nil) -> CodexHistoricalOwnershipContext
131140
{
132141
let resolvedIdentity = self.currentCodexRuntimeIdentity(
133142
source: self.settings.codexResolvedActiveSource,
@@ -139,9 +148,7 @@ extension UsageStore {
139148
let normalizedEmail = CodexIdentityResolver.normalizeEmail(
140149
preferredEmail ??
141150
activeSourceEmail ??
142-
self.snapshots[.codex]?.accountEmail(for: .codex) ??
143-
dashboard?.signedInEmail ??
144-
self.codexAccountEmailForOpenAIDashboard())
151+
self.snapshots[.codex]?.accountEmail(for: .codex))
145152
let canonicalIdentity: CodexIdentity = switch resolvedIdentity {
146153
case .unresolved:
147154
if let normalizedEmail {

0 commit comments

Comments
 (0)