#!/usr/bin/env bash # Regression harness for board-roll.sh — rolling oldest LIVE-board entries to LEDGER. # # Asserts: # 1. Under cap → no-op, exit 0, files unchanged. # 2. Over cap, no roll markers → exit 3, LIVE unchanged (never guesses). # 3. Over cap, markers present → rolls the fewest oldest entries to get under cap, # LIVE ends under cap, pinned preamble/footer + newest entries preserved. # 4. Rolled blocks land in the LEDGER verbatim, oldest set in original order. # 5. --dry-run changes nothing and reports a plan. # 6. Zone emptied but pinned sections alone exceed cap → exit 3. # 7. --help exits 0 and prints usage; an unknown flag exits nonzero (#701 discipline). # 8. More than one marker pair → exit 3, unchanged; curated content between the two # zones is never relocated to the LEDGER (rev0 #868 regression). set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SUT="$SCRIPT_DIR/board-roll.sh" fail=0 note() { echo "FAIL: $*" >&2; fail=1; } WORK="$(mktemp -d)" trap 'rm -rf "$WORK"' EXIT # builds a LIVE board: pinned preamble + roll zone with N dated entries (newest first), # each entry padded to be individually large so the cap math is predictable. make_board() { # $1 file $2 n_entries $3 with_markers(1/0) $4 pad_bytes local f=$1 n=$2 markers=$3 pad=$4 i padtxt padtxt=$(head -c "$pad" < /dev/zero | tr '\0' 'x') { echo "# BOARD — LIVE" echo "> pinned protocol blockquote, never rolled." echo echo "## Curated always-current section (pinned)" echo "- this stays no matter what" echo [[ "$markers" == 1 ]] && echo '' # newest first (i=n .. 1); oldest (i=1) ends at the bottom for (( i=n; i>=1; i-- )); do echo "### 2026-07-$(printf '%02d' $i) tick number $i" echo "- detail $i $padtxt" echo done [[ "$markers" == 1 ]] && echo '' } > "$f" return 0 } # ── 1. under cap → no-op ─────────────────────────────────────────────────────── L="$WORK/live1.md"; G="$WORK/ledger1.md"; : > "$G" make_board "$L" 2 1 10 before=$(cat "$L") if ! out=$(bash "$SUT" --live "$L" --ledger "$G" --cap 100000 2>&1); then note "under-cap should exit 0 (got nonzero): $out" fi [[ "$(cat "$L")" == "$before" ]] || note "under-cap modified LIVE" [[ -s "$G" ]] && note "under-cap wrote to LEDGER" # ── 2. over cap, no markers → exit 3, unchanged ──────────────────────────────── L="$WORK/live2.md"; G="$WORK/ledger2.md"; : > "$G" make_board "$L" 6 0 400 before=$(cat "$L") set +e; bash "$SUT" --live "$L" --ledger "$G" --cap 800 >/dev/null 2>&1; rc=$?; set -e [[ "$rc" -eq 3 ]] || note "no-markers over-cap should exit 3 (got $rc)" [[ "$(cat "$L")" == "$before" ]] || note "no-markers run modified LIVE (must never guess)" # ── 3+4. over cap with markers → rolls oldest, LIVE under cap, LEDGER gets them ─ L="$WORK/live3.md"; G="$WORK/ledger3.md"; echo "# LEDGER" > "$G" make_board "$L" 6 1 400 # 6 entries, each ~>400B big=$(wc -c < "$L") [[ "$big" -ge 2000 ]] || note "fixture too small to test rolling ($big B)" if ! out=$(bash "$SUT" --live "$L" --ledger "$G" --cap 2000 2>&1); then note "marker roll should exit 0 when it can get under cap: $out" fi after=$(wc -c < "$L") [[ "$after" -lt 2000 ]] || note "LIVE still >= cap after roll ($after B)" # pinned content survives grep -q "Curated always-current section" "$L" || note "roll dropped pinned section" grep -q 'BOARD-ROLL:START' "$L" || note "roll dropped START marker" grep -q 'BOARD-ROLL:END' "$L" || note "roll dropped END marker" # newest entry (07-06) stays; oldest (07-01) is the first to leave grep -q "### 2026-07-06 tick number 6" "$L" || note "roll dropped the newest entry" grep -q "### 2026-07-01 tick number 1" "$L" && note "oldest entry not rolled out of LIVE" # oldest went to LEDGER grep -q "### 2026-07-01 tick number 1" "$G" || note "oldest entry not appended to LEDGER" grep -q "board-roll:.*rolled from live3.md" "$G" || note "LEDGER missing provenance separator" # a rolled entry must not be duplicated (present in exactly one of LIVE/LEDGER) if grep -q "### 2026-07-01 tick number 1" "$L"; then note "rolled entry duplicated in LIVE"; fi # LEDGER original content preserved grep -q "^# LEDGER" "$G" || note "roll clobbered existing LEDGER content" # ── 5. --dry-run changes nothing ─────────────────────────────────────────────── L="$WORK/live5.md"; G="$WORK/ledger5.md"; echo "# LEDGER" > "$G" make_board "$L" 6 1 400 before_l=$(cat "$L"); before_g=$(cat "$G") out=$(bash "$SUT" --live "$L" --ledger "$G" --cap 2000 --dry-run 2>&1) || note "dry-run exited nonzero: $out" echo "$out" | grep -qi "dry run" || note "dry-run did not announce itself" echo "$out" | grep -q "would roll" || note "dry-run did not report a plan" [[ "$(cat "$L")" == "$before_l" ]] || note "dry-run modified LIVE" [[ "$(cat "$G")" == "$before_g" ]] || note "dry-run modified LEDGER" # ── 6. zone emptied, pinned alone over cap → exit 3 ──────────────────────────── # cap 120 is below the pinned preamble+footer size (~180B), so even after rolling # every zone entry the LIVE file stays over cap → must report the unsatisfiable case. L="$WORK/live6.md"; G="$WORK/ledger6.md"; echo "# LEDGER" > "$G" make_board "$L" 3 1 50 set +e; bash "$SUT" --live "$L" --ledger "$G" --cap 120 >/dev/null 2>&1; rc=$?; set -e [[ "$rc" -eq 3 ]] || note "unsatisfiable cap should exit 3 (got $rc)" # ── 7. help exits 0, unknown flag exits nonzero (#701) ───────────────────────── if ! out=$(bash "$SUT" --help 2>&1); then note "--help exited nonzero"; fi [[ "$out" == Usage:* ]] || note "--help did not print usage" bash "$SUT" -h >/dev/null 2>&1 || note "-h exited nonzero" if bash "$SUT" --not-a-real-flag >/dev/null 2>&1; then note "unknown flag was accepted"; fi if bash "$SUT" --live "$WORK/live3.md" >/dev/null 2>&1; then note "missing --ledger was accepted"; fi # ── 8. multiple marker pairs → exit 3, unchanged (no cross-zone relocation) ───── # Two separately-marked zones with a curated pinned section BETWEEN them. A naive # first-START..last-END span would sweep that curated section (and the intermediate # markers) into the LEDGER. board-roll must refuse (exit 3) and touch nothing. L="$WORK/live8.md"; G="$WORK/ledger8.md"; echo "# LEDGER" > "$G" pad8=$(head -c 300 < /dev/zero | tr '\0' 'x') { echo "# BOARD — LIVE" echo "> pinned protocol blockquote" echo echo '' echo "### 2026-07-10 zone-A newest" echo "- detail A2 $pad8" echo "### 2026-07-09 zone-A oldest" echo "- detail A1 $pad8" echo '' echo echo "## Curated-between-zones (pinned — must never move)" echo "- CANARY-BETWEEN keep me" echo echo '' echo "### 2026-07-08 zone-B newest" echo "- detail B2 $pad8" echo "### 2026-07-07 zone-B oldest" echo "- detail B1 $pad8" echo '' } > "$L" before8=$(cat "$L") set +e; bash "$SUT" --live "$L" --ledger "$G" --cap 80 >/dev/null 2>&1; rc=$?; set -e [[ "$rc" -eq 3 ]] || note "multi-pair board should exit 3 (got $rc)" [[ "$(cat "$L")" == "$before8" ]] || note "multi-pair run modified LIVE (must never guess across zones)" grep -q "CANARY-BETWEEN keep me" "$L" || note "multi-pair run relocated curated between-zones content" grep -q "CANARY-BETWEEN" "$G" && note "curated between-zones content leaked into LEDGER" if [[ "$fail" -eq 0 ]]; then echo "board-roll regression passed (8 groups)" fi exit "$fail"