server: exit flow (pay-on-foot validation)
A credential read at an exit lane validates the session, then opens. Adds a 'read' channel to the device bus (DeviceReadEvent: ticket/plate/qr/card); entry stays button-driven so reads are exit/identity events. Flow: read -> fold the SIGNED ledger for that identity -> validate open + PAID + within gracePeriodExitMin -> signed vehicle_exit -> pulseOpen -> close the session cache. Unpaid / grace-expired / unknown -> signed anomaly, barrier stays closed (a deliberate business reject, not a fail-state; 'exit fails open' is about host/power loss). Validation reads the ledger (authoritative), not the cache. No payment events exist until the pay station is built, so every transient exit currently rejects -- the correct end-state, not yet passable. Verified against stubs: unpaid->anomaly+no-open; paid+grace->exit+open+closed; expired->anomaly; unknown->anomaly; verifyChain ok across entry->pay->exit. Flagged: lane_devices has no entry/exit direction model (exit door hardcoded to 1); needs a lane-direction/role model before multi-reader lanes.
This commit is contained in:
+17
@@ -500,3 +500,20 @@ guarantee. Recorded in [[dingtian-relay]] (new Hardening section).
|
||||
new custody/session shape. Captured as [[valet-overcapacity]] + made [[capacity-occupancy]] FULL a
|
||||
soft policy; NOT built into the entry flow (clean seam left). Deferred.
|
||||
- New page [[valet-overcapacity]]; updated [[capacity-occupancy]], [[index]].
|
||||
|
||||
## [2026-06-15] build | Exit flow (pay-on-foot validation)
|
||||
- Built `apps/server/src/exit-flow.ts`. Added a `read` channel to the device bus (DeviceReadEvent:
|
||||
ticket/plate/qr/card) — readers/LPR emit reads; entry stays button-driven, so reads are
|
||||
unambiguously exit/identity events for now.
|
||||
- Flow: read → fold the SIGNED ledger for that identity → validate open + PAID + within
|
||||
`gracePeriodExitMin` → signed `vehicle_exit` → pulseOpen → close the session cache. Unpaid /
|
||||
grace-expired / unknown → signed `anomaly`, barrier stays closed (a deliberate business reject,
|
||||
NOT a fail-state; "exit fails open" is about host/power loss). Validation reads the ledger
|
||||
(authoritative), not the cache.
|
||||
- Pay station doesn't exist yet → no `payment` events → every transient exit currently REJECTS.
|
||||
Correct end-state, not passable until pay-station lands (decided).
|
||||
- VERIFIED against stubs: unpaid→anomaly+no-open; paid+grace→vehicle_exit+open+closed; grace-expired
|
||||
→anomaly; unknown ticket→anomaly; verifyChain ok across entry→pay→exit.
|
||||
- GAP flagged: lane_devices has no entry/exit DIRECTION model (door mapping hardcoded to 1 for exit);
|
||||
fine while entry=button/exit=read, but multi-reader lanes need a lane-direction/role model (ties to
|
||||
[[open-questions]] #1). Updated [[parking-session]] as-built + gap, [[index]].
|
||||
|
||||
Reference in New Issue
Block a user