packages feed

kiroku-otel-0.2.0.0: CHANGELOG.md

# kiroku-otel changelog

## Unreleased

## 0.2.0.0 — 2026-05-31

### Breaking Changes

#### Complete FSM-state span coverage (`Kiroku.Otel.Subscription`, MasterPlan 7 EP-2)

- The per-batch span is now keyed on the new `KirokuEventSubscriptionDelivered`
  event and named `kiroku.subscription.deliver` (replacing the `spanFetch` /
  `kiroku.subscription.fetch` span). It is emitted **once per delivered batch on
  every target** (`$all`, category, consumer group) and in **both** phases,
  carrying `kiroku.batch.rows` and a `kiroku.subscription.state` of `"catchup"`
  or `"live"`. This closes the gap where an `$all` subscription's live phase
  produced no per-batch span. `KirokuEventSubscriptionFetched` is now ignored by
  the tracer (matched for exhaustiveness) so the DB-driven live path does not emit
  two spans per batch.
- `kiroku.subscription.stopped` is **always** emitted on `Stopped` (carrying
  `kiroku.subscription.stop_reason` and `kiroku.checkpoint.global_position`), so
  the terminal state appears in the trace even for a healthy worker that stops
  from `Live` with no open episode span.
- Internal: the per-key span state is striped from one shared `MVar` into a
  lock-free `IORef (Map SpanKey (IORef OpenState))` (each key single-writer; the
  outer registry mutated only on `Started`/`Stopped`), so the now-per-batch span
  work never serializes workers on a shared lock. `base`-only, no public API
  change, behavior-identical.
- New exported span-name constants `spanDeliver` / `spanStopped` (replacing
  `spanFetch`). A database-backed end-to-end test now runs a real `$all` worker
  with the tracer installed and asserts the catch-up, live `deliver`, and
  `stopped` spans all appear.

### New Features

#### Subscription-state tracing (`Kiroku.Otel.Subscription`, MasterPlan 6 EP-5)

- `subscriptionTraceHandler :: Tracer -> IO (KirokuEvent -> IO ())` — a ready-made
  `eventHandler` callback that turns the subscription worker's finite-state
  lifecycle into OpenTelemetry spans. Install it on `ConnectionSettings`
  (or a per-subscription) `eventHandler` and every subscription emits spans.
- Short, promptly-ending spans (the SDK only exports a span on `endSpan`, so
  there is no worker-lifetime span): per-episode `kiroku.subscription.catchup` /
  `paused` / `reconnecting` / `retrying`, per-batch `kiroku.subscription.fetch`,
  and standalone `kiroku.subscription.dead_letter` / `db_error`. Each carries a
  `kiroku.*` attribute set (subscription name, state, consumer-group member/size,
  checkpoint position, attempt, batch rows, event position, dead-letter/stop
  reason); the span-name and attribute-key constants are exported.
- The library remains separate from `kiroku-store`, so `kiroku-store` gains no
  OpenTelemetry dependency. The handler keeps its open spans in a thread-safe
  `MVar` keyed by `(subscription name, member)`. Requires a **batch span
  processor** so the synchronous callback never blocks the worker on export.

### Other Changes

#### OpenTelemetry 1.0 and current semantic-convention keys

- `kiroku-otel` now depends on the OpenTelemetry 1.0 package family:
  `hs-opentelemetry-api ^>=1.0`, `hs-opentelemetry-propagator-w3c ^>=1.0`,
  and `hs-opentelemetry-semantic-conventions ^>=1.40`.
- Subscription spans now emit generated OpenTelemetry semantic-convention
  attribute keys alongside the existing `kiroku.*` keys. Delivery spans include
  `messaging.system = "kiroku"`, `messaging.destination.name`,
  `messaging.operation.type = "process"`, and
  `messaging.batch.message_count`; database-error spans include
  `db.system.name = "postgresql"` and `db.operation.name`.

## 0.1.0.0 — 2026-05-23

### New Features

- Initial release. Exposes `Kiroku.Otel.TraceContext` with two helpers:
  - `injectTraceContext :: SpanContext -> EventData -> EventData` — encodes
    the supplied `SpanContext` to W3C `traceparent` / `tracestate` strings
    and merges them into the event's `metadata` JSON object. Existing keys
    in `metadata` are preserved.
  - `extractTraceContext :: RecordedEvent -> Maybe SpanContext` — reads
    the same JSON keys back out and decodes them through
    `OpenTelemetry.Propagator.W3CTraceContext.decodeSpanContext`. Returns
    `Nothing` when `metadata` is absent, is not a JSON object, lacks a
    `traceparent` key, or carries an unparseable value. Never throws.