Implement the engine spine: claim, outbox, machine, API

Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).

Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.

Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.

FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.

Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
This commit is contained in:
tegwick 2026-08-29 12:52:49 +02:00
parent 624e43f554
commit 9c9528f5b2
29 changed files with 2121 additions and 26 deletions

View file

@ -56,11 +56,16 @@ its three tasks are already discharged by this file, `AGENTS.md`, and
```task
id: APPROVAL-WP-0001-T02
status: todo
status: done
priority: high
state_hub_task_id: "ca2ec789-b760-525a-9960-95d15760191e"
```
2026-08-29: `schemas/approval_claim.schema.json`, `docs/approval-claim.md`,
examples. Native digest is sorted-key JSON of action/actor/principal/purpose/target;
optional `pdp_digest` records `NewDecisionBinding.request_digest` at issue.
Yields to Taxonomy §17.
Specify the input claim `access-engine` consumes: approval identifier,
canonical-binding digest over the same binding the PDP already computes,
issuer (this engine), and freshness. Schema and examples in-repo.
@ -78,11 +83,17 @@ class.
```task
id: APPROVAL-WP-0001-T03
status: todo
status: done
priority: high
state_hub_task_id: "a2eb2e8f-9b49-598e-b4a1-fb93e65f1bf3"
```
2026-08-29: `docs/outbox-contract.md`. Classes issuance/use/supersession/revocation/heartbeat.
Payload is `audit-core.event.v1alpha1`. Synchronous `audit-core` inside the
mutation transaction is named forbidden.
Event classes (issuance, use, supersession, revocation, and the heartbeat
class T04 needs), same transaction as the object mutation, queue local to
this engine, at-least-once into the outbox (`audit-core` dedupes on event
@ -98,11 +109,17 @@ cannot satisfy it by emitting synchronously to `audit-core`.
```task
id: APPROVAL-WP-0001-T04
status: todo
status: done
priority: high
state_hub_task_id: "fce0df25-0106-5c0a-8187-87b956ca6220"
```
2026-08-29: `cadence.yaml` + `docs/emission-cadence.md`. `layer.yaml`
`evidence.cadence_status: declared`. Missing heartbeat and count divergence
are findings. Rate monitoring forbidden.
Approval evidence is load-bearing and low-volume. Rate monitoring is the
wrong form (statute §9.6). Publish the source-side declaration: expected
classes, the heartbeat (*nothing to report*, signed, itself able to go
@ -139,11 +156,17 @@ consumption from a decision record, no demo that "just consumes on allow".
```task
id: APPROVAL-WP-0001-T06
status: todo
status: done
priority: high
state_hub_task_id: "44c4997b-6833-5540-a554-0e24210809f2"
```
2026-08-29: SQLite object + closed machine in `approval_engine/store.py`.
CAS supersession (concurrent test), distinct-approver fail-closed, revocation
without holder cooperation. `_cas_consume` is unexported.
Depends on T02 and T03. Implement the object and the machine in SCOPE:
identifiers; bindings (action, target, actor, principal, purpose, validity
window, approvers); authenticated entries; distinct-approver counting;
@ -161,11 +184,16 @@ closed on duplicates; revocation is effective at the next introspection.
```task
id: APPROVAL-WP-0001-T07
status: todo
status: done
priority: high
state_hub_task_id: "ea51499d-ced5-50fa-b852-396719a8c0f4"
```
2026-08-29: `GET /v1/approvals/{id}/claim`. Tests forbid decision-shaped
keys and `/v1/check` / `/authorize` / `/consume`. Store unavailable → 503.
Depends on T02 and T06. An API that answers INTENT's question and returns
the T02 claim. No endpoint answers "may this actor do X". Fail-closed when
this engine's own store is unavailable.
@ -178,11 +206,17 @@ forbid a decision-shaped response.
```task
id: APPROVAL-WP-0001-T08
status: todo
status: done
priority: high
state_hub_task_id: "b9267011-71b3-5138-a3f9-5256374a6b3b"
```
2026-08-29: Outbox insert in the same IMMEDIATE transaction. Test fails the
transaction when emission fails (no leftover mutation). Revoke succeeds while
the drain sink is down; the revocation row stays undrained.
Depends on T03 and T06. Every issuance, use, supersession, and revocation
inserts the outbox row in the same transaction. Drain is asynchronous.
`audit-core` outage does not block a revocation.
@ -196,11 +230,18 @@ does not.
```task
id: APPROVAL-WP-0001-T09
status: todo
status: done
priority: medium
state_hub_task_id: "b4946568-5dda-5bd7-9941-83a5f0137700"
```
2026-08-29: `tests/test_t06_replay.py` encodes Canon T-06 for wrong target,
wrong action, later time, revoked, superseded. Consume-side replay stays out.
Handoff: `docs/flex-auth-handoff.md`. `FLEX-WP-0017` T03 is unblocked on this
object; T05 remains blocked only on consumption ordering.
Depends on T07, T08, and T05 (the last only for the consume-side replay
cases). Point Canon `T-06 — Approval Replay` at a live implementation.
Hand `access-engine` / `secrets-engine` a claim they can validate before