fix(u6): the booth check fails closed with a reason, and a dead write leaves no scratch

Two more from the in-session adversarial pass.

`booth link`'s new booth-URL check shells out to booth/links.py. When that
import cannot run, the command substitution under `set -e` aborted the script
with a bare ModuleNotFoundError traceback: the right DIRECTION (no row was
appended — a guard that fails open is not a guard) reached by accident, and
unactionable when it fires. Handled explicitly now: exit 3, and a message
naming what the check needs. The fail-closed direction is stated rather than
inherited from shell semantics, and a test pins it — the defeating change in
either direction goes red.

_write_all's scratch file was stranded beside the registry if the write died
between create and replace. Cleaned up on every exit path. The prior registry
was never at risk either way: os.replace is the only thing that publishes.

Also pins normalization idempotence, which `bench state <id|url>` and
`bench rm <id|url>` both rely on: they normalize whatever they are handed, so
an id that did not normalize to itself would miss the row it names.
This commit is contained in:
vh
2026-09-22 13:33:59 -07:00
parent 8c7f2127eb
commit 0a2bb1d26c
4 changed files with 84 additions and 3 deletions
+22
View File
@@ -506,3 +506,25 @@ def test_nothing_in_the_unit_touches_links_md(booth):
run(data, "bench", "rm", "http://new.test/")
after = hashlib.sha256((board / "links.md").read_bytes()).hexdigest()
assert before == after
def test_link_fails_CLOSED_when_the_booth_check_cannot_run(booth, tmp_path):
"""A guard that fails open is not a guard. If `booth.links` cannot be
imported, `booth link` must post NOTHING and say why — not append the row
it could not classify, and not abort with a bare traceback.
Defeating change: dropping the `|| pred_rc=$?` handling, which under
`set -e` aborts with a Python traceback (safe, but unactionable), or
treating a failed check as "not a booth" (unsafe — fails open)."""
data, _ = booth
lone = tmp_path / "lone" / "scripts"
lone.mkdir(parents=True)
(lone / "booth").write_text(SCRIPT.read_text())
(lone / "booth").chmod(0o755)
r = subprocess.run([str(lone / "booth"), "link", "https://ok.test/x", "a bookmark"],
capture_output=True, text=True, cwd="/tmp", timeout=30,
env={**os.environ, "BOOTH_DATA_DIR": str(data),
"BOOTH_URL": "http://booth.invalid"})
assert r.returncode != OK
assert "could not check" in r.stderr, r.stderr
assert not (data / "links" / "links.md").exists(), "a row landed despite an unusable check"