Files

96 lines
3.6 KiB
Bash

#!/usr/bin/env bash
# CHECK-troupe-tree-census
#
# Independently re-derives the top-level directory census of the troupe repo for a
# single pinned revision and compares it byte-for-byte against the committed
# artifact. Exit code is the verdict: 0 = correct, nonzero = wrong.
#
# The pinned revision is read from troupe-tree-census-2026-11-02.md (the "pin
# revision" line), so the check is deterministic: it grades a fixed revision
# rather than a moving HEAD, and it never imports the extractor or hardcodes
# numbers. Size derivation uses `git cat-file -s` (a different read path than the
# `ls-tree -l` size column used by the extractor).
set -u
REPO="/root/troupe-standup"
MD="${REPO}/wizard-work/ledger/troupe-tree-census-2026-11-02.md"
ARTIFACT="${REPO}/wizard-work/ledger/troupe-tree-census-2026-11-02.csv"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
# --- 1. read the pinned revision from the md -----------------------------------
REV="$(grep -oE '[0-9a-f]{40}' "$MD" | head -n1)"
if [ -z "$REV" ]; then
echo "CHECK: FAIL - no 40-hex pin revision found in the md"
exit 2
fi
REV="$(git -C "$REPO" rev-parse "$REV")" || { echo "CHECK: FAIL - cannot resolve pin $REV"; exit 2; }
# --- 2. independent re-derivation of the census for the pin --------------------
: > "${WORK}/recomputed.csv"
python3 - "$WORK/recomputed.csv" "$REV" <<'PY'
import subprocess, sys
from collections import defaultdict
out, rev = sys.argv[1], sys.argv[2]
counts = defaultdict(int)
sizes = defaultdict(int)
ls = subprocess.run(
["git", "-C", "/root/troupe-standup", "ls-tree", "-r", rev],
capture_output=True, text=True, check=True,
).stdout
for line in ls.splitlines():
if not line.strip():
continue
head, sep, path = line.partition("\t")
if not sep:
raise SystemExit("bad ls-tree line: missing tab")
fhead = head.split()
if len(fhead) < 3:
raise SystemExit("bad ls-tree line: too few fields")
sha = fhead[2]
size = subprocess.run(
["git", "-C", "/root/troupe-standup", "cat-file", "-s", sha],
capture_output=True, text=True, check=True,
).stdout.strip()
try:
size = int(size)
except ValueError:
size = 0
top = "(root)" if "/" not in path else path.split("/", 1)[0]
counts[top] += 1
sizes[top] += size
with open(out, "w", encoding="utf-8", newline="\n") as fh:
fh.write("top_level,files,sized_bytes\n")
for top in sorted(counts):
fh.write(f"{top},{counts[top]},{sizes[top]}\n")
PY
# --- 3. byte-for-byte compare with the committed artifact -----------------------
if ! diff -u "${WORK}/recomputed.csv" "${ARTIFACT}" > "${WORK}/diff.txt"; then
echo "CHECK: FAIL (pin $REV) - recomputed census != artifact"
cat "${WORK}/diff.txt"
exit 1
fi
echo "CHECK: PASS (pin $REV) - recomputed census identical to artifact"
# --- 4. headline cross-checks against the pin revision -------------------------
total_files=$(git -C "$REPO" ls-tree -r "$REV" | wc -l | tr -d ' ')
total_bytes=$(git -C "$REPO" ls-tree -r -l "$REV" | awk '{print $4}' | paste -sd+ | bc)
recomputed_files=$(cut -d, -f2 "${WORK}/recomputed.csv" | tail -n +2 | paste -sd+ | bc)
recomputed_bytes=$(cut -d, -f3 "${WORK}/recomputed.csv" | tail -n +2 | paste -sd+ | bc)
if [ "$total_files" -ne "$recomputed_files" ]; then
echo "CHECK: FAIL (pin $REV) - file count mismatch: git=$total_files recomputed=$recomputed_files"
exit 1
fi
if [ "$total_bytes" -ne "$recomputed_bytes" ]; then
echo "CHECK: FAIL (pin $REV) - byte total mismatch: git=$total_bytes recomputed=$recomputed_bytes"
exit 1
fi
echo "CHECK: headline ok ($recomputed_files files, $recomputed_bytes bytes)"
exit 0