approval-engine/INTENT.md
tegwick 2a5a47df89 Align to security layer model v0.7 and open the engine spine
The statute is accepted at v0.7; the operative form is
net-kingdom/SECURITY-COMPANION.md v0.2. INTENT now declares Engine / PIP
in its own voice, carries the §9.6 threat decomposition, the load-bearing
heartbeat obligation, issuer and freshness on the claim, consumption as a
mutation, and the custody question closed rather than held open.

SCOPE.md is the first-cut boundary (nothing shipped). layer.yaml is the
machine-readable declaration. The review under history/ scores intent vs
scope vs the empty implementation. APPROVAL-WP-0001 sequences contracts
before code and keeps consumption unimplemented until GH-WP-0002-T06.

Registered with State Hub as infotech / approval-engine.

Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 11:58:28 +02:00

16 KiB

layer role standard companion
Engine PIP net-kingdom/canon/standards/security-layer-model_v0.7.md net-kingdom/SECURITY-COMPANION.md

INTENT

Layer: Engine. Role: PIP. NetKingdom Security Layer Model §3.3 / §4 catalog — a deterministic API for a modeled concept, where the same authoritative input state yields the same result. This engine supplies facts a decision consumes as claims; it never renders one. A new engine is a PIP unless the statute is amended, and §6 means it can never be a second PDP.

Statute (accepted 2026-08-29): net-kingdom/canon/standards/security-layer-model_v0.7.md. Operative form: net-kingdom/SECURITY-COMPANION.md v0.2. The statute governs on disagreement; a disagreement is a finding for gate-house.

Machine-readable declaration: layer.yaml.

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 implementation.

Purpose

approval-engine owns the approval as a durable, authenticated, consumable object — issued before an action, verified at the moment of use, and provably not replayable.

It answers one question, totally and decidably:

Is this approval valid right now — for this exact action, target, actor, and purpose — and has it already been used?

Nothing else. It does not decide whether the action is permitted; that is access-engine's and it stays the only decision point. An approval is one input to that decision. An outage here is input degradation, which is this engine's fallback to own (§9.3) — never a consumer-invented substitute for the object.

Why This Exists

The gap was raised by flex-auth on 2026-08-28 (FLEX-DEC-2026-001) while assenting to the security layer model. Splitting policy authoring from policy evaluation left one half of the approval contract without an owner:

  • the design of the approval contract is gate-house's (Staff — doctrine);
  • the validation of an approval at decision time is access-engine's;
  • the durable object, authenticated entries, and atomic supersession were nobody's.

It is not gate-house's: Staff holds no state another layer depends on at runtime (standard §3.4). It is not access-engine's: it does not own the organizational approval lifecycle, and an evaluator that also owns the object it evaluates is self-dealing. It is not audit-core's: an approval needs mutable, in-path, current-state semantics, and an append-only audit fabric is built for the opposite property.

So it is a concept, and concepts get engines. The catalog types it PIP because "we need an engine for X" must not drift into "X now decides".

The Concept

An approval is an independent authorization artifact required for actions that exceed normal policy. It binds, at issue time, to:

  • the action and the target it authorizes;
  • the actor who may use it, and the principal on whose behalf;
  • the purpose or task it was granted for;
  • a validity window;
  • the approvers — identified, authenticated, and countable, so that distinct-approver requirements are enforceable rather than aspirational.

And it carries state that is deliberately not a maturity ladder:

requested → approved → valid → consumed
                         ↓
                    superseded / revoked / expired

An approval object is not a Railiance workload and never becomes one (standard §20.1). Human commands, broker actions, and approvals that are not themselves deployables sit outside the railiance-* / rail-* / rapp-* / reef-* axes. This engine does not acquire an operational axis by being consumed from one.

Rigidity Is the Point

This engine is deliberately small, boring, and strict. Its state model is a closed machine rather than a graded progression, because at the moment of use the answer must be binary, total, and reconstructable.

Atomic supersession and single consumption are the security properties that make Canon test T-06 — Approval Replay passable. An approval that is partly valid, or whose validity must be inferred by replaying a log, is an approval that can be replayed. Flexibility here is a defect.

