Skip to content

Codex + Cursor worker turns on one gunbai-secrets credential each (custody, spawn, grants) - #13359

Queued
gunbai-bot[bot] wants to merge 55 commits into
mainfrom
session/swift-stag-32
Queued

gunbai-bot[bot] wants to merge 55 commits into
mainfrom
session/swift-stag-32

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Node adhoc-932f2935-e8e. Builds on #13367 (wise-ibex-444, Claude), which is merged into this branch: the shared executor route, the env-from-file wrapper and the custody pattern. Once #13367 lands, this diff shrinks to the Codex/Cursor part.

Operator rulings this implements (relayed by swift-ibex-601, 2026-10-05)

  • Codex uses subscription (chatgpt) mode, not API-key mode.
  • No codex chatgptAuthTokens login. It is upstream-labelled [UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE (codex-rs app-server-protocol v2/account.rs).
  • Credentials are delivered by host custody under the fleet-converge run's WIF. No worker turn touches Secret Manager.
  • codex-auth-harness lives on exactly one custody host, and Codex dispatch is pinned to it.

Why Codex needs write-back (source-read, not inferred)

extdeps.llm.codex_auth now records the refresh path from github.com/openai/codex@7f892275, codex-rs/login auth/manager.rs:

  • When it refreshes: should_refresh_proactively fires within 5 minutes of access-token expiry, so roughly hourly.
  • It writes back: persist_tokens writes auth.json back, including the returned refresh_token.
  • The issuer detects reuse: refresh_token_reused maps to RefreshTokenFailedReason::Exhausted.
  • No cross-process lock: the refresh lock is per process.

Whether the issuer rotates the refresh token on every exchange stays CodexRotationUnobserved until it is observed. The experiment runs on a throwaway login, never the production secret.

What lands

Custody (gunbc.host_credential_custody_converge)

  • New CursorWorkerTurnApiKey and CodexWorkerTurnAuth rows, both on srv2.
  • CustodyAuthority marks the Codex row host-authoritative after placement. A Codex auth.json that differs from the store is never overwritten:
    • if only short-lived tokens moved, it is left in place;
    • if the refresh token rotated, the host file is added to codex-auth-harness as a new version, then re-read from the host;
    • if it names another account, the converge refuses.
  • Placement refuses any host-authoritative row scoped to many hosts. The single-host rule is structural, not a convention.

Spawn (gunbc.roadmap_dispatch_actuator dispatch_provider_inner_argv_for_instance)

  • Cursor: cursor-agent --print with stream-json, --force, --sandbox enabled and --trust. CURSOR_API_KEY is exported from the custody file inside the child (extdeps.posix.sh_invocation), never on argv.
  • Codex: codex exec with CODEX_HOME set to the custody directory, under flock --exclusive --nonblock --conflict-exit-code 75. The new extdeps.tools.util_linux_flock models it, and the lock lives exactly as long as the process.
  • Off the custody host: CursorProviderOffCustodyHost / CodexProviderOffCustodyHost refusals. This replaces the ambient Codex login path.
  • Behaviour change: the srv1 lab instance codex-provider-feedback-v0 can no longer spawn Codex. That follows the single-host ruling.

Grants (gunbc.auth.fleet_secret_accessor_roster)

  • secretAccessor on both secrets.
  • secretVersionAdder on codex-auth-harness, for the converge's write-back.

Both are folded by the existing converge and approval entries. The census row for the custody site covers the write-back.

Cursor credential (gunbc.cursor_harness_credential)

  • cursor-api-key-harness is pinned to an exact version (1), per the existing rule that Cursor never reads latest.
  • with_materialized_secret with an EnvVar binding.
  • Liveness comes from cursor-agent status --format json, which is ProbeInert. The witness's rejected-key output is copied from cursor-agent 2026.10.01.
  • The declared srv2 Cursor offer now carries the harness secret instead of CursorLocalLoginRef.

Other changes

  • extdeps.llm.cursor_cli: CursorRunShape separates the run from the credential, so the argv has one authority.
  • fleet-converge.yml: regenerated, adding the custody credential options.

Residual windows

  • Store lags the host: a Codex token rotated on the host reaches the store only at the next custody converge. Losing the host in that window means the operator logs in again; the next dispatch refuses rather than running on a spent token.
  • Converge reads during a write: a converge that reads auth.json while Codex is mid-write refuses at parse and retries next run.

Declared frontier B: a workload identity bound to the turn runner on the custody host would let a turn persist the rotation immediately. Trigger: select_access_token_source resolves a federated token on that host outside an Actions run.

Limits

  • Codex: limits are already read from the CLI (rateLimits → ProviderLimitObservation), and dispatch skips an exhausted bucket.
  • Cursor: cursor-agent 2026.10.01 exposes no usage or limits command. How exhaustion appears in a turn's output is observed with the live key, in a follow-up.

Witnesses

File Pass Covers
test.claim.codex_harness_credential 8 Payload admission; write-back owed vs not owed; account change; unreadable read-back
test.claim.cursor_harness_credential 9 Accepted vs rejected key through the real bracket; unprobed; store axis; route over the declared srv2 inventory
test.claim.host_credential_custody_converge 43 Host-authoritative differing file left or written back, never overwritten; many-host Codex row refused, Cursor row still placed; Codex delivered only to srv2
test.claim.worker_turn_dispatch_witness_test 4 The production spawn fold on the srv1/srv2 instances, both providers, both arms
test.claim.gcp_secret_access 37 Version-adder grant; the roster identity join, which was red on main

Pre-existing, not from this PR: two alignment witnesses in roadmap_dispatch_actuator_witness_test also fail on main at 5e9c7ea1. Their subject 2-scm-git-upstream-model gained a parent.

Still needed before the live turns

  1. The operator creates cursor-api-key-harness. Payload: the raw Cursor API key, one line, no trailing newline.
  2. The operator creates codex-auth-harness. Payload: a fresh auth.json (auth_mode chatgpt) from a login in a throwaway CODEX_HOME that no running codex uses afterwards.
  3. Run the accessor + versionAdder ensure via approval.
  4. Run the custody converge for both rows on srv2.
  5. One live turn per provider through ?executor=codex|cursor.

🤖 Generated with Claude Code

Brian Searls and others added 14 commits October 5, 2026 04:43
…ss backend

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ugh admitted_model

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…the site in the privileged-effect census

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…13004) beside the OpenRouter row

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…eview 76210)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Codex (operator ruling: subscription/chatgpt mode, option B):
- extdeps.llm.codex_auth: record the refresh path read from codex-rs source
  at a pinned revision (hourly proactive refresh, persist_tokens write-back,
  refresh_token_reused handling, no cross-process lock) beside the existing
  codex-cli 0.145.0 observation; rotation stays Unobserved.
