--- type: concept tags: [parking, setup, devices, integrity, admin] sources: [] updated: 2026-07-04 status: settled --- # Setup relay test (signed, admin-only barrier pulse) Commissioning a lane needs a way to prove **wiring**: does relay N on this controller actually lift *this* barrier? Before this feature (built 2026-07-01) the only way was to fake a credential or short an input — both of which pollute the flows they exercise. Now the Setup controller section offers a per-relay **Test** button that pulses the relay directly. ## The design constraint: a test open must be SIGNED The core anti-fraud rule ([[append-only-event-chain]]) is that **a physical barrier open with no matching signed command is THE fraud signal**. An unsigned test pulse would therefore read as fraud in any reconciliation of controller logs vs. ledger. So the test **signs a `barrier_open_command` BEFORE the pulse fires** — same ordering invariant as every real open: - `source: "manual"` (a deliberate human action, same as an operator barrier open), `identity: relay-test::`, - payload: `reasonPayload("setup.relayTest", { operator, relay, controller })` + `relayTest: true` so reconciliation and the feed can tell a test from an intervention. - No `EventLog` available (boot ordering) → the endpoint refuses (503) rather than fire unsigned. ## Guardrails - **Admin-only** (`site:update`) + CSRF — the operator (the [[threat-model]] adversary) cannot pulse barriers from Setup. - **Saved controllers only**, and only relays the saved config **declares** (unknown relay → 400, unknown controller → 404, nothing signed on any refusal). No free-form "pulse anything" seam. - **`radarAlert` (lamp) relays are excluded** in the UI — they are aux outputs, not barriers ([[button-light-indicator]]); the test drives `pulseOpen` only, so [[barrier-not-a-door]] holds (intent-only; the barrier firmware owns the close). - UI confirms before firing (`Test` → confirm dialog → pulse), per-relay buttons in the controller assignment row. ## As-built - Endpoint `POST /api/setup/test-relay` (`routes/setup.ts`) — validates, signs, then `registry.create(...)` → `pulseOpen(relay)`. Feature-detects `pulseOpen` on the built device. - UI: `RelayTester` in `SetupWizard.tsx` (access category only). Reason code `setup.relayTest` in `@parking/shared` + both web catalogs ([[i18n]]). - Tests: `setup-relay-test.test.ts` — RBAC 403, CSRF 403, signed command on success, 400/404 refusals sign nothing, bad relay value.