approval-engine/SCOPE.md
tegwick 9c9528f5b2 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
2026-08-29 12:52:49 +02:00

8.2 KiB

SCOPE

Implemented-and-first-cut boundary for agents and contributors. Aspirational direction belongs in INTENT.md; current work and operational gates belong in workplans/. Derived from INTENT after alignment to the accepted NetKingdom Security Layer Model v0.7 and its working companion.

One-liner

approval-engine is the PIP for the approval object: a durable, authenticated, consumable artifact that access-engine reads as an input claim and that this engine alone mutates.

Core Idea

An approval is current-state, not a log and not a decision. This repository owns the object, the closed state machine, the local outbox that makes every transition evidence, and the claim access-engine consumes. It does not decide whether the action is permitted, does not author the policy that requires an approval, and does not archive the trail.

The first cut is the spine that makes Canon T-06 — Approval Replay passable and unblocks FLEX-WP-0017 T03/T05 — and nothing that has to guess a contract the other side has not assented to.

In Scope

  • Durable approval objects with stable identifiers.
  • Bindings captured at issue time: action, target, actor, principal, purpose, validity window, identified approvers.
  • Authenticated approval entries and distinct-approver counting.
  • The closed state machine requested → approved → valid → consumed, with superseded / revoked / expired as terminal exits from valid.
  • Atomic supersession and single consumption (compare-and-swap, never read-then-write).
  • Revocation that does not require the holder's cooperation and is effective at the next use.
  • A local transactional outbox in this engine's own store; no synchronous audit-core dependency inside a state-change transaction.
  • An introspection API that returns an input claim: approval identifier, canonical-binding digest, issuer, freshness.
  • Emission-cadence declaration for load-bearing classes, as a heartbeat or as reconciliation against audit-core event counts — not rate monitoring.
  • Machine-readable layer declaration (layer.yaml) as Engine / PIP, with no decision surface and no Tooling contact.
  • Fail-closed when this engine's own store is unavailable. An audit-core outage does not block a revocation.

Out of Scope

  • Authorization decisions — access-engine (statute §6).
  • Approval policy: which actions require approval, how many approvers, which separations of duty — gate-house doctrine, arriving as claims or versioned policy rules.
  • Identity and authentication of approvers — key-cape.
  • The evidence archive — audit-core. This engine emits; it does not store the trail or answer "is this approval valid?" from records.
  • Stronger-than-shipped custody (WORM, object lock, transparency log) — decided against (statute §16).
  • Credentials materialized after a decision — secrets-engine.
  • Graded progression, readiness, gap register — maturity-engine.
  • Workflow, ticketing, inbox, notifications — the artifact, not the meeting.
  • A general state-machine service for other concepts.
  • PEP shape, unreachable-engine stance maps, Railiance rail-* / rapp-* / reef-* axes. An approval is not a workload (statute §20.1).
  • Consumption signaling relative to the decision, until GH-WP-0002-T06 settles it with access-engine. Guessing that contract is out of scope even as a prototype.

Relevant When

  • A protected action exceeds normal policy and needs a durable approval object bound to the exact request.
  • access-engine needs an input claim that distinguishes approved from approved for this exact action, target, actor, and purpose.
  • Distinct-approver requirements must be enforced rather than conventional.
  • An approval must be revocable without the holder's cooperation.
  • Canon T-06 — Approval Replay is the test, or FLEX-WP-0017 T03/T05 is blocked on the missing object.
  • An event about an approval (especially a revocation) must not be separable from the state change that made it true.

Not Relevant When

  • The question is "may this actor do X" — that is a decision.
  • The work is policy authoring, runbooks, or credential issuance.
  • The need is an audit query, a hash chain, or a WORM store.
  • The need is a workflow inbox, a meeting, or a notification surface.
  • The need is graded readiness or a maturity ladder.
  • The work is mapping Railiance operational axes onto security objects.
  • Consumption ordering is being "solved" from this side alone.

Current State

  • Status: first-cut spine. SQLite-backed object, closed machine, local outbox, WSGI introspection API, claim contract. Not a production deploy.
  • Layer declaration: INTENT frontmatter + layer.yaml. Cadence declared in cadence.yaml. No Tooling contacts.
  • Consumption is not a public API (APPROVAL-WP-0001-T05 waits on GH-WP-0002-T06).
  • Taxonomy request-claim schema is still unassigned; the local claim yields.
  • Work: APPROVAL-WP-0001. Tests: make test.

How It Fits

gate-house          designs the approval contract (Staff, doctrine)
key-cape            authenticates approvers (Tooling → claims)
approval-engine     owns and mutates the object (Engine, PIP)  ← this repo
access-engine       consumes the claim; never mutates (Engine, PDP)
audit-core          archives what this engine emitted (Engine, Evidence)
secrets-engine      materializes credentials after a decision (Engine, Lifecycle)

Staff never holds this object. access-engine never owns it. audit-core never answers whether it is still valid. Railiance consumes it as a claim; it does not become a Railiance axis.

Terminology

Term Meaning here
Approval The durable object this engine owns. Not a decision, not a workflow item.
Claim What access-engine reads. Identifier + binding digest + issuer + freshness.
PIP Policy Information Point. Supplies facts; does not decide.
PDP Policy Decision Point. access-engine only.
Consumption A mutation in this engine. Never inferred from a decision record.
Load-bearing A control's soundness depends on the event being present. Revocations are this.
Local outbox Durable queue in this engine's own transactional store.
Heartbeat Signed nothing to report for a quiet load-bearing class.
Other Relation Do not confuse with
access-engine / flex-auth Consumer of claims; owner of the decision. FLEX-WP-0017 T03/T05 wait on this object. This engine validating "may this actor do X".
gate-house Doctrine; designs the contract. GH-WP-0002 is the evidence-integrity work this spine must satisfy. This engine authoring who may approve what.
audit-core Evidence destination. AUDIT-IN-0001 conditions the outbox. This engine exposing an approval-validity query from records.
maturity-engine Deliberate opposite: open graded ladder vs closed binary machine. Levels as a substitute for valid/consumed.
secrets-engine Downstream of a decision. Approvals as credentials.
State Hub /decisions/{uuid} Not this object. Free-form decided_by is what FLEX-WP-0017 T03 rejected as a substitute. Storing approvals in the hub.

Provided Capabilities

type: api
title: Approval introspection claim
description: >
  GET /v1/approvals/{id}/claim returns an input claim with identifier,
  canonical-binding digest, optional PDP digest, issuer, and freshness.
  It does not decide whether an action is permitted.
keywords: [approval, claim, pip, digest, freshness]
type: api
title: Approval object lifecycle
description: >
  Create, collect authenticated entries with distinct-approver counting,
  atomically supersede, and revoke without holder cooperation. No public
  consume until consumption ordering is settled.
keywords: [approval, state-machine, cas, revocation, supersession]
type: infrastructure
title: Local transactional outbox
description: >
  Issuance, use, supersession, revocation, and heartbeat rows are inserted
  in the same SQLite transaction as the mutation. Drain is asynchronous;
  an audit-core outage does not block revocation.
keywords: [outbox, audit-core, emission-atomicity, heartbeat]