Advisories.
Curated intelligence written by a platform admin and published to every subscribed tenant, with an immutability rule that protects the delivery record.
Lifecycle
The states
draft— fully editable. Nothing has been delivered.queued— has ascheduled_send_at. Still editable.sent— delivered. Immutable.archived— retired from the active list, retained for audit.
Why sent is immutable
Once an advisory is sent, recipients hold that text. Editing the row afterwards would leave advisory_deliveries pointing at content that was never actually delivered — so the audit trail would claim something untrue.
The server refuses edits and deletes on a sent advisory. The correct move is to archive it and publish a correction, which leaves both the original and the correction in the record.
Fan-out
Sending inserts one delivery row per (tenant, channel) where the tenant's subscription is enabled and the advisory's severity meets their configured minimum. Subscriptions are backfilled with defaults, so a tenant does not have to opt in before it can receive anything.
The portal channel is delivered inline — a notification row is written and the delivery flipped to sent in the same operation. Email and webhook are left pending for the dispatcher.
Every mutation is audited
Create, update, schedule, unschedule, send, reopen, archive and discard each write an audit_log row with the actor, the target and the time. Publishing intelligence to tenants is exactly the class of action that needs provenance.
Scheduling is not yet dispatched
An advisory can be moved to queued with a scheduled_send_at, and both the workbench and the API will report that time. No worker consumes it. Nothing sends until a human presses send.
This is the one place where the interface currently promises behaviour the backend does not implement. Treat queued as “marked for sending”, not “will send”.