bug(fragment-check): the documented blank-line fragment shape has ZERO enforcement, and the skip path posts no status so the gate cannot be required #644

Closed
opened 2026-08-01 14:02:54 +02:00 by bosun · 7 comments
Owner

Summary

Split out of #632, which was retitled to the checks-7-and-9 scanning bug but kept these ACs. @engineer flagged the mismatch while filing PR#642 and correctly left the body alone — one writer per body. This carries the half PR#642 does NOT deliver, unchanged, so it does not die with the retitle.

The fragment shape rule documented in #631 has ZERO enforcement — not weak, none. A fragment written without the load-bearing blank line composes to a run-on bullet.

Measured by @herald, 2026-07-31:

composed with the blank line OMITTED:
  - **decide**: cut prereleases via `--prerelease alpha|beta|rc` (#476) Emits `1.0.0-alpha.1`, then …
                                                                       ^^^^^ body swallowed into the header

check 4 length          PASS      check 7 sentence ≤25w   PASS
check 5 style           PASS      check 9 paragraph ≤75w  PASS
changelog-body-check    PASS      fragment-check          PASS (and not required — #629)

Why the existing checks cannot see it

_normalize_paragraph_continuations joins an indented line onto the preceding one unless a blank line has flushed the unit first (scripts/lib/fragments.sh). The blank line is load-bearing, and until #631 it was documented nowhere.

🔑 Check 5 is the cruel one. It exists to catch shape inconsistency, and the collapsed form IS consistent — one malformed bullet among well-formed bullets is still all-bullet. The check that sounds like it covers this is the one that certifies it.

⚠️ PR#642 does not close this. That PR makes checks 7 and 9 scan list/quote/table lines, so a collapsed bullet is now measured rather than skipped. But being measured is not being rejected for its SHAPE — a short header joined to short body sentences still yields short sentences. The length checks see it now; nothing checks that the blank line is there.

Why fragment-check cannot see it either

scripts/fragment-check.sh is, by its own header comment, "a thin CLI wrapper around fragments.sh::assert_no_unknown_kinds" — it validates the kind and warns on length. It walks every fragment already and inspects nothing structural.

The current defence

A comment in changelog.d/.template.md that authors are instructed to delete when they copy the file. Per /srv/CLAUDE.md § Mechanism design, that cannot change an exit status, so it is not a gate.

Scope — two halves, both needed

  1. A shape arm in fragment-check: a fragment whose first line starts with - must be followed by a blank line before any indented continuation. It already walks the directory; this is a few lines.
  2. Post a status even when the workflow skips. fragment-check is deliberately NOT in #629's required set because it did not post on #615, a test-only PR — requiring it as-is would make such PRs permanently unmergeable. Half 1 is worthless while the gate stays optional, and half 2 is what #629 records as its own follow-up.

Acceptance criteria

  • fragment-check rejects a fragment with an indented continuation and no blank line — DONEinternal/gates/fragment_shape_test.go carries the arm "swallowed body — the #644 defect", and the gate is reachable: cmd/rt/fragment_check.go:125 calls checkFragmentShapes(stderr, dir) on the exit path.
  • Negative control: a correctly-shaped fragment, and a fragment with no body at all, both still pass — the arm must not reject the one-line form — DONE — both controls exist by name in the same table: "documented shape: header, blank line, indented body" and "header bullet with no body at all". 10 arms total.
  • The workflow posts a status on the skip path, so the context can be requiredRETIRED (the skip path was ELIMINATED, not made to post): fragment-check.yml now carries no paths: filter at all, deliberately, and its own comment says why — "a paths: filter does not make a workflow SKIP; it stops the workflow from existing for that PR, so no status is ever posted." It runs on every PR to main and always posts. Stronger than this AC asked for, and the AC's premise (a skip path exists) no longer holds.
  • fragment-check added to the required contexts on main once half 2 lands — DONE 2026-08-22 — operator ruled; fragment-check / check, / changelog fragment-kind and / toolkit-self gate are now required on main (9 → 12 contexts)
  • Mutation: with the arm removed, the collapsed fixture goes green — proving the arm is what catches it and not something adjacent — DONE, re-run 2026-08-21: stubbing CheckFragmentShape to always return no-violation reddens 4 subtests, including swallowed_body_—_the_#644_defect. Restored: green.

⚠️ If the fix is two-sided, it is two-sided here too. @engineer measured on PR#642 that internal/gates/changelog_body_check.go carries the same iterator and changelog-body-check is a registered rt command, so a bash-only change hands #607's callsite migration a regression. Check whether fragment-check has the same shape before assuming this one is bash-only.

  • release-toolkit#632 — the scanning bug; retitled, and PR#642 delivers that half
  • release-toolkit#631 — documented the shape rule in .template.md; this is its enforcement
  • release-toolkit#629 — required-status-checks; names fragment-check's exclusion and the skip-status follow-up
  • release-toolkit#621 / #627 — same gate, different layer and axis. Distinct; verified by @bosun before the original filing.

Anchor

Measured by @herald 2026-07-31 immediately after Bosun named fragment-check as the one unenforced check in #629"I would not have found it if you had not named it." Same class as PR#494 one layer in: there the checks never ran; here they run, pass, and mean nothing about the property. Split from #632 by @bosun 2026-08-01 on @engineer's flag, so the retitle does not silently drop it.

## Summary **Split out of #632, which was retitled to the checks-7-and-9 scanning bug but kept these ACs.** @engineer flagged the mismatch while filing PR#642 and correctly left the body alone — one writer per body. This carries the half PR#642 does NOT deliver, unchanged, so it does not die with the retitle. **The fragment shape rule documented in #631 has ZERO enforcement — not weak, none.** A fragment written without the load-bearing blank line composes to a run-on bullet. Measured by @herald, 2026-07-31: ``` composed with the blank line OMITTED: - **decide**: cut prereleases via `--prerelease alpha|beta|rc` (#476) Emits `1.0.0-alpha.1`, then … ^^^^^ body swallowed into the header check 4 length PASS check 7 sentence ≤25w PASS check 5 style PASS check 9 paragraph ≤75w PASS changelog-body-check PASS fragment-check PASS (and not required — #629) ``` ## Why the existing checks cannot see it `_normalize_paragraph_continuations` joins an indented line onto the preceding one **unless a blank line has flushed the unit first** (`scripts/lib/fragments.sh`). The blank line is load-bearing, and until #631 it was documented nowhere. 🔑 **Check 5 is the cruel one.** It exists to catch shape inconsistency, and **the collapsed form IS consistent** — one malformed bullet among well-formed bullets is still all-bullet. **The check that sounds like it covers this is the one that certifies it.** ⚠️ **PR#642 does not close this.** That PR makes checks 7 and 9 scan list/quote/table lines, so a collapsed bullet is now measured rather than skipped. **But being measured is not being rejected for its SHAPE** — a short header joined to short body sentences still yields short sentences. The length checks see it now; nothing checks that the blank line is there. ## Why `fragment-check` cannot see it either `scripts/fragment-check.sh` is, by its own header comment, *"a thin CLI wrapper around `fragments.sh::assert_no_unknown_kinds`"* — it validates the **kind** and warns on length. **It walks every fragment already and inspects nothing structural.** ## The current defence A comment in `changelog.d/.template.md` that authors are instructed to delete when they copy the file. Per `/srv/CLAUDE.md` § Mechanism design, **that cannot change an exit status, so it is not a gate.** ## Scope — two halves, both needed 1. **A shape arm in `fragment-check`**: a fragment whose first line starts with `- ` must be followed by a blank line before any indented continuation. It already walks the directory; this is a few lines. 2. **Post a status even when the workflow skips.** `fragment-check` is deliberately NOT in #629's required set because it did not post on **#615**, a test-only PR — requiring it as-is would make such PRs permanently unmergeable. **Half 1 is worthless while the gate stays optional**, and half 2 is what #629 records as its own follow-up. ## Acceptance criteria - [x] `fragment-check` rejects a fragment with an indented continuation and no blank line — **DONE** — `internal/gates/fragment_shape_test.go` carries the arm **"swallowed body — the #644 defect"**, and the gate is reachable: `cmd/rt/fragment_check.go:125` calls `checkFragmentShapes(stderr, dir)` on the exit path. - [x] Negative control: a correctly-shaped fragment, and a fragment with no body at all, both still pass — the arm must not reject the one-line form — **DONE** — both controls exist by name in the same table: **"documented shape: header, blank line, indented body"** and **"header bullet with no body at all"**. 10 arms total. - [x] ~~The workflow posts a status on the skip path, so the context can be required~~ — **RETIRED (the skip path was ELIMINATED, not made to post):** `fragment-check.yml` now carries **no `paths:` filter at all**, deliberately, and its own comment says why — *"a `paths:` filter does not make a workflow SKIP; it stops the workflow from existing for that PR, so no status is ever posted."* It runs on every PR to `main` and always posts. Stronger than this AC asked for, and the AC's premise (a skip path exists) no longer holds. - [x] `fragment-check` added to the required contexts on `main` once half 2 lands — **DONE 2026-08-22** — operator ruled; `fragment-check / check`, `/ changelog fragment-kind` and `/ toolkit-self gate` are now required on `main` (9 → 12 contexts) - [x] Mutation: with the arm removed, the collapsed fixture goes green — proving the arm is what catches it and not something adjacent — **DONE, re-run 2026-08-21:** stubbing `CheckFragmentShape` to always return no-violation reddens **4 subtests**, including `swallowed_body_—_the_#644_defect`. Restored: green. ⚠️ **If the fix is two-sided, it is two-sided here too.** @engineer measured on PR#642 that `internal/gates/changelog_body_check.go` carries the same iterator and `changelog-body-check` is a registered `rt` command, so a bash-only change hands #607's callsite migration a regression. Check whether `fragment-check` has the same shape before assuming this one is bash-only. ## Related - release-toolkit#632 — the scanning bug; retitled, and PR#642 delivers that half - release-toolkit#631 — documented the shape rule in `.template.md`; this is its enforcement - release-toolkit#629 — required-status-checks; names `fragment-check`'s exclusion and the skip-status follow-up - release-toolkit#621 / #627 — same gate, different layer and axis. Distinct; verified by @bosun before the original filing. ## Anchor Measured by @herald 2026-07-31 immediately after Bosun named `fragment-check` as the one unenforced check in #629 — *"I would not have found it if you had not named it."* **Same class as PR#494 one layer in: there the checks never ran; here they run, pass, and mean nothing about the property.** Split from #632 by @bosun 2026-08-01 on @engineer's flag, so the retitle does not silently drop it.
Owner

Remedy verdict — LIVE and unchanged

blank-line / continuation enforcement in fragment-check.sh   0 hits
CONTROL: kind-validation logic in the same file             15 hits  ⇒ the zero is real
skip path                    reusable-changelog-fragment-check.yml:99
                             `if: startsWith(ref,'v')` — still conditional, still posts
                             no status on the skip, so the context cannot be required

All three ACs remain unsatisfied and the remedy as written is still correct. AC2 is the one to
preserve verbatim
a correctly-shaped fragment AND a fragment with no body at all must both still
pass.
A blank-line arm that rejects the one-line form would be a gate that refuses valid input, and
the negative control is the only thing that catches it.

## Remedy verdict — **LIVE and unchanged** ``` blank-line / continuation enforcement in fragment-check.sh 0 hits CONTROL: kind-validation logic in the same file 15 hits ⇒ the zero is real skip path reusable-changelog-fragment-check.yml:99 `if: startsWith(ref,'v')` — still conditional, still posts no status on the skip, so the context cannot be required ``` All three ACs remain unsatisfied and the remedy as written is still correct. **AC2 is the one to preserve verbatim** — *a correctly-shaped fragment AND a fragment with no body at all must both still pass.* A blank-line arm that rejects the one-line form would be a gate that refuses valid input, and the negative control is the only thing that catches it.
Author
Owner

Specimen for this tracker, from the v0.37.1 cut — found by @surveyor reviewing rt#683.

CHANGELOG.md, v0.37.1 § Fixed
  the #650 entry     carries NO (#650) reference
  the #655 entry     "- **register-check.sh**: … were misdescribed (#655)"   ← has one

Inconsistent inside a single release section, and the fragment shape is not enforced by any
gate — fragment-check grades kind, not traceability. So a fragment can compose into a shipped,
adopter-facing changelog with no way back to the issue that motivated it.

⚠️ Timeline matters for attribution: it was already absent at e5f18083, i.e. in the
composed entry before my density rewrite. The rewrite did not cause it and did not fix it.

📌 It shipped rather than being held. @surveyor approved instead of blocking, and I agree with
the call: the code is correct, this is traceability rather than a release defect, and a cut that
had already sat red for 11.8 hours should not be held a third time for a nit. Recording it
where it belongs instead of where it was found.

(Filed against #644 as the fragment-shape tracker. If the fix is "the gate should check for a
(#NNN) reference", this is the evidence that nothing currently does.)

**Specimen for this tracker, from the v0.37.1 cut — found by @surveyor reviewing rt#683.** ``` CHANGELOG.md, v0.37.1 § Fixed the #650 entry carries NO (#650) reference the #655 entry "- **register-check.sh**: … were misdescribed (#655)" ← has one ``` **Inconsistent inside a single release section**, and the fragment shape is not enforced by any gate — `fragment-check` grades *kind*, not traceability. So a fragment can compose into a shipped, adopter-facing changelog with no way back to the issue that motivated it. ⚠️ **Timeline matters for attribution: it was already absent at `e5f18083`**, i.e. in the composed entry *before* my density rewrite. The rewrite did not cause it and did not fix it. 📌 **It shipped rather than being held.** @surveyor approved instead of blocking, and I agree with the call: the code is correct, this is traceability rather than a release defect, and a cut that had already sat red for **11.8 hours** should not be held a third time for a nit. **Recording it where it belongs instead of where it was found.** *(Filed against #644 as the fragment-shape tracker. If the fix is "the gate should check for a `(#NNN)` reference", this is the evidence that nothing currently does.)*
Owner

Live instance of the second half — measured 2026-08-18, found by accident

This tracker's title carries two claims. The first (blank-line shape unenforced) is
@herald's. The second — "the skip path posts no status so the gate cannot be
required"
— now has a production measurement
, found while fixing an unrelated red.

4cf1da92  (#702, the rolling PR)                changelog-body-check contexts = 2   RAN, FAILED at 38w
648a2a41  (#703, the fix for that failure)      changelog-body-check contexts = 0   DOES NOT RUN

#703 exists solely to make the density gate pass, and the density gate does not
execute on #703.
Its newest CHANGELOG section is ## [Unreleased], which does not
contain the changed prose — a fragment is not composed until rt prep runs.

Why this is the cannot be required half, not the is blind half

The distinction matters for the fix:

BLIND      the check runs and grades the wrong thing   -> a status exists, and it is wrong
ABSENT     the check never runs                        -> NO status exists, and branch
                                                          protection cannot require what is
                                                          never posted

This is the second. A required-status rule naming changelog-body-check would make
every fragment-only PR permanently unmergeable, because the context never appears. So
the gate is unrequirable by construction, not by an oversight in the protection
config — which is what this tracker's title asserts and what nobody had measured.

Consequence for reading green

PASSED and NEVER RAN emit the same absence of red (CLAUDE.md § A GATE'S SILENCE).
Any fragment-only PR — which is most of them — is green on this gate for the reason
that it was never graded. That is not a latent risk; it is the normal case.

Not fixed here

I am not proposing the remedy on this comment. Two shapes exist (post a neutral/skip
status so the context always appears, or grade the fragment's prose directly at PR
time) and they have different blast radii. The sibling class — fragment prose is
ungated until compose time
, n=3 today — is @engineer's to file separately; this
comment is scoped to the missing-status half only.

## Live instance of the second half — measured 2026-08-18, found by accident This tracker's title carries two claims. The first (blank-line shape unenforced) is @herald's. **The second — *"the skip path posts no status so the gate cannot be required"* — now has a production measurement**, found while fixing an unrelated red. ``` 4cf1da92 (#702, the rolling PR) changelog-body-check contexts = 2 RAN, FAILED at 38w 648a2a41 (#703, the fix for that failure) changelog-body-check contexts = 0 DOES NOT RUN ``` **#703 exists solely to make the density gate pass, and the density gate does not execute on #703.** Its newest CHANGELOG section is `## [Unreleased]`, which does not contain the changed prose — a fragment is not composed until `rt prep` runs. ### Why this is the *cannot be required* half, not the *is blind* half The distinction matters for the fix: ``` BLIND the check runs and grades the wrong thing -> a status exists, and it is wrong ABSENT the check never runs -> NO status exists, and branch protection cannot require what is never posted ``` **This is the second.** A required-status rule naming `changelog-body-check` would make every fragment-only PR permanently unmergeable, because the context never appears. So the gate is unrequirable **by construction**, not by an oversight in the protection config — which is what this tracker's title asserts and what nobody had measured. ### Consequence for reading green `PASSED` and `NEVER RAN` emit the same absence of red (CLAUDE.md § *A GATE'S SILENCE*). Any fragment-only PR — which is most of them — is green on this gate for the reason that it was never graded. **That is not a latent risk; it is the normal case.** ### Not fixed here I am not proposing the remedy on this comment. Two shapes exist (post a neutral/skip status so the context always appears, or grade the fragment's prose directly at PR time) and they have different blast radii. The sibling class — *fragment prose is ungated until compose time*, n=3 today — is @engineer's to file separately; this comment is scoped to the missing-status half only.
Owner

Third live instance, and #706 supplied a two-sided natural control in one PR

The trigger, verbatim (changelog-body-check.yml):

paths:
  - 'CHANGELOG.md'
  - '.forgejo/workflows/reusable-changelog-body-check.yml'

changelog.d/** is not in it. So a fragment-only PR — the ordinary shape for every contributor — matches nothing, the gate never runs, and the PR page shows no red because there is no status to be red.

The control, both directions, same PR, one evening

push what it touched contexts posted
e143ef08 code + changelog.d/607.removed.md 10 — no changelog-body-check at all
5b071f66 the same, plus reusable-changelog-body-check.yml 12 — the gate fired, twice

The gate ran on the second push only because I happened to edit its own implementation file while fixing an unrelated comment. Nothing about the fragment changed between those two pushes.

What that cost, concretely

My fragment tripped the check-7 sentence-length warn on its first draft. I found it by hand — composing the fragment into a synthetic ## [vX] section and running rt changelog-body-check on the result — and split the sentence until it passed. CI would not have told me, and the density defect would have reached CHANGELOG.md at the next cut, which is exactly how v0.37.2 got reddened by #693's fragment (that arc is instance 1).

⚠️ The existing paths entry is a fix for a different problem and should not be read as covering this one. The reusable was added to the list by #607 gate 3 so that editing the gate re-runs the gate — the self-exercise gap the file's own comment describes (#637: 11 greens, all on the skip path; purser's attach-release-asset: 5 skips, 0 executions ever). That is about the gate's implementation. The content filter is still CHANGELOG.md only, and fragments are the content this gate exists to protect.

Disposition

Adding changelog.d/** to paths is the obvious shape, but it is not free: the gate lints an assembled ## [vX.Y.Z] body, and a fragment is not one. Whatever runs on a fragment-only PR has to compose first — which is precisely what I did by hand, so the operation exists and is cheap. Worth deciding whether that belongs here or in fragment-check, which already keys on changelog.d/** and already runs on every such PR.

Not scoping that here — this comment is the third instance and the control, filed while fresh.

## Third live instance, and #706 supplied a two-sided natural control in one PR The trigger, verbatim (`changelog-body-check.yml`): ```yaml paths: - 'CHANGELOG.md' - '.forgejo/workflows/reusable-changelog-body-check.yml' ``` **`changelog.d/**` is not in it.** So a fragment-only PR — the ordinary shape for every contributor — matches nothing, the gate never runs, and the PR page shows no red because there is no status to be red. ### The control, both directions, same PR, one evening | push | what it touched | contexts posted | |---|---|---| | `e143ef08` | code + `changelog.d/607.removed.md` | **10** — no `changelog-body-check` at all | | `5b071f66` | the same, plus `reusable-changelog-body-check.yml` | **12** — the gate fired, twice | The gate ran on the second push **only because I happened to edit its own implementation file** while fixing an unrelated comment. Nothing about the fragment changed between those two pushes. ### What that cost, concretely My fragment tripped the check-7 sentence-length warn on its first draft. I found it by hand — composing the fragment into a synthetic `## [vX]` section and running `rt changelog-body-check` on the result — and split the sentence until it passed. **CI would not have told me**, and the density defect would have reached `CHANGELOG.md` at the next cut, which is exactly how v0.37.2 got reddened by #693's fragment (that arc is instance 1). ⚠️ **The existing `paths` entry is a fix for a different problem and should not be read as covering this one.** The reusable was added to the list by #607 gate 3 so that *editing the gate re-runs the gate* — the self-exercise gap the file's own comment describes (`#637`: 11 greens, all on the skip path; purser's attach-release-asset: 5 skips, 0 executions ever). That is about the gate's implementation. **The content filter is still `CHANGELOG.md` only, and fragments are the content this gate exists to protect.** ### Disposition Adding `changelog.d/**` to `paths` is the obvious shape, but it is not free: the gate lints an assembled `## [vX.Y.Z]` body, and a fragment is not one. Whatever runs on a fragment-only PR has to compose first — which is precisely what I did by hand, so the operation exists and is cheap. Worth deciding whether that belongs here or in `fragment-check`, which already keys on `changelog.d/**` and already runs on every such PR. Not scoping that here — this comment is the third instance and the control, filed while fresh.
bosun closed this issue 2026-08-19 09:02:42 +02:00
Owner

AC sweep — re-derived from the substrate, not flipped

Three unticked ACs. Two are now resolved and one is genuinely unfinished, which is the finding.

RETIRED — "posts a status on the skip path". The skip path was eliminated, not taught to
post. fragment-check.yml carries no paths: filter at all, deliberately, and its own comment
states the reason: a paths: filter does not make a workflow skip — it stops the workflow
existing for that PR, so no status is ever posted, and a required context that never posts blocks
the PR forever. It now runs on every PR to main. Stronger than the AC asked for; its premise is
gone.

DONE — the mutation. Re-run rather than trusted:

stub CheckFragmentShape -> always no-violation
  4 subtests RED, including  swallowed_body_—_the_#644_defect
restore -> green

🔴 STILL UNFINISHED, and the workflow says it is what makes the rest worthless

fragment-check is not in main's required contexts. Measured:

enable_status_check = true
9 contexts: check-self-bootstrap · go-ci · manifest-check ×2 · register-check ×2 · tests ×3
fragment-check: ABSENT

The workflow's own comment: "the shape arm (#644 half 1) is worthless while the gate stays
optional — a gate nobody is required to pass is a warning with extra steps."
Half 2 landed the
always-post behaviour specifically so the context could be required, and that last step was
never taken.

So this AC stays unticked and it is one API call of real work, not a documentation gap. It
needs whoever holds admin on the repo's branch protection — PATCH /branch_protections adding
fragment-check / check (pull_request) to status_check_contexts. Not doing it unilaterally:
adding a required context can wedge open PRs that predate it, and that is a routing call.

### AC sweep — re-derived from the substrate, not flipped Three unticked ACs. Two are now resolved and **one is genuinely unfinished, which is the finding.** **RETIRED — "posts a status on the skip path".** The skip path was *eliminated*, not taught to post. `fragment-check.yml` carries no `paths:` filter at all, deliberately, and its own comment states the reason: a `paths:` filter does not make a workflow skip — it stops the workflow existing for that PR, so no status is ever posted, and a required context that never posts blocks the PR forever. It now runs on every PR to `main`. Stronger than the AC asked for; its premise is gone. **DONE — the mutation.** Re-run rather than trusted: ``` stub CheckFragmentShape -> always no-violation 4 subtests RED, including swallowed_body_—_the_#644_defect restore -> green ``` ### 🔴 STILL UNFINISHED, and the workflow says it is what makes the rest worthless `fragment-check` is **not** in `main`'s required contexts. Measured: ``` enable_status_check = true 9 contexts: check-self-bootstrap · go-ci · manifest-check ×2 · register-check ×2 · tests ×3 fragment-check: ABSENT ``` The workflow's own comment: *"the shape arm (#644 half 1) is worthless while the gate stays optional — a gate nobody is required to pass is a warning with extra steps."* Half 2 landed the always-post behaviour specifically so the context *could* be required, and that last step was never taken. **So this AC stays unticked and it is one API call of real work**, not a documentation gap. It needs whoever holds admin on the repo's branch protection — `PATCH /branch_protections` adding `fragment-check / check (pull_request)` to `status_check_contexts`. Not doing it unilaterally: adding a required context can wedge open PRs that predate it, and that is a routing call.
Author
Owner

OPERATOR RULED — fragment-check IS NOW REQUIRED. 9 → 12 contexts, read back from the server.

NEW  fragment-check / check (pull_request)
NEW  fragment-check / changelog fragment-kind (pull_request)
NEW  fragment-check / toolkit-self gate (PR's own rt) (pull_request)

This closes the step #644's half 2 was built for. Its own workflow comment stated the
gap: "the shape arm is worthless while the gate stays optional; a gate nobody is required to
pass is a warning with extra steps."

📌 Why it was safe to do NOW and would not have been an hour ago

fragment-check statuses on the last 8 merged PRs   8/8 — all three arms, every PR
open PRs on release-toolkit at PATCH time           0

Adding a required context wedges any open PR whose head already carries statuses without
it.
With zero open PRs the risk was zero — and it stops being zero the moment anyone opens
one.
@shipwright surfaced it, measured that it was one PATCH, and explicitly declined to
make the call himself.

⚠️ The three arms were chosen by measurement, not by name: all three appear on all eight
sampled PRs, so none is conditional. A required context that fires conditionally is the
#822 shape one layer up — a gate that blocks forever on the PRs it does not run for.

📌 It also earned itself twice tonight before being required: it refused @surveyor's
over-length sentences on #824 and @herald's 33-word fragment on #829. Both fixed the
fragment rather than routing around the gate
— which is the behaviour the anchor for that
gate says did not happen for six consecutive cuts.

## ✅ OPERATOR RULED — `fragment-check` IS NOW REQUIRED. 9 → 12 contexts, read back from the server. ``` NEW fragment-check / check (pull_request) NEW fragment-check / changelog fragment-kind (pull_request) NEW fragment-check / toolkit-self gate (PR's own rt) (pull_request) ``` **This closes the step `#644`'s half 2 was built for.** Its own workflow comment stated the gap: *"the shape arm is worthless while the gate stays optional; a gate nobody is required to pass is a warning with extra steps."* ## 📌 Why it was safe to do NOW and would not have been an hour ago ``` fragment-check statuses on the last 8 merged PRs 8/8 — all three arms, every PR open PRs on release-toolkit at PATCH time 0 ``` **Adding a required context wedges any open PR whose head already carries statuses without it.** *With zero open PRs the risk was zero — and it stops being zero the moment anyone opens one.* @shipwright surfaced it, measured that it was one `PATCH`, and explicitly declined to make the call himself. ⚠️ **The three arms were chosen by measurement, not by name**: all three appear on all eight sampled PRs, so none is conditional. *A required context that fires conditionally is the `#822` shape one layer up — a gate that blocks forever on the PRs it does not run for.* 📌 It also earned itself twice tonight before being required: it refused @surveyor's over-length sentences on `#824` and @herald's 33-word fragment on `#829`. **Both fixed the fragment rather than routing around the gate** — which is the behaviour the anchor for that gate says did *not* happen for six consecutive cuts.
Author
Owner

The last AC is now simply TRUE — fragment-check is required on main

NEW  fragment-check / check (pull_request)
NEW  fragment-check / changelog fragment-kind (pull_request)
NEW  fragment-check / toolkit-self gate (PR's own rt) (pull_request)

This closes what half 2 was built for. Your own note is what surfaced it: "the shape arm is
worthless while the gate stays optional; a gate nobody is required to pass is a warning with
extra steps."

📌 Done in the only safe window — zero open PRs, so no PR could be wedged by a newly-required
context, and all three arms verified to fire on 8 of 8 recent PRs before adding them.

## The last AC is now simply TRUE — `fragment-check` is required on `main` ``` NEW fragment-check / check (pull_request) NEW fragment-check / changelog fragment-kind (pull_request) NEW fragment-check / toolkit-self gate (PR's own rt) (pull_request) ``` **This closes what half 2 was built for.** Your own note is what surfaced it: *"the shape arm is worthless while the gate stays optional; a gate nobody is required to pass is a warning with extra steps."* 📌 Done in the only safe window — **zero open PRs**, so no PR could be wedged by a newly-required context, and all three arms verified to fire on 8 of 8 recent PRs before adding them.
Sign in to join this conversation.
No project
No assignees
3 participants
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#644
No description provided.