fix(blur): fold the heid bug-hunt: two file names, a reader-judged writer, one predicate

The heid bug-hunt panel on 4cfbce5 (hulda, regin, kimi; groa timed out) found
four real defects in the round-trip fix, and three of its arms converged on the
worst: it re-created the bug it existed to fix.

- Two names, never a sniffed file (3/3). JSON went into the OLD `.blurred`, and
  the reader guessed the format from the bytes, so a legacy file whose one line
  is an item named `["a.png"]` read as {"a.png"} and blurred the neighbour. The
  set now lives in `.blurred.json`, JSON only. The legacy `.blurred` is read as
  lines only, and only while `.blurred.json` is absent; the first write retires
  it, after the new file is in place.
- A planted directory is a 409, not a 500 (2/3 plus a third angle, executed by
  the seat). The reader was hardened against it and the writer was not:
  os.replace and unlink raised IsADirectoryError through the route. Now the
  writer is judged by its reader: set_blurred re-reads after writing and raises
  BlurUnwritable unless the set on disk is the set asked for. That one check
  covers a directory at either name, a permission and a race.
- A lone surrogate is dropped on read (hulda, executed). `"\ud800"` is a valid
  JSON string that no filename can produce, and the UTF-8 encode raised on it
  at every later write.
- The writer respects the reader's size cap (2/3). Nothing capped the write,
  and the reader reads an oversized file as EMPTY, which reveals everything.
- One predicate, check_rel, for the route and the CLI (2/3). The CLI's `*..*`
  substring guard refused `a..b.png`, which the route accepts. It also refuses
  an empty path now (regin, kimi), and every item is checked before any is
  written.
- `booth blur` fails closed, with a message and exit 3, when its package is
  missing (kimi), as `link` already does.

Declined, with reasons: the Item positional-constructor break (booth_items is
the only constructor, INV-1), the fdopen fd leak and the short read (not
constructible on a local filesystem, and the `.seen` shape), and
unreadable-reads-as-revealed (blur is cosmetic; the `.seen` posture).
blur_storage.toml: 20/20 proved. One row came back VACUOUS on its first run,
because `set() or X` is X, and was rewritten before counting.
This commit is contained in:
vh
2026-09-23 23:01:18 -07:00
parent 4cfbce5109
commit c1f5543b77
7 changed files with 494 additions and 134 deletions
+18 -9
View File
@@ -31,15 +31,24 @@ _As of 2026-09-23:_
1:1 with native image drag killed; the mode is remembered per viewer. Pan
offset across items is parked to r3 (compare). Then **r3, compare mode**:
ours is only the `booth_items` support he asks for.
- ✅ **`.blurred` ROUND-TRIPS ANY REL** (operator: "fix the blur"). A JSON array
via stdlib-only `booth/blur.py`, the one writer for both the service and
`booth blur`. The legacy line format is still read, and a write upgrades it.
The bug design-dev's bug-hunt found (a stripped rel blurring its neighbour)
had no live victims: 6 `.blurred` files, 42 rels, 0 with edge whitespace.
`Item.blurred_self` came along, so blur state has one reader (invariant 3).
**Still open and ours, not done:** the "off"-means-ON idiom drift between
/blur, /blurbooth and /flag (forms only send 0/1), and the CLI's
`.blurbooth` `touch` still follows a symlink where the service no longer does.
- ✅ **THE BLUR SET ROUND-TRIPS ANY REL** (operator: "fix the blur"). It lives
in `.blurred.json`, a JSON array written through stdlib-only `booth/blur.py`,
which is the one writer and one `check_rel` predicate for both the service
and `booth blur`. The legacy `.blurred` is read as lines, only while no
`.blurred.json` exists, and the first write retires it. The original bug (a
stripped rel blurred its neighbour) had no live victims: 6 legacy files, 42
rels, none with edge whitespace, none parseable as JSON. The heid bug-hunt
(3 arms, groa timed out) folded: a planted directory now gets a 409 instead
of a 500, the legacy file is never sniffed for JSON, a lone-surrogate member
is dropped, the writer respects the reader's size cap, the CLI takes
`a..b.png` and refuses an empty path, and a missing package fails closed.
Declined: the `Item` positional-constructor break (booth_items is the only
constructor, INV-1), the fdopen fd leak, the short read, and
unreadable-reads-as-revealed (the `.seen` posture). `Item.blurred_self` came
along, so blur state has one reader (invariant 3). **Still ours, not done:**
"off" means ON for /blur and /blurbooth but OFF for /flag (forms only send
0/1), and the CLI's `.blurbooth` `touch` still follows a symlink where the
service no longer does.
- ⚠ **THE BROWSER SUITE WAS FLAKY UNDER LOAD, AND THE CAUSE IS STILL
UNCONFIRMED.** design-dev's suspect: Google Fonts stalling "networkidle". He
reproduced the exact error with a stalled font request (sufficiency only).