22 KiB
22 KiB
Persistent memory — booth
Last updated: 2026-09-24
Always check for
/tmp/booth-dev-handoff.md— if it exists and itsWritten: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 duplicatedata-regionids (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
mkdirsits 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 Exceptionmisses 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.
- the pickup-id
- ✅ 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
:8090WITH 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.jsonthrough stdlib-onlybooth/blur.py: one writer and onecheck_relfor the service andbooth blur. The legacy.blurredis read as lines, only while no.blurred.jsonexists, 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.blurboothtouchstill follows a symlink where the service no longer does. Recorded in4cfbce5'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.pybinds 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-finalandsindra-nude-poolwere 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.0YET (operator, 2026-09-23). The tag stays1.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, thengit 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.forevercame 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 httpxfiles=sends a literal%00and proves nothing.[2026-09-24]✅ r3 compare merged on the operator's word, gated by our own run — fast-forward tof8d136aonly 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. Nobooth_itemsor 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 thread01M3952NCDRRJX5XDFSPMSP5HJ.[2026-09-23]✅ The Desk's "Everything else" sorts by last UPDATE, not last activity — the operator chose the simple fix over repairing.viewedfrom 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, becausev1.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 renderedjavascript: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 FROMimportlib.metadataHERE; it reports a different artifact, andbooth/__init__.pyturns 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 atdocs/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 atdocs/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_fragmentslost 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 asv0.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-rowresolve_booththat 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 at42ea67f), 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 TOUCHINGplaywrightIN 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.foreverdiagnosis 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 answergot 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/boothwent 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.jsonwiped 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.jsonper 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-finalistsis 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.foreverdiagnosis 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 fromeshpfiinto 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 in8a78a9b.[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.foreveris 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