gate-house/docs/contracts/approval-emission-detection.md
tegwick 7408ff9234 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
2026-08-29 14:51:53 +02:00

111 lines
4.2 KiB
Markdown

# 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.