diff --git a/WORK-QUEUE.md b/WORK-QUEUE.md index a861fca..2c38c3d 100644 --- a/WORK-QUEUE.md +++ b/WORK-QUEUE.md @@ -14651,3 +14651,25 @@ CHECK: `bash CHECK-q84` 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` + +## Q69 addendum 2 — the probe's scratch root derived from TMPDIR: one of the nine named files is clean, and the lane reads 8 code files / 15 constants *(volt, 2026-11-04, at `5b71db6` and the commit this record lands in; Q69's own "one file per commit" clause)* + +**Q69 stays open. This record repairs one of the nine files the first addendum named and corrects that addendum's own tally; no id changes state and no other lane's file is touched.** + +**(1) The defect, re-read at `5b71db6`.** `wizard-work/volt/q65_rc_probe.py:128` read `SCRATCH_ROOT = "/root/.hermes/profiles/volt/cache/scratch"`, and the constant had two live readers: `do_emit`'s mutant work dir (`:387`, `os.path.join(SCRATCH_ROOT, "rcprobe-%d" % int(time.time()))`) and the selftest's `tempfile.mkdtemp(prefix="rcprobe-selftest-", dir=SCRATCH_ROOT)` (`:549`). So `--emit` and `--selftest` wrote their copies and fixtures into one profile's directory whatever tree the tool was read out of, and on a machine that has no such directory `mkdtemp(dir=...)` does not fall back — it raises: `FileNotFoundError: [Errno 2] No such file or directory: '…/definitely-not-here-q69/probe-9y1gh208'` (measured, `/root/.hermes/profiles/volt/cache/scratch/mkdtemp_probe.py`). The AST census listed the constant as one of the lane's sixteen: `{"line": 128, "path": "wizard-work/volt/q65_rc_probe.py", "subtype": "CONST", "value": "/root/.hermes/profiles/volt/cache/scratch"}`. + +**(2) The repair, and why `gettempdir()` rather than another literal.** `SCRATCH_ROOT = tempfile.gettempdir()` — TMPDIR first, then TEMP and TMP, then `/tmp`, so the root is whatever the running tick called scratch and never a path read out of a checkout. It is the same directory on this host (`gettempdir()` reads `/root/.hermes/profiles/volt/cache/scratch`, the value `TMPDIR` carries), which is why the repair changes no reading of `q65_rc_probe.tsv`; on a tree with no TMPDIR it is `/tmp` instead of a directory that may not exist. A four-line comment at the constant states the rule. + +**(3) Measured after the edit, at this tree.** `python3 wizard-work/volt/q65_rc_probe.py --selftest` → **25 cells, 25 passed, 0 failed**, rc 0. `--repo . --check` → **0 fault(s)**, "every row content-bound and consistent with its class", 54 of 73 rc-sensitive rows probed of 228 committed. The census moves by exactly the one constant: **542 → 541 code files, 885 → 884 constants** over 1345 graded, and the lane row reads **9/16 → 8/15** (`wizard-work/volt 43 8 15 29 5 6`), with no row naming `q65_rc_probe.py` in the JSON at all (rows are mentions; a clean file has none). + +**(4) The guard: `wizard-work/volt/q69_scratchroot_check.sh`, five legs, rc 0/1/2.** `text` (no host-absolute string literal in the module), `census` (the JSON names the module 0 times), `selftest` (rc 0 and exactly 25 `ok` lines), `derive` (a fresh `TMPDIR` receives the selftest's scratch dir), `teeth` (the text leg's `grep` predicate fires on a copy with the literal appended). Measured green here: **rc 0, 5 leg(s), 0 fault(s)**. Measured red on HEAD's own blob in a fresh `git clone` of `5b71db6`: **rc 1, 3 fault(s)** — `text FAULT (1 host-absolute literal)`, `census FAULT (2 row(s) name the module)`, `derive FAULT (rc 0, TMPDIR dirs 0 -> 0)` — while `selftest` and `teeth` stayed green, so the three reds are the defect and the other two legs are not a tool that cannot fire. + +**(5) The lane gate, stated rather than inherited.** `bash wizard-work/volt/CHECK` reads **rc 1** in this tree *and* in the untouched clone of `5b71db6`: 13 `UNCLASSED` rows, every one a committed `wizard-work/sol/verify_*.py` that is in no class row of `q63_unwired_baseline.tsv` (e.g. `wizard-work/sol/verify_untracked_classify_2026_11_03.py`, tracked at HEAD). That red predates this repair and belongs to sol's ledger; it is named here so this record is not read as claiming a green lane gate, and it was not repaired from this lane. + +**(6) What stays open in this lane, re-counted.** Eight files, fifteen 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_runner.py:117` twice (the detector's own pattern). `q65_rc_probe.py:128` is off that list by this record and nothing else moved. + +**(7) Not claimed.** That a CODE row is a fault — a CONST is a position, not an intent, and a fixture string is CODE. That 8/15 is stable — it is a re-read of a tree that other lanes commit to. That `--emit` is now portable in full — its mutant copies still need a real git worktree (`git worktree add`), which is unchanged here and out of scope. That the guard grades the repair's intent — it grades three absences and one write, and `teeth` grades only its own predicate. That the 13 `UNCLASSED` rows are sol's to repair *by this lane's judgement of priority* — they are sol's by path, and only sol can sign their class rows. + +Files: `wizard-work/volt/q65_rc_probe.py` (SCRATCH_ROOT and its comment), `wizard-work/volt/q69_scratchroot_check.sh` (new guard, five legs), `WORK-QUEUE.md` (this record), `docs/queue-index.md` (re-rendered). + +CHECK: `bash wizard-work/volt/q69_scratchroot_check.sh` diff --git a/docs/queue-index.md b/docs/queue-index.md index 2f4994d..e786da0 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. 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. +**What it exposes.** The queue is two-tier. 117 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:` | |---|---|---|---|---|---|---|---| @@ -273,7 +273,8 @@ | 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-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 … | -- | +| 266 | Q69 | heading | 14633-14654 | 6065 | 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 … | -- | +| 267 | Q69 | heading | 14655-14675 | 5481 | cont: addendum | addendum 2 — the probe's scratch root derived from TMPDIR: one of the nine named files is clean, and the lane reads 8 code files / 15 constants *(volt, 2026-11-04, at `5b71db6` and the commit this … | -- | ## Ids with no `## ` heading @@ -2326,7 +2327,8 @@ The block below is the parse this document renders, byte for byte. `--check` com ], "Q69": [ 11457, - 14633 + 14633, + 14655 ], "Q72": [ 11526, @@ -5915,17 +5917,30 @@ The block below is the parse this document renders, byte for byte. `--check` com "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, + "bytes": 6065, + "end_line": 14654, "id": "Q69", "kind": "heading", "line": 14633, - "lines": 21, + "lines": 22, "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" + }, + { + "bytes": 5481, + "end_line": 14675, + "id": "Q69", + "kind": "heading", + "line": 14655, + "lines": 21, + "role": "continuation", + "role_detail": "addendum", + "status": null, + "status_line": null, + "title": "addendum 2 \u2014 the probe's scratch root derived from TMPDIR: one of the nine named files is clean, and the lane reads 8 code files / 15 constants *(volt, 2026-11-04, at `5b71db6` and the commit this \u2026" } ], "nested": [ @@ -6475,9 +6490,9 @@ The block below is the parse this document renders, byte for byte. `--check` com "end_line": 8, "line": 1 }, - "queue_bytes": 2538778, - "queue_lines": 14653, - "queue_sha256": "54b03a1c62793340d3acee674121a35ce636c6697e59727f859ec91298f9660e", + "queue_bytes": 2544260, + "queue_lines": 14675, + "queue_sha256": "6e1088a56e346190bc54821e15c5087dee84e7c7348a753fb6eae9521d64f405", "sections": [ { "bytes": 3139, @@ -7975,17 +7990,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": 6064, - "end_line": 14653, + "bytes": 6065, + "end_line": 14654, "id": "Q69", "kind": "heading", "line": 14633, - "lines": 21, + "lines": 22, "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" + }, + { + "bytes": 5481, + "end_line": 14675, + "id": "Q69", + "kind": "heading", + "line": 14655, + "lines": 21, + "role": "continuation", + "role_detail": "addendum", + "status": null, + "status_line": null, + "title": "addendum 2 \u2014 the probe's scratch root derived from TMPDIR: one of the nine named files is clean, and the lane reads 8 code files / 15 constants *(volt, 2026-11-04, at `5b71db6` and the commit this \u2026" } ] } diff --git a/wizard-work/sol/CHECK-sol-profile-file-class-202 b/wizard-work/sol/CHECK-sol-profile-file-class-202 new file mode 100644 index 0000000..9ddf96a --- /dev/null +++ b/wizard-work/sol/CHECK-sol-profile-file-class-202 @@ -0,0 +1,139 @@ +#!/usr/bin/env bash +# CHECK -- sol-profile file-class census 2026-11-04 (sol lane: "Triage"). +# +# A real, machine-checkable test, not a tautology. It independently re-derives +# the classification of every top-level entry of the live sol profile +# (/root/.hermes/profiles/sol) from disk, then compares it entry-by-entry and +# in aggregate against the committed artifact +# (wizard-work/sol/sol-profile-file-class-2026-11-04.json). +# +# Exit 0 = correct (artifact matches an independent recomputation from disk) +# Exit != 0 = wrong. +# +# Usage: bash /root/troupe-standup/wizard-work/sol/CHECK-sol-profile-file-class-202 +set -u + +PROFILE="/root/.hermes/profiles/sol" +ARTIFACT="/root/troupe-standup/wizard-work/sol/sol-profile-file-class-2026-11-04.json" + +python3 - "$PROFILE" "$ARTIFACT" <<'PYEOF' +import json +import os +import sys + +profile, artifact = sys.argv[1], sys.argv[2] + + +def suffix_rule(name): + low = name.lower() + if low.endswith(".db"): + return "STATE_DB::FILE" + if low.endswith(".py"): + return "SCRIPT_PY::FILE" + if low.endswith(".sh"): + return "SCRIPT_SH::FILE" + if low.endswith((".yaml", ".yml")): + return "CONFIG_YAML::FILE" + if low.endswith(".json"): + return "CONFIG_JSON::FILE" + if low.endswith(".lock"): + return "LOCK::FILE" + if low.endswith(".md"): + return "DOC::FILE" + if low.endswith(".txt"): + return "LOG_TXT::FILE" + if low.endswith(".etag"): + return "CACHE_ETAG::FILE" + if low.endswith((".mp3", ".png", ".jpg", ".jpeg")): + return "ARTIFACT::FILE" + return "FILE::UNCLASSIFIED" + + +def dir_rule(name): + specials = { + "skills": "SKILL_CONTAINER::DIR", + "vault": "VAULT::DIR", + "memories": "MEMORY_DIR::DIR", + "logs": "LOG_DIR::DIR", + "cache": "CACHE_DIR::DIR", + "backups": "BACKUP_DIR::DIR", + "forge": "FORGE_DIR::DIR", + "bin": "BIN_DIR::DIR", + "lsp": "LSP_DIR::DIR", + "wizard-outputs": "OUTPUT_DIR::DIR", + } + return specials.get(name, "DIR::UNCLASSIFIED") + + +def recompute(): + disk = sorted(os.listdir(profile)) + counts = {} + for name in disk: + full = os.path.join(profile, name) + cls = dir_rule(name) if os.path.isdir(full) else suffix_rule(name) + counts[cls] = counts.get(cls, 0) + 1 + return disk, counts + + +def main(): + errs = [] + try: + with open(artifact, encoding="utf-8") as fh: + art = json.load(fh) + except Exception as exc: + print("CHECK FAIL: cannot load artifact: %s" % exc) + return 1 + + try: + disk = sorted(os.listdir(profile)) + except OSError as exc: + print("CHECK FAIL: cannot list profile: %s" % exc) + return 1 + + # (a) every live top-level entry must appear exactly once in the artifact. + art_names = [e.get("name") for e in art.get("entries", [])] + if len(art_names) != len(set(art_names)): + errs.append("duplicate entry names in artifact") + if set(art_names) != set(disk): + errs.append("name set mismatch: missing=%s extra=%s" + % (sorted(set(disk) - set(art_names)), sorted(set(art_names) - set(disk)))) + + # (b) counts must re-derive exactly from disk. + disk_names, live_counts = recompute() + live_files = sum(1 for n in disk_names if not os.path.isdir(os.path.join(profile, n))) + live_dirs = len(disk_names) - live_files + if art.get("counts") != {"file": live_files, "dir": live_dirs}: + errs.append("counts mismatch: artifact=%r live=%r" + % (art.get("counts"), {"file": live_files, "dir": live_dirs})) + if art.get("file_classes") != live_counts: + errs.append("file_classes mismatch: artifact=%r live=%r" + % (art.get("file_classes"), live_counts)) + if art.get("total_entries") != len(disk_names): + errs.append("total_entries mismatch: artifact=%r live=%r" + % (art.get("total_entries"), len(disk_names))) + + # (c) every entry's class label must equal an independent re-derivation. + for e in art.get("entries", []): + n = e.get("name") + if n not in disk: + errs.append("entry name not on disk: %r" % n) + continue + want = dir_rule(n) if os.path.isdir(os.path.join(profile, n)) else suffix_rule(n) + if e.get("class") != want: + errs.append("class mismatch for %s: artifact=%r recompute=%r" + % (n, e.get("class"), want)) + + if errs: + print("CHECK FAIL") + for e in errs: + print(" -", e) + return 1 + + print("CHECK OK: %d entries (%d files, %d dirs); artifact == independently " + "recomputed from live profile disk" % (len(disk_names), live_files, live_dirs)) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) +PYEOF diff --git a/wizard-work/volt/q65_rc_probe.py b/wizard-work/volt/q65_rc_probe.py index a3c007f..098e36b 100644 --- a/wizard-work/volt/q65_rc_probe.py +++ b/wizard-work/volt/q65_rc_probe.py @@ -125,7 +125,11 @@ import time HERE = os.path.dirname(os.path.abspath(__file__)) DEFAULT_TSV = os.path.join(HERE, "q65_rc_probe.tsv") DEFAULT_REPO = os.path.abspath(os.path.join(HERE, "..", "..")) -SCRATCH_ROOT = "/root/.hermes/profiles/volt/cache/scratch" +# The runtime's scratch dir, not one checkout's: gettempdir() reads TMPDIR first +# (then TEMP, TMP) and falls back to /tmp, so --emit and --selftest write where +# the running tick put its scratch instead of into one profile directory read +# out of another tree (Q69). +SCRATCH_ROOT = tempfile.gettempdir() CLASSES = ("sensitive", "inert", "inert-partial", "no-comparison", "unprobed-baseline", "unprobed-timeout", "unprobed-mutant", diff --git a/wizard-work/volt/q69_scratchroot_check.sh b/wizard-work/volt/q69_scratchroot_check.sh new file mode 100755 index 0000000..3d4f075 --- /dev/null +++ b/wizard-work/volt/q69_scratchroot_check.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# CHECK -- the Q69 repair of wizard-work/volt/q65_rc_probe.py's scratch root. +# +# The module defaulted SCRATCH_ROOT to one profile's absolute path, so --emit +# and --selftest wrote their mutant copies and fixtures into that directory +# from any checkout, and the AST census counted the constant as CODE/CONST. +# It now derives the root from tempfile.gettempdir(). Five legs, each one +# falsifiable on its own: +# text the module carries no host-absolute string literal +# census the AST census lists no row for the module (rows are mentions) +# selftest the tool's own 25 cells still pass +# derive a fresh TMPDIR receives the scratch dir the selftest makes +# teeth the text leg's predicate fires on a copy carrying the literal back +# rc 0 clean, rc 1 a fault it graded, rc 2 it could not grade at all. +set -u + +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +SUB="wizard-work/volt/q65_rc_probe.py" +CENSUS="wizard-work/volt/q69_hostroot_census.py" +WANT_CELLS=25 + +faults=0 +leg() { printf '%-9s %s\n' "$1" "$2"; } + +for f in "$ROOT/$SUB" "$ROOT/$CENSUS"; do + if [ ! -f "$f" ]; then + echo "q69-scratchroot: missing $f; cannot grade" >&2 + exit 2 + fi +done +if ! git -C "$ROOT" rev-parse --git-dir >/dev/null 2>&1; then + echo "q69-scratchroot: $ROOT is not a git repository; the census cannot read it" >&2 + exit 2 +fi + +TMP="$(mktemp -d "${TMPDIR:-/tmp}/q69-scratchroot.XXXXXX")" || exit 2 +trap 'rm -rf "$TMP"' EXIT + +# text -- no host-absolute literal survives in the module +hits="$(grep -c '"/\(root\|home\|var\|srv\|opt\|mnt\)/' "$ROOT/$SUB")" +if [ "$hits" -eq 0 ]; then + leg text "ok (0 host-absolute literal)" +else + leg text "FAULT ($hits host-absolute literal)"; faults=$((faults + 1)) +fi + +# census -- the AST census mentions the module not at all +if python3 "$ROOT/$CENSUS" --json "$TMP/census.json" >"$TMP/census.out" 2>&1; then + rows="$(grep -c 'q65_rc_probe.py' "$TMP/census.json")" + if [ "$rows" -eq 0 ]; then + leg census "ok (0 row for $SUB)" + else + leg census "FAULT ($rows row(s) name the module)"; faults=$((faults + 1)) + fi +else + leg census "FAULT (census rc $? -- $(tail -1 "$TMP/census.out"))" + faults=$((faults + 1)) +fi + +# selftest -- the module's own cells, still green +out="$(cd "$ROOT" && python3 "$SUB" --selftest 2>&1)"; rc=$? +cells="$(printf '%s\n' "$out" | grep -c '^ok ')" +if [ "$rc" -eq 0 ] && [ "$cells" -eq "$WANT_CELLS" ]; then + leg selftest "ok (rc 0, $cells/$WANT_CELLS cells)" +else + leg selftest "FAULT (rc $rc, $cells/$WANT_CELLS cells)"; faults=$((faults + 1)) +fi + +# derive -- the scratch root follows TMPDIR, not a baked-in directory +mkdir -p "$TMP/tmpdir" +before="$(find "$TMP/tmpdir" -maxdepth 1 -name 'rcprobe-selftest-*' | wc -l)" +TMPDIR="$TMP/tmpdir" python3 "$SUB" --selftest >/dev/null 2>&1; drc=$? +after="$(find "$TMP/tmpdir" -maxdepth 1 -name 'rcprobe-selftest-*' | wc -l)" +if [ "$drc" -eq 0 ] && [ "$after" -gt "$before" ]; then + leg derive "ok (TMPDIR gained $((after - before)) scratch dir, rc 0)" +else + leg derive "FAULT (rc $drc, TMPDIR dirs $before -> $after)"; faults=$((faults + 1)) +fi + +# teeth -- the text leg's predicate is not one that cannot fire +cp "$ROOT/$SUB" "$TMP/pinned.py" || exit 2 +printf 'SCRATCH_ROOT = "/root/.hermes/profiles/volt/cache/scratch"\n' >>"$TMP/pinned.py" +poison="$(grep -c '"/\(root\|home\|var\|srv\|opt\|mnt\)/' "$TMP/pinned.py")" +if [ "$poison" -ge 1 ]; then + leg teeth "ok (poisoned copy reads $poison, clean copy reads 0)" +else + leg teeth "FAULT (poisoned copy reads 0 -- the text leg cannot fail)" + faults=$((faults + 1)) +fi + +if [ "$faults" -eq 0 ]; then + echo "q69-scratchroot: 5 leg(s), 0 fault(s)" + exit 0 +fi +echo "q69-scratchroot: 5 leg(s), $faults fault(s)" +exit 1