fix(mirror): give the adopter-pin PR a body that can pass fragment-check (#1394) #1395

Merged
bosun merged 1 commit from i/1394-adopter-pin-pr-can-pass-its-own-gates into main 2026-09-06 22:39:46 +02:00
Owner

set-adopter-pin opens a PR after every mirror publish, and it carried neither a changelog.d/ fragment nor a no-changelog: declaration. fragment-check is a required context on main, so the job opened a PR that could not merge for a reason with nothing to do with its diff. #1391 sat red until @bosun edited the body by hand.

The decision the tracker asked to be made deliberately

A declaration, not a fragment. A fragment written by this job would land in the next release's notes while describing the pins of the release that just published. That is precisely the case #498 built the declaration for, and it is why the reason string says docs currency rather than something procedural.

The body is now a file

.forgejo/pr-body/adopter-pin.md, not a shell string — and the reason is the arm, not tidiness. A heredoc inside the workflow's block scalar cannot be graded without carrying YAML indentation into the PR body, and extracting a shell assignment by regex would grade a reconstruction rather than the artifact. As a file, the test reads the exact bytes the job sends.

(Implementer pre-flight: no new dependency and no wrapper. FragmentCoverage is the repo's existing gate and the arm calls it directly rather than re-implementing the grammar — the setter and the checker share one computation, which is #1378's own rule applied here.)

Two arms, because one is not enough

arm claim
TestAdopterPinBodySatisfiesFragmentCoverage the file passes the real FragmentCoverage
TestSetAdopterPinSendsTheBodyFileItIsGradedFrom the workflow reads that path and assigns it to the $body the payload carries

The second exists because a correct body that nothing reads is worth nothingcrew-doctrine#163's WIRE region, where the producer and the consumer are each individually fine.

Mutations

Each run separately, against a snapshot of the file rather than HEAD (crew-doctrine#144: in a tree that already carries uncommitted work, an applied-check against HEAD is dominated by that work and a sed that never matched reads as applied). The applied-count is printed beside every result, and an unmutated control at both ends reports applied=0if the control reported the same number as the mutants, the check would be measuring the tree.

body loses the declaration          rc=1  applied=1   body arm
declaration with an empty reason    rc=1  applied=2   body arm
two declarations                    rc=1  applied=1   body arm
workflow reads a different path     rc=1  applied=2   wire arm
payload stops carrying $body        rc=1  applied=2   wire arm
file read but not assigned          rc=1  applied=2   wire arm
control, both ends                  rc=0  applied=0   —

The comment-strip is measured, not assumed — and the first draft could not measure it

The wire arm strips comments before asserting. My first attempt to prove that clause load-bearing failed for an interesting reason: nothing in the step's comments carried the needle, so removing the path from the code removed it from the file entirely and the arm reddened without exercising the strip. The clause was inert, which is the same shape as the seventh region I wrote up an hour ago.

I added the path to the comment — where a reader wants it anyway — and the mutation then discriminates:

path removed from CODE only, strip ON    -> RED
path removed from CODE only, strip OFF   -> GREEN   (the comment rescues it)

Not changed

fragment-check's rules. The gate is right; the generated body was wrong. Third AC, and it is the one I would have been most tempted to violate — widening the gate to exempt bot PRs would have made #1391 green and left every future adopter-pin PR ungraded.

Verification

go build · go vet · gofmt -l · golangci-lint run — 0 issues · go test ./... 29 ok · scripts/workflow-parse-check.sh 36/36 · rt gitea-twin --check rc=0 (twin regenerated by --write, not hand-edited) · bats tests/workflows.bats 126/126 · rt fragment-check changelog.d rc=0. Base 4961795e, behind=0.

⚠️ One instrument note: my first gitea-twin --check reported check_rc=0 while printing ::error::. I had taken $? through a | tail -4. ¶9, on the gate that tells you the twin is stale.

Requesting @surveyor.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG

`set-adopter-pin` opens a PR after every mirror publish, and it carried neither a `changelog.d/` fragment nor a `no-changelog:` declaration. `fragment-check` is a **required** context on main, so the job opened a PR that could not merge for a reason with nothing to do with its diff. `#1391` sat red until @bosun edited the body by hand. ## The decision the tracker asked to be made deliberately **A declaration, not a fragment.** A fragment written by this job would land in the **next** release's notes while describing the pins of the release that just published. That is precisely the case `#498` built the declaration for, and it is why the reason string says *docs currency* rather than something procedural. ## The body is now a file `.forgejo/pr-body/adopter-pin.md`, not a shell string — and the reason is the arm, not tidiness. **A heredoc inside the workflow's block scalar cannot be graded without carrying YAML indentation into the PR body**, and extracting a shell assignment by regex would grade a *reconstruction* rather than the artifact. As a file, the test reads the exact bytes the job sends. *(Implementer pre-flight: no new dependency and no wrapper. `FragmentCoverage` is the repo's existing gate and the arm calls it directly rather than re-implementing the grammar — the setter and the checker share one computation, which is `#1378`'s own rule applied here.)* ## Two arms, because one is not enough | arm | claim | |---|---| | `TestAdopterPinBodySatisfiesFragmentCoverage` | the file passes the **real** `FragmentCoverage` | | `TestSetAdopterPinSendsTheBodyFileItIsGradedFrom` | the workflow **reads that path** and assigns it to the `$body` the payload carries | The second exists because **a correct body that nothing reads is worth nothing** — `crew-doctrine#163`'s WIRE region, where the producer and the consumer are each individually fine. ## Mutations Each run separately, against a **snapshot** of the file rather than `HEAD` (`crew-doctrine#144`: in a tree that already carries uncommitted work, an applied-check against `HEAD` is dominated by that work and a `sed` that never matched reads as applied). The applied-count is printed beside every result, and an unmutated control at both ends reports `applied=0` — **if the control reported the same number as the mutants, the check would be measuring the tree.** ``` body loses the declaration rc=1 applied=1 body arm declaration with an empty reason rc=1 applied=2 body arm two declarations rc=1 applied=1 body arm workflow reads a different path rc=1 applied=2 wire arm payload stops carrying $body rc=1 applied=2 wire arm file read but not assigned rc=1 applied=2 wire arm control, both ends rc=0 applied=0 — ``` ### The comment-strip is measured, not assumed — and the first draft could not measure it The wire arm strips comments before asserting. **My first attempt to prove that clause load-bearing failed for an interesting reason: nothing in the step's comments carried the needle**, so removing the path from the code removed it from the file entirely and the arm reddened without exercising the strip. The clause was inert, which is the same shape as the seventh region I wrote up an hour ago. I added the path to the comment — where a reader wants it anyway — and the mutation then discriminates: ``` path removed from CODE only, strip ON -> RED path removed from CODE only, strip OFF -> GREEN (the comment rescues it) ``` ## Not changed **`fragment-check`'s rules.** The gate is right; the generated body was wrong. Third AC, and it is the one I would have been most tempted to violate — widening the gate to exempt bot PRs would have made `#1391` green and left every future adopter-pin PR ungraded. ## Verification `go build` · `go vet` · `gofmt -l` · **`golangci-lint run` — 0 issues** · `go test ./...` 29 ok · `scripts/workflow-parse-check.sh` 36/36 · `rt gitea-twin --check` rc=0 (twin regenerated by `--write`, not hand-edited) · `bats tests/workflows.bats` 126/126 · `rt fragment-check changelog.d` rc=0. Base `4961795e`, `behind=0`. ⚠️ One instrument note: my first `gitea-twin --check` reported `check_rc=0` while printing `::error::`. I had taken `$?` through a `| tail -4`. `¶9`, on the gate that tells you the twin is stale. Requesting @surveyor. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
fix(mirror): give the adopter-pin PR a body that can pass fragment-check
All checks were successful
readme-pin-check / check (pull_request) Successful in 32s
tests / workflow-schema (pull_request) Successful in 29s
tests / bats (pull_request) Successful in 31s
tests / dated-examples (pull_request) Successful in 31s
tests / shellcheck (pull_request) Successful in 23s
go-ci / lint + build + test (pull_request) Successful in 1m9s
go-ci / page landing-tree failure (pull_request) Has been skipped
tests / contract-paths (pull_request) Successful in 28s
register-check / register-drift check (pull_request) Successful in 49s
register-check / check (pull_request) Successful in 0s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 27s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 28s
workflow-parse-check / check (pull_request) Successful in 0s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 50s
gitea-twin-check / check (push) Successful in 5s
prepared-uncut-check / toolkit-self prepared-uncut controls (push) Successful in 6s
tests / workflow-schema (push) Successful in 4s
go-ci / record reviewed vs landed commit (push) Successful in 16s
check-self-bootstrap / check (push) Successful in 24s
tests / shellcheck (push) Successful in 19s
tests / bats (push) Successful in 30s
tests / dated-examples (push) Successful in 28s
tests / contract-paths (push) Successful in 26s
prepared-uncut-check / prepared-but-uncut release (push) Successful in 44s
prepared-uncut-check / check (push) Successful in 0s
go-ci / lint + build + test (push) Successful in 1m6s
release / decide + act (push) Successful in 1m4s
release / release (push) Successful in 0s
go-ci / page landing-tree failure (push) Has been skipped
release / fire-cut (push) Has been skipped
8d2b16f31e
set-adopter-pin opens a PR after every mirror publish. It carried neither a
changelog.d/ fragment nor a `no-changelog:` declaration, and fragment-check is a
REQUIRED context on main -- so the job opened a PR that could not merge, for a
reason with nothing to do with its diff. #1391 sat red until a human edited the
body by hand.

A DECLARATION rather than a fragment, decided rather than defaulted: a fragment
written here would land in the NEXT release's notes while describing the pins of
the release that just published. That is the case #498 built the declaration for.

The body is now a FILE, .forgejo/pr-body/adopter-pin.md, not a shell string. That
is what lets an arm grade the exact bytes the job sends: a heredoc inside the
workflow's block scalar cannot do it without carrying YAML indentation into the PR
body, and extracting a shell assignment by regex would grade a reconstruction.

Two arms, because one is not enough:

  TestAdopterPinBodySatisfiesFragmentCoverage   runs the real FragmentCoverage
                                                over the file
  TestSetAdopterPinSendsTheBodyFileItIsGradedFrom  asserts the workflow READS that
                                                path and assigns it to the $body
                                                the payload carries

The second exists because a correct body that nothing reads is worth nothing --
crew-doctrine#163's WIRE region, where producer and consumer are each fine.

Mutations, each run separately against a SNAPSHOT of the file (never against HEAD,
per crew-doctrine#144), with the applied-count printed beside every result and an
unmutated control at both ends reporting applied=0:

  body loses the declaration        -> body arm
  declaration with an empty reason  -> body arm
  two declarations                  -> body arm
  workflow reads a different path   -> wire arm
  payload stops carrying $body      -> wire arm
  file read but not assigned        -> wire arm

The wire arm strips comments, and that clause is measured rather than assumed: the
step's comment names the body path, so with the path removed from the CODE only,
strip ON reddens and strip OFF goes GREEN. The comment was added to the workflow
for a reader's sake and it made the mutation discriminate -- the first draft could
not, because nothing in the comments carried the needle.

No change to fragment-check's rules. The gate is right; the generated body was
wrong.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
surveyor approved these changes 2026-09-06 22:34:29 +02:00
surveyor left a comment

APPROVE — reviewed at 8d2b16f31e63d343aa28b49fd2ffdd6cf1e97147, base clean (behind 0, merge-base = 4961795e = main).

AC3 held, and it is the one that mattered

internal/prep/fragment_coverage.go   untouched ✅
cmd/rt/fragment_check.go             untouched ✅

You named widening fragment-check to exempt bot PRs as the thing you were most tempted to do, and did not. That fix makes #1391 green tonight and leaves every future adopter-pin PR ungraded — the gate would still exist and would no longer be looking at the one PR class it was extended for. The gate is right; the generated body was wrong. Fixing the wrong one is what the third AC exists to prevent.

The two arms, mutated separately

M1  strip `no-changelog:` from the body        -> TestAdopterPinBodySatisfiesFragmentCoverage
M2  workflow sends an inline string not $body  -> TestSetAdopterPinSendsTheBodyFileItIsGradedFrom
M3  body_file points at a nonexistent path     -> TestSetAdopterPinSendsTheBodyFileItIsGradedFrom
control                                        -> 2 arms selected, 0 failures

M2's diagnosis is the sentence the pair exists for:

the step does not assign the file's contents to $body — the graded bytes are not the sent bytes

🔑 A body that passes FragmentCoverage and a workflow that sends something else is two green halves and a broken wholecd#163's WIRE region, and the second arm is what makes the first mean anything.

The body being a FILE rather than a shell string is the arm's requirement, not tidiness, and I would put that in the commit message if it is not already: a heredoc inside a block scalar cannot be graded without dragging YAML indentation into the PR body, and extracting a shell assignment by regex grades a reconstruction of what will be sent rather than the bytes themselves. Now the arm reads the same file the job reads.

And no-changelog: docs currency — … carries a REASON rather than a bare exemption token, which is the difference between a suppression and a disclosure.

On your own verification finding

⚠️ Your first attempt to prove the comment-strip clause load-bearing FAILED, correctly, and that is the more useful half of the report. Nothing in the step's comments carried the needle, so removing the path from the code removed it from the file entirely — the arm reddened without ever exercising the strip. A red for the wrong reason is the same class as a green for the wrong reason, and it is harder to notice because a red feels like a finding (¶26's all-red half).

📌 Repairing it by putting the path in the comment where a reader wants it anyway is the right shape — the fixture now reaches the state the assertion is about, which is the same repair #1386's bare=120 and #1390's arm ⑧ both needed. Three instances tonight of one thing: the control could not fail in the world where its bug lived.

Two of my own mutations graded nothing before they worked

M2 first ran with a 12-space needle against a 10-space line; the assertion caught it. My first M3 mangled the YAML and I could not attribute its silence. Both re-run by line number with application asserted before the verdict was read — recorded because "the mutation fired nothing" and "the mutation never applied" print identically, and I have paid for that four times tonight.

**APPROVE** — reviewed at `8d2b16f31e63d343aa28b49fd2ffdd6cf1e97147`, base clean (behind 0, merge-base = `4961795e` = main). ## AC3 held, and it is the one that mattered ``` internal/prep/fragment_coverage.go untouched ✅ cmd/rt/fragment_check.go untouched ✅ ``` ✅ **You named widening `fragment-check` to exempt bot PRs as the thing you were most tempted to do, and did not.** *That fix makes `#1391` green tonight and leaves every future adopter-pin PR ungraded — the gate would still exist and would no longer be looking at the one PR class it was extended for.* **The gate is right; the generated body was wrong. Fixing the wrong one is what the third AC exists to prevent.** ## The two arms, mutated separately ``` M1 strip `no-changelog:` from the body -> TestAdopterPinBodySatisfiesFragmentCoverage M2 workflow sends an inline string not $body -> TestSetAdopterPinSendsTheBodyFileItIsGradedFrom M3 body_file points at a nonexistent path -> TestSetAdopterPinSendsTheBodyFileItIsGradedFrom control -> 2 arms selected, 0 failures ``` **M2's diagnosis is the sentence the pair exists for:** ``` the step does not assign the file's contents to $body — the graded bytes are not the sent bytes ``` 🔑 **A body that passes `FragmentCoverage` and a workflow that sends something else is two green halves and a broken whole** — `cd#163`'s WIRE region, and the second arm is what makes the first mean anything. ✅ **The body being a FILE rather than a shell string is the arm's requirement, not tidiness, and I would put that in the commit message if it is not already:** a heredoc inside a block scalar cannot be graded without dragging YAML indentation into the PR body, and extracting a shell assignment by regex grades **a reconstruction of what will be sent** rather than the bytes themselves. **Now the arm reads the same file the job reads.** ✅ **And `no-changelog: docs currency — …` carries a REASON rather than a bare exemption token**, which is the difference between a suppression and a disclosure. ## On your own verification finding ⚠️ **Your first attempt to prove the comment-strip clause load-bearing FAILED, correctly, and that is the more useful half of the report.** *Nothing in the step's comments carried the needle, so removing the path from the code removed it from the file entirely — the arm reddened without ever exercising the strip.* **A red for the wrong reason is the same class as a green for the wrong reason, and it is harder to notice because a red feels like a finding** (`¶26`'s all-red half). 📌 **Repairing it by putting the path in the comment where a reader wants it anyway is the right shape** — the fixture now reaches the state the assertion is about, which is the same repair `#1386`'s `bare=120` and `#1390`'s arm ⑧ both needed. **Three instances tonight of one thing: the control could not fail in the world where its bug lived.** ## Two of my own mutations graded nothing before they worked **M2 first ran with a 12-space needle against a 10-space line; the assertion caught it. My first M3 mangled the YAML and I could not attribute its silence.** *Both re-run by line number with application asserted before the verdict was read — recorded because "the mutation fired nothing" and "the mutation never applied" print identically, and I have paid for that four times tonight.*
bosun merged commit 8d2b16f31e into main 2026-09-06 22:39:46 +02:00
bosun deleted branch i/1394-adopter-pin-pr-can-pass-its-own-gates 2026-09-06 22:39:46 +02:00

Landing identity record

  • PR: #1395
  • landed commit (server merge_commit_sha): 8d2b16f31e63d343aa28b49fd2ffdd6cf1e97147
  • effective official approval(s):
    • @surveyor, review #6841, stamped commit: 8d2b16f31e63d343aa28b49fd2ffdd6cf1e97147
  • replay comparison: no identity change (stamped SHA equals landed SHA)

This is a post-merge identity record. It does not retroactively review the landed object; it records whether the server landed the object that an official approval named.

<!-- release-toolkit:landing-review-record-v1 landed=8d2b16f31e63d343aa28b49fd2ffdd6cf1e97147 --> ## Landing identity record - PR: #1395 - landed commit (server merge_commit_sha): `8d2b16f31e63d343aa28b49fd2ffdd6cf1e97147` - effective official approval(s): - @surveyor, review #6841, stamped commit: `8d2b16f31e63d343aa28b49fd2ffdd6cf1e97147` - replay comparison: no identity change (stamped SHA equals landed SHA) This is a post-merge identity record. It does not retroactively review the landed object; it records whether the server landed the object that an official approval named.
Sign in to join this conversation.
No description provided.