feat(setup): operator-tested relay pulse, signed into the ledger
Add a per-relay "Test" control on each saved controller in /setup so an admin can prove barrier wiring without a vehicle. POST /api/setup/test-relay pulses a barrier relay — but because a physical open with no matching signed command is the fraud signal, the route SIGNS a barrier_open_command (reason setup.relayTest, source manual, attributed to the acting admin) BEFORE it fires. Reconciliation then reads the open as explained, not an anomaly, and there's an audit trail. - Admin-only (site:update), CSRF-guarded; fires only against a SAVED controller (real id → clean attribution; also stops a redirected/unsaved config from opening an arbitrary host's barrier). Sign-before-fire; a pulse failure is reported, not a 500. radarAlert relays (lamps) are excluded from the UI. - New reason code setup.relayTest in @parking/shared (+ EN template); sq/en keys. - EventLog constructed before setupRoutes so the route can sign. - Integration test (stub controller, no hardware): RBAC 403, CSRF 403, signed barrier_open_command on success, 400 unknown relay w/ no ledger row, 404 unknown controller, 400 bad relay value. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -460,6 +460,21 @@ export function testPrint(
|
||||
});
|
||||
}
|
||||
|
||||
export type RelayTestResult =
|
||||
| { ok: true; firedAt: string; tookMs: number }
|
||||
| { ok: false; reason: string; detail?: string; tookMs?: number };
|
||||
|
||||
/** Pulse a SAVED controller's barrier relay to test the wiring — physically opens the
|
||||
* barrier. The server signs a `barrier_open_command` (reason setup.relayTest) before
|
||||
* firing, so the open is explained, not a reconciliation anomaly. Saved controller only
|
||||
* (needs a persisted id for attribution). */
|
||||
export function testRelay(id: string, relay: number): Promise<RelayTestResult> {
|
||||
return apiFetch<RelayTestResult>("/api/setup/test-relay", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ id, relay }),
|
||||
});
|
||||
}
|
||||
|
||||
// --- Admin reports -------------------------------------------------------
|
||||
export type ReportBucket = "hour" | "day" | "month";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user