feat(release): publish verified GitHub releases - #14
Merged
Conversation
Add the draft-release state machine with the ReleaseReader, AssetReplacer, Publisher, and RefResolver ports, the ghrel, ghup, and gitx adapters, and publish github. The workflow now mints the App token and hands it to the CLI as a redacted secret; the CLI never creates a release, re-drafts one, or deletes an asset.
Round-1 review and conformance fixes: classify an already-public release under --no-undraft and an ambiguous undraft call as indeterminate, print an operator remediation hint for that class, restore VerifyHandoff's error strings after the retry extraction, gate the tag-to-commit binding before the attestation so a bad run cannot leave a stray attestation, correct the documented binary-path exit code, and cover the inherited GH_TOKEN drop.
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.
Implements PR 7 of the
release-cliprogram. The lastactions/github-scriptpublication programs are gone: the CLI now owns draft discovery, tag binding, upload, convergence, and the undraft, while the workflow keeps minting the App token and running the attestation.What lands
internal/stage/pubgh— theReleaseReader,AssetReplacer,Publisher, andRefResolverports (6–9 of the closed budget of 13), the value types, four classified errors, andPublish.internal/adapter/ghrel(go-github),internal/adapter/ghup(gh release upload --clobber),internal/adapter/gitx(git rev-list -n 1).internal/cli—publish github --dist PATH [--no-undraft] [--json]..github/workflows/publish-github-release.yml— three script steps deleted, one CLI step added, plus a cheap early tag-to-commit gate. Inputs, secrets, both outputs, permissions, timeouts, and every SHA pin unchanged.Guarantees
The tag must resolve to the workflow's commit; exactly one release may carry the tag; the release must be a draft to start; existing assets outside the expected set are refused, never deleted; uploads clobber expected names only; assets must converge to the expected count, names, states, and digests within 12 attempts a second apart (24 attempts five seconds apart for draft discovery); the undraft happens last and only when requested. The CLI never creates a release, never re-drafts one, never deletes an asset, and never mints a token — it receives one as a redacted
rel.Secret.Polling is engine-owned: the adapters take single snapshots and the engine owns both budgets through an injected sleep, so the state machine is exhaustively testable and instant.
Verification
mise exec -- moon run root:checkgreen.gh release listshows onlyv0.1.0, no rehearsal tags remain):--no-undraftfound the draft, bound the tag toGITHUB_SHA, uploaded four assets, converged their digests, and left the release a draft;draft:falsewith all four assets accepted;stray.txtmade the next run fail withrelease contains an unexpected asset: stray.txt, and the stray asset was still there afterwards — refuse, never delete;GITHUB_SHAwas refused before any mutation;release state is indeterminate: draft-only publication requested but release … is already public, and the CLI printed the operator hint on stderr.Review fixes in this PR
--no-undraftwas not enforced on the already-published branch. A public release with a matching asset set returned success under a draft-only rehearsal, so the workflow would have gone green while the release was public — the exact thing the flag exists to prevent. It is nowErrIndeterminate, refused before any asset read.Publisher.Publishfailure — which may have applieddraft:false— now carriesErrIndeterminatetoo, matching what the docs promise.actions/attestagain. Folding it into the CLI moved it after the attestation, so a moved or mis-bound tag would have left a stray, unwithdrawable provenance attestation and transparency-log entry before failing.VerifyHandoff's error strings; the original wording is restored with regression tests.RELEASE_GH_PATH/RELEASE_GIT_PATH(1 at first use, not 2), and coveredghup's inherited-GH_TOKENdrop with a test.Recorded follow-up
Conformance considers
ErrAmbiguousReleaseandErrUnexpectedAssetsentinels without production inspectors. Kept deliberately: the plan requires classified errors for these recovery categories, and they are what the operator-facing docs name.