Reuse native Codex CLI login and refresh in the Responses provider
Problem
Native Responses users need to reuse Codex CLI credentials and finish stock tasks without assuming the ChatGPT backend exposes the public token-count API. Refresh failures and streaming authentication failures must recover without duplicate refreshes or replaying partial output.
Design
API keys retain precedence. Catalog-authorized native Codex login uses the existing auth.json, ChatGPT Responses endpoint, top-level instructions, encrypted reasoning inclusion, store=false and session/account headers. Continuation bytes survive ordinary/native tool follow-ups and authentication recovery.
Credential staging files are private at creation: Windows CreateFileW receives SECURITY_ATTRIBUTES containing a protected current-owner-only DACL; Unix OpenOptions uses mode 0600. The Windows FFI stays in the filesystem crate's existing native_host boundary. Tests read permissions before writing, after writing and after replacement; an inherited-permissions write is the negative control. Every staging pass bounds the exact encoded merged document, including its trailing newline, to the same 1 MiB limit used by reads before writing credentials or replacing auth.json. A shrinking property supplies separately accepted auth/refresh inputs whose merge exceeds that bound and creates the original with owner-only permissions and checks its bytes, owner permissions and directory contents are unchanged. Unix checks owner/group/mode; Windows reads the protected owner-only DACL. A negative control omits the size guard while using the real staging writer and atomic replacement; the same property detects the changed original.
Registry-owned refresh ownership shares the token endpoint attempt's sanitized typed failure with queued callers as well as reusing successful token publication. Per-model account binding, file and publication errors are not cached across model bindings. A subsequent independent caller may retry. The actual state machine uses the crate-local synchronization shim in Loom, including failure and cancellation paths. An injected clock drives expiry decisions and publication timestamps.
One retry budget covers header-level authentication failures and HTTP 200 SSE authentication errors before output. Any published event prevents replay. Native recovery retains the issued allocation ledger and observes cancellation while refreshing or waiting for replacement headers.
Codex login explicitly removes the inherited remote counter. Counting anchors the previous response's authoritative input usage to that request's input footprint, then adds ceil(new serialized input bytes / 3). Generated output is charged once when included in a following request, rather than added to the input-only baseline as well. A prefix digest binds the usage anchor to model, tools and messages; unrelated resume/compaction input uses the full local estimate. Ordinary and native counting make no credential or HTTP request. Capacities remain explicit catalog data.
The stock fixture exposed redundant filesystem work: a batch lookup cloned a complete cached page before selecting a few entries. It now borrows shared decoded pages and copies only selected values. A shrinking property compares owned/shared results, checks work bounds and custody release, and detects the former eager clone as a negative control.
Deletions
Remove the inherited ChatGPT count route, duplicated header-only retry paths, success-only refresh model and direct production clock calls outside the default clock constructor. Consolidate ordinary/native recovery fixtures and reuse one stock-workflow helper. Move filesystem acceptance into its own file to keep the source-size limit.
Latest review-fix commit: +93/-15, net +78. Entire PR over merged main: +4,446/-603, net +3,843 across 54 files.
Tests run
Signed head: b9a715dff65e70e62ab50f4caa60ec2f6cca02b1.
- Latest Linux via bs.ps1: 153 provider tests passed in 1.24 s. Test compilation took 39.98 s. Log:
/home/var/log/buildbox/2026-10-16T162807294Z-231155.log. The unchanged synchronization model retains prior-head evidence: 11 Loom checks in 0.10 s. Includes the oversized-merge shrinking property, arbitrary output-token accounting property, queued account-binding regression and a Loom negative control for sharing model-local failures. The unchanged filesystem batch tests retain prior-head evidence: five passed in 0.02 s. - Scripted token endpoint covers expired/fresh tokens, exact injected expiry boundaries, successful rotation, revoked and transient failures, concurrent success/failure sharing, later retry, account switches and metadata-only edits. Independent ownership and missing failure sharing are detected by negative controls.
- Ordinary/native recovery covers pre-output SSE authentication errors, header/SSE combinations sharing one retry budget, terminal second failures, partial-output prohibition and cancellation during refresh.
- Codex stock execution enables threshold counting and serves no count route: 48,725 / 65,536 items, 16,811 remaining. The former inherited-counter negative control fails before generation.
- The original uncached four-adapter stock gate remains intact: Chat 63,478 (2,058 remaining), Anthropic 62,711 (2,825), Responses 62,719 (2,817), Gemini 62,521 (3,015). No allowance increased.
- Latest Windows permission/negative-control filter: 3 passed in 0.34 s, including the owner-only oversized-merge property, guard-removal negative control and existing oversized-buffer case. The unchanged private-file creation/replacement tests retain prior-head evidence: two passed in 0.02 s. Latest Windows
lb.ps1 cargo check -p acyclic-harness-providers --no-default-features --lib --tests --features acyclic-harness/filesystem,acyclic-harness/test-support,acyclic-fs/memorypassed in 1.38 s, excluding queue time. The unchanged filesystem crate retains its passing prior-head compile check. - Existing auth/JWT/stream shrinking properties and checked-in corpus run in the provider suite. The unchanged parser completed 256 pinned-nightly fuzz runs in under 1 s before these dispatch/counting changes.
All credentials and clocks are fixtures. Tests use explicit credential configuration, never a real ~/.codex/auth.json, live keys or external HTTP. No race tests sleep or poll.
Decisions
- Main through 808692b2e includes merged #496. Published history is preserved with signed merges and ordinary pushes.
- Use the user-directed conservative three bytes per token local policy for Codex login, rather than an unverified pre-inference endpoint. This is the user's explicit Codex-login accounting policy, not a verified tokenizer or a proof that arbitrary inputs cannot overflow the backend window. Do not use an unverified ChatGPT count route or reject stock execution for lack of that route. The byte/3 bot finding is an accepted policy limitation under that ruling. The usage anchor is transient derived state owned by the resolved model; transcript/credentials remain files. A fresh resolve or changed prefix falls back to a full estimate.
- The new counting-enabled Codex stock fixture uses the SDK's cached logical object-store composition, matching native filesystem composition, with the same 65,536-item allowance. The original bare-memory four-adapter gate is retained unchanged. An uncached counting-enabled Codex probe exceeded the allowance during result publication; this PR does not claim that additional configuration passes that allowance.
- Refresh ownership is in-process and registry-owned. No extra lock/cache files are written to CODEX_HOME. Publication stages and fsyncs beside auth.json, merges unrelated edits when token identity is unchanged, and rechecks before replacement. The final read/rename interval remains best-effort against an uncoordinated external CLI writer; this is not interprocess CAS.
- Codex login remains native-only. Browser registry bindings and TS provider deletion remain the next stacked PR, #508.