Skip to content

fix(codex): limit Direct hard-lock fallback to the locked account - #7328

Closed
luvs01 wants to merge 2 commits into
lidge-jun:devfrom
luvs01:fix/direct-hard-lock-caller-20261019
Closed

luvs01 wants to merge 2 commits into
lidge-jun:devfrom
luvs01:fix/direct-hard-lock-caller-20261019

Conversation

@luvs01

@luvs01 luvs01 commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Limit Direct-mode main hard-lock fallback to requests whose original credential ownership selects stored-main substitution or whose caller bearer and effective workspace match the observed main credential. Other callers keep their native route and credential when the observed main account is blocked.
  • Share Direct admission's memory-only ownership check, wait for pending startup binding for caller-owned credentials, and recheck identity and lock state before rewriting. Preserve immediate missing-bearer 401 responses, normal disabled-route 404 responses, and existing Pool selection behavior.
  • Update regression coverage and the account-control contract. Addresses the caller-identity review concern.

Verification

Initial caller-identity validation at eed9ca93cdf5a51bcbc4462179ec2c00ede761eb (Windows/Bun 1.4.3):

bun scripts/test.ts --changed=8640c2f30edbd2fbb6947ed5c25e2793f5ae72a6 tests/codex-integration/main-account-hard-lock-fallback-routing.test.ts tests/codex-integration/main-account-policy-binding-wait.test.ts tests/codex-integration/main-account-hard-lock-auth.test.ts tests/codex-integration/codex-envkey-admission-substitution.test.ts tests/codex-integration/bearer-admission-routed-provider.test.ts
  • 152 pass / 0 fail / 884 assertions, five explicitly selected files, 193.29 seconds. This is focused import-connected coverage, not the complete changed-test suite. It covers separate caller identities, matching-main and trusted-substitution fallback, startup-binding rechecks, missing credentials and disabled routes.
  • bun node_modules/typescript/bin/tsc --noEmit, bun scripts/structure-ssot.ts, bun scripts/privacy-scan.ts, bun scripts/file-size-ratchet.ts (repository-wide), and git diff --check — passed.
  • After the final documentation-only edit, structure, privacy, the full file-size ratchet and diff checks passed again; production/test hashes were unchanged.

CI cycle follow-up at 901fdde8c6a309f2ce45fbf1dd607084febb1669 (Windows/Bun 1.4.3):

  • The original head failed the module dependency-cycle check. Move mainHardLockFallbackCallerCheck verbatim into core-auth.ts, which owns its credential-domain helpers, and update the import. This removes the cycle through core-options.ts and main-hard-lock-routing.ts without changing routing or authentication policy.
  • bun test tests/responses/responses-core-modules.test.ts: 9 pass / 1 fail before the fix, then 10 pass / 0 fail / 414 assertions after it. The focused test through the official wrapper also recorded 10 pass / 414 assertions / exit 0. These are separate runs.
  • TypeScript, structure, privacy, the repository-wide file-size ratchet and diff checks passed for this follow-up.
  • The five-file follow-up execution finished, but its final counts and internal exit were not recovered; it is not claimed as passing. Changed-mode boundary-test attempts did not yield a verified passing run: one selected zero tests and exited 1; another exited 1 without captured output. The directly focused and wrapper runs above are the verified evidence.

The full local repository test suite, unfiltered local changed-test suite and real-provider E2E were not run. The selected five files took 193 seconds on a shared host, so local validation was limited to the changed behavior and directly connected authentication cases. Hosted CI coverage is recorded separately below. No GUI changes.

Current-head hosted CI at 901fdde8c6a309f2ce45fbf1dd607084febb1669: Cross-platform CI 39150023588 completed successfully; test 1/4, test 2/4, test 3/4 and test 4/4 each succeeded. Jobs marked skipped remain unexecuted and are not counted as passing platform validation.

This PR is ready for maintainer review. Maintainer security review of the authentication boundary and any required Code Owner approval remain pending before merge; local independent review is not that approval.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Maintainer security review of the authentication boundary and any required Code Owner approval are complete before merge.
  • CI passed on the current head 901fdde8c6a309f2ce45fbf1dd607084febb1669.

Summary by CodeRabbit

  • Bug Fixes
    • Direct-account fallback routing now applies only when the request uses the stored main credential or the caller’s credential matches the observed main account. Other callers retain their existing route.
    • Requests with a caller credential wait briefly for startup account-policy binding before routing; requests without one continue to receive an authentication error immediately.
    • Fallback decisions now recheck that the account remains blocked before changing the route.

