Events & outbox
The audit log + fanout substrate. Every state change publishes an immutable event row and queues outbox dispatches — atomically in the same transaction as the underlying mutation.
Two tables
events
Append-only fact log. UPDATE/DELETE blocked by triggers.
| Column | Notes |
|---|---|
id | Snowflake |
account_id | Tenant scope (NULL for system-level events) |
event_name | <entity>.<verb> — order.created, customer.redacted |
schema_version | Bump when payload shape changes |
entity_type, entity_id | What this event is about |
actor_type, actor_id, actor_name | Who triggered |
actor_metadata | Free JSONB — IP, agent session id, etc. |
source | `admin |
payload | Snapshot or change diff |
request_id | Correlates with X-Request-Id header |
idempotency_key | If the request was idempotent |
triggered_by_event_id | Causation chain (event A → event B) |
occurred_at | When |
outbox_dispatches
Per-consumer dispatch rows. One event → 0..N dispatches. The dispatcher processes these.
| Column | Notes |
|---|---|
event_id | Source event |
dispatch_type | `task |
target | Handler-specific JSONB (e.g. {"task_name":"send_email", ...}) |
status | `queued → processing → sent |
attempts, max_attempts | Retry count + cap |
next_attempt_at | Backoff scheduling |
locked_by, locked_at | Claim coordination |
last_error, result | Audit on failure / success |
Atomic publish
The application code calls one helper:
from nevios_events import publish_event, EventEnvelope, ActorInfo, EventSource
await publish_event(
session,
EventEnvelope(
event_name="order.created",
entity_type="order",
entity_id=order.id,
account_id=account_id,
actor=ActorInfo(actor_type="staff", actor_id="123", actor_name="Anna"),
source=EventSource.ADMIN,
payload={"order_id": str(order.id), "total_cents": order.total_cents},
),
)
This INSERTs into events. The same DB transaction also INSERTs the
business mutation (the order itself) — so events can never lag or
get phantomed.
For consumer fanout, the application explicitly inserts an
outbox_dispatches row alongside (e.g. checkout.complete enqueues
the email send dispatch).
LISTEN/NOTIFY
A trigger on outbox_dispatches fires:
PERFORM pg_notify('outbox_new', NEW.dispatch_type);
Workers subscribe to outbox_new for instant wakeup. Polling every
5s is the fallback in case NOTIFY is missed (network blip).
Standard event names
Common patterns you'll see across modules. None are required — they're just what the services emit today.
| Event | Source |
|---|---|
account.created, account.updated, account.archived | accounts |
market.created, market.updated | markets |
product.created, product.updated, product.archived | catalog |
variant.pricing_set | catalog |
customer.created, customer.updated, customer.redacted, customer.address_added | customers |
order.created, order.updated, order.cancelled | orders |
payment.created, payment.refunded | payments |
fulfillment.created, fulfillment.shipped, fulfillment.delivered | fulfillments |
return.requested, return.approved, return.completed | returns |
inventory.adjusted | inventory |
location.created | inventory |
discount.created, discount.updated, discount.archived | discounts |
voucher.issued, voucher.redeemed, voucher.disabled | vouchers |
shipping_zone.created, shipping_method.created, shipping_method.archived | shipping |
tax_category.created, tax_rate.set | tax |
setting.created, setting.updated, setting.deleted | settings |
theme.created, theme.activated, theme.archived | themes |
email_template.created, email_template.updated | messaging |
notification_preference.set | messaging |
email.sent, email.delivered, email.failed | |
checkout.created, checkout.payment_started, checkout.completed, checkout.abandoned | checkouts |
storefront_key.created, storefront_key.revoked | storefront keys |
Querying events
Events are SELECT-only via API (read endpoints not yet exposed — Phase 2). For now query directly:
SELECT * FROM public.events
WHERE account_id = 308... AND event_name LIKE 'order.%'
ORDER BY occurred_at DESC LIMIT 50;
A read API surface is on the backlog.
Outbox consumer slots
| dispatch_type | Handler | Status |
|---|---|---|
task / send_email | outbox-dispatcher | Live |
webhook_delivery | webhook delivery worker | Reserved |
connector_push | connector framework | Reserved |
flow_run | flow DSL executor | Reserved |
Next: Error codes.