approval-engine/docs/emission-cadence.md
tegwick 9c9528f5b2 Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).

Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.

Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.

FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.

Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00

2.2 KiB

Emission cadence — source declaration

Statute §9.6: approval evidence is load-bearing and low-volume. Rate monitoring is the wrong form. A handful of revocations a month has no rate to drop below; suppression is indistinguishable from a quiet month.

This file is the source side of GH-WP-0002-T04. That task is the detection surface; this declaration is what it reads. Machine-readable copy: ../cadence.yaml.

Form

Heartbeat, plus reconciliation counts. Not a rate.

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 — silence becomes a missing positive rather than a quiet month.

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

Reconciliation

Compare this 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.

This engine audit-core action
issuance approval.issuance
use approval.use
supersession approval.supersession
revocation approval.revocation
heartbeat approval.heartbeat

Undrained local rows are this engine's lag, not yet a divergence. A row with drained_at set that audit-core does not hold is the omission case §9.6 names.

What is a finding

  • No heartbeat arrives for more than one interval.
  • audit-core count for a class is less than this engine's drained count for that class.
  • Outbox depth or age exceeds the lag bound in cadence.yaml (drain stuck).

None of these are rate drops. None of these are informational logs.

Residual

Atomicity prevents accidental omission (crash between mutation and emit). Cadence and reconciliation detect adversarial omission after the fact. Nothing in the model prevents a compromised source from suppressing. That residual is stated, not closed.