fix(u6): fold the cold bug-hunt panel — a div in a span, a symlink split, and an append outside its lock

/heid-bug-hunt panel 01M35CRRK2RTVWWF1BN09AFQG3, diff-scoped against 91fd8bc.
The most severe of the three rounds, and three of its four convergent findings
were already closed by our own adversarial pass before the reply landed. Three
were not.

- The benches panel was nested inside the booth header's <span class="sub">.
  The insertion had matched the first `{% if board %}` in the template rather
  than the block-level one. A div inside a span is invalid HTML: the parser
  closes the span implicitly and hoists the div out, orphaning the rest of the
  sub-line. Nothing 500s, which is precisely why no test in this suite could
  see it. Moved to block level, pinned by an offset assertion, and verified
  with a real HTML parser.

- _booth_exists used a bare is_dir() while resolve_booth resolves and requires
  the parent to BE the data root. They disagreed on a symlink: the marker
  called a booth pointing outside the root alive while the page 404s it, so the
  row rendered healthy and the link was dead. Same containment now, and
  ValueError joins OSError in the guard -- one bad row must never cost the
  other 220.

- The board append opened its fd OUTSIDE the lock. `flock LOCK printf ... >>
  board` reads as locked and is not: the shell opens the append fd while
  parsing, before flock acquires. A concurrent unlink replaces the inode via
  os.replace, the old fd still points at the unlinked one, and the append
  succeeds, reports success, and vanishes. Pre-existing rather than this
  unit's, but it is silent data loss in the file this unit lives in. Proved by
  holding the lock and asserting nothing is written.

- The atomic write used a predictable .tmp.<pid> name; a pre-planted symlink
  there redirects the write straight through the replace. mkstemp with O_EXCL
  in the same directory, and an fsync before the replace -- os.replace orders
  the rename, not the data behind it.

Declined and recorded: on a host where booth.links cannot be imported, `booth
link` now refuses every URL rather than only booth ones. True, and kept. A
guard that fails open is not a guard, and that state is a broken install in
which most of the CLI is equally broken.

The sharpest line in the reply is one three arms found independently: this repo
had ALREADY paid for the RecursionError class in marks.py, and the new module
re-introduced the unguarded parse. Reading the new module in isolation would
never have surfaced that.

