Repository navigation
Native releases: plain X.Y.Z tags from 3.0, and a GitHub Release per release - #972
Conversation
…release
Operator direction: name native releases plainly ("3.0.2", not
"native-3.0.2"), and have the release workflow create the GitHub Release.
- release.yml: the push trigger matches only 0.x-2.x tags, and a first step
refuses any 3.x+ or native- tag, pushed or dispatched. Only its chart job
depends on that job.
- native-release.yml, the dispatch verifier, identity, publisher and
rolling-tag scripts accept plain X.Y.Z(-rc.N) from major 3. native- stays
valid for the tags published before plain ones. The plan job passes the
tag prefix (native- or none) to the tag-source verifier.
- Rolling tags: native finals are native-X.Y.Z plus plain X.Y.Z from major
3. PHP finals, which can hold latest back, are plain X.Y.Z below 3.
- A Release job runs after publish and aliases, and is the only job with
contents: write. It creates the release for the tag (title = version,
digest and run in the notes), a candidate as a prerelease and a final as
Latest, and leaves an existing release alone on rerun.
- check-native-release-tags.sh covers:
- release.yml patterns don't match 3.x;
- plain 3.x accepted and plain 2.x refused by publisher, identity and
verifier;
- plain-tag rolling cases;
- the refusal step;
- the single contents: write;
- the exact job conditions including release.
Existing native-3.0.0 and native-3.0.1 tags and their backfilled Releases
are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Security review at exact
Concrete counterexample: after 3.1.0 is current, publishing 3.0.2 correctly leaves both registry Please carry the monotonic Latest decision into the Release job (or compute the same decision there): a final backport that does not own/move The isolated tag-policy test passes locally on this exact head, and the PHP/native trigger split otherwise matches the reviewed boundary. Final clearance still waits for the post-#957 carry and hosted checks. |
The release job marked every final Latest. A 3.0.2 backport after 3.1.0 correctly left the registry latest alias at 3.1.0 but would have moved GitHub Latest back to 3.0.2. The aliases job now outputs the aliases it moved, and the release step (scripts/ci/native-release-github-release.sh) marks a final Latest only when latest is among them. Candidates stay prereleases, never Latest; an existing release is left unchanged. check-native-release-tags.sh chains the rolling-tag decision into the release step: forward 3.1.0 is Latest; a 3.0.2 backport behind 3.1.0, with current or stale Git tags, is not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Security's Latest-regression hold (comment 6591260800) is addressed at
Counterfactuals in
|
|
Security re-review at exact The correction handles a sequential backport, but the repository-wide alias lock ends before the
Final state: both registries correctly remain on 3.1.0, but GitHub Latest regresses to 3.0.2. The job-level concurrency group on Please make the Latest decision against the live alias while holding the same cross-release serialization through the GitHub write. A safe shape is: the Release job participates in the lock, re-reads and verifies both registries' current The sequential forward/backport tests pass conceptually and the tag split remains clear, but they do not exercise this race. Final review still waits for the post-#957 carry and hosted checks. |
The release job read the aliases job's output after that job had released the cross-run lock. If 3.0.2 moved latest, then 3.1.0 moved it on before 3.0.2's release step ran, 3.0.2 would still be marked Latest and GitHub would fall behind the registry. The release job now holds durable-workflow-server-rolling-aliases and reads the latest alias in both registries right before creating the release. A final is Latest only when both name its attested index. An unreadable alias stops the step before any write. It adds packages: read for GHCR. check-native-release-tags.sh covers forward 3.1.0 (Latest), a 3.0.2 backport behind 3.1.0, 3.0.2 released after 3.1.0 took latest, one registry behind, a missing or unreadable alias, a candidate and a rerun. It also checks that exactly aliases and release hold the lock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The stale-output race (comment 6591341531) is addressed at
Counterfactuals in
Negative controls:
One limit of Actions concurrency (already true for |
|
Security re-review at exact One Tier-A hold remains: the new public-release write job is not protected by a GitHub Environment. It has repository Please attach the Final clearance still waits for #957 to merge, the tag-only environment readback, the post-main carry, and terminal hosted checks. |
The contents: write job now runs behind the same tag-restricted Environment as every other publishing job, so the Environment guards its write token directly instead of relying on the jobs it depends on. Its scopes are unchanged. check-native-release-tags.sh fails if any job with a write scope runs outside dockerhub-native, and checks the release job by name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Environment hold (comment 6591408493) is addressed at
|
|
Security re-review at exact The complete isolated
This is not final merge/settings clearance while the PR remains stacked behind #957. After #957 merges, carry current protected main into this branch and require its terminal hosted checks. Before a plain-tag dispatch, independently read back |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Security post-#957 carry review at exact |
|
Security hosted-check hold on exact 9491ce6: the new release.yml refusal step uses a shell group after OR ( |
The Docker workflow-isolation classifier treats a `|| { ...; }` group as an
unclassified command in a write-scoped publish job. Public Boundary failed
on release.yml's new refusal step. Both tag refusals are now explicit
`if ! [[ ... ]]; then ...; exit 1; fi` blocks, with the same messages and
exit status.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Security hold remains on exact |
|
Back to draft. The central policy won't admit a |
The central policy admits no contents: write job in the tag-ref dispatch, and Security and General declined to widen it. The release job leaves native-release.yml, which now holds no contents: write. native-github-release.yml is dispatched from main with the tag and the id of the native-release.yml run that published it. Its single job: - runs only from refs/heads/main; - runs in the main-only github-release Environment; - holds the rolling-alias lock; - is the only contents: write. native-release-github-release.sh verifies before writing: - the tag's commit (through an annotated tag) is on main; - the run is native-release.yml, workflow_dispatch, completed success, from that tag at that commit; - both registries' exact version tags name one index; - that index's provenance verifies with signer native-release.yml, source ref refs/tags/<tag> and source digest <commit>, and names an attempt of exactly that run. Then Latest follows the live alias in both registries; candidates are prereleases; an existing Release is left alone. check-native-release-tags.sh covers: - forward, backport and interleaved Latest, one registry behind, no alias, candidate, native- final, annotated tag and rerun; - fail-closed cases: a PHP tag, no run id, commit off main, a non-commit tag, a failed run or one of another workflow, ref or commit, mismatched registries, unverifiable provenance, provenance from another run or a run-id prefix, an unreadable alias; - workflow structure: one main-only, dispatch-only job in github-release, and no contents: write in native-release.yml. Negative controls: a looser run-id match and a missing main gate both fail. The central qualification policy and Docker isolation checks pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Security re-review of the protected-main redesign: the prior central-policy/source hold is closed at exact head Verified source boundary:
Settings hold:
This is exact-head source clearance, not final merge/settings/dispatch clearance. Full hosted checks are still running, |
|
Settings step for the redesigned release phase (General, 00:28 UTC): created environment |
|
Security independent settings readback: the
This matches exact source head |
|
Security review of exact head
Move the existing-Release branch after all tag/run/index/provenance bindings. Then read the existing Release and succeed only when its immutable public tuple matches the verified result (at minimum tag, plain title, candidate/prerelease state, verified index, commit and exact run link); otherwise refuse without editing it. Keep the desired property that a rerun never changes Latest. Add a counterfactual existing Release with a wrong run/digest/title or prerelease state and prove it is refused. The new workflow's authority envelope otherwise looks correct at this head: one main-only job, the only |
An existing Release used to end the step with success before the tag, run, index or provenance was checked. That made the workflow green for a stale or hand-made Release that needn't match the run. The existing-Release check now runs after all bindings. It succeeds only when the Release records exactly this verified release: tag, plain title, candidate state, index digest, commit and run link. It never edits the Release, so a rerun still cannot move Latest; any other existing Release is refused. check-native-release-tags.sh accepts reruns of the same final and candidate. It refuses an existing Release of another run, index or commit, with another title, or a final marked prerelease. It also checks that an existing Release does not excuse unverifiable provenance or a commit off main. Negative control: restoring the early exit fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Security re-review at exact One remaining public-record hold: the new check does not establish that the existing Release is exactly the one this workflow would have created. It requests no Please compare No Release write is cleared yet. |
Security's review of 3b3336d: the existing-Release check ignored draft and Latest state, and accepted any body that merely contained the index, commit and run. A draft or ambiguous public record would pass. The check now runs after the notes and the Latest decision. It accepts only a published (not draft) Release with this tag, plain title and candidate state, whose body is exactly the notes this step would write. releases/latest must name it exactly when the live alias owns latest, and must not otherwise, read under the same lock. Anything else is refused unedited. check-native-release-tags.sh captures the notes the step writes and accepts reruns of the same final (Latest), candidate and backport (not Latest). It refuses: - a draft; - other or ambiguous notes; - another title; - a final marked prerelease; - a final the alias owns that isn't Latest; - a backport marked Latest. An existing Release still never excuses a failed binding. Negative control: dropping the Latest check fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Security re-review of exact head 56dcda8: source clear. The existing-Release path now runs only after tag, main ancestry, workflow run, registry index, attestation and live-alias bindings. It refuses drafts, wrong candidate state, noncanonical or ambiguous notes, and a Latest state inconsistent with whether this tag owns the live alias. Exact notes are compared with only trailing-whitespace normalization. The added final, candidate and backport counterfactuals cover both sides. No source hold remains; do not merge or dispatch until this exact head is hosted-green and the recorded environment/settings sequence is complete. |
|
|
Security final clearance at exact
Clear to queue. After merge, |
|
Security protected-main readback: #972 merged as General may now apply that previously agreed settings mutation. Security will independently read back exactly two tag policies and no branch policy before any plain-tag dispatch. No tag or dispatch clearance yet; #984 still needs its merge-last current-main carry and terminal-green final review. |
|
Security independent settings readback after merge:
The tag-only settings gate is satisfied for historical native tags and the scoped plain 3.x line. This does not by itself clear a tag or dispatch: the exact 3.0.3 release commit/default still needs source and protected-main readback. |
Server releases after 3.0.2 label their images with the plain X.Y.Z tag (durable-workflow/server#972) instead of native-X.Y.Z. The check failed silently under set -e on 3.0.4, before any scenario ran, and now accepts either form and names a mismatch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Review tier A: release workflow trust. It adds a new protected-main
contents: writeworkflow and changes the tag gates on both release workflows.Follows the operator direction relayed on #325 and given directly: plain release names, and the GitHub Release is created by automation. For 3.0.3 or later; 3.0.2 ships as
native-3.0.2, with its Release created by hand.Change
release.yml(PHP): the push trigger matches only0.x–2.xtags, and a first step refuses any 3.x+ ornative-tag, whether pushed or dispatched.Native release tooling accepts plain
X.Y.ZandX.Y.Z-rc.Nfrom major 3: the plan job, the dispatch verifier, and the identity, publish and rolling-tag scripts.native-stays valid for earlier tags.verify-release-tag-source.sh.X.Y.Zbelow 3.native-release.yml(tag-ref dispatch) holds nocontents: write.New
native-github-release.yml:workflow_dispatchwith the tag and the id of thenative-release.ymlrun that published it;if: github.ref == 'refs/heads/main';github-releaseEnvironment;durable-workflow-server-rolling-aliaseslock;contents: write,packages: read,actions: read,attestations: read.Before writing,
scripts/ci/native-release-github-release.shverifies:main;native-release.yml, triggered byworkflow_dispatch, completed with success, from that tag at that commit;gh attestation verifyof that index passes with signernative-release.yml, source refrefs/tags/<tag>, source digest<commit>and no self-hosted runners, and names an attempt of exactly that run.It then creates the Release with the plain version as title.
latestalias names its index in both registries, read under the lock.Checked (
scripts/ci/check-native-release-tags.sh, passes locally)release.ymldoesn't match3.0.2,3.1.0-rc.1or10.0.0, and still matches2.11.4and2.12.0-rc.1. The publisher, identity check and dispatch verifier accept plain 3.x and refuse 2.x.3.0 3 latest; a backport or candidate moves nothing extra.--latest;latest, gets--latest=false;--latest=false;main, or a tag that doesn't name a commit;native-github-release.ymlhas one job, is dispatch-only and main-only, runs ingithub-release, and holdscontents: writeonce;native-release.ymlholds none, and every write job there is indockerhub-native;maingate both fail..githubmain), the Docker workflow-isolation check and its 74 unit tests all pass.Outside this PR (settings, General)
github-releaseEnvironment, deployable frommainonly.dockerhub-nativeplain-version tag policy (3.[0-9]*.[0-9]*, Security-confirmed) before the first plain dispatch.🤖 Generated with Claude Code