Use the original route's credential ownership and observed-main caller check before applying Direct hard-lock fallback. Preserve binding, missing-bearer and disabled-route behavior; document and cover the boundary.
@coderabbitai

coderabbitai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: lidge-jun/opencodex/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: a6fedddb-24b0-49c9-95a5-44118cbaaf8f
📥 Commits

Reviewing files that changed from the base of the PR and between 8640c2f and 901fdde.

📒 Files selected for processing: 7
  • src/codex/auth-context.ts
  • src/server/responses/core-auth.ts
  • src/server/responses/main-hard-lock-routing.ts
  • src/server/responses/request-prepare.ts
  • structure/codex-account-controls.md
  • tests/codex-integration/main-account-hard-lock-fallback-routing.test.ts
  • tests/codex-integration/main-account-policy-binding-wait.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

📝 Walkthrough

Direct-mode hard-lock fallback routing now checks whether the request caller matches the observed main credential or uses stored-main substitution. The Responses request path awaits the check and applies a fallback only when the required conditions hold.

Changes: Main hard-lock fallback routing

Layer / File(s) Summary
Determine caller eligibility
src/codex/auth-context.ts:1083-1095, 1188, src/server/responses/core-auth.ts:5, 28, 595-612, tests/codex-integration/main-account-hard-lock-fallback-routing.test.ts:7, 91-189, 392, tests/codex-integration/main-account-policy-binding-wait.test.ts:25-26, 49, 168, 185-271
A shared predicate handles stored-main substitution and caller bearer checks, including bounded waiting for policy binding. The fallback caller check previews the source route and checks its credential context. Integration tests cover caller identity, admission modes, and startup binding behavior.
Apply the asynchronous fallback
src/server/responses/main-hard-lock-routing.ts:13, 76-101, src/server/responses/request-prepare.ts:103, 368-377, tests/codex-integration/main-account-hard-lock-fallback-routing.test.ts:191-248, 261, 278-285, 300-335, structure/codex-account-controls.md:87-94, 113-114
The override now requires caller proof in Direct mode and rechecks whether the lock remains blocked. Request preparation awaits the override and handles cancellation and mapped auth-context errors. Tests cover blocking windows, eligible pool accounts, and fallback selectors. The documentation describes Pool and Direct eligibility.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant RequestPreparation
  participant FallbackOverride
  participant CallerCheck
  participant RouteResolution
  participant AuthContext
  RequestPreparation->>FallbackOverride: await fallback override
  FallbackOverride->>CallerCheck: check source model
  CallerCheck->>RouteResolution: preview source model route
  CallerCheck->>AuthContext: check caller against observed main credential
  AuthContext-->>CallerCheck: caller eligibility
  CallerCheck-->>FallbackOverride: caller check result
  FallbackOverride->>FallbackOverride: recheck blocked state
  FallbackOverride-->>RequestPreparation: fallback rewrite or no override
Loading

Possibly related PRs

  • lidge-jun/opencodex#2847: Adds request-owned Codex bearer handling, which shares the caller-credential path checked by this PR’s Direct-mode fallback routing.

Suggested reviewers: lidge-jun

Merge Risk: ⚪ Minimal · up to 901fd

No actionable issue remains from the reviewed changes; the PR is mergeable after normal checks and required approvals.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 6 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: restricting Direct hard-lock fallback routing to the locked account. This matches the pull request objectives and changed files.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 6 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Oct 18, 2026
Move the caller-check factory into core-auth, which owns the credential-domain helpers, and update its request-preparation import. Keep the function body and routing/authentication policy unchanged.
@luvs01
luvs01 marked this pull request as ready for review October 18, 2026 17:46
@Ingwannu

Copy link
Copy Markdown
Owner

Intake/scoped review at 901fdde8c6a309f2ce45fbf1dd607084febb1669.

The change checks the original Direct caller's credential domain before applying stored-main fallback and rechecks state after the startup-binding wait. Keep foreign/missing bearer, workspace mismatch, trusted substitution and cancellation controls; the documented purpose is not to widen Direct admission. Current functional CI is successful; authentication/destination review remains required.

Existing current-head CI was inspected; its success is limited to the jobs actually selected, with skipped platforms retaining no runtime certification. No local suite was run for this branch in this intake. No workflow approval, new scan, live configuration change or merge was performed. Required review/readiness gates remain in force.

