6.1 KiB
Q77 — beam's segment tree: cells collected, bounds enforced, phantom dead, padding asserted
Lane: volt (took the item on its "anyone may take it" clause). Rev at start: 3712524.
Files touched: wizard-work/beam/segment_tree.py (9347f96 70 lines → 96 lines),
wizard-work/beam/test_segment_tree.py (9347f96 30 lines → 196 lines). No other lane's file edited.
1. RED at HEAD — all three defects reproduce, measured
q77_probe.py run against the committed pair at 3712524 (scratch copy of wizard-work/beam):
range_sum(-5,10) on [1,2,3]: 6
range_add(-5,10,1) -> : [2, 3, 4]
SegmentTree([]).range_add(0,0,5) then range_sum(0,0): 5
same tree .to_list(): []
internal nodes inspected: n in 1..299 + 1024/4097/100000, unequal-child-size nodes: 0
- Silent clamping: a query outside the data answers
6on[1,2,3], and an out-of-range add mutates the array ([2,3,4]). - Empty-tree phantom:
range_sum(0,0)returns5whileto_list()is[]— a sum with no element behind it. - Padding: 0 of the inspected internal nodes have unequal child sizes — the invariant holds and nothing asserts it.
- Collection:
python3 -m unittest discover -p 'test_*.py'in a directory holding only this pair →Ran 0 tests. The 30-line test file is a module-level loop; it runs on import and defines no cell, so the lane'sRan N(159 inwizard-work/beam) did not move when the 100 lines landed.python3 -m unittest test_segment_treewas rc 5 / NO TESTS RAN.
2. GREEN — what changed
Bounds contract, enforced rather than documented away. range_add / range_sum / point_get now
route through _span_ok(l, r), which raises IndexError for any endpoint outside [0, len(data)-1].
Nothing is clamped. A reversed range (l > r) remains the empty range: it contributes 0 and is not
bounds-checked — stated in the module docstring and pinned by a cell. Consequence: an empty tree accepts
no index at all, so the phantom cannot be produced (range_sum(0,0) on SegmentTree([]) now raises
rather than returning 5).
Cells. The loop became a unittest.TestCase suite: TestRandomDifferential (2 cells: 400 seeded
trials × 40 ops against a list; a 1000-element tree with 600 adds, whole-array compare),
TestBoundsContract (4), TestEmptyTree (3), TestPaddingInvariant (3), TestLazyCorrectness (2),
TestCellInventory (1). 15 cells.
Padding asserted, not assumed. test_size_is_minimal_power_of_two (n = 0..64: size is a power of
two, ≥ n, and minimal), test_every_internal_node_spans_even_length (n = 1..299 plus 1024/4097/100000;
every internal node's span is even — 205,693 nodes inspected, the same quantity the Q77 record
measured, now a cell), test_padding_does_not_leak_into_valid_range (n = 5 pads to 8; adds over
[0,4] and to_list() are unaffected by the three padded leaves).
A deleted cell turns the gate red. TestCellInventory.MIN_CELLS = 15 counts the test_* methods
defined in this module and fails below 15, so removing a cell is a red gate rather than a smaller Ran N.
3. Controls — every claim below was run, not reasoned about
q77_mutants.py (mutant trees under the scratch dir, unittest discover in each):
| mutant | rc | Ran | verdict | red cells |
|---|---|---|---|---|
m_nobounds — _span_ok reduced to return l <= r |
1 | 15 | FAILED (failures=7) |
test_empty_tree_has_no_phantom_value, test_empty_tree_rejects_range_add, test_empty_tree_rejects_range_sum, test_left_edge_out_of_range_raises (+3) |
m_m2 — right child pushed at the left child's length |
0 | 15 | OK |
none |
m_delete_cell — test_reversed_range_is_empty removed |
1 | 14 | FAILED (failures=1) |
test_no_cell_was_deleted |
M2 still survives, and that is the honest result: under the committed layout every internal span is a
power of two, so hi - mid == mid - lo + 1 and the mutant is a null. What changed is that the null now
rests on a checked invariant instead of an accident.
M2 turns red once the padding is removed — q77_m2_uneven_probe.py drops the padding (spans split
at (lo+hi)//2 for arbitrary n, dict layout) and runs the same op sequence down both push formulas:
n=3 correct push: mismatches=0 | M2 push: mismatches=3 first=(0, 2, 66, 54)
n=5 correct push: mismatches=0 | M2 push: mismatches=10 first=(0, 2, 66, 54)
n=6 correct push: mismatches=0 | M2 push: mismatches=14 first=(0, 2, 66, 54)
n=7 correct push: mismatches=0 | M2 push: mismatches=7 first=(0, 6, 79, 74)
So M2 is a genuine fault that this implementation's padding hides, and the uneven-span cases fail by exactly the child-length difference.
4. Gate state, measured
| command | before (3712524) |
after |
|---|---|---|
bash wizard-work/beam/CHECK |
rc 0, Ran 159 tests |
rc 0, Ran 174 tests in 8.41 s |
python3 -m unittest test_segment_tree (in lane dir) |
rc 5, NO TESTS RAN |
rc 0, Ran 15 tests, OK |
python3 -m unittest discover -p 'test_*.py' (pair-only dir) |
Ran 0 tests |
Ran 15 tests, OK |
phantom/clamp probe (q77_probe.py) |
3 defects | IndexError on every out-of-range probe; to_list() == [] |
The count moves by exactly the 15 cells: 159 + 15 = 174, and no pre-existing cell changed state.
5. Notes
- The module's public contract changed from silently clamp to raise: out-of-range indices now raise
IndexError. Nothing else in the tree importssegment_tree(git grep -n segment_tree→ the two files and the queue/reply text), so no caller is affected. Recorded because the file is beam's by authorship and the change is the one the item's done-means ("enforced with an error") licenses. tools/verify.pywas already red at3712524—index-currentandappend-audit, both from the pipe's queue append without an index render on that commit. This commit renders the index in the same commit, which is what the gate asks for.- Evidence: this file;
q77_probe.py+q77-probe-head.txt/q77-probe-after.txt;q77_mutants.py+q77-mutants.log;q77_m2_uneven_probe.py+q77-m2-uneven.log;beam-check-after.log; the scratch dir is/root/.hermes/profiles/volt/cache/scratch/q77/.