Appearance
Authority Doc / Public V1 Backend Events
The Event And Outbox Standard keeps backend side effects reliable without pretending delivery is exactly-once.
This page is the server-side source of truth for domain and integration event naming, envelope fields, transactional outbox persistence, publisher behavior, consumer idempotency, projection rebuild rules, dead-letter handling, and backend evidence gates for Public V1.
AuthorityBackend EventsTransactional OutboxPublic V1
Note
This page covers backend/server behavior only. Mobile operation queues and reconciliation belong to offline-sync-protocol.
Server Scope
Keep event authority on the server boundary. It coordinates persistence and delivery, not mobile local sync design.
This Standard Owns
- Server-side domain, integration, and job event categories.
- Event names, versioning, and required envelope fields.
- Transactional outbox persistence, publisher lifecycle, job lookup facts, and required claim/publish/retry/dead-letter transition evidence.
- Consumer dedupe, projection rebuild rules, and dead-letter handling.
- Feature-page event documentation and test expectations.
This Standard Does Not Own
- REST request and response contracts owned by api-contract-standard.
- Mobile local queue schemas, device receipts, and replay flows owned by offline-sync-protocol.
- Provider-specific webhook payload formats outside normalized internal events.
- Analytics visualization rules beyond the event facts they consume.
- Operational event names, metric names, aggregation, dashboards, and alerts owned by observability-plan.
Note
Server outbox is not the same thing as mobile local outbox.
Event Categories
Use one vocabulary for why a record exists before arguing about brokers, queues, or handlers.
| Category | Purpose | Rule |
|---|---|---|
| Domain event | Captures a business fact inside the bounded context. | Domain events describe past business facts and are emitted from aggregate decisions after state is accepted. |
| Integration event | Communicates a stable fact to another context or delivery concern. | Integration events are stable cross-context facts and must avoid leaking persistence or request-shape details. |
| Job command | Triggers operational work such as sending, materializing, or recalculating. | Job commands are operational instructions and must remain idempotent because retries are normal. |
Naming And Versions
Names should tell operators what happened without opening the payload.
Naming Rule
Use context.aggregate.fact.v1 for published event names.
client.invitation.consumed.v1client.relationship.activated.v1daily_execution.workout_log.completed.v1nutrition.meal_swap.approved.v1
Versioning Rules
- Breaking shape or semantic changes require a new event version and a new event name suffix.
- Additive fields stay in the current version when consumers can safely ignore them.
- Consumers must ignore unknown fields and keep required-field validation explicit.
Publication Scope
Only accepted operations publish. A rejected or conflicted sync operation produces a durable receipt and no event.
Envelope
Every published event needs a stable shell before payload-specific fields are considered safe.
| Field | Requirement | Rule |
|---|---|---|
eventId | Required | Stable unique identifier for the published event record. |
eventName | Required | Canonical event name such as client.relationship.activated.v1. |
eventVersion | Required | Explicit numeric or string version aligned with the name suffix. |
occurredAt | Required | UTC instant when the business fact happened. |
recordedAt | Required | UTC instant when the outbox record was persisted. |
producer | Required | Identifies the bounded context or module that emitted the event. |
aggregateType | Required | Named aggregate or source entity category. |
aggregateId | Required | Stable id for the source aggregate instance. |
workspaceId | Required when applicable | Workspace diagnostic scope when a workspace owns the event. |
relationshipId | Required when applicable | Relationship diagnostic scope when a coach-client relationship owns the event. |
actor | Required | Structured actor summary for user, service, or system initiator. |
correlationId | Required | API-issued identifier for one bounded product workflow, propagated across requests, events, jobs, and provider calls; distinct from W3C traceId. |
causationId | Required | Parent command, request, or event id that caused this publication. |
idempotencyKey | Required when applicable | Operation dedupe key used by the originating command. |
payload | Required | Business payload with only fields needed by approved consumers. |
metadata | Required | Non-business delivery metadata, diagnostics, and schema hints. |
Producers and consumers propagate the validated API-issued correlationId. They never synthesize a replacement from an untrusted client field.
Note
Payloads must not include bearer tokens, signed upload URLs, raw provider secrets, password/auth material, or unnecessary health/nutrition/media content.
Outbox Persistence
The outbox is a database fact table for reliable publication, not a magical delivery guarantee.
outbox_events
Persist integration events in outbox_events inside the same business transaction that accepted the aggregate change.
| Minimum Column | Purpose |
|---|---|
id | Primary row identifier for internal storage and admin tooling. |
event_id | Stable unique event identifier. |
event_name | Canonical event name. |
event_version | Explicit schema version. |
producer | Bounded context or module that emitted the event. |
aggregate_type | Producer aggregate category. |
aggregate_id | Producer aggregate id. |
workspace_id | Workspace diagnostic scope. |
relationship_id | Relationship diagnostic scope. |
correlation_id | API-issued identifier for one bounded product workflow, carried through chained work and distinct from W3C traceId. |
causation_id | Parent command, request, or event id that caused publication. |
idempotency_key | Originating operation dedupe key when one exists. |
payload jsonb | Structured business payload or full event envelope body. |
metadata jsonb | Delivery metadata, diagnostics, and schema hints. |
status | Publication lifecycle state. |
available_at | When the publisher may try again. |
attempt_count | Retry counter. |
last_attempt_at | UTC time of the latest publish attempt. |
published_at | Success time when published. |
locked_by | Current worker lease owner when the row is claimed. |
locked_until | Lease expiry time so stalled claims can recover. |
last_error_code | Latest retry or dead-letter code. |
last_error_message | Latest operator-readable failure detail. |
created_at | Insert time. |
updated_at | Last lifecycle update time. |
| Status | Meaning |
|---|---|
PENDING | Ready for the publisher scan. |
CLAIMED | A publisher worker has leased the row for an attempt. |
PUBLISHED | Broker handoff succeeded and the row was marked complete. |
FAILED_RETRYABLE | Attempt failed but another bounded retry is allowed. |
DEAD_LETTERED | Automatic retries stopped and operator review is required. |
DISCARDED | Operator intentionally retired the row with an auditable reason. |
Constraints
- Unique
event_idconstraint is mandatory. - Producer dedupe must prevent duplicate outbox inserts for the same accepted operation.
- For sync-originated commands the originating
clientOperationIdis the eventidempotencyKey, and the durable sync receipt is the producer dedupe record: a replayed operation returns its retained receipt and inserts no second outbox row. - Pending scans need an index on status plus availability time.
- Replay and operator tooling need indexes for status, event name, and published time.
- Workspace and relationship diagnostics need searchable scope columns.
Persistence Posture
The outbox row is durable evidence that the backend accepted responsibility to publish. It is not proof that every downstream consumer processed the event.
Transaction Rule
Never save business state and publication intent in separate commit boundaries.
Required Rule
The business state change and outbox insert commit in the same database transaction.
await tx.run(async () => {
const result = aggregate.accept(command);
await repository.save(result.aggregate);
await outbox.save(result.integrationEvents);
});Publisher Behavior
Publisher workers make progress through repeated, observable, bounded attempts.
1. Scan
Find PENDING or retryable rows whose availability time has arrived.
Scan
2. Claim
Lease rows into CLAIMED so parallel workers do not publish the same attempt blindly.
Claim
3. Publish
Send the event envelope to the broker or internal bus.
Publish
4. Resolve
Mark success, schedule a bounded retry, or dead-letter with logs and metrics.
Resolve
Reliability Rules
- Delivery is at-least-once, never exactly-once by assumption.
- Retry delays use bounded backoff with clear attempt ceilings.
- Every transition emits the required lifecycle fact with transition, outcome, event family, attempt/age, and approved lookup identifiers as applicable. Exact log-event and metric names are canonical in the Observability Plan.
- A crash after publish but before marking success can cause duplicate delivery, so consumers must tolerate repeats.
Runtime Job Lookup
jobId is the queue-adapter-normalized BullMQ runtime lookup id included with attempt number in each job completion/failure log. It is not a business identifier, event-envelope field, outbox column, domain record, or metric label.
Failure Outcome
Publisher failures must preserve the row, increment attempts, keep the latest error code, and move irrecoverable rows to dead-letter instead of silently dropping them.
Ordering
Ordering guarantees must be named precisely because brokers, workers, and retries all reorder work.
What Is Not Guaranteed
- Global ordering is not guaranteed.
- Cross-aggregate publication order is not guaranteed.
- Retry timing can place an older event behind a newer one.
What Must Be Explicit
- Per-aggregate ordering requires an explicit sequence or partition strategy.
- Consumers must no-op safely when stale or already-applied events arrive late.
- Feature pages must call out any ordering key needed for correctness.
Consumers
Consumers prove correctness by making duplicates boring.
Dedupe Rule
Consumers require processed-event receipts by eventId or a deterministic consumer dedupe key.
Success Rule
Duplicate delivery returns success or no-op rather than a noisy failure.
Atomic Consumer State
Projection or job state changes and the processed-event receipt save together in one transaction.
Projections
Read models are disposable if their rebuild source is explicit and durable.
| Store | Role | Rule |
|---|---|---|
| Source aggregate tables | Canonical business state. | Own validation and lifecycle truth. |
| Outbox delivery records | Reliable publication evidence. | Track event delivery lifecycle. |
| Analytics or event fact tables | Long-lived event-derived history. | Preserve durable analytical facts when feature analytics require them. |
| UI read models | Query-optimized views. | Feature pages must name the rebuild source so the model can be regenerated. |
Dead Letters
Dead letters are an operational queue with audit needs, not a hidden graveyard.
Authorized Operator Boundary
Inspect, retry, replay, or discard only through an approved operator mechanism. Record actor, reason, time, and target in audit evidence. Direct database editing is prohibited. Observability detects and links to this mechanism but does not authorize or perform recovery.
Recorded Facts
- Failure category.
- Last error code.
- Attempt count.
- Operator reason for replay or discard.
Replay Rules
- Replay of the same logical event uses the original
eventId. - A replacement event uses a new
eventId. - Discard must remain auditable with actor, reason, and time.
Security And Privacy
Events should carry just enough detail to do the job and no more.
Payload Minimization
- Prefer identifiers and normalized references over rich copies of private records.
- Use secure asset references instead of long-lived direct media URLs.
- Do not publish secrets, auth material, or provider internals into events.
Authority Hand-offs
Retention and purge rules belong to privacy-and-retention. Support and admin access rules belong to roles-and-permissions.
Feature-Page Rules
Every feature authority page that emits or consumes events must document the same checklist.
Required Checklist
- Emitted domain events.
- Promoted integration events.
- Consumed integration events.
- Ordering key or partition rule.
- Idempotency keys and dedupe strategy.
- Projection rebuild source.
- Dead-letter behavior.
- Retry and idempotency tests.
Testing Gate
Evidence must prove the event path from aggregate decision to duplicate-safe consumption.
| Required Evidence | What Must Pass |
|---|---|
| Domain event unit tests | Aggregate decisions emit the expected domain events for accepted state changes. |
| Transaction and outbox integration tests | Business state and outbox rows commit together. |
| Rollback tests | Failed transactions leave neither partial state changes nor stray outbox rows. |
| Publisher retry | Retryable publish failures reschedule with bounded backoff and preserved diagnostics. |
| Duplicate delivery | Crash-and-retry or duplicate publish attempts do not create unsafe double side effects. |
| Consumer idempotency | Receipts or dedupe keys make duplicates return success/no-op. |
| Dead-letter transition | Attempt ceilings move failed events into dead-letter with operator-readable facts. |
| Projection rebuild | Named rebuild sources can regenerate the read model. |
| Event schema or contract tests | Published envelopes match the approved name, version, and required fields. |
Related Docs
This standard depends on adjacent authority pages for workflow, permissions, REST contracts, sync boundaries, operations, and release proof.
Workflow State Map
Lifecycle transitions that often cause event publication.
Roles And Permissions
Backend authorization and support access boundaries for emitted facts.
API Contract Standard
REST request and response rules that hand work into backend events.
Offline Sync Protocol
Separate authority for mobile queues, receipts, replay, and conflict handling.
Integration Register
External systems and providers that may consume or produce normalized events.
Observability Plan
Canonical signal names, transition aggregation, dashboards, alerts, and operational detection; Event And Outbox retains lifecycle and recovery.
Test And Release Gate
CI, merge, and release evidence requirements for event-driven changes.