Files
booth/persistent-memory.md
T
vh c19d8c9718 fix(thumbs): fold the heid bug-hunt: a cache that cannot be planted, alpha, orientation
The heid bug-hunt panel on c2b1454 (4/4 arms, five seat-executed probes). The
new size rules governed only cache MISSES; the hit path trusted a name and an
mtime, inside a directory any fleet session can write into.

- A cache hit is a REGULAR file (lstat) carrying its source's EXACT mtime (4/4).
  A planted directory at the cache path was returned as the thumbnail, and a
  source replaced by `cp -p` or an archive extract kept an older stamp that
  `>=` served forever. The encoder now stamps the thumbnail with the source's
  mtime, so any change to the source is a miss.
- The cache directories are made component by component and never through a
  link (seat P4). A `.thumbs` planted as a link put the cache outside the
  booth, beyond the sweep. The booth-mtime restore now keys on creating
  `.thumbs` itself.
- The temp file is mkstemp (4/4, seat P5). The old `<out>.<pid>.tmp` was
  predictable, and a link planted there made the encoder overwrite its target
  (600 B became 316,400 B).
- Palette transparency survives (3/4, seat-executed, and INTRODUCED by
  c2b1454). The fits-but-heavy branch newly re-encoded palette PNGs, and
  getbands() of mode P has no A even with tRNS.
- EXIF orientation is honoured for sizing and for the saved image (groa,
  seat-verified). A camera portrait stored sideways was sized and tiled as a
  landscape.
- A 64 MP decode budget (2/4). A header claims any size, and a failure is not
  cached, so every request re-decoded it.
- The cache name carries the whole rule: width, height cap, quality and an
  encoding version (groa). The width alone would have served stale bytes after
  a quality change.

