diff --git a/INTENT.md b/INTENT.md index bfb8cf2..39f8a80 100644 --- a/INTENT.md +++ b/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.