Publish approval evidence-integrity contracts for GH-WP-0002
Adopt approval-engine's outbox wire as Gate House doctrine, specify the heartbeat-and-reconciliation detection surface, and settle consumption ordering: the PEP consumes by CAS before the side effect. T05's §11 check is written here and queued for statute v0.8. Assistant: grok Assistant-Session: 01a04d89-aaa5-7443-945e-b3055cd4b7e4
This commit is contained in:
parent
2ae611d2ad
commit
7408ff9234
5 changed files with 441 additions and 10 deletions
148
docs/contracts/approval-consumption.md
Normal file
148
docs/contracts/approval-consumption.md
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
# 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": "<canonical request digest from the decision binding>",
|
||||||
|
"decision_id": "<optional; the ALLOW this consume discharges>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`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.
|
||||||
111
docs/contracts/approval-emission-detection.md
Normal file
111
docs/contracts/approval-emission-detection.md
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
# 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.
|
||||||
105
docs/contracts/approval-outbox.md
Normal file
105
docs/contracts/approval-outbox.md
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
# 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": "<same as outbox.event_id>",
|
||||||
|
"observed_at": "<created_at>",
|
||||||
|
"tenant": "platform",
|
||||||
|
"scope": "netkingdom-approvals",
|
||||||
|
"source": "approval-engine",
|
||||||
|
"actor": "<actor or null for heartbeat>",
|
||||||
|
"action": "approval.revocation",
|
||||||
|
"resource": "approval:<approval_id>",
|
||||||
|
"outcome": "success",
|
||||||
|
"reason": null,
|
||||||
|
"details": {
|
||||||
|
"class": "revocation",
|
||||||
|
"approval_id": "<uuid>",
|
||||||
|
"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`.
|
||||||
57
history/2026-08-29-approval-evidence-integrity-contracts.md
Normal file
57
history/2026-08-29-approval-evidence-integrity-contracts.md
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
# 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.
|
||||||
|
|
@ -45,8 +45,8 @@ state_hub_task_id: "73a8feb0-02bd-5191-bdfd-82bcc42c6756"
|
||||||
Specify the transactional-outbox contract for `approval-engine`: same
|
Specify the transactional-outbox contract for `approval-engine`: same
|
||||||
transaction as the object mutation, queue local to the engine, at-least-once
|
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
|
into the outbox since `audit-core` dedupes on event id and a replay does not
|
||||||
fork the chain. Boundary and locality are in `approval-engine/INTENT.md` and
|
fork the chain. Done — `docs/contracts/approval-outbox.md` adopts
|
||||||
§9.4; the wire contract is not yet written.
|
`approval-engine`'s wire (`docs/outbox-contract.md`) as Gate House doctrine.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: GH-WP-0002-T03
|
id: GH-WP-0002-T03
|
||||||
|
|
@ -56,10 +56,10 @@ state_hub_task_id: "1fc8fd3c-7af4-50e7-a07c-f45016f8b1f3"
|
||||||
```
|
```
|
||||||
|
|
||||||
Decide the revocation failure mode explicitly: fail closed, or proceed with a
|
Decide the revocation failure mode explicitly: fail closed, or proceed with a
|
||||||
detectable gap. Both are defensible; undecided is not, and an implementation
|
detectable gap. Done — GH-DEC-2026-002. Fail-closed only when
|
||||||
accident is the worst outcome. Note v0.5's local-outbox rule narrows this
|
`approval-engine`'s own store cannot insert the outbox row. An `audit-core`
|
||||||
considerably — fail-closed now triggers only when the engine's own store is
|
outage MUST NOT block a revocation. Synchronous emission inside the mutation
|
||||||
down — but the ruling is still owed.
|
is forbidden. Proceed-with-gap is rejected for load-bearing approval evidence.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: GH-WP-0002-T04
|
id: GH-WP-0002-T04
|
||||||
|
|
@ -70,8 +70,11 @@ state_hub_task_id: "3d62dbf9-786e-58c1-bd87-fb43658ca865"
|
||||||
|
|
||||||
Give the gap a detection surface: outbox depth and age, or reconciliation of
|
Give the gap a detection surface: outbox depth and age, or reconciliation of
|
||||||
`approval-engine` object counts against `audit-core` event counts per class.
|
`approval-engine` object counts against `audit-core` event counts per class.
|
||||||
Today nothing would surface a silent loss. Relate to §9.6's silence-as-signal
|
Done — `docs/contracts/approval-emission-detection.md`. Form is heartbeat plus
|
||||||
rule, which `kings-guard` offered to implement at its own layer.
|
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.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: GH-WP-0002-T05
|
id: GH-WP-0002-T05
|
||||||
|
|
@ -82,6 +85,11 @@ state_hub_task_id: "2a2a73ea-2778-59d2-94ef-7a70a22bb169"
|
||||||
|
|
||||||
Add a §11 conformance check so the next engine catalogued as an evidence source
|
Add a §11 conformance check so the next engine catalogued as an evidence source
|
||||||
declares its emission guarantee rather than reintroducing this silently.
|
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
|
```task
|
||||||
id: GH-WP-0002-T06
|
id: GH-WP-0002-T06
|
||||||
|
|
@ -93,5 +101,7 @@ state_hub_task_id: "250cb9b3-713d-5607-ad1b-225e339693bf"
|
||||||
Settle the consumption ordering contract between `approval-engine` and
|
Settle the consumption ordering contract between `approval-engine` and
|
||||||
`access-engine` — who signals consumed, at what point relative to the decision,
|
`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
|
and the handling of an allow never consumed, a double consumption by racing
|
||||||
callers, and consumption after a failed action. Raised by `flex-auth`; required
|
callers, and consumption after a failed action. Done — GH-DEC-2026-003 and
|
||||||
before `FLEX-WP-0017` T05. Recorded unresolved in `approval-engine/INTENT.md`.
|
`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`.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue