fix(r3): fold heid's bug hunt — no link offers a pair that 404s, NUL booth names, a FIFO marker, encoded view-state names

Navigation was built from the review ring while the compare GET also demands
containment, so an outside symlink (which stays in the ring) was offered by
the strip, the steps, the review's Compare control and the flag landing, and
404ed on arrival. Every one is now built from the compare ring (the review
ring filtered by the same conjunction, _in_booth).

Two pre-existing gaps compare inherits, fixed at the source: resolve_booth
caught only OSError, so a NUL in the booth segment was a 500; record_view
opened its marker blocking, so a planted FIFO hung every look. Plus: the page
treats %73ide=a as side=a, and the subgrid engine floor is stated. Two
findings refuted (a chorded click mid-drag never fires pointerup, measured;
booth_items never yields an unquotable rel). r3.toml: 57 rows.
This commit is contained in:
vh
2026-09-24 14:39:06 -07:00
parent 23f1bdb41f
commit f8d136a521
7 changed files with 176 additions and 40 deletions
+53 -14
View File
@@ -10,11 +10,9 @@ label = "C1 the conjunction loses containment (an outside symlink in the ring op
file = "booth/app.py"
test = "tests/test_compare.py::test_an_outside_symlink_in_the_ring_is_404"
old = '''
if not str(target).startswith(str(booth) + os.sep) or not target.is_file():
raise HTTPException(status_code=404, detail="no such item")
if rel not in ring:'''
return str(target).startswith(str(booth) + os.sep) and target.is_file()'''
new = '''
if rel not in ring:'''
return target.is_file()'''
[[mutation]]
label = "C1 the conjunction loses the ring (a doc or a sidecar opens as a side)"
@@ -50,14 +48,12 @@ test = "tests/test_compare.py::test_a_look_records_both_seen"
old = '''
booth = resolve_booth(name)
items = booth_items(booth)
ring = review_chain(items)
a = _compare_side(booth, ring, a)'''
a = _compare_side(booth, review_chain(items), a)'''
new = '''
booth = resolve_booth(name)
record_view(booth)
items = booth_items(booth)
ring = review_chain(items)
a = _compare_side(booth, ring, a)'''
a = _compare_side(booth, review_chain(items), a)'''
[[mutation]]
label = "C6 compare does not carry data-booth (Reveal all and its restore bail)"
@@ -118,8 +114,8 @@ new = ''' side_a = side not in ("", "b")'''
label = "C2 the review's Compare does not wrap (the last item compares with itself)"
file = "booth/app.py"
test = "tests/test_compare.py::test_the_review_offers_compare_with_the_next_item"
old = ''' f"&b={quote(ring[(pos + 1) % len(ring)], safe='/')}"),'''
new = ''' f"&b={quote(ring[min(pos + 1, len(ring) - 1)], safe='/')}"),'''
old = ''' f"&b={quote(cring[(cring.index(f) + 1) % len(cring)], safe='/')}"),'''
new = ''' f"&b={quote(cring[min(cring.index(f) + 1, len(cring) - 1)], safe='/')}"),'''
# ---- C5: the regions and the JS-off flag landing
@@ -480,11 +476,9 @@ label = "C1 containment is a bare prefix (a sibling booth sharing the name opens
file = "booth/app.py"
test = "tests/test_compare.py::test_an_outside_symlink_in_the_ring_is_404"
old = '''
if not str(target).startswith(str(booth) + os.sep) or not target.is_file():
raise HTTPException(status_code=404, detail="no such item")'''
return str(target).startswith(str(booth) + os.sep) and target.is_file()'''
new = '''
if not str(target).startswith(str(booth)) or not target.is_file():
raise HTTPException(status_code=404, detail="no such item")'''
return str(target).startswith(str(booth)) and target.is_file()'''
[[mutation]]
label = "C1 the strip is not in ring order"
@@ -511,6 +505,51 @@ test = "tests/test_compare_browser.py::test_a_flags_A_in_place_and_the_stages_su
old = '''<a class="film-f{% if x.flagged %} is-flagged{% endif %}'''
new = '''<a class="film-f'''
# ---- the heid bug-hunt fold (01M3ANEPHTDMPP4Q18Z075181W)
[[mutation]]
label = "C1 navigation is built from the review ring (it offers an outside symlink that 404s)"
file = "booth/app.py"
test = "tests/test_compare.py::test_no_navigation_offers_a_pair_that_404s"
old = '''
return [r for r in review_chain(items) if _in_booth(booth, r)]'''
new = '''
return list(review_chain(items))'''
[[mutation]]
label = "a NUL in the booth segment is a 500 (ValueError is not an OSError)"
file = "booth/app.py"
test = "tests/test_compare.py::test_hostile_booth_names_are_404_not_500"
old = '''
resolved = candidate.resolve()
except (OSError, ValueError):'''
new = '''
resolved = candidate.resolve()
except OSError:'''
[[mutation]]
label = "a FIFO planted at .viewed hangs the look (a blocking open)"
file = "booth/app.py"
test = "tests/test_compare.py::test_a_planted_fifo_marker_cannot_hang_a_look"
old = '''os.O_WRONLY | os.O_CREAT | os.O_NOFOLLOW | os.O_NONBLOCK, 0o644)'''
new = '''os.O_WRONLY | os.O_CREAT | os.O_NOFOLLOW, 0o644)'''
[[mutation]]
label = "C2 an encoded view-state name survives the rewrite (%73ide=a outlives X)"
file = "booth/templates/compare.html"
test = "tests/test_compare_browser.py::test_an_encoded_view_state_name_is_still_view_state"
old = '''
return p && n !== 'side' && n !== 'link';'''
new = '''
return p && !/^(side|link)(=|$)/.test(p);'''
# Refuted, not rowed (bug hunt): "a right-click mid-drag ends the pan" — a
# second button pressed and released during a drag arrives as chorded
# `pointermove` events, never a `pointerup` (measured 3/3 in the test browser,
# the pan continuing each time). "A non-UTF-8 name 500s compare in `quote()`" —
# booth_items never yields a rel that quote() cannot encode (test_flow's
# test_ordinals_count_rendered_items_only).
#
# Accepted, not rowed: the mode's `onChange: settleAll` (re-deciding which stage
# can pan) is redundant with compare's ResizeObserver — 1:1 drops the stage's
# padding, so every mode change resizes the stage's content box and the