Files
troupe-standup/wizard-work/sol/verify_triage_2026_11_01.py
T

105 lines
4.0 KiB
Python

#!/usr/bin/env python3
"""Independent verifier for Triage-2026-11-01.json (sol lane, Fresh).
Exit 0 iff the committed manifest re-derives every item from the REAL frozen
seed (seed-2026-10-29.json) plus its own rule re-derivation; nonzero otherwise.
The verifier enforces three independent checks:
1. every artifact item's evidence AND summary are copied VERBATIM from the
seed (no fabrication of the item text),
2. category and decision re-derive from the seed fields using the same
keyword rules encoded in classify_2026_11_01.py (the verifier re-implements
the rule, it does not import the classifier),
3. the aggregate decision_counts and categories reconcile.
Run: python3 verify_triage_2026_11_01.py
"""
import json
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent.parent
SEED = REPO / "wizard-work" / "sol" / "seed-2026-10-29.json"
ART = REPO / "wizard-work" / "sol" / "Triage-2026-11-01.json"
# Independent re-implementation of the category rule (no import of classifier).
def re_category(evidence: str) -> str:
low = evidence.lower()
if any(k in low for k in ["hero", "funding table", "restats", "export drops",
"readme omits", "readme", "funding table rows lack"]):
return "archive_fix"
if any(k in low for k in ["publish", "submit", "release-notes"]):
return "app_pr"
return "general"
# Independent re-implementation of the decision rule.
def re_decision(action: str) -> str:
low = action.lower()
if any(k in low for k in ["batch", "hand", "record", "send"]):
return "table"
return "approve"
problems = []
if not SEED.exists():
problems.append(f"seed missing: {SEED}")
if not ART.exists():
problems.append(f"artifact missing: {ART}")
if not problems:
seed = json.loads(SEED.read_text(encoding="utf-8"))
art = json.loads(ART.read_text(encoding="utf-8"))
pending = seed.get("pending", [])
if len(pending) != 8:
problems.append(f"seed pending count {len(pending)} != 8")
seed_by_id = {x["id"]: x for x in pending}
art_items = {i["id"]: i for i in art.get("items", [])}
if sorted(art_items) != [1, 2, 3, 4, 5, 6, 7, 8]:
problems.append(f"artifact ids {sorted(art_items)} != 1..8")
# 1) evidence + summary must be verbatim copies of the seed (no fabrication).
for iid, s in seed_by_id.items():
a = art_items.get(iid)
if a is None:
continue
if a.get("evidence") != s.get("evidence"):
problems.append(f"id {iid}: evidence not verbatim from seed")
if a.get("summary") != s.get("summary"):
problems.append(f"id {iid}: summary not verbatim from seed")
# 2) category + decision must re-derive under the independent rule.
for iid, s in seed_by_id.items():
a = art_items.get(iid)
if a is None:
continue
if a.get("category") != re_category(s["evidence"]):
problems.append(f"id {iid}: category {a.get('category')!r} != rule {re_category(s['evidence'])!r}")
if a.get("decision") != re_decision(s["proposed_action"]):
problems.append(f"id {iid}: decision {a.get('decision')!r} != rule {re_decision(s['proposed_action'])!r}")
# 3) aggregates reconcile.
dc = art.get("decision_counts", {})
cats = art.get("categories", {})
exp_dc = {}
exp_cats = {}
for iid, s in seed_by_id.items():
exp_dc[re_decision(s["proposed_action"])] = exp_dc.get(re_decision(s["proposed_action"]), 0) + 1
exp_cats[re_category(s["evidence"])] = exp_cats.get(re_category(s["evidence"]), 0) + 1
if dc != exp_dc:
problems.append(f"decision_counts {dc} != re-derived {exp_dc}")
if cats != exp_cats:
problems.append(f"categories {cats} != re-derived {exp_cats}")
if problems:
print("FAIL")
for p in problems:
print(" -", p)
sys.exit(1)
print(f"PASS: Triage-2026-11-01.json re-derives 8/8 items from seed evidence; "
f"decision_counts={dc}, categories={cats}")
sys.exit(0)