Files
adebisi-fa 548e33390c feat(fpagent): show scanner availability and gate capture
Consume fpagent's fprint-device-status {available, device, captureAgentId}
in the /fingerprint wizard. Availability is tracked tri-state so an agent
that does not report it leaves capture working as before; the header tag
shows offline / no-scanner / ready, the capture panel warns, and capture is
disabled until a supported reader is attached.

Adds a vm-sandbox test for the wizard logic and the OpenSpec change
fpagent-device-availability, archived with the fingerprint-wizard spec.
2026-10-02 15:12:16 +01:00

3.3 KiB

Design

Context

See proposal.md — Why. fpagent pushes fprint-device-status {available, device, captureAgentId} to each connection on connect and to all connections when availability changes (2 s watcher; probes skipped while an enrollment runs). available means "a supported reader is attached", not that a capture will succeed. The wizard currently learns only agent-hello and the enrollment events, and its header tag reflects only the socket connection.

Goals / Non-Goals

Goals: surface scanner availability in the wizard; stop a capture from starting when no reader is attached; keep working against agents that do not emit the event.

Non-Goals: changing the agent or the event contract; blocking capture when the agent is merely unreachable (pre-existing behavior); a proactive device picker or wiring guidance.

Decisions

  1. The client is a pure listener. The agent already emits on connect and on change, so the wizard adds one handler and no polling or request event.

  2. Availability is tri-state, not boolean. agent.available starts null (unknown) and becomes true/false on the first status. An agent that never emits the event leaves it null, so captures are not blocked — the feature degrades to today's behavior instead of deadlocking older field laptops.

  3. The status tag has four labels. Offline (socket down), "Scanner not connected" (available: false), "Agent ready" (available: true), and "Agent connected" (unknown). The title carries the version and the device name when known.

  4. Capture is gated at both layers. The buttons are disabled when available === false, and capture() returns early on the same condition so a race (availability changing between click and handler) cannot start an enrollment. The agent remains authoritative and still rejects a capture with no reader.

  5. The gating logic gets a small vm-sandbox test. app.js is a browser IIFE, so test/fp-wizard.test.js loads it in a node:vm sandbox with stubbed Vue/io/document and exercises the availability state, the tag labels, and the capture refusal directly. No new dependency and no browser runtime; the remaining end-to-end pass against a real agent is manual, since /fingerprint requires a session.

Risks / Trade-offs

  • [Old agent never emits the event] → availability stays null, captures are allowed, behavior matches today. The status tag shows "Agent connected".
  • [Brief wrong tag between socket connect and first status] → the agent emits the status in the same connection handler as agent-hello, so the window is negligible; the null state shows "Agent connected" rather than a false "Scanner not connected".
  • [available read as "ready to capture"] → the agent's contract says it means "attached"; enrollment remains authoritative and a capture can still fail.
  • [Watcher is skipped during a capture] → availability is not updated mid-scan, which is correct: the reader is in use by that capture.

Migration Plan

Ship the wizard; it is backward compatible with agents that lack the event. No agent change is required to deploy the wizard, and the full benefit appears once the field laptops run the agent that emits fprint-device-status. Rollback = revert the wizard.

Open Questions

None.