fix(ci): validate the Version PR and preflight its release evidence - #1745
Conversation
|
Warning Review limit reachedNext included review available in 20 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (14)
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. Comment |
There was a problem hiding this comment.
Approved automatically: @FelixTJDietrich is listed in the REVIEW_POLICY_MAINTAINERS repository variable, which the repository treats as satisfying the review requirement. See the review policy in docs/contributor/ci-cd.mdx.
📚 Documentation Preview
|
🧩 Storybook Preview
|
The base branch was changed.
There was a problem hiding this comment.
Approved automatically: @FelixTJDietrich is listed in the REVIEW_POLICY_MAINTAINERS repository variable, which the repository treats as satisfying the review requirement. See the review policy in docs/contributor/ci-cd.mdx.
The changesets action pushes the Version PR with GITHUB_TOKEN, and a push made with that token starts no workflow run. The conclusion drawn from that was that the Version PR cannot be checked at all, so it merged through a standing ruleset bypass. workflow_dispatch is one of the two documented exceptions to the no-new-run rule, so the same token starts the same CI/CD workflow on the Version PR's own branch — no app, no personal access token, no long-lived credential. Every Version PR head commit gets one run, decided by asking whether the head already has one, so a missed dispatch heals on the next push to main. That run also carries a release evidence preflight. #1743 pinned subject parity — the pre-release scans cover the images the release covers — but the vulnerability policy is only one of the things the release gate evaluates, and a release was still the only thing that ever produced an evidence bundle. SBOM triple validation, the licence report binding, index membership, and the linux/arm64 vulnerability policy for the images we build could each fail for the first time at a release; the pinned upstream digests need no build, so #1743 already covers both of their platforms on the pull request that changes them. The preflight generates and verifies a real bundle over the images its own run built, through the one generator and the one verifier the release uses, so the only checks a release can be the first to perform are the signature checks that need signing material a release creates. Evidence generation and image-digest resolution move out of release.yml into scripts/generate-release-evidence.ts and scripts/resolve-release-images.ts so there is one of each rather than a second copy the preflight could drift from. Platform resolution reuses isImageIndex, so a release subject that is a single manifest fails rather than falling back to the index digest. ci-contract.test.ts asserts both callers run both scripts and that the verifier's only mode-conditional behaviour is signature verification, so a check gated on anything else fails the contract test rather than a release; release-management.mdx carries the check-by-check table and why the upstream images are deliberately judged twice on the Version PR. Verified with pnpm run format and pnpm run check, and with the new node:test coverage for the resolver's retry and hand-off, the generator's manifest against the real verifier, and the dispatcher's branch and run decisions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
c3c81db to
a6f1cd7
Compare
There was a problem hiding this comment.
Approved automatically: @FelixTJDietrich is listed in the REVIEW_POLICY_MAINTAINERS repository variable, which the repository treats as satisfying the review requirement. See the review policy in docs/contributor/ci-cd.mdx.
Problem
version-pr.ymlsaid it outright: "GITHUB_TOKEN-authored updates do not trigger CI, so release automation uses the ruleset bypass." The Version PR carried no checks and merged through a standing bypass, so the one commit whose merge cuts a release was the one commit nothing looked at before it landed.Pressure-testing that framing first: after #1743 the version commit is not unvalidated.
cicd.ymlgatesQuality,Security,Test,Compose,ActionlintandZizmoronversion-bump == 'true'for pushes, which is exactly the version commit, andrelease.ymlstarts only on a successfulCI/CDrun. #1743's upstream scan runs on every non-pull_requestevent, so the pinned digests are covered there too. The remaining cost was timing — a failure lands on redmainand blocks the release instead of on a red PR — plus a standing ruleset bypass with no expiry.But subject parity is not check parity.
verify-release-evidence.tsevaluates far more than the vulnerability policy, and a release was still the only thing that ever produced an evidence bundle. Three checks could fail for the first time at a release and none is structurally release-only:trivy --scanners licensebefore a release.linux/arm64for the images we build — findings and exceptions are matched per platform. fix(ci): scan the pinned upstream images before the release, not at it #1743 took both platforms of the pinned upstream digests, because their remedy is a digest bump that arrives in a pull request; the images we build are still scanned onlinux/amd64only, andvulnerability-remediation.mdxonmainalready names this preflight as what covers the other half.Fix
The Version PR gets real CI, with no new credential.
workflow_dispatchandrepository_dispatchare the two documented exceptions to the GITHUB_TOKEN no-new-run rule, soversion-pr.ymlstarts CI/CD onchangeset-release/mainwith the same token.scripts/dispatch-version-pr-ci.tsapplies one rule — every Version PR head commit gets a run — by asking whether the head already has one, so a missed dispatch heals on the next push tomainand a push that changed nothing costs nothing. The dispatched run reportsCI Status Gateonto the branch head, which is the pull request's head commit, so the Version PR can carry required checks like any other.The release gate becomes a re-verification. A new
Release evidence preflightjob generates a real bundle over the images its own run built and runsverify-release-evidence.tstwice — once writing the validation documents, once re-deriving them. Parity is by construction, not by a list: the same generator, the same verifier, and the verifier performs every check in every mode except the two guarded bymode === "verify-signatures".One generator, one resolver. A second copy of the evidence-generation shell is exactly the drift this removes, so digest resolution and bundle generation move out of
release.ymlintoscripts/resolve-release-images.tsandscripts/generate-release-evidence.ts, whichrelease.ymland the preflight both call. Neither redefines the subject set: the first-party half comes fromplanSubjects, the upstream half from #1743'splanUpstreamSubjects. Platform resolution reuses #1743'sisImageIndex, so a release subject that is a single manifest fails rather than falling back to the index digest.Check parity
check-release-image-inventory.tson every PR, plus a test feeding the real generator's manifest to the real verifierlinux/amd64linux/arm64ci-security-scan.ymlfor the pinned upstream digests, preflight for the images we buildcosign attestcreates it during the releaserelease.yml@refs/heads/mainplan-release.tsdecides the tagci-contract.test.tsasserts the two signature entries are the only mode-conditional behaviour in the verifier, so a new check gated on anything else fails the contract test rather than a release.docs/contributor/release-management.mdx§ Nothing may fail for the first time at a release is the durable home for the reasoning.The upstream images are judged twice on the Version PR, deliberately
The upstream scan is a minute of Trivy answering the one question a digest bump can break; the preflight is the whole evidence gate answering it while verifying a bundle. They cannot disagree — one planner, one evaluator, one policy file, and the same
selectPlatformDigestover the same index — so the cost is a duplicated scan, not a second opinion. Dropping the fast one takes the quick answer away from the PR that most needs it; dropping the upstream half of the bundle leaves a bundlevalidateManifestrejects. Written down inrelease-management.mdxso it reads as a choice rather than an oversight.Failure modes reasoned about, since
release.ymlcannot be rungeneratedAtformat are preserved deliberately;durationSecondsstays a number. Asserted by the generator test round-tripping throughvalidateManifest.set -euo pipefail, nothing catches it.resolve-release-images.tswrites<image>-digest=for every image;release.ymlstill reads the three it needs, andsubjects.sha256reads the widened TSV.gh workflow runvalidates inputs against the workflow file on the target ref.changeset-release/maindoes not exist today and is rebuilt frommain's tip whenever changesets runs, so it can never predate therelease-preflightinput.cancel-in-progressis on for pull requests (unchanged) and dispatches; merge groups and pushes stay uncancellable.release-preflight.Not included
No changeset:
verify-changesetsscopesSHIPPED_PATHStoserver,webappanddocker, and this touches only.github/,scripts/anddocs/. No vulnerability-policy exceptions — #1743 owns those. No gate was weakened. Nothing was added to therelease-imagespath filter's transitive import closure, so #1743'simportClosureassertion still holds unchanged.Maintainer action
Nothing is required for this to work. Once it has run once, the standing ruleset bypass on the Version PR has no remaining purpose and can be retired, in this order:
CI Status Gatelands green on its head commit.CI Status Gate,ActionlintandZizmorrequired.Doing 3 before 1 blocks releases behind checks that are not yet reporting.
Verification
pnpm run formatandpnpm run checkboth pass.ci-contract.test.tspasses as a whole — 27 tests, including #1743'simportClosureand both-platforms assertions. Newnode:testcoverage: the resolver's retry, digest validation and TSV hand-off; the generator's real manifest accepted by the realvalidateManifest; the dispatcher's branch derivation and run decisions.Model: Claude Fable 5. Harness: Claude Code.
🤖 Generated with Claude Code