packages feed

keiro-0.12.0.0: CHANGELOG.md

# Changelog

All notable changes to the `keiro` library are recorded here. The format follows
[Keep a Changelog](https://keepachangelog.com/), and the project aims to follow
the [Haskell Package Versioning Policy](https://pvp.haskell.org/).

## Unreleased

## 0.12.0.0 — 2026-08-14

### Breaking Changes

- `Keiro.Workflow.Awakeable` no longer exports
  `deterministicAwakeableId` or `legacyDeterministicAwakeableId`. Compatibility
  tooling that must inspect generation-0 rows should import
  `generation0AwakeableId` or `preUtf8Generation0AwakeableId` from
  `Keiro.Workflow.Awakeable.Compatibility`; ordinary workflow code must retain and
  pass the `AwakeableId` returned by allocation.
- The schema-versioned rebuild resume contract advances to
  `versioned-contract-v3` and its persisted runner to
  `keiro/versioned-rebuild/v3`. The contract adds run-scoped dedup staging and a
  persisted promotion admission limit. Complete active v2 versioned runs with the old
  runtime or abandon them before upgrading; they cannot resume under v3.
- `adoptCatalogGroups` now returns `CatalogAdoptionResult`, including adopted group
  metadata, per-registration update/insert outcomes, and removed old-name rows.
  `CatalogAdoptionPlan` likewise adds planned registration actions and orphan rows; code
  constructing or exhaustively matching either result must handle the registry-complete
  contract.
- `Keiro.Workflow.Instance.claimInstance` now returns `ClaimOutcome` instead of
  `Bool`, distinguishing an acquired lease from a live foreign lease, crash
  pacing, and an instance that became unavailable. `ResumeSummary` adds
  `advanced`, `paced`, `sleepDue`, and `unregisteredNames`; `advanced` counts
  only a fresh journal append by the re-invocation, a terminal failure recorded
  at the crash ceiling, or an external wake observed mid-pass. Replay-only
  re-suspensions and terminal short-circuit races report no advance, so bounded
  drains terminate on every reachable pool. `WorkflowRunOptions` adds
  `onJournalAppend`; direct record constructions must initialize it, while
  `defaultWorkflowRunOptions` users are unaffected.

- The catalog rebuild resume contract advances to `contract-v4:` and the persisted runner
  format to `keiro/projection-replay/v4`. The contract now pins replay-adapter application
  order. `CatalogRebuildError` gains `CatalogRebuildSliceMismatch`, and
  `abandonCatalogRebuild` compares group slices rather than resume contracts.

- Canonical identity advances to `catalog-v3:`, `slice-v2:`, `contract-v3:`, and
  `keiro/projection-replay/v3`; catalog inventory and rebuild preview JSON advance to v2.
  Stored `slice-v1:` groups require preview and explicit live-group adoption. An active v2
  replay cannot resume under the v3 runner: complete it with the old runtime or abandon it
  before upgrading and adopting metadata.
- Catalog fingerprints, group metadata, rebuild contracts, and grouped errors
  use the new canonical slice identity. `GroupRebuildMetadata.catalogFingerprint`
  is now `sliceFingerprint`; fingerprint-drift errors are slice-specific; and
  `RebuildRunReport` adds `groupSliceFingerprint`. Persisted replay format is
  `keiro/projection-replay/v3`. Complete or abandon active catalog rebuilds before
  migration `0024` or the v3 runner cutover, then explicitly adopt stale group rows.

- Requires `kiroku-store >=0.7 && <0.8`. Direct constructors of
  `SubscriptionDeclaration` and exhaustive matches on grouped
  `RebuildStartError` must adopt the explicit checkpoint lifecycle surface.
  `KeiroMetrics` gains the
  `projectionGlobalPositionDistance` gauge field; code constructing that record
  directly must initialize it. Exhaustive custom Kiroku interpreters must also
  implement the 0.5 checkpoint lifecycle, 0.6 visible-head effect surface, and
  0.7 renewable history-retention lease surface.
- Requires `keiro-core ^>=0.12.0.0` and `shibuya-core ^>=0.9.0.0`, replacing
  the 0.11 package-family and Shibuya 0.8 bounds.
- `Keiro.Workflow.JournalAppendOutcome` gains a `JournalRefusedTerminal !Text`
  constructor. The journal-append transaction now declines an ordinary
  `StepRecorded` append into a workflow generation that already carries a
  `__workflow_cancelled__` or `__workflow_failed__` marker, and reports which
  marker refused it. A refusal is not an error: the wake source should settle
  its own durable row and deliver nothing. `appendJournalEntry` /
  `appendJournalEntryReturningId` absorb it and return normally, so only code
  that pattern-matches on `JournalAppendOutcome` needs to change.

- A workflow that is terminally *failed* now stops at the next step boundary,
  as a cancelled one already did. Previously the failure marker was checked only
  at run entry, so a direct `runWorkflow` call that overlapped the resume
  worker's `WorkflowFailed` marker kept executing fresh side effects. Runs that
  relied on that behaviour will now return `Failed` at the boundary.

- `Keiro.Workflow.Instance.markInstanceSuspended` is replaced by
  `markInstanceSuspendedAwaiting :: WorkflowName -> WorkflowId -> Int -> Text ->
  Eff es ()`, which takes the run's generation and the awaited step name and
  arbitrates the suspended-status write against a concurrent wake delivery.

- `Keiro.Workflow.Instance.recordCrashTx` returns `Maybe Int32` instead of
  `Int32`. `Nothing` means the workflow reached a terminal status between
  crashing and having that crash recorded, so no attempt was counted — an
  ordinary race, not an error.

- `Keiro.Workflow.Gc.gcWorkflowsOnce` and `runWorkflowGcWorker` gain an
  `Error StoreError :> es` constraint, which they need to isolate a failing
  deletion. Callers running them under `runStoreIO` are unaffected.

- `Keiro.Workflow.Resume.ResumeLogEvent` gains a `ResumeCrashRecordSkipped
  !Text !Text` constructor (workflow name, workflow id). Only code that
  pattern-matches exhaustively on `ResumeLogEvent` needs to change.

- `Keiro.Workflow.Resume.WorkflowResumeOptions` gains a
  `maxConcurrentAdvances :: !Int` field. Only code that builds the record
  without `defaultWorkflowResumeOptions` needs to change; the default is 1,
  which is the behaviour every previous release had.

### New Features

- Projection revisions now bind every live closure to its exact inline owner or
  subscription/dedup delivery capability. Catalog validation requires one handler per
  declared capability and restricts it to the supplying projection's targets; command
  and subscription paths dispatch only their matching closure. The canonical identity
  advances to `catalog-v7:` and `slice-v6:`.
- Projection revisions may declare explicit `StreamScopedReplay` policies. The targeted
  runner repairs one complete retained stream against the persisted serving revision in
  one group-fenced transaction, verifies exact target evidence, backfills ordinary async
  dedup keys, leaves subscription checkpoints unchanged, and refuses truncated/deleted
  history, active rebuilds, slice drift, incomplete serving bindings, or a locked event
  count above the request's positive `maxEvents` admission limit. Oversized work refuses
  before the group-wide writer fence.
- `ProjectionCatalogOperations` adds database-backed targeted-reprojection preview and
  outcome reports. Preview v2 exposes event count, expected dedup claims, and the
  reviewed maximum; outcome v2 records the admitted maximum. Jitsurei demonstrates a V2
  serving-row repair that leaves unrelated streams unchanged.

- Projection catalogs now declare versioned all-row and keyed external read contracts,
  including validated query/shape/revision compatibility, immutable SQL signatures,
  application-owned keyed implementation identity, and monotonic surface generations.
  Canonical identity advances to `catalog-v5:` and `slice-v4:`.
- `Keiro.ReadModel.External` reconciles those declarations into execute-only,
  security-definer functions in `keiro_read`, backed by private managed bindings and a
  lifecycle guard with stable `KR001`/`KR002`/`KR003` SQLSTATEs. The adversarial release
  pass makes crossed-promotion snapshots fail retryably with `KR001`, caps all-row
  results at 100 with `KR004`, verifies exact keyed set-result types, and reports grants
  for the selected overload only. Registration,
  reviewed adoption, versioned-rebuild start, and promotion perform reconciliation in
  their own transaction; retirement is explicit and dependency/grant previewed.
  All-row bindings project only the stable composite type's attributes, candidate
  versions remain metadata-only until promotion, and an explicit implementation-backed
  compatibility wrapper may retain an old zero-argument signature after a breaking
  cutover.
- `ProjectionCatalogOperations` exposes external-read inspection and retirement reports
  with stable JSON envelopes over the same dependency/grant preview used by the runtime.
- `ProjectionGroupStatusV1`, `ServingPositionBasis`,
  `listProjectionGroupStatuses`, and `lookupProjectionGroupStatus` decode the frozen
  public SQL status contract. Catalog registration and reviewed adoption now reconcile
  per-group append/checkpoint cursor authority transactionally.
- Projection catalogs now declare executable `ProjectionRevision` bridges, total
  `PhysicalTargets` mappings, per-target schema/provisioner/validator contracts, and
  ordered promotion names. Candidate Language 5 accepts `projection-revision` blocks and scaffolds
  transaction-local provision, validation, live, replay, and verification holes.
- `Keiro.ReadModel.Rebuild` now implements durable schema-versioned target generations,
  converging candidate replay beside a live serving revision, renewable Kiroku history
  retention, resumable writer-fence/final-head phases, bounded atomic multi-target
  promotion, async dedup/checkpoint reconciliation, restricted exact-shape cloning, and
  dependency-aware retired-generation preview/drop. `ProjectionCatalogOperations`
  exposes dedicated versioned run and retirement reports without widening the legacy
  offline rebuild protocol.
- `Keiro.DeterministicId.deterministicIdProbes` centralizes the unchanged
  current-first, legacy-only-for-moved-seeds compatibility order shared by
  process-manager preflights and generation-0 awakeable adoption.
- `QueryFreshness`, `HeadScope`, `QueryCursorAuthority`, `ReadModelBlueprint`, truthful
  read-model builders, and `runQueryWithFreshness`. Immediate reads need no cursor;
  captured-head and caller-position waits require one durable cursor and fail with typed
  missing-cursor or missing-position errors before polling. Candidate Language 5 generated
  code now constructs only through `immediateReadModel` or `headWaitingReadModel`, deriving
  cursor authority from its validated projection owner.
- Catalog query inventory now records normalized freshness and the optional cursor derived
  from the validated projection owner. Validation rejects waiting queries with zero or
  several compatible durable cursors, and canonical identity normalizes set-valued owned
  targets.
- `ProjectionHandlerCapability`, `ResolvedQuerySupply`, and
  `resolvedQuerySupplies` expose the closure-free relationship from each validated
  query model to the single projection owner of its complete observed-target set.
  Validation rejects empty, missing, and split suppliers with stable diagnostic codes;
  several queries may resolve to one owner without duplicating its handler.

- `DomainDecision`, `DomainCommandOutcome`, `SilentCommandContext`,
  `SilentDomainDecision`, and `DomainCommandHandler`, plus `runDomainCommand`
  and `forgetDomainDecision`. Accepted decisions carry the exact non-empty
  event batch; explicitly selected state-preserving silent edges carry typed
  rejection/no-op payloads; unmatched commands and infrastructure failures
  remain `CommandError`.
- Domain-aware SQL and projection runners, including controlled transaction and
  catalog-fenced outcomes. Rejection/no-op opens no append transaction and runs
  no callback or inline projection; an optimistic conflict returns only the
  final rehydrated decision.
- Additive `DomainRouter` and `DomainProcessManager` configurations, detailed
  one-shot result families, and configurable/default workers. Accepted,
  rejection, and no-op are handled; accepted duplicates remain distinct; only
  genuine `CommandError` enters existing failure policy. Workers use strict
  payload-free summaries rather than retaining detailed fan-out results.
- The `keiro.command.decision` span attribute and
  `keiro.command.decisions` counter, with the closed value set `accepted`,
  `rejected`, and `no_op`. Application payloads never become telemetry labels
  or error descriptions.
- `subscriptionPositionFromInventory` derives a subscription's durable floor
  across all matching consumer-group members, and `readSubscriptionPosition`
  consumes Kiroku's public one-statement checkpoint inventory instead of
  querying the owned table. `storeHeadPosition` delegates to Kiroku 0.6's
  public payload-free visible-global-head effect; transactional rebuild
  completion uses its matching public statement.
- `recordProjectionGlobalPositionDistance` records the preferred
  `keiro.projection.global_position_distance` gauge and the deprecated
  compatibility `keiro.projection.lag` gauge from the visible head and durable
  member floor. Both use `{position}` and neither claims to count relevant
  events.
- Projection catalog subscriptions now carry Kiroku's
  `MissingCheckpointPolicy` through registration, inventory, stable rendering,
  ordering, fingerprints, and operator JSON. Validation rejects
  `FromCurrentHead` for replayable clear-before-replay ownership.
- Group rebuild uses `resetSubscriptionCheckpointsTx`, returns the exact reset
  member keys, and reports `RebuildSubscriptionCheckpointsMissing` while
  condemning the entire preparation transaction if a declared subscription has
  no persisted member. The legacy unmanaged rebuild also uses the public API.
- Projection catalog fingerprints now use an injective, length-prefixed
  canonical preimage and explicit `catalog-v3:` / `slice-v2:` prefixes. Group
  registration and rebuild lifecycle fences use only the affected group slice,
  while rebuild runs retain the whole catalog fingerprint as provenance.
- `previewCatalogAdoption` and `adoptCatalogGroups` provide a read-only plan and
  an all-or-nothing path for adopting reviewed slice changes and reconciling
  query-model registration metadata. Adoption normally requires a live group;
  a failed stale-format group may adopt while remaining fenced for recovery.
- `CatalogRebuildRunPreCanonical` and `preCanonicalRunSliceSentinel` make the
  migration-0024 recovery boundary explicit. Sentinel runs are never resumable
  but are inspectable and idempotently abandonable while active; failed
  stale-format groups can be adopted without lifting their fence, and a fresh
  rebuild can then start from `failed`.
- `CatalogAsyncDedupSpec`, `catalogAsyncIdempotencyKeys`,
  `AsyncDedupBackfill`, `collectAsyncDedupBackfill`,
  `resetDeclaredSubscriptions`, `insertProjectionDedupBatchStmt`, and
  `CatalogRebuildPromotionCheckpointsMissing` expose the catalog-derived
  redelivery-safety inputs and transactional primitives used by rebuild
  promotion.
- `Keiro.Workflow.Resume.resumeWorkflowsOnceUpTo` runs a resume pass over at
  most the requested number of candidates. `resumeWorkflowsOnce` retains its
  unbounded compatibility behavior and delegates to the bounded function.

- `Keiro.Projection.Catalog.Operations` derives versioned JSON inventory,
  pure/registered rebuild previews, and start/inspect/resume/abandon actions
  from one `ValidatedProjectionCatalog`. Callers cannot provide replacement
  targets, sources, handlers, subscriptions, or dedup lists.

- `Keiro.ReadModel.Rebuild.abandonCatalogRebuild` records explicit failure
  evidence against both the durable run and its catalog group while retaining
  the writer fence.

- `Keiro.Workflow.Schema.terminalMarkers` / `terminalMarkersTx` report which
  stopping terminal markers a workflow generation carries, in one query.

- `Keiro.Workflow.Schema.workflowStepLockKey` exposes the per-step advisory-lock
  key derivation shared by the append path and the suspend write.

- `Keiro.Workflow.Gc.runWorkflowGcWorkerWith` takes a `Text -> IO ()` logging
  hook, mirroring the resume worker's `logEvent`. It reports both a failed pass
  and a partial one (fewer workflows collected than scanned).

- A resume pass can advance several workflows at once. Set
  `WorkflowResumeOptions.maxConcurrentAdvances` above 1 and `resumeWorkflowsOnce`
  advances that many candidates concurrently, so one slow step body no longer
  delays every other workflow in the pass. It is safe by construction —
  discovery returns one row per instance, each advance holds its own lease, and
  the append path's per-step advisory lock already serializes same-step writers
  across processes — but it multiplies in-flight database traffic, so size it
  against the store's connection-pool headroom. The default of 1 preserves the
  previous sequential behaviour exactly. `logEvent` may now be called from
  several threads and must be thread-safe when concurrency is enabled.

- `Keiro.Workflow.Resume.ResumeSummary` has `Semigroup` and `Monoid` instances
  that add fields, so per-candidate deltas combine into a pass summary that does
  not depend on the order candidates finish in.

- `Keiro.Timer.drainDueTimersWith` / `drainDueTimers` claim and fire up to a
  caller-supplied number of due timers in one pass, returning how many were
  processed. Per-timer semantics are `runTimerWorkerWith`'s, unchanged; the
  requeue-and-gauge preamble now runs once per batch instead of once per timer,
  so a backlog of due timers no longer drains at one row per poll tick.
  `Keiro.Workflow.Sleep.drainWorkflowSleepTimers` is the batched sibling of
  `runWorkflowTimerWorker`, routing sleeps and process-manager timers the same
  way. `runTimerWorker` / `runTimerWorkerWith` are unchanged.

### Bug Fixes

- `Keiro.version` now renders Cabal's generated package metadata instead of a stale
  hand-maintained literal, so diagnostics and telemetry automatically follow each release.
- The durable-workflow reference and worked guide now document at-least-once
  step actions, opaque awakeable-id publication, fresh publication after
  `continueAsNew`, and the real `IOE` constraints. Compile-owned forwarding
  signatures keep the displayed workflow API aligned with the public modules.
- Fresh workflow awakeables are now opaque allocation results. Generation-0
  coordinate probes are isolated behind `Keiro.Workflow.Awakeable.Compatibility`,
  while runtime adoption and the compatibility surface share one internal identity
  implementation; ordinary workflow and generated-code APIs no longer expose a
  coordinate-derived candidate for a fresh awakeable.
- Schema-versioned rebuilds now enforce an absolute database-clock deadline for
  writer-fence and promotion lock attempts, acquire every target relation in one
  cumulative lock statement, and return typed phase-specific deadline failures.
  Async redelivery evidence is staged incrementally in PostgreSQL and admitted against
  a persisted operator limit; candidate verification, set-based dedup installation,
  checkpoint reconciliation, and lease release complete in a resumable preparation
  phase before target relations are locked.
- Cursorless read models built through `immediateReadModel` with `NoQueryCursor`
  now fail fast with `ReadModelMissingCursor` on every public wait path. The
  exported `waitFor` and deprecated `runQueryWith` waiting overrides no longer
  poll the private cursor sentinel for the full timeout or record a spurious
  `keiro.projection.wait.timeouts` increment. Models with durable cursors,
  including all directly constructed 0.11 records, retain their behavior.
- `Keiro.ReadModel.Rebuild.startRebuild` now recognizes a cursorless model through
  `readModelCursorAuthority` and skips the subscription-checkpoint reset because
  there is no cursor to reset. It no longer passes the private NUL-prefixed
  compatibility sentinel into Kiroku, while preserving the documented fence,
  truncate, and dedup clear used by generated inline Language-5 rebuild helpers.
- A workflow suspended on a due sleep while no timer worker ran no longer makes
  the documented `ResumeSummary.advanced > 0` drain loop spin forever. It now
  reports `advanced = 0` and `sleepDue = 1`, preserving discovery while naming
  the blocked remedy.
- Offline catalog rebuild promotion now backfills replayable async projection
  dedup rows for each subscription's replayed redelivery window and advances
  every declared checkpoint member to the captured head in the promotion
  transaction. Previously the reset checkpoints and deleted dedup rows caused
  every replayed event to be redelivered and re-applied after promotion,
  corrupting non-idempotent async read models.
- Catalog adoption no longer silently succeeds after a zero-row query-registration
  update. It updates or inserts every selected catalog registration and deletes a
  previewed renamed-model row only when no registration in the complete catalog claims
  the old name, all in the slice-adoption transaction.
- A database upgraded by migration 0024 while a catalog rebuild was
  `rebuilding` or `failed` can now recover entirely through supported APIs:
  abandon the pre-canonical run, adopt the fenced stale-format group, and start
  a fresh canonical rebuild.
- Resuming an interrupted catalog rebuild after a deploy reorders the group's replayable
  projection declarations now refuses with `CatalogRebuildContractMismatch` instead of
  silently applying the remaining history in a different adapter order.
- Multi-source catalog rebuilds again apply events in strictly ascending global position
  across sources. Buffered chunks are clamped to the smallest safe source horizon, and
  the runner records `replay.global-position-regression` or
  `replay.buffer-horizon-stalled` invariant evidence instead of promoting if ordering
  would regress or the merge cannot advance. Per-event source-read counts are unchanged.
- Upgrades with non-ASCII process-manager correlations, router keys, or
  awakeable labels now deduplicate against IDs written before deterministic seed
  encoding switched to UTF-8. The runtime probes the frozen historical identity
  alongside the current one, with no extra database probe for ASCII seeds, and
  adopts in-flight generation-0 awakeables rather than orphaning them. See ADR
  0024 for the operator-attested removal criteria.

- Awakeable cancellation now serializes with a concurrent suspend write under
  the awaited step's advisory lock. Suspend arbitration also consults a valid
  `awk:` awakeable row after an absent step-index result, keeping the instance
  `running` when the awakeable is already completed or cancelled. A cancellation
  that committed just before the stale suspend write can therefore no longer
  strand the workflow as undiscoverable `suspended` work.

- A workflow that goes terminal while the resume worker is recording its crash
  no longer aborts the rest of the pass. The crash-recording `UPDATE` matches no
  row once the workflow is terminal, which used to fail a single-row decoder;
  because the crash record sits outside the per-advance error handling, that
  store error escaped the whole pass and every remaining candidate was skipped
  until the next tick. The race is now logged as `ResumeCrashRecordSkipped`,
  counted under `transientErrors`, and the pass continues.

- Workflow garbage collection survives errors. A failing deletion is isolated to
  its own workflow instead of aborting the batch, and `WorkflowGcSummary`'s
  `deleted` now counts workflows actually collected rather than restating
  `scanned`. `runWorkflowGcWorker`'s loop catches store errors and synchronous
  exceptions per pass and continues on the next tick — previously a bare
  `forever` loop that the first transient error ended until process restart.

- Deterministic ids are derived from the UTF-8 bytes of their seed text rather
  than each character's codepoint truncated to eight bits. The old encoding gave
  two different seeds the same id whenever their characters agreed modulo 256
  (`"ā"` and `"\SOH"`, and many CJK pairs), which wedged the workflow journal —
  the colliding step's append was refused as a duplicate event id on every
  retry, until the resume worker marked the workflow failed — and silently
  suppressed a legitimate process-manager command. Affects
  `Keiro.Workflow.deterministicJournalId`, `Keiro.Workflow.Sleep.sleepTimerId`,
  `Keiro.Workflow.Awakeable.deterministicAwakeableId`, and
  `Keiro.ProcessManager.deterministicCommandId`.

  **For ASCII seeds the two encodings are byte-identical, so every id in a
  deployment that uses ASCII workflow names, ids, step names, sleep names,
  awakeable labels, patch ids, and correlation ids is unchanged.** There is no
  migration. Only non-ASCII seeds derive new ids; in-flight consequences are
  bounded (a journal still replays, a sleep may arm one duplicate timer whose
  fire collapses idempotently, a legacy generation-0 awakeable id is no longer
  adopted, and a retried process-manager emission may emit one duplicate
  command). The derivation is now frozen and pinned by fixtures: see
  `docs/adr/0024-deterministic-ids-hash-utf-8-seed-bytes-and-are-frozen-replay-identity.md`.

### Other Changes

- `ConsistencyMode`, `StrongScope`, `Strong`, `Eventual`, `PositionWait`,
  `defaultStrongWaitOptions`, `runQueryWith`, and the legacy `ReadModel` waiting/cursor
  record fields remain source-compatible in 0.12 and are scheduled for removal in 0.13.
  Use the truthful freshness/cursor façade; legacy `PositionWait` with no target retains
  its historical immediate behavior during the migration window.

- The source distribution now includes the BSD-3-Clause license file.

- Bounded workflow drains repeat while `ResumeSummary.advanced > 0`, not while
  `discovered > 0`. `discovered` remains the admitted pool size; `advanced`
  counts only fresh journal appends, crash-ceiling terminal failures, and
  externally delivered wakes observed mid-pass. Replay-only re-suspensions,
  paced retries, unregistered workflow names, foreign leases, transient errors,
  and due sleeps remain blocked in place; due sleeps are reported separately as
  `sleepDue`.

- Workflow discovery is now exact: `findUnfinishedWorkflowIds` returns an
  instance only when its status is `running`, or `suspended` with a due
  `wake_after`. A workflow parked on an awakeable, a child, or a future-dated
  sleep is no longer re-claimed, replayed, and re-suspended on every resume
  pass, so idle cost no longer scales with the number of parked workflows.
  Every path that resolves or abandons a wake writes the instance row in the
  same transaction; a third-party wake source that transitions its own durable
  row without appending to the journal must now flip the owning instance row
  itself. See `docs/adr/0023-workflow-discovery-is-exact-and-the-instance-row-is-the-complete-wake-ledger.md`.

- `Keiro.Workflow.Awakeable.cancelAwakeable` flips the owning workflow's
  instance row to `running` in the same transaction as the cancellation, so the
  workflow is discovered and can observe the cancellation. Its signature is
  unchanged; `Keiro.Workflow.Awakeable.Schema.cancelAwakeableTx` now returns
  `Maybe (Text, Text)` (the owner's name and id) instead of `Bool`.

- `Keiro.Workflow.Sleep.workflowSleepFireAction` clears `wake_after` only when
  its journal append is fresh, so a stale re-fire can no longer erase the wake
  hint written by a later sleep's first arm.

- The resume worker no longer unions `findRunningChildIds` into discovery; a
  freshly spawned child is already discovered through the instance row
  `spawnChild` writes. The query remains exported for operator inspection.

- Fewer database round-trips per workflow step: the run-entry terminal probe is
  one query instead of two, the terminal boundary check rides the append
  transaction instead of costing a separate query before and after each action,
  and `claimInstance` no longer resolves `MAX(generation)` on every claim.

- `Keiro.Workflow`'s overview gains a "Writing a custom wake source" section
  stating the four obligations a third-party wake source owes — a durable row
  keyed by the logical workflow, delivery under the awaited step name, an arm
  that re-checks the row and re-delivers, and an instance-row write on every
  lifecycle transition — with the rotation race spelled out on
  `appendJournalEntryReturningId`. The same contract is written for adopters in
  `docs/guides/durable-workflows.md` and `docs/user/durable-workflows.md`.

- `continueAsNew` documents that rotating abandons any awakeable id already
  handed out: the next generation re-runs the allocation step and hands out a
  fresh one, so the holder must be re-notified from that step. Stated on
  `continueAsNew`, on `Keiro.Workflow.Awakeable.awakeableNamed`, and in the
  guide.

- A "what suspension costs" section in both durable-workflow documents, the
  roadmap, and the production-status page: parked workflows are free, and the
  costs that remain are due sleeps awaiting a timer worker, crash retries, and
  journal replay under the default `snapshotPolicy = Never`.

- Corrected drift: `recordStepTx`'s haddock named a two-column conflict key that
  has been four columns since migration 0008, and `Keiro.Workflow.Gc` now states
  that collecting a terminal parent deliberately detaches its still-running
  children.
## [0.11.0.0] - 2026-08-05

### Breaking Changes

- Requires `keiki >=0.9 && <0.10` and `keiki-codec-json >=0.9 && <0.10`,
  replacing the 0.8 bounds. Keiki 0.9 makes `InCtor` and `WireCtor` read-only
  construction patterns; manual values must use `unavailableInCtor` and
  `unavailableWireCtor`, or use Generic/TH producers when trusted structural
  evidence is required. Validation can report fewer name-collision false
  positives when trusted structural heads differ or exact integral constraints
  prove replay candidates disjoint. The JSON event and snapshot formats are
  unchanged.

### Added

- `Keiro.Timer.Schema.TimerStatus` now derives `Enum` and `Bounded`, so a
  consumer can enumerate the complete stored lifecycle instead of restating
  it. Stored representations are unchanged. keiro-dsl's cross-package
  timer-vocabulary test relies on this.

## 0.10.0.0 — 2026-08-03

No user-facing changes beyond the lockstep `keiro-core ^>=0.10.0.0` bound.
Released with the package set for the `keiro-dsl` 0.10.0.0 work.

## 0.9.0.0 — 2026-08-02

### Breaking Changes

- Requires `keiki >=0.8 && <0.9` and `keiki-codec-json >=0.8 && <0.9`,
  replacing the previous `>=0.7 && <0.8` bounds. Consumers must solve for the
  same Keiki major; verification results and rendering may differ from the 0.7
  line.

### New Features

- `Keiro.Inbox.Types` re-exports `RetryDelay` from `Shibuya.Core.Ack`, so
  generated inbox dispositions carrying retry delays need only the `keiro`
  import.

## 0.8.0.0 — 2026-08-01

No changes this release. Version moves with the package set.

## 0.7.0.0 — 2026-08-01

### Breaking Changes

- Requires the conservative-projection Keiki releases (`keiki >=0.7 && <0.8`,
  `keiki-codec-json >=0.7 && <0.8`), replacing the previous `>=0.6 && <0.7`
  bounds. Keiki 0.7 treats a predicate that crosses a one-way generated
  projection as opaque to symbolic proof, so verification may return
  `UnverifiedOpaque` where an earlier release reported a `Verified*` result.
  Command execution and replay behavior are unchanged.
- Requires `keiro-core ^>=0.7.0.0`.

### New Features

- Re-exports `Keiro.Codec.IdDomain` from `keiro-core`, so generated version-3
  identifier bindings and harnesses keep a single direct `keiro` dependency
  alongside the existing structural and nominal binding re-exports.

## 0.6.0.0 — 2026-07-31

### Breaking Changes

- Requires the exact-`Integer` / total-`Natural` Keiki releases
  (`keiki >=0.6 && <0.7`, `keiki-codec-json >=0.6 && <0.7`), replacing the
  previous `>=0.4 && <0.5` bounds.

### New Features

- Re-exports `Keiro.Codec.Nominal` from `keiro-core`, so generated nominal
  consumer bindings keep a single direct `keiro` dependency alongside the
  existing structural binding re-exports.

## 0.5.0.0 — 2026-07-31

No changes this release. Released with the package set for the `keiro-dsl`
0.5.0.0 whole-workspace work.

## 0.4.0.1 — 2026-07-28

### Other Changes

- Adds PVP upper bounds to every dependency that previously carried a lower
  bound only, so `cabal check` reports no packaging warnings. No API or
  behaviour change from 0.4.0.0, which was tagged but never published.


## 0.4.0.0 — 2026-07-28

### Breaking Changes

- `Keiro.Timer.scheduleTimerOnceTx` now returns `Bool`: `True` when this call
  armed the timer and `False` when an existing row won the first-arm race.
- `Keiro.Workflow.Child.Schema.ChildRow` gains `failureReason`, and
  `markChildFailedTx` takes the terminal reason as its third argument.
- Snapshot hydration now requires codec version, register-layout hash, and
  control-state/fold hash to match; snapshots written without the new state
  discriminator are invalidated and rebuilt.
- Validated event-stream assembly rejects invalid codec schema versions, event
  tags, or upcaster chains. `mkEventStreamUnchecked` is the explicit
  emergency-forensics bypass.
- Requires Keiki and keiki-codec-json 0.4, including the typed structural field
  projection validation contracts.

### New Features

- Adds the read-only `Keiro.ReplayAudit` API for full or affected-event targeted
  replay checks. Audits report hydration failures, compare accepted snapshot
  seeds with full replay using RFC 8785 canonical JSON, emit stable SHA-256
  digests, support bounded parallelism, and expose resumable checkpoints.
- Exposes the existing `Hydrated`, `hydrate`, `hydrateFull`, and
  `hydrateSeeded` primitives from `Keiro.Command` so audit tooling can compare
  seeded and full replay without changing command execution.
- Adds `RunCommandOptions.seedVerifySampleRate`, defaulting to one verification
  per 1000 snapshot hits. A sampled hit asynchronously full-replays through
  the snapshot version and compares canonical encoded state. Divergence emits
  `keiro.snapshot.seed.divergence` plus a structured digest log without
  blocking the command or writing a snapshot; set the rate to `0` to disable
  the witness.
- Re-exports `Keiro.Codec.Structural` from `keiro-core`, so generated consumers
  retain a single direct `keiro` dependency for the stable binding API.
- Adds `Keiro.Workflow.Instance.resurrectFailedWorkflow`, which revives a
  terminally failed workflow while preserving append-only failure history and
  resetting only derived current-generation failure state.
- Adds `WorkflowRunOptions.leaseHeartbeat`, plus `LeaseHeartbeat` and
  `WorkflowLeaseLost`, so resume workers renew ownership at fresh action and
  await boundaries without charging lease loss as a crash attempt.
- `defaultStateCodec` derives a control-state discriminator, and
  `withFoldFingerprint` appends an application-owned fold token for changes
  that are invisible to register layout alone.

### Bug Fixes

- Active workflow patch sets are persisted atomically with a new generation's
  seed, closing the race where an early wake could silently select old patch
  branches.
- Sleep timers are generation-pinned, re-arms no longer postpone an existing
  wake, firing clears the wake hint atomically, and workflow GC removes
  surviving scheduled timers so stale fires cannot resurrect collected runs.
- Await lookup falls back from a journal snapshot to the authoritative step
  index, preventing completions appended during a run from being hidden by its
  snapshot.
- Failed child links preserve their terminal reason across parent generation
  rotation, and awakeable registration/signal races no longer lose a valid
  completion or fire compensation after cancellation wins.

### Other Changes

- Adopts Keiki 0.4 and keiki-codec-json 0.4, including typed field-projection
  validation contracts.
- Adopts keiki 0.3 (`EdgeMode`, plan 143): a `ReplayOnly` edge is excluded
  from forward stepping and serves two-phase inversion, so a tightened guard
  can retain its removed region (`old ∧ ¬new`) as a replay-only twin and
  keep stored history hydratable while new removed-region commands are
  rejected with `CommandRejected`. No keiro API change — machines built with
  `Keiki.Builder.replayOnly` (or `mode = ReplayOnly`) pass the existing
  `mkEventStream` boundary checks; the black-acuity regression is pinned in
  `keiro-test`. Rolling back a deployed replay-only twin re-creates exactly
  the hydration break it fixed (stored events in the removed region lose
  their inverting edge): delete a twin only when every affected stream is
  terminal or truncated.

## 0.3.0.0 — 2026-07-14

No user-facing changes to the `keiro` library. It is released at 0.3.0.0 to stay
in lockstep with the rest of the Keiro package set, and is rebuilt against
`keiro-core` 0.3.0.0. Applications that provision their test databases through
`keiro-test-support` should note the `withMigratedSuiteWith` change described in
the root changelog.

## 0.2.0.0 — 2026-07-13

### Breaking Changes

- **Keiro's framework tables moved out of the `kiroku` schema into a dedicated
  `keiro` PostgreSQL schema.** Every runtime query is now fully qualified
  (`keiro.keiro_snapshots`, `keiro.keiro_timers`, `keiro.keiro_outbox`, …) and no
  longer depends on `search_path`. Existing databases must run the
  `keiro-migrations` bootstrap that creates the schema and relocates the tables;
  application SQL reading bare `keiro_*` relations must be re-qualified.
- `runCommandWithSql`, `runCommandWithSqlEvents`, `runCommandWithProjections`,
  and the process-manager/router runners now require `KirokuStoreResource` so
  transactional appends can apply Kiroku's configured `enrichEvent` hook. Acquire
  the store with `withKirokuStore` and interpret `Store` with `runStoreResource`;
  plain `runCommand` is unchanged.
- Read-model queries no longer auto-register missing registry rows. Applications
  must call `registerReadModel` at projection startup; unknown models now return
  `ReadModelUnregistered` without mutating the registry.
- `ReadModel` now requires a `strongScope :: StrongScope` field. Use `EntireLog`
  for all-stream subscriptions or `CategoryHead category` for a category
  subscription, so that unrelated traffic cannot hold `Strong` reads behind.
- `ReadModel` now also requires a `schema :: Text` field naming the PostgreSQL
  schema its data table lives in. Keiro does not rewrite `query`; qualify the
  application's SQL with `qualifiedTableName` or `Keiro.Connection.qualifyTable`.
  The field is Haskell-level wiring only and is deliberately not persisted in the
  `keiro.keiro_read_models` registry.
- `AsyncProjection` now requires `readModelName`, naming the registry row that
  fences writes during a rebuild.
- `applyAsyncProjection` now returns `AsyncApplyOutcome` (`AsyncApplied`,
  `AsyncDuplicate`, or `AsyncFenced`) and live workers must not checkpoint a
  fenced event. Rebuild replayers use `applyAsyncProjectionUnfenced` between the
  new atomic `startRebuild` and guarded `finishRebuild` helpers.
- Router deterministic command ids are now derived from the resolved target
  stream name and same-stream occurrence rather than the target's list position
  (`deterministicRouterCommandId`). A transition point-probe recognizes legacy
  positional ids for stable resolver output and may be removed in a later
  release. If both the deployment version and resolver output change between
  attempts, a target command may be dispatched at most one extra time across that
  one-time upgrade window.
- `runWorkflow`, `runWorkflowWith`, and the child-workflow runtime now require
  `Error StoreError` in their effect rows, so post-commit workflow snapshot
  failures can be caught without leaving the typed error channel.
- `HydrationReplayFailed` now carries a typed `HydrationReplayReason` alongside
  the failing stream version. The reasons distinguish no inverting edge, ambiguous
  inversion, queue mismatch, and a truncated multi-event chain. `CommandError`
  also gains `CommandAmbiguous` (matched edge indices) and `HydrationGapDetected`
  (expected and observed stream versions).
- A command matching multiple transitions is now reported as `CommandAmbiguous`
  instead of `CommandRejected`. Process managers and routers halt on this
  aggregate-definition bug, while generated timer dispositions route it through
  their on-error arm rather than benign on-reject handling.
- `PMCommandResult.PMCommandFailed` now carries the target `StreamName` alongside
  its `CommandError`, so worker policy can identify the failing target.
- `WorkerOptions` gains a `rejectedCommandPolicy :: RejectedCommandPolicy` field,
  and `ShardedWorkerOptions` gains `handlerRetryDelay :: RetryDelay` and
  `retryPolicy :: RetryPolicy`. Record construction must supply them; the
  `defaultWorkerOptions` and `defaultShardedWorkerOptions` defaults are unchanged
  in behavior except as noted below.
- The `hydration_replay_failed` telemetry class is replaced by four
  reason-specific `error.type` values, and `command_ambiguous` is new. Dashboards
  keyed on the old hydration class must be updated.
- Stream validation is stricter through the re-exported `keiro-core` contracts.
  Keiki 0.2's head-recoverability, inversion-ambiguity, unguarded-input-read, and
  state-changing-silent-edge checks now reject a stream at `mkEventStream`, and
  `mkEventStreamWith` can no longer disable the head-recoverability or
  state-changing-epsilon checks. Snapshot-enabled streams whose codec cannot
  encode their initial state and registers are rejected at startup. `keiro` now
  requires `keiki >=0.2`, `keiki-codec-json >=0.2`, and `kiroku-store >=0.3`.

### New Features

- New `Keiro.DeadLetter`, `Keiro.DeadLetter.Schema`, and `Keiro.DeadLetter.Replay`
  modules. Process-manager and router workers can now park a rejected dispatch
  instead of halting: `RejectedCommandPolicy` selects `RejectedHalt` (the
  default), `RejectedDeadLetter` (persist a `DispatchDeadLetter` in
  `keiro.keiro_dead_letters` and acknowledge), or `RejectedSkip` (acknowledge and
  count). `recordDispatchDeadLetter` is idempotent under source-event redelivery,
  and `listDispatchDeadLetters` reads one dispatcher's witnesses newest-first.
  `replaySubscriptionDeadLetters` re-runs a caller-supplied handler over the rows
  Kiroku parked in `kiroku.dead_letters`, reporting `ReplayedFresh`,
  `ReplayedDuplicate`, `ReplayFailed`, or `ReplaySourceMissing` without deleting
  or mutating the Kiroku-owned rows.
- New `Keiro.Connection` module for application read-model and projection tables:
  `qualifyTable`, `quoteIdentifier`, `withProjectionSchema`,
  `keiroConnectionSettings`, and the opt-in `ensureProjectionSchema`. The store
  connection's `schema` stays `kiroku` because it drives the `LISTEN`/`NOTIFY`
  channel; a projection schema is reached by qualification and/or
  `extraSearchPath`. `ReadModel.qualifiedTableName` builds the model's
  `"schema"."table"` reference.
- `Keiro.Subscription.Shard.Worker` gains an acknowledgement-aware surface:
  `runShardedSubscriptionGroupAck` with `ShardAck`, `ShardDelivery`, and
  `ShardEventHandler`, plus per-event retry and dead-letter dispositions.
  `runShardedSubscriptionGroup` remains as the `RecordedEvent -> IO ()`
  compatibility wrapper.
- Added `keiro.dispatch.deadlettered` and `keiro.subscription.deadlettered`
  counters. `Keiro.Telemetry.kirokuEventBridge` installs on Kiroku's
  `eventHandler` to observe `KirokuEventSubscriptionDeadLettered`, the terminal
  retry-exhaustion signal, and delegates every event to the application's handler.
- Added `keiro.snapshot.encode.failures`, `keiro.snapshot.decode.failures`,
  `keiro.snapshot.read.hits`, `keiro.snapshot.read.misses`, and
  `keiro.snapshot.apply.divergence`. Snapshot lookup APIs (`lookupSnapshotSeed`,
  `SnapshotLookup`, `SnapshotMissReason`, `encodeSnapshotStrict`,
  `writeSnapshotEncoded`) now retain miss and decode reasons, while compatibility
  wrappers preserve the previous `Maybe` surface.
- `commandErrorClass` exposes a stable error-class string for a `CommandError`.
  `isRejectionClass`, `decideForFailures`, `DispatchFailure`, and
  `confirmBenignDuplicate` are exported from `Keiro.ProcessManager` and
  `Keiro.Router` so that custom workers can reuse the runtime's acknowledgement
  classification. `ReadModel.categoryHeadPosition` reads the latest global
  position originating in a Kiroku category.

### Bug Fixes

- Command hydration now detects stream-version gaps caused by Kiroku per-stream
  truncation, and returns `HydrationGapDetected` unless a snapshot covers the
  hidden prefix.
- Transactional command runners now apply Kiroku's configured `enrichEvent` hook
  before event preparation, so persisted events and the `runCommandWithSqlEvents`
  callback observe the same enriched metadata as plain `runCommand`.
- Router and process-manager duplicate-event rejections are now confirmed against
  the intended target stream before being treated as benign. Unconfirmed
  cross-stream or id-less collisions surface as command failures, causing workers
  to halt instead of silently dropping a dispatch.
- Sharded subscription readers now acknowledge each event only after its handler
  returns, using Kiroku's acknowledgement bridge. A shed or rebalanced bucket's
  checkpoint can no longer cover an unprocessed event, and a synchronous handler
  exception is retried in place under `retryPolicy` before Kiroku dead-letters the
  event; asynchronous exceptions write no acknowledgement, and the event is
  redelivered by the next owner.
- No-op commands now report `CommandResult.globalPosition = Nothing` instead of
  exposing Kiroku's per-stream-read sentinel `GlobalPosition 0`. Appended commands
  continue to report the real store-assigned position.

### Other Changes

- `RunCommandOptions.verifyReplayOnAppend` defaults on. Both command append paths
  replay each just-committed batch from the pre-command state, count an
  unreplayable batch through `keiro.snapshot.apply.divergence`, and attach a
  bounded typed reason to `keiro.replay.divergence` without turning an
  already-committed command into a reported failure.
- Aggregate snapshot encoding is forced before the store write. An `ErrorCall`
  from a partial state encoder or an uninitialized register is swallowed after the
  event append and counted, instead of escaping a successful command.
- Workflow snapshot writes after steps, completion, and continue-as-new rotation
  are advisory: store failures are swallowed and counted after the journal append
  commits.
- Corrected snapshot documentation: version non-regression applies within one
  codec version and shape hash, while an incompatible codec can replace a newer
  row to permit rollback. Upgrade notes cover the full-replay miss caused by
  Keiki EP-78's stable shape hash.
- Documented previously implicit runtime contracts: the inbox deduplication window
  closes when `garbageCollectCompleted` removes a completed row; outbox
  `created_at` is transaction-start time, so `PerKeyHeadOfLine` and
  `PerSourceStream` ordering is best-effort unless the caller serializes same-key
  enqueues; the default timer worker has no attempt ceiling and requeues claims
  left `Firing` for five minutes; and process-manager `correlate` joins across
  streams must be order-insensitive.
- The shared PostgreSQL test fixture now provisions templates through the native
  Kiroku/Keiro migration plan. Codd transition and remediation tests are retained
  behind the manual `legacy-codd-tools` flag.
- Kiroku 0.3, Keiki 0.2, and pg-migrate 1.0 now resolve from Hackage; their
  obsolete Git package overrides and the local Cabal overlay are no longer needed.

## 0.1.0.0 — 2026-07-05

The initial Hackage release of `keiro`, an event-sourcing framework and workflow
engine that composes the kiroku event store, the keiki aggregate core, and the
shibuya worker substrate.

### Breaking Changes

- Command-boundary APIs require `ValidatedEventStream` instead of bare
  `EventStream`: `runCommand`, `runCommandWithSql`, `runCommandWithSqlEvents`,
  `runCommandWithProjections`, `Router.targetEventStream`, and
  `ProcessManager.eventStream` / `targetEventStream`. Build stream definitions
  with `mkEventStream` or `mkEventStreamOrThrow` before wiring them into runners.
  This is source-level only; persisted events, snapshots, stream names, and wire
  formats are unchanged.

### New Features

- Command-side write APIs with optimistic concurrency, idempotent event ids,
  transactional SQL hooks, inline projections, command metadata, and OpenTelemetry
  spans.
- Typed event codecs, schema-version metadata, ordered upcasters, advisory
  snapshots, and replay-safe event-stream validation via `Keiro.EventStream.Validate`.
- Read models with consistency modes, rebuild lifecycle support, async projection
  deduplication, and strong consistency checks.
- Process managers, routers, database-backed timers, shard workers, and worker
  options for retry, halt, duplicate, and poison-message handling.
- Durable integration outbox and idempotent inbox support, including Kafka
  adapters, trace propagation, retry accounting, dead-lettering, and maintenance
  helpers.
- Durable workflow primitives: journaled named steps, sleep, await/signal,
  child workflows, continue-as-new, patching, push wake signals, resume workers,
  instance leasing, garbage collection, snapshots, and workflow telemetry.
- Metrics surfaces for command dispatch, projections, timers, outbox/inbox
  workers, and workflow execution.

### Bug Fixes

- Hardened command retry, snapshot boundary handling, workflow crash windows,
  process-manager/router ack finalization, timer requeueing, shard reader
  recovery, async projection deduplication, and inbox/outbox recovery paths.
- Fixed duplicate-event classification, outbox publish grouping, no-op late
  failure marks, and idempotent runtime release paths.

### Other Changes

- Re-exported the shared contracts from `keiro-core`, including codecs, streams,
  event streams, validation, integration events, and snapshot policies.
- Added user guides, migration notes, Haddock coverage, API references, and
  guide-backed examples for command-side usage, event evolution, snapshots,
  read models, process managers, routers, integration events, and durable
  workflows.