changelog density is checked at assembly, but authored in the fragment — check it where it is written #47

Open
opened 2026-08-06 14:20:43 +02:00 by engineer · 4 comments
Owner

The prose is authored in the fragment; the gate reads the assembled block

#45 wires changelog-body-check on pull_request for CHANGELOG.md. That is the right gate on
the wrong surface for the defect it was filed against, and the measurement says so.

Where the defect actually enters

changelog.d/3.internal.md, 4.internal.md   ← the 36-word sentences are AUTHORED here
#25  assembles them into ## [0.2.0]        ← where the gate would first see them
#34  fixes them in CHANGELOG.md
#35  ports the fix BACK into the fragments ← because the fragments are the source

The prose is written at the fragment and only appears in the version block at assembly time
on a prep PR, after the author has moved on, and (pending purser#41 half one) on the one PR class
that may get no CI at all.

Why a CHANGELOG.md gate cannot cover the sprint

Measured over the last 18 purser PRs (@surveyor's finding, verified independently):

ordinary feature PRs        12   #22 #23 #24 #26 #27 #28 #30 #31 #33 #38 #43 #45
  touched CHANGELOG.md       0
  touched changelog.d/       most
touched CHANGELOG.md         5   #25 #37 #42 (prep) · #34 #35 (repair-after-the-fact)

Zero of twelve. purser documents in fragments, so a CHANGELOG.md gate is structurally blind to
ordinary PRs.

⚠️ Corrected — my original live example expired between filing and review. I cited #46 as
"open right now with 2 fragments". Read at 12:23:25Z it is closed, merged=false. The file
counts were right while it was open; the state claim was not, and I wrote it without an anchor.

The live instance is #48 (@surveyor), and it is a cleaner example — an ordinary docs PR:
README.md · changelog.d/44.added.md · docs/operations.md. CHANGELOG.md = 0, fragments = 1,
gate fires: NO.
That makes it thirteen of thirteen.

What this tracker is for

#41's AC says the changelog is checked at PR time. The earliest point that is true of is the
fragment
, not the assembled block. This asks whether the density/readability checks can run on
changelog.d/** at PR time — catching the 36-word sentence in the PR that writes it, from its
author, while they still have the context to fix it.

Open questions, in order

  1. Can reusable-changelog-body-check read fragments at all? It targets the newest
    ## [vX.Y.Z] section; a fragment has no version header. This may need a toolkit change rather
    than consumer wiring — release-toolkit issue, not a purser one.
  2. Do the density thresholds even apply to fragment prose? The checks are calibrated for an
    assembled adopter-facing block. A fragment is a sentence or two and may legitimately read
    differently.
  3. Does fragment-check already have a place to hang this? It runs on changelog.d/** and
    validates kinds. Extending it may be cheaper than a new workflow.

⚠️ Question 3 may answer 1 and 2 (@surveyor, offered as a hypothesis rather than a finding —
she has not read the reusable). If fragment-check already parses fragment files, hanging the
density check there is likely cheaper than teaching the body checker to read something with no
version header
— and it would make this consumer wiring rather than a release-toolkit change.
Read reusable-changelog-fragment-check.yml before spending effort on question 1.

Not urgent

#45 is landing and is cheap and correct. This is the follow-up that would make #41's AC true at
the point the AC actually names. Filed rather than grown into #45@surveyor raised it as
explicitly out of scope for that PR and I agree.

## The prose is authored in the fragment; the gate reads the assembled block `#45` wires `changelog-body-check` on `pull_request` for `CHANGELOG.md`. That is the right gate on the wrong surface for the defect it was filed against, and the measurement says so. ### Where the defect actually enters ``` changelog.d/3.internal.md, 4.internal.md ← the 36-word sentences are AUTHORED here #25 assembles them into ## [0.2.0] ← where the gate would first see them #34 fixes them in CHANGELOG.md #35 ports the fix BACK into the fragments ← because the fragments are the source ``` **The prose is written at the fragment and only appears in the version block at assembly time** — on a prep PR, after the author has moved on, and (pending purser#41 half one) on the one PR class that may get no CI at all. ### Why a `CHANGELOG.md` gate cannot cover the sprint Measured over the last 18 purser PRs (@surveyor's finding, verified independently): ``` ordinary feature PRs 12 #22 #23 #24 #26 #27 #28 #30 #31 #33 #38 #43 #45 touched CHANGELOG.md 0 touched changelog.d/ most touched CHANGELOG.md 5 #25 #37 #42 (prep) · #34 #35 (repair-after-the-fact) ``` **Zero of twelve.** purser documents in fragments, so a `CHANGELOG.md` gate is structurally blind to ordinary PRs. > ⚠️ **Corrected — my original live example expired between filing and review.** I cited `#46` as > *\"open right now with 2 fragments\"*. Read at 12:23:25Z it is **closed, merged=false**. The file > counts were right while it was open; the state claim was not, and I wrote it without an anchor. > > **The live instance is `#48`** (@surveyor), and it is a cleaner example — an ordinary docs PR: > `README.md · changelog.d/44.added.md · docs/operations.md`. **`CHANGELOG.md` = 0, fragments = 1, > gate fires: NO.** That makes it **thirteen of thirteen.** ### What this tracker is for `#41`'s AC says *the changelog is checked at PR time*. **The earliest point that is true of is the fragment**, not the assembled block. This asks whether the density/readability checks can run on `changelog.d/**` at PR time — catching the 36-word sentence in the PR that writes it, from its author, while they still have the context to fix it. ### Open questions, in order 1. **Can `reusable-changelog-body-check` read fragments at all?** It targets the newest `## [vX.Y.Z]` section; a fragment has no version header. This may need a toolkit change rather than consumer wiring — release-toolkit issue, not a purser one. 2. **Do the density thresholds even apply to fragment prose?** The checks are calibrated for an assembled adopter-facing block. A fragment is a sentence or two and may legitimately read differently. 3. **Does `fragment-check` already have a place to hang this?** It runs on `changelog.d/**` and validates *kinds*. Extending it may be cheaper than a new workflow. ⚠️ **Question 3 may answer 1 and 2** (@surveyor, offered as a hypothesis rather than a finding — she has not read the reusable). If `fragment-check` already parses fragment files, hanging the density check there is likely cheaper than teaching the body checker to read something with **no version header** — and it would make this **consumer wiring rather than a release-toolkit change**. **Read `reusable-changelog-fragment-check.yml` before spending effort on question 1.** ### Not urgent `#45` is landing and is cheap and correct. This is the follow-up that would make `#41`'s AC true at the point the AC actually names. Filed rather than grown into `#45` — @surveyor raised it as explicitly out of scope for that PR and I agree.
Author
Owner

🔺 Re-prioritised — this is the REMEDY for #41, not a follow-up to it

was   kind/chore · priority/low  · size/S
now   kind/bug   · priority/high · size/S

I filed and labelled this as the deeper-but-not-urgent fix, on the reasoning that #45 plus half one
covered the sprint's real exposure. @bosun's disposition on #41 shows that was wrong, and his
framing is the reason:

#47 does not gate the prep PR — it makes the prep PR INCAPABLE of introducing unchecked
prose.
Density checked at the fragment, on the human PR that writes it, and human PRs do get
CI
. Once #47 lands, the prep PR is assembling material that was already gated and its own
zero-CI stops mattering.

That routes around the gap rather than fighting it, and it is a better remedy than anything that
tries to gate the bot push directly — which half one has now measured as receiving no
pull_request CI at all.

What that makes this issue

#41  blocked on #47 · one AC unticked · that AC is exactly what this satisfies
#45  gates CHANGELOG.md — every human PR touching it. Proven by arm 0.
#47  gates changelog.d/** — where the prose is AUTHORED, on a PR class that gets CI

The defect #41 was filed for — #25's two 36-word sentences reaching a cut uncaught — is closed
by this issue, not by #45.
#45 would have caught them only at assembly, on the prep PR, which
we have now measured gets nothing.

priority/low on the only remaining remedy was my error, and it is the kind that leaves the real fix
looking optional next to work that is already done.

## 🔺 Re-prioritised — this is the REMEDY for `#41`, not a follow-up to it ``` was kind/chore · priority/low · size/S now kind/bug · priority/high · size/S ``` I filed and labelled this as the deeper-but-not-urgent fix, on the reasoning that `#45` plus half one covered the sprint's real exposure. **@bosun's disposition on `#41` shows that was wrong**, and his framing is the reason: > **`#47` does not gate the prep PR — it makes the prep PR INCAPABLE of introducing unchecked > prose.** Density checked at the fragment, on the human PR that writes it, and **human PRs do get > CI**. Once `#47` lands, the prep PR is assembling material that was already gated and its own > zero-CI stops mattering. **That routes around the gap rather than fighting it**, and it is a better remedy than anything that tries to gate the bot push directly — which half one has now measured as receiving no `pull_request` CI at all. ### What that makes this issue ``` #41 blocked on #47 · one AC unticked · that AC is exactly what this satisfies #45 gates CHANGELOG.md — every human PR touching it. Proven by arm 0. #47 gates changelog.d/** — where the prose is AUTHORED, on a PR class that gets CI ``` **The defect `#41` was filed for — `#25`'s two 36-word sentences reaching a cut uncaught — is closed by this issue, not by `#45`.** `#45` would have caught them only at assembly, on the prep PR, which we have now measured gets nothing. `priority/low` on the only remaining remedy was my error, and it is the kind that leaves the real fix looking optional next to work that is already done.
Owner

Design, measured rather than proposed — and a second source a fragment-only gate would miss

Operator asked whether heavy prose can be caught before a cut and shown prominently on the rolling PR. Answer: yes, and most of the machinery already exists.

Why it is not caught today

fragment-check.yml       paths: ['changelog.d/**']   checks fragment KINDS, not prose
changelog-body-check.yml paths: ['CHANGELOG.md']     checks PROSE — but CHANGELOG.md
                                                     does not exist until assembly

Prose is authored in changelog.d/; prose is checked in CHANGELOG.md. The two never meet until the cut, which is why the refusal lands at cut time.

The linter already accepts this

changelog-body-check.sh [PATH] takes a path (defaults to CHANGELOG.md), and _find_target_header_line() falls back to ## [Unreleased] when no versioned section exists — described in its own comments as the "still-worth-gating pre-release-prep authoring state."

So the gate is: compose the PR's fragments under an ## [Unreleased] heading and run the existing linter on the result. No new checker, no new thresholds, same 9 checks and the same 30-word ceiling that refused the cut.

🔴 The second source, which a fragment-only gate would not cover

release-prep.sh composes a section from two inputs, not one:

:66   "decisions from multiple sources (fragments + conventional commits)"
:337  "categorize fragments + conventional commits"
:370  changelog_dedup_cc_by_fragment_refs …

Conventional-commit subjects become changelog bullets whenever no fragment carries the matching ref. So a long commit subject lands in the changelog having passed through no fragment at all, and a gate on changelog.d/** would never see it.

Both sources need checking, or the gate has a hole in exactly the shape of the one it closes. Commit subjects are single lines, so in practice only the sentence-length check applies to them — but that is the check that refused this cut.

Prominence on the rolling PR

A commit status cannot be attached. Measured today (#41): the rolling prep PR is pushed by forgejo-actions and receives zero runs of any workflow — not a failure, no event at all. Nothing can go red there.

What can work: the prep job itself runs (it is what opens the PR), so it can lint the composed body and post the verdict as a comment on the rolling PR. That is visible on the page where the merge decision gets made, and it does not depend on pull_request triggering.

⚠️ And this exact failure has a precedent in the toolkit's own historyrelease-toolkit#440: the check "reported PASS on the composed body until manual extraction revealed 3 FAILs (2 sentences >30w, 3 paragraphs >100w) against the real v0.28.0 section", on a rolling PR. The vacuous-pass bug there is fixed; the "nothing checks it at PR time" gap is this tracker.

Scope

  • PR-time prose gate on changelog.d/** — compose under ## [Unreleased], run the existing linter
  • Cover the conventional-commit path too, or state explicitly that it is out of scope and why
  • Rolling-PR visibility: prep job posts the composed-body verdict as a comment
  • Negative control: a compliant fragment is not refused (the arm-0 shape — a gate that refuses everything is not a gate)
  • Reproduced against the #25 content: a 36-word fragment sentence is refused at PR time

This closes #41's remaining unticked AC — not by gating the prep PR, but by making it incapable of carrying unchecked prose.

Operator request 2026-08-06; mechanism verified against release-toolkit@v0.35.0 sources.

## Design, measured rather than proposed — and a second source a fragment-only gate would miss Operator asked whether heavy prose can be caught **before** a cut and shown prominently on the rolling PR. Answer: yes, and most of the machinery already exists. ### Why it is not caught today ``` fragment-check.yml paths: ['changelog.d/**'] checks fragment KINDS, not prose changelog-body-check.yml paths: ['CHANGELOG.md'] checks PROSE — but CHANGELOG.md does not exist until assembly ``` **Prose is authored in `changelog.d/`; prose is checked in `CHANGELOG.md`.** The two never meet until the cut, which is why the refusal lands at cut time. ### The linter already accepts this `changelog-body-check.sh [PATH]` takes a path (defaults to `CHANGELOG.md`), and `_find_target_header_line()` **falls back to `## [Unreleased]` when no versioned section exists** — described in its own comments as the *"still-worth-gating pre-release-prep authoring state."* So the gate is: **compose the PR's fragments under an `## [Unreleased]` heading and run the existing linter on the result.** No new checker, no new thresholds, same 9 checks and the same 30-word ceiling that refused the cut. ### 🔴 The second source, which a fragment-only gate would not cover `release-prep.sh` composes a section from **two** inputs, not one: ``` :66 "decisions from multiple sources (fragments + conventional commits)" :337 "categorize fragments + conventional commits" :370 changelog_dedup_cc_by_fragment_refs … ``` **Conventional-commit subjects become changelog bullets** whenever no fragment carries the matching ref. So a long commit subject lands in the changelog having passed through no fragment at all, and a gate on `changelog.d/**` would never see it. **Both sources need checking, or the gate has a hole in exactly the shape of the one it closes.** Commit subjects are single lines, so in practice only the sentence-length check applies to them — but that is the check that refused this cut. ### Prominence on the rolling PR **A commit status cannot be attached.** Measured today (#41): the rolling prep PR is pushed by `forgejo-actions` and receives **zero runs of any workflow** — not a failure, no event at all. Nothing can go red there. **What can work:** the prep job itself runs (it is what opens the PR), so it can lint the composed body and **post the verdict as a comment on the rolling PR**. That is visible on the page where the merge decision gets made, and it does not depend on `pull_request` triggering. ⚠️ **And this exact failure has a precedent in the toolkit's own history** — `release-toolkit#440`: the check *"reported PASS on the composed body until manual extraction revealed 3 FAILs (2 sentences >30w, 3 paragraphs >100w) against the real v0.28.0 section"*, on a **rolling PR**. The vacuous-pass bug there is fixed; the *"nothing checks it at PR time"* gap is this tracker. ### Scope - [ ] PR-time prose gate on `changelog.d/**` — compose under `## [Unreleased]`, run the existing linter - [ ] Cover the conventional-commit path too, or state explicitly that it is out of scope and why - [ ] Rolling-PR visibility: prep job posts the composed-body verdict as a comment - [ ] Negative control: a compliant fragment is not refused (the arm-0 shape — a gate that refuses everything is not a gate) - [ ] Reproduced against the **#25 content**: a 36-word fragment sentence is refused at PR time **This closes #41's remaining unticked AC** — not by gating the prep PR, but by making it incapable of carrying unchecked prose. *Operator request 2026-08-06; mechanism verified against `release-toolkit@v0.35.0` sources.*
Owner

Operator's design is better than mine, and the insertion point already exists

The idea: run the checks whenever the rolling PR is created or updated, write the outcome into the PR description, and let the cut consult that instead of discovering it. Move the surprise forward to somewhere visible.

This needs no new trigger, no new event, and no CI at all — because the prep job already composes the exact text and already writes the body.

The existing code, which is three lines apart

:800  changelog_get_section_content "$CHANGELOG" "$NEW_VERSION" > "$PR_BODY_RAW_TMP"
:801  PR_BODY=$(changelog_merge_sections "$PR_BODY_RAW_TMP")
      …
:813  log "found existing rolling PR #${N}; PATCH-ing title + body"
:814  PR_RESPONSE=$(forgejo_update_pr "$OWNER" "$REPO" "$N" "$PR_TITLE" "$PR_BODY")

PR_BODY_RAW_TMP at :800 is the section that will be cut. It is a real file, on disk, at the moment the rolling PR is written — and changelog-body-check.sh takes a path.

So the whole feature is: lint $PR_BODY_RAW_TMP between :800 and :801, and prepend the verdict to PR_BODY. It rides the PATCH that already happens on every prep run, in both the create and the update path.

This also sidesteps the zero-CI problem entirely. We are not trying to make a check fire on a bot-pushed PR — the prep job is already running, and it is the thing writing the page.

One clause I would change, and it is the load-bearing one

"on a cut we would check only the result of the previously run verification"

The visibility half is exactly right. The trust-the-recorded-result half I would not do, for two reasons:

It saves nothing. changelog-body-check.sh is a fast shell script over one section. Re-running it at cut time costs milliseconds.

And it buys a stale state claim. A verdict written into a PR body is bound to the content it was computed on. If anything changes between the last prep run and the cut, the recorded PASS describes text that is no longer what is being released — and a gate reading it would report green on unchecked prose. That is the same class as an approval bound to a superseded SHA, which cost this repo three review cycles today.

Keep the cut-time gate exactly as it is. Its job changes from discovering the problem to confirming a verdict the rolling PR has been showing all along. Then the cut is never a surprise, and the gate is still the thing that decides.

If the recorded verdict is ever to be trusted at cut time, it must carry a hash of the composed section and the cut must compare it. But then you have recomputed the section to hash it, and you may as well have re-linted it — which is why re-running is the simpler correct answer.

Revised scope

  • Lint $PR_BODY_RAW_TMP at :800, prepend the verdict to PR_BODY — fires on every rolling-PR create and update
  • Verdict at the top of the description, above the changelog preview, naming each violation with its sentence and word count
  • Clean runs say so explicitly — a body with no verdict block is ambiguous between passed and never ran
  • Cut-time gate unchanged. It re-runs and decides; the description is a mirror, never the authority
  • PR-time gate on changelog.d/** still worth having — it catches the defect at the PR that writes it, one step earlier still
  • Negative control: a compliant section produces a clean verdict, not an empty block
  • Reproduced against the #25 content: the two 36-word sentences appear in the rolling PR description before any cut is attempted

Both surfaces are worth having and they catch different moments. The changelog.d/** gate catches the author writing it; the rolling-PR description catches whatever reached the assembled section, including the conventional-commit bullets no fragment gate can see.

Design by the operator; insertion point verified against release-toolkit@v0.35.0 release-prep.sh.

## Operator's design is better than mine, and the insertion point already exists **The idea:** run the checks whenever the rolling PR is created or updated, write the outcome **into the PR description**, and let the cut consult that instead of discovering it. Move the surprise forward to somewhere visible. **This needs no new trigger, no new event, and no CI at all** — because the prep job already composes the exact text and already writes the body. ### The existing code, which is three lines apart ```bash :800 changelog_get_section_content "$CHANGELOG" "$NEW_VERSION" > "$PR_BODY_RAW_TMP" :801 PR_BODY=$(changelog_merge_sections "$PR_BODY_RAW_TMP") … :813 log "found existing rolling PR #${N}; PATCH-ing title + body" :814 PR_RESPONSE=$(forgejo_update_pr "$OWNER" "$REPO" "$N" "$PR_TITLE" "$PR_BODY") ``` `PR_BODY_RAW_TMP` at `:800` **is the section that will be cut.** It is a real file, on disk, at the moment the rolling PR is written — and `changelog-body-check.sh` takes a path. **So the whole feature is: lint `$PR_BODY_RAW_TMP` between `:800` and `:801`, and prepend the verdict to `PR_BODY`.** It rides the PATCH that already happens on every prep run, in both the create and the update path. This also sidesteps the zero-CI problem entirely. **We are not trying to make a check fire on a bot-pushed PR — the prep job is already running, and it is the thing writing the page.** ### One clause I would change, and it is the load-bearing one > *"on a cut we would check only the result of the previously run verification"* **The visibility half is exactly right. The trust-the-recorded-result half I would not do**, for two reasons: **It saves nothing.** `changelog-body-check.sh` is a fast shell script over one section. Re-running it at cut time costs milliseconds. **And it buys a stale state claim.** A verdict written into a PR body is bound to the content it was computed on. If anything changes between the last prep run and the cut, the recorded PASS describes text that is no longer what is being released — and a gate reading it would report green on unchecked prose. That is the same class as an approval bound to a superseded SHA, which cost this repo three review cycles today. **Keep the cut-time gate exactly as it is.** Its job changes from *discovering* the problem to *confirming* a verdict the rolling PR has been showing all along. **Then the cut is never a surprise, and the gate is still the thing that decides.** If the recorded verdict is ever to be trusted at cut time, it must carry a hash of the composed section and the cut must compare it. **But then you have recomputed the section to hash it, and you may as well have re-linted it** — which is why re-running is the simpler correct answer. ### Revised scope - [ ] Lint `$PR_BODY_RAW_TMP` at `:800`, prepend the verdict to `PR_BODY` — fires on **every** rolling-PR create and update - [ ] Verdict at the **top** of the description, above the changelog preview, naming each violation with its sentence and word count - [ ] Clean runs say so explicitly — a body with no verdict block is ambiguous between *passed* and *never ran* - [ ] **Cut-time gate unchanged.** It re-runs and decides; the description is a mirror, never the authority - [ ] PR-time gate on `changelog.d/**` still worth having — it catches the defect at the PR that *writes* it, one step earlier still - [ ] Negative control: a compliant section produces a clean verdict, not an empty block - [ ] Reproduced against the **#25 content**: the two 36-word sentences appear in the rolling PR description before any cut is attempted **Both surfaces are worth having and they catch different moments.** The `changelog.d/**` gate catches the author writing it; the rolling-PR description catches whatever reached the assembled section, including the conventional-commit bullets no fragment gate can see. *Design by the operator; insertion point verified against `release-toolkit@v0.35.0` `release-prep.sh`.*
Owner

Correction — the implementation does NOT belong here. It is release-toolkit#621.

Operator caught the misplacement. My two design comments above describe changes to release-prep.sh, changelog-body-check.sh and the reusable workflows — all toolkit code. purser only consumes the pin.

And the tracker already exists: release-toolkit#621"the changelog density gate watches CHANGELOG.md — the one layer where density cannot be durably fixed", kind/bug priority/high size/M, unassigned. I filed it on 2026-07-31 with the evidence assembled by Herald, and did not recall it while designing the same thing from scratch today.

It already covers the fragment-layer gate, the wrong-axis LENGTH_WARN_CHARS signal, and check 7's per-paragraph under-reporting. The operator's rolling-PR-description design is added there as #621's comment 94302.

What stays on #47

This tracker is the purser-side consumer, and its remaining work is small:

  • Adopt the toolkit pin once #621 ships the fragment gate
  • Add fragment-check.yml's prose sibling (or extend its paths) per whatever shape #621 lands
  • Verify against #25's content: a 36-word fragment sentence is refused at purser PR time

Nothing here can be built before #621 lands, so #47 is blocked on it rather than on anything of its own — the same relationship #41 has to this tracker.

release-toolkit#621   the mechanism        priority/high, unassigned
purser#47             adopt the pin        blocked on #621
purser#41             the CI-gap tracker   blocked on #47

Worth naming

The design work above is not wasted and is not duplicated — it is on #621 where it acts. But the sequence is the point: the operator asked "is this possible", I designed it, and only his second question — "does that not belong to release-toolkit?" — surfaced a tracker I had written myself five weeks earlier.

The prompt that would have found it was a question about repo placement, not about prior art. Which is exactly why search before you build keeps failing to fire: nothing about designing a feature feels like a moment to go looking for whether it is already filed.

Placement correction: operator.

## Correction — the implementation does NOT belong here. It is `release-toolkit#621`. Operator caught the misplacement. My two design comments above describe changes to **`release-prep.sh`, `changelog-body-check.sh` and the reusable workflows — all toolkit code.** purser only consumes the pin. And the tracker already exists: **[`release-toolkit#621`](https://git.frankenbit.de/frankenbit/release-toolkit/issues/621)** — *"the changelog density gate watches CHANGELOG.md — the one layer where density cannot be durably fixed"*, `kind/bug` `priority/high` `size/M`, unassigned. **I filed it on 2026-07-31** with the evidence assembled by Herald, and did not recall it while designing the same thing from scratch today. It already covers the fragment-layer gate, the wrong-axis `LENGTH_WARN_CHARS` signal, and check 7's per-paragraph under-reporting. The operator's rolling-PR-description design is added there as `#621`'s comment 94302. ### What stays on #47 **This tracker is the purser-side consumer**, and its remaining work is small: - [ ] Adopt the toolkit pin once `#621` ships the fragment gate - [ ] Add `fragment-check.yml`'s prose sibling (or extend its `paths`) per whatever shape `#621` lands - [ ] Verify against **#25's content**: a 36-word fragment sentence is refused at purser PR time **Nothing here can be built before `#621` lands**, so #47 is blocked on it rather than on anything of its own — the same relationship #41 has to this tracker. ``` release-toolkit#621 the mechanism priority/high, unassigned purser#47 adopt the pin blocked on #621 purser#41 the CI-gap tracker blocked on #47 ``` ### Worth naming **The design work above is not wasted and is not duplicated** — it is on #621 where it acts. But the sequence is the point: the operator asked *"is this possible"*, I designed it, and only his second question — *"does that not belong to release-toolkit?"* — surfaced a tracker I had written myself five weeks earlier. **The prompt that would have found it was a question about repo placement, not about prior art.** Which is exactly why *search before you build* keeps failing to fire: nothing about designing a feature feels like a moment to go looking for whether it is already filed. *Placement correction: operator.*
Sign in to join this conversation.
No milestone
No project
No assignees
2 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/purser#47
No description provided.