diff --git a/.custodian-brief.md b/.custodian-brief.md deleted file mode 100644 index d1a5327..0000000 --- a/.custodian-brief.md +++ /dev/null @@ -1,34 +0,0 @@ - -# 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 deleted file mode 100644 index e4e0199..0000000 --- a/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# 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 deleted file mode 100644 index a0d151d..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,272 +0,0 @@ -# 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 deleted file mode 100644 index e29451e..0000000 --- a/INTENT.md +++ /dev/null @@ -1,333 +0,0 @@ ---- -layer: Engine -role: PIP -standard: net-kingdom/canon/standards/security-layer-model_v0.7.md -companion: net-kingdom/SECURITY-COMPANION.md ---- - -# INTENT - -> **Layer: Engine. Role: PIP.** NetKingdom Security Layer Model §3.3 / §4 -> catalog — a deterministic API for a modeled concept, where the same -> authoritative input state yields the same result. This engine **supplies -> facts a decision consumes as claims**; it never renders one. A new engine is -> a PIP unless the statute is amended, and §6 means it can never be a second -> PDP. -> -> Statute (accepted 2026-08-29): -> `net-kingdom/canon/standards/security-layer-model_v0.7.md`. -> Operative form: `net-kingdom/SECURITY-COMPANION.md` v0.2. The statute governs -> on disagreement; a disagreement is a finding for `gate-house`. -> -> Machine-readable declaration: `layer.yaml`. -> -> This file captures why this repository exists and the kind of system it is -> meant to become. It is aspirational and stable, not a description of current -> implementation. - -## Purpose - -> **approval-engine owns the approval as a durable, authenticated, consumable -> object — issued before an action, verified at the moment of use, and provably -> not replayable.** - -It answers one question, totally and decidably: - -> **Is this approval valid right now — for this exact action, target, actor, and -> purpose — and has it already been used?** - -Nothing else. It does not decide whether the action is permitted; that is -`access-engine`'s and it stays the only decision point. An approval is one -*input* to that decision. An outage here is **input degradation**, which is this -engine's fallback to own (§9.3) — never a consumer-invented substitute for the -object. - -## Why This Exists - -The gap was raised by `flex-auth` on 2026-08-28 (`FLEX-DEC-2026-001`) while -assenting to the security layer model. Splitting policy authoring from policy -evaluation left one half of the approval contract without an owner: - -- the **design** of the approval contract is `gate-house`'s (Staff — doctrine); -- the **validation** of an approval at decision time is `access-engine`'s; -- the **durable object, authenticated entries, and atomic supersession** were - nobody's. - -It is not `gate-house`'s: Staff holds no state another layer depends on at -runtime (standard §3.4). It is not `access-engine`'s: it does not own the -organizational approval lifecycle, and an evaluator that also owns the object it -evaluates is self-dealing. It is not `audit-core`'s: an approval needs mutable, -in-path, current-state semantics, and an append-only audit fabric is built for -the opposite property. - -So it is a concept, and concepts get engines. The catalog types it **PIP** -because "we need an engine for X" must not drift into "X now decides". - -## The Concept - -An **approval** is an independent authorization artifact required for actions -that exceed normal policy. It binds, at issue time, to: - -- the **action** and the **target** it authorizes; -- the **actor** who may use it, and the **principal** on whose behalf; -- the **purpose** or task it was granted for; -- a **validity window**; -- the **approvers** — identified, authenticated, and countable, so that - distinct-approver requirements are enforceable rather than aspirational. - -And it carries state that is deliberately not a maturity ladder: - -```text -requested → approved → valid → consumed - ↓ - superseded / revoked / expired -``` - -An approval object is **not a Railiance workload** and never becomes one -(standard §20.1). Human commands, broker actions, and approvals that are not -themselves deployables sit outside the `railiance-*` / `rail-*` / `rapp-*` / -`reef-*` axes. This engine does not acquire an operational axis by being -consumed from one. - -## Rigidity Is the Point - -This engine is deliberately small, boring, and strict. Its state model is a -closed machine rather than a graded progression, because at the moment of use -the answer must be binary, total, and reconstructable. - -**Atomic supersession** and **single consumption** are the security properties -that make Canon test `T-06 — Approval Replay` passable. An approval that is -partly valid, or whose validity must be inferred by replaying a log, is an -approval that can be replayed. Flexibility here is a defect. - -**Consumption is a state change, never an inference** (standard §9.7.3). An -approval is consumed by a mutation in this engine. It MUST NOT be inferred from -the existence of a decision record — the decision precedes the action and the -action precedes consumption, so a decision record proves an intent to act, not -an act. - -Progression modelling — graded, evidence-based, open-ended — belongs to -`maturity-engine`. The two engines are deliberate opposites and neither should -drift toward the other. - -## What approval-engine Owns - -- the durable approval object and its identifiers; -- authenticated approval entries, and distinct-approver counting; -- the state machine above, with **atomic** supersession and consumption; -- validity windows and expiry; -- binding to action, target, actor, principal, and purpose; -- revocation that does not require the holder's cooperation; -- an introspection API that `access-engine` consumes as an **input claim**, - carrying issuer and freshness so the PDP can state a deadline for this input - class (§9.7.2); -- **emission atomicity** — every issuance, use, supersession, and revocation is - queued in this engine's **local** transactional store in the **same - transaction** as the state change. See below; this is a contract, not an - implementation detail; -- **emission cadence** for load-bearing classes, in the form that actually - works for rare events: positive reconciliation or a heartbeat, not rate - monitoring. - -## Emission Atomicity - -An approval MUST NOT be issued, consumed, superseded, or revoked without the -corresponding event being durably queued in the same transaction that changed -the state. Transactional outbox, or an equivalent that cannot separate the two. - -**The queue is local.** The outbox lives in this engine's own transactional -store. No synchronous dependency on `audit-core` sits inside the state-change -transaction. With a local outbox, fail-closed triggers only when this engine's -own store is unavailable — where the change could not have been recorded anyway -— and an `audit-core` outage does not block a revocation. Emitting synchronously -to `audit-core` inside the transaction would also be atomic, and would turn an -audit outage into an inability to revoke: the operation least tolerable to block -during an incident. Standard §9.4, raised by `audit-core`. - -This is a condition of `audit-core`'s assent (`AUDIT-IN-0001`) and is in the -standard at §9.4. The reasoning is worth carrying here rather than by reference, -because it constrains the implementation from the first commit: - -An append-only archive with a verified hash chain proves that records were not -**altered or truncated after arrival**. It cannot prove that a record was never -sent (standard §9.6). A suppressed event leaves the chain perfectly intact and -verification reports intact. `audit-core` reports what it received and will not -imply it is everything that happened — correctly, because it cannot. - -For an approval object this is not a corner case. **The single most valuable -event for an adversary to suppress is a revocation**, and its absence is exactly -what chain verification cannot see. Completeness is therefore this engine's -obligation and nothing downstream can retrofit it. - -Consequence for design: emit-after-commit is a defect, not an optimization. A -best-effort publish, a fire-and-forget queue, or any path where the state change -can succeed while the event is lost is out of contract. - -## Which threat atomicity closes — and which it does not - -v0.6 read as though a local outbox closed §9.6's opening sentence. It does not. -The decomposition is now in the statute and is this engine's to carry, because -approval evidence is **load-bearing**: - -| Threat | Covered by | When | -| --- | --- | --- | -| Accidental omission — crash between mutation and emit | local outbox (§9.4) | **prevented** | -| Adversarial omission — a compromised source declines to insert, deletes before drain, or drains to nowhere | cadence and reconciliation | **detected, after the fact** | -| Adversarial omission at a compromised source | — | **nothing in the model prevents it** | - -The outbox sits inside this engine's blast radius. It makes emission atomic -against crash and partial failure, and nothing more. That residual is real. - -Approval events are **low-volume load-bearing**. Rate monitoring cannot work: -a handful of revocations a month has no rate to drop below, and suppression is -indistinguishable from a quiet month. The required form is **positive -reconciliation or a heartbeat** — compare this engine's object-state transitions -against `audit-core`'s event count per class, or assert *nothing to report* as -a signed positive claim that can itself go missing. `GH-WP-0002-T04` is the -reference instance. A source of load-bearing evidence **MUST** declare this -(standard §9.6). - -**Custody is not the residual's answer.** Whether approvals warrant archival -custody stronger than every other source is **decided: no** (standard §16). -Approval evidence carries the same bound as every other source. Archival -custody does not address omission at all. If a future requirement genuinely -needs WORM or a transparency log, that is a different store with a different -owner, raised then. This engine must not plan around a stronger archive. - -## The Claim Shape - -`access-engine` consumes approvals as input claims. The claim MUST carry: - -- the approval identifier; -- a digest over the same canonical binding the decision point already computes, - so that *approved* and *approved for this exact request* are distinguishable - at decision time; -- **issuer** (this engine) and a **freshness** rule, so the PDP can state a - deadline for this input class rather than a single fiction covering every - source (§9.7.2, §17 request-claim schema). - -Without the digest, wrong-action, wrong-target, and wrong-scope proofs have -nothing to compare against. Without issuer and freshness, a stated deadline -for the approval input class is unfalsifiable. - -Until Taxonomy publishes the shared request-claim schema (§17; ownership -proposed, not assigned), this engine publishes the contract at its own -boundary and yields to the schema when it exists. Inventing a permanent local -shape is the drift §17 exists to prevent. - -## Consumption Ordering — Unresolved - -The decision precedes the action, and the action precedes consumption. Three -failure modes are named, and each needs an owner (standard §9.7.4, §16): - -- an allow rendered against an approval that is then never consumed; -- an approval consumed twice by racing callers; -- an approval consumed after the action it authorized has already failed. - -`approval-engine` performs the mutation, because `access-engine` never mutates. -But *who signals consumption, at what point relative to the decision, and what -happens on each of the three states above* is a contract between the two engines -and is **not yet settled**. It is recorded in the standard's §16, in -`GH-WP-0002-T06`, and is required before `FLEX-WP-0017` T05. Raised by -`flex-auth`. - -Nothing here may be implemented by guessing that contract. - -## What approval-engine Does Not Own - -- **the decision** — `access-engine`, always and only (standard §6); -- **approval policy** — which actions require approval, how many approvers, and - which separations of duty apply: that is doctrine, and doctrine is - `gate-house`'s (standard §6.2, reaching decisions as input claims or - versioned policy rules); -- **identity and authentication of approvers** — `key-cape`, consumed as claims; -- **the evidence record** — `audit-core` holds the archive; this engine holds - only the operative current state. The guarantee is bounded: `audit-core` is - not WORM or object-locked, and its tamper evidence is conditional on live - preconditions. Stronger custody is decided against (§16); this engine must - not assume it has been reopened; -- **credentials** materialized after a decision — `secrets-engine`; -- **maturity, readiness, or progression of anything** — `maturity-engine`; -- **an unreachable-engine stance map** — this engine is not PEP-shaped. It - does not cause a protected side effect on the far side of a decision. Callers - that do are PEP-shaped and owe companion §5 / statute §6.4; -- **Railiance operational axes** — an approval is not a workload (§20.1). How - those axes meet this model beyond §20's first statement is unwritten; do not - assume a mapping. - -The repository's seed README described this as "an engine for modelling and -managing decisions". That wording is withdrawn: **decision** is `access-engine`'s -word under standard §8, and this engine must never claim it. - -## Design Principles - -1. **PIP, never PDP.** Valid or not, consumed or not. No grades, and no - "may this actor do X". -2. **Atomic or it does not count.** Supersession and consumption are - compare-and-swap operations, never read-then-write. -3. **The evaluator is not the owner.** `access-engine` reads; it does not mutate. -4. **Approvals are inputs, never authority.** Holding one is not permission; it - is one claim the decision point weighs. -5. **Revocation beats cooperation.** An approval can be killed without the - holder's participation. -6. **Every transition is evidence, atomically, and locally.** Issuance, use, - supersession, and revocation are queued in this engine's own store in the - same transaction that made them true. An event that can be lost is a state - change that never happened, as far as anyone auditing can tell. -7. **Atomicity prevents accidents; it does not prevent a compromised source.** - Cadence for load-bearing classes is a heartbeat or a reconciliation, and it - is a MUST. -8. **Consumption is a mutation.** Inferring it from a decision record is a - defect. -9. **Small on purpose.** Scope growth here is a security regression. - -## Non-Goals - -- Not a policy decision point, and never one. -- Not a workflow or ticketing system; it stores the artifact, not the meeting. -- Not an approvals *inbox* or notification surface. -- Not an audit archive. -- Not a general state-machine service for other concepts. -- Not a Railiance axis, a PEP, or an actuation surface. - -## What Would Make This Repository Wrong - -- **It starts deciding.** Any endpoint that answers "may this actor do X" rather - than "is this approval valid" is the failure standard §6 names. -- **Supersession is not atomic.** If two supersessions can interleave, the - replay protection is theatre. -- **It grows a policy layer.** If it starts holding rules about who may approve - what, it has taken doctrine from `gate-house`. -- **Nobody consumes it.** If `access-engine` does not read approvals as input - claims, the object is decorative. -- **Emission is best-effort.** If a revocation can succeed while its event is - lost, the evidence half is silently incomplete and the most security-relevant - transition is the one most likely to vanish. -- **It claims the outbox closes omission.** Atomicity covers crash. Treating it - as covering a compromised source, or skipping the heartbeat, is the v0.6 - overclaim this engine must not reintroduce. -- **It implements consumption by guessing.** The three races are named and - unowned. Code that picks a side is a contract with `access-engine` that - `access-engine` has not assented to. -- **It cites observation or containment that has not happened.** Nothing is - observed in production, and nothing can be contained automatically - (companion §10). - -## Success Criteria - -1. `access-engine` renders approval-bound decisions using this engine's claims, - with the approval identifier reconstructable from the decision record; -2. Canon test `T-06 — Approval Replay` passes against a live implementation; -3. an approval can be revoked without the holder's cooperation, and the - revocation is effective at the next use; -4. distinct-approver requirements are enforced by the engine rather than by - convention; -5. no state change can be committed without its event — demonstrated by a test - that fails the transaction when emission fails, not by inspection, and with - the queue local so an `audit-core` outage never blocks a revocation; -6. the claim carries a binding digest, issuer, and freshness, and a decision - rendered against approval A for request R cannot be replayed for request R'; -7. load-bearing classes declare a heartbeat or reconciliation, and divergence - from `audit-core`'s event count is a finding; -8. consumption is not implemented until `GH-WP-0002-T06` settles the contract; -9. `FLEX-WP-0017` T03 and T05 are unblocked. diff --git a/README.md b/README.md index 7793312..811f4d2 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,3 @@ # approval-engine -**The approval as a durable, authenticated, consumable object — issued before an -action, verified at the moment of use, and provably not replayable.** - -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? - -It does **not** decide whether the action is permitted. That is `access-engine`, -which stays NetKingdom's only policy decision point. An approval is one input to -that decision. - -Deliberately small, boring, and strict: atomic supersession and single -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) 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. +An engine for modelling and managing decisions. \ No newline at end of file diff --git a/SCOPE.md b/SCOPE.md deleted file mode 100644 index 03c7765..0000000 --- a/SCOPE.md +++ /dev/null @@ -1,151 +0,0 @@ -# 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 deleted file mode 100644 index 4a03db4..0000000 --- a/history/2026-08-29-security-layer-model-v0.7-scope-intent-review.md +++ /dev/null @@ -1,185 +0,0 @@ -# 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 deleted file mode 100644 index a52e1a1..0000000 --- a/layer.yaml +++ /dev/null @@ -1,76 +0,0 @@ -# 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 deleted file mode 100644 index 3855b99..0000000 --- a/workplans/APPROVAL-WP-0001-v07-alignment-and-engine-spine.md +++ /dev/null @@ -1,202 +0,0 @@ ---- -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.