packages feed

shibuya-core-0.13.0.0: CHANGELOG.md

# Changelog

## 0.13.0.0 — 2026-11-12

### Breaking Changes

- `Adapter` gains a strict `telemetry :: AdapterTelemetry` field. Record
  constructions must add `telemetry = defaultAdapterTelemetry`; positional
  construction requires the extra argument. Adapters should expose the value in
  their own configuration so users can override it.
- Per-message processing spans now carry a link to the producer's span when
  `Envelope.traceContext` decodes. Under the default `ParentAndLink` policy the span
  is still the producer span's child, so existing traces keep their shape and gain
  one link.
- Batch processing spans are always roots and no longer join the first member's
  trace. They link every member whose trace headers decode, in member order, each
  link carrying `messaging.message.id`, up to 128 links. Valid contexts beyond
  that are counted in `shibuya.batch.links.dropped`. Batch spans also carry
  `messaging.batch.message_count`.
- Processing and batch spans are renamed from `<destination> process` to
  `process <destination>`, following the messaging conventions'
  `{messaging.operation.name} {destination}`. Saved searches, alerts and sampling
  rules that match the span or operation name must change: a Jaeger or Tempo
  search for operation `orders process` becomes `process orders`. Lifecycle span
  names are unchanged.

### New Features

- Processing and batch spans carry `messaging.operation.name = "process"`, which
  the conventions mark Required, alongside `messaging.operation.type`.
  `Shibuya.Telemetry.Semantic` exports `attrMessagingOperationName`.
- New module `Shibuya.Telemetry.Correlation`, re-exported from `Shibuya.Adapter`,
  `Shibuya.Telemetry` and `Shibuya`. It provides `TraceCorrelation`
  (`ParentAndLink`, `LinkOnly`), `AdapterTelemetry` (constructor hidden, built from
  `defaultAdapterTelemetry` by record update), and the link helpers `creationLink`,
  `creationLinkWith` and `consumerSpanArgsLinkedTo`.
- `LinkOnly` makes each processing span with a usable creation context the root of
  a new trace, related to the producer only by the link. Use it for replayed,
  fanned-out or long-delayed sources and with tail-based sampling. Without usable
  headers both policies keep the ambient parent and add no link.
- New module `Shibuya.Telemetry.Process` with `withProcessSpan` (in `Tracing`) and
  `withProcessSpanIO` (for a plain `Tracer`). They open the same processing span
  as the runner for code that drains an adapter outside it. With valid headers they
  neither parent on nor link an ambient span; they are meant for independent
  message processing.
- `Shibuya.Telemetry.Correlation` also exports `maxBatchLinks`, `BatchLinks`,
  `collectBatchLinks` and `collectBatchLinksBy`, and `Shibuya.Telemetry.Semantic`
  exports `attrMessagingBatchMessageCount` and `attrShibuyaBatchLinksDropped`.
- `Shibuya.Telemetry.Effect.withRootContext` runs an action with an empty
  OpenTelemetry context attached.

### Other Changes

- With tracing disabled, per-message processing no longer decodes the W3C trace
  headers. Before, each message with a `traceparent` was decoded even under
  `runTracingNoop`.
- Per-message processing allocates about 208 bytes less on every single-message
  path. With tracing enabled and trace headers present it allocates about 210 bytes
  less, even with the added link, because the context attachment and the span now
  share one unlift.
- Accept a measured allocation increase on `Async` runners whose handlers wait:
  `concurrency-modes` `async-5` cells allocate 20–30% more than 0.12.0.0 (for
  example 1.17–1.21 MB to 1.47–1.57 MB for 100 messages with a 1 ms handler delay),
  `async-10` 4–5% and `async-20` 2–3% more, with unchanged time. `async-2`, every
  `Ahead` cell and `hot-path/async8` are flat or lower. The per-message code these
  cells run is the path that got cheaper everywhere else; the increase comes from
  streamly's worker dispatch, which is sensitive to the shape of per-message work
  (see `docs/plans/30-investigate-and-reduce-the-async-ahead-concurrency-allocation-regression.md`).