- gunbc.codex_harness_credential: lease codex-auth-harness (durable hold),
  admit the auth.json payload (chatgpt mode only), materialize a 0600
  per-turn CODEX_HOME, run the turn, and add a secret version when the
  refresh token rotated and the account is unchanged. End-of-turn write-back;
  residual window = one turn.

Cursor:
- gunbc.cursor_harness_credential: cursor-api-key-harness (exact version 1)
  bound as CURSOR_API_KEY through with_materialized_secret, liveness from
  `cursor-agent status --format json` (ProbeInert, observed on 2026.10.01).
- dispatch_selection: the declared srv2 Cursor offer and default request
  carry the harness secret instead of CursorLocalLoginRef, which the
  SDK-local binding refuses.

Witnesses: 8 Codex + 9 Cursor, including the route claim over the declared
srv2 inventory.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…kenCount (review 76237)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…; explicit arms

Operator rulings (via swift-ibex-601, 2026-10-05): host custody, no per-turn
Secret Manager access; codex-auth-harness on exactly one custody host.

- host_credential_custody_converge: CursorWorkerTurnApiKey and
  CodexWorkerTurnAuth rows on srv2. CustodyAuthority makes the codex row
  host-authoritative after placement: a differing host file is never
  overwritten; it is left (only short-lived tokens moved) or written back
  to the store as a new version (refresh token rotated); another account
  refuses. Placement refuses a host-authoritative row scoped to many hosts.
- codex_harness_credential: the per-turn SM bracket is deleted (replaced,
  not kept beside); the turn side is a durable hold over the custody
  CODEX_HOME. Frontier B (turn-host workload identity) declared with trigger.
