The standing link board carried three jobs because only one of them had a surface. Re-measured before contracting, its 221 rows split into 178 booth announcements (156 already dead) and 43 non-booth rows, of which 8 are the same bench re-posted. U5 gave the booth announcement a home; this gives the running service one, and refuses the one shape that now has somewhere better to go. - booth/benches.py (new, stdlib-only and sibling-free): the Bench record, URL normalization as the identity, a lenient read on the render path and a strict read on the write path, atomic replace under an flock, and a stated total order (state rank, name casefolded, id). - links.booth_target: ONE predicate for "is this a booth URL", consumed by the CLI refusal, the board's dead marker and bench import. Host-agnostic, path-shaped, percent-decoded, never raises. - booth link refuses a booth URL, names `booth new --why`, and writes nothing — not the row, not the board directory, not the announcement. - The board marks rows whose booth has been swept. Nothing here deletes a row: removal stays the operator's two clicks through the existing bulk control. - booth bench add|ls|state|rm|import. import writes nothing without --apply and never edits links.md. - docs/archive/links-2026-09-22.md: the board archived verbatim into git. Identity is the FULL normalized URL, not the origin, and that was measured: origin identity collapses the 43 non-booth rows to 19 groups by merging eight distinct gitea repositories into one row, three unrelated HuggingFace model cards into one, and the two LRPG surfaces on 10.100.10.50:8321 — the design doc's own example of two real benches — into one. Full-URL identity still collapses both cases that doc names: talk 5 to 1, Peedlar 3 to 1. booth link is NOT deprecated. Roughly 14 of the 35 distinct non-booth targets are reference bookmarks for which the board is the right and only home; the design doc's plan to deprecate it would have evicted a third of its live content. Corrected there, along with what "normalized URL" means. The seam review found three real defects in the contract before any code: the claim that test_stdlib_only already forbids sibling imports (it exempts `booth` on purpose), naming resolve_booth as the dead marker's existence check (it raises HTTPException(404), so one swept booth would have 404'd the whole board page), and silence on percent-encoding (booth links are emitted through quote(name, safe=""), so a raw comparison marks every encoded booth dead forever). That both list_booths and sweep_once skip the registry was verified against the real functions rather than assumed. 444 -> 555 tests. Deployed and verified live: 23/23 booths 200, and the board renders 156 dead of 221 rows, matching an independent pre-implementation count. NOT TAGGED: both cold gates are in flight (contract review 01M35BWCJ806MT75NA630Y4WFH, code review 01M35CK8YKEKMV7T15JXEF6A8N) and the bug-hunt has not run. Per the v0.2.0 lesson, the tag waits for the gates.
3.6 KiB
U6 landed — three surfaces, three jobs, one predicate
2026-09-22 · booth
The sixth of seven v1 units. Only U7 is left. 444 → 555 tests, suite green, deployed and verified live: 23/23 booths 200, and the board renders 156 dead of 221 rows — the exact count an independent shell measurement produced before a line of code was written, from two different implementations.
What shipped
booth/benches.py(new, stdlib-only AND sibling-free):Bench,normalize_bench_url, lenientread_benches, strictupsert_bench,set_bench_state,remove_bench,order_benches. Registry at~/booth-data/.benches.json— a dotfile at the DATA ROOT, keyed by id, so two rows with one identity are impossible by construction.links.booth_target— ONE predicate for "is this a booth URL", consumed by three callers (the CLI refusal, the board's dead marker,bench import). Host-agnostic and path-shaped; percent-decodes the name.booth linkrefuses a booth URL, namesbooth new --why, and writes nothing — not even the board directory.- The board marks dead rows. Removal stays the operator's two clicks through the bulk control that already existed. Nothing in the unit deletes a row.
booth bench add|ls|state|rm|import;importwrites nothing without--applyand never toucheslinks.md.docs/archive/links-2026-09-22.md— the board archived verbatim into git.
The decision that mattered most, and it was measured
Identity is the FULL normalized URL, not the origin. Collapsing the 43
non-booth rows by origin gives 19 groups; by full URL, 35. The difference is not
duplication — it is eight distinct gitea repos merged into one, three
unrelated HuggingFace model cards merged into one, and the two LRPG surfaces
on 10.100.10.50:8321, which are the IA doc's own example of two real benches,
merged into one. Origin identity destroys more than it dedups. Full-URL identity
still collapses both cases the doc names (talk 5→1, Peedlar 3→1).
Query is IN the identity (three ShutterChute rows differ only by ?token= and
are three real links); fragment is OUT; credentials are REFUSED, not stripped.
The seam review earned it again — three real contract defects
Run in-session against the real .py files, after the cold panel was dispatched:
- SR-1 — the contract claimed
test_stdlib_onlyalready forbids sibling imports. It does not: its failure set is{r for r in roots if r != "booth" and ...}, which exemptsboothon purpose. Only test_manifest.py has the strict copy. INV-9 would have shipped untested. - SR-2 — the contract named
resolve_boothas the dead marker's existence check. That function is a closure insidecreate_appand raises HTTPException(404) — per row, one swept booth would 404 the whole board page. - SR-7 — booth links are emitted through
quote(name, safe=""), so a predicate comparing the raw segment marks every encoded-name booth dead forever.
SR-4 and SR-5 were verified rather than assumed: both list_booths and
sweep_once skip a child that is not a directory AND one whose name starts with
a dot, so the registry is safe from the sweeper by two guards, not one. Had
either been absent the design would have eaten its own registry on tick one.
Still open at the time of writing
Both cold gates are IN FLIGHT — contract review 01M35BWCJ806MT75NA630Y4WFH,
code review 01M35CK8YKEKMV7T15JXEF6A8N. The bug-hunt has not run. Committed
but NOT tagged, per the v0.2.0 lesson: if a gate is outstanding, the tag waits.