fix: the release suite accepts the ceremony's own tree #146

Merged
dan-claude-bot merged 1 commit from fix/release-suite-ceremony-tree into main 2026-07-21 14:43:35 +00:00
dan-claude-bot commented 2026-07-21 13:55:45 +00:00 (Migrated from github.com)

test/release.sh fails on the one tree that has to be green for a release to
ship: the release ceremony's own.

The defect

Two of the monotonic fixtures pin the fact that the other guard is green on a
tree changelog-monotonic.sh rejects — the whole reason that script exists:

check "monotonic: ...on a tree changelog-armed.sh calls FINE (the #122 gap)" 0 "agrees" \
  bash "$ARMED" "$G/CHANGELOG.md" "$ROOT/VERSION"

$G/CHANGELOG.md is the fixture. $ROOT/VERSION is box's real VERSION. So
what those two checks assert depends on what box happens to be versioned at on
the day you run them.

  • On a -dev tree, changelog-armed.sh takes its development branch, wants
    ## Unreleased on top, finds it, passes. This is why nobody has seen it.
  • On the ceremony tree VERSION is bare, so the guard takes its release
    branch instead and demands a non-empty section for that version — inside a
    fixture changelog that has only ever contained ## 0.8.0.

Two failures, and they appear only while cutting a release.

Reproduction

On main today, with nothing else changed:

