Type to search pages and rules.

Browse pages

Durable and Live Delivery

Committed changes reach the outside world through durable delivery that survives crashes and through live updates that reach open screens in seconds. This page states how the product stages after-commit effects in the outbox for a Worker to deliver and when it replaces polling with realtime transport. A team selects it for delivery that survives process failure or for updates a measured target requires live.

Markdown56 provisions

Stack: any Layer: infrastructure

Activation#

Activation scope: local.

Applicable specification kinds: use-case, journey.

A use case selects this extension when a committed change needs delivery that survives a process failure. That delivery can carry an integration event to another system, a command that advances a journey step, or a side effect on an outside provider. Selecting it also activates litespec/backend/architecture#declare-the-execution-host-for-work-that-outlives-a-request: the project declares the execution host that runs the dispatcher.

A use case or journey selects this extension for live updates after the team measures polling against the update target: the polling interval, the visible delay, the request cost, and the required update speed. The selection also names the accepted cost of disconnects and missed messages: how the client recovers when the connection drops or a notification never arrives.

Baseline Relationship#

This extension replaces litespec/backend/persistence#collect-events-without-a-public-unit-of-work.

This extension switches on no optional record type: integration events, settings, and runbooks belong to the core. The project writes an integration-event record for each integration event it delivers, setting records for the delivery bounds, and runbook records for recovery and rollback. Each pushed update is a notification record, and each resource bound lives in a setting record.

One-way notifications travel over server-sent events, which need no added package because the platform ships them.

Standards#

Stage After-Commit Effects in the Outbox#

ruleruleguidance
litespec/ext/outbox#stage-after-commit-effects-in-the-outbox
Statement

A use case declaring after-commit emits or runs effects MUST stage each effect in the outbox in the same transaction as its changes.

Applies toartifact: command-handler

Why

After-commit timing promises the message follows the commit: staged in the same transaction, the change and its message share one fate. An emits effect is a domain event the aggregate raises, and persistence stages that event in the outbox in the same commit as the aggregate, as litespec/backend/application#emit-declared-integration-events-after-commit requires. An effect sent from memory instead would vanish on a crash between the commit and the send. litespec/core/use-cases#declare-every-effect-with-one-verb-and-its-timing states the effect verbs and their timing, and async effects dispatch without the transaction.

Checka reviewer answers

Reviewed forEach after-commit emits and runs effect stages in the outbox in the same transaction as its changes, with each domain event staged in the same commit as its aggregate.

Marked guidance: a reviewer answers this question.

Declare Published Facts as Integration Events#

ruleruleguidance
litespec/ext/outbox#declare-published-facts-as-integration-events
Statement

Each emits effect MUST name an integration-event record carrying direction, payload, and trust.

Applies toartifact: record:integration-event

Why

A published fact without a record is a promise nobody versioned: readers depend on fields no page states, and a field change breaks them without a diff. The integration-event record states the direction, the payload with its classifications, and the trust readers place in it. The edges page states the declaration and the record.

Checka reviewer answers

Reviewed forEach emits effect names an integration-event record carrying direction, payload, and trust.

Marked guidance: a reviewer answers this question.

Record Durable Delivery Behavior#

ruleruleguidance
litespec/ext/outbox#record-durable-delivery-behavior
Statement

A durable-delivery specification MUST document delivery guarantee, downstream boundary, duplicate behavior, retry horizon, and recovery runbook.

Applies toartifact: any

Why

Delivery crosses a system boundary, so the team agrees up front on the guarantee, the boundary, the duplicate behavior, the retry horizon, and the runbook that recovers when delivery fails. Without that agreement, the writer assumes once-only delivery while the target assumes retries, and both discover the mismatch in production. The agreement lands before the first message does.

Checka reviewer answers

Reviewed forEach delivery specification documents guarantee, downstream boundary, duplicate behavior, retry horizon, and recovery runbook.

Marked guidance: a reviewer answers this question.

Exclude Manually Repeatable Reactions and Jobs#

ruleruleguidance
litespec/ext/outbox#exclude-manually-repeatable-reactions-and-jobs
Statement

A project MUST NOT activate outbox for a reaction or job whose loss the operators accept and which they can repeat manually.

Applies toartifact: reaction, job

Why

Outbox storage, dispatch, retries, and replay exist to prevent loss; a reaction or job whose loss the operators accept and can repeat by hand needs none of that machinery. The reaction's or job's onFailure states its handling, and the use case page records the accepted gap.

Checka reviewer answers

Reviewed forReactions and jobs whose loss the operators accept and can repeat manually carry no outbox records.

Marked guidance: a reviewer answers this question.

Emit Excluded Reactions and Jobs After Commit#

