From 7f0f500050da0ed7f78434f5f2993d6f381c5f63 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 5 May 2026 14:12:43 -0500 Subject: [PATCH] =?UTF-8?q?phase=201.6:=20candidate=20intake=20UI=20?= =?UTF-8?q?=E2=80=94=20operator-driven=20consent=20+=20photo=20capture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the Gate 2 frontend that operators use to capture candidate biometric consent + photo at intake. Without this, the /biometric/subject/{id}/consent endpoint shipped in 76cb5ac has no caller — operators would have to build curl scripts by hand. Surfaces: - mcp-server serves /biometric/intake as static HTML (mounted at http://localhost:3700/biometric/intake; externally accessible via nginx at https://devop.live/lakehouse/biometric/intake) - URL must include ?candidate_id=WORKER-XXX Page flow (4 screens): - Step 1 — operator authentication. Pastes legal-tier audit token into a password field; stored in sessionStorage (cleared on tab close), never localStorage, never cookies. - Step 2 — consent. Renders the v1 consent template text inline (Disclosures 1/2/3 + plain-language summary, all matching docs/policies/consent/biometric_consent_template_v1.md as of this commit). Captures candidate printed name + checkbox accept; computes SHA-256 of the rendered consent block as consent_version_hash; computes SHA-256 of the click-acceptance evidence (method + name + ts + user_agent + page_origin) and records it as inline:sha256= evidence path. POSTs to gateway /biometric/subject/{id}/consent. - Step 3 — photo. Two paths: file upload OR getUserMedia camera capture. Preview before submit. Skip-photo button for consent-only intake (e.g. consent collected before equipment available). POSTs to gateway /biometric/subject/{id}/photo. - Step 4 — confirmation. Displays the audit row HMACs from both endpoint responses + the verify_biometric_erasure.sh command the operator can run for chain attestation. Design choices: - No framework, no build step. Single self-contained HTML file ~22KB. Matches the existing mcp-server precedent (onboard.html, console.html, etc.). - Neo-brutalist dark style matching mcp-server's other pages (tracked dark surfaces, monospace technical metadata, sharp borders). Consistent with j's UI preferences. - Server-authoritative timestamps. The page sends its own UA + click ts as evidence, but the canonical given_at on the manifest comes from the gateway's Utc::now() (per process_consent). Page displays whatever the gateway returns. - Gateway URL configurable via ?gw= query param; defaults to http://localhost:3100 for the same-box workstation pattern. External-access deployment requires a separate nginx route for the gateway (out of scope for v1 to avoid touching the devop.live nginx config). Verified live: - GET http://localhost:3700/biometric/intake?candidate_id=WORKER-100 returns 200 + 22KB HTML body - GET https://devop.live/lakehouse/biometric/intake?candidate_id=WORKER-100 returns 200 (nginx /lakehouse/ route proxies to :3700) - Gateway CORS preflight for POST /biometric/subject/{id}/consent from origin http://localhost:3700: 200 with allow-methods/headers/origin: * Co-Authored-By: Claude Opus 4.7 (1M context) --- mcp-server/biometric_intake.html | 467 +++++++++++++++++++++++++++++++ mcp-server/index.ts | 11 + 2 files changed, 478 insertions(+) create mode 100644 mcp-server/biometric_intake.html diff --git a/mcp-server/biometric_intake.html b/mcp-server/biometric_intake.html new file mode 100644 index 0000000..962b6ae --- /dev/null +++ b/mcp-server/biometric_intake.html @@ -0,0 +1,467 @@ + + + + + +Lakehouse — Biometric Consent Intake + + + + +
+

⚡ Biometric Consent Intake

+ step 1 of 4 + +
+ +
+ + +
+

Operator authentication

+

Paste the legal-tier audit token. Stored in this tab's session only; cleared on close. Never persists to disk.

+
+ + +
+ +
+
+
+
+ + + + + +
+

Photo capture

+

Take or upload a clear photo. Stored quarantined under data/biometric/uploads/ with mode 0700/0600 + SHA-256 integrity hash. Audit chain records the upload event.

+ +
+

Option A — File upload

+ + +

Option B — Camera capture

+ + + + + + +

Preview

+ +

No photo selected yet.

+ +
+ + +
+
+
+
+ + +
+
+

✓ Intake complete

+

Audit chain rows (HMAC-SHA256, persisted to data/_catalog/subjects/<id>.audit.jsonl):

+
+ +
+

Consent grant

+
+ StatusGiven + Given at + Retention until +
+

Audit hmac

+ +
+ + + +
+

Verification

+

Operator: confirm the audit chain by running:

+
./scripts/staffing/verify_biometric_erasure.sh <candidate_id>
+

Or hit GET /audit/subject/<id> with legal-tier auth to read the full chain.

+
+ +
+ +
+
+ +
+ + + + + + diff --git a/mcp-server/index.ts b/mcp-server/index.ts index c144b43..e2b0cd5 100644 --- a/mcp-server/index.ts +++ b/mcp-server/index.ts @@ -767,6 +767,17 @@ async function main() { }); } + // Biometric intake — Phase 1.6 Gate 2 frontend. Operator-driven + // candidate consent + photo capture flow. POSTs to gateway's + // /biometric/subject/{id}/consent + /photo. URL must include + // ?candidate_id=WORKER-XXX. Operator's legal-tier audit token + // is captured into sessionStorage (cleared on tab close). + if (url.pathname === "/biometric/intake") { + return new Response(Bun.file(import.meta.dir + "/biometric_intake.html"), { + headers: { ...cors, "Content-Type": "text/html" }, + }); + } + // Workspaces — per-contract state (Phase 8.5). UI layer over the // gateway's /workspaces/* routes: list, create, detail, handoff, // save-search, shortlist, log-activity. All persisted on the