--- id: INFD-WP-0001 type: workplan title: "Founding specs and approver-UI ownership" domain: infotech repo: informed-decision status: active owner: claude topic_slug: netkingdom created: "2026-09-09" updated: "2026-09-11" reviewed_at: "2026-09-09" reviewed_against_commit: "ee2cca5" reviewed_note: >- Reviewed against approval-engine's INTENT, SCOPE, layer.yaml, APPROVAL-WP-0002 and docs/keycape-service-registrations.md; the accepted NetKingdom security layer model v0.7 and companion v0.2; the repo-classification and project-repository-flavor standards; and the Decision Memo schema, state-transition tables and canonicalization vectors in history/20260909-initial-exploration. Repo-owned specification work can proceed. T05 and T07 remain externally gated on the gate-house ruling requested as INFD-IN-0001; T08 is additionally gated on APPROVAL-WP-0002-T01 and a deployed approval-engine. origin: founding origin_ref: history/20260909-initial-exploration/InitialExploration.md state_hub_workstream_id: "a985a65a-08f7-5a39-8645-b618ea022657" --- # INFD-WP-0001 — Founding specs and approver-UI ownership Establish `informed-decision` as a governed repository in the NetKingdom estate, settle who owns the browser-facing approver UI that `approval-engine` deliberately does not contain, and produce the specification set that Stage 1 implementation will be built against. The trigger is concrete and dated. On 2026-09-08 `key-cape` (`KEY-WP-0013-T02`) asked `approval-engine` for the human approver client's `client_id` and callback URI. `approval-engine` correctly declined to invent them and recorded in `docs/keycape-service-registrations.md` that the human approver flow *"belongs to whichever browser-facing approver UI presents `approval:approve` tokens to this engine. That component is not in this repo."* `APPROVAL-WP-0002-T01` remains `progress` partly because of it. This workplan closes that gap by naming the owner and shipping the contract. **Reviewed 2026-09-09; the plan is active.** Reviewed against the contracts listed in the frontmatter. T02 remains the gate for the *architecture*: if `gate-house` places this component differently, T05 and T07 are rewritten before they are written. T03, T04 and T06 are independent of the ruling because they describe what the surface must do and what the evidence must contain, neither of which changes with the catalog row. Scope boundary for this workplan: **specification and contract, plus one walking skeleton**. Full L3 product build is residual and belongs to `INFD-WP-0002`. ## Establish the founding documents ```task id: INFD-WP-0001-T01 status: done priority: high state_hub_task_id: "abe83f6a-18d2-5fe5-bd01-56b6fd0babbe" ``` Write the repository's stable statements of purpose and current stage, derived from the founding exploration rather than reinvented. Acceptance: `INTENT.md` states purpose, the Decision Memo concept, ownership and non-ownership against the named estate repositories, design principles, wrongness conditions and success criteria; `GOAL.md` states the Stage 1 outcome, exclusions, invariants and definition of done; `README.md` orients a new reader in under a minute; `history/20260909-initial-exploration/` is preserved unmodified as the provenance record. Completed 2026-09-09: `INTENT.md`, `GOAL.md`, `README.md`, `SCOPE.md`, `AGENTS.md` and `.repo-classification.yaml` written; repo registered in the State Hub and `INFD-WP-0001` indexed by `fix-consistency`. Two corrections made during the same task, recorded rather than silently fixed: - `GOAL.md` first declared `repo_flavor: project`. That is wrong. `project-repository-flavor_v0.1.md` reserves the `prj-` flavor for bounded cross-repo coordination efforts and states that durable products use `INTENT.md` and an ordinary category. This is a durable product; `.repo-classification.yaml` sets `category: product`. `GOAL.md` is retained as the *stage* statement, which the flavor standard does not forbid. - `SCOPE.md` was initially deferred to T06 on the reasoning that a scope file written before the layer ruling describes an imagined boundary. The Repo Manager requires it (C-35), and the reasoning was better served by writing an honestly empty one: the shipped `SCOPE.md` states plainly that nothing is implemented and that T06 rewrites it. T06 now rewrites rather than creates. ## Settle layer placement and approver-UI ownership with gate-house ```task id: INFD-WP-0001-T02 status: done priority: high state_hub_task_id: "4f94134b-2260-5404-84e0-12f2b08ef565" ``` Take the ownership question to `gate-house` as doctrine rather than asserting a catalog row. The proposal to argue: `informed-decision` owns the browser-facing approver surface, the presentation record and the evidence of informedness; it is **PEP-shaped** under statute §6.4 and companion §5 because it is browser-facing and causes a protected side effect on the far side of a decision; it supplies exactly one PIP-like fact — *what was presented* — as a claim, and never evaluates it. Raise explicitly, and do not paper over: - §17 has no request-claim schema owner assigned. A `view_hash`-bearing presentation claim must yield to that schema when it exists rather than inventing a permanent local shape. - `approval-engine`'s claim already carries *"a digest over the same canonical binding the decision point already computes"*. Whether `view_hash` is that digest, a sibling of it, or a distinct presentation attestation is a real question and the wrong answer creates two competing canonicalizations of the same act. This is the single highest-risk unknown in the workplan. - A surface that renders both the question and the answer is adjacent to the self-dealing objection that kept the object out of `access-engine`. State why it is not the same failure: this repository holds no state a decision reads as authority. Acceptance: an intake is filed with `gate-house`; a ruling or a recorded decision exists; `layer.yaml` is written from the ruling and matches the catalog row, not this workplan's prose; `INTENT.md` and `GOAL.md` are amended if the ruling differs from the proposal; the `view_hash`-versus-binding-digest relationship is recorded as a decision, not left implicit. Blocking for T05 and T07. ## Product Requirements Document ```task id: INFD-WP-0001-T03 status: done priority: high state_hub_task_id: "86465a35-1af5-5956-a767-57ad838dffa9" ``` Write `docs/specs/ProductRequirementsDocument.md` for Stage 1: the L3 approval approver surface, scoped to one real consumer. Must cover: the personas (approver holding a mandate, requester, observer/auditor); the disposition vocabulary and which verbs are legal on which step kinds; the required-highlight acknowledgment gate; the pre-sign/awareness split as it appears in the UI; the evidence bundle as an export; accessibility and locale (DE/EN, given the *Umlaufmappe* framing); and the explicit non-requirements from `GOAL.md` — no mandate graph, no QES, no notification transport. State the anti-requirements as first-class: no dark patterns, no dwell timers, no keystroke analytics, and a UI that makes unmistakable that the whole instrument is bound rather than only the acknowledged highlights. Acceptance: every requirement traces to either a `GOAL.md` definition-of-done item or a named external contract; each requirement is testable; the document names what it is deliberately not requiring and why. Completed 2026-09-09: `docs/specs/ProductRequirementsDocument.md`. 30 numbered requirements, each with a `trace:` line to a `GOAL.md` DoD item, an `INTENT.md` principle or wrongness condition, a state-transition guard, or a named external contract, and each with an observable pass condition. Anti-requirements (PR-70..75) are stated as testable absences: no dwell timers, no attention analytics, no dark patterns, no auto-approval, no approval-state caching, no authorization endpoint. Four known limitations are recorded up front rather than discovered later — chiefly that `escalate` without a mandate graph is forwarding (L-01) and that `view_hash` is computed by the renderer, so a compromised surface can present X and attest Y (L-02). Four open questions are left for review rather than answered by assumption. ## Use Case Catalog ```task id: INFD-WP-0001-T04 status: done priority: medium state_hub_task_id: "00a83db5-fe0e-522a-b885-6f9ef034bc07" ``` Write `docs/specs/UseCaseCatalog.md` covering the full depth spectrum L0–L5, with Stage 1 scope marked, so that scale invariance is testable as a design property rather than a claim in a vision statement. For each level: actor, trigger, requested act, binding level, the verbs that must be available, the evidence produced, and the estate repository that is the counterparty. Include the L0 login banner and the L2 ADR accept in full even though they are out of Stage 1 build scope — their purpose here is to constrain the schema so the object cannot fork later. Include the negative cases: `accept` on a Kenntnisnahme step (illegal by design), a bind attempt with unacknowledged required highlights, an agent attempting a disposition, and a tenant switch requiring a new bind. Acceptance: every use case maps onto the single Decision Memo schema with no level-specific object; each negative case names the invariant it protects; the catalog states for each level which estate repository would consume it. Completed 2026-09-09: `docs/specs/UseCaseCatalog.md`. Six use cases L0-L5 with counterparty and stage, ten negative cases each bound to a guard or isolation vector, and a closing section naming the four changes that would fork the object — a level-specific status, multiple questions per memo, a per-level packet model, and an approvals-inbox entity that acquires state. Each case records what it contributes as a *constraint* on the shared schema, so the scale-invariance claim is checkable rather than asserted. ## Architecture Blueprint ```task id: INFD-WP-0001-T05 status: done priority: high state_hub_task_id: "20ea118e-0657-5054-8aa2-b316444f4000" ``` Write `docs/specs/ArchitectureBlueprint.md`. Depends on T02 — the layer ruling determines what this component is permitted to be. Must cover: the component boundary and its position in the security layer model; the call graph to `key-cape` (OIDC authorization-code + PKCE for humans), `approval-engine` (bearer `approval:approve`, never `approval:consume`), `access-engine` (decision, never rendered here) and `audit-core` (evidence emission); the storage posture for memos, presentations and dispositions; the deployment shape including the Ingress and external origin that `approval-engine` explicitly does not have; and the failure modes — what the surface does when `approval-engine`, `key-cape` or `audit-core` is unavailable. Fail-closed is the default and must be stated per dependency. A surface that degrades into showing a memo it cannot bind is acceptable; a surface that degrades into binding without evidence is not. Acceptance: no component in the diagram renders an authorization decision; the token audiences, scopes and principal types match `approval-engine`'s `docs/keycape-service-registrations.md` exactly; every external dependency has a stated unavailable-stance; the blueprint names which parts are Stage 1 and which are placeholders. Completed 2026-09-09: `docs/specs/ArchitectureBlueprint.md`. Written after the ruling, as intended — the layer answer shaped it rather than being retrofitted. The constraint that did most of the work is `GH-DEC-2026-012` limit 3: the evidence copy must reach `audit-core` independently of this component, because here the actor being audited and the evidence source are the same. That is booked as four binding implementation consequences plus an open item (`O-02`) that must be resolved before T08 ships, rather than as a principle — "we will add the independent path later" is how limit 3 becomes limit-3-in-principle. Also fixed: `presentation/` is the only writer of `view_hash`; the `approval-engine` client must expose no validity cache; a fail-closed outcome is never recorded as an approver's decline, because the human did not make one; and the `assurance` shape is cited from `key-cape`'s contract rather than restated, so it cannot drift. ## Evidence model, schema promotion and canonicalization under test ```task id: INFD-WP-0001-T06 status: done priority: high state_hub_task_id: "47cb3f7a-e349-5c81-a304-86275e058a85" ``` Promote the exploration artifacts from `history/` into governed, tested repository assets, and write `docs/specs/EvidenceModel.md`. Move `decision-memo.schema.json` to `schemas/`, `canonicalize.py` into the package, and the fixtures in `vectors/` into the test suite. `history/` stays untouched as provenance; the governed copies are the ones that change. The four isolation properties from the exploration become tests that must stay green: 1. shuffling object keys does not change either hash; 2. editing an awareness field does not change `view_hash`; 3. changing `binding.target` does change `view_hash`; 4. selecting a role after login emits `session.hat_selected` and does not rewrite `view_hash`. `EvidenceModel.md` covers what an evidence bundle contains, how it verifies offline, its relationship to `audit-core`'s archive, and — carried over from `approval-engine`'s reasoning rather than rediscovered — the honest statement that a hash chain proves records were not altered after arrival and cannot prove a record was never sent. **Rewrite** `SCOPE.md` as the last step of this task. The version shipped in T01 is honestly empty — it states that nothing is implemented. Replace it with the real implemented-and-first-cut boundary once the layer ruling and the specs have fixed it, and drop the T01 status banner. Acceptance: schema, canonicalizer and vectors live outside `history/` and are exercised in CI; the three published expected hashes reproduce byte-for-byte; `EvidenceModel.md` states the residual it does not close; `SCOPE.md` describes the implemented-and-first-cut boundary rather than the aspiration, and no longer carries the T01 "nothing is implemented" banner. 2026-09-09 — substantive half done; task stays `progress` because the `SCOPE.md` rewrite is gated on T02. Delivered: - `schemas/decision-memo.schema.json` plus both worked examples; `informed_decision/canonicalize.py` as the governed canonicalizer, with the ad-hoc `__main__` block replaced by `python -m informed_decision`; fixtures under `tests/vectors/`. `history/` is untouched. - `tests/test_canonicalize.py` — 20 tests, all green. The three published hashes reproduce byte for byte, and all four isolation properties are pinned. - `docs/specs/EvidenceModel.md`. - `pyproject.toml`, `Makefile` (`make test`, `make check`). Two things worth recording rather than burying: - Isolation properties 1, 2 and 4 are all *negative* — they assert the hash does **not** change. A canonicalizer returning a constant would pass all three. Property 3 plus per-field variants over `question`, `requested_act`, `binding_level` and `packet` are what stop the suite being vacuous. - `test_governed_vectors_match_the_preserved_history_copy` asserts the governed fixtures have not drifted from the founding copies, so quietly editing a vector to make a failing test pass is itself a failure. Remaining for `done`: rewrite `SCOPE.md` after the T02 ruling. 2026-09-09 — **done.** `SCOPE.md` rewritten now the ruling and the specs have fixed the real boundary. It carries a "What this repository does not claim" section, because a scope file listing only capabilities overstates them: the decision path is not validated while `GH-DEC-2026-010` is open, the residual is not closed, `view_hash` is not inside the approval entry, and nothing is deployed. ## Publish the OIDC browser-client contract to key-cape ```task id: INFD-WP-0001-T07 status: done priority: high state_hub_task_id: "38a83a76-f152-55bf-8a9a-6132fd6d9642" ``` Own and publish the two strings `approval-engine` could not supply: the human approver client's `client_id` and its full callback URI. Depends on T02 for the ownership ruling and on T05 for the deployment origin. The registration is an authorization-code + PKCE public or confidential browser client — not `client_credentials` — and the resulting access token must carry `aud=approval-engine`, `principal_type: human`, `tenant: tenant:platform` and scope `approval:approve`. Redirect URIs match exactly at `/authorize`, so the origin must be a real deployed origin, decided in T05, not a placeholder. Do not request `approval:consume`: `approval-engine` refuses it for human principals, and consumption belongs to the PEP that causes the side effect. Acceptance: `docs/keycape-client-registration.md` publishes both strings and the expected token shape; the contract is sent to `key-cape` referencing `KEY-WP-0013-T02`, and to `approval-engine` referencing its `docs/keycape-service-registrations.md` follow-up; a token issued against the registration is accepted by `approval-engine`'s verifier; `KEY-WP-0013-T02` is unblocked. **This is the task that discharges the gap that created this repository.** 2026-09-10 — **unblocked.** `INFD-IN-0002` closed: `key-cape` had already implemented registration-bound tenancy (`329e48f`) correct under both candidate rulings, and `GH-DEC-2026-013` granted the shape as a declared bounded gap. Two obligations land here and are booked as PR-08/PR-09: never use the token's `tenant` claim as the act-scope (`binding.target` is, and already was), and record the claim's provenance, since `key-cape` emits it as a bare string and a consumer cannot otherwise tell a directory-asserted tenant from a registration-supplied one. Build to the registration-bound shape knowing it is transitional. The one remaining input is the **deployed origin**. Redirects match exactly, so `client_id` and the callback URI must name a real origin — the reason not to publish is now solely that, and no longer the tenant. 2026-09-10: `docs/keycape-client-registration.md` written and everything except the host is fixed — `client_id` `informed-decision-approver`, callback path `/auth/callback`, authorization-code + S256 PKCE public client, scopes `[openid, approval:read, approval:approve]`, the expected token shape, and the `assurance` shape cited from `key-cape` rather than restated so it cannot drift. §5 declares the tenant provenance rather than assuming it. Nothing populates `domain.User.Tenant` for approver users, so declaring `tenant:platform` reaches the token by the **gap route by construction**, not by accident. `GH-DEC-2026-013` was explicit that a tenant the directory does not carry must not be declared unless we are prepared to say so in the record. We are, and it is said there, along with the two consequences: the claim is stored with its provenance, and it is never used as the act-scope. 2026-09-10 — **origin assigned.** The operator assigned `decisions.coulomb.social`, not the `decide.coulomb.social` this workplan proposed. `docs/keycape-client-registration.md` §2 is corrected to the assigned name; the proposal is not preserved anywhere a reader could mistake it for the registration, because a redirect URI that is one character off fails closed at `/authorize` and presents as a rejected login rather than a registration defect. DNS resolves to the cluster address. 2026-09-10 14:32 UTC — **the origin is live.** `railiance-apps` applied `manifests/informed-decision-origin.yaml` and `manifests/informed-decision-ingress.yaml`; cert-manager issued a Let's Encrypt certificate (`CN=decisions.coulomb.social`, valid to 2026-12-09) and `GET https://decisions.coulomb.social/auth/callback` returns `200` over a verified chain. The path is served by a placeholder until T08 ships, which does not affect the registration — `key-cape` matches the redirect URI as a string at `/authorize` and never fetches it. Evidence: `railiance-apps/docs/informed-decision-origin.md`. Every input this task owns is now fixed and real. `docs/keycape-client-registration.md` is ready to submit. **The task stays `progress` until the remaining acceptance criteria are met, which are not document criteria:** the contract must actually reach `key-cape` citing `KEY-WP-0013-T02` and `approval-engine`'s `docs/keycape-service-registrations.md`, and a token issued against the registration must be accepted by `approval-engine`'s verifier. That last one cannot be demonstrated until `approval-engine` is deployed, so T07 will close alongside, not before, the deployment T08 also waits on. Superseded context: the origin was the sole blocker — DNS alone is not an origin, and a host that resolves but does not complete a TLS handshake fails the same way a wrong hostname does, only later and less legibly. Superseded context: the origin was previously the sole blocker in its unowned form — choosing a plausible hostname is not the same as owning one. Superseded context: 2026-09-09: blocked on `INFD-IN-0002`. `key-cape` found that a human access token cannot carry `tenant:platform` today — the tenant claim resolves from a directory record no adapter populates, so every human token falls back to `tenant:coulomb`, which `approval-engine` refuses by exact match. Registering the client before this is resolved would ship a login that fails closed at first use, and the failure would present as a rejected approval rather than as a registration defect. Position stated (registration-bound) with an evidence-model reason, and routed — it writes a cross-tenant capability into the issuer, so it is not this repository's to decide alone. ## Walking skeleton — one approval, end to end ```task id: INFD-WP-0001-T08 status: progress priority: medium state_hub_task_id: "b5c1d329-9580-5672-9640-2930cbbb729a" ``` Prove the specs against reality with the thinnest possible L3 path: sign in via `key-cape`, retrieve one named approval by id from `approval-engine`, render one as a Decision Memo with brief, packet and highlights, acknowledge the required highlights, and submit an approval entry with a stored presentation record carrying `view_hash`. `return` and `discuss` are in this skeleton, not deferred. They are the differentiator; a skeleton with only approve/reject proves the wrong product. Acceptance: one approval is approved by a real human through this surface against a deployed `approval-engine`; the approval entry is reconstructable from a stored presentation; a bind attempt with unacknowledged required highlights fails closed; `return` produces a structured reason and is distinguishable from `decline` in the record; no code path in this repository evaluates whether the act is permitted. Gated externally on `approval-engine` `APPROVAL-WP-0002-T01` reaching `done` and on the service being deployed with an origin this surface can reach. 2026-09-10 — **domain core built and tested; the live proof remains gated.** `approval-engine` `APPROVAL-WP-0002-T01` is still `progress` and the namespace has no pods, so the end-to-end proof against a deployed engine cannot run. Built everything that does not depend on it, with the engine behind a seam so its arrival is a wiring change rather than a build: - `memo.py` — the Decision Memo, versions, and the binding document. `Principal`, `Scope`, `Awareness` and `Hat` are dataclasses rather than dicts because the canonicalizer requires a shape, and a missing key should fail at construction rather than deep inside hashing. Field names follow the governed canonicalizer (`item_id`, `severity`, `locator`) — the vectors are the contract, so the object was aligned to them rather than the reverse. - `presentation.py` — the **sole writer** of `view_hash`. Acknowledgment is an explicit method call; nothing infers it. - `disposition.py` — the verb vocabulary and guards. `accept` is **absent** from weak steps rather than present-and-disabled, because a greyed-out accept still teaches the wrong model. - `provenance.py` — claim routes (A-16). `assert_human_control_dischargeable` refuses a registration-supplied `human`, so PR-11's limitation is visible at the point of use rather than buried in a document. - `evidence.py` — the local outbox, commitment-only records carrying the `GH-DEC-2026-014` §4 existence assertion, and a custody-locator guard that rejects secret-shaped values (PR-12). - `approval_client.py` — Protocol plus a fake with the engine's real semantics: `409 duplicate_approver` is success, `409 conflict` terminal, `503` fail-closed, and `approval:consume` refused before a token is ever requested. 87 tests pass, including every negative case in the Use Case Catalog: NC-01 through NC-08, the humanity-provenance guard, the existence assertion, and that a fail-closed outcome is recorded as a stance application with no `verb` field — never as a decline, because the human did not make one. Remaining for `done`: the live proof. Superseded context — 2026-09-09: additionally gated on `INFD-IN-0003` — `GH-DEC-2026-012` limit 3 requires the evidence copy to reach `audit-core` independently of this component, and the payload question is open. Design and decision request in `docs/evidence-path-design.md`. This task must not ship before it is answered. 2026-09-10 — **browser authentication and real HTTP adapter implemented.** `oidc.py` verifies KeyCape authorization-code/S256 PKCE, browser-bound single-use state, ID-token nonce and client audience, access-token resource audience, paired subject/tenant/provenance, exact scopes and MFA facts. Tokens stay in bounded ephemeral server sessions. `web.py` supplies the sign-in shell with secure cookies, CSRF sign-out, no token output and no memo/entry route. `approval_http.py` reads by id and submits human entries, requires the declared control and native digest format, and recovers duplicate correlation from the actual stored entry. No consume API, validity cache or automatic POST retry. 206 tests pass, including three checks against Approval Engine's actual JWT verifier/API and 100 existing domain/contract tests. Six installed-entrypoint HTTP smoke checks pass. These use signed synthetic identity fixtures: **no native human login, audit admission or factory execution is claimed.** The component test caught an unprefixed-digest assumption; the adapter now carries the native `sha256:<64 hex>` value unchanged. The package's dev extras now include PyYAML so layer conformance cannot silently disappear in a clean installation. Evidence: `docs/evidence/2026-09-10-browser-authentication.json`. The earlier principal-type provenance assumption is corrected against KeyCape `f9812ab`: its user-authenticated code flow emits `human`, while client credentials emit `service`; `tenant_source` distinguishes directory and registration. Unknown tenant provenance fails closed. This adopts the current issuer contract without assuming that it has been proven on the deployed login path. **Next within this task:** durable presentations/dispositions plus transactional outbox; entitlement before render; connect one named memo with required acknowledgments, accept/return/discuss and correct actor/presentation binding; independent audit delivery; then native registered login and deployed-engine proof. Ambiguous engine POSTs must reconcile against stored entry correlation before a UI retry. `/readyz` deliberately remains 503 and browser entry routes are absent until this protected path is wired. The task remains `progress`. See `docs/browser-authentication.md`. 2026-09-10 — **durable evidence and receiver integration implemented.** `store.py` / `records.py` persist packet bytes, immutable memo versions and presentations, explicit append-only acknowledgments, dispositions, submission correlation and a same-transaction evidence outbox in private SQLite storage. The new G_ACTOR guard refuses use of another person's presentation. The two older tests with mismatched placeholder actors were corrected; a dedicated negative regression now pins the real guard. Return/discuss remain local acts. Operation ids and atomic reservation prevent double submission. Lost engine responses and unknown duplicates remain unresolved and cannot be attached to a new presentation or silently retried. A confirmed correlation retrieves the original acknowledgment snapshot; later acks do not strengthen earlier evidence. Revisions cannot race in-flight/unresolved submissions. Backup/restore preserves the evidence and pending delivery state. `audit.py` uses the current Audit Core ingestion contract, stable event ids and bytes, scoped metadata-only envelopes, receiver references, retry/backoff and visible blocked records. Writer-only reconciliation and a separate auditor read were exercised against the actual receiver. Heartbeats are per class and do not mask undelivered evidence. The receiver counts by accepted_at, while the source counts by occurred_at: the report now carries both, and delayed acceptance is not automatically called a loss. Exact window shape came from the real API, not from a local fixture assumption. 258 tests pass (52 added), including process death before commit, injected outbox failures, concurrent clicks/reservations, restore, lost engine response and receiver deduplication. Actual Approval Engine and Audit Core APIs are exercised with synthetic identities and development custody. Evidence: `docs/evidence/2026-09-10-durable-review-evidence.json`. No native policy, human login, production audit custody, deployed binding UI or factory run is claimed. **Next within T08:** define/admit the exact PDP read/bind request and caller, then connect protected review/ack/accept/return/discuss routes to this store and its original-entry recovery rules. Persist the obtained policy observation and retain decision_attributable=false while its upstream gap remains. No Informed Decision package/registration was found in Flex Auth's checked examples, registry or docs at `88b3543`; a local allow rule is not a replacement. Schedule and admit audit draining/heartbeats, account for acceptance-time delay in reconciliation, supply native custody and registered human/deployed-engine proof. These remain live work in this task; `/readyz` stays 503 and browser bind routes remain absent. Details: `docs/durable-review-evidence.md`. 2026-09-11 — **protected review and actual browser exercise implemented.** The configured `0.2.0` service now connects the named memo to fresh Flex Auth checks before render, download, acknowledgment and each human response. The separate workload caller, exact package/version/digest and submitted request binding are validated; observations are immutable and retain `decision_attributable=false`. No presentation claim becomes a policy input. Actual native component checks cover caller enforcement, request digest wire format and registry enrichment, with fixture-only assignments and credentials. The browser supports explicit highlights, accept/return/discuss/decline, current actor/version/session/act checks, original confirmed correlation and visible uncertain submissions without POST retry. An audit thread drains bounded batches, emits declared heartbeats and saves a two-time-base reconciliation snapshot; unhealthy delivery closes acceptance readiness. Owner configuration provisions no identity, policy or custody. Existing v1 databases migrate atomically to v2; old memo UI releases require a new version. The first browser profile is English organizational approval; unsupported locale/step/level refuses before rendering. 344 automated checks pass, including actual Flex Auth, Approval Engine and Audit Core components. Twelve Chromium checks exercise local HTTPS, synthetic PKCE login, cookies, entitlement, escaped packet content, acknowledgment bypass, return, responsive layout, a single accepted entry/reload, audit delivery, caller refusal and sign-out. The browser caught `no-referrer` suppressing the form Origin; review pages now preserve same-origin headers while auth/downloads remain no-referrer. Missing/null/foreign form origins and bad CSRF still refuse. Plain-text native caller 401/403 responses also retain their refusal meaning. Evidence: `docs/evidence/2026-09-11-protected-browser-review.json` and the accompanying browser receipt/screenshots. This is disposable component/browser proof, **not native human login, admitted policy/custody, deployment or factory execution**. Factory attempts and paid calls remain zero. **Next within T08:** obtain owner admission against `docs/flex-auth-review-contract.md` for the exact production package/pins, caller and assignment source. Admit native review-sender custody with AUDIT-WP-0009-T11, registration rollout, service packaging/private state, backup/restore and uncertain-entry operating procedure; then exercise the deployed callback and actual human binding against the admitted Approval Engine. German UI acceptance (PR-60), broader product steps/awareness controls and the specified evidence export remain product work under this live task. They must not be silently claimed by the bounded factory profile. The exact operational setup and remaining limits are in `docs/protected-browser-review.md`. T08 remains `progress`; no residual has been hidden by finishing the workplan. ## Known risks - **T02 is a hard gate.** Writing the blueprint before the layer ruling risks building a component the statute does not permit in that shape. - **Two canonicalizations.** If `view_hash` and `approval-engine`'s binding digest are not reconciled in T02, the estate ends up with two hashes over the same act and no rule for which one is authoritative. - **Deployment origin is on someone else's critical path.** T07 cannot complete without a real external origin, and this repository does not yet own an Ingress. This is the most likely cause of slip. - **Scope pressure toward an approvals inbox.** The fastest way to close `KEY-WP-0013-T02` is to build a queue with two buttons. That would satisfy the dependency and abandon the thesis. T04 exists to make the cost of that visible. ## Session note — 2026-09-10, both blockers cleared **T07 origin: cleared and independently verified.** `railiance-apps` deployed `decisions.coulomb.social` at 14:32 UTC (their `7c2e51a`) and corrected the hostname in this repository's `docs/keycape-client-registration.md` and the T07 note — the assigned name is **not** the `decide.coulomb.social` this workplan proposed. Verified here rather than taken on report: `/` and `/auth/callback` both return `200` from `92.205.62.239`, TLS verify `0`, Let's Encrypt `CN=decisions.coulomb.social` issued by YR2, valid to 2026-12-09. The path is an nginx placeholder, which does not affect a registration matched as a string at `/authorize`. **T08 evidence path: cleared.** `audit-core` registered this source with every field as proposed (`AUDIT-IN-0003`, their `c4016a7`) and landed the detection half (`AUDIT-WP-0009` T04/T06/T07). `INFD-IN-0003` closed. **`INFD-IN-0004` ruled — `GH-DEC-2026-015`, and gate-house reversed itself.** Nesting is permitted for this pair. The decisive ground was not the cycle argument we led with: our binding slice canonicalizes `principal` and `target`, two of the five digest fields, so co-reference left us performing a partial recomputation of one act in a second vocabulary — *closer to the translation R3 forbade than nesting is*. Our ordering-dependency objection was withdrawn as mistaken. **The permission is conditioned and NOT ACTIVE**: it turns on when `approval-engine` states its presentation exclusion as normative and tested. `layer.yaml` is deliberately unchanged and carries `nesting_permission_active: false`. We do not activate on our own initiative. **`GH-DEC-2026-016` ruled NC-03.** Where an approval is declared as discharging a human-in-the-loop control, the approver must be human and `approval-engine` must refuse at bind time. Our surface enforcement stays — ours refuses earlier with a better error, theirs makes the refusal a property of the object. Its §5 lands here as PR-11 and is live rather than hypothetical: `principal_type: human` is a property of the *client registration*, the same shape as the gap-route tenant, so a human-in-the-loop control must not be discharged on it as verified humanity. **A-16 and A-17 were corrected with this repository's rider and precondition** (`gate-house@62c6399`), including the dependency that A-17 needs A-16 first. ### T07 — done **Submitted to `key-cape` 2026-09-10**, citing `KEY-WP-0013-T02`: ``` client_id informed-decision-approver redirect_uri https://decisions.coulomb.social/auth/callback ``` Both fixed and stable. The submission carries §5's tenant-provenance declaration — `tenant:platform` reaches the token by the `GH-DEC-2026-013` gap route by construction, not by accident — and raises PR-11's provenance question about `principal_type` back to `key-cape`, since `GH-DEC-2026-016` §5 now depends on that claim in a way it did not last week. **This is the task that discharges the gap that created this repository.** Step 3 — proving a token against `approval-engine` — is T08's and waits on `APPROVAL-WP-0002-T01` and their deployment. `heartbeat_classes` sent to `audit-core`: all three classes at `86400`, with the reasoning for declaring one on `presentation` despite their guidance putting it outside — at Stage 1 that class behaves like a low-volume one, and a class quiet because nobody used the surface is indistinguishable from one quiet because the emitter is broken. Offered for them to overrule. The activation condition for `GH-DEC-2026-015` was relayed to `approval-engine`: state the five-field set as normative **and add a test that fails if the digest input set changes**. Their reasoning already exists in substance; what is missing is that a contributor can make that change today with nothing stopping them.