diff --git a/decisions/decisions.md b/decisions/decisions.md index 94e420c..fe615a1 100644 --- a/decisions/decisions.md +++ b/decisions/decisions.md @@ -160,188 +160,3 @@ The falsifiers that should trigger reconsideration are in `gate-house/INTENT.md` § "What Would Make This Repository Wrong" — principally gate-house becoming a paper generator whose conformance loop never turns, or the estate declining to adopt the authority vocabulary. - -## GH-DEC-2026-002 — Revocation fails closed only when approval-engine's own store is down - -```yaml -id: GH-DEC-2026-002 -kind: decision -title: Revocation fails closed only when approval-engine's own store is down -status: resolved -owner: Bernd Worsch -repo: gate-house -standard: net-kingdom/canon/standards/security-layer-model_v0.7.md -source_note: history/2026-08-29-approval-evidence-integrity-contracts.md -requested_dispositions: -- approved -- revised -- rejected -affects: -- gate-house -- approval-engine -- audit-core -- flex-auth -created: '2026-08-29T12:50:04.747439Z' -updated: '2026-08-29T12:50:58.873684Z' -rationale: 'Approved as GH-WP-0002-T03. Local-outbox fail-closed is the only remaining - closed path: if approval-engine cannot insert the outbox row, the revocation does - not commit. An audit-core outage must not block a revocation. Synchronous emission - inside the mutation is forbidden even though it is atomic. Proceed-with-gap is rejected - for load-bearing approval evidence.' -decided_by: Bernd Worsch -decided_at: '2026-08-29T12:50:58.873684Z' -``` - -## Context - -`GH-IN-0001` (from `audit-core`) required that the revocation failure mode be a -recorded decision rather than an implementation accident. Both sides are -defensible on their own: fail-closed makes an audit dependency into an -availability risk on the revocation path; proceed-with-gap needs a detectable -marker so the gap is visible rather than silent. - -v0.5's local-outbox rule already narrowed this. With the queue in -`approval-engine`'s own store, fail-closed triggers only when that store is -down — where the change could not have been recorded anyway — and an -`audit-core` outage does not block a revocation. Satisfying atomicity by -emitting synchronously to `audit-core` inside the mutation is also atomic, and -turns an audit outage into an inability to revoke: the operation least -tolerable to block during an incident. - -The statute states the locality rule at §9.4. This record is the decision the -workplan still owed, so an implementer cannot pick the other side by accident. - -Wire: `docs/contracts/approval-outbox.md`. - -## Decision - -**If the local outbox insert cannot commit, the revocation does not commit.** -The same rule applies to issuance, use, and supersession. - -**An `audit-core` outage MUST NOT block a revocation.** Drain is asynchronous -and at-least-once. `audit-core` dedupes on `event_id`. - -**Synchronous emission to `audit-core` inside the state-change transaction is -forbidden**, even though it is atomic. - -Proceed-with-gap is rejected for load-bearing approval evidence. A revocation -that succeeds while its event is lost is the exact case `AUDIT-IN-0001` -conditioned assent on. - -## What this authorizes - -- `approval-engine` failing a revoke (and any other mutation) when its own - store cannot insert the outbox row. -- Continuing to revoke while `audit-core` is down, with the row draining later. -- Treating emit-after-commit, a second non-local queue, or a sync `audit-core` - call inside `BEGIN`…`COMMIT` as out of contract. - -## What this does not authorize - -- Claiming the outbox closes adversarial omission. Atomicity prevents crash; - cadence and reconciliation detect suppression after the fact (§9.6). -- A validity query on `audit-core`. -- Any change to another repository's workplans. - -## Reversal - -Revert this record and the outbox contract. The falsifier is operational: if -failing closed on the local store removes the ability to revoke during the -incidents this engine exists for, the trade needs revisiting — but the -alternative is a silent evidence gap on the event an attacker most wants -missing, which is worse. - -## GH-DEC-2026-003 — The PEP consumes an approval before the protected side effect - -```yaml -id: GH-DEC-2026-003 -kind: decision -title: The PEP consumes an approval before the protected side effect -status: resolved -owner: Bernd Worsch -repo: gate-house -standard: net-kingdom/canon/standards/security-layer-model_v0.7.md -source_note: docs/contracts/approval-consumption.md -requested_dispositions: -- approved -- revised -- rejected -affects: -- gate-house -- approval-engine -- flex-auth -- secrets-engine -- ops-warden -created: '2026-08-29T12:50:06.944092Z' -updated: '2026-08-29T12:51:01.355583Z' -rationale: Approved as GH-WP-0002-T06. The PEP consumes by compare-and-swap before - the protected side effect; the PDP never mutates; there is no unconsume. Same request - digest is idempotent success; a different digest against a consumed object is conflict. - This closes the three named races and unblocks APPROVAL-WP-0001-T05 and FLEX-WP-0017-T05. -decided_by: Bernd Worsch -decided_at: '2026-08-29T12:51:01.355583Z' -``` - -## Context - -Statute §16 left open who marks an approval consumed, and at what point -relative to the decision. `flex-auth` named three failure modes neither engine -closes alone: an ALLOW never consumed; a double consumption by racing callers; -consumption after a failed action. `approval-engine` performs the mutation -because `access-engine` never mutates, and refused to wire a public consume by -guessing the contract. `APPROVAL-WP-0001-T05` and `FLEX-WP-0017-T05` are wait -on this record. - -§9.7.3 forbids inferring consumption from a decision record. That stands. The -same paragraph currently reads as if the action must precede the consume call. -That reading cannot enforce single use: two racing PEPs can both act, and CAS -then prevents only the second record. - -## Decision - -**The PEP-shaped consumer consumes, by compare-and-swap, before the protected -side effect.** The PDP never mutates. Staff never calls consume. - -**There is no unconsume and no reserve/release.** An approval authorizes one -attempt, not one success. A consume followed by a failed action spends the -object; retry is a new approval. - -**Same `request_digest` against an already-consumed object is idempotent -success** (a retry of one logical request). A different digest is conflict, -and the PEP MUST NOT act. - -The three failure modes have owners: - -1. *Allow never consumed* — PEP duty to consume; PDP lifetime bounds the - window; a stale ALLOW without a matching `use` is a finding, not a consume. -2. *Double consumption* — `approval-engine` CAS; the PEP that receives - conflict does not act. -3. *Consumed then the action fails* — accepted as the cost of closing (2). - -Protocol: `docs/contracts/approval-consumption.md`. Statute v0.8 will replace -the §9.7.3 implication that action precedes the consume call; until then this -record governs the blocked implementers. - -## What this authorizes - -- `approval-engine` exposing `POST /v1/approvals/{id}/consume` against this - contract, including the digest column the internal CAS does not yet store. -- `FLEX-WP-0017-T05` requiring the PEP (secrets-engine, for that task) to - consume before the OpenBao call. -- Canon `T-06` consume-side replay (a second, different digest against a - consumed object) becoming in-scope once the endpoint exists. - -## What this does not authorize - -- The PDP consuming, or inferring consumption from a decision record. -- Unconsume, reserve, or any path that returns a consumed object to `approved`. -- A consume that is a decision ("may this actor do X"). -- Any change to another repository's workplans. Work structure stays with the - repository doing the work. - -## Reversal - -Revert this record and the consumption contract. The falsifier is operational: -if spending an approval on a failed attempt makes the estate unable to -complete the actions this object exists for, a reserve/commit protocol can be -raised then. Unconsume is not the alternative — it reopens replay. diff --git a/docs/contracts/approval-consumption.md b/docs/contracts/approval-consumption.md deleted file mode 100644 index 1860eb8..0000000 --- a/docs/contracts/approval-consumption.md +++ /dev/null @@ -1,148 +0,0 @@ -# Approval consumption ordering - -**Owner:** gate-house -**PIP:** approval-engine -**PDP:** access-engine (currently flex-auth) -**Status:** normative for GH-WP-0002-T06; recorded as `GH-DEC-2026-003` -**Date:** 2026-08-29 -**Statute:** `net-kingdom/canon/standards/security-layer-model_v0.7.md` §9.4, §9.7.3–4, §16 -**Blocks:** `APPROVAL-WP-0001-T05`, `FLEX-WP-0017-T05` - -This contract settles who marks an approval consumed, at what point relative -to the decision, and who owns each of the three named failure modes. Nothing -in `approval-engine` may implement a public consume by guessing this. - -## Sequence - -```text -1. PIP GET /v1/approvals/{id}/claim → valid_now (a fact, not permission) -2. PDP Check / decide → ALLOW | DENY | … -3. PEP POST /v1/approvals/{id}/consume → CAS valid → consumed -4. PEP protected side effect → only after consume succeeds -``` - -The PDP **never mutates**. Staff **never** calls consume. `audit-core` **never** -calls consume. The signaler is the PEP-shaped consumer that is about to cause -the protected side effect named in the binding — today `secrets-engine` for -OpenBao writes, `ops-warden` for the SSH signing lane, and any other consumer -companion §5 already treats as PEP-shaped. - -Holding a claim with `valid_now: true`, or an ALLOW against that claim, is not -authority to act. The consume is. - -## Why consume is before the side effect - -§9.7.3 says consumption MUST NOT be inferred from a decision record. That -stands. A decision record proves an intent to act, not an act. The consume -mutation is the act's evidence at this engine. - -The same paragraph currently reads as if the *action* must precede the -*consume call*. That reading is a protocol, and it is the wrong one. If the -PEP acts and then consumes, two racing PEPs can both observe `valid_now`, -both receive ALLOW, and both act; CAS then prevents only the second -*record*, not the second *side effect*. Single consumption would be theatre. - -**The PEP MUST obtain a successful consume before the protected side effect.** -In-flight duplicate ALLOWs are expected; the CAS serializes use. A later -CheckRequest sees `valid_now: false` and cannot mint a new ALLOW against the -same object. - -This is a protocol clarification of §9.7.3, not a retraction of the forensic -claim. It lands in statute v0.8. Until then this contract governs the -implementers who were blocked on it. - -## Consume request - -```text -POST /v1/approvals/{id}/consume -``` - -```json -{ - "request_digest": "", - "decision_id": "" -} -``` - -`request_digest` is the same digest companion §5.2 already uses as the -mechanical replay test (`NewDecisionBinding.request_digest` in flex-auth). -Go's `json.Marshal` of a CheckRequest is **not** this digest. Compare to the -binding the PDP already computed, not to a re-serialized CheckRequest. - -The mutation is compare-and-swap: - -| Current status | Digest on the object | Result | -| --- | --- | --- | -| `approved`, inside validity window | none | consumed; store this digest; emit `use` | -| `consumed` | same digest | **idempotent success** — same logical request, a retry | -| `consumed` | different digest | **conflict** — already used for another request | -| `revoked` / `superseded` / `expired` / outside window / not `approved` | — | **conflict**; MUST NOT act | - -The `use` outbox row is inserted in the same transaction as the status change -([`approval-outbox.md`](approval-outbox.md)). A failed insert rolls the -consume back. An `audit-core` outage does not. - -There is **no unconsume**. There is no reserve/release. An approval authorizes -one *attempt*, not one *success*. - -## The three failure modes - -### 1. Allow rendered, never consumed - -**Cause.** PEP crash or abandonment between ALLOW and consume. - -**Effect.** The object stays `approved`. A later PEP can still consume it, -inside the remaining validity window and inside the ALLOW's own lifetime -(§9.7.1). That is a delayed first use, not a replay of a completed one. - -**Owner.** The PEP is obligated to consume before the side effect. The -**approval-engine** does not auto-consume from the existence of a decision. -The **PDP** bounds the window with the decision lifetime. Detection of a -stale ALLOW with no matching `use` is a finding on this surface -([`approval-emission-detection.md`](approval-emission-detection.md)), not a -consume. - -### 2. Double consumption by racing callers - -**Cause.** Two PEPs, two ALLOWs in flight, both presenting consume. - -**Effect.** CAS: one success; the other is idempotent success if and only if -the digest matches, otherwise conflict. - -**Owner.** `approval-engine` performs the CAS. The PEP that receives conflict -MUST NOT perform the side effect. The PDP that rendered the second ALLOW has -done nothing wrong — it decided on a still-valid claim. - -Same-digest idempotent success is a retry of one logical request. Companion -§5.2 already permits reusing an ALLOW inside its binding and lifetime. The -side effect MUST itself be idempotent under that digest; that is the PEP's -obligation, not this engine's. - -### 3. Consumed, then the authorized action fails - -**Cause.** Consume succeeded; the protected side effect then failed or was -never reached (crash after consume). - -**Effect.** The approval is spent. Retry requires a new approval object. - -**Owner.** Accepted here as the cost of closing (2). Unconsume would reopen -replay. A flake is an issuance problem, not a reason to make consumption -reversible. The PEP MAY request a new approval; it MUST NOT infer that the -spent object is still valid. - -## What this is not - -- Not a second decision. Consume answers "did this engine accept the use", - never "may this actor do X". -- Not an inference from `audit-core`. The archive reports what it received. -- Not a PDP duty. `access-engine` reads the claim and never mutates. -- Not implemented until `approval-engine` wires `POST /consume` to the CAS - that already exists internally, with the digest column this contract adds. - -## Acceptance - -`APPROVAL-WP-0001-T05` may implement the public consume against this -document. `FLEX-WP-0017-T05` may require the PEP (secrets-engine, for that -task) to consume before the OpenBao call. Canon `T-06` consume-side replay — -a second, different digest against a consumed object — becomes in-scope with -that implementation. diff --git a/docs/contracts/approval-emission-detection.md b/docs/contracts/approval-emission-detection.md deleted file mode 100644 index 4b19952..0000000 --- a/docs/contracts/approval-emission-detection.md +++ /dev/null @@ -1,111 +0,0 @@ -# Approval emission detection surface - -**Owner:** gate-house -**Source:** approval-engine -**Archive:** audit-core -**Observer:** kings-guard (silence-as-signal; offered) -**Status:** normative for GH-WP-0002-T04 -**Date:** 2026-08-29 -**Statute:** `net-kingdom/canon/standards/security-layer-model_v0.7.md` §9.6 -**Source declaration:** `approval-engine/cadence.yaml` - -A hash chain proves accepted records were not altered or truncated. It proves -nothing about an event never emitted. Atomicity (the outbox contract) prevents -accidental omission. This surface is what makes a *silent* loss visible. - -It does not prevent a compromised source from suppressing. That residual is -stated in §9.6 and is not closed here. - -## Form - -Approval evidence is **load-bearing** and **low-volume**. Rate monitoring is -forbidden: a handful of revocations a month has no rate to drop below, and -suppression is indistinguishable from a quiet month. - -The required form is **heartbeat plus reconciliation**. Reference instance: -`approval-engine/cadence.yaml` and `approval-engine/docs/emission-cadence.md`. - -### Heartbeat - -A signed positive claim: *nothing to report*, together with per-class -transition counts since the previous heartbeat (or since process start on -the first). The claim can itself go missing, which is the point. - -| Field | Value | -| --- | --- | -| Class | `heartbeat` | -| Interval | 24 hours (SHOULD also be emittable on demand) | -| Assertion | `nothing-to-report` | -| Counts | `{issuance, use, supersession, revocation}` of committed outbox rows | -| Missing heartbeat | **finding** | - -Heartbeat rows use the outbox table and the same at-least-once drain. They are -not coupled to an object mutation. - -### Reconciliation - -Compare `approval-engine`'s committed outbox counts per class to `audit-core`'s -accepted event counts for `source=approval-engine` and the corresponding -`action`. Divergence is a **finding**, not a log line. - -| Source class | `audit-core` `action` | -| --- | --- | -| `issuance` | `approval.issuance` | -| `use` | `approval.use` | -| `supersession` | `approval.supersession` | -| `revocation` | `approval.revocation` | -| `heartbeat` | `approval.heartbeat` | - -Undrained local rows are lag, not yet a divergence. A row with `drained_at` set -that `audit-core` does not hold is the omission case §9.6 names. - -### Lag bound - -| Bound | Default (reference) | Exceed | -| --- | --- | --- | -| Outbox depth | 100 undrained rows | **finding** | -| Outbox age | 1 hour for the oldest undrained row | **finding** | - -These detect a stuck drain. They are not a completeness proof. - -## What is a finding - -- No heartbeat arrives for more than one interval. -- `audit-core` count for a class is less than the source's drained count for - that class. -- Outbox depth or age exceeds the lag bound. - -None of these are rate drops. None of these are informational logs. - -`kings-guard` consumes the finding as observation of the stream, not of its -contents. Until it reports that it is watching in production, no argument may -assume this surface is live (§12). The source MUST still publish the -declaration; a detector that is not yet watching does not excuse an undeclared -source. - -## Who owns which half - -| Half | Owner | -| --- | --- | -| Publish cadence and expose committed counts / lag | approval-engine | -| Hold accepted events and answer per-source per-class counts | audit-core | -| Raise silence, divergence, and lag as findings | kings-guard | -| Name the form and the residual | gate-house | - -## Conformance check (queued for statute v0.8 §11) - -GH-WP-0002-T05. Mechanically checkable, in the same list as the existing §11 -bullets: - -- every repository catalogued as an evidence source, or that emits - **load-bearing** evidence, declares its emission guarantee in a - machine-readable form at a path named in its layer declaration: - - **load-bearing:** local transactional outbox (this contract's shape) **and** - a heartbeat-or-reconciliation cadence declaration; - - **attributive, deliberately non-atomic:** the named trade, and a statement - that completeness is not claimed. - -A new engine catalogued under §9.4 / §4 as an evidence source that ships -without this declaration reintroduces the approval omission gap silently. -Until v0.8 lands, this paragraph is doctrine; `approval-engine`'s -`cadence.yaml` plus `layer.yaml` is the reference form. diff --git a/docs/contracts/approval-outbox.md b/docs/contracts/approval-outbox.md deleted file mode 100644 index d2ff8e0..0000000 --- a/docs/contracts/approval-outbox.md +++ /dev/null @@ -1,105 +0,0 @@ -# Approval-engine transactional outbox contract - -**Owner:** gate-house -**Implementer:** approval-engine -**Evidence engine:** audit-core -**Status:** normative for GH-WP-0002-T02 -**Date:** 2026-08-29 -**Statute:** `net-kingdom/canon/standards/security-layer-model_v0.7.md` §9.4, §9.6 -**Failure mode:** `GH-DEC-2026-002` -**Source draft:** `approval-engine/docs/outbox-contract.md` (adopted) - -This is the wire. Boundary and locality are in the statute and in -`approval-engine/INTENT.md`. An implementer MUST NOT satisfy this contract by -emitting synchronously to `audit-core` inside the state-change transaction. -That path is atomic and is forbidden: an audit outage would become an inability -to revoke. - -## Rule - -Every issuance, use, supersession, and revocation **inserts an outbox row in -the same transaction** that mutates the approval object. The durable queue -lives in `approval-engine`'s own store. Drain is asynchronous, at-least-once. -`audit-core` dedupes on `event_id`; a replay does not fork the chain. - -If the outbox insert cannot be committed, the mutation does not commit. -Emit-after-commit is a defect. - -Heartbeat rows (see [`approval-emission-detection.md`](approval-emission-detection.md)) -use the same table and the same at-least-once drain. They are not coupled to an -object mutation. - -## Event classes - -| Class | When | `audit-core` `action` | -| --- | --- | --- | -| `issuance` | object becomes `approved` (threshold met) | `approval.issuance` | -| `use` | object becomes `consumed` (public consume under [`approval-consumption.md`](approval-consumption.md)) | `approval.use` | -| `supersession` | object becomes `superseded` | `approval.supersession` | -| `revocation` | object becomes `revoked` | `approval.revocation` | -| `heartbeat` | signed *nothing to report* plus counts | `approval.heartbeat` | - -Expiry is a clock crossing, persisted on observation, and is **not** an -emitted class. The validity window is already on the object. - -## Outbox row - -| Field | Type | Notes | -| --- | --- | --- | -| `event_id` | UUID | Stable. Drain retries reuse it. | -| `class` | enum above | | -| `approval_id` | UUID or null | Null only for heartbeat. | -| `payload` | object | The `audit-core.event.v1alpha1` record, ready to POST. | -| `created_at` | RFC 3339 UTC | | -| `drained_at` | RFC 3339 UTC or null | Set after a successful `audit-core` ack. | - -## Payload (`audit-core` v1alpha1) - -```json -{ - "schema_version": "audit-core.event.v1alpha1", - "event_id": "", - "observed_at": "", - "tenant": "platform", - "scope": "netkingdom-approvals", - "source": "approval-engine", - "actor": "", - "action": "approval.revocation", - "resource": "approval:", - "outcome": "success", - "reason": null, - "details": { - "class": "revocation", - "approval_id": "", - "binding_digest": "sha256:…", - "request_digest": null, - "superseded_by": null - } -} -``` - -No secret values. `details` may add non-secret identifiers; it MUST NOT add a -validity verdict for consumers to branch on. `audit-core` MUST NOT expose an -approval-validity query; this payload does not invite one. - -For `use` events, `details.request_digest` is the digest the PEP presented at -consume (see the consumption contract). It is null on other classes. - -## Drain - -1. Select undrained rows, oldest first. -2. POST each payload to `audit-core`. -3. On success, set `drained_at`. -4. On failure, leave the row; retry later. **Do not** roll back the object - mutation — it already committed with the row. - -An `audit-core` outage therefore cannot block a revocation. This engine's -own store being unavailable can, and should: the change could not have been -recorded anyway. That is the whole of `GH-DEC-2026-002`. - -## Forbidden shapes - -- `audit-core` HTTP (or any client) inside `BEGIN` … `COMMIT` of a mutation. -- Best-effort publish after commit with no row. -- A second, non-local queue as the durability mechanism. -- Deduping in `approval-engine` instead of relying on `event_id` at `audit-core`. diff --git a/history/2026-08-29-approval-evidence-integrity-contracts.md b/history/2026-08-29-approval-evidence-integrity-contracts.md deleted file mode 100644 index ce33fc4..0000000 --- a/history/2026-08-29-approval-evidence-integrity-contracts.md +++ /dev/null @@ -1,57 +0,0 @@ -# Approval evidence integrity — contracts and rulings - -**Recorded:** 2026-08-29 -**Workplan:** GH-WP-0002 -**Audience:** approval-engine, access-engine / flex-auth, audit-core, kings-guard, secrets-engine, ops-warden -**Status:** Reference note. The normative artifacts are the contracts under -`docs/contracts/` and the decision records GH-DEC-2026-002 and GH-DEC-2026-003. - ---- - -## Why this landed as contracts, not a statute bump - -`approval-engine` shipped the object, the local outbox, and a cadence -declaration, and stopped at a public consume because GH-WP-0002-T06 was unset. -`FLEX-WP-0017-T05` is wait on the same unset. The workplan's remaining tasks -were doctrine, not another version of the layer model. - -v0.7 stays the accepted statute. Two protocol clarifications and one §11 -bullet are queued for v0.8 rather than silently rewritten into an accepted -text: - -1. §9.7.3 — consumption is never inferred from a decision record (stands); - the PEP consumes *before* the protected side effect (clarifies the - protocol the paragraph currently implies the other way). -2. §16 — "who marks an approval consumed" is no longer open. -3. §11 — evidence sources declare their emission guarantee (GH-WP-0002-T05). - -Inbox items that also belong in v0.8 (pep-stance inventory rows, the §13 -table becoming a pointer into `maturity-engine`, `railiance-master`'s own-voice -declaration) are not mixed into this workplan. - -## What each task produced - -| Task | Artifact | -| --- | --- | -| T02 | `docs/contracts/approval-outbox.md` — adopts `approval-engine`'s wire | -| T03 | GH-DEC-2026-002 — fail-closed only on the local store | -| T04 | `docs/contracts/approval-emission-detection.md` — heartbeat + reconciliation | -| T05 | detection contract, last section — queued for v0.8 §11 | -| T06 | `docs/contracts/approval-consumption.md` and GH-DEC-2026-003 | - -## The two rulings in one line each - -- **Revocation:** if the local outbox insert cannot commit, the revocation - does not commit. An `audit-core` outage MUST NOT block a revocation. - Synchronous emission to `audit-core` inside the mutation is forbidden. -- **Consumption:** the PEP consumes by CAS before the side effect. The PDP - never mutates. There is no unconsume. Same-digest retry is idempotent; - a different digest against a consumed object is conflict. - -## Assent still required of the implementers, not of the estate - -These contracts bind `approval-engine` and `access-engine` because they asked -for them and were blocked on them. They do not move a vocabulary away from a -repository that currently uses it, so they do not follow the GH-DEC-2026-001 -assent pattern. Contest is still welcome: a protocol that cannot be -implemented is a success of the loop, not a courtesy. diff --git a/workplans/GH-WP-0002-approval-evidence-integrity.md b/workplans/GH-WP-0002-approval-evidence-integrity.md index 4e61463..083c346 100644 --- a/workplans/GH-WP-0002-approval-evidence-integrity.md +++ b/workplans/GH-WP-0002-approval-evidence-integrity.md @@ -4,10 +4,9 @@ type: workplan title: "Approval evidence integrity" domain: infotech repo: gate-house -status: finished +status: active origin: GH-IN-0001 state_hub_workstream_id: "393d46ad-4f8c-5578-b63d-91cb0e8b9502" -updated: "2026-08-29" --- # Approval evidence integrity @@ -38,7 +37,7 @@ versus attributive distinction. ```task id: GH-WP-0002-T02 -status: done +status: progress priority: high state_hub_task_id: "73a8feb0-02bd-5191-bdfd-82bcc42c6756" ``` @@ -46,55 +45,47 @@ state_hub_task_id: "73a8feb0-02bd-5191-bdfd-82bcc42c6756" Specify the transactional-outbox contract for `approval-engine`: same transaction as the object mutation, queue local to the engine, at-least-once into the outbox since `audit-core` dedupes on event id and a replay does not -fork the chain. Done — `docs/contracts/approval-outbox.md` adopts -`approval-engine`'s wire (`docs/outbox-contract.md`) as Gate House doctrine. +fork the chain. Boundary and locality are in `approval-engine/INTENT.md` and +§9.4; the wire contract is not yet written. ```task id: GH-WP-0002-T03 -status: done +status: todo priority: high state_hub_task_id: "1fc8fd3c-7af4-50e7-a07c-f45016f8b1f3" ``` Decide the revocation failure mode explicitly: fail closed, or proceed with a -detectable gap. Done — GH-DEC-2026-002. Fail-closed only when -`approval-engine`'s own store cannot insert the outbox row. An `audit-core` -outage MUST NOT block a revocation. Synchronous emission inside the mutation -is forbidden. Proceed-with-gap is rejected for load-bearing approval evidence. +detectable gap. Both are defensible; undecided is not, and an implementation +accident is the worst outcome. Note v0.5's local-outbox rule narrows this +considerably — fail-closed now triggers only when the engine's own store is +down — but the ruling is still owed. ```task id: GH-WP-0002-T04 -status: done +status: todo priority: medium state_hub_task_id: "3d62dbf9-786e-58c1-bd87-fb43658ca865" ``` Give the gap a detection surface: outbox depth and age, or reconciliation of `approval-engine` object counts against `audit-core` event counts per class. -Done — `docs/contracts/approval-emission-detection.md`. Form is heartbeat plus -reconciliation, not rate monitoring; lag bounds on outbox depth and age. -`approval-engine/cadence.yaml` is the reference source declaration. -`kings-guard` remains the observer; until it reports watching in production, -the declaration is still required. +Today nothing would surface a silent loss. Relate to §9.6's silence-as-signal +rule, which `kings-guard` offered to implement at its own layer. ```task id: GH-WP-0002-T05 -status: done +status: todo priority: medium state_hub_task_id: "2a2a73ea-2778-59d2-94ef-7a70a22bb169" ``` Add a §11 conformance check so the next engine catalogued as an evidence source declares its emission guarantee rather than reintroducing this silently. -Done as doctrine — the check is the last section of -`docs/contracts/approval-emission-detection.md`. Queued for statute v0.8 §11 -rather than patched into accepted v0.7. Load-bearing sources declare a local -outbox plus heartbeat-or-reconciliation; attributive non-atomic sources declare -the trade and do not claim completeness. ```task id: GH-WP-0002-T06 -status: done +status: todo priority: high state_hub_task_id: "250cb9b3-713d-5607-ad1b-225e339693bf" ``` @@ -102,7 +93,5 @@ state_hub_task_id: "250cb9b3-713d-5607-ad1b-225e339693bf" Settle the consumption ordering contract between `approval-engine` and `access-engine` — who signals consumed, at what point relative to the decision, and the handling of an allow never consumed, a double consumption by racing -callers, and consumption after a failed action. Done — GH-DEC-2026-003 and -`docs/contracts/approval-consumption.md`. The PEP consumes by CAS before the -side effect; the PDP never mutates; there is no unconsume. Unblocks -`APPROVAL-WP-0001-T05` and `FLEX-WP-0017-T05`. +callers, and consumption after a failed action. Raised by `flex-auth`; required +before `FLEX-WP-0017` T05. Recorded unresolved in `approval-engine/INTENT.md`.