Consumption is a state change, never an inference (standard §9.7.3). An approval is consumed by a mutation in this engine. It MUST NOT be inferred from the existence of a decision record — the decision precedes the action and the action precedes consumption, so a decision record proves an intent to act, not an act.

Progression modelling — graded, evidence-based, open-ended — belongs to maturity-engine. The two engines are deliberate opposites and neither should drift toward the other.

What approval-engine Owns

  • the durable approval object and its identifiers;
  • authenticated approval entries, and distinct-approver counting;
  • the state machine above, with atomic supersession and consumption;
  • validity windows and expiry;
  • binding to action, target, actor, principal, and purpose;
  • revocation that does not require the holder's cooperation;
  • an introspection API that access-engine consumes as an input claim, carrying issuer and freshness so the PDP can state a deadline for this input class (§9.7.2);
  • emission atomicity — every issuance, use, supersession, and revocation is queued in this engine's local transactional store in the same transaction as the state change. See below; this is a contract, not an implementation detail;
  • emission cadence for load-bearing classes, in the form that actually works for rare events: positive reconciliation or a heartbeat, not rate monitoring.

Emission Atomicity

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:

An append-only archive with a verified hash chain proves that records were not altered or truncated after arrival. It cannot prove that a record was never sent (standard §9.6). A suppressed event leaves the chain perfectly intact and verification reports intact. audit-core reports what it received and will not imply it is everything that happened — correctly, because it cannot.

For an approval object this is not a corner case. The single most valuable event for an adversary to suppress is a revocation, and its absence is exactly what chain verification cannot see. Completeness is therefore this engine's obligation and nothing downstream can retrofit it.

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.

Which threat atomicity closes — and which it does not

v0.6 read as though a local outbox closed §9.6's opening sentence. It does not. The decomposition is now in the statute and is this engine's to carry, because approval evidence is load-bearing:

Threat Covered by When
Accidental omission — crash between mutation and emit local outbox (§9.4) prevented
Adversarial omission — a compromised source declines to insert, deletes before drain, or drains to nowhere cadence and reconciliation detected, after the fact
Adversarial omission at a compromised source nothing in the model prevents it

The outbox sits inside this engine's blast radius. It makes emission atomic against crash and partial failure, and nothing more. That residual is real.

Approval events are low-volume load-bearing. Rate monitoring cannot work: a handful of revocations a month has no rate to drop below, and suppression is indistinguishable from a quiet month. The required form is positive reconciliation or a heartbeat — compare this engine's object-state transitions against audit-core's event count per class, or assert nothing to report as a signed positive claim that can itself go missing. GH-WP-0002-T04 is the reference instance. A source of load-bearing evidence MUST declare this (standard §9.6).

Custody is not the residual's answer. Whether approvals warrant archival custody stronger than every other source is decided: no (standard §16). Approval evidence carries the same bound as every other source. Archival custody does not address omission at all. If a future requirement genuinely needs WORM or a transparency log, that is a different store with a different owner, raised then. This engine must not plan around a stronger archive.

The Claim Shape

access-engine consumes approvals as input claims. The claim MUST carry:

  • the approval identifier;
  • 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;
  • issuer (this engine) and a freshness rule, so the PDP can state a deadline for this input class rather than a single fiction covering every source (§9.7.2, §17 request-claim schema).

Without the digest, wrong-action, wrong-target, and wrong-scope proofs have nothing to compare against. Without issuer and freshness, a stated deadline for the approval input class is unfalsifiable.

Until Taxonomy publishes the shared request-claim schema (§17; ownership proposed, not assigned), this engine publishes the contract at its own boundary and yields to the schema when it exists. Inventing a permanent local shape is the drift §17 exists to prevent.

Consumption Ordering — Unresolved

