server: transient entry flow (button -> ticket -> signed entry -> open)
Closes the long-dangling thread from device-input-flow. On an access device's rising input edge: print the ticket (failover), then sign vehicle_entry, then pulseOpen, then cache the session projection. Two invariants enforced: - signed BEFORE open (an open with no signed event is the fraud signal); - HOLD on print failure — no ticket means a transient can't pay on exit, so sign an anomaly and do NOT open, and do NOT write a vehicle_entry for a car that never got in. Subscribes the same input bus as the device-telemetry writer (independent: telemetry always records; entry acts only on an access device's on-edge, debounced). Verified end to end against stubs: success path signs+opens+caches and verifyChain ok; printer-down path emits only an anomaly with no open and no entry; release edge ignored.
This commit is contained in:
@@ -5,6 +5,7 @@ import { randomUUID } from "node:crypto";
|
||||
import { createDb, deviceEvents as deviceEventsTable, type Db } from "@parking/db";
|
||||
import { TOKEN_COOKIE, requireJwtSecret } from "./auth.js";
|
||||
import { deviceEvents } from "./device-events.js";
|
||||
import { EntryFlow } from "./entry-flow.js";
|
||||
import { EventLog } from "./event-log.js";
|
||||
import { LaneMap } from "./lane-map.js";
|
||||
import { PrinterMonitor } from "./printer-monitor.js";
|
||||
@@ -79,6 +80,17 @@ export async function buildServer(opts: BuildOptions = {}): Promise<FastifyInsta
|
||||
// See wiki/decisions/event-streams-split.md.
|
||||
const eventLog = new EventLog(db, buildSigner(app.log));
|
||||
await eventRoutes(app, db, eventLog);
|
||||
|
||||
// Entry flow: a button press → print ticket → signed vehicle_entry → pulseOpen.
|
||||
// Subscribes to the SAME input bus as the telemetry writer below; the two are
|
||||
// independent (telemetry always records; the entry flow acts only on an access
|
||||
// device's rising edge). See wiki/concepts/device-input-flow.md + parking-session.md.
|
||||
const entryFlow = new EntryFlow(db, eventLog, laneMap, app.log);
|
||||
const unsubscribeEntry = deviceEvents.onInput((e) => {
|
||||
void entryFlow.onInput(e);
|
||||
});
|
||||
app.addHook("onClose", async () => unsubscribeEntry());
|
||||
|
||||
const unsubscribeInput = deviceEvents.onInput((e) => {
|
||||
// Resolve which lane the device belongs to. -1 marks "device fired but isn't
|
||||
// mapped to a lane" (assigned without a lane, or a stale id) — still recorded
|
||||
|
||||
Reference in New Issue
Block a user