Commit Graph

3 Commits

Author SHA1 Message Date
julian b8ddda86e7 feat(subscription): RFID enrollment, any-credential exit, prepaid booth handling
Rounds out subscriptions across enrollment, the barrier flow, and the booth.

- RFID credentials enabled with a "Read card" enrollment flow: the operator
  arms ONE chosen reader (CredentialCapture, single-shot + ~30s TTL); that
  reader's next read is captured into the form and NOT dispatched to the access
  flow — the OTHER reader keeps serving live entry/exit. Routes:
  /api/subscriptions/readers + /capture/{arm,cancel} + poll.
- Enter with one credential, exit with another: sessions are keyed by a
  per-occurrence id (SUBSESS-<short>), not the credential value, with
  permitId in the payload. Direction is decided by the barrier the reader sits
  at (entry-lane→entry, exit-lane→exit; "both" infers); a fleet (maxConcurrent>1)
  admits several cars and exits any with any credential, FIFO (oldest first).
- Booth treats a subscription occurrence as PREPAID: never quoted/charged; the
  pay/exit modal shows a subscription mode (snapshots + a single audited
  Open-barrier action) to assist a faulty exit reader / missing card;
  reopenBarrier authorizes paidAt!=null OR subscription. Active Sessions badges
  "abonim" and labels by holder name (not the raw key).
- Plus a per-read diagnostic log in the QR-reader route (serial → device →
  verdict/dir), which surfaced the earlier duplicate-reader-IP misroute.

Verified via buildServer+inject + reader-scan/TCP-capture simulations
(enrollment isolation, cross-credential + FIFO fleet, prepaid-not-charged,
subscription reopen, unpaid-transient guard). Updated wiki (subscription,
booth-exit-flow). No migration.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-18 16:26:48 +02:00
julian ca8c7f2fa2 fix(exit): stuck active session — paid ticket with no vehicle_exit
A paid car that left via a manual barrier re-open kept no vehicle_exit, so
activeSessions() saw it as permanently open and the grace-expiry eviction
(which only ran for exited sessions) never fired — it lingered forever
(ticket T-397815c0).

- reopenBarrier() now signs a vehicle_exit (source:manual) when the session
  is still open, closing it; still no second exit when already exited
  (phantom re-close — no double-count).
- activeSessions() ages out a PAID open session past grace even with no exit
  (unpaid open sessions never age out — a car owing money stays). Pure
  display filter; the signed log is untouched.

Verified both fixes + chain integrity on a fresh DB. A one-off corrective
vehicle_exit was appended to the live ledger to clear T-397815c0.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-18 13:14:45 +02:00
julian eb3dc18e67 feat(booth): active sessions panel + audited barrier re-open
Active Sessions panel lists sessions that are open OR exited-but-within-grace
(barrier state is unconfirmed, so a paid car is presumed possibly-present until
grace expires). Row click → pay/exit modal; 'Open barrier' (paid sessions only —
no payment, no button) fires a human-intervention re-pulse signed as an attributed
anomaly, never a second vehicle_exit. Wiki: booth-exit-flow.md.

Note: the backend (PayStation.activeSessions, ExitFlow.reopenBarrier, routes,
api.ts) landed with the prior commit's shared files.
2026-06-18 11:05:26 +02:00