The decision precedes the action, and the action precedes consumption. Three failure modes are named, and each needs an owner (standard §9.7.4, §16):

  • 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, in GH-WP-0002-T06, 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 decisionaccess-engine, always and only (standard §6);
  • approval policy — which actions require approval, how many approvers, and which separations of duty apply: that is doctrine, and doctrine is gate-house's (standard §6.2, reaching decisions as input claims or versioned policy rules);
  • identity and authentication of approverskey-cape, consumed as claims;
  • the evidence recordaudit-core holds the archive; this engine holds only the operative current state. The guarantee is bounded: audit-core is not WORM or object-locked, and its tamper evidence is conditional on live preconditions. Stronger custody is decided against (§16); this engine must not assume it has been reopened;
  • credentials materialized after a decision — secrets-engine;
  • maturity, readiness, or progression of anythingmaturity-engine;
  • an unreachable-engine stance map — this engine is not PEP-shaped. It does not cause a protected side effect on the far side of a decision. Callers that do are PEP-shaped and owe companion §5 / statute §6.4;
  • Railiance operational axes — an approval is not a workload (§20.1). How those axes meet this model beyond §20's first statement is unwritten; do not assume a mapping.

The repository's seed README described this as "an engine for modelling and managing decisions". That wording is withdrawn: decision is access-engine's word under standard §8, and this engine must never claim it.

Design Principles

  1. PIP, never PDP. Valid or not, consumed or not. No grades, and no "may this actor do X".
  2. Atomic or it does not count. Supersession and consumption are compare-and-swap operations, never read-then-write.
  3. The evaluator is not the owner. access-engine reads; it does not mutate.
  4. Approvals are inputs, never authority. Holding one is not permission; it is one claim the decision point weighs.
  5. Revocation beats cooperation. An approval can be killed without the holder's participation.
  6. Every transition is evidence, atomically, and locally. Issuance, use, supersession, and revocation are queued in this engine's own store in the same transaction that made them true. An event that can be lost is a state change that never happened, as far as anyone auditing can tell.
  7. Atomicity prevents accidents; it does not prevent a compromised source. Cadence for load-bearing classes is a heartbeat or a reconciliation, and it is a MUST.
  8. Consumption is a mutation. Inferring it from a decision record is a defect.
  9. Small on purpose. Scope growth here is a security regression.

Non-Goals

  • Not a policy decision point, and never one.
  • Not a workflow or ticketing system; it stores the artifact, not the meeting.
  • Not an approvals inbox or notification surface.
  • Not an audit archive.
  • Not a general state-machine service for other concepts.
  • Not a Railiance axis, a PEP, or an actuation surface.

What Would Make This Repository Wrong

  • It starts deciding. Any endpoint that answers "may this actor do X" rather than "is this approval valid" is the failure standard §6 names.
  • Supersession is not atomic. If two supersessions can interleave, the replay protection is theatre.
  • It grows a policy layer. If it starts holding rules about who may approve what, it has taken doctrine from gate-house.
  • Nobody consumes it. If access-engine does not read approvals as input claims, the object is decorative.
  • Emission is best-effort. If a revocation can succeed while its event is lost, the evidence half is silently incomplete and the most security-relevant transition is the one most likely to vanish.
  • It claims the outbox closes omission. Atomicity covers crash. Treating it as covering a compromised source, or skipping the heartbeat, is the v0.6 overclaim this engine must not reintroduce.
  • It implements consumption by guessing. The three races are named and unowned. Code that picks a side is a contract with access-engine that access-engine has not assented to.
  • It cites observation or containment that has not happened. Nothing is observed in production, and nothing can be contained automatically (companion §10).

Success Criteria

  1. access-engine renders approval-bound decisions using this engine's claims, with the approval identifier reconstructable from the decision record;
  2. Canon test T-06 — Approval Replay passes against a live implementation;
  3. an approval can be revoked without the holder's cooperation, and the revocation is effective at the next use;
  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, and with the queue local so an audit-core outage never blocks a revocation;
  6. the claim carries a binding digest, issuer, and freshness, and a decision rendered against approval A for request R cannot be replayed for request R';
  7. load-bearing classes declare a heartbeat or reconciliation, and divergence from audit-core's event count is a finding;
  8. consumption is not implemented until GH-WP-0002-T06 settles the contract;
  9. FLEX-WP-0017 T03 and T05 are unblocked.