@hackage / keiro-dsl

Typed specification toolchain for keiro services

Latest0.11.0.0

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.

[0.11.0.0] - 2026-08-05

Breaking Changes
  • DiagnosticCode gains DecodeBodyPostureUnsupported, DispatchOnAppendedUnsupported, TimerNotMineUnsupported, and IntakeBindHeaderUnknown; exhaustive matches must be extended. Each closes a spelling the grammar accepted that no runtime implements, and each warns on released languages below 4 and errors from language 4 on. intake … body lenient is refused because generated codecs decode a body strictly; on-appended other than AckOk is refused on both process and router dispatch rows because a successful append is always acked; not-mine Fired is refused because the timer worker marks a timer Fired only when the fire action returns an appended event id; and an intake bind … from header "x" naming a header outside keiro's canonical envelope set is refused because the Kafka inbox reads a fixed header set and cannot be remapped. The spelling that matches the runtime stays accepted and silent in every case.
  • A process dispatch-id line is now checked as strictly as a router's. It must read exactly strategy=uuidv5 from=(name, correlationId, sourceEventId, emitIndex) — the tuple Keiro.ProcessManager actually derives from — where previously any strategy identifier and any tuple parsed and were discarded. Routers keep their own (name, key, sourceEventId, targetStreamName, occurrence). A process spec that wrote anything else is now a parse error.
  • check's warning policy now covers structural coverage. When an invocation supplies --coverage-report, its findings are ordinary diagnostics of this run: --deny-warnings and --deny CoverageOpaqueSurface escalate them, they appear in the keiro-dsl/check-report/1 diagnostics array as line-0 entries, and the report's ok therefore accounts for them. Previously check spec --coverage-report r --deny-warnings printed coverage warnings and exited 0, so a CI job that believed it had a no-warning gate did not have one. Coverage output is now emitted with the other diagnostics, before --emit and --explain-bindings output rather than after.
  • check --deny CODE now refuses a code check cannot emit instead of accepting a denial that can never match. Cross-revision codes (for example EvtFieldWireKeyChanged, WorkflowShapeChanged) are a diff concern and are rejected outright; structural-coverage codes are rejected unless the same invocation passes --coverage-report; --deny CoverageOpaqueGateExceeded is rejected in every invocation, because that code is the error --fail-on-opaque itself raises and never a deniable warning — pass the flag instead. Any CI file that named such a code was not gating on it and now fails loudly. Keiro.Dsl.Validate exports the underlying DiagnosticOrigin and diagnosticOrigin registry.
  • keiro-dsl new intake no longer spells the unenforced required cross-check body flags on its bind row, so the intake skeleton passes the documented check --min-language 4 --deny-warnings CI gate as generated (previously it warned IntakeBindFlagUnenforced out of the box). The router and process skeletons keep their idiomatic benign-inversion spellings and therefore gate CI with a selective --deny list rather than --deny-warnings.
  • The keiro-dsl/coverage-report/1 JSON now spells warning severity "warning" rather than "advisory", matching keiro-dsl/check-report/1. There is one severity vocabulary across keiro-dsl's JSON. Consumers matching the literal string "advisory" (available since 0.4.0.0) must be updated.
  • DiagnosticCode loses EmitDeriveHoleUnrealized and WqFieldOptionalUnsupported, and gains TimerDecodeStatusUnknown, TimerDeadLetterTextInvalid, and PgmqFanoutFunctionInvalid; exhaustive matches must be extended. The code fired unconditionally on every emit node because derive … hole is mandatory grammar, so it carried no information and made check --deny-warnings permanently red for any emit-bearing service. The scaffold report's no-modules line already names each emit node that contributes nothing. WqFieldOptionalUnsupported is removed with the model it described: every workqueue payload field is required, because generated decoders read all of them with o .:. WqField therefore loses wqfRequired, the required marker is accepted but selects nothing, and adding a payload field is now classified breaking however it is spelled — a job already queued under the old shape does not contain it, which the previous "new optional field is additive" classification denied.
  • The three surfaces ExecPlan 197 recorded as descriptive-only are now checked against the references they name. A timer's decode unknown-status must be a status the timer table stores (Scheduled, Firing, Fired, Cancelled, Dead); its dead-letter reason must not be blank, since it is the text an operator-written worker passes to Keiro.Timer.deadLetterTimer; a pgmq dedup key must be a generated selector of its source read model, reported with the existing DispatchReadModelFieldUnknown; and a pgmq fanout body must be spellable as a Haskell value identifier. Emit source, key, and map discriminant names remain the one documented descriptive-only surface, because no typed source namespace exists to resolve them against.
  • WorkspaceScaffoldReport gains wsrInertNodes, and a workspace scaffold now renders the same no-modules: line the single-spec report has always rendered. Keiro.Dsl.ScaffoldRun exports the shared inertNodesOf and renderInertNodeSection.
  • Keiro.Timer.Schema.TimerStatus (in keiro) additionally derives Enum and Bounded so consumers can enumerate the lifecycle rather than restate it.
  • Keiro.Dsl.ScaffoldRun's Refusal gains SidecarMovesAlreadyApplied; exhaustive matches must be extended. Sidecar renames are applied before the later scaffold gates run, so a refusal after that point used to claim "nothing was written" while the renames were on disk. The note is appended to such a refusal set and states that the renames are idempotent and need no undo.
  • A legacy keiro-dsl-conformance-record.txt anywhere in the output tree is now migrated, not only when the run also plans a conformance package. A spec that stopped generating one previously left the record orphaned and unreadable, because the current reader has no legacy parser. The scan is depth-bounded and skips the .keiro-dsl-name-migrations backup root.
  • FieldWireKeyInvalid additionally refuses a wire key with leading or trailing whitespace or a control character, naming the offending codepoint. Aliases stay exempt from a declared wire … fields=camelCase convention — preserving a brownfield key is what they are for — so this is structural usability only. A spec that shipped as "family " now fails check.
  • Keiro.Dsl.CheckReport's reportLanguage is now Maybe CheckReportLanguage, and the new workspaceRefusalReport builds the report for a workspace refused during composition. Such a report serializes "language": null because no service graph was composed.
  • Scaffold sidecars now use role-bearing names. On disk, keiro-dsl-scaffold-record.<context>.txt becomes keiro-dsl-ledger.context.<context>.txt, the workspace form becomes keiro-dsl-ledger.workspace.<service>.txt, keiro-dsl-conformance-record.txt becomes keiro-dsl-conformance-ledger.txt, and the generated keiro-dsl-manifest.<context>.<ext> / keiro-dsl-manifest.workspace.* files become keiro-dsl-cabal-fragment.context.<context>.<ext> and keiro-dsl-cabal-fragment.workspace.<service>.<ext>. The Cabal fragment — not the former manifest name — is the authoritative generated build inventory. An out tree holding legacy names now refuses without writing; review the listed moves and rerun scaffold --apply-name-migrations to rename them losslessly. Duplicate old files that cannot be renamed are preserved under .keiro-dsl-name-migrations/sidecar-v1/.
  • Keiro.Dsl.ScaffoldRun's Refusal gains SidecarMigrationRequired and SidecarMigrationRefusal; ScaffoldReport gains reportSidecarMoves and WorkspaceScaffoldReport gains wsrSidecarMoves. Exhaustive matches and record constructions must be extended. Two new modules are exposed: Keiro.Dsl.SidecarNames (the role-bearing name vocabulary and legacy-name recognition) and Keiro.Dsl.SidecarMigration (the planned move set and its apply path).
  • The renamed conformance ledger also changes format: the whitespace-row keiro-dsl-conformance-record.txt becomes the versioned keiro-dsl conformance ledger v1 with typed JSON file rows. The parser ignores unknown row kinds and unknown JSON keys so future rows decode, while retaining the service-key, malformed-record, unsafe-path, and case-folded duplicate-path refusals. Legacy conformance records are converted only by the explicit --apply-name-migrations path; no implicit upgrade exists.
  • AggregateField gains aggregateFieldSelector :: Maybe Name and aggregateFieldWireKey :: Maybe Text, and ContractField gains cfSelector :: Maybe Name, cfWireKey :: Maybe Text, and cfLoc :: Loc (both in Keiro.Dsl.Grammar). Code that constructs these records positionally or matches them exhaustively must be updated; Nothing selector and wire-key values reproduce the pre-alias behavior exactly.
  • RouterReadModelUnverified previously existed as a declared but never-emitted DiagnosticCode constructor. It now carries real semantics and is emitted when a router's resolved read model cannot be verified, so a consumer that matched the constructor as unreachable — or a --deny list that named it as a no-op — now sees live diagnostics.
  • Router duplicate-disposition notices moved from ProcessBenignInversion to the distinct RouterBenignInversion. An adopter CI list that passes --deny ProcessBenignInversion silently stops matching router rows; add --deny RouterBenignInversion to preserve the previous gate.
  • Requires keiki >=0.9 && <0.10, replacing >=0.8 && <0.9. Generated aggregate domains continue to use deriveAggregateCtorsAll and deriveWireCtorsAll, which now produce Keiki's trusted structural constructor evidence. Consumers must solve with Keiki 0.9 and recompile; generated validation results may change where 0.9 can distinguish structural heads or prove replay candidates disjoint.
  • Generated module segments, types, constructors, values, selectors, and create-once source paths now use one checked UpperCamelCase/lowerCamelCase policy. Compound logical names such as service_oncall therefore move from Service_oncall to ServiceOncall; consumers must apply the reported source migration and recompile.
  • DiagnosticCode gains IdentUnsafeNormalization, GeneratedOccurrenceReserved, GeneratedOccurrenceCollision, and GeneratedHaskellNameChanged. Refusal gains GeneratedNameInvariantViolation, NameMigrationRequired, and NameMigrationRefusal. Exhaustive matches must be extended.
  • ScaffoldReport and WorkspaceScaffoldReport gain generated-name source-move evidence. ScaffoldRecord and WorkspaceRecord gain a generated-Haskell naming edition and stable module-role rows; missing additive rows decode as the legacy naming edition.
  • DiagnosticCode gains FieldWireKeyCollision, FieldWireKeyInvalid, and EvtFieldWireKeyChanged. Exhaustive matches must be extended.
  • DiagnosticCode appends LanguageVersionBelowMinimum and now derives Ord, Enum, and Bounded. Exhaustive matches must be extended. check, scaffold, and the working-tree side of diff add a stderr language-contract notice for compatibility-only sources, changing exact-stderr consumers.
  • DiagnosticCode appends AggregateEmpty, ContractEmpty, GeneratedPathCollision, GeneratedImportCycle, BehaviorDerivationInvalid, ConformanceFactKeyCollision, and GeneratedPlanningInvariantViolation. Exhaustive matches must be extended; specs that scaffold already could not lower now fail earlier during check.
  • DiagnosticCode appends thirteen accepted-surface warning and language-4 error codes, including process/router resolution, bounded windows, queue payload types, derived IDs, projection/outbox fields, and the distinct RouterBenignInversion; exhaustive matches must be extended. The never-emitted DuplicateUpcasterSource, IdentHaskellKeyword, IdentNotConstructorSafe, and MappedGuardUnsupported constructors are removed.
  • IdExpr gains the parsed ideField, and ScaffoldReport gains reportInertNodes; callers constructing or exhaustively matching these exported records must be updated.
