#!/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())
