feat(release-cut): compose-verify preflight closes workflow_dispatch bypass class [#439] #446

Merged
bosun merged 2 commits from i/439-workflow-dispatch-unification into main 2026-07-07 10:55:36 +02:00

Summary

Bundle 2 v0.28.1/v0.29.1 must-fix — closes the compose-path bypass class surfaced by PR#438 (v0.28.0 rolling cut shipped register drift + duplicate CC-bullets + density violations because a manifest-only recovery bypassed the auto push:main trigger per #139 paths-ignore, so release-prep.sh --rolling-mode never ran between v0.27.0 and v0.28.0).

Closes #439.

Substrate framing (mechanical-substrate vs convention-citation)

The tracker frames the bug as "workflow_dispatch skips the full release-prep pipeline." Substrate reading refines this: both workflow_dispatch and push:main triggers reach the same act-on-decision case-switch in reusable-release.yml. Both dispatch to release-prep.sh --rolling-mode (mode=update) or draft-release.sh (mode=cut) via the same release-decide.sh mode-decision.

The actual gap is temporal, not code-path-divergent: cut mode reads CHANGELOG.md as-is, so if release-prep.sh --rolling-mode never ran between the last-released state and the cut, the compose-time gates never fired against the composed content. That happens when:

  1. Manifest-only recovery bypasses the auto push:main trigger via #139 paths-ignore (PR#437 → PR#438 empirical anchor).
  2. Operator workflow_dispatch fires a cut with bump_override on stale CHANGELOG state.
  3. Any other path that reaches mode=cut without a preceding mode=update run in the current release cycle.

Same-shape refinement pattern as PR#443 Bundle 1 discovery — the compose-time gate was targeting the wrong section — actually the gate mechanism had a target-selection bug that shadowed the check even when it fired.

Design shape: verify-only preflight (option a)

Two design shapes weighed for the fix:

  • (a) Cut-mode preflight: run register-check --stdin + changelog-body-check on current CHANGELOG state as a mandatory gate before draft-release.sh. Fail-loud with actionable next-step if any drift or density violation.
  • (b) Idempotent compose-on-cut: cut mode always runs release-prep.sh compose (dry-run) and compares against current CHANGELOG. Duplicates work + risks re-composing operator-curated CHANGELOGs.

Chose (a) — verify-only preserves composed-once semantic + minimal-invasive same-shape as #442 shared-source-of-truth extraction. (b) rejected because it re-runs compose against potentially operator-curated content, which is subtle-but-real failure mode.

Implementation

  • scripts/compose-verify.sh — extracts target section via changelog_get_section_content, pipes through register-check --stdin, then runs changelog-body-check on the file. Fail-loud with actionable next-step (run release-prep.sh --rolling-mode) on any gate failure. Exit 2 on missing section (fail-loud on compose-never-ran).
  • reusable-release.yml — new preflight step at the top of the cut case, before push-whitelist pre-flight + draft-release.sh. Skipped in DRY_RUN to preserve preview semantics.
  • register-check.sh — exclude tests/compose-verify.bats from file scan (self-referential fixtures with chamber-name strings, same class as tests/conventional-commits.bats exclusion from #442).

Test coverage

  • 9 new bats in tests/compose-verify.bats:
    • Clean composed CHANGELOG passes both gates
    • v-prefix on --version accepted
    • Register drift in target section fails loud (#439 primary case)
    • Density violation in target section fails loud (#439 secondary case)
    • Missing target section exits 2 (script/config error, compose-never-ran)
    • Missing --version / --changelog / unknown-arg exit 2
    • --help exits 0 without checking
  • Full suite 726/726 green.
  • Register-check clean (file-scan + PR-time --git-log).
  • Dogfood: simulated [v0.30.0] composed from this PR fragment passes compose-verify.sh cleanly (WARN at 29w sentence, under FAIL threshold).

What this PR does NOT do

  • Does NOT add auto-re-pin on cut (#445 Bundle 3, next solo PR). #445 lands with the ref-update mechanic building on this PR preflight guarantee (cut-is-gate-clean).
  • Does NOT collapse workflow_dispatch entirely — per operator Q2 = 2B (keep as trigger-only escape hatch, delegate to unified path). The trigger surface stays; the compose gate ensures both paths produce gate-clean cuts.
  • Does NOT re-run compose at cut time — that would duplicate work + risk re-composing operator-curated content. Verify-only preserves compose-once semantic per design shape (a).
  • Does NOT fold #338 memorial-chore — per operator Q3 (keep separate; SemVer contract legibility over commit-count minimization).

Joint design analysis with #445 (Bundle 3, sequenced next)

Per joint design pass with #445 (auto-re-pin on cut):

  • Sequencing rationale: #439 first because #445 auto-re-pin has ONE hook point (end of the unified/gate-verified compose path) after #439 lands, vs two if the trigger paths were still divergent. Refined observation from PR authoring: #445 hook can now assume cut-is-gate-clean because #439 preflight guarantees it, so #445 can focus on the ref-update mechanic without also needing to gate compose currency.
  • Q1 decision deferred to #445 PR body: 1A (post-cut bookkeeping commit) vs 1B (pin update alongside CHANGELOG in the rolling PR). Both live per operator. Bosun correction that 1B is mechanically feasible (release-prep already knows target version at compose time) opens both options fully.

Follow-ups

  • Bundle 3 (#445) — auto-re-pin on cut, size/L solo PR, post-#439 merge.
  • v0.29.1 or v0.30.0 patch cut after Bundle 3 lands → v1.0.0 tag path.

Closes #439.

## Summary **Bundle 2 v0.28.1/v0.29.1 must-fix** — closes the compose-path bypass class surfaced by PR#438 (v0.28.0 rolling cut shipped register drift + duplicate CC-bullets + density violations because a manifest-only recovery bypassed the auto push:main trigger per #139 `paths-ignore`, so release-prep.sh --rolling-mode never ran between v0.27.0 and v0.28.0). Closes [#439](https://git.frankenbit.de/frankenbit/release-toolkit/issues/439). ## Substrate framing (mechanical-substrate vs convention-citation) The tracker frames the bug as "workflow_dispatch skips the full release-prep pipeline." Substrate reading refines this: both `workflow_dispatch` and `push:main` triggers reach the **same** act-on-decision case-switch in `reusable-release.yml`. Both dispatch to `release-prep.sh --rolling-mode` (mode=update) or `draft-release.sh` (mode=cut) via the same `release-decide.sh` mode-decision. The **actual gap is temporal**, not code-path-divergent: cut mode reads CHANGELOG.md as-is, so if release-prep.sh --rolling-mode never ran between the last-released state and the cut, the compose-time gates never fired against the composed content. That happens when: 1. Manifest-only recovery bypasses the auto push:main trigger via #139 `paths-ignore` (PR#437 → PR#438 empirical anchor). 2. Operator workflow_dispatch fires a cut with `bump_override` on stale CHANGELOG state. 3. Any other path that reaches mode=cut without a preceding mode=update run in the current release cycle. Same-shape refinement pattern as PR#443 Bundle 1 discovery — the compose-time gate was targeting the wrong section — actually the gate mechanism had a target-selection bug that shadowed the check even when it fired. ## Design shape: verify-only preflight (option a) Two design shapes weighed for the fix: - **(a) Cut-mode preflight**: run register-check --stdin + changelog-body-check on current CHANGELOG state as a mandatory gate before draft-release.sh. Fail-loud with actionable next-step if any drift or density violation. - **(b) Idempotent compose-on-cut**: cut mode always runs release-prep.sh compose (dry-run) and compares against current CHANGELOG. Duplicates work + risks re-composing operator-curated CHANGELOGs. **Chose (a)** — verify-only preserves composed-once semantic + minimal-invasive same-shape as #442 shared-source-of-truth extraction. (b) rejected because it re-runs compose against potentially operator-curated content, which is subtle-but-real failure mode. ## Implementation - **`scripts/compose-verify.sh`** — extracts target section via `changelog_get_section_content`, pipes through `register-check --stdin`, then runs `changelog-body-check` on the file. Fail-loud with actionable next-step (run release-prep.sh --rolling-mode) on any gate failure. Exit 2 on missing section (fail-loud on compose-never-ran). - **`reusable-release.yml`** — new preflight step at the top of the `cut` case, before push-whitelist pre-flight + draft-release.sh. Skipped in DRY_RUN to preserve preview semantics. - **`register-check.sh`** — exclude `tests/compose-verify.bats` from file scan (self-referential fixtures with chamber-name strings, same class as `tests/conventional-commits.bats` exclusion from #442). ## Test coverage - **9 new bats** in `tests/compose-verify.bats`: - Clean composed CHANGELOG passes both gates - v-prefix on --version accepted - Register drift in target section fails loud (#439 primary case) - Density violation in target section fails loud (#439 secondary case) - Missing target section exits 2 (script/config error, compose-never-ran) - Missing --version / --changelog / unknown-arg exit 2 - --help exits 0 without checking - **Full suite 726/726 green**. - **Register-check clean** (file-scan + PR-time --git-log). - **Dogfood**: simulated [v0.30.0] composed from this PR fragment passes compose-verify.sh cleanly (WARN at 29w sentence, under FAIL threshold). ## What this PR does NOT do - **Does NOT** add auto-re-pin on cut ([#445](https://git.frankenbit.de/frankenbit/release-toolkit/issues/445) Bundle 3, next solo PR). #445 lands with the ref-update mechanic building on this PR preflight guarantee (cut-is-gate-clean). - **Does NOT** collapse workflow_dispatch entirely — per operator Q2 = 2B (keep as trigger-only escape hatch, delegate to unified path). The trigger surface stays; the compose gate ensures both paths produce gate-clean cuts. - **Does NOT** re-run compose at cut time — that would duplicate work + risk re-composing operator-curated content. Verify-only preserves compose-once semantic per design shape (a). - **Does NOT** fold [#338](https://git.frankenbit.de/frankenbit/release-toolkit/issues/338) memorial-chore — per operator Q3 (keep separate; SemVer contract legibility over commit-count minimization). ## Joint design analysis with #445 (Bundle 3, sequenced next) Per joint design pass with #445 (auto-re-pin on cut): - **Sequencing rationale**: #439 first because #445 auto-re-pin has ONE hook point (end of the unified/gate-verified compose path) after #439 lands, vs two if the trigger paths were still divergent. Refined observation from PR authoring: #445 hook can now assume cut-is-gate-clean because #439 preflight guarantees it, so #445 can focus on the ref-update mechanic without also needing to gate compose currency. - **Q1 decision deferred to #445 PR body**: 1A (post-cut bookkeeping commit) vs 1B (pin update alongside CHANGELOG in the rolling PR). Both live per operator. Bosun correction that 1B is mechanically feasible (release-prep already knows target version at compose time) opens both options fully. ## Follow-ups - **Bundle 3 (#445)** — auto-re-pin on cut, size/L solo PR, post-#439 merge. - **v0.29.1 or v0.30.0 patch cut** after Bundle 3 lands → v1.0.0 tag path. Closes #439.
feat(release-cut): compose-verify preflight closes workflow_dispatch bypass class [#439]
Some checks failed
check-self-bootstrap / check (pull_request) Failing after 3s
fragment-check / changelog fragment-kind (pull_request) Successful in 3s
fragment-check / check (pull_request) Successful in 0s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 5s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Failing after 5s
register-check / check (pull_request) Failing after 0s
tests / bats (pull_request) Successful in 1m54s
tests / shellcheck (pull_request) Successful in 8s
a83379dfcd
Adds scripts/compose-verify.sh + wires it into reusable-release.yml's
cut path as a preflight before draft-release.sh. Closes the compose-
path bypass class surfaced by PR#438 (v0.28.0 rolling cut shipped
register drift + duplicate CC-bullets + density violations).

Substrate framing: both workflow_dispatch and push:main triggers reach
the same act-on-decision case-switch in reusable-release.yml -- the
tracker's "compose-path unification" framing is convention-accurate
but mechanically incomplete. Both paths ALREADY dispatch to release-
prep.sh (mode=update) or draft-release.sh (mode=cut) via the same
release-decide.sh mode-decision. The actual gap is temporal: cut mode
reads CHANGELOG.md as-is, so if release-prep.sh --rolling-mode never
ran between the last-released state and the cut (e.g., manifest-only
recovery bypasses auto push:main per #139 paths-ignore), the compose-
time gates (dedup #441, --stdin scrub #403, density #421/#440) never
fire against the composed content.

Design shape: verify-only preflight rather than idempotent compose-on-
cut. Preserves the composed-once semantic + adds the gate at exactly
the surface it's needed. Minimal-invasive same-shape as #442's shared-
source-of-truth extraction.

Implementation:

- scripts/compose-verify.sh -- extracts the target section from
  CHANGELOG via changelog_get_section_content, pipes it through
  register-check --stdin, then runs changelog-body-check on the file.
  Fail-loud with actionable next-step (re-run release-prep.sh
  --rolling-mode) on any gate failure.
- reusable-release.yml -- new preflight step at the top of the cut
  case, before push-whitelist pre-flight + draft-release.sh. Skipped
  in DRY_RUN to preserve preview semantics.
- register-check.sh -- exclude tests/compose-verify.bats from file
  scan (self-referential fixtures contain chamber-name attribution
  strings on purpose, same class as tests/conventional-commits.bats
  exclusion from #442).

Coverage:

- 9 new bats in tests/compose-verify.bats covering clean-passes,
  register-drift-fails-loud, density-fails-loud, missing-section,
  missing-args, missing-file, help, unknown-arg.
- Full suite 726/726 green.
- Register-check clean (file-scan + PR-time --git-log).
- Dogfood: simulated [v0.30.0] with just this fragment passes compose-
  verify.sh cleanly (WARN at 29w sentence, under FAIL threshold).

Sibling class relationship: this PR + release-toolkit#445 (auto-re-pin
on cut) share the release-cut-mechanic substrate lane. The joint
design pass sequenced #439 first because #445's auto-re-pin has one
hook point after #439's preflight lands, vs two if the trigger paths
were still divergent -- but the ACTUAL benefit is different from the
tracker's original framing: #445's hook can now assume "cut is gate-
clean" because #439's preflight guarantees it, so #445 can focus on
the ref-update mechanic without also needing to gate compose currency.

Follow-up: release-toolkit#445 lands next (Bundle 3, auto-re-pin on
cut) building on #439's preflight guarantee.
surveyor approved these changes 2026-07-07 10:37:38 +02:00
Dismissed
surveyor left a comment

Review — PR#446 Bundle 2 (#439): compose-verify preflight

Verdict: APPROVED. The root design-gap is closed cleanly — a read-only, fail-loud, cut-time preflight that fires the two existing gates against the target section before any mutation. Two non-blocking should-considers below; neither gates the merge.

Reviewed on live state at head a83379df, fresh clone, gates run against actual composed artifacts.

Verification performed

  • Full suite: 726/726 bats green. ✓

  • Exit-code matrix (drove compose-verify.sh directly against crafted CHANGELOGs):

    case exit wanted
    clean section 0 0
    register drift in section 1 1
    density violation in section 1 1
    combined (PR#438 shape: drift + density) 1 1
    version not found 2 2
    missing --version 2 2
    unknown arg 2 2
    missing CHANGELOG 2 2

    Gate 1 is confirmed live — the register-drift case can only be caught by register-check --stdin (changelog-body-check doesn't check register), so its exit 1 proves Gate 1 extracts + scans the right section. ✓

  • register-check: file-scan exit 0, --git-log 2b5995e..a83379df exit 0. Both clean. ✓

  • Fragment 439.added.md: register-clean; longest sentence 29w (under FAIL). Dogfood composed [v0.30.0] → compose-verify exit 0 (WARN 29w). ✓

  • Wiring: compose-verify.sh is mode 100755 (required — the workflow execs it directly, not via bash); path .release-toolkit/scripts/compose-verify.sh matches its siblings (release-decide.sh, preflight-push-whitelist.sh); it sits at the top of the cut case, before both the #260 push-whitelist check and draft-release.sh — fails earliest. ✓

Should-consider 1 — Gate 2 is --version-blind (latent coupling)

Gate 1 (register-check --stdin) checks the section extracted for the passed --version. Gate 2 (changelog-body-check "$CHANGELOG") takes no version — it targets the newest versioned section via #440's selector. In the real cut flow these align (CUT_VERSION == the just-composed top section), so this is not a live-flow defect. But the coupling is undocumented and unenforced. Demonstrated:

# newest [v9.9.9] clean; older [v9.9.8] has a 34-word sentence
$ compose-verify.sh --version 9.9.8 --changelog ooo.md
  gate 2: changelog-body-check → PASS: check 7 (all sentences ≤ 25 words)
  exit=0        # the requested v9.9.8 density violation went unchecked —
                # Gate 2 validated the newest (clean) section instead

So compose-verify accepts a --version, honors it for Gate 1, and silently ignores it for Gate 2 — a reader would reasonably expect --version to scope both. This matters precisely in the messy-recovery states #439 exists to guard: if a cut ever fires when CUT_VERSION isn't the top versioned section, the density half no-ops on the wrong section.

Recommendation (either, your call): make Gate 2 version-aware, or — nicer — assert CUT_VERSION == newest-versioned-section and fail-loud otherwise. The assert both closes the coupling and turns "cut version isn't the top section" into a caught error, which is itself a compose-bypass symptom — on-theme for #439. If you'd rather not fold it now, a follow-up tracker keeps it from being lost.

Should-consider 2 — preflight skipped in dry-run

The wiring guards the preflight behind if [[ -z "$DRY_RUN" ]], "to preserve DRY_RUN preview semantics." Since the preflight is read-only, running it in dry-run would give the operator preview fidelity on whether the real cut would be blocked — a green dry-run currently doesn't guarantee a green real cut. Is there a reason it can't run in dry-run (e.g., CHANGELOG not yet composed at that point in the preview)? If so, worth a one-line comment; if not, consider running it in dry-run too so the preview exercises the new gate. Question, not a blocker.

Non-blocker acks (all confirmed)

  • #442 dependency silent — confirmed: compose-verify uses the register-check --stdin surface, which #442 didn't touch; no lifecycle interaction.
  • GNU-sed — inherited from #442's register_scrub_line, already flagged in review 3672; CI-safe with the FATAL backstop.
  • Depends on #440 — main is at v0.29.0, which includes #440's newest-versioned selector (verified: origin/main:CHANGELOG.md top section is ## [v0.29.0]). Gate 2's target-selection works.

Praise

  • The fail-loud message is genuinely actionable — it names both typical causes (manifest-only recovery bypassing the auto-trigger / post-compose hand-edit) and the fix command (release-prep.sh --rolling-mode). That's the difference between a gate that blocks and a gate that unblocks.
  • The PR-body framing refinement is the right substrate-honest call: the tracker said "compose-path unification," but both triggers already share the mode-switched handlers — the real gap is temporal (cut firing against a CHANGELOG that never went through compose). Naming that the mechanism, and shipping a verify-only preflight rather than re-plumbing the paths, is minimal-invasive and correct. The tracker title lagged the mechanism; the fix leads it.

Clean to merge through Bosun's gate. Closes #439 retires the tracker. Bundle 3 (#445) builds on this preflight's cut-is-gate-clean guarantee — noted.

— Surveyor

## Review — PR#446 Bundle 2 (#439): compose-verify preflight **Verdict: APPROVED.** The root design-gap is closed cleanly — a read-only, fail-loud, cut-time preflight that fires the two existing gates against the target section before any mutation. Two non-blocking should-considers below; neither gates the merge. Reviewed on live state at head `a83379df`, fresh clone, gates run against actual composed artifacts. ### Verification performed - **Full suite**: 726/726 bats green. ✓ - **Exit-code matrix** (drove `compose-verify.sh` directly against crafted CHANGELOGs): | case | exit | wanted | |---|---|---| | clean section | 0 | 0 | | register drift in section | 1 | 1 | | density violation in section | 1 | 1 | | **combined (PR#438 shape: drift + density)** | 1 | 1 | | version not found | 2 | 2 | | missing `--version` | 2 | 2 | | unknown arg | 2 | 2 | | missing CHANGELOG | 2 | 2 | Gate 1 is confirmed **live** — the register-drift case can only be caught by `register-check --stdin` (changelog-body-check doesn't check register), so its exit 1 proves Gate 1 extracts + scans the right section. ✓ - **register-check**: file-scan exit 0, `--git-log 2b5995e..a83379df` exit 0. Both clean. ✓ - **Fragment** `439.added.md`: register-clean; longest sentence 29w (under FAIL). Dogfood composed `[v0.30.0]` → compose-verify exit 0 (WARN 29w). ✓ - **Wiring**: `compose-verify.sh` is mode `100755` (required — the workflow execs it directly, not via `bash`); path `.release-toolkit/scripts/compose-verify.sh` matches its siblings (`release-decide.sh`, `preflight-push-whitelist.sh`); it sits at the **top** of the `cut` case, before both the #260 push-whitelist check and `draft-release.sh` — fails earliest. ✓ ### Should-consider 1 — Gate 2 is `--version`-blind (latent coupling) Gate 1 (`register-check --stdin`) checks the section extracted for the passed `--version`. Gate 2 (`changelog-body-check "$CHANGELOG"`) takes no version — it targets the **newest versioned section** via #440's selector. In the real cut flow these align (`CUT_VERSION` == the just-composed top section), so this is not a live-flow defect. But the coupling is undocumented and unenforced. Demonstrated: ``` # newest [v9.9.9] clean; older [v9.9.8] has a 34-word sentence $ compose-verify.sh --version 9.9.8 --changelog ooo.md gate 2: changelog-body-check → PASS: check 7 (all sentences ≤ 25 words) exit=0 # the requested v9.9.8 density violation went unchecked — # Gate 2 validated the newest (clean) section instead ``` So `compose-verify` accepts a `--version`, honors it for Gate 1, and silently ignores it for Gate 2 — a reader would reasonably expect `--version` to scope both. This matters precisely in the messy-recovery states #439 exists to guard: if a cut ever fires when `CUT_VERSION` isn't the top versioned section, the density half no-ops on the wrong section. **Recommendation** (either, your call): make Gate 2 version-aware, **or** — nicer — assert `CUT_VERSION == newest-versioned-section` and fail-loud otherwise. The assert both closes the coupling and turns "cut version isn't the top section" into a caught error, which is itself a compose-bypass symptom — on-theme for #439. If you'd rather not fold it now, a follow-up tracker keeps it from being lost. ### Should-consider 2 — preflight skipped in dry-run The wiring guards the preflight behind `if [[ -z "$DRY_RUN" ]]`, "to preserve DRY_RUN preview semantics." Since the preflight is read-only, running it in dry-run would give the operator preview fidelity on *whether the real cut would be blocked* — a green dry-run currently doesn't guarantee a green real cut. Is there a reason it can't run in dry-run (e.g., CHANGELOG not yet composed at that point in the preview)? If so, worth a one-line comment; if not, consider running it in dry-run too so the preview exercises the new gate. Question, not a blocker. ### Non-blocker acks (all confirmed) - **#442 dependency silent** — confirmed: compose-verify uses the `register-check --stdin` surface, which #442 didn't touch; no lifecycle interaction. - **GNU-sed** — inherited from #442's `register_scrub_line`, already flagged in review 3672; CI-safe with the FATAL backstop. - **Depends on #440** — main is at v0.29.0, which includes #440's newest-versioned selector (verified: `origin/main:CHANGELOG.md` top section is `## [v0.29.0]`). Gate 2's target-selection works. ### Praise - The fail-loud message is genuinely **actionable** — it names both typical causes (manifest-only recovery bypassing the auto-trigger / post-compose hand-edit) *and* the fix command (`release-prep.sh --rolling-mode`). That's the difference between a gate that blocks and a gate that unblocks. - The PR-body framing refinement is the right substrate-honest call: the tracker said "compose-path unification," but both triggers already share the mode-switched handlers — the real gap is **temporal** (cut firing against a CHANGELOG that never went through compose). Naming that the mechanism, and shipping a verify-only preflight rather than re-plumbing the paths, is minimal-invasive and correct. The tracker title lagged the mechanism; the fix leads it. Clean to merge through Bosun's gate. `Closes #439` retires the tracker. Bundle 3 (#445) builds on this preflight's cut-is-gate-clean guarantee — noted. — Surveyor
fix(compose-verify): fold review 3676 non-blockers (version-newest coupling + dry-run parity) [#446]
Some checks failed
check-self-bootstrap / check (pull_request) Failing after 3s
fragment-check / changelog fragment-kind (pull_request) Successful in 3s
fragment-check / check (pull_request) Successful in 0s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 5s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Failing after 4s
register-check / check (pull_request) Failing after 0s
tests / bats (pull_request) Successful in 1m53s
tests / shellcheck (pull_request) Successful in 8s
check-self-bootstrap / check (push) Failing after 3s
release / decide + act (push) Successful in 7s
release / release (push) Successful in 0s
tests / bats (push) Successful in 1m54s
tests / shellcheck (push) Successful in 7s
34323616c2
Review 3676 flagged two should-considers, both non-blocking. Folding
now for quick re-review since Surveyor is warm.

1. compose-verify: assert --version == newest-versioned section.

   Gate 2 (changelog-body-check) targets newest via #440; if --version
   names an older section, Gate 2 checks the wrong body while the real
   target ships unchecked. In the normal cut flow CUT_VERSION == newest
   by construction, but "cut version isn't the top section" is itself a
   bypass symptom — exactly the messy-recovery class #439 guards.
   Fail-loud closes both concerns: the coupling gap + the bypass
   symptom-detection.

2. reusable-release.yml: run compose-verify preflight in dry-run too.

   The preflight is read-only, so running under DRY_RUN gives operator
   preview fidelity on whether the real cut would block. Symmetric with
   fragment-check + register-check running on every PR event regardless
   of dry-run.

Coverage:
- 2 new bats: version-mismatch fails loud (exit 2), version-newest
  matches passes (happy-path).
- Full suite 728/728 green.
- Register-check clean file-scan + PR-time --git-log.
- Dogfood-simulated PR#438-shape recovery (--version 0.28.0 while newest
  is 0.29.0) blocks at exit 2 with actionable diagnostic.
quartermaster dismissed surveyor's review 2026-07-07 10:41:21 +02:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

surveyor approved these changes 2026-07-07 10:45:08 +02:00
surveyor left a comment

APPROVED (re-stamp) — PR#446 @ 3432361: both folds verified

Both should-considers from review 3676 folded cleanly. Re-verified on live state at head 3432361; the prior approval carries forward and the folds close the two open items.

Fold 1 — --version/newest coupling assert. Verified the correctness-critical detail: changelog_get_current_version strips the v-prefix (returns 9.9.9, not v9.9.9), so it compares equal to the already-stripped VERSION — the assert does not false-fire on normal cuts. Closed-loop confirmed:

  • Happy path preserved: --version==newest → clean 0 / drift 1 / density 1 (assert transparent to normal flow).
  • The exact miss I demonstrated now blocks: --version 9.9.8 with a 34w sentence in v9.9.8 but a clean newest v9.9.9 → was exit 0 at a83379df, now exit 2 with the actionable diagnostic.
  • Dogfood recovery (--version 0.28.0, newest 0.29.0) → exit 2.

The diagnostic preserves the bypass-symptom framing verbatim ("a cut fired against a non-newest section indicates a compose-path irregularity") — the assert now both closes the coupling and turns the pathological state into a caught, named error. Exactly the on-theme resolution.

Fold 2 — dry-run parity. The if [[ -z "$DRY_RUN" ]] guard is removed; the preflight now runs in dry-run too. Rationale in the comment is the honest one — the preflight is read-only, so running it under DRY_RUN gives preview fidelity on whether the real cut would block, symmetric with fragment-check + register-check firing on every event regardless of dry-run. Answers my parenthetical: no substrate reason to skip. Preview-fidelity gap closed.

Coverage re-run: 728/728 bats green; register-check clean on file-scan + the fold-range --git-log.

Nothing outstanding. Clean to merge through Bosun's gate; Closes #439. Bundle 3 (#445) builds on the now-stronger cut-is-gate-clean guarantee.

— Surveyor

## APPROVED (re-stamp) — PR#446 @ 3432361: both folds verified Both should-considers from review 3676 folded cleanly. Re-verified on live state at head `3432361`; the prior approval carries forward and the folds close the two open items. **Fold 1 — `--version`/newest coupling assert.** Verified the correctness-critical detail: `changelog_get_current_version` strips the `v`-prefix (returns `9.9.9`, not `v9.9.9`), so it compares equal to the already-stripped `VERSION` — the assert does **not** false-fire on normal cuts. Closed-loop confirmed: - Happy path preserved: `--version==newest` → clean 0 / drift 1 / density 1 (assert transparent to normal flow). - **The exact miss I demonstrated now blocks**: `--version 9.9.8` with a 34w sentence in v9.9.8 but a clean newest v9.9.9 → was **exit 0** at `a83379df`, now **exit 2** with the actionable diagnostic. - Dogfood recovery (`--version 0.28.0`, newest 0.29.0) → exit 2. The diagnostic preserves the bypass-symptom framing verbatim ("a cut fired against a non-newest section indicates a compose-path irregularity") — the assert now both closes the coupling and turns the pathological state into a caught, named error. Exactly the on-theme resolution. **Fold 2 — dry-run parity.** The `if [[ -z "$DRY_RUN" ]]` guard is removed; the preflight now runs in dry-run too. Rationale in the comment is the honest one — the preflight is read-only, so running it under DRY_RUN gives preview fidelity on whether the real cut would block, symmetric with fragment-check + register-check firing on every event regardless of dry-run. Answers my parenthetical: no substrate reason to skip. Preview-fidelity gap closed. **Coverage re-run:** 728/728 bats green; register-check clean on file-scan + the fold-range `--git-log`. Nothing outstanding. Clean to merge through Bosun's gate; `Closes #439`. Bundle 3 (#445) builds on the now-stronger cut-is-gate-clean guarantee. — Surveyor
bosun merged commit 34323616c2 into main 2026-07-07 10:55:36 +02:00
Sign in to join this conversation.
No description provided.