Repository navigation
Qualify native releases from the tag and build promotion evidence - #1150
Merged
Merged
Conversation
Qualifiers now admit only when dispatched from the final release tag. The release part of the qualification subject is derived from that tag, its commit and the requested index, which must be the index GHCR's exact version tag names. The subject file keeps only the pinned MySQL and QEMU images, so a release no longer needs a hand-edited commit, the feat/server-native32-qualification-runner branch or a merge to main. The verifier accepts only the v1 manifest shape again, where both qualifier runner commits equal the release source, while still requiring the full v2 receipt: tag runner ref, subject blob read at the release commit, owned resource scope, local image identity and every dependency image. The runner-branch v2 manifest, its frozen-delta and ancestry reads and the transport's compare shapes for them are removed. scripts/ci/native_alias_manifest.py builds the manifest from the release, corpus and Compose run IDs by reading the provider API. It selects each receipt artifact by exact name and refuses ambiguity. Promotion and GitHub Release creation verify it as before. Refs #1123. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
Author
|
Owner review (server): the design is accepted.
Requesting a Security review because this changes promotion verification. It merges only after that and green CI. The first real end-to-end use will be 3.6.0. |
Member
Author
|
Security review: PASS (head Deriving the release subject from the tag makes the tag the trust anchor. I checked it holds end to end:
Optional: have |
rmcdaniel
marked this pull request as ready for review
November 11, 2026 23:40
rmcdaniel
enabled auto-merge
November 11, 2026 23:41
|
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.



Closes #1123 once the next native release is promoted this way.
New flow
A release is: tag,
native-release.yml, both qualifiers dispatched from the tag, a mechanically built evidence manifest, promotion, GitHub Release. The short checklist is indocs/native-release-promotion.mdunder "Release checklist":native-release.yml --ref "$tag".native-image-parity.ymlandpublished-compose.ymlwith--ref "$tag"and the published index digest.python3 -I scripts/ci/native_alias_manifest.py --tag … --release-run … --corpus-run … --compose-run …native-alias-promotion.yml --ref "$tag"with that manifest.native-github-release.yml --ref mainwith the same manifest.You no longer need a subject commit, the
feat/server-native32-qualification-runnerbranch, a merge to main or a hand-written evidence file.Design
admitnow runs only for aworkflow_dispatchfrom a final tag (refs/tags/<X.Y.Z>, workflow ref at that tag, canonicalghcr.io/durable-workflow/server@sha256:input). It derives the release subject fromGITHUB_REF_NAME,GITHUB_SHAand the requested index. It also requires GHCR's exact version tag to name that index, which replaces the old "image equals the committed subject's index" check.scripts/ci/native-qualification-subject.jsonkeeps only the pinned MySQL and QEMU images (schemanative-qualification-subject.v2), so no release needs to edit it. A final-tag dispatch must admit. Other dispatches and PRs still produce diagnostic receipts.native-qualification-receipt.v2. Itsrunner_refis nowrefs/tags/<tag>andrunner_commitis the release source. Itssubject_blobis the Git blob of the dependency file at the tag. The weaker v1 receipt path (no dependency, scope or local-image binding) is gone.native-alias-promotion-evidence.v1shape again, the one the runbook already described: norunnerblock and bothrunner_commits equal to the release source. The runner and the dependency subject are derived from the release (release_subject). The subject is read at the release commit through the contents API, its blob hash is checked, and it is read again beforeverifyreturns.scripts/ci/native_alias_manifest.pyreads the publication run and both qualifier runs, which must all be dispatched from the tag at one source commit. It takes the index from the trusted qualifier run titles, which must agree. It reads the current-attempt job IDs with the verifier's ownjobs()and required-step set. It selects each receipt artifact by exact name and refuses zero or several matches. Its output goes throughmanifest(). It only selects. Promotion still proves everything.RUNNER_REF,RUNNER_PATHS, the v2 manifest,runner_selection/runner_subject(frozen-delta compare and runner-on-main reads) and the transport's two compare shapes those reads used. Only<sha>...mainis still allowed. I dropped the runner path rather than keeping both because it only made sense when tag workflows could not qualify. Promotion runs from the tag's own code, so 3.5.0 and earlier keep their v2 verifier. The only main-side consumer isnative-github-release.yml, and every release up to 3.5.0 already has its GitHub Release.Preserved checks
Promotion and Release creation still verify all of the following:
runner_commitequal to the release source, and workflow blobs read at that commit.workflow_dispatch, head SHA, head branch equal to the tag, exact current attempt, and completed success. Titles bind the canonical image input.receipt.json, and the SHA-256 is checked before parsing.verifyreturns. The no-prior-Release check still runs before alias writes.The workflows are unchanged except for one comment line: same permissions, same pinned actions, and no new secrets or inputs. I updated the
check-docker-workflow-isolation.pyreviewed-byte hashes for the three changed modules.Tests
All of these ran in a bwrap sandbox with no network, a cleared env, no
/run,/varor/home, and a tripwire over/usr/bin/docker. The tripwire was never hit.python3 -m unittest discover -s tests/Unit/Support -p native_qualification_test.py: 43 OKpython3 -m unittest discover -s tests/Unit/Support -p 'native_alias_*test.py': 28 OK (8 new builder tests)python3 -m unittest discover -s tests/Unit/Support -p docker_workflow_isolation_policy_test.py: 84 OKpython3 scripts/ci/check-docker-workflow-isolation.py: passedbash scripts/ci/check-native-release-tags.sh: passedNew tests cover admission from the tag, exact-tag index mismatch, runner-branch, candidate, fork and push contexts, a local and committed subject mismatch, legacy v1 subject, receipt and v2-manifest refusals, a subject change during verification, the exact-tag transport read, and builder selection and refusals.
Live read-only check (
gh apionly, no Docker): on the real 3.5.0 runs the builder refuses withselection_corpus_run, because those qualifiers ran from the runner branch. With only that branch identity rewritten to the tag, it selected exactly the five artifact IDs that promotion run 43117894863 verified, in the same order.Review questions
workflow_dispatchof either qualifier from a final tag now fails if the image input is missing or not a canonical digest, where it used to produce a diagnostic run.🤖 Generated with Claude Code