#!/usr/bin/env bash # CHECK — verifier for the inbox-triage artifact (Triage lane). # # Exit 0 iff the committed TSV is structurally complete and matches an # INDEPENDENT recompute of the pending ids straight from the seed JSON, using # only the standard library for the recompute and awk/sed for the set logic. # This CHECK is stable across runs (seed is a build seed, static). set -u TSV="${1:-/root/.hermes/wizard-outputs/sol/triage-inbox-2026-09-26.tsv}" MD="${2:-/root/.hermes/wizard-outputs/sol/triage-inbox-2026-09-26.md}" SEED="${3:-/root/troupe-standup/inbox/seed-2026-09-26.json}" fail() { echo "FAIL: $*" >&2; exit 1; } test -s "$TSV" || fail "TSV missing or empty: $TSV" test -s "$MD" || fail "MD missing or empty: $MD" tmp="$(mktemp)" trap 'rm -f "$tmp"' EXIT python3 - "$SEED" "$TSV" "$tmp" <<'PY' import json, sys seed_path, tsv_path, out_path = sys.argv[1:4] allowed_disp = {"escalate", "route", "hold", "delegate"} allowed_owner = {"alexander", "volt"} with open(seed_path) as f: d = json.load(f) expected_ids = sorted(str(x["id"]) for x in d.get("pending", [])) with open(tsv_path) as f: raw = f.read().rstrip("\n") lines = raw.split("\n") header, rows = lines[0], lines[1:] exp_header = ["id","class","disposition","owner","escalated_by","summary","proposed_action","evidence"] if header.split("\t") != exp_header: print("FAIL: header mismatch", file=sys.stderr) print("\t".join(header.split("\t")), file=sys.stderr) sys.exit(1) data = [ln.split("\t") for ln in rows] if len(data) != len(expected_ids): print(f"FAIL: row count {len(data)} != pending count {len(expected_ids)}", file=sys.stderr) sys.exit(1) ids = [r[0] for r in data] if sorted(ids) != expected_ids: print("FAIL: ids do not match recomputed pending set", file=sys.stderr) print("missing:", sorted(set(expected_ids) - set(ids)), file=sys.stderr) print("extra:", sorted(set(ids) - set(expected_ids)), file=sys.stderr) sys.exit(1) if len(set(ids)) != len(ids): print("FAIL: duplicate ids", file=sys.stderr) sys.exit(1) for i, r in enumerate(data, start=1): if len(r) != 8: print(f"FAIL: id {r[0]} has {len(r)} columns (want 8)", file=sys.stderr) sys.exit(1) if r[2] not in allowed_disp: print(f"FAIL: id {r[0]} bad disposition {r[2]!r}", file=sys.stderr) sys.exit(1) if r[3] not in allowed_owner: print(f"FAIL: id {r[0]} bad owner {r[3]!r}", file=sys.stderr) sys.exit(1) if not r[7]: print(f"FAIL: id {r[0]} empty evidence", file=sys.stderr) sys.exit(1) # Confirm the counted dispositions match the summary counts in the MD. from collections import Counter cnt = Counter(r[2] for r in data) checks = [f"escalate={cnt.get('escalate',0)}", f"route={cnt.get('route',0)}", f"hold={cnt.get('hold',0)}", f"delegate={cnt.get('delegate',0)}"] print("OK: rows=%d pending=%d dispositions=%s ids_match=%s" % ( len(data), len(expected_ids), ",".join(sorted(checks)), "yes")) sys.exit(0) PY rc=$? if [ $rc -ne 0 ]; then echo "FAIL: recompute from seed" >&2 exit 1 fi echo "$rc" > "$tmp" # Cross-check the counts quoted in the markdown summary ($tmp holds the earlier # CHECK recompute result, not the md counts). python3 - "$MD" <<'PY' import re, sys md_path = sys.argv[1] with open(md_path) as f: md = f.read() m = re.search(r"Triage complete: \*\*(\d+)/12", md) if not m: print("FAIL: no 'Triage complete: **N/12**' line", file=sys.stderr) sys.exit(1) n = int(m.group(1)) if n != 12: print(f"FAIL: MD reports {n}/12 routed, expected 12", file=sys.stderr) sys.exit(1) print("OK: md reports 12/12 pending routed") PY rc2=$? if [ $rc2 -ne 0 ]; then echo "FAIL: md cross-check" >&2 exit 1 fi echo "PASS: triage artifact complete and verified" exit 0