Skip to content

fix(release-provenance): draft the release until its assets are attached - #30

Merged
bdelanghe merged 1 commit into
mainfrom
claude/next-task-d9yzsf
Aug 13, 2026
Merged

fix(release-provenance): draft the release until its assets are attached#30
bdelanghe merged 1 commit into
mainfrom
claude/next-task-d9yzsf

Conversation

@bdelanghe

Copy link
Copy Markdown
Contributor

Fixes #19.

The bug

A published GitHub release is immutable once the repo has immutable releases enabled, so whichever workflow publishes first locks every other one out. release-provenance.yml published the release the moment it created it — so drift-gate v0.2.0 shipped with no binaries: its separate binaries.yml, racing on the same tag, got

HTTP 422: Cannot upload assets to an immutable release.

Both workflows were written create-or-upload to be "order-independent", which is correct for mutable releases and precisely wrong for immutable ones — order-independence is what puts the publish first half the time.

The fix

Create the release as a draft (drafts stay mutable) and publish it in a final step, so it becomes immutable only once it is complete.

Two new inputs for callers that ship extra assets:

  • assets-artifact — name of an artifact from an earlier job in the same run, attached alongside the provenance. This is the one-writer path: the caller builds binaries in a needs: job rather than in a second tag-triggered workflow, so nothing races for the release at all. Fetched with gh run download "$GITHUB_RUN_ID" (a reusable workflow shares its caller's run id), which keeps the gh CLI already in use here and avoids pinning another action. Needs actions: read — declared here, and required on the caller too, since a called workflow only gets the intersection.
  • finalize — pass false to leave the draft for another workflow to publish. Documented as the weaker option: it moves the finish line without ordering the two workflows, so assets-artifact is the recommended path.

Default behaviour is unchanged for the repos that attach nothing extra — they now get draft → publish within the one job.

Tests

Two tests parse the workflow as a fixture (the release-cut.yml allow-list test's shape) and assert creation carries --draft and that publishing comes after the attach — the ordering is the fix, and an ordering is what a later edit undoes without noticing.

Both were mutation-checked: removing --draft, and moving the publish ahead of the attach, each fail the suite. Full suite 20/20 green; mint plan resolves the added intent to 0.5.0 → 0.6.0 (minor).

Follow-up (not in this PR)

drift-gate#5 is the downstream half — apply the pattern there (fold binaries.yml into release.yml as a needs: job feeding assets-artifact) and backfill the v0.2.0 binaries. That needs this merged and a mint tag to pin first.


Generated by Claude Code

A published GitHub release is immutable once the repo has immutable
releases enabled, so whichever workflow publishes first locks every other
one out. release-provenance published the release as soon as it created
it, which is why drift-gate v0.2.0 shipped with no binaries: its separate
binaries.yml, racing on the same tag, got

    HTTP 422: Cannot upload assets to an immutable release.

Create the release as a draft (drafts stay mutable) and publish it in a
final step, so it becomes immutable only once it is complete.

Add two inputs for callers that ship extra assets:

  - assets-artifact: name of an artifact from an earlier job in the same
    run, attached alongside the provenance. This is the one-writer path —
    the caller builds binaries in a needs: job instead of in a second
    tag-triggered workflow, so nothing races for the release. Needs
    actions: read, declared here and required on the caller too, since a
    called workflow only gets the intersection.
  - finalize: pass false to leave the draft for another workflow to
    publish. Documented as the weaker option — it moves the finish line
    without ordering the two workflows.

Tests parse the workflow and assert creation is a draft and publishing
comes after the attach; both fail if the ordering is undone.

Refs bounded-systems/drift-gate#5

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C5vCUb3KDboKFJnFRpTwWg
mkdir -p .mint-assets
# Fail loudly: a silent miss here ships a release MISSING the binaries
# it was asked to attach, which is the failure #19 is about.
gh run download "$GITHUB_RUN_ID" --name "${{ inputs.assets-artifact }}" --dir .mint-assets
# it was asked to attach, which is the failure #19 is about.
gh run download "$GITHUB_RUN_ID" --name "${{ inputs.assets-artifact }}" --dir .mint-assets
if [ -z "$(ls -A .mint-assets)" ]; then
echo "release-provenance: artifact '${{ inputs.assets-artifact }}' is empty" >&2
@bdelanghe
bdelanghe marked this pull request as ready for review August 13, 2026 05:17
@bdelanghe
bdelanghe merged commit 576e904 into main Aug 13, 2026
8 checks passed
@bounded-systems-front-desk bounded-systems-front-desk Bot moved this from Todo to Done in Front Desk Aug 13, 2026
bdelanghe pushed a commit to bounded-systems/drift-gate that referenced this pull request Aug 13, 2026
A published GitHub release is immutable, so whichever workflow publishes it
first locks every other one out. binaries.yml and release.yml were both
triggered by the v* tag, and two tag-triggered workflows are unordered by
construction — so mint's release job published the release before binaries.yml
could attach, and the upload died with

    HTTP 422: Cannot upload assets to an immutable release.

That is how v0.2.0 AND v0.3.0 both shipped with no binaries. Both workflows
were written create-or-upload to be "order-independent", which is correct for
mutable releases and precisely wrong for immutable ones — order-independence is
what puts the publish first half the time.

Fix it with one writer, the path mint's #30 added for exactly this: fold
binaries.yml into release.yml as a `needs:` job that uploads a workflow
artifact, and hand the artifact name to release-provenance via the new
`assets-artifact` input. It downloads it from the same run, attaches it next to
the provenance, and publishes once. Nothing races for the release.

Pin notes, since the pair looks mismatched on purpose:

  - the workflow pin moves to mint main @ #30 (576e904), the commit carrying
    the draft-until-attached fix. No mint tag contains it yet — v0.5.0 predates
    it — so a SHA is the only way to consume it, which is what the org
    convention asks for anyway.
  - `ref` stays at v0.3.1. It selects the mint code that RUNS (`mint attest`),
    and release-provenance.yml was byte-identical from v0.3.1 until #30, so
    this change carries the ordering fix and nothing else. Moving `ref` would
    put drift-gate's signed provenance onto three minors of unrelated mint
    changes. version.yml is on v0.3.1 too; bump both when mint tags.

The binaries job keeps the workflow's default contents: read; only the release
job is granted contents/id-token write plus `actions: read`, which a caller has
to declare itself because a called workflow only gets the intersection.

release_workflow_test.ts guards the shape rather than a run, so a regression
fails on the PR instead of six weeks later on a tag that ships an empty and
now-immutable release. It asserts the `needs:`, that the uploaded artifact name
matches the one requested, that `actions: read` is granted, that no other
workflow writes to the release at all, and that the mint pin has not regressed
below the fix.

That last one is not hypothetical. Dependabot converges `uses:` pins onto the
referenced repo's LATEST TAG (.github/dependabot.yml spells this out), mint's
latest tag is v0.5.0, and v0.5.0 predates #30 and does not define
`assets-artifact` — so next Monday's grouped "actions" bump is a live path back
to this bug. The guard accepts a 40-hex SHA or a tag from v0.6.0 up, so it
blocks the revert without standing in the way of the real re-pin later.

Incidental: the v* trigger now also builds binaries for prerelease tags, which
binaries.yml's stricter v[0-9]+.[0-9]+.[0-9]+ filter excluded. release.yml
already ran provenance on v*, so this aligns them.

Does NOT backfill the v0.2.0/v0.3.0 binaries the issue also asks for: both
releases are already published and immutable, so nothing can be attached to
them. See the PR for the options — it needs a maintainer decision.

Refs bounded-systems/mint#30.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014P74tnmfWfpq5PaZ67BKsh
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

release: mint's release-provenance finalizes an immutable Release, blocking a separate binaries-attach job (422)

3 participants