Two faults, both re-measured in a foreign clone atdbc2c4fand at HEAD, neither visible from the commit message: 1. test_trie.py had four module-level functions invoked only from __main__, so `python3 -m unittest test_trie` printed "Ran 0 tests / NO TESTS RAN" while `python3 test_trie.py` printed "ALL OK", and the lane gate never named trie: grep -ci trie over its -v transcript = 0, and the gate's count was 136 atdbc2c4f/ 139 at HEAD -- unchanged by the file landing. The four cells are now nine unittest.TestCase methods (12 cells) and `bash wizard-work/beam/CHECK` runs 151 tests, rc 0, with 12 trie cells in the transcript. 2. count_prefix was incremented on insert and never decremented, so it contradicted the module's own delete: ['cat','car','card','dog',''] then delete('car') read 3 where words_with_prefix('ca') returns 2; ['a','ab','abc'] then delete('a') read 3 vs 2; insert+delete 'zzz' read 1 with len(t) 0; insert('abc') twice read 2 with len(t) 1. It now counts DISTINCT words -- incremented only when the word was not already an end-of-word, decremented along the path on a real delete -- so count_prefix(p) == len(words_with_prefix(p)) by construction and by test, not by recomputation. The item allowed either reading; the docstring's own words and the existence of an independent enumeration are why this one. Cells and controls (scratch/q70/, harness q70_mutants.py): the shipped pre-fix blob is rc 1 with 8 of 12 cells red; seven one-place mutants (duplicate insert inflates, delete does not decrement, count_prefix +1, prune drops the is_end guard, insert counts only the last node, delete never prunes, count_prefix ignores the walk) are 7/7 rc 1; clean module rc 0. Gate also rc 0 from a foreign cwd with a hostile PYTHONPATH, under python3 -O, and with /root/.hermes masked by tmpfs (0 entries); rc 1 with the in-tree module deleted. The module pin is measured, not decorative: with it removed, cells pointed at a plausible foreign trie module grade GREEN (rc 0, Ran 11) -- and with it in place the same environment raises ImportError naming both paths. My first cut compared realpath on both sides and could not tell a symlinked module from its target; abspath was measured into place and the residual (a symlink at the expected path) is written down in the artifact rather than implied away. Not claimed: the 1200-character recursion boundary in __len__, words_with_prefix and delete is unchanged, verdict-identical to the pre-fix module; no timing. Files: 2 changed (229 79), plus wizard-work/volt/q70-2026-10-26.md. No host paths added (git grep -c -e /root/troupe-standup -e /root/.hermes = 0 on all three). Nothing from inbox/ quoted. Beam's files edited under Q70's own "anyone may take it". CHECK rc 0 at this commit: `bash wizard-work/beam/CHECK` -> Ran 151 tests, OK.
12 KiB
Q70 — beam's test_trie.py graded by nothing, and count_prefix a lifetime insert counter
Lane: volt (the Workhorse). Opened: 2026-10-25 by the pipe, per replies/2026-10-25-to-zephyrus-102.md §2.
Taken under Q70's own "beam's lane by authorship; anyone may take it". Closed: 2026-10-26.
Files touched: wizard-work/beam/trie.py, wizard-work/beam/test_trie.py (2 files, 229 79 numstat). No other lane's file edited. Nothing from inbox/ is quoted.
1. The item, and which of its numbers reproduce here
Both faults were re-measured rather than taken from the opening record. The item's counts are stated at the sha it names, dbc2c4f; HEAD has moved three tests since, so both numbers are given.
| measurement | dbc2c4f (the item's sha) |
32f87c5 (HEAD this work started from) |
|---|---|---|
python3 -m unittest discover -p 'test_*.py' in wizard-work/beam |
Ran 136 tests |
Ran 139 tests |
grep -ci trie over that -v transcript |
0 | 0 |
python3 -m unittest test_trie |
Ran 0 tests / NO TESTS RAN |
Ran 0 tests / NO TESTS RAN |
python3 test_trie.py |
ALL OK |
ALL OK |
So the item's "136" and "0 cells collected" are exact at the sha it names, and the fault is unchanged at HEAD. The cells existed, ran green when invoked by hand, and no gate invoked them — Q68's third fault, one commit after Q68 closed, in the same lane.
The count_prefix fault was probed with scratch/q70_probe.py, which prints the module path it actually imported first (…/wizard-work/beam/trie.py, in-tree) so the reading cannot be a host copy:
| cell | pre-fix | fixed |
|---|---|---|
['cat','car','card','dog',''], count_prefix('ca') before any delete |
3 | 3 |
same, after delete('car'): counter vs len(words_with_prefix('ca')) |
3 vs 2 | 2 vs 2 |
['a','ab','abc'], after delete('a') |
3 vs 2 (and len(t) 2) |
2 vs 2 |
insert 'zzz' then delete('zzz'): count_prefix('') |
1 (with len(t) 0) |
0 |
insert('abc') twice: count_prefix('a') vs len(t) |
2 vs 1 | 1 vs 1 |
| 300 seeded inserts + half deleted: every prefix agrees | mismatches at '', 'b', 'c' |
0 mismatches |
FAULTS: 5 before, FAULTS: 0 after (scratch/q70/{probe_before,probe_after}.txt).
2. The decision, stated
The item allows either reading — count distinct words, or rename the method to what it actually measures. Distinct words was taken, for three reasons, each checkable in the file:
- The attribute's own comment already said "number of words passing through this node", and
delete's docstring promise is removal. words_with_prefixis the enumeration of the same set, so the counter has an independent implementation to be graded against — a rename would leave that cross-check unavailable and the class of fault unfalsifiable.- The module's
test_duplicate_insertcomment ("one delete removes word entirely (count not tracked per multiplicity)") is already a statement that multiplicity is not the model.
count_prefix is therefore distinct words, maintained incrementally — incremented along the path only when the inserted word was not already an end-of-word, decremented along the path on a real delete — and not recomputed from the enumeration, so a test that compares the two is not comparing a function to itself. The docstring now says so in one line.
3. The fix
trie.py (48 changed lines):
insertwalks a_path()that returns the node list (root included), returns early when the last node is alreadyis_end(a duplicate adds no word), and otherwise incrementscounton exactly the nodes of that path.deleteclearsis_enditself, decrementscountalong_path_existing()(which never creates nodes), then calls_prune()._remove()— which used to clearis_endand prune, so it could not be reused for the decrement — became_prune(), a pure "may this node be dropped" predicate with the same pruning rule and the same(not node.is_end) and len(node.children) == 0guard.count_prefix('')returnsroot.count, which now equalslen(self); that identity is a cell, not a comment.
Behaviour that was already right and is untouched: pruning of unshared branches, sorted enumeration, the empty word, starts_with on a shared prefix.
4. The gate: 4 cells that ran by hand, 12 that run in CHECK
test_trie.py is now nine unittest.TestCase methods (12 cells with the inherited suite unchanged in substance):
TestBasic.test_basic (the original assertions verbatim) · test_module_resolved_beside_these_cells · TestDelete.test_delete (the original, including the prune case) · test_delete_prunes_counters_with_it · TestDuplicateInsert.test_duplicate_insert · test_duplicate_insert_does_not_inflate_counts · TestCountPrefixAgreesWithEnumeration.{test_after_delete_of_a_word, test_after_delete_of_a_prefix_word, test_after_insert_then_delete_of_everything, test_root_count_equals_len, test_interleaved_insert_delete_random} · TestRandomVsBruteforce.test_random_vs_bruteforce (the original, extended with the counter check after its deletes).
The interleaved cell is the load-bearing one: 400 seeded operations, each graded for len(t) == len(live), counter-vs-enumeration on eight prefixes, and set equality of words_with_prefix against brute force — so a counter that drifts by one on any single delete is caught at that operation, not only at the end.
bash wizard-work/beam/CHECK, same commit, before → after:
| before | after | |
|---|---|---|
Ran N tests |
139 | 151 (+12) |
grep -ci test_trie |
0 | 12 |
PASS lines · rc |
21 · 0 | 21 · 0 |
python3 test_trie.py still runs the same 12 cells (unittest.main(verbosity=2)), so the by-hand path the original offered is preserved.
5. The module pin — and the measurement that made me keep it
test_trie.py inserts its own directory at sys.path[0] and then raises ImportError (a raise, not an assert, so -O cannot strip it) unless the imported module's path is the file beside it. The one-line reason is Q68's measured fault: cells that pass against a host copy grade nothing.
- C3b — the guard is live, not decoration. Same cells with the local-path insert removed, a plausible foreign
triefirst onPYTHONPATH(scratch/q70/c2_bogus/trie.py: a set-backed class with the same API, importing cleanly): rc 1,ImportError: trie resolved to '…/c2_bogus/trie.py', expected '…/c3b_guard/trie.py' -- these cells would grade the wrong file. - C3c — the wrong file would otherwise have been GREEN. Same cells with both the insert and the guard removed, still pointed at the foreign module: rc 0,
Ran 11 tests,OK. An unguarded test file grades a different implementation green. That is the entire case for the pin, measured rather than asserted. - C2 — with the pin, the hostile path does not win:
PYTHONPATH=…/c2_bogus, rc 0,Ran 12 tests,OK, in-tree module. - What the pin does not catch, stated: a symlink at the expected path pointing elsewhere is imported as that path, so the guard cannot tell it from a real file (
c3_symlink.txt, rc 0, module a byte copy of the fixed source). That case is not the Q68 class — the file the cells read is the file at the path they name — but it is a reading this guard does not distinguish, so it is written down rather than implied. - My first cut compared
realpathon both sides; C3 measured it as the same string for a symlink target and it was changed toabspath. Logged because it is the second time in this queue that a guard I wrote needed a control to be shown live.
6. Control matrix — 7 mutants, the shipped pre-fix blob, and a clean control
Each mutant is a one-place edit of the fixed trie.py, dropped beside the new test_trie.py and graded with python3 -m unittest test_trie (scratch/q70/ctl_matrix.txt, harness scratch/q70_mutants.py):
| # | mutant | rc | cells that reddened |
|---|---|---|---|
| — | HEAD:trie.py (the blob that shipped) |
1 | 8 of 12, incl. test_after_delete_of_a_word, test_duplicate_insert, test_interleaved_insert_delete_random |
| M1 | duplicate insert inflates the count (early return removed) | 1 | test_duplicate_insert, test_duplicate_insert_does_not_inflate_counts, test_interleaved…, test_random_vs_bruteforce |
| M2 | delete never decrements |
1 | 7 of 12 |
| M3 | count_prefix reports count + 1 |
1 | 10 of 12 |
| M4 | prune drops the is_end guard on the branch |
1 | test_delete_prunes_counters_with_it, test_interleaved…, test_random_vs_bruteforce |
| M5 | insert counts only the last node |
1 | 10 of 12 |
| M6 | delete decrements but never prunes |
1 | test_delete (the original cell — pruning was already graded) |
| M7 | count_prefix ignores the walk (always root) |
1 | 5 of 12 |
| — | clean (fixed module) | 0 | Ran 12 tests, OK |
8 of 8 mutants red, clean green, none surviving. M4 is the one worth naming: it deletes a node that still terminates a word, and only the counter/enumeration cells catch it — test_delete does not.
7. Invocation environments
| environment | verdict |
|---|---|
shared tree (bash wizard-work/beam/CHECK) |
rc 0, Ran 151, 21 PASS |
foreign clone at another path (/root/q70-work-1792973217/clone) |
rc 0, Ran 151 — this is where every measurement above was taken |
/root/.hermes masked (unshare -m + tmpfs, 0 entries) |
rc 0, Ran 151 — the gate reads nothing under the author's tree |
foreign cwd + hostile PYTHONPATH (cd /root && …/beam/CHECK) |
rc 0, Ran 151 |
python3 -O -m unittest test_trie |
rc 0, Ran 12 |
in-tree trie.py deleted |
rc 1, ModuleNotFoundError: No module named 'trie' — red, not silently green |
Paths come from __file__; git grep -c -e /root/troupe-standup -e /root/.hermes over both files is 0, so this work adds no Q69 and no Q62 instance. Repo gate python3 tools/verify.py --repo <clone>: 8 legs, 8 pass, 0 fault, 0 ungradable, rc 0.
8. What is not claimed
- The recursion boundary is unchanged. Measured identically on pre-fix and fixed modules:
insert/searchhold at a 5,000-character word;__len__andwords_with_prefixraiseRecursionError, and so doesdelete, at 1,200. No docstring promises long words; the fix neither introduced nor repaired this (recursion_fixed.txtvsrecursion_old.txt, byte-identical verdicts). Not claimed fixed. - No performance claim.
count_prefixstays O(len(prefix));insertanddeletenow build one path list each, which is the same asymptotic cost and was not timed. - No claim about other lanes' trie-like code, no census, and no claim that
CHECKcovers anything beyond what it runs — thesubleg printsALL 200 RANDOM TRIALS PASSED, which is a different lane's file and was not edited. - Q69 is unmoved. Nothing here repairs
verify.sh:4orCHECK-git-history; this commit only avoids adding to that population.
9. Paths
- Changed:
wizard-work/beam/trie.py(4,359 B,sha256 a5621ed1…),wizard-work/beam/test_trie.py(7,113 B,sha256 31ba4f74…). - Harnesses:
scratch/q70_probe.py,q70_mutants.py,q70_guard_probe.py,q70_wronggreen_probe.py,q70_recursion.py. - Transcripts:
scratch/q70/{q70_before_discover.txt, q70_check_after.txt, ctl_matrix.txt, c2_pythonpath.txt, c3_symlink.txt, c3b_guard.txt, c3c_wronggreen.txt, c4_optimized.txt, c6_foreign_cwd.txt, verify_repo.txt, recursion_fixed.txt, recursion_old.txt, probe_before.txt, probe_after.txt}. - Old tree for the
dbc2c4fnumbers:git worktree add /root/q70-work-1792973217/old dbc2c4f; clone at/root/q70-work-1792973217/clone.