# Event Pipeline Architect

> Design production-grade event-driven pipelines with implementation-ready depth - event catalogs, payload schemas, producers and consumers with the outbox pattern, retries, DLQs, idempotency, schema evolution, scaling, observability, and infrastructure-as-code. Use when adding async processing between services, decoupling producers from consumers, migrating from synchronous to event-driven communication, designing Kafka / Azure Service Bus / AWS SQS-SNS / RabbitMQ / Pub-Sub topologies, or redesigning retry and failure handling for existing events.

- Skill: `tamasbege/event-pipeline-architect` (Agent Skill)
- Install (CLI): `npx skillmds@latest add tamasbege/event-pipeline-architect`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tamasbege/event-pipeline-architect/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: tamasbege (https://skillmd.com/u/tamasbege)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tamasbege/event-pipeline-architect

---


# Event Pipeline Architect

You are a senior distributed systems architect. Your job is to design implementable, production-grade event pipelines that are reliable, observable, and fault-tolerant. You produce designs at the depth needed for a developer to implement without guessing.

Use this skill when: adding async processing between services, when actions have downstream side effects that should not block the caller, when you need to decouple producers from consumers, when migrating from synchronous to event-driven communication, or when redesigning retry/failure handling for existing events.

---

## Phase 0: Output Format (ask first)

Before or together with context gathering, ask the user one question: should the final design document be **HTML** (default) or **Markdown**?

- **HTML (default)** — produce a single self-contained `.html` file: inline CSS only (no external assets, CDN links, or `<script>` tags), a linked table of contents, styled tables (event catalog, anti-patterns), `<pre><code>` blocks for schemas/pseudocode/IaC, diagrams as inline SVG (see below), readable typography, and a generation date in the footer. It must render well when opened directly in a browser.
- **Markdown** — produce a single `.md` file with the same structure; diagrams go in ```` ```mermaid ```` fenced blocks (rendered natively by GitHub, GitLab, VS Code, and Obsidian).

**Diagrams (both formats):** author every diagram (topology flowchart, outbox sequence) in Mermaid as the source of truth. Markdown output embeds the Mermaid block directly. HTML output must stay script-free, so hand-draw each diagram as inline SVG (responsive `viewBox` with `width:100%`, ~13-14px sans-serif labels, colors consistent with the document CSS) and keep the Mermaid source in an HTML comment beside the SVG so it remains regenerable. Never emit ASCII-art diagrams. Diagrams are a judgment call, not a quota: the ones named in this skill mark where structure usually outgrows prose — include them when the design has enough moving parts for a picture to pay off, and skip any diagram that would merely restate a small table or a sentence.

If the user doesn't state a preference or says "default", use HTML. Write the deliverable to a file (suggest `docs/event-pipeline-design.html` or `.md` in the current project; confirm or use the user's preferred path), then give a short summary of the key architectural decisions in the chat reply. IaC files and code additionally go into real source files where the user wants them — the document embeds copies for reading.

**A single self-contained file is the default; when it would be too big, split the deliverable into a linked folder instead.** This design routinely outgrows one page, so expect the folder form: use it when the finished document would run past roughly 1,500 lines (~100 KB), when it has more than about six top-level sections a reader would navigate between, or whenever the user asks for it. Below that, keep the single file — a short design scattered across eight pages is worse than one page.

```
docs/event-pipeline-design/
  index.html                       overview, topology diagram, full contents
  01-event-catalog.html
  02-producers.html
  03-consumers.html
  04-schema-evolution.html
  05-delivery-and-scaling.html
  06-security-and-observability.html
  07-infrastructure-and-rollout.html
  assets/styles.css                one shared stylesheet (still no CDN, no JS, no webfonts)
```

- **Split on top-level section boundaries only** — never mid-section, and never separate a table, diagram, schema, or code block from the prose explaining it. Aim for 4-8 content files: merge anything that would come out shorter than a screenful, split further anything that would still be enormous alone (a large event catalog can be its own file, one section per event).
- **Every page carries the same navigation**: the section list at the top (current page as plain text, not a link), previous/next links at the bottom, and a link home to `index.html`. `index.html` is the entry point — scope, the topology diagram, the full table of contents with a one-line summary per section, and a pointer to which file holds each Final Deliverable.
- **Relative links only** (`03-consumers.html#inventory-service`), so the folder works opened from disk, moved, zipped, or committed. Cross-references between events, producers, and consumers should be real links. Every link must resolve to a file you actually wrote and an anchor that exists — verify them before delivering; a dead nav link is a failed deliverable.
- **Keep the pages one document**: the folder (not each page) is now the self-contained unit — shared stylesheet inside it, nothing fetched from the network, identical header and footer, the same generation date on every page, section numbering matching the index.
- **Markdown splits the same way**: `README.md` as the index plus `01-*.md` files, the same top nav line and previous/next footer, relative links, Mermaid blocks unchanged.

The folder is the deliverable — give its path in the chat reply and list the files with a phrase each.

---

## Phase 1: Context Gathering (Mandatory)

Before designing anything, ask the user (inspect the codebase first where available — existing event catalogs, broker SDKs in dependencies, IaC — and only ask what the code cannot answer):

1. **System context** — What product/system is this for? What does it do?
2. **Known events** — Which events already exist? Are there event catalogs or schemas in the codebase?
3. **Tech stack and broker** — What message broker is in use or preferred? (Azure Service Bus, AWS SQS/SNS, Kafka, RabbitMQ, Google Pub/Sub, NATS, other?)
4. **Throughput** — Expected message volume (messages/sec or messages/day). Burst patterns?
5. **Delivery guarantee** — At-least-once or exactly-once? (Explain tradeoffs if the user is unsure.)
6. **Infrastructure constraints** — Existing infra, managed vs self-hosted, region/compliance requirements, budget limits.
7. **Team and operations** — Team size, on-call maturity, existing monitoring/alerting stack.
8. **Ordering requirements** — Are there events that must be processed in strict order?

Do not proceed until you have answers to at least items 1-5. Adapt all subsequent output to the user's broker and stack.

**Partial context protocol:** If the user cannot answer questions 1-3 (critical), ask once more with examples. If still unknown, produce a broker-agnostic design using generic publish/subscribe patterns and note where broker-specific decisions are deferred. For questions 4-8, proceed with stated assumptions (e.g., "assuming at-least-once delivery, moderate throughput"). Never ask the same question more than twice.

**Scope gate** — After gathering context, select the appropriate depth:
- **(A) Single event addition** to an existing pipeline: produce sections 3.1-3.5 + 3.11 only. Skip the rest and note which sections were skipped.
- **(B) New pipeline** with multiple events: produce all sections.
- **(C) Redesign** of an existing pipeline: produce all sections with emphasis on 3.10 (migration/rollout).

State which scope you selected and why. If the user's request is ambiguous, ask.

---

## Phase 2: Reference Example

This section demonstrates the expected depth for every event you design. Produce this level of detail for every event in your output.

### Catalog Entry

| Field | Value |
|-------|-------|
| Name | `OrderPlaced` |
| Trigger | User submits an order (checkout completes successfully) |
| Producer | `order-service` |
| Consumers | `inventory-service`, `notification-service`, `billing-service` |
| Priority | High |
| Retry policy | 3 retries, exponential backoff: 1s → 5s → 30s |
| DLQ routing | After 3 failures → `orders-dlq`, alert on-call via PagerDuty |
| Idempotency key | `orderId` (consumers deduplicate on this) |
| Ordering | Per-customer ordering (partition/session key = `customerId`) |
| Schema version | `1.0.0` |

### Topology Diagram

Every design opens with one topology diagram covering all events in the catalog — producers, topics/queues, consumers, and DLQs, with partition/session keys on the edges:

```mermaid
flowchart LR
    OS[order-service] -->|OrderPlaced, key: customerId| T[(orders topic)]
    T --> INV[inventory-service]
    T --> NOT[notification-service]
    T --> BIL[billing-service]
    INV -. "after 3 failed deliveries" .-> DLQ[(orders-dlq)]
    NOT -. "after 3 failed deliveries" .-> DLQ
    BIL -. "after 3 failed deliveries" .-> DLQ
    DLQ -.->|alert| PD[on-call / PagerDuty]
```

### Payload Schema

```json
{
  "eventId": "uuid-v4",
  "eventType": "OrderPlaced",
  "schemaVersion": "1.0.0",
  "timestamp": "2026-07-26T15:00:00Z",
  "correlationId": "trace-uuid",
  "partitionKey": "customer-123",
  "payload": {
    "orderId": "order-456",
    "customerId": "customer-123",
    "items": [
      { "sku": "WIDGET-01", "quantity": 2, "unitPrice": 29.99 }
    ],
    "totalAmount": 59.98,
    "currency": "EUR"
  }
}
```

### Producer Pseudocode (Outbox Pattern — atomic guarantee)

```
function placeOrder(orderRequest):
    // 1. Validate and persist order + outbox event in the SAME transaction
    beginTransaction()
    order = validateAndPersist(orderRequest)

    event = {
        eventId: generateUUID(),
        eventType: "OrderPlaced",
        schemaVersion: "1.0.0",
        timestamp: now(),
        correlationId: currentTraceId(),
        partitionKey: order.customerId,
        payload: buildPayload(order)
    }

    // Store event in outbox table (same DB, same transaction)
    outbox.insert(event, status: "PENDING")
    commitTransaction()
    // At this point, either BOTH the order and the outbox row exist, or NEITHER does.

    // 2. Separate relay process publishes from outbox to broker
    //    (runs as a background job or triggered by DB polling/CDC)
    //    On successful publish: mark outbox row as SENT
    //    On broker unavailable: relay retries with backoff — no data loss
```

**Why outbox, not dual-write:** If you persist the order then publish separately, a crash between the two steps means the order exists but the event is lost (or vice versa). The outbox pattern ensures atomicity by keeping both writes in one DB transaction. The relay process handles broker failures independently. Include this sequence diagram (adapted to the user's services) whenever the design uses the outbox pattern:

```mermaid
sequenceDiagram
    participant App as order-service
    participant DB as Database (orders + outbox)
    participant Relay as Outbox relay
    participant Broker
    participant Con as inventory-service
    App->>DB: BEGIN — insert order + insert outbox row (PENDING)
    App->>DB: COMMIT (both rows exist, or neither)
    Relay->>DB: poll / CDC — read PENDING rows
    Relay->>Broker: publish OrderPlaced
    Broker-->>Relay: ack
    Relay->>DB: mark outbox row SENT
    Broker->>Con: deliver (at-least-once)
    Note over Con: deduplicate on eventId, process, then ack
```

**Relay implementation options:**
- **Polling**: Background job queries outbox table every N seconds for PENDING rows. Simple but adds latency (up to N seconds).
- **Change Data Capture (CDC)**: Debezium (Kafka Connect), DynamoDB Streams, or PostgreSQL logical replication capture inserts to the outbox table and relay in near-real-time. Lower latency, more infrastructure.
- **Transaction log tailing**: Read the DB WAL directly (advanced, used by Debezium internally).

**Simpler alternative (accept at-least-once):** If your consumers are fully idempotent and you accept that a crash may cause a missed event (caught by reconciliation), you can publish directly after commit and skip the outbox. Document this tradeoff explicitly in your design.

### Consumer Pseudocode (inventory-service)

```
function handleOrderPlaced(message):
    event = deserialize(message)

    // 1. Deduplicate
    if (processedEvents.exists(event.eventId)):
        message.acknowledge()
        return

    // 2. Process
    try:
        reserveInventory(event.payload.items)
        processedEvents.record(event.eventId, now())
        message.acknowledge()
    catch TransientException:
        // Let broker retry (do NOT acknowledge)
        log.warn("Transient failure, will retry", event.eventId)
        throw  // triggers broker retry with backoff
    catch PermanentException:
        // Unrecoverable — send to DLQ
        message.deadLetter(reason: exception.message)
        alertOps("Permanent failure processing OrderPlaced", event.eventId)
```

---

## Phase 3: Design Output Structure

Produce these sections in order. Each section must contain implementation-ready detail, not just category labels.

### 3.1 Event Catalog

For scope B/C designs (new pipeline, redesign), open this section with the pipeline topology diagram (Phase 2 format): every producer, topic/queue, consumer, and DLQ in one flowchart, edges labeled with event names and partition/session keys. For scope A (single event addition), skip it unless the new event changes the topology in a way the catalog row can't show.

For each event, provide the full catalog entry as shown in the reference example. Include:
- Name, trigger (the business action with an example scenario)
- Payload schema (full JSON with field types and constraints)
- Producer service, target topic/queue name
- All consumer services with what each one does with this event
- Retry policy: max attempts, backoff schedule, what constitutes a transient vs permanent failure
- DLQ routing rule: when does a message go to DLQ, what alerting fires
- Idempotency key: which field(s) consumers use to deduplicate
- Ordering: partition/session key if ordering matters, or "unordered" if not
- Schema version number

### 3.2 Producers

For each producer, specify:
- The business action that triggers the event (with concrete example)
- Payload validation rules with examples of what gets rejected
- Serialization format and schema reference
- Target topic/queue name and any message properties (TTL, priority, headers)
- What happens if publish fails (outbox pattern? retry with backoff? local store and forward? alert?)
- Idempotency key generation logic
- How the publish relates to the DB transaction (outbox, change data capture, or accept dual-write risk)

### 3.3 Consumers

For each consumer, specify:
- What processing it performs (the business logic, briefly)
- Idempotency strategy: how it detects and handles redelivery
- Concurrency model: how many instances, prefetch count, lock duration
- Transient vs permanent failure classification (which exceptions are which)
- Completion/acknowledgment rules: when exactly does it ACK
- Side effects: does this consumer produce further events? (document the chain)
- Timeout handling: what if processing takes too long

### 3.4 Schema Evolution and Versioning

- Versioning strategy: how schemas are versioned (semver on payload)
- Backward compatibility rules: new fields must be optional, removed fields must be deprecated first
- Schema registry: where schemas live, how producers and consumers reference them
  - **Azure**: Azure Schema Registry (Event Hubs namespace), supports Avro
  - **AWS**: AWS Glue Schema Registry, supports Avro/JSON Schema/Protobuf
  - **Kafka**: Confluent Schema Registry with compatibility modes (BACKWARD, FORWARD, FULL)
  - **Lightweight alternative**: Schema files in a shared Git repo with CI validation
- Serialization format choice: JSON Schema (human-readable, larger), Avro (compact, schema evolution built-in), Protobuf (compact, strongly typed, good code generation). Recommend based on throughput needs and team familiarity.
- Contract testing: how to verify producer output matches consumer expectations before deployment (Pact for async, schema registry compatibility check in CI)
- Breaking change protocol: what happens when a payload must change incompatibly (parallel topics, version routing, migration window)

### 3.5 Delivery Guarantees

- State which guarantee applies (at-least-once or exactly-once) and why
- For at-least-once: how every consumer handles duplicates (idempotency strategy per consumer)
- For exactly-once: explain the implementation cost (transactions, dedup stores, performance impact)
- Acknowledge timing: process-then-ack vs ack-then-process, tradeoffs for this system
- What happens on consumer crash mid-processing:
  - **Azure Service Bus**: lock duration expires → message becomes visible again → redelivery. Set `maxDeliveryCount` for DLQ routing.
  - **AWS SQS**: visibility timeout expires → message reappears in queue. Set `maxReceiveCount` for DLQ.
  - **Kafka**: offset not committed → partition rebalance delivers from last committed offset. Duplicate processing of uncommitted batch is expected.
  - **RabbitMQ**: channel closes without ack → message requeued (unless `basic.reject` with `requeue=false`).

### 3.6 Security and Access Control

- Encryption in transit (TLS/mTLS for broker connections)
- Encryption at rest (broker-level or envelope encryption for sensitive payloads)
- Topic/queue access policies: which services can publish to which topics, which can subscribe
- PII handling: identify which event payloads contain PII, how to minimize or tokenize
- Audit trail: how to prove an event was published and consumed (for compliance)

### 3.7 Backpressure and Rate Limiting

- What happens when consumers fall behind (queue depth grows)
- Auto-scaling triggers: at what queue depth or lag do new consumer instances spin up
- Circuit breaker: when does a consumer stop pulling messages (downstream dependency failure)
- Rate limiting on producers: should any producer be throttled
- Broker limits: message size limits, throughput quotas, connection limits

### 3.8 Consumer Scaling Model

- Consumer groups / competing consumers: how work is distributed
- Partition assignment: how messages are routed to specific consumer instances
- Horizontal scaling: how to add/remove consumers without message loss or reprocessing
- Rebalancing behavior: what happens during deployment (rolling update, connection drain)
- Scaling policy: metric-based rules (e.g., "scale out at 1000 messages pending, scale in at 100")

**KEDA autoscaling (Kubernetes):**
When on Kubernetes, define a ScaledObject for each consumer deployment. Specify:
- Trigger type: `azure-servicebus` (scale on `queueLength`/`messageCount`), `kafka` (scale on `lagThreshold` per partition), or `aws-sqs-queue` (scale on `queueLength`)
- `minReplicaCount`: 1 for critical consumers (never scale to zero), 0 for batch/non-critical
- `maxReplicaCount`: based on partition count (Kafka) or concurrency needs
- `cooldownPeriod`: 300s minimum to prevent thrash
- `pollingInterval`: 15-30s
- Numeric thresholds in the design: "scale out when lag exceeds 500 per partition, scale in below 50, max 12 replicas"
- **Broker-specific auto-scaling:**
  - **Azure**: KEDA scaler for Service Bus (scales on queue length / topic subscription count)
  - **AWS**: Lambda event source mapping (auto-scales with SQS queue depth); for ECS use target tracking on `ApproximateNumberOfMessagesVisible`
  - **Kafka**: KEDA Kafka scaler (scales on consumer group lag per partition)
  - **K8s general**: HPA on custom metrics (queue depth exported via Prometheus adapter)

### 3.9 Distributed Tracing and Observability

- Correlation ID propagation: how trace context flows through the event chain. Use W3C Trace Context format (`traceparent` header) for interoperability. Propagate via message properties/headers, not inside the payload.
- Tracing integration: which spans are created (publish, broker transit, consume, process)
  - **OpenTelemetry**: Use semantic conventions for messaging (`messaging.system`, `messaging.operation`, `messaging.destination`). Producer injects span context into message headers via `TextMapPropagator`. Consumer creates a `CONSUMER` span with a **span link** (not child) to the producer span — they are separate traces connected by links since producers don't wait for consumers. Auto-instrumentation available for most broker SDKs.
  - **Azure**: Application Insights auto-correlates Service Bus operations with dependency tracking
  - **AWS**: X-Ray traces Lambda/SQS/SNS automatically when active tracing is enabled
- How to debug a multi-hop event chain (event A triggers B triggers C): query by correlationId across all services in your tracing backend
- Metrics to collect: queue depth, processing latency (p50/p95/p99), failure rate, retry rate, DLQ count, consumer lag, message age (time since publish)
- Dashboards: what the ops dashboard shows at a glance (queue depth trends, consumer lag, DLQ growth, processing latency heatmap)
- Alerts: specific thresholds (e.g., "DLQ count > 0 for 5 min → page on-call", "consumer lag > 10,000 messages → scale warning", "processing p99 > 30s → investigate")

### 3.10 Migration and Rollout Strategy

- How to deploy new events alongside existing synchronous workflows
- Shadow mode: publish events without consumers acting on them (validate payload, measure throughput)
- Cutover plan: when to switch consumers from shadow to active
- Rollback plan: how to revert if the event pipeline fails in production
- Blue/green for consumers: running old and new consumer versions in parallel during deployment
- Feature flags: how to gate event-driven behavior per tenant or environment

### 3.11 Testing Strategy

For each category, provide concrete test scenarios:
- **Unit tests**: producer builds correct payload, consumer handles happy path
- **Integration tests**: end-to-end through real broker using local emulators:
  - Azure Service Bus → Testcontainers with `azure-servicebus-emulator` or use a dedicated test namespace
  - AWS SQS/SNS → LocalStack (`localstack/localstack` Docker image)
  - Kafka → Testcontainers `confluentinc/cp-kafka` or embedded Kafka for JVM
  - RabbitMQ → Testcontainers `rabbitmq:management`
- **Failure tests**: broker unavailable, consumer crash mid-processing, poison message routing to DLQ
- **Idempotency tests**: same message delivered twice, consumer produces correct outcome both times
- **Ordering tests**: messages arrive out of order, system handles correctly
- **Load tests**: sustained throughput at expected volume, burst handling. Specify target: "sustain X messages/sec for Y minutes with <Z ms p99 latency"
- **Contract tests**: producer schema matches consumer expectations (Pact async message support, or schema registry compatibility check in CI pipeline)
- **Chaos tests**: kill consumer mid-processing (verify DLQ routing), introduce broker latency (verify backpressure/circuit breaker), revoke topic access (verify graceful degradation)

### 3.12 Infrastructure as Code

For every queue/topic/subscription in the design, provide the IaC resource definition:

- **Azure Service Bus (Bicep)**: Namespace (Standard/Premium), topics with `maxSizeInMegabytes` and `defaultMessageTimeToLive`, subscriptions with `maxDeliveryCount`, `lockDuration`, `deadLetteringOnMessageExpiration: true`
- **AWS SQS/SNS (Terraform)**: SNS topic (FIFO if ordering needed), SQS queues with `visibility_timeout_seconds` (6x expected processing time), `message_retention_seconds`, redrive policy pointing to DLQ, SNS subscriptions with `raw_message_delivery = true`
- **Kafka (Terraform with Confluent provider)**: Topics with `partitions_count`, `config = { "retention.ms", "cleanup.policy", "min.insync.replicas" }`
- Include: DLQ resources, access policies (IAM/RBAC), monitoring alerts (CloudWatch/Azure Monitor), environment parameterization

### 3.13 Build Order

Specify the implementation sequence with rationale. Each step should be deployable independently. Example structure:
1. Event schemas and registry (foundation everything else depends on)
2. Outbox table + publisher (producers can start writing without consumers)
3. First consumer (pick the simplest, prove the pattern)
4. DLQ handling and alerting (safety net before scaling)
5. Remaining consumers
6. Monitoring dashboards
7. Load testing and scaling policies

---

## Phase 4: Anti-Patterns to Flag

If you detect any of these in the user's existing system or proposed design, call them out explicitly with the risk and fix:

| Anti-Pattern | Risk | Fix |
|-------------|------|-----|
| Publishing events inside a DB transaction | Transaction commits but publish fails (or vice versa) — data inconsistency | Use the outbox pattern or change data capture |
| Fat events containing entire entity state | Tight coupling between producer and consumers, PII exposure surface, large message sizes | Include only the fields consumers need; let consumers call back for full state if needed |
| Missing correlation IDs | Debugging multi-service event chains becomes impossible | Propagate trace context in every event header |
| Unbounded retry without DLQ | Poison messages block the queue indefinitely | Always define max retries and a DLQ destination |
| Consuming events to call back to the producer synchronously | Circular dependency, defeats the purpose of decoupling | Redesign the data flow or include needed data in the event |
| Acknowledging before processing completes | Message loss on consumer crash | Process first, then acknowledge |
| No idempotency strategy in consumers | Duplicate processing on redelivery (double charges, double notifications) | Every consumer must declare how it handles redelivery |
| Shared topic for unrelated events | Consumers receive irrelevant messages, scaling becomes coupled | One topic per event type (or use subscriptions/filters) |

---

## Testable Constraints

Every design you produce must satisfy these. Verify each one before delivering output:

1. Every event specifies what happens if: (a) broker is unavailable, (b) consumer crashes mid-processing, (c) consumer rejects the message permanently.
2. Every consumer specifies its idempotency strategy with the deduplication key named explicitly.
3. Every event includes a DLQ routing rule with the alerting trigger.
4. Never recommend exactly-once delivery without explaining the implementation cost and performance tradeoff.
5. Every event payload includes `eventId`, `correlationId`, `schemaVersion`, and `timestamp`.
6. No event design publishes inside a database transaction without the outbox pattern.
7. Every consumer specifies its acknowledgment timing (when exactly it ACKs).
8. Schema changes document backward compatibility impact.
9. Every multi-hop event chain is traceable via correlation ID from origin to final consumer.
10. Scaling thresholds are numeric, not vague ("scale at 1000 pending" not "scale when busy").

---

## Final Deliverables Checklist

Before presenting your design — compiled into the HTML or Markdown deliverable chosen in Phase 0, one file or the linked folder if it was split — confirm you have delivered:

- [ ] Pipeline topology diagram (producers → topics → consumers → DLQs, keys on edges) — scope B/C designs
- [ ] Complete event catalog with payload schemas for every event
- [ ] Producer implementation detail for every producing service
- [ ] Consumer implementation detail for every consuming service
- [ ] Retry and DLQ policy for every event
- [ ] Idempotency strategy for every consumer
- [ ] Schema versioning approach
- [ ] Security model (access control, encryption, PII handling)
- [ ] Scaling model with numeric thresholds
- [ ] Observability: metrics, dashboards, alerts with thresholds
- [ ] Distributed tracing approach
- [ ] Migration/rollout plan (if replacing existing sync communication)
- [ ] Anti-patterns checked and flagged
- [ ] Build order with deployable increments
- [ ] Test strategy covering happy path, failure, idempotency, ordering, and load
- [ ] Infrastructure-as-code for all queues, topics, subscriptions, and DLQs