- The release comparison against 0.12.0.0 measured 10,000-message processing
  (`hot-path/serial-noop-10000`) at 9.247 to 9.098 ms, with allocation falling
  from 19,918,565 to 17,837,870 bytes. Every timing comparison passed the 10% gate
  in interleaved confirming runs at `--stdev 3`; measurements used package-default
  RTS settings on a shared workstation.

## 0.12.0.0 — 2026-10-26

### Breaking Changes

- `Ingested` gains `deliveryStatus`. Direct record constructors must add
  `deliveryStatus = pure DeliveryCurrent`; positional construction requires the
  extra field. `mkIngested` continues to build current deliveries.

### New Features

- Export `DeliveryStatus` so adapters can supersede buffered deliveries before
  handler admission. Sample status once; skip handlers, finalizers and processing
  outcomes for superseded deliveries. Filter batches in order, update their
  retained size, and skip processing when no current members remain.
- Require delivery-status actions to return promptly without side effects or
  synchronous exceptions. Unexpected exceptions propagate as infrastructure
  failures; cancellation preserves existing ownership behavior.

### Other Changes

- Add concurrent-key regression coverage for Ahead and Async runners, single and
  batch handlers, and basic and detailed metrics. Verify queued successors are
  checked after finalization and superseded deliveries skip handlers and finalizers.
- Accept this candidate's measured allocation increase of approximately 8 bytes
  per constructed record and 24 bytes per processed message in the existing
  framework workloads. All 62 timing comparisons passed the 10% gate; this
  candidate-specific acceptance leaves the general allocation policy unchanged.
- The release comparison against 0.11.0.0 measured 10,000-message processing at
  3.519 to 3.741 ms (+6.31%), with allocation increasing from 22,517,226 to
  22,756,034 bytes. Construction allocation increased from 15,017,773 to
  15,097,727 bytes for 10,000 records. Measurements used package-default RTS
  settings on a shared workstation.

## 0.11.0.0 — 2026-10-25

### Breaking Changes

- `AppConfig` gains `metricsDetail`; `ProcessorMetrics` gains optional `latency`,
  `oldestInFlight`, and `progress` fields. `MetricsHandle` gains detail state.
  Direct record construction must supply the new fields.
- `ProcessorState` gains `Paused`; `ConfigError` gains
  `InvalidMaxTrackedPartitions`. Exhaustive matches must handle these constructors.
- The exposed `SupervisedProcessor` and `AppHandle` records gain `pause` and
  `shutdownTracer`, respectively; direct construction must supply them.

### New Features

- Add processor intake pause/resume APIs with atomic admission control and refusal
  during draining. Already admitted polls and inbox work may finish while paused.
- Add opt-in detailed metrics with bounded synchronized in-flight slots, latency
  histograms, oldest in-flight inspection, and acknowledged cursor progress bounded
  per partition. Basic accounting remains the default.
- Add lossless tagged `Cursor` JSON and `MessageId` JSON instances, detail-aware
  handle and runner constructors, and read-only slot-miss diagnostics.
- Trace source-stream lifetimes and once-only adapter shutdowns as Internal spans
  carrying `shibuya.processor.id`, including exception and cancellation outcomes.
  Capture the startup tracer to preserve the existing stop API.

### Bug Fixes

- Reduce serial intake startup migration by waiting for input before processor
  setup and yielding the producer before it publishes messages. Preserve bounded
  admission, source ownership and cancellation using ordinary Haskell operations.
- Reduce intake overhead by checking queue emptiness and receiving in one STM
  transaction, preserving bounded admission and completion/halt wakeups.
- Preserve ingestion cancellation when tracing callbacks swallow asynchronous
  exceptions. Keep source effects on their owning thread and isolate SDK callbacks
  on an owned worker, preventing source entry after cancellation during span start.
- Remove per-message zip state from pause admission and avoid unreachable pause
  checks in blocking standalone helpers, preserving supervised intake control.
- Preserve shutdown deadlines and caller cancellation when tracing callbacks catch
  asynchronous exceptions; prevent cancelled shutdowns from starting more adapter
  cleanup.
