Pre-merge PR gate for changelog fragment-kind discipline (reusable workflow, sibling to manifest-check) #271

Closed
opened 2026-06-30 17:51:23 +02:00 by bosun · 1 comment
Owner

Problem

The fragment-kind discipline (release-prep.sh's check that <id>.<kind>.md uses one of added/changed/deprecated/removed/fixed/security/internal) only fires at release-prep time — well after the bad fragment has merged to main.

Concrete failure shape (tmux-tell, 2026-06-30): the docs-cluster PR #655 used .documentation.md as the fragment suffix. The PR merged cleanly (no pre-merge gate caught it). When release.yml next tried to roll a release-prep PR for v0.25.0, release-prep.sh exited FATAL:

error: unknown-kind fragment(s) found in changelog.d:
  - changelog.d/619.documentation.md (kind extracted: documentation; must be one of: added changed deprecated removed fixed security internal)
  - changelog.d/620.documentation.md
error: rename to <id>.<kind>.md with kind in the supported set, or remove the file.
[release-prep] FATAL: fragment-kind discipline failed; abort cut.

Required a recovery PR (tmux-tell#658 rename .documentation.md.added.md) before the next cut could proceed. The failure surfaces AFTER the substrate-of-record is wrong; the recovery cost (and the cut delay) compounds with cut cadence.

Proposed fix: reusable PR-gate workflow

Add reusable-changelog-fragment-check.yml to release-toolkit, sibling to the existing reusable-manifest-check.yml. Triggers on pull_request events that touch changelog.d/; runs the existing fragment-kind validation logic from scripts/lib/fragments.sh (or a thin wrapper around it); fails the PR check if any fragment has an unknown kind.

Consumers wire it in via uses: <toolkit-self>/.forgejo/workflows/reusable-changelog-fragment-check.yml@<rc-tag> in their manifest-check.yml (or a sibling fragment-check.yml), same pattern as the manifest check.

Acceptance criteria

  1. New workflow file .forgejo/workflows/reusable-changelog-fragment-check.yml:
    • Triggers on pull_request (or workflow_call consumed by per-repo pull_request wiring)
    • Filters to PRs touching changelog.d/**
    • Validates each touched fragment's kind against the accepted-kinds set
    • Exits non-zero with the same error message shape release-prep.sh emits
  2. Consumer wiring example documented (README + AGENTS.md): how to add to a .forgejo/workflows/fragment-check.yml in a consumer repo
  3. tmux-tell wires the new check in once landed; future .documentation.md-style mistakes fail PR check before merge
  4. Test coverage: bats test for the validation logic; existing tests in tests/ directory model the shape
  5. Self-hosted: release-toolkit's own PRs use the new check (dogfood)

Refs

  • Empirical anchor: tmux-tell#658 (recovery PR for the .documentation.md fragment-kind mistake from #655)
  • Existing precedent: reusable-manifest-check.yml (same shape: PR-gate for a release-toolkit invariant)
  • Existing validation logic: scripts/lib/fragments.sh (extract or wrap)
  • Bosun + operator framing 2026-06-30 — "catch the issue before it lands on main"

Anchor

2026-06-30 — empirical recurrence: 1 instance today (#655#658 recovery). Pre-merge gate prevents the recurrence + the compounding recovery cost. Composes with the wider release-toolkit substrate-care work this morning (release-toolkit#268 + #269 + #270 bake-reset commit guard cycle).

## Problem The fragment-kind discipline (release-prep.sh's check that `<id>.<kind>.md` uses one of `added/changed/deprecated/removed/fixed/security/internal`) only fires at **release-prep time** — well after the bad fragment has merged to main. Concrete failure shape (tmux-tell, 2026-06-30): the docs-cluster PR #655 used `.documentation.md` as the fragment suffix. The PR merged cleanly (no pre-merge gate caught it). When release.yml next tried to roll a release-prep PR for v0.25.0, release-prep.sh exited FATAL: ``` error: unknown-kind fragment(s) found in changelog.d: - changelog.d/619.documentation.md (kind extracted: documentation; must be one of: added changed deprecated removed fixed security internal) - changelog.d/620.documentation.md error: rename to <id>.<kind>.md with kind in the supported set, or remove the file. [release-prep] FATAL: fragment-kind discipline failed; abort cut. ``` Required a recovery PR (tmux-tell#658 rename `.documentation.md` → `.added.md`) before the next cut could proceed. The failure surfaces AFTER the substrate-of-record is wrong; the recovery cost (and the cut delay) compounds with cut cadence. ## Proposed fix: reusable PR-gate workflow Add `reusable-changelog-fragment-check.yml` to release-toolkit, sibling to the existing `reusable-manifest-check.yml`. Triggers on `pull_request` events that touch `changelog.d/`; runs the existing fragment-kind validation logic from `scripts/lib/fragments.sh` (or a thin wrapper around it); fails the PR check if any fragment has an unknown kind. Consumers wire it in via `uses: <toolkit-self>/.forgejo/workflows/reusable-changelog-fragment-check.yml@<rc-tag>` in their `manifest-check.yml` (or a sibling `fragment-check.yml`), same pattern as the manifest check. ## Acceptance criteria 1. New workflow file `.forgejo/workflows/reusable-changelog-fragment-check.yml`: - Triggers on `pull_request` (or `workflow_call` consumed by per-repo `pull_request` wiring) - Filters to PRs touching `changelog.d/**` - Validates each touched fragment's kind against the accepted-kinds set - Exits non-zero with the same error message shape release-prep.sh emits 2. Consumer wiring example documented (README + AGENTS.md): how to add to a `.forgejo/workflows/fragment-check.yml` in a consumer repo 3. tmux-tell wires the new check in once landed; future `.documentation.md`-style mistakes fail PR check before merge 4. Test coverage: bats test for the validation logic; existing tests in tests/ directory model the shape 5. Self-hosted: release-toolkit's own PRs use the new check (dogfood) ## Refs - Empirical anchor: tmux-tell#658 (recovery PR for the `.documentation.md` fragment-kind mistake from #655) - Existing precedent: `reusable-manifest-check.yml` (same shape: PR-gate for a release-toolkit invariant) - Existing validation logic: `scripts/lib/fragments.sh` (extract or wrap) - Bosun + operator framing 2026-06-30 — "catch the issue before it lands on main" ## Anchor 2026-06-30 — empirical recurrence: 1 instance today (#655 → #658 recovery). Pre-merge gate prevents the recurrence + the compounding recovery cost. Composes with the wider release-toolkit substrate-care work this morning (release-toolkit#268 + #269 + #270 bake-reset commit guard cycle).
Author
Owner

Substantive update from Surveyor's review of tmux-tell#662 (same-day recurrence)

Today's second .documentation.md recurrence: Engineer's tmux-tell#662 (anti-stall pin docs PR, closing #615) initially used .documentation.md as the changelog suffix — same mistake as tmux-tell#655 hours earlier (which forced the recovery PR #658). Surveyor caught it at review with REQUEST_CHANGES; Engineer renamed .documentation.md → .added.md.

Empirical reinforcement of #271's value: a competent chamber made the same mistake twice in one day even after the first incident was visible. The discipline-pin-alone-doesn't-fire pattern — what's needed is the substrate-side gate.

Sharpened root cause (per Surveyor's at-source diagnosis)

The bug isn't a missing validator — it's validator divergence:

  • changelog-assemble/main.go:60 ACCEPTS documentation as a kind
  • changie/release-prep.sh REJECTS it (accepts only added/changed/deprecated/removed/fixed/security/internal)

So a .documentation.md fragment passes the per-PR -check (changelog-assemble says "well-formed") + fails at release-prep time. The two validators have DIFFERENT kind-lists.

Updated fix scope

Original #271 framing: "add fragment-kind validation to per-PR gate". Sharper version: unify the kind-list across both validators. Either:

  • (α) Tighten changelog-assemble/main.go:60 to use the same accepted-kinds set as release-prep.sh — then the existing per-PR -check becomes the gate (no new workflow needed)
  • (β) Keep both validators but make them share a single source-of-truth for the kind list (e.g. extract to a shared constant or config)

Lean: (α) — substantively-simpler; the per-PR -check already runs on every PR touching changelog.d/; just needs the kind-list tightened. The new reusable-workflow approach I originally proposed is unnecessary if the existing -check becomes the gate.

Refs

  • Original filing: this issue
  • Surveyor review: tmux-tell#662#issuecomment-77118 (validator-divergence diagnosis + same-day recurrence framing)
  • tmux-tell#655 (first .documentation.md mistake; recovery PR #658)
  • tmux-tell#662 (second .documentation.md mistake; rename caught at REQUEST_CHANGES)
## Substantive update from Surveyor's review of tmux-tell#662 (same-day recurrence) **Today's second `.documentation.md` recurrence**: Engineer's tmux-tell#662 (anti-stall pin docs PR, closing #615) initially used `.documentation.md` as the changelog suffix — same mistake as tmux-tell#655 hours earlier (which forced the recovery PR #658). Surveyor caught it at review with REQUEST_CHANGES; Engineer renamed `.documentation.md → .added.md`. **Empirical reinforcement of #271's value**: a competent chamber made the same mistake twice in one day even after the first incident was visible. The discipline-pin-alone-doesn't-fire pattern — what's needed is the substrate-side gate. ## Sharpened root cause (per Surveyor's at-source diagnosis) The bug isn't a missing validator — it's **validator divergence**: - `changelog-assemble/main.go:60` ACCEPTS `documentation` as a kind - `changie/release-prep.sh` REJECTS it (accepts only added/changed/deprecated/removed/fixed/security/internal) So a `.documentation.md` fragment passes the per-PR `-check` (changelog-assemble says "well-formed") + fails at release-prep time. The two validators have DIFFERENT kind-lists. ## Updated fix scope Original #271 framing: "add fragment-kind validation to per-PR gate". Sharper version: **unify the kind-list across both validators**. Either: - **(α)** Tighten `changelog-assemble/main.go:60` to use the same accepted-kinds set as release-prep.sh — then the existing per-PR `-check` becomes the gate (no new workflow needed) - **(β)** Keep both validators but make them share a single source-of-truth for the kind list (e.g. extract to a shared constant or config) **Lean: (α)** — substantively-simpler; the per-PR `-check` already runs on every PR touching changelog.d/; just needs the kind-list tightened. The new reusable-workflow approach I originally proposed is unnecessary if the existing `-check` becomes the gate. ## Refs - Original filing: this issue - Surveyor review: tmux-tell#662#issuecomment-77118 (validator-divergence diagnosis + same-day recurrence framing) - tmux-tell#655 (first `.documentation.md` mistake; recovery PR #658) - tmux-tell#662 (second `.documentation.md` mistake; rename caught at REQUEST_CHANGES)
bosun closed this issue 2026-07-02 10:16:32 +02:00
Sign in to join this conversation.
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#271
No description provided.