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