A transactional outbox written in the same commit as the policy row. Publishing to Kafka from application code after the commit is the standard way to lose a policy change, and this design refuses it.
The topic is log-compacted, so a restarting pod replays only the current policy set rather than the whole history. Cold start reaches steady state in about 1.2 s and does not depend on PostgreSQL being reachable (FR8).
Every pod exports the policy version it is running as a gauge. Propagation delay is then a query — the spread between the published version and the fleet minimum — rather than a claim in a document (NFR9).
Guardrails before publish
A child scope's quota may not exceed its parent's; the API rejects the write rather than accepting a limit that can never bind.
A dry run replays the last 24 hours of decision metadata against the proposed policy and reports how many requests would have been rejected. Large policy cuts are caught before they reach a tenant.
Propagation target is p99 under 2 s; the staleness alert fires when any pod is more than 60 s behind.
Risks
A policy change is a behaviour change with no deployment. It goes through the same review as code (view 19) and is applied to one canary tenant before the fleet.
The outbox relay is at-least-once, so a pod can see the same policy version twice. Policy application is idempotent by version, so duplicates are inert.
A partitioned pod continues on its last-known-good policy indefinitely rather than failing. That is the correct choice for availability, and it is why the staleness alert exists.