ruleruleguidance
litespec/ext/outbox#emit-excluded-reactions-and-jobs-after-commit
Statement

A reaction or job the outbox excludes MUST emit after its commit succeeds without staging an outbox record.

Applies toartifact: reaction, job

Why

Without an outbox record, nothing survives a crash between the commit and the emit, so the reaction or job emits immediately after its commit succeeds and accepts the gap. Emitting before the commit risks worse: it fires for a change the database then rolls back.

Checka reviewer answers

Reviewed forExcluded reactions and jobs emit after commit success with no staged outbox record.

Marked guidance: a reviewer answers this question.

Stage Messages with Business Work#

ruleruleguidance
litespec/ext/outbox#stage-messages-with-business-work
Statement

A command post-handler MUST stage pending durable messages in the same scoped business session as its aggregate changes.

Applies toartifact: any

Why

The business change and its message share one fate: both persist or neither does. Staging the message in the same scoped business session as the aggregate changes puts both under one commit, so a crash can never leave an accepted change without its message or a message without its change.

Checka reviewer answers

Reviewed forPending durable messages stage in the same scoped session as their aggregate changes.

Marked guidance: a reviewer answers this question.

Serialize Durable Messages#

ruleruleguidance
litespec/ext/outbox#serialize-durable-messages
Statement

An outbox writer MUST serialize each pending durable message into an outbox record.

Applies toartifact: any

Why

The command scope ends when the request ends, while dispatch happens later in a Worker that never saw the request. Serializing the message into an outbox record gives that Worker everything it needs after the scope is gone.

Checka reviewer answers

Reviewed forEach pending durable message lands serialized in an outbox record.

Marked guidance: a reviewer answers this question.

Store Required Message Fields#

ruleruleguidance
litespec/ext/outbox#store-required-message-fields
Statement

An outbox record MUST contain message identity, kind, stable type, schema version, time, payload, tenant, state, attempt, and next-attempt data.

Applies toartifact: any

Why

The dispatcher, the retry logic, and the operator each read the record: identity and type route it, the schema version selects its reader, time and tenant scope it, and state, attempt, and next-attempt data drive the retry. A field stored nowhere forces one of those readers to guess.

Checka reviewer answers

Reviewed forEach outbox record carries identity, kind, type, schema version, time, payload, tenant, state, attempt, and next-attempt data.

Marked guidance: a reviewer answers this question.

Store Failure and Completion Evidence#

ruleruleguidance
litespec/ext/outbox#store-failure-and-completion-evidence
Statement

An outbox record MUST retain its last error summary and completion time.

Applies toartifact: any

Why

When delivery stalls, the operator opens the record first: the last error summary says why the previous attempt failed, and the completion time says which records finished. Without that evidence on the record, each investigation starts from scattered logs instead.

Checka reviewer answers

Reviewed forEach outbox record retains its last error summary and completion time.

Marked guidance: a reviewer answers this question.

Classify Message Kinds#

ruleruleguidance
litespec/ext/outbox#classify-message-kinds
Statement

An outbox record MUST name its message kind as integration event, journey-step command, or provider side effect, with an integration event citing its integration-event record.

Applies toartifact: record:integration-event

Why

An integration event, a journey-step command, and a provider side effect dispatch through different owners and transports, so the record names which one it holds. The integration event cites its integration-event record, so the staged message traces to the published fact it carries.

Checka reviewer answers

Reviewed forEach outbox record names integration event, journey-step command, or provider side effect, with integration events citing their integration-event record.

Marked guidance: a reviewer answers this question.

Reject Independent Outbox Commits#

ruleruleguidance
litespec/ext/outbox#reject-independent-outbox-commits
Statement

An outbox writer MUST NOT use another connection or independent commit for required durable delivery.

Applies toartifact: any

Why

Two commits on two connections can split around a crash: the business change persists while the message rolls back, or the message persists for a change that never happened. Sharing one PostgreSQL database does not make separate transactions atomic, so the message joins the business transaction instead.

Checka reviewer answers

Reviewed forWriter sources stage through the business session with no separate connection or commit.

Marked guidance: a reviewer answers this question.

Dispatch Claimed Records in Worker#

ruleruleguidance
litespec/ext/outbox#dispatch-claimed-records-in-worker
Statement

A Worker MUST claim pending outbox records, dispatch a bounded batch, and record success or retry information.

Applies toartifact: any

Why

Dispatch needs time, retries, and network calls that no request transaction can hold. The Worker claims pending records, dispatches a bounded batch, and records each outcome, so the request commits fast while delivery proceeds on its own schedule.

Checka reviewer answers

Reviewed forThe Worker claims pending records, dispatches bounded batches, and records each outcome.

