approval-engine/workplans/APPROVAL-WP-0007-bounded-execution-carrier.md
tegwick 0a61207b2f State Hub ids written back (APPROVAL-WP-0007)
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
2026-11-01 04:01:27 +01:00

135 lines
5.6 KiB
Markdown

---
id: APPROVAL-WP-0007
type: workplan
title: "Bounded-execution admission carrier: assurance floor, correspondence and revoker evidence"
domain: infotech
repo: approval-engine
status: active
flavor: implementation
owner: approval-engine
topic_slug: netkingdom
created: "2026-10-31"
updated: "2026-11-01"
depends_on: []
state_hub_workstream_id: "af365506-f451-50b5-a0a2-f0ba9a0a4d9c"
---
Demand: NetKingdom NK-WP-0056-T02 owner request (hub message
40344380-ea33-4670-9b27-dafca1699d36) to carry a Bounded Execution Admission
v0.1 (proposed) as an approval. Owner answer:
`docs/bounded-execution-admission-carrier.md`. The binding, consume and
revoke already work. This plan closes the engine-side gaps the answer found.
Every change is opt-in at issue, so existing approvals and consumers keep
their behavior. Native digest, CAS consumption and no-unconsume are unchanged.
## Declared minimum approver assurance
```task
id: APPROVAL-WP-0007-T01
status: done
priority: high
state_hub_task_id: "9ce0aca8-7694-53c6-a76a-f78d395a9861"
```
Add an optional `required_assurance` (minimum `level`, ordered aal1 < aal2 <
aal3) to `POST /v1/approvals`. Store it and serve it in the object and the
claim, and inherit it on supersede. `add_entry` refuses an entry whose
verified token `assurance.level` is weaker or missing. `_evaluate` and
`consume` refuse when an existing entry does not meet it. Schema migration
leaves old rows NULL, meaning no floor. Tests cover aal1 refused, aal2
accepted, missing level refused, supersede inheritance and the claim
reason_code. Serves BEA-C4. Whether KeyCape emits `aal2` is KeyCape's
question, outside this plan.
Done 2026-10-31 in source (schema v7, `tests/test_required_assurance.py`,
contract in `docs/approval-claim.md`). The member is present only when
declared, so undeclared claims keep their exact bytes for strict readers.
Not released or deployed: a v7 image and a founder-run rollout are still needed.
## Opt-in consumer = actor (`actor_consumes`)
```task
id: APPROVAL-WP-0007-T02
status: done
priority: medium
state_hub_task_id: "9657820d-23f9-54d9-920c-8f2a72110ad0"
```
Optional issue flag `actor_consumes: true` on `POST /v1/approvals`: the
consuming subject must equal `binding.actor`. Closes the denial-of-run gap
(today any tenant service with `approval:consume` can spend the object). Adds
no identity mapping, because the actor is already pinned to the creator's
token subject. Serves GH-DEC-2026-026 build-mode relaxation B2.
Split from the original T02 by founder decision 2026-11-01, relayed by the
net-kingdom coordinator: "Agree to split APPROVAL-WP-0007-T02: build
actor_consumes now (opt-in at issue; the bounded executor will set it);
principal_approves keeps waiting for the KeyCape subject mapping."
`principal_approves` moved to T05.
Done 2026-11-01 in source (schema v8, `tests/test_actor_consumes.py`,
contract in `docs/approval-claim.md`). Stored on the object, served in GET and
the claim only when declared (undeclared claims keep their exact bytes),
carried in audit `details`, inherited on supersession (existing-successor
mismatch is 409). Consume refuses with 403 when the consuming subject differs,
also on idempotent retries; an internal call without a consumer fails closed.
Legacy rows migrate without the flag. Same commit: under `human_control`,
`add_entry` refuses (403) the binding actor as approver (GH-DEC-2026-026
Ruling 6); human_control alone already covered a service actor, this covers a
human actor. Not released or deployed: a v8 image and a founder-run rollout
are still needed.
## Opt-in approver = principal (`principal_approves`)
```task
id: APPROVAL-WP-0007-T05
status: wait
priority: medium
state_hub_task_id: "b0b9398d-f9f9-507f-b8f7-91ed10c1a9b8"
```
Optional issue flag `principal_approves: true`: every entry subject must equal
`binding.principal`. It gives `principal` an enforced meaning, so it waits.
Wait reasons: the KeyCape subject mapping (NetKingdom's principal strings must
equal KeyCape subjects exactly) and the S5 doctrine. Split from T02 by the
founder decision of 2026-11-01 quoted under T02. Until it lands, any human
holding `approval:approve` for the tenant can approve, and the executor checks
the entry subject itself (`docs/bounded-execution-admission-carrier.md` §3).
## Revoker identity in revocation evidence
```task
id: APPROVAL-WP-0007-T03
status: todo
priority: medium
state_hub_task_id: "7ceb5ffd-3ba8-5e9d-96a2-5e89c38a5c43"
```
Record the verified revoker's subject, principal_type and assurance, plus an
optional bounded `reason`, in the `approval.revocation` outbox event. Persist
them on the object (the `revoked_by` fields) without changing the CAS or
terminal semantics. This is the evidence for the founder's pre-start kill
switch (NK-WP-0056-T07). Do not require the revoker to equal the principal:
revocation must stay possible without the holder.
## Synthetic bounded-execution carrier proof
```task
id: APPROVAL-WP-0007-T04
status: todo
priority: medium
state_hub_task_id: "673890fe-3d49-5fe3-8ad7-c8c73e4d9376"
```
Add an in-process test and an example request covering the full path: the
executor (service) creates the request (`action: bounded-execution`,
`target: {admission_id, admission_digest}`, `human_control: true`,
`pdp_path: false`); a human aal2 entry approves it; GET binding comparison;
consume with `request_digest` = admission digest; idempotent retry; a
different digest returns 409; revoke before consume gives 409 on consume;
consumption outside the window is refused. Synthetic fixtures only, with no
live service.
## Founder decision 2026-10-31
Approved by the founder (`proposed` -> `ready`) after the NK-WP-0056-T02 owner answers (hub decision 19afd15e).