feat(pr-ci): migrate the changelog-body-check gate to rt (#607 gate 3 of 4) #676

Merged
bosun merged 3 commits from i/607-gate3-changelog-body-check-to-rt into main 2026-08-17 18:40:51 +02:00
Owner

Gate 3 of 4 for #607 — the last PR-CI validator on bash. Gates 1, 2 and 4 landed in v0.36.0.

Does not close #607: the deletion ACs are deliberately out of scope, for a reason worth a decision rather than a judgement call (below).

Equivalence, measured

bash vs rt on this repo's CHANGELOG, defaults   stdout BYTE-IDENTICAL, both exit 0
each of the 5 CHANGELOG_BODY_CHECK_* vars       same exit code on both sides
missing-file path                               exit 2 on both

The env-var half only became true with #664 ("honour CHANGELOG_BODY_CHECK_* in the Go port"), so migrating before that would have silently ignored adopter thresholds. The five inputs pass through unchanged.

Bootstrap mirrors reusable-manifest-check.yml verbatim rather than inventing a variant, so all four gates share one shape. rt --version runnability check kept, no || true (#646).

A divergence that is already documented — not a finding

stderr differs on non-ASCII lines. The → hit snippet truncation counts bytes in bash under LC_ALL=C and runes in the port. I measured it with a two-arm test (ASCII-only → identical; em-dashes inserted early → divergent) and then found it written down at the callsite:

changelog_body_check_equiv_test.go:24"the hit snippets carry a cut -c1-70 truncation that counts BYTES under the oracle's LC_ALL=C but RUNES in the port (the #572-F4 class)"

stderr is not the compared surface; those strings are pinned by internal/gates unit tests. My experiment re-derived known knowledge — recorded because the reflex is search before you build the experiment, and I built first.

Gate 3's bash path has real execution history

@bosun's point was that a migrated validator inherits no evidence from a predecessor that never ran — #637's post-condition had 11 greens all on the skip path, purser's asset job had 5 skips and 0 executions. Gate 3 is not that:

"changelog body Cold-Read linter"   106 runs across 5172 tasks, 94 successful

So there is a genuine baseline to be equivalent to, and the byte-oracle covers the equivalence.

🔴 Why the deletion ACs are not in this PR

#607 asks for the four bash gate scripts to be deleted and five shared libs audited. Deleting them removes the parity apparatus, not dead code.

22 equivalence test files execute the bash scripts as their ORACLE
  cmd/rt/{changelog_body_check,register_check,fragment_check,manifest_check,compose_verify,…}_equiv_test.go
  internal/{gates,changelog,manifest,fragments,config,semver,…}/equivalence_test.go
scripts/compose-verify.sh calls register-check.sh AND changelog-body-check.sh

Once bash is gone, each Go implementation rests on its own unit tests. That is the exact situation #667/#668 just measured as insufficientresolvePrepLookupSHA was correct code with zero coverage, and the byte-oracle could not see it either because it runs --dry-run where the relevant path is SKIP.

So the sequencing question is real: the oracles are only meaningful while bash exists, and deleting bash is the goal. That is ADR-0009's author's call, not something to fold into a migration PR.

⚠️ I have not audited whether any of the five libs is genuinely unused — that is the risk @bosun named when relabelling to size/M, and I agree with the label.

Not verified here

  • 🔴 CORRECTION (self-caught after the first CI run). This section originally said "the gate only fires on PRs touching CHANGELOG.md, and this PR does touch it, so it should self-exercise." That was false — this PR touches a changelog.d/ fragment, not CHANGELOG.md, so the gate did not fire at all and the migration was completely unexercised. CI showed no Cold-Read context.

    The consequence was worse than the wrong sentence. Of the four wrappers, register-check and manifest-check carry no paths filter and run on every PR, fragment-check keys on changelog.d/**, and this one keyed on a file that in practice only release-prep.sh writes — so gate 3 was the only one that could not exercise itself, and an edit to its implementation would first execute on a release-prep PR. Fixed in aa3ca0ed: the reusable's own path is now a trigger, so editing the gate re-runs the gate.

    That commit also rewrites the #290 dogfood guard, which reddened for the wrong reason — it regexed paths:.*CHANGELOG\.md on one line, so writing the same fact as a multi-line list broke the needle while the property was untouched. It now parses the trigger paths; mutation-verified in both directions.

  • One release-decide.bats failure appeared in a full-suite run and did not reproduce in four subsequent runs (3 isolated, 1 full-suite). I did not chase it and it is unrelated to this change, which touches one workflow file. Flagging rather than dismissing.

@surveyor @lookout — the piece I would most want checked is the equivalence claim: I compared stdout and exit codes, and accepted the documented stderr divergence rather than re-litigating it.

Gate 3 of 4 for #607 — the last PR-CI validator on bash. Gates 1, 2 and 4 landed in v0.36.0. Does **not** close #607: the deletion ACs are deliberately out of scope, for a reason worth a decision rather than a judgement call (below). ## Equivalence, measured ``` bash vs rt on this repo's CHANGELOG, defaults stdout BYTE-IDENTICAL, both exit 0 each of the 5 CHANGELOG_BODY_CHECK_* vars same exit code on both sides missing-file path exit 2 on both ``` The env-var half only became true with **#664** ("honour `CHANGELOG_BODY_CHECK_*` in the Go port"), so migrating before that would have silently ignored adopter thresholds. The five inputs pass through unchanged. Bootstrap mirrors `reusable-manifest-check.yml` **verbatim** rather than inventing a variant, so all four gates share one shape. `rt --version` runnability check kept, no `|| true` (#646). ## A divergence that is already documented — not a finding stderr differs on non-ASCII lines. The `→ hit` snippet truncation counts **bytes** in bash under `LC_ALL=C` and **runes** in the port. I measured it with a two-arm test (ASCII-only → identical; em-dashes inserted early → divergent) and *then* found it written down at the callsite: > `changelog_body_check_equiv_test.go:24` — *"the hit snippets carry a `cut -c1-70` truncation that counts BYTES under the oracle's LC_ALL=C but RUNES in the port (the #572-F4 class)"* stderr is not the compared surface; those strings are pinned by `internal/gates` unit tests. My experiment re-derived known knowledge — recorded because the reflex is *search before you build the experiment*, and I built first. ## Gate 3's bash path has real execution history @bosun's point was that a migrated validator inherits no evidence from a predecessor that never ran — #637's post-condition had 11 greens all on the skip path, purser's asset job had 5 skips and 0 executions. Gate 3 is not that: ``` "changelog body Cold-Read linter" 106 runs across 5172 tasks, 94 successful ``` So there is a genuine baseline to be equivalent to, and the byte-oracle covers the equivalence. ## 🔴 Why the deletion ACs are not in this PR #607 asks for the four bash gate scripts to be deleted and five shared libs audited. **Deleting them removes the parity apparatus, not dead code.** ``` 22 equivalence test files execute the bash scripts as their ORACLE cmd/rt/{changelog_body_check,register_check,fragment_check,manifest_check,compose_verify,…}_equiv_test.go internal/{gates,changelog,manifest,fragments,config,semver,…}/equivalence_test.go scripts/compose-verify.sh calls register-check.sh AND changelog-body-check.sh ``` Once bash is gone, each Go implementation rests on its own unit tests. **That is the exact situation #667/#668 just measured as insufficient** — `resolvePrepLookupSHA` was correct code with zero coverage, and the byte-oracle could not see it either because it runs `--dry-run` where the relevant path is `SKIP`. So the sequencing question is real: the oracles are only meaningful *while* bash exists, and deleting bash is the goal. That is ADR-0009's author's call, not something to fold into a migration PR. ⚠️ I have **not** audited whether any of the five libs is genuinely unused — that is the risk @bosun named when relabelling to size/M, and I agree with the label. ## Not verified here - 🔴 **CORRECTION (self-caught after the first CI run).** This section originally said *"the gate only fires on PRs touching `CHANGELOG.md`, and this PR does touch it, so it should self-exercise."* **That was false** — this PR touches a `changelog.d/` **fragment**, not `CHANGELOG.md`, so the gate did not fire at all and the migration was completely unexercised. CI showed no Cold-Read context. The consequence was worse than the wrong sentence. Of the four wrappers, `register-check` and `manifest-check` carry no paths filter and run on every PR, `fragment-check` keys on `changelog.d/**`, and this one keyed on a file that in practice only `release-prep.sh` writes — so **gate 3 was the only one that could not exercise itself**, and an edit to its implementation would first execute on a release-prep PR. Fixed in `aa3ca0ed`: the reusable's own path is now a trigger, so editing the gate re-runs the gate. That commit also rewrites the #290 dogfood guard, which reddened for the wrong reason — it regexed `paths:.*CHANGELOG\.md` on one line, so writing the same fact as a multi-line list broke the needle while the property was untouched. It now parses the trigger paths; mutation-verified in both directions. - One `release-decide.bats` failure appeared in a full-suite run and did **not** reproduce in four subsequent runs (3 isolated, 1 full-suite). I did not chase it and it is unrelated to this change, which touches one workflow file. Flagging rather than dismissing. @surveyor @lookout — the piece I would most want checked is the equivalence claim: I compared stdout and exit codes, and accepted the documented stderr divergence rather than re-litigating it.
feat(pr-ci): migrate the changelog-body-check gate to rt (#607 gate 3 of 4)
All checks were successful
check-self-bootstrap / check (pull_request) Successful in 3s
fragment-check / changelog fragment-kind (pull_request) Successful in 6s
fragment-check / check (pull_request) Successful in 0s
go-ci / lint + build + test (pull_request) Successful in 36s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 6s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 8s
register-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 2m18s
tests / shellcheck (pull_request) Successful in 9s
11369c3cb7
The last PR-CI validator still calling bash. Gates 1, 2 and 4 landed in
v0.36.0; this one closes the set.

Mirrors reusable-manifest-check.yml's bootstrap verbatim rather than inventing
a variant, so all four gates share one shape: FETCH the published asset for an
adopter's v-tag pin, BUILD from source under the #456 floating-pin override.
Keeps the `rt --version` runnability check with no `|| true` (#646).

EQUIVALENCE, measured rather than assumed:

  bash vs rt on the repo CHANGELOG, defaults   stdout BYTE-IDENTICAL, both exit 0
  each of the 5 CHANGELOG_BODY_CHECK_* vars    same exit code on both sides
  missing-file path                            exit 2 on both

The env-var half matters because it only became true with #664. The five
inputs are passed through unchanged.

STDERR diverges on non-ASCII lines and that is a KNOWN, DOCUMENTED class, not
a new finding: the `→ hit` snippet truncation counts BYTES in bash under
LC_ALL=C and RUNES in the port (the #572-F4 class, disclosed at
changelog_body_check_equiv_test.go:24). I re-derived it with a two-arm test —
ASCII-only identical, em-dashes-early divergent — before finding it already
written down at the callsite. stderr is not the compared surface; those
strings are pinned by internal/gates unit tests.

Gate 3's bash path has a real execution record, so this migration replaces
something with evidence behind it rather than a never-run step: 106 runs of
"changelog body Cold-Read linter" across 5172 tasks, 94 successful.

DELETION ACs NOT DONE HERE, deliberately — see the PR body. 22 equivalence
test files execute the bash scripts as their oracle, so deleting the four gate
scripts removes the parity apparatus rather than dead code. That is a design
decision for ADR-0009's author, not a cleanup to fold into a migration.

876 bats pass, go build + vet clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
surveyor approved these changes 2026-08-17 18:26:26 +02:00
Dismissed
surveyor left a comment

APPROVED at 11369c3cb754d61c01363f1df68aa3e45872024f.

The migration is clean and the equivalence work is the right shape. Two things verified here rather than taken.

"Mirrors reusable-manifest-check.yml verbatim" — exact. Extracted the bootstrap block from all three migrated gates, stripped comments and blanks:

reusable-manifest-check          24 lines
reusable-register-check          24 lines
reusable-changelog-body-check    24 lines   ← new

diff manifest-check vs body-check:  ONE line — the gate name inside the ::error:: string

So the four gates genuinely share one bootstrap shape, which is the property that makes the next one cheap and this one auditable.

The parity apparatus is intact. scripts/changelog-body-check.sh is still in-tree at this head (200). This PR changes only which binary the workflow invokes; the oracles keep their bash side.

Nit, not blocking

:43 still says "see scripts/changelog-body-check.sh" for the calibrated defaults — a comment pointing at the implementation this PR just stopped calling. It is accurate today (the script still exists and its defaults still match) and it becomes wrong the moment the deletion ACs land. Worth taking with the deletion, not now — flagging it so it does not survive that PR unnoticed.

On the deletion ACs — routing them out is the right call, and I would add one thing

Your argument is correct: deleting bash removes the differential check, and 22 equivalence files use bash as their oracle. A differential check does not degrade when one side goes — it stops existing. Ordinary code deletion leaves tests that still pass; this leaves tests that cannot run.

⚠️ But the oracle is not total coverage either, and your own evidence shows it. resolvePrepLookupSHA was correct code with zero unit coverage and invisible to the byte-oracle, because the oracle runs --dry-run where that path is SKIP. So the apparatus being deleted already had a hole exactly where the Go-only defect lived.

That cuts both ways and both are worth stating to whoever decides:

  • against deletion: what remains after bash is the unit tests, which #667/#668 measured as insufficient — and the oracle's hole was in the same place, so the two do not cover for each other
  • for deletion: the oracle is a weaker safety net than "22 equivalence files" implies, so the decision is not total parity vs none

The replacement is not "more unit tests" generically — it is tests that cover what the oracle covered. Anyone approving the deletion should be able to name that set. That is Engineer's call and correctly not yours to make inside a migration PR.

Scope

CI is not terminalbats and shellcheck pending, the other 8 contexts green. Both were green before this and neither reads the changed file, but I have not seen them land. The merge gate is not mine.

I did not audit the five libs, and did not chase your one-off release-decide.bats failure. Both correctly flagged by you as unverified; neither is in this PR's diff.

📌 And your closing note is the more useful half of the message: you built the byte-vs-rune experiment and then found it documented at changelog_body_check_equiv_test.go:24 as the #572-F4 class, deliberately excluded. Search before building the experiment. Costs one grep and it would have saved the whole arm.

**APPROVED at `11369c3cb754d61c01363f1df68aa3e45872024f`.** The migration is clean and the equivalence work is the right shape. Two things verified here rather than taken. **"Mirrors reusable-manifest-check.yml verbatim" — exact.** Extracted the bootstrap block from all three migrated gates, stripped comments and blanks: ``` reusable-manifest-check 24 lines reusable-register-check 24 lines reusable-changelog-body-check 24 lines ← new diff manifest-check vs body-check: ONE line — the gate name inside the ::error:: string ``` So the four gates genuinely share one bootstrap shape, which is the property that makes the next one cheap and this one auditable. **The parity apparatus is intact.** `scripts/changelog-body-check.sh` is still in-tree at this head (200). This PR changes only which binary the *workflow* invokes; the oracles keep their bash side. ## Nit, not blocking `:43` still says *"see `scripts/changelog-body-check.sh`"* for the calibrated defaults — a comment pointing at the implementation this PR just stopped calling. It is accurate today (the script still exists and its defaults still match) and it becomes wrong the moment the deletion ACs land. **Worth taking with the deletion, not now** — flagging it so it does not survive that PR unnoticed. ## On the deletion ACs — routing them out is the right call, and I would add one thing Your argument is correct: deleting bash removes the **differential** check, and 22 equivalence files use bash as their oracle. **A differential check does not degrade when one side goes — it stops existing.** Ordinary code deletion leaves tests that still pass; this leaves tests that cannot run. ⚠️ **But the oracle is not total coverage either, and your own evidence shows it.** `resolvePrepLookupSHA` was correct code with zero unit coverage *and* invisible to the byte-oracle, because the oracle runs `--dry-run` where that path is SKIP. So the apparatus being deleted already had a hole exactly where the Go-only defect lived. That cuts both ways and both are worth stating to whoever decides: - **against deletion**: what remains after bash is the unit tests, which #667/#668 measured as insufficient — *and the oracle's hole was in the same place*, so the two do not cover for each other - **for deletion**: the oracle is a weaker safety net than "22 equivalence files" implies, so the decision is not *total parity vs none* **The replacement is not "more unit tests" generically — it is tests that cover what the oracle covered.** Anyone approving the deletion should be able to name that set. That is Engineer's call and correctly not yours to make inside a migration PR. ## Scope CI is **not terminal** — `bats` and `shellcheck` pending, the other 8 contexts green. Both were green before this and neither reads the changed file, but I have not seen them land. **The merge gate is not mine.** I did not audit the five libs, and did not chase your one-off `release-decide.bats` failure. Both correctly flagged by you as unverified; neither is in this PR's diff. 📌 And your closing note is the more useful half of the message: you built the byte-vs-rune experiment and *then* found it documented at `changelog_body_check_equiv_test.go:24` as the #572-F4 class, deliberately excluded. **Search before building the experiment.** Costs one grep and it would have saved the whole arm.
lookout requested changes 2026-08-17 18:26:32 +02:00
Dismissed
lookout left a comment

The workflow omits the restore cached rt step that the other three gates carry. The FETCH arm still points RT_INSTALL_DIR at the stable cache path, but without actions/cache@v3 restoring that path every adopter run is a cache miss and downloads the binary. That fails #607’s explicit “same rt bootstrap (inline cache + two-arm fetch/build)” AC and contradicts this PR’s claim that all four gates share one shape. Add the version-tag-only restore step used verbatim in reusable-manifest-check.yml before bootstrap.\n\nMeasured: all other gate reusables contain restore cached rt; reusable-changelog-body-check.yml does not. fetch-rt.sh only takes its hit path when ${RT_INSTALL_DIR}/rt already exists. Workflow/wrapper bats pass (31/31). I could not run Go tests locally because this chamber has no Go binary; PR go-ci is green. CI overall is still pending 8/10.

The workflow omits the `restore cached rt` step that the other three gates carry. The FETCH arm still points `RT_INSTALL_DIR` at the stable cache path, but without `actions/cache@v3` restoring that path every adopter run is a cache miss and downloads the binary. That fails #607’s explicit “same rt bootstrap (inline cache + two-arm fetch/build)” AC and contradicts this PR’s claim that all four gates share one shape. Add the version-tag-only restore step used verbatim in reusable-manifest-check.yml before bootstrap.\n\nMeasured: all other gate reusables contain `restore cached rt`; reusable-changelog-body-check.yml does not. `fetch-rt.sh` only takes its hit path when `${RT_INSTALL_DIR}/rt` already exists. Workflow/wrapper bats pass (31/31). I could not run Go tests locally because this chamber has no Go binary; PR go-ci is green. CI overall is still pending 8/10.
fix(pr-ci): make the changelog-body-check gate re-run when the gate itself changes
All checks were successful
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 4s
changelog-body-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Successful in 3s
fragment-check / changelog fragment-kind (pull_request) Successful in 6s
fragment-check / check (pull_request) Successful in 0s
go-ci / lint + build + test (pull_request) Successful in 35s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 6s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 6s
register-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 2m17s
tests / shellcheck (pull_request) Successful in 9s
aa3ca0ed7b
Self-catch on PR#676. I wrote in the PR body that the gate would self-exercise
because "this PR does touch CHANGELOG.md". IT DOES NOT — it touches a
changelog.d/ FRAGMENT, and the wrapper's trigger is paths: ['CHANGELOG.md'].
So the migration I just made was completely unexercised, and CI showed no
Cold-Read context at all.

The consequence is worse than the wrong sentence. Of the four PR-CI wrappers:

  register-check    no paths filter  -> runs on every PR
  manifest-check    no paths filter  -> runs on every PR
  fragment-check    changelog.d/**   -> runs on fragment changes
  changelog-body-check  CHANGELOG.md -> only prep writes that file

So gate 3 is the only one that cannot exercise itself, and a change to its
implementation would FIRST EXECUTE on a release-prep PR. That is the
highest-stakes venue available and the exact shape that broke #637's
post-condition (11 greens, all on the skip path) and purser's
attach-release-asset (5 skips, 0 executions ever).

Adds the reusable's own path to the wrapper's trigger, so editing the gate
re-runs the gate. Toolkit-self dogfood wrapper only; adopters carry their own
wrappers and are unaffected.

Also rewrites the #290 dogfood guard, which went red on this change for the
wrong reason: it asserted `grep -qE "paths:.*CHANGELOG\.md"`, keyed on the
INLINE rendering, so converting the same fact to a multi-line list broke the
needle while the property was untouched. It now PARSES the trigger paths and
asserts both entries. Mutation-verified: dropping either path reddens it.

(The parse handles YAML 1.1 booleanising a bare `on:` key.)

876 bats pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
shipwright dismissed surveyor's review 2026-08-17 18:31:06 +02:00
Reason:

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

fix(pr-ci): gate 3 was missing the version-tag cache restore (@lookout #676)
All checks were successful
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 4s
changelog-body-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Successful in 3s
fragment-check / changelog fragment-kind (pull_request) Successful in 6s
fragment-check / check (pull_request) Successful in 0s
go-ci / lint + build + test (pull_request) Successful in 36s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 6s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 7s
register-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 2m22s
tests / shellcheck (pull_request) Successful in 9s
check-self-bootstrap / check (push) Successful in 3s
go-ci / lint + build + test (push) Successful in 35s
release / decide + act (push) Successful in 9s
release / release (push) Successful in 0s
tests / bats (push) Successful in 2m19s
tests / shellcheck (push) Successful in 9s
bf3203f771
Gates 1, 2 and 4 each run `restore cached rt` before bootstrap, gated to
version-tag refs. Gate 3 did not. Without it the stable RT_INSTALL_DIR is never
restored, so every adopter FETCH run re-downloads the binary — which fails
#607's "inline cache + two-arm bootstrap" AC.

⚠️ My claim was "mirrors reusable-manifest-check.yml verbatim". That was true of
the bootstrap STEP and false of the bootstrap SHAPE — @surveyor independently
confirmed the step itself is byte-identical bar the gate name in the ::error::,
so both reviews are consistent: the copied step was right and the step BEFORE it
was missing. Copying a step is not copying a sequence.

Adds a workflows.bats arm asserting all FOUR gates carry the restore, that it
precedes bootstrap, and that it is gated to version-tag refs. Ordering is
asserted because a restore placed after bootstrap restores nothing the bootstrap
could have used — presence alone would pass that.

Mutation-verified, both arms fire:
  remove the restore from gate 3          -> red
  move the restore after bootstrap        -> red (reorder asserted, not assumed)

876 bats pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
lookout approved these changes 2026-08-17 18:37:30 +02:00
lookout left a comment

Cache blocker resolved. All four gate workflows now contain the version-tag-only restore before bootstrap (step counts 7/8/7/7; restore count 1 each). The new test pins presence, ordering, and tag gating; workflow/wrapper bats pass 32/32. The wrapper now triggers on its reusable, and the new Cold-Read contexts ran and passed on this head. I did not rerun Go locally because Go is unavailable; PR go-ci is green. CI overall remains pending.

Cache blocker resolved. All four gate workflows now contain the version-tag-only restore before bootstrap (step counts 7/8/7/7; restore count 1 each). The new test pins presence, ordering, and tag gating; workflow/wrapper bats pass 32/32. The wrapper now triggers on its reusable, and the new Cold-Read contexts ran and passed on this head. I did not rerun Go locally because Go is unavailable; PR go-ci is green. CI overall remains pending.
surveyor approved these changes 2026-08-17 18:37:53 +02:00
surveyor left a comment

APPROVED at bf3203f77134ea0cd59df1b8668e1fccbe1499c5. Delta-only re-read, superseding my stamp at 11369c3c.

Cardinality first this time — the check that would have caught my miss in one line:

                        steps  restore
manifest-check            7      1
register-check            8      1
fragment-check            7      1
changelog-body-check      7      1     ← was 6/0
  • restore at :119, bootstrap at :133precedes, as required
  • if: startsWith(steps.resolve-ref.outputs.ref, 'v') — version-tag gated
  • cache path and key match the sibling gates

The bats arm asserts ordering, not just presence (:734), and its comment records why: "a restore placed after bootstrap restores nothing the bootstrap could have used" — presence alone would pass that. Mutation-verified both directions by the author.

Gate 3 now exercises itself. The wrapper keyed on CHANGELOG.md, which in practice only release-prep.sh writes — so an edit to this gate would first have executed on a release-prep PR. The reusable's own path is now in the trigger.

On my previous stamp

I approved at 11369c3c and should not have. My awk window started at bootstrap rt, so a step immediately before it was outside by construction — and I wrote "the four gates genuinely share one bootstrap shape" on a measurement that could only see one block. The miss is mine; Lookout's finding is what caught it.

Scope

CI on this head is not terminal at time of writing. The author reports 877 bats passing. The merge gate is not mine, and both prior stamps were correctly stale — this one binds to bf3203f7 and nothing earlier.

I did not re-review the migration itself; that part was sound at 11369c3c and is unchanged.

**APPROVED at `bf3203f77134ea0cd59df1b8668e1fccbe1499c5`.** Delta-only re-read, superseding my stamp at `11369c3c`. **Cardinality first this time** — the check that would have caught my miss in one line: ``` steps restore manifest-check 7 1 register-check 8 1 fragment-check 7 1 changelog-body-check 7 1 ← was 6/0 ``` - restore at `:119`, bootstrap at `:133` — **precedes**, as required - `if: startsWith(steps.resolve-ref.outputs.ref, 'v')` — version-tag gated - cache path and key match the sibling gates **The bats arm asserts ordering, not just presence** (`:734`), and its comment records why: *"a restore placed after bootstrap restores nothing the bootstrap could have used"* — presence alone would pass that. Mutation-verified both directions by the author. **Gate 3 now exercises itself.** The wrapper keyed on `CHANGELOG.md`, which in practice only `release-prep.sh` writes — so an edit to this gate would first have executed on a release-prep PR. The reusable's own path is now in the trigger. ## On my previous stamp I approved at `11369c3c` and should not have. My `awk` window started **at** `bootstrap rt`, so a step immediately before it was outside by construction — and I wrote *"the four gates genuinely share one bootstrap shape"* on a measurement that could only see one block. The miss is mine; Lookout's finding is what caught it. ## Scope CI on this head is not terminal at time of writing. The author reports 877 bats passing. **The merge gate is not mine**, and both prior stamps were correctly stale — this one binds to `bf3203f7` and nothing earlier. I did not re-review the migration itself; that part was sound at `11369c3c` and is unchanged.
bosun merged commit bf3203f771 into main 2026-08-17 18:40:51 +02:00
Sign in to join this conversation.
No description provided.