- fleet_secret_accessor_roster: accessor rows for both secrets and a
  secretVersionAdder row for codex-auth-harness, folded by the converge and
  approval entries. Census custody row covers the write-back.
- fleet-converge.yml regenerated (custody credential options).
- Floor fix: no wildcard arms over closed coproducts in new code.
- gcp_secret_access witness: name fabric_state_key_accessor_row (the
  roster identity join was red on main).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…licit executor request

An operator dispatch may name ?executor=claude; the harness stays the default.
The explicit Claude draw runs one stream-json trip under the custodied
setup-token, projects the CLI's own rate_limit_event/result lines onto
per-credential provider standing, selects over srv1's live inventory (which now
offers Claude, discharging the "until the WorkerTurn PR" deferral), and spawns
claude with CLAUDE_CODE_OAUTH_TOKEN exported from the srv1 custody file into the
child's environment only. Delivery is the existing WIF custody converge under a
new accessor grant. Attempts record their spawn's process fingerprint so
observation and stop judge a Claude pane against its own fingerprint.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	.github/workflows/fleet-converge.yml
#	dag/gunbc/auth/fleet_secret_accessor_roster.dag
#	dag/gunbc/auth/privileged_effect_census.dag
#	dag/gunbc/fleet/host_credential_custody_converge.dag
#	dag/test/claim/gcp_secret_access_witness_test.dag
…lare the sh-string scaffold (review 76293)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… refusal

Builds on #13367 (merged into this branch): dispatch_provider_inner_argv_for_instance.
- Cursor: on its custody host (srv2) the turn runs cursor-agent --print
  stream-json --force --sandbox enabled --trust in the attempt worktree, with
  CURSOR_API_KEY exported from the custody file inside the child through the
  shared extdeps.posix.sh_invocation wrapper; elsewhere CursorProviderOffCustodyHost.
- Codex: on its custody host the turn runs codex exec with CODEX_HOME = the
  custody directory, under an exclusive non-blocking flock (conflict exit 75);
  elsewhere CodexProviderOffCustodyHost. Replaces the durable-hold bracket.
- extdeps.tools.util_linux_flock (new); extdeps.llm.cursor_cli splits the run
  shape from the credential so the argv has one authority.
- Witnesses over the production spawn fold for srv1 and srv2 instances.
- Parse: move annotations out of declaration bodies (floor parse phase).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nesses

- extdeps.exec.program: flock_program joins the uncataloged_program caller
  roster (constructor call admission refused it).
- roadmap_dispatch_actuator witnesses: codex worker turns are pinned to the
  custody host (srv2), so the codex tmux shape is shown on the srv2 lab
  instance; the srv1 lab's repo/tmux/state witness uses the Claude spawn and
  adds the control that a codex spawn there refuses at
  codex-credential-custody-host; the continuation-origin witness uses Claude.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot changed the title Codex + Cursor worker-turn credentials from gunbai-secrets Codex + Cursor worker turns on one gunbai-secrets credential each (custody, spawn, grants) Oct 5, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review October 5, 2026 08:21
@gunbai-bot

gunbai-bot Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

On review 76372: all three findings are on extdeps.posix.sh_invocation (posix_sh_export_from_file_then_exec_script / _words / _command). That module is #13367's (wise-ibex-444), and this branch carries it only through the merge of #13367. To keep one writer per file, the fix lands in #13367: an env-name carrier, and the read-export-exec protocol moved out of extdeps or modeled with a dissolution trigger. I'll merge it here once it's pushed and retype this PR's one call site (Cursor's cursor_api_key_env_var in gunbc.cursor_harness_credential cursor_worker_turn_argv) to the new carrier. The reviewer notes the new extdeps.tools.util_linux_flock row is a faithful upstream model; it stays as is. — sent from swift-stag-32

gunbc-ci-auto-heal and others added 3 commits October 5, 2026 08:37
… wrapper (review 76372)

