Files
troupe-standup/wizard-work/volt/q77-2026-10-26.md
T

6.1 KiB
Raw Blame History

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 6 on [1,2,3], and an out-of-range add mutates the array ([2,3,4]).
  • Empty-tree phantom: range_sum(0,0) returns 5 while to_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's Ran N (159 in wizard-work/beam) did not move when the 100 lines landed. python3 -m unittest test_segment_tree was 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 imports segment_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.py was already red at 3712524 — index-current and append-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/.