--- title: "Reliability & governance helpers" description: "The optional, zero-dependency reliability and governance helpers in BabelQueue.Core — idempotency (Idempotency.Wrap + seen-set IIdempotencyStore), the transactional outbox (BabelQueue.Outbox, async), DLQ redrive + replay-bypass, GDPR field encryption (BabelQueue.Gdpr, AesGcmCipher), and OpenTelemetry traceparent (BabelQueue.Tracing, on System.Diagnostics.Activity). Each composes with the core, adds nothing to the frozen envelope, and mirrors the cross-SDK spec." source: https://babelqueue.com/docs/babelqueue-dotnet/1.x/reliability-and-governance/ updated: 2026-06-21T00:00:00.000Z --- # Reliability & governance helpers Beyond produce/consume, `BabelQueue.Core` ships **optional, zero-dependency** helpers that mechanise the reliability and governance contracts the wire spec defines. None touch the frozen envelope (`schema_version: 1`); each is a tooling layer over the codec. They are the .NET face of the cross-SDK spec — follow the linked spec page for the full contract. ## Idempotency `Idempotency.Wrap(store, handler)` (namespace `BabelQueue`) makes a handler **run at most once per `meta.id`**, even on at-least-once redelivery. The store contract is **synchronous**: ```csharp using BabelQueue; IIdempotencyStore store = new InMemoryStore(); // tests / single process Handler guarded = Idempotency.Wrap(store, handler); ``` The in-memory `InMemoryStore` is the reference; for a fleet, implement `IIdempotencyStore` (`Seen` / `Remember` / `Forget`) over a shared backend (no persistent store ships in core yet — bring your own). See [Idempotency](/docs/spec/1.x/idempotency/) and the [store deep-dive](/docs/spec/1.x/idempotency-stores/). ## Transactional outbox The `BabelQueue.Outbox` namespace removes the producer **dual write**: persist the encoded envelope in the **same DB transaction** as your business row, then a relay publishes the durable rows verbatim. It is async throughout (`CancellationToken`-threaded): ```csharp using BabelQueue.Outbox; IOutboxStore store = new InMemoryOutboxStore(); // production: an ADO.NET-backed IOutboxStore var outbox = new Outbox(store); // inside YOUR DB transaction, beside the business write — no commit of its own: string id = await outbox.WriteAsync(envelope, ct); // encodes via EnvelopeCodec, calls SaveAsync // later, a relay drains the durable rows to the broker: OutboxRelayResult res = await new OutboxRelay(publisher, store).DrainAsync(0, ct); // res.Published / res.Failed ``` `IOutboxStore` is the four-method async contract (`SaveAsync` / `FetchUnpublishedAsync` / `MarkPublishedAsync` / `MarkFailedAsync`); the relay forwards through the `OutboxPublisher` delegate (`(body, queue, ct)`). The transaction boundary is **yours**. (Under `using BabelQueue.Outbox;` the writer type `Outbox` is disambiguated from the same-named namespace with a `using` alias — the standard C# pattern.) See [Transactional Outbox](/docs/spec/1.x/outbox/). ## DLQ redrive & replay-bypass `Redrive.RedriveAsync(transport, dlq, options)` (namespace `BabelQueue`) moves dead-lettered messages back onto a queue — `dead_letter` removed, `attempts` reset to `0`, everything else preserved — with `ToQueue` (sandbox), `Max`, `DryRun`, `Select` and `Bypass`. The `Replay` guard takes the delivered headers explicitly and skips effects that already fired: ```csharp using BabelQueue; public async Task Handle(IDictionary data, IReadOnlyDictionary headers) { SaveOrder(data); // idempotent core await Replay.BypassExternalEffectsAsync(headers, () => SendEmailAsync(data)); // skipped on replay } ``` `Redrive` with `Bypass = true` stamps the `bq-replay-bypass` marker through an `IHeaderPublisher` transport. See [DLQ redrive & replay-bypass](/docs/spec/1.x/redrive-and-replay/). ## GDPR field encryption `Gdpr.Protect()` / `Gdpr.Unprotect()` (namespace `BabelQueue.Gdpr`) encrypt only the `data` leaves a schema marks `x-gdpr-sensitive`, in place. `ICipher` is caller-bound; `AesGcmCipher` (on the in-box `System.Security.Cryptography.AesGcm`) is the reference. **Validate cleartext** — protect after validation on produce, unprotect before validation on consume. ```csharp using BabelQueue.Gdpr; ICipher cipher = new AesGcmCipher(key); // or implement ICipher over a KMS Gdpr.Protect(data, schema, cipher); // producer: encrypt marked leaves try { Gdpr.Unprotect(data, schema, cipher); // consumer: inverse } catch (ProtectedFieldException) { // wrong key / tampered → retry / dead-letter } ``` `BabelQueue.Schema.SchemaSensitivity.SensitivePaths(schema)` exposes the marked leaves directly. See [GDPR field encryption](/docs/spec/1.x/gdpr-field-encryption/). ## OpenTelemetry (`traceparent`) `BabelQueue.Tracing.Telemetry` emits `publish ` / `process ` activities and, on a header-carrying transport, injects/extracts the W3C `traceparent` so a consumer span is a true child of the producer span — degrading to v0.1 `trace_id` correlation otherwise. `Telemetry.Wrap(handler, headers)` and the header-aware `Telemetry.PublishAsync(…, headers, …)` overloads are the entry points; `Traceparent.Inject` / `Traceparent.RemoteParentFromHeaders` do the W3C work. It is built only on the in-box `System.Diagnostics.Activity`, so the core stays zero-dependency. The SQS, Redis and MassTransit transports carry the header. See [Observability](/docs/spec/1.x/observability/). ## Per-URN schema validation The core validator (producer guard + consumer wrap, via `MapProvider`) validates a message's `data` against the JSON Schema registered for its URN. See [Per-URN schema validation](/docs/spec/1.x/schema-validation/).