105 lines
4.0 KiB
Python
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)
|