- Introduce `@oh-my-pi/omptype` as a new ArkType-compatible schema validation package featuring a lazy JIT runtime, JSON Schema emission, and compatibility adapters.
- Replace `arktype` across workspace packages and test utilities with `@oh-my-pi/omptype`.
- Add benchmark suites, tests, and documentation for the new validation engine and adapters.
- Update workspace build, test runner, and release configurations to include the new package.
Fail broker-facing block mutations on the first corrupt SQLite write and every later latched call instead of returning a false durability acknowledgement. Internal request-path persistence remains an in-memory no-op, preserving local availability.
Verify RemoteAuthCredentialStore keeps its optimistic rate-limit block and does not refresh an empty snapshot when the broker rejects persistence.
Codex reports one account-level limit flag alongside independent chat and Spark meters. Treating that flag, ranking windows, or persisted backoff as provider-wide made an exhausted meter block requests that still had headroom.
Scope exhaustion, ranking, and reactive backoff to the meter each request spends. Every selection path honors the request scope plus the legacy shared scope, including sibling availability, session pinning, and final OAuth resolution.
Reconciliation now evaluates and deletes each persisted meter block independently. The local and broker-backed stores support targeted deletion while the existing all-block deletion endpoint remains compatible.
Signed-off-by: Christian Stewart <christian@aperture.us>
- Added optional `source?` field with value `'login'` to `apiKeyCredentialSchema` so snapshots accept login-sourced API keys.
- Updated CHANGELOG with a fixed entry describing the correction.
- Added a test verifying that a snapshot containing `source: "login"` passes client wire validation.
- Implement `isInvalidatedOAuthTokenError` to identify specific upstream auth failures.
- Enable automatic credential rotation in `AuthStorage` and stream retries when an invalidated token is detected.
- Update `proxy.test.ts` to correctly handle `NO_PROXY` and `no_proxy` environment variables during testing.
- Introduced a `POST /v1/usage/stale` endpoint to allow remote clients to invalidate server-side usage caches.
- Updated `AuthCredentialStore` to include an optional `invalidateUsageCache` hook.
- Added `invalidateUsageCache` method to `AuthStorage` for local cache invalidation and notification of remote stores.
Final internal gate review on 6b061b41e:
- matchUsageReport/findMatchingReportIndex no longer fall back to org-less
aggregate reports for an org-scoped credential/overlay. With a scoped
row plus an org-less legacy row (a supported state), the legacy row's
sole surviving report could be handed to the scoped credential without
any base-identity check and rank/block it on the wrong pool; an
org-scoped overlay could likewise merge into that org-less report.
Presence mismatch now yields no-match in both directions, matching the
rule used everywhere else. Regression test included.
- Changelog wording: omp token --list labels rows with the org; it does
not mark an active row (that is /logout's selector).
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.
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.
One Anthropic account email can hold multiple organizations (a Team seat
plus a personal Max plan), each with its own org-scoped OAuth token and
independent 5h/7d limit pools. Credentials were deduped by bare email, so
logging in with the second subscription silently replaced the first, and
usage reports from the two pools merged into one row with mixed numbers.
- capture organization uuid/name at login (token exchange response, with
a claude_cli/bootstrap fallback); token refreshes never rewrite it
- key anthropic credential identity as email + org; a legacy email-keyed
row is claimed in place by the first org-scoped login with the same
email, and org-less credentials never clobber org-scoped rows
- partition usage-report dedupe and the per-credential usage cache by
org so the two subscriptions' limit pools stay distinct for rotation
- show the organization in omp usage (redaction-safe) and name the
stored account/org in the login success message
- Implemented persistent storage for credential rate-limit blocks with automatic expiry and pruning.
- Added broker API routes and client methods to manage, persist, and synchronize credential block states.
- Integrated rate-limit checks into the credential selection logic, specifically refining Fable/Mythos tier exhaustion gating.
- Extended schema versioning to include the new credential block table and verified persistence via comprehensive unit testing.
- Added `getModel` to `AgentLoopConfig` to allow runtime model resolution.
- Updated `streamAssistantResponse` to resolve the model dynamically per provider call instead of using the stale configuration snapshot.
- Enabled mid-run model switches to take effect immediately for context promotion and retry fallbacks.
- Added client-side usage overlays to the auth broker to support granular, credential-specific usage reporting.
- Integrated Fable weekly usage windows and limits into the Claude rate-limiting and ranking strategies.
- Optimized rate-limit handling by caching null results during backoff and disabling retries for 429 status codes.
- Updated credential storage to ingest and persist overlay-based usage data for improved account selection.
RemoteAuthCredentialStore.#loadUsageReports() only wrote the 15s cache on
success, so every sequential fetchUsageReports()/getUsageReport() call
after a broker failure kicked off a fresh client.fetchUsage() — the exact
opposite of the 'client absorbs transient broker outages … re-attempting
after the 15s window' contract in docs/auth-broker-gateway.md.
Extend UsageCacheEntry.reports to UsageReport[] | null, write the null
result in the .catch branch alongside fetchedAt = Date.now(), and let
the existing TTL check serve later callers. Single-flight coalescing
and successful-path caching are unchanged.
Fixes#4045
Batch-migrated all packages/ai/test/ files that used fs.rm/fs.rmSync
directly to use removeWithRetries/removeSyncWithRetries from
@oh-my-pi/pi-utils. This fixes EBUSY failures on Windows where SQLite
database files are still locked by the process when afterEach cleanup
hooks run.
Files migrated (24 total):
- 5 auth-broker test files (auth-broker-*.test.ts)
- 11 auth-storage test files (auth-storage-*.test.ts)
- 4 non-auth test files (aws-credentials, issue-1417, issue-957, model-cache)
- 3 remaining test files (remote-auth-store, request-debug, stream)
- 2 sync migration files (anthropic-alignment, image-limits)
Total: 28 fs.rm/fs.rmSync calls replaced with retry-enabled versions.
The retry logic: 40 retries, 25ms delay, EBUSY/EPERM/ENOTEMPTY on Windows.
- Refactor deep imports by targeting specific sub-modules in `@oh-my-pi/pi-ai` to reduce barrel file overhead.
- Utilize jitless ArkType scopes in schema definitions to reduce startup JIT codegen costs by approximately 65%.
- Reorganize internal `auth-storage` exports to maintain clean boundaries between core and broker-specific functionality.
- Derived descriptors, default-model map, env keys, login list, and refresh dispatch from one ProviderDefinition per provider.
- Disabled OpenAI Codex stream obfuscation and interrupted whitespace-only tool-call argument deltas.
- Derived auth-broker callback ports and paste-code login set from the registry.
- Updated markCredentialSuspect to apply the credential entry returned from refreshCredential and throw when the broker returns a non-OAuth credential.
- Added #applyCredentialEntry to replace or append a credential in the in-memory snapshot for immediate state updates.
- Added a test covering suspect-credential refresh snapshot updates and token refresh invocation.
- Added remote-write hooks to `AuthCredentialStore` and routed `AuthStorage` set/upsert/remove flows through them when present.
- Implemented `RemoteAuthCredentialStore` methods to upsert, replace, and delete credentials via broker endpoints and refresh local snapshots in-place.
- Added integration coverage for broker-backed API-key replacement and provider logout disabling all remote credentials.
- Added generation-aware snapshot contracts with generation, serverNowMs, refresher, and rotatesInMs fields.
- Reworked /v1/snapshot serving and client fetching for If-None-Match long-poll with 304/200 status handling.
- Added status checks in remote-store and SDK/CLI snapshot paths, applying updates only when fetch returns 200.
- Added StreamOptions.onAuthError and stream one-shot 401 retry dispatch using refreshed credentials.
- Added `AuthBrokerClient`, `RemoteAuthCredentialStore`, `AuthBrokerRefresher`, and `startAuthBroker` server in `packages/ai/src/auth-broker`.
- Renamed `AuthCredentialStore` class to `SqliteAuthCredentialStore`; extracted `AuthCredentialStore` as a persistence interface.
- Added `exportSnapshot`, `forceRefreshCredentialById`, `disableCredentialById`, and `upsertCredential` to `AuthStorage` for broker wire protocol.
- Added `omp auth-broker` CLI subcommand (serve, token, login, logout, import, status) and `discoverAuthStorage` broker-mode path keyed on `OMP_AUTH_BROKER_URL`.