Status: Planned
Version: 1.0
Date: 2025-12-28
Priority: High
Satisfies: ADR-032 (NATS JetStream)
| 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 |
| 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) |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |