- Optional required_assurance {"level": aal1|aal2|aal3} at issue, schema v7.
Enforced at add_entry (403), claim (assurance_unsatisfied) and consume (409)
from the verified token level only; inherited on supersession; legacy rows
NULL. Served only when declared so undeclared claims keep exact bytes.
- deploy/networkpolicies.yaml: pod-scoped ingress for sso +
app.kubernetes.io/name=bounded-executor on 8080 (hub 39a3bffa). Not applied.
- docs/decision-services-alignment-answer.md: owner answer to the NetKingdom
decision services review (hub 81cafbf3): roles accepted, T02 split proposed,
presentation_ref at create declined with an entry-level counter-proposal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
6.2 KiB
Decision services alignment — Approval Engine owner answer
Status: owner answer to the NetKingdom review
net-kingdom/docs/proposals/2026-10-31-decision-services-alignment-review.md
(hub message 81cafbf3-8d54-44c6-9235-6f46e95aaf74), 2026-10-31. Also answers
the pod-scoped ingress request (hub 39a3bffa-2c7c-4ca6-8a57-61e4ed94820f).
This is an engine-owner technical position, not a founder decision. Nothing
here is deployed.
Summary
| Item | Position |
|---|---|
| Roles (§4.1) | Accepted. No change of role is needed here |
| Present an existing approval (§4.2) | Works on today's API. Step 3 needs only GET /v1/approvals/{id} with approval:read |
| S6 / T01 aal2 floor | Implemented in source (schema v7). Not yet released or deployed |
S6 / T02 actor_consumes |
Supported, still waiting. Owner proposes splitting it from principal_approves |
§6.3 presentation_ref at create |
Declined as proposed. Counter-proposal: the link goes on the entry, from the presenter |
| Executor ingress | Prepared in source: deploy/networkpolicies.yaml, policy approval-engine-bounded-executor. Live apply is founder-run |
1. Roles
The proposal matches this engine's INTENT and SCOPE:
- The engine keeps the object: binding, entries, consume, supersede, revoke and outbox evidence.
- Only the actor creates.
POST /v1/approvalsalready refuses unlessbinding.actorequals the token subject. informed-decision stays a legitimate actor for approvals it uses itself. - The engine stays not-an-inbox: get by id, no list and no notification. Discovery belongs to State Hub (the index) and informed-decision (the surface).
For the §4.2 verify step, informed-decision reads everything it needs from one
GET: status, entries, binding.human_control, binding.actor and
binding.digest. It should also check binding.required_assurance (T01, once
deployed) when a C2 class requires aal2. With human_control: true, the engine
already refuses an entry posted under informed-decision's own service token.
The founder's entry must carry the founder's token, as it does today.
One addition to §4.2 step 3: compare validity as well, so a memo cannot
present an approval whose window has already passed or is far wider than the
packet says.
2. S6
T01 (aal2 floor): done in source.
POST /v1/approvalsaccepts an optionalrequired_assurance: {"level": "aal1"|"aal2"|"aal3"}.POST /entriesrefuses a weaker, missing or unknown level from the verified token.- The claim reports
assurance_unsatisfied, andconsumerefuses a persisted entry below the floor. - The floor is inherited on supersession and is not part of the act digest.
- Legacy rows migrate to NULL, meaning no floor.
The member appears in GET and the claim only when declared. The access fact
reader rejects unknown members, so undeclared claims keep their exact bytes.
Contract: docs/approval-claim.md. It needs a v7 image release and a
founder-run rollout with a verified pre-v7 backup. Until then the executor keeps
its own aal2 check. Whether KeyCape issues aal2 stays KeyCape's question.
T02 (correspondence): supported, not implemented. The owner proposes splitting it:
actor_consumes(consumer subject must equalbinding.actor). It is low-risk and closes the denial-of-run gap: today any tenant service holdingapproval:consumecan spend the object. It adds no new identity mapping, because the actor is already pinned to the creator's token subject. The owner is ready to implement it as an opt-in issue flag once Gate House (or the founder) agrees thatactormeans "the only consumer" when the flag is set.principal_approves(entry subject must equalbinding.principal). It stays waiting. NetKingdom'sprincipallabels (for exampleadmin) must first equal KeyCape subjects exactly, and C2 doctrine (S5) may name approvers that are not the principal.
3. Open question 6.3 — presentation_ref at create
Declined as proposed, for three reasons:
- Ordering. In §4.2 the actor creates the approval before it submits the memo, so no memo id exists at create. Setting the field later would make the object mutable outside its state machine. The binding is immutable by design.
- Wrong asserter. A reference set by the actor is the actor's claim about where it will be presented. The evidence that matters is what the approver was shown, and only the presenting surface can state that.
- The join already exists in one direction. The memo carries
approval_idand the binding digest (§4.2 step 2), and audit-core can join on(approval_id, subject, approved_at).
Counter-proposal (needs informed-decision agreement; not implemented): an
optional entry-level presentation record. The approving surface posts it with
the entry, for example {"memo_id", "revision", "view_hash"}. The engine
stores it on that entry, serves it in GET and emits it in the issuance and use
threshold evidence. It is labelled surface-asserted: the engine checks its
shape, not its truth. Identity, principal type and assurance still come only
from the token. This closes Finding 5 (view_hash not inside the entry)
without changing creation. If agreed, it becomes a new APPROVAL-WP-0007 task.
4. Executor ingress
approval-engine-bounded-executor admits TCP 8080 to the approval-engine pod
from pods labelled app.kubernetes.io/name=bounded-executor in namespace
sso. The rule needs both selectors to match: they sit in one from element.
It does not label sso and does not change approval-engine-callers.
tests/test_networkpolicies.py holds that shape, so a split into two peers,
which would admit all of sso, fails the test. Network admission grants no
authority: the executor still needs its KeyCape token and scopes. To apply
(founder-run):
kubectl apply -f deploy/networkpolicies.yaml
Founder decisions needed
- Release and roll out the v7 image (T01) when the pilot needs the engine-side floor.
- Agree the T02 split, and that
actor_consumesmay be implemented now. - Accept or decline the entry-level
presentationcounter-proposal (with informed-decision). - Apply the executor ingress policy.