New Features
  • Generated-name collision planning now registers the selector generation actually emits — the explicit haskell alias when declared, otherwise the raw DSL name — instead of a camelized rendering of it. {foo_bar, fooBar} in one record is no longer reported as a collision between two fields that in fact generate distinct selectors, and the diagnostic no longer claims foo_bar "normalizes to" fooBar, which generation never did. A name that cannot be a Haskell selector is still refused, by the generated-name audit that owns that rule and names the offending declaration.
  • check --report-out creates missing parent directories, and a workspace refused during composition now writes the report a single spec has always written for the equivalent failure. A parse failure and an unreadable or unparseable workspace manifest still write none: they precede any coded diagnostic.
  • scaffold --apply-name-migrations applies an explicitly reviewed legacy-to-current source move, backs up original generated and create-once files, rewrites exact Haskell module references outside comments/literals, journals content digests, and resumes exact interrupted states. Ordinary scaffolding reports the complete plan and writes nothing.
  • Generated-only source evolution is classified as consumer-build advisory with re-scaffold, recompile, and conformance remedies while wire, replay, SQL, runtime, and persisted identities remain compatible.
  • Language 4 direct aggregate and integration-contract fields accept independent haskell <selector> and as "<wire-key>" aliases. Generated records use the selector, codecs/goldens use the wire key, and fields(Command) preserves both.
  • check --min-language N enforces a registered released-language floor. --deny-warnings and repeatable/comma-separated --deny CODE make selected warnings CI-failing without changing their severity. --report-out writes the append-only keiro-dsl/check-report/1 schema for sources and workspaces through the new Keiro.Dsl.CheckReport module.
  • Language 4 now resolves every internally decidable process, router, projection, publisher, queue, pgmq source-key, read-model identity, and timer-ID surface, and rejects duration values that cannot fit the runtime Int seconds representation. Released languages 1–3 keep their prior acceptance.
