Changelog

Changelog

All notable changes to keiro-dsl are recorded here. The format follows Keep a Changelog, and the package follows the Haskell Package Versioning Policy.

[Unreleased]

0.8.0.0 — 2026-08-01

Breaking Changes
  • LanguageDefinition gains two fields — definitionSyntaxProfile and definitionRuntimeSemantics. The type is exported as LanguageDefinition (..) from Keiro.Dsl.LanguageVersion, so positional construction and non-wildcard record patterns no longer compile. definitionBodyParser is retained as a compatibility projection but is no longer consulted for parser dispatch.
New Features
  • Adds the public Keiro.Dsl.Source, Keiro.Dsl.Syntax, and Keiro.Dsl.Frontend advanced API. Source-aware tooling can parse an ordered, located SurfaceSource, inspect exact half-open spans, and lower explicitly to the existing ParsedSource/Spec semantic boundary.
  • Adds structured frontend failures with source-selection, body-parsing, and lowering phases; stable codes; exact primary spans; messages; expected items; and supported-version metadata. Megaparsec types remain internal.
  • Adds syntaxProfileIdentifier, syntaxProfileSupportsFeature, languageVersionsSupportingFeature, and sourceLanguageDiagnosticMessage to Keiro.Dsl.LanguageVersion.
Other Changes
  • Organizes the .keiro grammar into internal concern modules behind the stable Keiro.Dsl.Parser compatibility facade. CLI and workspace members still parse once through that facade; semantic checking, scaffolding, diffing, fingerprints, and replay consume only lowered semantic values.
  • Makes every released registry entry explicitly select an immutable syntax profile and runtime- semantics identity. Version 3 deliberately reuses version 2's syntax profile; adding a future version no longer inherits syntax or runtime behavior from numeric ordering.
  • Keeps parseSource, parseSpec, parseSpecText, their rendered diagnostics, the complete 0.7 acceptance matrix, and generated Haskell bytes unchanged. Canonical pretty printing remains non-lossless: the located surface layer does not retain comments or whitespace.
  • Derives languageFeatureMinimumVersion and languageSupportsFeature from the registry's syntax profiles instead of numeric version ordering.

0.7.0.0 — 2026-08-01

Breaking Changes
  • Language version 3 makes each generated prefix-bearing ID abstract. Import parseX, mkX, and xText from Generated.<Context>.Nominals; the raw constructor and unsafeXFromLegacyText live only in the generated internal replay module. Version 1, version 2, and legacy-unversioned generated output retain their released constructor and decoder behavior.

  • DiagnosticCode gains the append-only IdDomainContractChanged constructor. Exhaustive matches must classify the new command/public-codec, replay, snapshot, persisted-identity, and consumer-build vector.

  • Generated service-level IDs and enums now live in one context-level Generated.<Context>.Nominals module instead of being redeclared in every aggregate Domain. Hand-owned modules that construct these values must import the constructors from Nominals explicitly. Re-scaffolding overwrites only generated files; event wire bytes and canonical nominal identities are unchanged.

  • Requires keiki >=0.7 && <0.8. Keiki 0.7 deliberately treats predicates crossing a one-way generated projection as opaque to symbolic proof, so verification may now return UnverifiedOpaque where an earlier release reported Verified*. Runtime stepping and replay are unchanged. Supply an exact projection with its reverse witness when proof-strength verification is required; behavior-conformance reports retain this surface as unverified.

  • DiagnosticCode gains the append-only EventOutputCommandMismatch and AggregateEventlessStateChange constructors. Exhaustive matches must be extended. A version-2 transition that emits no event may no longer change control state or registers; only an actual no-op is accepted.

  • Requires keiro-core ^>=0.7.0.0 for the Keiro.Codec.IdDomain contract that generated version-3 identifier code imports. The bound is now explicit; the 0.6.0.0 library dependency on keiro-core carried no version constraint.

