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
333 lines
16 KiB
Markdown
333 lines
16 KiB
Markdown
---
|
|
layer: Engine
|
|
role: PIP
|
|
standard: net-kingdom/canon/standards/security-layer-model_v0.7.md
|
|
companion: 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:
|
|
|
|
```text
|
|
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 decision** — `access-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 approvers** — `key-cape`, consumed as claims;
|
|
- **the evidence record** — `audit-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 anything** — `maturity-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.
|