Declined: the utime-restore failing on a foreign-owned booth (booths are the
service user's), and regin's two solos (the THUMB_MAX export is not imported
anywhere; the live fixture is function-scoped). thumbs.toml: 14/14 proved.
2026-09-23 23:06:45 -07:00

158 lines
20 KiB
Markdown

# Persistent memory — booth
_Last updated: 2026-09-23_
> **Always check for `/tmp/booth-dev-handoff.md`** — if it exists and its
> `Written:` stamp is under 8 hours old, read it (it carries the in-flight
> handoff from the previous session), then delete it. Older than 8 hours:
> stale — delete it unread.
## Repo purpose
The Booth is the fleet's **operator-review surface**: agents post work by
making a folder under `~/booth-data`, the operator looks at it and judges it in
the browser, and the judgment gets back to the agent that posted it. It was
built as a file-shuttle and is being converged, unit by unit, onto the review
loop it turned out to actually be.
## Current state / in-flight
_As of 2026-09-23:_
- 🛑 **THE ONE THING BLOCKING: design-dev's blur merge is HELD, awaiting his
ping.** `design-dev/svos-retheme` @ `5ded5ff` (Reveal all + the booth-blur
control) is fetched, merge-tree against `091f4b5` is CLEAN, and his commit
records the contract panel 4/4 folded, 14/14 mutations, 765 passing —
**but the heid code-review and bug-hunt panels were dispatched 17:53 and are
NOT folded.** He said explicitly: *"Hold… I will ping 'merge it' when both
are folded; whoever picks it up after a clear should look for that ping before
merging."* **DO NOT MERGE IT WITHOUT THAT PING.** The operator's "merge
everything" predates this and was not about overriding his gate.
- 🔶 **After that ping: merge, restart, verify 30 booths.** Then his SECOND
merge (Desk row revisions + the dark/light/system theme toggle) follows the
same way.
- ⚠ **THE BROWSER SUITE IS FLAKY UNDER LOAD AND IT IS NOT FIXED.** Three
different tests, one failure each, all passing in isolation. Two real defects
were fixed chasing it and NEITHER is proven to be the cause. **Do not read a
green suite as proof.** Operator ruled design-dev diagnoses it properly.
→ `persistent-memory.d/2026-09-23-the-browser-suite-is-flaky-under-load.md`
- ✅ **THE REDESIGN IS LIVE.** R2 (the Desk, the lightbox, the reel) merged and
deployed; release/wipe moved onto the facts line. 30 booths at 200.
- ✅ **BOOTH BLUR: STORAGE, ROUTE AND CLI ARE LANDED — ONLY THE UI IS PENDING**
(it is what `5ded5ff` holds). Marker `<booth>/.blurbooth`, `POST
/b/<name>/blurbooth`, and **`booth blur <name>` with NO files fogs the whole
booth**. COMPOSES with `.blurred`, never overrides. All 17 handles can
self-blur at post time.
- ✅ **THUMBNAILS ARE LIVE, AND SIZED FOR THE TILE'S WIDTH** (operator,
2026-09-23: "blurry until selected"). The first cut capped the LONGEST side at
512, so a 704x1408 portrait got 256px of width for a 361px tile, stretched
1.4x at 1x and 2.8x on a 2x screen. Now they are 768 wide (2x the widest
desktop tile) and capped at 4096 tall, and an original that fits but weighs
over 64 KB is still re-encoded. Measured on the 381 live images: all
thumbnails 4.8 → 14.2 MB, still ~27x under the originals. ⚠ **768 is a LAYOUT
number:** `tests/test_thumbs_browser.py` holds it against the rendered grid,
so if a redesign widens the tiles, that test goes red. The 2-column (≤472px)
and 1-column (≤650px) reflows are softer than 768 covers at 2x; 1024 would
cover 2 columns for 18.5 MB total. Four surfaces (tile, Desk strip, flag tray,
filmstrip); the review stage keeps the original. The heid bug-hunt (4/4
arms) folded: a cache hit must be a regular file carrying its source's EXACT
mtime (a planted directory, or a `cp -p` older source, no longer pins a
thumbnail); the cache dirs are made without following links; the temp file is
mkstemp (the old `<out>.<pid>.tmp` could be planted as a link and was written
through); palette transparency survives; EXIF orientation is honoured; and
there's a 64 MP decode budget. The cache name carries the whole rule
(`.768x4096q78v2.webp`).
→ `persistent-memory.d/2026-09-23-the-cache-that-aged-the-thing-it-cached.md`
- ✅ **CREATION + UPDATE DATES ARE ON THE RECORD** for all 30 booths
(`created_at` via `statx`, `landed_at` already existed). design-dev renders
them when his sequencing reaches it; **None must render as nothing.**
→ `persistent-memory.d/2026-09-23-dates-and-the-guess-wearing-a-facts-clothes.md`
- ⚠ **THE DESK EXPOSES 84 IMAGES ACROSS 22 BOOTHS on the page he opens first.**
The pre-redesign index showed ONE cover per booth; four-up multiplied exposure
by four and nothing posted before the redesign opted into it.
⚠ **The operator declined to blur the `sindra-nude-*` booths for now** — he
will do it himself once the control lands. **Do not blur them on his behalf.**
- 🛑 **NO `1.0.0` YET** (operator, 2026-09-23). The tag stays `1.0.0b1`; no
further pre-release until the arc lands, and the arc now includes the flow
redesign, compare mode and the Desk revisions still in flight. ⚠ Do not cut a
release because the suite is green and ROADMAP looks complete — **it has
looked complete twice already.**
- 🔶 **COMPARE MODE (r3) is ruled INTO this arc** and unparked; design-dev
starts it after the two merges land. The item-record work it needs is ours,
not deferred — he tells us what a compare view wants from `booth_items`.
- 🛑 **STANDING: NO ANNOUNCEMENTS out of this repo until the whole arc is done,
and the operator sends that one himself.** Do not offer, draft-and-await, or
raise it.
- ⚠ **`booth/__init__.py` IS A FOURTH STDLIB-ONLY MODULE** — `scripts/booth`
executes it before every documented one. Covered by `test_stdlib_only`.
- ⚠ **Read the staged ref, never a SHA written here** — design-dev rebases and
rewrites it in place. `git show-ref | grep svos`, then `git merge-tree`.
- **770 green on a clean run; 49+ falsifiers proved across three mutation
tables (`scripts/mutation_check.py`). Tree clean, pushed, 0 ahead.**
## Recent decisions
- `[2026-09-23]` ✅ **The four flow rulings, and what they cost the beta** — all four taking design-dev's recommendation; READ BEFORE CUTTING ANY RELEASE, because `v1.0.0b1`'s "no new features" promise no longer describes the arc and an alpha drop-back is illegal → `persistent-memory.d/2026-09-23-the-flow-rulings-and-what-they-cost-the-beta.md`
- `[2026-09-23]` ✅ **Creation dates came from a syscall, after three guesses wearing a fact's clothes** — READ BEFORE REACHING FOR A PROXY; the system already recorded what looked unavailable, and one of the rejected proxies was a shape we had just finished paying for → `persistent-memory.d/2026-09-23-dates-and-the-guess-wearing-a-facts-clothes.md`
- `[2026-09-23]` ⚠ **The browser suite is flaky under load — OPEN, owned by design-dev** — three tests, two real defects fixed, NEITHER proven causal; do not read a green suite as proof → `persistent-memory.d/2026-09-23-the-browser-suite-is-flaky-under-load.md`
- `[2026-09-23]` ⚠ **The cache that aged the thing it cached** — thumbnails 77.5MB→0.78MB; READ BEFORE PARKING ANYTHING ON A MEASUREMENT (we counted images and the cost was in bytes), and BEFORE PUTTING A SERVER-WRITTEN CACHE INSIDE A BOOTH (excluding its contents does not stop it aging the booth) → `persistent-memory.d/2026-09-23-the-cache-that-aged-the-thing-it-cached.md`
- `[2026-09-23]` ⚠ **The probe that nearly dismissed a live injection vector** — the link board rendered `javascript:` hrefs; READ BEFORE TRUSTING A NEGATIVE RESULT FROM AN OBVIOUS PROBE, and before assuming an existing scheme check is the guard you are looking for → `persistent-memory.d/2026-09-23-the-probe-that-nearly-dismissed-a-live-vector.md`
- `[2026-09-23]` ✅ **The bug-hunt panel found six defects and five vacuous falsifiers** — READ BEFORE BUILDING ANY FRAGMENT ANCHOR (browsers match raw before decoded, so both sides must be encoded), and before trusting a well-commented diff's guards → `persistent-memory.d/2026-09-23-the-bug-hunt-panel-and-five-vacuous-falsifiers.md`
- `[2026-09-22]` ✅ **v1.0.0b1 — the v1 target staged as a beta, and a version that was two copies** — READ BEFORE DERIVING A VERSION FROM `importlib.metadata` HERE; it reports a different artifact, and `booth/__init__.py` turns out to be stdlib-only → `persistent-memory.d/2026-09-22-v1-staged-as-a-beta-and-a-second-copy-of-the-version.md`
- `[2026-09-22]` ✅ **U7 landed — and the number that justified it did not reproduce** — all seven v1 units are in; READ BEFORE TRUSTING A MEASUREMENT INSIDE A CONTRACT, and before assuming a degeneracy guard covers the degeneracy you actually have → `persistent-memory.d/2026-09-22-u7-landed-and-a-table-that-did-not-reproduce.md`
- `[2026-09-22]` ⚠ **A mutation harness certified a broken test, twice, for two reasons** — no green baseline, and the pyc cache silently reverting same-size mutations; READ BEFORE WRITING ONE → `persistent-memory.d/2026-09-22-a-mutation-harness-that-certified-a-broken-test.md`
- `[2026-09-22]` 🛑 **STANDING: no announcements out of this repo until the arc is done, and he sends that one himself** — verbatim *"no announcements until the entire arc is done, and even then i'll do it myself."* Stricter than the house broadcast gate: the send is not the agent's to make, so **asking is also out of scope**. The drafted 17-handle note is REASSIGNED to him, not blocked — see the in-flight row above; do not raise it again.
- `[2026-09-22]` **The operator ruled on all five open items at once** — four executed incl. the first push; the broadcast was blocked by the permission layer and is drafted at `docs/pending/` → `persistent-memory.d/2026-09-22-operator-ruled-on-the-open-five.md`
- `[2026-09-22]` **U7 is three-quarters built and blocked on one word** — the ratified three landed; the sections-vs-groups departure is NOT built and is the operator's call, tracked at `docs/contracts/u7_navigation.contract.md` → `persistent-memory.d/2026-09-22-u7-three-quarters-and-one-ruling.md`
- `[2026-09-22]` **An approved directive misrouted because pane_find addresses by a rolling pane title** — resolved; the MECHANISM is the durable part, reported to infra-ops, untracked by booth-dev → `persistent-memory.d/2026-09-22-a-directive-misrouted-by-pane-title.md`
- `[2026-09-22]` **U7 re-measured before scoping — sections are dead, filename prefixes are not** — PRE-WORK ONLY, no unit started; read before writing U7's contract → `persistent-memory.d/2026-09-22-u7-remeasured-before-scoping.md`
- `[2026-09-22]` **The last open defect closed, and building its falsifier found another** — the wrong-shaped answer fixed at `_hydrate`; `_safe_fragments` lost its natural trigger and its handler could not survive the failure it handled → `persistent-memory.d/2026-09-22-a-wrong-shaped-answer-500s-the-gallery.md`
- `[2026-09-22]` **U6 released as `v0.6.0` — benches, and the number that was two defects** — six of seven v1 units landed, NOT PUSHED → `persistent-memory.d/2026-09-22-u6-benches-released.md`
- `[2026-09-22]` **Three cold panels on one unit, and what each lens could only see alone** — READ BEFORE DECIDING TO SKIP A GATE; all five passes found something the others structurally could not → `persistent-memory.d/2026-09-22-three-cold-panels-on-one-unit.md`
- `[2026-09-22]` **U6 landed — three surfaces, three jobs, one predicate** — the seam review caught three real contract defects incl. a per-row `resolve_booth` that would have 404'd the board → `persistent-memory.d/2026-09-22-u6-benches-landed.md`
- `[2026-09-22]` **The 69% link-board rot was two defects wearing one number** — READ BEFORE SCOPING ANY LINK-BOARD WORK; U5 closed the larger half and full-URL-vs-origin identity is a measured call → `persistent-memory.d/2026-09-22-one-number-was-two-defects.md`
- `[2026-09-22]` **U3 landed — the page declares the seam, the Booth mounts into it** — ten regexes against author HTML replaced by a substring test and a `+` → `persistent-memory.d/2026-09-22-u3-declared-embed-seam-landed.md`
- `[2026-09-22]` **A wrong-shaped answer 500s the gallery and the marks page** — PRE-EXISTING (measured at `42ea67f`), NOT U3; the v0.2.2 lesson is only half-implemented → `persistent-memory.d/2026-09-22-a-wrong-shaped-answer-500s-the-gallery.md`
- `[2026-09-22]` **The browser became a test surface** — READ BEFORE TOUCHING `playwright` IN pyproject; the pinned upper bound is the foot-gun, and these tests SKIP rather than fail → `persistent-memory.d/2026-09-22-the-browser-became-a-test-surface.md`
- `[2026-09-22]` **A vacuity pass that tries the contract's own mutation agrees with itself** — U3 ran one, reported 7/7, and a cold panel then showed one of the seven was vacuous; READ BEFORE WRITING A *Falsifiable:* LINE → `persistent-memory.d/2026-09-22-seven-of-seven-falsifiers.md`
- `[2026-09-22]` **U4 landed — lifetime is derived, not declared** — three states, viewing is activity, and no new arithmetic anywhere → `persistent-memory.d/2026-09-22-u4-derived-lifetime-landed.md`
- `[2026-09-22]` **The `.forever` diagnosis got a live positive control** — 3 of the 4 booths awaiting an answer were ALSO hand-pinned — RE-COUNT 2026-10-06 → `persistent-memory.d/2026-09-22-forever-had-a-live-positive-control.md`
- `[2026-09-22]` **No fleetwide notice for U4, and what that does to the prediction** — READ BEFORE THE 2026-10-06 RE-COUNT; a flat rate does not falsify the diagnosis → `persistent-memory.d/2026-09-22-no-notice-and-what-it-does-to-the-prediction.md`
- `[2026-09-22]` **Four independent paths to one fail-open delete** — the bug-hunt panel's class, and the zsh word-splitting trap that shipped an empty bundle → `persistent-memory.d/2026-09-22-four-paths-to-one-fail-open-delete.md`
- `[2026-09-22]` **Two reads of one file are not one read of one state** — a TOCTOU seam that composes two correct readers into a fail-open delete → `persistent-memory.d/2026-09-22-two-reads-are-not-one-state.md`
- `[2026-09-22]` **Five of seven INV falsifiers did not falsify anything** — read before writing a *Falsifiable:* line; a green test cited one rather than being one → `persistent-memory.d/2026-09-22-vacuous-falsifiers.md`
- `[2026-09-22]` **The third one-branch template miss** — this repo's recurring blind spot; read before adding a fact to any template → `persistent-memory.d/2026-09-22-third-one-branch-template-miss.md`
- `[2026-09-22]` **The size cap opened a service-wide hang** — a FIFO has st_size 0; a bound that trusts it inherits what it does not mean → `persistent-memory.d/2026-09-22-size-cap-opened-a-hang.md`
- `[2026-09-22]` **An existing test stopped me retiring documented behaviour** — the clean fix for the mtime race would have silently changed TTL doctrine → `persistent-memory.d/2026-09-22-doctrine-not-defect.md`
- `[2026-09-22]` **Two U5 panels, and prose reached a released outage** — read the detail before assuming a conformance finding stops at its own module → `persistent-memory.d/2026-09-22-u5-panels-reached-a-released-bug.md`
- `[2026-09-22]` **U5's adoption prediction split in two** — the handle rides for free, the why must be learned — RE-MEASURE 2026-09-29 → `persistent-memory.d/2026-09-22-u5-adoption-split-in-two.md`
- `[2026-09-22]` **The U2 bug-hunt panel was not ceremony** — the lock-unlink race and the TTL guard that was failing at its own job → `persistent-memory.d/2026-09-22-u2-bug-hunt-panel.md`
- `[2026-09-22]` **The lenient reader's blast radius was the whole service** — marks_for runs per booth per index load; a raise there is an outage → `persistent-memory.d/2026-09-22-lenient-reader-blast-radius.md`
- `[2026-09-22]` **`booth marks` / `booth answer` got real exit codes** — read it before changing anything the 17 consuming handles call → `persistent-memory.d/2026-09-22-cli-exit-codes.md`
- `[2026-09-22]` **`scripts/booth` went from zero tests to five** — they run the real script under system python3, so they also check INV-1 → `persistent-memory.d/2026-09-22-scripts-booth-got-tests.md`
- `[2026-09-21]` **v0.2.0 was tagged while a gate was in flight** — the sequencing lesson: if a gate is outstanding, the tag waits → `persistent-memory.d/2026-09-21-v020-tagged-with-a-gate-in-flight.md`
- `[2026-09-21]` **A write over a damaged `.marks.json` wiped the booth** — the reads-lenient / writes-strict asymmetry, and why it exists → `persistent-memory.d/2026-09-21-marks-write-wiped-judgment.md`
- `[2026-09-21]` **Seam review and cold panel had zero overlap, twice** — evidence for running both; neither substitutes for the other → `persistent-memory.d/2026-09-21-two-gates-are-complementary.md`
- `[2026-09-21]` **Every code-changing finding came from the AMBIGUITY pass** — a finding about the /heid-contract-review skill, not about this repo → `persistent-memory.d/2026-09-21-ambiguity-pass-did-the-work.md`
- `[2026-09-21]` **Deterministic order is a cross-cutting v1 invariant** — operator directive; read before adding ANY ordered surface → `persistent-memory.d/2026-09-21-deterministic-order-invariant.md`
- `[2026-09-21]` **U2 (marks) landed — one primitive for three mechanisms** — what moved where, and the HTTP mirror remote sessions poll → `persistent-memory.d/2026-09-21-u2-marks-landed.md`
- `[2026-09-21]` **A partially-answered pick counts as OPEN** — declared, not smuggled; it is the reading that makes U4 correct → `persistent-memory.d/2026-09-21-partial-answer-counts-as-open.md`
- `[2026-09-21]` **The U2 seam review earned its place, and how** — inline.place indexes by subscript — the miss a cold panel cannot see → `persistent-memory.d/2026-09-21-u2-seam-review-earned-it.md`
- `[2026-09-21]` **Marks are one `.marks.json` per booth** — operator decision with two rejected alternatives; read before restructuring → `persistent-memory.d/2026-09-21-marks-storage-decision.md`
- `[2026-09-21]` **U7's section premise is half wrong** — every booth that needs navigation is FLAT — read before starting U7 → `persistent-memory.d/2026-09-21-u7-section-premise-half-wrong.md`
- `[2026-09-21]` **`sindra-finalists` is U2's flag motivation, caught live** — evidence, not argument → `persistent-memory.d/2026-09-21-sindra-finalists-is-the-motivation.md`
- `[2026-09-21]` **The information architecture and the v1 gate landed** — the single defect the seven units decompose → `persistent-memory.d/2026-09-21-ia-and-v1-gate-landed.md`
- `[2026-09-21]` **The `.forever` diagnosis is a falsifiable prediction** — U4's success criterion — re-count a fortnight AFTER U4 lands → `persistent-memory.d/2026-09-21-forever-diagnosis-is-a-prediction.md`
- `[2026-09-21]` **Extracted from `eshpfi` into its own repo** — test_booth.py is the regression net the v1 rewrite is checked against → `persistent-memory.d/2026-09-21-extracted-from-eshpfi.md`
## Tried and abandoned
- `[2026-09-21]` **Tagging a release while a review gate was in flight** — cost a same-hour v0.2.1 and a correction to 15 handles → `persistent-memory.d/2026-09-21-tagging-with-a-gate-in-flight.md`
- `[2026-09-21]` **Letting the write path share the read path's leniency** — a tolerant reader and a tolerant writer are not the same decision → `persistent-memory.d/2026-09-21-tolerant-writer-over-tolerant-reader.md`
- `[2026-09-21]` **Letting Jinja hot-reload templates in the deployment root** — caused a live outage: 19 of 25 booths at 500. Why auto_reload=False → `persistent-memory.d/2026-09-21-jinja-hot-reload-outage.md`
- `[2026-09-21]` **Five mechanisms to get one question beside one artifact** — the accretion signature this whole v1 rewrite is undoing → `persistent-memory.d/2026-09-21-five-mechanisms-one-job.md`
- `[2026-09-21]` **Regex-injecting chrome into arbitrary author HTML** — the defect U3 exists to close → `persistent-memory.d/2026-09-21-regex-injecting-chrome.md`
- `[2026-09-21]` **A boolean escape hatch as the lifetime mechanism** — why `.forever` is a symptom; the defect U4 exists to close → `persistent-memory.d/2026-09-21-boolean-escape-hatch-as-lifetime.md`
- `[2026-09-21]` **Letting the link board absorb the announce job** — 69% rot; U5 gave the job a home, which is what unblocks U6 → `persistent-memory.d/2026-09-21-link-board-absorbing-announce.md`