Local outbox, claim shape, and the unresolved consumption ordering
From the v0.5 review round. - The outbox is local, in this engine's own transactional store, with no synchronous audit-core dependency inside the state-change transaction. Raised by audit-core: "or equivalent" would otherwise let an implementer satisfy atomicity by emitting synchronously, turning an audit outage into an inability to revoke. - The claim must carry the approval identifier and a digest over the same canonical binding access-engine already computes, so "approved" and "approved for this exact request" are distinguishable at decision time. Raised by flex-auth, needed for FLEX-WP-0017 T05. - Consumption ordering is recorded as UNRESOLVED rather than guessed. The decision precedes the action and the action precedes consumption, so an allow never consumed, a double consumption by racing callers, and consumption after a failed action are gaps neither engine closes alone. Nothing may be implemented by assuming that contract. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 2564823@bnt-lap001 Assistant-Session: 2a7ed827-4928-4b9f-8613-9135c9cadfe9
This commit is contained in:
parent
35fee0ea84
commit
ab5d69e8f1
1 changed files with 40 additions and 3 deletions
43
INTENT.md
43
INTENT.md
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
> **Layer: Engine.** NetKingdom Security Layer Model §4 catalog — a deterministic
|
||||
> API for a modeled concept, where the same authoritative input state yields the
|
||||
> same result. Standard: `net-kingdom/canon/standards/security-layer-model_v0.4.md`.
|
||||
> same result. Standard: `net-kingdom/canon/standards/security-layer-model_v0.5.md`.
|
||||
>
|
||||
> This file captures why this repository exists and the kind of system it is
|
||||
> meant to become. It is aspirational and stable, not a description of current
|
||||
|
|
@ -98,6 +98,15 @@ An approval MUST NOT be issued, consumed, superseded, or revoked without the
|
|||
corresponding event being durably queued in the same transaction that changed
|
||||
the state. Transactional outbox, or an equivalent that cannot separate the two.
|
||||
|
||||
**The queue is local.** The outbox lives in this engine's own transactional
|
||||
store. No synchronous dependency on `audit-core` sits inside the state-change
|
||||
transaction. With a local outbox, fail-closed triggers only when this engine's
|
||||
own store is unavailable — where the change could not have been recorded anyway
|
||||
— and an `audit-core` outage does not block a revocation. Emitting synchronously
|
||||
to `audit-core` inside the transaction would also be atomic, and would turn an
|
||||
audit outage into an inability to revoke: the operation least tolerable to block
|
||||
during an incident. Standard §9.4, raised by `audit-core`.
|
||||
|
||||
This is a condition of `audit-core`'s assent (`AUDIT-IN-0001`) and is in the
|
||||
standard at §9.4. The reasoning is worth carrying here rather than by reference,
|
||||
because it constrains the implementation from the first commit:
|
||||
|
|
@ -117,6 +126,31 @@ Consequence for design: emit-after-commit is a defect, not an optimization. A
|
|||
best-effort publish, a fire-and-forget queue, or any path where the state change
|
||||
can succeed while the event is lost is out of contract.
|
||||
|
||||
## The Claim Shape
|
||||
|
||||
`access-engine` consumes approvals as input claims. The claim MUST carry the
|
||||
approval identifier **and a digest over the same canonical binding the decision
|
||||
point already computes**, so that *approved* and *approved for this exact
|
||||
request* are distinguishable at decision time. Without it, wrong-action,
|
||||
wrong-target, and wrong-scope proofs have nothing to compare against.
|
||||
|
||||
## Consumption Ordering — Unresolved
|
||||
|
||||
The decision precedes the action, and the action precedes consumption. Three
|
||||
states follow that neither engine closes alone:
|
||||
|
||||
- an allow rendered against an approval that is then never consumed;
|
||||
- an approval consumed twice by racing callers;
|
||||
- an approval consumed after the action it authorized has already failed.
|
||||
|
||||
`approval-engine` performs the mutation, because `access-engine` never mutates.
|
||||
But *who signals consumption, at what point relative to the decision, and what
|
||||
happens on each of the three states above* is a contract between the two engines
|
||||
and is **not yet settled**. It is recorded in the standard's §16 and is required
|
||||
before `FLEX-WP-0017` T05. Raised by `flex-auth`.
|
||||
|
||||
Nothing here may be implemented by guessing that contract.
|
||||
|
||||
## What approval-engine Does Not Own
|
||||
|
||||
- **the decision** — `access-engine`, always and only (standard §6);
|
||||
|
|
@ -185,5 +219,8 @@ word under standard §8, and this engine must never claim it.
|
|||
4. distinct-approver requirements are enforced by the engine rather than by
|
||||
convention;
|
||||
5. no state change can be committed without its event — demonstrated by a test
|
||||
that fails the transaction when emission fails, not by inspection;
|
||||
6. `FLEX-WP-0017` T03 and T05 are unblocked.
|
||||
that fails the transaction when emission fails, not by inspection, and with
|
||||
the queue local so an `audit-core` outage never blocks a revocation;
|
||||
6. the claim carries a binding digest, and a decision rendered against approval
|
||||
A for request R cannot be replayed for request R';
|
||||
7. `FLEX-WP-0017` T03 and T05 are unblocked.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue