Do not lose an accepted observation.
Messages are persistent, the publishing channel uses confirms, and the consumer acknowledges only after the SQL transaction commits.
Independent systems-integration lab / 2026
A C#/.NET worker consumes equipment events over RabbitMQ, commits them to SQL, and acknowledges only after storage owns the delivery. The publisher deliberately creates duplicates, delay and reordering so the failure semantics have something real to answer.
01 / Requirements
The lab starts from failure behaviour, not from the framework template.
Messages are persistent, the publishing channel uses confirms, and the consumer acknowledges only after the SQL transaction commits.
Every envelope carries a stable event identity. That identity is the SQL primary key, so the second delivery becomes evidence instead of a second row.
Sequence is assessed against a replay-scoped equipment cursor. A late unique observation is stored and marked; it is not silently discarded.
Invalid JSON is rejected without requeue. A failed SQL operation is negatively acknowledged with requeue so it can recover.
02 / Implementation
A long-lived worker owns consumption. A repository owns SQL semantics. A deterministic schedule owns fault injection.
Explicit cancellation + long-lived connection/channel
RabbitMQ deliveries remain sequential on one consumer channel. The body is copied inside the callback, persisted, then acknowledged by delivery tag.
Durable topic exchange + queue + manual ack
Publisher confirmations expose broker rejection. Mandatory routing and stable topology names make an unroutable event an error rather than a silent success.
Primary-key idempotency + transactional counters
The local database keeps the lab reproducible. The storage contract is deliberately narrow enough to replace the engine without changing delivery semantics.
Duplicate identity + delayed sequence + reversed windows
The injector transforms a clean sequence before publishing. Tests assert the exact schedule so the experiment itself is repeatable.
03 / Failure semantics
| Condition | Decision | Recorded evidence | What it does not prove |
|---|---|---|---|
| Duplicate event ID | Ignore second insert; acknowledge after transaction. | Delivery +1, duplicate +1, stored row unchanged. | Exactly-once delivery. |
| Lower sequence | Store unique event and flag it late. | Insert +1, out-of-order +1. | Whether late data should alter an operational state estimate. |
| Invalid envelope | Reject without requeue. | Structured warning and broker rejection path. | A complete dead-letter policy. |
| SQL failure | Nack with requeue; stable identity protects retry. | Error log and eventual redelivery. | Distributed transaction atomicity. |
04 / Delivery
xUnit pins the fault schedule and SQL decisions. A broker-backed integration test crosses the real AMQP boundary. GitHub Actions runs formatting, build and both suites; PowerShell and Docker Compose give another engineer the same entry points locally.