30e7fe85de450db2afd159a763e2c288e9b67a59
Three booth-integrity improvements that share the entry/exit flows and activity log. Refusal snapshots: previously only an accepted open captured a camera image; now every refusal/hold anomaly fires the directional camera too (a turned-away car is exactly the evidence wanted) — entry refused-full/held, exit refused closed/no-session/unpaid/grace-expired (booth + reader paths), refused subscription. A refused entry has no ticket id, so a synthetic REFUSED- ref keys the anomaly + photo together. Same fire-and-forget contract; failed captures still show as tiles. Subscriber access medium: the subscription flow already signed `via` (qr|card|plate) into entry/exit payloads; surface it as a typed LedgerPayload.via, a cyan chip in the ticker, and an "Entry medium" modal row (sq+en). Display-only. One car = one ticket: the entry button could be mashed to mint many tickets per car (corrupting occupancy + enabling ticket-shopping at exit) — the old #inFlight guard only blocked overlapping presses. Add a per-relay guard configured on the relay spec: PRESENCE mode (presenceInput ties ticketing to a vehicle loop on a Dingtian input — one ticket per car, re-armed when the loop clears) or COOLDOWN fallback (entryCooldownSec) when there's no barrier feedback. A suppressed press is unsigned device_events telemetry, not a signed anomaly. SetupWizard exposes both fields. Fail-closed entry and barrier-is-not-a-door invariants untouched; guard state is in-memory/rebuildable, starts armed after restart. Wiki: new entry-double-press; updated entry-exit-points, booth-console, index. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
Description
No description provided