New Features
  • Adds language keiro-dsl 3, selecting keiro-dsl/runtime-semantics/2. Prefix-bearing IDs now use the frozen keiro-dsl/id-domain/typeid-v7/1 contract: canonical lowercase TypeID-v7 text, the declared prefix and one underscore, a 26-character Crockford suffix, UUIDv7 version/variant bits, and JSON strings. Runtime admission and Keiki 0.7 exact textual projection domains derive from the same contract.

  • Generated and consumer-bound v3 IDs validate before binding conversion, current JSON decoding, literals, and scaffold samples. Binding harnesses add exact projection, fixture-domain, distinct-representation, wrong-prefix, and normalization probes. Scaffold/workspace records and --explain-bindings persist the ID-domain version independently of nominal equality.

  • Historical generated-event decoding retains an explicitly named internal legacy constructor, while the same malformed text is rejected at current command/public decoding with its JSON field path. Domain adoption emits IdDomainContractChanged: old history remains readable, old snapshots miss, public consumers break, consumer builds are advisory, and rollout is producer-last. The compiled keiro-dsl-conformance-id-domain-migration target and restoring mutation script pin this split.

  • Adds the public CheckedService/EffectiveLanguageContract semantic boundary. Single-source and workspace CLI routes now retain the selected contract through validation, scaffold and harness planning, generated fold fingerprints, diff, replay-impact analysis, inspection JSON, and additive scaffold-record rows. Spec-only APIs remain explicit legacy/version-1 compatibility wrappers. Grammar-only v1/v2 differences preserve generated and fold bytes; a future runtime-semantic generation receives its own fingerprint discriminator.

  • Plans one deterministic Haskell owner for every generated service-level ID and enum across single-file and multi-file services. Aggregate rings import only their resolved uses, unused declarations are not imported into unrelated domains, and member reordering or ownership moves leave generated nominal type identity unchanged. A compiled two-aggregate workspace conformance suite passes shared ID and enum values across both rings and round-trips both codecs.

  • Generates version-2 events declared as fields(Command) directly from one checked total identity mapping. The runtime no longer imports a create-once identity-copy function for direct, aliased-wire, optional, nominal, Time, Natural, or structural command fields. Explicit event fields remain hand-owned, and stale generated-identity hooks are reported as obsolete.

  • Adds behavior-obligations FILE --format=text|json for single specs and workspaces. Schema keiro-dsl/behavior-obligations/1 inventories stable semantic keys for every live transition from a live-reachable state, every reachable rejection cell, and every replay-only transition, with owner and conservative evidence metadata but no consumer fill claims.

  • Generates an aggregate-specific BehaviorContract plus create-once BehaviorHoles. Typed live and replay witnesses execute through the generated codec and Keiki 0.7 detailed attribution. Schema keiro/behavior-conformance/1 reports required, filled, pending, missing, duplicate, stale, failed, verified, and unverified keys. The default gate fails incomplete or false evidence; --fail-on-unverified opts into a stricter proof policy.

Bug Fixes
  • Parses the optional language keiro-dsl N preamble only after leading trivia and before context. Nested language fields and declarations no longer look like misplaced or duplicate preambles, and version-2 feature gates now arise from their grammar productions instead of raw source substrings. Comments, strings, wire keys, and legal identifiers containing using, Integer, implementation hole, reg., or cmd. remain inert.

0.6.0.0 — 2026-07-31

Breaking Changes
  • DiagnosticCode gains AggregateTypeUnknown, AggregateTypeUnsupportedAtUse, AggregateRegisterInitialInvalid, AggregateGuardTypeMismatch, AggregateGuardCapabilityUnsupported, WorkspaceLanguageVersionMismatch, and SourceLanguageDeclarationChanged. The additions are append-only, but exhaustive matches must be extended.
  • DiagnosticCode also gains the nominal-check codes NominalMissingIngredient, NominalInvalidHaskellSource, NominalInvalidQualifiedName, NominalInvalidIdentity, NominalInvalidIdPrefix, NominalUnsupportedRepresentation, NominalEmptyEnumRepresentation, NominalMissingInitialValue, and NominalNameCollision, plus the nominal diff codes NominalBindingChanged, NominalFixturesChanged, NominalCanonicalTypeChanged, NominalInitialChanged, NominalRepresentationChanged, and NominalIdDecoderTightened.
  • DiagnosticCode gains AggregateExpressionRootUnknown, AggregateExpressionRootAmbiguous, AggregateExpressionPathInvalid, AggregateExpressionPathUnsupported, AggregateExpressionLiteralNeedsType, AggregateExpressionLiteralInvalid, AggregateExpressionOperandTypeMismatch, AggregateExpressionOperatorUnsupported, AggregateExpressionBooleanRequired, AggregateExpressionGuardBoolRequired, AggregateExpressionWriteTargetUnknown, AggregateExpressionWriteTypeMismatch, AggregateTransitionOwnershipConflict, and CollectionExpressionUnsupported. The additions are append-only, but exhaustive matches must be extended.
  • The public expression AST gains located arithmetic, scalar literals, explicit roots/paths, and transition implementation ownership. Exhaustive matches over Expr, TypeExpr, or transition implementation must be extended.
  • Aggregate command/event fields now use the located AggregateField type and aggregate register types use TypeExpr instead of a raw Name. The library requires the exact-Integer/total-Natural keiki >=0.6 && <0.7 and keiki-codec-json >=0.6 && <0.7 releases.
