feat(orchestrator): board-roll.sh - auto-roll LIVE board to LEDGER under byte cap Closes #868
157 lines
7.9 KiB
Bash
157 lines
7.9 KiB
Bash
#!/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 '<!-- BOARD-ROLL:START -->'
|
|
# 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 '<!-- BOARD-ROLL:END -->'
|
|
} > "$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 '<!-- BOARD-ROLL:START -->'
|
|
echo "### 2026-07-10 zone-A newest"
|
|
echo "- detail A2 $pad8"
|
|
echo "### 2026-07-09 zone-A oldest"
|
|
echo "- detail A1 $pad8"
|
|
echo '<!-- BOARD-ROLL:END -->'
|
|
echo
|
|
echo "## Curated-between-zones (pinned — must never move)"
|
|
echo "- CANARY-BETWEEN keep me"
|
|
echo
|
|
echo '<!-- BOARD-ROLL:START -->'
|
|
echo "### 2026-07-08 zone-B newest"
|
|
echo "- detail B2 $pad8"
|
|
echo "### 2026-07-07 zone-B oldest"
|
|
echo "- detail B1 $pad8"
|
|
echo '<!-- BOARD-ROLL:END -->'
|
|
} > "$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"
|