Marked guidance: a reviewer answers this question.

Exclude Request-Transaction Dispatch#

ruleruleguidance
litespec/ext/outbox#exclude-request-transaction-dispatch
Statement

WebApi MUST NOT dispatch a durable message inside the request transaction.

Applies toartifact: endpoint

Why

A network call inside the request transaction holds database locks while the outside world answers, and a slow target slows every request behind it. The request commits only to make the message eligible; the Worker performs the delivery later.

Checka reviewer answers

Reviewed forRequest paths commit eligibility only, with dispatch living in the Worker.

Marked guidance: a reviewer answers this question.

Define Claim Ownership#

ruleruleguidance
litespec/ext/outbox#define-claim-ownership
Statement

An outbox claim MUST use a short transaction with the lease owner, lease expiry, and fencing value that litespec/ext/jobs#store-execution-leases defines.

Applies toartifact: any

Why

Several Workers poll the same store, so each dispatch attempt needs one owner: the lease owner, the lease expiry, and the fencing value name it. The short transaction takes the claim without holding locks into the delivery itself.

Checka reviewer answers

Reviewed forEach claim takes owner, expiry, and fencing value in a short transaction.

Marked guidance: a reviewer answers this question.

Release Claim Transactions Before Calls#

ruleruleguidance
litespec/ext/outbox#release-claim-transactions-before-calls
Statement

A Worker MUST NOT hold an outbox claim transaction open during a network call.

Applies toartifact: any

Why

A network call to a slow target can last seconds or minutes. Holding the claim transaction open across that call pins database locks and connection slots, starving the Workers behind it; releasing the claim first lets the database serve other work while the call runs.

Checka reviewer answers

Reviewed forClaim transactions close before the Worker makes network calls.

Marked guidance: a reviewer answers this question.

Restrict Completion to Current Lease Owners#

ruleruleguidance
litespec/ext/outbox#restrict-completion-to-current-lease-owners
Statement

A Worker MUST update an outbox record only while it owns the matching lease and fencing value.

Applies toartifact: any

Why

A Worker that lost its lease, through a crash, a pause, or an expiry, can wake up after another Worker claimed the same record. Checking the lease and fencing value before each update stops the stale Worker from overwriting the current owner's result.

Checka reviewer answers

Reviewed forRecord updates succeed only under the current lease and fencing value.

Marked guidance: a reviewer answers this question.

Requeue Expired Claims#

ruleruleguidance
litespec/ext/outbox#requeue-expired-claims
Statement

An outbox store MUST return expired claims to eligible work.

Applies toartifact: any

Why

A Worker can die between claiming a record and dispatching it. Returning expired claims to eligible work lets a live Worker pick them up, so one crash delays a message instead of losing it.

Checka reviewer answers

Reviewed forExpired claims return to eligible work for a live Worker.

Marked guidance: a reviewer answers this question.

Accept Repeated Delivery#

ruleruleguidance
litespec/ext/outbox#accept-repeated-delivery
Statement

An event handler that receives an outbox delivery MUST handle the same message more than once.

Applies toartifact: reaction

Why

The target can accept a message while the Worker crashes before recording completion, and the requeued claim then dispatches it again. Delivery promises at-least-once, so every event handler handles the same message more than once without corrupting its state.

Checka reviewer answers

Reviewed forEach event handler keeps correct state when the same message arrives twice.

Marked guidance: a reviewer answers this question.

Identify Repeated Delivery#

ruleruleguidance
litespec/ext/outbox#identify-repeated-delivery
Statement

An event handler that receives an outbox delivery MUST use message ID or a business idempotency key at its Command or side-effect boundary.

Applies toartifact: reaction

Why

Handling a repeat means recognizing it: the message ID or a business idempotency key gives the event handler a stable identity to check at its command or side-effect boundary. Without that key, the handler processes the repeat as new work and pays, notifies, or ships twice.

Checka reviewer answers

Reviewed forEach event handler checks message ID or business key at its Command or side-effect boundary.

Marked guidance: a reviewer answers this question.

Deliver Each Event to Each Handler Separately#

ruleruleguidance
litespec/ext/outbox#deliver-each-event-to-each-handler-separately
Statement

The outbox MUST deliver an event to each of its handlers as a separate delivery with its own retries and dead letter.

Applies toartifact: any

Why

One delivery for all handlers retries every handler when one fails. Each handler therefore gets its own delivery with its own retries and dead letter, and each delivery still ends in repeat-safe handling under litespec/ext/outbox#accept-repeated-delivery.

Example

A loan-borrowed event feeds a due-notice handler and a statistics handler. The notice delivery retries on its own schedule while the statistics delivery stays complete.

Checka reviewer answers

