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.

ColumnNotes
idSnowflake
account_idTenant scope (NULL for system-level events)
event_name<entity>.<verb>order.created, customer.redacted
schema_versionBump when payload shape changes
entity_type, entity_idWhat this event is about
actor_type, actor_id, actor_nameWho triggered
actor_metadataFree JSONB — IP, agent session id, etc.
source`admin
payloadSnapshot or change diff
request_idCorrelates with X-Request-Id header
idempotency_keyIf the request was idempotent
triggered_by_event_idCausation chain (event A → event B)
occurred_atWhen

outbox_dispatches

Per-consumer dispatch rows. One event → 0..N dispatches. The dispatcher processes these.

ColumnNotes
event_idSource event
dispatch_type`task
targetHandler-specific JSONB (e.g. {"task_name":"send_email", ...})
status`queued → processing → sent
attempts, max_attemptsRetry count + cap
next_attempt_atBackoff scheduling
locked_by, locked_atClaim coordination
last_error, resultAudit 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.

EventSource
account.created, account.updated, account.archivedaccounts
market.created, market.updatedmarkets
product.created, product.updated, product.archivedcatalog
variant.pricing_setcatalog
customer.created, customer.updated, customer.redacted, customer.address_addedcustomers
order.created, order.updated, order.cancelledorders
payment.created, payment.refundedpayments
fulfillment.created, fulfillment.shipped, fulfillment.deliveredfulfillments
return.requested, return.approved, return.completedreturns
inventory.adjustedinventory
location.createdinventory
discount.created, discount.updated, discount.archiveddiscounts
voucher.issued, voucher.redeemed, voucher.disabledvouchers
shipping_zone.created, shipping_method.created, shipping_method.archivedshipping
tax_category.created, tax_rate.settax
setting.created, setting.updated, setting.deletedsettings
theme.created, theme.activated, theme.archivedthemes
email_template.created, email_template.updatedmessaging
notification_preference.setmessaging
email.sent, email.delivered, email.failedemail
checkout.created, checkout.payment_started, checkout.completed, checkout.abandonedcheckouts
storefront_key.created, storefront_key.revokedstorefront 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_typeHandlerStatus
task / send_emailoutbox-dispatcherLive
webhook_deliverywebhook delivery workerReserved
connector_pushconnector frameworkReserved
flow_runflow DSL executorReserved

Next: Error codes.