diff --git a/WORK-QUEUE.md b/WORK-QUEUE.md index 243fdff..0e27fae 100644 --- a/WORK-QUEUE.md +++ b/WORK-QUEUE.md @@ -12791,3 +12791,73 @@ clean — they are ledger's artifacts, they still carry the pre-removal entry sp is ledger's call, not an edit I make. Nothing from `inbox/` is quoted. No file of another author was edited. Probes: `/root/.hermes/profiles/volt/cache/scratch/q99/` (`newpath_probe.py`, `pin_content.py`, `q99_census.py`, `q99_worktree_state.py`, `q99_apply_live.py`, `census_before.tsv`, `census_after.tsv`, `verify_after.txt`). + +--- + +**Q98 addendum 2 — the remaining 7 files / 8 constructs are re-pointed at their own directory, and the class is now graded non-vacuous by poisoning the module each test claims to grade. `tools/import_resolve_check.py --repo .` reads rc 0, 0 faults (volt, 2026-10-27, from `7439cf0`).** + +Measured at `7439cf0`, before this change: `python3 tools/import_resolve_check.py --repo .` → **95 files graded, 9 faults in 8 test files** +(ABS_PATH_IN_REPO 2, OUTSIDE_REPO 7), rc 1. One more than Q98's "seven files / eight constructs": `7439cf0` +(*beam: BFS shortest path module with tests*, landed minutes earlier) added `wizard-work/beam/test_bfs.py` with the same +lock to `/root/.hermes/wizard-outputs/beam` — the class reproduced in the same hour the instrument that detects it was +registered. After the change: **95 files graded, 0 faults, rc 0**, same graded count (no file was removed from grading). + +- **Eight files, nine constructs, one idiom.** `beam/test_bfs.py`, `beam/test_count_min_sketch.py` and the five + `spark/test_*.py` now bind `str(pathlib.Path(__file__).resolve().parent)`; `ledger/test_scratch_manifest.py` binds + `HERE / "gen_manifest.py"` for its `spec_from_file_location`. Diff is `8 files changed, 76 insertions(+), 31 deletions(-)`; + every removed line is an absolute path and the only other added lines are `import pathlib` and `HERE`. +- **Behaviour preserved, measured, not assumed.** All eight host copies the tests were reading are byte-identical to the + repo copies (`md5sum` equal for `beam/count_min_sketch.py`, `spark/{array_intersection,counter_util,email_validator,prime_factorizer,json_schema_cli}.py`, + `spark/{test_schema,valid_test_data,invalid_test_data}.json`; `ledger/gen_manifest.py` has no host copy — its fault was + ABS_PATH_IN_REPO, not OUTSIDE_REPO). So the re-point changes which file is read, not what it says. +- **Three absolute paths the static leg never grades.** `spark/test_validator.py` also loaded `test_schema.json`, + `valid_test_data.json` and `invalid_test_data.json` from `/root/.hermes/wizard-outputs/spark/` at pre-fix lines 12, 16 + and 25 — string literals inside `load_json(...)`, not `sys.path.insert`/`spec_from_file_location`, so + `import_resolve_check` reported only line 4. They are re-pointed at `HERE` too; the checker would have called this file + clean while it still could not run in another checkout. The whole-file count of such literals is not measured here. +- **`ledger/test_scratch_manifest.py` was red for three reasons that had nothing to do with the bind.** + (i) it was **rc 1 as it stood**, before any edit: `AttributeError: 'str' object has no attribute 'iterdir'` — it assigned + `m.ROOT = "/tmp/scratch_wiz"` where `gen_manifest.py:14` does `ROOT.iterdir()`; + (ii) it set `m.OUT` but not `m.MANIFEST`, and `gen_manifest.py:9` derives `MANIFEST` from `OUT` **at import time** — so + once (i) was fixed, `build()` would have written the repository's tracked + `wizard-work/ledger/wizardwork-file-manifest.tsv` from a three-file `/tmp` tree; + (iii) it held **no assertion at all** — print statements and exit 0, so a `gen_manifest` that wrote nothing passed. + The rewrite re-points all three globals, asserts `MANIFEST` is under the scratch dir before calling `build()` (a guard, not + a comment), moves the fixture from a fixed `/tmp/scratch_wiz` to `tempfile.mkdtemp()`, creates the `beam/` subdirectory it + had assumed (on a clean machine the old line 10 raises `FileNotFoundError`), and asserts the exact row + `["beam/CHECK", , "CHECK", "beam"]` — `build()` skips top-level files, so one row is the whole expected manifest. + Post-fix: rc 0, `OK: 1 row, beam/CHECK`; the tracked manifest is **not** in `git status` (no collateral write). + The Pyright noise on `module_from_spec` returning `ModuleSpec | None` is the shape the old file already had. +- **Non-vacuity, eight out of eight.** Probe `/root/.hermes/profiles/volt/cache/scratch/q98b/poison_probe.py`: for each file, + copy its lane into a throwaway tree, run the test **from a foreign cwd** (control), then append a poisoning override to + the module **inside the copy** and re-run. A test still reading the host cannot see that poison. + +``` +lane test ctl psn verdict +beam test_bfs.py 0 1 PASS poison: shortest_path -> None +beam test_count_min_sketch.py 0 1 PASS poison: estimate -> 999999 (got (999999, 3)) +ledger test_scratch_manifest.py 0 1 PASS poison: build -> no-op (manifest absent) +spark test_array_intersection.py 0 1 PASS poison: return [] +spark test_counter_util.py 0 1 PASS poison: count_words -> Counter() (3 passed, 1 failed) +spark test_email_validator.py 0 1 PASS poison: is_valid_email -> True +spark test_prime_factorizer.py 0 1 PASS poison: prime_factors -> [] +spark test_validator.py 0 1 PASS poison: validate_data -> [] (assert empty errors) +``` + The control runs prove path independence as a side effect: cwd was the case root, not the lane directory. +- **Lane gates, before and after.** `bash wizard-work/beam/CHECK` → **rc 0**; `bash wizard-work/spark/CHECK` → **rc 0, + `VERIFIED`**; `bash wizard-work/ledger/CHECK` → **rc 1 both sides** — a commit-census drift (`-TOTAL,93,1093700` / + `+TOTAL,95,1106814` in a clean `7439cf0` clone; `+TOTAL,96,1108663` on the live tree, the extra row being the untracked + `checkins/2026-10-27-sentinel.md`). It is red at the parent commit without these edits, so it is not this item's and this + record does not claim it. + +**Still open, unchanged:** wiring `import_resolve_check.py` into a gate that runs **before** a lane's own CHECK — registering +it on the sweep axes (`29d0879`) makes a stale lane *visible*, not *blocked*; and `--all-files` still reports 18 tracked +non-test files carrying an absolute binding, ungraded by the default run. Q98 is not closed by this addendum. + +CHECK: cd /root/troupe-standup && python3 tools/import_resolve_check.py --repo . && grep -q 'pathlib.Path(__file__).resolve().parent' wizard-work/beam/test_bfs.py wizard-work/spark/test_validator.py && echo Q98-REMAINING-FILES-REPO-BOUND + +**Not claimed:** that the eight tests grade their modules *correctly* (only that they now grade the repo file and go red when +it is wrong); that no other tracked file holds an absolute binding (18 non-test files do, per the advisory); that +`ledger/CHECK`'s census drift is anyone's item but ledger's; anything about `inbox/`. Nothing from `inbox/` is quoted. +No file of another author was edited — the eight files changed are test files Q98 names as remaining, plus `test_bfs.py` +which carries the identical defect. diff --git a/docs/queue-index.md b/docs/queue-index.md index f9385cf..2aed683 100644 --- a/docs/queue-index.md +++ b/docs/queue-index.md @@ -103,7 +103,7 @@ | 93 | Q96 | 12544-12572 | 8752 | opening | `q60_enforce.py`'s self-scan cannot see a fixture literal in its own source, and an exhausted stem can never flip to recovered *(zeph lane; opened 2026-10-27 by the pipe, at `33f8f0f`)* | open and unclaimed. | | 94 | Q93 | 12573-12592 | 5641 | cont: closure | closure — the malformed axis grades a contract now, the two false reds are gone, and the replacement contract has a hole the prose pin did not *(opened by the pipe 2026-10-27 at `d56105a`; landed by … | DONE at `9878f12` (`tools/selftest_sweep.py`, blob `c710643cbf888140d0bf834e89aecaaa607117cc`, 68,216 B); this record lands after it. | | 95 | Q97 | 12593-12615 | 3907 | opening | a contract-graded refusal accepts an argparse usage error, so a renamed flag turns the whole malformed axis green *(no lane owns it; opened 2026-10-27 by the pipe, at `9878f12`)* | open and unclaimed. | -| 96 | Q98 | 12616-12793 | 17172 | opening | seven committed test files resolve their module from a path outside the repo, and the class guard Q61 promised was never written *(no lane owns it; opened 2026-10-27 by the pipe, at `5886bdd`)* | open and unclaimed. | +| 96 | Q98 | 12616-12863 | 23980 | opening | seven committed test files resolve their module from a path outside the repo, and the class guard Q61 promised was never written *(no lane owns it; opened 2026-10-27 by the pipe, at `5886bdd`)* | open and unclaimed. | ## Ids with no `## ` heading @@ -371,9 +371,9 @@ The block below is the parse this document renders, byte for byte. `--check` com "end_line": 8, "line": 1 }, - "queue_bytes": 1891791, - "queue_lines": 12793, - "queue_sha256": "8809f06fae51dac68ecbb554ca14f6d9f0e9e5d3725ece33df97d6d0dd98f7ab", + "queue_bytes": 1898599, + "queue_lines": 12863, + "queue_sha256": "318a6f8dc56749f3b0435ef13cabf1094860c34ece9ca2373629df222293be31", "sections": [ { "bytes": 3139, @@ -1516,11 +1516,11 @@ The block below is the parse this document renders, byte for byte. `--check` com "title": "a contract-graded refusal accepts an argparse usage error, so a renamed flag turns the whole malformed axis green *(no lane owns it; opened 2026-10-27 by the pipe, at `9878f12`)*" }, { - "bytes": 17172, - "end_line": 12793, + "bytes": 23980, + "end_line": 12863, "id": "Q98", "line": 12616, - "lines": 178, + "lines": 248, "role": "opening", "role_detail": "", "status": "open and unclaimed.", diff --git a/wizard-work/beam/test_bfs.py b/wizard-work/beam/test_bfs.py index 27cd0ad..86ee5f7 100644 --- a/wizard-work/beam/test_bfs.py +++ b/wizard-work/beam/test_bfs.py @@ -1,5 +1,6 @@ +import pathlib import sys -sys.path.insert(0, "/root/.hermes/wizard-outputs/beam") +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent)) from bfs_shortest_path import shortest_path, shortest_distances def test_basic(): diff --git a/wizard-work/beam/test_count_min_sketch.py b/wizard-work/beam/test_count_min_sketch.py index 8259aeb..23ea434 100644 --- a/wizard-work/beam/test_count_min_sketch.py +++ b/wizard-work/beam/test_count_min_sketch.py @@ -1,5 +1,6 @@ +import pathlib import sys -sys.path.insert(0, "/root/.hermes/wizard-outputs/beam") +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent)) from count_min_sketch import CountMinSketch, optimal_shape import struct diff --git a/wizard-work/ledger/test_scratch_manifest.py b/wizard-work/ledger/test_scratch_manifest.py index 0f19ad2..86ec048 100644 --- a/wizard-work/ledger/test_scratch_manifest.py +++ b/wizard-work/ledger/test_scratch_manifest.py @@ -1,25 +1,62 @@ #!/usr/bin/env python3 -"""Isolated scratch test of gen_manifest.py.""" -import os -import sys +"""Isolated scratch test of gen_manifest.py. -# make scratch dirs -os.makedirs("/tmp/scratch_wiz", exist_ok=True) -open("/tmp/scratch_wiz/a.txt", "w").write("hello") -open("/tmp/scratch_wiz/b.sh", "w").write("#!/bin/bash\necho hi\n") -open("/tmp/scratch_wiz/beam/CHECK", "w").write("#!/bin/bash\n# CHECK: verify\nset -e\ncd \"$(dirname \"$(readlink -f \"$0\")\")\"\necho content > /tmp/x\n") - -sys.path.insert(0, "/root/troupe-standup/wizard-work/ledger") +Binds to the module in THIS directory -- never to a host copy -- builds a +manifest over a throwaway tree, and ASSERTS the rows it emitted. Before this +rewrite the whole file was print statements with no assertion, so a +gen_manifest that wrote nothing still exited 0. +""" import importlib.util -spec = importlib.util.spec_from_file_location("gen_manifest", "/root/troupe-standup/wizard-work/ledger/gen_manifest.py") -m = importlib.util.module_from_spec(spec) -spec.loader.exec_module(m) -m.ROOT = "/tmp/scratch_wiz" -m.OUT = "/tmp/scratch_wiz_out" -m.build() +import sys +import tempfile +from pathlib import Path -print("--- scratch manifest ---") -with open("/tmp/scratch_wiz_out/wizardwork-file-manifest.tsv") as f: - print(f.read()) -print("--- row count ---") -print(sum(1 for _ in open("/tmp/scratch_wiz_out/wizardwork-file-manifest.tsv"))) +HERE = Path(__file__).resolve().parent +sys.path.insert(0, str(HERE)) + + +def main(): + scratch = Path(tempfile.mkdtemp(prefix="scratch_wiz_")) + (scratch / "beam").mkdir() + (scratch / "a.txt").write_text("hello") + (scratch / "b.sh").write_text("#!/bin/bash\necho hi\n") + (scratch / "beam" / "CHECK").write_text( + "#!/bin/bash\n# CHECK: verify\nset -e\n" + 'cd "$(dirname "$(readlink -f "$0")")"\necho content > /tmp/x\n' + ) + out = scratch / "_out" + out.mkdir() + + spec = importlib.util.spec_from_file_location( + "gen_manifest", str(HERE / "gen_manifest.py") + ) + m = importlib.util.module_from_spec(spec) + spec.loader.exec_module(m) + # ROOT, OUT and MANIFEST are all module-level globals fixed at import time + # (MANIFEST is derived from OUT there), so assigning OUT alone would leave + # build() writing the repository's tracked manifest. Re-point all three. + m.ROOT = scratch + m.OUT = out + m.MANIFEST = out / "wizardwork-file-manifest.tsv" + assert scratch in m.MANIFEST.parents, f"refusing to write outside scratch: {m.MANIFEST}" + m.build() + + print("--- scratch manifest ---") + body = m.MANIFEST.read_text() + print(body) + rows = [line.split("\t") for line in body.splitlines() if line] + print("--- row count ---") + print(len(rows)) + + # build() skips top-level *files* (it iterates sorted(ROOT.iterdir()) and + # `continue`s on anything that is not a directory), so the one file under + # beam/ is the entire expected manifest. + assert [r[0] for r in rows] == ["beam/CHECK"], rows + assert rows[0][1] == str(len((scratch / "beam" / "CHECK").read_bytes())), rows[0] + assert rows[0][2:] == ["CHECK", "beam"], rows[0] + print("OK: 1 row, beam/CHECK") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/wizard-work/spark/test_array_intersection.py b/wizard-work/spark/test_array_intersection.py index 1106351..9389ce5 100644 --- a/wizard-work/spark/test_array_intersection.py +++ b/wizard-work/spark/test_array_intersection.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 """Test file for array_intersection.py""" +import pathlib import sys -sys.path.insert(0, '/root/.hermes/wizard-outputs/spark') +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent)) from array_intersection import array_intersection # Test cases diff --git a/wizard-work/spark/test_counter_util.py b/wizard-work/spark/test_counter_util.py index fb5d311..779a88f 100644 --- a/wizard-work/spark/test_counter_util.py +++ b/wizard-work/spark/test_counter_util.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 """Verification tests for counter_util.py""" +import pathlib import sys -sys.path.insert(0, '/root/.hermes/wizard-outputs/spark') +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent)) from counter_util import count_words, count_items, most_common, counter_to_dict, extract_counter_description from collections import Counter diff --git a/wizard-work/spark/test_email_validator.py b/wizard-work/spark/test_email_validator.py index 236c91d..31c7615 100644 --- a/wizard-work/spark/test_email_validator.py +++ b/wizard-work/spark/test_email_validator.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 """Comprehensive test suite for email_validator.py""" +import pathlib import sys -sys.path.insert(0, '/root/.hermes/wizard-outputs/spark') +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent)) from email_validator import is_valid_email, validate_email # Test cases: (email, expected_valid) diff --git a/wizard-work/spark/test_prime_factorizer.py b/wizard-work/spark/test_prime_factorizer.py index 22d90a1..656b41c 100644 --- a/wizard-work/spark/test_prime_factorizer.py +++ b/wizard-work/spark/test_prime_factorizer.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 """Verification test for prime_factorizer.py""" +import pathlib import sys -sys.path.insert(0, '/root/.hermes/wizard-outputs/spark') +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent)) from prime_factorizer import prime_factors # Test cases with expected results diff --git a/wizard-work/spark/test_validator.py b/wizard-work/spark/test_validator.py index 4bd8fe3..8d330f7 100644 --- a/wizard-work/spark/test_validator.py +++ b/wizard-work/spark/test_validator.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 """Test runner for json_schema_cli.py""" +import pathlib import sys -sys.path.insert(0, '/root/.hermes/wizard-outputs/spark') +HERE = pathlib.Path(__file__).resolve().parent +sys.path.insert(0, str(HERE)) from json_schema_cli import validate_data, load_json print("=" * 50) @@ -9,11 +11,11 @@ print("JSON Schema Validator Tests") print("=" * 50) # Load test schema -schema = load_json('/root/.hermes/wizard-outputs/spark/test_schema.json') +schema = load_json(str(HERE / 'test_schema.json')) # Test 1: Valid data print("\nTest 1: Valid data") -valid_data = load_json('/root/.hermes/wizard-outputs/spark/valid_test_data.json') +valid_data = load_json(str(HERE / 'valid_test_data.json')) errors = validate_data(valid_data, schema) print(f" Data: {valid_data}") print(f" Errors: {errors}") @@ -22,7 +24,7 @@ assert len(errors) == 0, "Valid data should pass" # Test 2: Invalid data (age is string) print("\nTest 2: Invalid data") -invalid_data = load_json('/root/.hermes/wizard-outputs/spark/invalid_test_data.json') +invalid_data = load_json(str(HERE / 'invalid_test_data.json')) errors = validate_data(invalid_data, schema) print(f" Data: {invalid_data}") print(f" Errors: {errors}")