Files
booth/persistent-memory.md
T

22 KiB

Persistent memory — booth

Last updated: 2026-09-24

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-24:

  • ✅ r3 COMPARE IS LIVE AND PUSHED (operator: "merge r3 once the mutation check is clean", then "merge and push", 2026-09-24). origin/main is the r3 arc's tip: r3 (f8d136a), design-dev's race fix (d54bb04: one compare ring per request, the review's Compare control hidden when its item vanished mid-request, and a NUL in a raw file path returning 404) and our upload-name fix (225ba32). The gate on that exact tip: 928 passed, and 245/245 falsifiers across all 8 tables. The service was restarted at 1804. Our seam pass on the contract caught 12 mismatches before code; the two that mattered were duplicate data-region ids (a save would have made B's flag button flag A) and 22 mutation-table rows anchored in the script that moved.
  • ⚠ THE UPLOAD ROUTE HAS THREE KNOWN LIFECYCLE GAPS, unfixed on purpose (hulda, 2026-09-24; the operator was told and did not ask for them):
    • the pickup-id mkdir sits outside the try, so a racing writer that creates the same id first gives a FileExistsError, a 500;
    • rmtree(ignore_errors=True) hides its own failure, so a failed cleanup can leave a half-written booth;
    • except Exception misses CancelledError, so a cancelled upload skips cleanup. All three are rare. The helper next to them, safe_upload_name, is done: unencodable characters go first, the cap is in bytes, and a cut never manufactures a kind.
  • ✅ Pushed on 09-23 (d5ead3f, 888 green). Landed that night, in order: r2b merge 1 (b92b002, reveal all + booth fog), r2b merge 2 (cce6a20, Desk row + dates + theme toggle), the Desk sort (64f6488), the blur round-trip (6880ab3), 768-wide thumbnails (1d31ab0), r2c the review stage (fde082e), and strict blur writes (8a78a9b).
  • ⚠ A peer's relayed "merge it" or "push now" is NOT the operator's approval. The permission layer refused a merge on design-dev's word alone (r2b), and design-dev's relay of "approve r2c, push now" was held until the operator said it here. Miranda is the only named relay.
  • ⚠ DO NOT SWEEP :8090 WITH GETS OF BOOTH PAGES. Each GET records a look. Two sessions did it on 2026-09-23, which emptied "new since you looked" and collapsed the Desk. Check live with /healthz, / and ?thumb=1; check pages on an rsync'd COPY (CLAUDE.md "Working in here").
  • ✅ THE BLUR SET ROUND-TRIPS ANY REL (operator: "fix the blur"). It lives in .blurred.json through stdlib-only booth/blur.py: one writer and one check_rel for the service and booth blur. The legacy .blurred is read as lines, only while no .blurred.json exists, and the first write retires it. Writes are strict (_load) and the reader is lenient. Mechanics live in CLAUDE.md invariant 2. booth blur <name> with NO files fogs the whole booth (.blurbooth), which composes with the per-item set and never overrides it. Still ours, not done, not scheduled: "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. Recorded in 4cfbce5's message; raise them with the operator before starting.
  • ✅ THUMBNAILS: 768 wide, capped at 4096 tall, and the operator KEPT 768 (2026-09-24; do not re-raise it). tests/test_thumbs_browser.py binds 768 to the rendered tile width, so a redesign that widens tiles turns it red. The cache is planting-proof (heid 4/4 folded). 381 live thumbnails, 14.5 MB. → persistent-memory.d/2026-09-23-the-cache-that-aged-the-thing-it-cached.md
  • ⚠ THE BROWSER SUITE WAS FLAKY UNDER LOAD; THE CAUSE IS STILL UNCONFIRMED. The offline test browser (in b92b002) gives 0 reds in 24 against a pre-fix rate of ~1 in 8, which is consistent with the fix and nothing more. Do not read a green suite as proof. → persistent-memory.d/2026-09-23-the-browser-suite-is-flaky-under-load.md
  • ⚠ sindra-nude-final and sindra-nude-pool were fogged at 21:44 on 09-23, minutes after the control went live. It wasn't booth-dev; presumably the operator, as he said he would. Do not blur, unblur or reveal them on his behalf. The Desk still shows up to 4 images per booth.
  • 🛑 NO 1.0.0 YET (operator, 2026-09-23). The tag stays 1.0.0b1, with no further pre-release until the arc lands. r3 compare, the last unit we held named in it, landed 2026-09-24; whether that closes the arc is the operator's call, never ours. ⚠ It has looked complete twice already.
  • 🛑 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.
  • ⚠ Read a staged ref, never a SHA written here. design-dev rebases in place: git show-ref | grep design-dev, then git merge-tree.

Recent decisions

  • [2026-09-24] ✅ Upload names: two crashes found, then two holes in the fix — a NUL and a name over NAME_MAX in BYTES both 500'd /upload. Our first fix then dropped a lone surrogate AFTER the dot rule (\ud800.forever came out as the keep marker) and let a cut manufacture .png. A single hulda arm found both after heid's own read found nothing. READ BEFORE WRITING A SANITISER: drop everything droppable FIRST, then apply the structural rules. A NUL test through httpx files= sends a literal %00 and proves nothing.
  • [2026-09-24] ✅ r3 compare merged on the operator's word, gated by our own run — fast-forward to f8d136a only after 918 green and 148/148 falsifiers on that exact commit, run here rather than taken from design-dev's report. The seam pass is what made the build clean: 12 contract-vs-code mismatches folded before a line was written.
  • [2026-09-24] ✅ r3 compare ruled: pick two, flag the winner — operator, in design-dev's session. No booth_items or marks work; the URL is rel-keyed. The A/same/B pairwise verdict is PARKED (deferred), with our ordered-pair-of-rels note attached. Tracked in design-dev's r3 contract parked entry, althing thread 01M3952NCDRRJX5XDFSPMSP5HJ.
  • [2026-09-23] ✅ The Desk's "Everything else" sorts by last UPDATE, not last activity — the operator chose the simple fix over repairing .viewed from the access log. READ BEFORE CHECKING THE LIVE SERVICE → persistent-memory.d/2026-09-23-desk-sorts-by-last-update.md
  • [2026-09-23] ✅ The blur round-trip, and the migration that recreated the bug it fixed — three rounds, and the third was our own 09-21 marks lesson repeated. READ BEFORE ANY DOTFILE FORMAT CHANGE → persistent-memory.d/2026-09-23-blur-round-trip-and-the-migration-that-recreated-it.md
  • [2026-09-23] ✅ Thumbnails sized for the tile's width at 2x — 768 is a layout number, held by a browser test; the operator kept it. READ BEFORE CHANGING TILE WIDTH OR THE THUMB RULE → persistent-memory.d/2026-09-23-thumbnails-sized-for-the-tile.md
  • [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-24] The blur writer building on the renderer's lenient reader — an unreadable file read as empty was then overwritten; the 09-21 marks lesson below, repeated in a new module and live one night. Fixed in 8a78a9b.
  • [2026-09-23] Writing a new format into the old file name and sniffing it — a legacy line naming ["a.png"] parses as JSON and blurs the neighbour (heid 3/3). A format change gets a new name (c1f5543).
  • [2026-09-23] Verifying the live service by GETting every booth page — each GET records a look. It emptied "new since you looked" and scrambled the Desk. Check pages on a copy.
  • [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