APPROVAL-WP-0007-T02 done: actor_consumes; T05 principal_approves split out
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." - Optional actor_consumes (boolean) at issue, schema v8. Consume refuses with 403 unless the consuming subject equals binding.actor, also on idempotent retries; internal calls without a consumer fail closed. Inherited on supersession (existing-successor mismatch 409), in audit details, served in GET/claim only when declared so undeclared claims keep exact bytes. Legacy rows migrate without the flag. - GH-DEC-2026-026 Ruling 6: under human_control, add_entry refuses (403) the binding actor as approver. human_control already covered a service actor; this covers a human actor. - Workplan: T02 done (actor_consumes); new T05 principal_approves (wait). - Not released or deployed: needs a v8 image and founder-run rollout. 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
This commit is contained in:
parent
e9e9f93317
commit
5c45d3c157
11 changed files with 344 additions and 36 deletions
|
|
@ -425,3 +425,34 @@ entry, never a request body:
|
|||
Migration leaves existing rows NULL (no floor). The declaration is separate from
|
||||
the five-field act digest. Whether KeyCape issues `aal2` for a step-up login is
|
||||
KeyCape's question.
|
||||
|
||||
### Declared consumer = actor — `binding.actor_consumes`
|
||||
|
||||
APPROVAL-WP-0007-T02 (actor half), schema v8 store. `POST /v1/approvals` may
|
||||
declare `"actor_consumes": true` (a JSON boolean; anything else is 422; `false`
|
||||
is the same as absent). The flag is stored on the object, served in `GET` and
|
||||
the claim as `binding.actor_consumes: true`, carried on audit events in
|
||||
`details.actor_consumes`, and inherited on supersession (linking an existing
|
||||
successor with a different declaration is a 409).
|
||||
|
||||
The member is **present only when declared**, so undeclared objects keep their
|
||||
exact pre-v8 bytes. Absence means any tenant service or agent holding
|
||||
`approval:consume` may consume, as before.
|
||||
|
||||
When declared, `POST /consume` refuses with **403** unless the authenticated
|
||||
consuming subject equals `binding.actor`. The check runs before the idempotent
|
||||
branch, so another service gets no receipt from a retry either. An internal
|
||||
call with no authenticated consumer fails closed. The check does not change
|
||||
`valid_now`: the claim describes the object, not who is reading it.
|
||||
|
||||
Migration leaves existing rows without the flag. The declaration is separate
|
||||
from the five-field act digest.
|
||||
|
||||
### Actor is never an approver under `human_control`
|
||||
|
||||
GH-DEC-2026-026 Ruling 6. Under `human_control: true`, `POST /entries` refuses
|
||||
(403) an entry whose verified subject equals `binding.actor`. For a service
|
||||
actor the existing human-only rule already made this impossible; the check
|
||||
covers a human actor, who may create the request but not approve it. Objects
|
||||
without `human_control` are unchanged. Entries persisted before this check
|
||||
are not re-evaluated by the claim or consume.
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ unchanged:
|
|||
"validity": {"not_before": "<window start>", "expires_at": "<window end>"},
|
||||
"required_count": 1,
|
||||
"human_control": true,
|
||||
"required_assurance": {"level": "aal2"},
|
||||
"actor_consumes": true,
|
||||
"pdp_path": false
|
||||
}
|
||||
```
|
||||
|
|
@ -64,7 +66,16 @@ Things the requester must know:
|
|||
- **Declare `human_control: true`.** Then `add_entry` refuses any non-human
|
||||
approver and consume refuses without the required human entries
|
||||
(`Engine.add_entry`, `Engine.consume`). This keeps the service actor from
|
||||
approving its own request.
|
||||
approving its own request. GH-DEC-2026-026 Ruling 6 (the actor is never an
|
||||
approver of its own approval) is therefore already met for this service
|
||||
actor by `human_control` alone. For a human actor it is not, so `add_entry`
|
||||
also refuses (403), under `human_control`, an entry whose subject equals
|
||||
`binding.actor` (`docs/approval-claim.md`).
|
||||
- **Declare `actor_consumes: true`** (APPROVAL-WP-0007-T02, schema v8, in
|
||||
source, not yet deployed). Consume then refuses with 403 unless the
|
||||
consuming subject equals `binding.actor` (see §3). The `required_assurance`
|
||||
and `actor_consumes` members are accepted only by a v7/v8 image; until one is
|
||||
live, omit them and keep the executor's own checks.
|
||||
- **`pdp_path: false` for the pilot.** `pdp_path: true` requires a PDP request
|
||||
digest at issue (GH-DEC-2026-008). There is no access-engine decision for
|
||||
executor operations yet, so the pilot is off the PDP path. When access-engine
|
||||
|
|
@ -72,8 +83,10 @@ Things the requester must know:
|
|||
- **`principal` is a bound label, not an approver constraint.** The engine does
|
||||
not check that the approving human equals `binding.principal`. Any human
|
||||
holding `approval:approve` for the tenant can approve. Proposal
|
||||
APPROVAL-WP-0007-T02 lets an issue declare that correspondence; until then
|
||||
the executor checks the entry subject itself (see §3).
|
||||
APPROVAL-WP-0007-T05 (`principal_approves`, split from T02 by founder
|
||||
decision 2026-11-01) would let an issue declare that correspondence; it
|
||||
waits for the KeyCape subject mapping. Until then the executor checks the
|
||||
entry subject itself (see §3).
|
||||
|
||||
## 2. Assurance (BEA-C4)
|
||||
|
||||
|
|
@ -143,11 +156,14 @@ either one.
|
|||
`GET /v1/approvals/{id}` (scope `approval:read`) is the existing authoritative
|
||||
read surface for confirming consumption
|
||||
(`docs/task-execution-consumption-contract.md`, "Existing route and trust
|
||||
boundary"). The engine does not check whether a digest is equivalent and does
|
||||
not check that the consuming subject equals `binding.actor`. Any service in
|
||||
the tenant that holds `approval:consume` could spend the object. That cannot
|
||||
make anything run, but it can deny the run. Proposal APPROVAL-WP-0007-T02 adds
|
||||
an opt-in check that the consumer is the actor. The reading we intend for the
|
||||
boundary"). The engine does not check whether a digest is equivalent. Without a
|
||||
declaration it does not check that the consuming subject equals
|
||||
`binding.actor`: any service in the tenant that holds `approval:consume` could
|
||||
spend the object. That cannot make anything run, but it can deny the run.
|
||||
**Update (APPROVAL-WP-0007-T02, in source, not yet deployed):** issue with
|
||||
`"actor_consumes": true` and consume refuses (403) every other subject,
|
||||
including on idempotent retries; GET and the claim serve
|
||||
`binding.actor_consumes: true`. The reading we intend for the
|
||||
executor:
|
||||
|
||||
1. Before consuming, GET the object and require: `status == "approved"`; the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue