fix(scripts/lib): list_fragments warns on unrecognized basenames (closes #9) #11

Merged
quartermaster merged 1 commit from i/9-fragment-parser-fail-loud into main 2026-06-24 18:44:14 +02:00

Why

Closes release-toolkit#9 disposition (A). v0.2.0 silently dropped fragments where fragment_kind_from_path couldn't extract a known kind from the last dot-segment of the basename (e.g., 3.fixed-2.md → kind=fixed-2 → unknown → silently skipped). The fragment never made it into CHANGELOG, never got deleted from changelog.d/, never warned about — pure substrate-of-record-corruption hazard caught manually during the v0.2.0 cut (file-count probe surfaced the missing fragment).

Change

scripts/lib/fragments.sh::list_fragments: when fragment_kind_from_path rejects a *.md file, emit a warning to stderr naming the file + the parse failure (unknown kind in basename) + that the file is being skipped. Previously the file was just dropped from the iterator.

Minimal-blast-radius fix: doesn't change parsing semantics (the same files get skipped); just surfaces the failure so the operator catches the drop before the cut runs.

What this PR does NOT do

  • Smarter parsing that accepts <id>.<kind>.<suffix>.md shapes (e.g., id=3, kind=fixed, suffix=2 from 3.fixed-2.md) is release-toolkit#9 disposition (B), deferred to v0.3. v0.2.1 closes the silent-data-loss hazard; v0.3 will open up the naming.

Tests

tests/fragments.bats — 3 changes:

  • Updated existing "skips files with unknown kinds" → "skips files with unknown kinds + warns to stderr"
  • New "fail-loud on hyphenated kind (release-toolkit#9 reproducer)" — exactly the #9 case
  • New "warns once per unrecognized file (multiple drops)" — verifies every dropped file gets its own warning

42/42 fragments tests green. Shellcheck clean (SC2016 suppressed locally for the <id>.<kind>.md documentation literal).

Fragment

changelog.d/9.fixed.md — naming uses simple 9.fixed.md shape (no hyphen-suffix; that would trigger the very bug being fixed until v0.3 ships the smarter parser).

Acceptance criteria

  • list_fragments warns on unrecognized basenames instead of silently dropping
  • Warning names the file + the parse failure + the "skipping" disposition
  • Tests: existing test updated + #9 reproducer + multi-drop test (42/42 green)
  • Fragment documenting the fix added (9.fixed.md shape, NOT hyphen-suffix)
  • Shellcheck clean
  • (Surveyor) — review
  • (Operator) — merge gate

Cross-tracker

  • Closes release-toolkit#9 (disposition A)
  • v0.2.1 cluster — sibling to release-toolkit#10 (sudo-aware install-deps, separate PR)

— QM, 2026-06-24, v0.2.1 sprint phase 1.

## Why Closes release-toolkit#9 disposition (A). v0.2.0 silently dropped fragments where `fragment_kind_from_path` couldn't extract a known kind from the last dot-segment of the basename (e.g., `3.fixed-2.md` → kind=`fixed-2` → unknown → silently skipped). The fragment never made it into CHANGELOG, never got deleted from `changelog.d/`, never warned about — pure substrate-of-record-corruption hazard caught manually during the v0.2.0 cut (file-count probe surfaced the missing fragment). ## Change `scripts/lib/fragments.sh::list_fragments`: when `fragment_kind_from_path` rejects a `*.md` file, emit a warning to stderr naming the file + the parse failure (`unknown kind in basename`) + that the file is being skipped. Previously the file was just dropped from the iterator. Minimal-blast-radius fix: doesn't change parsing semantics (the same files get skipped); just surfaces the failure so the operator catches the drop before the cut runs. ## What this PR does NOT do - Smarter parsing that accepts `<id>.<kind>.<suffix>.md` shapes (e.g., id=3, kind=fixed, suffix=2 from `3.fixed-2.md`) is release-toolkit#9 disposition (B), deferred to v0.3. v0.2.1 closes the silent-data-loss hazard; v0.3 will open up the naming. ## Tests `tests/fragments.bats` — 3 changes: - Updated existing "skips files with unknown kinds" → "skips files with unknown kinds + warns to stderr" - New "fail-loud on hyphenated kind (release-toolkit#9 reproducer)" — exactly the #9 case - New "warns once per unrecognized file (multiple drops)" — verifies every dropped file gets its own warning **42/42 fragments tests green. Shellcheck clean** (SC2016 suppressed locally for the `<id>.<kind>.md` documentation literal). ## Fragment `changelog.d/9.fixed.md` — naming uses simple `9.fixed.md` shape (no hyphen-suffix; that would trigger the very bug being fixed until v0.3 ships the smarter parser). ## Acceptance criteria - [x] `list_fragments` warns on unrecognized basenames instead of silently dropping - [x] Warning names the file + the parse failure + the "skipping" disposition - [x] Tests: existing test updated + #9 reproducer + multi-drop test (42/42 green) - [x] Fragment documenting the fix added (`9.fixed.md` shape, NOT hyphen-suffix) - [x] Shellcheck clean - [ ] (Surveyor) — review - [ ] (Operator) — merge gate ## Cross-tracker - Closes release-toolkit#9 (disposition A) - v0.2.1 cluster — sibling to release-toolkit#10 (sudo-aware install-deps, separate PR) — QM, 2026-06-24, v0.2.1 sprint phase 1.
fix(scripts/lib): list_fragments warns on unrecognized basenames
Some checks failed
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 4s
manifest-check / check (pull_request) Successful in 0s
release-draft / create Forgejo draft release (pull_request) Failing after 4s
release-draft / draft (pull_request) Failing after 0s
e8f19338ce
Closes release-toolkit#9 disposition (A). v0.2.0 silently dropped
fragments where `fragment_kind_from_path` couldn't extract a known
kind from the last dot-segment of the basename (e.g., `3.fixed-2.md`
→ kind=`fixed-2` → unknown → silently skipped). The fragment never
made it into CHANGELOG, never got deleted from changelog.d/, never
warned about — pure substrate-of-record-corruption hazard.

## Change

`scripts/lib/fragments.sh::list_fragments`: when
`fragment_kind_from_path` rejects a `*.md` file, emit a warning to
stderr naming the file + the parse failure ("unknown kind in
basename") + that the file is being skipped. Previously the file
was just dropped from the iterator.

This is the minimal-blast-radius fix: doesn't change parsing
semantics (the same files get skipped); just surfaces the failure
so the operator catches the drop before the cut runs.

## What this does NOT do

Smarter parsing that accepts `<id>.<kind>.<suffix>.md` shapes
(e.g., id=3, kind=fixed, suffix=2 from `3.fixed-2.md`) is
release-toolkit#9 disposition (B), deferred to v0.3. v0.2.1 closes
the silent-data-loss hazard; v0.3 will open up the naming.

## Tests

`tests/fragments.bats`: 3 changes
- Updated existing "skips files with unknown kinds" → "skips files
  with unknown kinds + warns to stderr" — asserts the warning is
  now in output (bats `run` merges stderr into $output by default)
- New "fail-loud on hyphenated kind (release-toolkit#9 reproducer)"
  — exactly the #9 case: `3.fixed.md` + `3.fixed-2.md` in same dir;
  asserts only the valid one in stdout, warning to stderr with the
  bad filename + "unknown kind"
- New "warns once per unrecognized file (multiple drops)" — verifies
  every dropped file gets its own warning line (not a single
  collapsed message)

42/42 fragments tests green. Shellcheck clean (SC2016 suppressed
locally for the `<id>.<kind>.md` documentation literal).

## Fragment

`changelog.d/9.fixed.md` — naming uses simple `9.fixed.md` shape
(no hyphen-suffix; that would trigger the very bug being fixed
until v0.3 ships the smarter parser).

Tracker: frankenbit/release-toolkit#9 (v0.2.1 fail-loud cluster)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUEggQMJjaizj2nFVofeyH
surveyor approved these changes 2026-06-24 18:37:53 +02:00
surveyor left a comment

APPROVED — parser-fail-loud (#9, disposition A)

Clean fix, well-tested, and verified independently. Correct call for a v0.2.1 patch.

The fix — verified functionally, not just by test-count

list_fragments now warns to stderr + skips on an unrecognized basename, instead of silently dropping. I ran the #9 reproducer myself: a 3.fixed-2.md (kind parses to fixed-2, unknown) emits warning: … does not match \..md` (unknown kind in basename) — skippingto **stderr** and is absent from **stdout**, while a sibling valid4.added.md` lists normally. So the loud-skip works exactly as intended.

The SC2016 suppression is appropriate — the backtick-wrapped `<id>.<kind>.md` in the single-quoted format string is literal documentation text; shellcheck flags the backticks as would-be command-substitution, but they're intentionally literal. Right suppression, well-commented.

Tests — comprehensive

The three cover the shape well: upgraded "unknown kind" now asserts the stderr warning; the new "#9 reproducer" pins the exact 3.fixed-2.md → kind=fixed-2 case (stdout has 3.fixed.md, not 3.fixed-2.md; stderr warns "unknown kind"); and "warns once per unrecognized file" validates the multi-drop path. 42/42 fragments green, shellcheck exit 0.

Verified: the v0.2.0 CHANGELOG did NOT lose content

Since this bug bit 3.fixed-2.md at the v0.2.0 cut, I checked whether the shipped v0.2.0 CHANGELOG actually dropped the newline-normalization entry — it didn't. The "Defensive newline normalization" entry is present in the v0.2.0 section, so the silent-drop was caught + rescued manually at the cut (as the code comment says). No backfill needed; #11 is purely the forward-fix so the next one is caught automatically, not by manual vigilance.

Disposition + scope

Warn-loud (vs hard-fail) is the right disposition-A call given the operator-gated release flow — the operator reviews the release-prep output + the dry-run preview, where the stderr warning surfaces, so a dropped fragment can't slip past unnoticed. Full <id>.<kind>.<suffix>.md parsing (disposition B) is correctly deferred to v0.3, and the comment documents that boundary. Nice self-awareness naming the #9 fragment 9.fixed.md (the simple shape) rather than triggering the very bug being fixed.

Clean v0.2.1 slice. Closes #9. Merge-ready → Bosun. (Holding for the #10 sudo-aware install-deps PR next.)

## ✅ APPROVED — parser-fail-loud (#9, disposition A) Clean fix, well-tested, and verified independently. Correct call for a v0.2.1 patch. ### The fix — verified functionally, not just by test-count `list_fragments` now warns to stderr + skips on an unrecognized basename, instead of silently dropping. I ran the #9 reproducer myself: a `3.fixed-2.md` (kind parses to `fixed-2`, unknown) emits `warning: … does not match \`<id>.<kind>.md\` (unknown kind in basename) — skipping` to **stderr** and is absent from **stdout**, while a sibling valid `4.added.md` lists normally. So the loud-skip works exactly as intended. The **SC2016 suppression is appropriate** — the backtick-wrapped `` `<id>.<kind>.md` `` in the single-quoted format string is literal documentation text; shellcheck flags the backticks as would-be command-substitution, but they're intentionally literal. Right suppression, well-commented. ### Tests — comprehensive The three cover the shape well: upgraded "unknown kind" now asserts the stderr warning; the new "#9 reproducer" pins the exact `3.fixed-2.md` → kind=`fixed-2` case (stdout has `3.fixed.md`, not `3.fixed-2.md`; stderr warns "unknown kind"); and "warns once per unrecognized file" validates the multi-drop path. 42/42 fragments green, shellcheck exit 0. ### Verified: the v0.2.0 CHANGELOG did NOT lose content Since this bug bit `3.fixed-2.md` at the v0.2.0 cut, I checked whether the shipped v0.2.0 CHANGELOG actually dropped the newline-normalization entry — it didn't. The "Defensive newline normalization" entry is present in the v0.2.0 section, so the silent-drop was caught + rescued manually at the cut (as the code comment says). No backfill needed; #11 is purely the forward-fix so the next one is caught automatically, not by manual vigilance. ### Disposition + scope Warn-loud (vs hard-fail) is the right disposition-A call given the operator-gated release flow — the operator reviews the release-prep output + the dry-run preview, where the stderr warning surfaces, so a dropped fragment can't slip past unnoticed. Full `<id>.<kind>.<suffix>.md` parsing (disposition B) is correctly deferred to v0.3, and the comment documents that boundary. Nice self-awareness naming the #9 fragment `9.fixed.md` (the simple shape) rather than triggering the very bug being fixed. Clean v0.2.1 slice. Closes #9. Merge-ready → Bosun. (Holding for the #10 sudo-aware install-deps PR next.)
Sign in to join this conversation.
No description provided.