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
This commit is contained in:
tegwick 2026-08-29 11:58:28 +02:00
parent ab5d69e8f1
commit 2a5a47df89
9 changed files with 1069 additions and 35 deletions

34
.custodian-brief.md Normal file
View file

@ -0,0 +1,34 @@
<!-- custodian-brief: generated by statehub register; fix-consistency may replace this file -->
# Custodian Brief - approval-engine
**Project:** approval-engine
**Domain:** infotech
**State Hub:** http://127.0.0.1:8000
**Topic ID:** `cee7bedf-2b48-46ef-8601-006474f2ad7a`
**Layer:** Engine / PIP (statute v0.7)
## Open Workplans
### v0.7 alignment and the engine spine
Workplan file: `workplans/APPROVAL-WP-0001-v07-alignment-and-engine-spine.md`
Open tasks:
- T01 - Align declaration to the accepted statute (done)
- T02 - Publish the approval claim contract
- T03 - Specify the local transactional-outbox contract
- T04 - Declare load-bearing cadence as heartbeat or reconciliation
- T05 - Wait on consumption ordering; do not implement it
- T06 - Durable object, closed state machine, authenticated entries
- T07 - Introspection API as input claims
- T08 - Local outbox in the mutation transaction
- T09 - Canon T-06 and the FLEX-WP-0017 handoff
## Session Start
1. Read `INTENT.md`, `SCOPE.md`, `layer.yaml`, and `AGENTS.md`.
2. Check inbox: `GET /messages/?to_agent=approval-engine&unread_only=true`.
3. Scan `workplans/`.
4. Update task statuses in workplan files as work progresses.
Last generated: 2026-08-29

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
# state-hub: track .claude/rules
# Claude Code local state (track shared rules; ignore machine-specific files)
.claude/*
!.claude/rules/
!.claude/rules/*.md

272
AGENTS.md Normal file
View file

@ -0,0 +1,272 @@
# approval-engine — Agent Instructions
## Repo Identity
**Purpose:** PIP for the approval object: durable, authenticated, consumable, atomically supersedable.
**Domain:** infotech
**Repo slug:** approval-engine
**Topic ID:** `cee7bedf-2b48-46ef-8601-006474f2ad7a`
**Workplan prefix:** `APPROVAL-WP-`
---
## State Hub Integration
The Custodian State Hub tracks work across all domains. Codex uses HTTP REST and
the `statehub` CLI by default. MCP is opt-in because the current Codex MCP bridge
adds severe call latency; the full administrative MCP surface remains available
to clients that need it.
| Context | URL |
|---------|-----|
| Local workstation | `http://127.0.0.1:8000` |
| Remote via tunnel | `http://127.0.0.1:18000` |
| Optional local edge relay | http://127.0.0.1:18080 |
When an operator has enabled the edge relay, set API_BASE to the relay URL.
Queueable writes return an explicit queued receipt if the central hub is
unreachable. Treat that as pending local evidence, then ask the operator to run
statehub outbox status/replay after connectivity returns.
Codex workspace-write sandboxes need network access enabled to reach the host's
loopback listener. Bootstrap this once with `make -C ~/state-hub configure-codex`
and restart Codex. The canonical REST health endpoint is `/state/health`, not
`/health`. If a sandboxed loopback probe fails, retry it with escalated execution
before declaring State Hub unavailable; a managed Codex permission profile may
still enforce isolated networking. Experimental MCP can be enabled explicitly
with `make -C ~/state-hub configure-codex WITH_MCP=1`.
### Orient at session start
```bash
# Offline brief — works without hub connection
cat .custodian-brief.md
# Active workplans for this domain
curl -s "http://127.0.0.1:8000/workplans/?topic_id=cee7bedf-2b48-46ef-8601-006474f2ad7a&status=active" \
| python3 -m json.tool
# Check inbox
curl -s "http://127.0.0.1:8000/messages/?to_agent=approval-engine&unread_only=true" \
| python3 -m json.tool
```
Mark a message read:
```bash
curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
-H "Content-Type: application/json" -d '{}'
```
### Log progress (required at session close)
```bash
curl -s -X POST http://127.0.0.1:8000/progress/ \
-H "Content-Type: application/json" \
-d '{
"summary": "what was done",
"event_type": "note",
"author": "codex",
"workplan_id": "<uuid>",
"task_id": "<uuid>"
}'
```
Omit `workplan_id` / `task_id` when not applicable.
### Update task status
```bash
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
-H "Content-Type: application/json" \
-d '{"status": "progress"}'
# values: wait | todo | progress | done | cancel
```
### Flag a task for human review
```bash
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
-H "Content-Type: application/json" \
-d '{"needs_human": true, "intervention_note": "reason"}'
```
---
## Session Protocol
**Start:**
1. `cat .custodian-brief.md` — domain goal and open workplans (offline-safe)
2. Check inbox: `GET /messages/?to_agent=approval-engine&unread_only=true`; mark read
3. Scan workplans: `ls workplans/` — note `status: ready`, `active`, or `blocked` files and open tasks
4. Check human-needed tasks: `GET /tasks/?needs_human=true`
**During work:**
- Update task statuses in workplan files as tasks progress
- Record significant decisions via `POST /decisions/`
**Close:**
1. Update workplan file task statuses to reflect progress
2. If finishing a workplan: hand off **residuals** as live work records first
(intake with `origin: residual` + `origin_ref: <WP-id>`, or a next workplan /
decision / engagement). Do not park leftovers only in prose or `SCOPE.md`.
Canon: `the-custodian/canon/standards/work-record-types_v0.1.md` § Residuals.
3. Log: `POST /progress/` with a summary of what changed (name handoff ids)
4. After workplan file changes, run:
```bash
statehub fix-consistency
```
Coding agents should run this directly; ask the operator only if the CLI or
State Hub API is unavailable. This syncs task status from files into the hub DB.
If C-06/C-11 reports that this host is not the identifier registrar, do not
retry, export `STATEHUB_REGISTRAR`, or register records by hand. Commit and
push the file-backed work first, then run the repo-manager fallback once:
```bash
uv run --project ~/repo-manager rmgr registrar-reconcile \
--path . --confirm-primary --push
```
If unavailable, send one deduplicated registrar request to `repo-manager`
naming the repo and canonical ids; UUID absence does not block local work.
---
## Credential and access routing
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
for inference. Run this check **before** requesting secrets, API keys, SSH access,
login tokens, or database passwords — in any repo, not only `ops-warden`.
The companion (`net-kingdom/SECURITY-COMPANION.md`) says what the rules are;
`ops-warden` stewards the paths through them. **Do not** message `ops-warden`
on State Hub expecting a secret value; the reply is a pointer, not a key.
### Lookup (do this first)
```bash
warden route find "<describe your need>" --json
warden route show <catalog-id> --json
```
Requires the `warden` CLI from `~/ops-warden`.
| Agent runtime | How to orient |
| --- | --- |
| **Codex / Grok** (shell, HTTP State Hub) | `warden route`; inbox `to_agent=approval-engine` is for coordination, not secret vending |
| **Claude Code** (MCP when available) | domain summary for workplans; **still** use `warden route` for credential ownership |
| **llm-connect** | Never put secret retrieval in prompts |
### Quick routing table
| I need… | Owner | ops-warden executes? |
| --- | --- | --- |
| SSH cert (`adm`/`agt`/`atm`) | ops-warden | **Yes**`warden sign` |
| API key, DB password, provider token | OpenBao | No — route only |
| Login / OIDC / MFA | key-cape | No — route only |
| Authorization decision | access-engine (`flex-auth`) | No — route only |
| Approval current-state | **this engine** (not yet implemented) | No |
| SSH tunnel | ops-bridge | No — route only |
### Anti-patterns
- Asking State Hub or `ops-warden` to vend a secret
- Pasting secrets into Git, State Hub, workplans, logs, or chat
- Treating a callable tool as permission (companion §7)
**Canon:** `~/ops-warden/wiki/CredentialRouting.md`
<!-- REPO-AGENTS-EXTENSIONS -->
<!-- Append repo-specific agent instructions below this marker.
The state-hub template sync preserves content after this line. -->
## This repository's layer
Engine, role **PIP**. Statute v0.7, companion v0.2. Declaration: `INTENT.md`
frontmatter and `layer.yaml`.
- Answer "is this approval valid for this exact binding, and has it been used?"
Never "may this actor do X".
- Do not implement consumption until `GH-WP-0002-T06` / `APPROVAL-WP-0001-T05`
settles the contract with `access-engine`.
- The outbox is local. Do not emit synchronously to `audit-core` inside a
mutation transaction.
- Approval evidence is load-bearing. Atomicity covers crash, not compromise.
- An approval is not a Railiance workload.
- Remote hub: this host reaches State Hub on `http://127.0.0.1:8000` (primary
on railiance01). Do not use `127.0.0.1:18000` — that reverse tunnel is being
retired (`CUST-WP-0067`).
---
## Workplan Convention (ADR-001)
Work items originate as files in this repo — not in the hub. The hub is a
read/cache/index layer that rebuilds from files.
**File location:** `workplans/APPROVAL-WP-NNNN-<slug>.md`
**Archived location:** finished workplans may move to
`workplans/archived/YYMMDD-APPROVAL-WP-NNNN-<slug>.md`. The `YYMMDD` prefix is
the completion/archive date; the frontmatter `id` does not change.
**Ad Hoc Tasks:** small opportunistic fixes discovered during a session use
`workplans/ADHOC-YYYY-MM-DD.md`, workplan id
`APPROVAL-WP-ADHOC-YYYY-MM-DD`, and task ids
`APPROVAL-WP-ADHOC-YYYY-MM-DD-T01`, etc. `APPROVAL-WP` includes its final `-WP`
token. Unqualified historic `ADHOC-*` ids are grandfathered and must not be
copied into new records. Use this only for low-risk work completed directly;
create a normal workplan for anything needing analysis, design, approval,
dependencies, or multiple phases.
**Frontmatter:**
```yaml
---
id: APPROVAL-WP-NNNN
type: workplan
title: "..."
domain: infotech
repo: approval-engine
status: proposed | ready | active | blocked | backlog | finished | archived
owner: codex
topic_slug: ...
created: "YYYY-MM-DD"
updated: "YYYY-MM-DD"
state_hub_workstream_id: "<uuid>" # fix-consistency — do not edit (legacy field name; workplan UUID)
---
```
Use `proposed` for a new draft, `ready` after review against current repo
state, and `finished` after implementation. `stalled` and `needs_review` are
derived health labels, not frontmatter statuses.
**Terminology:** workplan is the fleet term; `workstream` appears only in legacy
API/MCP/frontmatter bridges until `STATE-WP-0069` retires them — see
`the-custodian/canon/standards/workplan-terminology-fleet_v0.1.md`.
**Task block format** (one per `##` section):
```
## Task Title
` ` `task
id: APPROVAL-WP-NNNN-T01
status: wait | todo | progress | done | cancel
priority: high | medium | low
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
` ` `
Task description text.
```
Status progression: `todo``progress``done`; use `wait` for waiting/blocked work and `cancel` for stopped work.
**Residuals when finishing:** actionable leftovers become live work records
before `status: finished` — usually an intake (`origin: residual`,
`origin_ref: APPROVAL-WP-NNNN`) or a spawned workplan. Residual is a *role*,
not a kind. Fleet list lives on State Hub, not in `SCOPE.md`.
To create a new workplan:
1. Write the file following the format above
2. Run `statehub fix-consistency` locally.
3. On a non-registrar C-06/C-11 skip, use the repo-manager fallback documented
above exactly once; never set registrar authority directly.

169
INTENT.md
View file

@ -1,8 +1,25 @@
---
layer: Engine
role: PIP
standard: net-kingdom/canon/standards/security-layer-model_v0.7.md
companion: net-kingdom/SECURITY-COMPANION.md
---
# INTENT # INTENT
> **Layer: Engine.** NetKingdom Security Layer Model §4 catalog — a deterministic > **Layer: Engine. Role: PIP.** NetKingdom Security Layer Model §3.3 / §4
> API for a modeled concept, where the same authoritative input state yields the > catalog — a deterministic API for a modeled concept, where the same
> same result. Standard: `net-kingdom/canon/standards/security-layer-model_v0.5.md`. > 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 > 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 > meant to become. It is aspirational and stable, not a description of current
@ -21,7 +38,9 @@ It answers one question, totally and decidably:
Nothing else. It does not decide whether the action is permitted; that is 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 `access-engine`'s and it stays the only decision point. An approval is one
*input* to that decision. *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 ## Why This Exists
@ -41,7 +60,8 @@ 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 in-path, current-state semantics, and an append-only audit fabric is built for
the opposite property. the opposite property.
So it is a concept, and concepts get engines. 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 ## The Concept
@ -63,6 +83,12 @@ requested → approved → valid → consumed
superseded / revoked / expired 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 ## Rigidity Is the Point
This engine is deliberately small, boring, and strict. Its state model is a This engine is deliberately small, boring, and strict. Its state model is a
@ -74,6 +100,12 @@ 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 partly valid, or whose validity must be inferred by replaying a log, is an
approval that can be replayed. Flexibility here is a defect. 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 Progression modelling — graded, evidence-based, open-ended — belongs to
`maturity-engine`. The two engines are deliberate opposites and neither should `maturity-engine`. The two engines are deliberate opposites and neither should
drift toward the other. drift toward the other.
@ -86,11 +118,16 @@ drift toward the other.
- validity windows and expiry; - validity windows and expiry;
- binding to action, target, actor, principal, and purpose; - binding to action, target, actor, principal, and purpose;
- revocation that does not require the holder's cooperation; - revocation that does not require the holder's cooperation;
- an introspection API that `access-engine` consumes as an **input claim**; - 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 - **emission atomicity** — every issuance, use, supersession, and revocation is
emitted to `audit-core` in the **same transaction** as the state change queued in this engine's **local** transactional store in the **same
(transactional outbox or equivalent). See below; this is a contract, not an transaction** as the state change. See below; this is a contract, not an
implementation detail. 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 ## Emission Atomicity
@ -126,18 +163,62 @@ 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 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. 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 ## The Claim Shape
`access-engine` consumes approvals as input claims. The claim MUST carry the `access-engine` consumes approvals as input claims. The claim MUST carry:
approval identifier **and a digest over the same canonical binding the decision
point already computes**, so that *approved* and *approved for this exact - the approval identifier;
request* are distinguishable at decision time. Without it, wrong-action, - a digest over the same canonical binding the decision point already computes,
wrong-target, and wrong-scope proofs have nothing to compare against. 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 ## Consumption Ordering — Unresolved
The decision precedes the action, and the action precedes consumption. Three The decision precedes the action, and the action precedes consumption. Three
states follow that neither engine closes alone: 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 allow rendered against an approval that is then never consumed;
- an approval consumed twice by racing callers; - an approval consumed twice by racing callers;
@ -146,8 +227,9 @@ states follow that neither engine closes alone:
`approval-engine` performs the mutation, because `access-engine` never mutates. `approval-engine` performs the mutation, because `access-engine` never mutates.
But *who signals consumption, at what point relative to the decision, and what 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 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 and is **not yet settled**. It is recorded in the standard's §16, in
before `FLEX-WP-0017` T05. Raised by `flex-auth`. `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. Nothing here may be implemented by guessing that contract.
@ -160,12 +242,18 @@ Nothing here may be implemented by guessing that contract.
versioned policy rules); versioned policy rules);
- **identity and authentication of approvers**`key-cape`, consumed as claims; - **identity and authentication of approvers**`key-cape`, consumed as claims;
- **the evidence record**`audit-core` holds the archive; this engine holds - **the evidence record**`audit-core` holds the archive; this engine holds
only the operative current state. Note the guarantee is bounded: `audit-core` only the operative current state. The guarantee is bounded: `audit-core` is
is not WORM or object-locked, and its tamper evidence is conditional on live not WORM or object-locked, and its tamper evidence is conditional on live
preconditions. Whether approvals warrant stronger custody is an open gap in preconditions. Stronger custody is decided against (§16); this engine must
the standard's §13, and this engine must not assume it has been closed; not assume it has been reopened;
- **credentials** materialized after a decision — `secrets-engine`; - **credentials** materialized after a decision — `secrets-engine`;
- **maturity, readiness, or progression of anything**`maturity-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 The repository's seed README described this as "an engine for modelling and
managing decisions". That wording is withdrawn: **decision** is `access-engine`'s managing decisions". That wording is withdrawn: **decision** is `access-engine`'s
@ -173,7 +261,8 @@ word under standard §8, and this engine must never claim it.
## Design Principles ## Design Principles
1. **Binary at the point of use.** Valid or not, consumed or not. No grades. 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 2. **Atomic or it does not count.** Supersession and consumption are
compare-and-swap operations, never read-then-write. compare-and-swap operations, never read-then-write.
3. **The evaluator is not the owner.** `access-engine` reads; it does not mutate. 3. **The evaluator is not the owner.** `access-engine` reads; it does not mutate.
@ -181,11 +270,16 @@ word under standard §8, and this engine must never claim it.
is one claim the decision point weighs. is one claim the decision point weighs.
5. **Revocation beats cooperation.** An approval can be killed without the 5. **Revocation beats cooperation.** An approval can be killed without the
holder's participation. holder's participation.
6. **Every transition is evidence, atomically.** Issuance, use, supersession, 6. **Every transition is evidence, atomically, and locally.** Issuance, use,
and revocation reach `audit-core` in the same transaction that made them supersession, and revocation are queued in this engine's own store in the
true. An event that can be lost is a state change that never happened, as far same transaction that made them true. An event that can be lost is a state
as anyone auditing can tell. change that never happened, as far as anyone auditing can tell.
7. **Small on purpose.** Scope growth here is a security regression. 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 ## Non-Goals
@ -194,6 +288,7 @@ word under standard §8, and this engine must never claim it.
- Not an approvals *inbox* or notification surface. - Not an approvals *inbox* or notification surface.
- Not an audit archive. - Not an audit archive.
- Not a general state-machine service for other concepts. - 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 ## What Would Make This Repository Wrong
@ -208,6 +303,15 @@ word under standard §8, and this engine must never claim it.
- **Emission is best-effort.** If a revocation can succeed while its event is - **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 lost, the evidence half is silently incomplete and the most security-relevant
transition is the one most likely to vanish. 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 ## Success Criteria
@ -221,6 +325,9 @@ word under standard §8, and this engine must never claim it.
5. no state change can be committed without its event — demonstrated by a test 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 that fails the transaction when emission fails, not by inspection, and with
the queue local so an `audit-core` outage never blocks a revocation; 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 6. the claim carries a binding digest, issuer, and freshness, and a decision
A for request R cannot be replayed for request R'; rendered against approval A for request R cannot be replayed for request R';
7. `FLEX-WP-0017` T03 and T05 are unblocked. 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.

View file

@ -3,8 +3,9 @@
**The approval as a durable, authenticated, consumable object — issued before an **The approval as a durable, authenticated, consumable object — issued before an
action, verified at the moment of use, and provably not replayable.** action, verified at the moment of use, and provably not replayable.**
An Engine in the NetKingdom security layer model. It answers one question, An Engine, role **PIP**, in the NetKingdom security layer model (statute v0.7,
totally and decidably: accepted; operative form `net-kingdom/SECURITY-COMPANION.md`). It answers one
question, totally and decidably:
> Is this approval valid right now — for this exact action, target, actor, and > Is this approval valid right now — for this exact action, target, actor, and
> purpose — and has it already been used? > purpose — and has it already been used?
@ -18,5 +19,6 @@ consumption are what make Canon test `T-06 — Approval Replay` passable.
Flexibility here would be a defect. Graded, evidence-based progression belongs to Flexibility here would be a defect. Graded, evidence-based progression belongs to
`maturity-engine`; the two engines are deliberate opposites. `maturity-engine`; the two engines are deliberate opposites.
See [INTENT.md](INTENT.md). Origin: `flex-auth` `FLEX-DEC-2026-001`, raised while See [INTENT.md](INTENT.md) and [SCOPE.md](SCOPE.md). Declaration: [layer.yaml](layer.yaml).
assenting to the security layer model. Origin: `flex-auth` `FLEX-DEC-2026-001`, raised while assenting to the security
layer model.

151
SCOPE.md Normal file
View file

@ -0,0 +1,151 @@
# 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: **seed**. The repository holds `INTENT.md`, this file, `layer.yaml`,
and a README. There is no store, no API, no outbox, no tests, no runtime.
- Layer declaration exists in this engine's own voice (INTENT frontmatter +
`layer.yaml`). Conformance checks against Tooling contact are vacuously
clean: there is no code to contact anything.
- Consumption ordering remains unresolved estate-wide (`GH-WP-0002-T06`).
- Taxonomy request-claim schema is proposed, not assigned (statute §17).
- Work: `APPROVAL-WP-0001`.
## How It Fits
```text
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. |
## Related / Overlapping
| 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
None. The spine described above is INTENT and first-cut SCOPE, not a shipped
surface. No store, no API, no claim, no outbox.
When the first surface ships, this section gains `capability` blocks for the
introspection API, the mutation API, and the outbox. Until then, declaring
capabilities would advertise a PIP that does not exist.

View file

@ -0,0 +1,185 @@
# Security layer model v0.7 — approval-engine scope vs intent review
**Date:** 2026-08-29
**Statute:** `net-kingdom/canon/standards/security-layer-model_v0.7.md` (accepted)
**Companion:** `net-kingdom/SECURITY-COMPANION.md` v0.2
**Reviewed against:** this repository's v0.5-aligned `INTENT.md` (commits
`0c481de`, `35fee0e`, `ab5d69e`), the empty implementation, and the first-cut
`SCOPE.md` written from the aligned intent
**Outcome:** INTENT aligned to v0.7; SCOPE created as a first-cut boundary;
implementation is seed-only. The work to evolve the repo is
`APPROVAL-WP-0001`.
Hub MCP tools were not exposed in this session. Orientation and the inbox
read used the State Hub HTTP API at `http://127.0.0.1:8000`. The gate-house
broadcast `ACCEPTED: security layer model v0.7 — and start here:
net-kingdom/SECURITY-COMPANION.md` is the trigger.
---
## What v0.5 already had right
The seed INTENT, written against v0.5, already carried the properties that
make this engine an engine:
- the object, not the decision;
- atomic supersession and single consumption;
- local transactional outbox, no synchronous `audit-core` in the mutation
path;
- claim shape with identifier and canonical-binding digest;
- consumption ordering recorded as unresolved rather than guessed;
- the "what would make this repository wrong" list.
Those survive. v0.7 does not reopen them. It types them, bounds them, and
adds obligations the v0.5 text did not know it owed.
## What v0.6/v0.7 changed that this engine must carry
| Change | Where | Effect on this repo |
| --- | --- | --- |
| Engines are typed. This one is **PIP**. | §3.3, §4 | INTENT frontmatter `layer: Engine` / `role: PIP`. A new engine is a PIP unless the statute is amended; it can never be a second PDP. |
| Machine-readable declaration | §11, companion §2 | `layer.yaml` in this engine's own voice. A layer stated *about* this repo is not a declaration. |
| Statute accepted; companion is the operative form | §14, companion | Cite v0.7 as accepted. Disagreement with the companion is a finding, not a workaround. |
| Threat decomposition on §9.6 | §9.6, v0.7 change 5 | Local outbox **prevents accidental omission**. It does **not** prevent a compromised source. Cadence/reconciliation **detects** the adversarial case after the fact. The residual is stated. |
| Cadence is MUST for load-bearing, and rate monitoring is the wrong form | §9.6 | Revocations are rare. Required form: heartbeat or positive reconciliation against `audit-core` (`GH-WP-0002-T04`). |
| Stronger custody decided: no | §13, §16 | v0.5 INTENT treated this as an open gap this engine must not assume closed. v0.7 closes it: same bound as every other source. Archival custody does not address omission. |
| Time in the model | §9.7 | Every allow has a lifetime (the object's validity window). Consumption is a mutation, never an inference from a decision record. Three races named, each needing an owner. |
| Claim schema includes issuer and freshness | §17, §9.7.2 | The v0.5 claim (id + binding digest) is necessary and not sufficient. PDP deadlines are per input class; this PIP must publish freshness. |
| Approval objects are not Railiance workloads | §20.1 | Do not grow a `rail-*` / `rapp-*` mapping. How the axes meet the model is unwritten. |
| Observation and actuation at zero | companion §10, §9.2, §12 | Do not cite "observed in operation". Do not plan on automatic containment. |
| Evidence outage rule is a trade, not a property | §3.3 | `audit-core` MUST NOT block the recorded operation *by default*. Independent-recording-before-effect remains raisable. This engine's local-outbox rule is how availability and atomicity coexist. |
## Scope vs intent
`INTENT.md` is the kind of system this repository is meant to become.
`SCOPE.md` is the first-cut boundary: what the first workplan is allowed to
build, and what it is forbidden to guess.
There is no contradiction of ownership. Every in-scope item is an INTENT
item. The gaps are maturity, missing contracts, and a missing runtime.
### Intent scorecard
| Intent area | SCOPE | Implementation | Material gap |
| --- | --- | --- | --- |
| PIP, never PDP | In scope as a prohibition | No endpoints exist, so no decision surface exists either | Vacuous. The failure mode is the first endpoint that answers "may". Guard with `layer.yaml` `decision_surfaces_exposed: none` and a test once code exists. |
| Durable object + closed state machine | In scope | None | Entire store and machine. First implementation slice. |
| Atomic supersession and single consumption | In scope as CAS | None | Must be in the first mutation API. Read-then-write is a defect, not a stepping stone. |
| Authenticated entries, distinct-approver counting | In scope | None | Depends on `key-cape` claims for identity; this engine counts and binds, it does not authenticate. |
| Validity window / expiry | In scope | None | The object's lifetime is how §9.7.1 lands here. |
| Revocation without holder cooperation | In scope | None | Load-bearing event. Must hit the local outbox in the same transaction. |
| Claim: id + binding digest | In scope | None | Needed for `FLEX-WP-0017` T05. |
| Claim: issuer + freshness | In scope | None | v0.7 addition. Until Taxonomy's request-claim schema exists, publish a local contract and yield later. |
| Local outbox, no sync `audit-core` in the mutation txn | In scope | None | Prose contract in INTENT; wire contract not written (`GH-WP-0002-T02`). |
| Fail-the-transaction-if-emission-fails test | Success criterion; in scope | None | Demonstrable, not inspectable. |
| Cadence / heartbeat / reconciliation | In scope as declaration + design | None | MUST for load-bearing. `GH-WP-0002-T04` is the detection surface; this engine owes the source side. |
| Consumption ordering | **Explicitly out of first-cut** | Must stay unimplemented | Unresolved in §16 / `GH-WP-0002-T06`. Implementing it is the failure INTENT names. |
| Stronger custody | Out of scope (decided no) | n/a | Closed. Do not reopen in this repo. |
| Policy, identity, archive, maturity, inbox | Out of scope | n/a | Unchanged from v0.5. |
| PEP stance / Railiance axes | Out of scope | n/a | v0.7 clarification. |
| Canon `T-06` | Success criterion | Unrunnable | No implementation to point the test at. |
| `FLEX-WP-0017` T03 / T05 | Success criterion | Blocked | T03 waits on the object; T05 waits on T03 and on consumption ordering. |
### Where SCOPE is narrower than INTENT on purpose
1. **Consumption signaling.** INTENT owns the mutation. SCOPE forbids
implementing *when* it fires until the other engine assents. This is not
a missing SCOPE item; it is a named wait.
2. **Shipped capabilities.** INTENT describes a live PIP. SCOPE currently
provides **none**. Advertising a capability block would claim a surface
that does not exist.
3. **Taxonomy claim schema.** INTENT will yield to it. SCOPE publishes a
local contract in the meantime rather than waiting on an unassigned
owner.
### Where SCOPE would be wrong if it grew
- A "may this actor do X" helper for local tests.
- Storing approvals in the State Hub as a substitute object
(`FLEX-WP-0017` T03 already rejected that shape).
- Emitting synchronously to `audit-core` "for now" to skip the outbox.
- Inferring consumption from a decision record, because it is easier than
a mutation.
- A workflow inbox, because operators will ask for one.
- A Railiance mapping, because §20 looks adjacent.
## Implementation necessities
The repository at review time contains documentation and git history. There
is no language runtime, schema, store, or test. Everything in SCOPE is
therefore a necessity, sequenced so that contracts precede code, and so that
the one unowned contract is not filled in from this side.
### P0 — must exist before any mutation API is reachable
1. **Claim contract** — identifier, canonical-binding digest, issuer,
freshness. `access-engine` cannot consume what is not specified.
2. **Local outbox wire contract** — event classes, at-least-once into the
outbox, `audit-core` dedupes on event id. Boundary is in INTENT; the
wire is `GH-WP-0002-T02`.
3. **Keep consumption unimplemented** until `GH-WP-0002-T06`. A reachable
`consume` that guesses the races is worse than no `consume`.
### P0 — first implementation slice, after the contracts
4. **Durable object and closed state machine** with CAS supersession and
CAS consumption *once T06 has settled the latter*.
5. **Authenticated entries and distinct-approver counting.**
6. **Introspection API** that returns the claim and never a decision.
7. **Revocation** without holder cooperation, outbox-atomic, effective at
next use.
8. **Fail-closed on this store's outage; never on `audit-core`'s.** Proven
by a test that fails the transaction when emission fails, and by a test
that revokes while the evidence engine is down.
### P1 — load-bearing evidence residual
9. **Heartbeat or reconciliation** for issuance / use / supersession /
revocation classes. Rate monitoring is not an acceptable substitute.
Source side of `GH-WP-0002-T04`.
10. **Layer conformance test** once code exists: no decision surface, no
undeclared Tooling contact, outbox remains local.
### P2 — yield, do not invent forever
11. Replace the local claim contract with Taxonomy's request-claim schema
when that artifact exists and is assented.
12. Do not cite production observation or automatic containment. Both are
at zero estate-wide.
## Dependencies this repo does not own
| Work | Owner | What we owe |
| --- | --- | --- |
| `GH-WP-0002-T02` outbox wire | `gate-house` | Satisfy the contract; do not author doctrine |
| `GH-WP-0002-T03` revocation failure mode | `gate-house` | Local-outbox already narrows it; ship the engine to the ruling |
| `GH-WP-0002-T04` detection surface | `gate-house` | Heartbeat / counts from this side |
| `GH-WP-0002-T06` consumption ordering | `gate-house` + `access-engine` | Mutation API after assent, not before |
| `FLEX-WP-0017` T03 / T05 | `access-engine` / `flex-auth` | The object and the claim; not a hub substitute |
| `AUDIT-IN-0001` | `audit-core` | Local outbox, no validity query from records |
| Taxonomy request-claim schema | unassigned | Local contract, then yield |
| Canon `T-06` | `whitehat-security` / canon | A live implementation to aim at |
## Conformance snapshot
| §11 state | Now | Notes |
| --- | --- | --- |
| Layer declared in own voice | yes | INTENT frontmatter + `layer.yaml` |
| Claims inside layer permissions | yes, as prose | Reviewable, not compileable, until there is code |
| Tooling contacts | none | Vacuously conforming. Intended non-Tooling clients listed so they cannot appear as silence. |
| Decision surface | none | Required to stay none |
| Blocked-clean items | none of ours | Consumption wait is an unsettled contract, not a missing engine surface |
## Position
approval-engine assents to v0.7 as the statute it already declared under at
v0.5, typed and bounded. The PIP role is correct. The local-outbox rule is
already in the seed INTENT. The new obligations that bite are the heartbeat
for load-bearing classes, issuer and freshness on the claim, the explicit
ban on inferring consumption, and the custody question being closed rather
than held open.
The implementation gap is total. The dangerous gap is not the missing code;
it is the temptation to fill `GH-WP-0002-T06` from this side so that a
demo `consume` exists. SCOPE forbids that. `APPROVAL-WP-0001` is the work
that remains.

76
layer.yaml Normal file
View file

@ -0,0 +1,76 @@
# approval-engine — NetKingdom security layer declaration
#
# Framework: net-kingdom/canon/standards/security-layer-model_v0.7.md
# Companion: net-kingdom/SECURITY-COMPANION.md v0.2
# Voice: INTENT.md (this repository's own, per §11 "who must declare")
#
# Reference form offered by ops-warden and adopted by audit-core and
# kings-guard. Prose cannot distinguish a declaration from a transcribed
# review; this file is the mechanical half.
schema_version: "0.1"
framework: netkingdom-security-layer-model
standard_version: "0.7"
companion_version: "0.2"
repository: approval-engine
layer: engine
role: pip # §3.3 engine typing; §4 catalog
declared_by: INTENT.md
declared_at: "2026-08-29"
# §4 catalog entry, transcribed so drift between the catalog and this file
# is visible. The statute is authoritative for the row.
catalog_entry:
owns:
- the approval object — durable, authenticated, consumable, atomically supersedable
statute: "§9.4"
# §3.3: a PIP supplies facts a decision consumes as claims. Outage is input
# degradation, which is this engine's fallback to own (§9.3).
# §6: no repository other than access-engine exposes an authorization decision.
decision_surfaces_exposed: none
# §9.4 — callers needing current state ask this engine. audit-core must not
# expose an approval-validity query; this engine must not expose a decision.
approval_validity_query: owned # current-state introspection, not a verdict on "may"
# §5 applies to Staff. This is an Engine. Its future transactional store is
# its own operational store, not a §4 Tooling row (same reasoning as
# audit-core's PostgreSQL custody).
tooling_contacts: []
# §11: record non-Tooling clients so the check is total. None exist: there
# is no runtime. Listed targets are the intended Engine APIs and the
# uncatalogued hub write, to be filled in as code appears rather than
# discovered later as silence.
non_tooling_clients: []
intended_non_tooling_clients:
- target: audit-core
layer: engine
rationale: "Evidence destination for the local outbox drain. Engine API."
- target: access-engine
layer: engine
rationale: "Consumer of claims. This engine does not call it to decide."
- target: state-hub
layer: not-catalogued
rationale: >-
Progress events. Outside §5 by the v0.5 scope rule. Recorded, not
policed, and must not become a state plane another layer reads for
approval current-state.
# §9.6 — approval evidence is load-bearing. Atomicity prevents accidental
# omission; it does not prevent a compromised source. Cadence for
# low-volume load-bearing classes is reconciliation or a heartbeat.
evidence:
kind: load-bearing
atomicity: local-outbox # required; not yet implemented
cadence_form: heartbeat-or-reconciliation
cadence_status: undeclared # APPROVAL-WP-0001-T04
residual: adversarial-omission-at-compromised-source
custody: same-bound-as-every-other-source # §16 decided: no stronger archive
declared_shapes:
"5.1": []
"5.2": []
"5.3": []

View file

@ -0,0 +1,202 @@
---
id: APPROVAL-WP-0001
type: workplan
title: "v0.7 alignment and the engine spine"
domain: infotech
repo: approval-engine
status: active
owner: grok
topic_slug: netkingdom
created: "2026-08-29"
updated: "2026-08-29"
---
# APPROVAL-WP-0001 — v0.7 alignment and the engine spine
Evolve this repository from a v0.5 seed INTENT into a v0.7-aligned PIP with
the contracts and first implementation that make Canon `T-06` passable and
unblock `FLEX-WP-0017` T03/T05.
Source:
- `net-kingdom/canon/standards/security-layer-model_v0.7.md` (accepted)
- `net-kingdom/SECURITY-COMPANION.md` v0.2
- `history/2026-08-29-security-layer-model-v0.7-scope-intent-review.md`
- `INTENT.md`, `SCOPE.md`, `layer.yaml`
**Rule of the workplan.** Contracts before code. Consumption is not
implemented until `GH-WP-0002-T06` settles it. A reachable mutation that
guesses the three races is a defect, not a prototype.
Peer work this does not own and must not duplicate: `GH-WP-0002` (T02 outbox
wire, T03 revocation failure mode, T04 detection surface, T06 consumption
ordering), `FLEX-WP-0017` T03/T05, `AUDIT-IN-0001`.
## T01 — Align declaration to the accepted statute
```task
id: APPROVAL-WP-0001-T01
status: done
priority: high
```
2026-08-29: INTENT frontmatter `layer: Engine` / `role: PIP`; aligned to
v0.7 (PIP typing, threat decomposition, load-bearing cadence, custody
closed, consumption as mutation, issuer/freshness, Railiance non-axis).
SCOPE.md written as first-cut boundary with standard sections. `layer.yaml`
declared in this repository's own voice. Review filed under `history/`.
Registered with State Hub (`infotech`, topic custodian). Register's
bootstrap workplan was discarded: it collided on `APPROVAL-WP-0001` and
its three tasks are already discharged by this file, `AGENTS.md`, and
`SCOPE.md`.
## T02 — Publish the approval claim contract
```task
id: APPROVAL-WP-0001-T02
status: todo
priority: high
```
Specify the input claim `access-engine` consumes: approval identifier,
canonical-binding digest over the same binding the PDP already computes,
issuer (this engine), and freshness. Schema and examples in-repo.
Until Taxonomy's request-claim schema exists (statute §17, ownership
proposed not assigned), this is the boundary contract. Mark it as yielding
to Taxonomy; do not treat a local shape as permanent.
Acceptance: a document `access-engine` can implement a consumer against
without reading this engine's source; wrong-action / wrong-target /
wrong-scope are distinguishable; a PDP can state a deadline for this input
class.
## T03 — Specify the local transactional-outbox contract
```task
id: APPROVAL-WP-0001-T03
status: todo
priority: high
```
Event classes (issuance, use, supersession, revocation, and the heartbeat
class T04 needs), same transaction as the object mutation, queue local to
this engine, at-least-once into the outbox (`audit-core` dedupes on event
id). No synchronous `audit-core` call inside the state-change transaction.
Coordinate with `GH-WP-0002-T02`; do not author doctrine this engine does
not own. INTENT already states the boundary and the locality.
Acceptance: a wire contract another repository can review; an implementer
cannot satisfy it by emitting synchronously to `audit-core`.
## T04 — Declare load-bearing cadence as heartbeat or reconciliation
```task
id: APPROVAL-WP-0001-T04
status: todo
priority: high
```
Approval evidence is load-bearing and low-volume. Rate monitoring is the
wrong form (statute §9.6). Publish the source-side declaration: expected
classes, the heartbeat (*nothing to report*, signed, itself able to go
missing) and/or reconciliation of this engine's transitions against
`audit-core` event counts per class.
Coordinate with `GH-WP-0002-T04`, which is the detection surface. This
task is the source that surface reads.
Acceptance: `layer.yaml` `evidence.cadence_status` is no longer
`undeclared`; a missing heartbeat or a count divergence is specified as a
finding, not as a log line.
## T05 — Wait on consumption ordering; do not implement it
```task
id: APPROVAL-WP-0001-T05
status: wait
priority: high
```
Blocked on `GH-WP-0002-T06` / `access-engine`. The three races (allow never
consumed; double consumption; consume after failed action) are named in
statute §9.7.4 and §16. This engine performs the mutation; it does not
choose the signal, the point relative to the decision, or the handling of
the three states.
Acceptance: a written contract both engines have assented to, recorded
here, *then* a consume path. Until then, no `consume` endpoint, no inferred
consumption from a decision record, no demo that "just consumes on allow".
## T06 — Durable object, closed state machine, authenticated entries
```task
id: APPROVAL-WP-0001-T06
status: todo
priority: high
```
Depends on T02 and T03. Implement the object and the machine in SCOPE:
identifiers; bindings (action, target, actor, principal, purpose, validity
window, approvers); authenticated entries; distinct-approver counting;
CAS supersession; validity/expiry; revocation without holder cooperation.
Do not ship a public `consume` until T05 is unblocked. Internal CAS
consumption may exist behind an unexported seam so T08 can be tested
without guessing the ordering contract.
Acceptance: state transitions are total and reconstructable; two concurrent
supersessions cannot both succeed; distinct-approver requirements fail
closed on duplicates; revocation is effective at the next introspection.
## T07 — Introspection API as input claims
```task
id: APPROVAL-WP-0001-T07
status: todo
priority: high
```
Depends on T02 and T06. An API that answers INTENT's question and returns
the T02 claim. No endpoint answers "may this actor do X". Fail-closed when
this engine's own store is unavailable.
Acceptance: `access-engine` can fetch a claim; a decision rendered against
approval A for request R cannot be replayed for R' on the digest; tests
forbid a decision-shaped response.
## T08 — Local outbox in the mutation transaction
```task
id: APPROVAL-WP-0001-T08
status: todo
priority: high
```
Depends on T03 and T06. Every issuance, use, supersession, and revocation
inserts the outbox row in the same transaction. Drain is asynchronous.
`audit-core` outage does not block a revocation.
Acceptance: a test that fails the transaction when emission fails, not an
inspection of the code; a test that revokes while the evidence engine is
unreachable; no path where the state change commits and the outbox row
does not.
## T09 — Canon T-06 and the FLEX-WP-0017 handoff
```task
id: APPROVAL-WP-0001-T09
status: todo
priority: medium
```
Depends on T07, T08, and T05 (the last only for the consume-side replay
cases). Point Canon `T-06 — Approval Replay` at a live implementation.
Hand `access-engine` / `secrets-engine` a claim they can validate before
privileged production actions (`FLEX-WP-0017` T03/T05).
Acceptance: `T-06` passes; T03 is unblocked on the object (not on a hub
substitute); T05 remains blocked only on consumption ordering if T05 of
this workplan is still `wait`, never on a missing object or a missing
digest.