# Browser authentication and Approval Engine transport Implemented under `INFD-WP-0001-T08`. The service currently supplies sign-in and sign-out. It does not yet render memos or expose an approval-entry route. Install and exercise from the repository: ```sh uv venv uv pip install -e '.[dev]' uv run python -m pytest -q INFD_APPROVAL_ENGINE_SOURCE=/home/worsch/approval-engine uv run python -m pytest -q uv run python tools/smoke_browser_shell.py INFD_KEYCAPE_ISSUER=https://kc.coulomb.social uv run informed-decision-web ``` The component checks require the explicit Approval Engine source path. They use its actual JWT verifier and API with locally signed synthetic identity fixtures. They are not native human authentication or custody proof. The smoke script needs free loopback port 8080, checks the installed entrypoint and stops it. It never follows the authorization redirect or contacts KeyCape. `--receipt PATH` writes metadata-only results. The entrypoint listens on `127.0.0.1:8080` for a local reverse proxy. The public origin/callback is fixed at `https://decisions.coulomb.social/auth/callback`; Host, forwarded headers and return URLs cannot replace it. A development HTTP listener does not replace this HTTPS callback registration. Deployment and registration remain pending. `/healthz` checks this process; `/readyz` returns 503 because the protected approval path is incomplete. ## Identity boundary `KeyCapeLogin` uses a configured HTTPS issuer with its `/authorize`, `/token` and `/jwks` endpoints. No discovery or JWT header can substitute an endpoint. It requests only `openid approval:read approval:approve`, with public-client S256 PKCE. State, a separate browser cookie and nonce are random; the callback consumes its five-minute pending state before token exchange. The access token never enters a browser cookie, HTML, URL or error message. The verifier accepts RS256 only and checks both tokens against the issuer JWKS: ID-token audience is the client, access-token audience is `approval-engine`. Subject, tenant, provenance and assurance must agree. Nonce, time bounds, exact scopes, human principal, platform tenant and the published KeyCape MFA facts are required. JWKS refresh failure refuses login; expired cached keys are not a fallback. Login does not decide entitlement to view or approve anything. KeyCape source `f9812ab3b2bfe8f0817185f44071e612264ec3ee`, specifically `src/internal/server/oidc/token.go`, is the provenance contract reviewed here: the code handler looks up the authenticated user and emits `human`; client credentials emit `service`. `tenant_source=directory` and `registration` stay distinct. Unknown/default provenance is refused in this flow. A verified registration-supplied tenant remains the bounded GH-DEC-2026-013 gap. MFA `at` is preserved as authentication time. A successful login does not assert that MFA meets an action-specific freshness policy; the policy integration must use these facts for the action. No local approval verdict is inferred. Sessions last at most fifteen minutes and never outlive either token. They are bounded to 1,024 entries, protected by a process lock, rotated on sign-in and deleted on sign-out/expiry. Restart requires reauthentication. This is a single-process design; replicas need a separate shared-session design. These sessions contain no approval current state or evidence. Cookies are Secure, HttpOnly, SameSite=Lax with the `__Host-` prefix. Sign-out requires POST, the exact origin and a session CSRF token. Responses prohibit caching and framing. The proxy must omit callback query strings, bearer headers and cookies from access logs. Waitress has no request-target access log enabled by this entrypoint. ## Approval transport boundary `ApprovalHTTPClient` implements get-by-id and add-entry using the session's access token. Each read reaches the engine. It requires exact `binding.human_control=true` for this first factory integration and preserves the native `sha256:<64 hex>` act digest. A pre-entry read rejects an undeclared object before mutation. Identifiers cannot inject extra paths or query strings. The default origin is HTTPS. The current in-cluster service can be wired with explicit `allow_internal_http=True` and a fixed `.svc`/`.svc.cluster.local` origin; that option also permits numeric loopback for component tests. Public HTTP origins are refused. Redirects are never followed, response bodies are limited to 256 KiB, and socket operations use a five-second timeout. No ambient proxy credentials are inherited. POST bodies are empty objects: Approval Engine derives identity and evidence from its verified token and discards caller content. The adapter extracts `(approval_id, subject_id, approved_at)` from the matching actual human entry, never `updated_at` or local time. A `409 duplicate_approver` triggers a GET of that original entry and returns `duplicate=True`. The eventual UI must preserve the original presentation correlation; it cannot attach a new presentation to an old entry just because a duplicate exists. Other conflicts remain refusals. A transport failure after POST is ambiguous: the engine may have committed. There is no automatic POST retry. Durable disposition processing must reconcile the current engine entry and its correlation before reporting success or offering a retry. A failed dependency must never be recorded as a human decline. The client is an internal seam, not a sufficient binding flow. Before a browser route can call it, T08 must connect: 1. The access-engine entitlement decision before rendering a named memo. 2. Wire the durable presentation/version, actor match and required acknowledgments. 3. Wire persisted dispositions and the transactional outbox, including return/discuss. 4. Independent audit custody/delivery and entry-correlation reconciliation. 5. Native registered KeyCape login and the deployed Approval Engine proof. The browser currently exposes no memo or entry route and no consume capability. The legacy `evidence.Outbox` is an in-memory test double. `Store` now supplies durable atomic state/evidence, and `audit.py` supplies delivery to the real receiver contract; see [durable-review-evidence.md](durable-review-evidence.md). Native custody, policy/controller integration and live admission stay in T08.