Files
tools-legacy/openspec/changes/archive/2026-09-30-fpagent-tools-ui/tasks.md
T
adebisi-fa 109a69961a docs(openspec): fpagent-tools-ui change — proposal, specs, design, tasks
Synced main specs (tools-app, fingerprint-wizard); change archived at
2026-09-30-fpagent-tools-ui. Includes openspec project config and the
agent skills installed with it.
2026-09-30 23:12:51 +01:00

4.6 KiB

Tasks

1. Scaffold the tools app

  • 1.1 Create tools/ with package.json (express, pocketbase; ESM), server.js with hardcoded constants (port 4173, portal, PocketBase, a2loft, agent URL, domain catalog) and static serving; verify node server.js boots and GET / serves the landing page linking https://smart-pxa.yrka.app and /fingerprint

2. YOUreka Auth session

  • 2.1 Port yrka-auth.js from one-school-chat/src/lib/server/yrka-auth.ts (cookie → PocketBase session load, refresh when invalid/expiring <5 min); verify with node --test and a stubbed PocketBase client covering valid, refresh-needed, and unrecoverable paths
  • 2.2 Add GET /auth/login (create portal web-auth session with callback URL, redirect to login URL), GET /auth/callback (redeem session id, set httpOnly session + profile cookies 30d, redirect /fingerprint; error page on unredeemable id), GET /auth/logout (clear cookies, redirect /); verify with a stubbed portal upstream that cookies are set on success and not set on failure
  • 2.3 Add session middleware (recover/refresh/clear per design) and route protection: /fingerprint redirects anonymous to login, API routes reject anonymous; verify with curl both with and without a session cookie
  • 2.4 Add permit.js (OpenFGA verify with domain:<code> then domain:idid fallback, fail-closed per domain, phone +-stripped), cache permitted domains per operator phone for 5 minutes (in-memory map storing the allowed set + checked time), have GET /auth/session return token, phone, name, agent URL, the cached domain list, and the last-checked time, and add a force-refresh endpoint that re-checks all domains bypassing the cache but refuses while the cache is fresh; verify with node --test and stubbed fetch covering allow, deny, fallback-allow, transport-error, cache-hit-with-zero-portal-calls, re-check-after-expiry, and refresh-refused-while-fresh cases

3. Fingerprint wizard page

  • 3.1 Port public/capture/ UI to tools/public/fingerprint/ (Vue 2 + Bootstrap 4 + socket.io + axios + swal via CDN); fetch /auth/session on load; replace the fixed domain with a dropdown of permitted domains; show the no-permitted-domains message when the list is empty; verify the page renders for a sessioned user and redirects anonymous users
  • 3.2 Wire the socket to the agent URL from the session: agent-hello → show version/captureAgentId; capture buttons emit fprint-enroll {token, domain, finger, reference}; fprint-enroll-status → "Scan N of 5" progress; fprint-enroll-done ok:true → store fingerprint/image/id and show enrollment id, ok:false → swal the failure message and reset to idle; cancel emits fprint-enroll-stop; disable both fingers' capture controls while a capture is in flight; verify against a running fpagent (hardware or its virtual-driver harness) for capture, fail, and cancel paths
  • 3.3 Add the permitted-domains refresh control to the wizard: disabled until 5 minutes have elapsed since the session response's last-checked time; on click call the force-refresh endpoint and update the dropdown and last-checked time (a refused refresh leaves the list and selection unchanged); verify in the browser that the control is disabled within the TTL, enables after, and a click refreshes the list

4. a2loft proxies

  • 4.1 Add session-required proxies POST /api/:domain/staff/:reference and POST /api/:domain/staff/:reference/fingerprints that call a2loft with ?phone=<session phone>&otp=<query> and unchanged request/response shapes; verify with node --test and a fake a2loft upstream asserting the forwarded query and body
  • 4.2 Switch the wizard's lookup and save to the proxies with per-call OTP inputs (lookup step and save step), keeping the both-fingers-required rule and the reset-on-new-enrollment behavior; verify the full lookup → details → save round trip against a2loft

5. Cleanup and docs

  • 5.1 Run the parity walkthrough on field hardware (login → filtered domains → lookup → capture thumb + index → save; saved fingerprints verify as before) and only then delete app.py, requirement.txt, and public/
  • 5.2 Update README: run instructions (node tools/server.js, port 4173), hardcoded endpoints, and the fpagent -allowed-origins requirement including http://localhost:4173; verify a fresh clone can follow it end to end

6. End-to-end verification

  • 6.1 Run fpagent with -allowed-origins http://localhost:4173 and the tools app; walk login → permitted domains → staff lookup → two captures → save against the real portal/PocketBase/a2loft, and confirm the saved fingerprints appear in a2loft for the profile