131 lines
4.8 KiB
Python
Executable File
131 lines
4.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""CHECK-2026-09-28: the verdict for the Triage-2026-09-28 classification.
|
|
|
|
Exit 0 iff the committed manifest (Triage-2026-09-28-classify.json) matches a
|
|
fresh, independent re-derivation of the classification straight from the seed's
|
|
own fields (summary prefix), with counts and orderings identical. A tautology
|
|
or a mismatch both exit nonzero.
|
|
|
|
The subject is resolved from this file's own location -- the tree the CHECK
|
|
ships in -- never from a pinned absolute path, so a clone whose own manifest is
|
|
doctored is graded as doctored. Q106 measured the old form, which pinned the
|
|
host worktree's absolute path, printing VERDICT TRUE rc 0 over a poisoned clone.
|
|
|
|
python3 wizard-work/sol/CHECK-2026-09-28 # grade this tree
|
|
python3 wizard-work/sol/CHECK-2026-09-28 --root DIR # grade another tree
|
|
|
|
Exit codes follow the repo's tool contract: 0 verdict TRUE, 1 verdict FALSE,
|
|
2 could not grade (usage error, or an input the tree does not carry).
|
|
"""
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
HERE = os.path.dirname(os.path.realpath(__file__))
|
|
DEFAULT_ROOT = os.path.dirname(os.path.dirname(HERE))
|
|
|
|
# Independent re-implementation of the classifier (deliberately separate
|
|
# expression shape from the builder that produced the manifest).
|
|
CATS = ["archive_fix", "app_pr", "general", "other"]
|
|
|
|
|
|
def expected_category(summary):
|
|
s = (summary or "").strip()
|
|
if s.startswith("Archive fix:"):
|
|
return "archive_fix"
|
|
if s.startswith("Turd app:"):
|
|
return "app_pr"
|
|
if s.startswith("Trial by Timmy:") or s.startswith("Q9:"):
|
|
return "general"
|
|
return "other"
|
|
|
|
|
|
def read_json(path):
|
|
with open(path, encoding="utf-8") as f:
|
|
return json.load(f)
|
|
|
|
|
|
def main(argv=None):
|
|
ap = argparse.ArgumentParser(
|
|
prog="CHECK-2026-09-28",
|
|
description="Grade Triage-2026-09-28-classify.json against the seed it "
|
|
"claims to derive from.",
|
|
)
|
|
ap.add_argument("--root", default=DEFAULT_ROOT,
|
|
help="tree to grade (default: the tree this CHECK lives in)")
|
|
args = ap.parse_args(argv)
|
|
|
|
root = os.path.abspath(args.root)
|
|
seed_path = os.path.join(root, "inbox", "seed-2026-09-26.json")
|
|
manifest_path = os.path.join(root, "wizard-work", "sol",
|
|
"Triage-2026-09-28-classify.json")
|
|
|
|
print(f"CHECK-2026-09-28: subject root={root}")
|
|
|
|
for label, path in (("seed", seed_path), ("manifest", manifest_path)):
|
|
if not os.path.isfile(path):
|
|
print(f"CHECK-2026-09-28: CANNOT GRADE -- {label} not readable at {path}")
|
|
return 2
|
|
|
|
try:
|
|
seed = read_json(seed_path)
|
|
manifest = read_json(manifest_path)
|
|
except (OSError, ValueError) as exc:
|
|
print(f"CHECK-2026-09-28: CANNOT GRADE -- {type(exc).__name__}: {exc}")
|
|
return 2
|
|
|
|
if set(seed.keys()) != {"cutover", "decided", "exported_at", "pending", "source"}:
|
|
print("CHECK-2026-09-28: CANNOT GRADE -- seed keys are not the 2026-09-26 "
|
|
f"shape: {sorted(seed.keys())}")
|
|
return 2
|
|
|
|
expected_items = []
|
|
for it in sorted(seed["pending"], key=lambda x: int(x["id"])):
|
|
expected_items.append({
|
|
"id": int(it["id"]),
|
|
"category": expected_category(it["summary"]),
|
|
"status": "table",
|
|
})
|
|
|
|
expected_counts = {"archive_fix": 0, "app_pr": 0, "general": 0, "other": 0}
|
|
for it in expected_items:
|
|
expected_counts[it["category"]] += 1
|
|
|
|
errors = []
|
|
|
|
# Structural keys that any genuine manifest carries (not a flake).
|
|
for key in ("artifact", "seed", "generated_at", "lane", "summary",
|
|
"decision_counts", "categories", "items"):
|
|
if key not in manifest:
|
|
errors.append(f"manifest missing key {key!r}")
|
|
|
|
if [i["id"] for i in manifest.get("items", [])] != [i["id"] for i in expected_items]:
|
|
errors.append("item id order differs from seed")
|
|
|
|
if manifest.get("decision_counts") != {"approve": 0, "reject": 0, "table": 12}:
|
|
errors.append(f"decision_counts {manifest.get('decision_counts')!r} != expected")
|
|
|
|
if manifest.get("categories") != expected_counts:
|
|
errors.append(f"categories {manifest.get('categories')!r} != expected {expected_counts}")
|
|
|
|
got = [(i["id"], i.get("category")) for i in manifest.get("items", [])]
|
|
exp = [(i["id"], i.get("category")) for i in expected_items]
|
|
if got != exp:
|
|
errors.append(f"per-item categories differ: {got} != {exp}")
|
|
|
|
if errors:
|
|
print("CHECK-2026-09-28: VERDICT FALSE")
|
|
for e in errors:
|
|
print(" -", e)
|
|
return 1
|
|
|
|
print("CHECK-2026-09-28: VERDICT TRUE "
|
|
f"(categories={manifest['categories']} "
|
|
f"table={manifest['decision_counts']['table']})")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|