PosixShellName is a sole_constructor carrier minted only by admit_posix_shell_name
(XBD 3.235, posix_name_ok); the sh builder takes it, so a name holding ;, $() or
a space cannot become shell text. State why the builder is homed in extdeps.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- cursor_worker_turn_argv admits CURSOR_API_KEY through admit_posix_shell_name
  and builds the argv from posix_sh_export_from_file_then_exec_command via
  argv_words (the _words builder was deleted upstream, review 76293).
- New CursorProviderCredentialUnbound arm for a refused name, distinct from
  the custody-host refusal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Review 76372 is addressed: #13367's fix is merged in at b3321e2.

  • Typed variable name: extdeps.posix.shell_command_language PosixShellName has a sole constructor and is minted only by admit_posix_shell_name (XBD 3.235). The shell wrapper takes only that type.
  • Cursor call site: gunbc.cursor_harness_credential cursor_worker_turn_argv now admits CURSOR_API_KEY before it builds the wrapper. A refused name surfaces as a new CursorProviderCredentialUnbound arm with spawn step cursor-credential-binding, kept distinct from the custody-host refusal.
  • Scaffold marker and dissolution trigger: Drive Claude (subscription setup-token) from belt dispatch via an explicit executor request #13367 has them on the wrapper.
  • Layer placement: the wrapper stays in extdeps. Its module comment states the divergence and the reason: the ArgvCommand mint admits only per-tool extdeps builders. Which variable, credential and path to use stays in the gunbc credential layer.

Witnesses run with claim_batch on BuildBuddy:

Witness file Result
worker_turn_dispatch 4/4
cursor_harness_credential 9/9
claude_code_dispatch 22/22
roadmap_dispatch_actuator 50/52

The two roadmap_dispatch_actuator failures (the_preamble_names_the_derived_refusal_for_an_unrooted_node, an_unresolved_alignment_refuses_the_real_spawn_fold_with_zero_commands) also fail on main at 5e9c7ea. Their subject node 2-scm-git-upstream-model gained a parent there.

gunbc-ci-auto-heal and others added 5 commits October 5, 2026 09:01
… scaffold (review 76385)

posix_sh_export_from_file_then_exec_program builds the program from
v2.extdeps.languages.bash_build constructors and serializes it with
v2.workflow.bash_emit bash_emit_stmts; an emitter refusal is a typed arm.
The string-joined script, its Scaffold row and its dissolution trigger are
deleted: the capability they waited on already exists.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…efusal

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…w-end terms; seed the day pool from the upstream's remaining

P1: the response deadline (2348 s) was the lease term for both pools, and a lapsing 60 s window refuses
any lease that outlives it (LeaseCrossesLapse), so no request could ever be sent. Each pool is now
leased to the end of its own window (gunbc.fabric_quota QuotaTermToWindowEnd, read off the same clock
reading) and settled at one before the POST, so the charge no longer depends on the response lifetime;
a request whose fate is unknown stays charged.

P2: the day pool started from the tier ceiling and ignored free_model_daily_requests.remaining. The
bind now records that reading on the day partition as the existing PoolUpstreamObserved event
(gunbc.fabric_quota fabric_quota_observe_upstream), so observe_upstream_remaining holds the shortfall.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…the dispatched instance

Each Claude attempt copies the custody bytes once, owner-only, to a path derived
from the attempt identity; the standing trip reads it and the child's
grammar-emitted wrapper reads and removes it before exec, so a custody
replacement between admission and spawn cannot reach the child and a missing
snapshot refuses the launch (exit 78). Executed A->B control: child got A,
relaunch refused, B never seen. Explicit vendor selection now runs on the
dispatching HostDashboardInstance (dispatch_actuator_selection_for_provider_on);
inventory_with_observed_standing generalizes the standing join to all providers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brian Searls and others added 3 commits October 5, 2026 18:46
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	dag/gunbc/roadmap/roadmap_dispatch_actuator.dag
…n optional lock

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LAND at ff14668. This approval supersedes my REQUEST_CHANGES review 5433130046. No remaining merge-blocking finding from me for the current custody workflow and the previously accepted staged activation scope.

Reviewed ca6ad6a directly, its final production callers, the decision witnesses, and ff14668's one-line RemoteFileModeOwnerReadWrite import. The delta from 035cd96 is exactly these two commits.

