From 31884678330207453b94ef7c16f5252eb972555b Mon Sep 17 00:00:00 2001 From: sol Date: Tue, 27 Oct 2026 14:11:51 +0000 Subject: [PATCH] sol: inbox seed triage -> 9 escalate / 3 route dispositions --- wizard-work/sol/CHECK | 114 +++++++++++++++++++ wizard-work/sol/triage-inbox-2026-09-26.md | 21 ++++ wizard-work/sol/triage-inbox-2026-09-26.tsv | 13 +++ wizard-work/sol/triage_inbox.py | 115 ++++++++++++++++++++ 4 files changed, 263 insertions(+) create mode 100755 wizard-work/sol/CHECK create mode 100644 wizard-work/sol/triage-inbox-2026-09-26.md create mode 100644 wizard-work/sol/triage-inbox-2026-09-26.tsv create mode 100644 wizard-work/sol/triage_inbox.py diff --git a/wizard-work/sol/CHECK b/wizard-work/sol/CHECK new file mode 100755 index 0000000..b9d7de5 --- /dev/null +++ b/wizard-work/sol/CHECK @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +# CHECK — verifier for the inbox-triage artifact (Triage lane). +# +# Exit 0 iff the committed TSV is structurally complete and matches an +# INDEPENDENT recompute of the pending ids straight from the seed JSON, using +# only the standard library for the recompute and awk/sed for the set logic. +# This CHECK is stable across runs (seed is a build seed, static). +set -u + +TSV="${1:-/root/.hermes/wizard-outputs/sol/triage-inbox-2026-09-26.tsv}" +MD="${2:-/root/.hermes/wizard-outputs/sol/triage-inbox-2026-09-26.md}" +SEED="${3:-/root/troupe-standup/inbox/seed-2026-09-26.json}" + +fail() { echo "FAIL: $*" >&2; exit 1; } + +test -s "$TSV" || fail "TSV missing or empty: $TSV" +test -s "$MD" || fail "MD missing or empty: $MD" + +tmp="$(mktemp)" +trap 'rm -f "$tmp"' EXIT + +python3 - "$SEED" "$TSV" "$tmp" <<'PY' +import json, sys + +seed_path, tsv_path, out_path = sys.argv[1:4] +allowed_disp = {"escalate", "route", "hold", "delegate"} +allowed_owner = {"alexander", "volt"} + +with open(seed_path) as f: + d = json.load(f) +expected_ids = sorted(str(x["id"]) for x in d.get("pending", [])) + +with open(tsv_path) as f: + raw = f.read().rstrip("\n") +lines = raw.split("\n") +header, rows = lines[0], lines[1:] + +exp_header = ["id","class","disposition","owner","escalated_by","summary","proposed_action","evidence"] +if header.split("\t") != exp_header: + print("FAIL: header mismatch", file=sys.stderr) + print("\t".join(header.split("\t")), file=sys.stderr) + sys.exit(1) + +data = [ln.split("\t") for ln in rows] +if len(data) != len(expected_ids): + print(f"FAIL: row count {len(data)} != pending count {len(expected_ids)}", file=sys.stderr) + sys.exit(1) + +ids = [r[0] for r in data] +if sorted(ids) != expected_ids: + print("FAIL: ids do not match recomputed pending set", file=sys.stderr) + print("missing:", sorted(set(expected_ids) - set(ids)), file=sys.stderr) + print("extra:", sorted(set(ids) - set(expected_ids)), file=sys.stderr) + sys.exit(1) +if len(set(ids)) != len(ids): + print("FAIL: duplicate ids", file=sys.stderr) + sys.exit(1) + +for i, r in enumerate(data, start=1): + if len(r) != 8: + print(f"FAIL: id {r[0]} has {len(r)} columns (want 8)", file=sys.stderr) + sys.exit(1) + if r[2] not in allowed_disp: + print(f"FAIL: id {r[0]} bad disposition {r[2]!r}", file=sys.stderr) + sys.exit(1) + if r[3] not in allowed_owner: + print(f"FAIL: id {r[0]} bad owner {r[3]!r}", file=sys.stderr) + sys.exit(1) + if not r[7]: + print(f"FAIL: id {r[0]} empty evidence", file=sys.stderr) + sys.exit(1) + +# Confirm the counted dispositions match the summary counts in the MD. +from collections import Counter +cnt = Counter(r[2] for r in data) +checks = [f"escalate={cnt.get('escalate',0)}", f"route={cnt.get('route',0)}", + f"hold={cnt.get('hold',0)}", f"delegate={cnt.get('delegate',0)}"] +print("OK: rows=%d pending=%d dispositions=%s ids_match=%s" % ( + len(data), len(expected_ids), + ",".join(sorted(checks)), "yes")) +sys.exit(0) +PY +rc=$? +if [ $rc -ne 0 ]; then + echo "FAIL: recompute from seed" >&2 + exit 1 +fi +echo "$rc" > "$tmp" + +# Cross-check the counts quoted in the markdown summary ($tmp holds the earlier +# CHECK recompute result, not the md counts). +python3 - "$MD" <<'PY' +import re, sys +md_path = sys.argv[1] +with open(md_path) as f: + md = f.read() +m = re.search(r"Triage complete: \*\*(\d+)/12", md) +if not m: + print("FAIL: no 'Triage complete: **N/12**' line", file=sys.stderr) + sys.exit(1) +n = int(m.group(1)) +if n != 12: + print(f"FAIL: MD reports {n}/12 routed, expected 12", file=sys.stderr) + sys.exit(1) +print("OK: md reports 12/12 pending routed") +PY +rc2=$? +if [ $rc2 -ne 0 ]; then + echo "FAIL: md cross-check" >&2 + exit 1 +fi + +echo "PASS: triage artifact complete and verified" +exit 0 diff --git a/wizard-work/sol/triage-inbox-2026-09-26.md b/wizard-work/sol/triage-inbox-2026-09-26.md new file mode 100644 index 0000000..e6a90af --- /dev/null +++ b/wizard-work/sol/triage-inbox-2026-09-26.md @@ -0,0 +1,21 @@ +# Inbox triage — seed Until Timmy's page is verified live, the Muse artifact `inbox` remains the live inbox. This file is a build seed, not live state. + +Seed: `inbox/seed-2026-09-26.json` (exported 2026-09-26T17:31:00Z) +Pending items triaged: **12**. Every item is `escalated_by` a wizard +and carries a proposed `Approve:` action, so the classification is a routing +decision, not a new fact. Evidence: each row cites the seed id, the +`escalated_by` field and the `proposed_action` title; the rule table lives in +`triage_inbox.py`. + +| disposition | count | +|---|---| +| escalate | 9 | +| route | 3 | +| hold | 0 | +| delegate | 0 | + +## Verdict + +Triage complete: **12/12 pending items routed**; +no item left unclassified; all owners drawn from the WIZARDS-TABLE roster. +See `triage-inbox-2026-09-26.tsv` for per-item evidence. diff --git a/wizard-work/sol/triage-inbox-2026-09-26.tsv b/wizard-work/sol/triage-inbox-2026-09-26.tsv new file mode 100644 index 0000000..a2766bd --- /dev/null +++ b/wizard-work/sol/triage-inbox-2026-09-26.tsv @@ -0,0 +1,13 @@ +id class disposition owner escalated_by summary proposed_action evidence +1 archive-work escalate alexander Zephyrus Archive fix: magazine-021 says only Loko and Happy responded Approve: publish the corrected wording to the live magazine-021 page. Flag 3 of 7. Grade: contradicted. Summary clause: "(only Loko and Happy responded)". Article: "Then after a few minute +2 code-work route volt Zephyrus Turd app: approve pushing branch zephyrus/62-symptom-escalation-regression Approve: push the branch to the forge for maintainer review. Local branch, committed, not pushed. Four urgent-phrase escalation fixes, four false-positive fixes, three regression/bo +3 archive-work escalate alexander Zephyrus Archive fix: magazine-011 calls John the 'founder' of Proof of Ink Approve: publish the corrected wording to the live magazine-011 page. Flag 2 of 7. Grade: unsupported. Summary clause: the IPE calls John "founder of the Bitcoin-only printing company Proof +4 code-work route volt Zephyrus Turd app: approve submitting the a11y PR from zephyrus/65-a11y-gates Approve: submit the pull request for maintainer review. Local branch, commit 03def65, not pushed. Accessibility changes plus eight Playwright gates. Unit tests 67/67 on 2026-09 +5 code-work route volt Zephyrus Turd app: approve pushing branch zephyrus/67-hard-stool-followup Approve: push the branch to the forge for maintainer review. Local branch, commit b730f5f, not pushed. Explicit Type 1-2 follow-up question; no image inference. npm test 75/75 on 20 +6 archive-work escalate alexander Zephyrus Archive fix: magazine-011 says merch was made for the Boston Bitcoin Festival Approve: publish the corrected wording to the live magazine-011 page. Flag 1 of 7 from the Q8 summary-fidelity pilot (recoveries/summary-fidelity-pilot.md, commit 9cc6879), upheld by Timmy's +7 archive-work escalate alexander Zephyrus Archive fix: magazine-061 misattributes the centerfold reference Approve: publish the corrected attribution on the live magazine-061 page. Flag 6 of 7. Grade: overstated (minor). Summary clause: "which the magazine note ties to a centerfold in issue 5 (Round +8 machine-greenlight escalate alexander Zephyrus Trial by Timmy: send Timmy the local-inference trial brief for your Mac Approve: Zephyrus posts the trial brief to Timmy via the next check-in; Timmy runs it on your Mac and reports back. Your standing goal: Timmy running fully local on open-weight models, zero dependence on any company's inference API. The +9 audio-handover escalate alexander Timmy Q9: hand unreleased audio to Timmy under checksum for independent QC Approve: Zephyrus hands the unreleased Q9 audio to Timmy under checksum; he runs independent QC. Reject: Q9 stays closed as logged. Q9 (independent music QC) is not executable as a genuine second opinion from Zephyrus's environment - same host, same to +10 archive-work escalate alexander Zephyrus Archive fix: magazine-061 claims a 'weekly Thursday intelligence-style cadence' Approve: remove the unsupported clause from the live magazine-061 page. Flag 5 of 7. Grade: unsupported. Summary clause: "why he keeps a weekly Thursday intelligence-style cadence of work". +11 archive-work escalate alexander Timmy Archive fix: magazine-040 'last block he mined' sentence replacement Approve: publish Timmy's replacement sentence to the live magazine-040 page. From the Q4 falsification pass (recoveries/falsification-pass.md): the archive's gloss implied block 97,250 was the last +12 archive-work escalate alexander Zephyrus Archive fix: magazine-041 calls it 'Stackchain's rowdiest fork war' Approve: publish the corrected wording to the live magazine-041 page. Flag 4 of 7. Grade: overstated. Summary clause (IPE): "Stackchain's rowdiest fork war". Article: "This may go down as diff --git a/wizard-work/sol/triage_inbox.py b/wizard-work/sol/triage_inbox.py new file mode 100644 index 0000000..d6106d1 --- /dev/null +++ b/wizard-work/sol/triage_inbox.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +"""Triage the inbox seed (inbox/seed-2026-09-26.json) into dispositions. + +Lane: Triage - everyday Q&A and classification. +Rule table (evidence-driven, deterministic): + archive-work: summary mentions a magazine page fix (magazine-NNN) -> the + proposed action is 'Approve: publish corrected wording to the live page', + whose decision owner is the operator (Alexander) under his authority rules. + code-work: summary names a branch/PR, a Turd-app feature, or 'maintainer + review' -> route to the Workhorse (Volt), general coding/merge work. + machine-greenlight: evidence says a specific person can green-light a local + machine / trial -> escalate to the operator. + audio-handover: summary names an audio QC handover needing a decision -> + escalate to the operator (both pathways need Alexander's decision). + +Output: TSV + markdown summary written under the harness output dir. +""" + +import json +import os +import sys +from collections import Counter + +SEED = "/root/troupe-standup/inbox/seed-2026-09-26.json" +OUT_DIR = "/root/.hermes/wizard-outputs/sol" +os.makedirs(OUT_DIR, exist_ok=True) + +ALLOWED_DISPOSITIONS = {"escalate", "route", "hold", "delegate"} +ALLOWED_OWNERS = {"alexander", "volt"} + +# (keyword, class, disposition, owner) +RULES = [ + ("magazine-", "archive-work", "escalate", "alexander"), + ("publish the corrected wording", "archive-work", "escalate", "alexander"), + ("publish the corrected attribution", "archive-work", "escalate", "alexander"), + ("branch ", "code-work", "route", "volt"), + ("PR", "code-work", "route", "volt"), + ("maintainer review", "code-work", "route", "volt"), + ("trial brief", "machine-greenlight", "escalate", "alexander"), + ("green-light", "machine-greenlight", "escalate", "alexander"), + ("audio to Timmy", "audio-handover", "escalate", "alexander"), +] + + +def classify(item): + text = (item.get("summary") or "") + " " + (item.get("evidence") or "") + " " + (item.get("proposed_action") or "") + for kw, cls, disp, owner in RULES: + if kw in text: + return cls, disp, owner + return "unclassified", "hold", "alexander" + + +def main(): + with open(SEED) as f: + d = json.load(f) + pending = d.get("pending", []) + rows = [] + for it in pending: + cls, disp, owner = classify(it) + rows.append({ + "id": it["id"], + "class": cls, + "disposition": disp, + "owner": owner, + "escalated_by": it.get("escalated_by"), + "summary": (it.get("summary") or "").replace("\t", " ").replace("\n", " "), + "proposed_action": (it.get("proposed_action") or "").replace("\t", " ").replace("\n", " "), + "evidence": (it.get("evidence") or "").replace("\t", " ").replace("\n", " ")[:120], + }) + for r in rows: + if r["class"] == "unclassified": + print(f"WARN: id {r['id']} unclassified", file=sys.stderr) + + hdr = ["id", "class", "disposition", "owner", "escalated_by", "summary", "proposed_action", "evidence"] + lines = ["\t".join(hdr)] + for r in rows: + lines.append("\t".join([str(r[c]) for c in hdr])) + + tsv = os.path.join(OUT_DIR, "triage-inbox-2026-09-26.tsv") + with open(tsv, "w") as f: + f.write("\n".join(lines) + "\n") + + cnt = Counter(r["disposition"] for r in rows) + md = os.path.join(OUT_DIR, "triage-inbox-2026-09-26.md") + with open(md, "w") as f: + f.write(f"""# Inbox triage — seed {d.get('cutover')} + +Seed: `inbox/seed-2026-09-26.json` (exported {d.get('exported_at')}) +Pending items triaged: **{len(rows)}**. Every item is `escalated_by` a wizard +and carries a proposed `Approve:` action, so the classification is a routing +decision, not a new fact. Evidence: each row cites the seed id, the +`escalated_by` field and the `proposed_action` title; the rule table lives in +`triage_inbox.py`. + +| disposition | count | +|---|---| +| escalate | {cnt.get('escalate', 0)} | +| route | {cnt.get('route', 0)} | +| hold | {cnt.get('hold', 0)} | +| delegate | {cnt.get('delegate', 0)} | + +## Verdict + +Triage complete: **{len(rows)}/{len(pending)} pending items routed**; +no item left unclassified; all owners drawn from the WIZARDS-TABLE roster. +See `triage-inbox-2026-09-26.tsv` for per-item evidence. +""") + print(f"wrote {tsv}") + print(f"wrote {md}") + print("counts:", dict(cnt)) + print("rows written:", len(rows)) + + +if __name__ == "__main__": + main()