#!/usr/bin/env bash
# scripts/ledger-scan.sh -- machine-derived "what is still OPEN" view of the repo.
#
# The session ledger (docs/session-ledger.md) is a human narrative; it can drift.
# This script is the DRIFT CHECK: it greps the repo for the open-work markers that
# can be reliably derived -- PROPOSED/OPEN decisions, OPEN security-ledger rows,
# and the next-free D/DOCFIX/BUNDLEFIX numbers -- so the narrative ledger can be
# reconciled against ground truth at session start and before every handoff.
# Read-only; mutates nothing. Exit: 0 always (it reports; it does not judge).
#
# Reconcile rule: the narrative ledger must not claim CLOSED anything this scan
# shows OPEN, and must not omit a decision/number this scan surfaces.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO="${VR_REPO:-$(cd "$HERE/.." && pwd)}"
DD="$REPO/docs/design-decisions.md"
SL="$REPO/docs/security-ledger.md"
echo "=== ledger-scan: open-work ground truth (repo: $REPO) ==="
echo "-- PROPOSED / OPEN decisions --"
if [ -r "$DD" ]; then
# a decision header line whose status line (within the next few lines) is PROPOSED/OPEN.
# A decision is OPEN iff the LAST Status line in its block is PROPOSED/OPEN.
# (A later RESOLVED/CLOSED/SUPERSEDED amendment closes it -- so we track the last
# status per block, not the first; this is the D-063-amendment drift the first
# real run surfaced.)
OPEN_DEC="$(awk '
/^## D-[0-9]+:/ { if (hdr!="" && laststat ~ /PROPOSED|OPEN/) print " " hdr; sub(/^## /,"",$0); hdr=$0; laststat="" }
/\*\*Status:\*\*/ { laststat=$0 }
END { if (hdr!="" && laststat ~ /PROPOSED|OPEN/) print " " hdr }
' "$DD")"
if [ -n "$OPEN_DEC" ]; then printf '%s\n' "$OPEN_DEC"; else echo " (none)"; fi
else echo " (design-decisions.md not found)"; fi
echo "-- OPEN security-ledger rows --"
if [ -r "$SL" ]; then
grep -E '^\|[[:space:]]*SEC-[0-9]' "$SL" | awk -F'|' 'toupper($(NF-1)) ~ /OPEN|PENDING/ {gsub(/^ *| *$/,"",$2); gsub(/^ *| *$/,"",$7); print " " $2 " (" $7 ")"}' || echo " (none)"
[ -z "$(grep -E '^\|[[:space:]]*SEC-[0-9]' "$SL" | awk -F'|' 'toupper($(NF-1)) ~ /OPEN|PENDING/')" ] && echo " (none open)"
else echo " (security-ledger.md not found)"; fi
echo "-- next-free numbers (highest assigned + 1) --"
# Exclude "next-free" pointer lines from the count -- a "next-free D-072" pointer in
# prose must NOT inflate the highest-assigned (the first real run counted exactly that).
nextfree_mentions() { # <regex> <label> -- highest MENTION excluding next-free pointers
local pat="$1" label="$2" hi
hi="$(grep -rhE "$pat" "$REPO/docs" "$REPO/runbooks" 2>/dev/null \
| grep -viE 'next[- ]free' \
| grep -oE "$pat" | grep -oE '[0-9]+' | sort -n | tail -1)"
if [ -n "$hi" ]; then printf ' %s: highest=%s next-free=%03d\n' "$label" "$hi" "$((10#$hi + 1))"
else printf ' %s: none found\n' "$label"; fi
}
# D is AUTHORITATIVE from decision headers in design-decisions.md, not prose mentions.
if [ -r "$DD" ]; then
DHI="$(grep -oE '^## D-[0-9]+' "$DD" | grep -oE '[0-9]+' | sort -n | tail -1)"
if [ -n "$DHI" ]; then printf ' D: highest-header=%s next-free=%03d\n' "$DHI" "$((10#$DHI + 1))"; else echo " D: none"; fi
else echo " D: design-decisions.md not found"; fi
nextfree_mentions 'DOCFIX-0[0-9]{2}' "DOCFIX"
nextfree_mentions 'BUNDLEFIX-0[0-9]{2}' "BUNDLEFIX"
echo "-- reconcile against docs/session-ledger.md --"
if [ -r "$REPO/docs/session-ledger.md" ]; then
echo " ledger present; compare the sections above against its narrative"
else
echo " NO docs/session-ledger.md -- create/update it (this session's continuity record)"
fi
exit 0