113 lines
4.8 KiB
Bash
113 lines
4.8 KiB
Bash
#!/usr/bin/env bash
|
|
# CHECK — verifier for the 2026-10-29 work-queue census (Extract lane: data extraction).
|
|
#
|
|
# Exits 0 iff the committed census is a faithful replay of the source
|
|
# WORK-QUEUE.md. Steps: (1) count source Q sections, (2) count TSV data rows,
|
|
# (3) re-parse the source with an independent inline parser and diff the TSV,
|
|
# (4) confirm MD renders match the TSV, (5) reconcile JSON tallies vs TSV.
|
|
# No `|| true` / `; exit 0` fallbacks; the last command's exit code is the verdict.
|
|
|
|
set -u
|
|
|
|
cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../../.." || exit 1
|
|
SRC="$PWD/WORK-QUEUE.md"
|
|
CENSUS="$PWD/wizard-work/ledger/2026-10-29-workqueue-census"
|
|
|
|
# --- Step 1: independent count of source Q sections -------------------------
|
|
src_heads=$(grep -Ec '^## Q[0-9]+ — ' "$SRC") || exit 1
|
|
|
|
# --- Step 2: independent count of committed TSV data rows -------------------
|
|
tsv_rows=$(tail -n +2 "$CENSUS/workqueue-census.tsv" | grep -c .) || exit 1
|
|
|
|
test "$src_heads" -eq "$tsv_rows" \
|
|
|| { echo "FAIL: source Q sections=$src_heads, TSV data rows=$tsv_rows"; exit 1; }
|
|
|
|
# --- Step 3+5: full replay from source + tally reconciliation ----------------
|
|
python3 - "$SRC" "$CENSUS" <<'PY'
|
|
import json, sys
|
|
|
|
src, census = sys.argv[1], sys.argv[2]
|
|
|
|
# Independent inline parser (duplicated so the check is not circular).
|
|
import re
|
|
HEAD_RE = re.compile(r"^## Q([0-9]+) — (.*)$", re.MULTILINE)
|
|
NOTE_RE = re.compile(r" \*\(.*\)\s*$")
|
|
SEP = re.compile(r"^## ")
|
|
status_pat = re.compile(r"^\*\*Status:\*\*(.*)$")
|
|
done_pat = re.compile(r"^\*\*Done means:\*\*(.*)$")
|
|
|
|
lines = open(src, encoding="utf-8").read().splitlines()
|
|
heads = []
|
|
for i, line in enumerate(lines):
|
|
m = HEAD_RE.match(line)
|
|
if m:
|
|
title = NOTE_RE.sub("", m.group(2)).strip()
|
|
j = i + 1
|
|
while j < len(lines) and not SEP.match(lines[j]):
|
|
j += 1
|
|
block = lines[i + 1:j]
|
|
status = done = ""
|
|
for ln in block:
|
|
if ln.startswith("**Status:"):
|
|
status = ln.split("**Status:", 1)[1].strip()
|
|
if ln.startswith("**Done means:"):
|
|
done = ln.split("**Done means:", 1)[1].strip()
|
|
heads.append({"q": m.group(1), "title": title, "status": status,
|
|
"done_implies": done})
|
|
|
|
# --- 3) TSV replay ---
|
|
tsv = open(f"{census}/workqueue-census.tsv", encoding="utf-8").read().splitlines()
|
|
if tsv[0] != "Q\ttitle\tstatus\tdone_implies":
|
|
print("FAIL: TSV header wrong"); sys.exit(1)
|
|
if len(tsv) - 1 != len(heads):
|
|
print(f"FAIL: TSV rows {len(tsv)-1} != source {len(heads)}"); sys.exit(1)
|
|
# (compare line by line, tolerant of tab-split)
|
|
for k, (h, gl) in enumerate(zip(heads, tsv[1:])):
|
|
gl = gl.split("\t")
|
|
if len(gl) != 4:
|
|
print(f"FAIL: TSV row {k+1} not 4 cols"); sys.exit(1)
|
|
exp_key = [h["q"], h["title"], h["status"], h["done_implies"]]
|
|
for a, b in zip(exp_key, gl):
|
|
if a != b:
|
|
print(f"FAIL: TSV row {k+1} q={a!r} != {b!r}"); sys.exit(1)
|
|
|
|
# --- 4) MD replay: first data row must equal first TSV data row -------------
|
|
md = open(f"{census}/workqueue-census.md", encoding="utf-8").read().splitlines()
|
|
data_rows = [l for l in md if l.startswith("| ") and not l.startswith("| q |")]
|
|
if len(data_rows) != len(heads):
|
|
print(f"FAIL: MD data rows {len(data_rows)} != {len(heads)}"); sys.exit(1)
|
|
for k, h in enumerate(heads):
|
|
want = f"| {h['q']} | {h['title'].replace('|','\\|')} | {h['status'].replace('|','\\|')} | {h['done_implies'].replace('|','\\|')} |"
|
|
if k >= len(data_rows) or data_rows[k] != want:
|
|
print(f"FAIL: MD row {k+1} mismatch"); sys.exit(1)
|
|
|
|
# --- 5) JSON tallies vs TSV -------------------------------------------------
|
|
j = json.load(open(f"{census}/workqueue-census.json", encoding="utf-8"))
|
|
if j.get("row_count") != len(heads):
|
|
print(f"FAIL: JSON row_count {j.get('row_count')} != {len(heads)}"); sys.exit(1)
|
|
if j.get("source") != src:
|
|
print("FAIL: JSON source mismatch"); sys.exit(1)
|
|
|
|
counts = {}
|
|
for h in heads:
|
|
s = (h["status"] or "").strip().lower()
|
|
if not s:
|
|
counts["UNCLAIMED"] = counts.get("UNCLAIMED", 0) + 1
|
|
elif "closed" in s or "satisf" in s:
|
|
counts["CLOSED"] = counts.get("CLOSED", 0) + 1
|
|
elif "done" in s:
|
|
counts["DONE"] = counts.get("DONE", 0) + 1
|
|
elif "open" in s:
|
|
counts["OPEN"] = counts.get("OPEN", 0) + 1
|
|
elif "unclaimed" in s or "unowned" in s:
|
|
counts["UNCLAIMED"] = counts.get("UNCLAIMED", 0) + 1
|
|
else:
|
|
counts["OTHER"] = counts.get("OTHER", 0) + 1
|
|
if j.get("summary") != counts:
|
|
print(f"FAIL: JSON summary {j.get('summary')} != computed {counts}"); sys.exit(1)
|
|
|
|
print(f"PASS: {len(heads)} work-queue sections reconciled to census "
|
|
f"(CLOSED {counts['CLOSED']}, DONE {counts['DONE']}, OPEN {counts['OPEN']}, "
|
|
f"UNCLAIMED {counts['UNCLAIMED']}, OTHER {counts['OTHER']})")
|
|
PY
|
|
exit $? |