Reviewed forEach event reaches each of its handlers as a separate delivery with its own retries and dead letter.

Marked guidance: a reviewer answers this question.

Await Target Acceptance#

ruleruleguidance
litespec/ext/outbox#await-target-acceptance
Statement

A Worker MUST NOT mark an outbox record complete before the target boundary accepts it.

Applies toartifact: any

Why

Marking a record complete before the target accepts it declares a delivery that never happened, and no retry follows because the record looks done. The Worker waits for the target boundary's acceptance before recording completion.

Checka reviewer answers

Reviewed forCompletion records follow target acceptance, with no early completion.

Marked guidance: a reviewer answers this question.

Bound Delivery Retries#

ruleruleguidance
litespec/ext/outbox#bound-delivery-retries
Statement

An outbox dispatcher MUST use bounded exponential backoff with jitter.

Applies toartifact: any

Why

A failing target with ten thousand pending messages meets a retry storm when every message retries at once on a fixed interval. Bounded exponential backoff with jitter spreads the retries over time and caps the total, so the target recovers instead of drowning.

Checka reviewer answers

Reviewed forRetries run bounded exponential backoff with jitter.

Marked guidance: a reviewer answers this question.

Classify Permanent Delivery Failure#

ruleruleguidance
litespec/ext/outbox#classify-permanent-delivery-failure
Statement

An outbox dispatcher MUST classify permanent failures, stop automatic retry at its retry bound, and retain safe investigation and replay data.

Applies toartifact: any

Why

Some failures never clear on retry: a deleted destination, a rejected payload, a revoked credential. Retrying those forever burns resources and buries the real backlog, so the dispatcher classifies them, stops at its retry bound, and keeps the evidence an operator needs to investigate and replay safely.

Checka reviewer answers

Reviewed forPermanent failures stop at the retry bound with safe investigation and replay data retained.

Marked guidance: a reviewer answers this question.

Distinguish Dependency Outage#

ruleruleguidance
litespec/ext/outbox#distinguish-dependency-outage
Statement

A dispatcher MUST distinguish an unavailable outbox store from a failed claimed message.

Applies toartifact: any

Why

A dead outbox store and a failed delivery ask for different recoveries: the first needs a database restored, the second needs one destination investigated. Each carries its own signal. The store's availability is a gauge the readiness endpoint reads, while a claimed message's outcome is a counter carrying the result, following the OpenTelemetry metrics specification. A single error counter cannot tell an operator whether to restore a database or investigate one destination.

Checka reviewer answers

Reviewed forStore availability reads as a gauge while claimed-message outcomes read as a counter.

Marked guidance: a reviewer answers this question.

Back off Unavailable-Store Polling#

ruleruleguidance
litespec/ext/outbox#back-off-unavailable-store-polling
Statement

A Worker MUST apply bounded backoff and rate-limited logging when its outbox store is unavailable.

Applies toartifact: any

Why

A Worker that polls a dead store on a tight loop writes an exception per iteration and fills disks and log quotas before anyone reads the first one. Bounded backoff with rate-limited logging keeps the Worker alive and quiet until the store returns.

Checka reviewer answers

Reviewed forPolling during a store outage backs off with rate-limited logging.

Marked guidance: a reviewer answers this question.

Restrict Message Retry to Claimed Records#

ruleruleguidance
litespec/ext/outbox#restrict-message-retry-to-claimed-records
Statement

A Worker MUST apply message retry and poison handling only to claimed outbox records.

Applies toartifact: any

Why

Retry and poison handling act on a claimed message; when the store itself is down, no claim exists and no message waits for delivery. Running retry logic against the outage double-counts attempts and can dead-letter messages whose only fault was an unreachable store.

Checka reviewer answers

Reviewed forRetry and poison handling touch claimed records only.

Marked guidance: a reviewer answers this question.

Gate Dispatch on Readiness#

ruleruleguidance
litespec/ext/outbox#gate-dispatch-on-readiness
Statement

A Worker host exposing readiness MUST gate dispatch and stop polling stores that cannot serve requests.

Applies toartifact: any

Why

A host that reports unready still serves traffic it cannot finish when its Workers keep polling dead stores. Gating dispatch on readiness stops the polling, so the host fails fast and recovers instead of churning against storage that cannot answer.

Checka reviewer answers

Reviewed forUnready hosts stop dispatch and dead-store polling.

Marked guidance: a reviewer answers this question.

Version Message Types#

ruleruleguidance
litespec/ext/outbox#version-message-types
Statement

An outbox message MUST use a stable type name and explicit schema version.

Applies toartifact: any

Why

An outbox record outlives the deployment that wrote it, so the dispatcher meets records written by several versions at once. A stable type name with an explicit schema version tells the dispatcher which reader parses each record; without the pair, one shape change breaks every record written before it.

