fix(embed,mutation): SPYRJA fold — report input on every path; an instrument that cannot certify what it did not run

The heid bug-hunt (hulda, with heid's second voice) on 377e652 found eight
issues. Every fixed one has a red-first test.

embed.js:
- H1: the report-input guard covered only the batch path. A lone changed
  answer went out as a native POST, whose 303 navigation took the report's
  typed text with it. Unsaved report input now routes even a lone answer
  in place. With nothing of ours to send, the submit block says so.
- H7 / V1: a bare <select>, and a range or color input with no value
  attribute, read as typed-into by their default attributes, so every clean
  batch refused its reload with a false message. Report controls are now
  measured against how they stood when the Booth mounted. A control added
  later falls back to its defaults, counting a select's first option as its
  default.
- H2 / V2: a contenteditable region counts as report input.

scripts/mutation_check.py:
- H5: any non-zero exit counted as proof, including a collection error
  where the test never ran. Only pytest's "tests failed" (1) proves now.
- H6: the test run has a timeout (300 s). A hang reports "timed out" and
  the source is still restored.
- H3: source is read and restored as bytes, so a CRLF file comes back
  byte-exact.
- H4: one run per tree, enforced by a lock. The in-flight marker lives with
  the tree it guards.
- H8: anchors are counted with overlaps. The check is `matches()`, not
  str.count.

Tool controls +5 (tests/test_mutation_check.py). u3_submit_all +3 rows.
This commit is contained in:
vh
2026-09-28 17:49:13 -07:00
parent 377e652670
commit 213071b6ce
6 changed files with 295 additions and 23 deletions
+70
View File
@@ -142,3 +142,73 @@ def test_an_ambiguous_anchor_is_reported_not_guessed(tmp_path):
"old": " x = 2\n", "new": " x = 3\n"}, repo=repo)
assert not proved
assert "ambiguous" in note
# ---- SPYRJA (heid bug-hunt, hulda, 2026-09-28): the instrument's own edges --
def test_a_mutation_that_stops_the_test_running_is_not_a_proof(tmp_path):
"""A mutation that breaks collection (a syntax error) exits non-zero
without the assertion ever running. `rc != 0` certified that as PROVED:
the tool's one claim, that the test caught the change, was never tested."""
repo = _tree(tmp_path, "def f():\n return 2\n",
"def test_f():\n assert f() == 2\n")
proved, note = check(
{"label": "syntax", "file": "mod.py", "test": "test_probe.py::test_f",
"old": "return 2", "new": "return ("}, repo=repo)
assert not proved
assert "did not run" in note
def test_an_overlapping_anchor_is_ambiguous_too(tmp_path):
"""`str.count` counts NON-overlapping matches: `"aaa".count("aa") == 1`
while `aa` starts at two places. The ambiguity guard must see both."""
repo = _tree(tmp_path, 'def f():\n return len("aaa")\n',
"def test_f():\n assert f() == 3\n")
proved, note = check(
{"label": "overlap", "file": "mod.py", "test": "test_probe.py::test_f",
"old": "aa", "new": "b"}, repo=repo)
assert not proved
assert "ambiguous" in note
def test_a_crlf_source_is_restored_byte_for_byte(tmp_path):
"""Text-mode I/O read CRLF as LF and wrote LF back, then compared LF with
LF and called it restored — and reset the mtime so nothing looked touched."""
repo = _tree(tmp_path, "def f():\n return 2\n",
"def test_f():\n assert f() == 2\n")
mod = repo / "mod.py"
mod.write_bytes(b"def f():\r\n return 2\r\n")
before = mod.read_bytes()
check({"label": "flip", "file": "mod.py", "test": "test_probe.py::test_f",
"old": "return 2", "new": "return 3"}, repo=repo)
assert mod.read_bytes() == before
def test_a_mutation_that_hangs_is_stopped_and_the_source_restored(tmp_path):
"""No timeout meant a mutation that loops forever held the checker — and
the mutated file — until someone killed it by hand."""
repo = _tree(tmp_path, "def f():\n return 2\n",
"def test_f():\n assert f() == 2\n")
before = (repo / "mod.py").read_text()
proved, note = check(
{"label": "hang", "file": "mod.py", "test": "test_probe.py::test_f",
"old": "return 2", "new": "while True: pass"}, repo=repo, timeout=10)
assert not proved
assert "timed out" in note.lower()
assert (repo / "mod.py").read_text() == before
def test_a_second_run_is_refused_while_one_holds_the_repo(tmp_path, monkeypatch):
"""Two checkers interleaving on one tree can restore each other's
mutation back in. One run at a time, by lock."""
import fcntl
import mutation_check as mc
monkeypatch.setattr(mc, "REPO", tmp_path)
monkeypatch.setattr(mc, "INFLIGHT", tmp_path / ".mutation-inflight")
monkeypatch.setattr(mc, "TABLES", tmp_path / "tables")
(tmp_path / "tables").mkdir()
with open(tmp_path / ".mutation-lock", "w") as held:
fcntl.flock(held, fcntl.LOCK_EX | fcntl.LOCK_NB)
assert mc.main(["mutation_check.py"]) == 2