fix(list_fragments): FAIL-LOUD on unknown-kind fragments (silent content-loss class — Surveyor d586) #170

Closed
opened 2026-06-27 10:56:02 +02:00 by quartermaster · 0 comments

The phenomenon

Surveyor d586 §6 verify-at-source on rolling PR #165 surfaced two mis-named fragments:

  • changelog.d/153.feat.md (kind: feat)
  • changelog.d/164-fix-self-pin.fix.md (kind: fix)

These use conventional-commit TYPES (feat/fix) as the kind, but the fragment convention wants changelog KINDS (added/fixed). When list_fragments encountered them:

list_fragments: "153.feat.md does not match <id>.<kind>.md (unknown kind) - skipping"
list_fragments: "164-fix-self-pin.fix.md does not match <id>.<kind>.md (unknown kind) - skipping"

The fragments were silently dropped from the CHANGELOG build + delete_fragments left them on disk (because the deletion glob also goes through the same kind-validation).

Why it didn't break v0.12.0

Pure luck: the feat: + fix: COMMITS independently contributed the CHANGELOG entries (### Added from the conventional-commit type, ### Fixed from same). So the rolling PR's body looked correct. The fragments contributed NOTHING.

The CHANGELOG was right by backstop, not by fragment-fold. Empirically demonstrated by inspecting the rolling PR's body (### Added: auto-prune... matches the feat: commit; ### Fixed: prune-rc-tags... matches the fix: commit).

The deeper risk (the real reason to file)

warn-and-skip on unknown-kind is a silent content-loss class:

  • A fragment authored without a matching commit (e.g., a docs-only or future-state change captured in changelog.d/ alone) would be SILENTLY dropped if its kind has a typo
  • The CHANGELOG ships missing the entry, and the operator only notices when reading the release notes after the cut
  • No CI signal, no warning surface, no rolling-PR-body discrepancy hint

The v0.12.0 case is the benign version (commit-backed); the future cases where someone writes .feature.md instead of .added.md (typo) or .feat.md instead of .added.md (cross-convention confusion) lose content silently.

The fix

Replace warn-and-skip with fail-loud on unknown-kind:

  • list_fragments (scripts/lib/fragments.sh) emits an error to stderr + exits non-zero when it encounters an <id>.<kind>.md filename where <kind> isn't in the known set (added, changed, deprecated, removed, fixed, security)
  • release-prep.sh propagates the non-zero exit → the rolling PR build fails red → the operator sees the failure surface before merge

Bats coverage:

  • Mutation-verify: a fragment with kind feat (or any other not-in-set) → fragments.bats reds at the lint test
  • Known kinds still pass (regression guard)
  • Fragments with proper <id>.<kind>.md shape (e.g., 99.added.md) still get picked up + folded normally

Composition

  • Adjacent to #163 (alignment-enforcement at re-pin time) + the planned re-pin-time auto-prune evolution — all three are substrate-of-record hygiene at different mechanism surfaces (commit-walk vs fragment-fold vs tag-lifecycle).
  • The empirical exposure (v0.12.0 cut + Surveyor's at-source verify catching the dead cruft) is the n=1 for this discipline; future PRs that touch fragments should pre-flight check the kind set per the new fail-loud.

What this PR will NOT do

  • Will NOT change the fragment-kind set (still added / changed / deprecated / removed / fixed / security per Keep-a-Changelog convention)
  • Will NOT auto-rename or "fix" mis-named fragments (deletes are operator decisions — the script's job is to refuse to silently lose content)
  • Will NOT touch the conventional-commit-based CHANGELOG entry path (independent code path; fragments are the deliberate-authoring complement)

Refs

  • Surveyor d586 §6 verify-at-source on rolling PR #165 (the catch — fragments persist, "unknown kind" warnings, content-loss class framing)
  • scripts/lib/fragments.sh list_fragments function (the change site)
  • AGENTS.md section X (kept-a-changelog kinds — to be cross-referenced if added)
  • v0.12.0 cut (the empirical exposure)
## The phenomenon Surveyor d586 §6 verify-at-source on rolling PR #165 surfaced two mis-named fragments: - `changelog.d/153.feat.md` (kind: `feat`) - `changelog.d/164-fix-self-pin.fix.md` (kind: `fix`) These use **conventional-commit TYPES** (`feat`/`fix`) as the kind, but the fragment convention wants **changelog KINDS** (`added`/`fixed`). When `list_fragments` encountered them: ``` list_fragments: "153.feat.md does not match <id>.<kind>.md (unknown kind) - skipping" list_fragments: "164-fix-self-pin.fix.md does not match <id>.<kind>.md (unknown kind) - skipping" ``` The fragments were **silently dropped** from the CHANGELOG build + `delete_fragments` left them on disk (because the deletion glob also goes through the same kind-validation). ## Why it didn't break v0.12.0 Pure luck: the `feat:` + `fix:` COMMITS independently contributed the CHANGELOG entries (`### Added` from the conventional-commit type, `### Fixed` from same). So the rolling PR's body looked correct. The fragments contributed NOTHING. The CHANGELOG was right by backstop, not by fragment-fold. Empirically demonstrated by inspecting the rolling PR's body (`### Added: auto-prune...` matches the feat: commit; `### Fixed: prune-rc-tags...` matches the fix: commit). ## The deeper risk (the real reason to file) `warn-and-skip` on unknown-kind is a **silent content-loss class**: - A fragment authored without a matching commit (e.g., a docs-only or future-state change captured in `changelog.d/` alone) would be SILENTLY dropped if its kind has a typo - The CHANGELOG ships missing the entry, and the operator only notices when reading the release notes after the cut - No CI signal, no warning surface, no rolling-PR-body discrepancy hint The v0.12.0 case is the benign version (commit-backed); the future cases where someone writes `.feature.md` instead of `.added.md` (typo) or `.feat.md` instead of `.added.md` (cross-convention confusion) lose content silently. ## The fix Replace `warn-and-skip` with `fail-loud` on unknown-kind: - `list_fragments` (`scripts/lib/fragments.sh`) emits an error to stderr + exits non-zero when it encounters an `<id>.<kind>.md` filename where `<kind>` isn't in the known set (`added`, `changed`, `deprecated`, `removed`, `fixed`, `security`) - `release-prep.sh` propagates the non-zero exit → the rolling PR build fails red → the operator sees the failure surface before merge Bats coverage: - Mutation-verify: a fragment with kind `feat` (or any other not-in-set) → fragments.bats reds at the lint test - Known kinds still pass (regression guard) - Fragments with proper `<id>.<kind>.md` shape (e.g., `99.added.md`) still get picked up + folded normally ## Composition - Adjacent to #163 (alignment-enforcement at re-pin time) + the planned re-pin-time auto-prune evolution — all three are **substrate-of-record hygiene** at different mechanism surfaces (commit-walk vs fragment-fold vs tag-lifecycle). - The empirical exposure (v0.12.0 cut + Surveyor's at-source verify catching the dead cruft) is the n=1 for this discipline; future PRs that touch fragments should pre-flight check the kind set per the new fail-loud. ## What this PR will NOT do - Will NOT change the fragment-kind set (still `added` / `changed` / `deprecated` / `removed` / `fixed` / `security` per Keep-a-Changelog convention) - Will NOT auto-rename or "fix" mis-named fragments (deletes are operator decisions — the script's job is to refuse to silently lose content) - Will NOT touch the conventional-commit-based CHANGELOG entry path (independent code path; fragments are the deliberate-authoring complement) ## Refs - Surveyor d586 §6 verify-at-source on rolling PR #165 (the catch — fragments persist, "unknown kind" warnings, content-loss class framing) - `scripts/lib/fragments.sh` `list_fragments` function (the change site) - AGENTS.md section X (kept-a-changelog kinds — to be cross-referenced if added) - v0.12.0 cut (the empirical exposure)
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#170
No description provided.