Checka reviewer answers

Reviewed forEach message carries a stable type name with an explicit schema version.

Marked guidance: a reviewer answers this question.

Support Coexisting Message Producers#

ruleruleguidance
litespec/ext/outbox#support-coexisting-message-producers
Statement

A deployed dispatcher MUST process records from every application version that can coexist during rollout or rollback.

Applies toartifact: any

Why

During rollout and rollback, several application versions share one store and write different valid shapes into it. The dispatcher reads from every version that can coexist, or the deployment itself causes delivery failures.

Each message record therefore carries its own shape version, and the dispatcher selects a reader from that value. Adding an optional member with a defined absent-value behavior needs no new version. Removing a member, renaming one, narrowing a type, or changing a discriminator needs a new version, and the reader for each version stays until no record carries that version.

Checka reviewer answers

Reviewed forThe dispatcher reads records from every coexisting application version.

Marked guidance: a reviewer answers this question.

Publish Backlog Indicators#

ruleruleguidance
litespec/ext/outbox#publish-backlog-indicators
Statement

An outbox deployment MUST publish pending count, failed count, oldest pending age, attempts, dispatch duration, and success rate.

Applies toartifact: any

Why

Pending count, failed count, oldest pending age, attempts, dispatch duration, and success rate together show whether delivery keeps up: growth in the backlog or age reveals a stall, while duration and success rate reveal a struggling target. Without these indicators, the first sign of failure is a customer asking where their message went.

Checka reviewer answers

Reviewed forThe deployment publishes pending count, failed count, oldest pending age, attempts, dispatch duration, and success rate.

Marked guidance: a reviewer answers this question.

Set Delivery Alerts#

ruleruleguidance
litespec/ext/outbox#set-delivery-alerts
Statement

An outbox deployment MUST set alert thresholds as setting records from its use-case delivery target.

Applies toartifact: record:setting

Why

A shipment notice delayed an hour and a receipt delayed a week carry different business consequences, so each use case sets alert thresholds from its own delivery target. Setting records hold those thresholds, so the alert fires when delay passes what the business accepted.

Checka reviewer answers

Reviewed forAlert thresholds sit in setting records derived from each use-case delivery target.

Marked guidance: a reviewer answers this question.

Model Outbox Lifecycle States#

ruleruleguidance
litespec/ext/outbox#model-outbox-lifecycle-states
Statement

An outbox record MUST model its lifecycle as an abstract state base with one sealed record for each of pending, processing, dispatched, and dead-letter.

Applies toartifact: state-record

Why

pending becomes processing, and processing becomes dispatched, pending, or dead-letter, while audited replay moves dead-letter back to pending. Three of the four states carry data the others do not: processing holds a lease expiry, dispatched holds a dispatch time, and dead-letter holds a failure reason and an attempt count. A four-value label forces those onto nullable columns that mean something in one state each, which is the shape litespec/backend/domain#model-every-closed-set-of-domain-values-as-a-closed-type-hierarchy removes everywhere else.

Example

OutboxState.Processing carries one unexpired lease before a dispatcher records OutboxState.Dispatched or a retry state.

Checka reviewer answers

Reviewed forOutbox lifecycle states form sealed records over an abstract base with documented transitions.

Marked guidance: a reviewer answers this question.

Preserve Message Identity During Retry#

ruleruleguidance
litespec/ext/outbox#preserve-message-identity-during-retry
Statement

An outbox dispatcher MUST preserve message ID across retry and replay.

Applies toartifact: any

Why

The target suppresses a repeat by recognizing its identity, which works only when every retry and replay carries the same message ID. A dispatcher that mints a new ID per attempt turns each retry into a new message the target processes again.

Checka reviewer answers

Reviewed forMessage ID stays identical across retry and replay.

Marked guidance: a reviewer answers this question.

Audit Outbox Replay#

ruleruleguidance
litespec/ext/outbox#audit-outbox-replay
Statement

An outbox replay MUST record actor, reason, time, and failure without editing the payload: an audit record when audit is selected, a standalone record otherwise.

Applies toartifact: any

Why

A replay dispatches a message the system already handled or dead-lettered, so the team records who replayed it, why, when, and which failure it answers. Without that record, a duplicate delivery looks like a dispatcher bug instead of an operator's decision, and nobody can reconstruct the choice.

Checka reviewer answers

Reviewed forEach replay records actor, reason, time, and failure without editing the payload.

Marked guidance: a reviewer answers this question.

Protect Active Worker Compatibility#

ruleruleguidance
litespec/ext/outbox#protect-active-worker-compatibility
Statement