The metadata rollback is closed: custody_action's identical-content/wrong-metadata CustodyWrite is mapped by host_authoritative_custody_action to CustodyRepairMetadataInPlace. converge_host_credential_custody executes only chown/chmod on the existing path for that arm; it does not invoke the store-byte stage/rename writer. If A was observed and the host refreshes to B before metadata repair, B survives. Because this arm has no host_content reading, the final comparison may still compare against A and refuse; that is fail-closed stale observation, not a rollback or a false success.

First placement is now CustodyCreateOnly: stage -> chown staged file -> chmod staged file -> ln -T staged destination, without -f. An independently appearing destination makes publication fail rather than being overwritten. The failed leg is retained as write_refusal, staging cleanup and metadata/content readback still run, and custody_run_settled cannot report success over that failure. Store-authoritative rows retain the existing CustodyWrite behavior. Existing matching and differing host-authoritative cases retain leave/write-back/refusal behavior.

Independent EXECUTED local GNU-utility controls, using dummy credentials only (not gunbc, its emitter, its decision functions, SSH transport, or Codex): (1) in-place A->B refresh followed by chown/chmod preserved B and its inode, set 0600, and cmp against old A exited 1; (2) atomic-replacement A->B refresh gave the same result; (3) absent destination published A successfully and staging cleanup left one link; (4) B appearing after staging made ln exit 1 and left B and its inode untouched; (5) a destination directory and (6) a destination symlink were each preserved with ln exit 1. All six controls passed. This supplements the source review; it does not turn the added decision/dispatch witnesses into an enrolled end-to-end execution witness.

Scope clarification: this is not an endorsement of arbitrary concurrent invocations of the raw create_only_publish helper. The fixed .gunbc-staging name and hard-link publication rely on the existing serialized custody route. I checked fleet_converge_workflow: HostCredentialCustodyConverge maps to the shared job and ExecutorDomain; its job uses the host mutation concurrency group with cancel_in_progress=false. Preserve that serialization. Supporting parallel custody writers would need private, exclusively owned staging and cleanup, not ln -T alone. This is not a new landing condition for the current route.

The earlier instance/standing and Cursor snapshot repairs retain their accepted disposition. Codex's unobserved runtime-fingerprint gate and Cursor's unmodeled entitlement trip remain activation work; approval does not claim either provider is already selectable or live-qualified.

Verified workflow 37523876216 on this exact head: emit-build, floor, generated, rust-unit-tests, and witnesses all completed successfully. Normal merge-queue checks still apply. No queue, merge, grant, or credential action taken.

@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Oct 7, 2026
Any commits made after this event will not be merged.
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 7, 2026
@gunbai-bot

gunbai-bot Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Heads-up before queueing: this PR adds import v2.std.optional, but #13388 re-homed that module to std.optional, so it no longer resolves on main. The PR's own run may pass because it predates main's change. In the merge queue it will fail the floor (unresolved import: module 'v2.std.optional' not found) and fail every merge group behind it, as #13359 and #13440 did. Please merge main in and repoint those imports to std.optional before enqueueing. — sent from swift-bat-828

Brian Searls and others added 5 commits October 7, 2026 18:29
… imports to std.optional

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…optional .first() fields (floor: optional vs required '==')

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…2.std.optional was re-homed by #13388)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Oct 7, 2026
Any commits made after this event will not be merged.
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Oct 8, 2026
Brian Searls and others added 2 commits October 8, 2026 12:12
Conflicts, union (both sides kept):
- dag/gunbc/auth/fleet_secret_accessor_roster.dag: the OpenRouter free-tier key row and the oracle OCI API signing key row are both rows and both roster entries.
- dag/test/claim/gcp_secret_access_witness_test.dag: named_accessor_rows names the openrouter and oracle rows beside the existing ones; my duplicate fabric_state_key import is dropped now that main imports it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…egenerate fleet-converge.yml

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Oct 8, 2026
Any commits made after this event will not be merged.
…or roster rows

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot removed this pull request from the merge queue due to a manual request Oct 8, 2026
Brian Searls and others added 2 commits October 8, 2026 22:50
…ness form

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… in the main merge; cover BearerHeaderFile in the cursor witness binding_marker

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Oct 9, 2026
Any commits made after this event will not be merged.

This branch has not been deployed

No deployments
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