# U4 landed — lifetime is derived, not declared _2026-09-22 · booth_ **A booth's lifetime stopped being a boolean somebody remembered to press.** Three states now, and `sweep_once` is the only thing that honours the first two: KEPT `.forever` present never swept (unchanged) HELD an open pick, or marks we cannot read never swept (new) EPHEMERAL everything else 24h (unchanged) Plus **viewing is activity**: a deliberately-served response from a booth's own page route writes `.viewed`. That dotfile is not a `.lock` dotfile, so `_newest_mtime` already counts it — **there is no new arithmetic anywhere**. `booth_age_seconds`, `is_expired` and `expires_in` are byte-for-byte what they were. A view is one more thing in the tree, which is the same trick `.booth.json` used in U5. **What counts as a view, and why the exclusions matter more than the inclusions.** `/b//` (gallery, verbatim report, `?download=1` zip), `/b//view` and `/b//marks` count. `/b//marks.json`, asset GETs, `/`, `/healthz` and a zoom URL that 404s do NOT. The marks.json exclusion is load-bearing: **an agent must not be able to hold its own booth open by polling for the answer it is waiting on.** `/b//asks` is a 308 into `/marks` and records through it — one call, not two. Checked because it would have been silent: **nothing in the fleet polls a booth page.** Homepage's `siteMonitor` for the Booth is `/healthz`, which is on the not-a-view list. Had it been pointed at a booth URL, every booth would have become immortal on deploy and nothing would have reported it. **The hold is unbounded and that is the point** — unanswered is unfinished. What makes it safe is visibility plus two exits that already existed: the card and every Booth-owned header say `held until answered` where the countdown was, and `booth rm` / the UI x / `DELETE /b/` take a held booth exactly as they take a kept one. **A hold is protection from the timer, never from the operator.** **Release is activity, stated rather than accidental.** Releasing a kept board still buys a full TTL — unchanged — but now because `booth_unkeep` calls `record_view`, which is a rule, and no longer because unlinking a file happened to bump a directory's mtime, which is not. The CLI warning against "unkeep and let it expire" stays and stays true. ⚠ **Running `scripts/layout-probe.py` over booth pages resets every booth's clock**, because a GET of a booth page is a view and the probe is not exempt from its own rule. Harmless, recoverable, and noted in the probe so nobody debugs it later as a sweeper that stopped working. Contract: `docs/contracts/u4_derived_lifetime.contract.md`. Both heid panels ran and the bug hunt after them; see the sibling entries.