CONTRIBUTING.md has said since #96 that the release PR is where the full real-hardware drill hangs, recorded in drill/RUNS.md. No release has ever done it: #95, #114 and #148 all shipped as a VERSION bump plus a CHANGELOG.md stamp, and RUNS.md carries no '## Release drill' section at all. A reviewer bot finally blocked on it — which is the point. The one time it was caught is the one time somebody happened to look, and that is not a gate. So the rule moves out of the document and into CI. .github/scripts/drill-recorded.sh, keyed on VERSION the same way changelog-armed.sh is: a -dev tree passes with nothing to assert (which is what keeps the guard installable — a version that fired on every PR would be switched off inside a day), and a bare VERSION must carry a section headed '## Release drill — <version>', optional ' — <date>' tail, with prose under it. The version is compared as a whole awk field, never as a substring, so 0.9.0 cannot be satisfied by a 0.9.0-rc1 drill or vice versa — release-notes.sh's trap, solved the same way so the two cannot disagree about what "the section for X" means. What it asserts is a RECORD, deliberately, not a passing drill: CI cannot run the drill (real hardware, the better part of an hour — ci.yml says as much about the rehearsal job it runs instead). That also keeps the maintainer waiver honest — a release that must ship undrilled writes that under the same heading, so the skip is a reviewable line in the diff rather than silence. Wired into ci.yml as its own step, NOT pull-request-only, for the reasoning #143 applied to the monotonic guard: the merge that publishes a release is a push to main carrying the same bare VERSION, so a PR-only check would leave the tree that actually ships unasserted. test/release.sh grows 27 cases (134 -> 161). Every fixture carries its own VERSION and its own RUNS.md — reaching for $ROOT/VERSION is the coupling #146 had to fix, and it goes red on the ceremony tree, the one tree where the release suite most needs to be trustworthy. CONTRIBUTING.md now states the flow (draft -> ready -> bot round -> drill -> state:needs-human -> merge), the heading format, that three releases shipped through the gap, and the recorded-waiver escape. It also describes the drill as ONE orchestrated run over the whole stack, because box and rig are mutually recursive and cannot be linearly ordered: rig sits below box as the host-builder ('rig bootstrap --host yes' installs box and runs setup-host) and above it as the guest-converger (a box new seed curls rig's installer and runs 'rig bootstrap <tenant>-box'), the inverted edge bin/box already documents as rig#28. The run is host bootstrap -> box new -> tenant converge -> cast. It drills CANDIDATE REFS, not released artifacts: RIG_REPO/RIG_REF are mint-time environment variables defaulting to heavy-duty/rig@main (bin/box:1116-1117), so a run pins the exact commits under test and no repo must be released before another can be drilled. Drilling the candidate is drilling the release — a release diff is VERSION + CHANGELOG.md, so nothing executable differs. One run, one shared run ID; each repo records its own legs citing that ID and the other two SHAs, and the guard reads only this repo's file. Recorded as a known gap, not fixed here: a released box still defaults RIG_REF to main, so a box minted a week after a drill is not the drilled combination. Pinning RIG_REF to a released rig tag in the templates is the outstanding step from #81 (rig#32 step 5). LABELS.md documents blocker:drill-pending — ceremony correct but unevidenced, maintainer-created because the bot account gets a 403 on label creation, with `blocked` standing in until it exists. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
182 lines
6.9 KiB
Markdown
182 lines
6.9 KiB
Markdown
# 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
|
|
|
|
- `box import` stamps the trip, leaving the artifact's own mint stamp intact
|
|
(#131)
|
|
- A minted box records how it was minted, and `box info` reads it back (#103)
|
|
- A clone re-stamps its own provenance instead of inheriting its source's
|
|
(#103)
|
|
- `box info` grew a provenance block, blank on boxes that predate the stamp
|
|
(#103)
|
|
- Every fresh mint marks a `pristine` snapshot, before rig converges anything
|
|
(#104, heavy-duty/rig#62)
|
|
- A mint that converges a tenant role marks a `bootstrapped` snapshot (#130)
|
|
- CI refuses a release PR with no drill record in `drill/RUNS.md`
|
|
|
|
### Changed
|
|
|
|
- `state:needs-human` is set at handoff, not by the cron (#141)
|
|
- PR labels split into two axes: `state:*` (whose ball) and `blocker:*` (what
|
|
is in the way); `state:needs-rebase` is retired
|
|
- BREAKING: the tenant templates carry rig's family suffix — `claude` →
|
|
`claude-box`, `codex` → `codex-box`, `grok` → `grok-box`, `staging` →
|
|
`staging-box` (#123, heavy-duty/rig#76)
|
|
- Changelog entries are one line each, and the whole file now follows the rule
|
|
(#147)
|
|
|
|
### Fixed
|
|
|
|
- `test/release.sh` is green on the release ceremony's own tree
|
|
- `changelog-monotonic.sh` no longer lets a duplicate heading through when it
|
|
cannot see the base (#143)
|
|
- An unreadable check rollup no longer reads as "nothing is failing"
|
|
- `state:needs-human` no longer appears on PRs a human cannot merge (#136)
|
|
- CI's shellcheck sweep now lints `.github/scripts/*.sh` (#116)
|
|
- A PR can no longer delete or duplicate a shipped changelog section and stay
|
|
green (#122)
|
|
- An upgrade over a pre-0.7.0 flat `/opt/box` no longer skips host setup (#115)
|
|
- Host setup runs the version it just installed, not whatever `current` points
|
|
at (#115)
|
|
- The pre-0.7.0 migration says what it left behind, and how to keep or reap it
|
|
(#117)
|
|
- `teardown-host.sh` refuses a terminal-less run instead of aborting mute
|
|
(#113)
|
|
- `drill/wipe.sh` no longer carries #102's SIGPIPE shape, and the pin sweeps
|
|
the class (#107)
|
|
- The racing-reader sweep guards the class, not one spelling, and names
|
|
`incus config trust list` as a second writer (#124)
|
|
|
|
## 0.8.0 — 2026-07-19
|
|
|
|
### Added
|
|
|
|
- Merging the release PR is the release, and the release re-arms main itself
|
|
(#96)
|
|
|
|
### Fixed
|
|
|
|
- The release ceremony re-arms `CHANGELOG.md`, and CI refuses to let main sit
|
|
disarmed (#108, heavy-duty/rig#67)
|
|
- Ctrl-D at a confirmation prompt aborts out loud instead of exiting in
|
|
silence (#111)
|
|
- `box restore` asks before it destroys, in the row's own words rather than
|
|
`rm`'s (#105)
|
|
- `box-firewall` could hand a UFW host the no-UFW firewall, ~2% of the time
|
|
(#102)
|
|
- A missing firewall log now diagnoses itself (#102)
|
|
- `box grant` provisions an `incus-admin` member instead of refusing them
|
|
(#99)
|
|
|
|
## 0.7.0 — 2026-07-19
|
|
|
|
### Added
|
|
|
|
- The installer defaults to the latest release, and releases publish
|
|
themselves (#83)
|
|
- `setup-host` auto-picks a free subnet — nested box-in-box with zero flags
|
|
(#80)
|
|
- `setup-host` refuses a claimed subnet, and `BOX_SUBNET` picks another (#80)
|
|
- `box doctor` knows the #80 signature: a gateway held as a local address, and
|
|
duplicate connected routes for the uplink subnet
|
|
- The `staging` template — a server-class, creds-free seed (#81)
|
|
- The `BOX_BOOTSTRAP_ROLE` template key, auto-run at mint (#81)
|
|
- The rig pin point: `RIG_REPO` / `RIG_REF` (#81)
|
|
- Server-posture template keys `BOX_REQUIRE_VM` and `BOX_AUTOSTART` (#81)
|
|
- The template test suite discovers `templates/*/` instead of hardcoding the
|
|
list (#81)
|
|
- `box export` / `box import` — a box's state that survives the box and the
|
|
host (#70)
|
|
- Versioned installs at `<root>/versions/<v>`, with `box versions` and
|
|
`box use` (#66)
|
|
- A real uninstall: `box uninstall [<version>] [--all] [--purge-host]`, ending
|
|
in an absence assert
|
|
- `BOX_INSTALL_SOURCE=<dir-or-tarball>` installs from a local tree, and CI's
|
|
rehearsal drills the uninstall to zero residue
|
|
- `test/cli.sh` drives real installs against throwaway roots and a fake incus
|
|
(154 checks)
|
|
|
|
### Changed
|
|
|
|
- Thin templates — box mints a creds-free seed, rig's bootstrap roles converge
|
|
the tenant content (#81, heavy-duty/rig#31)
|
|
|
|
### Fixed
|
|
|
|
- A wedged `incus launch` fails loudly, not forever: the launch phase is
|
|
narrated and time-boxed (#93)
|
|
- UFW's gateway carve-out converges with the bridge, and the doctor can see it
|
|
(#86)
|
|
- The boot-time gateway fallback is gone — an unaddressed bridge leaves the
|
|
persisted UFW rules alone (#86)
|
|
- `revoke --purge` re-checks the incus-user state, and stats it through
|
|
`$SUDO`
|
|
- A wedged `$BINDIR/box` no longer blocks installing
|
|
|
|
## 0.6.0 — 2026-07-18
|
|
|
|
### Added
|
|
|
|
- The restricted tier: `box grant` / `box revoke` give a user their own boxes
|
|
on the shared hardened `boxnet` (#74)
|
|
- CI runs the multi-user rehearsal on a real Incus
|
|
- Global / root install — one world-readable tree at `/opt/box` (#71)
|
|
- CI and a test suite: `.github/workflows/ci.yml` and `test/cli.sh`
|
|
|
|
### Fixed
|
|
|
|
- `box restore` never worked against Incus 6 — it dispatched `incus restore`,
|
|
which does not exist
|
|
- `box tmux` works on every template — tmux is in each template's package list
|
|
(#65)
|
|
- `box setup-host` finishes in one run, re-execing itself under
|
|
`sg incus-admin` (#63)
|
|
- `setup-host` works as root, with or without `sudo`
|
|
- `setup-host` grants `incus-admin` to the human, not to root
|
|
- `box-firewall.service` reports its state honestly, via `RemainAfterExit=yes`
|
|
- `setup-host`'s apt calls can no longer hang on the dpkg lock
|
|
|
|
### Changed
|
|
|
|
- `drill.sh` asserts the post-install stack instead of building it itself
|
|
- `install.sh` asks, sets up the host, and no-ops on re-run (#64)
|
|
- `install.sh` never overwrites an existing install
|
|
|
|
## 0.5.0 — 2026-07-15
|
|
|
|
The release the project was renamed in: the repo is `heavy-duty/box`, matching
|
|
the CLI it ships. Everything legacy-facing is honored forever — the
|
|
`user.claudebox=1` tag, the `.claudebox/` runbook folder, the old symlink the
|
|
installer retires — but nothing current carries the old name.
|
|
|
|
### Added
|
|
|
|
- `codex` and `grok` templates
|
|
- `box expose <box> <port> [<host-port>]` — a loopback-only door to a port
|
|
inside a box
|
|
- Inline resource overrides on `new`: `--cpu`, `--memory`, `--disk` (#57)
|
|
- Host lifecycle as verbs: `box setup-host`, `box teardown-host`,
|
|
`box migrate-host`
|
|
- The `.box/` recipe convention, renamed from `.claudebox/` (both spellings
|
|
read)
|
|
|
|
### Fixed
|
|
|
|
- VM mints no longer hang at GRUB — boxes launch with
|
|
`security.secureboot=false`
|
|
- `box expose` actually delivers packets
|
|
- Firewall rules converge on upgrade instead of pinning a host to the release
|
|
that first ran there
|
|
- Failed mints tell you why
|
|
- `grok` installs the binary it actually ships
|
|
|
|
### Changed
|
|
|
|
- Debrand complete — env vars, install dir, docs, template descriptions and
|
|
the README all say `box`; the install URL is `heavy-duty/box`
|
|
- The drill grew from 47 to 84 checks
|