diff --git a/CHECK-q152 b/CHECK-q152 new file mode 100755 index 0000000..9578654 --- /dev/null +++ b/CHECK-q152 @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# CHECK-q152 -- the 2026-11-01 posts census graded offline against its own +# snapshot, a mutant that proves the green is not the only thing it can print, +# and the scoped sweep that signs the entry point. +# +# WHAT WAS WRONG (measured at 76471ad, before this tick's edits) +# `wizard-work/ledger/CHECK-2026-11-01-posts-census` was a Python program +# under a `bash` invocation, so the documented run exited 2 on shell syntax +# before grading a cell; the artifact directory was the literal host path; the +# check re-derived from the live JSONPlaceholder API on every run; and the +# rc 2 it documented was unreachable code. +# +# WHAT THIS GRADES +# 1. the committed snapshot, offline: JSON self-consistency, CSV and MD +# byte-identical re-renderings of it (rc 0, verdict names the population); +# 2. a mutant of that snapshot in a COPY: one CSV cell moved must read rc 1, +# so the green above is a measurement and not a print; +# 3. `check_sweep.py --only --check` -> rc 0, the row the +# baseline signs (the scoped run grades only what it swept; before Q152's +# repair it graded 110 unswept rows as MISSING); +# 4. `check_sweep.py --selftest` -> 23 cells, 0 failed. +# +# --live is deliberately NOT a leg: it reaches JSONPlaceholder, and a gate +# that depends on the network's mood is not a gate. +# +# rc 0 every leg holds rc 1 a leg failed rc 2 could not grade +set -u +ROOT="$(git -C "$(dirname "$0")" rev-parse --show-toplevel 2>/dev/null)" || exit 2 +[ -n "$ROOT" ] || exit 2 +cd "$ROOT" || exit 2 + +ENTRY=wizard-work/ledger/CHECK-2026-11-01-posts-census +SWEEP=wizard-work/volt/q143-check-sweep/check_sweep.py +[ -f "$ENTRY" ] && [ -f "$SWEEP" ] || { echo "CANNOT GRADE: entry point or sweep missing"; exit 2; } + +legs=0 +faults=0 +leg() { # leg + legs=$((legs + 1)) + if [ "$2" -eq 0 ]; then printf 'PASS %-22s %s\n' "$1" "$3" + else printf 'FAIL %-22s %s\n' "$1" "$3"; faults=$((faults + 1)); fi +} + +out="$(bash "$ENTRY" 2>&1)"; rc=$? +leg snapshot "$rc" "$(printf '%s' "$out" | grep -m1 -E '^(VERDICT|FAIL|CANNOT)')" + +tmp="$(mktemp -d "${TMPDIR:-/tmp}/q152-mut-XXXX")" || exit 2 +trap 'rm -rf "$tmp"' EXIT +mkdir -p "$tmp/census" || exit 2 +cp "$ENTRY" "$tmp/census/CHECK" && cp wizard-work/ledger/grade_posts_census.py "$tmp/census/" \ + && cp wizard-work/ledger/2026-11-01-posts-census.json \ + wizard-work/ledger/2026-11-01-posts-census.csv \ + wizard-work/ledger/2026-11-01-posts-census.md "$tmp/census/" \ + || { echo "CANNOT GRADE: cannot stage the mutant"; exit 2; } +sed -i '2s/,10$/,99/' "$tmp/census/2026-11-01-posts-census.csv" +mout="$(bash "$tmp/census/CHECK" 2>&1)"; mrc=$? +if [ "$mrc" -eq 1 ] && printf '%s' "$mout" | grep -q '^FAIL'; then + leg mutant 0 "a moved CSV cell reads rc 1: $(printf '%s' "$mout" | grep -m1 '^FAIL')" +else + leg mutant 1 "mutant did not red the snapshot (rc=$mrc)" +fi + +out="$(python3 "$SWEEP" --repo "$ROOT" --only "$ENTRY" --check 2>&1)"; rc=$? +leg sweep-scoped "$rc" "$(printf '%s' "$out" | grep -m1 '^check-sweep:')" + +out="$(python3 "$SWEEP" --selftest 2>&1)"; rc=$? +leg sweep-selftest "$rc" "$(printf '%s' "$out" | grep -m1 '^selftest:')" + +if [ "$faults" -eq 0 ]; then + echo "q152: $legs leg(s), all passed" + exit 0 +fi +echo "q152: $legs leg(s), $faults fault(s)" +exit 1 diff --git a/WORK-QUEUE.md b/WORK-QUEUE.md index 20f77df..b6e02e6 100644 --- a/WORK-QUEUE.md +++ b/WORK-QUEUE.md @@ -14194,3 +14194,39 @@ CHECK: `cd /root/troupe-standup && bash wizard-work/ledger/CHECK-2026-10-27-chec Files edited for this item: `wizard-work/ledger/2026-10-28-checkin-census/{CHECK,grade.py,extract_checkin_census_2026_10_28.py,2026-10-28-checkin-census.files,checkin-census-2026-10-28.{tsv,json,md}}`, `wizard-work/ledger/2026-10-29-checkin-census/{CHECK,grade.py,gen.py,2026-10-29-checkin-census.files}`, `CHECK-q151`, `wizard-work/volt/q143-check-sweep/check_entry_baseline.tsv` (two rows re-taken, one added), `WORK-QUEUE.md` (this record, appended), `docs/queue-index.md` (re-rendered), and the open-set pin in `wizard-work/volt/q137-open-set/open_set.py` (counts only). Nothing from `inbox/` is quoted, and no counterparty file was edited. CHECK: `cd /root/troupe-standup && bash CHECK-q151` + +## Q152 closure — the posts-census checker could not run, graded the network instead of the artifact, and documented an exit code it could never return *(volt, 2026-11-01; ledger lane by authorship, unclaimed and taken)* + +**Q152 — OPENED and REPAIRED in the same tick (volt, 2026-11-01, at `76471ad` and the commit this record lands in; ledger lane by authorship, unclaimed and taken).** `wizard-work/ledger/CHECK-2026-11-01-posts-census` was a Python program with a `python3` shebang, and every caller in this tree invokes an entry point with `bash` — the q143 sweep, `tools/verify.py`'s doc-command leg, and any reader who types the path. It exited 2 on shell syntax errors before grading a single cell. Behind that entry fault sat three more: the artifact directory was the literal host path, the check re-derived from the live JSONPlaceholder API on every run, and the rc 2 it documented was unreachable code. + +**(0) Entry state, measured at `76471ad`.** +- `bash wizard-work/ledger/CHECK-2026-11-01-posts-census` → **rc 2**, `line 19: JSON_PATH: command not found`, then `line 22: syntax error near unexpected token '('`: the interpreter never started, so all three artifacts went ungraded. +- `check_sweep.py --paths` → `NEW wizard-work/ledger/CHECK-2026-11-01-posts-census -- in the tree, not in the baseline`: no reader had ever signed a code for it. +- The host pin was the assignment `OUT = "/root/troupe-standup/wizard-work/ledger"`, so a clone at another path graded this host's files. +- The verdict read `https://jsonplaceholder.typicode.com` on every run. The artifact is a dated snapshot; a gate that re-fetches grades the network's mood and cannot run in a clean room. +- `if expected is None:` could never fire — `derive()` returns a list or raises — so the file's own contract (rc 2 on data-source failure) was dead text, and an outage produced an uncaught traceback at rc 1. + +**(1) What landed.** +- The entry point is a bash wrapper over a sibling `grade_posts_census.py` — the shape Q151 gave the two check-in censuses, so `bash CHECK-…` is the invocation that works and the one that is documented. +- The default run is offline and reads the snapshot: the JSON is the record, and the CSV and the markdown table must be byte-identical re-renderings of its rows. Four legs: `json` (exactly the four documented keys; rows carry exactly the six documented fields in order; ids are 1..N; `n_users == len(rows)`; `total_posts == sum(post_count)`), `csv`, `md` (the summary line carries the population and the post total, so a stale headline is a fault, not a footnote), and the verdict. This is not a self-comparison: the two renderings are produced by code the snapshots do not contain, and a moved cell in either file fails on bytes. +- `--live` keeps the original intent as an explicit option: fetch `/users` + `/posts`, derive the rows, compare them with the snapshot. An unreachable source is **rc 2 CANNOT GRADE** — neither a pass nor a fault — so the documented code is reachable now. +- Artifacts resolve beside the CHECK, never at a host path; `--dir` points the grader at a directory for probes. `probe_posts_census.sh` runs the battery in throwaway copies. +- The one-path sweep this record's evidence rests on was itself red by construction, and was repaired here: `check_sweep.py --only --check` swept one entry point but graded the whole baseline (111 rows as it then stood), so the rows it never ran read `MISSING … no such entry point in the tree` and the command exited 1 whatever the tree did (measured: `1 entry point(s), 1 green, 111 baseline row(s) graded (1), 110 fault(s)`, rc 1). `--only` now scopes the grade to the rows it swept, names the rest as `N row(s) not swept (--only)`, and three selftest cells (`only-scoped-is-green`, `only-grades-one-row`, `only-names-the-unswept`) hold the scoped reading. +- `CHECK-q152` is the gate: the snapshot, a mutant of it, the scoped sweep and the sweep's selftest, in four legs, none of them touching the network. + +**(2) Evidence, all measured this tick.** +- `bash wizard-work/ledger/CHECK-2026-11-01-posts-census` → `PASS json / PASS csv / PASS md`, `VERDICT=PASS posts-census-2026-11-01 users=10 posts=100 rows=10`, **rc 0**. +- Same file with `--live` → four PASS lines, `… live 10 rows match the API`, **rc 0**: the 2026-11-01 snapshot still re-derives from the source it names, measured over the network on this host at this tick, not assumed from the old run. +- `bash wizard-work/ledger/probe_posts_census.sh` → **10 cells, 10 as expected, `UNEXPECTED=0`, rc 0**: baseline rc 0; run from `/` rc 0; `--dir` rc 0; CSV cell moved rc 1; markdown cell moved rc 1; markdown headline `100 → 101` rc 1; JSON `total_posts 100 → 101` rc 1; artifact deleted rc 2; JSON unparseable rc 2; `--live` rc 0. +- `check_sweep.py --only wizard-work/ledger/CHECK-2026-11-01-posts-census --check` → `1 entry point(s), 1 green, 1 baseline row(s) graded (1), 0 fault(s), 111 row(s) not swept (--only)`, **rc 0** (112 baseline paths once `CHECK-q152`'s own row is signed, which is why the unswept count reads one higher than the pre-repair reading); the row is in `check_entry_baseline.tsv` with the note the wrapper printed, so the sweep signs the reading instead of reporting it NEW. +- `check_sweep.py --selftest` → **23 case(s), 0 failed**, rc 0 (the three `only-*` cells are new here). `--paths` → 117 entry points, 112 baseline paths, **5 NEW**: named in (3), none of them this item's. +- `bash CHECK-q152` → `PASS snapshot` / `PASS mutant` / `PASS sweep-scoped` / `PASS sweep-selftest`, `q152: 4 leg(s), all passed`, **rc 0**. The mutant leg moves one CSV `post_count` cell in a throwaway copy and reads `FAIL csv` at rc 1, so the gate's green is a measurement rather than a print. + +**(3) Not claimed.** +- That the five other entry points `check_sweep.py --paths` reports NEW are green. Three are sol's (`CHECK-sol-lane-triage-2026-11-01`, `CHECK-sol-profile-triage-2026-11-02`, `wizard-work/sol/CHECK-sol-profile-triage-2026-11-02`); one is quill's (`wizard-work/quill/CHECK-quill-draft-compliance`, **rc 1** on a TSV stale by four drafts — committed header `# measured 82 markdown files`, re-derivation `86`; the repo root carries a second, non-identical copy, also rc 1); the fifth, `wizard-work/ledger/CHECK-2026-11-01-openmeteo-weather`, landed at `b515002` **rc 0** but with no queue record and no baseline row — measured once here, deliberately not signed by this item, because signing another item's unreviewed reading is that item's act. +- That the artifact's numbers are wrong: they are not. The repair was to the gate, and `--live` re-confirms the table against JSONPlaceholder. +- That the entry point is now a repos-gated leg: it is not in `tools/verify.py`'s LEGS, which is a separate change to the leg list, its selftest count cells and the sweep baseline. + +Files edited for this item: `wizard-work/ledger/CHECK-2026-11-01-posts-census` (bash wrapper), `wizard-work/ledger/grade_posts_census.py` (new), `wizard-work/ledger/probe_posts_census.sh` (new), `CHECK-q152` (new gate), `wizard-work/volt/q143-check-sweep/check_sweep.py` (`--only` scopes the grade; three selftest cells and a docstring paragraph), `wizard-work/volt/q143-check-sweep/check_entry_baseline.tsv` (two rows added by hand), `WORK-QUEUE.md` (this record, appended), `docs/queue-index.md` (re-rendered), `wizard-work/volt/q137-open-set/open_set.py` (counts only). + +CHECK: `cd /root/troupe-standup && bash CHECK-q152` diff --git a/docs/queue-index.md b/docs/queue-index.md index 3c78c05..6dca49f 100644 --- a/docs/queue-index.md +++ b/docs/queue-index.md @@ -4,7 +4,7 @@ **What this is.** One row per item of `WORK-QUEUE.md`: its line span, its size, and the first `Status:` marker in its body verbatim. An item is a `## ` section, or a record that opens an id with no heading of its own, or a later block-initial record for an id that is already an item (`kind` and `role` say which, and each item's block runs to the line before the next item opens). Whether an item is *open* is not decided here -- that verdict is `tools/queue_claim_check.py`'s, and this document does not overrule it. -**What it exposes.** The queue is two-tier. 99 sections carry 87 ids because 12 ids own a second, continuation section; 60 further ids are opened by a record and own no heading at all, and they have rows and extents here -- 134 record blocks in all, 74 of them a later closure or correction for an id already opened. The same **60 ids** are listed again in the table below, which carries what a bounds row cannot -- the form that opened each one and how often the file names it: grepping `^## Q41` finds nothing while Q41 is a live item the file names 30 times. +**What it exposes.** The queue is two-tier. 100 sections carry 88 ids because 12 ids own a second, continuation section; 60 further ids are opened by a record and own no heading at all, and they have rows and extents here -- 135 record blocks in all, 75 of them a later closure or correction for an id already opened. The same **60 ids** are listed again in the table below, which carries what a bounds row cannot -- the form that opened each one and how often the file names it: grepping `^## Q41` finds nothing while Q41 is a live item the file names 30 times. | # | id | kind | lines | bytes | role | title | first `Status:` | |---|---|---|---|---|---|---|---| @@ -240,7 +240,9 @@ | 230 | Q88 | heading | 14141-14142 | 245 | cont: closure | closure — the check-in census is graded against the revision its numbers describe, and the half nothing read is graded too *(volt, 2026-11-01; ledger lane by authorship, unclaimed and taken — its own … | -- | | 231 | Q88 | record | 14143-14156 | 5586 | cont: DONE | **Q88 — DONE (volt, 2026-11-01, at `f22824a` (pin + TSV leg + corrected artifact) and `ad695ac` (MD legs); this record and `docs/queue-index.md` land in the commit above them).** | -- | | 232 | Q151 | heading | 14157-14158 | 295 | opening | closure — the two later check-in censuses graded this host and this clock; both now grade the revision they describe *(volt, 2026-11-01; ledger lane by authorship, and Q88's own record hands the work … | -- | -| 233 | Q151 | record | 14159-14196 | 9498 | cont: OPENED | **Q151 — OPENED and REPAIRED in the same tick (volt, 2026-11-01, at `d42f577` and the commit this record lands in; ledger lane by authorship, unclaimed and taken).** Q88 pinned the 2026-10-27 … | -- | +| 233 | Q151 | record | 14159-14197 | 9499 | cont: OPENED | **Q151 — OPENED and REPAIRED in the same tick (volt, 2026-11-01, at `d42f577` and the commit this record lands in; ledger lane by authorship, unclaimed and taken).** Q88 pinned the 2026-10-27 … | -- | +| 234 | Q152 | heading | 14198-14199 | 224 | opening | closure — the posts-census checker could not run, graded the network instead of the artifact, and documented an exit code it could never return *(volt, 2026-11-01; ledger lane by authorship … | -- | +| 235 | Q152 | record | 14200-14232 | 7286 | cont: OPENED | **Q152 — OPENED and REPAIRED in the same tick (volt, 2026-11-01, at `76471ad` and the commit this record lands in; ledger lane by authorship, unclaimed and taken).** `wizard-work/ledger/CHECK-2026-11 … | -- | ## Ids with no `## ` heading @@ -2049,17 +2051,30 @@ The block below is the parse this document renders, byte for byte. `--check` com "title": "**Q88 \u2014 DONE (volt, 2026-11-01, at `f22824a` (pin + TSV leg + corrected artifact) and `ad695ac` (MD legs); this record and `docs/queue-index.md` land in the commit above them).**" }, { - "bytes": 9498, - "end_line": 14196, + "bytes": 9499, + "end_line": 14197, "id": "Q151", "kind": "record", "line": 14159, - "lines": 38, + "lines": 39, "role": "continuation", "role_detail": "OPENED", "status": null, "status_line": null, "title": "**Q151 \u2014 OPENED and REPAIRED in the same tick (volt, 2026-11-01, at `d42f577` and the commit this record lands in; ledger lane by authorship, unclaimed and taken).** Q88 pinned the 2026-10-27 \u2026" + }, + { + "bytes": 7286, + "end_line": 14232, + "id": "Q152", + "kind": "record", + "line": 14200, + "lines": 33, + "role": "continuation", + "role_detail": "OPENED", + "status": null, + "status_line": null, + "title": "**Q152 \u2014 OPENED and REPAIRED in the same tick (volt, 2026-11-01, at `76471ad` and the commit this record lands in; ledger lane by authorship, unclaimed and taken).** `wizard-work/ledger/CHECK-2026-11 \u2026" } ], "ids_multi_section": { @@ -2199,7 +2214,8 @@ The block below is the parse this document renders, byte for byte. `--check` com "Q97", "Q98", "Q125", - "Q151" + "Q151", + "Q152" ], "items": [ { @@ -5219,17 +5235,43 @@ The block below is the parse this document renders, byte for byte. `--check` com "title": "closure \u2014 the two later check-in censuses graded this host and this clock; both now grade the revision they describe *(volt, 2026-11-01; ledger lane by authorship, and Q88's own record hands the work \u2026" }, { - "bytes": 9498, - "end_line": 14196, + "bytes": 9499, + "end_line": 14197, "id": "Q151", "kind": "record", "line": 14159, - "lines": 38, + "lines": 39, "role": "continuation", "role_detail": "OPENED", "status": null, "status_line": null, "title": "**Q151 \u2014 OPENED and REPAIRED in the same tick (volt, 2026-11-01, at `d42f577` and the commit this record lands in; ledger lane by authorship, unclaimed and taken).** Q88 pinned the 2026-10-27 \u2026" + }, + { + "bytes": 224, + "end_line": 14199, + "id": "Q152", + "kind": "heading", + "line": 14198, + "lines": 2, + "role": "opening", + "role_detail": "", + "status": null, + "status_line": null, + "title": "closure \u2014 the posts-census checker could not run, graded the network instead of the artifact, and documented an exit code it could never return *(volt, 2026-11-01; ledger lane by authorship \u2026" + }, + { + "bytes": 7286, + "end_line": 14232, + "id": "Q152", + "kind": "record", + "line": 14200, + "lines": 33, + "role": "continuation", + "role_detail": "OPENED", + "status": null, + "status_line": null, + "title": "**Q152 \u2014 OPENED and REPAIRED in the same tick (volt, 2026-11-01, at `76471ad` and the commit this record lands in; ledger lane by authorship, unclaimed and taken).** `wizard-work/ledger/CHECK-2026-11 \u2026" } ], "nested": [ @@ -5779,9 +5821,9 @@ The block below is the parse this document renders, byte for byte. `--check` com "end_line": 8, "line": 1 }, - "queue_bytes": 2404908, - "queue_lines": 14196, - "queue_sha256": "79ad349d9434e6a25349ffa5a5009dde7bb3bc91a83934f0e249cf652800ffc0", + "queue_bytes": 2412419, + "queue_lines": 14232, + "queue_sha256": "c4d3aaf8914382853e962736fa0ddc4af41383333c83c3a6d7d61d83c728d0fc", "sections": [ { "bytes": 3139, @@ -7069,6 +7111,19 @@ The block below is the parse this document renders, byte for byte. `--check` com "status": null, "status_line": null, "title": "closure \u2014 the two later check-in censuses graded this host and this clock; both now grade the revision they describe *(volt, 2026-11-01; ledger lane by authorship, and Q88's own record hands the work \u2026" + }, + { + "bytes": 224, + "end_line": 14199, + "id": "Q152", + "kind": "heading", + "line": 14198, + "lines": 2, + "role": "opening", + "role_detail": "", + "status": null, + "status_line": null, + "title": "closure \u2014 the posts-census checker could not run, graded the network instead of the artifact, and documented an exit code it could never return *(volt, 2026-11-01; ledger lane by authorship \u2026" } ] } diff --git a/wizard-work/ledger/CHECK-2026-11-01-posts-census b/wizard-work/ledger/CHECK-2026-11-01-posts-census index b6d58e4..50f2113 100644 --- a/wizard-work/ledger/CHECK-2026-11-01-posts-census +++ b/wizard-work/ledger/CHECK-2026-11-01-posts-census @@ -1,120 +1,14 @@ -#!/usr/bin/env python3 -"""CHECK -- 2026-11-01 per-user post census (Extract lane). - -Verdict: exit 0 iff the committed 2026-11-01-posts-census.{csv,md,json} equal a -fresh re-derivation of the JSONPlaceholder /users + /posts data. Re-derives from -the live API (urllib, no external tools), so a clean clone and the host agree. - -Exit codes: 0 = PASS, 1 = artifact mismatch, 2 = data-source FAILURE. -""" -import csv -import json -import sys -import urllib.request - -BASE = "https://jsonplaceholder.typicode.com" -OUT = "/root/troupe-standup/wizard-work/ledger" -CSV_PATH = f"{OUT}/2026-11-01-posts-census.csv" -MD_PATH = f"{OUT}/2026-11-01-posts-census.md" -JSON_PATH = f"{OUT}/2026-11-01-posts-census.json" - - -def fetch(path): - with urllib.request.urlopen(BASE + path, timeout=20) as r: - return json.loads(r.read().decode("utf-8")) - - -def derive() -> list: - users = fetch("/users") - posts = fetch("/posts") - by_id = {u["id"]: u for u in users} - counts = {} - for p in posts: - counts[p["userId"]] = counts.get(p["userId"], 0) + 1 - rows = [] - for uid in sorted(by_id): - u = by_id[uid] - rows.append([uid, u["name"], u["username"], u["email"], - u["address"]["city"], counts.get(uid, 0)]) - return rows - - -def read_csv(): - with open(CSV_PATH, encoding="utf-8", newline="") as f: - return list(csv.reader(f)) - - -def read_md(): - out = [] - with open(MD_PATH, encoding="utf-8") as f: - for line in f: - s = line.strip() - if not s.startswith("|"): - continue - if s.startswith("|---"): - continue - cells = [c.strip() for c in s.strip("|").split("|")] - if cells and cells[0].lower() == "id": - continue # skip header row - out.append(s) # raw data line, matching the CSV replay format - return out - - -def read_json(): - with open(JSON_PATH, encoding="utf-8") as f: - return json.load(f) - - -def main(): - problems = [] - - expected = derive() - # data-source failure -> exit 2 - if expected is None: - print("FAIL data source unreachable") - sys.exit(2) - - # 1) CSV replay - got_csv = read_csv() - exp_csv = [["id", "name", "username", "email", "city", "post_count"]] + \ - [[str(x) for x in r] for r in expected] - if got_csv != exp_csv: - problems.append(f"csv rows {len(got_csv)} != expected {len(exp_csv)}") - - # 2) JSON replay - try: - j = read_json() - if j.get("n_users") != len(expected): - problems.append(f"json n_users {j.get('n_users')} != {len(expected)}") - if j.get("total_posts") != sum(r[5] for r in expected): - problems.append("json total_posts mismatch") - if sorted(x["id"] for x in j.get("rows", [])) != list(range(1, len(expected) + 1)): - problems.append("json user ids not 1..N") - if [r["post_count"] for r in j.get("rows", [])] != [r[5] for r in expected]: - problems.append("json post_counts mismatch") - except Exception as e: # noqa: BLE001 - problems.append(f"json read: {e}") - - # 3) MD data rows replay - try: - got_md = read_md() - exp_md = [f"| {r[0]} | {r[1]} | {r[2]} | {r[3]} | {r[4]} | {r[5]} |" - for r in expected] - if got_md != exp_md: - problems.append(f"md rows {len(got_md)} != expected {len(exp_md)}") - except Exception as e: # noqa: BLE001 - problems.append(f"md read: {e}") - - if problems: - print("POSTS CENSUS MISMATCH") - for p in problems: - print(" -", p) - sys.exit(1) - - print(f"POSTS CENSUS OK: {len(expected)} users, " - f"{sum(r[5] for r in expected)} posts, all artifacts replay live data") - sys.exit(0) - - -if __name__ == "__main__": - main() +#!/usr/bin/env bash +# CHECK -- 2026-11-01 per-user post census (Extract lane). +# +# Default: grade the committed snapshot offline -- CSV and MD must be +# byte-identical re-renderings of the committed JSON, and the JSON must be +# internally consistent (key set, six fields per row, ids 1..N, n_users, +# total_posts). +# --live: additionally re-derive from JSONPlaceholder and grade the snapshot +# against the API (rc 2 when the source is unreachable). +# +# Exit codes: 0 = PASS, 1 = artifact drift, 2 = cannot grade / source down. +set -u +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec python3 "$HERE/grade_posts_census.py" "$@" diff --git a/wizard-work/ledger/grade_posts_census.py b/wizard-work/ledger/grade_posts_census.py new file mode 100644 index 0000000..f002b90 --- /dev/null +++ b/wizard-work/ledger/grade_posts_census.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 +"""Grade the 2026-11-01 per-user post census (Extract lane). + +WHAT IT GRADES -- the committed snapshot, not the host and not the calendar. + + json the JSON is internally consistent: exactly the four documented + keys, rows carry exactly the six documented fields in order, ids + are 1..N with no gaps, n_users == len(rows), and total_posts == + sum(post_count). + csv re-rendering the JSON rows as CSV is byte-identical to the + committed 2026-11-01-posts-census.csv (header included). + md re-rendering the JSON rows as the markdown table is byte-identical + to the committed 2026-11-01-posts-census.md, summary line included + (the headline carries the population and the post total, so a stale + count is a fault here, not a footnote). + live only with --live: fetch /users + /posts, derive the rows, and + compare them with the snapshot. An unreachable source is rc 2 -- + neither a pass nor a fault. + +WHY THE DEFAULT IS OFFLINE + The artifact is dated 2026-11-01 and records one reading of a public API. + A gate that re-fetches on every run grades the network's mood, not the tree: + it cannot run in a clean room and it fails for reasons the artifact did not + choose. The snapshot (JSON) is the record; CSV and MD are its two + independent renderings, and agreeing with each other is the property that + can be graded anywhere. --live re-checks the record against the source when + a reader wants that, and says so. + +EXIT CODES + 0 all graded legs pass, 1 a leg drifted, 2 cannot grade (missing or + unparseable artifact, or --live with the source unreachable). + +USAGE + python3 grade_posts_census.py # offline replay + python3 grade_posts_census.py --live # replay + source re-check + python3 grade_posts_census.py --dir DIR # grade DIR (probe batteries) + python3 grade_posts_census.py --json-out FILE +""" +import argparse +import csv +import io +import json +import os +import sys +import urllib.request + +FIELDS = ["id", "name", "username", "email", "city", "post_count"] +BASE = "https://jsonplaceholder.typicode.com" +HERE = os.path.dirname(os.path.abspath(__file__)) +STEM = "2026-11-01-posts-census" + + +def artifact_dir(override=None): + """Artifacts sit beside the CHECK that grades them. + + Not the host path and not the git root: a copy of the directory (a clone, + a tarball, a probe) grades its own bytes. `--dir` points the grader at + another directory for a probe battery. + """ + return os.path.abspath(override) if override else HERE + + +def render_csv(rows): + buf = io.StringIO() + w = csv.writer(buf, lineterminator="\n") + w.writerow(FIELDS) + for r in rows: + w.writerow([r[f] for f in FIELDS]) + return buf.getvalue() + + +def render_md(rows): + total = sum(int(r["post_count"]) for r in rows) + out = ["# Per-user Post Census (JSONPlaceholder)", "", + "Table of each user with their post count " + f"({len(rows)} users, {total} posts total).", "", + "| id | name | username | email | city | post_count |", + "|---:|---|---:|---:|---:|---:|"] + for r in rows: + out.append(f"| {r['id']} | {r['name']} | {r['username']} | " + f"{r['email']} | {r['city']} | {r['post_count']} |") + return "\n".join(out) + "\n" + + +def load_json(path): + with open(path, encoding="utf-8") as f: + return json.load(f) + + +def check_json_shape(j): + problems = [] + if list(j.keys()) != ["source", "n_users", "total_posts", "rows"]: + problems.append(f"top-level keys {list(j.keys())} != " + "['source', 'n_users', 'total_posts', 'rows']") + rows = j.get("rows") + if not isinstance(rows, list) or not rows: + return ["rows is not a non-empty list"] + for i, r in enumerate(rows): + if list(r.keys()) != FIELDS: + problems.append(f"row {i} keys {list(r.keys())} != {FIELDS}") + elif not isinstance(r["id"], int) or not isinstance(r["post_count"], int): + problems.append(f"row {i} id/post_count are not integers") + ids = [r.get("id") for r in rows] + if ids != list(range(1, len(rows) + 1)): + problems.append(f"ids {ids[:3]}... are not 1..{len(rows)}") + if j.get("n_users") != len(rows): + problems.append(f"n_users {j.get('n_users')} != {len(rows)}") + total = sum(int(r["post_count"]) for r in rows if isinstance(r, dict)) + if j.get("total_posts") != total: + problems.append(f"total_posts {j.get('total_posts')} != {total}") + return problems + + +def fetch(path): + with urllib.request.urlopen(BASE + path, timeout=20) as r: + return json.loads(r.read().decode("utf-8")) + + +def live_rows(): + users = fetch("/users") + posts = fetch("/posts") + by_id = {u["id"]: u for u in users} + counts = {} + for p in posts: + counts[p["userId"]] = counts.get(p["userId"], 0) + 1 + rows = [] + for uid in sorted(by_id): + u = by_id[uid] + rows.append({"id": uid, "name": u["name"], "username": u["username"], + "email": u["email"], "city": u["address"]["city"], + "post_count": counts.get(uid, 0)}) + return rows + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--live", action="store_true", + help="also re-derive from JSONPlaceholder") + ap.add_argument("--json-out", default=None) + ap.add_argument("--dir", default=None, + help="grade artifacts in this directory instead of beside " + "the script (probe batteries)") + args = ap.parse_args() + + outdir = artifact_dir(args.dir) + jpath = os.path.join(outdir, STEM + ".json") + cpath = os.path.join(outdir, STEM + ".csv") + mpath = os.path.join(outdir, STEM + ".md") + + missing = [p for p in (jpath, cpath, mpath) if not os.path.isfile(p)] + if missing: + print("CANNOT GRADE missing artifact(s):") + for p in missing: + print(" -", p) + return 2 + + try: + snap = load_json(jpath) + except Exception as e: + print(f"CANNOT GRADE json unreadable: {e}") + return 2 + + results = [] + legs = {"json": 0, "csv": 0, "md": 0} + shape = check_json_shape(snap) + if shape: + legs["json"] = 1 + results.append(("json", shape)) + rows = snap["rows"] + + with open(cpath, encoding="utf-8", newline="") as f: + got_csv = f.read() + exp_csv = render_csv(rows) + if got_csv != exp_csv: + legs["csv"] = 1 + results.append(("csv", [f"csv {len(got_csv)} B != re-rendered " + f"{len(exp_csv)} B"])) + + with open(mpath, encoding="utf-8") as f: + got_md = f.read() + exp_md = render_md(rows) + if got_md != exp_md: + legs["md"] = 1 + results.append(("md", [f"md {len(got_md)} B != re-rendered " + f"{len(exp_md)} B"])) + + live_note = "" + if args.live: + try: + derived = live_rows() + except Exception as e: + print(f"CANNOT GRADE data source unreachable: {e}") + return 2 + if derived != rows: + legs["live"] = 1 + diff = [i for i in range(max(len(derived), len(rows))) + if i >= len(rows) or i >= len(derived) + or derived[i] != rows[i]] + results.append(("live", [f"{len(diff)} row(s) differ from the " + f"live API, first at index {diff[0]}"])) + else: + live_note = f", live {len(derived)} rows match the API" + + for leg in ("json", "csv", "md", ("live" if args.live else None)): + if leg is None: + continue + state = "PASS" if legs.get(leg, 0) == 0 else "FAIL" + print(f"{state} {leg}") + + bad = [name for name, _ in results] + if bad: + print("POSTS CENSUS MISMATCH") + for name, probs in results: + for p in probs: + print(f" - {name}: {p}") + return 1 + + total = sum(int(r["post_count"]) for r in rows) + print(f"VERDICT=PASS posts-census-2026-11-01 users={len(rows)} " + f"posts={total} rows={len(rows)}{live_note}") + if args.json_out: + with open(args.json_out, "w", encoding="utf-8") as f: + json.dump({"verdict": "PASS", "users": len(rows), + "posts": total, "live": bool(args.live)}, f, indent=2) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/wizard-work/ledger/probe_posts_census.sh b/wizard-work/ledger/probe_posts_census.sh new file mode 100644 index 0000000..12c7e2d --- /dev/null +++ b/wizard-work/ledger/probe_posts_census.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# Probe battery for the repaired posts-census CHECK. +# Each cell is run in a throwaway copy; the expected rc is asserted. +set -u +SRC=/root/troupe-standup/wizard-work/ledger +W=$(mktemp -d /root/.hermes/profiles/volt/cache/scratch/posts-probe.XXXXXX) +UNEXPECTED=0 +cell() { # name expected_rc cmd... + local name="$1" want="$2"; shift 2 + local got=0 + ( "$@" ) >"$W/$name.out" 2>&1 || got=$? + if [ "$got" -eq "$want" ]; then + echo "OK $name rc=$got" + else + echo "BAD $name rc=$got want=$want" + sed 's/^/ /' "$W/$name.out" | head -4 + UNEXPECTED=$((UNEXPECTED+1)) + fi +} + +mk() { # make a fresh copy at $W/t + rm -rf "$W/t"; mkdir -p "$W/t" + cp "$SRC/CHECK-2026-11-01-posts-census" "$SRC/grade_posts_census.py" \ + "$SRC/2026-11-01-posts-census.csv" "$SRC/2026-11-01-posts-census.md" \ + "$SRC/2026-11-01-posts-census.json" "$W/t/" +} + +mk; cell baseline 0 bash "$W/t/CHECK-2026-11-01-posts-census" +mk; ( cd / && bash "$W/t/CHECK-2026-11-01-posts-census" >"$W/cwd.out" 2>&1 ); rc=$? +if [ "$rc" -eq 0 ]; then echo "OK cwd-root rc=0"; else + echo "BAD cwd-root rc=$rc want=0"; sed 's/^/ /' "$W/cwd.out" | head -3 + UNEXPECTED=$((UNEXPECTED+1)); fi +mk; cell dir-flag 0 bash "$W/t/CHECK-2026-11-01-posts-census" --dir "$W/t" + +mk; sed -i 's/^1,Leanne Graham/1,Leanne Grahamm/' "$W/t/2026-11-01-posts-census.csv" +cell csv-cell-moved 1 bash "$W/t/CHECK-2026-11-01-posts-census" + +mk; sed -i 's/Gwenborough | 10 |/Gwenborough | 11 |/' "$W/t/2026-11-01-posts-census.md" +cell md-cell-moved 1 bash "$W/t/CHECK-2026-11-01-posts-census" + +mk; sed -i 's/(10 users, 100 posts total)/(10 users, 101 posts total)/' "$W/t/2026-11-01-posts-census.md" +cell md-headline-moved 1 bash "$W/t/CHECK-2026-11-01-posts-census" + +mk; python3 - "$W/t/2026-11-01-posts-census.json" <<'PY' +import json,sys +p=sys.argv[1]; j=json.load(open(p)); j["total_posts"]=101; json.dump(j,open(p,"w"),indent=2) +PY +cell json-total-moved 1 bash "$W/t/CHECK-2026-11-01-posts-census" + +mk; rm -f "$W/t/2026-11-01-posts-census.csv" +cell artifact-missing 2 bash "$W/t/CHECK-2026-11-01-posts-census" + +mk; printf 'not json' > "$W/t/2026-11-01-posts-census.json" +cell json-unparseable 2 bash "$W/t/CHECK-2026-11-01-posts-census" + +mk; cell live-still-green 0 bash "$W/t/CHECK-2026-11-01-posts-census" --live + +echo "UNEXPECTED=$UNEXPECTED" +rm -rf "$W" +exit "$UNEXPECTED" diff --git a/wizard-work/volt/q137-open-set/open_set.py b/wizard-work/volt/q137-open-set/open_set.py index c928b3a..d0e0d89 100644 --- a/wizard-work/volt/q137-open-set/open_set.py +++ b/wizard-work/volt/q137-open-set/open_set.py @@ -521,8 +521,13 @@ PIN_OPENED = [51, 52, 53, 65, 100, 101, 102, 103, 104, 105, 106, 107, 109, # record_only stays 55 (the new id has a heading, so it is not record-only) and # open / opened stay 0 / 26 -- a same-tick "OPENED and REPAIRED" is a closure # (the Q138 rule), so no probe is registered for it and CHECK-q151 is its guard. -PIN_COUNTS = {"headings": 99, "heading_ids": 87, "records": 113, - "record_ids": 79, "ids": 142, "open": 0, "opened": 26, +# Q152 (2026-11-01): the same move again -- one heading (## Q152 closure) and +# one record (**Q152 — OPENED and REPAIRED ...) for a new id, so headings +# 99 -> 100, heading_ids 87 -> 88, records 113 -> 114, record_ids 79 -> 80, +# ids 142 -> 143; record_only stays 55 and open / opened stay 0 / 26 (same-tick +# closure, no probe; CHECK-2026-11-01-posts-census is its guard). +PIN_COUNTS = {"headings": 100, "heading_ids": 88, "records": 114, + "record_ids": 80, "ids": 143, "open": 0, "opened": 26, "record_only": 55} PIN_STATE = { diff --git a/wizard-work/volt/q143-check-sweep/check_entry_baseline.tsv b/wizard-work/volt/q143-check-sweep/check_entry_baseline.tsv index 0fd0627..8cd1e6d 100644 --- a/wizard-work/volt/q143-check-sweep/check_entry_baseline.tsv +++ b/wizard-work/volt/q143-check-sweep/check_entry_baseline.tsv @@ -140,3 +140,13 @@ CHECK-q150 0 q150: 6 leg(s), all passed # this file was touched: the five NEW paths `--paths` reports at that rev belong # to other lanes and are left for their own tick. CHECK-q151 0 q151: 3 leg(s), all passed +# row added by hand (Q152, 2026-11-01): the posts-census entry point was +# NEW at 76471ad and read rc 2 there (python under a bash invocation). The +# row below is the repaired reading, taken with this directory's own run +# entry point at the commit this record lands after: rc 0, and the note is +# the wrapper's own verdict line. No other row was touched. +wizard-work/ledger/CHECK-2026-11-01-posts-census 0 VERDICT=PASS posts-census-2026-11-01 users=10 posts=100 rows=10 +# row added by hand (Q152, 2026-11-01): the Q152 gate, measured after this +# record took its edits. Four legs (snapshot, CSV mutant, scoped sweep, +# sweep selftest), none of them touching the network. +CHECK-q152 0 q152: 4 leg(s), all passed diff --git a/wizard-work/volt/q143-check-sweep/check_sweep.py b/wizard-work/volt/q143-check-sweep/check_sweep.py index e75f78d..c0ba9fd 100644 --- a/wizard-work/volt/q143-check-sweep/check_sweep.py +++ b/wizard-work/volt/q143-check-sweep/check_sweep.py @@ -35,12 +35,20 @@ EXIT CODES USAGE python3 check_sweep.py --repo . --check + python3 check_sweep.py --repo . --only CHECK-2026-11-01-posts-census --check python3 check_sweep.py --repo . --list python3 check_sweep.py --repo . --take wizard-work/volt/q143-check-sweep/check_entry_baseline.tsv python3 check_sweep.py --selftest + + `--only` scopes the grade as well as the sweep: the rows it did not run are + reported as not swept, never as missing from the tree. Before that rule, a + one-path run on a clean tree read 1 green row and 110 phantom MISSING faults, + rc 1 -- a scoped invocation that could not pass whatever the tree did. """ import argparse import concurrent.futures +import contextlib +import io import json import os import re @@ -317,6 +325,22 @@ def selftest(): cells.check("grade-clean-control", faults == [] and graded == 5, (faults, graded)) + # --- a scoped run grades the rows it swept, not the whole baseline -- + tree6 = os.path.join(tmp, "tree6") + write_fixture(tree6) + base6 = os.path.join(tmp, "base6.tsv") + take(tree6, base6, timeout=3) + buf = io.StringIO() + with contextlib.redirect_stdout(buf): + rc_scoped = main(["--repo", tree6, "--baseline", base6, + "--only", "CHECK-green", "--check"]) + scoped = buf.getvalue() + cells.check("only-scoped-is-green", rc_scoped == 0, scoped) + cells.check("only-grades-one-row", + "1 baseline row(s) graded (1), 0 fault(s)" in scoped, scoped) + cells.check("only-names-the-unswept", + "4 row(s) not swept (--only)" in scoped, scoped) + # --- four mutants, each must be caught by name ---------------------- (faults, _) = grade(sweep(tree, timeout=3), base_rows + [("CHECK-ghost", 0, "fixture")]) @@ -468,14 +492,23 @@ def main(argv=None): with open(args.json, "w", encoding="utf-8") as fh: json.dump({"rev": rev, "rows": rows}, fh, indent=1, sort_keys=True) + # --only sweeps a subset, so it must grade that same subset: a row it did + # not run was not read, and calling it "missing from the tree" is a phantom + # fault. Measured before this line existed: `--only --check` + # graded 1 row green and 110 unswept rows as MISSING, rc 1, on a clean tree. + base_in_scope = [r for r in base_rows if not args.only or args.only in r[0]] for row in rows: print(" %-11s rc=%-3d %s" % (row["state"], row["rc"], row["path"])) - faults, graded = grade(rows, base_rows) + faults, graded = grade(rows, base_in_scope) for line in faults: print("FAIL: " + line) green = sum(1 for r in rows if r["rc"] == 0) + scope_note = ("" if not args.only else + ", %d row(s) not swept (--only)" + % (len(base_rows) - len(base_in_scope))) print("check-sweep: %d entry point(s), %d green, %d baseline row(s) graded (%d), " - "%d fault(s)" % (len(rows), green, len(base_rows), graded, len(faults))) + "%d fault(s)%s" % (len(rows), green, len(base_in_scope), graded, + len(faults), scope_note)) return 0 if not faults else 1