New WebApi code MUST emit a message only after every active Worker can read its type and schema version.

Applies toartifact: endpoint

Why

WebApi writers and Worker dispatchers deploy independently, so a writer can run ahead of the oldest active Worker. When the writer emits a shape that Worker cannot read, those messages dead-letter on arrival through no fault of their own.

Checka reviewer answers

Reviewed forEvery active Worker reads the type and schema version of new messages.

Marked guidance: a reviewer answers this question.

Deploy Readers Before Writers#

ruleruleguidance
litespec/ext/outbox#deploy-readers-before-writers
Statement

A release MUST deploy compatible readers before writers for additive event versions.

Applies toartifact: any

Why

In a mixed-version rollout, old and new records sit in one store. Deploying compatible readers before the writers that emit new shapes keeps every record dispatchable at every step; the reverse order strands new records until the readers catch up.

Checka reviewer answers

Reviewed forReleases deploy compatible readers before additive writers.

Marked guidance: a reviewer answers this question.

Plan Undispatched Rollback Records#

ruleruleguidance
litespec/ext/outbox#plan-undispatched-rollback-records
Statement

A rollback Runbook MUST include records created by the new writer but not yet dispatched.

Applies toartifact: record:runbook

Why

Rolling back the deployment does not roll back the store: records the new writer staged but nobody dispatched stay behind. The rollback Runbook names how the previous version handles them, or they sit undispatched with no owner and no reader.

Checka reviewer answers

Reviewed forThe rollback Runbook covers records the new writer staged but nobody dispatched.

Marked guidance: a reviewer answers this question.

Record Realtime Need#

ruleruleguidance
litespec/ext/outbox#record-realtime-need
Statement

A realtime use case MUST document polling behavior, measured cost, update latency, expected connections, and recovery behavior.

Applies toartifact: record:notification

Why

Open connections cost memory, CPU, and operating attention on every host, so the team writes down what polling costs, what the update target requires, and how many connections to expect before choosing transport. The use case page carries that record, so a later reader sees which measurements justified the choice.

Checka reviewer answers

Reviewed forThe use case page holds the polling record, measured cost, update target, expected connections, and recovery behavior.

Marked guidance: a reviewer answers this question.

Reject Cosmetic Realtime Adoption#

ruleruleguidance
litespec/ext/outbox#reject-cosmetic-realtime-adoption
Statement

A product MUST NOT activate realtime only to make an interface feel immediate.

Applies toartifact: record:notification

Why

Realtime transport that serves only a feeling of immediacy spends connection capacity and operating attention without moving any target. The extension asks for a measured update requirement first, so polling stays where polling suffices.

Checka reviewer answers

Reviewed forThe realtime decision cites a measured update requirement.

Marked guidance: a reviewer answers this question.

Use Server-Sent Events for Notifications#

ruleruleguidance
litespec/ext/outbox#use-server-sent-events-for-notifications
Statement

A one-way server notification use case MUST use server-sent events.

Applies toartifact: record:notification

Why

A notification that flows one way, from server to browser, needs no bidirectional channel with groups and fallback. Server-sent events carry it over plain HTTP, which passes through proxies and firewalls without extra negotiation.

Checka reviewer answers

Reviewed forEach one-way notification travels over the server-sent events boundary in code.

Marked guidance: a reviewer answers this question.

Use SignalR for Interactive Messaging#

ruleruleguidance
litespec/ext/outbox#use-signalr-for-interactive-messaging
Statement

A use case requiring bidirectional messages, server groups, or transport fallback MUST use SignalR.

Applies toartifact: record:notification

Why

Two-way messages, server-managed groups, and transport fallback need a channel built for interaction, which one-way events cannot provide. SignalR supplies all three, so the use case that needs them takes SignalR instead of stretching events past their shape.

Checka reviewer answers

Reviewed forEach bidirectional use case runs on SignalR groups, messages, or fallback in code.

Marked guidance: a reviewer answers this question.

Record Transport Support#

ruleruleguidance
litespec/ext/outbox#record-transport-support
Statement

A realtime use case MUST record its transport choice and expected hosted-platform support.

Applies toartifact: record:notification

Why

Hosted platforms limit connection duration, idle timeouts, and transport upgrades in ways that differ per host. Recording the transport choice with its expected platform support surfaces the mismatch before deployment instead of during the first long-lived connection.

Checka reviewer answers

Reviewed forThe use case page names its transport choice with the expected hosted-platform support.

Marked guidance: a reviewer answers this question.

Declare Realtime Freshness#

ruleruleguidance
litespec/ext/outbox#declare-realtime-freshness
Statement

A query served over realtime MUST declare a freshness object with mode of immediate or advisory on its output.

