Skip to content

mif-docs: Cut an Attested Release

This runbook is the tactical procedure for cutting an attested v-release of the mif-docs plugin from the modeled-information-format/mif-docs-plugin repo. A release is “attested” when .github/workflows/release.yml builds a reproducible git archive tarball, generates SLSA build provenance, and fail-closed verifies that provenance with gh attestation verify --signer-workflow before uploading mif-docs-plugin-<tag>.tar.gz. It covers the full path from a green main to a marketplace pin update.

It does not cover authoring docs or changing gates — for that, see the attested-delivery decision record (adr-0003-attested-delivery) and the validate/author how-to (how-to-validate-and-author). This runbook assumes the gates are already wired and you simply need to ship.

Confirm these before you start; sorting out access mid-cut wastes a release window.

  • gh CLI authenticated as a user account (a PAT, not a bot) with write access to modeled-information-format/mif-docs-plugin. The human/PAT identity is load-bearing in section 5 — a bot-created release does not fire the release: published event that triggers the workflow.
  • Push access to main (for the version bump) and permission to create releases.
  • Local checkout of mif-docs-plugin on an up-to-date main.
  • A workstation with gh attestation verify available (gh 2.40+).

3. Pre-flight: confirm CI is green and bump the version

Section titled “3. Pre-flight: confirm CI is green and bump the version”
  1. Confirm CI green on main. Do not cut from a red tree.

    Terminal window
    gh run list --repo modeled-information-format/mif-docs-plugin \
    --branch main --workflow ci.yml --limit 1

    Expected: the latest ci.yml run is completed / success (pin-check + actionlint, validate, adr-smadr all green). If it is failing, stop and fix CI first — an attested release of a broken tree is still broken.

  2. Bump the plugin version. Edit plugin.json so version is the new release (e.g. 0.1.00.2.0), commit on a branch, and merge to main the normal way. The tag you cut in section 5 must match this version.

    Terminal window
    gh release list --repo modeled-information-format/mif-docs-plugin --limit 3

    Expected: the new version is not already an existing release tag. Re-using a tag will not re-attest a prior artifact — pick the next unused vX.Y.Z.

Before cutting anything immutable, prove the pipeline is green end to end. workflow_dispatch runs release.yml in dry-run mode — it builds, attests, and verifies but does not upload.

Terminal window
gh workflow run release.yml --repo modeled-information-format/mif-docs-plugin --ref main
gh run watch --repo modeled-information-format/mif-docs-plugin \
"$(gh run list --repo modeled-information-format/mif-docs-plugin \
--workflow release.yml --limit 1 --json databaseId -q '.[0].databaseId')"

Expected result: the run reaches success with the build → attest → verify steps all green. If verify fails here, the signer-workflow identity or the attestation is wrong — fix it now, while nothing has been published. Do not proceed to the real cut until the dry-run is clean.

5. Cut the release (this triggers the attested upload)

Section titled “5. Cut the release (this triggers the attested upload)”

Create the GitHub Release at the target commit as a user/PAT, not a bot. The release: published event is what triggers release.yml to run for real: gate → reproducible tarball → SLSA attest → fail-closed verify → upload mif-docs-plugin-<tag>.tar.gz.

Terminal window
# Replace v0.2.0 with your bumped version; --target pins the exact commit.
gh release create v0.2.0 \
--repo modeled-information-format/mif-docs-plugin \
--target main \
--title "mif-docs v0.2.0" \
--notes "Attested release. Artifact verified fail-closed before upload."

Expected result: the publish triggers a release.yml run on the release event. Watch it to completion:

Terminal window
gh run watch --repo modeled-information-format/mif-docs-plugin \
"$(gh run list --repo modeled-information-format/mif-docs-plugin \
--workflow release.yml --event release --limit 1 \
--json databaseId -q '.[0].databaseId')"

When it is green, the release has mif-docs-plugin-v0.2.0.tar.gz attached. The same run also tags mif-docs--v0.2.0 at this commit (read from plugin.json’s name/version) — that is the tag shape Claude Code’s dependency resolver looks for when another plugin declares a semver range on mif-docs (e.g. "version": "^0.2.0"), rather than the bare v0.2.0 tag above.

Independently verify the uploaded artifact’s attestation — do not trust the run’s own green check alone.