604 -> 607 tests.
This commit is contained in:
vh
2026-09-22 14:20:06 -07:00
parent e3853e2692
commit 8cb21193dc
7 changed files with 220 additions and 55 deletions
+11 -2
View File
@@ -1008,8 +1008,17 @@ def create_app(
178 of 221 rows today, on the ONE booth that carries a links.md.
"""
try:
return (data_dir / name).is_dir()
except OSError:
candidate = (data_dir / name).resolve()
# THE SAME CONTAINMENT `resolve_booth` ENFORCES. Without it the two
# disagree on a symlink: the marker would call a booth pointing
# outside the data root ALIVE while the page 404s it, so the row
# renders healthy and the link is dead — the worst of both, and
# invisible. 3-of-4 cold bug-hunt arms found the disagreement.
return candidate.parent == data_dir and candidate.is_dir()
except (OSError, ValueError):
# ValueError, not only OSError: an embedded NUL raises it rather
# than an OSError, and this predicate runs once per board row — one
# bad row must never cost the other 220.
return False
def _mark_redirect(name: str, form, anchor: str) -> RedirectResponse:
+17 -2
View File
@@ -27,6 +27,7 @@ import fcntl
import json
import os
import stat
import tempfile
from dataclasses import dataclass, replace
from datetime import datetime, timezone
from pathlib import Path
@@ -299,9 +300,23 @@ def _write_all(root: Path, benches: dict[str, Bench]) -> None:
raise ValueError(
f"that registration would push the registry past {BENCHES_MAX_BYTES} "
f"bytes, which its own reader refuses; nothing was written")
tmp = path.with_suffix(path.suffix + f".tmp.{os.getpid()}")
# AN UNPREDICTABLE SCRATCH NAME, IN THE SAME DIRECTORY. `.tmp.<pid>` is
# guessable, and a pre-planted symlink there redirects the write straight
# through the atomic replace — the replace is atomic, not safe. mkstemp
# creates with O_EXCL and 0600, so it cannot land on someone else's file.
# Same directory because os.replace is only atomic within a filesystem.
fd, tmpname = tempfile.mkstemp(dir=str(root), prefix=".benches-", suffix=".tmp")
tmp = Path(tmpname)
try:
tmp.write_text(body)
with os.fdopen(fd, "w", encoding="utf-8") as fh:
fh.write(body)
fh.flush()
# FSYNC BEFORE THE REPLACE. os.replace orders the rename, not the
# DATA behind it: without this, a power loss can publish a name
# pointing at bytes that never reached the disk, which is a
# truncated registry wearing a successful write's clothes.
os.fsync(fh.fileno())
os.chmod(tmp, 0o644) # mkstemp's 0600 is tighter than the rest
os.replace(tmp, path)
except BaseException:
# A write that dies between create and replace would otherwise strand
+56 -48
View File
@@ -66,7 +66,62 @@
{% else %}
<h1>{{ name }}</h1>
{% endif %}
<span class="sub">{% if uploaded %}<span class="badge">⬆ pickup</span> {% endif %}{% if is_board %}
<span class="sub">{% if uploaded %}<span class="badge">⬆ pickup</span> {% endif %}{% if board %}{{ board|length }} link{{ '' if board|length == 1 else 's' }}{% if items %} · {{ items|length }} file{{ '' if items|length == 1 else 's' }}{% endif %} · {{ lifetime(kept, hold, expires_in) }}{% else %}{% if marks_open %}<span class="badge badge-mark">{{ marks_open }} open</span> · {% endif %}{{ items|length }} item{{ '' if items|length == 1 else 's' }} · {{ lifetime(kept, hold, expires_in) }}{% endif %}</span>
{% if items %}<a class="dl-link" href="/b/{{ name_url }}/?download=1" title="download this booth as a zip">⬇ zip</a>{% endif %}
{{ provenance(manifest) }}
{# A durable multi-writer board gets no one-click wipe — same rule as the
kept lane on the index. Remove rows with the per-row ×, or release the
board from the index and wipe it from there. #}
{# Promote or release without going back to the index. `next` keeps you on
this page instead of bouncing you to /. #}
{% if kept %}
<form class="keep-lg" method="post" action="/b/{{ name_url }}/unkeep">
<input type="hidden" name="next" value="/b/{{ name_url }}/">
<button title="release — rejoins the TTL sweep">★ kept — release</button>
</form>
{% else %}
<form class="keep-lg" method="post" action="/b/{{ name_url }}/keep">
<input type="hidden" name="next" value="/b/{{ name_url }}/">
<button title="keep — exempt from the TTL sweep">☆ keep</button>
</form>
{% endif %}
{% if not board %}
<form class="wipe wipe-lg" method="post" action="/b/{{ name_url }}/delete"
onsubmit="return confirm('Wipe this booth now?')">
<button>Wipe now</button>
</form>
{% endif %}
</div>
{% if uploaded %}
<div class="pickup-note">
📦 Pickup <code>{{ name }}</code>
<button type="button" class="copy-btn" data-copy="{{ name }}" title="copy id to clipboard">⧉ copy</button>
— download files below, or on nh3-dev grab <code>~/booth-data/{{ name }}/</code>
</div>
{% endif %}
{# The marks panel: the session's questions, the operator's notes, and the way
back to the flagged items. Always rendered on a gallery booth — the add-note
field is a control, not a result, so it has to be there before the first
mark exists. #}
{# `marks or not board`: the standing link board renders as a board rather than
a gallery, and the add-note control would be noise on it — but the
suppression was unconditional, so a pick declared on a booth that happens to
carry a links.md had no form to answer it and nothing said so. #}
{% if marks or not board %}
{% include "_marks.html" %}
{% endif %}
{# THE BENCH REGISTRY — BLOCK LEVEL, and that placement is load-bearing.
This <div> spent one commit nested inside the `<span class="sub">` of the
booth header, because the insertion matched the FIRST `{% if board %}` in
the file rather than the block-level one. A <div> inside a <span> is
invalid HTML: the parser closes the span implicitly and hoists the div
out, orphaning the rest of the sub-line. Three of four cold bug-hunt arms
found it and the seat confirmed it in the live document by byte offset.
Keep this block between the marks panel and the board form. #}
{% if is_board %}
{# THE BENCH REGISTRY. A bench is a running thing — jackdaw's current bench,
talk's current bench, the things that get promoted to Homepage when they
are fully deployed. NOT a booth (a booth announces itself and is swept) and
@@ -127,53 +182,6 @@
</div>
{% endif %}
{% if board %}{{ board|length }} link{{ '' if board|length == 1 else 's' }}{% if items %} · {{ items|length }} file{{ '' if items|length == 1 else 's' }}{% endif %} · {{ lifetime(kept, hold, expires_in) }}{% else %}{% if marks_open %}<span class="badge badge-mark">{{ marks_open }} open</span> · {% endif %}{{ items|length }} item{{ '' if items|length == 1 else 's' }} · {{ lifetime(kept, hold, expires_in) }}{% endif %}</span>
{% if items %}<a class="dl-link" href="/b/{{ name_url }}/?download=1" title="download this booth as a zip">⬇ zip</a>{% endif %}
{{ provenance(manifest) }}
{# A durable multi-writer board gets no one-click wipe — same rule as the
kept lane on the index. Remove rows with the per-row ×, or release the
board from the index and wipe it from there. #}
{# Promote or release without going back to the index. `next` keeps you on
this page instead of bouncing you to /. #}
{% if kept %}
<form class="keep-lg" method="post" action="/b/{{ name_url }}/unkeep">
<input type="hidden" name="next" value="/b/{{ name_url }}/">
<button title="release — rejoins the TTL sweep">★ kept — release</button>
</form>
{% else %}
<form class="keep-lg" method="post" action="/b/{{ name_url }}/keep">
<input type="hidden" name="next" value="/b/{{ name_url }}/">
<button title="keep — exempt from the TTL sweep">☆ keep</button>
</form>
{% endif %}
{% if not board %}
<form class="wipe wipe-lg" method="post" action="/b/{{ name_url }}/delete"
onsubmit="return confirm('Wipe this booth now?')">
<button>Wipe now</button>
</form>
{% endif %}
</div>
{% if uploaded %}
<div class="pickup-note">
📦 Pickup <code>{{ name }}</code>
<button type="button" class="copy-btn" data-copy="{{ name }}" title="copy id to clipboard">⧉ copy</button>
— download files below, or on nh3-dev grab <code>~/booth-data/{{ name }}/</code>
</div>
{% endif %}
{# The marks panel: the session's questions, the operator's notes, and the way
back to the flagged items. Always rendered on a gallery booth — the add-note
field is a control, not a result, so it has to be there before the first
mark exists. #}
{# `marks or not board`: the standing link board renders as a board rather than
a gallery, and the add-note control would be noise on it — but the
suppression was unconditional, so a pick declared on a booth that happens to
carry a links.md had no form to answer it and nothing said so. #}
{% if marks or not board %}
{% include "_marks.html" %}
{% endif %}
{% if board %}
{# THE STANDING LINK BOARD. Every agent session on the fleet appends here, so
this is the one booth where the useful granularity is the ROW, not the