Files
tools-legacy/openspec/specs/fingerprint-wizard/spec.md
T
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

9.0 KiB

fingerprint-wizard Specification

Purpose

The Fingerprint Agent enrollment wizard at /fingerprint: offers the operator only the domains they may capture for, runs two-finger enrollment through the local fpagent capture agent, and saves the resulting templates to a2loft with per-call OTP validation.

Requirements

Requirement: Permitted domain selection

The wizard SHALL offer, from the hardcoded catalog — UNICAL (uc), Metropolitan Hotel (mtr), UCTH (ucth), UNICAL Radio International (uri), YOUreka (yrka) — only the domains the operator is permitted to capture for. Permission SHALL be determined after login against the same public portal OpenFGA verify endpoint fpagent uses (https://portal.yrka.app/api/openfga/verify), requesting the capture_biometric relation for domain:<code> on subsystem idid for the operator's phone (leading + stripped), with a fallback check against domain:idid; the operator is permitted if either check allows. Each domain's checks SHALL fail closed on error or denial. Catalog checks SHALL run in parallel, and their results SHALL be cached per operator phone for 5 minutes: session fetches within that window SHALL serve the cached result without contacting the portal, and all catalog domains SHALL be re-checked in parallel once that period expires.

Scenario: Permitted domain is offered

  • WHEN the portal verify endpoint allows the operator for a catalog domain
  • THEN that domain appears in the wizard's domain selection

Scenario: Denied or unavailable domain is hidden

  • WHEN the portal verify endpoint denies a domain, or the check errors
  • THEN that domain is not offered

Scenario: Fallback object allows capture

  • WHEN the domain:<code> check denies but the domain:idid check allows
  • THEN the domain is offered

Scenario: Cached result within the TTL

  • WHEN a session fetch occurs within 5 minutes of the last permission check for the operator
  • THEN the cached permitted-domain list is served without calling the portal verify endpoint

Scenario: Re-check after the TTL

  • WHEN a session fetch occurs after the 5-minute cache period has elapsed
  • THEN all catalog domains are re-checked and the list reflects current permissions

Scenario: No permitted domains

  • WHEN no catalog domain is permitted for the operator
  • THEN the wizard explains they are not permitted to capture fingerprints for any domain and offers no capture UI

Requirement: Manual refresh of permitted domains

The wizard SHALL offer a refresh control for the permitted-domain list that stays disabled until the 5-minute cache period since the last check has elapsed. Using it SHALL call the app's force-refresh endpoint, update the offered domains, and update the displayed last-checked time; a refresh refused because the cache is still fresh SHALL leave the current list and state unchanged.

Scenario: Refresh control locked while the cache is fresh

  • WHEN fewer than 5 minutes have passed since the permitted-domain set was last checked
  • THEN the refresh control is disabled

Scenario: Refresh after the cache elapses

  • WHEN the cache period has elapsed and the operator uses the refresh control
  • THEN the wizard calls the force-refresh endpoint and the offered domains update to the re-checked result

Scenario: Refused refresh leaves state unchanged

  • WHEN a refresh request is refused because the cache is still fresh
  • THEN the wizard keeps the current domain list and selection

Requirement: Enrollment captures via fpagent

The wizard SHALL capture fingerprints through fpagent at the URL provided by the session endpoint, emitting fprint-enroll with the session's PocketBase token, the selected domain code, the finger name, and the staff reference. It SHALL display capture progress from fprint-enroll-status events, record the returned fingerprint template, image, and enrollment id on fprint-enroll-done with ok: true, and surface the failure message and return to idle on ok: false. The wizard SHALL offer cancellation of an in-flight capture (emitting fprint-enroll-stop), SHALL prevent starting a capture while another is in flight (fpagent serves one enrollment at a time), and SHALL display the agent version reported on connect.

Scenario: Successful capture

  • WHEN the operator starts a capture for a finger and fpagent reports fprint-enroll-done with ok: true
  • THEN the wizard records the fingerprint, image, and enrollment id for that finger and displays the enrollment id

Scenario: Failed capture

  • WHEN fpagent reports fprint-enroll-done with ok: false (authentication failed, authorization unavailable, not authorized, or capture failed)
  • THEN the wizard shows the failure message and returns to the idle state without recording artifacts

Scenario: Cancel in-flight capture

  • WHEN the operator cancels a capture in progress
  • THEN the wizard emits the stop event and returns to the idle state

Scenario: No concurrent captures

  • WHEN a capture is in flight
  • THEN the capture controls for both fingers are disabled until it completes or is cancelled

Requirement: Two-finger enrollment flow

The wizard SHALL enroll a profile by: selecting a permitted domain, looking up staff by reference (OTP-validated through the a2loft proxy), displaying the staff details, capturing the right thumb and right index finger, and saving both fingerprints through the a2loft proxy with an OTP. The wizard SHALL offer per-finger recapture until save, SHALL require both fingers before save, and SHALL reset domain-independent state (reference, staff details, captured artifacts) when starting a new enrollment.

Scenario: Complete enrollment

  • WHEN the operator looks up staff, captures both fingers, and saves with an OTP
  • THEN both fingerprints are saved through the a2loft proxy and the wizard reports success

Scenario: Save requires both fingers

  • WHEN the operator attempts to save with only one finger captured
  • THEN the wizard rejects the save and explains both fingers are required

Scenario: New enrollment resets state

  • WHEN the operator starts a new enrollment after a save
  • THEN the reference, staff details, and captured finger artifacts are cleared while the permitted domain list persists

Requirement: OTP validated on every a2loft call

The wizard SHALL collect an OTP for each a2loft call — staff lookup and fingerprint save — and pass it to the same-origin proxy; a2loft validates it. Submission SHALL be blocked while the OTP is missing.

Scenario: Lookup with OTP

  • WHEN the operator submits a reference with an OTP
  • THEN the wizard calls the staff-lookup proxy with that OTP and displays the returned staff details

Scenario: Save requires a fresh OTP

  • WHEN the operator saves the captured fingerprints
  • THEN the wizard sends the OTP entered for the save call

Scenario: Missing OTP blocks submission

  • WHEN the OTP field is empty for a lookup or save
  • THEN the corresponding submit control is disabled

Requirement: Scanner availability is shown and gates capture

The wizard SHALL consume fpagent's fprint-device-status {available, device, captureAgentId} event and SHALL distinguish the agent being offline (socket not connected), connected with no supported reader attached (available false), and ready (available true). Until a status is received, availability SHALL be treated as unknown. The wizard SHALL NOT start a capture while no supported reader is attached, SHALL re-enable capture when a reader becomes available, and SHALL clear availability when the agent disconnects. Availability means "a supported reader is attached"; it does not guarantee a capture will succeed.

Scenario: Reader attached on connect

  • WHEN the page connects and fpagent reports fprint-device-status with available: true
  • THEN the wizard shows the agent ready and leaves capture enabled

Scenario: No reader attached on connect

  • WHEN the page connects and fpagent reports fprint-device-status with available: false
  • THEN the wizard shows that the scanner is not connected, explains captures are disabled, and disables the capture controls

Scenario: Unplug blocks capture

  • WHEN fpagent reports available: false while the operator is capturing or between captures
  • THEN the wizard disables the capture controls and a capture attempt is refused

Scenario: Replug re-enables capture

  • WHEN fpagent reports available: true after the reader is reconnected
  • THEN the wizard re-enables the capture controls

Scenario: Status absent or old agent

  • WHEN the connected agent never reports fprint-device-status
  • THEN availability stays unknown and the wizard does not block capture

Scenario: Disconnect clears availability

  • WHEN the agent socket disconnects
  • THEN the wizard shows the agent offline and does not present the reader as available