diff --git a/wizard-work/sol/CHECK-demands-class-triage-2026-10-31 b/wizard-work/sol/CHECK-demands-class-triage-2026-10-31 new file mode 100644 index 0000000..4f55850 --- /dev/null +++ b/wizard-work/sol/CHECK-demands-class-triage-2026-10-31 @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# CHECK -- sol: demands class triage 2026-10-31 (forge: verify lane). +# Exit 0 iff the committed Demands-ClassTriage-2026-10-31.json re-derives from +# the frozen demands.json evidence via the independent verifier above. +set -u + +cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1 +# The script sits in wizard-work/sol/, two levels below the repo root. +REPO_ROOT="$(cd ../.. && pwd)" +cd "$REPO_ROOT" || exit 1 + +exec python3 wizard-work/sol/verify_demands_class_triage_2026_10_31.py diff --git a/wizard-work/sol/Demands-ClassTriage-2026-10-31.json b/wizard-work/sol/Demands-ClassTriage-2026-10-31.json new file mode 100644 index 0000000..271cf26 --- /dev/null +++ b/wizard-work/sol/Demands-ClassTriage-2026-10-31.json @@ -0,0 +1,78 @@ +{ + "generated_utc": "2026-10-31T06:10:42Z", + "source": "/root/troupe-standup/wizard-work/sol/demands.json", + "seed_sha256": "209a13c57183217e23e36e9c958213848063e94961fe638a723efe9f0e1b5ff0", + "count": 8, + "categories": { + "ops": 4, + "admin": 2, + "archive": 1, + "support": 1 + }, + "rubric": { + "support": [ + "how does", + "how can", + "explain", + "what is" + ], + "archive": [ + "closed", + "turned off", + "went stale", + "removed", + "done" + ], + "admin": [ + "summarize", + "cost", + "choose", + "provider", + "quarter" + ], + "ops": [ + "migrate", + "fix", + "deploy", + "send", + "run", + "watch", + "approve", + "smoke" + ] + }, + "items": [ + { + "id": "D1", + "category": "ops" + }, + { + "id": "D2", + "category": "ops" + }, + { + "id": "D3", + "category": "admin" + }, + { + "id": "D4", + "category": "ops" + }, + { + "id": "D5", + "category": "archive" + }, + { + "id": "D6", + "category": "support" + }, + { + "id": "D7", + "category": "admin" + }, + { + "id": "D8", + "category": "ops" + } + ] +} \ No newline at end of file diff --git a/wizard-work/sol/verify_demands_class_triage_2026_10_31.py b/wizard-work/sol/verify_demands_class_triage_2026_10_31.py new file mode 100644 index 0000000..12f8808 --- /dev/null +++ b/wizard-work/sol/verify_demands_class_triage_2026_10_31.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +"""Independent verifier for wizard-work/sol/Demands-ClassTriage-2026-10-31.json. + +Exit 0 iff the committed artifact re-derives every category for the 8 demands +straight off the frozen seed evidence (demands.json), using an embedded +keyword rubric that is a deliberately different expression shape from the +builder. Also checks the self-derived counts, the seed sha256 and the count +field match the self-derived values. Exit code IS the verdict: 0 = correct, +nonzero = wrong. +""" + +import hashlib +import json +import sys +from pathlib import Path + +HERE = Path(__file__).resolve().parent +REPO = HERE.parent.parent +DEMANDS = REPO / "wizard-work" / "sol" / "demands.json" +ART = HERE / "Demands-ClassTriage-2026-10-31.json" + + +def re_derive(t: str) -> str: + """Independent re-derivation: a flat list of (category, keyword) pairs, + scanned in order. Deliberately a different expression shape than the + builder (which nests the same lists in a dict + if/elif chain).""" + low = (t or "").strip().lower() + for cat in ("support", "archive", "admin", "ops"): + for kw in RUBRIC[cat]: + if kw in low: + return cat + return "other" + + +RUBRIC = { + "support": ("how does", "how can", "explain", "what is"), + "archive": ("closed", "turned off", "went stale", "removed", "done"), + "admin": ("summarize", "cost", "choose", "provider", "quarter"), + "ops": ("migrate", "fix", "deploy", "send", "run", "watch", "approve", "smoke"), +} + + +def expected(seed) -> dict[str, str]: + out = {} + for it in seed.get("demands", []): + out[it["id"]] = re_derive(it.get("text", "")) + return out + + +def main() -> int: + problems = [] + seed = None + art = None + + if not DEMANDS.exists(): + problems.append("frozen demands seed missing: %s" % DEMANDS) + if not ART.exists(): + problems.append("artifact missing: %s" % ART) + + if not problems: + seed = json.loads(DEMANDS.read_text(encoding="utf-8")) + art = json.loads(ART.read_text(encoding="utf-8")) + + if len(seed.get("demands", [])) != 8: + problems.append("seed demands != 8") + + exp = expected(seed) + got = {i["id"]: i.get("category") for i in art.get("items", [])} + + if set(exp) != set(got): + problems.append("id sets differ: %s vs %s" + % (sorted(exp), sorted(got))) + + for iid, want in sorted(exp.items()): + if got.get(iid) != want: + problems.append( + "id %s: got %r, expected %r" % (iid, got.get(iid), want)) + + from collections import Counter + + exp_items = dict(exp) + exp_counts = dict(Counter(exp.values())) + if art.get("categories") != exp_counts: + problems.append( + "categories %r != re-derived counts %r" + % (art.get("categories"), exp_counts)) + + want_counts = {} + for v in exp.values(): + want_counts[v] = want_counts.get(v, 0) + 1 + if art.get("categories") != want_counts: + problems.append( + "category counts %r != %r" % (art.get("categories"), want_counts)) + + want_sha = hashlib.sha256(DEMANDS.read_bytes()).hexdigest() + if art.get("seed_sha256") != want_sha: + problems.append( + "seed_sha256 %r != %r" % (art.get("seed_sha256"), want_sha)) + + if art.get("count") != len(seed.get("demands", [])): + problems.append("count field %r != %r" + % (art.get("count"), len(seed.get("demands", [])))) + + if problems: + print("FAIL") + for p in problems: + print(" -", p) + return 1 + + print("PASS: Demands-ClassTriage-2026-10-31.json re-derives 8/8 demands " + "from evidence; categories=%s" % (art.get("categories"))) + return 0 + + +if __name__ == "__main__": + sys.exit(main())