Files
booth/persistent-memory.md
T
vh 5c20e2f4d5 fix(u3): seven defects two cold panels found in the declared seam
The /heid-code-review and /heid-bug-hunt panels, artifact-only over the U3
diff, between them found four real defects and three vacuous falsifiers. Both
snapshots predate the contract-review fixes, so two of their findings were
already closed; the rest are here.

Prototype pollution in the placement maps. A mark id and a question key are
both [A-Za-z0-9][A-Za-z0-9._-]*, so `toString` and `constructor` are legal in
each. Against a plain `{}` an anchor naming NO mark returned an inherited
function, passed the guard meant to reject it, and threw on .questions.length
-- aborting placement before the tail, so one typo in author markup cost the
page every ask. The `placed` set had the mirror bug: inherited
`got.constructor` read as already-placed and silently dropped a question.
Object.create(null), three times. Found independently by both panels.

A declaring page was not served as written. read_text() opens in
universal-newline mode, so a CRLF report came back LF, and errors="replace"
replaced every byte that was not valid UTF-8. That is this unit's headline
promise, broken by the read itself, and the test could not see it because its
fixture was LF-only ASCII. The verbatim branch reads and serves bytes now; the
decoded copy answers only "does it declare the seam?".

A submit anchor inside the author's own <form> lost ours -- the parser drops a
nested form element outright -- while the code still recorded the pick as
submitted, so no fallback was appended. Every control's form= pointed at
nothing and the button did nothing. It counts as submitted only if the form
survived.

A broken pick's diagnostic never rendered from a submit-only anchor: an errored
pick's submit block is empty, and mounting that then marking it placed made the
tail skip the "broken ask" box entirely. The anchor is left alone instead.

An author's own element could hijack the open-ask chip -- id="bk-ask-winner-
background" satisfies any prefix rule, hyphen boundary included. The chip now
searches only elements this script mounted, which is the identity the deleted
bk-ask-<id>-top anchor used to guarantee, and takes the earliest by
compareDocumentPosition.

No error boundary around fragment rendering. A .marks.json that is well-formed
JSON with a wrong-shaped answer hydrates with no error and then raises in the
macro; this endpoint renders every pick on every load of the report, so that
was the whole seam gone while hold_read called the file readable. Reproduced
before building for it. _safe_fragments gives it the per-mark leniency
_hydrate_safe already applies one layer down.

The gallery and marks pages still 500 on that same entry. Measured at 42ea67f
-- it predates this unit, they render the same macro with no guard, and the
gallery is named out of scope in the contract. Recorded, not quietly widened:
persistent-memory.d/2026-09-22-a-wrong-shaped-answer-500s-the-gallery.md

Also corrected: several comments claimed a multi-question pick POSTs a 400
unless every question is answered. It does not -- an empty submission is
refused, a partial one is recorded on purpose. The real reason an unplaced
question must still be appended is that a question which never reaches the page
cannot be answered at all.

Vacuity pass rebuilt around the rule this session learned: the mutation comes
from the invariant's claim, never from the falsifier's example. 21 mutations,
21 caught, unmutated control green. Getting there took three rounds -- it
passed INV-3 with the contract's own mutation, then found its own fix's hole,
then flagged seven stale mutations and one genuinely vacuous fixture whose
sibling-mark arrangement made the right answer also the first answer.

444 tests. Deployed and verified: 23/23 booths 200, and all four live verbatim
reports served at exactly +46 bytes -- len(EMBED_SCRIPT_TAG) -- with the
authors' own wrappers and headings intact and no console errors.
2026-09-22 11:22:37 -07:00

14 KiB

Persistent memory — booth

