Essays and engineering notes on event sourcing, CQRS, and building with the Cratis Platform. Its components can be adopted independently. For current component guides and reference, read the Cratis documentation.
Ten posts on keeping a decision true at the moment it is written, and one page that tells you which mechanism a rule needs, what fails without an error, and where each answer is explained in full.
A guard is a position in the log and a filter. This part takes one apart: where the position and the filter come from, what a second read does to them, how an explicit scope and declared concurrency join in, and how to print what a commit actually guarded.
A test that runs one command at a time passes with a plain read too. One line in an Arc command scenario puts a competing write between the read and the commit, and turns 'it works' into 'it still holds when someone else is faster'.
Under Arc's read protection is one Chronicle call you can make yourself: note where the log was, fold the state, and append only if nothing it depended on arrived since. This part writes that guard by hand, and says exactly what it is in DCB terms and what it is not.
One cast.yaml becomes a personal site that replays your career as events, shows your public GitHub work as it happens, and carries a badge it can't give itself. The directory at cast.cratis.io is where that badge is checked.
Read protection is the answer for state on a key you read. It is the wrong answer for a unique email, a document two people edit, or a rule you would rather repair later. Pick by the shape of the rule, and know which choices fail silently.
You can't switch a running application to 'refuse unguarded reads' on a hunch. Measure runs every command as before and reports which reads On would guard, which it would refuse, and which appends it would reject. Here is the path from Off to On, what a real report looks like, and how to back out.
Read protection says no in two ways. A 400 means something the command read changed: read again and decide. A 500 means the code reads something Arc can't guard: change the code. And under Off, a caller can turn a validator's no into a yes.
A monthly withdrawal limit depends on one month of one account. Declare that stream on the read model and route the command to it, and the guard covers that month only: a withdrawal in October conflicts, one in November doesn't.
A member may hold three loans, and a book has one copy left. Two read models in one validator, one fact for each entity, one atomic append, and if either side changed after the read, nothing is written.
A command that reads a read model and then appends can approve the same last copy twice. One project setting turns that read into a guard: the same command, the same validator, and the loser of the race gets a 400 that says nothing was written.
Is there stock? Is the name free? Is the balance enough? Every check-then-act decision has a gap between the check and the write, and two requests can both pass it. Nothing throws, the tests are green, and the bad state turns up weeks later. Why that is, what the database defaults do and don't do, and how to spot it in your own code.
Chronicle and Arc used to derive database names and connect with one credential. Now they can ask. This post shows how the question and the answer work, and wires one example through a Chronicle kernel, an Arc application and a plain client with no Arc in it.
Chronicle's own documentation says it isn't a DCB event store. So why do I keep using the term? A proposal to drop the acronym and name what we actually want from it: freedom in how events are modeled, one owner for every event, and rules that live with the command.
A walkthrough of how Cratis Studio is built: the services and what each one owns, the edge and the lobby in front of them, four event stores that talk in facts, and the infrastructure underneath. Studio is built with the platform it's meant to help you use.
Give each stage of an entity its own read model and let Chronicle move it from one to the next. No Status column, no pile of nullable fields, nothing to index just to find the backlog. How projection variants work, model-bound and fluent.
Model-bound projections put the mapping on the read model. Fluent projections keep it beside the model, in IProjectionFor<T>. Same engine, a different trade-off, and how both differ from a reducer.
Sign-in, tenant, identity and invitations are the same chores in every service. AuthProxy is a container you configure at the ingress, in front of services written in anything, so they can assume the request is already authenticated, tenant-scoped and enriched. Here's how it works, what it buys you in security, and where Arc and Ante fit.
Arc generates the commands and queries. Cratis Components is the layer that turns them into dialogs, forms and tables, without tying you to a UI library. Here's why it exists, how it works, and what 'agnostic' honestly covers.
Most of a form isn't the fields. A command form links a form to a command by its type, so you write the fields and Arc handles the state, the validation and the errors. And because it's all typed, the compiler guards the way for you, and for your AI assistant.
The classic way to enforce uniqueness in an event-sourced system, check a read model and then append, loses races by design. Chronicle constraints move the check into the kernel, and in doing so put the Dynamic Consistency Boundary to work.
Most event sourcing mistakes are about treating the log like a table: updating it, reading it right after writing it, or checking a rule before writing instead of letting the write decide. Seven of them, and what to do instead.
An event store doesn't have to be a special database. Chronicle keeps events in the database you already run, and this is where they land: databases per event store and namespace, the shape of the event record, the indexes and the read models.
A year in, the stored event has one Name and the code wants a first and a last name. Events can't be edited, so Chronicle translates them: generations, upcasts and downcasts, kept side by side by the kernel.
A projection describes a read model. A reactor does something about the outside world. A reducer is the one in between: you write the code that decides what the read model looks like, and Chronicle keeps the storage consistent.
The JVM has bytecode and .NET has IL. Over the last couple of weeks Screenplay became that for the business: one .play model that people, AI assistants, Studio and your C# code all write, and that VS Code, your MCP host, your running Arc application and the Cratis CLI all draw as the same event model board.
From an empty folder to a running Cratis application with dotnet new cratis or cratis new, the samples and what each leaves out, and the smaller tools around the stack: Fundamentals, Specifications, Synopsis, Narrator, Lens and the docs. It closes with where to start and the full series index.
Where an assistant meets Cratis: the AI corpus and its hooks, the CLI's effect labels, Chronicle MCP, Screenplay MCP and the docs assistant. What each one lets an assistant read, propose or change, and the checks that run whether it listened or not.
How a person admits agent work in Cratis Direct, what each gate records, and who merges the pull request. The control plane we run our own engineering on, followed through one issue, and where it stops.
A shared board and canvas where people and AI agents work on the same event model, Screenplay text you can diff, and opt-in code for one slice in your own repository. What Cratis Studio does at each step, and where each step stops.
Prologue stands beside a running system, records the metadata of what it does, and proposes a Screenplay model for a person to correct. What it watches, what it never records, how it groups what it sees, and what a language model may and may not change.
Stage renders a narrow slice of a Screenplay model into C# on Arc and Chronicle, boots a throwaway sandbox and runs the model's examples, and refuses with a code whatever it can't express. Scene is the UI model between a screen in the model and React.
The .play language for writing an event model down next to the code, a compiler whose diagnostic codes don't change, personal data declared once on a value, and an MCP server that lets an assistant propose changes a person applies.
Replays you can stop and resume, what a crash in the middle of one delivers twice, how events reach observers without making appends wait, and what running Chronicle on more than one node does and doesn't promise.
What Chronicle's production image won't start without, how the storage choice changes where read models land, what a client trusts until you tell it otherwise, where a trace stops today, and the backup order that decides whether a restore can be read.
A diagnostic summary that fails on a failed partition and not on lag, a full-screen terminal workbench for the box you reached over SSH, and a command catalog that tells an agent which commands change the server.
The Chronicle server serves its own browser tool on the port your clients already use. What each screen shows, how to follow a failed partition back to one author, and which screens can change a store.
Three places Arc's conventions make a decision you might not see: a validator that runs in the browser and on the server but not identically, a proxy folder the generator owns, and routes, event identities and mappings chosen by a name. What each one does, and the fix.
A new event shape, a new view over old events, a correction to a stored fact and an observer stuck on one stream look alike, and they aren't. What each one changes in Chronicle, what the kernel works out on its own, and what an operator still decides.
Chronicle has clients for .NET, Kotlin and Java, TypeScript and Elixir, and Arc has servers on ASP.NET Core, Spring Boot and, as a source preview, Node.js. What each one gives you today, which registry it comes from, and the defaults that still differ from .NET.
Most specs in a Cratis application can call a method and compare the event it returns. What the in-process scenarios add on top, the ways a green spec proves nothing, and the few tests that still need a real Chronicle and a running host.
AuthProxy signs the user in, picks a tenant and forwards it as x-cratis-tenant-id, the header Arc reads by default, and Chronicle turns the tenant into a namespace. Every hop from the edge to storage, what a missing tenant becomes at each one, and what stays yours to check.
The author list and the register dialog from the cratis template, built with Cratis Components 4. Which parts come from Components and which from Arc, how a field binds to a generated command, how a taken name gets back to the Name field, theming and renderer adapters, and where the library stops.
In Arc, a query that loads once and a query that keeps pushing results to React differ by one return type, given data that notifies on change. The whole path from a write to the screen, the transport underneath, the hook and its states, command dialogs, view models and live tables, and which hop is live and which is a snapshot.
Every place a rule can live in a Cratis application, from the rules copied into the browser to Chronicle's append. What each layer can and can't guarantee, why a taken author name is caught in only one of them, and which rejections find their way back to a form field.
A command is a record with a Handle method and a query is a static method on the read model it returns. What Arc builds from those two shapes, in which order it checks a request, what reaches the generated TypeScript, and what stays the application's job.
Checking a plain read model before you append enforces nothing. Constraints, concurrency scopes, decision reads, Arc's read protection and Arc aggregates check at write time, and each reaches a different set of appends.
Reactors, webhooks and outbox-to-inbox subscriptions carry what happened to whoever cares. A failing handler stops one partition, a reactor can return its follow-up facts, and a stable delivery id is what lets a side effect survive running twice.
One attribute turns AuthorRegistered into a list on a screen. Model-bound, fluent and PDL projections, reducers for when attributes can't say it, where read models are stored, how a live list learns about changes, and what Chronicle does when a projection changes or has to be rebuilt.
Chronicle's append returns without waiting for a single projection. What the kernel does inside that call, how observers and partitions pick the event up afterwards, and what happens when one of them fails.
An event records something that happened, and no event is taken out of the log afterwards. How to name and shape events that still read in five years, and what Chronicle stores with each one: a sequence number, its source, the operation it belonged to, who caused it and why.
The sticky-note feature followed from its model to production, into year two and to agents writing its code, through every Cratis product this post names, with 26 more posts that take each one all the way down.
The zero-to-first-projection walkthrough again — same store, same domain, same destination — but this time Claude Code does the typing, using the official .NET templates and the Cratis AI `cratis/application/csharp` profile installed with the Cratis CLI, with Arc commands carrying the full loop.
An append-only event log is the worst possible place to keep personal data — unless the store itself knows which values are personal and can make them disappear without rewriting history. How Chronicle's [PII] adornments, per-subject keys, and crypto-shredding resolve the collision.
Scaffold a full-stack Cratis application from the official .NET templates, model a small library with Arc commands and a Chronicle read model, and see it live in a React UI.
Chronicle's kernel sits behind a language-agnostic gRPC/protobuf boundary — 29 canonical .proto contracts that any language can implement. Here's how the contract is layered, how the existing clients are built on it, and what a new client implements.
Most event sourcing comparisons reduce to popularity. We compiled a version-pinned, source-cited comparison of KurrentDB, Marten, and Cratis Chronicle instead — every row reproducible from public documentation, with the same rules applied to our own product. Here's how to use it.
What Cratis is, what Chronicle covers, and how Arc, Components, the CLI, Workbench, and the model-first layer fit together — all open source and MIT licensed.