- Omit invalid contexts from W3C propagation instead of emitting unusable headers.

### Other Changes

- The final matched comparison against 0.10.0.1 passed all 62 existing benchmarks
  at the 10% timing threshold with package-default RTS settings. The 100-message
  standalone counter measured 41.32 to 38.16 microseconds (-7.7%) after the startup
  handoff repair. These measurements were taken on a shared workstation.
- The same comparison observed higher allocation for serial delayed I/O
  (471,316 to 570,252 bytes per invocation, +21.0%) and ahead-5
  (2,132,353 to 2,171,847 bytes, +1.85%). Their timing changes were +2.9% and
  -2.7%, respectively; the allocation increases remain unexplained.
- Share the private disabled compatibility tracer across invocations and document
  its non-recording, zero-ID callback sentinel.
- Specialize basic workers without per-message detail closures; document bracketed
  application acquisition and cleanup in the OpenTelemetry guide.

## 0.10.0.1 — 2026-10-05

### Other Changes

- Admit random 1.3 alongside 1.2 for the existing retry jitter API.

## 0.10.0.0 — 2026-09-21

### Breaking Changes

- `ShutdownConfig` gains `totalShutdownTimeout`; existing record construction must choose an
  overall shutdown deadline. The default keeps the 30-second drain timeout and adds a
  60-second bound covering adapter shutdown and graceful drain before forced supervisor
  stop begins.
- `ConfigError` gains `DuplicateProcessorId`; `PolicyError` gains `InvalidConcurrency` and
  `ConcurrencyCapacityOverflow`. Exhaustive matches must handle the new constructors.
- Permanent framework-owned finalization failure now throws `ProcessorFailure` with the
  failed message identity instead of being caught as graceful `ProcessorHalt`.
- `ProcessorState.Processing` gains a sampled last-progress timestamp in addition to its
  burst-start timestamp. Exhaustive constructor matches must accept the third field; its
  JSON object gains the additive `lastProgress` field.

### Bug Fixes

- Reject duplicate processor IDs and invalid or overflow-prone concurrency before acquiring
  resources.
- Wake idle Serial, Ahead, Async, partitioned, and batch intake when a handler halts or
  finalization fails.
- Stop keyed input immediately on worker failure and close the worker start-gate ownership
  gap.
- Make startup and shutdown exception safe: all adapter shutdowns are attempted, the master
  is always stopped, a blocking adapter is bounded by the total deadline, and repeated or
  concurrent stop calls invoke adapters once.
- Retain a bounded internal terminal lifecycle snapshot after live metrics unregister so
  ignored failures remain observable with processor and message identity.
- Observe batch ticker failure from the consuming stream instead of allowing an unmonitored
  ticker death to leave the batcher blocked.
- Reset active state on the final completion, restamp every observed burst, floor duplicate
  completions at zero in-flight, and track progress during metrics sampling without adding a
  clock read to the handler hot path.

### Other Changes

- Document the accepted batching resource boundary: `inboxSize` does not cap distinct
  in-progress batch keys, so callers must bound externally controlled key cardinality. The
  release evidence observed a conservative upper envelope of 703 bytes per additional key
  over 1,000 to 50,000 keys; this finite measurement is not an implementation-enforced
  production limit.
- Depend directly on `effectful-core`, which provides every Effectful module used by the core,
  tests, examples, and benchmarks. The accepted range keeps the 2.6 family and 2.7.1.1 or
  later while excluding 2.7.0.0 through 2.7.1.0, whose upstream changelog records a
  per-operation performance regression for dynamically dispatched effects.

## 0.9.0.3 — 2026-09-20

### Bug Fixes

- Start the NQE supervisor without linking it to the thread that called `runApp`.
  NQE links every process it starts, and a supervisor with no children left can
  only be woken through a mailbox reachable solely through the application handle.
  A caller whose processors had all finished, halted or failed, and which dropped
  the handle and kept running, was therefore killed at the next major garbage
  collection by `ExceptionInLinkedThread ... thread blocked indefinitely in an STM
  transaction`. Such a supervisor is now simply collected.
