# U3 amendment, 2026-09-27: one submit saves every ask on a verbatim report. # The operator answered three asks top to bottom, pressed the last button, and # the 303 reload wiped the first two (`auk-audition`, 15:02:23). Contract: # docs/contracts/u3_declared_embed_seam.contract.md, "Submitting several asks at # once" and INV-8. Every row is a change tests/test_embed_browser.py claims to # forbid. The flight-window rows came from the heid bug-hunt panel on the first # cut, where every guard of that window survived its mutation. unit = "u3 submit all" [[mutation]] label = "only the pressed form is sent (the reported defect)" file = "booth/static/embed.js" test = "tests/test_embed_browser.py::test_one_submit_saves_every_answered_ask_on_the_page[a3]" old = ''' var batch = forms.filter(dirty);''' new = ''' var batch = [form];''' [[mutation]] label = "the pressed form is sent whether or not it is dirty (a blank one 400s)" file = "booth/static/embed.js" test = "tests/test_embed_browser.py::test_a_blank_ask_is_skipped_never_refused" old = ''' var batch = forms.filter(dirty);''' new = ''' var batch = forms.filter(function (f) { return f === form || dirty(f); });''' [[mutation]] label = "every form is sent, touched or not (re-dates an answer nobody gave)" file = "booth/static/embed.js" test = "tests/test_embed_browser.py::test_an_ask_nobody_touched_is_not_re_sent" old = ''' var batch = forms.filter(dirty);''' new = ''' var batch = forms;''' [[mutation]] label = "a lone dirty form is intercepted instead of left to the browser" file = "booth/static/embed.js" test = "tests/test_embed_browser.py::test_one_changed_ask_still_submits_as_a_plain_form" old = ''' if (!others) return; // the browser's own POST and 303''' new = '''''' [[mutation]] label = "the forms are sent in reverse document order" file = "booth/static/embed.js" test = "tests/test_embed_browser.py::test_the_asks_are_sent_in_document_order" old = ''' var batch = forms.filter(dirty);''' new = ''' var batch = forms.filter(dirty).reverse();''' [[mutation]] label = "a refusal stops the forms after it" file = "booth/static/embed.js" test = "tests/test_embed_browser.py::test_a_refused_ask_costs_only_itself_and_clears_nothing" old = ''' return send(f, bodies[n]).then(function (why) {''' new = ''' if (failed.length) return; return send(f, bodies[n]).then(function (why) {''' [[mutation]] label = "a refusal reloads the page and clears what was entered" file = "booth/static/embed.js" test = "tests/test_embed_browser.py::test_a_refused_ask_costs_only_itself_and_clears_nothing" old = ''' if (!failed.length && !changed && !host) { location.reload(); return; }''' new = ''' location.reload(); return;''' [[mutation]] label = "only dirtiness blocks the reload (a refused form set back to its first value reloads over the failure)" file = "booth/static/embed.js" test = "tests/test_embed_browser.py::test_a_refusal_blocks_the_reload_even_when_nothing_reads_dirty" old = ''' if (!failed.length && !changed && !host) { location.reload(); return; }''' new = ''' if (!changed && !host) { location.reload(); return; }''' [[mutation]] label = "a refusal is never said" file = "booth/static/embed.js" test = "tests/test_embed_browser.py::test_a_refused_ask_costs_only_itself_and_clears_nothing" old = ''' st.hidden = false;''' new = '''''' [[mutation]] label = "an author's own form is taken over (no ownership check at the entry)" file = "booth/static/embed.js" test = "tests/test_embed_browser.py::test_an_authors_own_form_is_never_taken_over" old = ''' if (forms.indexOf(form) < 0) return;''' new = '''''' [[mutation]] label = "an author's form wearing our id prefix counts as ours (no mounted-root check)" file = "booth/static/embed.js" test = "tests/test_embed_browser.py::test_an_authors_own_form_is_never_taken_over" old = ''' if (ours[j].contains(all[i])) { out.push(all[i]); break; }''' new = ''' out.push(all[i]); break;''' [[mutation]] label = "a press during the flight falls through to the browser (a native POST races the batch)" file = "booth/static/embed.js" test = "tests/test_embed_browser.py::test_a_press_inside_the_flight_never_fires_a_native_post" old = ''' if (sending) { ev.preventDefault(); return; }''' new = '''''' [[mutation]] label = "a change made during the flight is reloaded away" file = "booth/static/embed.js" test = "tests/test_embed_browser.py::test_input_made_during_the_flight_is_kept_and_saved_on_the_next_press" old = ''' var changed = forms.some(dirty);''' new = ''' var changed = false;''' [[mutation]] label = "a saved form keeps its old baseline, so a retry re-sends (re-dates) it" file = "booth/static/embed.js" test = "tests/test_embed_browser.py::test_a_retry_after_a_refusal_sends_only_what_did_not_save" old = ''' if (why === null) f.__bkSaved = bodies[n].toString(); else failed.push''' new = ''' if (why !== null) failed.push''' [[mutation]] label = "the empty status line shows under a host `p{display:block}`" file = "booth/static/embed.js" test = "tests/test_embed_browser.py::test_the_empty_status_line_stays_hidden_under_host_css" old = ''' ".bk-ask-status[hidden]{display:none}",''' new = '''''' [[mutation]] label = "the reload ignores the report's own inputs (text typed into the host page is lost)" file = "booth/static/embed.js" test = "tests/test_embed_browser.py::test_a_clean_batch_does_not_reload_over_text_typed_into_the_report" old = ''' if (!failed.length && !changed && !host) { location.reload(); return; }''' new = ''' if (!failed.length && !changed) { location.reload(); return; }'''