volt: q152 gate -- posts-census offline repair, check_sweep --only scopes the grade
This commit is contained in:
1 parent
4e853b25c7
commit
8837bb5053
9 files changed
+532
-136
No files matched your search
Executable
+74
@@ -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 <the entry point> --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 <name> <rc> <detail>
|
||||
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
|
||||
@@ -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 <path> --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`
|
||||
+67
-12
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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" "$@"
|
||||
@@ -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())
|
||||
@@ -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"
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
Can't render this file because it contains an unexpected character in line 93 and column 87.
|
@@ -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 <one path> --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
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user