# Protected browser review Implemented under `INFD-WP-0001-T08`, release `0.2.0`. The configured service now presents one named memo, records explicit highlight acknowledgments and accept/return/discuss/decline responses, and submits an accepted request to Approval Engine. This source has passed a disposable Chromium exercise; native policy, human login, custody and deployment admission remain open. This first browser profile accepts English, organizational `approve` memos only. Other locales, weaker steps and AES/QES levels refuse before rendering, so an English interface cannot attest to a German presentation or imply a signature level it does not implement. German UI acceptance (PR-60), broader steps and awareness controls remain T08 product work; the domain model retains their vocabulary. The initial factory browser proof uses this bounded profile. ## Request and evidence path 1. KeyCape login verifies the human, paired token claims and MFA facts. The browser holds only an opaque session cookie. The human access token is used only for Approval Engine and never as the Flex Auth workload credential. 2. `review.py` checks the named recipient and requests a fresh Flex Auth `read` decision before exposing memo/packet content or calling Approval Engine. The [consumer contract](flex-auth-review-contract.md) defines all six actions. 3. The immutable policy observation is persisted before use. The returned package/version/digest, submitted request digest, core binding and lifetime must match. Unsupported effects/obligations and unavailable callers refuse. Registry enrichment is observed, never reproduced by this consumer. 4. The live Approval Engine object must declare `human_control=true` and match the memo's carried act digest. The requesting party and named human approver remain distinct. The presentation privately retains the observed binding. 5. Every acknowledgment or response obtains another applicable decision. Accept requires the current memo, required acknowledgments, a live human session, current audit readiness and an open matching approval. Immediately before the engine POST, the adapter rechecks the binding and the controller checks policy/session freshness and reserves the single durable attempt. 6. Acceptance stores the original engine entry correlation. Reload does not POST again. A lost response, in-flight attempt or unknown duplicate remains visible and cannot acquire a new presentation link. Return, discussion and decline record local human responses and never create an engine entry. The governed canonicalizer remains the sole writer of `view_hash`. No presentation/acknowledgment fact is submitted to its own permission decision. Policy observations stay in private content, outside the presentation claim. `decision_attributable=false` remains explicit: checking the current unsigned envelope is not GH-DEC-2026-010 decision-path validation (`FLEX-WP-0024`). The renderer requires `memo.ui_release=informed-decision@0.2.0`. An older memo must receive a new immutable version; it is not silently relabelled or rendered as though the old interface produced the current view. Schema v1 databases migrate atomically to v2 by adding append-only policy observations, preserving old evidence and submission records. ## Browser surface | Route | Behavior | | --- | --- | | `GET /` | Sign-in or named memo identifier form; no approvals inbox | | `GET /review?memo_id=...` | Entitled current memo; existing intent retrieves its original view | | `GET /presentations/` | Entitled historical presentation and current submission result | | `GET /presentations//packet/` | Fresh read permission and full attachment bytes | | `POST /presentations//ack` | Explicit acknowledgment of selected highlights | | `POST /presentations//act` | Accept, return, discuss or decline with an operation id | Forms require the exact fixed origin and session CSRF token, with bounded bodies and rejection of duplicate/unknown fields. Session expiry and actor/version checks apply server-side, including when a user bypasses a disabled button. The page names the complete request and packet as the object of acceptance. Highlights do not narrow its scope. Untrusted memo text is escaped, previews are bounded text, and downloads are attachments with `nosniff`. Pages use `Referrer-Policy: same-origin`, while authentication paths and downloads use `no-referrer`. The Chromium test caught that using `no-referrer` on a form page produces `Origin: null` and rejects legitimate submissions. Missing, foreign and null form origins remain refused. Cross-origin referrers, inline scripts, framing and caching remain prohibited. There is no telemetry or client-side token storage. The reverse proxy must still omit auth queries, bearer headers and cookies from its logs. ## Owner configuration and audit readiness Set `INFD_REVIEW_CONFIG` to an absolute, owned, regular 0600 JSON file. Its parent evidence directory must already exist with mode 0700. Without this configuration, the entrypoint remains the login shell and `/readyz` stays 503. The following is a shape example, **not an admitted configuration**; the placeholders intentionally do not pass validation: ```json { "schema": "informed-decision.review-runtime.v1", "evidence_db": "/OWNER-PRIVATE-PATH/review.sqlite", "approval_origin": "https://APPROVAL-OWNER-ORIGIN", "policy": { "origin": "https://FLEX-OWNER-ORIGIN", "package": "OWNER-ADMITTED-PACKAGE", "version": "OWNER-ADMITTED-VERSION", "package_digest": "OWNER-ADMITTED-SHA256", "caller_token_file": "/OWNER-PROJECTED-PATH/flex-caller-token" }, "audit": { "origin": "https://AUDIT-OWNER-ORIGIN", "sender_token_file": "/OWNER-PRIVATE-PATH/audit-sender-token" } } ``` `INFD_KEYCAPE_ISSUER` remains mandatory. Credential files are read afresh when needed, so projected rotation does not require storing a token in configuration or SQLite. Runtime loading provisions nothing. Fixed internal `.svc` origins are supported explicitly; public cleartext origins and redirects are refused. The server remains one Waitress process, loopback port 8080, four threads. Multi-replica sessions/storage require separate work. Single-writer container packaging and the review-only deployment packet are now supplied in [deploy/README.md](../deploy/README.md); publication and native rollout remain open. The audit thread ticks every 30 seconds, generates the declared per-class heartbeats, drains at most ten records per tick and writes a private `audit-reconciliation.json` at most every five minutes. A successful drain and reconciliation with no retained delivery debt make readiness current for 90 seconds. Errors or blocked records clear readiness; accept refuses while it is closed. This is a process/delivery health signal, not an assertion of native policy admission or authorization. Dispositions still persist transactionally. Credential/schema refusals need operator repair and explicit requeue; no failed event is discarded. Source `occurred_at` and receiver `accepted_at` are retained as different count windows, without an automatic loss or completeness claim. An independent auditor reads the receiver in component tests. Production sender/receiver custody, backup/restore and reconciliation procedures remain admission work under T08 and `AUDIT-WP-0009-T11`. Uncertain entry recovery must preserve causation. Finding a later entry is insufficient because Approval Engine stores no caller presentation/operation id. Inspect the original immutable intent and independent evidence; never reset an unresolved attempt merely to make the button available. If causation cannot be established, keep it unresolved and obtain an owner-directed disposition of the approval. This UI offers no shortcut that fabricates the missing link. ## Reproduce the component and browser checks Build the checked Flex Auth source with its own `go build ./cmd/flex-auth`, then set explicit source paths and the resulting binary: ```sh export INFD_APPROVAL_ENGINE_SOURCE=/path/to/approval-engine export INFD_AUDIT_CORE_SOURCE=/path/to/audit-core export INFD_FLEX_AUTH_BINARY=/absolute/path/to/flex-auth make check ``` With the Python dev dependencies, Playwright and Chromium installed, start `PYTHONPATH=. python tests/browser_fixture.py /tmp/infd-browser-UNIQUE` and run `node tests/browser_review.mjs /tmp/infd-browser-UNIQUE` in another terminal. `INFD_PLAYWRIGHT_PACKAGE` can name an installed Playwright package, and `INFD_CHROMIUM_EXECUTABLE` can name its Chromium executable. The driver stops the fixture and writes the result plus desktop/mobile screenshots. The harness uses real local HTTPS, browser redirects/forms/cookies, the actual Flex Auth server with enforced caller authentication, and the actual Approval Engine/Audit Core APIs with separate SQLite stores. Its issuer, TokenReview, policy assignments, identities, credentials and act are synthetic. Browser hostnames resolve to loopback; only the fixture certificate's public key is trusted by the override. The harness control endpoints live only in tests. Exact production callback/registration strings remain covered separately. The result covers login, escaped content, acknowledgment bypass refusal, explicit acknowledgment, packet download, return, mobile fit, a single accepted entry/reload, receiver delivery, workload caller refusal, sign-out and absence of script execution. It proves no native human login, production policy/custody, deployed UI or factory execution. See the dated JSON evidence for exact sources.