From 2a5a47df89738f63cea8c3bbcb9405071a6ac1cb Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 29 Aug 2026 11:58:28 +0200 Subject: [PATCH] Align to security layer model v0.7 and open the engine spine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .custodian-brief.md | 34 +++ .gitignore | 5 + AGENTS.md | 272 ++++++++++++++++++ INTENT.md | 169 +++++++++-- README.md | 10 +- SCOPE.md | 151 ++++++++++ ...ty-layer-model-v0.7-scope-intent-review.md | 185 ++++++++++++ layer.yaml | 76 +++++ ...-WP-0001-v07-alignment-and-engine-spine.md | 202 +++++++++++++ 9 files changed, 1069 insertions(+), 35 deletions(-) create mode 100644 .custodian-brief.md create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 SCOPE.md create mode 100644 history/2026-08-29-security-layer-model-v0.7-scope-intent-review.md create mode 100644 layer.yaml create mode 100644 workplans/APPROVAL-WP-0001-v07-alignment-and-engine-spine.md diff --git a/.custodian-brief.md b/.custodian-brief.md new file mode 100644 index 0000000..d1a5327 --- /dev/null +++ b/.custodian-brief.md @@ -0,0 +1,34 @@ + +# 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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e4e0199 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..a0d151d --- /dev/null +++ b/AGENTS.md @@ -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//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": "", + "task_id": "" + }' +``` + +Omit `workplan_id` / `task_id` when not applicable. + +### Update task status + +```bash +curl -s -X PATCH "http://127.0.0.1:8000/tasks/" \ + -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/" \ + -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: `, 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 "" --json +warden route show --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` + + + + +## 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-.md` + +**Archived location:** finished workplans may move to +`workplans/archived/YYMMDD-APPROVAL-WP-NNNN-.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: "" # 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: "" # 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. diff --git a/INTENT.md b/INTENT.md index 39f8a80..e29451e 100644 --- a/INTENT.md +++ b/INTENT.md @@ -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 -> **Layer: Engine.** NetKingdom Security Layer Model §4 catalog — a deterministic -> API for a modeled concept, where the same authoritative input state yields the -> same result. Standard: `net-kingdom/canon/standards/security-layer-model_v0.5.md`. +> **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 @@ -21,7 +38,9 @@ It answers one question, totally and decidably: 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. +*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 @@ -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 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 @@ -63,6 +83,12 @@ 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 @@ -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 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. @@ -86,11 +118,16 @@ drift toward the other. - 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**; +- 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 - emitted to `audit-core` in the **same transaction** as the state change - (transactional outbox or equivalent). See below; this is a contract, not an - implementation detail. + 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 @@ -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 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 **and a digest over the same canonical binding the decision -point already computes**, so that *approved* and *approved for this exact -request* are distinguishable at decision time. Without it, wrong-action, -wrong-target, and wrong-scope proofs have nothing to compare against. +`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 -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 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. But *who signals consumption, at what point relative to the decision, and what happens on each of the three states above* is a contract between the two engines -and is **not yet settled**. It is recorded in the standard's §16 and is required -before `FLEX-WP-0017` T05. Raised by `flex-auth`. +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. @@ -160,12 +242,18 @@ Nothing here may be implemented by guessing that contract. 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. Note the guarantee is bounded: `audit-core` - is not WORM or object-locked, and its tamper evidence is conditional on live - preconditions. Whether approvals warrant stronger custody is an open gap in - the standard's §13, and this engine must not assume it has been closed; + 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`. +- **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 @@ -173,7 +261,8 @@ word under standard §8, and this engine must never claim it. ## 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 compare-and-swap operations, never read-then-write. 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. 5. **Revocation beats cooperation.** An approval can be killed without the holder's participation. -6. **Every transition is evidence, atomically.** Issuance, use, supersession, - and revocation reach `audit-core` 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. **Small on purpose.** Scope growth here is a security regression. +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 @@ -194,6 +288,7 @@ word under standard §8, and this engine must never claim it. - 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 @@ -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 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 @@ -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 that fails the transaction when emission fails, not by inspection, and with the queue local so an `audit-core` outage never blocks a revocation; -6. the claim carries a binding digest, and a decision rendered against approval - A for request R cannot be replayed for request R'; -7. `FLEX-WP-0017` T03 and T05 are unblocked. +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. diff --git a/README.md b/README.md index 9a2d501..7793312 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,9 @@ **The approval as a durable, authenticated, consumable object — issued before an action, verified at the moment of use, and provably not replayable.** -An Engine in the NetKingdom security layer model. It answers one question, -totally and decidably: +An Engine, role **PIP**, in the NetKingdom security layer model (statute v0.7, +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 > 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 `maturity-engine`; the two engines are deliberate opposites. -See [INTENT.md](INTENT.md). Origin: `flex-auth` `FLEX-DEC-2026-001`, raised while -assenting to the security layer model. +See [INTENT.md](INTENT.md) and [SCOPE.md](SCOPE.md). Declaration: [layer.yaml](layer.yaml). +Origin: `flex-auth` `FLEX-DEC-2026-001`, raised while assenting to the security +layer model. diff --git a/SCOPE.md b/SCOPE.md new file mode 100644 index 0000000..03c7765 --- /dev/null +++ b/SCOPE.md @@ -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. diff --git a/history/2026-08-29-security-layer-model-v0.7-scope-intent-review.md b/history/2026-08-29-security-layer-model-v0.7-scope-intent-review.md new file mode 100644 index 0000000..4a03db4 --- /dev/null +++ b/history/2026-08-29-security-layer-model-v0.7-scope-intent-review.md @@ -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. diff --git a/layer.yaml b/layer.yaml new file mode 100644 index 0000000..a52e1a1 --- /dev/null +++ b/layer.yaml @@ -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": [] diff --git a/workplans/APPROVAL-WP-0001-v07-alignment-and-engine-spine.md b/workplans/APPROVAL-WP-0001-v07-alignment-and-engine-spine.md new file mode 100644 index 0000000..3855b99 --- /dev/null +++ b/workplans/APPROVAL-WP-0001-v07-alignment-and-engine-spine.md @@ -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.