lidge-jun added a commit that referenced this pull request Oct 19, 2026
…locks, and scope Direct fallback to the locked main (#7393)

* fix(codex): renew opted-in pool credit evidence on the existing sweep

* test(codex): assert renewal performs no native-main access

* fix(codex): limit Direct hard-lock fallback to the locked account

Use the original route's credential ownership and observed-main caller check before applying Direct hard-lock fallback. Preserve binding, missing-bearer and disabled-route behavior; document and cover the boundary.

* fix(responses): break hard-lock caller-check import cycle

Move the caller-check factory into core-auth, which owns the credential-domain helpers, and update its request-preparation import. Keep the function body and routing/authentication policy unchanged.

* fix(codex): revalidate hard-lock fallback targets before request rewriting

* fix(codex): clear failure windows when account identities are removed

Forget account-scoped failure samples and pinned degradation warnings during deletion and physical main-account replacement. Preserve sibling account windows and cover both lifecycle paths.

* fix(codex): fence late failure-window samples by dispatch identity

Carry the main dispatch proof and stored Pool credential generation into terminal outcomes. Reject stale success and transient samples after identity or credential replacement, while preserving existing health and probe handling. Cover main identity ABA, same-ID Pool recreation, context reuse, and valid-current controls.

* fix(codex): revalidate tagged failure samples after credential replacement

Retain each stored Pool sample's dispatch credential generation and filter it against one current store snapshot per insertion or lookup. Reset retired-generation degradation, recovery timers and pin warnings without clearing a replacement on stale arrival. Cover both unlocked-read boundaries, derived state and the 512-sample snapshot bound.

* fix(codex): preserve failure windows when credential snapshots fail

Distinguish failed reads or parsing from valid absent/empty credential stores. Preserve retained samples and derived failure-window state while evidence is unknown, keeping existing boolean liveness callers compatible. Cover fixture read/parse failures, recovery and empty-store retirement without adding snapshot reuse optimization.

* fix(codex): reject non-object credential snapshot shapes

Require a non-null, non-array object before normalizing store rows. Preserve failure-window evidence for invalid top-level arrays or scalars while keeping missing files and empty objects valid. Cover six top-level shapes without changing row normalization.

* refactor(responses): keep passthrough-dispatch within its line budget

* fix(codex): keep a paused main caller out of Pool fallback

Carries the paused-main regression from #7290 and the production guard it proves is needed, plus a per-generation credit-renewal pacing test requested at intake.

Co-authored-by: Hylouis233 <88263959+Hylouis233@users.noreply.github.com>

* fix(codex): recheck a predicted-reset main hard lock with metadata (#7312)

A predicted reset now arms the capped WHAM recovery schedule instead of suppressing it, so an early reset redeemed outside opencodex is learned from verified metadata. The schedule never runs past the predicted reset, skips paused and needs-reauth mains, and a predicted reset still never lifts the lock by itself.

* test(codex): await the async hard-lock fallback override in #7340 cases

#7328 made applyMainHardLockFallbackOverride async for the Direct caller check; the #7340 revalidation cases were written against the synchronous form and asserted on a Promise once both landed.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* fix(codex): recheck Direct hard-lock ownership synchronously before rewriting

The async caller check now resolves to a synchronous ownership proof that is evaluated after the last await, so a main identity or bearer change during the policy-binding wait cannot reroute the original caller onto the fallback.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* fix(codex): keep predicted-reset hold rechecks paced across transitions (#7312)

Track the first hold per identity/credential separately from the backoff reservation so transient 401/403 retries are not re-armed, arm a hold that appears during token preparation instead of reading immediately, pull the due time in when the predicted reset moves earlier or passes, and keep hold reads at least one base interval apart.

* fix(codex): honor a revised reset that elapses between hold sweeps (#7312)

Recovery scheduling now reads the latest blocking reset from current evidence even after it has passed, so a reset revised earlier and expired before the next sweep releases the old reservation. It remains scheduling evidence only; admission still requires verified metadata.

---------

Co-authored-by: Ingwannu <ingwannu@users.noreply.github.com>
Co-authored-by: Epinephrine <luvs01@hanmail.net>
Co-authored-by: Hylouis233 <88263959+Hylouis233@users.noreply.github.com>
Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
@lidge-jun

Copy link
Copy Markdown
Owner

Carried into #7393 (squash d3dafd3) on dev with a Co-authored-by trailer, as part of the B1 codex pool / hard-lock bundle. It ships in the next release (2.90.0). Thanks!

@lidge-jun lidge-jun closed this Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants