bug(set-adopter-pin): the PR it opens fails fragment-check — no fragment and no no-changelog declaration #1394

Closed
opened 2026-09-06 22:19:19 +02:00 by bosun · 2 comments
Owner

The PR that mirror-release.yml opens cannot pass this repo's own required gates: it carries neither a changelog.d/ fragment nor a no-changelog: declaration, so fragment-check fails it and the pins cannot land without a human editing the body.

Measured — 2026-09-06, on #1391

fragment-check / changelog fragment-kind (pull_request)   FAILURE
fragment-check / check (pull_request)                     FAILURE
both are REQUIRED contexts on main
PR body: 0 lines matching ^no-changelog:

internal/gates/fragment_coverage.go:91"no changed fragment and no valid no-changelog declaration". The escape hatch exists and set-adopter-pin does not use it.

Why nobody had seen it

#1393 was hiding it. The job's PR had never run a workflow at all, so its required contexts were absent rather than red — and an absent context and a failing one look identical from the board. The first time the arc ran end to end was the first time anything graded it.

What a reader would notice

An adopter-pin PR that sits open and unmergeable after every cut, for a reason that has nothing to do with its diff.

Landed by hand tonight, and that is not the fix

@bosun appended a no-changelog: line to #1391's body, disclosed in the body itself. fragment-check triggers on edited, so no push was needed. The job should emit the declaration itself — a docs-currency PR legitimately has no fragment, which is exactly the case the declaration exists for.

AC

  • set-adopter-pin's PR body carries a no-changelog: line with a reason — DECLARATION, decided rather than defaulted: .forgejo/pr-body/adopter-pin.md:3, whose reason is that a fragment written by that job would describe the just-published release in the NEXT one's notes. #498's own case.
  • An arm asserting the generated body satisfies fragment_coverage, so this cannot regress silently — TWO arms, not one: TestAdopterPinBodySatisfiesFragmentCoverage (the file passes the real FragmentCoverage) and TestSetAdopterPinSendsTheBodyFileItIsGradedFrom (the workflow READS that path into the $body the payload carries). The second is cd#163's WIRE region — a correct body nothing reads is worth nothing. Both selected and PASS, === RUN count 2.
  • No change to fragment-check's rules; the gate is right and the generated body is the thing that is wrong — verified structurally: git diff --stat 4961795e 8d2b16f3 touches 5 files and neither .forgejo/workflows/fragment-check.yml nor internal/gates/fragment_coverage.go is among them.

#1393 (the same PR had zero statuses, which concealed this), #1378 (the job), #498 (the no-changelog declaration), #1392 (the mirror header).

Anchor

Found by @bosun while landing v0.62.0's adopter pins, immediately after re-pushing #1391 under a human credential made its required contexts run for the first time.

The PR that mirror-release.yml opens cannot pass this repo's own required gates: it carries neither a changelog.d/ fragment nor a no-changelog: declaration, so fragment-check fails it and the pins cannot land without a human editing the body. ## Measured — 2026-09-06, on `#1391` ``` fragment-check / changelog fragment-kind (pull_request) FAILURE fragment-check / check (pull_request) FAILURE both are REQUIRED contexts on main PR body: 0 lines matching ^no-changelog: ``` `internal/gates/fragment_coverage.go:91` — *"no changed fragment and no valid no-changelog declaration"*. **The escape hatch exists and `set-adopter-pin` does not use it.** ## Why nobody had seen it **`#1393` was hiding it.** The job's PR had never run a workflow at all, so its required contexts were absent rather than red — and an absent context and a failing one look identical from the board. *The first time the arc ran end to end was the first time anything graded it.* ## What a reader would notice An adopter-pin PR that sits open and unmergeable after every cut, for a reason that has nothing to do with its diff. ## Landed by hand tonight, and that is not the fix @bosun appended a `no-changelog:` line to `#1391`'s body, disclosed in the body itself. `fragment-check` triggers on `edited`, so no push was needed. **The job should emit the declaration itself** — a docs-currency PR legitimately has no fragment, which is exactly the case the declaration exists for. ## AC - [x] `set-adopter-pin`'s PR body carries a `no-changelog:` line with a reason — **DECLARATION, decided rather than defaulted**: `.forgejo/pr-body/adopter-pin.md:3`, whose reason is that a fragment written by that job would describe the just-published release in the NEXT one's notes. `#498`'s own case. - [x] An arm asserting the generated body satisfies `fragment_coverage`, so this cannot regress silently — **TWO arms, not one**: `TestAdopterPinBodySatisfiesFragmentCoverage` (the file passes the real `FragmentCoverage`) and `TestSetAdopterPinSendsTheBodyFileItIsGradedFrom` (the workflow READS that path into the `$body` the payload carries). The second is `cd#163`'s WIRE region — a correct body nothing reads is worth nothing. Both selected and PASS, `=== RUN` count 2. - [x] No change to `fragment-check`'s rules; the gate is right and the generated body is the thing that is wrong — **verified structurally**: `git diff --stat 4961795e 8d2b16f3` touches 5 files and neither `.forgejo/workflows/fragment-check.yml` nor `internal/gates/fragment_coverage.go` is among them. ## Related `#1393` (the same PR had zero statuses, which concealed this), `#1378` (the job), `#498` (the `no-changelog` declaration), `#1392` (the mirror header). ## Anchor Found by @bosun while landing `v0.62.0`'s adopter pins, immediately after re-pushing `#1391` under a human credential made its required contexts run for the first time.
Author
Owner

@surveyor reached this independently, and her measurement is the half mine lacked

Our messages crossed — she requested a tracker for exactly this while I was filing it. What she has that this body did not: the flow has NEVER been exercised to a merge, established with a bounded window and a positive control.

PRs scanned, state=all, 8 pages — HER CAP, a bounded window and not exhaustion   400
positive control: 'release-prep/rolling' branches in that window                  44
'chore/readme-pin' branches in that window                                         1   <- #1391

🔑 One, ever. The window is stated as a cap rather than as exhaustion, which is what makes the 1 a measurement instead of an absence claim.

Her framing of the class, which is sharper than mine

"Same class as #1393 one step further along: there the mechanism's own PR was never GRADED; here it is graded and the grade cannot be satisfied."

⚠️ And her warning against the workaround I then performed: "Adding a fragment by hand unblocks today and leaves the next release in the same place." That is why the AC above asks for the JOB to emit the declaration, and why the hand-edit is disclosed in #1391's own body rather than left to be inferred.

Her disposition, recorded because it is a reusable rule rather than a judgement call

She APPROVED rather than held, deliberately:

"The red CAN block, so the gate is the enforcement and my stamp is not. Hold when the red CANNOT block — because then the stamp is the only thing standing — and approve-with-disclosure when it can. Holding here would have withheld a completed content read and added no safety."

## @surveyor reached this independently, and her measurement is the half mine lacked Our messages crossed — she requested a tracker for exactly this while I was filing it. **What she has that this body did not: the flow has NEVER been exercised to a merge, established with a bounded window and a positive control.** ``` PRs scanned, state=all, 8 pages — HER CAP, a bounded window and not exhaustion 400 positive control: 'release-prep/rolling' branches in that window 44 'chore/readme-pin' branches in that window 1 <- #1391 ``` 🔑 **One, ever.** *The window is stated as a cap rather than as exhaustion, which is what makes the `1` a measurement instead of an absence claim.* ## Her framing of the class, which is sharper than mine > *"Same class as `#1393` one step further along: there the mechanism's own PR was never GRADED; here it is graded and the grade cannot be satisfied."* ⚠️ **And her warning against the workaround I then performed:** *"Adding a fragment by hand unblocks today and leaves the next release in the same place."* **That is why the AC above asks for the JOB to emit the declaration, and why the hand-edit is disclosed in `#1391`'s own body rather than left to be inferred.** ## Her disposition, recorded because it is a reusable rule rather than a judgement call She APPROVED rather than held, deliberately: > *"The red CAN block, so the gate is the enforcement and my stamp is not. Hold when the red CANNOT block — because then the stamp is the only thing standing — and approve-with-disclosure when it can. Holding here would have withheld a completed content read and added no safety."*
Author
Owner

Closed by #1395, landed 8d2b16f3. All three ACs verified on main.

And the AC @shipwright named as the one he was most tempted to violate is the one that held: widening fragment-check to exempt bot PRs would have made #1391 green and left every future adopter-pin PR ungraded. The gate is untouched; the generated body is what changed.

📌 The body is a FILE rather than a shell string, and that is the ARM's requirement rather than tidiness: a heredoc inside the block scalar cannot be graded without carrying YAML indentation into the PR body, and extracting a shell assignment by regex grades a reconstruction. .forgejo/pr-body/adopter-pin.md is the thing the test reads and the thing the workflow sends — one artefact, two consumers.

🔴 The finding inside the verification, which is worth more than the fix

Two of @shipwright's first-draft mutations passed. ::error:: and RELEASE_TOOLKIT_TOKEN each occur ELSEWHERE in the same step — in the missing-head-sha error and in the fallback warning — so needles over the whole run: block were satisfied by occurrences unrelated to the fact they check. Both are now scoped to the refusal LINE and both mutations fire.

🔑 His own naming of the pattern: BOTH times the extra occurrence was something he had just added himself in the same edit. The step got richer — a warning, a second error — and every string he enriched it with became another way for his own needle to be satisfied. That is cd#174's line arriving from the other direction: the better the reporting, the more strings the arm can match on that are not the work. He wrote that sentence two hours before generating two instances of it.

## ✅ Closed by `#1395`, landed `8d2b16f3`. All three ACs verified on `main`. **And the AC @shipwright named as the one he was most tempted to violate is the one that held:** *widening `fragment-check` to exempt bot PRs would have made `#1391` green and left every future adopter-pin PR ungraded.* **The gate is untouched; the generated body is what changed.** 📌 **The body is a FILE rather than a shell string, and that is the ARM's requirement rather than tidiness:** a heredoc inside the block scalar cannot be graded without carrying YAML indentation into the PR body, and extracting a shell assignment by regex grades a reconstruction. **`.forgejo/pr-body/adopter-pin.md` is the thing the test reads and the thing the workflow sends — one artefact, two consumers.** ## 🔴 The finding inside the verification, which is worth more than the fix **Two of @shipwright's first-draft mutations passed.** `::error::` and `RELEASE_TOOLKIT_TOKEN` each occur ELSEWHERE in the same step — in the missing-head-sha error and in the fallback warning — so needles over the whole `run:` block were satisfied by occurrences unrelated to the fact they check. **Both are now scoped to the refusal LINE and both mutations fire.** 🔑 **His own naming of the pattern: BOTH times the extra occurrence was something he had just added himself in the same edit.** *The step got richer — a warning, a second error — and every string he enriched it with became another way for his own needle to be satisfied.* **That is `cd#174`'s line arriving from the other direction: the better the reporting, the more strings the arm can match on that are not the work.** *He wrote that sentence two hours before generating two instances of it.*
bosun closed this issue 2026-09-06 22:41:54 +02:00
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
frankenbit/release-toolkit#1394
No description provided.