- Deliver a processor failure to the caller exactly once under `StopAllOnFailure`.
  The supervisor's link re-delivered the failure that the processor's own link had
  already delivered, so a caller that handled the first `ExceptionInLinkedThread`
  could be killed by a second one moments later. Sibling shutdown and propagation
  are unchanged; they never depended on the supervisor's link.

### Other Changes

- Add a second process-isolated garbage-collection regression,
  `shibuya-core-gc-finished-test`, covering an application that has finished and
  whose handle has been dropped, and a lifecycle test asserting a single failure
  delivery. `cabal test shibuya-core` now runs three suites.

## 0.9.0.2 — 2026-09-19

### Bug Fixes

- Remove the unused linked master mailbox actor. Its inbox had no senders, so
  a caller that started an idle application and retained only `waitApp` could
  receive `ExceptionInLinkedThread ... thread blocked indefinitely in an STM
  transaction` during major garbage collection. `Master` now owns only the
  NQE supervisor and metrics registry; processor supervision, failure
  propagation, metrics access, and explicit shutdown behavior are unchanged.

### Other Changes

- Add a process-isolated garbage-collection regression that exercises bare
  `runApp`/`waitApp` liveness under the normal optimized test profile.

## 0.9.0.1 — 2026-09-15

### Other Changes

- Raise the `effectful` upper bound from `<2.7` to `<2.8` so downstream
  packages can move to effectful 2.7. Built and tested against effectful
  2.7.1.0 / effectful-core 2.7.1.2 on GHC 9.12.4; no source changes were
  needed. Nothing here uses the APIs 2.7 deprecated, renamed, or removed:
  `withLiftMap`, `stateM`/`modifyM`, the `*StateMVar` functions,
  `Effectful.Internal.MTL`, `SharedSuffix`, `KnownEffects`, or the `LocalEnv`
  `handlerEs` parameter. The lower bound stays at 2.6.1 so consumers are not
  forced to upgrade.

## 0.9.0.0 — 2026-08-10

### Breaking Changes

- `DeadLetterReason` gains `ApplicationFailure DeadLetterCode Text`. Exhaustive
  matches must handle the new constructor or migrate to the total
  `deadLetterReasonCode`, `deadLetterReasonDetail`, and
  `renderDeadLetterReason` functions. Under PVP this ships as 0.9.0.0;
  downstream `shibuya-core ^>=0.8` bounds intentionally exclude it and must be
  reviewed before widening.

### New Features

- Add opaque, startup-validated application dead-letter codes and canonical
  code/detail projections. Existing poison, invalid-payload, and retry-exhaustion
  render strings remain byte-for-byte unchanged.
- Single-message processing spans now emit
  `shibuya.dead_letter.reason.code` for dead-letter decisions and use the
  canonical rendered reason as the error status description. Human detail is
  not an attribute or metric label.

## 0.8.0.1 — 2026-07-04

### Other Changes

- Reduced per-message allocation on the `Async` and `Ahead` concurrency paths
  by widening the streamly dispatch buffer to `2 * n` (the thread bound stays
  at `n`). A buffer equal to the thread count throttled worker dispatch and
  caused dispatch churn that allocated up to +90% on the `Async`
  concurrency-levels benchmarks (EP-30). No API or behavior change.
- Documented the accepted ~126 bytes/message cost of the separate handler and
  finalizer exception frames that guarantee "always finalize" (EP-31).

## 0.8.0.0 — 2026-07-04

### Breaking Changes

- New application code should import the `Shibuya` umbrella module. It
  re-exports the stable app, handler, batch, retry, policy, metrics, and
  tracing surface. `Shibuya.Core` remains as a deprecated compatibility
  re-export for this release.
- Runner internals moved under `Shibuya.Internal.Runner.*` and carry no PVP
  stability guarantee. The public moves are:
  `Shibuya.Runner.Metrics` -> `Shibuya.Core.Metrics`;
  `Shibuya.Runner.{Master,Supervised,Batcher,BatchProcessor}` ->
  `Shibuya.Internal.Runner.*`; `Shibuya.Runner.{Halt,Ingester}` ->
  `Shibuya.Internal.Runner.*` as hidden implementation modules.
  `Shibuya.Runner.Serial` and `Shibuya.Runner.Processor` were deleted.
  `Shibuya.Prelude` is no longer exposed.
