Skip to content

feat(quota): generalize historical pace across providers - #58

Merged
Nanako0129 merged 30 commits into
mainfrom
codex/plan-provider-pace
Jul 17, 2026
Merged

feat(quota): generalize historical pace across providers#58
Nanako0129 merged 30 commits into
mainfrom
codex/plan-provider-pace

Conversation

@Nanako0129

Copy link
Copy Markdown
Owner

Summary

This PR generalizes quota pace from the Codex Weekly special case to every eligible recurring percentage window exposed by Codex, Claude, Grok, Antigravity, and Copilot.

Rust now owns account-scoped history identity, duration evidence, lifecycle state, historical expected usage, ETA, will-last, and run-out risk. Swift decodes the typed result, applies the selected Historical/Linear/Off policy, and renders explicit learning or unavailable states without reconstructing backend history.

No provider network request was added, vendor/ is unchanged, and the existing tb_agent_usage() / tb_free() ownership contract is preserved.

Data and ownership changes

Layer Change
Rust account scope Adds domain-separated HMAC account scopes and authenticated lineage metadata without persisting raw account identifiers or credentials
Installation key Stores one exact 32-byte random key in the hardened Application Support directory (0700 directory, 0600 file), with atomic replacement, cross-process locking, reload-on-every-resolution, and fail-closed path/inode/mode/length checks
Duration lifecycle Resolves duration using provider -> contract -> observed, with durable watching -> candidate -> ready observed-rollover state
History Adds a generic v3 store keyed by { providerId, accountScope, windowKey }, bounded to 512 series / 65,536 samples, with 48 phase buckets, complete-cycle confidence gates, monotonic expected curves, ETA, will-last, and risk
Codex migration Imports only byte-exact current-account Codex v2 records into v3; v1 is ignored and legacy files remain read-only
Provider adapters Classifies stable cardId / windowKey, account scope, reset, and duration for all five providers; malformed or unknown evidence fails closed
Rust wire Emits required typed paceStatus plus an optional coherent historicalPace; windowMinutes remains derived compatibility output only
Swift core Strictly decodes v3 invariants, treats only a missing whole paceStatus as legacy, and rejects null, unknown, contradictory, or out-of-range payloads
Selection Uses `clientId
Presentation Distinguishes learning duration, learning history, available historical pace, typed unavailable reasons, and legacy data

Pace state contract

State Historical mode Linear mode
learningDuration Learning reset duration; no pace or projection No pace or projection
learningHistory Learning history · Linear estimate using Rust-owned exact duration Linear estimate
available Uses the coherent Rust historical result Ignores historical values and computes Linear from the exact duration
unavailable Displays the typed reason; no fallback Displays the typed reason; no fallback
legacy missing key Displays legacy unavailable copy; never revives Linear from windowMinutes Same

Historical warning color is restricted to an actual historical-basis deficit. The quota bar's existing green/amber/red remaining-capacity colors are independent health signals and do not grant historical deficit semantics.

Claude Extra Usage remains unavailable(missingReset) by contract.

Persistence and recovery

The account-scope installation key no longer uses Keychain. Ad-hoc rebuilds cannot provide a stable designated requirement for the previous creator-default legacy ACL, which caused authorization prompts across rebuilds.

The replacement file path enforces exact owner-only permissions and regular-file identity. Creation and key-loss recovery share the account-scope process mutex and metadata file lock. Pre-replace failures leave no partial winner; a post-replace parent-directory sync failure may leave a valid winner but still fails the current poll, and the next poll reloads and validates it.

If a missing key is accompanied by canonical metadata, v3 history, or a production-canonical orphan marker, metadata is quarantined, v3 remains in place, a new key is created, and the first winning poll returns typed unavailable before subsequent polls use the persisted winner. Existing malformed keys are never silently replaced. The old development Keychain item is ignored rather than read, modified, migrated, or deleted.

Fixtures and downstream handoff

  • Adds a Rust production-serializer-locked provider-quota-pace-v3.json fixture consumed by the production Swift decoder and CrossCheck harness.
  • Covers seven lifecycle windows plus projection, selection, legacy, malformed-contract, duplicate-label, and exact-duration cases.
  • Adds a deterministic --demo popover fixture marked FIXTURE for learningDuration, learningHistory, available historical deficit, and typed unavailable acceptance.
  • Documents the Windows DTO/state-machine/selection/presentation delta. Windows port and parity remain pending; this PR does not write to the Windows repository.

Verification

Gate Result
tb_core_ffi tests 225 passed
vendored tokscale-core tests 1,052 passed, 1 ignored
integration tests 10 passed
Hermes tests 3 passed
streaming snapshot regression 1 passed
cargo clippy --workspace --all-targets Passed
make build Passed
swift run TokenBar --selftest Passed, including deterministic pace lifecycle and historical-only color assertions
Knowledge self-test / validation / make check-docs Passed
Production Rust serializer -> production Swift decoder fixture Passed
Monitored live smoke Exit 0; no SecurityAgent or authorizationhost; live directory 0700, key file 0600, exact 32-byte metadata
Historical / Linear / Off popover acceptance Passed with window-only captures; test processes were intentionally terminated after capture
Fresh post-GUI verifier CONFIRMED

The full workspace cargo fmt --all -- --check still reports only pre-existing out-of-scope formatting in hourly_report.rs, model_report.rs, and vendored sources. Touched Rust modules pass scoped formatting checks. The smoke command's print-only hourly and Agents drift probes reported small mismatches; those diagnostics do not fail the smoke contract and their cause was not attributed to account-scope storage in this scope.

Out of scope

  • Windows implementation or parity claims
  • Provider authentication redesign
  • Additional provider requests
  • History management UI
  • Developer ID-gated migration back to a restrictive Keychain ACL
  • Merge, tag, release, appcast, or Homebrew changes

The future Developer ID migration is tracked as a parked item. It must preserve and byte-verify the same 32-byte installation key before cutover so account scopes and history do not reset.

🤖 Generated with Claude Code

Nanako0129 and others added 23 commits July 17, 2026 10:43
Promote provider-wide quota pacing to the canonical design and retain the shipped Codex Weekly v2 work as migration and evaluator evidence rather than the final cross-provider contract.

Freeze the end-to-end Rust-owned lifecycle for recurring percentage cards: secure account scope derivation, stable series identity, provider/contract/observed duration precedence, durable rollover state, cycle-aware sampling and retention, confidence gates, coherent historical output, and current-account-only Codex v2 import. The plan also records exact provider mappings for Codex, Claude, Grok, Antigravity, and Copilot, plus the required paceStatus wire invariants and Mac presentation states.

Route the knowledge index and current-state pages to the new plan, mark the Codex-specific plan superseded, and document Stage 0-7 ownership, stop conditions, verification commands, Windows zero-write handoff, and release authorization boundaries.

Verification: python3 scripts/check_knowledge.py --self-test; python3 -m py_compile scripts/check_knowledge.py; python3 scripts/check_knowledge.py; make check-docs; git diff --cached --check.

Co-Authored-By: Claude <noreply@anthropic.com>
Introduce the Rust-owned foundation for provider-neutral quota pacing without changing the provider wire or Swift presentation layer in this commit.

Add `agent_account_scope` with a non-synchronizing Security.framework installation key, domain-separated HMAC-SHA256 scopes, authenticated lineage metadata, app-controlled refresh transfer, owner-only persistence, descriptor/path inode checks, and atomic no-overwrite quarantine. The lock order keeps network and Keychain work outside metadata and v3 transactions, while metadata failures fail closed for pace history without blocking credential refresh.

Add `agent_quota_duration` with provider -> contract -> observed precedence, strict current-cycle evidence validation, and the durable watching -> candidate -> ready rollover lifecycle. Adjacent resets must be observed within the frozen 15-minute boundary window; backward, sliding, missed, malformed, and over-400-day evidence remains unavailable or returns to duration learning instead of guessing a cycle.

Add the schema-3 quota history transaction engine keyed by providerId, opaque accountScope, and semantic windowKey. One process mutex plus an fs2 file lock owns load, migration, duration transition, 48-bucket sampling, cycle-aware retention, deterministic capacity admission, atomic save, and 169-point evaluation. The store preserves exact 28-31 day durations, exposes retained complete-cycle counts, handles stale callers against the lock-time clock, and imports only byte-exact current-account Codex v2 samples while leaving v2 bytes, mtime, and path unchanged.

Expose the existing exact-half weighted median to the generic evaluator, register the three focused modules, and add the hmac, sha2, fs2, and macOS security-framework dependencies required by the frozen security contract.

Verification used an isolated tree exported from the staged index: `cargo test -p tb_core_ffi` passed 178 tests, and `cargo clippy --workspace --all-targets` completed with existing dead-code and argument-count warnings only. Stage 1, Stage 2, and Stage 3 also passed their fresh-context verifier gates before staging.

Co-Authored-By: Claude <noreply@anthropic.com>
Connect the existing quota fetch paths to the secure account-scope, duration, and generic history core while preserving the current C ABI entry point and heap ownership contract.

Extend Rust `UsageWindow` serialization with required `cardId` and nested `paceStatus` data, exact duration evidence, complete-cycle counts, and optional coherent `historicalPace`. `enrich_snapshot` now records each provider snapshot through one batch transaction, maps results back in provider order, removes later card/window identity collisions, skips history when account scope or early classification is unavailable, and rejects contradictory duration, identity, ETA, will-last, risk, or historical fields before JSON leaves Rust.

Route Codex and Claude windows through stable semantic keys and exact provider or contract durations. Codex refresh keeps request-account authority and gates v2 migration on the successful request's actual account ID and accepted opaque scope. Claude credential refresh preserves lineage, JSON and header windows converge on the same 5-hour and 7-day identities, Extra Usage remains `missingReset`, and cached 429 snapshots clear stale scope before enrichment.

Attach the same foundation to Grok, Antigravity, and Copilot. Grok uses the exact auth.x.ai credential lineage and separates weekly, monthly, observed, and unknown period routes. Antigravity uses authenticated local identity or remote credential lineage and carries exact model IDs into structural quota rows. Copilot selects the first valid OpenCode refresh/access credential, keeps Premium and Chat histories separate, omits zero-entitlement placeholders, and applies the UTC first-of-month calendar contract without adding a provider request.

Retain the Stage 0 Swift self-test assertion that characterizes the pre-Stage-5 silent Linear fallback; this commit does not yet change Swift decoding or presentation semantics.

Verification used an isolated tree exported from the staged index: `cargo test -p tb_core_ffi` passed 209 tests, `cargo clippy --workspace --all-targets` completed with existing warnings only, the release `tb_core_ffi` static library built successfully, and `swift run TokenBar --selftest` passed. The provider-neutral Stage 4A wire contract also passed a fresh verifier after adversarial fixes for complete-cycle ownership, chained identity collisions, and ETA/will-last equivalence.

Co-Authored-By: Claude <noreply@anthropic.com>
Complete the Codex adapter identity contract without reintroducing a Weekly-only history path.

Keep recognized 18,000-second and 604,800-second main windows mapped to `main.session.v1` and `main.weekly.v1` regardless of whether the provider reports them as primary or secondary. Unknown main durations now retain a provider-slot structural `cardId`, omit `windowKey`, serialize `unavailable(windowIdentity)`, and never enter the history batch.

Build identified additional-limit series from the trimmed `metered_feature` value when available, otherwise the trimmed `limit_name`, hashing that semantic source before adding the fixed primary or secondary slot. When both identity fields are missing, retain only the provider-order first row for each slot, use the structural `row.additional.unknown.<slot>.v1` identity and `Unknown` presentation, and keep the row out of history instead of presenting the shared `Codex extra limit` fallback as a learnable quota.

Remove the test-only mapper that bypassed production slot identity and extend hermetic coverage through production `codex_windows` and `enrich_snapshot_with`, including serialized state/reason assertions and a history callback that must remain unused. The successful-request account-ID migration gate, opaque account scope, and provider-neutral batch enrichment remain unchanged.

Verification: 10 focused Codex tests passed; `cargo test -p tb_core_ffi` passed 210 tests; `cargo clippy -p tb_core_ffi --all-targets`, scoped rustfmt, and `git diff --check` completed successfully with existing warnings only. A fresh verifier returned CONFIRMED.

Co-Authored-By: Claude <noreply@anthropic.com>
Complete the Claude Stage 4C acceptance boundary by exercising the production mappers and provider-neutral enrichment seam without changing the existing fetch or wire implementation.

Verify that OAuth JSON and unified rate-limit headers converge on `session.v1` with an exact 18,000-second contract duration and `weekly.v1` with 604,800 seconds. Cover the named OAuth Apps, Sonnet, and Opus rows plus every frozen Design and Routines schema alias, asserting canonical `cardId`, `windowKey`, `DurationSource::Contract`, and the initial `learningHistory` lifecycle.

Strengthen the Extra Usage fixture through `enrich_snapshot_with`: `extra_usage.v1` remains `unavailable(missingReset)`, carries no duration or historical result, and never invokes the history transaction even when a valid opaque account scope exists.

Extend the process-wide OAuth 429 gate test with a previously authenticated cached snapshot. `claude_gate_fallback` must retain display rows while replacing the stale account scope with `NoTrustedEvidence`; generic enrichment consequently emits `unavailable(accountScope)` and performs zero history callbacks.

Verification: `cargo test -p tb_core_ffi` passed 211 tests; `cargo clippy -p tb_core_ffi --all-targets`, scoped rustfmt, and `git diff --check` completed successfully with existing warnings only. An independent Codex review returned CONFIRMED after the configured verifier repeatedly failed before reading the diff because of the agent API prompt-length limit.

Co-Authored-By: Claude <noreply@anthropic.com>
Complete the Grok Stage 4D adapter contract while preserving the existing billing request, credential lineage, and provider-neutral enrichment flow.

Update `period_details` so provider duration evidence is constructed only when both timestamps parse and `end > start`. Contradictory or malformed start/end pairs now carry no duration evidence and are emitted as typed `unavailable(invalidEvidence)` rows instead of retaining a negative provider duration that could violate Rust wire serialization.

Replace the helper-only Stage 0 baseline with a production `map_billing` wire test. The fixture locks exact 28-, 29-, 30-, and 31-day monthly durations as provider seconds without a 30-day normalization; verifies Weekly end-only payloads retain their reset and enter `learningDuration` for the shared observed rollover lifecycle; and confirms unknown period types keep a structural `cardId` while omitting `windowKey` as `unavailable(windowIdentity)`.

The same hermetic matrix covers contradictory bounds plus malformed start and end values, asserting that recognized Weekly identity remains stable while duration and history evidence fail closed. No provider request, account-scope rule, or shared duration/history implementation changed.

Verification: `cargo test -p tb_core_ffi` passed 211 tests; `cargo clippy -p tb_core_ffi --all-targets`, scoped rustfmt, and `git diff --check` completed successfully with existing warnings only. A fresh verifier independently returned CONFIRMED.

Co-Authored-By: Claude <noreply@anthropic.com>
Complete the Antigravity Stage 4E adapter contract across the authenticated local IDE, remote available-model catalog, and remote quota-bucket sources without changing network requests or account-scope ownership.

Normalize only surrounding Unicode whitespace from provider model IDs, preserve the remaining bytes and case, and derive identified `cardId` and `windowKey` values as `model.<exactModelId>.v1`. Remote object keys that collide after trimming now enter the same deterministic binding selection used by CLI and bucket rows: prefer valid evidence, then the lowest remaining fraction, earliest future reset, and earliest source index. Display labels never participate in identity or merging.

Keep missing-ID entries as structural presentation rows for each source family (`row.cli.config.<index>.v1`, `row.models.<index>.v1`, and `row.quota.bucket.<index>.v1`) with typed `unavailable(windowIdentity)` state.

Route every emitted fraction through `quota_window`. Finite values outside `[0, 1]` and non-finite internal evidence are display-sanitized and retained as `unavailable(invalidEvidence)` for identified models instead of either producing invalid JSON or silently turning a successful provider response into a zero-card snapshot. Missing percentage fields remain non-rows. Duplicate selection prefers a valid candidate over an invalid lower numeric value so malformed evidence cannot displace a usable row.

Hermetic fixtures cover trimmed remote and CLI collisions, byte-exact case preservation, duplicate labels, missing IDs across all three source shapes, valid/past/future reset tie ordering, source-order ties, boundary fractions, out-of-range rows, missing fractions, and a serializable non-finite guard.

Verification: `cargo test -p tb_core_ffi` passed 212 tests; `cargo clippy -p tb_core_ffi --all-targets`, scoped rustfmt, and `git diff --check` completed successfully with existing warnings only. The first verifier pass found the zero-card invalid-fraction regression; after preserving typed unavailable rows, a new fresh verifier returned CONFIRMED.

Co-Authored-By: Claude <noreply@anthropic.com>
Complete the Copilot Stage 4F acceptance boundary without changing its HTTP request, OpenCode credential lineage, zero-entitlement omission, or shared duration lifecycle.

Extract the existing response-to-window assembly into `map_user`, which remains the production path called by `fetch`. The mapper parses the single `quota_reset_date` once, propagates it to both Premium and Chat snapshots, preserves the independent `premium_interactions.v1` and `chat.v1` identities, and returns the normalized Copilot plan alongside the windows.

Replace the helper-only Stage 0 fixture with a hermetic production-body test through `map_user`. It asserts that both cards receive the identical UTC reset, retain their separate semantic keys, and serialize the exact preceding 31-day calendar month as 2,678,400 contract seconds. The existing non-first-of-month fixture continues to lock the observed fallback, and the existing placeholder test continues to omit an explicit zero-entitlement Chat row.

No provider network request, OAuth scope, account-scope source, observed rollover implementation, or wire schema changed.

Verification: `cargo test -p tb_core_ffi` passed 212 tests; `cargo clippy -p tb_core_ffi --all-targets`, scoped rustfmt, and `git diff --check` completed successfully with existing warnings only. A fresh verifier independently returned CONFIRMED.

Co-Authored-By: Claude <noreply@anthropic.com>
Harden the provider-neutral `UsageWindow` enrichment boundary after all Stage 4 adapters have attached semantic identities.

Sanitize invalid provider percentages in `UsageWindow::with_identity` before serialization. Finite out-of-range readings are clamped to the display-safe 0–100 range and non-finite readings become 0%, while identified rows retain typed `unavailable(invalidEvidence)` status. This keeps malformed Claude header values and equivalent provider evidence out of history without allowing Rust `NaN` values to serialize as JSON `null` and break Swift's required `Double` decoder contract.

Keep structural rows coherent when account-scope resolution fails. `enrich_snapshot_with` now applies `unavailable(accountScope)` only to pace-capable rows with a semantic `windowKey`; unknown or missing-ID presentation rows retain `unavailable(windowIdentity)`, preserving the invariant that a nil `windowKey` maps exactly to the window-identity failure state. No history callback runs when account scope is unavailable.

Extend hermetic tests to cover finite out-of-range and non-finite percentages through production header parsing and JSON serialization, plus mixed identified and structural rows under an account-scope failure.

Verification: focused regression tests passed; `cargo test` passed the workspace suites, including 212 `tb_core_ffi` tests and 1,052 vendored/core tests with one ignored; `cargo clippy --workspace --all-targets`, scoped rustfmt, release static-library build, `swift run TokenBar --selftest`, and `git diff --check` completed successfully with existing warnings only. A fresh verifier inspected the working-tree diff and returned CONFIRMED. The repository-wide `cargo fmt --all -- --check` remains blocked by pre-existing out-of-scope and vendored formatting drift; this commit does not modify `vendor/`.

Co-Authored-By: Claude <noreply@anthropic.com>
Establish the Swift side of the provider-wide v3 quota wire before changing pace calculation or presentation behavior.

Extend `UsageWindow` with the backend-owned `cardId`, exact `durationSeconds`, and nested `PaceStatus`. Add typed `UsagePaceState`, `UsagePaceDurationSource`, and `UsagePaceUnavailableReason` values matching the frozen Rust serializer contract, including the internal-only `legacyMissing` marker.

Implement custom production decoding for quota windows and historical projections. A payload enters the legacy path only when the complete `paceStatus` key is absent; that path uses the fixed `legacy.missing.v1` presentation identity and never derives a duration from `windowMinutes`. When v3 status is present, null or unknown typed values, missing required fields, empty identities, invalid percentages, contradictory state/history combinations, invalid duration/source combinations, incoherent historical ETA and will-last values, and out-of-range historical values all fail decoding instead of silently degrading to Linear pace.

Keep `windowMinutes` as compatibility output only: duration-ready v3 rows must carry the exact integer `durationSeconds / 60` value emitted by Rust, while duration-less rows must not carry `windowMinutes`. Durations are bounded to positive values no greater than 400 days, and `windowKey == nil` is accepted only for `unavailable(windowIdentity)`.

Guard programmatic fixtures with the same validation used by decoding. `HistoricalPace`, `PaceStatus`, and v3 `UsageWindow` initializers use preconditions so tests and demo construction cannot bypass finite/range, identity, duration, or historical coherence rules.

Replace the Stage 0 decoder baseline with hermetic fixtures for all four v3 states, whole-key legacy handling, a complete production-shaped payload with unique card IDs, and malformed null, enum, identity, history, reason, and duration cases. This commit deliberately leaves `UsagePace`, selection identity, and UI behavior unchanged for the following Stage 5 scopes.

Verification: `swift run --package-path /private/tmp/tokenbar-provider-pace-plan TokenBar --selftest`, `swift build --package-path /private/tmp/tokenbar-provider-pace-plan`, and `git diff --check` passed. The first fresh verifier found that a duration-ready row could omit derived `windowMinutes`; after tightening that invariant and adding a regression fixture, a new fresh verifier returned CONFIRMED. Existing macOS 26 archive versus macOS 14 link-target warnings remain unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Replace Swift's implicit historical-to-linear fallback with the frozen typed pace-state policy and backend-owned duration contract.

Add `UsagePaceBasis` to every computed result and expose `isHistoricalDeficit` as the single policy gate for the later UI color change. Historical mode now consumes the Rust projection only for `available` windows, uses an explicitly identifiable Linear estimate only for `learningHistory`, and returns no pace for `learningDuration`, `unavailable`, or `legacyMissing`. Linear mode and the direct Linear convenience API accept only duration-ready `available` or `learningHistory` windows; Off remains nil.

Move timing from legacy `windowMinutes` to the exact nested `paceStatus.durationSeconds`. Reset validity, elapsed-zero guards, expected usage, local Linear ETA, and past-reset rejection remain unchanged, but legacy payloads can no longer revive pace merely by carrying a minute count.

Keep historical projection ownership intact. Backend expected usage, ETA, will-last, and risk are used together only for a Historical `available` result. `UsagePace.presentation` and `runOutRiskLabel` suppress nested risk for Linear basis results, including the temporary `learningHistory` estimate.

Rewrite the UsagePace selftest fixtures as explicit v3 windows. The cases cover exact non-minute durations, Historical and Linear mode selection, all unavailable or learning states, legacy rejection, Historical-only deficit gating, risk isolation, reset bounds, elapsed-zero behavior, ETA, and exhausted quota coherence. The Stage 0 silent Linear fallback assertion is now an old-fail/new-pass regression.

Verification: `swift run --package-path /private/tmp/tokenbar-provider-pace-plan TokenBar --selftest`, `swift build --package-path /private/tmp/tokenbar-provider-pace-plan`, and `git diff --check` passed with existing actor-isolation and deployment-target warnings only. A fresh verifier independently returned CONFIRMED.

Co-Authored-By: Claude <noreply@anthropic.com>
Move quota selection identity from mutable display labels to the Rust-owned `cardId` contract while preserving a bounded migration path for existing preferences.

Add `AgentUsageSnapshot.uniqueCardWindows` as the shared order-preserving card view. Duplicate IDs within one provider snapshot keep the first row and fail closed for every later occurrence; labels never repair, suffix, or merge an identity collision.

Change the canonical persisted selection to `clientId|cardId`. `QuotaResolver.canonicalSelection` preserves a well-formed explicit value while no payload is available, then resolves exact card IDs first once a payload exists. If no exact card exists, a legacy `clientId|label` value migrates only when exactly one unique card has that label. Unknown clients, stale values, duplicate-label ambiguity, and malformed strings normalize to Auto.

Route Auto resolution, explicit resolution, and all-candidates-excluded detection through the same canonical selection and unique-card view. Auto continues to skip provider errors and hidden clients, while a valid explicit card remains honored even when its client is hidden. A stale or ambiguous explicit value becomes Auto and therefore follows Auto exclusion semantics.

Temporarily retain the label-spelled selection overload and `fallbackUnknownExplicit` parameter solely so the not-yet-migrated Stage 5C2 consumers continue to compile. Both demo and live policy callers already use the same canonical behavior; the compatibility surface is removed in the next checkpoint.

Expand SelfTest coverage for exact card selection, unique-label migration, duplicate labels, stale and malformed values, payload-not-yet-loaded preservation, exact-card precedence, duplicate-card fail-closed ordering, hidden explicit selections, Auto exclusion, and identical demo/live fallback behavior.

Verification: `swift run --package-path /private/tmp/tokenbar-provider-pace-plan TokenBar --selftest`, `swift build --package-path /private/tmp/tokenbar-provider-pace-plan`, and `git diff --check` passed with existing warnings only. A fresh verifier independently returned CONFIRMED.

Co-Authored-By: Claude <noreply@anthropic.com>
Complete the Mac consumer migration from display-label identity to the canonical `clientId|cardId` selection contract.

Remove the temporary label-spelled selection overload, the demo/live `fallbackUnknownExplicit` branch, and the corresponding `UsageDataSource` capability. Tray and Settings preview callers now share the same payload-aware canonical migration and exclusion behavior.

Update Settings and the status-item menu to build options from `AgentUsageSnapshot.uniqueCardWindows` and persist `cardId` values while continuing to display provider labels. The Settings radio binding canonicalizes legacy preferences against the current payload, and the status menu fetches its payload once per opening before canonicalizing the current checkmark.

Render Agent limits rows only from the unique-card view. SwiftUI iteration is keyed by `cardId`, each row carries the explicit `providerId:cardId` identity, duplicate later rows remain suppressed, and status/data-presence checks use the same filtered card set. Smoke output also reports the unique card count.

Upgrade DemoData before enabling the new consumers so its two legacy rows do not collide on the fixed legacy identity. Every demo provider now emits `session.v1` and `weekly.v1` as typed `learningHistory` cards with exact 18,000- and 604,800-second contract durations, derived compatibility minutes, matching 5h/7d reset text, and future reset timestamps. No synthetic historical result is claimed.

Refresh SelfTest to assert two unique canonical demo cards per provider, exact durations and compatibility minutes, contract sources, zero complete cycles, and absent historical results. Remove the obsolete demo/live fallback divergence assertions while retaining the Stage 5A, 5B, and 5C1 coverage.

Verification: `swift run --package-path /private/tmp/tokenbar-provider-pace-plan TokenBar --selftest`, `swift build --package-path /private/tmp/tokenbar-provider-pace-plan`, stale-symbol searches, quota-row identity searches, and `git diff --check` passed with existing warnings only. A fresh verifier independently returned CONFIRMED.

Co-Authored-By: Claude <noreply@anthropic.com>
Apply the provider-wide pace lifecycle to the shared Agent limits UI without duplicating behavior across Overview, single-client, and Settings consumers.

Add the pure `AgentLimitsCard.PacePresentation` policy for state copy, typed unavailable reasons, and historical deficit coloring. Historical `learningHistory` rows explicitly display `Learning history · Linear estimate`; `learningDuration` displays `Learning reset duration`; legacy and unavailable rows explain why pace is absent. Current unavailable reasons map to user-facing unknown-window, missing-reset, invalid-data, account-identity, storage-capacity, history-unavailable, and non-recurring messages.

Refactor the footer so typed status, pace label, backend projection, and historical risk compose through one layout path. Duration-learning, unavailable, and legacy rows render no marker, pace label, or ETA. Linear and learning-history estimates retain a neutral marker and text, while available Historical results continue to use the Rust-owned ETA, will-last, and risk values.

Gate every orange expected marker and deficit label through `UsagePace.isHistoricalDeficit`. Raw `PaceStage.isDeficit` no longer controls Agent limits color, so a Linear estimate cannot be mistaken for a learned Historical comparison.

Update Settings copy to state that Historical learns each quota window's usage pattern, labels the temporary Linear estimate, and that Linear uses the exact reset duration. Remove the weekly-only and silent-fallback description.

Extend SelfTest through the pure presentation policy for the exact learning messages, all typed unavailable reasons, legacy and Off behavior, available-state copy suppression, and Historical-only warning color.

Verification: `swift run --package-path /private/tmp/tokenbar-provider-pace-plan TokenBar --selftest`, `swift build --package-path /private/tmp/tokenbar-provider-pace-plan`, `git diff --check`, and the Agent limits bare-`stage.isDeficit` search passed with existing warnings only. A fresh verifier independently returned CONFIRMED.

Co-Authored-By: Claude <noreply@anthropic.com>
Replace the account-scope installation-key read and add operations with explicit Security.framework item dictionaries. `SystemBackend::keychain_read` now uses `SecItemCopyMatching`, and `keychain_add_if_absent` uses add-only `SecItemAdd`, while preserving the fixed `com.nyanako.tokenbar.account-scope.v1` service, `installation-key` account, non-synchronizing storage, and explicit default file Keychain.

Set `kSecUseAuthenticationUI` to `kSecUseAuthenticationUIFail` on every operation so background quota polling never opens authentication or legacy ACL UI. Treat only `errSecItemNotFound` as an absent item; locked, denied, untrusted, interaction-required, malformed-result, and other failures map to the existing fail-closed `KeychainUnavailable` result. Validate the returned CoreFoundation object as `CFData` and preserve create-rule ownership through the copy boundary.

Keep installation-key lookup uncached. Every account-scope resolution and refresh transaction re-reads Keychain, allowing another process's key-loss recovery to replace K1 with K2 without leaving a stale process that quarantines valid K2-authenticated metadata. Duplicate creators remain add-only: the loser re-reads and adopts the Keychain winner rather than updating it.

Add direct macOS-target dependencies on the already-resolved `core-foundation` and `security-framework-sys` crates for the typed query and FFI surface. Add `key_loss_reloads_replacement_key_before_metadata_recovery` to lock the orphan defer, K2 scope transition, and stable post-recovery metadata behavior.

Verification: 38 focused `agent_account_scope` tests passed offline, including key-loss quarantine, duplicate creation, refresh lineage, and raw-secret persistence checks. Scoped rustfmt, offline cargo check, cargo clippy, and git diff checks passed with baseline warnings only. The initial process-cache design was independently refuted for cross-process split-brain; the cache was removed, and a fresh verifier returned CONFIRMED for the final noninteractive implementation.

Co-Authored-By: Claude <noreply@anthropic.com>
Document the v3 quota-window payload at the C ABI boundary without changing any exported function or heap-ownership signature. The `tb_agent_usage` comment now records `cardId`, required `paceStatus`, Rust-owned `durationSeconds`, derived compatibility `windowMinutes`, available-only `historicalPace`, and the fail-closed legacy rule that a missing status does not restore an implicit Linear estimate.

Tighten `UsageWindow.usagePercentageValidationError` so `usedPercent` and `remainingPercent` must be finite, individually bounded, and complementary within a 1e-6 floating-point tolerance. Because both production decoding and public initializers use this shared validator, contradictory payloads cannot bypass the trust boundary through either construction path.

Add an old-fail/new-pass production decoder fixture for `usedPercent: 80` with `remainingPercent: 80`. The malformed row previously decoded because each value was independently valid; it is now rejected before pace-state interpretation.

Verification: `swift run --package-path /private/tmp/tokenbar-provider-pace-plan TokenBar --selftest`, `swift build --package-path /private/tmp/tokenbar-provider-pace-plan`, and `git diff --check` passed. A full fresh Stage 5 verifier initially exposed the percentage contradiction, then returned CONFIRMED after the shared validation and regression fixture were added. No live provider, network, or Keychain path was exercised.

Co-Authored-By: Claude <noreply@anthropic.com>
Add a Mac-owned schema-v3 cross-port bundle whose nested `payload` is the exact production `AgentUsagePayload` wire shape. The sanitized fixture contains seven lifecycle windows covering Historical available deficit and reserve results, 18,000-second learning history, observed duration learning, typed missing-reset unavailability, and duplicate display labels with distinct card identities.

Add twelve cross-check cases around that payload: three production `UsagePace` projections, exact and migrated quota selections, ambiguous-label fallback to Auto, a legacy payload with the entire `paceStatus` key absent, and five raw malformed windows that the production decoder must reject. Output records the typed lifecycle, projection basis, stage, ETA, risk, Historical-only deficit flag, canonical selection, and fail-closed decode decisions.

Extend `crosscheck-harness` with optional `usage-pace`, `format`, and `provider-quota-pace-v3` selectors while preserving the no-selector invocation. Legacy usage fixtures are decoded one window at a time through the current production `UsageWindow` decoder, so newly invalid rows become explicit `rejected: true` results instead of aborting the complete 116-case run; this does not restore the removed legacy Linear fallback.

Lock the fixture payload from Rust with `provider_quota_pace_v3_fixture_locks_production_serializer`. The test constructs production `AgentUsagePayload`, `AgentUsageSnapshot`, and custom-serialized `UsageWindow` values, then compares their serde output with the tracked fixture payload to prevent Rust, C, and Swift contract drift.

Verification: scoped rustfmt, the targeted Rust serializer test, an offline release build, and `git diff --check` passed. The no-selector Swift harness completed 42 pace plus 74 format cases; all 74 format cases matched the existing C# reference. The current Mac contract intentionally differs in 28 of 42 legacy pace cases while Windows v3 parity remains pending. The new v3 harness produced seven lifecycle rows and twelve cases, and a fresh verifier returned CONFIRMED. All generated output stayed under `/private/tmp`; this change did not write the Windows repository.

Co-Authored-By: Claude <noreply@anthropic.com>
Promote the provider-wide quota pace plan from draft to active and record the implemented Mac boundary through Stage 6. Current state and the plan registry now distinguish the completed secure account scope, duration lifecycle, generic v3 history, provider adapters, Swift lifecycle/selection/presentation, and Rust-locked fixture from the still-pending Stage 7 repository and live UX gates.

Add the Windows semantic handoff without claiming parity. The canonical plan now names the required DTO fields, exact duration ownership, Historical/Linear/Off state policy, `clientId|cardId` selection migration, Historical-only deficit presentation, and shared v3 fixture gate. Windows remains `port/parity pending`, and this checkpoint records that the Mac task did not write the Windows repository.

Update the cross-language verification contract to remove the obsolete nil-history Linear fallback. Record the current no-selector baseline: all 116 legacy cases complete, all 74 format cases remain equal to the existing C# reference, and 43 field differences across 28 of 42 pace cases are intended consequences of strict percentage rejection, typed lifecycle, nested historical ownership, and no silent legacy fallback. The Mac-owned v3 bundle adds seven lifecycle windows and twelve projection, selection, legacy, and malformed cases locked by the Rust production serializer.

Verification: the knowledge self-test, Python bytecode compile, canonical knowledge check, `make check-docs`, and `git diff --check` passed. A fresh verifier independently returned CONFIRMED for the fixture evidence, relative links, status claims, Windows pending boundary, and privacy-safe documentation.

Co-Authored-By: Claude <noreply@anthropic.com>
Register every emitted semantic SeriesKey as active before typed unavailable and invalid-evidence gates. The provider-neutral enrichment transaction now receives active rows even when a poll cannot produce an observation, while observations and samples remain limited to valid reset and percentage evidence.

This preserves learned history during 512-series admission: a temporarily missing-reset card can no longer be treated as inactive and evicted when another card is admitted in the same snapshot. Account-scope failures and windowIdentity rows still fail closed before constructing a series key.

Add a hermetic production-integration regression that fills the v3 store with 512 completed series, advances beyond reset grace, emits Claude weekly as unavailable(missingReset), admits a new valid quota card, and asserts the weekly series survives. Update the Extra Usage contract test to assert active-without-observation behavior.

Verified with cargo test -p tb_core_ffi (215 passed), focused capacity and emitted-without-observation tests, cargo clippy -p tb_core_ffi --all-targets, scoped rustfmt --check, and git diff --check. Clippy reports only existing dead-code and too-many-arguments warnings.
Replace the ad-hoc-incompatible generic-password item with an owner-only
installation-key file in the existing account-scope storage directory.
SystemBackend keeps Security.framework only for SecRandom, while
ensure_installation_key serializes read, first creation, and key-loss
recovery through ACCOUNT_SCOPE_PROCESS_LOCK and the existing fs2 metadata
lock.

Persist exactly 32 bytes as
quota-account-scope-installation-key-v1.bin under a 0700 directory and 0600
file. The write path uses a create-new temporary file, write/flush/fsync,
atomic replacement, and parent-directory sync. Every resolution reopens and
validates the persisted winner; there is no process cache. Reject wrong
lengths, permissive modes, symlinks, non-regular paths, and inode swaps
without replacing the key or mutating metadata and v3 history.

Preserve key-loss evidence across failures. Canonical metadata is quarantined
byte-for-byte, v3 scopes remain in place, and production-canonical orphaned
metadata filenames keep the first successful replacement poll unavailable.
The scanner treats matching non-regular paths and inspection failures as
errors, ignores forged names outside the nonnegative i64 timestamp and
positive u32 suffix grammar, and never reads or rewrites orphan contents.
Pre-replace failures leave no partial key or temp file; a post-replace parent
sync failure may leave a valid winner for the next poll to reload.

Keep credential refresh independent from account-scope availability while
preserving refresh-lock then metadata-lock ordering. A key failure makes pace
unavailable and prevents history writes without blocking the provider's
existing credential refresh flow. Remove the direct core-foundation and
security-framework-sys dependencies.

Update the canonical architecture, release, verification, current-state, and
provider pace documents. The release contract now records that ad-hoc signing
cannot sustain a restrictive Keychain ACL. A future Developer ID migration
must import the same 32 bytes, verify byte equality before switching the
source of truth, and never rotate account scopes or history.

Verification:
- rustfmt --edition 2021 --check crates/tb_core_ffi/src/agent_account_scope.rs
- cargo test: 225 tb_core_ffi; 1052 tokscale-core with 1 ignored; 10 + 3 + 1 integration tests
- cargo clippy --workspace --all-targets
- make build
- swift run TokenBar --selftest
- scripts/check_knowledge.py self-test, compile, canonical check, and make check-docs
- git diff --check
- fresh verifier: CONFIRMED

Full cargo fmt remains known-red only in pre-existing out-of-scope
hourly_report.rs, model_report.rs, and vendored tokscale-core formatting.
Live smoke and popover UX remain separate Stage 7 gates requiring fresh user
authorization.
Record the fresh-authorized Stage 7 smoke after the account-scope storage
migration. The monitored `TokenBar --smoke` run exited zero without spawning
SecurityAgent or authorizationhost, and a metadata-only inspection confirmed
the live account-scope directory is 0700 while the persisted installation key
is exactly 32 bytes with mode 0600.

Keep the remaining status precise: the popover Historical/Linear/Off UX gate
and Windows parity are still pending. The smoke's print-only hourly and agents
drift probes reported small mismatches; the smoke contract did not fail, the
diagnostic is unrelated to account-scope storage, and its root cause was not
claimed within this scope.

Verification:
- TokenBar --smoke: exit 0 under authorization-process monitoring
- stat metadata inspection only; installation-key bytes were not read
- scripts/check_knowledge.py self-test, compile, and canonical check
- make check-docs
- git diff --check
Expand the hidden --demo AgentUsage payload into an explicit Stage 7 acceptance surface. The first two provider cards now cover learningDuration, learningHistory, an available historical deficit, and typed unavailable(missingReset) while keeping canonical cardId/windowKey identities and Rust-owned duration semantics.

Use half-cycle reset timestamps so Linear and historical projections are actually computable during a GUI run. Mark snapshots as FIXTURE in the rendered badge, and keep the remaining demo providers on the standard contract-backed learning-history path.

Extend SelfTest to lock each lifecycle state, duration shape, historical-only deficit gate, and projection suppression. Verified with swift run --package-path /private/tmp/tokenbar-provider-pace-plan TokenBar --selftest and git diff --cached --check.

Co-Authored-By: Claude <noreply@anthropic.com>
Record the deterministic --demo popover acceptance across Historical, Linear, and Off modes. The checkpoint distinguishes quota-bar health colors from historical pace warning colors and documents the FIXTURE badge, learning lifecycle copy, typed unavailable behavior, and historical-only marker/risk presentation.

Capture runs rendered only the TokenBar popover, monitored SecurityAgent and authorizationhost, and intentionally terminated each test process after its screenshot. No live provider request, Keychain path, Windows write, push, merge, or release was performed.

The final fresh verifier returned CONFIRMED. Re-ran the 20-test knowledge self-test, Python compilation and knowledge validation, make check-docs, Swift selftest, and staged diff whitespace checks.

Co-Authored-By: Claude <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4aefbbfd37

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/TokenBar/QuotaSelectionPolicy.swift
Keep well-formed clientId|cardId selections explicit when the current AgentUsagePayload omits the client or card, or when a legacy label is ambiguous. QuotaResolver.resolve now returns nil for that unresolved explicit identity, allowing TrayAnimator and the Settings preview to retain the selected source's last-good remaining percentage instead of silently resolving another provider through Auto.

Exact card IDs still resolve directly, unique legacy labels still migrate to canonical card IDs, malformed or empty selections still normalize to Auto, and duplicate card IDs retain the existing first-wins behavior. Update the Swift self-test, production cross-check case, and canonical Windows handoff contract to lock the partial-payload semantics.

Verified with swift run TokenBar --selftest, the provider-quota-pace-v3 CrossCheckHarness run, the Rust production serializer fixture lock, knowledge validation, git diff --check, and a fresh verifier result of CONFIRMED.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d63844272a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/tb_core_ffi/src/agent_grok.rs
Comment thread crates/tb_core_ffi/src/agent_usage.rs Outdated
Treat account-scope metadata as the durable authority for refresh-token lineage. When Grok rotates its refresh marker and the lineage transfer fails, keep the new access token in memory for the current billing request but skip credential persistence so the old on-disk marker remains linked to the existing account scope and quota history.

Successful transfers still persist the rotated pair, unchanged or empty replacement tokens retain the existing save path, and credential-write failures continue to leave the old marker recoverable. Extend the production-sequence crash test to prove metadata bytes remain unchanged, disk keeps the old refresh token, and the next poll resolves the original scope after a MetadataWrite failure.

Verified with the focused refresh crash-boundary test, all 14 Grok tests, the full 225-test tb_core_ffi suite, single-file rustfmt checks, standard Clippy, git diff --check, and a fresh verifier result of CONFIRMED.
Move Claude gate caching after generic quota enrichment so the last-good snapshot already contains its typed duration and historical pace. Gate fallbacks now return that display-ready snapshot directly after clearing stale account-scope evidence, avoiding both history writes without fresh authentication and the previous accountScope rewrite that erased cached pace.

No-cache cooldown and ordinary error snapshots retain their existing behavior. Expand the gate lifecycle regression with an available historical fixture and assert that the fallback preserves complete cycles and expected usage while exposing no trusted account scope.

Verified with the focused Claude gate test, the full 225-test tb_core_ffi suite, single-file rustfmt checks, standard Clippy, and git diff --check. The fresh verifier service failed twice before inspection with a prompt-length API error; the completed implementation was therefore traced and reviewed in the main session.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 488fb85787

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/tb_core_ffi/src/agent_usage.rs
Comment thread crates/tb_core_ffi/src/agent_antigravity.rs Outdated
Reject non-finite and out-of-range remainingFraction values at the shared Antigravity quota_window trust boundary instead of clamping them into displayable UsageWindow rows. The local user-status, available-models, and quota-bucket adapters omit malformed percentage evidence so it cannot drive tray Auto selection or quota gauges.

Enable only serde_json raw_value support and deserialize each quota row as an isolated raw JSON value before applying the existing lenient field accessors. A syntactically valid but unrepresentable number such as 1e400 or a wrong-typed sibling field can invalidate or degrade only that row; valid duplicate and sibling rows continue through local and remote adapters.

Code Assist responses are read as text: loadCodeAssist retains Value decoding, while fetchAvailableModels and retrieveUserQuota decode raw-row envelopes. This preserves prior fallback and windowIdentity behavior without changing serde_json overflow semantics for vendored tokscale-core.

Stage 4 regressions cover 1e400 at every source, malformed CLI and available-model row fields, and a numeric bucket modelId alongside a valid row. Verified with full cargo test (tb_core_ffi 225; tokscale-core 1052 with 1 ignored; integration 10; hermes 3; streaming 1), workspace Clippy with existing warnings only, rustfmt, and git diff --check.

Co-Authored-By: Claude <noreply@anthropic.com>
Prevent Codex, Claude, and Antigravity refresh paths from persisting a rotated credential marker when account-scope lineage transfer fails. Each provider compares the canonical post-refresh marker with the reloaded on-disk marker after the network refresh, performs refresh.transfer, and returns the refreshed access token for the current poll without calling its provider-store save when a real rotation lacks durable metadata.

Unchanged or missing replacement markers remain eligible for access-token and expiry write-back even when scope resolution is unavailable because the persisted lineage marker does not change. Successful transfer and checkpoint ordering remain unchanged; Grok already enforces the same invariant and Copilot does not own refresh persistence.

Extend the production-sequence crash-boundary tests for all three providers to prove rotated-marker failure preserves the old disk marker and resolvable scope while keeping the new access token in memory, and unchanged-marker failure still saves non-lineage fields.

Verified with the three focused refresh tests, cargo test -p tb_core_ffi (225 passed), cargo clippy -p tb_core_ffi --all-targets (existing warnings only), rustfmt, and git diff --check.

Co-Authored-By: Claude <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e2e6e0b19b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/TokenBarCore/QuotaResolver.swift
Move quota-percentage validation to the Rust provider trust boundary so
malformed readings never become finite clamped gauges on the v3 wire. Add
checked UsageWindow constructors for absolute used percentages and remaining
fractions, and route Claude, Codex, Antigravity, Grok, and Copilot adapters
through them before identity, history, or serialization.

Isolate Codex, Claude, and Copilot quota rows with serde_json RawValue before
strong deserialization. Numeric overflow such as 1e400 and wrong-typed values
now discard only the affected row while valid siblings remain available.
Delay Codex card-id admission until mapping succeeds so an invalid duplicate
cannot suppress a later valid window with the same semantic identity.

Preserve provider-specific fallback contracts. Claude design and routines
alias groups skip invalid first aliases and continue to the next valid alias;
Extra Usage and unified-header readings are omitted when their percentage is
invalid. Grok keeps a missing GrokBuild percentage eligible for the existing
overall-credit fallback, while a present malformed or out-of-range product
percentage fails closed. Copilot rejects both invalid explicit percentages
and non-finite derived ratios. Antigravity reuses the shared checked fraction
constructor without changing its existing RawValue row isolation.

Keep Swift selection policy separate from pace evidence: QuotaResolver does
not broadly reject paceStatus invalidEvidence because reset or duration
failures can coexist with a valid percentage gauge.

Verification:
- cargo test (tb_core_ffi 230; tokscale-core 1052 passed, 1 ignored;
  integration suites 10 + 3 + 1)
- cargo clippy --workspace --all-targets (exit 0; existing warnings only)
- swift run TokenBar --selftest
- git diff --cached --check
- focused malformed-row, invalid-duplicate, Claude alias, Grok, Copilot, and
  Antigravity regression tests

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e143d863d6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

_ raw: String
) -> (clientId: String, value: String)? {
guard !raw.isEmpty, raw != auto else { return nil }
let parts = raw.split(separator: "|", omittingEmptySubsequences: false)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve delimiter characters in card selections

When a quota card ID itself contains |, this parser treats the persisted value from QuotaResolver.selection(clientId:cardId:) as malformed because it splits on every separator. The Antigravity adapter embeds the provider's exact model ID into card IDs (model.<exactModelId>.v1) without rejecting |, so a valid model ID containing that character can be shown in Settings/menu but immediately canonicalizes to Auto and resolves the wrong tray source instead of the selected card. Split only once after the client ID so the remainder stays the full card ID.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in follow-up PR #60 (fcb7ae17). parseExplicitSelection now splits at the first | only, keeping the full remainder as the provider-owned card ID. Exact and unmatched selections containing additional delimiters are preserved, while values with no delimiter or an empty client/card side still normalize to Auto. Added Swift self-tests for a resolvable model.gpt|preview.v1 card ID, nil-payload preservation, unmatched explicit selections, and structurally malformed inputs; swift run TokenBar --selftest passes.

Comment thread Sources/TokenBar/QuotaSelectionPolicy.swift
Persist a pre-v3 `clientId|label` quota source after a live payload proves one
unambiguous `clientId|cardId` migration. QuotaSelectionPolicy now returns a
write-back candidate only when canonicalization produces a non-Auto value
that differs from the stored selection; exact card IDs, stale explicit picks,
ambiguous labels, malformed values, and Auto remain untouched.

Perform the one-time UserDefaults write at TrayAnimator's existing fresh-quota
persistence boundary rather than inside the frequently evaluated
`quotaRemaining` getter. This preserves the observer re-entry guard, keeps
demo data process-local, and lets the same arrival persist both the canonical
source and the last-good remaining percentage.

Add a Swift self-test covering the proven legacy migration, exact-card no-op,
and stale-selection no-op cases.

Verification:
- swift run TokenBar --selftest
- git diff --cached --check
@Nanako0129
Nanako0129 merged commit e12361d into main Jul 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant