Kafka Event-Driven Design Review¶
Quick Reference¶
§1 Scope · §2 Mandatory Gates · §3 Depth & Reference Loading · §4 Degradation Modes · §5 Design Checklist · §6 Partition Design · §7 Anti-Examples · §8 Scorecard · §9 Output Contract
Before scoring a version-sensitive configuration item, read references/version-client-matrix.md. Kafka defaults changed materially at 3.0 and again at 4.0, and the four major client libraries disagree with each other. Which items are version-sensitive — and which fact each one needs — is §2's Minimum context per scored item; most of the checklist needs no version at all.
§1 Scope¶
In scope — Kafka event-driven architecture for production backend services:
- Topic design (naming, partition count, replication factor, retention)
- Partition key strategy (ordering guarantees, hot partition avoidance)
- Event schema design (Avro/Protobuf/JSON Schema, schema evolution, compatibility)
- Producer configuration (acks, retries, idempotence, transactional producers)
- Consumer group design (assignment strategy, rebalance handling, commit strategy)
- Idempotent consumption (deduplication, idempotency keys, exactly-once semantics)
- Dead letter queue (DLQ) and retry patterns
- Backpressure and consumer lag management
- Schema Registry integration and compatibility modes
Out of scope — say so and redirect rather than answering partially:
| Out of scope | Why it is not this skill |
|---|---|
| Broker sizing, heap/page-cache tuning, disk layout | Cluster operations, not client design |
| KRaft/ZooKeeper administration, controller config, 3.9→4.x migration | Platform runbook territory |
| Partition reassignment tooling, cruise-control balancing, quotas | Operational, though its client-visible consequences (AE-12 key remapping) are in scope |
| ACL administration and principal management | In scope only as a design requirement (§5.5 item 16), not as operator commands |
| MirrorMaker / cross-cluster replication topology | Separate architecture |
| Kafka Streams / Kafka Connect internals | Different programming models with their own semantics |
Application code unrelated to Kafka → go-code-reviewer | |
General REST/gRPC API design → api-design |
The boundary: this skill reviews what a service's producer/consumer code and topic contract do. If the answer would be a broker config change or an SRE command against the cluster, say which parts you can cover and which you cannot, rather than shipping a partial answer with the operational half quietly missing.
§2 Mandatory Gates¶
Execute gates sequentially. Each gate has a STOP condition.
Gate 1: Context Collection¶
| Item | Why it matters | If unknown |
|---|---|---|
| Kafka version (2.x / 3.x / 4.x) | KRaft-only, KIP-848 and KIP-890 land at 4.0; the 3.0 default flip is a client fact, not a broker one | Gates feature-availability verdicts only |
| Client library + version (kafka-clients / librdkafka / sarama / franz-go) | Defaults differ between clients on the same broker | Gates every producer-durability verdict |
group.protocol (classic / consumer) | Under consumer (4.0+), partition.assignment.strategy, session.timeout.ms, heartbeat.interval.ms do not exist | Assume classic (still the client default in 4.x) |
| Schema format (Avro / Protobuf / JSON / none) | Compatibility rules differ per format — Avro verdicts do not port to JSON Schema | Must clarify |
| Ordering requirement | Per-entity? Per-tenant? Global? None? | Must clarify — drives partition key |
| Delivery guarantee needed | At-most-once / At-least-once / Exactly-once | Assume at-least-once |
| Throughput estimate (events/sec) | Determines partition count and consumer scaling | Ask; assume moderate |
| Consumer count / group topology | Single consumer group? Multiple? Fan-out? | Must clarify |
| Retention policy | Time-based / size-based / compacted | Assume 7 days time-based |
| Schema Registry | Confluent / AWS Glue / Apicurio / external governance / none | Ask; drives evolution strategy, not automatically a defect if absent |
| Data sensitivity (PII / payment / regulated) | Drives topic ACLs, field-level redaction, retention limits | Ask; assume non-regulated and record the assumption |
STOP: Cannot determine what events are being produced/consumed (no domain context). Clarify before proceeding.
Minimum context per scored item¶
There is no global version gate. Each item names the one fact it depends on, and is NOT SCOREABLE when that fact is missing — whatever else is known, and without dragging unrelated items down with it.
| Scored item | Minimum context | Why that one, and not the other |
|---|---|---|
Producer durability — acks, enable.idempotence, retries (§5.2 item 5) | client library + its version | Defaults are a client property. An absent acks line is safe on kafka-clients ≥3.0 and a data-loss defect on sarama; the broker version moves neither verdict |
Consumer-protocol keys — assignor, session.timeout.ms, heartbeat.interval.ms, rebalance advice (§5.3 item 13) | group.protocol, or the broker version to infer it (classic below 4.0) | These keys do not exist under group.protocol=consumer; which client sets them is irrelevant |
Feature availability — KIP-848 server-side assignors, transaction.version=2, KRaft-only behaviour, removed APIs | broker version | The feature is either in the cluster or it is not |
| Everything else — partition key, atomicity, DLQ policy, schema governance, lag alerting, retention, naming, backpressure, PII (§5 items 1–4, 6–12, 14–17) | none | Scoreable from domain context alone. Do not withhold these because a version is unknown |
STOP: Asked to score an item whose minimum context is missing. Report both branches explicitly and mark that item NOT SCOREABLE — never guess, and never extend the block to items that did not need the missing fact.
PROCEED: At least event type, ordering requirement, and delivery guarantee are known.
Gate 2: Scope Classification¶
| Mode | Trigger | Output |
|---|---|---|
| review | User provides existing Kafka config/code | Safety analysis with findings |
| design | User describes event-driven requirements | Complete topic + schema + consumer design |
| troubleshoot | User reports issues (lag, duplication, ordering) | Root cause + fix plan |
STOP: Not Kafka-related (e.g., REST API design). Redirect to appropriate skill.
PROCEED: Kafka event-driven intent confirmed.
Gate 3: Risk Classification¶
| Risk | Definition | Required action |
|---|---|---|
| SAFE | Single topic, simple consumer, at-least-once | Standard review |
| WARN | Multi-topic transactions, schema evolution, exactly-once | Off-peak deployment + monitoring |
| UNSAFE | Partition key change on live topic, consumer group migration, schema breaking change | Staged rollout + rollback plan mandatory |
STOP: Any UNSAFE item without mitigation plan.
PROCEED: Every component has risk level and mitigation.
Gate 4: Output Completeness¶
Before delivering output, verify all §9 Output Contract sections present. §9.9 Uncovered Risks must never be empty.
§3 Depth & Reference Loading¶
| Depth | When to use | Gates | References to load |
|---|---|---|---|
| Lite | Single producer/consumer pair, simple schema | 1–4 | version-client-matrix.md if any config verdict is given |
| Standard | Multi-consumer topology, schema evolution, DLQ design | 1–4 | event-schema-patterns.md + version-client-matrix.md |
| Deep | Cross-service event mesh, exactly-once, CQRS/ES pattern | 1–4 | All four reference files |
Force Standard or higher when any signal appears: schema evolution requirement, exactly-once semantics, multi-consumer-group fan-out, partition key redesign, consumer group migration, compacted topics for event sourcing, Kafka 4.x consumer-protocol migration (group.protocol=consumer), transactional producer fencing, PII/regulated data on the topic.
Two loads override the depth column: any producer/consumer config verdict loads references/version-client-matrix.md even at Lite depth (likewise group.protocol, group.remote.assignor, transaction.version, KRaft, any 4.x version, or a sendOffsetsToTransaction call); consumer failure or lag signals load references/consumer-failure-modes.md. Extended anti-example matching loads references/consumer-anti-examples.md.
§4 Degradation Modes¶
When context is incomplete, degrade gracefully — never fabricate assumptions about ordering requirements.
| Available context | Mode | What you can do | What you cannot do |
|---|---|---|---|
| Full (version, schema, ordering, delivery, throughput) | Full | Complete design with quantified guarantees | — |
| Event type + ordering known, infra unknown | Degraded | Schema + consumer design; flag infra unknowns | Partition count, replication recommendations |
| Only code snippets, no architecture context | Minimal | Static review of producer/consumer patterns | Full topology design |
| No code (greenfield design) | Planning | Propose event architecture from requirements | Review existing implementation |
Hard rule: Never claim "exactly-once" without verifying transactional producer + consumer isolation.level=read_committed + enable.auto.commit=false + idempotent processing at any non-Kafka sink. In Degraded/Minimal mode, flag "delivery guarantee unverified" in §9.9.
Hard rule: Never state a numeric threshold you did not derive from the context in front of you. Throughput ceilings, lag alert values, retry counts and partition counts all depend on the workload; a memorised figure is a fabrication with a number attached, harder to challenge than an honest "derive this from your p99". Give the formula and the inputs it needs instead (§6 shows the worked form).
Hard rule: Never give a config verdict without the context that item names in Gate 1's Minimum context per scored item table — client + version for producer durability, group.protocol (or the broker version) for consumer-protocol keys, broker version for feature availability. Missing it, present both branches and mark that one item not scoreable; the rest of the review still stands. See references/version-client-matrix.md.
§5 Design Checklist¶
Execute every item. Mark PASS / WARN / FAIL / N/A / NOT SCOREABLE with evidence. The bracketed tier is the item's weight in §8 — these seventeen items are the scorecard, and every one of them carries a tier. An item that is reviewed but not scored is a hole: the finding gets written down and the verdict still comes out PASS. Inapplicable items leave via N/A, which shrinks the denominator honestly; they do not leave by being unscored.
5.1 Topic Design¶
-
Topic naming convention (Hygiene) — follow
{domain}.{entity}.{event-type}or{team}.{service}.{event}pattern, or the org's own. Names should be greppable, meaningful, and avoid generic names like "events" or "messages". -
Partition count sized for throughput (Hygiene) — each partition is the unit of parallelism. Rule of thumb: partitions ≥ max expected consumer instances. Over-partitioning wastes resources; under-partitioning caps throughput. Partition count cannot be decreased (only increased, which breaks key-based ordering).
-
Replication factor sized to the durability requirement (Hygiene) — RF≥3 +
min.insync.replicas=2+acks=allsurvives one broker loss; the right default wherever loss is a business incident. FAIL if the topic holds a source of record or the low RF is unexamined; WARN with owner and rationale recorded when it is a documented trade-off (dev cluster, rebuildable projection, accepted-loss telemetry). Two traps:min.insync.replicasdoes nothing withoutacks=all, and RF=2 +min.insync.replicas=2blocks writes the moment any broker restarts. -
Retention policy matches use case (Hygiene) — time-based (default 7 days) for event streaming;
compactfor entity-state topics (latest value per key); and compatible with any deletion obligation Gate 1 recorded. For event sourcing, use two topics: an append-only event log (deletewith retention long enough to replay from zero, or unbounded) plus a separate compacted snapshot topic. Compaction on the event log itself, keyed by aggregate ID, deletes the history the design exists to keep — silently. Detail:references/event-schema-patterns.md§4.3.
5.2 Producer Design¶
- Durability config verified against version and client, not assumed (Critical) — the target is
acks=all+ retries +enable.idempotence=true. FAIL on an explicit downgrade, or on defaults that are unsafe for that client; PASS when kafka-clients ≥3.0 defaults already satisfy it. Whether silence about it is a defect is entirely a function of the client, so establish the client before scoring:
| Client | Safe by default? | What to flag |
|---|---|---|
| kafka-clients ≥ 3.0 | both | only an explicit downgrade |
| kafka-clients ≤ 2.8 | neither | silence is the defect |
| sarama | neither | silence; and Net.MaxOpenRequests = 1 beside Idempotent = true is required by sarama's Validate() — not an over-restriction |
| librdkafka | acks only | the idempotence, not the acks |
| franz-go | both | an explicit DisableIdempotentWrite() |
On the Java client, idempotence preserves ordering for any max.in.flight.requests.per.connection ≤ 5; "must be 1" is sarama-specific. Without idempotence, in-flight > 1 plus retries genuinely can reorder. Full matrix and sources: references/version-client-matrix.md.
- Partition key chosen for ordering + distribution (Standard) — the key determines the partition; records sharing a key share a partition and are therefore ordered relative to each other. Common keys: entity ID (
order_id,user_id), tenant ID. FAIL a null key on an order-sensitive topic, or a sharded key presented as order-preserving. What to actually watch for: - Null key ≠ round-robin. Placement is batch-sticky (Java, librdkafka) or random (sarama) — no mainstream client round-robins by default. Report the loss of per-key ordering, not a distribution shape.
- High key cardinality does not create partitions. Partition count is a topic property; keys hash modulo it. The real problems are the opposite — too few distinct keys (idle partitions) and skew (one key dominating).
- A timestamp key is not automatically hot under the default hash partitioner. It is a defect because it destroys per-entity ordering; it becomes a hot partition only under a range-style or time-bucketing partitioner. Cite whichever reason applies.
-
Hot-key sharding trades away the guarantee it appears to preserve.
key:{shard}means Kafka no longer orders that entity — the broker cannot know the shards are related. Never present composite/sharded keys as "keeps ordering and fixes the hot partition". Trade-offs per fix: §6. -
Event carries the metadata its consumers actually need (Standard) — load-bearing:
event_id(dedup key), andevent_typeon multi-type topics (routing). Strong defaults:timestamp,source,correlation_id.schema_versionis redundant under Schema Registry — the wire format carries the schema ID. Routing/tracing metadata belongs in record headers; a header-based envelope is not a missing envelope. Missingevent_idwith non-idempotent consumers is a real finding; missingsourceon a single-producer topic is hygiene. Field weights:references/event-schema-patterns.md§1. -
Atomicity mechanism matches the boundary being crossed (Critical) — a dual-write with no atomicity mechanism loses events or invents them, silently and unrecoverably; that is the same class of defect as no durability and no dedup, not a design preference. Two problems, two tools:
- Kafka → Kafka: Kafka transactions (
initTransactions…sendOffsetsToTransaction…commitTransaction) plusisolation.level=read_committedandenable.auto.commit=falseon the consumer. - Database → Kafka: the outbox pattern — business rows and outbox row in one database transaction, then a relay publishes and marks published. A Kafka transaction cannot include a database write. The outbox is at-least-once by construction, so it never removes the consumer's idempotency requirement. Fencing, abort paths and relay ordering:
references/consumer-failure-modes.md§4 andevent-schema-patterns.md§6.
5.3 Consumer Design¶
-
Consumer survives duplicate delivery (Critical) — at-least-once means duplicates will arrive. Handle them by: a dedup marker on
event_idcommitted in the same transaction as the side effect, naturally idempotent operations (INSERT ... ON CONFLICT), or — Kafka-to-Kafka only — EOS withread_committed+auto.commit=false. Cache-TTL dedup alone does not satisfy this (race, crash window, and expiry all bypass it). There is no such thing as a "transactional consumer": the transaction belongs to the producer (transactional.id), and the consumer contributes offsets viasendOffsetsToTransactionwhile readingread_committed. The term matters because it locates the fencing identity a review must check. -
Commit strategy explicit (Standard) —
enable.auto.commitis stilltrueby default in Kafka 4.x and commits on a timer, so a crash can drop or replay a batch. At-least-once: manual commit after processing. EOS:enable.auto.commit=falseandisolation.level=read_committed(defaultread_uncommittedreads aborted records), offsets committed inside the transaction. -
A defined, owned policy for unprocessable messages (Critical) — something specific must happen to a message that cannot be processed, and someone must own it. DLQ after N retries is the usual answer, not the only correct one: strictly ordered state machines (event-sourced aggregates, ledgers) are corrupted by skipping, so halt-and-page is right there; transient downstream failures deserve retry-with-backoff first. FAIL when no policy exists ("retry forever", "log and continue"); WARN for a documented non-DLQ policy with an owner. When a DLQ exists, an unchecked publish error before the offset advances is itself a FAIL — that is a data-loss path, not a style issue. Trade-offs:
references/consumer-failure-modes.md§2. -
Consumer lag monitoring with an alert threshold defined (Standard) — lag per partition is the primary health metric. Derive the alert from time-to-drain (
records_lag_max / records_consumed_rate) against a stated freshness SLO; a copied message count is meaningless across workloads. Onread_committedconsumers, non-zero lag on a drained transactional topic is normal — aborted records occupy offsets. -
Consumer group configuration is coherent with the protocol in use (Standard) — the
classicandconsumer(KIP-848, 4.0+) protocols take disjoint config: undergroup.protocol=consumer,partition.assignment.strategy,session.timeout.msandheartbeat.interval.msdo not exist, and the assignor moves server-side (group.remote.assignor). Setting both is a broken config, not a belt-and-braces one. Static membership (group.instance.id) only avoids a rebalance for a restart that completes insidesession.timeout.ms— it buys a window, it does not abolish rebalancing, and a rolling deploy slower than that window rebalances as usual. NOT SCOREABLE withoutgroup.protocolor the broker version to infer it (§2). Detail:references/consumer-failure-modes.md§1.
5.4 Schema Evolution & Operations¶
-
Schema compatibility mode chosen deliberately, and stated in the right direction (Standard) — an equivalent external schema-governance process with validating consumers also satisfies this. BACKWARD: a new reader reads old data → upgrade consumers first. FORWARD: an old reader reads new data → upgrade producers first. FULL: both, so order is free. BACKWARD does permit deleting an optional/defaulted field, and FULL permits adding and deleting optional fields — "BACKWARD/FULL can never delete" is wrong and drives permanent field accretion.
*_TRANSITIVEchecks all previous versions: right when old data or old readers span more than one version (replay, event sourcing, un-upgradable clients), needless friction otherwise;BACKWARDis the registry default and defensible. Rules differ per format — JSON Schema turns on the open/closed content model — so never port an Avro verdict to a JSON Schema subject. Table, per-format rules and the verification command:references/event-schema-patterns.md§2. -
Backpressure handling defined (Hygiene) — what happens when consumers can't keep up? Options: scale consumers (up to partition count), increase batch size, apply rate limiting on producer, drop low-priority events. "Consumer crashes under load" is not a strategy.
5.5 Governance¶
-
Sensitive data in events is classified and controlled (Standard) — a topic is a durable, replayable copy readable by every principal with read access. For PII, payment, credentials, or regulated data verify: topic ACLs; field-level redaction/tokenisation for fields no consumer needs; retention compatible with the deletion obligation. Erasure against an append-only or compacted topic is a design problem, not an ops task — see
references/consumer-anti-examples.mdAE-17 for crypto-shredding. N/A with a reason when Gate 1 recorded the data as non-sensitive. -
Topic ownership and consumer inventory recorded (Hygiene) — who owns the schema, and which consumer groups read this topic. Without it, no one can judge whether a schema or partition-count change is safe, and the compatibility mode in item 14 is being chosen blind.
§6 Partition Design (Standard + Deep)¶
Quick decision guide — for schema patterns load references/event-schema-patterns.md.
| Ordering need | Partition key | Example |
|---|---|---|
| Per-entity ordering | Entity ID | order_id → all events for order 123 in same partition |
| Per-tenant ordering | Tenant ID | tenant_id → tenant isolation per partition |
| Per-user ordering | User ID | user_id → user action sequence preserved |
| No ordering needed | null (client-dependent placement, not round-robin) | Metrics, logs, analytics events |
| Global ordering | Single partition | Throughput is capped at one consumer — see below |
Global ordering has no fixed events/sec threshold. The ceiling is whatever one consumer instance processes end-to-end, which spans orders of magnitude. Derive it: max sustainable rate ≈ 1 / p99_processing_time, then leave 2–3× for spikes and replay; a memorised threshold like "<100 events/sec" is a fabricated number. Test the requirement first — most "we need global ordering" cases are per-entity ordering plus a report that wanted a total order. The choice is near-irreversible: growing the topic later needs the AE-12 migration.
You cannot have per-entity Kafka-native ordering and spread one entity's events across partitions. If both are demanded, one is not a real requirement.
Hot-partition symptoms, causes, and the four mitigations with what each costs (composite key, sharded key, dedicated topic, more partitions): references/event-schema-patterns.md §7.
§7 Anti-Examples¶
Seventeen WRONG/RIGHT pairs live in references/consumer-anti-examples.md. Load it whenever a snippet resembles one of these; the titles are the index.
| # | What it gets wrong | Fires on |
|---|---|---|
| AE-1 | acks=1 on critical events | sarama NewConfig() left as-is |
| AE-2 | Consumer without idempotency | redelivery double-applies |
| AE-3 | Unchecked DLQ publish | offset advances past an unpublished message |
| AE-4 | Null partition key | per-key ordering lost |
| AE-5 | Compatibility direction inverted | BACKWARD reasoned about as FORWARD |
| AE-6 | Kafka defect mislabelled as app logic | "double charge" filed as a business bug |
| AE-7 … AE-11 | Auto-commit with slow handlers · blocking I/O in the poll loop · single partition for "global ordering" · group.id reuse across environments · compacted topic with no tombstone handling | |
| AE-12 … AE-17 | Partition increase on a live topic · no consumer-side schema validation · Kafka 4.x assignor config under group.protocol=consumer · random transactional.id · abortTransaction() without seeking back · PII with no deletion path |
§8 Kafka Design Scorecard¶
Every item resolves to exactly one of PASS / WARN / FAIL / N/A / NOT SCOREABLE.
| Verdict | When | Counts as |
|---|---|---|
| PASS | Requirement met | credit |
| WARN | Risk examined and accepted: alternative mechanism in place, owner named, trade-off recorded | credit — a WARN never fails the scorecard, and must appear in §9.9 |
| FAIL | Unexamined gap: failure path undefined, or config contradicts a stated requirement | no credit |
| N/A | Inapplicable for a reason stated from Gate 1 context ("no PII — Gate 1 recorded non-regulated telemetry") | removed from both numerator and denominator |
| NOT SCOREABLE | Gate 1 did not establish the minimum context that item names (§2) — and only that item | removed from both, and forces the overall verdict to not scoreable |
The PASS/WARN split is what stops the scorecard producing false positives: "no DLQ" is FAIL when the failure path is retry forever, and WARN when the team deliberately runs halt-and-page on an ordered ledger topic.
The arithmetic, stated mechanically¶
The scored items are the §5 checklist items themselves; this section weights them, it does not restate them. Per tier, A = applicable items (tier size minus N/A minus NOT SCOREABLE) and C = items scored PASS or WARN.
| Tier | §5 items | Size | Passes when |
|---|---|---|---|
| Critical | 5 durability · 8 atomicity mechanism · 9 duplicate delivery · 11 unprocessable-message policy | 4 | C == A — every applicable item is PASS or WARN |
| Standard | 6 partition key · 7 event metadata · 10 commit strategy · 12 lag alerting · 13 consumer-group config · 14 schema governance · 16 sensitive data | 7 | C ≥ ceil(0.8 × A) |
| Hygiene | 1 naming · 2 partition count · 3 replication · 4 retention · 15 backpressure · 17 ownership | 6 | C ≥ ceil(0.75 × A) |
There is no unscored tier. Four items used to sit outside the scorecard, which made a green verdict reachable with a Critical defect written down in the same report: a dual-write with no atomicity mechanism (§5 item 8) was found, reported, and cost nothing. Inapplicable items leave through N/A, which shrinks A; nothing leaves by having no tier.
Thresholds scale with A; they are not fixed counts, and ceil stops a partially-applicable tier getting an easier ride than a full one. Standard:
Applicable A | Needed C | Reading |
|---|---|---|
| 7 | 6 | the full tier |
| 5 | 4 | the familiar "4 of 5" |
| 4 | 4 | all four — 3/4 is 75%, below the bar |
| 3 | 3 | all three |
| 0 | 0 | tier vacuously passes; report Standard N/A |
Overall PASS requires all three tiers to pass. A single NOT SCOREABLE item forces overall not scoreable regardless of the rest — an incomplete review must not present itself as a green one. Otherwise FAIL.
Three rules that keep this mechanical rather than a matter of taste:
- A WARN needs all three of an alternative mechanism, a named owner, and a recorded trade-off. Missing any one, it is a FAIL. "The team knows about it" is not an owner.
- N/A needs a Gate 1 citation, not a judgement. If Gate 1 did not record the fact that makes the item inapplicable, the item is NOT SCOREABLE, not N/A.
- NOT SCOREABLE is per item. Only the items whose minimum context (§2) is missing are unscoreable. An unknown client version blocks item 5 and nothing else; it does not excuse an unscored DLQ policy or partition key.
In a CI gate, treat not scoreable as a failure to gate rather than a pass, and gate on FAIL count only — failing on WARNs pushes teams to relabel accepted risks as PASS, which destroys the accepted-risk register.
Report the verdict as C/A per tier, so the denominator shows what was actually judged — never a fixed /3, /5, /4:
Had Hygiene scored 2/3 there, the overall verdict would be FAIL — ceil(0.75 × 3) = 3, so 2 is short. Writing the denominator down is what makes that visible. Not scoreable is likewise a valid overall verdict: with the client library unknown, say Critical: not scoreable (item 5 — client unknown) and still report the other two Critical items' verdicts.
§9 Output Contract¶
Every design review MUST produce these sections. Write "N/A — [reason]" if inapplicable.
### 9.1 Context Gate
| Item | Value | Source |
### 9.2 Depth & Mode
[Lite/Standard/Deep] × [review/design/troubleshoot] — [rationale]
### 9.3 Risk Assessment
| Component | Risk | Notes |
### 9.4 Architecture Design (Standard/Deep; "N/A — Lite" for Lite)
- Topic topology + partition strategy
- Producer + consumer configuration
- Schema design + evolution strategy
### 9.5 Implementation (topic config, producer/consumer code patterns)
### 9.6 Validation Plan
- End-to-end event flow test; duplicate-delivery test (kill the consumer between
side effect and offset commit)
- Schema evolution test — run the registry's own `/compatibility/subjects/...`
endpoint against the candidate; deploy in the order the mode requires
(BACKWARD → consumers first; FORWARD → producers first)
### 9.7 Failure Handling
- Unprocessable-message policy (DLQ / halt-and-alert / compensating) + owner
- DLQ publish-failure path (must not commit past an unpublished message)
- Consumer rebalance behavior — state which protocol (`classic` / `consumer`)
- Broker failure recovery; for EOS: abort path (seek back to last committed)
and `transactional.id` fencing identity
### 9.8 Monitoring & Alerts
- Consumer lag per partition; producer error rate, batch size, latency
- DLQ message rate **and** DLQ publish failure rate
- For EOS: transaction abort rate, `ProducerFencedException` count
### 9.9 Uncovered Risks (MANDATORY — never empty)
| Area | Reason | Impact | Follow-up |
Volume rules: FAIL always fully detailed; WARN up to 10, overflow to §9.9; PASS summary only. §9.9 must document every assumption — especially an unverified delivery guarantee, and each Gate 1 field left unknown (broker version, client library, group.protocol) with the specific items it left NOT SCOREABLE.
Scorecard summary (append after §9.9), in §8's C/A form — the denominator is applicable items, so it shrinks with each N/A and NOT SCOREABLE:
Scorecard: Critical C/A · Standard C/A · Hygiene C/A — PASS | FAIL | not scoreable
Non-scoring items: [N/A count + reason each] [NOT SCOREABLE count + missing input]
Data basis: [full context | degraded | minimal | planning]
Version basis: [broker version | client library+version | group.protocol | "unknown — see §9.9"]