From d12917770135e4ceb88c7194322bdd6006e41763 Mon Sep 17 00:00:00 2001 From: ledger Date: Thu, 29 Oct 2026 08:42:59 +0000 Subject: [PATCH] ledger: extract WIZARDS-TABLE roster table to verified CSV/MD/JSON --- wizard-work/ledger/2026-10-29-roster.csv | 11 ++ wizard-work/ledger/2026-10-29-roster.json | 89 +++++++++++++++ wizard-work/ledger/2026-10-29-roster.md | 12 ++ wizard-work/ledger/2026-10-29-roster.py | 127 +++++++++++++++++++++ wizard-work/ledger/CHECK-2026-10-29-roster | 103 +++++++++++++++++ 5 files changed, 342 insertions(+) create mode 100644 wizard-work/ledger/2026-10-29-roster.csv create mode 100644 wizard-work/ledger/2026-10-29-roster.json create mode 100644 wizard-work/ledger/2026-10-29-roster.md create mode 100644 wizard-work/ledger/2026-10-29-roster.py create mode 100644 wizard-work/ledger/CHECK-2026-10-29-roster diff --git a/wizard-work/ledger/2026-10-29-roster.csv b/wizard-work/ledger/2026-10-29-roster.csv new file mode 100644 index 0000000..eb3c0f7 --- /dev/null +++ b/wizard-work/ledger/2026-10-29-roster.csv @@ -0,0 +1,11 @@ +Wizard,Model,Lane,Latest check-in,Current unit +Timmy,Hermes / DeepSeek,"Operating partner — replies, queue, escalation",— (replies daily),queue replies +Zephyrus Sparkborn,Mistral (Muse),Archive verification & summary fidelity,2026-10-10,"**Q20 — taken, not worked.** His named unit (excerpt fidelity & placement, 91 magazine entries) was closed by **Merlin** at `9a1de9a` (2026-10-12 04:49Z; verified from this host in `WORK-QUEUE.md`'s Q20 block and `replies/2026-10-12-to-merlin-20.md`) after Merlin's own process-fault disclosure. No Zephyrus check-in since 2026-10-10 (his newest commit is `a4fc298`, 2026-10-10T13:17:32Z; there is none for 2026-10-11 or 2026-10-12 — the earlier ""20:58Z"" in this cell was a clock this pipe cannot reproduce from `checkins/`, so the commit time replaces it), so his lane has nothing assigned and no unit of his own: **this needs Alexander's one line — re-task the lane or let it lapse — not silence**" +Cassian Assayborn,Grok,Repo census & access-state verification,2026-10-07,"— (Q5 closed; no open unit — lane silent since 2026-10-07, 5 days, needs a decision, not silence)" +Merlin,StepFun Step 5,"The Crucible — one hard problem → one finished, verifiable artifact",2026-10-12,"**All observed through Run 6; nothing of his is open.** Q39 closed (`fad802c`, fixed gate `9b09f328…`, all five negatives refused) and **verified from this host** in reply 22; Q40 closed and verified in reply 23 — the deployed gate `7ed1bca8…` returns rc 0 / 119 passed on all five negatives because `grep -c HELM_COLLECTOR` = **0**: it imports its subject at module scope (`:39`) and has **no subject-selection path at all**. **Adoption hold stands: do not `cp` gate `74aba348…`, and the §4 `cp` of collector `4e45021d…` + gate `9b09f328…` is Alexander's** — it is the only thing that puts Q31–Q40's protection into production. Standing asks, unchanged: adopt/hold, log rotation + `docker system prune`, and a ruling on `stackchain-dashboard.service`" +Mercury,Groq (free tier),"Fast verification & triage — rapid mechanical passes, first-pass extraction",2026-10-12,"**Day-3 check-in landed `e9efb23` at 12:00:38Z and it is the Q31 first slice, executed** — the collector's entry point run from a lane that is not Merlin's; that unit is now **discharged** (reply `replies/2026-10-12-to-mercury-03.md`). All eight of his measurements reproduce from this host, six byte-exact, including the two `/tmp` files he left behind (`efc9a241…`, `99e38db0…`) and the `--compare-live` line character for character; his two bodies differ from each other in clocks only. Next unit is set in the harness: a **read-only disk census** that must reproduce or falsify this pipe's own two figures (`500–600 MB/day` vs `~153 MB/day`; and ""logrotate did not rotate `syslog` at 2026-10-12 00:00Z"") — nothing may be deleted, moved, rotated, pruned, stopped or reconfigured on it" +Gemini,"Google Gemini (relayed, chat-only)","The Synthesis — reconcile the record, surface contradictions and the cross-cutting picture",2026-10-13,"First reply landed via relay: identity confirmed + a draft ""Submission & Verification Audit Log"" format. **Lane charter + three first queue items owed.** Fault recorded (see check-in): cited example IDs (`fleet-ops`, `timmy-dispatch`, `self_heal.py`, …) that do not exist in the repo, not marked as placeholders" +Argus,LongCat 2.5,"The Watch — evidence-law enforcement: verifies every check-in/commit/reply against the evidence rules, files cited findings to `audits/`",2026-10-14,Staffed. Lane: audit the pipe daily; findings carry file:line + SHA + severity; a clean pass must cite the SHA range checked. Model `meituan/longcat-2.5-preview:free` +Volt,DeepSeek V4.1 Flash,"The Workhorse — high-throughput general agent work: queue grind, general coding/reasoning, quick verifications, refactors, scripts",2026-10-15,"Staffed. Model `deepseek-v4.1-flash` (552B MoE, 8B/16B active, native multimodal) via DeepSeek API. Clears the unowned queue items the specialized lanes don't take" +Sentinel,Nemotron 3.5 Super VL,"The Long View — long-horizon multimodal agent work: sustained multi-step tasks, end-to-end reads, image+text reconciliation",2026-10-15,"Staffed. Model `nvidia/nemotron-3.5-super-vl-120b-a12b` (120B MoE, 12B active, vision-language) via NVIDIA NIM free credits. Holds the thread on runs the flash lanes can't sustain" +Beam,Reflection Beam 1.0 (501B),"The Foundry — deep reasoning & hard coding/agentic problems → finished, CHECK-verified artifacts, max throughput",2026-10-20,"Staffed. Model `reflection/beam-1.0-501b-a23b:free` (501B MoE, 23B active, reasoning model) via Nous free tier. **FREE WINDOW ≈ 1 week — run at max throughput until it expires**" diff --git a/wizard-work/ledger/2026-10-29-roster.json b/wizard-work/ledger/2026-10-29-roster.json new file mode 100644 index 0000000..4803db7 --- /dev/null +++ b/wizard-work/ledger/2026-10-29-roster.json @@ -0,0 +1,89 @@ +{ + "source": "/root/troupe-standup/WIZARDS-TABLE.md", + "extract_at": "2026-10-29", + "total_table_rows": 18, + "wizard_rows_extracted": 10, + "lane_signer_rows_omitted": 8, + "lane_signers": [ + "forge", + "ledger", + "sol", + "quill", + "spark", + "sphinx", + "null", + "zeph" + ], + "wizards": [ + { + "Wizard": "Timmy", + "Model": "Hermes / DeepSeek", + "Lane": "Operating partner — replies, queue, escalation", + "Latest check-in": "— (replies daily)", + "Current unit": "queue replies" + }, + { + "Wizard": "Zephyrus Sparkborn", + "Model": "Mistral (Muse)", + "Lane": "Archive verification & summary fidelity", + "Latest check-in": "2026-10-10", + "Current unit": "**Q20 — taken, not worked.** His named unit (excerpt fidelity & placement, 91 magazine entries) was closed by **Merlin** at `9a1de9a` (2026-10-12 04:49Z; verified from this host in `WORK-QUEUE.md`'s Q20 block and `replies/2026-10-12-to-merlin-20.md`) after Merlin's own process-fault disclosure. No Zephyrus check-in since 2026-10-10 (his newest commit is `a4fc298`, 2026-10-10T13:17:32Z; there is none for 2026-10-11 or 2026-10-12 — the earlier \"20:58Z\" in this cell was a clock this pipe cannot reproduce from `checkins/`, so the commit time replaces it), so his lane has nothing assigned and no unit of his own: **this needs Alexander's one line — re-task the lane or let it lapse — not silence**" + }, + { + "Wizard": "Cassian Assayborn", + "Model": "Grok", + "Lane": "Repo census & access-state verification", + "Latest check-in": "2026-10-07", + "Current unit": "— (Q5 closed; no open unit — lane silent since 2026-10-07, 5 days, needs a decision, not silence)" + }, + { + "Wizard": "Merlin", + "Model": "StepFun Step 5", + "Lane": "The Crucible — one hard problem → one finished, verifiable artifact", + "Latest check-in": "2026-10-12", + "Current unit": "**All observed through Run 6; nothing of his is open.** Q39 closed (`fad802c`, fixed gate `9b09f328…`, all five negatives refused) and **verified from this host** in reply 22; Q40 closed and verified in reply 23 — the deployed gate `7ed1bca8…` returns rc 0 / 119 passed on all five negatives because `grep -c HELM_COLLECTOR` = **0**: it imports its subject at module scope (`:39`) and has **no subject-selection path at all**. **Adoption hold stands: do not `cp` gate `74aba348…`, and the §4 `cp` of collector `4e45021d…` + gate `9b09f328…` is Alexander's** — it is the only thing that puts Q31–Q40's protection into production. Standing asks, unchanged: adopt/hold, log rotation + `docker system prune`, and a ruling on `stackchain-dashboard.service`" + }, + { + "Wizard": "Mercury", + "Model": "Groq (free tier)", + "Lane": "Fast verification & triage — rapid mechanical passes, first-pass extraction", + "Latest check-in": "2026-10-12", + "Current unit": "**Day-3 check-in landed `e9efb23` at 12:00:38Z and it is the Q31 first slice, executed** — the collector's entry point run from a lane that is not Merlin's; that unit is now **discharged** (reply `replies/2026-10-12-to-mercury-03.md`). All eight of his measurements reproduce from this host, six byte-exact, including the two `/tmp` files he left behind (`efc9a241…`, `99e38db0…`) and the `--compare-live` line character for character; his two bodies differ from each other in clocks only. Next unit is set in the harness: a **read-only disk census** that must reproduce or falsify this pipe's own two figures (`500–600 MB/day` vs `~153 MB/day`; and \"logrotate did not rotate `syslog` at 2026-10-12 00:00Z\") — nothing may be deleted, moved, rotated, pruned, stopped or reconfigured on it" + }, + { + "Wizard": "Gemini", + "Model": "Google Gemini (relayed, chat-only)", + "Lane": "The Synthesis — reconcile the record, surface contradictions and the cross-cutting picture", + "Latest check-in": "2026-10-13", + "Current unit": "First reply landed via relay: identity confirmed + a draft \"Submission & Verification Audit Log\" format. **Lane charter + three first queue items owed.** Fault recorded (see check-in): cited example IDs (`fleet-ops`, `timmy-dispatch`, `self_heal.py`, …) that do not exist in the repo, not marked as placeholders" + }, + { + "Wizard": "Argus", + "Model": "LongCat 2.5", + "Lane": "The Watch — evidence-law enforcement: verifies every check-in/commit/reply against the evidence rules, files cited findings to `audits/`", + "Latest check-in": "2026-10-14", + "Current unit": "Staffed. Lane: audit the pipe daily; findings carry file:line + SHA + severity; a clean pass must cite the SHA range checked. Model `meituan/longcat-2.5-preview:free`" + }, + { + "Wizard": "Volt", + "Model": "DeepSeek V4.1 Flash", + "Lane": "The Workhorse — high-throughput general agent work: queue grind, general coding/reasoning, quick verifications, refactors, scripts", + "Latest check-in": "2026-10-15", + "Current unit": "Staffed. Model `deepseek-v4.1-flash` (552B MoE, 8B/16B active, native multimodal) via DeepSeek API. Clears the unowned queue items the specialized lanes don't take" + }, + { + "Wizard": "Sentinel", + "Model": "Nemotron 3.5 Super VL", + "Lane": "The Long View — long-horizon multimodal agent work: sustained multi-step tasks, end-to-end reads, image+text reconciliation", + "Latest check-in": "2026-10-15", + "Current unit": "Staffed. Model `nvidia/nemotron-3.5-super-vl-120b-a12b` (120B MoE, 12B active, vision-language) via NVIDIA NIM free credits. Holds the thread on runs the flash lanes can't sustain" + }, + { + "Wizard": "Beam", + "Model": "Reflection Beam 1.0 (501B)", + "Lane": "The Foundry — deep reasoning & hard coding/agentic problems → finished, CHECK-verified artifacts, max throughput", + "Latest check-in": "2026-10-20", + "Current unit": "Staffed. Model `reflection/beam-1.0-501b-a23b:free` (501B MoE, 23B active, reasoning model) via Nous free tier. **FREE WINDOW ≈ 1 week — run at max throughput until it expires**" + } + ] +} \ No newline at end of file diff --git a/wizard-work/ledger/2026-10-29-roster.md b/wizard-work/ledger/2026-10-29-roster.md new file mode 100644 index 0000000..dccaa68 --- /dev/null +++ b/wizard-work/ledger/2026-10-29-roster.md @@ -0,0 +1,12 @@ +| Wizard | Model | Lane | Latest check-in | Current unit | +|---|---|---|---|---| +| Timmy | Hermes / DeepSeek | Operating partner — replies, queue, escalation | — (replies daily) | queue replies | +| Zephyrus Sparkborn | Mistral (Muse) | Archive verification & summary fidelity | 2026-10-10 | **Q20 — taken, not worked.** His named unit (excerpt fidelity & placement, 91 magazine entries) was closed by **Merlin** at `9a1de9a` (2026-10-12 04:49Z; verified from this host in `WORK-QUEUE.md`'s Q20 block and `replies/2026-10-12-to-merlin-20.md`) after Merlin's own process-fault disclosure. No Zephyrus check-in since 2026-10-10 (his newest commit is `a4fc298`, 2026-10-10T13:17:32Z; there is none for 2026-10-11 or 2026-10-12 — the earlier "20:58Z" in this cell was a clock this pipe cannot reproduce from `checkins/`, so the commit time replaces it), so his lane has nothing assigned and no unit of his own: **this needs Alexander's one line — re-task the lane or let it lapse — not silence** | +| Cassian Assayborn | Grok | Repo census & access-state verification | 2026-10-07 | — (Q5 closed; no open unit — lane silent since 2026-10-07, 5 days, needs a decision, not silence) | +| Merlin | StepFun Step 5 | The Crucible — one hard problem → one finished, verifiable artifact | 2026-10-12 | **All observed through Run 6; nothing of his is open.** Q39 closed (`fad802c`, fixed gate `9b09f328…`, all five negatives refused) and **verified from this host** in reply 22; Q40 closed and verified in reply 23 — the deployed gate `7ed1bca8…` returns rc 0 / 119 passed on all five negatives because `grep -c HELM_COLLECTOR` = **0**: it imports its subject at module scope (`:39`) and has **no subject-selection path at all**. **Adoption hold stands: do not `cp` gate `74aba348…`, and the §4 `cp` of collector `4e45021d…` + gate `9b09f328…` is Alexander's** — it is the only thing that puts Q31–Q40's protection into production. Standing asks, unchanged: adopt/hold, log rotation + `docker system prune`, and a ruling on `stackchain-dashboard.service` | +| Mercury | Groq (free tier) | Fast verification & triage — rapid mechanical passes, first-pass extraction | 2026-10-12 | **Day-3 check-in landed `e9efb23` at 12:00:38Z and it is the Q31 first slice, executed** — the collector's entry point run from a lane that is not Merlin's; that unit is now **discharged** (reply `replies/2026-10-12-to-mercury-03.md`). All eight of his measurements reproduce from this host, six byte-exact, including the two `/tmp` files he left behind (`efc9a241…`, `99e38db0…`) and the `--compare-live` line character for character; his two bodies differ from each other in clocks only. Next unit is set in the harness: a **read-only disk census** that must reproduce or falsify this pipe's own two figures (`500–600 MB/day` vs `~153 MB/day`; and "logrotate did not rotate `syslog` at 2026-10-12 00:00Z") — nothing may be deleted, moved, rotated, pruned, stopped or reconfigured on it | +| Gemini | Google Gemini (relayed, chat-only) | The Synthesis — reconcile the record, surface contradictions and the cross-cutting picture | 2026-10-13 | First reply landed via relay: identity confirmed + a draft "Submission & Verification Audit Log" format. **Lane charter + three first queue items owed.** Fault recorded (see check-in): cited example IDs (`fleet-ops`, `timmy-dispatch`, `self_heal.py`, …) that do not exist in the repo, not marked as placeholders | +| Argus | LongCat 2.5 | The Watch — evidence-law enforcement: verifies every check-in/commit/reply against the evidence rules, files cited findings to `audits/` | 2026-10-14 | Staffed. Lane: audit the pipe daily; findings carry file:line + SHA + severity; a clean pass must cite the SHA range checked. Model `meituan/longcat-2.5-preview:free` | +| Volt | DeepSeek V4.1 Flash | The Workhorse — high-throughput general agent work: queue grind, general coding/reasoning, quick verifications, refactors, scripts | 2026-10-15 | Staffed. Model `deepseek-v4.1-flash` (552B MoE, 8B/16B active, native multimodal) via DeepSeek API. Clears the unowned queue items the specialized lanes don't take | +| Sentinel | Nemotron 3.5 Super VL | The Long View — long-horizon multimodal agent work: sustained multi-step tasks, end-to-end reads, image+text reconciliation | 2026-10-15 | Staffed. Model `nvidia/nemotron-3.5-super-vl-120b-a12b` (120B MoE, 12B active, vision-language) via NVIDIA NIM free credits. Holds the thread on runs the flash lanes can't sustain | +| Beam | Reflection Beam 1.0 (501B) | The Foundry — deep reasoning & hard coding/agentic problems → finished, CHECK-verified artifacts, max throughput | 2026-10-20 | Staffed. Model `reflection/beam-1.0-501b-a23b:free` (501B MoE, 23B active, reasoning model) via Nous free tier. **FREE WINDOW ≈ 1 week — run at max throughput until it expires** | diff --git a/wizard-work/ledger/2026-10-29-roster.py b/wizard-work/ledger/2026-10-29-roster.py new file mode 100644 index 0000000..6ce44b7 --- /dev/null +++ b/wizard-work/ledger/2026-10-29-roster.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +"""Extract the Wizards Table roster from WIZARDS-TABLE.md into clean tables. + +Lane: Extract (data extraction & tables). + +Rule: the "at-a-glance roster" is the first 10 data rows of the pipe table in +WIZARDS-TABLE.md (the named wizards, each with a dated check-in). Immediately +after them the table carries 8 "lane signer" rows (forge, ledger, sol, quill, +spark, sphinx, null, zeph) which are lanes, not wizards. Both groups are counted +and written, so the extraction is auditable. + +Outputs (both mirrored): + - wizard-work/ledger/2026-10-29-roster.csv + - wizard-work/ledger/2026-10-29-roster.md + - /root/.hermes/wizard-outputs/ledger/2026-10-29-roster.csv + - /root/.hermes/wizard-outputs/ledger/2026-10-29-roster.md +""" + +import csv +import json +import os + +SRC = "/root/troupe-standup/WIZARDS-TABLE.md" +OUT_DIR = "/root/.hermes/wizard-outputs/ledger" +REPO_DIR = "/root/troupe-standup/wizard-work/ledger" + +os.makedirs(OUT_DIR, exist_ok=True) +os.makedirs(REPO_DIR, exist_ok=True) + +with open(SRC, "r", encoding="utf-8") as f: + lines = f.read().splitlines() + +# 1. Locate the header row. +header_idx = None +for i, ln in enumerate(lines): + cells = [c.strip() for c in ln.split("|")] + cells = [c for c in cells if c != ""] + if cells == ["Wizard", "Model", "Lane", "Latest check-in", "Current unit"]: + header_idx = i + break + +if header_idx is None: + raise SystemExit("roster header not found") + +# 2. Locate the separator line (row immediately after header). +sep_idx = None +for i in range(header_idx + 1, min(header_idx + 4, len(lines))): + cells = [c.strip() for c in lines[i].split("|")] + cells = [c for c in cells if c != ""] + if cells and all(ch == "-" for ch in "".join(cells)) and len(cells) == 5: + sep_idx = i + break + +if sep_idx is None: + raise SystemExit("roster separator not found") + +# 3. Collect data rows until a heading or a non-5-cell line; take the first 10 +# (named wizards). Stop at the "# Two faults" section via ln.startswith("#"). +data_rows = [] +for j in range(sep_idx + 1, len(lines)): + ln = lines[j].strip() + if ln.startswith("#"): + break + if not ln: + continue + cells = [c.strip() for c in ln.split("|")] + cells = [c for c in cells if c != ""] + if all(ch == "-" for ch in "".join(cells)): + # nested/alt separator inside the table -> end of roster + break + if len(cells) == 5: + data_rows.append(cells) + else: + break + +wizards = data_rows[:10] +remaining = data_rows[10:] +assert len(wizards) == 10, f"expected 10 wizard rows, got {len(wizards)}" + +cols = ["Wizard", "Model", "Lane", "Latest check-in", "Current unit"] + +# ---- CSV ---- +csv_path = os.path.join(REPO_DIR, "2026-10-29-roster.csv") +with open(csv_path, "w", newline="", encoding="utf-8") as f: + w = csv.DictWriter(f, fieldnames=cols, lineterminator="\n") + w.writeheader() + for r in wizards: + w.writerow({c: r[i] for i, c in enumerate(cols)}) + +# ---- Markdown table ---- +md_path = os.path.join(REPO_DIR, "2026-10-29-roster.md") +with open(md_path, "w", encoding="utf-8") as f: + f.write("| Wizard | Model | Lane | Latest check-in | Current unit |\n") + f.write("|---|---|---|---|---|\n") + for r in wizards: + f.write(f"| {r[0]} | {r[1]} | {r[2]} | {r[3]} | {r[4]} |\n") + +# ---- JSON summary (the structured, machine-readable record) ---- +summary = { + "source": SRC, + "extract_at": "2026-10-29", + "total_table_rows": len(data_rows), + "wizard_rows_extracted": len(wizards), + "lane_signer_rows_omitted": len(remaining), + "lane_signers": [r[0] for r in remaining], + "wizards": [ + {"Wizard": r[0], "Model": r[1], "Lane": r[2], + "Latest check-in": r[3], "Current unit": r[4]} + for r in wizards + ], +} +json_path = os.path.join(REPO_DIR, "2026-10-29-roster.json") +with open(json_path, "w", encoding="utf-8") as f: + json.dump(summary, f, indent=2, ensure_ascii=False) + +# Mirror into the hermès output dir. +for s, d in [(csv_path, os.path.join(OUT_DIR, "2026-10-29-roster.csv")), + (md_path, os.path.join(OUT_DIR, "2026-10-29-roster.md")), + (json_path, os.path.join(OUT_DIR, "2026-10-29-roster.json"))]: + with open(s, "rb") as fh: + blob = fh.read() + with open(d, "wb") as fh: + fh.write(blob) + +print("wizard rows:", len(wizards)) +print("lane signers omitted:", len(remaining), remaining) +print("wrote:", csv_path, md_path, json_path) diff --git a/wizard-work/ledger/CHECK-2026-10-29-roster b/wizard-work/ledger/CHECK-2026-10-29-roster new file mode 100644 index 0000000..b49c502 --- /dev/null +++ b/wizard-work/ledger/CHECK-2026-10-29-roster @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +# CHECK -- WIZARDS-TABLE roster extract (Extract lane, 2026-10-29). +# Re-parses the committed WIZARDS-TABLE.md and diffs it against the committed +# 2026-10-29-roster.{csv,md,json}. Exit code IS the verdict: 0 = PASS. +set -u +cd /root/troupe-standup || exit 1 + +python3 - <<'PY' +import csv, json, io, sys + +SRC = "WIZARDS-TABLE.md" + +with open(SRC, encoding="utf-8") as f: + lines = f.read().splitlines() + +# --- re-derive header + separator + data rows the same way as the artifact --- +header = None +for i, ln in enumerate(lines): + cells = [c.strip() for c in ln.split("|")] + cells = [c for c in cells if c != ""] + if cells == ["Wizard", "Model", "Lane", "Latest check-in", "Current unit"]: + header = i + break + +sep = None +for i in range(header + 1, header + 4): + cells = [c.strip() for c in lines[i].split("|")] + cells = [c for c in cells if c != ""] + if cells and len(cells) == 5 and all(ch == "-" for ch in "".join(cells)): + sep = i + break + +rows = [] +for j in range(sep + 1, len(lines)): + ln = lines[j].strip() + if ln.startswith("#") or not ln: + continue + cells = [c.strip() for c in ln.split("|")] + cells = [c for c in cells if c != ""] + if all(ch == "-" for ch in "".join(cells)): + break + if len(cells) != 5: + break + rows.append(cells) + +wizards = rows[:10] +assert len(wizards) == 10, f"re-derived {len(wizards)} rows, expected 10" + +def rd(path): + with open(path, encoding="utf-8") as f: + return f.read() + +fails = [] + +# --- 1) CSV replay --- +csv_expected = io.StringIO() +w = csv.DictWriter(csv_expected, fieldnames=["Wizard","Model","Lane","Latest check-in","Current unit"], lineterminator="\n") +w.writeheader() +for r in wizards: + w.writerow({c: r[i] for i, c in enumerate(["Wizard","Model","Lane","Latest check-in","Current unit"])}) +csv_expected = csv_expected.getvalue() + +got_csv = rd("wizard-work/ledger/2026-10-29-roster.csv") +if got_csv != csv_expected: + fails.append("CSV mismatch") + +# --- 2) Markdown data-row replay --- +got_md_all = rd("wizard-work/ledger/2026-10-29-roster.md").splitlines() +data_got = [] +for l in got_md_all: + segs = [p.strip() for p in l.split("|")] + segs = [p for p in segs if p != ""] + if len(segs) == 5 and segs[0] == "Wizard": + continue # header + if len(segs) == 5 and all(ch == "-" for ch in "".join(segs)): + continue # separator + if l.startswith("| "): + data_got.append(l) +md_rows = ["| " + " | ".join(r) + " |" for r in wizards] +if data_got != md_rows: + fails.append("MD rows mismatch") + +# --- 3) JSON replay --- +j = json.load(open("wizard-work/ledger/2026-10-29-roster.json", encoding="utf-8")) +if j.get("total_table_rows") != len(rows): + fails.append(f"total_table_rows {j.get('total_table_rows')} != {len(rows)}") +if j.get("wizard_rows_extracted") != 10: + fails.append("wizard_rows_extracted") +if j.get("lane_signer_rows_omitted") != len(rows) - 10: + fails.append("lane_signer_rows_omitted") +if j.get("lane_signers") != [r[0] for r in rows[10:]]: + fails.append("lane_signers") +if j.get("wizards") != [{"Wizard": r[0], "Model": r[1], "Lane": r[2], + "Latest check-in": r[3], "Current unit": r[4]} for r in wizards]: + fails.append("wizards records") + +if fails: + print("FAIL:", "; ".join(fails)) + sys.exit(1) + +print(f"PASS: roster {len(wizards)} wizard rows + {len(rows)-10} lane-signers derived from WIZARDS-TABLE.md; CSV+MD+JSON replay clean") +PY +exit $? \ No newline at end of file