From 12f956a51564b0b4320dc4eae089a47860574c1d Mon Sep 17 00:00:00 2001 From: spark Date: Mon, 26 Oct 2026 00:20:31 +0000 Subject: [PATCH] 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. --- wizard-work/beam/test_trie.py | 250 ++++++++++++++++++++++------- wizard-work/beam/trie.py | 48 ++++-- wizard-work/volt/q70-2026-10-26.md | 125 +++++++++++++++ 3 files changed, 349 insertions(+), 74 deletions(-) create mode 100644 wizard-work/volt/q70-2026-10-26.md diff --git a/wizard-work/beam/test_trie.py b/wizard-work/beam/test_trie.py index 879d0cc..33a90c3 100644 --- a/wizard-work/beam/test_trie.py +++ b/wizard-work/beam/test_trie.py @@ -1,73 +1,201 @@ +"""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 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 - -def test_basic(): - t = Trie() - for w in ["cat", "car", "card", "dog", ""]: - t.insert(w) - assert t.search("cat") - assert t.search("") - assert t.search("card") - assert not t.search("ca") - assert t.starts_with("ca") - assert not t.starts_with("z") - assert sorted(t.words_with_prefix("ca")) == ["car", "card", "cat"] - assert t.count_prefix("ca") == 3 - assert len(t) == 5 +# 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_delete(): - t = Trie() - for w in ["car", "card", "care"]: - t.insert(w) - assert t.delete("car") - assert not t.search("car") - assert t.search("card") - assert t.search("care") - assert t.starts_with("car") - assert not t.delete("car") - # delete shared prefix leaf should prune - t.delete("card"); t.delete("care") - assert not t.starts_with("car") +class TestBasic(unittest.TestCase): + def test_basic(self): + t = Trie() + for w in ["cat", "car", "card", "dog", ""]: + t.insert(w) + self.assertTrue(t.search("cat")) + self.assertTrue(t.search("")) + self.assertTrue(t.search("card")) + self.assertFalse(t.search("ca")) + self.assertTrue(t.starts_with("ca")) + self.assertFalse(t.starts_with("z")) + self.assertEqual(t.words_with_prefix("ca"), ["car", "card", "cat"]) + self.assertEqual(t.count_prefix("ca"), 3) + self.assertEqual(len(t), 5) + + def test_module_resolved_beside_these_cells(self): + self.assertEqual(_GOT, _EXPECTED) -def test_duplicate_insert(): - t = Trie() - t.insert("abc"); t.insert("abc") - assert t.search("abc") - t.delete("abc") - assert not t.search("abc") # one delete removes word entirely (count not tracked per multiplicity) - # acceptable: delete removes end flag +class TestDelete(unittest.TestCase): + def test_delete(self): + t = Trie() + for w in ["car", "card", "care"]: + t.insert(w) + self.assertTrue(t.delete("car")) + self.assertFalse(t.search("car")) + self.assertTrue(t.search("card")) + self.assertTrue(t.search("care")) + self.assertTrue(t.starts_with("car")) + self.assertFalse(t.delete("car")) + # deleting the shared-prefix leaves should prune the branch + t.delete("card") + t.delete("care") + self.assertFalse(t.starts_with("car")) + + def test_delete_prunes_counters_with_it(self): + t = Trie() + for w in ["car", "card", "care"]: + 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) -def test_random_vs_bruteforce(): - rng = random.Random(123) - words = [] - alphabet = "ab" - for _ in range(200): - w = "".join(rng.choice(alphabet) for _ in range(rng.randint(0, 5))) - words.append(w) - t = Trie() - s = set() - for w in words: - t.insert(w); s.add(w) - for w in set(words): - assert t.search(w) == (w in s) - # delete half - to_del = list(s)[: len(s) // 2] - for w in to_del: - assert t.delete(w) - s.discard(w) - for w in set(words): - assert t.search(w) == (w in s), (w, t.search(w), w in s) - # prefix enumeration matches - for p in ["", "a", "b", "aa", "ab"]: - got = set(t.words_with_prefix(p)) - exp = {w for w in s if w.startswith(p)} - assert got == exp, (p, got, exp) +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") + # one delete removes the word entirely: multiplicity is not tracked, + # 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"))) + + +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) + words = [] + alphabet = "ab" + for _ in range(200): + w = "".join(rng.choice(alphabet) for _ in range(rng.randint(0, 5))) + words.append(w) + t = Trie() + s = set() + for w in words: + t.insert(w) + s.add(w) + for w in set(words): + self.assertEqual(t.search(w), w in s) + # delete half + to_del = list(s)[: len(s) // 2] + for w in to_del: + self.assertTrue(t.delete(w)) + s.discard(w) + for w in set(words): + self.assertEqual(t.search(w), w in s, (w, t.search(w), w in s)) + # prefix enumeration matches + for p in ["", "a", "b", "aa", "ab"]: + self.assertEqual(set(t.words_with_prefix(p)), {w for w in s if w.startswith(p)}) + # and the counter agrees with it after the deletes + 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__": - test_basic(); test_duplicate_insert(); test_delete(); test_random_vs_bruteforce() - print("ALL OK") + unittest.main(verbosity=2) diff --git a/wizard-work/beam/trie.py b/wizard-work/beam/trie.py index f1dc0dc..b6e744a 100644 --- a/wizard-work/beam/trie.py +++ b/wizard-work/beam/trie.py @@ -6,6 +6,9 @@ Correctness notes: - search is exact word match; starts_with is prefix match. - words_with_prefix returns sorted list (deterministic). - 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 @@ -16,20 +19,39 @@ class _Node: def __init__(self): self.children: dict[str, "_Node"] = {} 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: def __init__(self): 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.count += 1 + path = [node] for ch in word: 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.is_end = True def search(self, word: str) -> bool: node = self._walk(word) @@ -51,22 +73,21 @@ class Trie: node = self._walk(word) if node is None or not node.is_end: return False - # decrement counts along path; prune empty branches - self._remove(self.root, word, 0) + node.is_end = False + for n in self._path_existing(word): + n.count -= 1 + self._prune(self.root, word, 0) # drop branches that carry no word 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 not node.is_end: - return False - node.is_end = False - return len(node.children) == 0 + return (not node.is_end) and len(node.children) == 0 ch = word[depth] child = node.children.get(ch) if child is None: return False - should_delete_child = self._remove(child, word, depth + 1) - if should_delete_child: + if self._prune(child, word, depth + 1): del node.children[ch] return (not node.is_end) and len(node.children) == 0 return False @@ -86,6 +107,7 @@ class Trie: self._collect(node.children[ch], prefix + ch, out) def count_prefix(self, prefix: str) -> int: + """Distinct words having `prefix`; count_prefix("") == len(self).""" node = self._walk(prefix) return node.count if node is not None else 0 diff --git a/wizard-work/volt/q70-2026-10-26.md b/wizard-work/volt/q70-2026-10-26.md new file mode 100644 index 0000000..f89fa05 --- /dev/null +++ b/wizard-work/volt/q70-2026-10-26.md @@ -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 `: **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`.