96 lines
3.6 KiB
Bash
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
|