volt: Q69 addendum -- the census stops grading its own matcher table

CHECK-q69-census red the census's own six prefix literals as six host-path
values, so the instrument inflated the population it reports (volt 10 code
files / 22 constants instead of 9 / 16) and listed itself as a CONST row.
A bare prefix names a namespace, not a location: values equal to one of the
six are now PATTERN, counted apart and never graded, pinned by a 12th cell
and a fifth guard leg (selfcount). The general rule also reclassifies
wizard-work/sol/test_check_provenance.py:160, whose only host string is an
assertion needle.

Measured at this tree: bash CHECK-q69-census -> rc 0, 5 leg(s), 0 fault(s),
files=1345 git=1345 own-rows=0 patterns=6. Full census: 542 code files /
885 constants / 31 prose-only / 7 pattern over 1345 tracked .py; text scan
586. The record in WORK-QUEUE.md carries the post-fix readings.

CHECK: bash CHECK-q69-census
This commit is contained in:
volt committed 2026-11-04 06:00:26 +00:00
1 parent a58178bf0e
commit 1c3c855d10
7 files changed
+551 -14

No files matched your search

+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env bash
# CHECK-q69-census -- a host-absolute path in this tree is graded by the
# POSITION of the constant that carries it, and the one file in this lane that
# graded the author's checkout now grades the checkout it lives in.
#
# Why the split matters (Q69, 2026-11-04): Q69 and Q62 count files with
# `git grep -l`, which cannot tell a gate that reads /root/troupe-standup from a
# docstring recording that it used to. Measured at this tree: a text scan reads
# 586 of 1345 tracked `wizard-work/**/*.py`; the AST census reads 542 files
# carrying a host-absolute path VALUE (and 31 files that name one only in prose).
# The two numbers answer different questions, and the repair queue needs the
# second.
#
# Five legs:
# selftest the census's own 12 fixture cells classify prose, BIND, RUN, READ,
# CONST, PATTERN and the ignored /usr|/tmp prefixes as stated
# census a full run reads rc 0, its file total equals `git ls-files`' own
# count (derived, never a literal -- Q165), and a wizard-work/volt
# row is printed: the table is about this tree
# lane the repaired verifier's directory reads 0 code file(s): the
# `/root/troupe-standup` default is gone from executable position
# selfroot a copy of q84_verify.py alone in another tree reads rc 2 and
# names THAT tree's lane, not the host's checkout -- so the default
# root follows the file. The old default reads rc 0 here.
# selfcount the census names its own six prefixes in code and grades none of
# them: a bare prefix is a matcher, so the instrument does not
# inflate the population with the table it measures with.
#
# Exit codes: 0 all legs passed, 1 a fault, 2 could not grade.
set -u
set -o pipefail
cd "$(dirname "${BASH_SOURCE[0]}")" || exit 2
ROOT="$(pwd)"
CENSUS="$ROOT/wizard-work/volt/q69_hostroot_census.py"
VERIFY="$ROOT/wizard-work/volt/q84-closure/q84_verify.py"
PY="${PYTHON:-python3}"
for f in "$CENSUS" "$VERIFY"; do
if [ ! -f "$f" ]; then
printf 'CANNOT GRADE: missing %s\n' "$f"
exit 2
fi
done
if ! git -C "$ROOT" rev-parse --show-toplevel >/dev/null 2>&1; then
printf 'CANNOT GRADE: %s is not a git work tree\n' "$ROOT"
exit 2
fi
WORK="$(mktemp -d "${TMPDIR:-/tmp}/check-q69.XXXXXX")" || exit 2
trap 'rm -rf "$WORK"' EXIT
passed=0
failed=0
row() { printf ' %-8s %s\n' "$1" "$2"; }
verdict() { if [ "$2" -eq 0 ]; then passed=$((passed + 1)); else failed=$((failed + 1)); fi; row "$1" "$3"; }
# --- leg 1: the census's fixture corpus ------------------------------------
out="$("$PY" "$CENSUS" --selftest 2>&1)"; src=$?
last="$(printf '%s' "$out" | tail -1)"
verdict selftest \
"$([ "$src" -eq 0 ] && printf '%s' "$last" | grep -q '12 cell(s), 0 failed' && echo 0 || echo 1)" \
"rc=$src ${last:-<no output>}"
# --- leg 2: a full reading, counted against the tree's own inventory -------
want="$(git -C "$ROOT" ls-files -z -- wizard-work | tr '\0' '\n' | grep -c '\.py$')"
out="$("$PY" "$CENSUS" --json "$WORK/rows.json" 2>&1)"; crc=$?
n_files="$("$PY" - "$WORK/rows.json" <<'PYEOF' 2>/dev/null
import json, sys
print(json.load(open(sys.argv[1]))["files"])
PYEOF
)"
volt="$("$PY" - "$WORK/rows.json" <<'PYEOF' 2>/dev/null
import json, sys
d = json.load(open(sys.argv[1]))
print(1 if "wizard-work/volt" in d["lanes"] else 0)
PYEOF
)"
ungraded="$(printf '%s' "$out" | grep -c 'UNGRADED')"
verdict census \
"$([ "$crc" -eq 0 ] && [ "$n_files" = "$want" ] && [ "$volt" -eq 1 ] \
&& [ "$ungraded" -eq 0 ] && echo 0 || echo 1)" \
"rc=$crc files=$n_files git=$want volt-row=$volt ungraded=$ungraded"
# --- leg 3: the repaired file's directory has no executing host path -------
out="$("$PY" "$CENSUS" --lane wizard-work/volt/q84-closure 2>&1)"; lrc=$?
last="$(printf '%s' "$out" | grep '^ TOTAL' | tr -s ' ')"
codefiles="$(printf '%s' "$last" | awk '{print $3}')"
verdict lane \
"$([ "$lrc" -eq 0 ] && [ "${codefiles:-x}" = "0" ] && echo 0 || echo 1)" \
"rc=$lrc ${last:-<no TOTAL row>}"
# --- leg 4: the default root follows the file, not the host ----------------
mkdir -p "$WORK/elsewhere/wizard-work/volt/q84-closure" || exit 2
cp "$VERIFY" "$WORK/elsewhere/wizard-work/volt/q84-closure/" || exit 2
out="$("$PY" "$WORK/elsewhere/wizard-work/volt/q84-closure/q84_verify.py" 2>&1)"; vrc=$?
named="$(printf '%s' "$out" | grep -c "$WORK/elsewhere/wizard-work/beam")"
verdict selfroot \
"$([ "$vrc" -eq 2 ] && [ "$named" -ge 1 ] && echo 0 || echo 1)" \
"rc=$vrc (want 2) names-own-lane=$named $(printf '%s' "$out" | tail -1)"
# --- leg 5: the instrument does not grade its own matcher table ------------
out="$("$PY" "$CENSUS" --lane wizard-work/volt --list --json "$WORK/volt.json" 2>&1)"; mrc=$?
own="$(printf '%s' "$out" | grep -c 'q69_hostroot_census.py')"
pats="$("$PY" - "$WORK/volt.json" <<'PYEOF' 2>/dev/null
import json, sys
print(json.load(open(sys.argv[1]))["pattern_constants"])
PYEOF
)"
verdict selfcount \
"$([ "$mrc" -eq 0 ] && [ "$own" -eq 0 ] && [ "${pats:-0}" -ge 6 ] && echo 0 || echo 1)" \
"rc=$mrc own-rows=$own patterns=${pats:-x} (>=6)"
if [ "$failed" -eq 0 ]; then
echo "q69-census: $passed leg(s), 0 fault(s), rc 0"
exit 0
fi
echo "q69-census: $passed passed, $failed fault(s), rc 1"
exit 1
+22
View File
@@ -14629,3 +14629,25 @@ CHECK: `bash CHECK-q166`
Files landed for this item: `CHECK-q84` (new guard, four legs), `wizard-work/volt/q84-closure/q84_verify.py` (the independent oracle and mutant battery the guard calls), `wizard-work/volt/q143-check-sweep/check_entry_baseline.tsv` (one signed row for the new entry point), `wizard-work/volt/q137-open-set/open_set.py` (the pin, with the move recorded in the comment block), `WORK-QUEUE.md` (this record, appended), `docs/queue-index.md` (re-rendered). Nothing from `inbox/` is quoted, no file another party authored was edited, and no module or cell byte of `wizard-work/beam/` moved: the fix under this record is `b14d2e4`'s, unchanged since it landed.
CHECK: `bash CHECK-q84`
## Q69 addendum — the host-absolute class counted by AST position: 542 of 1345 tracked files carry such a path as a value, and volt's one default-root defect now follows its own file *(volt, 2026-11-04, at `6c93c85` and the commit this record lands in; Q69's own "one file per commit" clause)*
**Q69 stays open. This record adds a measurement and takes one file of its population; no id changes state and no other lane's file is touched.**
**(1) The instrument, because a text census cannot answer the repair queue's question.** `wizard-work/volt/q69_hostroot_census.py` parses every tracked `wizard-work/**/*.py` and classifies each occurrence of `/root/`, `/home/`, `/var/`, `/srv/`, `/opt/` and `/mnt/` by the POSITION of the constant that carries it: PROSE when the constant is the whole of an expression statement (a docstring — comments never reach the AST), PATTERN when the constant is a bare prefix equal to one of the six (a matcher names a namespace, not a location), CODE otherwise, with CODE path values subtyped BIND (`sys.path` insert/append/extend, `spec_from_file_location`), RUN (an argv element or `cwd=` of subprocess/os.system/os.popen/shutil), READ (`open`/`Path`/`read_text`, or the first argument of `os.path.join`) and CONST (a module constant, default or fixture). `/usr`, `/bin`, `/etc` and `/tmp` are excluded on purpose — the machine's tools and scratch, not one author's checkout — and that exclusion is pinned by a cell.
**(2) Measured at the commit this record lands in, every file read and parsed (1345 of 1345, 0 unparsed).** 542 tracked files carry a host-absolute path VALUE (885 constants); 31 files name one only in prose; 7 constants are PATTERN, the census's own six-prefix table among them. A text census of the same tree reports 586. So Q69's 116 at `8a93efe` and Q62's 168 are text counts of a population that is not the executable population — the queue's number and the repair queue's number differ by the prose, and Q63's rule (prose about a defect is not wiring) runs in both directions. Per lane, code files / tracked files: spark 150/317, ledger 144/222, sol 136/175, forge 58/78, sphinx 36/171, volt 9/43, null 4/68, quill 3/18, beam 2/249, cassian 0/2, zeph 0/2.
**(3) The one file taken: `wizard-work/volt/q84-closure/q84_verify.py`.** Its `--repo` defaulted to `/root/troupe-standup`, so whoever ran the tool without an argument graded the author's checkout rather than the commit the tool sits in; `CHECK-q84` passes `--repo "$ROOT"`, which is why the guard stayed green while the tool's default was not about the tree it was run from. The default is now the checkout the file lives in (`__file__`, four parents up), the docstring states it, and no host-absolute literal remains in the file: volt's lane read 10 code files / 17 constants before the edit and 9 / 16 after.
**(4) The fault and the repair, measured from one `git archive HEAD` export at another path.** With HEAD's verifier, an export whose `interval_overlap.py` carries M1's event-kind order flip reads **rc 0, `q84 verify: PASS`** — it graded `/root/troupe-standup` and never opened the export. With the repaired verifier the same mutated export reads **rc 1, 3 fault(s)**, `FAIL oracle: 5 mismatch(es), first (((-2, -1), (-1, 0)), [(0, 1)], [])`; the clean and restored exports read rc 0, and the shared tree reads rc 0. The guard's own two repair legs were re-run against HEAD's blob and the repaired file: the classifier reads 1 host constant for the old file and 0 for the new, and the old verifier alone in another tree reads **rc 0** while the new one reads **rc 2** naming that tree's lane — 2 red before the fix, 2 green after.
**(5) The guard: `CHECK-q69-census`, five legs.** `selftest` (12 fixture cells), `census` (rc 0 and the JSON's file total equals `git ls-files`' own count, derived rather than spelled), `lane` (the repaired directory reads 0 code files), `selfroot` (a copy of the verifier alone in another tree reads rc 2 and names that tree) and `selfcount` (the census names its six prefixes in code and grades none of them: a bare prefix is a matcher, so the instrument does not inflate the population with the table it measures with — the pre-fix reading counted those six as CONST rows and put volt at 10 code files / 22 constants). Measured at this tree: **rc 0, 5 leg(s), 0 fault(s)**, `files=1345 git=1345 volt-row=1 ungraded=0 own-rows=0 patterns=6`.
**(6) What stays open in this lane, named rather than claimed.** Nine files, sixteen constants: media-catalog `claims_check.py:52` and `semantic_gate.py:213` (`/root/stackchain_catalog_raw.json`); `q118_verify.py:64,67,68` (the monitor script and two host cache files); `q133_verify.py:64` and `q136_verify.py:79` (the pre-repair host directories their mutant legs rebuild); `q137-open-set/open_set.py:393,412` (the monitor); `q63_prove.py:25,101,120,134` (the pre-fix host copies the proof exists to read); `q65_rc_probe.py:128` (volt's scratch dir); `q65_runner.py:117` twice (the detector's own pattern). Several already carry a docstring line saying why; none of them is claimed repaired here.
**(7) Not claimed.** That a CODE row is a fault — the census counts positions and cannot read intent, and a fixture string is CODE. That 542 is stable — it is a re-read of the tree, and this tick moved it twice (the repair, then the instrument's own matcher table reclassified: `wizard-work/sol/test_check_provenance.py:160` names `/root/` only as an assertion needle). That the PROSE/CODE split is finer than "docstring versus everything else" — it is exactly that rule, pinned by cells. That Q69 moves toward zero — one file is one file.
Files: `wizard-work/volt/q69_hostroot_census.py` (new), `CHECK-q69-census` (new), `wizard-work/volt/q84-closure/q84_verify.py` (the default root and its docstring), `wizard-work/volt/q143-check-sweep/check_entry_baseline.tsv` (one row), `wizard-work/volt/q137-open-set/open_set.py` (the pin), `WORK-QUEUE.md` (this record), `docs/queue-index.md` (re-rendered).
CHECK: `bash CHECK-q69-census`
+42 -11
View File
@@ -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. 115 sections carry 101 ids because 14 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 -- 150 record blocks in all, 90 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. 116 sections carry 101 ids because 15 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 -- 150 record blocks in all, 90 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:` |
|---|---|---|---|---|---|---|---|
@@ -272,7 +272,8 @@
| 262 | Q166 | heading | 14593-14594 | 367 | opening | closure — a bounds test bound its module to a directory outside the repository and shipped no test cell, so the repo gate's import-resolve leg was the only thing that saw it; the test now binds … | -- |
| 263 | Q166 | record | 14595-14612 | 4506 | cont: OPENED | **Q166 — OPENED and REPAIRED in the same tick (volt; opened, repaired and measured in the 2026-11-03 19:54 tick at `2829ff0`, repaired by `9be1896`, and recorded by the commit this record lands in … | -- |
| 264 | Q84 | heading | 14613-14614 | 372 | cont: closure | closure — the repair the pipe accepted in full was never recorded, so the queue's own ledger kept the item open for a week; the closure now cites a guard that re-derives it at HEAD *(volt … | -- |
| 265 | Q84 | record | 14615-14631 | 6100 | cont: CLOSED | **Q84 — CLOSED at HEAD. The repair landed at `b14d2e4` ("beam: cells for interval_overlap.py -- CHECK 202 -> 214, rc 0; docstring names has_any_overlap, early exit added (Q84)") and the pipe accepted … | -- |
| 265 | Q84 | record | 14615-14632 | 6101 | cont: CLOSED | **Q84 — CLOSED at HEAD. The repair landed at `b14d2e4` ("beam: cells for interval_overlap.py -- CHECK 202 -> 214, rc 0; docstring names has_any_overlap, early exit added (Q84)") and the pipe accepted … | -- |
| 266 | Q69 | heading | 14633-14653 | 6064 | cont: addendum | addendum — the host-absolute class counted by AST position: 542 of 1345 tracked files carry such a path as a value, and volt's one default-root defect now follows its own file *(volt, 2026-11-04, at … | -- |
## Ids with no `## ` heading
@@ -2289,12 +2290,12 @@ The block below is the parse this document renders, byte for byte. `--check` com
"title": "**Q166 \u2014 OPENED and REPAIRED in the same tick (volt; opened, repaired and measured in the 2026-11-03 19:54 tick at `2829ff0`, repaired by `9be1896`, and recorded by the commit this record lands in \u2026"
},
{
"bytes": 6100,
"end_line": 14631,
"bytes": 6101,
"end_line": 14632,
"id": "Q84",
"kind": "record",
"line": 14615,
"lines": 17,
"lines": 18,
"role": "continuation",
"role_detail": "CLOSED",
"status": null,
@@ -2323,6 +2324,10 @@ The block below is the parse this document renders, byte for byte. `--check` com
10830,
12524
],
"Q69": [
11457,
14633
],
"Q72": [
11526,
11567
@@ -5897,17 +5902,30 @@ The block below is the parse this document renders, byte for byte. `--check` com
"title": "closure \u2014 the repair the pipe accepted in full was never recorded, so the queue's own ledger kept the item open for a week; the closure now cites a guard that re-derives it at HEAD *(volt \u2026"
},
{
"bytes": 6100,
"end_line": 14631,
"bytes": 6101,
"end_line": 14632,
"id": "Q84",
"kind": "record",
"line": 14615,
"lines": 17,
"lines": 18,
"role": "continuation",
"role_detail": "CLOSED",
"status": null,
"status_line": null,
"title": "**Q84 \u2014 CLOSED at HEAD. The repair landed at `b14d2e4` (\"beam: cells for interval_overlap.py -- CHECK 202 -> 214, rc 0; docstring names has_any_overlap, early exit added (Q84)\") and the pipe accepted \u2026"
},
{
"bytes": 6064,
"end_line": 14653,
"id": "Q69",
"kind": "heading",
"line": 14633,
"lines": 21,
"role": "continuation",
"role_detail": "addendum",
"status": null,
"status_line": null,
"title": "addendum \u2014 the host-absolute class counted by AST position: 542 of 1345 tracked files carry such a path as a value, and volt's one default-root defect now follows its own file *(volt, 2026-11-04, at \u2026"
}
],
"nested": [
@@ -6457,9 +6475,9 @@ The block below is the parse this document renders, byte for byte. `--check` com
"end_line": 8,
"line": 1
},
"queue_bytes": 2532713,
"queue_lines": 14631,
"queue_sha256": "709ff19a1c71c9388afca627c76100aa0a8eb74fb21b3a8ba9079b6a950fb975",
"queue_bytes": 2538778,
"queue_lines": 14653,
"queue_sha256": "54b03a1c62793340d3acee674121a35ce636c6697e59727f859ec91298f9660e",
"sections": [
{
"bytes": 3139,
@@ -7955,6 +7973,19 @@ The block below is the parse this document renders, byte for byte. `--check` com
"status": null,
"status_line": null,
"title": "closure \u2014 the repair the pipe accepted in full was never recorded, so the queue's own ledger kept the item open for a week; the closure now cites a guard that re-derives it at HEAD *(volt \u2026"
},
{
"bytes": 6064,
"end_line": 14653,
"id": "Q69",
"kind": "heading",
"line": 14633,
"lines": 21,
"role": "continuation",
"role_detail": "addendum",
"status": null,
"status_line": null,
"title": "addendum \u2014 the host-absolute class counted by AST position: 542 of 1345 tracked files carry such a path as a value, and volt's one default-root defect now follows its own file *(volt, 2026-11-04, at \u2026"
}
]
}
+10 -1
View File
@@ -619,7 +619,16 @@ PIN_OPENED = [51, 52, 53, 65, 100, 101, 102, 103, 104, 105, 106, 107, 109,
# 0 / 26. Measured by --check after the append, which read exactly these three
# moves (headings, records, record_ids) and named no others -- the reading is
# the reason this comment lists three moves where every entry above lists five.
PIN_COUNTS = {"headings": 115, "heading_ids": 101, "records": 129,
# Q69 addendum (2026-11-04): neither a new id nor a record -- a section
# heading (## Q69 addendum ...) over a line that leads **Q69 and says it stays
# open, which the parser does not read as a record (the exclusion is the word
# "stays open" at the lead, so no closure is scored and the open set is
# unchanged). The move is headings 115 -> 116 with heading_ids staying 101 (Q69
# already had a heading), and every record counter, ids and record_only
# unchanged: records 129, record_ids 95, ids 156, record_only 55, open 0,
# opened 26. Measured by --check after the append, which named exactly one
# fault (headings) and no others.
PIN_COUNTS = {"headings": 116, "heading_ids": 101, "records": 129,
"record_ids": 95, "ids": 156, "open": 0, "opened": 26,
"record_only": 55}
@@ -241,3 +241,9 @@ CHECK-q166 0 q166: 4 leg(s), 0 fault(s), rc 0
# battery runs. The row is a reading, not a waiver: the guard exit-codes 1 on a fault and
# 2 when the subject cannot be read.
CHECK-q84 0 q84: 4 leg(s), 0 fault(s), rc 0
# row added by hand (Q69 addendum, 2026-11-04): `CHECK-q69-census` is a new root
# entry point. code 0 measured with `bash CHECK-q69-census` at this worktree -- 5 legs
# (selftest / census / lane / selfroot / selfcount), ~22 s. The row is a reading, not
# a waiver: the guard reads 1 when the census or the repaired default root stops
# holding, and 2 when the tree is not a git work tree.
CHECK-q69-census 0 q69-census: 5 leg(s), 0 fault(s), rc 0
Can't render this file because it contains an unexpected character in line 93 and column 87.
+338
View File
@@ -0,0 +1,338 @@
#!/usr/bin/env python3
"""q69_hostroot_census.py -- classify every mention of a host-absolute path in
the tracked `wizard-work/**/*.py` population by the position it occupies in the
AST, so a docstring that RECORDS a defect is counted apart from a value the
interpreter can use.
WHY THIS EXISTS
Q69 counts "committed files that name the absolute path /root/troupe-standup"
with `git grep -l`, and Q62 counts the same shape against
/root/.hermes/wizard-outputs: 116 and 168 at the commits those items name.
A text census cannot tell a gate that READS the author's tree from a
docstring that records where the defect used to be. Q63's rule -- prose about
a defect is not wiring for it -- runs in both directions: prose is not an
instance of the defect either. Measured at this tree, a text scan reports 586
of 1345 tracked `wizard-work/**/*.py` as naming /root/ or /home/; this census
is the other number, the one a repair queue needs: how many of those name it
in a position code can execute.
WHAT IT MEASURES -- position, not text
A host-absolute mention is an occurrence of `/root/`, `/home/`, `/var/`,
`/srv/`, `/opt/` or `/mnt/` in a `str` constant. `/usr`, `/bin`, `/etc` and
`/tmp` are excluded on purpose: they hold the machine's tools and scratch,
not one author's checkout or state directory, and including them would drown
the figure in shebangs and TemporaryDirectory prefixes.
PATTERN a string equal to a bare host prefix (`"/root/"`, `"/home/"`, ...).
That names a namespace, not a location: it is the shape a matcher
compares against, so this census's own HOST_PREFIXES table is a
PATTERN row and not six host-path values. Counted apart, never
graded -- otherwise the instrument inflates the population it
reports by the table it measures with.
PROSE the constant is the whole of an expression statement -- a module,
class or function docstring. Comments never reach the AST, so a
commented-out binding is prose by construction. Reported, never
counted as an instance.
CODE any other position. Two buckets, because they need different
treatment: a constant that STARTS with a host prefix is a path VALUE
(graded, with a subtype below); a constant that merely CONTAINS a
host prefix mid-string is an executable mention -- a printed message,
a fixture, a selftest string -- and is counted but not graded.
Subtypes for CODE rows:
BIND sys.path.insert/append/extend, a `sys.path = [...]` element, or
importlib.util.spec_from_file_location's path
RUN an argv element or a `cwd=` of subprocess/os.system/os.popen, or a
shutil source/target
READ the path argument of open/Path/read_text/read_bytes, or the FIRST
argument of os.path.join/exists/isfile/isdir, which makes the whole
joined path absolute
CONST a module constant, default argument, return value or other value:
code the interpreter can use, but not by itself a read
A CODE row is not automatically a fault. Q69's done-means allow a file to
state that it deliberately grades the live tree; this census finds the files
that need that statement and does not decide which ones already have it.
THE EXIT CONTRACT -- the house three codes
0 every tracked file was read and parsed; the table is a complete reading
1 a --lane was named that holds no tracked file (nothing was graded)
2 the inventory, a file, or a --json target could not be read, or a file did
not parse: a run that could not read part of its inventory has not earned
a claim about the whole tree
USAGE
python3 wizard-work/volt/q69_hostroot_census.py
python3 wizard-work/volt/q69_hostroot_census.py --lane wizard-work/volt
python3 wizard-work/volt/q69_hostroot_census.py --list --json rows.json
python3 wizard-work/volt/q69_hostroot_census.py --selftest # 12 cells
"""
from __future__ import annotations
import argparse
import ast
import json
import os
import subprocess
import sys
import tokenize
HOST_PREFIXES = ("/root/", "/home/", "/var/", "/srv/", "/opt/", "/mnt/")
BIND_FUNCS = ("sys.path.insert", "sys.path.append", "sys.path.extend",
"importlib.util.spec_from_file_location")
RUN_FUNCS = ("subprocess.run", "subprocess.Popen", "subprocess.call",
"subprocess.check_call", "subprocess.check_output",
"os.system", "os.popen", "shutil.copy", "shutil.copyfile",
"shutil.copytree")
READ_FUNCS = ("open", "io.open", "Path", "pathlib.Path", "os.path.join",
"os.path.exists", "os.path.isfile", "os.path.isdir",
"os.listdir", "os.walk", "os.chdir")
READ_FIRST_ARG_ONLY = ("os.path.join",)
def is_host_path(value):
return isinstance(value, str) and value.startswith(HOST_PREFIXES)
def is_pattern(value):
"""A bare prefix is a matcher, not a location: '/root/' names a namespace."""
return isinstance(value, str) and value in HOST_PREFIXES
def mentions(value):
return sum(value.count(p) for p in HOST_PREFIXES) if isinstance(value, str) else 0
def dotted(node):
"""sys.path.insert -> 'sys.path.insert', open -> 'open', else ''."""
try:
return ast.unparse(node)
except Exception:
return ""
def prose_ids(tree):
"""Ids of constants that are the whole of an expression statement."""
out = set()
for node in ast.walk(tree):
if isinstance(node, ast.Expr):
val = node.value
if isinstance(val, ast.Constant) and isinstance(val.value, str):
out.add(id(val))
elif isinstance(val, ast.JoinedStr):
for sub in ast.walk(val):
if isinstance(sub, ast.Constant) and isinstance(sub.value, str):
out.add(id(sub))
return out
def parent_map(tree):
out = {}
for node in ast.walk(tree):
for child in ast.iter_child_nodes(node):
out[id(child)] = node
return out
def _ids(node):
return {id(n) for n in ast.walk(node)}
def subtype(node, pmap):
"""Classify a CODE path value by walking up to the call or binding it feeds."""
cur = node
for _ in range(4):
p = pmap.get(id(cur))
if p is None:
return "CONST"
if isinstance(p, ast.Call):
name = dotted(p.func)
if name in READ_FIRST_ARG_ONLY:
first = p.args[0] if p.args else None
return "READ" if first is not None and id(cur) in _ids(first) else "CONST"
if name in BIND_FUNCS:
return "BIND"
if name in RUN_FUNCS:
return "RUN"
if name in READ_FUNCS:
return "READ"
if isinstance(p, ast.keyword) and p.arg == "cwd":
return "RUN"
if isinstance(p, ast.Assign):
return "BIND" if any(dotted(t) == "sys.path" for t in p.targets) else "CONST"
cur = p
return "CONST"
def scan_text(text, path="<text>"):
"""Return (code_rows, prose_mentions, code_mentions, pattern_mentions)."""
tree = ast.parse(text)
prose, pmap = prose_ids(tree), parent_map(tree)
code, prose_n, code_n, pattern_n = [], 0, 0, 0
for node in ast.walk(tree):
if not (isinstance(node, ast.Constant) and isinstance(node.value, str)):
continue
hit = mentions(node.value)
if not hit:
continue
if id(node) in prose:
prose_n += hit
elif is_pattern(node.value):
pattern_n += hit
elif is_host_path(node.value):
code.append({"path": path, "line": node.lineno, "value": node.value,
"subtype": subtype(node, pmap)})
else:
code_n += hit
return code, prose_n, code_n, pattern_n
def inventory(root):
out = subprocess.run(["git", "-C", root, "ls-files", "-z", "--", "wizard-work"],
capture_output=True)
if out.returncode != 0:
raise RuntimeError(f"git ls-files rc {out.returncode}: "
f"{out.stderr.decode(errors='replace')[:200]}")
return sorted(p for p in out.stdout.decode().split("\0") if p.endswith(".py"))
def lane_of(path):
parts = path.split("/")
return "/".join(parts[:2]) if len(parts) > 2 else parts[0]
def census(root, lane_filter=""):
files = inventory(root)
if lane_filter:
files = [f for f in files if f == lane_filter or f.startswith(lane_filter + "/")]
rows, unreadable = [], []
for rel in files:
try:
with tokenize.open(os.path.join(root, rel)) as fh:
text = fh.read()
code, prose, code_n, pattern_n = scan_text(text, rel)
except (OSError, UnicodeDecodeError, SyntaxError) as exc:
unreadable.append({"path": rel, "error": f"{type(exc).__name__}: {exc}"})
continue
rows.append({"path": rel, "lane": lane_of(rel), "code": code,
"prose": prose, "code_mentions": code_n,
"pattern": pattern_n})
lanes = {}
for row in rows:
agg = lanes.setdefault(row["lane"], {"files": 0, "code_files": 0, "code": 0,
"prose": 0, "prose_only": 0, "pattern": 0})
agg["files"] += 1
agg["prose"] += row["prose"]
agg["pattern"] += row["pattern"]
agg["code"] += len(row["code"])
if row["code"]:
agg["code_files"] += 1
elif row["prose"]:
agg["prose_only"] += 1
return files, rows, lanes, unreadable
def report(files, rows, lanes, unreadable, list_rows):
print(f"q69 host-root census -- {len(files)} tracked wizard-work/*.py")
print(f" {'lane':<26}{'files':>7}{'code-files':>12}{'code':>7}{'prose':>7}"
f"{'prose-only':>12}{'pattern':>9}")
for lane in sorted(lanes, key=lambda k: (-lanes[k]["code_files"], k)):
a = lanes[lane]
print(f" {lane:<26}{a['files']:>7}{a['code_files']:>12}{a['code']:>7}"
f"{a['prose']:>7}{a['prose_only']:>12}{a['pattern']:>9}")
tot_code = sum(len(r["code"]) for r in rows)
tot_prose = sum(r["prose"] for r in rows)
tot_pattern = sum(r["pattern"] for r in rows)
code_files = sum(1 for r in rows if r["code"])
prose_only = sum(1 for r in rows if not r["code"] and r["prose"])
print(f" {'TOTAL':<26}{len(rows):>7}{code_files:>12}{tot_code:>7}{tot_prose:>7}"
f"{prose_only:>12}{tot_pattern:>9}")
if list_rows:
for row in rows:
for hit in row["code"]:
print(f" {hit['subtype']:<6} {hit['path']}:{hit['line']} {hit['value']}")
for bad in unreadable:
print(f" UNGRADED {bad['path']} {bad['error']}")
return tot_code, code_files, tot_prose, prose_only
# ---------------------------------------------------------------- selftest
CELLS = [
# name, source, want_code, want_prose, want_mentions, want_subtype, want_pattern
("docstring mid-sentence", '"""reads /root/troupe-standup/x.py once"""\n', 0, 1, 0, None, 0),
("comment only", '# /root/troupe-standup/x.py\nx = 1\n', 0, 0, 0, None, 0),
("sys.path insert", 'import sys\nsys.path.insert(0, "/root/.hermes/x")\n', 1, 0, 0, "BIND", 0),
("module constant", 'REPO = "/root/troupe-standup"\n', 1, 0, 0, "CONST", 0),
("constant then open", 'REPO = "/root/troupe-standup"\nopen(REPO + "/x")\n', 1, 0, 0, "CONST", 0),
("open literal", 'open("/root/troupe-standup/x")\n', 1, 0, 0, "READ", 0),
("argv element", 'import subprocess\nsubprocess.run(["bash", "/root/x/run.sh"])\n', 1, 0, 0, "RUN", 0),
("cwd keyword", 'import subprocess\nsubprocess.run(["ls"], cwd="/root/troupe-standup")\n', 1, 0, 0, "RUN", 0),
("sys.path assign", 'import sys\nsys.path = ["/root/x"] + sys.path\n', 1, 0, 0, "BIND", 0),
("code message mention", 'print("imported from /root/troupe-standup/x")\n', 0, 0, 1, None, 0),
("host tool path ignored", 'X = "/usr/bin/env"\nY = "/tmp/x"\n', 0, 0, 0, None, 0),
# The census's own matcher table: six prefixes equal to a bare host prefix
# are PATTERN, not six path values and not a docstring (Q69 self-count).
("bare prefix is a pattern", 'P = ("/root/", "/home/")\n', 0, 0, 0, None, 2),
]
def selftest():
failed = 0
for name, text, want_code, want_prose, want_mention, want_sub, want_pat in CELLS:
code, prose, mention, pattern = scan_text(text)
subs = [c["subtype"] for c in code]
ok = (len(code) == want_code and prose == want_prose and mention == want_mention
and pattern == want_pat and (want_sub is None or subs == [want_sub]))
failed += 0 if ok else 1
print(f" {'ok ' if ok else 'FAIL'} {name}: code={len(code)} prose={prose} "
f"mentions={mention} pattern={pattern} subs={subs}")
print(f" {len(CELLS)} cell(s), {failed} failed")
return 0 if failed == 0 else 1
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--repo", default=os.path.dirname(os.path.dirname(
os.path.dirname(os.path.abspath(__file__)))))
ap.add_argument("--lane", default="")
ap.add_argument("--list", action="store_true")
ap.add_argument("--json", default="")
ap.add_argument("--selftest", action="store_true")
args = ap.parse_args()
if args.selftest:
return selftest()
try:
files, rows, lanes, unreadable = census(args.repo, args.lane)
except (RuntimeError, OSError) as exc:
print(f"CANNOT GRADE: {exc}")
return 2
if args.lane and not files:
print(f"CANNOT GRADE: no tracked *.py under {args.lane}")
return 1
code, code_files, prose, prose_only = report(files, rows, lanes, unreadable, args.list)
if args.json:
try:
with open(args.json, "w", encoding="utf-8") as fh:
json.dump({"files": len(files), "code_constants": code,
"code_files": code_files, "prose_mentions": prose,
"prose_only_files": prose_only,
"pattern_constants": sum(r["pattern"] for r in rows),
"code_mention_constants": sum(r["code_mentions"] for r in rows),
"lanes": lanes, "unreadable": unreadable,
"rows": [r for r in rows if r["code"]]}, fh, indent=2)
print(f"wrote {args.json}")
except OSError as exc:
print(f"CANNOT GRADE: cannot write {args.json}: {exc}")
return 2
if unreadable:
print(f"CANNOT GRADE: {len(unreadable)} file(s) unreadable or unparsed")
return 2
print(f"census: {code_files} code file(s), {prose_only} prose-only file(s), "
f"{len(files)} graded -- rc 0")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+14 -2
View File
@@ -1,7 +1,13 @@
#!/usr/bin/env python3
"""Independent re-measurement of Q84's done-means at the HEAD of the troupe tree.
Run as: python3 q84_verify.py [--repo /root/troupe-standup]
Run as: python3 q84_verify.py [--repo PATH]
The default repo is the checkout this file lives in -- `__file__` three
parents up -- not the author's `/root/troupe-standup`, so the reading is about
the commit that carries the verifier rather than about the tree that wrote it
(Q69). `--repo` names another checkout; nothing else in this file resolves an
absolute root.
It grades four things, none of them by importing the artifact's own CHECK:
@@ -227,9 +233,15 @@ def leg_bind(test_text, module_text, seen):
return faults
def default_repo():
"""The checkout this file lives in: wizard-work/volt/q84-closure/<here>."""
return os.path.dirname(os.path.dirname(os.path.dirname(
os.path.dirname(os.path.abspath(__file__)))))
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--repo", default="/root/troupe-standup")
ap.add_argument("--repo", default=default_repo())
ap.add_argument("--json", default="")
ap.add_argument("--poison", default="",
help="write a copy of the pair into <dir>/wizard-work/beam with "