- `AppHandle` and `Master` are now opaque in public modules. Use
  `getAppMaster`, `getAppMetrics`, `waitApp`, `stopApp`, and
  `stopAppGracefully` instead of record fields. Metrics servers should import
  `Master`, `getAllMetricsIO`, and `getProcessorMetricsIO` from
  `Shibuya.App`.
- `runApp` now takes an `AppConfig` record and validates `inboxSize`.
  Invalid sizes return `Left (AppConfigInvalid (InvalidInboxSize n))`
  instead of crashing or stalling.

  ```haskell
  -- before
  runApp IgnoreFailures 100 processors

  -- after
  runApp defaultAppConfig processors

  -- custom strategy/size
  runApp defaultAppConfig {strategy = IgnoreFailures, inboxSize = 100} processors
  ```

- Handlers now receive `Message es msg` instead of `Ingested es msg`.
  Handlers that only use `.envelope` or `.lease` need a signature update;
  handlers can no longer call the adapter finalizer directly. The framework
  owns finalization and calls `AckHandle.finalize` after the handler returns
  or throws. Adapter finalizers should remain idempotent or phase-tracked.
- `BatchHandler` likewise receives `NonEmpty (Message es msg)` instead of
  `NonEmpty (Ingested es msg)`.
- Removed dead or misleading surface: `HandlerError.HandlerTimeout`,
  `RuntimeError.InboxOverflow`, `StreamStats.dropped`, `incDropped`,
  the always-zero `shibuya_messages_dropped_total` Prometheus metric, and
  `Shibuya.Telemetry.Config`.
- Renamed `Ordering` to `OrderingPolicy`, eliminating the need for
  `import Prelude hiding (Ordering)`. `runSupervised` also takes the ordering
  policy before `Concurrency`.
- `BatchingProcessor` now rejects `PartitionedInOrder` combined with `Ahead`
  or `Async`, because batches are scheduled by `BatchKey`, not
  `Envelope.partition`.
- Renamed `Shibuya.Stream.batchStream` to `chunksOf`.

### New Features

- Added `mkEnvelope :: MessageId -> msg -> Envelope msg`. It fills optional
  metadata with defaults; set optional fields with record update syntax. This
  is the recommended construction path because future optional `Envelope`
  fields can then be added without forcing adapter code to change, avoiding
  the kind of major bumps required by 0.5.0.0 and 0.7.0.0.
- Added `mkIngested :: Envelope msg -> AckHandle es -> Ingested es msg`.
- Added `Message es msg`, the read-only handler view containing envelope and
  optional lease.
- `PartitionedInOrder` with `Ahead` or `Async` is now enforced for
  single-message processors. Messages sharing an `Envelope.partition` key are
  processed and acknowledged in arrival order, distinct partitions run
  concurrently up to the configured bound, and messages with no partition key
  are unconstrained.

### Bug Fixes

- Handler exceptions on the single-message runner path now finalize the message
  with `AckRetry (RetryDelay 0)` using the same bounded finalizer retry helper
  as batch processing, so adapters observe a disposition instead of silently
  losing or stranding the delivery. Exhausted finalizer retry now halts the
  processor loudly with the failed message id.
- Batch accumulation failures, including exceptions from user-provided
  `batchKey` functions, now fail the processor loudly instead of letting the
  batcher consumer die while the processor reports clean completion.
- Batch processing now isolates `AckHalt`: batches already emitted after a halt
  no longer re-enter the user batch handler, and their messages are finalized
  with `AckRetry (RetryDelay 0)` instead of being left unacknowledged.
- The keyed batch scheduler now keeps its pending queue bounded and owns its
  reader and worker threads with structured cleanup, preventing unbounded pulls
  under slow handlers and preventing finalization after forced shutdown returns.

### Other Changes

- Corrected `Ahead` documentation. It preserves stream-yield order only;
  handler execution and acknowledgement may complete in any order.
