PRD-022: Reliable Cross-Context Messaging Platform

Status: Planned
Version: 1.0
Date: 2025-12-28
Priority: High
Satisfies: ADR-032 (NATS JetStream)


Overview


Requirements

REQ-050: Transactional Outbox

Field Value
REQ-ID REQ-050
Type functional
EARS When a bounded context commits a state change, the system SHALL atomically insert an outbox event in the same transaction.
Bounded-Context sea, vibespro
Data Touched outbox_events table
Invariants INV-022

Acceptance Criteria:

1
2
3
4
5
Given a command that modifies domain state
When the command handler commits
Then the state change and outbox event are in the same transaction
And if the transaction fails, neither is persisted
And INV-022 (atomic state+event) holds

Idempotency Requirement: | Aspect | Specification | |——–|—————| | Repeatable? | yes | | Safe to retry? | yes | | Dedup strategy | outbox event UUID |


REQ-051: Inbox Idempotency

Field Value
REQ-ID REQ-051
Type functional
EARS When an event is received, the system SHALL record it in inbox before processing to ensure exactly-once semantics.
Bounded-Context sea, vibespro
Data Touched inbox_messages table
Invariants INV-021

Acceptance Criteria:

1
2
3
4
5
Given an incoming event message
When the consumer attempts to process
Then it first inserts into inbox_messages with message_id PK
And if conflict (duplicate), processing is skipped
And INV-021 (exactly-once processing) holds

Idempotency Requirement: | Aspect | Specification | |——–|—————| | Repeatable? | yes | | Safe to retry? | yes | | Dedup strategy | inbox PK (message_id) |


REQ-052: JetStream Redelivery

Field Value
REQ-ID REQ-052
Type functional
EARS When processing fails transiently, the system SHALL allow JetStream to redeliver the message.
Bounded-Context sea, vibespro
Data Touched N/A
Invariants INV-020

Acceptance Criteria:

1
2
3
4
5
6
Given a message being processed
When the handler returns 5xx or times out
Then the consumer does NOT ACK the message
And JetStream redelivers after ack_wait expires
And the message appears again for processing
And INV-020 (at-least-once delivery) holds

Idempotency Requirement: | Aspect | Specification | |——–|—————| | Repeatable? | yes | | Safe to retry? | yes | | Dedup strategy | JetStream + inbox |


REQ-053: Dead Letter Queue

Field Value
REQ-ID REQ-053
Type functional
EARS When processing fails as poison (422), the system SHALL route to DLQ and ACK the original.
Bounded-Context sea, vibespro
Data Touched DLQ stream
Invariants POL-022

Acceptance Criteria:

1
2
3
4
5
6
Given a message with invalid payload or schema mismatch
When the handler returns 422 Unprocessable
Then the consumer publishes to DLQ subject
And marks original message as failed in inbox
And ACKs the original message (prevents infinite retry)
And POL-022 (poison handling) holds

Idempotency Requirement: | Aspect | Specification | |——–|—————| | Repeatable? | yes | | Safe to retry? | yes | | Dedup strategy | DLQ message ID |


REQ-054: Publisher Deduplication

Field Value
REQ-ID REQ-054
Type functional
EARS When publishing to JetStream, the system SHALL set Nats-Msg-Id header for deduplication.
Bounded-Context sea, vibespro
Data Touched JetStream stream
Invariants INV-023

Acceptance Criteria:

1
2
3
4
5
Given an outbox event to publish
When the publisher sends to JetStream
Then the Nats-Msg-Id header is set to outbox.id (UUID)
And JetStream deduplicates within its window
And republishes after crash don't create duplicates

Idempotency Requirement: | Aspect | Specification | |——–|—————| | Repeatable? | yes | | Safe to retry? | yes | | Dedup strategy | Nats-Msg-Id header |


REQ-055: Per-Context Streams

Field Value
REQ-ID REQ-055
Type constraint
EARS Each bounded context SHALL own its own JetStream stream with clear subject namespace.
Bounded-Context sea, vibespro
Data Touched JetStream streams
Invariants N/A

Acceptance Criteria:

1
2
3
4
5
Given SEA™ and VibesPro™ contexts
When configuring JetStream
Then SEA_EVENTS stream captures sea.event.>
And VIBESPRO_EVENTS stream captures vibespro.event.>
And each context owns retention/limits for its stream

Idempotency Requirement: | Aspect | Specification | |——–|—————| | Repeatable? | yes | | Safe to retry? | yes | | Dedup strategy | N/A |


REQ-056: Pull-Based Consumers

Field Value
REQ-ID REQ-056
Type constraint
EARS Consumers SHALL use pull-based subscription with explicit ACKs for backpressure control.
Bounded-Context sea, vibespro
Data Touched Consumer state
Invariants N/A

Acceptance Criteria:

1
2
3
4
5
Given a Rust consumer worker
When fetching messages from JetStream
Then it uses pull-based fetch with batch size
And explicitly ACKs after successful processing
And can control concurrency via max_ack_pending

Idempotency Requirement: | Aspect | Specification | |——–|—————| | Repeatable? | yes | | Safe to retry? | yes | | Dedup strategy | N/A |


REQ-057: Event Versioning

Field Value
REQ-ID REQ-057
Type constraint
EARS Event subjects SHALL include version suffix and payload meaning MUST NOT change for a given version.
Bounded-Context All
Data Touched Event schemas
Invariants INV-023

Acceptance Criteria:

1
2
3
4
5
6
Given an event type vibe_created
When publishing
Then subject is vibespro.event.vibe_created.v1
And .v1 payload schema is immutable
And breaking changes require .v2
And consumers can handle multiple versions

Idempotency Requirement: | Aspect | Specification | |——–|—————| | Repeatable? | yes | | Safe to retry? | yes | | Dedup strategy | N/A |


Success Metrics

KPI Target Measurement Method
Message delivery rate 99.99% outbox published / total
Duplicate processing rate 0% inbox conflicts / total
DLQ rate < 0.1% DLQ count / total
End-to-end latency p99 < 500ms event timestamp diff

Dependencies