Skip to content

protean outbox

The protean outbox command group manages the transactional outbox. Today it exposes a single command, reconcile, which repairs the crash window described in ADR-0015: an event that reached the event store (the durable anchor of the commit) but whose relational outbox row never committed, leaving the event durable yet unpublished.

All commands accept a --domain option to specify the domain module path (defaults to the current directory).

Commands

Command Description
protean outbox reconcile Recreate outbox rows for stored events that are missing them

protean outbox reconcile

Scans the tail of the event store and creates an outbox row for any event that is durable in the store but has no internal-broker outbox row. This is the manual counterpart to the automatic startup sweep — run it on demand after a suspected crash, or from a cron job as a periodic safety net.

# Reconcile the default provider's outbox
protean outbox reconcile --domain=my_domain

# Reconcile a named provider, scanning a wider window
protean outbox reconcile --provider=analytics --limit=5000 --domain=my_domain

Options

Option Description Default
--domain Domain module path . (current directory)
--provider Provider whose outbox to reconcile default
--limit Most recent events to scan for gaps 1000

Output

Reconciled 2 outbox row(s) from the event store.

When the outbox already matches the event store — the common, no-crash case — nothing is rewritten:

Nothing to reconcile: the outbox is consistent with the event store.

The scan is cheap when there is nothing to repair: it first checks the single newest event, and only walks the --limit window when that newest event is itself missing its row (the signature of a crash at the tail). Reconciliation is idempotent — the composite unique index on (message_id, target_broker) means running it repeatedly, or concurrently with the startup sweep, never duplicates a row.

Only the internal-broker row is reconciled. External published-broker rows (from [outbox].external_brokers) are re-derived by the outbox processor once the internal row is published, and are out of scope for this command.

Automatic startup sweep

The same reconciliation runs once automatically when the server boots, so a crash before the relational commit self-heals on restart without operator action:

protean server --domain=my_domain

The sweep is gated on the outbox being enabled, is cheap in the common case (the newest-event check above), and can never block startup — a failure during the sweep is logged and boot continues. With --workers N it runs once per worker; the idempotent index makes the overlap safe.

Error Handling

Condition Behavior
Invalid domain path Aborts with "Error loading Protean domain"
Outbox not enabled for the domain Aborts with "Outbox is not enabled for this domain"
Nothing to reconcile Prints "Nothing to reconcile: the outbox is consistent with the event store"

How reconciliation works

The commit sequence appends events to the event store before committing the relational transaction that carries aggregate state and the outbox rows, so the event store is the durable anchor. A crash in the window between the two leaves events stored but their outbox rows uncommitted. Reconciliation reads those events back from the store and re-derives the missing rows. The full rationale, including why this ordering was chosen over two-phase commit, is in ADR-0015: Event-Store Append as the Durable Anchor.

See the Outbox Guide for the operational walkthrough.

Domain Discovery

The protean outbox commands use the same domain discovery mechanism as other CLI commands. See Domain Discovery for the full resolution logic.