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.
This commit is contained in:
spark committed 2026-10-26 00:20:31 +00:00
1 parent daf30cde0c
commit 12f956a515
3 files changed
+324 -49

No files matched your search

+164 -36
View File
@@ -1,47 +1,173 @@
"""Cells for trie.Trie.
Every cell is a unittest.TestCase method so `python3 -m unittest discover`
collects it. The module imported is pinned to the file beside this one: an
environment that shadows `trie` (a host copy, an installed package) raises
instead of grading the wrong file green -- Q68's fault, measured in this lane.
`python3 test_trie.py` still runs the same cells from __main__.
"""
import os
import random import random
import sys
import unittest
_HERE = os.path.dirname(os.path.abspath(__file__))
if sys.path and sys.path[0] != _HERE:
sys.path.insert(0, _HERE)
import trie as _trie_module
from trie import Trie from trie import Trie
# abspath, not realpath: realpath would resolve a symlinked module to the same
# string as its target and let a symlink-out pass as "the file beside me".
_EXPECTED = os.path.abspath(os.path.join(_HERE, "trie.py"))
_GOT = os.path.abspath(_trie_module.__file__)
if _GOT != _EXPECTED:
raise ImportError(
"trie resolved to %r, expected %r -- these cells would grade the wrong file"
% (_GOT, _EXPECTED)
)
def test_basic():
class TestBasic(unittest.TestCase):
def test_basic(self):
t = Trie() t = Trie()
for w in ["cat", "car", "card", "dog", ""]: for w in ["cat", "car", "card", "dog", ""]:
t.insert(w) t.insert(w)
assert t.search("cat") self.assertTrue(t.search("cat"))
assert t.search("") self.assertTrue(t.search(""))
assert t.search("card") self.assertTrue(t.search("card"))
assert not t.search("ca") self.assertFalse(t.search("ca"))
assert t.starts_with("ca") self.assertTrue(t.starts_with("ca"))
assert not t.starts_with("z") self.assertFalse(t.starts_with("z"))
assert sorted(t.words_with_prefix("ca")) == ["car", "card", "cat"] self.assertEqual(t.words_with_prefix("ca"), ["car", "card", "cat"])
assert t.count_prefix("ca") == 3 self.assertEqual(t.count_prefix("ca"), 3)
assert len(t) == 5 self.assertEqual(len(t), 5)
def test_module_resolved_beside_these_cells(self):
self.assertEqual(_GOT, _EXPECTED)
def test_delete(): class TestDelete(unittest.TestCase):
def test_delete(self):
t = Trie() t = Trie()
for w in ["car", "card", "care"]: for w in ["car", "card", "care"]:
t.insert(w) t.insert(w)
assert t.delete("car") self.assertTrue(t.delete("car"))
assert not t.search("car") self.assertFalse(t.search("car"))
assert t.search("card") self.assertTrue(t.search("card"))
assert t.search("care") self.assertTrue(t.search("care"))
assert t.starts_with("car") self.assertTrue(t.starts_with("car"))
assert not t.delete("car") self.assertFalse(t.delete("car"))
# delete shared prefix leaf should prune # deleting the shared-prefix leaves should prune the branch
t.delete("card"); t.delete("care") t.delete("card")
assert not t.starts_with("car") t.delete("care")
self.assertFalse(t.starts_with("car"))
def test_delete_prunes_counters_with_it(self):
def test_duplicate_insert():
t = Trie() t = Trie()
t.insert("abc"); t.insert("abc") for w in ["car", "card", "care"]:
assert t.search("abc") t.insert(w)
t.delete("card")
t.delete("care")
self.assertEqual(t.count_prefix("car"), 1) # only "car" left below it
self.assertEqual(t.count_prefix("car"), len(t.words_with_prefix("car")))
t.delete("car")
self.assertEqual(t.count_prefix(""), 0) # branch pruned, root empty
self.assertEqual(len(t), 0)
class TestDuplicateInsert(unittest.TestCase):
def test_duplicate_insert(self):
t = Trie()
t.insert("abc")
t.insert("abc")
self.assertTrue(t.search("abc"))
t.delete("abc") t.delete("abc")
assert not t.search("abc") # one delete removes word entirely (count not tracked per multiplicity) # one delete removes the word entirely: multiplicity is not tracked,
# acceptable: delete removes end flag # so the counter must read 1, not 2, for the whole lifetime
self.assertFalse(t.search("abc"))
self.assertEqual(len(t), 0)
self.assertEqual(t.count_prefix(""), 0)
def test_duplicate_insert_does_not_inflate_counts(self):
t = Trie()
t.insert("abc")
t.insert("abc")
self.assertEqual(len(t), 1)
self.assertEqual(t.count_prefix("a"), 1)
self.assertEqual(t.count_prefix("ab"), 1)
self.assertEqual(t.count_prefix("abc"), 1)
self.assertEqual(t.count_prefix("a"), len(t.words_with_prefix("a")))
def test_random_vs_bruteforce(): class TestCountPrefixAgreesWithEnumeration(unittest.TestCase):
"""The item's cell: the counter may not disagree with the enumeration."""
def assert_agrees(self, t, prefixes):
for p in prefixes:
self.assertEqual(
t.count_prefix(p),
len(t.words_with_prefix(p)),
"prefix %r: counter %d vs enumerated %d"
% (p, t.count_prefix(p), len(t.words_with_prefix(p))),
)
def test_after_delete_of_a_word(self):
t = Trie()
for w in ["cat", "car", "card", "dog", ""]:
t.insert(w)
t.delete("car")
self.assert_agrees(t, ["", "c", "ca", "car", "cat", "card", "d", "do", "dog", "z"])
def test_after_delete_of_a_prefix_word(self):
t = Trie()
for w in ["a", "ab", "abc"]:
t.insert(w)
t.delete("a")
self.assertEqual(len(t), 2)
self.assert_agrees(t, ["", "a", "ab", "abc"])
def test_after_insert_then_delete_of_everything(self):
t = Trie()
t.insert("zzz")
t.delete("zzz")
self.assertEqual(len(t), 0)
self.assert_agrees(t, ["", "z", "zz", "zzz"])
def test_root_count_equals_len(self):
t = Trie()
for w in ["a", "ab", "abc", "", "b"]:
t.insert(w)
self.assertEqual(t.count_prefix(""), len(t))
self.assertEqual(t.count_prefix(""), 5)
def test_interleaved_insert_delete_random(self):
rng = random.Random(1234)
t = Trie()
live = set()
prefixes = ["", "a", "b", "c", "aa", "ab", "ba", "cc"]
for _ in range(400):
w = "".join(rng.choice("abc") for _ in range(rng.randint(0, 3)))
if rng.random() < 0.6:
t.insert(w)
live.add(w)
else:
removed = t.delete(w)
self.assertEqual(removed, w in live, "delete(%r) returned %r" % (w, removed))
live.discard(w)
self.assertEqual(len(t), len(live))
self.assert_agrees(t, prefixes)
for p in prefixes:
self.assertEqual(
set(t.words_with_prefix(p)), {x for x in live if x.startswith(p)}
)
class TestRandomVsBruteforce(unittest.TestCase):
def test_random_vs_bruteforce(self):
rng = random.Random(123) rng = random.Random(123)
words = [] words = []
alphabet = "ab" alphabet = "ab"
@@ -51,23 +177,25 @@ def test_random_vs_bruteforce():
t = Trie() t = Trie()
s = set() s = set()
for w in words: for w in words:
t.insert(w); s.add(w) t.insert(w)
s.add(w)
for w in set(words): for w in set(words):
assert t.search(w) == (w in s) self.assertEqual(t.search(w), w in s)
# delete half # delete half
to_del = list(s)[: len(s) // 2] to_del = list(s)[: len(s) // 2]
for w in to_del: for w in to_del:
assert t.delete(w) self.assertTrue(t.delete(w))
s.discard(w) s.discard(w)
for w in set(words): for w in set(words):
assert t.search(w) == (w in s), (w, t.search(w), w in s) self.assertEqual(t.search(w), w in s, (w, t.search(w), w in s))
# prefix enumeration matches # prefix enumeration matches
for p in ["", "a", "b", "aa", "ab"]: for p in ["", "a", "b", "aa", "ab"]:
got = set(t.words_with_prefix(p)) self.assertEqual(set(t.words_with_prefix(p)), {w for w in s if w.startswith(p)})
exp = {w for w in s if w.startswith(p)} # and the counter agrees with it after the deletes
assert got == exp, (p, got, exp) for p in ["", "a", "b", "aa", "ab"]:
self.assertEqual(t.count_prefix(p), len(t.words_with_prefix(p)))
self.assertEqual(t.count_prefix(""), len(t))
if __name__ == "__main__": if __name__ == "__main__":
test_basic(); test_duplicate_insert(); test_delete(); test_random_vs_bruteforce() unittest.main(verbosity=2)
print("ALL OK")
+35 -13
View File
@@ -6,6 +6,9 @@ Correctness notes:
- search is exact word match; starts_with is prefix match. - search is exact word match; starts_with is prefix match.
- words_with_prefix returns sorted list (deterministic). - words_with_prefix returns sorted list (deterministic).
- Empty string is a valid word if inserted. - Empty string is a valid word if inserted.
- count_prefix counts DISTINCT words, so for every prefix p it equals
len(words_with_prefix(p)); a duplicate insert adds no word and a delete
removes one. It is maintained incrementally along the path, not recomputed.
""" """
from __future__ import annotations from __future__ import annotations
@@ -16,20 +19,39 @@ class _Node:
def __init__(self): def __init__(self):
self.children: dict[str, "_Node"] = {} self.children: dict[str, "_Node"] = {}
self.is_end: bool = False self.is_end: bool = False
self.count: int = 0 # number of words passing through this node self.count: int = 0 # number of distinct words passing through this node
class Trie: class Trie:
def __init__(self): def __init__(self):
self.root = _Node() self.root = _Node()
def insert(self, word: str) -> None: def _path(self, word: str) -> list["_Node"]:
"""Nodes along `word`, root included; missing nodes are created."""
node = self.root node = self.root
node.count += 1 path = [node]
for ch in word: for ch in word:
node = node.children.setdefault(ch, _Node()) node = node.children.setdefault(ch, _Node())
path.append(node)
return path
def _path_existing(self, word: str) -> list["_Node"]:
"""Nodes along an existing `word`, root included. Creates nothing."""
node = self.root
path = [node]
for ch in word:
node = node.children[ch]
path.append(node)
return path
def insert(self, word: str) -> None:
path = self._path(word)
last = path[-1]
if last.is_end:
return # already a distinct word: the count must not move
last.is_end = True
for node in path:
node.count += 1 node.count += 1
node.is_end = True
def search(self, word: str) -> bool: def search(self, word: str) -> bool:
node = self._walk(word) node = self._walk(word)
@@ -51,22 +73,21 @@ class Trie:
node = self._walk(word) node = self._walk(word)
if node is None or not node.is_end: if node is None or not node.is_end:
return False return False
# decrement counts along path; prune empty branches node.is_end = False
self._remove(self.root, word, 0) for n in self._path_existing(word):
n.count -= 1
self._prune(self.root, word, 0) # drop branches that carry no word
return True return True
def _remove(self, node: _Node, word: str, depth: int) -> bool: def _prune(self, node: _Node, word: str, depth: int) -> bool:
"""Return True when `node` itself may be removed from its parent."""
if depth == len(word): if depth == len(word):
if not node.is_end: return (not node.is_end) and len(node.children) == 0
return False
node.is_end = False
return len(node.children) == 0
ch = word[depth] ch = word[depth]
child = node.children.get(ch) child = node.children.get(ch)
if child is None: if child is None:
return False return False
should_delete_child = self._remove(child, word, depth + 1) if self._prune(child, word, depth + 1):
if should_delete_child:
del node.children[ch] del node.children[ch]
return (not node.is_end) and len(node.children) == 0 return (not node.is_end) and len(node.children) == 0
return False return False
@@ -86,6 +107,7 @@ class Trie:
self._collect(node.children[ch], prefix + ch, out) self._collect(node.children[ch], prefix + ch, out)
def count_prefix(self, prefix: str) -> int: def count_prefix(self, prefix: str) -> int:
"""Distinct words having `prefix`; count_prefix("") == len(self)."""
node = self._walk(prefix) node = self._walk(prefix)
return node.count if node is not None else 0 return node.count if node is not None else 0
+125
View File
@@ -0,0 +1,125 @@
# 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`.