ci(release): auto-repin embedded x402 image pins + release freshness gate - #618
Closed
bussyjd wants to merge 1 commit into
Closed
ci(release): auto-repin embedded x402 image pins + release freshness gate#618bussyjd wants to merge 1 commit into
bussyjd wants to merge 1 commit into
Conversation
…n freshness Every docker-publish-x402 branch build now lands a pin-bump commit (repin-embedded-pins job) updating the embedded x402-verifier / serviceoffer-controller / x402-buyer references to the images just built, and the release workflow gains a verify-image-pins gate that fails the tag when any source in the binaries' live import graph (go list -deps) changed after the pinned build commit. Together they make the rc14 stale-pin trap — a release whose embedded pins predate its own payment-path changes — structurally impossible: the bump is automatic, and a tag cut before the bump lands cannot release. The bump is committed through the GraphQL createCommitOnBranch API, so the commit is signed by GitHub itself (verified, github-actions bot) — compatible with the repo ruleset rejecting unsigned commits, which a workflow git push could never satisfy. expectedHeadOid is the live remote head with one retry on race; only the two guarded template files are ever sent. The gate is fail-closed (a go-list failure refuses to pass rather than degrade to a partial path set), binds each embedded digest to what GHCR serves for the pinned tag (a fresh tag with a hand-edited digest fails), ignores _test.go/testdata churn, and hunk-filters the two pin-carrying templates so pin bumps don't self-stale while any other edit to them still counts. release/** branches get the same build+repin treatment as main. The exact-ref pin tests become invariant tests: pins must be digest- pinned, share one build commit, and descend from the named fix commits (ancestry-verified via git, skipped on shallow clones) — so the bot can bump pins without touching Go files while the carries-fix-X guarantees get stronger.
bussyjd
force-pushed
the
chore/auto-repin-x402-images
branch
from
June 11, 2026 03:43
4b9bb7b to
93d8f62
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Automates the release-time image repin so it can never be forgotten again. Two mechanisms, belt and suspenders:
flowchart LR P["push to main / release/**<br/>(or workflow_dispatch)"] --> B["docker-publish-x402<br/>build + push images<br/>:shortsha"] B --> R["repin-embedded-pins job<br/>rewrite x402.yaml + llm.yaml pins<br/>commit chore(ci): repin … [auto]"] R --> T["operator tags vX.Y.Z"] T --> G{"release.yml<br/>verify-image-pins gate"} G -->|pins fresh| REL["binaries built,<br/>draft release"] G -->|stale / digest mismatch /<br/>unresolvable pin| BLOCK["release FAILS<br/>with fix instructions"]repin-embedded-pinsjob indocker-publish-x402.yml): after every successful branch image build,.github/scripts/repin-x402-images.shrewrites the embeddedx402-verifier/serviceoffer-controller/x402-buyerpins to the multi-arch index digests of the images just built, and the job pushes achore(ci): repin x402 images to <sha> [auto]commit. Branch refs only — never tags.verify-image-pinsinrelease.yml,releasenow needs it):.github/scripts/verify-x402-pins.shfails the tag when any source in the three binaries' live import graph (go list -deps, not a hand-maintained path list) changed after the pinned build commit.This closes the trap that hit v0.10.0-rc14: the train's pins were its own merge base (
04bebbc), so the shipped manifests deployed images containing none of the train's verifier/buyer changes until a manual rebuild+repin (2db429b, the rc11 pattern). With this PR that manual step is automatic, and a tag cut without it cannot release.Gate properties (each adversarially tested)
go listfailure refuses to pass rather than silently degrading to a partial path set (reproduced: with a brokengo, the old draft waved stale pins through; now exits 1).VERIFY_X402_PINS_OFFLINE=trueskips for air-gapped runs._test.go/testdata churn is ignored (doesn't compile into the binaries).agent_render.go,openapi.go,go.mod, … — exactly the trap.Workflow security
github.ref_name,github.sha) are env-bound in the repin job, never interpolated into script text (branch names may contain shell metacharacters).contents: write, the new gate job iscontents: read.createCommitOnBranchAPI: GitHub signs the commit itself (verified, github-actions bot), so the job is compatible with arequired_signaturesruleset — a workflowgit pushcan never produce a verified commit.expectedHeadOidis the live remote head with one retry on race; only the two guarded files are ever sent. No recursion: API commits made withGITHUB_TOKENdon't trigger workflows.release/**pushes now build+repin like main.Test refactor
The exact-ref equality tests (
TestEmbeddedImages_X402ControllerAndBuyerUseFixPins,TestX402VerifierImage_CarriesAgentAuthFix,TestServiceOfferControllerImage_CarriesSecretCreateOnlyFix) became invariant tests, so the bot can bump pins without editing Go files while the guarantees get stronger:<repo>:<short-sha>@sha256:<digest>(digest discipline already covered byTestEmbeddedImages_NamedImagesAreDigestPinned),TestEmbeddedImages_X402PinsShareOneBuildCommit),b39bcaa(Secret-create-only),abfd55a(agent auth),ab71481,86b8c9f— ancestry-verified via git (TestEmbeddedImages_X402PinsCarryRequiredFixes), skipped gracefully on shallow clones where the release gate covers it. Only main-reachable commits belong in that list (release-branch SHAs stop being ancestors after a squash-merge; documented in the test).Review
42-finding adversarial review pass (4 lenses × refutation agents): 1 major fixed (fail-open on
go listfailure), 2 minors fixed (ref_name injection, digest binding), nits fixed (gate job permissions, multi-pin extraction, diff-header anchor that could hide YAML doc-separator deletions, ambiguous-SHA error hint). Branch-protection findings refuted by measurement (no protection/rulesets on main).Validation
shellcheckclean on all three scripts; both workflows parse.go).2db429bimages verified end-to-end (gate + tests pass on bumped state), then restored.go test ./...green (34 packages).Stacks on #616 (branched from the v0.10.0-rc14 tag commit); merge that first.