claude: narrow the image-publish trigger to real Dockerfile inputs (#777)#790
Open
TomHennen wants to merge 1 commit into
Open
claude: narrow the image-publish trigger to real Dockerfile inputs (#777)#790TomHennen wants to merge 1 commit into
TomHennen wants to merge 1 commit into
Conversation
TomHennen
temporarily deployed
to
integration-test
July 13, 2026 00:06 — with
GitHub Actions
Inactive
The publish trigger matched all of tools/, so a dev script or an action-pattern action.yml — in no image's build context — rebuilt all five images, minted new digests (the builds are not bit-reproducible), and forced a catalog bump + pin convergence. It also red check_catalog_provenance_freshness, a release gate, which diffed the same coarse tools/+lib/ set. Narrow both to the paths the Dockerfiles actually read, and make the go build stages COPY only what they compile so that set is derivable rather than asserted — a cross-package import added later is then a hard build failure, not a silently stale image. test/check_publish_trigger.py derives each image's inputs from the publish matrix's Dockerfiles and fails if the trigger or the gate's diff-set misses one. The two must also agree with each other, so no source change can red the release gate without a rebuild that clears it. Fixes #777 Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Owner
Author
|
claude: Adversarial review round — an independent reviewer (fresh context) was given one brief: find a change to a tool image's real build inputs that this trigger would MISS. It found no live under-trigger (every file the 5 Dockerfiles read is matched), but four ways the enforcement failed open. All four are now fixed, each with a test that fails without the fix:
Suite: 1438 ok, 0 not ok (11 pre-existing integration skips). The trigger↔Dockerfile↔release-gate agreement tests are up to 12. |
TomHennen
force-pushed
the
fix/777-narrow-publish-trigger
branch
from
July 13, 2026 00:20
ac58931 to
c33b536
Compare
TomHennen
marked this pull request as ready for review
July 13, 2026 00:20
TomHennen
temporarily deployed
to
integration-test
July 13, 2026 00:20 — with
GitHub Actions
Inactive
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.
Fixes #777.
The bug
publish tool imagestriggered on all oftools/**, but most files there feed no image. A dev script landing (open_catalog_bump_pr.sh,bump_version_refs.sh, …) rebuilt all 5 images; the builds are not bit-reproducible, so unchanged content still minted new digests → catalog stale → bump PR → pin convergence → merge commit. It fired four needless times cutting v0.4.0.Derivation (from the Dockerfiles, not intuition)
Build context is the repo root. Context reads per image, after this PR:
osvtools/go.mod,tools/go.sum,tools/osv/adapter.sh,tools/osv/render_md.sh,lib/sanitize.sh,lib/sarif_adapter_exit.shwrangle-linttools/go.mod,tools/go.sum,tools/wrangle-lint/(whole dir),lib/sarif_adapter_exit.shattest-toolboxtools/go.mod,tools/go.sum,tools/wrangle-attest/(whole dir)zizmortools/zizmor/requirements.txt,tools/zizmor/adapter.sh,lib/sarif_adapter_exit.shsyfttools/syft/install.sh,tools/syft/adapter.sh,lib/download_verify.shPlus each image's own
Dockerfile(which carries the base-image digest pins).The three Go images previously did
COPY tools/ ./tools/— the whole directory, dev scripts and all — which is why the trigger had to betools/**. They now copy onlygo.mod/go.sumplus the first-party package dir they compile (whole dir, so a future//go:embedtarget is covered). External tools (osv-scanner, ampel, bnd, cosign) build from the module graph alone — verified by a real build of all three images. A cross-package import added later is now a hard build failure, not a silently stale image.The trigger
The enforcement (the part that matters)
test/check_publish_trigger.pyreads every job that callsbuild_and_publish_container.yml, parses each image's Dockerfile, expands every contextCOPY/ADDsource to files, and fails if any is not matched byon.push.paths— GitHub's glob semantics (*stops at/, last match wins,!negates). A Dockerfile cannot grow an input without the trigger growing with it. What it cannot model, it refuses (exit 2): a bind-mounted build context,ONBUILD, wildcard sources, adockerfile:that isn't the matrix path.The release gate had to move too.
check_catalog_provenance_freshness.shdiffed a coarsetools+libset to decide an image is stale. Narrowing only the trigger would have made that worse: a dev-script push would no longer rebuild, yet would still red the gate — with nothing able to clear it. ItsPROVENANCE_DIFF_PATHSis now the same set, and--check-gateholds the two in agreement in both directions (gate ⊇ Dockerfile inputs, so it cannot call a moved image fresh; gate ⊆ trigger, so it cannot red the release with a staleness no push can rebuild away). The two globs are different dialects — git's:(glob) **matches zero directories, GitHub's does not — and the checker models each separately.test/test_publish_trigger_coverage.bats(12 tests) pins all of it, including every red path: each was verified to actually fail by injecting the corresponding disagreement.Adversarial review
An independent reviewer with one brief — find a change to a real build input this trigger would MISS — found no live under-trigger, but four ways the enforcement failed open: a bind mount masked by a stage mount on the same line, a second publishing job, the git-vs-GitHub
**dialect gap, andONBUILD. All four are fixed, each with a test (see the comment below). Those fixes postdate the review and rest on my own tests.Candor
build_and_publish_container.yml(buildx flags, labels). Neither the old nor the new trigger watches it, and the coverage test cannot derive it from a Dockerfile. Say the word and I'll add it topaths:— the cost is a rebuild cycle on any edit to that workflow.tools/osv-scanner.tomlis not an image input (no Dockerfile copies it; it is read from the scanned source tree at run time).build_shell.yml's tool-image cache key still uses the coarsehashFiles('tools/**', 'lib/**', ...). Left alone deliberately: a coarse cache key only costs a cache miss, never staleness.test/image/contract suites pass (14/14); the osv image was run against a fixture and writesoutput.sarifunder the adapter contract../test.sh quick: 1438 ok, 0 not ok (11 skips, all pre-existing integration-tool skips). All 12 new tests run no-skip in the CItestjob.🤖 Generated with Claude Code