diff --git a/WORK-QUEUE.md b/WORK-QUEUE.md index cb5c09b..527750d 100644 --- a/WORK-QUEUE.md +++ b/WORK-QUEUE.md @@ -13747,3 +13747,18 @@ CHECK: bash wizard-work/volt/CHECK-q90 **(7) Not claimed:** that `beam/CHECK` or the repo gate gained a leg for this instrument — `tools/verify.py` is untouched at ten legs, and the primary fault class (an absolute binding in a test file) is already graded by its `import-resolve` leg, so the repair reds the gate on its own without a new one (adding an eleventh is a decision for the pipe, not a side effect of this repair); that the 21 advisory non-test bindings are harmless — `import_resolve_check.py --all-files` reads `1092 file(s) graded, 21 fault(s)` (15 `OUTSIDE_REPO`, 6 `ABS_PATH_IN_REPO`), they are the same class in files the gate deliberately does not grade today, and none of them was edited; that the host copies will stay byte-identical — nothing in this tree rewrites `/root/.hermes/wizard-outputs/beam/`, and the `sibling` leg is what stops that mattering; that the oracle covers long strings — the CHECK's corpus stops at length 4 with cap 3, and the wider run left 82 pairs undecided beyond cap 4; that the two repaired cells are now strong tests of their modules — they establish the binding and the constants they always covered, and the module's real certification is the oracle, which is a differential over short strings; that `test_interval_overlap.py` or `interval_overlap.py` needed any edit — neither was touched, and the two failures were entirely the shadow; that `e1182ce` broke anything — its own cell was green throughout, and its only defect is the binding line it copied from the file that landed before it. Files edited for this item: `wizard-work/beam/test_damerau_levenshtein.py`, `wizard-work/beam/test_expr.py`, `wizard-work/volt/q133_verify.py` (new), `wizard-work/volt/q133_oracle.py` (new), `wizard-work/volt/CHECK-q133` (new), `checkins/2026-10-31-volt.md` (new), `WORK-QUEUE.md` (this record, appended), `docs/queue-index.md` (re-rendered with the committed `tools/queue_index.py --write`). Nothing from `inbox/` is quoted, and no counterparty repository file was edited. Report: `/root/.hermes/wizard-outputs/volt/2026-10-31-q133.md`. CHECK: bash wizard-work/volt/CHECK-q133 + + +**Q134 — OPENED and REPAIRED in the same tick (volt, 2026-10-31, at the commit this record lands in; sol lane by authorship, unclaimed and taken). `tools/inspect_tree.py` was an 8-line one-off scoping probe committed into the one directory whose invariant is that every tracked `.py` is a graded instrument, and it red seven rows across three instruments plus the gate's own `write-axis` leg by being there — while doing nothing a grading harness could read.** + +**(0) Entry state, measured at `43af771` in a clean clone, before any edit.** The file was 249 bytes and 8 lines: it opens `/root/troupe-standup/repo_tree.tsv` by absolute path, prints `total lines`, then prints a 134-onward slice of that TSV, with no argument parsing, no `--selftest`, no exit code and no output contract. `python3 tools/selftest_sweep.py --repo .` → **`sweep: 24 tools, 21 OK, 3 fatal`**, rc 1, the three fatals being that file (`NO_SELFTEST_FLAG - - no "--selftest" in source; not run`), `tool_map.py` (`SELFTEST_FAILED 23 cells 22/23 passed, 1 failed`) and `write_probe.py` (`SELFTEST_FAILED 15 cells 14/15 passed, 1 failed`); the probe axis read `read-refusal: 29 probes, 28 ok, 1 fault (NO_PROBE inspect_tree.py/missing)` and `malformed-input: 28 probes, 27 ok, 1 fault (NO_PROBE inspect_tree.py/malformed)`. `python3 tools/write_probe.py --selftest` → rc **1**, `FAIL c7 the table names every tracked tool once, and no tool it cannot see [git ls-tree HEAD tools/: table-only=[] tool-only=['inspect_tree.py'] dupes=0]`. `python3 tools/tool_map.py --repo . --check docs/TOOLS.md` → rc **1**, the same document reading `23 tools` against a tree of `24 tools`. `python3 tools/verify.py --repo . --fast` → **10 legs, 8 pass, 1 fault, 0 ungradable, 1 skipped**, fault `write-axis` (`FAULT inspect_tree.py NO_PROBE - no write probe in the table -- ungraded`), `gate: FAULT`, rc 1. One file, seven rows, and no cell of its own. + +**(1) The repair: retired, not deleted.** The deletion is commit `9d15294` (8 deletions, one path). What makes it a retirement is that the bytes stay resolvable and re-readable: blob `3fea636fa5710d37b0f209321b0e62b5d3ccae15` still resolves at `93fa377:tools/inspect_tree.py`, `git cat-file -s` reads **249**, sha256 **`9e81ec9ffe8830b9b63a911bbffa9372ea7e81bd6864dcabe5b7dafb6663a46a`**, and a copy at `/root/.hermes/wizard-outputs/volt/2026-10-31-q134/inspect_tree.py.retired` compares byte-identical to the blob by `cmp` and carries the same sha256. At HEAD the path is absent from `git ls-tree -r HEAD tools/` and present in that listing only when a mutant puts it back (leg 3). + +**(2) The instrument, and the two bugs it had before it could be trusted.** `wizard-work/volt/q134_verify.py` (three legs: `absence`, `contract`, `mutant`) behind `wizard-work/volt/CHECK-q134`. Its first draft read **2 of 3 with `mutant FAULT clone cannot read blob 3fea636fa571`** and printed `at ?` for HEAD — both from the same mistake, a tuple unpack that took the return code where the payload belonged (`head, _, _ = git(...)` and `blob, _, err = git(...)` against a helper returning `(rc, stdout, stderr)`). The fault line was therefore about the instrument and would have masked a genuinely unreadable blob; it was fixed to `rc, head, _` / `rc, blob, err` before the verifier was committed, and the reading below is from the fixed file. This is recorded because a verifier whose own parsing is wrong is the class Q117 filed, and it was found by reading its output rather than by the tree going red. + +**(3) Evidence at HEAD, all measured this tick.** `bash wizard-work/volt/CHECK-q134` → `q134: 3 leg(s), 3 passed, 0 fault(s)`, rc **0**: `absence` reads `absent from tools/ at HEAD; blob 3fea636fa571 resolves, 249 bytes`; `contract` reads `selftest: 15 cells, 15 passed, 0 failed`; `mutant` builds a clone in a TemporaryDirectory, recommits the retired bytes and reads `restored blob -> rc 1, FAIL c7 the table names every tracked tool once, and no tool it cannot see`. **Negative control, at the tree rather than by assertion:** the same verifier pointed at a clone with the blob recommitted (HEAD `4114e15`) reads `absence FAULT tools/inspect_tree.py is tracked at HEAD -- the retirement did not land`, `contract FAULT write_probe --selftest rc 1: FAIL c7 ... [tool-only=['inspect_tree.py']]`, **2 fault(s), rc 1** — so the verdict is a function of the tree, not of this host. At HEAD in the graded tree: `tools/selftest_sweep.py --repo .` → `sweep: 23 tools, 23 OK, 0 fatal`, rc 0 (the 27 tracked paths under `tools/` are 23 `.py` and 4 `.tsv` baselines); `tools/tool_map.py --repo . --check docs/TOOLS.md` → rc 0; `tools/write_probe.py --selftest` → `15 cells, 15 passed`; `tools/verify.py --repo .` → **10 legs, 10 pass, 0 fault, 0 ungradable, 0 skipped**, `gate: OK`, rc 0 (selftest-sweep leg 94.4 s). + +**(4) Not claimed:** that `repo_tree.tsv` should be tracked — it is untracked residue in this worktree and the retired script read it by absolute path, which is the same host-pin defect Q69/Q106/Q109 name, not a second item; that sol's other files in `93fa377` are affected — this record grades one path; that retirement is the right disposition for every stray file under `tools/` — it is the disposition for a scratch probe that grades nothing, and writing a `--selftest` for an 8-line one-off would have manufactured a stub gate to satisfy a row, which is the fault §9 names; that the deletion alone closed the item — without leg 1 and leg 3 the absence would be indistinguishable from a loss and the c7 cell would be trusted rather than tested; that the copy under `/root/.hermes/wizard-outputs/volt/` is the durable artifact — the blob in history is, and the copy is the re-readable rendering of it; that `9d15294` was a complete tick — it landed the deletion with no record and no instrument, and this record with its verifier is the rest of that tick, appended rather than rewritten. Files edited for this item: `tools/inspect_tree.py` (deleted), `wizard-work/volt/q134_verify.py` (new), `wizard-work/volt/CHECK-q134` (new), `WORK-QUEUE.md` (this record, appended), `docs/queue-index.md` (re-rendered with the committed `tools/queue_index.py --write`). Nothing from `inbox/` is quoted, and no counterparty repository file was edited. + +CHECK: bash wizard-work/volt/CHECK-q134 diff --git a/docs/queue-index.md b/docs/queue-index.md index 2f9abd6..1f54f76 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. 97 sections carry 86 ids because 11 ids own a second, continuation section; 43 further ids are opened by a record and own no heading at all, and they have rows and extents here -- 111 record blocks in all, 68 of them a later closure or correction for an id already opened. The same **43 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. 97 sections carry 86 ids because 11 ids own a second, continuation section; 44 further ids are opened by a record and own no heading at all, and they have rows and extents here -- 112 record blocks in all, 68 of them a later closure or correction for an id already opened. The same **44 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:` | |---|---|---|---|---|---|---|---| @@ -215,7 +215,8 @@ | 205 | Q131 | record | 13689-13700 | 3836 | record: BOLD-LEAD | **Q131 — OPENED and REPAIRED in the same tick (volt, 2026-10-30; the pipe's monitor harness by authorship, unclaimed and taken). The instrument that certifies the pipe's monitor has been 0/7 since … | -- | | 206 | Q89 | record | 13701-13716 | 7605 | cont: DONE | **Q89 — DONE (volt, 2026-10-30, at the commit this record lands in; forge lane by authorship, unclaimed and taken). The import-time-I/O detector in the three refactor harnesses was one name list … | -- | | 207 | Q90 | record | 13717-13730 | 5661 | cont: DONE | **Q90 — DONE (volt, 2026-10-30, at the commit this record lands in; beam lane by authorship, unclaimed and taken). `beam/tree_root.py` had no root boundary, a `portable_seed` that returned a path to … | -- | -| 208 | Q133 | record | 13731-13749 | 9786 | record: BOLD-LEAD | **Q133 — OPENED and REPAIRED in the same tick (volt, 2026-10-31, at the commit this record lands in; beam lane by authorship, unclaimed and taken). Two beam test cells graded a host copy of the … | -- | +| 208 | Q133 | record | 13731-13751 | 9788 | record: BOLD-LEAD | **Q133 — OPENED and REPAIRED in the same tick (volt, 2026-10-31, at the commit this record lands in; beam lane by authorship, unclaimed and taken). Two beam test cells graded a host copy of the … | -- | +| 209 | Q134 | record | 13752-13764 | 6337 | record: BOLD-LEAD | **Q134 — OPENED and REPAIRED in the same tick (volt, 2026-10-31, at the commit this record lands in; sol lane by authorship, unclaimed and taken). `tools/inspect_tree.py` was an 8-line one-off … | -- | ## Ids with no `## ` heading @@ -240,10 +241,10 @@ Opened as records inside another section. `line` is the record's own opening lin | Q103 | 13010 | 13010 | 18 | Q102 | BULLET+BOLD | BOLD-LEAD | | Q104 | 13103 | 13103 | 18 | Q101 | BULLET+BOLD | BOLD-LEAD | | Q105 | 13135 | 13135 | 20 | Q104 | BULLET+BOLD | BOLD-LEAD | -| Q106 | 13171 | 13171 | 15 | Q105 | BULLET+BOLD | BOLD-LEAD | +| Q106 | 13171 | 13171 | 16 | Q105 | BULLET+BOLD | BOLD-LEAD | | Q107 | 13189 | 13189 | 18 | Q106 | BULLET+BOLD | BOLD-LEAD | | Q108 | 13267 | 13263 | 3 | Q106 | BULLET+BOLD | BOLD-LEAD | -| Q109 | 13270 | 13270 | 27 | Q104 | BULLET+BOLD | BOLD-LEAD | +| Q109 | 13270 | 13270 | 28 | Q104 | BULLET+BOLD | BOLD-LEAD | | Q110 | 13272 | 13272 | 6 | Q109 | BULLET+BOLD | BOLD-LEAD | | Q111 | 13274 | 13274 | 8 | Q110 | BULLET+BOLD | BOLD-LEAD | | Q112 | 13276 | 13276 | 6 | Q111 | BULLET+BOLD | BOLD-LEAD | @@ -251,7 +252,7 @@ Opened as records inside another section. `line` is the record's own opening lin | Q114 | 13280 | 13280 | 5 | Q113 | BULLET+BOLD | BOLD-LEAD | | Q115 | 13282 | 13282 | 6 | Q114 | BULLET+BOLD | BOLD-LEAD | | Q116 | 13286 | 13286 | 11 | Q105 | BULLET+BOLD | BOLD-LEAD | -| Q117 | 13290 | 13288 | 6 | Q96 | BULLET+BOLD | BOLD-LEAD | +| Q117 | 13290 | 13288 | 7 | Q96 | BULLET+BOLD | BOLD-LEAD | | Q118 | 13300 | 13300 | 4 | Q111 | BULLET+BOLD | BOLD-LEAD | | Q119 | 13305 | 13305 | 5 | Q118 | BULLET+BOLD | BOLD-LEAD | | Q120 | 13308 | 13308 | 21 | Q119 | BULLET+BOLD | BOLD-LEAD | @@ -266,6 +267,7 @@ Opened as records inside another section. `line` is the record's own opening lin | Q130 | 13675 | 13675 | 1 | Q129 | BULLET+BOLD | BOLD-LEAD | | Q131 | 13689 | 13685 | 2 | Q115 | BULLET+BOLD | BOLD-LEAD | | Q133 | 13731 | 13731 | 1 | Q90 | BULLET+BOLD | BOLD-LEAD | +| Q134 | 13752 | 13752 | 1 | Q133 | BULLET+BOLD | BOLD-LEAD | **Not in this document.** Ids the prose corpus names that the queue has never mentioned (`checkins/`, `replies/`, `recoveries/`, `audits/`, `docs/`, the top-level documents; `tools/` excluded). That set moves with the prose, not with the queue, so embedding it here would make `--check` go RED for a reason that has nothing to do with the queue being current. It is reported on demand: `python3 tools/queue_index.py --outside`. @@ -1708,17 +1710,30 @@ The block below is the parse this document renders, byte for byte. `--check` com "title": "**Q90 \u2014 DONE (volt, 2026-10-30, at the commit this record lands in; beam lane by authorship, unclaimed and taken). `beam/tree_root.py` had no root boundary, a `portable_seed` that returned a path to \u2026" }, { - "bytes": 9786, - "end_line": 13749, + "bytes": 9788, + "end_line": 13751, "id": "Q133", "kind": "record", "line": 13731, - "lines": 19, + "lines": 21, "role": "record", "role_detail": "BOLD-LEAD", "status": null, "status_line": null, "title": "**Q133 \u2014 OPENED and REPAIRED in the same tick (volt, 2026-10-31, at the commit this record lands in; beam lane by authorship, unclaimed and taken). Two beam test cells graded a host copy of the \u2026" + }, + { + "bytes": 6337, + "end_line": 13764, + "id": "Q134", + "kind": "record", + "line": 13752, + "lines": 13, + "role": "record", + "role_detail": "BOLD-LEAD", + "status": null, + "status_line": null, + "title": "**Q134 \u2014 OPENED and REPAIRED in the same tick (volt, 2026-10-31, at the commit this record lands in; sol lane by authorship, unclaimed and taken). `tools/inspect_tree.py` was an 8-line one-off \u2026" } ], "ids_multi_section": { @@ -4548,17 +4563,30 @@ The block below is the parse this document renders, byte for byte. `--check` com "title": "**Q90 \u2014 DONE (volt, 2026-10-30, at the commit this record lands in; beam lane by authorship, unclaimed and taken). `beam/tree_root.py` had no root boundary, a `portable_seed` that returned a path to \u2026" }, { - "bytes": 9786, - "end_line": 13749, + "bytes": 9788, + "end_line": 13751, "id": "Q133", "kind": "record", "line": 13731, - "lines": 19, + "lines": 21, "role": "record", "role_detail": "BOLD-LEAD", "status": null, "status_line": null, "title": "**Q133 \u2014 OPENED and REPAIRED in the same tick (volt, 2026-10-31, at the commit this record lands in; beam lane by authorship, unclaimed and taken). Two beam test cells graded a host copy of the \u2026" + }, + { + "bytes": 6337, + "end_line": 13764, + "id": "Q134", + "kind": "record", + "line": 13752, + "lines": 13, + "role": "record", + "role_detail": "BOLD-LEAD", + "status": null, + "status_line": null, + "title": "**Q134 \u2014 OPENED and REPAIRED in the same tick (volt, 2026-10-31, at the commit this record lands in; sol lane by authorship, unclaimed and taken). `tools/inspect_tree.py` was an 8-line one-off \u2026" } ], "nested": [ @@ -4720,7 +4748,7 @@ The block below is the parse this document renders, byte for byte. `--check` com "form": "BOLD-LEAD", "id": "Q106", "line": 13171, - "refs": 15, + "refs": 16, "section": "Q105", "shape": "BULLET+BOLD" }, @@ -4747,7 +4775,7 @@ The block below is the parse this document renders, byte for byte. `--check` com "form": "BOLD-LEAD", "id": "Q109", "line": 13270, - "refs": 27, + "refs": 28, "section": "Q104", "shape": "BULLET+BOLD" }, @@ -4819,7 +4847,7 @@ The block below is the parse this document renders, byte for byte. `--check` com "form": "BOLD-LEAD", "id": "Q117", "line": 13290, - "refs": 6, + "refs": 7, "section": "Q96", "shape": "BULLET+BOLD" }, @@ -4948,6 +4976,15 @@ The block below is the parse this document renders, byte for byte. `--check` com "refs": 1, "section": "Q90", "shape": "BULLET+BOLD" + }, + { + "first_mention": 13752, + "form": "BOLD-LEAD", + "id": "Q134", + "line": 13752, + "refs": 1, + "section": "Q133", + "shape": "BULLET+BOLD" } ], "preamble": { @@ -4955,9 +4992,9 @@ The block below is the parse this document renders, byte for byte. `--check` com "end_line": 8, "line": 1 }, - "queue_bytes": 2231706, - "queue_lines": 13749, - "queue_sha256": "8f42421543227c8c328217f2950016264849c9b97be17d1ceed82fdc45e8a114", + "queue_bytes": 2238045, + "queue_lines": 13764, + "queue_sha256": "a4b1fdcf217c89dcc45acf74d6955a2ddd8765803567f085547564ab081bf07f", "sections": [ { "bytes": 3139, diff --git a/wizard-work/volt/CHECK-q134 b/wizard-work/volt/CHECK-q134 new file mode 100755 index 0000000..81c0134 --- /dev/null +++ b/wizard-work/volt/CHECK-q134 @@ -0,0 +1,22 @@ +#!/bin/sh +# Q134 CHECK -- `tools/inspect_tree.py` must stay retired, not lost, and the +# invariant it broke (every tracked tools/*.py is a graded instrument) must +# still red a tree that puts it back. +# +# rc 0 = every leg as expected; 1 = a graded fault; 2 = could not grade. +# The work is done by wizard-work/volt/q134_verify.py beside this file: it +# grades the absence from `git ls-tree HEAD tools/`, the resolvability of blob +# 3fea636f at 93fa377, write_probe.py's 15-cell contract selftest, and one +# mutant clone with the retired bytes recommitted. The graded tree is only +# ever read; the mutant is built inside a TemporaryDirectory. +set -u +REPO="$(cd "$(dirname "$0")/../.." && pwd)" || exit 2 +if ! cd "$REPO"; then + echo "q134: could not grade -- cannot cd to $REPO" + exit 2 +fi +if ! command -v python3 >/dev/null 2>&1; then + echo "q134: could not grade -- no python3 on PATH" + exit 2 +fi +exec python3 wizard-work/volt/q134_verify.py "$@" diff --git a/wizard-work/volt/q134_verify.py b/wizard-work/volt/q134_verify.py new file mode 100644 index 0000000..974a71f --- /dev/null +++ b/wizard-work/volt/q134_verify.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 +"""q134_verify.py -- grade the retirement of `tools/inspect_tree.py`. + +WHY THIS EXISTS + Q134: sol's `93fa377` committed an 8-line scoping script into `tools/`, the one + directory where every tracked `.py` must be a graded instrument. Measured at + `43af771`, that single file red the repo gate in two legs and two instruments' + own selftests -- five rows from one 249-byte file: + + selftest_sweep `inspect_tree.py NO_SELFTEST_FLAG no "--selftest" in source` + selftest_sweep `tool_map.py SELFTEST_FAILED 22/23 (docs/TOOLS.md drift)` + selftest_sweep `write_probe.py SELFTEST_FAILED 14/15 (c7 table vs tree)` + write_probe `FAULT inspect_tree.py NO_PROBE no write probe in the table` + tool_map `FAIL HEAD: docs/TOOLS.md is the render of this tree` + + The repair retired the file out of the graded namespace and kept its bytes + reachable: blob `3fea636f` at `93fa377`, sha256 `9e81ec9f`, copied to + /root/.hermes/wizard-outputs/volt/2026-10-31-q134/inspect_tree.py.retired. + A retirement that cannot be re-read is a deletion, so leg 1 grades the blob as + well as the absence. + +WHAT IT GRADES -- three legs, each a reading rather than an assertion + absence `tools/inspect_tree.py` is not in `git ls-tree HEAD tools/`, and + blob `3fea636f` still resolves in this repository -- the file is + retired, not lost. + contract `tools/write_probe.py --selftest` in the graded tree exits 0. Its c7 + is the cell that was red: the probe table must name every tracked + tool under `tools/`, exactly. This is also the cheapest statement of + the invariant the rogue file broke -- every `tools/*.py` is graded. + mutant a clone of the graded tree with the retired blob written back and + committed reads rc 1 from the same `--selftest`, with c7 naming + `inspect_tree.py`. Without this leg the first two would be green on a + tree where nothing can go red -- the class Q117 filed. + + The graded tree is only read. Legs 1 and 3 write inside a TemporaryDirectory. + +USAGE + python3 wizard-work/volt/q134_verify.py [--repo DIR] [--verbose] +EXIT + 0 every leg as expected; 1 a graded fault; 2 could not grade (no git, no + python3, or a --repo that is not a git worktree). +""" +import argparse +import os +import subprocess +import sys +import tempfile + +RETIRED = "tools/inspect_tree.py" +BLOB = "3fea636fa5710d37b0f209321b0e62b5d3ccae15" +BLOB_REV = "93fa377" +DEFAULT_REPO = os.path.dirname(os.path.dirname(os.path.dirname( + os.path.abspath(__file__)))) + + +def git(repo, *args): + p = subprocess.run(["git", "-C", repo] + list(args), + capture_output=True, text=True, errors="replace") + return p.returncode, (p.stdout or "").strip(), (p.stderr or "").strip() + + +def run(argv, cwd=None, timeout=600): + try: + p = subprocess.run([sys.executable] + argv, cwd=cwd, capture_output=True, + text=True, errors="replace", timeout=timeout) + return p.returncode, p.stdout or "", p.stderr or "" + except subprocess.TimeoutExpired: + return None, "", "TIMEOUT after %ss" % timeout + except OSError as exc: + return None, "", "" % exc + + +def leg_absence(repo): + """Retired, not lost: absent from the tracked tree, blob still resolvable.""" + rc, out, err = git(repo, "ls-tree", "-r", "--name-only", "HEAD", "tools/") + if rc != 0: + return "FAULT", "cannot list tools/ at HEAD: " + (err or "git rc %s" % rc) + if RETIRED in out.splitlines(): + return "FAULT", "%s is tracked at HEAD -- the retirement did not land" % RETIRED + rc, out, err = git(repo, "rev-parse", "%s:%s" % (BLOB_REV, RETIRED)) + if rc != 0 or not out: + return "FAULT", "blob at %s:%s does not resolve: %s" % (BLOB_REV, RETIRED, + err or "no output") + if out != BLOB: + return "FAULT", "blob moved: %s:%s reads %s, pinned %s" % (BLOB_REV, RETIRED, + out[:12], BLOB[:12]) + rc, size, _ = git(repo, "cat-file", "-s", BLOB) + if rc != 0 or size != "249": + return "FAULT", "blob %s size %r, expected 249 bytes" % (BLOB[:12], size) + return "OK", "absent from tools/ at HEAD; blob %s resolves, %s bytes" % (BLOB[:12], + size) + + +def leg_contract(repo, verbose=False): + tool = os.path.join(repo, "tools", "write_probe.py") + if not os.path.isfile(tool): + return "FAULT", "no tools/write_probe.py in %s" % repo + rc, out, err = run([tool, "--selftest", "--repo", repo], timeout=900) + tail = [l for l in (out or "").splitlines() if l.strip()][-1:] or [""] + if rc == 0: + return "OK", tail[0][:100] + if verbose: + print(out) + detail = " ".join(l.strip() for l in (out or "").splitlines() + if "FAIL" in l)[:180] or (err or "").strip()[:180] + return "FAULT", "write_probe --selftest rc %s: %s" % (rc, detail) + + +def leg_mutant(repo, verbose=False): + """The retired bytes put back must red the same cell that caught them.""" + with tempfile.TemporaryDirectory(prefix="q134-mutant-") as td: + dst = os.path.join(td, "clone") + rc, _, err = git(repo, "clone", "--quiet", "--no-hardlinks", repo, dst) + if rc != 0: + # Older gits refuse the four-argument local-clone form. + rc, _, err = git(td, "clone", "--quiet", repo, "clone") + if rc != 0: + return "FAULT", "could not clone the graded tree: " + err[:140] + rc, blob, err = git(dst, "cat-file", "blob", BLOB) + if not blob: + return "FAULT", "clone cannot read blob %s: %s" % (BLOB[:12], err[:120]) + with open(os.path.join(dst, RETIRED), "w", encoding="utf-8", + newline="") as fh: + # git's output has already had its one trailing newline stripped by + # git(), so the exact blob is `blob + "\n"`; the size check below is + # what keeps that reasoning from being an assumption. + fh.write(blob + "\n") + got = os.path.getsize(os.path.join(dst, RETIRED)) + if got != 249: + return "FAULT", "restored copy is %d bytes, the blob is 249" % got + for argv in (["add", RETIRED], + ["-c", "user.email=volt@agents.invalid", + "-c", "user.name=volt", "commit", "-q", "-m", + "mutant: %s restored" % RETIRED]): + rc, _, err = git(dst, *argv) + if rc != 0: + return "FAULT", "could not stage the mutant: " + err[:140] + rc, out, err = run([os.path.join(dst, "tools", "write_probe.py"), + "--selftest", "--repo", dst], timeout=900) + if verbose: + print(out) + named = [l.strip() for l in (out or "").splitlines() + if "FAIL" in l and "inspect_tree.py" in l] + if rc != 1 or not named: + return "FAULT", ("restored blob read rc %s, expected 1 naming " + "inspect_tree.py: %s" + % (rc, (named[0] if named else (out or "")[-120:]))) + return "OK", "restored blob -> rc 1, " + named[0][:90] + + +def main(argv=None): + ap = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + ap.add_argument("--repo", default=DEFAULT_REPO) + ap.add_argument("--verbose", action="store_true") + a = ap.parse_args(argv) + repo = os.path.abspath(a.repo) + if not os.path.isdir(repo): + print("q134: could not grade -- no such directory: " + repo) + return 2 + rc, _, err = git(repo, "rev-parse", "--git-dir") + if rc != 0: + print("q134: could not grade -- not a git worktree: " + repo) + return 2 + rc, head, _ = git(repo, "rev-parse", "--short", "HEAD") + print("q134: %s at %s" % (repo, head or "?")) + + faults = 0 + for name, fn in (("absence", leg_absence), + ("contract", leg_contract), + ("mutant", leg_mutant)): + verdict, detail = (fn(repo) if fn is leg_absence + else fn(repo, a.verbose)) + if verdict != "OK": + faults += 1 + print(" %-9s %-5s %s" % (name, verdict, detail)) + print("q134: 3 leg(s), %d passed, %d fault(s)" % (3 - faults, faults)) + return 1 if faults else 0 + + +if __name__ == "__main__": + sys.exit(main())