keiro-dsl-0.5.0.0: CHANGELOG.md
# Changelog
All notable changes to `keiro-dsl` are recorded here. The format follows
[Keep a Changelog](https://keepachangelog.com/), and the package follows the
[Haskell Package Versioning Policy](https://pvp.haskell.org/).
## [Unreleased]
## 0.5.0.0 — 2026-07-31
### Breaking Changes
- `DiagnosticCode` gains nine constructors:
`WorkspaceMemberUnreadable`, `WorkspaceMemberParseFailed`,
`WorkspaceContextMismatch`, `WorkspaceAuthorityConflict`,
`WorkspaceDuplicateDeclaration`, `WorkspaceDuplicateNodeName`,
`WorkspacePathCollision`, `OwnershipMoved`, and `WorkspaceAuthorityChanged`.
Additions are append-only, but exhaustive matches over the type must be
extended.
- `Keiro.Dsl.ScaffoldRun.Refusal` gains `GoldenRootDivergence`, raised only by
the workspace path when a golden payload fixture sits beside a member that
the one workspace golden root lacks.
- `Keiro.Dsl.ScaffoldRun.WriteDisposition` gains `Unchanged`, produced only by
the workspace write path when a Generated module's bytes already match.
- `Keiro.Dsl.DiffReport.Remedy` gains `RemedyRescaffoldWorkspace`.
No behaviour of the single-file path changed: it keeps its context-keyed
record and manifest names, its report bytes, and its `Overwritten`
disposition, and no existing generated bytes moved.
### New Features
- Adds **service workspaces**: a `.keiro-workspace` manifest names a service and
lists its member `.keiro` files, and `keiro-dsl check <manifest>` validates
them as one service contract. Shared ids, enums, rules, and mapped structural
types resolve once across all members, so an aggregate in one file may use a
declaration or feed a read model owned by another. A single `.keiro` file is
unchanged and behaves as a one-member workspace.
Membership is a set: member paths are normalized and canonically sorted, so
listing order changes neither the parsed manifest nor any output. Composition
refuses, before producing a graph, when members declare different contexts,
when a member's `module`/`layout` clause contradicts the manifest authority,
when a shared declaration or node is owned by two members (identical
duplicates never silently merge), or when two members claim generated module
paths that collide under case folding. One diagnostic can cite several files:
the primary location keeps the established
`<file>:<line>: error[<Code>]: <message>` shape and each further location
follows as an indented `note:` line.
`check --emit`, `--explain-bindings`, `--coverage-report`, and
`--fail-on-opaque` all work against the merged whole-service graph, and
`keiro-dsl parse <manifest>` round-trips the manifest canonically.
Dispatch is by file extension, so every single-file branch is unchanged.
New `Keiro.Dsl.Workspace` module; `Keiro.Dsl.Validate.nodeIdentity` is now
exported. Recorded as ADR-14.
- **Whole-workspace scaffolding.** `keiro-dsl scaffold <manifest> --out DIR`
emits the complete generated module set for every member in one invocation,
with unchanged flags. The set is emitted once from the merged spec, so the
structural projection facade and the replay-audit assembly are produced
exactly once from the complete graph, and every existing refusal gate runs
over the whole set. Both preflights — golden fixtures stranded beside a
member, and Generated paths lacking the `@generated` banner — are evaluated
across the whole workspace before the output directory is created, so a
failure in any member leaves the tree, the record, and the build manifest
byte-for-byte untouched.
History is workspace-keyed (`workspace.<service>`, a name a context can never
collide with), and each record row carries its producing member, so an
aggregate moved between member files is reported as an ownership move with
zero stale churn and zero content change. A Generated module whose bytes
already match is reported `(unchanged)` rather than rewritten, making
idempotence observable. Ownership is attributed structurally through the new
`scaffoldStructuralOwners` and `bindingSkeletonOwners` seams in
`Keiro.Dsl.Scaffold`, never by parsing the human-readable `origin` string.
New `Keiro.Dsl.WorkspaceRecord` and `Keiro.Dsl.WorkspaceScaffold` modules;
`Keiro.Dsl.ScaffoldRun` now exports the gates and helpers the workspace path
reuses (`pureRefusals`, `missingGeneratedBanners`, `staleAgainst`,
`constraintPlan`, `mappingDrift`, `newBindingObligations`,
`obligationKindLabel`, `renderMappingIdentity`) so the two paths cannot
diverge on what counts as a refusal.
- **Adoption of pre-workspace output.** The first whole-workspace scaffold into
a directory that already holds per-context output imports what is
attributable, reports everything, and claims nothing silently. A file is
claimed only with evidence — `record` when a legacy per-context record for
this workspace's effective context lists it, or `banner` when it sits at a
planned Generated path carrying the `@generated` banner while no surviving
record lists it. Hole paths are never claimed; everything else is listed as
unclaimed and left untouched, and a bannerless file at a planned Generated
path still refuses the run. Nothing is deleted or renamed: the superseded
legacy record gains one appended `superseded-by:` line that its own v1 parser
ignores. The migration report is persisted once as
`keiro-dsl-migration-report.workspace.<service>.txt`. New
`Keiro.Dsl.WorkspaceAdoption` module; recorded as ADR-15.
- **Whole-workspace diffing.** `keiro-dsl diff <manifest> --since <rev>`
composes the historical workspace from the manifest and member blobs at that
revision through the same loader and diffs it against the working tree as one
service. Membership deltas are covered, and when the manifest itself does not
exist at the old revision an adoption baseline is composed from the current
membership and flagged in the report.
Findings from the merged graph are annotated with the member file and line
that own the changed declaration and each use site, so a shared declaration's
blast radius is visible across files. The report carries workspace metadata
(service identity, manifest, `since`, old and new membership, adoption-baseline
flag), and replay-impact and coverage output are merged across members. Owner-map
and manifest-authority changes are reported as the non-blocking consumer-build
advisories `OwnershipMoved` and `WorkspaceAuthorityChanged`, classified
independently of wire evolution so they never mask a real wire finding.
The `keiro-dsl/diff-report/1` JSON schema is preserved: single-file reports
keep their original bytes, and workspace inputs add a top-level `workspace`
object plus optional per-finding `declaration` and `useSites` keys, which
version-1 readers ignore. New `Keiro.Dsl.WorkspaceDiff` module; new
`Keiro.Dsl.DiffReport` exports `OwnedSite`, `WorkspaceChange`,
`WorkspaceMeta`, `WorkspaceDiffReport`, and `workspaceDiffReport`. ADR-4 is
amended with the composed-workspace diff boundary.
## 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
### New Features
- Structural scaffolding now creates hand-owned binding/fixture/initial
skeletons grouped by their declared owner modules, persists granular hole
obligations for non-overwriting re-scaffold reports, derives `Generic` for
private shape types, and provides `check --explain-bindings` for deterministic
package/module/signature/use-site reports.
- Adds `Keiro.Dsl.CodecCompare`, a historical-codec comparison engine that
classifies RFC 8785 canonical-JSON parity between a declared codec and a
historical one, reports structured migration differences and declared-versus-
observed branch coverage gaps, and writes stable reports atomically.
`scaffold --codec-comparison MAPPED-NAME --comparison-out FILE` emits a
non-production comparison module and runner for one structural mapped type.
- Adds `Keiro.Dsl.Coverage` and reporting-only `--coverage-report FILE` on both
`check` and `diff`, recording structural and opaque mapped-root coverage as
JSON. `check --fail-on-opaque` fails when a private persisted root still
contains an opaque boundary; `diff --fail-on-opaque-increase` fails when a
change adds a named opaque boundary. Without the flags, coverage is purely
informational.
- Adds checked `mapped structural` and `mapped opaque` declarations with a
resolved, total type-expression graph. Validation rejects recursive,
ambiguous, non-injective, or incomplete mappings; recursive diff findings
carry six-surface compatibility vectors and complete command/event/register
use-site paths.
- Structural consumer-type scaffolding now emits private
`Generated.<Context>.Structural.Shape.*` wire representations, generates
structural codecs from declared keys/defaults/tags, delegates opaque values
only at their declared JSON boundary, imports consumer types into aggregate
domains, and exposes eligible total scalar getters through a narrow Keiki
0.4 `StructuralProjections` witness facade.
- Scaffold preflight now reports consumer packages/modules, refuses consumer
modules inside the generated namespace before writing, includes consumer
requirements in manifests, persists canonical structural/opaque mapping
identities as forward-compatible JSON rows, and reports mapping drift on
subsequent runs. Mapped register wire/binding/initial identities now
participate in the aggregate fold fingerprint.
- Generated harnesses exercise both structural binding laws, declared codec
policy and current payload goldens, enum/union/optional fixture coverage,
canonical projection witness agreement, and forward-versus-replay equality
over every mapped and scalar register. The committed structural conformance
suite includes opaque-boundary checks and three falsifying mutations.
- `Keiro.Dsl.ReplayImpact` and `diff --replay-impact-out FILE`. Diffs now
print whether stored-data replay is unchanged and can emit a stable JSON
affected set for targeted auditing. New aggregates, events, transitions,
and syntactically proven guard loosenings are replay-neutral; changed
decode/fold surfaces identify conservative event types and whether
snapshot-bearing streams must be included.
- Scaffolding now emits one context-wide
`Generated.<Context>.ReplayAudit` module. Its typed target list includes
every aggregate, validates discovered stream names against the generated
category, and documents the replay-neutral/targeted/full deployment tiers.
- `diff` now emits non-breaking, coded advisories when a router resolve or
dispatch surface, process handle surface, or unversioned process-timer
payload changes. The advisories explain the subscription drain and
dead-letter procedure needed to avoid mixed old/new deterministic fan-out
during a deployment.
- First-class replay-only transitions for guard evolution (plan 143). A
`replay-only` prefix on a transition line marks it as serving inversion
only: the parser accepts it, the pretty-printer round-trips it, and the
scaffolder lowers it to `B.replayOnly` (keiki's `ReplayOnly` edge mode) in
the transducer skeleton. New validator rules: `ReplayOnlyEmitsNothing`
(error — a replay-only transition with no emit can invert nothing) and
`ReplayOnlyCommandStillLive` (warning — no live sibling for the (source,
command) pair; the fuller procedure is event retirement). A deprecated
event may keep being emitted by a replay-only transition — replay-only
transitions are not the write path.
- `diff` computes the guard-tightening remedy (`AggGuardTightened`
advisory): on any live-transition guard change without a replay-only twin,
it prints a paste-ready `replay-only` twin whose guard is the removed
region `old ∧ ¬new`, negation eliminated inside the guard grammar by the
new total `Keiro.Dsl.Grammar.complementExpr` (De Morgan, comparison
flipping, `x == false` for bare boolean atoms). The twin carries the old
transition's writes/emits/goto and re-parses as-is; it is printed, never
auto-applied.
- `Keiro.Dsl.PrettyPrint.renderTransition` renders one transition in
concrete `.keiro` syntax (used by the advisory).
- Requires `keiki >=0.4 && <0.5`, including `EdgeMode` and the typed structural
projection contracts.
## 0.3.0.0 — 2026-07-14
No user-facing changes. `keiro-dsl` is released at 0.3.0.0 to stay in lockstep
with the rest of the Keiro package set.
## 0.2.0.0 — 2026-07-13
### Breaking Changes
- The process `saga` clause is now `saga <Aggregate> category "<camelCase>"`,
replacing `saga <Aggregate> stream="<prefix>-" <> correlationId`. Categories are
validated (`SagaCategoryIllegal`) against the same rules as
`Keiro.Stream.category`: non-empty, not `$all`, no `-` (Kiroku's category/id
boundary), no whitespace or control characters, and no `:` (reserved for the
`wf:<name>` workflow stream family). Generated process and router fills now
build saga streams with `entityStream` and a typed category constant instead of
splicing a string prefix.
- `process` nodes must now declare node-level `rejected => <policy>` and
`poison => <policy>` clauses (`halt`, `deadLetter`, or `skip`), and every timer
`fire` disposition must carry an `on-ambiguous` arm alongside `on-ok`,
`on-reject`, `on-error`, and `not-mine`. Mapping `on-ambiguous => Fired` is
rejected (`AmbiguousMarkedBenign`): `CommandAmbiguous` means several aggregate
edges matched, and is never a benign success.
- Identifier lexemes are restricted to ASCII, and the validator now enforces
per-category Haskell hygiene: constructor-position names must be
constructor-safe (`IdentNotConstructorSafe`), no identifier may be a Haskell
keyword (`IdentHaskellKeyword`), and generated vertex constructors may not
collide (`VertexCtorCollision`). Specs that previously scaffolded to
uncompilable Haskell are now rejected at `check`.
- All numeric literal sites parse through `Integer` and fail with a positioned
diagnostic when the value exceeds `maxBound :: Int`, instead of silently
wrapping. `maxBound` itself still parses exactly.
- Duplicate singleton blocks and duplicate `goto` clauses are reported at their
second occurrence rather than being silently accepted with a last-one-wins
reading; a missing `goto` is now anchored to its transition.
- `Text` registers require a quoted initial, and a register whose type is an enum
or the aggregate's vertex type must start at a declared constructor or state.
The scaffolder refuses the run rather than emitting a broken initial.
- Validation is substantially stricter, so specs that checked under 0.1.0.0 may
now be rejected: duplicate node, command, event, enum-constructor, enum-wire and
id-prefix names; dangling aggregate, projection, timer, workflow, signal and
operation references; non-total or unresolved `rule` domains; duplicate workflow
replay labels; incomplete or shadowed disposition tables; duplicate and dangling
status-map keys; emit/intake topic-affinity mismatch; workqueue
`queueRef`/table/DLQ divergence; unresolved dispatch dedup queues and fields;
invalid process timer ceilings; and write targets that are not registers.
- `keiro-dsl scaffold` now plans the entire module set before writing any byte,
and refuses the run on module-path collisions, self-firewall breaches, or
unlowerable spec content. It also refuses to overwrite a file on a `Generated`
path that lacks the `@generated` banner; pass the new
`--force-generated-overwrite` flag to override.
- `keiro-dsl diff` gained a `WARNING:` tier, and its change lines now read
`<node> <facet> <subject>: <detail>` rather than always saying `event`. Only
`BREAKING:` changes exit non-zero.
### New Features
- Added a first-class `readmodel` node declaring `table`, `schema`, a typed
`columns` block, `version`, captured `shape`, `consistency`, `scope`, `feed`
(`inline` or `subscription`), and `subscription`. The validator owns a closed
column-type vocabulary (`text`, `int`, `bigint`, `bool`, `timestamptz`, `jsonb`,
`numeric`), detects shape-hash drift against the declared columns, rejects
`Strong` + `inline` (no subscription worker advances the cursor a strong read
waits on), rejects `scope` without `Strong`, and rejects an `inline` model that
no aggregate projection feeds. `scaffold` emits `ReadModel` and `ReadModelTable`
generated modules plus `ReadModelHoles`. Aggregate `projection` consistency is
now optional and must agree with the read-model node when one exists; a
projection with no `readmodel` node warns that registration, schema identity,
and rebuild helpers are unavailable.
- Query operations and PGMQ dispatch source/dedup read-model references now
actually resolve against declared nodes — these were explicitly deferred no-ops
in 0.1.0.0. Query `consistency` must be `Strong`, `Eventual`, or `PositionWait`,
and a dispatch dedup field must be a column of the named model.
- Added the `router` node for stateless content-based routing: `input`, `key`,
`resolve stable via read-model <name> row { ... }` (or `via hole`), `target`,
`projections`, `dispatch-each`, a runtime-owned `dispatch-id` strategy keyed on
target stream name and occurrence, and node-level `rejected`/`poison` policies.
Validation resolves the target aggregate, its command and command fields, the
key field, and the read-model reference, and confines dispatch bindings to the
`input.` and `resolved.` scopes plus quoted literals. `scaffold` emits
`Router`/`RouterHarness` generated modules with `RouterHoles`/`RouterValue`
stubs, and `keiro-dsl new router` prints a starter.
- Added node-level `rejected` and `poison` worker policies on `process` and
`router` nodes, reconciled against the per-dispatch `on-failed` arms: a dispatch
declaring `DeadLetter` under a node that does not is a `PolicyContradiction`,
divergent `on-failed` actions within one node are rejected because the runtime
applies a single `RejectedCommandPolicy` to the whole failure group, and an
unacknowledged `rejected => deadLetter` warns.
- Added aggregate `snapshot every <n>` / `snapshot on-terminal` with a captured
`state-codec version=<n> shape-hash="..."` fixture, lowered into the generated
aggregate modules and pinned against the live runtime. Intervals below 1, and
empty or zero-version codec fixtures, are rejected.
- Added workqueue `ordering` (`unordered`, `fifo-throughput`, `fifo-round-robin`),
`group key from <field> via <fn>`, and provisioning (standard, `unlogged`,
partitioned). The validator rejects FIFO without a group key, a group key
without FIFO, and an empty partition spec, and flags unlogged durability; the
generated `QueuePolicy` is pinned to the live pgmq configuration types.
- Added intake `persist = full-envelope | dedupe-only`, lowered to the live
`InboxPersistence` value and wired through `runInboxTransactionWith`.
- Added a durable-workflow evolution surface: guarded `patch <id> { ... }` blocks
and a terminal `continueAsNew <SeedType>`, lowered into the workflow run
options. Duplicate and malformed patch ids, and a non-terminal `continueAsNew`,
are rejected.
- Added a `partial` token on `status-map`, so an intentionally incomplete
event-to-status mapping is expressible instead of failing the totality rule.
- Publisher backoff accepts optional `max=` and `multiplier=`, and exponential
backoff now lowers to `ExponentialBackoff ExponentialBackoffOptions{..}` with
faithful duration units.
- Rebuilt `diff` on an exhaustive node-family registry: every `Node` constructor
either has a differ or carries a recorded out-of-scope rationale, so a new node
kind can no longer be silently treated as safe. Classification now covers
aggregate decode evolution through command indirection (field type, removal,
wire spelling, enum add/drop, schema-version regression, old-anchored upcaster
continuity, undeprecation), contract topic/schema-version/discriminator/field/
event changes, workqueue payload, identity, ordering and provisioning changes,
process input and timer-window changes, intake decode posture, dedupe identity
and persistence, emit mapping and ordering, publisher policy, workflow shape,
body, patch and continue-as-new seed changes, read-model
version/shape/feed/consistency evolution, projection and id-prefix re-keying,
router stable-name changes, and dispatch retargeting.
- `scaffold` now writes a versioned per-context record
(`keiro-dsl-scaffold-record.<context>.txt`) and reports still-present modules
made stale by node, layout, or `--module-root` changes. It never deletes
generated or hand-owned files.
- Diagnostics carry row-level source locations, so validator-owned rows
(dispositions, status maps, timers, dispatches) are anchored precisely instead
of at the enclosing node.
### Bug Fixes
- String literals now decode and re-render the closed DSL escape set, so topics,
emit maps, and quoted field bindings survive a parse/pretty-print round trip
instead of being corrupted.
- The scaffolder escapes payload literal splices, closing a template-injection
path where a quoted spec literal could break out of the generated Haskell
string. Its status-map and harness lowering is now total.
- Generated `Stream` and stream-category imports are emitted in the
repository-standard post-qualified (`ImportQualifiedPost`) form.
- The self-firewall is derived from Keiki's exported surface as a single
token-aware scan, rather than a hand-maintained substring list that both missed
operators and produced false positives.
### Other Changes
- Added conformance suites that round-trip every node family, compile every
`keiro-dsl new <kind>` starter, cold-start the new read-model, router, snapshot,
queue-ordering, and workflow-rotation surfaces, and exercise the generated
read-model and router modules against the live runtime.
- Test fixtures and conformance corpora resolve from either the package root or
the repository root.
## 0.1.0.0 — 2026-07-05
Initial Hackage release.
### Breaking Changes
- Renamed the typed-spec file extension from `.kdsl` to `.keiro` before the first
public release.
### New Features
- Added grammar, parser, pretty-printer, validator, diff engine, scaffold
generator, harness emitter, and CLI for typed `.keiro` service specs.
- Added aggregate, process manager, durable timer, integration contract, inbox,
publisher, PGMQ workqueue, workflow, and operation nodes.
- Added configurable module placement, build-wiring manifests, self-firewall
checks, post-scaffold reports, per-iteration ergonomics, and `new <kind>`
starter skeletons.
- Generated validated event streams compatible with Keiro's command boundaries.
### Bug Fixes
- Tolerated formatter comma style in scaffold conformance tests.