Other Changes
  • The conformance-corpus driver cross-checks its plan against keiro-dsl.cabal: a suite compiling generated Haskell that no plan entry regenerates now fails corpus-regen check naming the suite, and a plan entry no test-suite compiles fails too. The plan is derived from tracked ledgers alone, so deleting one silently dropped a suite from regeneration and from both existing consistency checks, which are scoped to plan entries. regenerate also refuses a dirty corpus (with --allow-dirty for local iteration) and prints the exact git checkout -- recovery line.

  • The scaffold report's two sidecar lines are relabelled fragment: and ledger: to match the files they name; they read manifest: and record: while pointing at keiro-dsl-cabal-fragment.* and keiro-dsl-ledger.*. Consumers scraping those stderr labels must be updated.

  • The generated-output Cabal stanza adds -Werror behind the new manual flag werror-generated (default off, because Hackage rejects an unconditional -Werror; this repository's cabal.project turns it on), so a warning regression in generated code fails the build instead of building green. src keeps -Wall non-fatal. Enabling it exposed five real import over-approximations in the generator, all now fixed: a publisher imported ExponentialBackoffOptions under a constant backoff; a nominal-projections module imported the four Keiki.ProjectionDomain text combinators used only by the unenforced-ID pattern; a transducer imported the type of every write source, when only guard operands are rendered with a K.Index … Type annotation; a transducer imported an enforced ID's parse<Id>, which only a literal names; and a contract codec imported (.:) when every field decodes through explicitParseField. Generated output is correspondingly smaller; no behavior changed. keiro-dsl-conformance-skeletons keeps -Wunused-imports/-Wunused-matches non-fatal, because its hole modules are deliberately unfilled templates.

  • Generated behavior contracts and harnesses now carry complete signatures, annotated behavior cells, named sample constants, runtime-backed read-model facts, evidence-rich failures, and usage-conditional imports. The keiro/behavior-conformance/1 JSON failure object adds the append-only subject field. The conformance corpus was regenerated under -Wall and now has a clean-tree regeneration policy gate; consumers should re-scaffold and recompile. Behavior keys, wire data, shape hashes, fold identity, and replay semantics are unchanged.

  • Adds a record-derived conformance-corpus driver with focused public-CLI replay, create-once preservation, record/disk and Cabal/disk consistency checks, and a test-owned accept mode for the six renderer goldens.

  • Adds the exported stable ModuleRole/moduleRole projection for ScaffoldModule artifacts and a generated-source naming audit plus repository policy gate.

  • Language 4 selects syntax profile 3. The generated occurrence reserved set is narrowed to the 23 term identifiers GHC rejects under the advertised GHC2024 contract, so contextual words including family, via, and qualified are accepted and compile-proven.

  • Generated aggregate conformance now derives transducer rendering, predicate verification, behavior EdgeRefs, and initial live probes from one source-wide transition layout. Replay-only initial edges receive detailed replay witnesses but never step-based acceptance helpers, and duplicate generated declarations are rejected before writes.

  • Source and workspace checking and both scaffold planners now share one pure gate order: fold surface, lowering, module construction, conformance-package planning, then module-plan refusals. Workspace planning therefore reports a lowering refusal before a simultaneous facade-key refusal. Valid generated output is unchanged.

  • Accepted but inert intake flags, emit derivations, optional queue markers, and inline subscriptions now warn; scaffold reports list emit, pgmq dispatch, and operation nodes that contribute no modules. Timer dead-letter text, pgmq fanout functions, and pgmq top-level dedupe keys are explicitly descriptive-only. Existing valid generated output remains byte-identical.

0.10.0.0 — 2026-08-03

Breaking Changes
  • Keiro.Dsl.AggregateType no longer exports aggregateHaskellType or aggregateImports. A resolved aggregate type now yields the typed AggregateHaskellSource returned by aggregateConsumerHaskellSource, read through aggregateSourceReferences and aggregateSourceStaticImports and rendered with renderAggregateHaskellSource, so consumer-owned types flow into the import planner instead of being flattened into module-qualified text.
  • Keiro.Dsl.ScaffoldRun's Refusal gained the DuplicateConformanceFactKeys and ConformancePackageRefusal constructors, and ScaffoldReport gained the reportConformancePackage field. Exhaustive matches and literal report construction must be updated.
  • Keiro.Dsl.Workspace's WorkspaceManifest gained the wmfRuntimePackage and wmfRuntimePackageLoc fields.
  • The generated build manifest now emits the complete Cabal fragment: a default-language: GHC2024 line, an OverloadedStrings default-extensions block, and — when a service conformance facade is generated — an exposed-modules block that removes the facade from other-modules. Consumers must repaste the fragment rather than merging only the module and dependency blocks.
New Features
  • A configured Keiro service now generates at most one local conformance package. scaffold emits a service-level conformance facade (<Generated prefix>.Conformance) in the consumer's runtime package and a separate runnable conformance package whose runner imports only that facade, so per-node harness modules stay out of the consumer's public API. See ADR 20.
  • The runtime Cabal package is now explicit build metadata. A workspace manifest may carry an optional runtime-package <cabal-name> clause, and keiro-dsl scaffold accepts --runtime-package PACKAGE, which takes precedence over the manifest value. The name is validated against Cabal's package-name grammar and is never inferred from the service name, directory, or nearby Cabal files. New module Keiro.Dsl.RuntimePackage exports RuntimePackageName, mkRuntimePackageName, and isCabalPackageName.
  • New modules Keiro.Dsl.ServiceHarness and Keiro.Dsl.ConformancePackage expose the facade and package planning surfaces.
  • Generated Haskell now has an explicit, checked language contract. The manifest owns the GHC2024 + OverloadedStrings baseline, and overwriteable generated modules declare specialized pragmas locally only when their emitted syntax needs them, drawn from a closed set (BlockArguments, DeriveAnyClass, DuplicateRecordFields, OverloadedLabels, OverloadedRecordDot, QualifiedDo, TemplateHaskell, TypeFamilies). Tracked generated output is independently checked against that set. See ADR 19.
Other Changes
  • Generated event codecs now derive one named event-type allow-list per aggregate and render unknown-event diagnostics from that same value, so the diagnostic text can no longer drift from the accepted set.
  • Generated structural record fields and union payloads now use minimal precedence-correct parentheses without changing schema or wire semantics.
  • Generated Haskell now plans consumer-owned imports once per module. Unique type names use explicit unqualified imports; collisions, external values, constructors, generated shapes, and binding APIs use deterministic short qualified aliases. Imports are merged, deduplicated, and sorted without changing wire schemas, fingerprints, provenance, or create-once ownership.

0.9.0.0 — 2026-08-02

Breaking Changes
  • Requires keiki >=0.8 && <0.9, replacing the previous >=0.7 && <0.8 bound, in lockstep with keiro-core and keiro.
  • Aggregate fold fingerprints widen from 16-hex-digit FNV-1a-64 to 32-lowercase-hex-digit FNV-1a-128. This intentionally invalidates snapshots produced with the old fold discriminator; re-scaffold generated transducers before deploying 0.9.0.0. Read-model shape, mapped-wire, and behavior-key 64-bit identities are unchanged.
  • Aggregate fold, diff, replay-impact, and workspace-diff service APIs now return Either FoldSurfaceError; scaffold planning refuses the same error before module generation. Removes the misleading legacy/version-1 Spec-only wrappers aggregateFoldFingerprint, aggregateFoldSurface, diffSpecs, replayImpact, nominalEqualityContract, nominalEqualityIdentity, and nominalEqualityIdentities. Retain and pass a CheckedService, or explicitly use legacyCheckedService at a deliberate compatibility boundary.
  • DiagnosticCode gains append-only constructors for contract TypeID admission and for closed policy, numeric, duplicate, identity, external-name, intake-coupling, topic-alias, and wire-clause validation. Exhaustive matches over DiagnosticCode must be extended. RolloutConstraint also gains RolloutProducerFirst.
  • Removes the unreachable exported grammar types Derivation, DerivStrategy, Disposition, DispAction, EnvelopeBinding, and EnvelopeLayer. No parser, renderer, validator, or generator constructed them; use the live node-specific derivation, disposition, and intake binding types instead.
  • Workqueue jobOutcomeFor functions now accept a generated queue-named closed outcome sum instead of Text; inbox generation exposes closed outcome and detailed disposition sums carrying retry delays, dead-letter reasons, and runtime failure detail. Replace string literals and bare acknowledgement matches with the generated constructors.
  • Structural projection witnesses are renamed from hex-mangled identifiers to deterministic owner-and-JSON-pointer names, with a stable suffix only for normalized-name collisions. Re-scaffold and update hand-owned imports and uses; witness values and projection semantics are unchanged.
  • Aggregate stream categories are fixed to their generated event-stream definition phantom. PMCommand and router consumers must use the new <aggregate>CommandCategory, fixed to the command phantom; generated process categories are fixed to the saga event-stream definition.
  • workflowFacts changes from packed [(String, String)] values to the generated WorkflowFacts record with separate body, await-label, and patch-identifier lists. Honest zero-constructor command/event declarations are now empty datatypes rather than a synthetic () constructor.
New Features
  • Runtime semantics now uses private monotone capability profiles rather than consumer-maintained identifier-string lists. Aggregate fold identity and replay transition comparison use a total frozen canonical encoder independent of presentation pretty-printing, and replay pairing of guard-disambiguated sibling transitions is declaration-order invariant. Fold identity applies the standard FNV-1a-128 octet fold to that frozen UTF-8 surface.
  • Adds language keiro-dsl 4, selecting syntax profile 2 and keiro-dsl/runtime-semantics/3. Relative to language 3, aggregate ID admission, fold fingerprints, and replay classification remain unchanged; versions 1 through 3 retain their released semantics.
  • Designates language 4 as the sole stable authoring contract. inspect now reports language 4 as stable and versions 1 through 3 as compatibility-only; every new <kind> skeleton starts at language 4. Historical and unversioned sources remain accepted with their released semantics and are never silently upgraded.
  • Moves the primary conformance baseline to language 4: 226 stable fixtures and 30 stable-primary compiled suites are enforced against 14 named source exceptions, two compatibility suites, and one version-independent suite. keiro-dsl-conformance-contract now owns the typed TypeID contract proof; the former permissive target is explicitly keiro-dsl-conformance-contract-v1-compat, and the redundant keiro-dsl-conformance-contract-typeid target is removed.
  • Closes accepted-but-unenforced service surfaces. Values that cannot lower to working generated code are rejected under every language version; language 4 additionally enforces numeric floors, duplicate/shadowing rules, stable runtime identity uniqueness, Kafka and PostgreSQL naming, intake envelope and schema coupling, contract topic aliases, and the aggregate wire convention. Emit source/key/discriminant names remain explicitly descriptive-only.
  • Language-4 public contract fields declared as typeid "inc" now scaffold as KindID "inc". Generated encoders keep canonical JSON strings, while field-path-aware decoders reject malformed text, the wrong prefix, non-canonical spelling, and non-v7 UUIDs through Keiro's frozen admission policy. Their manifests add keiro-core and mmzk-typeid; single-file and workspace records persist stable per-field ID-domain identities.
  • Adds service-aware scaffoldContractForService, manifestDependenciesForService, and renderManifestForService entry points. Existing Spec-only functions remain legacy/version-1 wrappers, and CLI and workspace semantic routes use the checked service.
  • Diffing an unchanged contract TypeID field from language 3 to language 4 now emits ContractTypeIdDomainChanged: public consumers and consumer builds are breaking, with producer-first and drain-required rollout. Producers must emit the frozen TypeID-v7 domain, legacy-invalid in-flight messages must drain or be remediated, and consumers must then be re-scaffolded, recompiled, and run through contract conformance.
  • Generated contract topic constants are exported. Generated contract, aggregate, mapped-enum, nominal, and union decoders now report the rejected value, complete expected set, and Aeson field path without changing accepted wire bytes.
  • Generated files carry the package version, effective language version, and a stable node origin in their provenance banner. Overwrite, stale, and workspace-adoption checks accept both this frozen stamped shape and the historical banner, but reject unrelated marker comments.
  • Regenerated committed fixtures pass the repository Fourmolu configuration; event decode arms render one field per line, contract topics no longer need private-binding warning suppression, and generated workflow facts expose typed list structure.
Other Changes
  • Generated build manifests now declare the complete consumer compilation contract: default-language: GHC2024 and OverloadedStrings as the sole default extension, followed by the module and dependency blocks. Generated modules emit specialized local LANGUAGE pragmas only when their syntax needs them. Re-scaffold before compiling under the narrower advertised profile; create-once hand-owned files keep their existing local pragmas.

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.