Terminal window
gh release download v0.2.0 \
--repo modeled-information-format/mif-docs-plugin \
--pattern 'mif-docs-plugin-v0.2.0.tar.gz'
gh attestation verify mif-docs-plugin-v0.2.0.tar.gz \
--repo modeled-information-format/mif-docs-plugin \
--signer-workflow modeled-information-format/mif-docs-plugin/.github/workflows/release.yml

Also confirm the dependency-resolution alias tag landed:

Terminal window
git ls-remote --tags https://github.com/modeled-information-format/mif-docs-plugin.git \
| grep 'refs/tags/mif-docs--v0.2.0'

Expected result for the alias-tag check: a line containing refs/tags/mif-docs--v0.2.0. Empty output means the tagging step in release.yml failed or didn’t run — check the run’s logs.

Expected result: gh attestation verify prints a success line confirming the provenance was issued by the release.yml signer workflow for this repo. A non-zero exit means the artifact is not trustworthy — go to section 8.

7. Marketplace registration — automatic, verify it actually landed

Section titled “7. Marketplace registration — automatic, verify it actually landed”

This step is automated as of the six-app / plugin-catalog-update-hub rollout — do not open the marketplace PR by hand. release.yml’s notify-catalog-hub job (needs attest-release, runs only on a real release event, never on the section-4 dry-run) fires a plugin-released repository_dispatch at modeled-information-format/.github, naming claude-code-plugins as the marketplace to re-check. The central plugin-catalog-update-hub workflow there re-pins mif-docs’s marketplace entry to the new attested release (tag + commit sha, per the org’s sha-pin policy) and opens a zero-touch, pre-approved, auto-merge PR — the same PR shape git log --grep 're-pin mif-docs' in claude-code-plugins shows for every prior release.

Verify it actually happened rather than assuming the dispatch landed:

Terminal window
gh pr list --repo modeled-information-format/claude-code-plugins \
--search "re-pin mif-docs in:title" --state all --limit 3 \
--json number,title,state,autoMergeRequest

Expected result: a chore(catalog): re-pin mif-docs to v0.2.0-shaped PR, either already MERGED (auto-merge cleared it) or OPEN with autoMergeRequest non-null in the JSON output and its checks running. If a check on that PR fails for a reason unrelated to the re-pin itself (e.g. a pre-existing dependency vulnerability in that repo’s own site), fix it there — auto-merge will not clear a red required check on its own, and the PR will sit stuck indefinitely without the failure being obviously connected to this release. Also note that pushing a fix commit to the auto-generated branch dismisses the hub’s own pre-approval (standard branch-protection behavior on new commits) — merge with --admin rather than waiting for a fresh approval that has to come from a human anyway.

Fallback — only if 20+ minutes pass with no PR appearing at all (the dispatch failed, or the hub run itself errored): trigger it by hand —

Terminal window
gh workflow run plugin-catalog-update-hub.yml \
--repo modeled-information-format/.github --field repo=modeled-information-format/claude-code-plugins

— or, as a last resort, edit the marketplace entry for mif-docs directly (pin both the tag and its commit sha — git rev-parse v0.2.0 gets the sha) and open the PR by hand. catalog-admission re-verifies the release attestation fail-closed either way; once merged, claude plugin install mif-docs@modeled-information-format resolves to the new version.

Symptom: a tag was published but no artifact appeared. The attestation step failed — the verify gate is fail-closed, so a failed attest/verify uploads nothing. The published tag with an empty release is the tell.

Terminal window
gh run list --repo modeled-information-format/mif-docs-plugin \
--workflow release.yml --event release --limit 1
gh run view --repo modeled-information-format/mif-docs-plugin \
"$(gh run list --repo modeled-information-format/mif-docs-plugin \
--workflow release.yml --event release --limit 1 \
--json databaseId -q '.[0].databaseId')" --log-failed

Read the failed step: a red verify step means the signer-workflow path or the attestation did not match; a red attest step means provenance generation failed.

Rollback. A published release with no verified artifact is not a usable release — retract it rather than leaving a half-cut tag:

Terminal window
gh release delete v0.2.0 --repo modeled-information-format/mif-docs-plugin --cleanup-tag

Then fix the root cause, re-run the section 4 dry-run until green, and re-cut from section 5. Do not update the marketplace pin (section 7) until section 6 verification passes from a workstation — an unverified artifact must never be the pinned target.