server: permit entry/exit branch + read dispatcher
A credential read now routes by what the credential IS: matches a permit (card/QR credential or a bound plate) -> permit flow; else -> transient exit flow. Lane resolved once (readerLaneWithAccess); ExitFlow.onRead -> handleAt so the dispatcher owns lane resolution. Permit direction is inferred from session state for that car (the read value is the per-car session key): no open session -> ENTRY (enforce maxConcurrent, sign vehicle_entry, open); open -> EXIT (sign vehicle_exit, open, close). Fleet permit = one session per car; anti-passback falls out naturally. maxConcurrent enforced as a fold over the signed ledger (null = unbound). Validity window + status + plate-OR-card identity as designed. No ticket/fee; every use is a signed event carrying permitId. Refusals (revoked / out-of-window / at-capacity) are signed anomalies, barrier stays closed. Verified against stubs: card entry -> inferred exit; fleet cap 2 (F3 rejected at 2/2, then admitted after F1 exits); plate-bound opens; revoked rejects; unknown credential falls through to exit reject; verifyChain ok.
This commit is contained in:
@@ -104,7 +104,10 @@ follow this page and [[tariff]]; the decision is recorded in [[session-model]].
|
||||
- **Entry flow** (`apps/server/src/entry-flow.ts`): access-device input edge → print ticket
|
||||
(failover) → signed `vehicle_entry` → `pulseOpen`. Holds (anomaly, no open, no entry) if printing
|
||||
fails. See [[device-input-flow]].
|
||||
- **Exit flow** (`apps/server/src/exit-flow.ts`): a credential **read** (new `read` bus channel) →
|
||||
- **Read dispatch** (`apps/server/src/read-dispatch.ts`): a credential read routes to the
|
||||
**permit flow** if it matches a permit (card/QR/bound plate), else to the transient **exit flow**.
|
||||
Lane resolved once (`readerLaneWithAccess`). See [[permit]] as-built.
|
||||
- **Exit flow** (`apps/server/src/exit-flow.ts`): a credential **read** (the `read` bus channel) →
|
||||
fold the signed ledger for that identity → validate **open + PAID + within `gracePeriodExitMin`**
|
||||
→ signed `vehicle_exit` → `pulseOpen`. Unpaid / expired / unknown → signed `anomaly`, barrier
|
||||
stays closed. Validation folds the **ledger** (authoritative), then updates the `sessions` cache.
|
||||
|
||||
Reference in New Issue
Block a user