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.