ci(nightly-next): train drive job runs only feat-cmux-next code with the release App; fix signing-guard base red

Merged
TE
16d ago
+208-6

Fixes the signing-guard red on feat-cmux-next, and closes the review finding on #22669 (receipt) that the train's drive job runs code from inputs.tip while holding the release App token.

What changes

  • nightly-next-train.yml drive job:

    • It checks out github.sha, the dispatched feat-cmux-next head (the job runs only on refs/heads/feat-cmux-next). It no longer checks out inputs.tip.
    • A new step, "Check the tip is on feat-cmux-next", runs before the token is minted and has no if:. It refuses a tip that isn't exactly 40 hex characters, or isn't an ancestor of that head. An empty tip still means the head.
    • Before this, anyone who could dispatch the workflow could run an unreviewed commit's nightly_next_train_drive.py with a token that can write contents and workflows.
    • No current caller passes tip: the tick sends only mode, and fix-forward sends the build and quarantine inputs. So nothing that works today breaks.
  • tests/test_nightly_next_train_drive_pin.py (ci-guards, group ci) checks that:

    • the checkout ref is ${{ github.sha }};
    • inputs.tip never appears in a run: or with:;
    • the check runs before the mint step and has no if: or continue-on-error.

    It also runs the real check step's script against a scratch repo: empty, the head and an ancestor pass; a side-branch commit, a short SHA, a branch name and a tip with a newline are refused.

  • tests/test_ci_signing_secrets_need_release_environment.py: #22661 added the drive job's CMUX_RELEASE_APP_* read in environment nightly-next-train, and that turned this guard red on feat-cmux-next. The guard now allows that one job, only the release App credentials, and only that environment. That's the same shape as the promote jobs' existing allowance, and the same change as in #22669.

  • Second path, from the review of 185c159a (receipt): good_build could name a fork's pull_request run on a branch called nightly-next. Its commit's train trailers then named "carried" cars, and the car guards check out and run those cars while the token is held. Fixed twice:

    • nightly_next_train.require_on_tip: before any car guard runs, the drive refuses a train whose source or any car commit isn't an ancestor of the tip. The tip is pinned to the dispatched head.
    • nightly_next_fix_forward.build_commit: a build is placed only by a push, schedule or dispatch run whose head repository is this one. find-nightly-next-recovery.py already checks the same.
    • New tests refuse a crafted base naming an off-tip car, an off-tip source and a short SHA. A fork run and a pull_request run now place no build. The drive-pin test also checks that require_on_tip comes before run_car_guards, and that no step moves the checkout to the tip.

Risk

risk:high: the change touches release credentials. It makes the workflow strictly safer. coord-deputy is taking it to Leo together with #22669.

Observed / Not run

  • Ran:

    • actionlint on nightly-next-train.yml and ci-guards.yml: clean;
    • prepush-gate.py: no new violations;
    • the check step's extracted script under bash in a scratch repo: all seven cases behave as described.

    Writing that step turned up a bug in my first draft: grep -x matches each line on its own, so a valid first line followed by a newline got through. The step now uses bash =~, and the test includes that case.

  • Not run locally: the Python tests. A hook on this Mac blocks test-file runs, so CI's ci-guards runs them on this head.

  • The train itself hasn't run with NIGHTLY_NEXT_TRAIN=on, and environment nightly-next-train doesn't exist yet.

Changelog

none

Agent session: cosmic-gray

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Fixes the nightly-next train's drive job so it holds the release App token only while running code from the dispatched feat-cmux-next head, and fixes the signing-guard red that #22661 caused on that branch.

  • The drive job checks out github.sha instead of inputs.tip, and a new step before token minting refuses a tip that isn't a 40-hex ancestor of that head.
  • Previously anyone able to dispatch the workflow could run an unreviewed commit's drive script with a token that can write contents and workflows; no current caller passes tip.
  • A new require_on_tip check refuses a train whose source or car commits fall outside the tip's history, closing a path where a crafted good_build could run unreviewed cars under the token.
  • build_commit now only places a build from a push, schedule, or dispatch run coming from this repository, so a fork's pull_request run can't name the base.
  • New ci-guards tests pin the checkout ref and check order, keep inputs.tip out of run:/with:, and exercise the real check script and the new refusal paths.
  • The signing guard now allows the drive job's CMUX_RELEASE_APP_* read in environment nightly-next-train only, in the same shape as the promote jobs' existing allowance.