- Touch: on a coarse pointer every row control is at least 28px square again (32px), and wipe stands clear of the zip link. The move onto the facts line had dropped the deliberate 28px floor to ~21px, 4-6px from zip; with scripts off no confirm fires, so a mis-tap on wipe is the delete. The zip link no longer breaks between its glyph and its word, and each separator is glued to the item after it. - The wipe dialog shows the name as it should be read: control and bidi formatting characters in an agent-made name show as U+FFFD, so U+202E or a newline cannot rewrite what the operator approves. An unknown data-confirm word now prompts generically instead of submitting unguarded (fail closed). - No page scrolls sideways: `code` wraps anywhere, so a long unbreakable install path in the footer or the empty Desk no longer widens every page. The overflow test now sweeps 390/720/850/1000/1400 with the heaviest row the Desk draws, and compares scrollWidth with the page's own clientWidth. Its first fixture used a hyphenated path, which wrapped by itself; the test passed with the bug present until the path became one unbreakable run. r2_flow.toml: 27/27 proved. 749 passed.
246 lines
9.7 KiB
TOML
246 lines
9.7 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'''
|
|
|
|
[[mutation]]
|
|
label = "Desk row controls hidden until hover again (opacity 0 keeps their box)"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_a_rows_keep_release_and_wipe_take_no_room_of_their_own"
|
|
old = '''
|
|
.desk-facts form{display:inline;margin:0}'''
|
|
new = '''
|
|
.desk-facts form{display:inline;margin:0;opacity:0}'''
|
|
|
|
[[mutation]]
|
|
label = "Desk row renders its side column with no badge in it (a reserved gap)"
|
|
file = "booth/templates/index.html"
|
|
test = "tests/test_flow_browser.py::test_a_rows_keep_release_and_wipe_take_no_room_of_their_own"
|
|
old = '''
|
|
{% if b.marks_open or b.hold == "unreadable" or section == 'new' or b.uploaded %}
|
|
<div class="desk-side">'''
|
|
new = '''
|
|
{% if true %}
|
|
<div class="desk-side">'''
|
|
|
|
[[mutation]]
|
|
label = "the stacked Desk column is a bare 1fr (content sets its minimum)"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_the_desk_never_scrolls_sideways_at_any_width"
|
|
old = '''
|
|
@media (max-width:1000px){.desk{grid-template-columns:minmax(0,1fr)}}'''
|
|
new = '''
|
|
@media (max-width:1000px){.desk{grid-template-columns:1fr}}'''
|
|
|
|
[[mutation]]
|
|
label = "a long unbreakable install path in <code> scrolls the page sideways"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_the_desk_never_scrolls_sideways_at_any_width"
|
|
old = '''
|
|
padding:1px 6px;border-radius:var(--radius-sm);border:1px solid var(--border-subtle);overflow-wrap:anywhere}'''
|
|
new = '''
|
|
padding:1px 6px;border-radius:var(--radius-sm);border:1px solid var(--border-subtle)}'''
|
|
|
|
[[mutation]]
|
|
label = "the row's text column cannot shrink (the 700-1000px window overflows)"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_the_desk_never_scrolls_sideways_at_any_width"
|
|
old = '''
|
|
.desk-main{flex:1 1 auto;min-width:0}'''
|
|
new = '''
|
|
.desk-main{flex:1 1 auto}'''
|
|
|
|
[[mutation]]
|
|
label = "a coarse pointer gets the compact ~21px controls"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_on_a_touch_screen_the_row_controls_keep_their_tap_floor"
|
|
old = '''
|
|
.desk-facts form button{min-height:32px;min-width:32px;padding:0 10px;margin-left:6px;vertical-align:middle}'''
|
|
new = '''
|
|
.desk-facts form button{padding:0 10px;margin-left:6px;vertical-align:middle}'''
|
|
|
|
[[mutation]]
|
|
label = "the zip link may break between its glyph and its word"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_on_a_touch_screen_the_row_controls_keep_their_tap_floor"
|
|
old = '''
|
|
.desk-facts .dl-link{white-space:nowrap}'''
|
|
new = '''
|
|
.desk-facts .dl-link{}'''
|
|
|
|
[[mutation]]
|
|
label = "the wipe dialog shows the agent-made name raw (bidi, newline)"
|
|
file = "booth/templates/index.html"
|
|
test = "tests/test_flow_browser.py::test_the_wipe_dialog_shows_what_is_being_wiped_and_never_fails_open"
|
|
old = '''
|
|
if (!confirm(word(shown(form.getAttribute('data-booth') || '')))) ev.preventDefault();'''
|
|
new = '''
|
|
if (!confirm(word(form.getAttribute('data-booth') || ''))) ev.preventDefault();'''
|
|
|
|
[[mutation]]
|
|
label = "an unknown data-confirm word submits with no prompt (fail open)"
|
|
file = "booth/templates/index.html"
|
|
test = "tests/test_flow_browser.py::test_the_wipe_dialog_shows_what_is_being_wiped_and_never_fails_open"
|
|
old = '''
|
|
var word = WORDS[form.getAttribute('data-confirm')] || ASK;
|
|
if (!confirm('''
|
|
new = '''
|
|
var word = WORDS[form.getAttribute('data-confirm')];
|
|
if (word && !confirm('''
|