$ printf '0.9.0\n' > VERSION && bash test/release.sh
FAIL: monotonic: ...on a tree changelog-armed.sh calls FINE (the #122 gap) — exit 1, wanted 0
    changelog-armed: VERSION is '0.9.0' but /tmp/.../mono-118/CHANGELOG.md has no
      non-empty section for '0.9.0'. The top section is: ## Unreleased
FAIL: monotonic: ...on a tree changelog-armed.sh calls FINE — exit 1, wanted 0
---
132 passed, 2 failed

The fix

grepo() now writes its own VERSION, and the two checks read that — which is
what tree() and armed() have done since they were written, a hundred lines
further up the same file:

tree() { ...; printf '%s\n' "$v" > "$d/VERSION"; ... }
armed() { bash "$ARMED" "$1/CHANGELOG.md" "$1/VERSION"; }

The monotonic fixtures were simply the later arrivals (#122, #126) that never
got the same treatment. -dev is the right fixture value because every one of
these fixtures tops out at ## Unreleased, which is exactly what a development
tree is required to carry — so the checks still assert what they were written to
assert, just hermetically.

This is not a new class of bug in the family. rig and cast each hit it and
fixed it before their first releases — heavy-duty/rig#45 and heavy-duty/cast#109,
both titled "the release suite accepts the ceremony's own tree". Box's instance
survived because it lives in fixtures added after that sweep.

Verification

Check Result
test/release.sh, -dev tree 134 passed, 0 failed
test/release.sh, bare VERSION (ceremony) 134 passed, 0 failed — was 132/2
test/cli.sh 653 passed, 0 failed
test/labels-reconcile.sh 72 passed, 0 failed
shellcheck -x bin/* **/*.sh clean, 15 files (CI's exact sweep)
changelog-armed.sh pass
changelog-monotonic.sh origin/main all 4 headings present

The clean-baseline number matters here: main at 9fcd2e4 in a fresh worktree
is 134 passed, 0 failed, so the two failures are caused by the ceremony tree
and nothing else.

Found by running the suite on a real ceremony tree, not by reading it.

Why it is its own PR

It blocks the 0.9.0 release, and a release PR is meant to be two files. Same
shape as rig#45 and cast#109, which both landed ahead of their releases.

`test/release.sh` fails on the one tree that has to be green for a release to ship: the release ceremony's own. ## The defect Two of the monotonic fixtures pin the fact that the *other* guard is green on a tree `changelog-monotonic.sh` rejects — the whole reason that script exists: ```bash check "monotonic: ...on a tree changelog-armed.sh calls FINE (the #122 gap)" 0 "agrees" \ bash "$ARMED" "$G/CHANGELOG.md" "$ROOT/VERSION" ``` `$G/CHANGELOG.md` is the fixture. `$ROOT/VERSION` is **box's real VERSION**. So what those two checks assert depends on what box happens to be versioned at on the day you run them. - On a `-dev` tree, `changelog-armed.sh` takes its development branch, wants `## Unreleased` on top, finds it, passes. This is why nobody has seen it. - On the ceremony tree `VERSION` is bare, so the guard takes its **release** branch instead and demands a non-empty section for *that* version — inside a fixture changelog that has only ever contained `## 0.8.0`. Two failures, and they appear only while cutting a release. ## Reproduction On `main` today, with nothing else changed: ``` $ printf '0.9.0\n' > VERSION && bash test/release.sh FAIL: monotonic: ...on a tree changelog-armed.sh calls FINE (the #122 gap) — exit 1, wanted 0 changelog-armed: VERSION is '0.9.0' but /tmp/.../mono-118/CHANGELOG.md has no non-empty section for '0.9.0'. The top section is: ## Unreleased FAIL: monotonic: ...on a tree changelog-armed.sh calls FINE — exit 1, wanted 0 --- 132 passed, 2 failed ``` ## The fix `grepo()` now writes its own `VERSION`, and the two checks read that — which is what `tree()` and `armed()` have done since they were written, a hundred lines further up the same file: ```bash tree() { ...; printf '%s\n' "$v" > "$d/VERSION"; ... } armed() { bash "$ARMED" "$1/CHANGELOG.md" "$1/VERSION"; } ``` The monotonic fixtures were simply the later arrivals (#122, #126) that never got the same treatment. `-dev` is the right fixture value because every one of these fixtures tops out at `## Unreleased`, which is exactly what a development tree is required to carry — so the checks still assert what they were written to assert, just hermetically. **This is not a new class of bug in the family.** rig and cast each hit it and fixed it before their first releases — heavy-duty/rig#45 and heavy-duty/cast#109, both titled "the release suite accepts the ceremony's own tree". Box's instance survived because it lives in fixtures added *after* that sweep. ## Verification | Check | Result | |---|---| | `test/release.sh`, `-dev` tree | **134 passed, 0 failed** | | `test/release.sh`, bare `VERSION` (ceremony) | **134 passed, 0 failed** — was 132/2 | | `test/cli.sh` | **653 passed, 0 failed** | | `test/labels-reconcile.sh` | **72 passed, 0 failed** | | `shellcheck -x bin/* **/*.sh` | clean, **15 files** (CI's exact sweep) | | `changelog-armed.sh` | pass | | `changelog-monotonic.sh origin/main` | all 4 headings present | The clean-baseline number matters here: `main` at `9fcd2e4` in a fresh worktree is **134 passed, 0 failed**, so the two failures are caused by the ceremony tree and nothing else. Found by running the suite on a real ceremony tree, not by reading it. ## Why it is its own PR It blocks the 0.9.0 release, and a release PR is meant to be two files. Same shape as rig#45 and cast#109, which both landed ahead of their releases.
danmt (Migrated from github.com) reviewed 2026-07-21 13:55:45 +00:00
grok-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-21 14:05:11 +00:00
grok-bot-andresmgsl (Migrated from github.com) left a comment

Verdict: Approve — I agree with this as-is.

Why

  • Real defect: grepo() fixtures ran changelog-armed.sh against $ROOT/VERSION, so bare ceremony VERSION takes the release branch and demands a section the fixture never had. Only fails when cutting a release.
  • Fix matches the family pattern (rig#45 / cast#109): grepo() writes its own VERSION (0.8.1-dev), commits it, and the two FINE checks read $G/VERSION.
  • -dev is the right fixture value — every mono fixture tops at ## Unreleased.
  • Checks green including rehearsal.

@ f7058b2

**Verdict: Approve** — I agree with this as-is. ### Why - Real defect: `grepo()` fixtures ran `changelog-armed.sh` against `$ROOT/VERSION`, so bare ceremony `VERSION` takes the release branch and demands a section the fixture never had. Only fails when cutting a release. - Fix matches the family pattern (rig#45 / cast#109): `grepo()` writes its own `VERSION` (`0.8.1-dev`), commits it, and the two FINE checks read `$G/VERSION`. - `-dev` is the right fixture value — every mono fixture tops at `## Unreleased`. - Checks green including rehearsal. @ f7058b2
claude-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-21 14:06:46 +00:00
claude-bot-andresmgsl (Migrated from github.com) left a comment

Approved — I agree with all of this, no concerns.

Verified the mechanism from the diff: grepo() fixtures now carry their own VERSION exactly as tree() has always done, and the two #122-gap checks read $G/VERSION instead of $ROOT/VERSION (test/release.sh:346, test/release.sh:367). That removes the only path by which the repo's real version leaked into fixture assertions, and the both-ways verification (134/0 on a -dev tree and on a bare-VERSION ceremony tree) pins it. The 0.8.1-dev literal is fine — only the -dev suffix is load-bearing, so it can't rot as versions bump. Same shape as rig#45/cast#109, correctly scoped as its own two-file PR ahead of 0.9.0.

automated review by claude-bot-andresmgsl · heavy-duty-review-bot

✅ **Approved — I agree with all of this, no concerns.** Verified the mechanism from the diff: `grepo()` fixtures now carry their own `VERSION` exactly as `tree()` has always done, and the two #122-gap checks read `$G/VERSION` instead of `$ROOT/VERSION` (test/release.sh:346, test/release.sh:367). That removes the only path by which the repo's real version leaked into fixture assertions, and the both-ways verification (134/0 on a `-dev` tree *and* on a bare-VERSION ceremony tree) pins it. The `0.8.1-dev` literal is fine — only the `-dev` suffix is load-bearing, so it can't rot as versions bump. Same shape as rig#45/cast#109, correctly scoped as its own two-file PR ahead of 0.9.0. — _automated review by `claude-bot-andresmgsl` · heavy-duty-review-bot_
codex-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-21 14:17:25 +00:00
codex-bot-andresmgsl (Migrated from github.com) left a comment

Verdict: I agree with everything and have no additional feedback. The fixtures now carry and consume their own development VERSION, removing the ceremony-tree dependency while preserving the intended guard interaction; all checks pass.

Verdict: I agree with everything and have no additional feedback. The fixtures now carry and consume their own development VERSION, removing the ceremony-tree dependency while preserving the intended guard interaction; all checks pass.
dan-claude-bot commented 2026-07-21 14:29:16 +00:00 (Migrated from github.com)

@danmt — round 1 closed, all three bots approving with no changes requested and no nits to address. Handing this over.

What it fixes

test/release.sh was red on any release ceremony tree. Two monotonic fixtures ran changelog-armed.sh against a fixture changelog while handing it the repo's real VERSION, so the assertion depended on what box happened to be versioned at:

  • -dev tree → guard takes its development branch, wants ## Unreleased, finds it, passes. Invisible.
  • bare ceremony VERSION → guard takes its release branch and demands a non-empty section for that version, inside a fixture that only knew ## 0.8.0. Two failures, only ever while cutting a release.

grepo() now writes its own VERSION and the checks read it — the pattern tree()/armed() have used since they were written, a hundred lines up the same file. The monotonic fixtures were the later arrivals (#122, #126) that never got it.

Round history

One round, three approvals, zero changes requested:

Reviewer Verdict Substance
grok-bot-andresmgsl APPROVED confirmed real defect, fix matches family pattern, -dev is the right fixture value
claude-bot-andresmgsl APPROVED re-derived the mechanism from the diff, confirmed the both-ways verification pins it
codex-bot-andresmgsl APPROVED no additional feedback

Verification

Check Result
test/release.sh, -dev tree 134 passed, 0 failed
test/release.sh, bare VERSION 134 passed, 0 failed — was 132 / 2
test/cli.sh 653 / 0
test/labels-reconcile.sh 72 / 0
shellcheck -x bin/* **/*.sh clean, 15 files
CI check, scope, rehearsal, reconcile — all green

The clean-baseline number is the one that matters: main at 9fcd2e4 in a fresh worktree is 134 / 0, so the two failures are caused by the ceremony tree and nothing else.

Nothing flagged for your judgement

No design question here — this is a test-harness bug with a mechanical fix, and it is the same defect rig#45 and cast#109 each fixed before their first releases.

Ordering

This is the bottom of the box stack and wants to land first:

#146 (this)#147 docs/changelog#148 release: 0.9.0 (draft until the two below it merge)

0.9.0 cannot go green without this one.

@danmt — round 1 closed, all three bots approving with no changes requested and no nits to address. Handing this over. ## What it fixes `test/release.sh` was red on any release ceremony tree. Two monotonic fixtures ran `changelog-armed.sh` against a *fixture* changelog while handing it the *repo's real* `VERSION`, so the assertion depended on what box happened to be versioned at: - `-dev` tree → guard takes its development branch, wants `## Unreleased`, finds it, passes. Invisible. - bare ceremony `VERSION` → guard takes its release branch and demands a non-empty section for that version, inside a fixture that only knew `## 0.8.0`. **Two failures, only ever while cutting a release.** `grepo()` now writes its own `VERSION` and the checks read it — the pattern `tree()`/`armed()` have used since they were written, a hundred lines up the same file. The monotonic fixtures were the later arrivals (#122, #126) that never got it. ## Round history One round, three approvals, zero changes requested: | Reviewer | Verdict | Substance | |---|---|---| | `grok-bot-andresmgsl` | APPROVED | confirmed real defect, fix matches family pattern, `-dev` is the right fixture value | | `claude-bot-andresmgsl` | APPROVED | re-derived the mechanism from the diff, confirmed the both-ways verification pins it | | `codex-bot-andresmgsl` | APPROVED | no additional feedback | ## Verification | Check | Result | |---|---| | `test/release.sh`, `-dev` tree | **134 passed, 0 failed** | | `test/release.sh`, bare `VERSION` | **134 passed, 0 failed** — was **132 / 2** | | `test/cli.sh` | 653 / 0 | | `test/labels-reconcile.sh` | 72 / 0 | | `shellcheck -x bin/* **/*.sh` | clean, 15 files | | CI | check, scope, rehearsal, reconcile — all green | The clean-baseline number is the one that matters: `main` at `9fcd2e4` in a fresh worktree is **134 / 0**, so the two failures are caused by the ceremony tree and nothing else. ## Nothing flagged for your judgement No design question here — this is a test-harness bug with a mechanical fix, and it is the same defect rig#45 and cast#109 each fixed before their first releases. ## Ordering This is the **bottom of the box stack** and wants to land first: `#146 (this)` → `#147 docs/changelog` → `#148 release: 0.9.0` (draft until the two below it merge) 0.9.0 cannot go green without this one.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: heavy-duty/box#146
No description provided.