# Changelog History before 0.5.0 lives in git and in [drill/RUNS.md](drill/RUNS.md), which records not just what changed but what each drill run proved. ## Unreleased ### Added - **Merging the release PR IS the release — and the release re-arms main itself** (#96) — the 0.7.0 ceremony ended in an absence: the release PR merged with four approvals and nothing happened, correctly, because publishing hung off a separate, manual, silent-when-forgotten tag push — a failure shape with no error and no red X. The ship decision already lives in the release PR (the one PR whose whole diff is "the version leaves `-dev`"), so `release.yml` now fires on pushes to main (fork-sourced ceremony PRs get a read-only token on `pull_request` events), reading the transition from the push itself: `event.before` to the pushed head. A decide step answers four states — release-flow *work* merged under the `release` label (`-dev` endstates, the post-release window) no-ops green with a NOTICE; the two genuinely ambiguous bare states refuse loudly; a true transition then requires a merged, `release`-labeled PR behind the commit (read via the API — the label is the operator's declared intent) before anything is created. Then, in the same job, it tags the merge commit via the API, publishes — and bumps main to `X.Y.(Z+1)-dev` itself, direct push with a loud open-a-PR fallback, so no follow-up bump PR exists on the paved road. Same-job on purpose: a `GITHUB_TOKEN`-created tag triggers no workflows, which is also what makes double-publish impossible. The tag-push path stays unchanged as the documented manual fallback and backfill (it shipped 0.7.0 itself). `test/release.sh` grep-pins the gate, every decide verdict, the single `on.push` key, and the same-job tag+publish+re-arm in the same daemon-free, fail-closed style. ### Fixed - **The release ceremony re-arms `CHANGELOG.md`, and CI refuses to let `main` sit disarmed** (#108) — the ceremony stamps `## Unreleased` into `## X.Y.Z — DATE` by hand, and nothing put the heading back, so `main` sat with no `## Unreleased` from the release until the next PR that happened to re-create one. A PR authored *before* the release wrote its entry under `## Unreleased`; with that heading gone, git lands the entry under whatever now occupies the position — **the section that just shipped** — and it merges **cleanly**. No conflict, no error, no red X: the one signal an author would trust is absent exactly when the outcome is wrong, and the changelog credits a released version with a change it does not contain until a human reads the file. Confirmed in the sibling repo (heavy-duty/rig#66); box has not drifted yet, and the reason is luck rather than design — 0.6.0's ceremony (`77599ab`) added its heading *without* removing `## Unreleased`, so main was never disarmed, while 0.7.0 did disarm it and left a window that nothing happened to cross. Two halves land together. The ceremony step in `CONTRIBUTING.md` is now explicitly **two edits**: stamp, then put an empty `## Unreleased` back above the section just stamped — it belongs there and not in `release.yml`, which only ever touches `VERSION`. And `.github/scripts/changelog-armed.sh` enforces it in CI, keyed on `VERSION` because the two states are genuinely different: a `-dev` tree must carry `## Unreleased` on top, a bare-`VERSION` tree (the ceremony PR, and the merge that publishes it) may carry either that or its own stamped section. The keying is the whole design and not an over-complication — box previously had **no** top-section guard at all, and the obvious one, an unconditional `## Unreleased` requirement, is false by construction on the ceremony PR's own tree, which is why rig#44 and heavy-duty/cast#108 both had to revert it. So a forgotten re-arm does not block the release; it turns `main` red on the very next push, the automatic `-dev` bump the release itself makes. - **`box restore` asks before it destroys — and the confirmation prompt is now the row's, not rm's** (#105) — `restore` and `rm` both irreversibly discard user state, and only one of them asked. The table gave `restore` the preconditions `box,arg2`: the instance is ours, a snapshot name is present, go. So `box restore work stale-label` silently threw away everything done in the box since that snapshot, with no prompt, no `--force`, and no way to take it back — a warning in `--help` is not a gate. It has been that way since the verb shipped, and it is about to become routine rather than rare (heavy-duty/rig#62's pristine snapshot), which is the wrong time to still be relying on the operator typing the right label. The reason it stayed ungated is worth recording, because it is the actual bug: `confirm` was already a precondition token, but the dispatch line hardcoded the *words* — `confirm "delete $inst and all its snapshots"` — so the one-token fix would have gated restore behind a prompt offering to DELETE the box the operator was trying to rescue. A gate that names the wrong act is worse than no gate; it is how people learn to answer `y` without reading. So the prompt moved into the table as a seventh field, each row saying what it is about to do in its own words, and `restore` now asks to "roll `` back to snapshot `