Skip to content

Introduction

Weltenwanderer is a compiler for formally verified Event Sourcing systems. It takes .ddd files — written in the language of domain experts — and verifies their internal consistency before producing executable code.

Domain language is specification. Everything else is infrastructure.

The Problem It Solves

Runtime frameworks catch errors at runtime or in tests. If your decide function misses a state transition, you discover that in production or when a test case covers it. Weltenwanderer catches those errors at compile time, before any code runs.

Domain models also drift from code over time. Comments go stale. Tests cover the happy path. The .ddd file is the single source of truth — the compiler and the generated code are both derived from it, so they cannot diverge.

A First Look at .ddd

This snippet from examples/minimal/registration.ddd shows the core constructs:

context Registration {
type Email = String
type DisplayName = String
command Register {
email: Email
name: DisplayName
validate email != ""
else "Email must not be empty"
}
event Registered {
email: Email
name: DisplayName
}
decider User {
commands: Register
events: Registered
state: Unregistered | Active(email: Email, name: DisplayName)
initial: Unregistered
decide(Register, Unregistered) -> [Registered { email, name }]
decide(Register, Active) -> already "User is already registered"
evolve(Unregistered, Registered) -> Active
evolve(Active, Registered) -> Active
}
}

command declares an intent with its fields. The validate clause specifies value-level constraints. event records a fact. decider ties commands, events, and state together: decide specifies which events each command produces in each state; evolve specifies how each event advances the state.

Four Constraint Layers

Every constraint in .ddd maps to one of four layers, each with a distinct scope and failure mode:

LayerKeywordScopeOn Failure
Type: TypeNameCompile timeCompile error
ValuevalidateCommand constructionErr(ValidationError[]) — command not created
Business Rulerequiredecide invocationErr(RejectionError) — no events produced
PostconditionensureAfter evolveCompile error — derived from evolve definition

Type constraints are resolved statically. Value constraints become smart constructors — the command object cannot be created if the constraint fails. Business rules are state-dependent guards checked at decision time. Postconditions are verified by the compiler from the shape of the evolve function, not at runtime.

Three Computation Layers

The language partitions behavior into three layers with distinct decidability properties:

LayerDSL ConstructFormal ClassDecidable?
Pure ProjectionevolveDynPROP (regular languages)Yes, fully
Cross-Aggregateprojection with lookupDynQF / DynFOYes, with constraints
Reactive Flowsflow (events → commands → events)Turing-completeBounded cases only

The evolve function is a left-fold (catamorphism). When the state type is a finite algebraic data type, the projection is a finite automaton — all properties are decidable (Gelade, Marquardt, Schwentick 2012). The system becomes Turing-complete only through flow composition across deciders.

For the mathematical foundation, see Why Category Theory?.

What the Compiler Verifies

The compiler performs eight checks on every .ddd file:

CheckWhat It Catches
ExhaustivenessMissing decide branches — command/state pairs with no handler
Evolve totalityMissing evolve branches — event/state pairs with no handler
Guard consistencyContradictory require clauses that can never both be satisfied
Postcondition derivabilityensure postconditions that cannot be verified from the evolve definition
Dead code detectionCommands and events declared but not referenced by any decider
Terminal state enforcementdecide clauses that target a terminal state, or deciders with no non-terminal state
Positioned diagnosticsEvery error includes file, line, column, and an actionable fix suggestion
Expression grammarOperator-precedence grammar for validate and require expressions

All checks operate on the AST and are platform-independent. They apply regardless of the code generation target. If the compiler accepts a .ddd file, the stated properties hold. See Validation Reference for the full specification of each check.

What Gets Generated

From a validated .ddd file, the compiler generates TypeScript code targeting the Emmett framework: a decider class, smart constructors from validate constraints, event and state union types, and Emmett wiring. See Code Generation for details.

How It Differs from Frameworks

AspectWeltenwandererRuntime Frameworks
VerificationStatic, at compile timeDynamic, at runtime or in tests
CorrectnessProven by the compiler for all inputsTested by the developer for sampled inputs
Code generationFrom a single verified sourceManual wiring of events, states, and handlers
Domain languageFirst-class DSL with compile-time semanticsLibrary conventions embedded in the host language

Where to Go Next