Applies toartifact: record:notification

Why

A realtime channel delivers an update seconds after the change, so the served query promises {"mode": "immediate"}; where the pushed value still awaits a later decision, it promises {"mode": "advisory", "authority": "<record id>"} naming the record that holds the authoritative value. Without the declared freshness, a reader cannot tell whether the live value is final or provisional.

Checka reviewer answers

Reviewed forWhich realtime-served queries lack an immediate or advisory freshness object on their output? Look at the served query beside the realtime use case.

Marked guidance: a reviewer answers this question.

Authenticate and Authorize Subscriptions#

ruleruleguidance
litespec/ext/outbox#authenticate-and-authorize-subscriptions
Statement

A realtime server MUST authenticate each connection and authorize every user-, tenant-, or resource-scoped subscription.

Applies toartifact: record:notification

Why

Connecting proves who the caller is; it says nothing about which user's, tenant's, or resource's updates the caller may hear. Each user-, tenant-, or resource-scoped subscription checks its own authorization, so one valid connection cannot listen in on another tenant's data.

Checka reviewer answers

Reviewed forThe connection tests show authenticated connections only, with each scoped subscription authorized.

Marked guidance: a reviewer answers this question.

Revalidate Long-Lived Access#

ruleruleguidance
litespec/ext/outbox#revalidate-long-lived-access
Statement

A realtime server MUST revalidate long-lived access according to token and session lifetime.

Applies toartifact: record:notification

Why

A connection can outlive the grant that opened it: tokens expire, roles change, sessions end. Revalidating access on token and session lifetime closes the connection whose grant died instead of streaming updates to a caller who no longer qualifies.

Checka reviewer answers

Reviewed forA connection whose token or session expired loses access in the revalidation tests.

Marked guidance: a reviewer answers this question.

Reconnect and Refresh Authoritative State#

ruleruleguidance
litespec/ext/outbox#reconnect-and-refresh-authoritative-state
Statement

A realtime client MUST reconnect with bounded backoff and refresh authoritative state after a connection gap.

Applies toartifact: record:notification

Why

Connections drop, and a notification sent during the gap never arrives. Bounded backoff reconnects without hammering the server, and refreshing authoritative state after the gap replaces whatever the missed notifications would have changed.

Checka reviewer answers

Reviewed forThe client reconnects with bounded backoff and refreshes authoritative state after a gap in the recovery tests.

Marked guidance: a reviewer answers this question.

Tolerate Notification Ordering Differences#

ruleruleguidance
litespec/ext/outbox#tolerate-notification-ordering-differences
Statement

A realtime client MUST safely handle duplicate and out-of-order notifications.

Applies toartifact: record:notification

Why

The transport can deliver a notification twice or deliver two of them out of order. A client that applies each notification as an absolute update corrupts its state on the first duplicate; a client built for repeats and reordering converges on the truth either way.

Checka reviewer answers

Reviewed forDuplicate and reordered notifications leave client state correct in the client tests.

Marked guidance: a reviewer answers this question.

Bound Realtime Resources#

ruleruleguidance
litespec/ext/outbox#bound-realtime-resources
Statement

A realtime deployment MUST set four setting-record bounds: concurrent connections per host, receive message size, parallel invocations per connection, and client timeout.

Applies toartifact: record:notification

Why

Each open connection holds memory, each message costs CPU, and a flood of either starves the host. The deployment sets four bounds as setting records - concurrent connections per host, receive message size, parallel invocations per connection, and client timeout - with each value drawn from the deployment's own connection count and message size and the observation that produced it. The platform's realtime security guidance names the same settings.

Checka reviewer answers

Reviewed forThe four deployment bounds live as setting records with their values beside the deployment.

Marked guidance: a reviewer answers this question.

Reject Unbounded Slow-Client Buffering#

ruleruleguidance
litespec/ext/outbox#reject-unbounded-slow-client-buffering
Statement

A realtime server MUST NOT buffer an unbounded stream for a slow client.

Applies toartifact: record:notification

Why

A client that reads slower than the server writes grows its buffer without end, consuming host memory meant for every subscriber. Bounded buffering with backpressure disconnects or slows the laggard instead of letting one slow reader starve the rest.

Checka reviewer answers

Reviewed forSlow-client buffering in the server code is bounded with backpressure.

Marked guidance: a reviewer answers this question.

Conventions#

Group Outbox Infrastructure#

conventiondefaultguidance
litespec/ext/outbox#group-outbox-infrastructure
Default

Keep outbox record, storage, claim, and dispatch infrastructure under Infrastructure/Messaging/Outbox/.

ReplacementA project names another home for outbox infrastructure in its documentation.

Applies toartifact: any

Why

