From 2fd9cb7dedab08975b1116b74330d4e34e2ff296 Mon Sep 17 00:00:00 2001 From: claude-bot-andresmgsl Date: Sat, 25 Jul 2026 13:12:30 +0000 Subject: [PATCH] test: sentinel rows across the shape predicate, the armed guard, and the assembler Must-pass and must-fail rows from #182's test plan: the flip shape green under the sentinel, drift and malformed sentinels red with file named, the D3 fragment-list assertion, and D5 sentinel survival through consumption. Co-Authored-By: Claude Fable 5 --- test/changelog-armed.test.sh | 42 ++++++++++++ test/changelog-assemble.test.sh | 45 +++++++++++++ test/changelog.test.sh | 115 ++++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+) diff --git a/test/changelog-armed.test.sh b/test/changelog-armed.test.sh index df00bdb..fdb3700 100644 --- a/test/changelog-armed.test.sh +++ b/test/changelog-armed.test.sh @@ -362,6 +362,48 @@ check "fragment mode refuses a flat set over a grouped published section" 1 \ "changelog.d/115.md' is flat but newest published section '1.2.3'" \ in_tree fragments-dev-flat-over-grouped +# The declared anchor (#182): the flip tree — a grouped set under a +# 'grouped' sentinel over a flat published section — is green, where the +# same tree minus the sentinel is the all-grouped-over-flat red row above. +fragment_tree fragments-dev-flip 1.2.4-dev <<'EOF' +# Changelog + +## 1.2.3 — 2026-07-20 + +- The shipped entry. +EOF +printf '%s\n' "grouped" >"$TMP/fragments-dev-flip/changelog.d/shape" +cat >"$TMP/fragments-dev-flip/changelog.d/115.md" <<'EOF' +### Fixed + +- Grouped fragment. +EOF +check "fragment mode: 'grouped' sentinel admits the flip tree over a flat published section" 0 \ + "fragment mode" in_tree fragments-dev-flip + +# Post-flip drift is refused on its own PR: a flat probe fragment atop the +# flip tree goes red — beside grouped fragments the mix rule names it first. +printf '%s\n' "- Flat probe." >"$TMP/fragments-dev-flip/changelog.d/116.md" +check "fragment mode: a flat probe atop the flip tree is refused" 1 \ + "changelog.d/115.md' is grouped but fragment 'changelog.d/116.md' is not" \ + in_tree fragments-dev-flip +rm "$TMP/fragments-dev-flip/changelog.d/116.md" + +# And once the grouped fragments are consumed, the sentinel alone still +# holds the shape: an all-flat set under 'grouped' is refused, sentinel +# named — the published-section inference never gets a say. +rm "$TMP/fragments-dev-flip/changelog.d/115.md" +printf '%s\n' "- Flat probe." >"$TMP/fragments-dev-flip/changelog.d/116.md" +check "fragment mode: a flat set under the 'grouped' sentinel refused, sentinel named" 1 \ + "changelog.d/116.md' is flat but 'changelog.d/shape' declares grouped" \ + in_tree fragments-dev-flip +rm "$TMP/fragments-dev-flip/changelog.d/116.md" + +printf '%s\n' "Grouped" >"$TMP/fragments-dev-flip/changelog.d/shape" +check "fragment mode: a malformed sentinel is refused, file named" 1 \ + "'changelog.d/shape' declares neither shape" \ + in_tree fragments-dev-flip + fragment_tree fragments-dev-no-published 1.2.4-dev <<'EOF' # Changelog diff --git a/test/changelog-assemble.test.sh b/test/changelog-assemble.test.sh index 34f625b..de008b9 100644 --- a/test/changelog-assemble.test.sh +++ b/test/changelog-assemble.test.sh @@ -143,6 +143,51 @@ check "grouped: the written file is exact" 0 "" \ assert_file "$TMP/grouped/CHANGELOG.md" \ $'# Changelog\n\nPreamble prose belongs to no section.\n\n## 0.2.0 — 2026-07-24\n\n'"$GROUPED_BODY"$'\n\n## 0.1.0 — 2026-07-01\n\n### Fixed\n\n- The shipped entry.' +# --- the declared anchor: the first grouped release over a flat history ------ +# The flip ceremony (#182): a 'grouped' sentinel admits grouped fragments +# over a flat newest published section, the sentinel is never a stray file, +# and it survives the consumption (D5) — the next -dev tree still declares +# its shape. + +tree flip <"$TMP/flip/changelog.d/shape" +frag flip 40.md <<'EOF' +### Added + +- Forty landed. +EOF +check "sentinel: the flip release assembles grouped over a flat published section" 0 \ + "consumed 1 fragment" in_tree flip 0.2.0 2026-07-24 +check "sentinel: the written flip section is exact" 0 "" \ + assert_file "$TMP/flip/CHANGELOG.md" \ + $'# Changelog\n\nPreamble prose belongs to no section.\n\n## 0.2.0 — 2026-07-24\n\n### Added\n\n- Forty landed.\n\n## 0.1.0 — 2026-07-01\n\n- The shipped entry.' +check "sentinel: changelog.d/shape survives consumption" 0 "" \ + test -e "$TMP/flip/changelog.d/shape" + +tree flip-flat-frag <"$TMP/flip-flat-frag/changelog.d/shape" +frag flip-flat-frag 41.md <<'EOF' +- Flat forty-one. +EOF +check "sentinel: a flat fragment under 'grouped' refuses, sentinel named" 1 \ + "changelog.d/shape' declares grouped" in_tree flip-flat-frag 0.2.0 2026-07-24 + +tree flip-malformed <"$TMP/flip-malformed/changelog.d/shape" +frag flip-malformed 42.md <<'EOF' +### Added + +- Forty-two. +EOF +check "sentinel: a malformed sentinel refuses, file named" 1 \ + "changelog.d/shape' declares neither shape" in_tree flip-malformed 0.2.0 2026-07-24 + # --- a changelog holding only its preamble ----------------------------------- tree preamble-only <<'EOF' diff --git a/test/changelog.test.sh b/test/changelog.test.sh index a04aa74..57f7b74 100755 --- a/test/changelog.test.sh +++ b/test/changelog.test.sh @@ -469,4 +469,119 @@ rm "$SHAPE_DIR/1.md" check "shape: empty fragment set makes the anchor rule vacuous" 0 "" \ changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR" +# --- the declarable anchor: /shape (#182) ------------------------------- +# The sentinel pins the set's shape and outranks the newest-published-section +# inference — the door a deliberate flip walks through, while the undeclared +# drift rows above stay red, verbatim. + +cat >"$SHAPE_CHANGELOG" <<'EOF' +# Changelog + +## 2.0.0 — 2026-07-24 + +- Newest section is flat. +EOF +cat >"$SHAPE_DIR/1.md" <<'EOF' +### Fixed + +- Grouped fragment. +EOF +printf 'grouped\n' >"$SHAPE_DIR/shape" +check "shape: 'grouped' sentinel admits a grouped set over a flat published section" 0 "" \ + changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR" +check "shape: the sentinel binds with no changelog at all — the assembler's call" 0 "" \ + changelog_shape_problem "" "$SHAPE_DIR" + +printf -- '- Flat fragment.\n' >"$SHAPE_DIR/1.md" +check "shape: flat fragment under a 'grouped' sentinel refused, fragment and sentinel named" 1 \ + "fragment '$SHAPE_DIR/1.md' is flat but '$SHAPE_DIR/shape' declares grouped" \ + changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR" + +cat >"$SHAPE_CHANGELOG" <<'EOF' +# Changelog + +## 2.0.0 — 2026-07-24 + +### Fixed + +- Newest section is grouped. +EOF +printf 'flat\n' >"$SHAPE_DIR/shape" +check "shape: 'flat' sentinel admits a flat set over a grouped published section" 0 "" \ + changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR" +cat >"$SHAPE_DIR/1.md" <<'EOF' +### Fixed + +- Grouped fragment. +EOF +check "shape: grouped fragment under a 'flat' sentinel refused, fragment and sentinel named" 1 \ + "fragment '$SHAPE_DIR/1.md' is grouped but '$SHAPE_DIR/shape' declares flat" \ + changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR" + +printf 'grouped\n' >"$SHAPE_DIR/shape" +printf -- '- Flat two.\n' >"$SHAPE_DIR/2.md" +check "shape: a mixed set is refused regardless of the sentinel" 1 \ + "fragment '$SHAPE_DIR/1.md' is grouped but fragment '$SHAPE_DIR/2.md' is not" \ + changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR" +rm "$SHAPE_DIR/1.md" "$SHAPE_DIR/2.md" + +check "shape: empty fragment set with a valid sentinel passes" 0 "" \ + changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR" + +printf 'Grouped\n' >"$SHAPE_DIR/shape" +check "shape: a capitalized sentinel is refused, file named" 1 \ + "'$SHAPE_DIR/shape' declares neither shape" \ + changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR" +: >"$SHAPE_DIR/shape" +check "shape: an empty sentinel is refused — never a silent fallback" 1 \ + "'$SHAPE_DIR/shape' declares neither shape" \ + changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR" +printf 'grouped\nflat\n' >"$SHAPE_DIR/shape" +check "shape: a two-line sentinel is refused" 1 \ + "'$SHAPE_DIR/shape' declares neither shape" \ + changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR" + +# The sentinel is not a fragment (#182 D3): the *.md glob is the mechanism, +# but the assertion is on the list itself, so a glob change cannot silently +# start publishing the sentinel. +printf 'grouped\n' >"$SHAPE_DIR/shape" +cat >"$SHAPE_DIR/1.md" <<'EOF' +### Fixed + +- Grouped fragment. +EOF +assert_fragments_exclude_sentinel() { + local out + out="$(changelog_fragments "$SHAPE_DIR")" + [ -n "$out" ] || { echo "wanted a non-empty fragment list"; return 1; } + if printf '%s\n' "$out" | grep -q '/shape$'; then + printf 'the sentinel leaked into the fragment list:\n%s\n' "$out" + return 1 + fi +} +check "fragments: the shape sentinel never enters the fragment list" 0 "" \ + assert_fragments_exclude_sentinel +rm "$SHAPE_DIR/1.md" "$SHAPE_DIR/shape" + +AS="$TMP/assemble-sentinel" +mkdir -p "$AS" +printf 'grouped\n' >"$AS/shape" +cat >"$AS/30.md" <<'EOF' +### Fixed + +- Fixed thirty. +EOF +cat >"$AS/31.md" <<'EOF' +### Added + +- Added thirty-one. +EOF +check "assemble: the sentinel never assembles, and canonical order holds under it" 0 "" \ + assert_assemble "$AS" $'### Added\n\n- Added thirty-one.\n\n### Fixed\n\n- Fixed thirty.' +rm "$AS/30.md" "$AS/31.md" +printf -- '- Flat probe.\n' >"$AS/29.md" +check "assemble: a flat set under a 'grouped' sentinel refuses to assemble" 1 \ + "declares grouped" \ + changelog_assemble "$AS" + summary