fix(board-roll): reject >1 marker pair instead of spanning across zones
Some checks failed
ci/woodpecker/pr/ci Pipeline failed
Some checks failed
ci/woodpecker/pr/ci Pipeline failed
rev0 (PR #868, Gitea comment #18505) caught a real data-relocation bug: the marker scan locked start_idx to the FIRST START but overwrote end_idx on EVERY END, so a board with two BOARD-ROLL zones collapsed into one first-START..last-END span. That span engulfed the curated `##` content and the intermediate START/END markers between the two zones, silently relocating pinned lines into the LEDGER — the exact failure this tool exists to prevent. Fix (conservative, matches the "no markers = exit 3" posture): count START and END markers; if either appears more than once, refuse with exit 3 and change nothing. Single-zone behavior is unchanged. Adds regression group 8: two zones with a curated CANARY section between them, cap 80 → asserts exit 3, LIVE unchanged, and the canary never leaks into the LEDGER. shellcheck -S warning clean; 8/8 groups pass. Refs #868
This commit is contained in:
@@ -45,8 +45,9 @@
|
||||
# 0 LIVE is under cap (already, or after rolling); on --dry-run, 0 = a plan exists
|
||||
# (or nothing to do)
|
||||
# 2 usage / argument / IO error (bad flag, missing file, unwritable target)
|
||||
# 3 cannot satisfy the cap: no roll markers present, OR the zone was emptied and
|
||||
# LIVE is still over cap (curated pinned sections need a manual trim)
|
||||
# 3 cannot satisfy the cap: no roll markers present, MORE THAN ONE marker pair
|
||||
# (multiple zones are refused, not guessed), OR the zone was emptied and LIVE
|
||||
# is still over cap (curated pinned sections need a manual trim)
|
||||
#
|
||||
# NOTE: line endings are normalized to LF on rewrite (boards are LF markdown); a
|
||||
# trailing newline is always ensured. Writes are atomic (temp file + mv) so a
|
||||
@@ -116,11 +117,30 @@ if (( orig_size < CAP )); then
|
||||
fi
|
||||
|
||||
# --- locate the roll-zone markers ----------------------------------------------
|
||||
start_idx=-1; end_idx=-1
|
||||
# Exactly ONE marker pair is supported. If a board carries more than one START or
|
||||
# END marker we REFUSE (exit 3, zero changes) rather than guess: a naive
|
||||
# first-START..last-END span would swallow the curated content and the intermediate
|
||||
# markers sitting between two intended zones and silently relocate that pinned text
|
||||
# to the LEDGER — the exact data-loss this tool exists to prevent. Refusing matches
|
||||
# the "no markers = exit 3" conservative posture.
|
||||
start_idx=-1; end_idx=-1; start_count=0; end_count=0
|
||||
for i in "${!LINES[@]}"; do
|
||||
[[ "${LINES[$i]}" == "$START_MARK" && $start_idx -eq -1 ]] && start_idx=$i
|
||||
[[ "${LINES[$i]}" == "$END_MARK" ]] && end_idx=$i
|
||||
if [[ "${LINES[$i]}" == "$START_MARK" ]]; then
|
||||
if (( start_count == 0 )); then start_idx=$i; fi
|
||||
start_count=$(( start_count + 1 ))
|
||||
fi
|
||||
if [[ "${LINES[$i]}" == "$END_MARK" ]]; then
|
||||
end_idx=$i
|
||||
end_count=$(( end_count + 1 ))
|
||||
fi
|
||||
done
|
||||
if (( start_count > 1 || end_count > 1 )); then
|
||||
echo "board-roll: LIVE is ${orig_size}B (>= cap ${CAP}B) but has ${start_count} START / ${end_count} END" >&2
|
||||
echo " markers — only a SINGLE '$START_MARK' … '$END_MARK' roll zone is supported." >&2
|
||||
echo " Multiple zones are refused (not guessed) so content between zones is never relocated." >&2
|
||||
echo " Consolidate the archival ticks into one zone, or trim manually." >&2
|
||||
exit 3
|
||||
fi
|
||||
if (( start_idx < 0 || end_idx < 0 || end_idx <= start_idx )); then
|
||||
echo "board-roll: LIVE is ${orig_size}B (>= cap ${CAP}B) but no usable roll zone" >&2
|
||||
echo " (need '$START_MARK' then '$END_MARK'). Add the markers around the" >&2
|
||||
|
||||
Reference in New Issue
Block a user