--- title: "Reliability & governance helpers" description: "Using the PHP core's reliability and governance helpers from the Laravel driver — idempotency (seen-set + persistent PdoStore/RedisStore), the transactional outbox, DLQ redrive + replay-bypass, GDPR field encryption, and OpenTelemetry traceparent. The standard worker stays untouched; the helpers compose with your handlers and add nothing to the frozen envelope." source: https://babelqueue.com/docs/laravel/1.x/reliability-and-governance/ updated: 2026-06-21T00:00:00.000Z --- # Reliability & governance helpers The Laravel driver is a thin adapter on the framework-agnostic PHP core, so the core's **optional reliability and governance helpers** are available to a Laravel app directly — they live in `babelqueue/php-sdk`, which the driver already pulls in. None touch the frozen envelope (`schema_version: 1`); each is a tooling layer that composes with your handlers and the standard `queue:work` worker. They are the Laravel face of the cross-SDK spec — follow the linked spec page for the full contract, and the [php-sdk reference](/docs/php-sdk/1.x/reliability-and-governance/) for the complete API. ## Idempotency A handler that fires an external effect (a charge, an email) should be idempotent under at-least-once delivery. Wrap the work in `Idempotent::wrap` so a redelivered `meta.id` is **skipped, not re-run**: ```php use BabelQueue\Idempotency\Idempotent; use BabelQueue\Idempotency\PdoStore; final class OrderCreatedHandler { public function handle(array $data, array $meta, string $traceId): void { $store = new PdoStore(\DB::connection()->getPdo()); // shared across the fleet Idempotent::wrap($store, fn () => $this->chargeOnce($data))($data, $meta); } } ``` For a fleet, use the shipped persistent `PdoStore` / `RedisStore` (both implement the `ClaimingStore` claim/release contract) instead of `InMemoryStore`; `PdoStore::ddl()` emits the portable `CREATE TABLE` for a migration. See [Idempotency](/docs/spec/1.x/idempotency/) and the [store deep-dive](/docs/spec/1.x/idempotency-stores/). ## Transactional outbox To remove the **dual write** between your Eloquent write and the publish, persist the encoded envelope in the **same DB transaction**, then drain it with a relay (a scheduled command or queued job): ```php use BabelQueue\Outbox\Outbox; \DB::transaction(function () use ($order, $outbox) { $order->save(); // business row $outbox->write($envelope); // the message, same transaction — no commit of its own }); ``` `Outbox`, `OutboxRelay` and the four-method `OutboxStore` (`save` / `fetchUnpublished` / `markPublished` / `markFailed`) live in `BabelQueue\Outbox`; back the store with a PDO/ORM adapter whose `save()` runs on the open transaction. See [Transactional Outbox](/docs/spec/1.x/outbox/). ## DLQ redrive & replay-bypass `BabelQueue\Redrive\Redrive::run($io, $dlq, $options)` moves dead-lettered messages back onto a queue (reset, dry-run, sandbox `toQueue`, `select`, `bypass`) — typically from an Artisan command. The `ReplayBypass` guard lets a deliberate replay re-run the idempotent core while **skipping effects that already fired**: ```php use BabelQueue\Redrive\ReplayBypass; public function handle(array $data, array $meta, string $traceId): void { $this->saveOrder($data); // idempotent core ReplayBypass::bypassExternalEffects($message, fn () => $this->sendEmail($data)); } ``` See [DLQ redrive & replay-bypass](/docs/spec/1.x/redrive-and-replay/). ## GDPR field encryption `BabelQueue\Gdpr\Gdpr::protect()` / `::unprotect()` encrypt only the `data` leaves a schema marks `x-gdpr-sensitive`, leaving `data` pure JSON and the envelope frozen. `OpenSslCipher` (over `ext-openssl`) is the reference; bind your own `Cipher` to a KMS in production. Protect before publishing, unprotect at the top of the handler — and **validate cleartext** on both sides. See [GDPR field encryption](/docs/spec/1.x/gdpr-field-encryption/). ## OpenTelemetry (`traceparent`) `BabelQueue\Otel\Tracing` emits `publish ` / `process ` spans and, on a header-carrying transport (Redis/AMQP/SQS), 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. The `trace_id` already threaded through your handler logs is the v0.1 floor. See [Observability](/docs/spec/1.x/observability/). ## Per-URN schema validation `BabelQueue\Schema\SchemaValidated::assert()` (producer guard) / `::wrap()` (consumer safety net) validate a message's `data` against the JSON Schema registered for its URN, bridged from a `babelqueue-registry` manifest via `DirProvider` — run `bqschema` (including the `gdpr --require` PII audit) in CI. See [Per-URN schema validation](/docs/spec/1.x/schema-validation/).