Skip to content

Release workflow: versions.json, label categories, preamble insertion, tags and attested image push #60

Closed

Description

@Fly-Playgroud

Parent

Spec: #33 (Baseline release). Section numbers refer to its Implementation Decisions.

What to build

Cutting a release is one workflow_dispatch: the workflow validates, records the pins in versions.json, tags, publishes the GitHub Release with the pins line, the Release preamble and generated notes, and pushes the multi-arch image with provenance and SBOM attestations. Spec sections 14 and 15; ADR-0008; ticket #19, #31, #32.

Acceptance criteria

  • workflow_dispatch with a version input (vX.Y.Z or vX.Y.Z-rc.N) and a dry-run input; validates semver, greater than the last tag, required checks green on main's head, the pins check and protocol zero diff.
  • Appends one row (wand version, Target Chrome, Protocol roll, Companion Chromium, read through the pins printer) to the root versions.json, commits on main through the App token, tags that commit, and creates the Release: title = tag, first line the three pins, then docs/releases/<tag>.md when present at the tagged commit (with {{rc}} and {{date}} placeholders substituted), then GitHub's label-grouped notes from .github/release.yml (roll in its own section, plus bug, enhancement, documentation and dependencies categories); -rc.N marked pre-release.
  • Builds and pushes ghcr.io/headlesslab/wand as one multi-arch manifest per tag (vX.Y.Z or vX.Y.Z-rc.N, plus latest for a non-rc) and :dev; records a build-provenance attestation and an SPDX SBOM attestation for every pushed manifest digest with push-to-registry; gh attestation verify oci://ghcr.io/headlesslab/wand:<tag> -R headlesslab/wand succeeds, demonstrated on a throwaway image tag (deleted afterwards; git tags are never created in a dry run).
  • versions.json seeded as an empty array; the release-blocking label exists; the bad-release procedure (retract directive, fixed patch, Release marked withdrawn) is documented.
  • Merging a Roll never tags; no version constant exists in code.

Blocked by

Activity

  1. self-assigned this
    on Sep 7, 2026
  2. Fly-Playgroud commented on Sep 7, 2026

    @Fly-Playgroud
    ContributorAuthor

    The last half of criterion 3, done now that the workflow is on main: run 34127182928, -f version=v0.1.0-rc.1 -f dry-run=true, green in four jobs (Validate, Push linux/amd64, Push linux/arm64, Publish).

    Both architectures built natively and pushed under no tag; the two digests became one manifest per tag; each manifest took its two attestations with push-to-registry, and each was verified before the run went on. Read back from outside the run, the two published manifests carry exactly what the criterion asks for:

    $ gh api repos/headlesslab/wand/attestations/sha256:4eec8fd5… --jq '…'
    https://spdx.dev/Document/v2.3   subject=ghcr.io/headlesslab/wand@sha256:4eec8fd56f7c…
    https://slsa.dev/provenance/v1   subject=ghcr.io/headlesslab/wand@sha256:4eec8fd56f7c…
    
    $ gh api repos/headlesslab/wand/attestations/sha256:85c415ec… --jq '…'      # :dev
    https://spdx.dev/Document/v2.3   subject=ghcr.io/headlesslab/wand@sha256:85c415ecb43e…
    https://slsa.dev/provenance/v1   subject=ghcr.io/headlesslab/wand@sha256:85c415ecb43e…
    

    Each is signed by the Public Good Sigstore instance and in Rekor (log indices 2751062361, 2751062721, 2751064265, …).

    The throwaway tags are gone, and GITHUB_TOKEN turned out to be allowed to delete a package version after all — the warning path the workflow keeps for the case it is not never ran:

    dry run: ghcr.io/headlesslab/wand:dry-run-34127182928-v0.1.0-rc.1 was pushed, attested and verified, and is deleted below.
    No commit, no tag and no Release were created.
    deleted ghcr.io/headlesslab/wand:dry-run-34127182928-v0.1.0-rc.1
    deleted ghcr.io/headlesslab/wand:dry-run-34127182928-dev
    

    And nothing was left behind, checked from here afterwards: git ls-remote --tags origin is empty, gh release list is empty, and main is still e7d4a99, the merge commit.

    The first dispatch was refused, correctly

    The dispatch two minutes after the merge stopped at the first job: the Gate's own push run on e7d4a99 had barely started, so ten of the fourteen required checks were not green yet and nothing was built or pushed.

    ##[error]e7d4a99… is not green: Generate (zero diff), Image linux/amd64, Image linux/arm64, Tier 1 darwin/arm64 (Go stable), …
    

    Then Tier 1 darwin/arm64 (Go stable) went red on that push run — TestScrollScreenshot at 61.79 s against about two seconds normally, the flake of #99, green when the job alone was re-run. So the first real use of this gate found the thing it exists to find and also the thing #99 is about; the cost of those flakes is now a blocked release rather than a red run, which is noted there.

    What is left for a human

    Making ghcr.io/headlesslab/wand public and linking it to the repository, in the package's settings. GHCR created it private on this first push and exposes no REST endpoint for the setting, so the notes carry it under "What stays human" rather than the workflow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

ready-for-agentFully specified, ready for an AFK agent

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions