Connect policy-gated browser review and audit runtime
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
parent
2cc32168ac
commit
83849b75d4
35 changed files with 2381 additions and 83 deletions
|
|
@ -1,7 +1,8 @@
|
|||
# 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.
|
||||
Implemented under `INFD-WP-0001-T08`. The service supplies sign-in and sign-out;
|
||||
explicit owner configuration enables the protected memo and human-response
|
||||
routes described in [protected-browser-review.md](protected-browser-review.md).
|
||||
|
||||
Install and exercise from the repository:
|
||||
|
||||
|
|
@ -25,8 +26,9 @@ 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.
|
||||
registration rollout remain pending. `/healthz` checks this process; `/readyz`
|
||||
returns 503 without owner review configuration and recent audit delivery health.
|
||||
Readiness is not a declaration of native policy/login/custody admission.
|
||||
|
||||
## Identity boundary
|
||||
|
||||
|
|
@ -84,7 +86,7 @@ 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
|
||||
that original entry and returns `duplicate=True`. The controller preserves
|
||||
the original presentation correlation; it cannot attach a new presentation to
|
||||
an old entry just because a duplicate exists. Other conflicts remain refusals.
|
||||
|
||||
|
|
@ -93,17 +95,12 @@ 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:
|
||||
The client is an internal seam. `review.py` now connects fresh PDP entitlement,
|
||||
durable presentations, actor/version/acknowledgment guards, local responses,
|
||||
the transactional outbox and the original-entry recovery rules. `runtime.py`
|
||||
schedules bounded delivery and reconciliation. No consume route exists.
|
||||
|
||||
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.
|
||||
The legacy `evidence.Outbox` is an in-memory test double; `Store` supplies
|
||||
durable atomic state/evidence. See [durable-review-evidence.md](durable-review-evidence.md).
|
||||
Native policy/caller assignment, registered human login, deployment and
|
||||
independent custody remain in T08. Disposable browser success does not close them.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
# Durable review evidence and audit delivery
|
||||
|
||||
Implemented under `INFD-WP-0001-T08`. `store.py` replaces the domain tests'
|
||||
in-memory outbox with a persistent internal store. It does not expose a browser
|
||||
route, decide entitlement, or authorize an approval entry. `web.py` still has
|
||||
no memo/entry routes and `/readyz` remains 503.
|
||||
in-memory outbox with a persistent internal store. `review.py` and `web.py` now
|
||||
connect it to the protected browser flow; the store itself never decides
|
||||
entitlement. See [protected-browser-review.md](protected-browser-review.md)
|
||||
for configuration, policy observations, schema v2 migration and readiness.
|
||||
|
||||
## Storage and recovery
|
||||
|
||||
|
|
@ -56,7 +57,7 @@ These are internal delivery records, not cached approval validity:
|
|||
1. Record the human's accept intent and its evidence before an engine POST.
|
||||
2. The controller must obtain a fresh applicable access-engine decision, verify
|
||||
the session and compare the memo's carried digest with the live approval.
|
||||
**That controller/policy integration is still pending.**
|
||||
`review.py` supplies these checks and persists the exact observation before use.
|
||||
3. `begin_submission` atomically reserves one attempt. A stale presentation
|
||||
cannot start; a memo cannot be revised while its entry attempt is in flight
|
||||
or unresolved. Network I/O happens outside the SQLite transaction.
|
||||
|
|
@ -76,7 +77,8 @@ does not prove which presentation caused it. The store does not attach it to
|
|||
the latest view or automatically POST again. A crash after reservation likewise
|
||||
does not reopen dispatch. The records remain visible for explicit controller/
|
||||
operator recovery; no recovery path in this slice fabricates that missing link.
|
||||
The remaining T08 controller must handle this state visibly before shipping.
|
||||
The controller and UI now expose this state and remove the submission button;
|
||||
native operator recovery admission remains in T08.
|
||||
|
||||
## Audit Core contract
|
||||
|
||||
|
|
@ -103,8 +105,9 @@ and auditor identities. That is contract proof, not production custody proof.
|
|||
|
||||
Per-class heartbeat generation uses the existing 86,400-second declarations.
|
||||
It does not emit “nothing to report” for a class with undelivered evidence.
|
||||
The host must schedule heartbeat generation, bounded draining and monitoring;
|
||||
no background service or cadence is installed by this source change.
|
||||
The configured `AuditPump` schedules 30-second ticks, bounded draining and
|
||||
five-minute reconciliation. The deployment owner must admit its private
|
||||
storage, credentials and monitoring; no running service was installed here.
|
||||
|
||||
## Reconciliation has two time bases
|
||||
|
||||
|
|
@ -127,18 +130,20 @@ The GH-DEC-2026-014 commitment-only limitation remains.
|
|||
```sh
|
||||
INFD_APPROVAL_ENGINE_SOURCE=/home/worsch/approval-engine \
|
||||
INFD_AUDIT_CORE_SOURCE=/home/worsch/audit-core \
|
||||
INFD_FLEX_AUTH_BINARY=/absolute/path/to/flex-auth \
|
||||
uv run python -m pytest -q
|
||||
```
|
||||
|
||||
258 tests pass: 52 added tests cover transactional rollback, process death,
|
||||
The prior 258-test milestone added 52 tests for transactional rollback, process death,
|
||||
restore, actor/version/ack guards, concurrent clicks, original correlation,
|
||||
ambiguous submissions, audit retry/refusal and actual receiver contracts.
|
||||
The opt-in suites use the actual Approval Engine and Audit Core implementations
|
||||
with synthetic identity/custody. They make no policy decision and spend nothing.
|
||||
with synthetic identity/custody. The current 344-test suite also exercises the
|
||||
actual Flex Auth HTTP server; 12 Chromium checks cover the complete disposable
|
||||
browser flow. No native credential or paid provider is used.
|
||||
|
||||
Remaining under T08: an admitted PDP package/caller and exact read/bind request,
|
||||
protected review/ack/accept/return/discuss routes, durable policy observations,
|
||||
visible unresolved-entry recovery, scheduled audit delivery/reconciliation,
|
||||
native registered human login and deployed-engine/custody proof. No Informed
|
||||
Remaining under T08: native PDP package/caller/assignment admission against the
|
||||
[exact consumer contract](flex-auth-review-contract.md), registered human login,
|
||||
deployed-engine/custody proof and operator recovery/backup admission. No Informed
|
||||
Decision policy package or registration was found in the checked Flex Auth
|
||||
examples, registry and docs at `88b3543`; do not substitute a local allow rule.
|
||||
|
|
|
|||
81
docs/evidence/2026-09-11-browser-result.json
Normal file
81
docs/evidence/2026-09-11-browser-result.json
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
{
|
||||
"fixture_only": true,
|
||||
"browser": "Chromium",
|
||||
"checks_passed": 12,
|
||||
"checks": [
|
||||
"PKCE callback and secure browser session",
|
||||
"Entitled page escapes memo and packet markup",
|
||||
"Server refuses bypass of missing acknowledgments",
|
||||
"Explicit acknowledgment unlocks the form",
|
||||
"Complete attachment download stays behind entitlement",
|
||||
"Return records a memo response without an engine entry",
|
||||
"Mobile review fits the viewport",
|
||||
"One actual approval entry, stable on browser reload",
|
||||
"Audit receiver holds all commitments; unsigned decision gap retained",
|
||||
"Real workload caller refusal hides review content",
|
||||
"Sign-out clears the session; all forms carry the exact origin",
|
||||
"No page errors or injected script execution"
|
||||
],
|
||||
"fixture_transport": "real HTTPS on ephemeral ports; synthetic issuer and TokenReview",
|
||||
"native_human_login_proven": false,
|
||||
"native_policy_admitted": false,
|
||||
"factory_attempts": 0,
|
||||
"paid_model_calls": 0,
|
||||
"final_component_state": {
|
||||
"entries": 1,
|
||||
"intents": [
|
||||
{
|
||||
"disposition_id": "disp-8ad75d50-1222-4512-a74b-253a15e7d159",
|
||||
"approval_id": "fixture",
|
||||
"subject": "human-fixture",
|
||||
"state": "confirmed",
|
||||
"attempt": "980d4cd4-753e-40f7-8026-f21923a340fd",
|
||||
"approved_at": "2026-09-10T22:29:05+00:00",
|
||||
"presentation_id": "pres-5673438f-ad6c-4ce1-bf26-edfc9211e476",
|
||||
"operation_id": "c60eaa0c-b627-4f59-8e84-d35fc69d4963"
|
||||
}
|
||||
],
|
||||
"outbox_states": [
|
||||
"delivered",
|
||||
"delivered",
|
||||
"delivered",
|
||||
"delivered",
|
||||
"delivered",
|
||||
"delivered",
|
||||
"delivered",
|
||||
"delivered",
|
||||
"delivered"
|
||||
],
|
||||
"policy_outcomes": [
|
||||
"allow",
|
||||
"allow",
|
||||
"allow",
|
||||
"allow",
|
||||
"allow",
|
||||
"allow",
|
||||
"allow",
|
||||
"allow",
|
||||
"allow",
|
||||
"allow",
|
||||
"allow",
|
||||
"caller_refused"
|
||||
],
|
||||
"decision_attributable": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"entry_posts": 1,
|
||||
"consume_calls": 0,
|
||||
"tokenreviews": 12
|
||||
}
|
||||
}
|
||||
93
docs/evidence/2026-09-11-protected-browser-review.json
Normal file
93
docs/evidence/2026-09-11-protected-browser-review.json
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
{
|
||||
"schema": "informed-decision.protected-browser-review.v1",
|
||||
"observed_at": "2026-09-10T22:29:50.412419+00:00",
|
||||
"base_commit": "2cc32168acedb19698fa196911385745fbf897b1",
|
||||
"contract_source_commits": {
|
||||
"approval-engine": "a0a602976eef818f36dde35f76f7f2e589bd051b",
|
||||
"audit-core": "5c0ad522fb36092aa7ec2e8d72f63a5a91853b5b",
|
||||
"flex-auth": "88b354377c8e26b162f1234e673072f1c06dcd89",
|
||||
"key-cape": "139994cfac28ff97163ce4bf263f2bb035bbe0a7"
|
||||
},
|
||||
"source_task": "INFD-WP-0001-T08",
|
||||
"source_task_status": "progress",
|
||||
"verification": {
|
||||
"command": "INFD_APPROVAL_ENGINE_SOURCE=/home/worsch/approval-engine INFD_AUDIT_CORE_SOURCE=/home/worsch/audit-core INFD_FLEX_AUTH_BINARY=<compiled-flex-auth> make check",
|
||||
"tests_passed": 344,
|
||||
"tests_failed": 0,
|
||||
"tests_skipped": 0,
|
||||
"new_tests": 86,
|
||||
"schema_json_valid": true,
|
||||
"flex_binary_sha256": "c7a1f35aa2cd7cf2733272923ace7fc4b4e55171db4ef8f3d0d6629183dcf1fa",
|
||||
"browser_checks_passed": 12,
|
||||
"browser_receipt": "2026-09-11-browser-result.json",
|
||||
"components": "actual Flex Auth HTTP server with caller enforcement and Approval Engine/Audit Core APIs; synthetic issuer, TokenReview, assignments and credentials"
|
||||
},
|
||||
"implemented": [
|
||||
"fresh pinned PDP checks before render, download and each action, with separate workload caller",
|
||||
"immutable policy observations and original act binding snapshot; decision_attributable remains false",
|
||||
"English organizational approval UI with explicit acknowledgments and accept/return/discuss/decline",
|
||||
"original confirmed entry and visible unresolved state; no automatic mutation retry",
|
||||
"owner runtime configuration, rotating token file callbacks, scheduled bounded audit delivery and readiness",
|
||||
"atomic SQLite schema v1-to-v2 upgrade; old UI-release/profile refusal"
|
||||
],
|
||||
"browser_findings_fixed": [
|
||||
"no-referrer produced Origin:null on real form POST; same-origin pages preserve exact-origin CSRF checks"
|
||||
],
|
||||
"component_findings_fixed": [
|
||||
"native caller plain-text 401/403 must remain caller_refused instead of policy_unavailable"
|
||||
],
|
||||
"source_sha256": {
|
||||
"SCOPE.md": "b4568fe033b443d66833d1501fd5b04e01c9d6e95b44bae9666134e914ae758a",
|
||||
"docs/browser-authentication.md": "9354f8a95914cb353b93256df9e85c455bcf425107f507dd8f1ba6206026794a",
|
||||
"docs/durable-review-evidence.md": "f7f4a15faa479f3a132120a6fae27837bd2673afda93df7dbd29e8171a7911d0",
|
||||
"docs/evidence/2026-09-11-browser-result.json": "f405f5d11ae695a9ca8098f6c6a5f125042a62c3af9e87f21bc5e266b6f5a397",
|
||||
"docs/evidence/2026-09-11-recorded-desktop.png": "89f6e5396094155f2b1e04599f0a2b2d040f4dce004afa4dda752f234eef566a",
|
||||
"docs/evidence/2026-09-11-review-desktop.png": "e050eceb62cb389d516e4a889c67b1610df3f5539d04efdbafa4874700e90ca3",
|
||||
"docs/evidence/2026-09-11-review-mobile.png": "3a0b5b1e37b3a54c4eb34688b04bb38b18181b94106b062150fc1b70aa4f3f8a",
|
||||
"docs/flex-auth-review-contract.md": "3324b7b022fb5397d02520310bf43e0bfbff8c919e95ba9fecc94334dae8caf9",
|
||||
"docs/protected-browser-review.md": "28f5666a795997db6b6483ab6c3e0c6dab4cc7df948e57f1e5d02e5c45968317",
|
||||
"informed_decision/approval_http.py": "203f462ff78f1409f2f924d4723197fdd502087c7f6da319f46ffffce2a9d478",
|
||||
"informed_decision/http_transport.py": "fc9c1fd05cc89120d70bfcdb4c60fe244b81c8054d21505df8cfa9c5a229f7bc",
|
||||
"informed_decision/memo.py": "697058863a4e6de2c143a2433eaaf5e15e5e7375dc5459af6911b146567e8b5d",
|
||||
"informed_decision/oidc.py": "35b37bae67968c2179b22af8151afe2981d5468debf4488771eaf3121b75e221",
|
||||
"informed_decision/policy.py": "7bf814a8332ff054bf5f1f651d8816a408138b1f197b17da0abb7f87411e8812",
|
||||
"informed_decision/review.py": "c2d2aa1cf48d10d85b3606acf2830ab22701c1274221e5e2760d29901adfd786",
|
||||
"informed_decision/runtime.py": "6bc6a74657f526743cca6c6cdc0a86fff43d8282e0b867dc9288344636abe5eb",
|
||||
"informed_decision/store.py": "7a58b36d409cb4161b65985545e13573a5196779b4c6e95ca03ce45c6fada808",
|
||||
"informed_decision/ui.py": "69fb7fc1794c8842f42e7f0f5a261c4cf64176053ef0a532db46ee6566621219",
|
||||
"informed_decision/web.py": "00300dc25af3e272db7de748c255f8b1aa0b0182a54f2573e874ed5865da0fd8",
|
||||
"pyproject.toml": "26e77b2da1ce7d6f1878fb6ca46fdb2acebbad0f6e17f5528d5a1f1d01defe42",
|
||||
"tests/browser_fixture.py": "31feaf6e3351d9560498ffac2f06cae602f0762a01474bcff2c919c33db3cfcf",
|
||||
"tests/browser_review.mjs": "0285f2cff70e5d569f31c7e99c8b3a5b5ce98b3eb51c2203de3f2e958f2ac684",
|
||||
"tests/fixtures/review-policy/fixtures.json": "4c84a411383bfdbd32e15763ce2e934eefec570d93bc68389d6c81d11f3d4243",
|
||||
"tests/fixtures/review-policy/pin.json": "91c77e4daa0a1c8c394e14e6b0c9d9c493ff21389fefa592656aa363c1104611",
|
||||
"tests/fixtures/review-policy/policy.md": "cd0a5a79db840e1dad7cabece3976d1683387c71d8a5507476f49b421d180c24",
|
||||
"tests/fixtures/review-policy/registry.json": "cd358b139f5987dcd0b4c8ac9fffee7d86951b15a22216f6ec21240a51ec41cf",
|
||||
"tests/review_components.py": "415ef229aedd4def7af737274b4d341ddf4f40a912a8103a017d901b2cdecf2e",
|
||||
"tests/test_browser_auth.py": "c70e24495d2f968c0a48e4082032666b4f4325f60d7e736c5a27d0fb99ef0f33",
|
||||
"tests/test_http_transport.py": "d3dcf8051a7acfd152a583f7a865327ae60d880b9b07abc5f5fa9c693e95354e",
|
||||
"tests/test_policy_client.py": "8ab6f8a06e8b28b4933e0d8d23650c9781a5365343fc4926080c7c2d5e2962ed",
|
||||
"tests/test_review_component.py": "cd28100fbc66b64703f065c2a3638486043bee5e3e4a6e75de19eb58235f0d47",
|
||||
"tests/test_review_controller.py": "df3ddfb60dd06728ee871225df96da13462d69f648f214a5ea1b040388c78d53",
|
||||
"tests/test_review_runtime.py": "e230d9f590b86675b4d42f978cfd2bc769de404b3f5ad510a400ff2fcec62bbd",
|
||||
"workplans/INFD-WP-0001-founding-specs-and-approver-ui-ownership.md": "49f68a11c301b436655144d4819e827d0ec036fdb2b4463d2679ce8c36cc3b3a"
|
||||
},
|
||||
"limits": [
|
||||
"fixture-only policy; native caller/package/assignment admission pending",
|
||||
"unsigned decisions remain unattributable under GH-DEC-2026-010 / FLEX-WP-0024",
|
||||
"no native registration rollout, human login, deployment or audit custody proof",
|
||||
"English organizational approve profile only; German and wider product acceptance remain T08",
|
||||
"uncertain entry causation remains unresolved; no reset/retry shortcut",
|
||||
"audit accepted_at and source occurred_at counts do not prove completeness or reconstructability",
|
||||
"production packaging, private state, backup/restore and recovery admission pending"
|
||||
],
|
||||
"browser_routes_implemented": true,
|
||||
"native_policy_admitted": false,
|
||||
"native_human_login_proven": false,
|
||||
"production_audit_custody_proven": false,
|
||||
"deployed": false,
|
||||
"factory_attempts": 0,
|
||||
"paid_model_calls": 0,
|
||||
"remaining_live_task": "INFD-WP-0001-T08",
|
||||
"factory_gate_task": "HFACT-WP-0001-T03"
|
||||
}
|
||||
BIN
docs/evidence/2026-09-11-recorded-desktop.png
Normal file
BIN
docs/evidence/2026-09-11-recorded-desktop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 135 KiB |
BIN
docs/evidence/2026-09-11-review-desktop.png
Normal file
BIN
docs/evidence/2026-09-11-review-desktop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 144 KiB |
BIN
docs/evidence/2026-09-11-review-mobile.png
Normal file
BIN
docs/evidence/2026-09-11-review-mobile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 141 KiB |
104
docs/flex-auth-review-contract.md
Normal file
104
docs/flex-auth-review-contract.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# Flex Auth review consumer contract
|
||||
|
||||
Source integration under `INFD-WP-0001-T08`; **production admission pending**.
|
||||
This is the concrete consumer request for owner review. It does not grant a
|
||||
role, register a caller or publish an allow policy. The checked Flex Auth source
|
||||
at `88b354377c8e26b162f1234e673072f1c06dcd89` has no Informed Decision
|
||||
production package in the examined examples, registry or docs.
|
||||
|
||||
## Caller and subject
|
||||
|
||||
`POST /v1/check` uses an independently provisioned workload credential, expected
|
||||
to be a projected Kubernetes service-account token for audience `flex-auth`.
|
||||
Flex Auth must admit the exact caller/system binding with TokenReview. The
|
||||
fixture exercises `system:serviceaccount:informed-decision:review` bound to
|
||||
system `informed-decision`; **that spelling is a proposal until the deployment
|
||||
and policy owners admit it**. A human `approval-engine` token is never the
|
||||
caller credential. Wrong service accounts and human-token substitution fail
|
||||
before evaluation in the actual component test.
|
||||
|
||||
The subject comes from the verified KeyCape human session, including verified
|
||||
roles and unchanged assurance facts. Membership tenant and act scope remain
|
||||
distinct. The tenant provenance values are the consumer's typed
|
||||
`directory-asserted` or `registration-supplied`; human provenance is
|
||||
`authentication-derived`. Unknown routes refuse before a request is made.
|
||||
The production package must deliberately interpret the admitted transitional
|
||||
tenant route and action-specific MFA freshness; this surface does not infer a
|
||||
mandate from membership, the named recipient match or a successful login.
|
||||
|
||||
## Closed request profile
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "fresh-uuid-for-each-check",
|
||||
"tenant": "tenant:platform",
|
||||
"subject": {
|
||||
"id": "VERIFIED-HUMAN-SUBJECT",
|
||||
"type": "human",
|
||||
"tenant": "tenant:platform",
|
||||
"attributes": {
|
||||
"tenant_source": "registration-supplied",
|
||||
"principal_type_source": "authentication-derived",
|
||||
"assurance": {"mfa": {"satisfied": true, "method": "totp", "at": "ISSUER-TIMESTAMP"}},
|
||||
"roles": ["VERIFIED-ISSUER-ROLE"]
|
||||
}
|
||||
},
|
||||
"action": "read",
|
||||
"resource": {
|
||||
"id": "memo:EXACT-MEMO-ID",
|
||||
"type": "decision-memo",
|
||||
"system": "informed-decision",
|
||||
"tenant": "tenant:platform"
|
||||
},
|
||||
"context": {
|
||||
"memo_version": 1,
|
||||
"approval_id": "EXACT-APPROVAL-ID",
|
||||
"approval_binding_digest": "CARRIED-NATIVE-SHA256-DIGEST"
|
||||
},
|
||||
"policy_version": "OWNER-ADMITTED-VERSION"
|
||||
}
|
||||
```
|
||||
|
||||
The assurance object above is illustrative; `build_request` carries the verified
|
||||
issuer object without synthesizing stronger facts. Supported actions are
|
||||
`read`, `acknowledge`, `accept`, `return`, `discuss`, `decline`. Read covers memo
|
||||
rendering, original presentation retrieval and packet download. Other verbs
|
||||
always get their own decision. Only accept can cause an Approval Engine POST.
|
||||
No `consume`, wildcard action, `view_hash`, presentation id, acknowledgment
|
||||
state or presentation claim is supplied. This prevents the renderer's evidence
|
||||
from becoming an input to its own permission decision.
|
||||
|
||||
The owner must provide the exact package/version/digest, resource/subject
|
||||
assignment source, admissible caller, scope and positive/negative fixtures.
|
||||
The consumer does not guess an operator group or ship a local allow fallback.
|
||||
The separately pending CCR-2026-0019 reader group does not automatically confer
|
||||
permission to read or approve a memo.
|
||||
|
||||
## Required response and refusal behavior
|
||||
|
||||
The response is `flex-auth.decision-record.v1`, with matching request id,
|
||||
submitted request digest, core actor/resource/action/context binding and exact
|
||||
package/version/digest pins. The evaluated digest and registry snapshot digest
|
||||
are observed from the owner; registry enrichment is not repeated locally.
|
||||
`submitted_digest` implements this consumer's bounded Go JSON request profile,
|
||||
not a second approval-act canonicalizer. Actual Go checks cover enrichment,
|
||||
Unicode and HTML escaping; the native approval digest is always carried.
|
||||
|
||||
Only an allow with a current TTL and no obligations is supported. Maximum use
|
||||
is 30 seconds from dispatch, bounded by the returned expiry and not-before;
|
||||
there is no decision cache. A declared obligation needs explicit implementation
|
||||
before it can be admitted. Deny, unsupported effects/obligations, stale or
|
||||
mismatched responses and transport/caller failures all refuse. HTTP 401/403
|
||||
remains `caller_refused` even when the native caller gate returns plain text.
|
||||
|
||||
Each obtained observation is append-only in the private evidence database,
|
||||
before content exposure or an act. Diagnostic response text and bearer tokens
|
||||
are not retained. Policy linkage is private content, not part of the emitted
|
||||
presentation claim. The envelope remains unsigned, so
|
||||
`decision_attributable=false`; native deployment cannot close `FLEX-WP-0024`.
|
||||
|
||||
`tests/fixtures/review-policy/` is deliberately a **fixture-only** package, with
|
||||
one synthetic human/memo/approval and no production assignment. Its native
|
||||
package pin proves the consumer seam and cannot be used as admission evidence.
|
||||
Owner return must include admitted policy/caller pins and deployed allow/refusal
|
||||
receipts for the exact native human/act before T08 can claim live binding.
|
||||
166
docs/protected-browser-review.md
Normal file
166
docs/protected-browser-review.md
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
# 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/<id>` | Entitled historical presentation and current submission result |
|
||||
| `GET /presentations/<id>/packet/<index>` | Fresh read permission and full attachment bytes |
|
||||
| `POST /presentations/<id>/ack` | Explicit acknowledgment of selected highlights |
|
||||
| `POST /presentations/<id>/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 and deployment packaging require separate work.
|
||||
|
||||
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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue