ADR-032: NATS JetStream Messaging Architecture

Status: Proposed
Version: 1.0
Date: 2025-12-28
Supersedes: N/A
Related ADRs: ADR-033 (Kernel-Shell), ADR-030 (VibesPro™ Foundation)
Related PRDs: PRD-022


Context

SEA™ and VibesPro™ are separate bounded contexts with:

These contexts need reliable communication for:

The transcript evaluated multiple options:

Decision

We adopt NATS JetStream with the following architecture:

Per-Context Streams

Stream Owner Subjects Purpose
SEA_EVENTS SEA™ sea.event.> Events emitted by SEA™
VIBESPRO_EVENTS VibesPro™ vibespro.event.> Events emitted by VibesPro™

Subject Convention

1
{context}.event.{snake_case_name}.v{n}

Examples:

Rule: Never change meaning of .v1 once emitted. Create .v2 on breaking changes.

Outbox + Inbox Pattern

Outbox (per context DB):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CREATE TABLE outbox_events (
  id UUID PRIMARY KEY,
  aggregate_type TEXT NOT NULL,
  aggregate_id TEXT NOT NULL,
  event_type TEXT NOT NULL,
  payload JSONB NOT NULL,
  occurred_at TIMESTAMPTZ NOT NULL,
  correlation_id UUID,
  causation_id UUID,
  published_at TIMESTAMPTZ,
  publish_attempts INT DEFAULT 0,
  publish_error TEXT
);

CREATE INDEX idx_outbox_unpublished ON outbox_events (occurred_at)
  WHERE published_at IS NULL;

Inbox (per context DB):

1
2
3
4
5
6
7
8
CREATE TABLE inbox_messages (
  message_id UUID PRIMARY KEY,
  subject TEXT NOT NULL,
  received_at TIMESTAMPTZ NOT NULL,
  processed_at TIMESTAMPTZ,
  attempts INT DEFAULT 0,
  last_error TEXT
);

Pull-Based Consumers

1
2
Durable Consumer: {target_context}__from_{source_context}
Example: sea__from_vibespro
Setting Value Rationale
Ack Policy Explicit Reliable processing
Ack Wait 60-120s Handle slow handlers
Max Deliver 10-20 DLQ after failures
Max In-Flight 10-50 Backpressure control

Publish Deduplication

Use Nats-Msg-Id header set to outbox event UUID:

Rationale

  1. NATS JetStream advantages:

  2. Per-context streams:

  3. Outbox pattern:

  4. Inbox pattern:

Constraints

MUST

MUST NOT

Isomorphic Guarantees

Spec Concept SEA-DSL Target Mapping
OutboxEvent Entity node 1:1 field mapping
InboxMessage Entity node 1:1 field mapping
JetStream stream Infrastructure config Per-context
Subject convention Event contract Schema preservation

System Invariants

INV-ID Invariant Type Enforcement
INV-020 At-least-once delivery System JetStream redelivery
INV-021 Exactly-once processing System Inbox PK constraint
INV-022 State and event atomically written Process Same DB transaction
INV-023 Events versioned immutably Contract Subject naming

Quality Attributes

Attribute Target Rationale
Reliability Zero lost events Outbox guarantees
Idempotency No duplicate processing Inbox PK
Latency p99 < 500ms event delivery Pull batch efficiency
Recoverability Full replay from stream JetStream persistence

Bounded Contexts Impacted

Consequences

Positive

Negative

Mitigations


ACK Rules (Critical)

When Rust consumer pulls a message:

  1. Extract message_id from Nats-Msg-Id header
  2. INSERT INTO inbox_messages ... ON CONFLICT DO NOTHING
  3. If conflict → ACK immediately (already processed)
  4. Call handler (FastAPI /handle)
  5. Response handling: