83 lines
2.6 KiB
Python
83 lines
2.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Independent verifier for untracked-classify-2026-10-31.json.
|
|
|
|
Re-derives the category of every frozen path from the rule (no lookup of the
|
|
artifact's own per-item rows), checks the artifact matches, every frozen path
|
|
still exists on disk, and aggregate counts are self-consistent.
|
|
|
|
Exit code IS the verdict: 0 = correct classification; nonzero = wrong.
|
|
"""
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
REPO = "/root/troupe-standup"
|
|
ART = os.path.join(REPO, "wizard-work", "sol", "untracked-classify-2026-10-31.json")
|
|
SNAPSHOT = os.path.join(REPO, "wizard-work", "sol", "untracked-classify-snapshot.txt")
|
|
|
|
|
|
def category(p):
|
|
base = os.path.basename(p)
|
|
if p.startswith("wizard-work/null/"):
|
|
return "probe"
|
|
if p.startswith("wizard-work/spark/"):
|
|
return "deliverable"
|
|
if p.startswith("wizard-work/sphinx/"):
|
|
return "report"
|
|
if p.startswith("wizard-work/ledger/"):
|
|
return "report"
|
|
if p.startswith("wizard-work/"):
|
|
return "other"
|
|
if base.endswith(".py"):
|
|
return "tool"
|
|
if base.endswith((".json", ".csv", ".tsv", ".db", ".yaml", ".yml",
|
|
".toml", ".lock", ".html", ".css")):
|
|
return "data"
|
|
if base.endswith((".md", ".txt")):
|
|
return "report"
|
|
return "other"
|
|
|
|
|
|
def main():
|
|
errors = []
|
|
with open(SNAPSHOT, encoding="utf-8") as f:
|
|
snapshot = [ln.strip() for ln in f if ln.strip()]
|
|
with open(ART, encoding="utf-8") as f:
|
|
art = json.load(f)
|
|
|
|
if art.get("count") != len(snapshot):
|
|
errors.append(f"count {art.get('count')} != snapshot {len(snapshot)}")
|
|
|
|
by_path = {f["path"]: f["category"] for f in art.get("files", [])}
|
|
if set(snapshot) != set(by_path):
|
|
errors.append("snapshot != artifact file paths")
|
|
|
|
for p in sorted(set(snapshot) | set(by_path)):
|
|
want = category(p)
|
|
got = by_path.get(p, "<missing>")
|
|
if got != want:
|
|
errors.append(f"category mismatch {p!r}: manifest={got} rule={want}")
|
|
|
|
counts = {}
|
|
for f in art.get("files", []):
|
|
counts[f["category"]] = counts.get(f["category"], 0) + 1
|
|
if counts != art.get("categories"):
|
|
errors.append(f"categories {counts} != manifest {art.get('categories')}")
|
|
|
|
for p in snapshot:
|
|
if not os.path.exists(os.path.join(REPO, p)):
|
|
errors.append(f"frozen path missing on disk: {p}")
|
|
|
|
if errors:
|
|
print(f"VERDICT: FAIL ({len(errors)})")
|
|
for e in errors:
|
|
print(" -", e)
|
|
sys.exit(1)
|
|
print(f"VERDICT: PASS ({len(snapshot)} untracked files classified; "
|
|
f"re-derived; all paths present)")
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|