groa's late retry on the blur bug-hunt, adjudicated against the landed code. Its four bugs were already fixed, but a robustness note (mkstemp's 0600 locks out a reader under another uid, which then "sees nothing and replaces it") pointed at a real gap. set_blurred built on read_blurred, the renderer's lenient reader, which turns an unreadable, oversized or malformed `.blurred.json` into an empty set. The writer then replaced the file, and whatever it held was gone. This is the `.marks.json` wipe of 2026-09-21 in a new module, and it shipped for a night. - `_load` is the one parse with two postures. read_blurred maps its refusal to "nothing blurred" (a damaged file costs the blur, never the page). set_blurred lets it raise BlurUnwritable, which the route answers with 409 and the CLI with exit 3, and changes nothing. - It refuses only for a REGULAR file it cannot read. A link, a directory or a FIFO at either name holds no set anyone wrote, so it reads as empty, and the postcondition judges whether the write can land: a link is replaced, a directory refused. - The file is 0644 again, as the line-format writer left it (fchmod after mkstemp). The open flags in `_read_capped` became a second layer behind the new lstat check, and the mutation run caught their rows VACUOUS through the public API. They are now held to account by direct tests, because they still close the lstat-to-open race. blur_storage.toml: 25/25. No second panel was run: this folds one reviewer note plus the repo's own recorded lesson, with a test and a proved row for each behaviour.
261 lines
8.8 KiB
TOML
261 lines
8.8 KiB
TOML
# Per-item blur storage: `.blurred` round-trips any rel, whoever writes it.
|
|
# The fix for the wrong-item write the heid bug-hunt found through r2b merge 1
|
|
# (a stripped rel blurred its neighbour), operator-ruled 2026-09-23. Every row
|
|
# is a change tests/test_blur.py claims to forbid.
|
|
#
|
|
# NOT here, on purpose: the S_ISREG guard in read_blurred. With O_NONBLOCK a
|
|
# FIFO opens and reads as EOF, a symlink is already refused by O_NOFOLLOW, and a
|
|
# device node needs root to plant, so no test here can see that guard go. It
|
|
# stays as the `.seen` shape, and it is not claimed as a proven falsifier.
|
|
|
|
unit = "blur storage round-trip"
|
|
|
|
[[mutation]]
|
|
label = "the writer strips the rel (the old line format's loss)"
|
|
file = "booth/blur.py"
|
|
test = "tests/test_blur.py::test_a_leading_space_rel_round_trips"
|
|
old = '''
|
|
current.add(rel)'''
|
|
new = '''
|
|
current.add(rel.strip())'''
|
|
|
|
[[mutation]]
|
|
label = "the route strips `f` before writing (the reported wrong-item write)"
|
|
file = "booth/app.py"
|
|
test = "tests/test_blur.py::test_the_blur_route_blurs_exactly_the_item_it_names"
|
|
old = '''
|
|
rel = f.lstrip("/")'''
|
|
new = '''
|
|
rel = f.strip().lstrip("/")'''
|
|
|
|
[[mutation]]
|
|
label = "a JSON-only reader: every live line-format file un-blurs on deploy"
|
|
file = "booth/blur.py"
|
|
test = "tests/test_blur.py::test_the_legacy_line_format_still_reads"
|
|
old = '''
|
|
return {ln.strip() for ln in text.splitlines() if ln.strip()}'''
|
|
new = '''
|
|
return set()'''
|
|
|
|
[[mutation]]
|
|
label = "a legacy file that is not JSON reads as nothing instead of falling back"
|
|
file = "booth/blur.py"
|
|
test = "tests/test_blur.py::test_a_legacy_rel_that_starts_with_a_bracket_still_reads"
|
|
old = '''
|
|
if path is legacy:
|
|
return {ln.strip()'''
|
|
new = '''
|
|
if path is legacy:
|
|
try:
|
|
json.loads(text)
|
|
except ValueError:
|
|
return set()
|
|
return {ln.strip()'''
|
|
|
|
[[mutation]]
|
|
label = "a FIFO blocks the read (no O_NONBLOCK)"
|
|
file = "booth/blur.py"
|
|
test = "tests/test_blur.py::test_the_raw_read_never_blocks_on_a_fifo"
|
|
old = '''
|
|
fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK)'''
|
|
new = '''
|
|
fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)'''
|
|
|
|
[[mutation]]
|
|
label = "the read follows a planted symlink (no O_NOFOLLOW)"
|
|
file = "booth/blur.py"
|
|
test = "tests/test_blur.py::test_the_raw_read_never_follows_a_link"
|
|
old = '''
|
|
fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK)'''
|
|
new = '''
|
|
fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK)'''
|
|
|
|
[[mutation]]
|
|
label = "the write goes through a planted symlink instead of replacing it"
|
|
file = "booth/blur.py"
|
|
test = "tests/test_blur.py::test_a_write_replaces_a_planted_symlink_rather_than_writing_through_it"
|
|
old = '''
|
|
os.replace(tmp, path)'''
|
|
new = '''
|
|
path.write_bytes(Path(tmp).read_bytes()); os.unlink(tmp)'''
|
|
|
|
[[mutation]]
|
|
label = "the stored order is not the stated one (invariant 6)"
|
|
file = "booth/blur.py"
|
|
test = "tests/test_blur.py::test_the_file_is_a_json_array_in_sorted_order"
|
|
old = '''
|
|
body = json.dumps(sorted(current), ensure_ascii=False)'''
|
|
new = '''
|
|
body = json.dumps(sorted(current, reverse=True), ensure_ascii=False)'''
|
|
|
|
[[mutation]]
|
|
label = "the CLI ignores the verb: `unblur` blurs"
|
|
file = "scripts/booth"
|
|
test = "tests/test_blur.py::test_the_cli_writes_the_format_the_service_reads"
|
|
old = '''
|
|
on = sys.argv[1] == "blur"'''
|
|
new = '''
|
|
on = True'''
|
|
|
|
[[mutation]]
|
|
label = "the CLI writes past a refused '..' path (the shared predicate loses its component check)"
|
|
file = "booth/blur.py"
|
|
test = "tests/test_blur.py::test_the_cli_still_refuses_a_dotdot_path"
|
|
old = '''
|
|
if not rel or rel.startswith("/") or ".." in rel.split("/"):'''
|
|
new = '''
|
|
if not rel or rel.startswith("/"):'''
|
|
|
|
[[mutation]]
|
|
label = "the item's own blur is the composed one (booth fog leaks into it)"
|
|
file = "booth/items.py"
|
|
test = "tests/test_blur.py::test_the_item_record_carries_its_own_blur_apart_from_the_booths"
|
|
old = '''
|
|
blurred_self=rel in blurred,'''
|
|
new = '''
|
|
blurred_self=rel in blurred or booth_blur,'''
|
|
|
|
[[mutation]]
|
|
label = "app.py reads the blur file a second time (invariant 3)"
|
|
file = "booth/app.py"
|
|
test = "tests/test_blur.py::test_app_py_never_reads_the_blur_file_itself"
|
|
old = '''
|
|
out = []
|
|
for it in booth_items(child):'''
|
|
new = '''
|
|
out = []
|
|
read_blurred(child)
|
|
for it in booth_items(child):'''
|
|
|
|
# ---- the heid bug-hunt on this change (hulda, regin, kimi), folded -------------
|
|
|
|
[[mutation]]
|
|
label = "the legacy file is sniffed for JSON again (a `[\"a.png\"]` line blurs the neighbour)"
|
|
file = "booth/blur.py"
|
|
test = "tests/test_blur.py::test_a_legacy_line_that_is_valid_json_still_reads_as_a_line"
|
|
old = '''
|
|
if path is legacy:
|
|
return {ln.strip()'''
|
|
new = '''
|
|
if path is legacy:
|
|
try:
|
|
d = json.loads(text)
|
|
if isinstance(d, list):
|
|
return {r for r in d if isinstance(r, str)}
|
|
except ValueError:
|
|
pass
|
|
return {ln.strip()'''
|
|
|
|
[[mutation]]
|
|
label = "no postcondition: a planted directory's OSError is swallowed as success"
|
|
file = "booth/blur.py"
|
|
test = "tests/test_blur.py::test_a_planted_directory_at_the_blur_file_is_a_refusal_not_a_crash"
|
|
old = '''
|
|
if read_blurred(booth) != current:'''
|
|
new = '''
|
|
if False:'''
|
|
|
|
[[mutation]]
|
|
label = "the route turns a disk-state refusal into a 500"
|
|
file = "booth/app.py"
|
|
test = "tests/test_blur.py::test_the_route_answers_a_planted_directory_with_409"
|
|
old = '''
|
|
raise HTTPException(status_code=409, detail=str(exc))'''
|
|
new = '''
|
|
raise'''
|
|
|
|
[[mutation]]
|
|
label = "a lone surrogate from a planted file reaches the writer"
|
|
file = "booth/blur.py"
|
|
test = "tests/test_blur.py::test_a_lone_surrogate_in_the_file_is_skipped_and_writes_still_work"
|
|
old = '''
|
|
return {r for r in data if isinstance(r, str) and r and _encodable(r)}'''
|
|
new = '''
|
|
return {r for r in data if isinstance(r, str) and r}'''
|
|
|
|
[[mutation]]
|
|
label = "the writer writes a set the reader would refuse and read as nothing"
|
|
file = "booth/blur.py"
|
|
test = "tests/test_blur.py::test_the_writer_never_writes_a_set_the_reader_would_refuse"
|
|
old = '''
|
|
if len(body) > BLUR_MAX_BYTES:'''
|
|
new = '''
|
|
if False:'''
|
|
|
|
[[mutation]]
|
|
label = "an empty item path is accepted and stored"
|
|
file = "booth/blur.py"
|
|
test = "tests/test_blur.py::test_the_cli_refuses_an_empty_item_path_before_writing"
|
|
old = '''
|
|
if not rel or rel.startswith("/") or ".." in rel.split("/"):'''
|
|
new = '''
|
|
if rel.startswith("/") or ".." in rel.split("/"):'''
|
|
|
|
[[mutation]]
|
|
label = "a double dot INSIDE a name is refused (the old `*..*` substring rule)"
|
|
file = "booth/blur.py"
|
|
test = "tests/test_blur.py::test_the_cli_accepts_a_double_dot_inside_a_name"
|
|
old = '''
|
|
if not rel or rel.startswith("/") or ".." in rel.split("/"):'''
|
|
new = '''
|
|
if not rel or rel.startswith("/") or ".." in rel:'''
|
|
|
|
[[mutation]]
|
|
label = "the CLI dies with a traceback when its package is missing"
|
|
file = "scripts/booth"
|
|
test = "tests/test_blur.py::test_the_cli_fails_closed_without_its_package"
|
|
old = '''
|
|
except ImportError as exc:
|
|
src = os.environ["BOOTH_SRC"]'''
|
|
new = '''
|
|
except ZeroDivisionError as exc:
|
|
src = os.environ["BOOTH_SRC"]'''
|
|
|
|
# ---- reads lenient, writes strict (groa's retry; the .marks.json lesson) -------
|
|
|
|
[[mutation]]
|
|
label = "the writer builds on the lenient reader (an unreadable set is overwritten)"
|
|
file = "booth/blur.py"
|
|
test = "tests/test_blur.py::test_an_unreadable_blur_file_is_never_overwritten"
|
|
old = '''
|
|
current = _load(booth)'''
|
|
new = '''
|
|
current = read_blurred(booth)'''
|
|
|
|
[[mutation]]
|
|
label = "an unreadable or oversized regular file reads as empty for the writer"
|
|
file = "booth/blur.py"
|
|
test = "tests/test_blur.py::test_an_oversized_blur_file_is_never_overwritten"
|
|
old = '''
|
|
raise BlurUnwritable(f"{path.name} in {booth.name!r} is not a readable file of sane size")'''
|
|
new = '''
|
|
return set()'''
|
|
|
|
[[mutation]]
|
|
label = "a malformed set reads as empty for the writer"
|
|
file = "booth/blur.py"
|
|
test = "tests/test_blur.py::test_a_malformed_blur_file_is_never_overwritten"
|
|
old = '''
|
|
raise BlurUnwritable(f"{BLUR_FILE} in {booth.name!r} is not JSON") from exc'''
|
|
new = '''
|
|
return set()'''
|
|
|
|
[[mutation]]
|
|
label = "the set is written 0600 (mkstemp's default)"
|
|
file = "booth/blur.py"
|
|
test = "tests/test_blur.py::test_the_blur_file_is_world_readable_as_it_always_was"
|
|
old = '''
|
|
os.fchmod(fd, 0o644)'''
|
|
new = '''
|
|
pass'''
|
|
|
|
[[mutation]]
|
|
label = "a link or a FIFO at the name blocks the writer instead of reading as no set"
|
|
file = "booth/blur.py"
|
|
test = "tests/test_blur.py::test_a_write_replaces_a_planted_symlink_rather_than_writing_through_it"
|
|
old = '''
|
|
if not stat.S_ISREG(st.st_mode):
|
|
return set()'''
|
|
new = '''
|
|
pass'''
|