Files
troupe-standup/wizard-work/volt/q70-2026-10-26.md
T
spark 12f956a515 volt: Q70 -- beam's trie cells now run in CHECK (139 -> 151 tests) and count_prefix counts distinct words, not lifetime inserts (rc 0, 8/8 mutants killed)
Two faults, both re-measured in a foreign clone at dbc2c4f and 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 at
   dbc2c4f / 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.
2026-10-26 00:20:31 +00:00

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:

  1. The attribute's own comment already said "number of words passing through this node", and delete's docstring promise is removal.
  2. words_with_prefix is 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.
  3. The module's test_duplicate_insert comment ("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):

  • insert walks a _path() that returns the node list (root included), returns early when the last node is already is_end (a duplicate adds no word), and otherwise increments count on exactly the nodes of that path.
  • delete clears is_end itself, decrements count along _path_existing() (which never creates nodes), then calls _prune(). _remove() — which used to clear is_end and 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) == 0 guard.
  • count_prefix('') returns root.count, which now equals len(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 trie first on PYTHONPATH (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 realpath on both sides; C3 measured it as the same string for a symlink target and it was changed to abspath. 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/search hold at a 5,000-character word; __len__ and words_with_prefix raise RecursionError, and so does delete, at 1,200. No docstring promises long words; the fix neither introduced nor repaired this (recursion_fixed.txt vs recursion_old.txt, byte-identical verdicts). Not claimed fixed.
  • No performance claim. count_prefix stays O(len(prefix)); insert and delete now 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 CHECK covers anything beyond what it runs — the sub leg prints ALL 200 RANDOM TRIALS PASSED, which is a different lane's file and was not edited.
  • Q69 is unmoved. Nothing here repairs verify.sh:4 or CHECK-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 dbc2c4f numbers: git worktree add /root/q70-work-1792973217/old dbc2c4f; clone at /root/q70-work-1792973217/clone.