- `TraceHeaders` remains a type alias with the same representation as
  `Headers`; its docs now clarify that it is only the parsed W3C trace-context
  projection, while `Headers` is the complete broker header set.
- `shibuya-pgmq-adapter` and `shibuya-kafka-adapter` should migrate envelope
  construction to `mkEnvelope` in their next releases.
- Removed unused dependencies from `shibuya-core`: `effectful-core`,
  `generic-lens`, `lens`, `uuid`, and `vector`. Internal unlift imports now use
  the stable top-level `Effectful` exports.

## 0.7.1.0 — 2026-06-15

### Bug Fixes

- Supervised processors now observe their ingester async after draining
  already-ingested messages. If the source dies with an exception, the
  processor is marked `Failed`, its `done` flag is set, and the exception is
  rethrown to the supervisor instead of being reported as a clean stream
  completion.

## 0.7.0.0 — 2026-06-05

### Breaking Changes

- `Envelope` gained a `headers :: !(Maybe Headers)` field carrying every
  message header the source broker delivered, in order and including
  duplicates. Direct constructions of `Envelope` must add the field.
  `Nothing` means the adapter does not surface headers; `Just []` means
  it does and the message had none. The new `Headers` type alias
  (`[(ByteString, ByteString)]`) is exported from `Shibuya.Core` and
  `Shibuya.Core.Types`. The W3C trace headers continue to appear in
  `traceContext` as before; they now also appear verbatim in `headers`.

## 0.6.0.0 — 2026-05-31

### Breaking Changes

- OpenTelemetry messaging spans now emit `messaging.operation.type =
  "process"` instead of the deprecated `messaging.operation = "process"`
  wire key. The Haskell constant `attrMessagingOperation` keeps the same
  name and now resolves to the current semantic-conventions key, so source
  imports continue to compile. Dashboards, alerts, and trace queries that
  filter on `messaging.operation` must be updated to
  `messaging.operation.type`.

### Other Changes

- Upgrade OpenTelemetry dependencies to the 1.0 ecosystem:
  `hs-opentelemetry-api ^>= 1.0`,
  `hs-opentelemetry-propagator-w3c ^>= 1.0`, and test-only
  `hs-opentelemetry-exporter-in-memory ^>= 1.0`.
- Move `hs-opentelemetry-semantic-conventions` to the latest Haskell
  generated package available with the 1.0 release, `^>= 1.40`, and source
  Shibuya's generic messaging keys from its typed exports.

## 0.5.0.0 — 2026-05-05

### Breaking Changes

- `Envelope` gained an `attributes :: !(HashMap Text Attribute)` field
  carrying adapter-supplied OpenTelemetry attributes for the per-message
  processing span. Direct constructions of `Envelope` must add the
  field; pass `Data.HashMap.Strict.empty` when the adapter has nothing
  to contribute (the common case). `Envelope`'s `NFData` instance is
  now hand-written rather than derived (because `Attribute` from
  `hs-opentelemetry-api` does not ship `NFData`); the strictness shape
  is unchanged for every other field.

### New Features

- `Shibuya.Runner.Supervised`'s `processOne` now applies
  `envelope.attributes` to its Consumer-kind span after setting the
  framework-default `messaging.*` attributes, so adapter-supplied
  keys override framework defaults of the same name. This lets
  broker-aware adapters (Kafka in particular) emit typed attributes
  (`messaging.kafka.destination.partition`,
  `messaging.kafka.message.offset`) and override the
  `messaging.system` default — without opening a second span. The
  per-record `Shibuya.Adapter.Kafka.Tracing.traced` wrapper that
  previously bolted these on can now be removed; see the audit
  document `docs/plans/9-audit-and-improve-opentelemetry-api.md` (Finding F1, P0)
  for the full motivation.