New Features
  • Adds language version 2 syntax for binding direct aggregate IDs, enums, and nominal scalar wrappers to consumer-owned Haskell types. Version 1 and legacy-unversioned sources reject these declarations at source-language dispatch. The pretty command is an explicit alias for canonical parsing and rendering.

  • Adds the public total Keiro.Codec.Nominal binding/fixture API, a checked nominal registry, generated prefix-safe KindID codecs, closed private enum representations, built-in scalar codecs, create-once binding skeletons, and context-level nominal scalar projections. Consumer-owned registers retain the existing consumer-JSON snapshot cache boundary.

  • Nominal consumer provenance is fingerprinted and diff-visible, persisted in additive nominal-mapping scaffold/workspace rows, and included in generated package requirements. Bound-ID adoption at an existing event use reports a named decoder-tightening historical-read advisory and targeted replay audit.

  • Adds a compiled nominal conformance ring with binding laws, pinned wire bytes, malformed/wrong-prefix ID and unknown-enum rejection, snapshot and canonical identity checks, projection agreement, forward/replay parity, mutation gates, and a compile-fail partial-inverse fixture.

  • Adds authoritative version-2 scalar aggregate expressions. Guards and writes accept typed reg./cmd. roots, required structural scalar paths, all scalar literal families, exact Integer +/-/*, and total Natural +/monus/*. Machine-Int arithmetic, coercion, division/remainder, Time arithmetic, predicate-valued Bool writes, and collection expressions fail before scaffolding.

  • Adds generated per-aggregate Expressions and Transducer modules. Every version-2 transition is exclusively generated-owned or explicitly implementation hole; Hole transitions retain a generated structural envelope, require a per-transition FoldVersion, and expose conservative ownership/predicate-verification reports where opaque terms remain unverified. Version-1 generated output remains frozen.

  • Adds a compiled scalar-expression conformance service with a 360-case oracle, concrete/symbolic/replay/snapshot agreement, all scalar literal families, Natural 2 - 5 = 0, required structural projection identity, and eight mutation sentinels for arithmetic, authority, envelope, fold-version, and verification drift.

  • Adds an explicit source-language contract. A first-significant-clause language keiro-dsl 1 preamble selects the frozen released v1 parser before body parsing; unsupported future versions fail at that boundary. Unversioned input remains readable as legacy-unversioned with effective version 1 and is never silently rewritten by parse/pretty.

  • Adds Keiro.Dsl.LanguageVersion, provenance-preserving parsing/rendering, keiro-dsl inspect FILE --format=json, and canonical per-member workspace inspection. Workspace composition compares effective versions before graph merge while keeping the merged Spec semantic.

  • Single-file and workspace scaffold records now carry additive source-language rows, report provenance drift, and interpret missing rows from older records as legacy. Source-aware diff reports declaration-only changes with an all-compatible vector and no-semantic-action remedy; generated bytes, fold fingerprints, and replay impact remain unchanged.

  • Every new <kind> skeleton and ordinary checked-in .keiro fixture now declares version 1. Dedicated legacy, future, malformed, zero, duplicate, and misplaced fixtures preserve compatibility and rejection coverage. Automated upgrades remain deferred to IR-5.

  • Adds Keiro.Dsl.AggregateType, the single resolution and capability policy used by aggregate validation, Haskell lowering, imports, packages, JSON and snapshot samples, fold fingerprints, diffs, replay impact, and scaffold refusals.

  • Direct aggregate Time and Natural fields and registers now check, scaffold, compile, encode, snapshot, and replay. Time/UTCTime normalize to Time and lower to exact UTCTime constructors; Natural accepts only non-negative integral initials. Equality and ordering are checked against Keiki's released symbolic capabilities. Version 1 retains the original no-arithmetic grammar; version 2 adds only the exact/total arithmetic listed above.

  • Direct aggregate Json and container shapes now parse far enough to receive a located remediation toward mapped structural, and malformed scalar initials, mismatched comparisons, and unsupported ordering fail during check. Generated imports and Cabal dependencies remain type-directed and minimal.

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.