Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a09cbb-87c6-7900-a145-4ce53ba9f1a6
827 lines
46 KiB
Markdown
827 lines
46 KiB
Markdown
---
|
||
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`.
|
||
|
||
2026-09-11 — **correction to this task's premise.** This note and the
|
||
registration doc previously said publishing would "close `KEY-WP-0013-T02`,
|
||
blocked since 2026-09-08". Checked against `key-cape` rather than against this
|
||
file's own prose: `KEY-WP-0013-T02` is `done`, and so is `KEY-WP-0013-T05`, the
|
||
task that actually held the human registration. `key-cape` did not wait — it
|
||
published the contract shape from its side (accepting the `approval:read` scope
|
||
gap as ours-was-right, documenting the `assurance` object, and fixing `at` from
|
||
mint time to authentication time), and closed `KEY-WP-0030`, a follow-up this
|
||
repo prompted. The gap that created this repository is discharged on the issuer
|
||
side already. What is left is this repo submitting its half.
|
||
|
||
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.
|
||
|
||
2026-09-11 — **container and deployment candidate prepared and exercised.**
|
||
`Containerfile` and hashed dependency lock build the installed wheel on the
|
||
digest-pinned Alpine base. The runtime has no package installer, runs as 10001
|
||
on a read-only root filesystem, and copies bounded projected configuration into
|
||
owned ephemeral 0600 storage. It keeps private persistent evidence at 0700/0600
|
||
and takes a process-lifetime lock before opening the serving runtime. This
|
||
preserves the store's checks despite Kubernetes projected-file ownership and
|
||
prevents a second service from sharing its evidence volume.
|
||
|
||
The installed custody CLI creates SQLite-consistent backups and reports only
|
||
schema/delivery/submission counts. Eleven real-container checks passed with no
|
||
external network or published port: restart and restore onto a second volume
|
||
preserved exact content, undelivered evidence and a synthetic unresolved intent.
|
||
All created containers/volumes were removed. The full suite passes 371 tests
|
||
(27 added). Runtime files in the image match all 22 source modules; dependencies
|
||
match the hashed lock. Trivy returned zero HIGH/CRITICAL findings; its Alpine
|
||
lifecycle-list warning is retained in the receipt.
|
||
|
||
The final lifecycle check exposed a real PID-1 shutdown defect: the first image
|
||
needed a forced kill (exit 137). An explicit SIGTERM handler now lets Waitress
|
||
drain and the audit pump stop; the final image exits cleanly within 15 seconds.
|
||
Recreate rollout and the 45-second pod termination window retain one writer.
|
||
|
||
The renderer prepares a Recreate Deployment, scoped projected caller token,
|
||
immutable runtime config, PVC/Service and exact network rules. Kubernetes serving
|
||
health remains separate from audit acceptance readiness, preserving refusal/
|
||
recovery access during an audit outage. The source now includes exact ingress
|
||
proposals for Approval Engine and the admitted PDP, because the existing caller
|
||
rules do not automatically admit this pod. No namespace-wide caller label is
|
||
added and no Secret, RBAC, policy assignment or Ingress is created.
|
||
|
||
Seven objects passed server dry-run in their exact namespaces. The eighth,
|
||
Approval Engine ingress, found its namespace absent. Its unchanged policy shape
|
||
passed in a representative existing namespace; that is schema validation only.
|
||
Observed Traefik label/websecure port, KeyCape public IP and local-path storage
|
||
were used to make the candidate concrete, not to claim CNI or custody proof.
|
||
|
||
Evidence: `docs/evidence/2026-09-11-container-candidate.json`; operating packet:
|
||
`deploy/README.md`. The image is local and unpublished. T08 stays `progress` for
|
||
the admitted native policy/caller/assignments, AUDIT-WP-0009-T11 custody, image
|
||
publication, owner service/namespace and exact peer admission, registration and
|
||
real human/deployed binding, platform backup/restore and product acceptance.
|
||
No cluster apply, native secret read, factory attempt or paid call occurred.
|
||
|
||
2026-09-11 — **native probe exposed fsGroup startup incompatibility.**
|
||
The native synthetic sender Job failed during private-store bootstrap, before
|
||
any audit HTTP request. A filesystem regression reproduced the same refusal:
|
||
Kubernetes fsGroup-style setgid volume roots cause `mkdir(0700)` to create a
|
||
`2700` child, which the evidence store correctly refuses. `private_directory`
|
||
now clears inherited setgid only on a directory this invocation just created,
|
||
through a non-symlink directory descriptor. Unsafe existing directories keep
|
||
their mode and remain refused. The private evidence contract stays 0700/0600.
|
||
|
||
The new regression failed before the correction and passes after it; existing
|
||
2700/2770 directories remain unchanged and refused. The available full suite
|
||
passes 335 tests, with 39 optional checks skipped in this environment. The native
|
||
sender retry consumed this exact corrected helper and passed ingestion,
|
||
restart/duplicate and scope/read refusal checks. The rebuilt local image
|
||
`sha256:92cde609fca63141740d53594a80a513e250a4d68cdde98b973a9de66403d8c8`
|
||
passes eleven container checks, including verified setgid-volume startup,
|
||
restart and restore. The fixture now asserts the volume's actual 2770 mode;
|
||
its one-shot volume initializer explicitly retains FSETID for that setup only.
|
||
The service container still drops all capabilities. Independent archive
|
||
readback, bearer-revocation acceptance, image scan/publication and cutover remain.
|
||
Evidence: `docs/evidence/2026-09-11-factory-native-producers.json`.
|
||
T08 stays progress for native acceptance and the existing deployment/human
|
||
binding/recovery gates; no service cutover or human disposition occurred.
|
||
|
||
## 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.
|
||
|
||
|
||
### T03 scoped review deployment — 2026-09-14
|
||
|
||
The operator explicitly admitted `net-kingdom-admins` as the human review group
|
||
for only the T03 apply, verify and read-only key-check records, separately from
|
||
its credential-reader membership. The review service is live and ready at
|
||
https://decisions.coulomb.social with verified KeyCape groups, fresh MFA and a
|
||
dedicated caller-bound Flex Auth policy. Its mandate does not grant consumption.
|
||
|
||
The new `secrets-engine-requester` client has subject `secrets-engine`, tenant
|
||
`tenant:platform`, role `secrets-engine-requester`, and only `approval:create`.
|
||
CCR-2026-0024 and CCR-2026-0025 provide distinct verifier and attended reader
|
||
custody. Native signature, subject, scope and TTL checks passed; excess scopes
|
||
and a wrong secret were refused. Existing consumer identity is unchanged.
|
||
Three real requested approvals were created with human control, required count
|
||
one, and zero entries. Platform evidence is
|
||
`docs/evidence/2026-09-14-t03-native-approval-requests.json`.
|
||
|
||
Review image: sha256:8f55bcecf37a8d65f96e073510b1ffb4636c0a91d75e1ee7d582ad4bce8b953a.
|
||
Policy image: sha256:c9f028b49dfcede930a9cc48757ec8371ecc71d20b1bfee2733e55298dffcc7c.
|
||
Review tests: 339 passed, 39 optional integration tests skipped; 11 container
|
||
checks and HIGH/CRITICAL image scan passed. Policy checks: 57 local and 6 native
|
||
caller checks passed, using synthetic subjects, not human binding evidence.
|
||
Approval Engine CPU request was reduced from 25m to 10m after observing 1m use;
|
||
review requests 20m and its PDP 5m. Limits are unchanged. Native services ready.
|
||
|
||
Remaining T03 gate: the operator's exact signed-in account is needed to address
|
||
three prepared immutable memos, followed by real acknowledgements and acceptance
|
||
in Informed Decision. No human entry or consume has been generated by an agent.
|
||
Then execute claim -> validated PDP Check -> CAS consume separately for apply,
|
||
verify and exec using scoped attended authority, and capture native denial,
|
||
revocation, workload health and key-check evidence. No OpenRouter credential has
|
||
been read and no inference or spend was performed. T03 remains waiting; this
|
||
entry supersedes earlier statements that requester or group admission is missing.
|
||
|
||
|
||
### Live browser registration correction — 2026-09-14
|
||
|
||
The user's login exposed `invalid_profile_usage: unknown client_id`: the public
|
||
`informed-decision-approver` registration existed in the source example but was
|
||
absent from live KeyCape. Applied exactly the existing admitted registration,
|
||
with UID/resourceVersion guards and byte-preserving insertion; unrelated clients,
|
||
configuration, signing key and pinned image were preserved. KeyCape is ready.
|
||
The actual review-site `/auth/start` now redirects through KeyCape to
|
||
`auth.coulomb.social`. Wrong redirect, consume scope and absent PKCE are refused.
|
||
Receipt: key-cape/docs/evidence/2026-09-14-informed-decision-browser-registration.json.
|
||
Repeatable contained helper: key-cape/tools/register-informed-decision.py
|
||
(default preflight; --apply mutates only a missing exact registration).
|
||
Human callback/MFA/token proof and T03 approval entries remain pending. The
|
||
previous ready check established service health, not browser login acceptance.
|