- `Shibuya.Telemetry.Propagation.currentTraceHeaders ::
  (Tracing :> es, IOE :> es) => Eff es (Maybe TraceHeaders)` looks
  up the currently-active OTel span (via thread-local context) and
  encodes its trace context as W3C headers, ready for an adapter
  to attach to an outgoing message. Returns `Nothing` when tracing
  is disabled or there is no active span. The intended call sites
  are adapter-side DLQ writes (so the failing-consumer's trace
  links to the resulting DLQ message) and ad-hoc producer paths.
  See the audit document `docs/plans/9-audit-and-improve-opentelemetry-api.md`
  (Findings F3 and F5).

## 0.4.0.0 — 2026-04-29

### Breaking Changes

- `Envelope` gained an `attempt :: !(Maybe Attempt)` field carrying the
  adapter's delivery counter (zero-indexed; `Nothing` if unknown). Direct
  constructions of `Envelope` must add the field. The new `Attempt`
  newtype is exported from `Shibuya.Core` and `Shibuya.Core.Types`.

### New Features

- New module `Shibuya.Core.Retry` providing `BackoffPolicy`, `Jitter`
  (`NoJitter`, `FullJitter`, `EqualJitter`), `defaultBackoffPolicy`, the
  pure evaluator `exponentialBackoffPure`, the effectful
  `exponentialBackoff`, and the handler convenience `retryWithBackoff`.
  Handlers can now compute exponentially-growing, jittered retry delays
  with a single call: `retryWithBackoff defaultBackoffPolicy
  ingested.envelope`. Pulls in `random ^>=1.2` as a new build-depends
  (already a transitive dep — ships with GHC). See the module haddock and
  the new `RetrySpec` test for usage patterns.
- A runnable end-to-end demonstration of the new API lives in the
  sibling [`shibuya-pgmq-adapter`](https://github.com/shinzui/shibuya-pgmq-adapter)
  repo at `shibuya-pgmq-example/`, exposed via the `backoff-demo`
  subcommand of `shibuya-pgmq-consumer`. The plan
  `docs/plans/8-demonstrate-backoff-end-to-end.md` records setup
  instructions and captured transcripts.

## 0.3.0.0 — 2026-04-24

Version bumped to track the shared release version. No user-visible
changes to `shibuya-core` itself.

## 0.2.0.0 — 2026-04-22

### Breaking Changes

- `Shibuya.Telemetry.Semantic`: rename `processMessageSpanName :: Text` to
  `processSpanName :: Text -> Text`. The span name is now built from the
  destination (processor id), yielding e.g. `"shibuya-consumer process"`, in
  line with the OpenTelemetry messaging-spans recommendation.
- `Shibuya.Telemetry.Semantic`: remove `attrMessagingDestinationPartitionId`
  (replaced by the Shibuya-specific `attrShibuyaPartition`).
- `Shibuya.Telemetry.Semantic`: remove `eventHandlerException`.

### New Features

- `Shibuya.Telemetry.Semantic`: add `attrMessagingOperation` and
  `attrShibuyaPartition` attribute keys. Messaging attribute keys are now
  sourced from the typed `AttributeKey` values exported by
  `OpenTelemetry.SemanticConventions`, so upstream renames surface as
  compile errors rather than silent wire-format drift.
- `Shibuya.Core.Types`: add `NFData` instances for `MessageId`, `Cursor`,
  and `Envelope a` (when `a` itself has an `NFData` instance). Benchmark
  authors no longer need to declare these as orphans.

## 0.1.0.0 — 2026-02-24

Initial release.

### New Features

- Multi-queue processing with `runApp` and `QueueProcessor` API
- Backpressure via bounded inbox
- AckHalt support to stop processing on halt decision
- Serial, Ahead, and Async concurrent processing modes
- Policy validation enforcement (StrictInOrder requires Serial)
- Graceful shutdown with configurable drain timeout
- NQE-based supervision via Master/Supervisor
- OpenTelemetry tracing integration with W3C trace context propagation
- Mock adapter for testing

### Bug Fixes

- Fix race conditions and incomplete cleanup in runner
- Fix data races in concurrent test handlers using atomicModifyIORef'

### Other Changes

- Consolidate error handling with unified error types
- Define own SupervisionStrategy type to decouple from NQE
- Use registerDelay for cleaner timeout handling
- Replace polling with STM blocking in waitApp