The outbox record, storage, claim, and dispatch code form one mechanism with one owner. Keeping it under Infrastructure/Messaging/Outbox/ puts every persistent dispatch question in one folder instead of scattered across the project.

Checka reviewer answers

Reviewed forOutbox infrastructure sits under Infrastructure/Messaging/Outbox/, or the project names another home in its documentation.

Marked guidance: a reviewer answers this question.

Isolate the Worker Host#

conventiondefaultguidance
litespec/ext/outbox#isolate-the-worker-host
Default

Keep the outbox Worker host in its own project.

ReplacementA project hosts outbox dispatch inside its request host.

Applies toartifact: any

Why

The Worker runs as its own process with its own lifetime, scaling, and configuration, apart from the request host. Its own project keeps that independence visible and stops request concerns leaking into dispatch configuration.

Checka reviewer answers

Reviewed forThe outbox Worker host lives in its own project, or the project hosts dispatch inside its request host.

Marked guidance: a reviewer answers this question.

Keep Provider Mapping with Integration#

conventiondefaultguidance
litespec/ext/outbox#keep-provider-mapping-with-integration
Default

Keep Integration Event-to-provider mapping in the integration that owns the side effect.

ReplacementA project keeps provider mapping in the shared dispatch path.

Applies toartifact: any

Why

Each provider speaks its own transport with its own mapping, retries, and quirks. The integration that owns the side effect owns that mapping, so a provider change touches one integration instead of the shared dispatch path.

Checka reviewer answers

Reviewed forProvider mapping sits in the owning integration, or the project keeps it in the shared dispatch path.

Marked guidance: a reviewer answers this question.

Dispatch Journey-Step Commands in a New Scope#

conventiondefaultguidance
litespec/ext/outbox#dispatch-journey-step-commands-in-a-new-scope
Default

Dispatch a journey-step command through ICommandMediator in a fresh Worker scope.

ReplacementA project runs journey-step commands inline in the dispatcher scope.

Applies toartifact: any

Why

A journey-step command is a full command with validation, authorization, and its own transaction, not a subroutine of the dispatcher. Sending it through ICommandMediator in a fresh Worker scope runs the whole application pipeline for it.

Checka reviewer answers

Reviewed forJourney-step commands run through ICommandMediator in a fresh Worker scope, or the project runs them inline in the dispatcher scope.

Marked guidance: a reviewer answers this question.

Avoid Automatic Durable-Bus Adoption#

conventiondefaultguidance
litespec/ext/outbox#avoid-automatic-durable-bus-adoption
Default

Use project-owned Marten records and Worker dispatch unless a manifest-pinned durable package passes a shared-transaction integration test.

ReplacementA project names a durable bus package as its dispatch transport in its documentation.

Applies toartifact: any

Why

A durable bus package earns its place only when an integration test proves it commits the message in the same transaction as the business work. Until that evidence exists, project-owned Marten records with Worker dispatch carry delivery without depending on behavior nobody verified.

Checka reviewer answers

Reviewed forDispatch runs on project-owned Marten records, or the project names a pinned durable bus package as its transport in its documentation.

Marked guidance: a reviewer answers this question.

Place Realtime Code at Outer Boundaries#

conventiondefaultguidance
litespec/ext/outbox#place-realtime-code-at-outer-boundaries
Default

Keep hubs or endpoints in WebApi, publication adapters in Infrastructure, and browser connections under lib/realtime/.

ReplacementA consumer records a realtime-placement convention naming where its transport code lives.

Applies toartifact: record:notification

Why

Hubs, endpoints, publication adapters, and browser connections deal in transport and provider detail, not business rules. Keeping them at the outer boundaries leaves business features owning only the narrow functions they call.

Checka reviewer answers

Reviewed forRealtime transport code sits at the documented boundaries, or the realtime-placement convention names where it lives.

Marked guidance: a reviewer answers this question.

Subscribe Through Narrow Functions#

conventiondefaultguidance
litespec/ext/outbox#subscribe-through-narrow-functions
Default

Let feature modules subscribe through narrow use-case-specific functions.

ReplacementA consumer records a realtime-subscription convention naming how its features subscribe.

Applies toartifact: record:notification

Why

A feature that touches the raw connection spreads subscription strings and payload shapes through business code. Narrow use-case-specific functions show each subscription's inputs and effects beside the feature that owns them.

Checka reviewer answers

Reviewed forFeatures subscribe through use-case-specific functions, or the realtime-subscription convention names how they subscribe.

Marked guidance: a reviewer answers this question.

Dependencies#

Packages: Selecting SignalR adds @microsoft/signalr. The project records a decision for the package and pins it in the manifest.

Related extensions: integrations, jobs.

Guides: None.