Files
booth/tests/mutations/r2_flow.toml
T
vh 39a3cb2262 test(r2): commit the round's falsifiers as a mutation table; one flag predicate
tests/mutations/r2_flow.toml: 18 falsifiers, each proved RED under its
change by scripts/mutation_check.py (18/18). Its first run found three
vacuous proofs, now resolved:
- landed_at's per-entry skip: the symlink-loop fixture stopped raising
  once the clock moved to lstat. New fixture: a folder that lists but
  cannot be searched.
- the Desk's bench URL guard: the test covered bookmarks only. A
  hand-edited registry bench now rides with it.
- flagged_targets' `error is None`: defence in depth (hydration already
  strips a damaged mark's target), so no single-guard row; named in the
  table header instead.

The rail's flagged filter and the orphan-flag list read flagged_targets
rather than restating it; no reachable behaviour changes.
2026-09-23 10:38:02 -07:00

161 lines
6.3 KiB
TOML

# R2 — the review flow: falsifiers the round claims, and the change each forbids.
#
# Every row was proved RED under its mutation in the session that wrote it,
# then committed here so the proof is an artifact rather than scrollback. The
# browser rows need the Playwright Chromium the browser tests already use.
#
# Deliberately ABSENT: single guards inside a defence in depth, each of which
# stays green when removed alone because another layer still holds — so a row
# for any one of them would be a vacuous proof, and this table's own first run
# said so. `.seen`'s O_NOFOLLOW, O_NONBLOCK and S_ISREG (the FIFO/symlink test
# covers them together); and `flagged_targets`' `error is None`, since
# hydration already strips the target from a damaged mark.
#
# The serialization row is only a falsifier because its test HOLDS the first
# refresh in the client: localhost alone never lost the race, and the first
# draft of that test stayed green with serialization deleted.
unit = "the Desk, the lightbox, the review, and the in-place client"
[[mutation]]
label = 'C1 ordinals count from 0, not 1'
file = "booth/items.py"
test = "tests/test_flow.py::test_a_filtered_tile_keeps_its_number_in_the_whole_set"
old = '''ordinal=len(items) + 1,'''
new = '''ordinal=len(items),'''
[[mutation]]
label = 'C2 .seen: a nested-too-deep marker escapes the never-raises read'
file = "booth/items.py"
test = "tests/test_flow.py::test_a_deeply_nested_seen_marker_reads_as_nothing_seen"
old = '''except (UnicodeDecodeError, ValueError, RecursionError):'''
new = '''except (UnicodeDecodeError, ValueError):'''
[[mutation]]
label = 'C3 a non-finite q is accepted as a q-value'
file = "booth/app.py"
test = "tests/test_flow.py::test_a_non_finite_q_is_malformed"
old = ''' raise ValueError("non-finite q")'''
new = ''' pass'''
[[mutation]]
label = 'C3 204 on an explicit JSON Accept becomes the 303'
file = "booth/app.py"
test = "tests/test_flow.py::test_an_explicit_json_accept_gets_204_and_the_write_still_lands"
old = ''' return Response(status_code=204)'''
new = ''' pass'''
[[mutation]]
label = 'C3 back=view lands on the review for a doc too (ring check dropped)'
file = "booth/app.py"
test = "tests/test_flow.py::test_back_view_lands_on_the_review_only_for_a_media_item"
old = ''' if f in ring:'''
new = ''' if True:'''
[[mutation]]
label = 'C4 the Desk counts orphan flags'
file = "booth/app.py"
test = "tests/test_flow.py::test_a_flag_on_a_file_that_is_gone_stays_visible_and_withdrawable"
old = '''"flags": len(flagged_targets(marks) & {it.rel for it in items}),'''
new = '''"flags": len(flagged_targets(marks)),'''
[[mutation]]
label = 'C4 landed_at follows symlinks'
file = "booth/app.py"
test = "tests/test_flow.py::test_the_content_clock_reads_the_booth_not_what_its_links_point_at"
old = ''' st = p.lstat()'''
new = ''' st = p.stat()'''
[[mutation]]
label = 'C4 one unreadable entry reads the whole booth as landed NOW'
file = "booth/app.py"
test = "tests/test_flow.py::test_one_unreadable_entry_costs_that_entry_not_the_booth"
old = '''pin it in "new" forever.
continue'''
new = '''pin it in "new" forever.
return time.time()'''
[[mutation]]
label = 'C4 a non-web bookmark URL becomes a link'
file = "booth/templates/index.html"
test = "tests/test_flow.py::test_the_desk_never_makes_a_non_web_url_clickable"
old = '''{% set web = e.url.lower().startswith(('http://', 'https://')) %}'''
new = '''{% set web = true %}'''
[[mutation]]
label = 'C4 a non-web bench URL becomes a link'
file = "booth/templates/index.html"
test = "tests/test_flow.py::test_the_desk_never_makes_a_non_web_url_clickable"
old = '''{% set web = b.url.lower().startswith(('http://', 'https://')) %}'''
new = '''{% set web = true %}'''
[[mutation]]
label = 'C5 audio/video tiles lose their review link'
file = "booth/templates/booth.html"
test = "tests/test_flow.py::test_a_sound_only_booth_can_open_the_review"
old = '''{% if it.kind in ('video', 'audio') %}<a class="rv-link"'''
new = '''{% if false %}<a class="rv-link"'''
[[mutation]]
label = 'C6 a NUL in ?f escapes as a 500'
file = "booth/app.py"
test = "tests/test_flow.py::test_a_nul_in_the_review_path_is_a_404_not_a_500"
old = ''' except (OSError, ValueError):
# ValueError: an embedded NUL.'''
new = ''' except OSError:
# ValueError: an embedded NUL.'''
[[mutation]]
label = 'C3 client: no busy guard (a double-click writes twice)'
file = "booth/templates/base.html"
test = "tests/test_flow_browser.py::test_an_unsaved_choice_survives_a_save_elsewhere_and_a_double_click_writes_once"
old = ''' if (form.__busy) return;
'''
new = ''''''
[[mutation]]
label = 'C3 client: an unsent radio is not carried across a swap'
file = "booth/templates/base.html"
test = "tests/test_flow_browser.py::test_an_unsaved_choice_survives_a_save_elsewhere_and_a_double_click_writes_once"
old = ''' if (el.checked !== el.defaultChecked) t.checked = el.checked;'''
new = ''''''
[[mutation]]
label = 'C3 client: saves are not serialized'
file = "booth/templates/base.html"
test = "tests/test_flow_browser.py::test_quick_successive_flags_all_show"
old = ''' queue = queue.then(function () { return run(form, data); })'''
new = ''' queue = run(form, data)'''
[[mutation]]
label = 'C3 the standalone marks page has no region'
file = "booth/templates/marks.html"
test = "tests/test_flow_browser.py::test_the_standalone_marks_page_updates_in_place"
old = '''<div class="marks-panel" data-region="marks-panel">'''
new = '''<div class="marks-panel">'''
[[mutation]]
label = "C6 the next arrow's rail offset applies at phone width"
file = "booth/templates/view.html"
test = "tests/test_flow_browser.py::test_the_next_arrow_clears_the_rail_only_beside_it"
old = ''' .vprev{left:0}.vnext{right:0}
@media (min-width:901px){.vnext{right:360px}}
'''
new = ''' .vprev{left:0}.vnext{right:360px}
'''
[[mutation]]
label = "resolver: an entry that cannot be stat'd raises out of booth_items"
file = "booth/items.py"
test = "tests/test_items.py::test_a_folder_that_lists_but_cannot_be_searched_costs_its_files_not_the_index"
old = '''
if not p.is_file():
continue
except OSError:
continue'''
new = '''
if not p.is_file():
continue
except FileNotFoundError:
continue'''