approval-engine/docs/audit-source-registration.md
tegwick a786b061da Derive heartbeat cadence from the shared coordination frame
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-15 20:41:49 +02:00

97 lines
5.1 KiB
Markdown

# Platform approval audit sender
Source return for `APPROVAL-WP-0002-T01/T04` and `AUDIT-WP-0009-T09`,
2026-09-08. This records the existing platform store's required registration;
it neither provisions a credential nor admits another tenant.
| Field | Required value |
| --- | --- |
| Sender and permitted source | `approval-engine` (exact) |
| Tenants | `["tenant:platform"]` (exact) |
| Write / read | `true` / `false` |
| Evidence kind | `load-bearing` |
| Completeness trade | none |
| Secret policy | `redact` |
| Consumer mount | Secret `approval-engine/approval-engine-audit`, key `audit-token` |
The accepted platform tenant decision is already implemented in the Engine
default, production CLI and deployment arguments. The audit envelope carries
that same store tenant without normalization. This is the tenant list Audit
Core requested; neither `platform`, `tenant:coulomb`, nor `*` is required.
A future store serving another tenant needs a separate registration review.
Choose `redact` explicitly: the receiver should retain a legitimate revocation
record with an accidentally secret-shaped field removed, rather than reject
the event and leave its delivery pending. Approval payloads must still contain
no credentials. Redaction findings do not excuse a producer defect.
Audit Core's scope overlay does not set this field; the protected sender
registry entry must explicitly carry `secret_policy: redact` when provisioned.
## Heartbeat classes
Per class, not per source (`AUDIT-WP-0009-T04`, audit-core
`docs/stream-completeness.md`). A per-source heartbeat is discharged by
whichever class happens to be busy and says nothing about the quiet one — here
that is `revocation`, whose silence is the only silence that matters. Requested
`heartbeat_classes`, all four at the same bound:
| Class | Registered wait before receiver grace |
| --- | --- |
| `issuance` | 2f = 7200s (2h) |
| `use` | 2f = 7200s (2h) |
| `supersession` | 2f = 7200s (2h) |
| `revocation` | 2f = 7200s (2h) |
These are registered base waits, not the effective alert threshold. Founder
coordination-frame decision 2026-10-15 sets f=3600s. The prepared emitter
interval is f; the receiver wait is 2f=7200s, and Audit Core's existing 1.5
grace gives an effective deadline of 3f=10800s. This source request is not a
live registry receipt or proof that deployment has changed pace. The engine emits all four in a
single transaction: a partial emission would report some classes healthy and
others stalled, which is a worse signal than none.
The first beat is emitted at startup rather than one interval later —
`heartbeat_due` is true when no heartbeat exists — because audit-core raises
`no_heartbeat_since_registration` for a declared-but-never-sent class, which is
the shape a naive compare-against-last-seen would drop silently.
Heartbeat events carry `type: audit-core.heartbeat` with `class` and
`assertion` on `data`. They are ordinary events on the same append-only chain
and the same durable drain; there is no separate heartbeat path here either.
## Reconciliation
`GET /v1/reconciliation?source=&tenant=&since=&until=` returns per-class counts
for this source's own events. `may_read: false` still holds and is not
weakened: a source asking how many of its own events the archive holds learns
nothing it did not itself emit. Both bounds must be supplied — a count whose
window this engine did not choose is not comparable against
`transition_counts()`. A request for another source's counts returns `403`, not
zero; a zero would be a false answer to a completeness question.
**The bound, restated so it is not lost:** agreement on counts proves neither
completeness nor that any event occurred, and a missing heartbeat is not proof
of suppression. Both controls cover loss, outage, drain failure and accident.
Neither covers this engine lying about itself — a compromised emitter suppresses
the event and its own heartbeat together. That is the residual, and it is not
closed by anything in this document.
The transactional outbox emits issuance, use, supersession and revocation;
heartbeat uses the same durable drain. Its atomicity and retry contract remain
in [outbox-contract.md](outbox-contract.md). Receiver identity/scope/ingress and
credential admission precede live drain proof. Attestation freshness, omission
detection and reconciliation retain their separate AUDIT-WP-0009-T02/T04/T06
owners and bounds; this registration does not claim their completion.
First provisioning still requires the platform-owned custody record linking
the receiver's protected sender registry and this mounted credential. No
credential value belongs in this document, Git, Hub or a rollout receipt.
Current timing values derive from coordination-engine/config/coordination-frame.json
through net-kingdom/tools/coordination-frame/render.py. The generated declaration,
consumer constants and deployment pod annotation carry its SHA-256. Existing
credential scope and the native September 15 bearer-revocation proof are
retained; do not repeat that unchanged proof as if it were missing. Actual
lifecycle reconciliation, class registration, lag findings and operational
observer readback remain APPROVAL-WP-0002-T04.