Last updated: 2026-09-22

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

  • v1 is gated on seven units in ROADMAP.md, dependency-ordered U1 → U2 → {U3, U4, U5} → U7, with U6 independent.
  • U1, U2, U3, U4 and U5 are landed — the whole middle tier is closed. U1 ce598b3; U2 c7f9437 → v0.2.0, 5e41108 → v0.2.1, 026a1fc → v0.2.2; U5 c015a91 + 95beede → v0.3.0; U4 c3a97c1 → v0.4.0. U3 landed 2026-09-22 — 431 tests green (410 → 431), deployed and verified live, 21/21 booth pages 200, and each of the four verbatim booths grew by exactly 46 bytes, which is len(EMBED_SCRIPT_TAG) — one append, nothing else. NOT PUSHED (push is the operator's call and he has not given it).
  • U4 released as v0.4.0 (operator approved the minor on 2026-09-22). c3a97c1 is the unit; the release commit carries the pre-existing fixes the bug-hunt panel surfaced in touched files. The tag waited for the last gate to close, per the v0.2.0 lesson — see Tried and abandoned.
  • ⚠ The 17 consuming handles are NOT being told that keep no longer means "waiting on an answer" — operator decision, 2026-09-22, no broadcast. This is deliberate and it CHANGES HOW THE 2026-10-06 RE-COUNT READS: the hold rides for free, but not-pressing-keep has to be learned, so a flat .forever rate does not falsify anything. Read its entry before measuring.
  • TWO UNITS LEFT TO v1, and they do not depend on each other. U6 (benches, independent, closes the 69% link-board rot) and U7 (navigation at 270 items, which U3 just unblocked — its only dependency was {U3, U4, U5}). Which goes next is the operator's call. ⚠ Before starting U7, read persistent-memory.d/2026-09-21-u7-section-premise-half-wrong.md: every booth that actually needs navigation is FLAT, so half its premise is already known to be wrong.
  • ⚠ U3's RELEASE TIER IS WITH THE OPERATOR. It reads minor — the verbatim path gained a declared public API (<script src="/_booth/embed.js" defer>), a JavaScript dependency it did not have, and an author-facing anchor syntax — and minor needs his explicit approval. pyproject.toml still says 0.4.0; nothing is tagged. Do not bump it on your own.
  • ALL FOUR U3 GATES ARE CLOSED. In-session seam review (5 findings, SR-2 a real payload-shape bug); /heid-contract-review (01M351WKV666D681SSRNY7D7X6, 12 findings, 10 adopted, 2 already settled by the seam review while it was in flight, 1 declined); /heid-code-review (01M352RXV1ZET566KV73C7TSB8, 3 more vacuous falsifiers
    • the prototype-pollution bug); /heid-bug-hunt (01M352TPCSN52G6NGJ07T5WSGY, 5 net-new, incl. the byte-exactness break). Seven of the adopted findings were CODE fixes, not wording — the cold gates were not ceremony on this unit. The seam review ran in-session and is folded in — five findings as a table at the end of the U3 contract, and SR-2 was a real payload-shape bug the cold panel structurally could not see. U4's three and U5's three are all closed (01M34VX0SH23Y3VC92E7GM4S70, 01M34WAFJC3RTERFYBBZJN1SVG, 01M34Y2R0RAJRSN36Q8K4KAB36; 01M340PNVRS21HPASZT38PXQPN, 01M341E9XAPZEFBSPK9HPGAM0S, 01M343SXX27Z47C3STXXRC7M42).
  • Two dated predictions are pending and must not be forgotten. U5's adoption re-measure on 2026-09-29 (two counts, see its entry — already at 3 of 24 announced and 2 with a why, all from peers told nothing), and the .forever re-count on or after 2026-10-06, a fortnight after U4 landed, which is U4's success criterion. ⚠ Only 4 booths carry marks at all, so the hold's live blast radius is small and the prediction rests on both halves of U4 — see its entry for what a null result would and would not mean.
  • FOUR methodology proposals sit with the operator, all UNTRACKED BY OPERATOR CHOICE (no issue, no ticket — they are /heid* skill changes, not this repo's work, and are recorded here only so they are not lost). Three are from the U5 round: reshaping the paraphrase gate toward a drift-check for narrative-heavy contracts, a standing "green-tests-prove-nothing" direction for the code-review gate, and regin's table-vs-signature consistency pass. The fourth is new and is the one with evidence behind it: a contract-time VACUITY PASS — for each invariant, name a change that defeats it and check the test goes red. Regin and Kimi proposed it independently on the U4 paraphrase round; the code-review panel then showed five of seven U4 falsifiers were vacuous, and heid rates that the strongest single data point for it so far. See persistent-memory.d/2026-09-22-vacuous-falsifiers.md.
  • The booth set churns hard: 26 → 24 → 25 across the last two sessions as the sweeper ran. Re-count rather than trusting any number written here.

Recent decisions

  • [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