<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:fh="http://purl.org/syndication/history/1.0"><channel><title>Cratis Blog | Blog</title><description>The Cratis blog — essays and engineering explainers on event sourcing, CQRS, and building the open-source (MIT) Cratis stack: Chronicle, Arc, Components, and friends.</description><link>https://blog.cratis.io/</link><language>en</language><atom:link rel="self" href="https://blog.cratis.io/rss.xml"/><atom:link rel="prev-archive" href="https://blog.cratis.io/rss/2026-09.xml"/><item><title>The assembly language of the business</title><link>https://blog.cratis.io/screenplay-the-assembly-language-of-the-business/</link><guid isPermaLink="true">https://blog.cratis.io/screenplay-the-assembly-language-of-the-business/</guid><description>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.</description><pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img alt=&quot;A diagram titled Every platform has its IL, with three rows. .NET: C# and F# compile to IL, which runs on the CLR. JVM: Java and Kotlin compile to bytecode, which runs on the JVM. Business: teams, AI and code produce Screenplay, which becomes boards and apps.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2000&quot; src=&quot;https://blog.cratis.io/_astro/intermediate-language.CDUHSI4d_ZsVp1C.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Let’s face it, nobody gets excited about intermediate languages. Hardly anyone writes IL by hand, and I’ve yet to meet someone who reads JVM bytecode for fun. Yet I would argue they’re among the most important ideas our industry ever had.&lt;/p&gt;
&lt;p&gt;Think about what IL actually did for .NET. C#, F# and VB all compile down to the same thing. The runtime only has to understand that one thing. And every tool that came after, debuggers, decompilers, profilers, analyzers, obfuscators, got to work on &lt;em&gt;every&lt;/em&gt; language for free, because they only had to read IL. The JVM got the exact same superpower from bytecode, which is why Kotlin and Scala could show up years later and inherit an entire ecosystem on day one. Languages above, tools below, and a small, stable, well-defined format in the middle that both sides agree on.&lt;/p&gt;
&lt;p&gt;The thing is, the business side of software has never had that. We’ve had whiteboards, sticky notes, Miro boards, Word documents, Jira epics and a lot of very good intentions. None of them are something a compiler can check, a tool can read, or a pull request can diff. So the moment the code exists, the model starts drifting away from it.&lt;/p&gt;
&lt;p&gt;That’s what &lt;a href=&quot;https://cratis.io/screenplay/&quot;&gt;Screenplay&lt;/a&gt; is for, and over the last couple of weeks it really clicked for me what it has become: &lt;strong&gt;the assembly language of the business&lt;/strong&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-quick-recap-of-what-screenplay-is&quot;&gt;A quick recap of what Screenplay is&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Screenplay is a small, indentation-based language for writing down an event model: modules and features, slices, commands, events, read models, screens, policies, personas and the specifications that say what should happen. It lives in &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; files next to your code, it has a compiler with stable diagnostic codes, and it prints back to the same text it read. It’s young, so expect it to keep moving.&lt;/p&gt;
&lt;p&gt;This is a slice from the Library sample that ships with Screenplay:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;slice StateChange AddBook&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;description &quot;The librarian adds a newly bought book to the catalog&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;command AddBook&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bookId BookId identifier&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;isbn   Isbn&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title  BookTitle&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;author AuthorName&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;authorize IsLibrarian&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;validate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;isbn matches &quot;^[0-9]{13}$&quot;  message &quot;An ISBN has 13 digits&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title not empty             message &quot;A book needs a title&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;produces BookAdded&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;for bookId&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;isbn   = isbn&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title  = title&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;author = author&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;event BookAdded&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;isbn   Isbn&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title  BookTitle&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;author AuthorName&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If you want the language tour, Sindre did a great job with that in &lt;a href=&quot;https://blog.cratis.io/screenplay-an-event-model-you-can-compile/&quot;&gt;Screenplay: an event model you can compile&lt;/a&gt;. What I want to talk about here is not the language itself. It’s everything that now sits &lt;em&gt;around&lt;/em&gt; it.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A diagram titled One language in the middle. On the left, in a group labeled Written by: People, AI assistants, Studio, C# code and Live systems, each with an arrow into a central box labeled Screenplay, .play. From that box, arrows go to a group on the right labeled Read by: Event models, Running apps, Generated code and Diagnostics.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2000&quot; src=&quot;https://blog.cratis.io/_astro/screenplay-hub.BgoE5pkB_1PGaEo.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;That picture is the whole point of this post. On the left are all the ways a Screenplay document comes into existence. A person writes it in an editor. An AI assistant proposes it through the Screenplay MCP server. &lt;a href=&quot;https://cratis.io/studio/&quot;&gt;Cratis Studio&lt;/a&gt; exports it from the board you’ve been modeling on together. The Cratis CLI generates it from Arc, Marten or Wolverine source code with &lt;a href=&quot;https://cratis.io/cli/reference/screenplay/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;cratis screenplay generate&lt;/code&gt;&lt;/a&gt;. And &lt;a href=&quot;https://cratis.io/cli/reference/prologue/&quot;&gt;Prologue&lt;/a&gt; interprets one from what an existing system is observed doing. On the right are the things that read it. Same file format in the middle, every single time.&lt;/p&gt;
&lt;p&gt;Exactly like IL. Nobody on the left has to know who’s on the right.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;one-board-extracted-out-of-studio&quot;&gt;One board, extracted out of Studio&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The first piece of the puzzle was making sure that whatever reads Screenplay can &lt;em&gt;show&lt;/em&gt; it, and show it the same way everywhere.&lt;/p&gt;
&lt;p&gt;Studio has had a very nice event model board for a while. The problem was that it lived inside Studio. So we pulled it out. &lt;a href=&quot;https://www.npmjs.com/package/@cratis/event-models&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;@cratis/event-models&lt;/code&gt;&lt;/a&gt; is now a published, MIT-licensed npm package containing the exact board Studio draws, both the read-only viewer and the editor. It’s released together with Studio and carries Studio’s version number, which is still &lt;code dir=&quot;auto&quot;&gt;0.x&lt;/code&gt;, so pin a minor version and expect the API to move a bit.&lt;/p&gt;
&lt;p&gt;Rendering a board in your own React app is pretty much this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;createRoot&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;react-dom/client&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;EventModelBoard&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;readEventModelDocument&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/event-models&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/components/tokens&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/components/styles&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/components/theme&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/event-models/theme&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/event-models/styles&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/scene/styles&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;primeicons/primeicons.css&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;./library-model.json&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;readEventModelDocument&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;eventModel&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;createRoot&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;document&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;root&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;span&gt; height&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;100vh&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}}&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;EventModelBoard&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;document&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;model&lt;/span&gt;&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;readOnly&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That’s the snippet I ran in a throwaway Vite app to take the screenshot below, with &lt;code dir=&quot;auto&quot;&gt;@cratis/event-models&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;@cratis/scene&lt;/code&gt; at &lt;code dir=&quot;auto&quot;&gt;0.117.3&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;@cratis/components&lt;/code&gt; at &lt;code dir=&quot;auto&quot;&gt;4.25.0&lt;/code&gt;. The JSON it reads is the board model of a small library application, which I’ll get to in a moment.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Cratis event model board rendered by the @cratis/event-models package in a plain web page. A Lending module holds Catalog and Loans features with the slices AddBook, Books, BooksOnLoan, BorrowBook and ReturnBook, each showing its command or read model above the event it produces or consumes.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2200&quot; height=&quot;962&quot; src=&quot;https://blog.cratis.io/_astro/event-models-package.LxRD8zAg_ZSObqq.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The board from &lt;code dir=&quot;auto&quot;&gt;@cratis/event-models&lt;/code&gt;, in a page that contains nothing else.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once the board was a package, it could go anywhere. And it went to a lot of places in a short amount of time.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A diagram titled One board, many hosts. A central box labeled @cratis/event-models, the board Studio draws, has arrows up to Studio, VS Code and MCP Apps hosts, and arrows down to Arc explorer, cratis view and Your own app.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2000&quot; src=&quot;https://blog.cratis.io/_astro/one-board-many-hosts.B_ZvOURt_TTkGX.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;open-a-play-file-get-a-board&quot;&gt;Open a .play file, get a board&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The &lt;a href=&quot;https://cratis.io/screenplay/vscode/&quot;&gt;Screenplay VS Code extension&lt;/a&gt; now opens &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; files on the event model board by default. It’s the same board as Studio, with modules and features across the top, a column per slice, the personas as rows with screens drawn as small prototypes in them, and the Given/When/Then specifications hanging underneath each slice.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;code&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--install-extension&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cratis.screenplay&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The text is still the source of truth. Run &lt;strong&gt;Show Source&lt;/strong&gt; from the command palette and you get the &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file side by side with the board, with highlighting, completion, hover and diagnostics, and the board redraws as you type.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;VS Code with the Library sample’s library.play open twice side by side. On the left, the event model board shows the Catalog feature: AddBook, BookCatalog and OpeningHours slices with persona rows for Librarian and Member, commands, read models, events, and Given/When/Then specifications below. On the right, the Screenplay source of the AddBook slice with its command, validation, produces block, event, constraint, screen and specification.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2200&quot; height=&quot;1344&quot; src=&quot;https://blog.cratis.io/_astro/vscode-board-and-source.CQXFw2or_1mbJJG.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Library sample in VS Code: the board on the left, the Screenplay it’s drawn from on the right.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Zoom out and you get the whole application in one picture. A picture of the system is something I’ve wanted for every system I’ve built, and this one is drawn from the very file the rest of the tooling reads.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The full Library sample on the event model board in VS Code, zoomed out: the Lending module with Catalog, Members and Loans features, eight slices including a WelcomeNewMembers automation, and the specifications for each slice below them.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2200&quot; height=&quot;1344&quot; src=&quot;https://blog.cratis.io/_astro/vscode-board-overview.WOFh-Axk_NdAdk.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;your-ai-assistant-gets-a-whiteboard&quot;&gt;Your AI assistant gets a whiteboard&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is the one that made me smile the most.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://cratis.io/screenplay/mcp/&quot;&gt;Screenplay MCP server&lt;/a&gt; has been around for a bit, letting an assistant explore a model, find declarations, find gaps in your specifications and &lt;em&gt;propose&lt;/em&gt; changes that a person reviews and applies. Over the last week it gained a bunch of new capabilities. It now picks its workspace by itself (from the folder you name, the folder your client offers, or the folder you started it in), it ships as &lt;a href=&quot;https://cratis.io/screenplay/mcp/install/&quot;&gt;desktop bundles for Claude Desktop and a local plugin for ChatGPT Desktop&lt;/a&gt;, and the CLI can install and update those for you with &lt;code dir=&quot;auto&quot;&gt;cratis screenplay mcp install&lt;/code&gt;. It guides the assistant towards valid models &lt;em&gt;before&lt;/em&gt; it proposes anything, and it can propose typed repairs for some diagnostics instead of just complaining about them.&lt;/p&gt;
&lt;p&gt;And it can draw.&lt;/p&gt;
&lt;p&gt;The server now offers the board as an &lt;a href=&quot;https://modelcontextprotocol.io/extensions/apps/overview&quot;&gt;MCP App&lt;/a&gt;, a view the host renders right inside the conversation, through a tool called &lt;code dir=&quot;auto&quot;&gt;visualize-model&lt;/code&gt;. In &lt;a href=&quot;https://cratis.io/screenplay/mcp/view/&quot;&gt;hosts that render MCP Apps&lt;/a&gt;, like Claude Desktop and GitHub Copilot in VS Code, you can just say &lt;em&gt;“Show me the application as an event model board”&lt;/em&gt; and there it is. While it’s on screen it follows the files on disk, so when the assistant applies a change or you edit a file by hand, the board catches up on its own.&lt;/p&gt;
&lt;p&gt;The part I find really powerful is the what-if. Ask the assistant to sketch something without changing anything:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Without changing anything, sketch what a Reservations feature could look like next&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;to the existing Lending module, and show it on the board. Do not propose it yet.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The board then shows the application as the sketch would leave it, with a &lt;strong&gt;Current&lt;/strong&gt; and &lt;strong&gt;Proposed&lt;/strong&gt; switch in the toolbar. Whatever both share stays in place, so the only thing that moves is the difference.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Screenplay MCP board toggling between Current and Proposed for the Library sample. In Proposed, a new Reservations feature appears to the right of Loans with a ReserveBook command producing BookReserved, and a ReservationQueue slice building a Reservation read model from it.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1400&quot; height=&quot;778&quot; src=&quot;https://blog.cratis.io/_astro/mcp-sketch-toggle.BfAHwoMF_Z2laSV9.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A sketch of a Reservations feature drawn over the Library sample. Nothing is written to disk.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To be honest about how I took that one: I didn’t screenshot Claude Desktop. The capture is the actual &lt;code dir=&quot;auto&quot;&gt;ui://&lt;/code&gt; view served by &lt;code dir=&quot;auto&quot;&gt;cratis screenplay mcp&lt;/code&gt; (Screenplay 4.60.1, as bundled in Cratis CLI 3.27.0), rendered in a minimal MCP Apps host I put together with the official &lt;code dir=&quot;auto&quot;&gt;@modelcontextprotocol/ext-apps&lt;/code&gt; bridge, so I could drive it from a script. The sketch it draws is a real &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; document. I compiled it together with the sample with &lt;code dir=&quot;auto&quot;&gt;cratis screenplay validate&lt;/code&gt; first, and the server’s own summary of it read &lt;em&gt;“which would have 10 slice(s), 6 event(s), 0 error(s)”&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This changes the conversation with an assistant quite a bit. Instead of reading a wall of generated text and trying to imagine what it does to the model, you &lt;em&gt;look&lt;/em&gt; at what it would do, before anything is written. And since the board is the same board you’d see in Studio or VS Code, there’s nothing new to learn.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A still of the Proposed view of the Reservations sketch on the MCP board, with the new ReserveBook and ReservationQueue slices at the right of the Lending module.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2200&quot; height=&quot;1222&quot; src=&quot;https://blog.cratis.io/_astro/mcp-sketch-proposed.DxQTZWL-_1axt0f.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;your-running-application-already-knows-its-model&quot;&gt;Your running application already knows its model&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Now for the other direction. Everything so far is about a Screenplay someone, or something, wrote. But what about the code you already have?&lt;/p&gt;
&lt;p&gt;In &lt;a href=&quot;https://cratis.io/arc/&quot;&gt;Arc&lt;/a&gt;, we’ve added &lt;code dir=&quot;auto&quot;&gt;Cratis.Arc.Screenplay.Embedded&lt;/code&gt;. During compilation it analyzes your commands, events, read models, projections and reactors, generates Screenplay documents from them, one for the assembly and one per module and feature, and embeds them in your assembly. Generation errors fail the build rather than embedding something the Screenplay compiler would reject. The &lt;code dir=&quot;auto&quot;&gt;Cratis&lt;/code&gt; metapackage includes it, and &lt;code dir=&quot;auto&quot;&gt;UseCratis()&lt;/code&gt; maps a read-only explorer at &lt;code dir=&quot;auto&quot;&gt;/.cratis/event-model/&lt;/code&gt; for a Debug-built application. You don’t have to do anything.&lt;/p&gt;
&lt;p&gt;I verified that with a fresh application from the template (&lt;code dir=&quot;auto&quot;&gt;dotnet new cratis&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Cratis&lt;/code&gt; 22.50.3), where I replaced the sample feature with a small library (adding books, borrowing and returning them, registering members and a reactor welcoming them), ran it with &lt;code dir=&quot;auto&quot;&gt;dotnet run&lt;/code&gt;, and opened the explorer.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The embedded event model explorer at /.cratis/event-model/ in a running Arc application. A sidebar lists the Library assembly, the Lending module and the Catalog, Loans and Members features. The Members board shows RegisterMember producing MemberRegistered and a WelcomeMembers automation reacting to it. The View menu is open with detail level, properties and visualization options. Warnings at the top list what the board cannot show.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2200&quot; height=&quot;1375&quot; src=&quot;https://blog.cratis.io/_astro/arc-embedded-explorer.4NSh_U9T_Z13fHuk.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The explorer in a running Arc application, with the View options open.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A few things worth calling out here.&lt;/p&gt;
&lt;p&gt;This is source-derived documentation, not a view of live data. It shows what your code &lt;em&gt;says&lt;/em&gt; the model is, not what’s in the event store. It’s also read-only, of course, since the code is the source.&lt;/p&gt;
&lt;p&gt;The board is honest about what it can’t draw. See those warnings at the top? The analysis found things a board card can’t express, like a reaction whose body is code, or property mappings on a produced event, and instead of quietly dropping them it tells you. The &lt;strong&gt;Source&lt;/strong&gt; tab always gives you the full generated Screenplay.&lt;/p&gt;
&lt;p&gt;And debug detection isn’t authorization. The explorer exposes your application’s structure, so the &lt;a href=&quot;https://cratis.io/arc/backend/csharp/embedded-event-model/&quot;&gt;docs&lt;/a&gt; cover turning it off or putting a policy on it if a debug build is reachable by anyone but you.&lt;/p&gt;
&lt;p&gt;One thing I ran into while writing this: a project that pulls in &lt;code dir=&quot;auto&quot;&gt;Microsoft.CodeAnalysis.Analyzers&lt;/code&gt; transitively can hit an MSBuild target cycle with the embedded generation in Debug (&lt;a href=&quot;https://github.com/Cratis/Arc/issues/2979&quot;&gt;Arc#2979&lt;/a&gt;). Our own Library showcase in the Samples repository turns the generation off for that reason until it’s fixed. The template app doesn’t have the problem.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;no-need-to-run-anything-cratis-view&quot;&gt;No need to run anything: cratis view&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Now, starting an application just to look at its model is a bit much. Especially when the model is the thing you wanted to look at &lt;em&gt;before&lt;/em&gt; you set up its database.&lt;/p&gt;
&lt;p&gt;So the Cratis CLI got &lt;a href=&quot;https://cratis.io/cli/reference/view/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;cratis view&lt;/code&gt;&lt;/a&gt;. Run it in the folder of a &lt;code dir=&quot;auto&quot;&gt;.csproj&lt;/code&gt; and it serves the very same explorer from your machine and opens a browser on it.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;view&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If the built assembly embeds Screenplay documents, those are what you see. If it doesn’t, or you pass &lt;code dir=&quot;auto&quot;&gt;--from-source&lt;/code&gt;, it generates them from the project’s source in memory with the same Arc generator the build uses, without writing a single file. None of your application’s code runs, the assembly isn’t locked so you can keep rebuilding, and it only listens on loopback.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;cratis view showing the Loans feature of the library application: BooksOnLoan, BorrowBook and ReturnBook slices, with the warnings the board reports at the top.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2200&quot; height=&quot;1375&quot; src=&quot;https://blog.cratis.io/_astro/cratis-view-loans.B4SXiHhq_Z27M6L.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Source tab of cratis view, showing the generated Screenplay for the library application with syntax highlighting: the Library domain, its concepts, the Lending module and its Catalog and Loans features.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2200&quot; height=&quot;1375&quot; src=&quot;https://blog.cratis.io/_astro/cratis-view-source.UQK7lHZT_15EKQV.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The same application through &lt;code dir=&quot;auto&quot;&gt;cratis view&lt;/code&gt; (Cratis CLI 3.27.0), without starting it. Board on top, the generated Screenplay below.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That last screenshot is the one I keep coming back to. That’s C# code, compiled down to the business’s assembly language, and then drawn as an event model. It’s ILDASM for your domain.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-the-format-in-the-middle-matters&quot;&gt;Why the format in the middle matters&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Let me get back to the IL thing, because I think it’s the real story here and not just a cute analogy.&lt;/p&gt;
&lt;p&gt;None of what I’ve shown you required the tools to know about each other. VS Code doesn’t know about Arc. The MCP server doesn’t know about the CLI’s &lt;code dir=&quot;auto&quot;&gt;view&lt;/code&gt; command. Arc doesn’t know about Studio. They all speak Screenplay, and they all draw it with the same board package. Every new thing that can &lt;em&gt;produce&lt;/em&gt; a &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file gets every viewer for free, and every new viewer works for every producer. That’s exactly the leverage IL gave .NET.&lt;/p&gt;
&lt;p&gt;It goes the other way too. Screenplay is what &lt;a href=&quot;https://cratis.io/cli/reference/run/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;cratis run&lt;/code&gt;&lt;/a&gt; boots into a live sandbox, and what &lt;code dir=&quot;auto&quot;&gt;cratis render&lt;/code&gt; turns into an application. The more things that can read and write the format, the more valuable each &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file becomes.&lt;/p&gt;
&lt;p&gt;Which is also why so much of the last couple of weeks went into the language itself. Applications composed from many files through imports, events declared inline where they’re produced, optional values written as &lt;code dir=&quot;auto&quot;&gt;Type optional&lt;/code&gt;, deterministic clocks, reactions, triggers and captures. On top of that you can now write down event sources and streams, external systems and command operations, and handler implementation intent, although those are syntax only for now: the compiler and the editors understand them, but nothing executes them yet. Studio exports more of its model to Screenplay and says out loud what it couldn’t carry. An intermediate language is only as good as what it can express, and every one of those makes it a little more complete.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I’ve spent most of my career building platforms for developers, and the one thing I’ve always wanted is for the model the business talks about and the code we ship to be the &lt;em&gt;same thing&lt;/em&gt;. Not two things we try to keep in sync. Screenplay is not there yet, and there are still things the board can’t draw. But for the first time I can point at a single format that people, AI assistants, Studio and our C# code all write, and that VS Code, an MCP host, a running Arc application and the CLI all read and draw the same way.&lt;/p&gt;
&lt;p&gt;If you want to give it a go:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install the &lt;a href=&quot;https://cratis.io/screenplay/vscode/&quot;&gt;VS Code extension&lt;/a&gt; and open a &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file. The &lt;a href=&quot;https://github.com/Cratis/Screenplay/tree/main/Samples&quot;&gt;Screenplay samples&lt;/a&gt; are a good start, Library in particular.&lt;/li&gt;
&lt;li&gt;Run &lt;a href=&quot;https://cratis.io/cli/reference/view/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;cratis view&lt;/code&gt;&lt;/a&gt; in one of your Arc projects and look at the model you’ve already written.&lt;/li&gt;
&lt;li&gt;Connect the &lt;a href=&quot;https://cratis.io/screenplay/mcp/install/&quot;&gt;Screenplay MCP server&lt;/a&gt; to Claude Desktop or Copilot in VS Code, and ask it to show you the board.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And if you build something that reads or writes Screenplay, I’d love to hear about it. That’s kind of the whole point.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Tested with: .NET SDK 10.0.400, Cratis CLI 3.27.0 (bundling Screenplay 4.60.1), the &lt;code dir=&quot;auto&quot;&gt;cratis.screenplay&lt;/code&gt; VS Code extension 4.62.1, &lt;code dir=&quot;auto&quot;&gt;Cratis&lt;/code&gt; 22.50.3 from &lt;code dir=&quot;auto&quot;&gt;Cratis.Templates&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;@cratis/event-models&lt;/code&gt; 0.117.3, on October 5, 2026.&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>screenplay</category><category>event-modeling</category><category>mcp</category><category>ai</category><category>arc</category><category>cli</category><category>cratis-studio</category></item><item><title>From script to stage · Part 26: Getting started with Cratis, and the tools around it</title><link>https://blog.cratis.io/getting-started-with-cratis/</link><guid isPermaLink="true">https://blog.cratis.io/getting-started-with-cratis/</guid><description>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.</description><pubDate>Wed, 30 Sep 2026 09:26:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt; · Part 26 of 26&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;dotnet new cratis -n MyStore&lt;/code&gt; writes a command that registers a name, a projection that lists the names, a reactor that logs each one and a React page for all of it, before you’ve written a line of your own. Three more commands start Chronicle, the backend and the frontend. The name you register on that page is an event you can open in the Workbench.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-first-hour&quot;&gt;The first hour&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The C# path needs the .NET 10 SDK, because every .NET template targets &lt;code dir=&quot;auto&quot;&gt;net10.0&lt;/code&gt; and nothing else. It also needs Docker with the Compose plugin, and Node.js &lt;code dir=&quot;auto&quot;&gt;^20.19.0&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;&gt;=22.12.0&lt;/code&gt; with yarn, pnpm or npm. The Kotlin and Java variants build with JDK 17 instead of the .NET SDK. The &lt;a href=&quot;https://cratis.io/templates/getting-started/&quot;&gt;getting started guide&lt;/a&gt; goes from an empty folder to the first event:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dotnet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis.Templates&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dotnet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyStore&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyStore&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;compose&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;up&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dotnet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;yarn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;dotnet run&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;yarn dev&lt;/code&gt; each want a terminal of their own.&lt;/p&gt;
&lt;p&gt;Creating the project runs post-creation actions that add the Cratis NuGet packages at their current release and install the frontend dependencies. The template source leaves its package versions open on purpose, and the generated &lt;code dir=&quot;auto&quot;&gt;.csproj&lt;/code&gt; records the versions it resolved, so two projects scaffolded a week apart can differ. &lt;code dir=&quot;auto&quot;&gt;dotnet new&lt;/code&gt; asks before it runs the frontend install. In a script or a dev container nobody is there to answer, and Cratis.Templates 1.7.2 keeps re-prompting instead of moving on, so pass &lt;code dir=&quot;auto&quot;&gt;--allow-scripts yes&lt;/code&gt;. Add &lt;code dir=&quot;auto&quot;&gt;--packageManager none&lt;/code&gt; as well to pin the packages and skip the install.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;--Database&lt;/code&gt; picks MongoDB, the default, or PostgreSQL, SQL Server or SQLite. Any choice other than MongoDB swaps Arc’s MongoDB package for its Entity Framework Core one and points Chronicle’s sink at SQL.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;docker compose up -d&lt;/code&gt; starts the Chronicle development image, which bundles MongoDB, and an Aspire dashboard. The compose file is for development. It publishes its ports on every network interface and the Chronicle image uses well-known development credentials, so keep it on a machine you trust. With the default MongoDB setup, events and read models live inside the Chronicle container. &lt;code dir=&quot;auto&quot;&gt;docker compose stop&lt;/code&gt; keeps them and &lt;code dir=&quot;auto&quot;&gt;docker compose down&lt;/code&gt; deletes them.&lt;/p&gt;
&lt;p&gt;The backend listens on &lt;code dir=&quot;auto&quot;&gt;http://localhost:5000&lt;/code&gt;, with Swagger at &lt;code dir=&quot;auto&quot;&gt;/swagger&lt;/code&gt;. Vite serves the frontend on &lt;code dir=&quot;auto&quot;&gt;http://localhost:9000&lt;/code&gt; and forwards &lt;code dir=&quot;auto&quot;&gt;/api&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;/.cratis&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;/swagger&lt;/code&gt; to the backend. Port 5000 has to be free because the Vite proxy is fixed to it, and so does 27017, because the Chronicle image advertises its MongoDB replica-set member as &lt;code dir=&quot;auto&quot;&gt;localhost:27017&lt;/code&gt;. Register a name on the Demo page and three things happen. The backend logs &lt;code dir=&quot;auto&quot;&gt;Registered: &amp;#x3C;name&gt;&lt;/code&gt; from the sample reactor, the name appears in the list, and the event shows up in the &lt;a href=&quot;https://cratis.io/chronicle/workbench/&quot;&gt;Chronicle Workbench&lt;/a&gt; at &lt;code dir=&quot;auto&quot;&gt;https://localhost:35000&lt;/code&gt;, once you’ve accepted the self-signed development certificate and signed in with the development account.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/app-41-event-in-workbench.ClrD4nqC_PpWqo.webp&quot; alt=&quot;Screen recording of the Chronicle Workbench event log of the MyStore store with four Registered events. One event is opened and its content shows the name Corwin Ashdown that was typed into the dialog.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A registered name, opened as an event in the Workbench.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/first-hour.Xek5MQSq_mPFoH.webp&quot; alt=&quot;A diagram titled The first hour, six numbered steps. 1 Install: dotnet new install Cratis.Templates. 2 Scaffold: dotnet new cratis -n MyStore. 3 Start: docker compose up -d. 4 Run: dotnet run and yarn dev. 5 Register: a name on the Demo page. 6 Inspect: the event in the Workbench. Below, a folder labeled What you get: SomeModule/SomeFeature/, with Registration holding a command, an event, a reactor and a dialog, and Listing holding a read model, an observable query and a list page. A footer reads: Needs the .NET 10 SDK, Docker Compose and Node. Development only.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;From an empty folder to an event you can open.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;what-the-template-writes&quot;&gt;What the template writes&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The &lt;a href=&quot;https://cratis.io/templates/cratis-web/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;cratis&lt;/code&gt; template&lt;/a&gt; puts its sample under &lt;code dir=&quot;auto&quot;&gt;SomeModule/SomeFeature/&lt;/code&gt;, in two vertical slices.&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-0&quot; id=&quot;tab-0-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-1&quot; id=&quot;tab-0-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-2&quot; id=&quot;tab-0-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-3&quot; id=&quot;tab-0-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-4&quot; id=&quot;tab-0-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-0&quot; aria-labelledby=&quot;tab-0-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;SomeModule/SomeFeature/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── Listing/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── index.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── Listing.cs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── Listing.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   └── ListingPage.tsx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── Registration/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── index.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── RegisterDialog.tsx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── Registration.cs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   └── Registration.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── index.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── SomeFeature.tsx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── SomeId.cs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;└── SomeName.cs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-1&quot; aria-labelledby=&quot;tab-0-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;src/main/kotlin/com/example/somemodule/somefeature/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── listing/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   └── Listing.kt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── registration/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   └── Registration.kt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── SomeId.kt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;└── SomeName.kt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;SomeModule/SomeFeature/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── Listing/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── index.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   └── ListingPage.tsx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── Registration/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── index.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   └── RegisterDialog.tsx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── index.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;└── SomeFeature.tsx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-2&quot; aria-labelledby=&quot;tab-0-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;src/main/java/com/example/somemodule/somefeature/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── listing/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── Listing.java&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   └── ListingReducer.java&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── registration/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── Register.java&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── Registered.java&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   └── RegistrationReactor.java&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── SomeId.java&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;└── SomeName.java&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;SomeModule/SomeFeature/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── Listing/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── index.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   └── ListingPage.tsx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── Registration/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── index.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   └── RegisterDialog.tsx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── index.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;└── SomeFeature.tsx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-3&quot; aria-labelledby=&quot;tab-0-3&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;No TypeScript template: &lt;code dir=&quot;auto&quot;&gt;--language&lt;/code&gt; takes &lt;code dir=&quot;auto&quot;&gt;csharp&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;kotlin&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;java&lt;/code&gt;.&lt;/p&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-4&quot; aria-labelledby=&quot;tab-0-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;No Elixir template: &lt;code dir=&quot;auto&quot;&gt;--language&lt;/code&gt; takes &lt;code dir=&quot;auto&quot;&gt;csharp&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;kotlin&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;java&lt;/code&gt;.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;Registration/&lt;/code&gt; holds a &lt;code dir=&quot;auto&quot;&gt;[Command]&lt;/code&gt; record, a &lt;code dir=&quot;auto&quot;&gt;Registered&lt;/code&gt; event, a reactor and a dialog, and &lt;code dir=&quot;auto&quot;&gt;Listing/&lt;/code&gt; a read model built from that event, a query and a list page. C# projects the read model with &lt;code dir=&quot;auto&quot;&gt;[FromEvent&amp;#x3C;Registered&gt;]&lt;/code&gt; behind an observable query. Kotlin and Java use a reducer and a plain query, and keep the backend under &lt;code dir=&quot;auto&quot;&gt;src/main&lt;/code&gt;, apart from the frontend. The command is short:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-0&quot; id=&quot;tab-1-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-1&quot; id=&quot;tab-1-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-2&quot; id=&quot;tab-1-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-3&quot; id=&quot;tab-1-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-4&quot; id=&quot;tab-1-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-0&quot; aria-labelledby=&quot;tab-1-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Command&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Register&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;SomeName&lt;/span&gt;&lt;span&gt;&lt;span&gt; Name&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;SomeId&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Registered&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Handle&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; eventSourceId &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SomeId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;New&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;eventSourceId&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Name&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-1&quot; aria-labelledby=&quot;tab-1-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@Command&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@AllowAnonymous&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Register&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;@CommandKey&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; id: &lt;/span&gt;&lt;span&gt;SomeId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; name: &lt;/span&gt;&lt;span&gt;SomeName&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;(): &lt;/span&gt;&lt;span&gt;Registered&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Registered&lt;/span&gt;&lt;span&gt;(name)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-2&quot; aria-labelledby=&quot;tab-1-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Command&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;AllowAnonymous&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Register&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;/span&gt;&lt;span&gt;CommandKey&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SomeId&lt;/span&gt;&lt;span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;SomeName&lt;/span&gt;&lt;span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CompletionStage&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Registered&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CompletableFuture&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;completedFuture&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Registered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;()));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-3&quot; aria-labelledby=&quot;tab-1-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;There is no TypeScript template. This is the equivalent command in the source-only Arc for TypeScript preview, not generated template output.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Register&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;SomeName&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SomeName&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;someId&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SomeId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tuple&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;eventSourceIdResponse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;someId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;()),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Registered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-4&quot; aria-labelledby=&quot;tab-1-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Arc for Elixir doesn’t exist; use the Chronicle Elixir client directly.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;In C# and TypeScript, &lt;code dir=&quot;auto&quot;&gt;Handle()&lt;/code&gt; returns the identity it chose and the event to append, and Arc appends it. The Kotlin and Java templates take the identity as the command’s &lt;code dir=&quot;auto&quot;&gt;@CommandKey&lt;/code&gt;, return only the event, and Arc appends it to that key. The project has no controller and registers no route. &lt;a href=&quot;https://cratis.io/arc/backend/csharp/proxy-generation/configuration/routing/&quot;&gt;Arc derives the route from the namespace&lt;/a&gt;, and the build generates the TypeScript proxy the dialog calls, next to its C# source. The route settings live in &lt;code dir=&quot;auto&quot;&gt;appsettings.json&lt;/code&gt; and have matching &lt;code dir=&quot;auto&quot;&gt;CratisProxies*&lt;/code&gt; properties in the &lt;code dir=&quot;auto&quot;&gt;.csproj&lt;/code&gt;, and the two have to change together. &lt;a href=&quot;https://blog.cratis.io/arc-gotchas/&quot;&gt;&lt;em&gt;Arc gotchas: where a convention decides for you&lt;/em&gt;&lt;/a&gt; shows what happens when they don’t, and why a project laid out like this one should leave &lt;code dir=&quot;auto&quot;&gt;CratisProxiesSkipOutputDeletion&lt;/code&gt; at &lt;code dir=&quot;auto&quot;&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For the author feature, &lt;code dir=&quot;auto&quot;&gt;Register&lt;/code&gt; becomes &lt;code dir=&quot;auto&quot;&gt;RegisterAuthor&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;SomeName&lt;/code&gt; becomes a concept for the author’s name, and the listing becomes the list of authors. &lt;code dir=&quot;auto&quot;&gt;RegisterAuthor&lt;/code&gt; takes the author’s id from the caller, as the Kotlin and Java templates do, instead of creating it in &lt;code dir=&quot;auto&quot;&gt;Handle()&lt;/code&gt;. Uniqueness within the organization is the part the template leaves out. That’s a Chronicle constraint, covered in &lt;a href=&quot;https://blog.cratis.io/rules-that-hold-when-you-write/&quot;&gt;&lt;em&gt;Rules that hold when you write&lt;/em&gt;&lt;/a&gt;, and the tenant comes from the edge, as &lt;a href=&quot;https://blog.cratis.io/tenancy-and-identity-end-to-end/&quot;&gt;&lt;em&gt;Tenancy and identity end to end&lt;/em&gt;&lt;/a&gt; explains.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;cratis-new-the-same-templates-from-the-cli&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;cratis new&lt;/code&gt;: the same templates from the CLI&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/cli/new/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;cratis new&lt;/code&gt;&lt;/a&gt; creates the same full-stack skeleton from the &lt;a href=&quot;https://cratis.io/templates/&quot;&gt;Cratis templates&lt;/a&gt;, in C# or on Spring Boot with Kotlin or Java, for the database you pick, with proxy generation configured:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--language&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;csharp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyStore&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyStore&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The first line starts a wizard that asks for the project, the language and the database. The second lists what’s available, and the third scaffolds directly. The CLI carries its own template engine, which reads the format &lt;code dir=&quot;auto&quot;&gt;dotnet new&lt;/code&gt; uses, so scaffolding doesn’t need a .NET SDK installed. Building the generated C# still does, and the CLI restores packages only when it finds a build toolchain.&lt;/p&gt;
&lt;p&gt;A few differences from &lt;code dir=&quot;auto&quot;&gt;dotnet new&lt;/code&gt; matter on the first run. &lt;code dir=&quot;auto&quot;&gt;--language&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;csharp&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;kotlin&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;java&lt;/code&gt;) is required when you scaffold directly. &lt;code dir=&quot;auto&quot;&gt;-o&lt;/code&gt; is the output directory here, while everywhere else in the CLI it selects the output format. &lt;code dir=&quot;auto&quot;&gt;--allow-scripts&lt;/code&gt; defaults to &lt;code dir=&quot;auto&quot;&gt;no&lt;/code&gt;, so pass &lt;code dir=&quot;auto&quot;&gt;yes&lt;/code&gt; to get the frontend install. An unknown option is an error that names the valid set, &lt;code dir=&quot;auto&quot;&gt;--dry-run&lt;/code&gt; reports what it would create and writes nothing, and &lt;code dir=&quot;auto&quot;&gt;--force&lt;/code&gt; lets it write into a folder that isn’t empty. &lt;code dir=&quot;auto&quot;&gt;--database&lt;/code&gt; takes &lt;code dir=&quot;auto&quot;&gt;mongodb&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;postgresql&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;mssql&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;sqlite&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When it’s done, &lt;code dir=&quot;auto&quot;&gt;cratis new&lt;/code&gt; synchronizes the project’s AI configuration, the same work &lt;code dir=&quot;auto&quot;&gt;cratis ai update&lt;/code&gt; does. Every template ships a &lt;code dir=&quot;auto&quot;&gt;.cratis/ai.json&lt;/code&gt; that selects the Cratis AI profiles, languages and assistant integrations, so after &lt;code dir=&quot;auto&quot;&gt;dotnet new&lt;/code&gt; you run &lt;code dir=&quot;auto&quot;&gt;cratis ai update&lt;/code&gt; yourself, and after &lt;code dir=&quot;auto&quot;&gt;cratis new&lt;/code&gt; there’s nothing left to run. &lt;a href=&quot;https://blog.cratis.io/ai-across-the-cratis-stack/&quot;&gt;&lt;em&gt;AI across the Cratis stack&lt;/em&gt;&lt;/a&gt; goes into what that installs, and &lt;a href=&quot;https://blog.cratis.io/the-cratis-cli/&quot;&gt;&lt;em&gt;The Cratis CLI, from diagnose to llm-context&lt;/em&gt;&lt;/a&gt; covers the rest of the CLI.&lt;/p&gt;
&lt;p&gt;The package holds four .NET templates. &lt;code dir=&quot;auto&quot;&gt;cratis&lt;/code&gt; is the web application above, and &lt;code dir=&quot;auto&quot;&gt;cratis-aspire&lt;/code&gt; is the same application orchestrated with .NET Aspire. A minimal AppHost that runs Chronicle for a project looks like this:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-0&quot; id=&quot;tab-2-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-1&quot; id=&quot;tab-2-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-2&quot; id=&quot;tab-2-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-3&quot; id=&quot;tab-2-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-4&quot; id=&quot;tab-2-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-0&quot; aria-labelledby=&quot;tab-2-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; builder &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;DistributedApplication&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;CreateBuilder&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; chronicle &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;AddCratisChronicle&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;AddContainer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;api&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;my-org/my-api&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WithReference&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Build&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Run&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-1&quot; aria-labelledby=&quot;tab-2-1&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;No Kotlin equivalent: Aspire hosting for Chronicle is a .NET package.&lt;/p&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-2&quot; aria-labelledby=&quot;tab-2-2&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;No Java equivalent: Aspire hosting for Chronicle is a .NET package.&lt;/p&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-3&quot; aria-labelledby=&quot;tab-2-3&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;No TypeScript equivalent: Aspire hosting for Chronicle is a .NET package.&lt;/p&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-4&quot; aria-labelledby=&quot;tab-2-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;No Elixir equivalent: Aspire hosting for Chronicle is a .NET package.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;The template’s AppHost also sets &lt;code dir=&quot;auto&quot;&gt;Cratis__Chronicle__ConnectionString&lt;/code&gt; on the backend, because the client reads &lt;code dir=&quot;auto&quot;&gt;Cratis:Chronicle:ConnectionString&lt;/code&gt;, not Aspire’s &lt;code dir=&quot;auto&quot;&gt;ConnectionStrings&lt;/code&gt;. &lt;code dir=&quot;auto&quot;&gt;cratis-chronicle-console&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;cratis-chronicle-web&lt;/code&gt; connect a console app and an ASP.NET Core app to Chronicle, for when you want the event store without Arc. The Kotlin and Java variants of &lt;code dir=&quot;auto&quot;&gt;cratis&lt;/code&gt; generate their proxies through the Arc Gradle plugin, and &lt;a href=&quot;https://blog.cratis.io/beyond-dotnet/&quot;&gt;&lt;em&gt;Beyond .NET&lt;/em&gt;&lt;/a&gt; covers how they differ from the .NET stack.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;one-feature-one-folder&quot;&gt;One feature, one folder&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Arc recommends a &lt;a href=&quot;https://cratis.io/arc/vertical-slices/&quot;&gt;vertical slice&lt;/a&gt;, with the command, the read model, the screen and the specs in one feature folder, so one change is one diff. Arc doesn’t enforce it. We still recommend it, because when a feature is spread across five layer folders, reviewing one change means opening all five. The C# template’s &lt;code dir=&quot;auto&quot;&gt;Registration/&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Listing/&lt;/code&gt; folders are already laid out that way.&lt;/p&gt;
&lt;p&gt;In the author feature, the registration slice keeps the command and its event, with the rule that names are unique, side by side:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-0&quot; id=&quot;tab-3-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-1&quot; id=&quot;tab-3-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-2&quot; id=&quot;tab-3-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-3&quot; id=&quot;tab-3-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-4&quot; id=&quot;tab-3-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-0&quot; aria-labelledby=&quot;tab-3-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;Authors/Registration/Registration.cs&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Command&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;&lt;span&gt; Id&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;&lt;span&gt; Name&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Handle&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Name&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;property&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Unique&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;UniqueAuthorName&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;&lt;span&gt; Name&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-1&quot; aria-labelledby=&quot;tab-3-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;Authors/Registration/Registration.kt&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@Command&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;@CommandKey&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; id: &lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; name: &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;(): &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;(name)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@EventType&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;@Unique&lt;/span&gt;&lt;span&gt;(id &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;UniqueAuthorName&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; name: &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-2&quot; aria-labelledby=&quot;tab-3-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;Java keeps one public type per file, all in the same folder:&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;Authors/Registration/RegisterAuthor.java&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Command&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;/span&gt;&lt;span&gt;CommandKey&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Authors/Registration/AuthorRegistered.java&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Unique&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;UniqueAuthorName&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-3&quot; aria-labelledby=&quot;tab-3-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;From Arc for TypeScript, an early source preview whose packages aren’t on npm yet:&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;Authors/Registration/Registration.ts&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;eventType&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;unique&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;UniqueAuthorName&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-4&quot; aria-labelledby=&quot;tab-3-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Arc for Elixir doesn’t exist; use the Chronicle Elixir client directly.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/one-feature-one-folder.BEN-WgLm_ZGAKXQ.webp&quot; alt=&quot;A diagram titled One feature, one folder, with two panels side by side. By layer: Controllers, Services, Data, Frontend and Tests folders, captioned A piece of the change in each. By feature: a Registration folder labeled Arc, holding Registration.cs, Author.cs, RegisterDialog.tsx and when_registering_author.cs, captioned Command, read model, screen and spec.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2000&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;One feature, one folder, one diff.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;samples-and-what-each-one-leaves-out&quot;&gt;Samples, and what each one leaves out&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The template is a starting point for your own application. The &lt;a href=&quot;https://cratis.io/samples/&quot;&gt;samples&lt;/a&gt; are for learning one idea at a time. A catalog file in the &lt;a href=&quot;https://github.com/Cratis/Samples&quot;&gt;Samples repository&lt;/a&gt; drives the roster on the site, and each entry lists its level, the products it uses, its prerequisites, the exact commands that verify it and what it doesn’t cover. The tracks run from getting started, event processing and full-stack CQRS to boundaries and isolation, operations, model-first work and full applications.&lt;/p&gt;
&lt;p&gt;What a sample leaves out is part of its design. &lt;em&gt;Chronicle Backend&lt;/em&gt; appends a fact and reads one event source’s history over HTTP, without projections or a frontend, and its specs run without external infrastructure. &lt;em&gt;Idea Loom&lt;/em&gt; shows generated contracts, model-bound commands, an observable query and a &lt;code dir=&quot;auto&quot;&gt;CommandDialog&lt;/code&gt; on Arc and React, and it deliberately doesn’t use Chronicle, keeping its current state in memory. &lt;em&gt;Chronicle Operations Diagnosis&lt;/em&gt; creates one failed partition on purpose, for you to find with the CLI and the Workbench. &lt;em&gt;Model-First Library&lt;/em&gt; compiles one &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file and runs its accepted and rejected examples through Stage. It needs locally built Screenplay and Stage checkouts, and it checks the model without running a live Chronicle command. &lt;em&gt;Library&lt;/em&gt; is the larger one, with lending and membership. &lt;em&gt;Full-stack author registration&lt;/em&gt; is the author feature from the hub as a sample: &lt;code dir=&quot;auto&quot;&gt;RegisterAuthor&lt;/code&gt; returns &lt;code dir=&quot;auto&quot;&gt;AuthorRegistered&lt;/code&gt;, a projection keeps an observable author list, and a React screen calls the generated proxies. It covers registration and listing only, so the uniqueness constraint isn’t in it, and it runs on the template’s development credentials.&lt;/p&gt;
&lt;p&gt;Neither the samples nor the templates are production templates. The Samples repository has no release tags, so there’s no sample version to match against your packages. If a sample doesn’t build against the version you have installed, report it in the Samples repository. One loop works for any of them: read the README, run the happy path, open the Workbench when Chronicle is involved, and change one thing.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;fundamentals-underneath-everything&quot;&gt;Fundamentals, underneath everything&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/supporting-cast.DxvvASPN_ZFdVgL.webp&quot; alt=&quot;A diagram titled The supporting cast around one slice. In the middle, a Registration folder holding RegisterAuthor, AuthorRegistered, the author list and a spec. Around it, five tools, each with one verb. Above, Narrator: browses the event log from VS Code, read only, and Lens: runs commands as a chosen user and tenant. On the left, Specifications: shapes the spec as when and should. On the right, Synopsis: reads the specs into one HTML page, without running them. Below, Fundamentals: wraps values as concepts and registers services by convention.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;1900&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Five smaller tools, each doing one thing around the feature folder.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/fundamentals/&quot;&gt;Fundamentals&lt;/a&gt; holds the shared building blocks beneath the Cratis stack. For .NET that means concepts, serialization, convention-based dependency injection and type discovery. For TypeScript it means GUIDs, serialization of derived types, property-path proxies and decorators. Chronicle, Arc and Components build on it, and you can use it on its own. Parity between the two isn’t a goal, because the environments offer different things.&lt;/p&gt;
&lt;p&gt;Most people meet it through a &lt;a href=&quot;https://cratis.io/fundamentals/csharp/concepts/&quot;&gt;concept&lt;/a&gt;. &lt;code dir=&quot;auto&quot;&gt;ConceptAs&amp;#x3C;T&gt;&lt;/code&gt; wraps a primitive so the compiler stops you passing an author’s ID where a book’s ID belongs, and a personal value such as a social security number also carries Chronicle’s &lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt; marker. In .NET, services are registered &lt;a href=&quot;https://cratis.io/fundamentals/csharp/dependency_injection/&quot;&gt;by convention&lt;/a&gt;:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-0&quot; id=&quot;tab-4-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-1&quot; id=&quot;tab-4-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-2&quot; id=&quot;tab-4-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-3&quot; id=&quot;tab-4-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-4&quot; id=&quot;tab-4-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-0&quot; aria-labelledby=&quot;tab-4-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;PII&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SocialSecurityNumber&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt; Value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ConceptAs&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-1&quot; aria-labelledby=&quot;tab-4-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@Pii&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SocialSecurityNumber&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; rawValue: &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;) : &lt;/span&gt;&lt;span&gt;ArcConceptAs&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&gt;, &lt;/span&gt;&lt;span&gt;ChronicleConceptAs&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;(): &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; rawValue&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; rawValue&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-2&quot; aria-labelledby=&quot;tab-4-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Pii&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SocialSecurityNumber&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&lt;span&gt; value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;implements&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ConceptAs&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;,&lt;/span&gt;&lt;span&gt; io.cratis.chronicle.concepts.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;ConceptAs&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;getValue&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; value&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-3&quot; aria-labelledby=&quot;tab-4-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;pii&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SocialSecurityNumber&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ConceptAs&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;valueType&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-4&quot; aria-labelledby=&quot;tab-4-4&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;defmodule&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SocialSecurityNumber&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Concept&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;pii&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-5-0&quot; id=&quot;tab-5-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-5-1&quot; id=&quot;tab-5-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-5-2&quot; id=&quot;tab-5-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-5-3&quot; id=&quot;tab-5-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-5-4&quot; id=&quot;tab-5-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-5-0&quot; aria-labelledby=&quot;tab-5-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;services&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;AddBindingsByConvention&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IUserDirectory&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;UserDirectory&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;IUserDirectory&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// Registered as Transient&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-5-1&quot; aria-labelledby=&quot;tab-5-1&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;No Kotlin equivalent: Arc for Kotlin runs on Spring Boot, which registers the services.&lt;/p&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-5-2&quot; aria-labelledby=&quot;tab-5-2&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;No Java equivalent: Arc for Kotlin runs on Spring Boot, which registers the services.&lt;/p&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-5-3&quot; aria-labelledby=&quot;tab-5-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;Arc for TypeScript registers services explicitly:&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;UserDirectory&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ArcApplication&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;createBuilder&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;services&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;addTransient&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;UserDirectory&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-5-4&quot; aria-labelledby=&quot;tab-5-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;No Elixir equivalent.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;A concept wraps exactly one value. The serializers and converters Fundamentals provides unwrap it to the primitive, so a property you add to a concept is ignored and lost. C# gives you an implicit conversion from the concept to the primitive but not back, so you add that one yourself. &lt;code dir=&quot;auto&quot;&gt;AddBindingsByConvention()&lt;/code&gt; pairs &lt;code dir=&quot;auto&quot;&gt;IUserDirectory&lt;/code&gt; with &lt;code dir=&quot;auto&quot;&gt;UserDirectory&lt;/code&gt; only when both sit in the same namespace and the interface has one implementation. Registrations are transient unless the class says &lt;code dir=&quot;auto&quot;&gt;[Singleton]&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;[IgnoreConvention]&lt;/code&gt; opts a type out. &lt;a href=&quot;https://blog.cratis.io/arc-commands-and-queries/&quot;&gt;&lt;em&gt;Arc: commands and queries without the plumbing&lt;/em&gt;&lt;/a&gt; shows what Arc does with concepts, from validation to the TypeScript types.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-shape-every-spec-takes&quot;&gt;The shape every spec takes&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/specifications/&quot;&gt;Specifications&lt;/a&gt; brings specification by example to xUnit and NUnit, in the style of Machine.Specifications. &lt;code dir=&quot;auto&quot;&gt;Establish()&lt;/code&gt; is the given, &lt;code dir=&quot;auto&quot;&gt;Because()&lt;/code&gt; the when, each &lt;code dir=&quot;auto&quot;&gt;[Fact]&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;[Test]&lt;/code&gt; method a then, and &lt;code dir=&quot;auto&quot;&gt;Destroy()&lt;/code&gt; the cleanup. The lifecycle methods are found by convention, take no arguments, can return &lt;code dir=&quot;auto&quot;&gt;void&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;Task&lt;/code&gt;, and run across the inheritance chain, so a context you reuse lives in a &lt;code dir=&quot;auto&quot;&gt;given/&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;The folder and class names carry the meaning. A &lt;code dir=&quot;auto&quot;&gt;for_&amp;#x3C;unit&gt;&lt;/code&gt; folder, a &lt;code dir=&quot;auto&quot;&gt;when_&amp;#x3C;behavior&gt;&lt;/code&gt; class and &lt;code dir=&quot;auto&quot;&gt;should_&amp;#x3C;fact&gt;&lt;/code&gt; methods read as a sentence, and a spec at &lt;code dir=&quot;auto&quot;&gt;for_RegisterAuthor/when_registering/and_the_name_is_taken.cs&lt;/code&gt; says what it checks before you open it. The specs run on the standard test runners, so IDE, editor and CI tooling work unchanged. The packages are &lt;code dir=&quot;auto&quot;&gt;Cratis.Specifications&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Cratis.Specifications.XUnit&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Cratis.Specifications.NUnit&lt;/code&gt;, and the naming style triggers compiler warnings you switch off in the test projects. How the shape combines with Arc’s command scenarios and Chronicle’s in-memory tests is in &lt;a href=&quot;https://blog.cratis.io/testing-with-arc-and-chronicle/&quot;&gt;&lt;em&gt;Testing with Arc and Chronicle&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;reading-the-specs-back-with-synopsis&quot;&gt;Reading the specs back with Synopsis&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Cratis/Synopsis&quot;&gt;Synopsis&lt;/a&gt; reads the executable examples a repository already has and writes them out as one searchable HTML page, organized by module and feature, with every statement linked back to the code it came from. It turns recognized specification shapes in C#, JavaScript, TypeScript, Gherkin and Screenplay into that page. On the C# side that covers Cratis Specifications and six other frameworks, xUnit, NUnit and MSTest among them, and on the JavaScript side runners from Vitest and Jest to Playwright.&lt;/p&gt;
&lt;p&gt;A spec written with Cratis Specifications:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-6-0&quot; id=&quot;tab-6-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-6-1&quot; id=&quot;tab-6-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-6-2&quot; id=&quot;tab-6-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-6-3&quot; id=&quot;tab-6-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-6-4&quot; id=&quot;tab-6-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-6-0&quot; aria-labelledby=&quot;tab-6-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;when_borrowing_an_available_book&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;given&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;a_registered_member&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Because&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_receipt&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_checkout&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Borrow&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_book&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;_member&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Fact&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;should_confirm_the_loan&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_receipt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Confirmed&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ShouldBeTrue&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-6-1&quot; aria-labelledby=&quot;tab-6-1&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;No Kotlin equivalent: Cratis Specifications runs on xUnit and NUnit.&lt;/p&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-6-2&quot; aria-labelledby=&quot;tab-6-2&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;No Java equivalent: Cratis Specifications runs on xUnit and NUnit.&lt;/p&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-6-3&quot; aria-labelledby=&quot;tab-6-3&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;No TypeScript equivalent: Cratis Specifications runs on xUnit and NUnit.&lt;/p&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-6-4&quot; aria-labelledby=&quot;tab-6-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;No Elixir equivalent: Cratis Specifications runs on xUnit and NUnit.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;becomes a card on the page:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;FOR  Checkout                           Backend - C#&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;     &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Borrowing an available book&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;GIVEN  A registered member&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;WHEN   Borrowing an available book&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;THEN   Confirm the loan&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;It works from static analysis. C# goes through Roslyn syntax trees without a semantic compilation, JavaScript and TypeScript through a scanner, and it never loads or runs code from the repository it reads, so there’s no restore and no test run. That also sets its limit. The page says what the specs describe, not whether they pass, and it doesn’t judge whether they’re good specs. Install it with &lt;code dir=&quot;auto&quot;&gt;dotnet tool install --global Cratis.Synopsis.Tool&lt;/code&gt; and run &lt;code dir=&quot;auto&quot;&gt;synopsis . --open&lt;/code&gt;. The output is one file with full-text search and no server or external assets, and &lt;code dir=&quot;auto&quot;&gt;--format both&lt;/code&gt; writes a JSON model beside it. Module and feature names come from folder conventions, and a &lt;code dir=&quot;auto&quot;&gt;synopsis.json&lt;/code&gt; handles other layouts.&lt;/p&gt;
&lt;p&gt;Synopsis is a standalone tool and library today. There’s no &lt;code dir=&quot;auto&quot;&gt;cratis synopsis&lt;/code&gt; command and no Studio view of it yet. Its README is the documentation.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;narrator-and-lens-two-windows-onto-a-running-system&quot;&gt;Narrator and Lens: two windows onto a running system&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Workbench ships with Chronicle, and the same running system is also visible from a terminal with &lt;code dir=&quot;auto&quot;&gt;cratis chronicle workbench&lt;/code&gt; and from VS Code with &lt;a href=&quot;https://cratis.io/tools/vscode-extension/&quot;&gt;Narrator&lt;/a&gt;, which we still treat as experimental.&lt;/p&gt;
&lt;p&gt;Narrator is a VS Code extension for browsing Chronicle artifacts and event history. From the activity bar you browse event stores and namespaces down to event types, read models, observers and projections, and page through the event sequence with each event’s sequence number, type, event source, time and payload. You can open an event type’s or a read model’s schema, or a projection’s declaration, and switch between servers. It reads the same &lt;code dir=&quot;auto&quot;&gt;~/.cratis/config.json&lt;/code&gt; the CLI uses and picks up changes to it:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;activeContext&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;contexts&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;server&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;chronicle://localhost:35000&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;It reads the log and the schemas and changes nothing in the store. That config file can hold client credentials and an access token in plain text, so treat it like any other file with a secret in it. &lt;a href=&quot;https://blog.cratis.io/chronicle-workbench/&quot;&gt;&lt;em&gt;Chronicle Workbench, screen by screen&lt;/em&gt;&lt;/a&gt; covers the browser tool.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/tools/lens/&quot;&gt;Lens&lt;/a&gt; is a browser extension for the Arc side, and its current release is 0.0.1. It detects a running Arc application in the current tab, lists its commands and queries by namespace, runs them from a form built from their schema and shows diagnostics for observable queries. It also switches the active user and tenant, which it adds as request headers when the application calls its backend. That makes it a development tool for exercising an Arc application as different users and tenants. It needs an Arc application it can detect, and it doesn’t replace testing your authorization.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;cratisio-and-where-each-page-comes-from&quot;&gt;cratis.io, and where each page comes from&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/&quot;&gt;cratis.io&lt;/a&gt; is a Starlight site built from many repositories. Most pages are written in the owning product’s repository, in its &lt;code dir=&quot;auto&quot;&gt;Documentation/&lt;/code&gt; folder, and the site build copies them in. The edit link on a page therefore points at the product, where a fix belongs. Site-level pages, such as the front page, the scenarios, the AI section, the samples and the tools, live in the Documentation repository. A few tools, Synopsis among them, have no page on the site yet.&lt;/p&gt;
&lt;p&gt;A newcomer usually wants one of a handful of pages. &lt;a href=&quot;https://cratis.io/cratis-stack/&quot;&gt;The Cratis stack&lt;/a&gt; shows how the products fit, &lt;a href=&quot;https://cratis.io/build-a-full-app/&quot;&gt;Build a full app&lt;/a&gt; is a guided journey through Arc, Chronicle, generated contracts and React, and &lt;a href=&quot;https://cratis.io/learning-paths/&quot;&gt;learning paths&lt;/a&gt; order the rest. &lt;a href=&quot;https://cratis.io/whats-new/&quot;&gt;What’s new&lt;/a&gt; and &lt;a href=&quot;https://cratis.io/compatibility/&quot;&gt;compatibility&lt;/a&gt; matter once you’ve picked versions. Parts of the model-first layer are experimental and in early development.&lt;/p&gt;
&lt;p&gt;For an assistant, the site publishes &lt;a href=&quot;https://cratis.io/llms.txt&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;llms.txt&lt;/code&gt;&lt;/a&gt;, &lt;code dir=&quot;auto&quot;&gt;llms-full.txt&lt;/code&gt; and an &lt;code dir=&quot;auto&quot;&gt;llms.txt&lt;/code&gt; for each product. The bulk archives aren’t the best default context for one question.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-to-start&quot;&gt;Where to start&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The whole life cycle, by the moment you’re in:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;When&lt;/th&gt;
&lt;th&gt;What you reach for&lt;/th&gt;
&lt;th&gt;What you stop writing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model it&lt;/td&gt;
&lt;td&gt;Cratis Studio, event modeling, Screenplay, Screenplay MCP&lt;/td&gt;
&lt;td&gt;A design document that drifts from the code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Start it&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;cratis new&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;dotnet new cratis&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;cratis ai install&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Project wiring and proxy setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build it&lt;/td&gt;
&lt;td&gt;AuthProxy, Ante, Arc for .NET, JVM or TypeScript, Components, Chronicle&lt;/td&gt;
&lt;td&gt;Controllers, DTOs, fetch wrappers, uniqueness lookups before the write&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test it&lt;/td&gt;
&lt;td&gt;Cratis Specifications, Arc’s &lt;code dir=&quot;auto&quot;&gt;CommandScenario&lt;/code&gt;, Chronicle’s in-memory scenarios, Stage’s specification runner&lt;/td&gt;
&lt;td&gt;A fake event log, and a running server for every spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protect it&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;[NotAudited]&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;[Encrypted]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per-field encryption code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run it&lt;/td&gt;
&lt;td&gt;A namespace per tenant, outbox and inbox subscriptions&lt;/td&gt;
&lt;td&gt;Tenant filters in every event query, relays between stores&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debug it&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;cratis chronicle&lt;/code&gt;, the terminal workbench, Workbench, Narrator&lt;/td&gt;
&lt;td&gt;Ad-hoc queries against the storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evolve it&lt;/td&gt;
&lt;td&gt;Generations, replay, revision, redaction&lt;/td&gt;
&lt;td&gt;One-off scripts that rewrite the store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automate it&lt;/td&gt;
&lt;td&gt;Cratis Direct, the AI corpus and its hooks, the CLI’s command catalog, Chronicle MCP, Screenplay MCP&lt;/td&gt;
&lt;td&gt;Your conventions, retyped into every assistant&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Ante, in the Build it row, is the separate invitation and onboarding lobby for users who have signed in but belong to no tenant yet, from &lt;a href=&quot;https://blog.cratis.io/tenancy-and-identity-end-to-end/&quot;&gt;&lt;em&gt;Tenancy and identity end to end&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Not everything in that table is at the same stage. Stage, Scene and Prologue are experimental, and so are Narrator and Prompter, which answers questions about the docs in Discord, so their syntax and behavior can still change. Arc for TypeScript is an early preview, available as source only. The Cratis AI corpus and its hooks are in preview. &lt;a href=&quot;https://cratis.studio&quot;&gt;Cratis Studio&lt;/a&gt; is live and in beta. &lt;a href=&quot;https://cratis.direct&quot;&gt;Cratis Direct&lt;/a&gt; is a control plane for agentic engineering. We run our own engineering on it today. For everyone else it’s coming soon at cratis.direct.&lt;/p&gt;
&lt;p&gt;Starting fresh, scaffold with &lt;code dir=&quot;auto&quot;&gt;cratis new&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;dotnet new cratis&lt;/code&gt;, then follow &lt;a href=&quot;https://cratis.io/build-a-full-app/&quot;&gt;Build a full app&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To learn by example, start with &lt;em&gt;Full-stack author registration&lt;/em&gt;, or pick one of the &lt;a href=&quot;https://cratis.io/samples/&quot;&gt;samples&lt;/a&gt; that matches what you want to understand, and read what it leaves out first.&lt;/p&gt;
&lt;p&gt;For the event history on its own, follow &lt;a href=&quot;https://cratis.io/chronicle/get-started/&quot;&gt;Get started with Chronicle&lt;/a&gt;, append a few events and open the &lt;a href=&quot;https://cratis.io/chronicle/workbench/&quot;&gt;Workbench&lt;/a&gt;. To operate it, &lt;code dir=&quot;auto&quot;&gt;brew tap cratis/cratis &amp;#x26;&amp;#x26; brew install cratis&lt;/code&gt;, then run &lt;a href=&quot;https://cratis.io/cli/chronicle/diagnose/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;cratis chronicle diagnose&lt;/code&gt;&lt;/a&gt;, and keep &lt;code dir=&quot;auto&quot;&gt;retry-partition&lt;/code&gt; ahead of &lt;code dir=&quot;auto&quot;&gt;replay&lt;/code&gt; in your runbook.&lt;/p&gt;
&lt;p&gt;To model first, sketch one slice with &lt;a href=&quot;https://cratis.io/event-modeling/&quot;&gt;event modeling&lt;/a&gt;, write it as a &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file and run &lt;code dir=&quot;auto&quot;&gt;cratis screenplay validate&lt;/code&gt; on the folder. At the edge, put &lt;a href=&quot;https://cratis.io/authproxy/&quot;&gt;AuthProxy&lt;/a&gt; in front of an Arc app and set Arc’s tenant header to &lt;code dir=&quot;auto&quot;&gt;Tenant-ID&lt;/code&gt;. For personal data, mark one value’s type &lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt;, then read &lt;a href=&quot;https://blog.cratis.io/the-log-remembers-pii-crypto-shredding-in-chronicle/&quot;&gt;The log remembers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With an assistant, run &lt;a href=&quot;https://cratis.io/ai/getting-started/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;cratis ai install&lt;/code&gt;&lt;/a&gt; for the harnesses, profiles and languages you use, keep &lt;code dir=&quot;auto&quot;&gt;cratis screenplay validate&lt;/code&gt; and your build in the assistant’s loop, and review its work as a proposal, a model, a spec and the event log.&lt;/p&gt;
&lt;p&gt;Earlier posts on the blog go deeper on single decisions. &lt;a href=&quot;https://blog.cratis.io/choosing-an-event-sourcing-stack-for-dotnet/&quot;&gt;Choosing an event sourcing stack for .NET&lt;/a&gt; is about whether event sourcing fits your stack, and &lt;a href=&quot;https://blog.cratis.io/event-sourcing-in-any-language/&quot;&gt;Event sourcing in any language&lt;/a&gt; about how the clients share one kernel. &lt;a href=&quot;https://blog.cratis.io/event-sourcing-in-dotnet-with-chronicle/&quot;&gt;Event sourcing in .NET with Chronicle&lt;/a&gt; builds a runnable first slice, and &lt;a href=&quot;https://blog.cratis.io/from-zero-to-first-projection-with-an-ai-assistant/&quot;&gt;From zero to first projection with an AI assistant&lt;/a&gt; has an assistant do the same.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-series&quot;&gt;The series&lt;/h2&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/events-are-facts/&quot;&gt;Events are facts&lt;/a&gt;. A message is gone once it’s sent, and a recorded fact can still feed next year’s view.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/inside-chronicle/&quot;&gt;Inside Chronicle&lt;/a&gt;. What the kernel does during an append, and why no projection has run yet when the call returns.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/from-event-to-read-model/&quot;&gt;From event to read model&lt;/a&gt;. One attribute turns &lt;code dir=&quot;auto&quot;&gt;AuthorRegistered&lt;/code&gt; into a read model, and a reducer takes over when attributes can’t say it.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/reacting-to-facts/&quot;&gt;Reacting to facts&lt;/a&gt;. Reactors, webhooks and outbox-to-inbox subscriptions, each of which has to cope with running twice.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/rules-that-hold-when-you-write/&quot;&gt;Rules that hold when you write&lt;/a&gt;. Constraints, concurrency scopes, decision reads and aggregates, and which one we’d reach for first.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/arc-commands-and-queries/&quot;&gt;Arc: commands and queries without the plumbing&lt;/a&gt;. Arc gives a record with a &lt;code dir=&quot;auto&quot;&gt;Handle&lt;/code&gt; method its route, validation, authorization and the TypeScript your frontend compiles against.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/validation-all-the-way-down/&quot;&gt;Validation all the way down&lt;/a&gt;. Every place a rule can live, from the rules copied into the browser to Chronicle’s append, what each layer can’t guarantee, and which rejections find their way back to a form field.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/live-uis-with-arc/&quot;&gt;Live UIs&lt;/a&gt;. Queries that push updates to React, and forms that keep their button disabled until the backend’s shared rules pass.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/cratis-components-the-react-layer/&quot;&gt;Cratis Components, the React layer&lt;/a&gt;. The author list and the register dialog built with Components 4, how a field binds to a generated command, how a taken name gets back to the Name field, and where the library stops.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/tenancy-and-identity-end-to-end/&quot;&gt;Tenancy and identity end to end&lt;/a&gt;. A tenant picked at AuthProxy travels as a header Arc reads, and Arc’s Chronicle integration turns it into a Chronicle namespace. Membership is still yours to check.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/testing-with-arc-and-chronicle/&quot;&gt;Testing with Arc and Chronicle&lt;/a&gt;. The ladder from a direct method call to a real Chronicle, and the ways a green spec proves nothing.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/beyond-dotnet/&quot;&gt;Beyond .NET&lt;/a&gt;. Arc and Chronicle in other languages, four storage providers, and where each still differs from .NET.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/personal-data-in-an-event-log/&quot;&gt;Personal data in an event log&lt;/a&gt;. Making a person’s data unreadable in an append-only log, and exactly where that stops.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/four-changes-in-year-two/&quot;&gt;Year two&lt;/a&gt;. Why a new event shape, a new view, a correction and a stopped processor are four kinds of change, none of them a script against the store.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/arc-gotchas/&quot;&gt;Arc gotchas: where a convention decides for you&lt;/a&gt;. Validation that runs in two places, a proxy folder that gets cleared, and names that do the mapping for you until they don’t.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/chronicle-workbench/&quot;&gt;Chronicle Workbench, screen by screen&lt;/a&gt;. The browser tool that ships with Chronicle.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/the-cratis-cli/&quot;&gt;The Cratis CLI, from diagnose to llm-context&lt;/a&gt;. From &lt;code dir=&quot;auto&quot;&gt;diagnose&lt;/code&gt; to a full-screen terminal workbench, and a command catalog that tells an agent which commands change the server.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/running-chronicle-in-production/&quot;&gt;Running Chronicle and a Cratis application in production&lt;/a&gt;. What the production image won’t start without, what a client trusts until you tell it otherwise, where a trace stops today, and the order a restore has to follow.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/chronicle-jobs-scale-and-performance/&quot;&gt;Chronicle jobs, scale and performance&lt;/a&gt;. Replays you can stop and resume, and the clustering default that, with two servers, quietly gives you two clusters.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/screenplay-an-event-model-you-can-compile/&quot;&gt;Screenplay: an event model you can compile&lt;/a&gt;. An event model written as text that a compiler can check in CI before the application has ever started.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/stage-and-scene-after-a-model-compiles/&quot;&gt;Stage and Scene: what happens after a model compiles&lt;/a&gt;. A model rendered into an Arc and Chronicle application by a renderer that refuses what it can’t express.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/prologue-a-first-model-of-an-existing-system/&quot;&gt;Prologue: a first model of the system you already have&lt;/a&gt;. A first model drafted from a running system that nobody ever modeled.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/cratis-studio-event-modeling-together/&quot;&gt;Cratis Studio: one event model for the whole team&lt;/a&gt;. A team and its AI agents on one event model, and a slice’s code generated into your repository.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/cratis-direct-agentic-development-life-cycle/&quot;&gt;Cratis Direct and our agentic development life cycle&lt;/a&gt;. How a person admits agent work, what each gate records and who merges.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cratis.io/ai-across-the-cratis-stack/&quot;&gt;AI across the Cratis stack&lt;/a&gt;. The AI corpus, its hooks and the MCP servers, what each lets an assistant read or change, and the checks that run whether it listened or not.&lt;/li&gt;
&lt;li&gt;Getting started with Cratis, and the tools around it. &lt;code dir=&quot;auto&quot;&gt;cratis new&lt;/code&gt;, the samples, and the supporting pieces, from Fundamentals and Specifications to Synopsis and Narrator.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The hub, &lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt;, follows the author feature through all of them once, briefly.&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href=&quot;https://blog.cratis.io/ai-across-the-cratis-stack/&quot;&gt;Part 25 — AI across the Cratis stack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next: back to the start of the series, &lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>templates</category><category>samples</category><category>fundamentals</category><category>specifications</category><category>synopsis</category><category>narrator</category></item><item><title>From script to stage · Part 25: AI across the Cratis stack</title><link>https://blog.cratis.io/ai-across-the-cratis-stack/</link><guid isPermaLink="true">https://blog.cratis.io/ai-across-the-cratis-stack/</guid><description>Where an assistant meets Cratis: the AI corpus and its hooks, the CLI&apos;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.</description><pubDate>Wed, 30 Sep 2026 09:25:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt; · Part 25 of 26&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In Claude Code and Pi, the Cratis store-mutation guard can stop an assistant’s &lt;code dir=&quot;auto&quot;&gt;cratis chronicle observers replay&lt;/code&gt; before the shell runs it, even with &lt;code dir=&quot;auto&quot;&gt;--yes&lt;/code&gt;. It reads the command text, so a call hidden in a script file or made through MCP is outside what it sees.&lt;/p&gt;
&lt;p&gt;That guard is one of seven places an assistant meets the Cratis stack. The others run from the corpus in your repository to an MCP server on a running Chronicle. Most of them let an assistant read something, and a few let it propose or change something. The corpus states the principle behind all of them in one of its rules: “A tool grant is not authority. A configured token, an installed CLI, a writable branch, or an MCP server in the session says only that the action is mechanically possible.”&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/ai-six-touchpoints.rJxK3Zln_1z8RCI.webp&quot; alt=&quot;A diagram titled Seven places an assistant meets Cratis, drawn as four stages from top to bottom, joined by arrows. Build: AI corpus, cratis ai install; Hooks, run inside the assistant’s tool. Model: Screenplay MCP, cratis screenplay mcp. Run and debug: CLI catalog, cratis llm-context; Chronicle MCP, connects to a running Chronicle. Learn: Prompter, in Discord; llms.txt, on cratis.io. A band along the bottom reads A tool grant is not authority.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Seven touchpoints, from the repository to the running store.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-spec-the-compiler-reads&quot;&gt;A spec the compiler reads&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.cratis.io/cratis-direct-agentic-development-life-cycle/&quot;&gt;&lt;em&gt;Cratis Direct and our agentic development life cycle&lt;/em&gt;&lt;/a&gt; covers deciding what an agent works on and checking what comes back. The event model gives that work an intent a compiler can read.&lt;/p&gt;
&lt;p&gt;The most useful critique we’ve read comes from the spec-driven development side. In her &lt;a href=&quot;https://martinfowler.com/articles/exploring-gen-ai/sdd-3-tools.html&quot;&gt;look at three spec-driven tools&lt;/a&gt; on martinfowler.com, Birgitta Böckeler separates three levels. Spec-first writes a spec to guide one change. Spec-anchored keeps the spec and evolves it with the feature. Spec-as-source makes the spec the only thing people edit. She finds the markdown these tools produce can be harder to review than the code, and she wonders whether spec-as-source, and even spec-anchored, could end up with the downsides of both model-driven development and language models: inflexibility and non-determinism.&lt;/p&gt;
&lt;p&gt;We sit at spec-anchored, with a spec a compiler reads. The event model is the spec and the &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file is its text. Its commands, events and projections are the concepts Arc and Chronicle run, so the spec and the running system share one vocabulary. For the narrow part Stage can render, it moves a step toward spec-as-source. A textual model with a code generator is the model-driven shape she warns about. The renderer is deterministic, so that step adds no unpredictability of its own, but the rigidity risk does apply to us, and it’s why hand-written code has its own place.&lt;/p&gt;
&lt;p&gt;For the narrow backend vertical Stage supports, &lt;code dir=&quot;auto&quot;&gt;cratis render&lt;/code&gt; writes the code. Code written by hand, by a person or an agent, belongs in &lt;code dir=&quot;auto&quot;&gt;Customizations/&lt;/code&gt;, and an edit to a generated file shows up as a refused render instead of a silent overwrite, unless someone passes &lt;code dir=&quot;auto&quot;&gt;--force&lt;/code&gt;. &lt;a href=&quot;https://blog.cratis.io/stage-and-scene-after-a-model-compiles/&quot;&gt;&lt;em&gt;Stage and Scene&lt;/em&gt;&lt;/a&gt; goes through it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;guidance-in-the-repository&quot;&gt;Guidance in the repository&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The &lt;a href=&quot;https://cratis.io/ai/&quot;&gt;Cratis AI corpus&lt;/a&gt; is a free, MIT-licensed set of rules, skills, agents and prompts for building with Cratis, and it’s still in preview. It lives in one folder, &lt;code dir=&quot;auto&quot;&gt;.cratis/ai/&lt;/code&gt;, and each supported assistant gets a thin adapter that points back to it, so the guidance is installed once instead of being copied into each tool’s instructions. &lt;a href=&quot;https://cratis.io/ai/getting-started/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;cratis ai install&lt;/code&gt;&lt;/a&gt; puts it into your repository:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ai&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--harnesses&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;claude,codex,copilot,cursor,opencode,pi&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--profiles&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cratis/application/csharp,cratis/documentation&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--languages&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;csharp,typescript&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ai&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;It records the selection in &lt;code dir=&quot;auto&quot;&gt;.cratis/ai.json&lt;/code&gt; and every file with its hash in &lt;code dir=&quot;auto&quot;&gt;.cratis/ai.manifest.json&lt;/code&gt;, refuses to overwrite files you own, and &lt;code dir=&quot;auto&quot;&gt;cratis ai status&lt;/code&gt; tells you when a managed file was edited locally. &lt;a href=&quot;https://cratis.io/ai/profiles/&quot;&gt;Profiles&lt;/a&gt; choose the scope, such as a C# application or documentation, and rules are scoped by language, so only the languages you list install theirs. In a terminal it asks for the harnesses, profiles and languages, and in a script or an agent’s own terminal you pass them as above. The Cratis templates preselect it. &lt;code dir=&quot;auto&quot;&gt;cratis new&lt;/code&gt; runs the sync itself, and &lt;code dir=&quot;auto&quot;&gt;dotnet new&lt;/code&gt; prints a reminder to run &lt;code dir=&quot;auto&quot;&gt;cratis ai update&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That gives an assistant context about how Cratis is meant to be used, with dozens of skills for commands, projections, reactors, concepts and specs. It doesn’t give it access to anything, and it doesn’t make its output correct. The corpus’s own checks are static and don’t run anything through a model. The CLI installs the corpus from its current main unless you give it a local source.&lt;/p&gt;
&lt;p&gt;An assistant that hasn’t read your conventions guesses at your framework, and the result is plausible code that’s subtly wrong. Each layer of Cratis also gives an assistant something concrete to read so it doesn’t have to guess. AuthProxy’s configuration is declarative JSON, the generated TypeScript proxies are typed contracts the frontend compiles against, registered event types have schemas it can look up, and a &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file is text a compiler checks.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/ai-one-corpus-six-adapters.CRzazE71_Z1Vb4NQ.webp&quot; alt=&quot;A diagram titled One corpus, six adapters. On the left, a folder card, .cratis/ai: rules, skills, agents, prompts and hooks, installed once with cratis ai install, recorded in ai.json and ai.manifest.json. Six arrows lead to six assistant rows on the right: Claude Code, Codex, Copilot, Cursor, OpenCode and Pi. The Claude Code and Pi rows carry a tag reading hooks wired. A band at the bottom reads Guidance, not access.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;One folder of guidance, and an adapter for each assistant that points back to it.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;checks-that-run-whether-the-agent-listened-or-not&quot;&gt;Checks that run whether the agent listened or not&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Böckeler also notes that agents often ignore the instructions they’re given, and asks whether all those files and checklists give a “false sense of control”. A rules file is advice. What holds is a check that runs either way, and the author feature meets several:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;cratis screenplay validate&lt;/code&gt; rejects a broken model before any code exists, and the Stage specification runner checks the model’s Given/When/Then examples against the model.&lt;/li&gt;
&lt;li&gt;The compiler and the generated TypeScript proxies turn a renamed field into a compile error on both sides.&lt;/li&gt;
&lt;li&gt;Arc and Chronicle’s .NET client ship &lt;a href=&quot;https://cratis.io/code-analysis/&quot;&gt;Roslyn analyzers&lt;/a&gt; that report some misuses of their APIs as build diagnostics. They aren’t specific to agents, and they treat an agent’s code like anyone else’s.&lt;/li&gt;
&lt;li&gt;The corpus ships &lt;a href=&quot;https://cratis.io/ai/harnesses/&quot;&gt;hooks&lt;/a&gt; that run inside the assistant’s own tool.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The hooks are the part written with agents in mind. Each one runs on an event in the assistant’s tool:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Pattern pass   PostToolUse on a write   appends a one-line reminder to context, never blocks&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Hard block     PreToolUse on a write    exits 2 - the write does not happen&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Hard block     PreToolUse on Bash       exits 2 - the store-changing cratis chronicle command does not run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Quality gate   Stop (Claude)            exits 2 - the turn does not end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The write guard blocks edits to a file whose header marks it as Cratis-generated output, such as the proxies Arc writes, and to package-management files, lock files and &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; files. &lt;a href=&quot;https://blog.cratis.io/arc-gotchas/&quot;&gt;&lt;em&gt;Arc gotchas&lt;/em&gt;&lt;/a&gt; explains why the generated files matter.&lt;/p&gt;
&lt;p&gt;Take the store-mutation guard. Say an assistant working on the author feature decides the &lt;code dir=&quot;auto&quot;&gt;Authors&lt;/code&gt; read model is stale and reaches for a replay:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;observers&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;replay&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;observer-id&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--yes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;With &lt;code dir=&quot;auto&quot;&gt;--yes&lt;/code&gt;, the CLI doesn’t stop to ask first. In Claude Code and Pi, the hook runs before the shell does. It finds every &lt;code dir=&quot;auto&quot;&gt;cratis&lt;/code&gt; in command position, including after &lt;code dir=&quot;auto&quot;&gt;&amp;#x26;&amp;#x26;&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;|&lt;/code&gt;, inside &lt;code dir=&quot;auto&quot;&gt;$( )&lt;/code&gt;, behind wrappers such as &lt;code dir=&quot;auto&quot;&gt;env&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;sudo&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;timeout&lt;/code&gt;, and in the script of &lt;code dir=&quot;auto&quot;&gt;bash -c&lt;/code&gt;, and checks each &lt;code dir=&quot;auto&quot;&gt;cratis chronicle&lt;/code&gt; command against a read-only allowlist. &lt;code dir=&quot;auto&quot;&gt;observers replay&lt;/code&gt; is on its list of known mutations, so the hook exits 2 and the command never runs. A command on neither list, including one a newer CLI adds, is blocked as unknown, so the allowlist fails closed.&lt;/p&gt;
&lt;p&gt;The block message is written for the agent. It lists each refused command path and what it changes, tells the agent not to retry or work around the guard, and asks it to report the exact command, the target context or server, what it changes and why, and to ask the user. From there the replay is a person’s decision. If they want it, they set &lt;code dir=&quot;auto&quot;&gt;CRATIS_HOOKS_ALLOW_STORE_MUTATIONS=1&lt;/code&gt; in the environment the assistant was started from. The hook reads its own environment, so an agent that writes the assignment into the command changes nothing.&lt;/p&gt;
&lt;p&gt;The guard is there to stop an agent reaching for &lt;code dir=&quot;auto&quot;&gt;--yes&lt;/code&gt;, and it isn’t a sandbox. It reads the command text, so &lt;code dir=&quot;auto&quot;&gt;cratis&lt;/code&gt; reached through a variable, an alias or a script file is invisible to it, and it doesn’t see MCP tool calls.&lt;/p&gt;
&lt;p&gt;In Claude Code, when the assistant tries to finish and relevant files have changed, a quality gate runs one build and test pass, and a failure goes back to the assistant instead of letting it stop. In Pi the gate doesn’t run on its own. It’s a tool called at a verification checkpoint, it stops after 300 seconds by default, and a gate that times out or is cancelled reports that nothing was verified, never a pass. The gates are data a repository can override. Today the hooks are wired for Claude Code and Pi, and other assistants get the rules and skills without them.&lt;/p&gt;
&lt;p&gt;None of this proves the author feature does what was asked. A green build says the code has the right shape, and a spec checks one example of the behavior, as &lt;a href=&quot;https://blog.cratis.io/testing-with-arc-and-chronicle/&quot;&gt;&lt;em&gt;Testing with Arc and Chronicle&lt;/em&gt;&lt;/a&gt; goes into.&lt;/p&gt;
&lt;p&gt;The CLI catalog is how an assistant learns which commands change state; &lt;a href=&quot;https://blog.cratis.io/the-cratis-cli/&quot;&gt;&lt;em&gt;The Cratis CLI, from diagnose to llm-context&lt;/em&gt;&lt;/a&gt; covers effect labels, confirmation and the fields stable enough for scripts.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;chronicle-mcp-an-assistant-at-a-running-store&quot;&gt;Chronicle MCP: an assistant at a running store&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/chronicle-mcp/&quot;&gt;Chronicle MCP&lt;/a&gt; connects an assistant to a running Chronicle server, whatever client language the application uses. It ships as a container, and &lt;a href=&quot;https://cratis.io/chronicle-mcp/getting-started/&quot;&gt;an editor can start it&lt;/a&gt; like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;servers&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;stdio&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;-i&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;--rm&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;-eCratis__Chronicle__Mcp__ConnectionString=chronicle://host.docker.internal:35000&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;cratis/chronicle-mcp&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Configuration is optional. It takes explicit options first, then &lt;code dir=&quot;auto&quot;&gt;CHRONICLE_CONNECTION_STRING&lt;/code&gt;, then the active CLI context, and falls back to development defaults, which means the development client ID and secret when no credentials are set. Don’t rely on those.&lt;/p&gt;
&lt;p&gt;It has two sets of tools. The operate side reads what the server holds, from event types and observers to failed partitions, events and read-model instances. The design-time side describes the system, lists event types that nothing consumes and &lt;a href=&quot;https://cratis.io/chronicle-mcp/design-time/causal-trace/&quot;&gt;explains a causal trace&lt;/a&gt;, the ordered history of one event source with its correlation and causation chains. For the author feature, an assistant can read the trace behind one author and say which command appended which event. The design-time tools introspect the store before generating anything, take field names from the schema registered with each event type, and say so when the store can’t answer. They never change the store, and the code they return is a proposal to review.&lt;/p&gt;
&lt;p&gt;The only tools that change anything are three for stopping, resuming and deleting jobs. The server marks that through MCP tool annotations, so a client can let the read-only tools run and ask before the others. It can’t append, redact or replay.&lt;/p&gt;
&lt;p&gt;An assistant connected to it can read event payloads, including personal data. Don’t treat it as read-only, scope what you connect it to, and treat anything it reads as data, not instructions.&lt;/p&gt;
&lt;p&gt;The corpus is more careful than the server. Its Chronicle MCP skill doesn’t admit any of these tools for an agent’s use yet, and tells an agent not to invoke or configure them, so installing the corpus doesn’t teach an assistant to call them. A person wires the server in.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;screenplay-mcp-proposals-a-person-applies&quot;&gt;Screenplay MCP: proposals a person applies&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;An assistant works on the event model through the &lt;a href=&quot;https://cratis.io/screenplay/mcp/&quot;&gt;Screenplay MCP server&lt;/a&gt;, which &lt;a href=&quot;https://cratis.io/cli/reference/screenplay-mcp/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;cratis screenplay mcp&lt;/code&gt;&lt;/a&gt; starts over stdio for one application’s folder. It can read the model, list the slices that have no &lt;code dir=&quot;auto&quot;&gt;then&lt;/code&gt; assertion or prepare a model-aware rename. The assertion check looks at the examples people wrote, which isn’t the same as executed coverage. Proposals are tied to the revision they were made against, and a stale revision means proposing again. Only an explicit &lt;code dir=&quot;auto&quot;&gt;apply&lt;/code&gt; (or workspace recovery) writes files, which is exactly where your client’s approval prompt belongs. None of its tools run specifications, and a proposal can be valid as source while still marked as not ready to execute.&lt;/p&gt;
&lt;p&gt;For a system that already exists, a Prologue draft enters at the same point, as a proposal a person corrects.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;cratis ai install&lt;/code&gt; registers it for your assistants when the profiles you pick include &lt;code dir=&quot;auto&quot;&gt;cratis/screenplay&lt;/code&gt;. The corpus declares it like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;screenplay&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;profiles&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;cratis/screenplay&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;transport&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;stdio&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;screenplay&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;mcp&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;defaultRoot&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;.cratis/screenplay&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Understand and safely author one Screenplay application through typed AST operations, reviewed proposals, durable identities, and explicit recovery.&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The install never creates &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; files, and it leaves MCP servers you added yourself alone.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;cratis-studio-and-cratis-direct&quot;&gt;Cratis Studio and Cratis Direct&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Cratis Studio and Cratis Direct are applications you use in the browser, and assistants meet them as well. &lt;a href=&quot;https://blog.cratis.io/cratis-studio-event-modeling-together/&quot;&gt;&lt;em&gt;Cratis Studio&lt;/em&gt;&lt;/a&gt; has an MCP server that lets external AI agents read and change an organization’s event models and brainstorming boards. Scope it like any tool that can write. Inside Studio, a slice of the author feature can be assigned to an AI agent and, where the application has code generation switched on, rendered through Stage into your connected Git repository. Cratis Direct’s issue admission and gates are covered in &lt;a href=&quot;https://blog.cratis.io/cratis-direct-agentic-development-life-cycle/&quot;&gt;&lt;em&gt;Cratis Direct and our agentic development life cycle&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-docs-for-an-assistant-that-asks-or-reads&quot;&gt;The docs, for an assistant that asks or reads&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/prompter/&quot;&gt;Prompter&lt;/a&gt;, still experimental, answers questions about the Cratis docs in Discord. It grounds each answer in cratis.io with citations you can follow, or declines when it can’t ground an answer. Its &lt;code dir=&quot;auto&quot;&gt;/issue&lt;/code&gt; command files a GitHub issue only when the person asking says so, and the question text goes to two outside providers, as &lt;a href=&quot;https://cratis.io/prompter/concepts/privacy/&quot;&gt;its privacy page&lt;/a&gt; explains. For an assistant that reads instead of asking, cratis.io publishes &lt;a href=&quot;https://cratis.io/llms.txt&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;llms.txt&lt;/code&gt;&lt;/a&gt;, &lt;code dir=&quot;auto&quot;&gt;llms-full.txt&lt;/code&gt; and an &lt;code dir=&quot;auto&quot;&gt;llms.txt&lt;/code&gt; for each product, and the index says the bulk archives aren’t the best default context for one question.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-person-reviews-or-sets-who-merges&quot;&gt;A person reviews, or sets who merges&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Reviewing a pull request an agent wrote is a different job from reviewing a colleague’s. &lt;a href=&quot;https://tianpan.co/blog/2026/04/27/reviewing-agent-prs-different-not-faster&quot;&gt;One recent piece on it&lt;/a&gt; makes two points we agree with. The intent sits in a prompt that rarely travels with the pull request, and tests the same agent wrote tend to share its blind spots.&lt;/p&gt;
&lt;p&gt;The Cratis pieces give the reviewer the intent as a diff of its own. A change to the author feature can arrive as a &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; change that states the command, its rules and its event, a render manifest that says which files are generated, and code in one feature folder. The reviewer reads the intent first, then checks the code against it. The second point applies here too. A spec an agent wrote next to its code can miss what the code misses, so read the model’s examples as intent.&lt;/p&gt;
&lt;p&gt;A proposal, a declared model, a passing specification and a recorded event each answer a different question, and a passing spec neither approves a proposal nor proves deployed behavior. An assistant’s diff that stops two authors in tenant Acme registering under the same name is a proposal. The rule written into the event model is a declaration. A spec that registers the same name twice and expects a rejection checks one example. The event log in Acme’s namespace shows what was actually recorded. Someone writes the first three, and the fourth is what happened. In a review, it helps to say which of the four you’re looking at.&lt;/p&gt;
&lt;p&gt;With the public pieces, however a pull request was produced, whether it merges is a person’s decision. The corpus’s own pull-request rule lets an assistant take a change through to merge only when a person asks it to ship, and even then it stops before a major release until a person explicitly confirms the break. That rule is about an assistant working in your repository. The separate repository merge policy is covered in &lt;a href=&quot;https://blog.cratis.io/cratis-direct-agentic-development-life-cycle/&quot;&gt;&lt;em&gt;Cratis Direct and our agentic development life cycle&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;read-propose-change&quot;&gt;Read, propose, change&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Put side by side, the touchpoints form a ladder. On the lowest rung an assistant reads, through Chronicle MCP’s list and get tools, Screenplay’s understanding tools, the CLI’s read-only commands and &lt;code dir=&quot;auto&quot;&gt;llms.txt&lt;/code&gt;. One rung up it proposes, with Screenplay’s proposal tools and Chronicle’s design-time code. Above that it can change state, but the tools don’t all enforce confirmation. The CLI’s &lt;code dir=&quot;auto&quot;&gt;add&lt;/code&gt; commands don’t prompt, and &lt;code dir=&quot;auto&quot;&gt;--yes&lt;/code&gt; bypasses the prompt on commands that do. In Claude Code and Pi, the store-mutation guard blocks those &lt;code dir=&quot;auto&quot;&gt;cratis chronicle&lt;/code&gt; commands unless a person set the variable. Chronicle MCP’s three job tools carry annotations; whether to ask for approval is up to the client. Writing model files takes Screenplay’s &lt;code dir=&quot;auto&quot;&gt;apply&lt;/code&gt; behind your client’s approval, and the write guard keeps it out of generated files. None of these tools merges or releases.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/ai-authority-ladder.Z6Uv1pCc_ZNU2xi.webp&quot; alt=&quot;A diagram titled Read, propose, change, drawn as a ladder of five rungs from bottom to top. Read: Chronicle MCP reads, Screenplay understanding tools, read-only CLI commands, llms.txt. Propose: Screenplay proposals, Chronicle design-time code. Change state: CLI add never prompts; –yes skips prompts. MCP approval is client-controlled. Write: Screenplay apply with client approval; generated files blocked by the write guard. Merge and release: not done by these tools. A note beside the lowest rung reads Rules and skills are text, and a note beside the upper three rungs reads Hooks enforce guards; annotations describe effects.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Each rung up asks more of a person.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;run-it-read-the-log-start-again&quot;&gt;Run it, read the log, start again&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The loop closes at the event log. The model says what the system is meant to record, and the log says what it did record. An event type nothing consumes, a partition that keeps failing, or a causal trace that surprises someone is the input to the next change. None of the public tools here turns that finding into work on its own. An agent can read it, and a person decides what starts.&lt;/p&gt;
&lt;p&gt;For the alert-to-backlog path and where it stops, see &lt;a href=&quot;https://blog.cratis.io/cratis-direct-agentic-development-life-cycle/&quot;&gt;&lt;em&gt;Cratis Direct and our agentic development life cycle&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The rendered vertical is narrow, and taking a second model change through to a running application isn’t proven yet, so the model anchors the code without replacing it. The hooks cover two assistants, and nothing here makes an agent’s code correct. Whether agents working this loop ship faster or with fewer defects is unmeasured. The &lt;a href=&quot;https://blog.cratis.io/from-zero-to-first-projection-with-an-ai-assistant/&quot;&gt;AI assistant walkthrough&lt;/a&gt; shows one of these tools on a real task, including where the assistant was told not to use one.&lt;/p&gt;
&lt;p&gt;To start, run &lt;code dir=&quot;auto&quot;&gt;cratis ai install&lt;/code&gt; in your repository for the corpus and its hooks, and &lt;code dir=&quot;auto&quot;&gt;cratis init&lt;/code&gt; to write the command catalog into your assistant’s context files.&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href=&quot;https://blog.cratis.io/cratis-direct-agentic-development-life-cycle/&quot;&gt;Part 24 — Cratis Direct and our agentic development life cycle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next: &lt;a href=&quot;https://blog.cratis.io/getting-started-with-cratis/&quot;&gt;Part 26 — Getting started with Cratis, and the tools around it&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>ai</category><category>mcp</category><category>cli</category><category>chronicle</category><category>screenplay</category></item><item><title>From script to stage · Part 24: Cratis Direct and our agentic development life cycle</title><link>https://blog.cratis.io/cratis-direct-agentic-development-life-cycle/</link><guid isPermaLink="true">https://blog.cratis.io/cratis-direct-agentic-development-life-cycle/</guid><description>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.</description><pubDate>Wed, 30 Sep 2026 09:24:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt; · Part 24 of 26&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Since the Cratis Direct GitHub app was created on 26 August 2026, it has opened 320 pull requests across 20 Cratis repositories, 238 of them merged, and filed 412 issues (as of 29 September 2026). They count what the app opened and filed. How many went through a full journey, and what the work cost or saved, isn’t in them. The work itself is ordinary engineering: bug fixes, dependency alignment across repositories, fixes for flaky integration tests, and, through its content journeys, blog posts and a weekly digest.&lt;/p&gt;
&lt;p&gt;Cratis Direct is a control plane for agentic engineering. It turns repository signals into one prioritized queue of bounded AI work, checks each stage against recorded evidence, and brings the results back as pull requests. The agents do the work. Cratis Direct owns what they’re allowed to start, how far each piece of work may go, what counts as done, and the evidence of what happened.&lt;/p&gt;
&lt;p&gt;Cratis runs its own engineering on Cratis Direct today. For everyone else it’s coming soon: access opens in stages at &lt;a href=&quot;https://cratis.direct&quot;&gt;cratis.direct&lt;/a&gt;. The login there admits only accounts we’ve enabled.&lt;/p&gt;
&lt;p&gt;The author feature arrives in Cratis Direct as an issue: two authors in one organization ended up with the same name.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-hard-part-moved&quot;&gt;The hard part moved&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Once agents can write code, the hard part moves. &lt;a href=&quot;https://cratis.direct&quot;&gt;Cratis Direct’s page&lt;/a&gt; puts it this way: “It becomes deciding what they work on, proving what they did, and noticing when the world changed underneath them.” That was our problem. Agents could investigate an issue, write the change and open a pull request, across many repositories, faster than anyone could decide what they should be doing or check what they had done. So we built the system around them.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;an-agentic-development-life-cycle&quot;&gt;An agentic development life cycle&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Once agents write much of the code, people have started calling the resulting life cycle an agentic development life cycle, or ADLC. &lt;a href=&quot;https://www.dataknobs.com/agentic-ai/agentic-ai-development-life-cycle/&quot;&gt;DataKnobs&lt;/a&gt; and &lt;a href=&quot;https://www.voodootikigod.com/adlc-tldr&quot;&gt;one open version&lt;/a&gt; disagree on the stages, and &lt;a href=&quot;https://www.ibm.com/think/topics/agent-development-lifecycle-adlc&quot;&gt;IBM uses the same letters&lt;/a&gt; for something else, the life cycle of building AI agents as products. The shape they share is that the familiar stages stay, and each one has to hand the next something an agent can read and a person can check. Ours is the issue journey in Cratis Direct. It runs Intake, Investigation, Plan, Implementation and Release, with a gate after each stage.&lt;/p&gt;
&lt;p&gt;In ours, the work waits for a person at several points. A person admits the work and sets how far it may go. After reading the plan, a person can raise that ceiling or leave a comment that steers the next stage. A gate can hand its result to a person for review, and when remediation has failed twice, a person decides. Only a person activates a new version of the rules, and by default a person merges.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/direct-where-a-person-decides.DyvcTES1_1f38cG.webp&quot; alt=&quot;A diagram titled Where a person decides in one journey. A column titled Stages and gates lists five stages: Intake, Investigation, Plan, Implementation and Release. A second column, A person decides, points arrows at four of them: Admit the issue, set a ceiling, at Intake; Raise the ceiling or comment, at Plan; After remediation fails twice, at Implementation; and Merge, a person by default, set per repository, at Release. Beside Investigation, without an arrow, a note reads At any gate: human review. Below, one card reads Only a person activates a new rule version, and an agent card reads One unit of work at a time.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The work waits for a person at each marked point.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;who-its-for&quot;&gt;Who it’s for&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Teams that already use coding agents, or are about to, and have more repositories, issues and alerts than people to look at them. That means engineering leads who decide what agents work on, platform teams who own the rules the work has to pass, and maintainers who review what comes back.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-its-for&quot;&gt;What it’s for&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;An agent that starts on everything spends on everything. In Cratis Direct, classifying an issue describes it and never starts work. No work on it starts until a person admits the issue into a journey and sets a ceiling: stop after the investigation, after the plan, after the implementation, or go through to release. AI capacity is shared within a concurrency limit, with usage tracked per provider and model, so issue work spends only on what a person chose to start.&lt;/p&gt;
&lt;p&gt;A merged pull request doesn’t tell anyone what was checked. Every stage ends at a gate that evaluates recorded evidence against versioned rules, and keeps the verdict, the evidence and the rule version.&lt;/p&gt;
&lt;p&gt;The gate that lets an agent proceed is the same evidence a reviewer reads later. Behind each pull request is its journey, with the plan, the verdicts of the gates and the evidence they read, and each repository decides how much a person has to review at all.&lt;/p&gt;
&lt;p&gt;The world also changes under a running agent. An issue is closed mid-journey, a pull request is merged early, a worker fails, the rules change. Cratis Direct checks each journey against reality every 15 minutes and whenever something changes, stops a journey that no longer matches, and asks a person.&lt;/p&gt;
&lt;p&gt;In Cratis Direct an alert is deduplicated and an agent investigates it straight away, so a person gets findings instead of a bare notification.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;one-issue-through-one-journey&quot;&gt;One issue, through one journey&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Cratis Direct takes in GitHub issues, pull requests and builds, plus alerts from running systems. It classifies and screens each issue without starting any work, keeps one prioritized backlog that people order, and starts work only when a person admits an issue into a journey and sets how far it may go.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-signal&quot;&gt;The signal&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Someone files the issue in the repository that holds the author feature. GitHub issues, pull requests and builds arrive through webhooks, with a daily reconciliation to catch anything missed. Alerts from running systems arrive by webhook too, and resource probes check what’s running. It works only with GitHub.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;screened-and-classified&quot;&gt;Screened and classified&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Every issue and comment is screened for spam, conduct and other content problems. Every issue is classified by its kind, how feasible it is, how critical it is, a guess at its semantic-versioning impact and which model tier suits it. A person’s classification is recorded beside the machine’s. Issues from people outside the team are kept from agents until someone decides. None of this starts work on the issue.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;one-queue&quot;&gt;One queue&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The backlog is one ordered list across repositories. You drag to rank and drop to group, and a group is scheduled only when all its issues are ready. Triage reads the repository’s vision documents. A roadmap plan can be generated from selected issues, and it stays a proposal until someone applies it. A chained issue waits until the release of the one before it exists.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;admitted-with-a-ceiling&quot;&gt;Admitted with a ceiling&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A person picks the duplicate-names issue and sets how far it may go, for example “stop after the plan”. That’s the only way issue work starts.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;investigation-and-plan&quot;&gt;Investigation and plan&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The agent does one unit of work at a time in a worker Cratis Direct launches for it, while Cratis Direct keeps the state, the sequence and the capacity. Each journey is a stream of events in &lt;a href=&quot;https://cratis.io/chronicle/&quot;&gt;Chronicle&lt;/a&gt;, the event store the rest of this series is about, so its history is the record. No screen can set a stage directly, and what happened can be read back in order.&lt;/p&gt;
&lt;p&gt;With the ceiling at the plan, the journey stops once the plan has passed its gate. That’s where a person finds out what the agent intends to change for the duplicate names, before any code exists. They can raise the ceiling to implementation, or leave a comment, and the comment steers the next stage. A running worker can also be watched in a live console and talked to.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/direct-journey-stream.OSXa_6tF_1vAWq0.webp&quot; alt=&quot;A diagram titled One journey, one stream of events. A frame labeled A stream of events in Chronicle holds eight numbered rows in order: 1, Issue recorded, screened and classified; 2, Admitted, ceiling after the plan; 3, Investigation gate, tagged verdict, evidence, rule version; 4, Plan gate, with the same tag; 5, Ceiling raised to implementation, by a person; 6, Implementation gate, with the same tag; 7, Pull request opened; 8, Merged, release gate passed. A band below reads No screen sets a stage directly.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A journey’s history is the record, in the order it happened.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-implementation-gate&quot;&gt;The implementation gate&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Each Cratis Direct journey moves an issue through investigation, plan, implementation and release, and each stage ends at a gate that evaluates versioned rules against recorded evidence. Unknown is never a pass: an inconclusive result goes to a person.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/direct-01-journey-stages.CV5hgOoL_Z15XC90.webp&quot; alt=&quot;The journey panel in Cratis Direct for the issue Show correct error message when service unavailable, in the repository Cratis/AuthProxy. A stage bar lists Intake, Investigation, Plan, Implementation, Release and Complete, each with a status line: Gate passed for the first three, Held at gate for Implementation, Upcoming for Release and Beyond ceiling for Complete. A panel headed Implementation quality says different work is already running for this issue. The Journey activity tab lists Journey started at Implementation and Existing work adopted.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;686&quot; height=&quot;900&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The stage view of a journey, from one of our own issues rather than the author feature. This journey started at the implementation stage and is held at that gate.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/cratis-direct-gate-outcomes.QmVU1EMG_2hwrse.webp&quot; alt=&quot;A ladder titled Five outcomes of a gate, from least to most restrictive: Allow; Requires human review; Requires explanation; Requires remediation; Block. A separate chip, Unknown, by default, points to Requires human review, with the label never Allow.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;1800&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The most restrictive outcome wins.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A gate collects the rules bound to it and evaluates each against recorded evidence. The evaluators range from deterministic checks and Roslyn analysis to a decision engine, external checks and an LLM’s judgment, and the gate takes the most restrictive of five outcomes: allow, requires human review, requires explanation, requires remediation, or block. A low-confidence result counts as unknown. An unknown result can’t map to allow, and by default it goes to human review.&lt;/p&gt;
&lt;p&gt;For the duplicate-names fix, the implementation gate’s evidence includes the build, the specs, static analysis and an LLM check that the tests weren’t weakened. That last one is judgment, not proof, and a low-confidence answer from it counts as unknown. A failing build, test coverage or static analysis rule sends the work back for remediation, and after two attempts a person decides. A failing check that the tests weren’t weakened goes to human review instead.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/direct-02-gate-rule-when-unknown.Dl3Iab1K_1xbFqJ.webp&quot; alt=&quot;The Cratis Direct journey designer with five stages from Intake to Release, each followed by a gate. Gate G04, Implementation quality, is selected. Its rule Tests not weakened, an LLM check at version 1, maps a pass to Allow, a failure to Requires human review and an unknown result to Requires human review, with the note: an unknown result never allows, and evaluator errors fail closed.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1224&quot; height=&quot;764&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The rule that checks the tests weren’t weakened: a failure and an unknown result both go to human review.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The rules are versioned. A new version is drafted, validated, simulated, reviewed and approved, only a person activates it, and a journey keeps the version it started with.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-pull-request-and-who-merges&quot;&gt;The pull request, and who merges&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Results come back as pull requests. Who merges is a per-repository setting: a person by default, or, for a repository that opts in, an agent once the change is classified as safe to merge, optionally limited to paths the repository allows.&lt;/p&gt;
&lt;p&gt;Each pull request is on its own branch, linked to its issue and marked for review. The default is called Human, and in it a person reviews and merges. The opt-in comes in two forms. Either Cratis Direct merges a pull request on its own when a language model reads the change as safe to merge, or it first hands the uncertain ones to an agent for a full review. The classification behind each automatic merge decision is recorded with its reason. We use both the default and the opt-in on our own repositories. The release gate passes only once the pull request is merged.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/direct-review-policy.DQ-9Twu6_Z1bSdgu.webp&quot; alt=&quot;A diagram titled Who merges, set per repository. A Pull request card on the left has arrows to three rows. Human, tagged default: a person reviews and merges. Auto, tagged opt-in: merges when a language model reads the change as safe to merge. Agent review, tagged opt-in: the uncertain ones go to an agent for a full review. All three rows lead to a card on the right, Release gate, passes once merged. Below, one note reads Opt-in: optionally limited to paths the repository allows, and another reads Each automatic merge decision is recorded with its reason.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A person by default. An agent only where the repository opts in.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;from-production-back-to-the-backlog&quot;&gt;From production back to the backlog&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Cratis Direct can deduplicate an alert from a running system, have an agent investigate it, and hand a person the findings or turn the alert into a tracked issue. Turning an alert into a full journey isn’t built yet, and some watchdog checks are paused while we harden them.&lt;/p&gt;
&lt;p&gt;Alerts are deduplicated by their fingerprint, and production access is opt-in. An issue made from an alert lands back at the start of the queue, where a person decides whether it starts, like any other. That line from production back to the backlog is the step that’s easiest to leave out, and it’s how what the running system says becomes work someone can admit.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;content-goes-through-it-too&quot;&gt;Content goes through it too&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The same machinery runs content. A blog journey goes Brief, Draft, Editorial review, Publish, with editorial gates, and publishing is a button a person presses. Cratis Direct’s page describes the whole as five loops: deliver, assure, operate, publish and steer. The first, the issue journey, is what we mean by an agentic development life cycle.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-it-stops&quot;&gt;Where it stops&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;It works only with GitHub. Agents are only as good as the rules the gates hold them to, and an LLM rule is judgment, not proof. Production access is opt-in. Some watchdog checks are paused while we harden them. It isn’t generally available, and access opens in stages. We’re building MCP access to Cratis Direct.&lt;/p&gt;
&lt;p&gt;Nothing else in Cratis needs it. It isn’t in a request’s path. Cratis Direct works on the issues, pull requests and alerts around the application. It earns its place when agent work outgrows what one person can watch. For one repository and one assistant, the AI corpus and the checks in &lt;a href=&quot;https://blog.cratis.io/ai-across-the-cratis-stack/&quot;&gt;&lt;em&gt;AI across the stack&lt;/em&gt;&lt;/a&gt;, plus your CI, may well be enough.&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href=&quot;https://blog.cratis.io/cratis-studio-event-modeling-together/&quot;&gt;Part 23 — Cratis Studio: one event model for the whole team&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next: &lt;a href=&quot;https://blog.cratis.io/ai-across-the-cratis-stack/&quot;&gt;Part 25 — AI across the Cratis stack&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>cratis-direct</category><category>agents</category><category>ai</category><category>engineering</category><category>chronicle</category></item><item><title>From script to stage · Part 23: Cratis Studio: one event model for the whole team</title><link>https://blog.cratis.io/cratis-studio-event-modeling-together/</link><guid isPermaLink="true">https://blog.cratis.io/cratis-studio-event-modeling-together/</guid><description>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.</description><pubDate>Wed, 30 Sep 2026 09:23:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt; · Part 23 of 26&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;An event-modeling workshop usually ends as a photo of sticky notes. In Cratis Studio it ends as a model the whole team edited, with the AI agents working on the same one.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.studio&quot;&gt;Cratis Studio&lt;/a&gt; is a collaborative workspace where people and AI agents design, visualize and edit the same event model. It runs in the browser, and it’s built on Chronicle and Arc. Cratis Studio is live and in beta. Open signup is coming soon at cratis.studio, with a 14-day free trial.&lt;/p&gt;
&lt;p&gt;In Cratis Studio, a slice a team sketched together can be assigned to an AI agent, rendered into C# for Arc and Chronicle and committed to your connected Git repository, with the event model beside it as Screenplay text. Code generation is opt-in for each application. It works on one slice at a time, and it only goes from the model to the code.&lt;/p&gt;
&lt;p&gt;In Studio, the author feature starts as a handful of sticky notes and can end as a commit.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-wall-ends-up-as-a-photo&quot;&gt;The wall ends up as a photo&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/event-modeling/&quot;&gt;Event modeling&lt;/a&gt; happens in a room, or on a call, with a product owner, someone who knows the domain and a couple of developers arguing about what actually happens in the business. Someone photographs the wall of sticky notes that comes out of it and retypes part of it into a tool, and by the time the first slice is built, the model, the conversation and the code have started to drift apart.&lt;/p&gt;
&lt;p&gt;The author feature shows where it goes wrong. Someone at the wall asks whether two organizations can each have an author with the same name. The answer, that names are unique within an organization, is the rule the whole feature turns on, and on the wall it’s a yellow note with a question mark. It’s hard to read in the photo and easy to lose in the retyping, and it tends to come back later as a bug report.&lt;/p&gt;
&lt;p&gt;Coding agents widen the gap. An assistant can write the &lt;code dir=&quot;auto&quot;&gt;RegisterAuthor&lt;/code&gt; command, its projection and its spec in a few minutes, and the reviewer then compares that code with what they remember of a workshop.&lt;/p&gt;
&lt;p&gt;Studio moves the conversation onto the model itself, where people and AI agents edit the same thing, and what the team agrees on can be exported as text you commit.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;who-its-for&quot;&gt;Who it’s for&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Studio is for the people who have to agree on what a system should do: developers, domain experts and product owners, in one room or in a remote workshop. We also build it for whoever runs the workshop, and for whoever has to turn the result into a system afterwards.&lt;/p&gt;
&lt;p&gt;Each of them uses a different part of it. A domain expert starts on the board and names what happens in the business in its own words, such as &lt;code dir=&quot;auto&quot;&gt;AuthorRegistered&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;AuthorRenamed&lt;/code&gt;. A product owner can follow the model after the workshop ends, because every slice can be assigned to a teammate or an AI agent, and the project’s progress board shows where each one is. A developer gets the model as Screenplay to review as a diff and, where the application has code generation switched on, a slice’s code in the repository. Someone who joins the team half a year later can ask Studio to explain a feature at the level they need.&lt;/p&gt;
&lt;p&gt;In a remote workshop everyone’s cursor is on the same board, so the facilitator isn’t the only person who can point at a note.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-we-built-it&quot;&gt;Why we built it&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;We think the model and the code belong together. A model nothing is generated from goes stale, and code nobody can trace back to a model is hard to review, whether a person or an agent wrote it. Studio keeps the two coupled. The team works on one model, the model is text you can diff, and the code for a slice is generated from it into your repository.&lt;/p&gt;
&lt;p&gt;The commands, events and read models on the canvas are the concepts &lt;a href=&quot;https://cratis.io/arc/&quot;&gt;Arc&lt;/a&gt; and &lt;a href=&quot;https://cratis.io/chronicle/&quot;&gt;Chronicle&lt;/a&gt; run. The text form is &lt;a href=&quot;https://cratis.io/screenplay/&quot;&gt;Screenplay&lt;/a&gt;, the event-modeling language covered in &lt;a href=&quot;https://blog.cratis.io/screenplay-an-event-model-you-can-compile/&quot;&gt;&lt;em&gt;Screenplay&lt;/em&gt;&lt;/a&gt;, and Play and code generation go through &lt;a href=&quot;https://github.com/Cratis/Stage&quot;&gt;Stage&lt;/a&gt;, which &lt;a href=&quot;https://blog.cratis.io/stage-and-scene-after-a-model-compiles/&quot;&gt;&lt;em&gt;Stage and Scene&lt;/em&gt;&lt;/a&gt; takes apart. Studio is where those pieces meet for a whole team.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-author-feature-from-board-to-commit&quot;&gt;The author feature, from board to commit&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;on-a-shared-board&quot;&gt;On a shared board&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Open a brainstorming board, and everyone adds events and sticky notes at once: &lt;code dir=&quot;auto&quot;&gt;AuthorRegistered&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;AuthorRenamed&lt;/code&gt;, and a note asking “same name in two organizations?”. Group them by theme, and transfer the events that hold up into the event model.&lt;/p&gt;
&lt;p&gt;Everyone sees who else is on the board and where their cursor is while they work, so a remote session doesn’t depend on one person’s shared screen. The events are what you transfer. The yellow note stays on the board as the question the slice will have to answer.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;on-the-canvas&quot;&gt;On the canvas&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;On the event-modeling canvas, the author feature becomes a module, a feature and a slice on a timeline with swimlanes, with the &lt;code dir=&quot;auto&quot;&gt;RegisterAuthor&lt;/code&gt; command, the &lt;code dir=&quot;auto&quot;&gt;AuthorRegistered&lt;/code&gt; event and a read model as color-coded cards. Assign the slice to a teammate or to an AI agent, and follow it on the project’s progress board.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/studio-01-event-model-canvas.DKN130JG_ZNeyXT.webp&quot; alt=&quot;Cratis Studio’s event-modeling canvas under a beta banner. A module named Authors contains a feature named Registration with two slices side by side. The Register author slice has a blue command card, RegisterAuthor, above an orange event card, AuthorRegistered. The Author list slice has a green read-model card, Author, above the same AuthorRegistered event.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1200&quot; height=&quot;1040&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The author feature on the canvas: two slices, with a command, an event and a read model.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The answer to the yellow note belongs to this slice. Registering a name that’s already taken in the organization is rejected, and that’s the behavior the specification on the slice writes down.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/studio-author-board-and-canvas.CxjMi3M__3w09l.webp&quot; alt=&quot;A diagram titled The author feature in Cratis Studio, in two sections. Top, Brainstorming board: event notes AuthorRenamed and AuthorRegistered, a sticky note reading Same name in two organizations?, and two cursor labels, Domain expert and Developer. An arrow labeled Transfer the events leads from the AuthorRegistered note down to the event model. Bottom, Event model: Authors, Registration: a blue command card RegisterAuthor, an orange event card AuthorRegistered and a green read-model card Authors, joined left to right. Below them, a card Specification on the slice reads Given AuthorRegistered. When RegisterAuthor with the same name. Then rejected. Beside it, a card reads Assigned to an AI agent.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The same feature on the board and on the canvas. Events transfer; the question stays on the board.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;a-specification-on-the-slice&quot;&gt;A specification on the slice&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Add a given/when/then specification to the slice, such as “registering the same name twice is rejected”. It’s an example against the model, and not against a running system, like the ones the Stage specification runner in &lt;a href=&quot;https://blog.cratis.io/stage-and-scene-after-a-model-compiles/&quot;&gt;&lt;em&gt;Stage and Scene&lt;/em&gt;&lt;/a&gt; checks.&lt;/p&gt;
&lt;p&gt;This is where the question from the board ends up, as an example with a given, a when and a then that a domain expert can read as easily as a developer.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/studio-02-slice-specifications.DUq0LNHV_Z2fxbKx.webp&quot; alt=&quot;Two specifications under the Register author slice in Cratis Studio. Registering a new name: given nothing, when RegisterAuthor, then AuthorRegistered. Registering a taken name is rejected: given AuthorRegistered, when RegisterAuthor, then the red error AuthorNameAlreadyTaken.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;534&quot; height=&quot;1080&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Two specifications on the Register author slice. The second is the duplicate-name rule, written as an example.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;ai-agents-on-the-same-model&quot;&gt;AI agents on the same model&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Cratis Studio’s AI agents can generate modules, features and slices from a prompt, explain a domain, feature or module, and answer in chat on the model. They run on a model provider your organization configures, and AI stays off until one is set up.&lt;/p&gt;
&lt;p&gt;For the author feature, ask Studio to generate the missing slices of a feature from a prompt, such as renaming an author, and they appear on the canvas as cards the team reviews like any other change to the model. Ask it to explain the feature to someone new, at the level they need, and read aloud if they want. Dictate instead of typing, let predictive modeling suggest the next cards, or mention an agent in a slice’s chat and let it answer there.&lt;/p&gt;
&lt;p&gt;Studio doesn’t ship a language model. Every AI feature runs on a provider your organization configures, such as OpenAI, Azure OpenAI or Anthropic, and until one is configured the AI features stay switched off and say why. The choice of provider, and the terms that come with it, stay yours.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;play&quot;&gt;Play&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Press Play, and Studio starts a disposable Stage session for the model, so you can try the application it describes before anyone has written it. The session is for exploring the model. Don’t host the application there.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/studio-03-play-register-author.DYLdoEH5_2aHN8s.webp&quot; alt=&quot;Cratis Studio running the model with Play. A generated page titled Registerauthor shows a form with an authorName field containing Ola Nordmann and a button, Execute Register Author.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1440&quot; height=&quot;900&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A Play session: the register form generated from the model.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/studio-gif-01-canvas-specs-play.ouZ9Me_D_1XtweY.webp&quot; alt=&quot;Animated sequence from Cratis Studio. The canvas shows the Register author and Author list slices. The view scrolls to the two specifications under Register author and back, then to the Play button. Studio starts a Play session that opens an API view and a generated frontend with a Registerauthor page. The name Kari Holm is entered and executed, and the Authorlist page then lists that one author.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;960&quot; height=&quot;600&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;From the canvas to a Play session. The wait for the session to start is shortened.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;screenplay-out-and-back-in&quot;&gt;Screenplay out, and back in&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Export the model as Screenplay, as one &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; document or a folder of them, or as Mermaid or JSON. Commit the Screenplay next to the code and review it as a diff. Importing it back shows a preview of what will change and warns about anything it can’t bring in.&lt;/p&gt;
&lt;p&gt;Screenplay reads well as a diff. This one, written by hand as an illustration and not exported from Studio, adds a rule to the &lt;code dir=&quot;auto&quot;&gt;RegisterAuthor&lt;/code&gt; slice of the author feature:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;slice StateChange RegisterAuthor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;command RegisterAuthor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;authorId AuthorId identifier&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name     AuthorName&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;authorize IsSignedIn&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;validate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name not empty  message &quot;A name is required&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;produces AuthorRegistered&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;for authorId&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name = name&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The rule sits beside the command it guards, so a change to the author feature can be reviewed in a pull request before anyone writes code for it, and the reviewer reads the intent in a few changed lines.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;code-for-one-slice&quot;&gt;Code for one slice&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;When a slice is assigned to an AI agent, Cratis Studio can render it through Stage into C# for Arc and Chronicle in a connected Git repository, commit the result, and optionally let the agent refine it. Code generation is opt-in for each application.&lt;/p&gt;
&lt;p&gt;In practice, generation starts when the author slice, assigned to an AI agent, is marked ready for implementation. Studio renders it through Stage’s Cratis renderer into a working copy of your connected Git repository, commits the code and the &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; files separately, lets the agent refine the result if the application is set up for that, and pushes. What arrives is a change in your repository, and it goes through the same review as any other.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/studio-code-generation-settings.Ctxx9szD_2mXyus.webp&quot; alt=&quot;A diagram titled Three code-generation settings, in three rows. No code generation: the slice stays in the model and in Screenplay, and nothing is written to the repository. Screenplay only: Render through Stage, then an arrow to Your repository, code and .play files, committed separately. Screenplay with an agent on top: Render through Stage, then Agent pass, then Your repository. Every arrow points toward the repository. A band along the bottom reads Nothing reads the code back into the model.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Code generation is a setting on each application, and every path runs from the model to the repository.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;everyone-on-the-same-model&quot;&gt;Everyone on the same model&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Collaboration is live. You see who else is on the board and where their cursor is, and a change one person makes shows up on everyone’s screen. Comments and chat sit on features and slices, and agents take part in them as well as people.&lt;/p&gt;
&lt;p&gt;A project holds several applications, so each bounded context keeps its own model, and each organization has its own projects. For the author feature, that means the registration context can have its own model without sharing a canvas with, say, billing.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-code-generation-stops&quot;&gt;Where code generation stops&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Code generation is opt-in for each application, with three settings: no code generation, Screenplay only, or Screenplay with an agent on top. It goes through Stage’s Cratis renderer, but through its older direct-write path, and not the planned render that &lt;code dir=&quot;auto&quot;&gt;cratis render&lt;/code&gt; uses in &lt;a href=&quot;https://blog.cratis.io/stage-and-scene-after-a-model-compiles/&quot;&gt;&lt;em&gt;Stage and Scene&lt;/em&gt;&lt;/a&gt;. The output is C# on Arc and Chronicle, and not every construct becomes rendered code. What the renderer can’t express is marked with a TODO and a diagnostic instead of being refused, and the refusal and &lt;code dir=&quot;auto&quot;&gt;Customizations/&lt;/code&gt; guarantees of &lt;code dir=&quot;auto&quot;&gt;cratis render&lt;/code&gt; don’t apply here. What the renderer leaves out is for the agent pass, or a person, to write.&lt;/p&gt;
&lt;p&gt;Coupled doesn’t mean synchronized. Generation goes one way, one slice at a time. Nothing reads the code back into the model, and Studio doesn’t detect when code and model have drifted apart. What the coupling gives you is code that starts from the model, in the model’s own vocabulary, with the model sitting in the repository as Screenplay beside it. Review what comes out like any other change.&lt;/p&gt;
&lt;p&gt;The Screenplay round trip has the same kind of limit. It’s tested for the constructs Studio models, and the import warns where it can’t carry something, so it isn’t lossless in every case. It’s the rule from &lt;a href=&quot;https://blog.cratis.io/screenplay-an-event-model-you-can-compile/&quot;&gt;&lt;em&gt;Screenplay&lt;/em&gt;&lt;/a&gt;: expose what you can’t represent instead of dropping it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;assistants-outside-studio&quot;&gt;Assistants outside Studio&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Cratis Studio has an MCP server that lets external AI agents read and change an organization’s event models and brainstorming boards. That’s the route for an assistant in your editor or terminal to work on the author feature’s model without anyone copying it out of Studio first.&lt;/p&gt;
&lt;p&gt;Scope it like any tool that can write. &lt;a href=&quot;https://blog.cratis.io/ai-across-the-cratis-stack/&quot;&gt;&lt;em&gt;AI across the stack&lt;/em&gt;&lt;/a&gt; puts it next to the other places an assistant meets Cratis.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-it-fits-with-the-rest-of-cratis&quot;&gt;How it fits with the rest of Cratis&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Studio isn’t in a request’s path. It works on the model and generates code into your repository. It uses the pieces the rest of this series describes. Its text form is Screenplay, Play and code generation run through Stage, and the generated code runs on Arc and Chronicle. You don’t need Studio to use any of them, and modeling in Studio doesn’t require adopting the rest of the stack. Stage is experimental, and some features inside Studio are labeled preview or experimental.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/prologue/&quot;&gt;Prologue&lt;/a&gt;, which drafts a model from a running system, has a place in Studio too for systems that already exist, and it’s experimental there as well. &lt;a href=&quot;https://blog.cratis.io/prologue-a-first-model-of-an-existing-system/&quot;&gt;&lt;em&gt;Prologue&lt;/em&gt;&lt;/a&gt; covers what it does and what it can’t know.&lt;/p&gt;
&lt;p&gt;When signup opens, the author feature makes a good first board: brainstorm it with the people who know the domain, transfer the events that hold up into the event model, add the example about duplicate names, and export it as Screenplay.&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href=&quot;https://blog.cratis.io/prologue-a-first-model-of-an-existing-system/&quot;&gt;Part 22 — Prologue: a first model of the system you already have&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next: &lt;a href=&quot;https://blog.cratis.io/cratis-direct-agentic-development-life-cycle/&quot;&gt;Part 24 — Cratis Direct and our agentic development life cycle&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>cratis-studio</category><category>event-modeling</category><category>screenplay</category><category>stage</category><category>ai</category></item><item><title>From script to stage · Part 22: Prologue: a first model of the system you already have</title><link>https://blog.cratis.io/prologue-a-first-model-of-an-existing-system/</link><guid isPermaLink="true">https://blog.cratis.io/prologue-a-first-model-of-an-existing-system/</guid><description>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.</description><pubDate>Wed, 30 Sep 2026 09:22:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt; · Part 22 of 26&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Prologue can stand beside a running system, watch what it does, and hand back a &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file describing it, without recording a single row value or request body. What it does record is metadata: which endpoint was called, which tables and columns changed, which spans the system emitted. That metadata can still be sensitive, and the model it produces is a draft for a person to correct.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/prologue/&quot;&gt;Prologue&lt;/a&gt; is an experimental assisted-discovery tool that captures approved metadata signals and proposes a Cratis event model for human correction. Most systems exist long before anyone models them, and those are the ones it’s for. The model it proposes is written in Screenplay, the language from &lt;a href=&quot;https://blog.cratis.io/screenplay-an-event-model-you-can-compile/&quot;&gt;&lt;em&gt;Screenplay&lt;/em&gt;&lt;/a&gt;, so the first model of an old system starts from a draft instead of a blank page.&lt;/p&gt;
&lt;p&gt;Picture the author feature already running in an ASP.NET application with a SQL database and no event model anywhere. Prologue can see a &lt;code dir=&quot;auto&quot;&gt;POST&lt;/code&gt; reach it and the write to an authors table that follows. Whether that pair of observations is called &lt;code dir=&quot;auto&quot;&gt;RegisterAuthor&lt;/code&gt;, and that names are unique per organization, is for a person to settle.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;evidence-instead-of-memory&quot;&gt;Evidence instead of memory&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A system that predates any event model has two usual sources for one. The code says what the authors wrote, and the people who remember the system say what they think it does. &lt;a href=&quot;https://cratis.io/prologue/why-prologue/&quot;&gt;Why Prologue&lt;/a&gt; proposes a third: watch the system run. Prologue observes three things the system already does, without a change to its code. It sees the state-changing HTTP requests that reach it, which tables and columns changed in each database transaction right after a command, and the telemetry it already emits.&lt;/p&gt;
&lt;p&gt;The output is an &lt;code dir=&quot;auto&quot;&gt;ExtractionResult&lt;/code&gt;, a structured tree, and a generated &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file. That file is the way into everything else in the modeling layer. &lt;code dir=&quot;auto&quot;&gt;cratis screenplay validate&lt;/code&gt; checks it, &lt;code dir=&quot;auto&quot;&gt;cratis run&lt;/code&gt; starts it in a Stage sandbox, and a person edits it. Prologue itself is self-contained. It needs no Cratis Studio, no Orleans and no Chronicle, and runs as three ordinary containers, the Extractor, the Interpreter and an optional Receiver, with shared contract packages on NuGet.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-the-extractor-watches&quot;&gt;What the Extractor watches&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The Extractor runs next to the system and is the only piece that runs all the time, as the &lt;code dir=&quot;auto&quot;&gt;cratis/prologue-extractor&lt;/code&gt; image or a downloadable binary. It has four &lt;a href=&quot;https://cratis.io/prologue/reference/capture-sources/&quot;&gt;capture sources&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For SQL Server it uses change data capture, and it enables CDC itself unless you set &lt;code dir=&quot;auto&quot;&gt;enableChangeDataCapture&lt;/code&gt; to &lt;code dir=&quot;auto&quot;&gt;false&lt;/code&gt;. A &lt;code dir=&quot;auto&quot;&gt;tables&lt;/code&gt; allowlist narrows what it watches, and an empty list means every user table. It records which tables and columns changed in each transaction.&lt;/p&gt;
&lt;p&gt;For PostgreSQL it uses logical replication. The Extractor creates its own publication and replication slot. The server has to run with &lt;code dir=&quot;auto&quot;&gt;wal_level = logical&lt;/code&gt;, and that’s a setting you make, because the Extractor can’t.&lt;/p&gt;
&lt;p&gt;For HTTP it runs a YARP reverse proxy in front of the system, on port 8080 by default. Every request passes through unchanged. For &lt;code dir=&quot;auto&quot;&gt;POST&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;PUT&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;DELETE&lt;/code&gt; it records the method, the path, the response status, and the trace ID from a W3C &lt;code dir=&quot;auto&quot;&gt;traceparent&lt;/code&gt; header when there is one. &lt;code dir=&quot;auto&quot;&gt;GET&lt;/code&gt; requests pass through and leave nothing in the captures, since reads don’t change state. Traffic that goes around the proxy produces nothing.&lt;/p&gt;
&lt;p&gt;For OpenTelemetry it runs an OTLP proxy over HTTP and gRPC. It records the metadata of spans, metrics and logs: the service name, filtered by &lt;code dir=&quot;auto&quot;&gt;serviceNames&lt;/code&gt; or all services when that’s empty, and the values of the attribute keys you allowlist in &lt;code dir=&quot;auto&quot;&gt;attributeKeys&lt;/code&gt;. Telemetry is forwarded unchanged to your collector, and with no upstream configured the Extractor is the last stop.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/beside-the-running-system.B4nsL-BY_Zj90Ym.webp&quot; alt=&quot;A diagram titled Beside the running system. At the top, an existing system with three taps: a reverse proxy for POST, PUT and DELETE; a database, through CDC or logical replication; and an OTLP tap for spans, metrics and logs. Each has an arrow down to the Extractor, labeled metadata only, which joins a command to what follows within 2 s by default. Below, capture files lead to the Interpreter, heuristics then an optional language model, which writes a .play file.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2200&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Extractor watches from the side, and the Interpreter turns what it recorded into a draft.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-it-records-and-why-that-still-matters&quot;&gt;What it records, and why that still matters&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;None of the four sources records the content of what passes through. There are no row values from the database, and no request or response bodies from HTTP. That boundary is deliberate.&lt;/p&gt;
&lt;p&gt;Metadata is still information about the system and sometimes about people. An HTTP path can carry an identifier, such as a customer number in &lt;code dir=&quot;auto&quot;&gt;/customers/{id}&lt;/code&gt;. Table and column names describe the data model. The values of allowlisted telemetry attributes are recorded exactly as sent, so an attribute that holds an email address ends up in a capture file. And captures are written to disk as files, or posted to a Receiver that stores them in MongoDB. Choose the allowlists and the place captures go the way you’d choose them for a log that contains personal data, because it might.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;one-capture-is-a-command-and-what-it-caused&quot;&gt;One capture is a command and what it caused&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A single HTTP call is rarely the whole fact. The call plus the database transactions it caused usually is. Prologue’s correlator groups observations into &lt;a href=&quot;https://cratis.io/prologue/concepts/&quot;&gt;captures&lt;/a&gt;. A capture is an HTTP command plus every database transaction, span or log observed within &lt;code dir=&quot;auto&quot;&gt;correlation.windowMilliseconds&lt;/code&gt; after it, 2,000 milliseconds by default, or that shares the command’s trace ID.&lt;/p&gt;
&lt;p&gt;Telemetry that carries a trace ID but has no command in front of it becomes one capture per trace, which is how background work shows up. Anything else becomes a capture of its own. A schema change, a table or column appearing or disappearing, is recorded but never used as evidence to group other observations. A capture is written only once nothing newer than the window could still join it, so one capture never ends up split across two runs.&lt;/p&gt;
&lt;p&gt;This is a heuristic, and it links events by when they happened. Two requests that arrive inside the same window can share database transactions, and one of them can get credit for the other’s writes. Every relationship in the resulting model is provisional, and a person should read it that way.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;from-captures-to-a-first-model&quot;&gt;From captures to a first model&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The Interpreter turns a folder of capture files into a model. By default it runs to completion: it reads every &lt;code dir=&quot;auto&quot;&gt;.jsonl&lt;/code&gt; file, rebuilds the correlated captures, and writes &lt;code dir=&quot;auto&quot;&gt;extraction-result.json&lt;/code&gt; and a &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file. It also has a &lt;a href=&quot;https://cratis.io/prologue/guides/running-the-interpreter/&quot;&gt;service mode&lt;/a&gt; that keeps running as a resumable session backed by MongoDB. The Receiver is an optional HTTP endpoint that stores captures in MongoDB when the Extractor posts them to it instead of writing files.&lt;/p&gt;
&lt;p&gt;The first step is deterministic. Each capture becomes one or more provisional slice drafts, the drafts are grouped into modules, features and slices, and drafts that describe the same behavior are merged by name. The merge keeps the first item it finds for each name. Only projections combine their source events, so a property that appears only in a later capture of the same command or event is dropped. The result has the right structure and mechanical names, CRUD placeholders derived from HTTP verbs and database operations.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://cratis.io/prologue/reference/extraction-result/&quot;&gt;extraction result&lt;/a&gt; follows the module, feature and slice structure of a Screenplay model. A system has modules, a module has features that can nest, and a feature has slices of type &lt;code dir=&quot;auto&quot;&gt;StateChange&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;StateView&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Automation&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;Translation&lt;/code&gt;. A slice holds commands (at most one for a &lt;code dir=&quot;auto&quot;&gt;StateChange&lt;/code&gt;), events, read models, projections and constraints. For a library system it looks like this, trimmed:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;systemName&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Library&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;modules&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Catalog&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;features&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Books&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;slices&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Reservation&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;StateChange&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;commands&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;ReserveBook&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;properties&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Isbn&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;isRequired&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;maxLength&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}],&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;validations&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;events&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;BookReserved&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;properties&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Isbn&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;isRequired&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;maxLength&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;readModels&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[],&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;projections&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[],&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;constraints&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A command’s validations come from rejected requests, as &lt;code dir=&quot;auto&quot;&gt;Required&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;MaxLength&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;MinLength&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;Pattern&lt;/code&gt; with an argument and the rejection’s message. Constraints are uniqueness and invariant rules inferred from rejected requests too. For the author feature, that’s where the unique-name rule could show up, if registering a duplicate name produced a distinct rejection while Prologue was watching. A rule the system enforces without a distinguishing HTTP response or database write leaves no evidence, and a person has to add it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-language-model-may-rename-never-restructure&quot;&gt;A language model may rename, never restructure&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The second step is optional. With a language model configured in the &lt;code dir=&quot;auto&quot;&gt;llm&lt;/code&gt; section of &lt;code dir=&quot;auto&quot;&gt;cratis-prologue.json&lt;/code&gt;, or in the CLI’s &lt;code dir=&quot;auto&quot;&gt;~/.cratis/config.json&lt;/code&gt; via &lt;code dir=&quot;auto&quot;&gt;cratis llm use&lt;/code&gt;, the Interpreter asks it to refine the draft. Refinement is off until you configure it. In &lt;code dir=&quot;auto&quot;&gt;cratis-prologue.json&lt;/code&gt; the kinds are Ollama (the default kind, self-hosted, no token), OpenAI, Azure OpenAI (where the model ID is the deployment name), any OpenAI-compatible &lt;code dir=&quot;auto&quot;&gt;/v1&lt;/code&gt; endpoint, and Anthropic. &lt;code dir=&quot;auto&quot;&gt;cratis llm use&lt;/code&gt; offers &lt;code dir=&quot;auto&quot;&gt;anthropic&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;openai&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;local&lt;/code&gt;, an OpenAI-compatible endpoint such as Ollama’s.&lt;/p&gt;
&lt;p&gt;The refinement prompt is in the public repository. These three lines set its contract.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;The structure of the model is fixed - you never add, remove, or move anything.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Commands are imperative and express business intent (for example RegisterAuthor, PlaceOrder, ReserveBook) - name the intent, not the HTTP verb (not CreateAuthor).&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Events are past-tense facts naming what happened in the domain (for example AuthorRegistered, OrderPlaced, BookReserved) - name the fact, not the database operation (not AuthorCreated).&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The language model gets to return renames, keyed on the provisional names it was given, a description for each module, feature, slice and command, a system name in PascalCase, and questions. It infers intent from request paths, the tables and columns that changed, span names and the observed schema. The structure it was handed is the structure that comes back. For the author feature, that’s the difference between a placeholder named after the insert and &lt;code dir=&quot;auto&quot;&gt;RegisterAuthor&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Questions are meant to be rare. The prompt asks for one only when the model is unsure about a decision that would change the result materially, and zero is the normal case. An interactive run asks them one at a time, with choices and always a free-text answer. A non-interactive run, in CI, with piped output or with &lt;code dir=&quot;auto&quot;&gt;-y&lt;/code&gt;, never asks and finishes with its best effort. Without a configured model, the Interpreter doesn’t go beyond the heuristics.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/what-the-model-may-change.BcDimGjy_ZyUayf.webp&quot; alt=&quot;A diagram titled What the model may change, three columns under a label that reads structure is fixed. Heuristics decide structure: modules, features, slices, commands, events, read models. The model may rename and describe: renames, descriptions, system name, a few questions. A person decides the rest: business rules with no evidence, boundaries.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;1600&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The heuristics decide the structure, a language model can only name it, and a person decides the rest.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;With a hosted provider, the prompt leaves your network. It carries the provisional outline of the model, the provisional names, the observed behavior, including request paths, and the observed database schema. That’s metadata again, and a path can carry an identifier. Nothing is sent anywhere unless you enable refinement, and Ollama runs wherever you host it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;running-it&quot;&gt;Running it&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The &lt;a href=&quot;https://cratis.io/prologue/getting-started/&quot;&gt;getting-started guide&lt;/a&gt; runs everything in Docker. A minimal configuration that watches only HTTP writes captures to a folder and forwards requests to the system on port 5000:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;prologue&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;output&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;kind&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Json&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;directory&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/captures&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;maxEntriesPerFile&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;10000&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;correlation&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;windowMilliseconds&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;2000&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;sqlServer&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[],&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;postgres&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[],&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;openTelemetry&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;enabled&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;reverseProxy&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;routes&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;monitored&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;clusterId&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;monitored&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;match&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{**catch-all}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;clusters&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;monitored&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;destinations&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;primary&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;address&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;http://host.docker.internal:5000/&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Create the &lt;code dir=&quot;auto&quot;&gt;captures&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;output&lt;/code&gt; folders first, so Docker doesn’t create them as root. Then the Extractor runs with that file and the &lt;code dir=&quot;auto&quot;&gt;captures&lt;/code&gt; folder mounted, you send state-changing requests through port 8080, and the Interpreter reads the captures and writes to &lt;code dir=&quot;auto&quot;&gt;output&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;captures&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;output&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--rm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;8080:8080&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;$(&lt;/span&gt;&lt;span&gt;pwd&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;/cratis-prologue.json:/config/cratis-prologue.json:ro&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;$(&lt;/span&gt;&lt;span&gt;pwd&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;/captures:/captures&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;cratis/prologue-extractor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--rm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;$(&lt;/span&gt;&lt;span&gt;pwd&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;/captures:/captures&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;$(&lt;/span&gt;&lt;span&gt;pwd&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;/output:/output&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;cratis/prologue-interpreter&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;a href=&quot;https://cratis.io/cli/reference/prologue/&quot;&gt;CLI&lt;/a&gt; wraps the same steps:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;prologue&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;prologue&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;interpret&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./captures&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--file&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MySystem.play&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;cratis prologue start&lt;/code&gt; is an interactive wizard that writes &lt;code dir=&quot;auto&quot;&gt;cratis-prologue.json&lt;/code&gt;, with the sources, the allowlists, any upstream collectors, and an output folder or Receiver endpoint. It needs a terminal and fails with &lt;code dir=&quot;auto&quot;&gt;-y&lt;/code&gt; or piped input, so in CI you write the file yourself. &lt;code dir=&quot;auto&quot;&gt;cratis prologue interpret&lt;/code&gt; reads the &lt;code dir=&quot;auto&quot;&gt;.jsonl&lt;/code&gt; files, prints the path it wrote, the system name it derived and the counts of modules, features and slices, and suggests &lt;code dir=&quot;auto&quot;&gt;cratis run&lt;/code&gt; as the next step. For the language model it uses the &lt;code dir=&quot;auto&quot;&gt;llm&lt;/code&gt; section of a &lt;code dir=&quot;auto&quot;&gt;cratis-prologue.json&lt;/code&gt; it finds, when that’s enabled, then &lt;code dir=&quot;auto&quot;&gt;~/.cratis/config.json&lt;/code&gt;, and otherwise runs on heuristics alone and says so.&lt;/p&gt;
&lt;p&gt;Every capture carries a &lt;code dir=&quot;auto&quot;&gt;prologueId&lt;/code&gt;. When a folder holds captures from several runs, &lt;code dir=&quot;auto&quot;&gt;--prologue-id&lt;/code&gt; picks the one to interpret. The Library sample in the Prologue repository is an ordinary ASP.NET and EF Core application with no Cratis code in it, and its Aspire composition wires up the whole pipeline, with the Receiver storing captures in MongoDB. It’s a demonstration, and not a pattern for production.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;two-ways-into-a-model&quot;&gt;Two ways into a model&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/cli/reference/screenplay/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;cratis screenplay generate&lt;/code&gt;&lt;/a&gt; is the other way to get a model you didn’t write. It reads a &lt;code dir=&quot;auto&quot;&gt;.sln&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.slnx&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;.csproj&lt;/code&gt; with Roslyn, for Arc, Marten, and Marten with Wolverine. It doesn’t start the application or connect to Chronicle or PostgreSQL, its output is byte-identical for unchanged source, and whatever it can’t express is reported with a stable diagnostic code. Generation reads what the code declares, and needs the source. Prologue records what the system did, and needs the system running with traffic going through it, though no source.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/two-on-ramps.BGAUYc1D_Z2oplA1.webp&quot; alt=&quot;A diagram titled Two on-ramps. Two arrows lead into one .play file. The upper one comes from Prologue: a running system, no source, with the cost needs traffic. The lower one comes from generate: source, nothing running, with the cost needs the source. From the .play file, an arrow labeled review leads to a person.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;1800&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Two ways to a first model, and both end with a person reviewing it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Neither one replaces the person who reviews the model, and neither is a migration tool.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-the-draft-is-for&quot;&gt;What the draft is for&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A Prologue draft is a provisional model built from evidence. It isn’t the system’s recovered intent, and it isn’t a lossless reconstruction. It shows only the traffic you sent through the proxy, the database changes on SQL Server or PostgreSQL, and the telemetry you allowed. Reads, message queues with no database effect, and requests that went around the proxy are absent, and so is any path nobody exercised while it was watching. Its names stay mechanical until a language model or a person renames them.&lt;/p&gt;
&lt;p&gt;For the author feature, the draft gets you a slice with a command, an event and the tables behind them, maybe with a sensible name. It won’t know that names are unique per organization unless the system rejected a duplicate while Prologue was watching, and it can’t know what an author means in your domain. That’s what the review is for. The draft enters the loop at the same point a hand-written model does, as a proposal a person corrects, then validates with &lt;code dir=&quot;auto&quot;&gt;cratis screenplay validate&lt;/code&gt;. A corrected model can go on to &lt;code dir=&quot;auto&quot;&gt;cratis run&lt;/code&gt;, and &lt;a href=&quot;https://blog.cratis.io/stage-and-scene-after-a-model-compiles/&quot;&gt;&lt;em&gt;Stage and Scene&lt;/em&gt;&lt;/a&gt; covers what Stage will and won’t do with it. Stage renders a narrow vertical, so don’t expect a draft of an old system to render unchanged.&lt;/p&gt;
&lt;p&gt;Prologue is the wrong tool for a system that doesn’t exist yet, for moving data out of an old database, and for a system with neither an HTTP surface nor database access to watch.&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href=&quot;https://blog.cratis.io/stage-and-scene-after-a-model-compiles/&quot;&gt;Part 21 — Stage and Scene: what happens after a model compiles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next: &lt;a href=&quot;https://blog.cratis.io/cratis-studio-event-modeling-together/&quot;&gt;Part 23 — Cratis Studio: one event model for the whole team&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>prologue</category><category>screenplay</category><category>event-modeling</category><category>cli</category><category>ai</category></item><item><title>From script to stage · Part 21: Stage and Scene: what happens after a model compiles</title><link>https://blog.cratis.io/stage-and-scene-after-a-model-compiles/</link><guid isPermaLink="true">https://blog.cratis.io/stage-and-scene-after-a-model-compiles/</guid><description>Stage renders a narrow slice of a Screenplay model into C# on Arc and Chronicle, boots a throwaway sandbox and runs the model&apos;s examples, and refuses with a code whatever it can&apos;t express. Scene is the UI model between a screen in the model and React.</description><pubDate>Wed, 30 Sep 2026 09:21:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt; · Part 21 of 26&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A Screenplay model in which one event declares two generations passes &lt;code dir=&quot;auto&quot;&gt;cratis screenplay validate&lt;/code&gt;. Hand the same model to &lt;code dir=&quot;auto&quot;&gt;cratis render&lt;/code&gt;, and it reports &lt;code dir=&quot;auto&quot;&gt;STAGE-ESM-016&lt;/code&gt;, explains it with &lt;code dir=&quot;auto&quot;&gt;CLI-RENDER-003&lt;/code&gt;, and publishes nothing. The model is valid, and the renderer can’t turn it into code faithfully, so it doesn’t turn it into code at all.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Cratis/Stage&quot;&gt;Stage&lt;/a&gt; is the experimental part of the Cratis modeling layer that takes a compiled model somewhere. It renders the part of the model it supports into a C# application on Arc and Chronicle, boots a disposable sandbox, and runs the model’s Given/When/Then examples. &lt;a href=&quot;https://github.com/Cratis/Scene&quot;&gt;Scene&lt;/a&gt;, also experimental, describes a user interface without describing a platform, and it’s how a &lt;code dir=&quot;auto&quot;&gt;screen&lt;/code&gt; in the model reaches React. Both are in early development.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.cratis.io/screenplay-an-event-model-you-can-compile/&quot;&gt;&lt;em&gt;Screenplay&lt;/em&gt;&lt;/a&gt; introduced the slice model using &lt;code dir=&quot;auto&quot;&gt;BorrowBook&lt;/code&gt;; the author feature follows the same pattern with &lt;code dir=&quot;auto&quot;&gt;RegisterAuthor&lt;/code&gt;. What happens to that slice next depends on which of Stage’s jobs you ask for, and on whether the slice stays inside what Stage supports.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;one-input-three-jobs&quot;&gt;One input, three jobs&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Stage reads Screenplay source, either one &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file or a folder compiled as one application. Its own JSON contracts are internal seams produced from that compile, and it doesn’t depend on Cratis Studio. Any tool can hand it the same source. It does three things with it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The renderer turns the compiled model into a reviewable C# application on Arc and Chronicle. It’s the job Stage puts first.&lt;/li&gt;
&lt;li&gt;The &lt;code dir=&quot;auto&quot;&gt;cratis/stage&lt;/code&gt; image is a disposable sandbox: the Stage host plus an in-memory Chronicle kernel, for poking at the modeled backend behavior Stage understands.&lt;/li&gt;
&lt;li&gt;The &lt;code dir=&quot;auto&quot;&gt;cratis/stage-specrunner&lt;/code&gt; job compiles the model, checks its specifications, writes a results file and exits.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/screenplay-to-code.BxJmHOIf_1rq9KR.webp&quot; alt=&quot;A diagram titled From model to code. At the top left, a .play model leads down into a tall Compiler check card, cratis screenplay validate. From it three labeled arrows lead to the right: render, to a C# app on Arc and Chronicle with a hash manifest and the Customizations folder kept; run, to a sandbox with an API, frontend and Workbench; and specs, to model-level results. A band across the bottom reads: Unsupported model: no new files written.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;1900&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;One model, three uses, and a gate that fails closed.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;rendering-is-planning&quot;&gt;Rendering is planning&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The renderer keeps planning apart from writing. &lt;code dir=&quot;auto&quot;&gt;CratisRendering.Plan(...)&lt;/code&gt; returns an &lt;code dir=&quot;auto&quot;&gt;ArtifactRenderPlan&lt;/code&gt; with normalized paths, the exact bytes of every file, a SHA-256 hash for each, and typed diagnostics. Planning reads no file system, process, network, environment, clock or random source. A plan that fails carries its diagnostics and no files, and only a plan whose &lt;code dir=&quot;auto&quot;&gt;Success&lt;/code&gt; is true is published.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://cratis.io/cli/reference/screenplay/&quot;&gt;CLI&lt;/a&gt; does the publishing:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./plays&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--target&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--destination&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./out&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyApplication&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;cratis render&lt;/code&gt; publishes new files only after the model compiles, binds to an executable model, is admitted for execution, plans for the target and passes artifact validation. If an earlier publication was interrupted, it recovers that first. The output is deterministic, down to a manifest that records the model’s semantic revision and the hash of every file. It refuses to overwrite a generated file you’ve edited unless you pass &lt;code dir=&quot;auto&quot;&gt;--force&lt;/code&gt;, and it never overwrites a file it didn’t generate, with or without &lt;code dir=&quot;auto&quot;&gt;--force&lt;/code&gt;. It runs no Git command. &lt;code dir=&quot;auto&quot;&gt;--name&lt;/code&gt; sets the application’s identity, while &lt;code dir=&quot;auto&quot;&gt;--project-name&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;--root-namespace&lt;/code&gt; change only how it’s rendered.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;cratis&lt;/code&gt; is the only target that ships, and the &lt;a href=&quot;https://cratis.io/screenplay/stage/guides/build-renderer-target/&quot;&gt;renderer-target guide&lt;/a&gt; describes how another one would be built. A plan for a whole application holds the generated C# for concepts, commands, events, projections and specifications, a frontend shell, and eight backend scaffold files. With cli 3.21.0, an author model renders to this, scaffold files marked:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;out/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── .frontend/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── index.css&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── index.html&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── main.tsx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── tsconfig.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── tsconfig.node.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   └── vite.config.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── Authors/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   └── Registration/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│       ├── AuthorList/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│       │   └── AuthorList.cs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│       └── RegisterAuthor/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│           ├── RegisterAuthor.cs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│           ├── UniqueAuthorName.cs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│           └── when_registering_an_author.cs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── Common/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── AuthorId.cs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   └── AuthorName.cs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── GeneratedPolicies/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   └── Policies.cs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── src/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   └── bindings.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── .cratis-render.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── .gitignore&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── appsettings.json                           # scaffold&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── Directory.Build.props                      # scaffold&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── Directory.Build.targets                    # scaffold&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── Directory.Packages.props                   # scaffold&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── docker-compose.yml                         # scaffold&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── GeneratedPolicyRegistration.cs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── MyApplication.csproj                       # scaffold&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── MyApplication.slnx                         # scaffold&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── package.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── Program.cs                                 # scaffold&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── scene.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;└── tsconfig.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Stage 4.22.1 renders for .NET 10 and pins Arc 22.25.0 and Chronicle 19.8.1, with Components 4.14.0, Scene 4.2.0 and Fundamentals 7.19.6 for the frontend. cli 3.21.0 renders with, and &lt;code dir=&quot;auto&quot;&gt;cratis run&lt;/code&gt; starts, Stage 4.20.0, which has the same pins. The specification runner described below is the 4.22.1 image, which you pull by tag. Those versions belong to those Stage releases, and they say nothing about which other versions work.&lt;/p&gt;
&lt;p&gt;The generated code follows the model closely. A command implements &lt;code dir=&quot;auto&quot;&gt;ICanProvideEventSourceId&lt;/code&gt; from the property the model marks as &lt;code dir=&quot;auto&quot;&gt;identifier&lt;/code&gt;, its event is appended through the value &lt;code dir=&quot;auto&quot;&gt;Handle()&lt;/code&gt; returns, and the generated acceptance specs check both the event source ID and the payload.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-the-renderer-admits&quot;&gt;What the renderer admits&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;cratis&lt;/code&gt; target covers a narrow backend vertical. It admits concepts and composite types, one command-to-event production path with &lt;code dir=&quot;auto&quot;&gt;not empty&lt;/code&gt; validation, the event with its mappings, a one-instance projection, an optional by-key query over a snapshot, and the model’s specifications. Anything reachable outside that is a blocking diagnostic. The renderer doesn’t leave behavior out, and it doesn’t generate TODOs.&lt;/p&gt;
&lt;p&gt;The refusals have codes. An event with more than one declared generation gets &lt;code dir=&quot;auto&quot;&gt;STAGE-ESM-016&lt;/code&gt;. Code validations, named rule predicates and code validations on concepts get &lt;code dir=&quot;auto&quot;&gt;STAGE-ESM-005&lt;/code&gt;, because Arc doesn’t give a generated validator the received-at time in &lt;code dir=&quot;auto&quot;&gt;RuleContext.Occurred&lt;/code&gt;. A guarded production gets &lt;code dir=&quot;auto&quot;&gt;STAGE-ESM-006&lt;/code&gt;. Through &lt;code dir=&quot;auto&quot;&gt;cratis render&lt;/code&gt;, bodied reducers and opaque policies are refused as well, for now.&lt;/p&gt;
&lt;p&gt;The renderer also admits declarative unique constraints, rendered as named Chronicle constraints, and policies whose expression requires a signed-in caller. The author feature’s unique name and its sign-in rule are both inside. A policy that checks only a role or claim, or one written as code, gets &lt;code dir=&quot;auto&quot;&gt;STAGE-ESM-015&lt;/code&gt;. For that vertical you stop typing the concepts, command, event, constraint, policy, projection, query and specs by hand, and everything outside it is still code somebody writes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/refusal-gate.DV10J9pn_15m8mF.webp&quot; alt=&quot;A diagram titled The refusal gate. A .play file leads down to the first gate, validate: source is valid, which belongs to Screenplay. It leads to the second gate, plan: backend admitted, which belongs to Stage. From the second gate, an arrow labeled published leads to a manifest with a SHA-256 per file, and an arrow labeled refused leads down to a red box that reads nothing published and lists STAGE-ESM-016, STAGE-ESM-005 and STAGE-ESM-006.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2000&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Valid Screenplay is the first gate. The renderer adds its own, and a refusal publishes nothing.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A successful render is a plan that validated. Building and running the generated code is still your step. Taking a second model change through to a running application isn’t proven yet.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;hand-written-code-goes-in-customizations&quot;&gt;Hand-written code goes in Customizations/&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every render plans &lt;code dir=&quot;auto&quot;&gt;Program.cs&lt;/code&gt;, the project file, &lt;code dir=&quot;auto&quot;&gt;appsettings.json&lt;/code&gt; and the frontend shell again, so an edit to one of them is either lost or blocks the next render. The &lt;a href=&quot;https://cratis.io/screenplay/stage/guides/customize-rendered-application/&quot;&gt;customization guide&lt;/a&gt; gives hand-written code an unmanaged folder at the application root, &lt;code dir=&quot;auto&quot;&gt;Customizations/&lt;/code&gt;. The generated &lt;code dir=&quot;auto&quot;&gt;Program.cs&lt;/code&gt; ends like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ConfigureServices&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; app &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Build&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;UseDefaultFiles&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;UseStaticFiles&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;UseCratis&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;MapHealthChecks&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/healthz&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;MapFallbackToFile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/index.html&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ConfigureApplication&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;RunAsync&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Optional partial methods disappear when Customizations/Program.cs supplies no implementation.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;partial&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Program&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;partial&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ConfigureServices&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;WebApplicationBuilder&lt;/span&gt;&lt;span&gt;&lt;span&gt; builder&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;partial&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ConfigureApplication&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;WebApplication&lt;/span&gt;&lt;span&gt;&lt;span&gt; app&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;Customizations/Program.cs&lt;/code&gt; supplies either partial method, or both. The generated project imports &lt;code dir=&quot;auto&quot;&gt;Customizations/Dependencies.props&lt;/code&gt; when it exists, and the frontend loads &lt;code dir=&quot;auto&quot;&gt;Customizations/styles.css&lt;/code&gt; after its own tokens and styles. No plan contains a path under &lt;code dir=&quot;auto&quot;&gt;Customizations/&lt;/code&gt;, and the planner never reads one. A model that would generate a file there is rejected with &lt;code dir=&quot;auto&quot;&gt;STAGE-CRATIS-005&lt;/code&gt;, whatever the capitalization.&lt;/p&gt;
&lt;p&gt;That makes the folder a seam for agents too. Code written by hand, by a person or an assistant, goes in &lt;code dir=&quot;auto&quot;&gt;Customizations/&lt;/code&gt;, and an edit to a generated file shows up as a refused render, unless someone passes &lt;code dir=&quot;auto&quot;&gt;--force&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-sandbox-to-poke-at&quot;&gt;A sandbox to poke at&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/cli/reference/run/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;cratis run&lt;/code&gt;&lt;/a&gt; starts the model before anyone renders it. It needs Docker, mounts the file or folder read-only, and starts the &lt;code dir=&quot;auto&quot;&gt;cratis/stage&lt;/code&gt; image, pulled on first use at the Stage version the CLI renders with, 4.20.0 for cli 3.21.0:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis run [PATH]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Stage API           http://localhost:9090&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;API reference       http://localhost:9090/scalar/v1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Chronicle Workbench https://localhost:35000&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;HTTPS only&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Ready - event model &apos;Invoicing&apos; as event store &apos;gentle-zephyr&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Press Ctrl+C to stop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The sandbox serves an API, a generated frontend and Chronicle Workbench. The Workbench port only speaks HTTPS, because Kestrel serves gRPC and HTTP/1.1 on it through ALPN, so a plain &lt;code dir=&quot;auto&quot;&gt;http://&lt;/code&gt; request gets no reply. The certificate is a self-signed development one. The kernel stores everything in memory, so each run starts empty, and &lt;code dir=&quot;auto&quot;&gt;--rm&lt;/code&gt; removes the container afterwards. The image is built on the Chronicle kernel image, pinned to the exact development tag the host’s client was built against, because the gRPC contract changes between versions.&lt;/p&gt;
&lt;p&gt;Calling a command appends the events its &lt;code dir=&quot;auto&quot;&gt;produces&lt;/code&gt; block maps to Chronicle and echoes the payload back. A projection translation that could be read more than one way fails at registration, and the sandbox doesn’t pick one. The sandbox doesn’t yet enforce the model’s command validation or authorization, and it doesn’t evaluate query authorization. For the author feature, that means a blank name and an anonymous caller both get through. It isn’t a production host. &lt;code dir=&quot;auto&quot;&gt;cratis run&lt;/code&gt; publishes both ports on every interface. On a machine other people can reach, run the image yourself with &lt;code dir=&quot;auto&quot;&gt;-p 127.0.0.1:9090:9090 -p 127.0.0.1:35000:35000&lt;/code&gt;, as Stage’s own Docker examples do.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;running-the-models-examples&quot;&gt;Running the model’s examples&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The specification runner is for a build pipeline. It compiles the model, checks its specifications and writes a results file, and it starts no server and needs no event store. Exit code 0 means the run completed and the file was written. A failing specification still completes the run, so a pipeline has to read the outcomes.&lt;/p&gt;
&lt;p&gt;The model-first Library model in &lt;a href=&quot;https://cratis.io/samples/&quot;&gt;Cratis Samples&lt;/a&gt; has a specification of this kind:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;slice StateChange AddBook&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;command AddBook&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bookId     BookId identifier&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title      BookTitle&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;authorName AuthorName&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;validate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title not empty       message &quot;A title is required&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;authorName not empty  message &quot;An author name is required&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;produces BookAddedToCatalog&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title      = title&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;authorName = authorName&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;event BookAddedToCatalog&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title      BookTitle&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;authorName AuthorName&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;specification RejectingABookWithoutATitle&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;when AddBook&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bookId     = &quot;69064f39-6a76-458a-8a34-088eb4752650&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title      = &quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;authorName = &quot;Mara North&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;then error &quot;A title is required&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;There are two engines. The default one is structural, and it works on the model: it checks that the events and commands a specification names resolve to its slice and that the rules and expected errors agree. Each result is &lt;code dir=&quot;auto&quot;&gt;Passed&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Failed&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;Inconclusive&lt;/code&gt; (when that kind of slice can’t be verified yet), with a note saying what was and wasn’t checked. The structural engine is deprecated, stays the default for existing users, and goes away in the next major version.&lt;/p&gt;
&lt;p&gt;The semantic engine is opt-in with &lt;code dir=&quot;auto&quot;&gt;--engine semantic&lt;/code&gt;. It executes admitted commands through Arc’s in-memory command pipeline, with a fresh in-memory Chronicle event log for each specification. It handles Given events with an explicit source, direct &lt;code dir=&quot;auto&quot;&gt;when append&lt;/code&gt;, unconditional production, event assertions, and command authorization checked before dispatch with &lt;code dir=&quot;auto&quot;&gt;given caller&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;then denied&lt;/code&gt;. It also checks command rules, and unique-value and unique-event constraints before the append, which is where the author feature’s “same name twice” example belongs. Flat and event-source-keyed projections and snapshot queries by identifier are covered too.&lt;/p&gt;
&lt;p&gt;Each semantic result is &lt;code dir=&quot;auto&quot;&gt;Passed&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Failed&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Unsupported&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;Cancelled&lt;/code&gt;. Given read models, conditional production, implicit identity allocation and external effects come back as &lt;code dir=&quot;auto&quot;&gt;Unsupported&lt;/code&gt;, with the kind, the construct and the reason, and an unsupported case never counts as a pass. It all runs in memory. Nothing in a released Stage runs a &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; specification against the generated application on a live Chronicle.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;scene-a-screen-that-isnt-react&quot;&gt;Scene: a screen that isn’t React&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Scene separates what a screen is from how it’s drawn. It has three layers, and a blueprint around them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;Scene.Model&lt;/code&gt; holds screens, layouts, forms, contribution points, UI profiles and themes (NuGet &lt;code dir=&quot;auto&quot;&gt;Cratis.Scene.Model&lt;/code&gt;, npm &lt;code dir=&quot;auto&quot;&gt;@cratis/scene.model&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;Scene.Engine&lt;/code&gt; walks that graph and resolves its data, action, form and navigation bindings, and drives whichever renderer is plugged in (&lt;code dir=&quot;auto&quot;&gt;@cratis/scene.engine&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;Scene.React&lt;/code&gt; is one renderer (&lt;code dir=&quot;auto&quot;&gt;@cratis/scene.react&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;A blueprint supplies the application around the screens, meaning layouts, screen and dialog templates, shell components and themes, built from declared packages, and an application selects one.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each layer knows only the one above it. The model has no URL, CSS class, React type or HTTP verb in it. The engine imports no UI framework and never touches &lt;code dir=&quot;auto&quot;&gt;window&lt;/code&gt;, so “entering this screen runs that query once” can be a unit test instead of a browser test. The renderer is the only place a URL is built, and the model only says “navigate to” a screen. Only Stage knows what an Arc endpoint is. React is the only renderer today. Native and desktop renderers are reserved positions, and vendor component packages are adapters to the renderer contract.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/scene-seams.BBg-EJ4b_Z2r4Vuk.webp&quot; alt=&quot;A diagram titled Scene seam rules, five stacked layers from top to bottom: Screenplay model, Scene model, Scene engine, Scene.React renderer, and blueprint and packages. Labels on the arrows between the first four read: no URL in the model; no window in the engine; only the renderer builds a URL. On the right, a Stage box that reads only Stage knows an Arc endpoint has an arrow to the renderer, and a note reads: native and desktop, reserved positions.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2000&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Each layer knows only the one above it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In a &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file, a &lt;a href=&quot;https://cratis.io/screenplay/screens/&quot;&gt;screen&lt;/a&gt; lives in a &lt;code dir=&quot;auto&quot;&gt;StateView&lt;/code&gt; slice. The lending example from &lt;a href=&quot;https://blog.cratis.io/screenplay-an-event-model-you-can-compile/&quot;&gt;&lt;em&gt;Screenplay&lt;/em&gt;&lt;/a&gt; declares its screen at the level of intent, with the data and the query it comes from:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;screen OnLoan&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;data OnLoanReadModel[] via query BooksOnLoan&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A screen can add named sections, tables and summaries that fill a screen template’s slots, or drop down to a template with inline code. It never names the layout. A &lt;a href=&quot;https://cratis.io/screenplay/ui-profile/&quot;&gt;UI profile&lt;/a&gt; picks the shell once per build, which keeps the screen portable between targets.&lt;/p&gt;
&lt;p&gt;Stage resolves a render plan for each target from the UI profile, using Scene.Engine’s own rules, so the plan and the running application can’t disagree about what a screen resolves to. When a target doesn’t fully resolve, the plan says so with &lt;code dir=&quot;auto&quot;&gt;IsComplete&lt;/code&gt; set to false.&lt;/p&gt;
&lt;p&gt;In the sandbox, the host serves a browser bundle at &lt;code dir=&quot;auto&quot;&gt;/&lt;/code&gt;, fetches the Scene translation from &lt;code dir=&quot;auto&quot;&gt;/stage/scene&lt;/code&gt; (produced from the same compile as the runtime model) and draws it with &lt;code dir=&quot;auto&quot;&gt;@cratis/scene.react&lt;/code&gt;. A model without screens gets query views and command forms built from the Arc routes. Commands with string or GUID values get inputs named after the proxy’s properties and a submit action, and a combination the sandbox can’t handle shows a diagnostic instead of a partial form. Screens you’ve written take precedence. Layout and template composition, data from queries and command forms are all still growing.&lt;/p&gt;
&lt;p&gt;The model is checked for the wire shape of its screens. Nothing verifies that every component and binding resolves, and a native build doesn’t prove that a browser can submit a command and read the result back. &lt;a href=&quot;https://blog.cratis.io/live-uis-with-arc/&quot;&gt;&lt;em&gt;Live UIs&lt;/em&gt;&lt;/a&gt; covers the React side you write by hand today.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;stage-inside-cratis-studio&quot;&gt;Stage inside Cratis Studio&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Cratis Studio, live and in beta, uses Stage in two places. Play runs the model in a disposable Stage session, which is for exploring and not for hosting. When a slice is assigned to an AI agent, Cratis Studio can render it through Stage into C# for Arc and Chronicle in a connected Git repository, commit the result, and optionally let the agent refine it. Code generation is opt-in for each application. It goes one slice at a time, through Stage’s older direct-write path instead of the planned render &lt;code dir=&quot;auto&quot;&gt;cratis render&lt;/code&gt; uses. &lt;a href=&quot;https://blog.cratis.io/cratis-studio-event-modeling-together/&quot;&gt;&lt;em&gt;Cratis Studio&lt;/em&gt;&lt;/a&gt; goes through both.&lt;/p&gt;
&lt;p&gt;Stage’s vertical covers the author feature’s basic command with its validation and sign-in policy, event, unique-name constraint, projection and by-key query. The executable model refuses compliance-marked concepts (&lt;code dir=&quot;auto&quot;&gt;PLAY0268&lt;/code&gt;), so the name’s personal-data handling stays outside that vertical and its &lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt; marking is still yours to write. The C# for anything Stage refuses still goes in &lt;code dir=&quot;auto&quot;&gt;Customizations/&lt;/code&gt; or beside it, written by a person or an agent.&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href=&quot;https://blog.cratis.io/screenplay-an-event-model-you-can-compile/&quot;&gt;Part 20 — Screenplay: an event model you can compile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next: &lt;a href=&quot;https://blog.cratis.io/prologue-a-first-model-of-an-existing-system/&quot;&gt;Part 22 — Prologue: a first model of the system you already have&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>stage</category><category>scene</category><category>screenplay</category><category>event-modeling</category><category>arc</category><category>chronicle</category></item><item><title>From script to stage · Part 20: Screenplay: an event model you can compile</title><link>https://blog.cratis.io/screenplay-an-event-model-you-can-compile/</link><guid isPermaLink="true">https://blog.cratis.io/screenplay-an-event-model-you-can-compile/</guid><description>The .play language for writing an event model down next to the code, a compiler whose diagnostic codes don&apos;t change, personal data declared once on a value, and an MCP server that lets an assistant propose changes a person applies.</description><pubDate>Wed, 30 Sep 2026 09:20:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt; · Part 20 of 26&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;cratis screenplay validate&lt;/code&gt; compiles an event model on a machine where the application has never started. It reads a folder of &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; files, merges them into one application, and reports each problem with a code that stays the same from one release to the next. &lt;a href=&quot;https://cratis.io/screenplay/&quot;&gt;Screenplay&lt;/a&gt; is the language those files are written in.&lt;/p&gt;
&lt;p&gt;In a model, the author feature is a &lt;code dir=&quot;auto&quot;&gt;RegisterAuthor&lt;/code&gt; command, the &lt;code dir=&quot;auto&quot;&gt;AuthorRegistered&lt;/code&gt; event it produces, and a list of authors built from those events. The code below uses the library example from the Screenplay docs, where a member borrows a book. It has the same shape, with one command, one event and one list.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;write-the-intent-down-first&quot;&gt;Write the intent down first&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/event-modeling/&quot;&gt;Event modeling&lt;/a&gt; draws a feature as a timeline of screens, commands, events, read models and automations. For the author feature that timeline is short. A librarian fills in a form, the system records that an author was registered, and a list shows the authors. Drawing it first lets the people involved argue about cause and information flow while changing their minds is still cheap.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/model-timeline.BJvHtdXA_bOzsT.webp&quot; alt=&quot;A diagram titled One slice as an event model, drawn as five horizontal lanes. Screen: an Author form issues the RegisterAuthor command, which produces the AuthorRegistered event. AuthorRegistered is projected into the Authors view, which feeds an Author list screen. In the Event log lane, a recorded AuthorRegistered is an instance of the modeled event. A band at the bottom reads: One .play file. The compiler, Stage, Scene and specs read it.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;One slice as an event model, and the file that holds it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;An event model on a whiteboard stops matching the code soon after the code exists. Screenplay writes the model down as a versioned &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file next to the code it describes, so a change to the model turns up in the same pull request as the change to the code. The language reaches from the concepts a value is made of to the policy that decides who may run a command. It doesn’t make any one runtime the authority on what those mean, which is why several tools can read the same file.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-slice-is-the-unit&quot;&gt;The slice is the unit&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file is indented like Python and has no braces. Nesting is decided by spaces, so a tab gets a warning (&lt;code dir=&quot;auto&quot;&gt;PLAY0006&lt;/code&gt;), and comments start with &lt;code dir=&quot;auto&quot;&gt;//&lt;/code&gt;. A file declares a &lt;code dir=&quot;auto&quot;&gt;domain&lt;/code&gt;, the concepts and policies it needs, and then a &lt;code dir=&quot;auto&quot;&gt;module&lt;/code&gt; that holds &lt;a href=&quot;https://cratis.io/screenplay/slices/&quot;&gt;features and slices&lt;/a&gt;. Features nest as deep as the domain needs them to.&lt;/p&gt;
&lt;p&gt;The slice is the smallest thing a model is made of, and its four types follow event modeling:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Slice type&lt;/th&gt;
&lt;th&gt;What it models&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;StateChange&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a command that produces events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;StateView&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a query, its projection and a screen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;Automation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a reaction or a reducer that runs when something happens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;Translate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;external data captured as events&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The author feature is one &lt;code dir=&quot;auto&quot;&gt;StateChange&lt;/code&gt; slice and one &lt;code dir=&quot;auto&quot;&gt;StateView&lt;/code&gt; slice. In a lending version adapted from &lt;a href=&quot;https://cratis.io/screenplay/getting-started/&quot;&gt;Getting started&lt;/a&gt;, a member borrows a book:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;slice StateChange BorrowBook&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;command BorrowBook&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bookId     BookId identifier&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;borrowedBy MemberId&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;authorize  IsMember&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;validate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bookId not empty  message &quot;A book is required&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;produces BookBorrowed&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;for bookId&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;borrowedBy   = borrowedBy&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;event BookBorrowed&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;borrowedBy MemberId&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A reviewer sees the whole write in one diff. &lt;code dir=&quot;auto&quot;&gt;authorize IsMember&lt;/code&gt; names the policy that decides who may run the command. The &lt;code dir=&quot;auto&quot;&gt;validate&lt;/code&gt; block holds the rules, each with the message a user gets back. &lt;code dir=&quot;auto&quot;&gt;identifier&lt;/code&gt; makes the book the command’s event source, &lt;code dir=&quot;auto&quot;&gt;produces&lt;/code&gt; appends the event to it with &lt;code dir=&quot;auto&quot;&gt;for bookId&lt;/code&gt; and maps the command’s values onto the event. An event never carries its event source ID, so &lt;code dir=&quot;auto&quot;&gt;bookId&lt;/code&gt; isn’t on &lt;code dir=&quot;auto&quot;&gt;BookBorrowed&lt;/code&gt;, and marking one of an event’s properties as the &lt;code dir=&quot;auto&quot;&gt;identifier&lt;/code&gt; is a compile error (&lt;code dir=&quot;auto&quot;&gt;PLAY0019&lt;/code&gt;). When it happened travels in the event’s context.&lt;/p&gt;
&lt;p&gt;For the author feature, &lt;code dir=&quot;auto&quot;&gt;RegisterAuthor&lt;/code&gt; sits where &lt;code dir=&quot;auto&quot;&gt;BorrowBook&lt;/code&gt; is. The rule that names are unique goes in a &lt;code dir=&quot;auto&quot;&gt;constraint&lt;/code&gt;, another construct a slice can hold:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;constraint UniqueAuthorName&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;unique name on AuthorRegistered&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;message &quot;An author with this name is already registered.&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A constraint covers one event sequence in one namespace, and a namespace per tenant makes that one organization. A declared event is a statement of intent. &lt;code dir=&quot;auto&quot;&gt;AuthorRegistered&lt;/code&gt; in the model says the system is meant to be able to record it, and only the event log says it did.&lt;/p&gt;
&lt;p&gt;An event can also declare a &lt;code dir=&quot;auto&quot;&gt;generation&lt;/code&gt;. Generations have to be in range and contiguous within a slice (&lt;code dir=&quot;auto&quot;&gt;PLAY0446&lt;/code&gt; to &lt;code dir=&quot;auto&quot;&gt;PLAY0448&lt;/code&gt;), and &lt;a href=&quot;https://blog.cratis.io/four-changes-in-year-two/&quot;&gt;&lt;em&gt;Year two&lt;/em&gt;&lt;/a&gt; goes through what they’re for.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-read-side-in-the-same-file&quot;&gt;The read side in the same file&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The same page adds the read side of lending to the file:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;slice StateView OnLoan&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;query BooksOnLoan =&gt; OnLoanReadModel[]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;projection OnLoan =&gt; OnLoanReadModel&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from BookBorrowed&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;borrowedBy = borrowedBy&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;borrowedAt = $eventContext.occurred&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;screen OnLoan&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;data OnLoanReadModel[] via query BooksOnLoan&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;BooksOnLoan&lt;/code&gt; returns a list of &lt;code dir=&quot;auto&quot;&gt;OnLoanReadModel&lt;/code&gt;. The &lt;code dir=&quot;auto&quot;&gt;projection&lt;/code&gt; block is written in PDL, the projection language, embedded in the model: for each &lt;code dir=&quot;auto&quot;&gt;BookBorrowed&lt;/code&gt;, keyed by the book it happened to, copy who borrowed it and when onto the read model. The &lt;code dir=&quot;auto&quot;&gt;screen&lt;/code&gt; names the data it shows and where that data comes from, and says nothing about how it looks. &lt;a href=&quot;https://blog.cratis.io/stage-and-scene-after-a-model-compiles/&quot;&gt;&lt;em&gt;Stage and Scene&lt;/em&gt;&lt;/a&gt; picks up from that screen, and &lt;a href=&quot;https://blog.cratis.io/from-event-to-read-model/&quot;&gt;&lt;em&gt;From event to read model&lt;/em&gt;&lt;/a&gt; goes deeper on projections. PDL and CDL, a language for change data capture, are both built in and can be used on their own.&lt;/p&gt;
&lt;p&gt;A model means something without any code in it. A few points in the language accept a short inline block in &lt;code dir=&quot;auto&quot;&gt;csharp&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;typescript&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;react&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;html&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;sql&lt;/code&gt;, or a &lt;code dir=&quot;auto&quot;&gt;file&lt;/code&gt; reference to one, and Screenplay treats that as realization metadata. None of it is required. The construct keywords themselves are a closed set. An editor extension can highlight a keyword of its own, and the compiler discards it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;personal-data-is-declared-on-the-value&quot;&gt;Personal data is declared on the value&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/screenplay/concepts/&quot;&gt;Concepts&lt;/a&gt; are the typed values a model is built from, and they’re where personal data is marked:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;concept EmailAddress     : String   @pii&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;concept NationalIdNumber : String   @pii @sensitive&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;concept BankAccount : String @pii @sensitive&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pii reason &quot;Partner payout bank account - financial data. Lawful basis: contract performance / legal obligation.&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;sensitive reason &quot;Fraud-sensitive - a leaked account number enables direct financial harm.&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;EmailAddress&lt;/code&gt; is marked &lt;code dir=&quot;auto&quot;&gt;@pii&lt;/code&gt; once, and every command, event and read model property of that type inherits the mark. In the model, the decision about an email address is made in one place. Nothing released carries it into a running application yet: the executable model behind Stage refuses a concept with compliance attributes (&lt;code dir=&quot;auto&quot;&gt;PLAY0268&lt;/code&gt;), so in code the &lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt; marking is still yours to write. The indented &lt;code dir=&quot;auto&quot;&gt;reason&lt;/code&gt; lines record why, and a reason for an attribute the concept doesn’t declare is a compile error (&lt;code dir=&quot;auto&quot;&gt;PLAY0012&lt;/code&gt;). What Chronicle does with personal data, including encryption per subject and erasure, is the subject of &lt;a href=&quot;https://blog.cratis.io/personal-data-in-an-event-log/&quot;&gt;&lt;em&gt;Personal data in an event log&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;examples-live-in-the-slice&quot;&gt;Examples live in the slice&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A slice can carry &lt;a href=&quot;https://cratis.io/screenplay/specifications/&quot;&gt;specifications&lt;/a&gt;, written as Given/When/Then over facts. &lt;code dir=&quot;auto&quot;&gt;given&lt;/code&gt; sets up earlier events, a read model’s state, or the caller with their roles and claims. &lt;code dir=&quot;auto&quot;&gt;when&lt;/code&gt; runs a command or appends an event directly. &lt;code dir=&quot;auto&quot;&gt;then&lt;/code&gt; expects events, a read model, a query result, an error with its message, or &lt;code dir=&quot;auto&quot;&gt;denied&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;then denied&lt;/code&gt; expects the typed unauthorized rejection, which is a different outcome from a failed validation. A scenario for a command with an &lt;code dir=&quot;auto&quot;&gt;authorize&lt;/code&gt; line has to say who the caller is with &lt;code dir=&quot;auto&quot;&gt;given caller&lt;/code&gt;, and without it binding fails with &lt;code dir=&quot;auto&quot;&gt;PLAY0389&lt;/code&gt;. Values in an executable specification are concrete literals, objects and lists. A &lt;code dir=&quot;auto&quot;&gt;null&lt;/code&gt; in a command or event value is rejected (&lt;code dir=&quot;auto&quot;&gt;PLAY0350&lt;/code&gt;), because in Chronicle an optional fact is a separate event.&lt;/p&gt;
&lt;p&gt;For the author feature, “registering the same name twice is rejected” would have an earlier &lt;code dir=&quot;auto&quot;&gt;AuthorRegistered&lt;/code&gt; under &lt;code dir=&quot;auto&quot;&gt;given&lt;/code&gt;, the same name under &lt;code dir=&quot;auto&quot;&gt;when&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;then error&lt;/code&gt; with the message a user sees. The compiler checks that the specification fits the model. Running it is a different job, done by the Stage specification runners in &lt;a href=&quot;https://blog.cratis.io/stage-and-scene-after-a-model-compiles/&quot;&gt;&lt;em&gt;Stage and Scene&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;one-folder-is-one-application&quot;&gt;One folder is one application&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A &lt;a href=&quot;https://cratis.io/screenplay/folders/&quot;&gt;folder&lt;/a&gt; of &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; files compiles as a single application. The files are merged before any name is resolved, so an event declared in one file and produced in another resolves. Compile the same files one at a time and you get false warnings about unknown types, policies and events (&lt;code dir=&quot;auto&quot;&gt;PLAY0165&lt;/code&gt; to &lt;code dir=&quot;auto&quot;&gt;PLAY0167&lt;/code&gt;). Compile the folder and you get none. A folder can hold at most one &lt;code dir=&quot;auto&quot;&gt;domain&lt;/code&gt;, and two files that each declare one get &lt;code dir=&quot;auto&quot;&gt;PLAY0172&lt;/code&gt;: “a folder compiles to one application, which can have at most one”. Unrelated applications need separate folders.&lt;/p&gt;
&lt;p&gt;Checking the model needs nothing running:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;screenplay&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;validate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./plays&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--warnings-as-errors&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;a href=&quot;https://cratis.io/cli/reference/screenplay/&quot;&gt;CLI&lt;/a&gt; compiles every &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file under the folder as one application and prints compiler-style errors, so it fits in CI on a machine where the application has never started. By default warnings and information don’t fail the run, and an error does. &lt;code dir=&quot;auto&quot;&gt;--warnings-as-errors&lt;/code&gt; makes a warning fail it too. In CI that’s usually what you want, since a warning almost always means an undeclared name. Piped output is JSON, and &lt;code dir=&quot;auto&quot;&gt;-o plain&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;-o table&lt;/code&gt; prints something for a person. Validation checks the model only. Whether a renderer can build it is a separate question, and &lt;a href=&quot;https://blog.cratis.io/stage-and-scene-after-a-model-compiles/&quot;&gt;&lt;em&gt;Stage and Scene&lt;/em&gt;&lt;/a&gt; answers it.&lt;/p&gt;
&lt;p&gt;The compiler is also a .NET global tool, &lt;code dir=&quot;auto&quot;&gt;Cratis.Screenplay.Tool&lt;/code&gt;, which puts a &lt;code dir=&quot;auto&quot;&gt;screenplay&lt;/code&gt; command on the path, and a NuGet library, &lt;code dir=&quot;auto&quot;&gt;Cratis.Screenplay&lt;/code&gt;, for tools of your own.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;diagnostics-you-can-match-on&quot;&gt;Diagnostics you can match on&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every problem is a &lt;a href=&quot;https://cratis.io/screenplay/diagnostics/&quot;&gt;diagnostic&lt;/a&gt; with a severity, a code, a message and a line and column. The &lt;a href=&quot;https://cratis.io/screenplay/tool/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;screenplay&lt;/code&gt; tool&lt;/a&gt; prints them like a compiler:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;nested/broken.play(3,5): error PLAY0028: Unknown slice type &apos;Wat&apos; - expected StateChange, StateView, Automation or Translate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;3 |     slice Wat DoIt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|     ^&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;2 file(s) compiled - 1 error(s), 0 warning(s)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;cratis screenplay validate&lt;/code&gt; reports the same code and message, as JSON when piped. Match on the code. A message can be reworded between releases, and a code is never reused or renumbered. New codes are appended at the end whatever area they belong to, so a number tells you when a code was added, and says nothing about where it belongs. Two constructs that hit the same condition share a code. The library declares every code as a named constant in &lt;code dir=&quot;auto&quot;&gt;DiagnosticCodes&lt;/code&gt;, so a misspelled code in your own tool is a compile error. At Screenplay 4.44.1 the catalogue has 391 codes.&lt;/p&gt;
&lt;p&gt;The prefix is &lt;code dir=&quot;auto&quot;&gt;PLAY&lt;/code&gt;. Arc has a generator that writes a &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file from C# source, and its codes use &lt;code dir=&quot;auto&quot;&gt;SP&lt;/code&gt;. The two can run one after the other and land in the same build log, so the prefixes share nothing on purpose. An &lt;code dir=&quot;auto&quot;&gt;SP&lt;/code&gt; code describes what the generator couldn’t express, and a &lt;code dir=&quot;auto&quot;&gt;PLAY&lt;/code&gt; code what the compiler couldn’t read.&lt;/p&gt;
&lt;p&gt;Editors get part of the catalogue. The Monaco language service (&lt;code dir=&quot;auto&quot;&gt;@cratis/screenplay-language&lt;/code&gt;) and the VS Code extension (&lt;code dir=&quot;auto&quot;&gt;cratis.screenplay&lt;/code&gt;) check a subset of the conditions and report the compiler’s own code for each, so the squiggle under an unknown type and the CLI both say &lt;code dir=&quot;auto&quot;&gt;PLAY0165&lt;/code&gt;. A few structural editor checks in captures and projections have no code, because the compiler has no matching diagnostic. The full set runs only in the compiler.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/compile-once.BGNbMMSv_Z1eHduj.webp&quot; alt=&quot;A diagram titled Compile once, report by code. On the left, a box labeled one application holds three .play files and a note, at most one domain. Each file has an arrow to the compiler, cratis screenplay validate. From the compiler, arrows lead to a syntax tree; to printed .play text, which has an arrow labeled round trip back to the tree; and to a PLAY0167 warning and a PLAY0028 error, grouped under a padlock labeled code never changes.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2000&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A folder compiles as one application, and every finding carries a code that doesn’t change.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-tree-that-prints-back-to-the-same-text&quot;&gt;A tree that prints back to the same text&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;As a library, &lt;code dir=&quot;auto&quot;&gt;new ScreenplayCompiler().Compile(source)&lt;/code&gt; returns a result with &lt;code dir=&quot;auto&quot;&gt;Success&lt;/code&gt;, the syntax tree in &lt;code dir=&quot;auto&quot;&gt;Value&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;Diagnostics&lt;/code&gt;. &lt;code dir=&quot;auto&quot;&gt;PlayFileCompiler&lt;/code&gt; compiles a file or a folder. The nodes of the tree are immutable records, and each carries its location in the source. &lt;code dir=&quot;auto&quot;&gt;ScreenplaySyntaxWalker&lt;/code&gt; is the supported way to traverse the tree, and a new kind of node arrives as a new &lt;code dir=&quot;auto&quot;&gt;Visit&lt;/code&gt; method whose default keeps walking.&lt;/p&gt;
&lt;p&gt;New node kinds can be added, and an unknown form never throws. Enum members keep their numeric values, and public constants, every diagnostic code among them, never change. A node grows by an init-only property, never by a new constructor parameter. The positional parameter list, an exhaustive &lt;code dir=&quot;auto&quot;&gt;switch&lt;/code&gt; over a base type and the element types of collections are outside those promises, so tools shouldn’t depend on them.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;ScreenplayPrinter&lt;/code&gt; turns a tree back into &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; text, with two spaces per level, escaped strings and numbers that print the same in every culture. &lt;a href=&quot;https://cratis.io/screenplay/printing/&quot;&gt;Printing&lt;/a&gt; a compiled document gives an equivalent tree, and printing that again gives identical text. Comments survive and surrounding whitespace is normalized. That round trip keeps a generated &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file readable as a diff, whichever tool wrote it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;valid-and-runnable-are-two-verdicts&quot;&gt;Valid and runnable are two verdicts&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A document that compiles is valid Screenplay. Whether it can execute is decided separately. An executable semantic model is bound from the checked model and versioned on its own, and handler code blocks executable admission (&lt;code dir=&quot;auto&quot;&gt;PLAY0268&lt;/code&gt;). The reference executor can’t run inline or file-backed code, so anything that depends on it comes back as unsupported. The standalone tool and &lt;code dir=&quot;auto&quot;&gt;PlayFileCompiler&lt;/code&gt; compile syntax only and don’t read attached files. &lt;code dir=&quot;auto&quot;&gt;cratis screenplay validate&lt;/code&gt; gives the first verdict. The Stage renderer, its sandbox and its specification runners each give part of the second, for the constructs they support.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;an-assistant-on-the-model&quot;&gt;An assistant on the model&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The &lt;a href=&quot;https://cratis.io/screenplay/mcp/&quot;&gt;Screenplay MCP server&lt;/a&gt; lets an assistant work on the same files a person does. It ships in the .NET tool and in the native CLI as &lt;a href=&quot;https://cratis.io/cli/reference/screenplay-mcp/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;cratis screenplay mcp&lt;/code&gt;&lt;/a&gt;, which needs no separate .NET install. The CLI embeds its own copy of the server, so it can trail the newest Screenplay release. cli 3.21.0 embeds server 4.43.0, while the newest Screenplay release is 4.44.1. For VS Code, the &lt;a href=&quot;https://cratis.io/screenplay/install-mcp/&quot;&gt;install guide&lt;/a&gt; points the tool at a folder:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;servers&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;screenplay&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;stdio&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;screenplay&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;mcp&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;${workspaceFolder}/specifications&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;With the CLI, it’s &lt;code dir=&quot;auto&quot;&gt;cratis screenplay mcp .cratis/screenplay&lt;/code&gt;. &lt;code dir=&quot;auto&quot;&gt;cratis ai install&lt;/code&gt; registers that form, with &lt;code dir=&quot;auto&quot;&gt;.cratis/screenplay&lt;/code&gt; as the default root, when the profile you pick includes Screenplay. Installing creates the root folder if it’s missing and never creates a &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file. Starting the server creates nothing, and a missing root is an error.&lt;/p&gt;
&lt;p&gt;At cli 3.21.0 the server lists 27 tools, and most of them read. &lt;code dir=&quot;auto&quot;&gt;describe-application&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;find-references&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;dependencies&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;find-assertion-gaps&lt;/code&gt; navigate the model, and &lt;code dir=&quot;auto&quot;&gt;diagnostics&lt;/code&gt; returns the compiler’s findings. On a small library model with two slices, &lt;code dir=&quot;auto&quot;&gt;describe-application&lt;/code&gt; reports one file, 13 declarations, one module, one feature, two slices and no diagnostics, with a hash of the source revision. &lt;code dir=&quot;auto&quot;&gt;find-assertion-gaps&lt;/code&gt; lists both slices as having no specifications, and notes that a gap isn’t proof of missing runtime test coverage.&lt;/p&gt;
&lt;p&gt;Changes go through proposals. &lt;code dir=&quot;auto&quot;&gt;propose-ast&lt;/code&gt; takes a typed edit whose shape comes from &lt;code dir=&quot;auto&quot;&gt;syntax-schema&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;propose-rename&lt;/code&gt; renames with the whole model in view, and &lt;code dir=&quot;auto&quot;&gt;read-proposal&lt;/code&gt; shows the exact bytes that would be written. A proposal is tied to the revision it was made against, so one made before somebody else’s edit is rejected when it’s applied. Only two tools write files, &lt;code dir=&quot;auto&quot;&gt;apply&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;recover-workspace&lt;/code&gt;, which is where your client’s approval prompt belongs. Keep approval on for both.&lt;/p&gt;
&lt;p&gt;One root is one application, and symlinks are rejected. Paging and oversize results fail loudly instead of truncating. Identities live in &lt;code dir=&quot;auto&quot;&gt;.screenplay/identities.json&lt;/code&gt;, which belongs in source control. An interrupted &lt;code dir=&quot;auto&quot;&gt;apply&lt;/code&gt; leaves a &lt;code dir=&quot;auto&quot;&gt;pending.json&lt;/code&gt; that blocks reads and edits until someone inspects it and recovers the workspace explicitly, since rollback isn’t crash-atomic. None of the tools runs specifications, so an assertion being present says nothing about runtime coverage.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/mcp-short-leash.cuvQ_35__2lUTYd.webp&quot; alt=&quot;A diagram titled An assistant, on a short leash. On the left, an assistant with two arrows into the Screenplay MCP server. One leads to a box labeled read and navigate, listing describe-application, find-references and find-assertion-gaps. The other leads to propose, with propose-ast and read-proposal, then through a gate labeled client approval prompt to apply. Below them sits recover-workspace. Only apply and recover-workspace have arrows to the model folder, with .screenplay/identities.json. A band at the bottom reads: No tool runs specifications.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Most tools read. Only apply and recover-workspace write, so that’s where your client’s approval prompt belongs.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The server checks what reaches the files. It doesn’t make an assistant’s model correct. &lt;a href=&quot;https://blog.cratis.io/ai-across-the-cratis-stack/&quot;&gt;&lt;em&gt;AI across the stack&lt;/em&gt;&lt;/a&gt; puts it next to Chronicle MCP and the Cratis AI corpus.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-reads-a-play-file&quot;&gt;What reads a .play file&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file is an input for several tools. &lt;code dir=&quot;auto&quot;&gt;cratis render&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;cratis run&lt;/code&gt; hand it to Stage (at cli 3.21.0, Stage 4.20.0), which renders a narrow backend vertical into C# on Arc and Chronicle, starts a disposable sandbox and checks the model’s specifications. A &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file is a good home for a model once someone has written it down. Getting there is usually a conversation between several people. A Cratis Studio event model can be exported as Screenplay text, Mermaid or JSON and imported back from Screenplay with a preview. That round trip isn’t lossless, and Studio is live and in beta. &lt;code dir=&quot;auto&quot;&gt;cratis screenplay generate&lt;/code&gt; goes from Arc, Marten or Critter Stack source in C# to a &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; file, and Prologue drafts one from a running system. &lt;a href=&quot;https://blog.cratis.io/stage-and-scene-after-a-model-compiles/&quot;&gt;&lt;em&gt;Stage and Scene&lt;/em&gt;&lt;/a&gt;, &lt;a href=&quot;https://blog.cratis.io/prologue-a-first-model-of-an-existing-system/&quot;&gt;&lt;em&gt;Prologue&lt;/em&gt;&lt;/a&gt; and &lt;a href=&quot;https://blog.cratis.io/cratis-studio-event-modeling-together/&quot;&gt;&lt;em&gt;Cratis Studio&lt;/em&gt;&lt;/a&gt; each take one of those. Everything above works without any of them, because &lt;code dir=&quot;auto&quot;&gt;validate&lt;/code&gt; only reads files. And outside what Stage renders, nothing checks hand-written code against the model.&lt;/p&gt;
&lt;p&gt;There’s one rule in this layer we care about more than any single feature. Producers and consumers of a model have to declare what they can handle and expose what they can’t represent, instead of dropping it silently. A tool that quietly loses part of your model is worse than one that refuses it, so Stage publishes nothing when a model reaches past what it supports, and Studio’s import warns about anything it can’t bring in.&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href=&quot;https://blog.cratis.io/chronicle-jobs-scale-and-performance/&quot;&gt;Part 19 — Chronicle jobs, scale and performance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next: &lt;a href=&quot;https://blog.cratis.io/stage-and-scene-after-a-model-compiles/&quot;&gt;Part 21 — Stage and Scene: what happens after a model compiles&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>screenplay</category><category>event-modeling</category><category>cli</category><category>mcp</category><category>ai</category></item><item><title>From script to stage · Part 19: Chronicle jobs, scale and performance</title><link>https://blog.cratis.io/chronicle-jobs-scale-and-performance/</link><guid isPermaLink="true">https://blog.cratis.io/chronicle-jobs-scale-and-performance/</guid><description>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&apos;t promise.</description><pubDate>Wed, 30 Sep 2026 09:19:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt; · Part 19 of 26&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Replaying a projection in Chronicle runs as a job, and a job can be stopped halfway and resumed later from where it stopped, as long as the application that owns the observer is connected. If the server crashes instead, the replay picks up from its last checkpoint, and the batches it processed since that checkpoint are delivered a second time.&lt;/p&gt;
&lt;p&gt;Change the projection behind the &lt;code dir=&quot;auto&quot;&gt;Author&lt;/code&gt; read model and deploy it, and Chronicle decides whether the change needs a replay. The job’s progress and the cost of restarting it matter once that author list has a long history.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-runs-as-a-job&quot;&gt;What runs as a job&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A job is long-running work the kernel does in the background, split into steps. These are the kinds it runs as of Chronicle 19.23.1:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Replay observer&lt;/td&gt;
&lt;td&gt;Reprocesses an observer from the start of the log&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replay observer partition&lt;/td&gt;
&lt;td&gt;Reprocesses one partition of an observer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Catch up observer, catch up partition&lt;/td&gt;
&lt;td&gt;Brings an observer or one partition up to the tail, after downtime, a new subscription or a full queue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retry failed partition&lt;/td&gt;
&lt;td&gt;Makes one more attempt at a failed partition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Migrate existing events for type&lt;/td&gt;
&lt;td&gt;Back-fills a new generation of an event type across the stored events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reindex constraints&lt;/td&gt;
&lt;td&gt;Rebuilds a constraint’s index after its definition changed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A replay isn’t split the same way for every observer. A projection’s replay is a single step that reads the log in global order, from the first event to the last. A reducer’s or reactor’s replay is one step per event source, taken from the observer’s index of keys, so for the author feature that’s one step per author.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/replay-job-anatomy.BLj7Ow7D_ZIHh1d.webp&quot; alt=&quot;A diagram titled Anatomy of a replay job, two frames side by side. Projection replay: one step that reads the log in global order, first to last. Reducer or reactor replay: steps labeled author 1, author 2, author 3 and so on, each with an arrow into a box labeled parallel step slots, and under them, one step per event source. A band at the bottom reads: Every step checkpoints its position as it goes.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2000&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A projection replays in one ordered step; a reducer or reactor replays one event source per step.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Two operations that sound like background work aren’t jobs. Erasing a person’s key is a synchronous call from the client, and webhooks are delivered by an observer.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-jobs-life&quot;&gt;A job’s life&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A job moves through &lt;code dir=&quot;auto&quot;&gt;PreparingJob&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;PreparingSteps&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;StartingSteps&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Running&lt;/code&gt;, and ends as &lt;code dir=&quot;auto&quot;&gt;CompletedSuccessfully&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;CompletedWithFailures&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Stopped&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;Failed&lt;/code&gt;. &lt;code dir=&quot;auto&quot;&gt;Removing&lt;/code&gt; is the state it passes through on its way out. Each step has a status of its own, from &lt;code dir=&quot;auto&quot;&gt;Scheduled&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Running&lt;/code&gt; through to completed, stopped or failed.&lt;/p&gt;
&lt;p&gt;The controls are stop and resume. Stopping keeps the job’s progress, and resuming a stopped job continues where it left off. A job that ended as &lt;code dir=&quot;auto&quot;&gt;Failed&lt;/code&gt; doesn’t resume.&lt;/p&gt;
&lt;p&gt;Stop and resume have two rules for replays. Resuming a replay needs the observer to be subscribed, so a replay you stopped can’t continue while the application is down. And starting a replay deletes the observer’s other jobs, so a stopped replay doesn’t survive the next one.&lt;/p&gt;
&lt;p&gt;The kernel cleans up after itself on a schedule. A job stuck in preparation with no steps is removed after an hour. Every 60 seconds a watchdog checks that running replay and catch-up jobs are still making progress, and an observer that stays stuck preparing a catch-up is quarantined after five recovery attempts.&lt;/p&gt;
&lt;p&gt;Jobs belong to an event store and a namespace, like the observers they work for.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-a-crash-delivers-twice&quot;&gt;What a crash delivers twice&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A replay or catch-up step doesn’t write its position after every event. It makes a checkpoint durable every 100 batches or every five seconds, whichever comes first, and on every status change. After a crash, the step resumes from its last checkpoint and reads forward again, so everything between that checkpoint and the crash is delivered a second time.&lt;/p&gt;
&lt;p&gt;For a projection that only sets values, a second delivery writes the same values again. For one that accumulates, such as a count, it would change the result. Chronicle narrows that in two ways. For read models keyed by the event source ID, the sink write is guarded by a watermark by default, so a batch that was already applied isn’t applied again. A projection that combines several event sources into one document, through a join, a constant key or a parent, can’t be guarded that way. Its per-partition steps checkpoint after every batch, which keeps what’s read again to the batch in flight, and that doesn’t make it idempotent.&lt;/p&gt;
&lt;p&gt;Reactors have no such guard, and they don’t need a special case either. Chronicle can deliver an event to an observer more than once. A timeout, a retry and a replay can all deliver an event again, so a reactor has to cope with running twice regardless, and a crash during a job is one more way that happens. For the author feature, a reactor that sends a welcome email needs to know whether it already sent one. &lt;a href=&quot;https://blog.cratis.io/reacting-to-facts/&quot;&gt;&lt;em&gt;Reacting to facts&lt;/em&gt;&lt;/a&gt; goes through delivery identity and &lt;code dir=&quot;auto&quot;&gt;[OnceOnly]&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-many-steps-run-at-once&quot;&gt;How many steps run at once&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/chronicle/hosting/configuration/job-throttling/&quot;&gt;Job throttling&lt;/a&gt; caps the number of job steps running in parallel. Each step takes a slot before it runs, waits in a queue when none is free, and gives the slot back when it finishes, successfully or not. The default is the processor count minus one, with a minimum of one, so an 8-core machine runs up to seven steps at a time.&lt;/p&gt;
&lt;p&gt;In &lt;code dir=&quot;auto&quot;&gt;chronicle.json&lt;/code&gt; it goes at the top level:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Jobs&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;MaxParallelSteps&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;As an environment variable, it’s &lt;code dir=&quot;auto&quot;&gt;Cratis__Chronicle__Jobs__MaxParallelSteps=4&lt;/code&gt;. Don’t nest the section under &lt;code dir=&quot;auto&quot;&gt;Cratis&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Chronicle&lt;/code&gt; inside &lt;code dir=&quot;auto&quot;&gt;chronicle.json&lt;/code&gt;. Chronicle already publishes that file under the &lt;code dir=&quot;auto&quot;&gt;Cratis:Chronicle:&lt;/code&gt; path, so a nested copy binds to the wrong path and is silently ignored.&lt;/p&gt;
&lt;p&gt;There’s a second ceiling in the kernel’s own code. The effective cap is the smaller of &lt;code dir=&quot;auto&quot;&gt;MaxParallelSteps&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Observers.MaxConcurrentPartitions&lt;/code&gt;, which defaults to 32 and isn’t in the configuration reference. On a machine with 34 or more cores the default stops tracking the processor count, and a configured value above 32 is capped at 32 as well.&lt;/p&gt;
&lt;p&gt;For the author feature, that cap matters for reducers and reactors, whose replays are made of many small steps. A projection’s replay is one step, so the cap doesn’t speed it up.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;watching-and-steering-jobs&quot;&gt;Watching and steering jobs&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The Workbench’s Jobs page shows each job’s type, status and progress, with Stop, Resume and Delete. The terminal workbench has a Jobs view where &lt;code dir=&quot;auto&quot;&gt;S&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;U&lt;/code&gt; stop and resume. From the &lt;a href=&quot;https://cratis.io/cli/chronicle/jobs/&quot;&gt;CLI&lt;/a&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;jobs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;jobs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;JOB_ID&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;jobs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stop&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;JOB_ID&gt;&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;# progress is preserved; needs --yes non-interactively&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;jobs get&lt;/code&gt; shows each step with its own progress and any error. The CLI has no command to delete a job. &lt;a href=&quot;https://cratis.io/chronicle-mcp/&quot;&gt;Chronicle MCP&lt;/a&gt; has three tools for stopping, resuming and deleting jobs, and they’re the only tools in it that change anything on the server. Chronicle MCP can read personal data and isn’t read-only, so scope what you connect it to. The jobs API lists jobs and their steps, stops, resumes and deletes them, and the .NET client exposes it as &lt;code dir=&quot;auto&quot;&gt;IJobs&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;replays-chronicle-recommends-instead-of-running&quot;&gt;Replays Chronicle recommends instead of running&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The classification and &lt;code dir=&quot;auto&quot;&gt;definitionEvolution&lt;/code&gt; policy in &lt;a href=&quot;https://blog.cratis.io/from-event-to-read-model/&quot;&gt;&lt;em&gt;From event to read model&lt;/em&gt;&lt;/a&gt; determine whether a changed definition starts a job or leaves a recommendation. To let partial replays run while reserving full ones for a person:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;observers&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;maxRetryAttempts&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;definitionEvolution&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;PartialOnly&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;As of Chronicle 19.23.1, a recommendation is always a replay candidate. It’s filed for a projection or reducer definition change the policy didn’t apply, for a reactor or webhook definition change when &lt;code dir=&quot;auto&quot;&gt;replayOnDefinitionChange&lt;/code&gt; is off, and for a retired projection that wrote to the same container as the one replacing it. Performing a recommendation replays the observer server-side and removes the recommendation when the replay succeeds. Ignoring it drops it.&lt;/p&gt;
&lt;p&gt;We’d set &lt;code dir=&quot;auto&quot;&gt;PartialOnly&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;Manual&lt;/code&gt; on a store where a full replay of a large projection is expensive, so that a deploy doesn’t start one on its own and a person decides when it runs.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-events-reach-observers&quot;&gt;How events reach observers&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;An append doesn’t wait for observers. Once an event is in the log, it’s handed to observers through queues, and the number of queue loops is &lt;code dir=&quot;auto&quot;&gt;events.queues&lt;/code&gt;, 2 by default. It’s the setting to raise for higher expected event throughput, and it comes without a sizing rule.&lt;/p&gt;
&lt;p&gt;Each queue is a bounded channel, 2,000 batches by default, set by &lt;code dir=&quot;auto&quot;&gt;Events.QueueBoundedCapacity&lt;/code&gt; in the kernel’s configuration code. When a channel is full, the append still doesn’t wait. The observers on that queue are moved to their catch-up path, which reads the log again from each observer’s persisted position, and catching up is a job like the others. Setting the capacity to 0 makes the channel unbounded, and then nothing is ever moved. The setting isn’t in the configuration reference, so treat it as an internal.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/appends-dont-wait.Di4WiIBn_RBUlz.webp&quot; alt=&quot;A diagram titled Appends don’t wait. Writers lead to the event log, and the event log leads to a bounded queue. A note under the writers reads: the append returns either way. From the queue, an arrow labeled queue has room leads straight to observers, and an arrow labeled queue full leads to a catch-up job that reads from each observer’s persisted position and delivers to the observers.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;1900&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A full queue sends observers to catch up from the log; the append doesn’t block.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The append path does one related piece of housekeeping. The kernel looks for changed constraint definitions at most once a second per sequence, because checking on every append would tie append throughput to how fast one cluster-wide grain can take its turns.&lt;/p&gt;
&lt;p&gt;On the observer side, order is per event source. Live delivery hands events over in sequence order, one partition’s batch at a time, to every observer that wants it at once. Partitions run side by side only inside jobs, where a reducer’s or reactor’s replay or catch-up is one step per event source under the step cap. A failed partition stops alone and retries with backoff while the rest continue. Across partitions there’s no order. An event can reach an observer more than once, and projections always run asynchronously, which is Chronicle’s &lt;a href=&quot;https://cratis.io/chronicle/projections/eventual-consistency/&quot;&gt;eventual consistency&lt;/a&gt; model.&lt;/p&gt;
&lt;p&gt;When several instances of one application connect, the observer fans delivery out across them. The default, &lt;code dir=&quot;auto&quot;&gt;round-robin&lt;/code&gt;, assigns each partition to one instance by its key, so a partition stays with the same instance and keeps its order. &lt;code dir=&quot;auto&quot;&gt;random&lt;/code&gt; picks an instance per delivery. When an instance disconnects, it’s dropped at once and its partitions go to the others.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;subscriberTimeout&lt;/code&gt;, 30 seconds by default, bounds how long an observer waits for a subscriber to answer a batch. Giving up abandons the wait, and the subscriber may still be working on the batch when it’s delivered again on retry. A partition whose last attempt was a &lt;code dir=&quot;auto&quot;&gt;Timeout&lt;/code&gt; doesn’t count toward the quarantine thresholds, because a timeout says the system was congested, and congestion clears.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;read-cost&quot;&gt;Read cost&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A read model is &lt;a href=&quot;https://cratis.io/chronicle/read-models/consistency/&quot;&gt;materialized by default&lt;/a&gt;. The projection writes it as events arrive, a read is a lookup, and the read is eventually consistent. A passive read model computes its state from the log on every read instead, so it’s always current, and its cost grows with the history of the key being read. For the &lt;code dir=&quot;auto&quot;&gt;Author&lt;/code&gt; list, materialized is the obvious fit. A passive read suits something read rarely, over a short history, where being current matters more than the cost.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;running-more-than-one-node&quot;&gt;Running more than one node&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.cratis.io/inside-chronicle/&quot;&gt;&lt;em&gt;Inside Chronicle&lt;/em&gt;&lt;/a&gt; covers the kernel architecture and why two servers on default clustering don’t form one cluster. That architecture makes no scale or throughput guarantee.&lt;/p&gt;
&lt;p&gt;The server logs a warning when it sees localhost clustering against storage that isn’t local, and it starts anyway. Every node in a multi-node deployment has to set the clustering type to &lt;code dir=&quot;auto&quot;&gt;MongoDB&lt;/code&gt;, with the same cluster ID and service ID:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Cratis__Chronicle__Clustering__Type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;MongoDB&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Cratis__Chronicle__Clustering__ClusterId&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Cratis__Chronicle__Clustering__ServiceId&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;There are only two clustering types, &lt;code dir=&quot;auto&quot;&gt;Localhost&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;MongoDB&lt;/code&gt;, and the second keeps cluster membership in the configured MongoDB storage. MongoDB has to run as a replica set, because appends use transactions. The nodes share the same encryption certificate and storage, the Orleans ports, 11111 and 30000 by default, are open between them, and port 35000 sits behind a load balancer. Nodes on one host need different silo and gateway ports and an explicitly set advertised address. The &lt;a href=&quot;https://cratis.io/chronicle/hosting/production/&quot;&gt;production guide&lt;/a&gt; has a two-node Compose example. In-memory storage doesn’t share state across a cluster.&lt;/p&gt;
&lt;p&gt;Nodes can take roles. &lt;code dir=&quot;auto&quot;&gt;roles.eventSequences&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;roles.observers&lt;/code&gt;, both on by default, let you separate the nodes that take appends from the nodes that run observers. Role placement is best-effort today. A node only sees its own role settings, so a grain can still land on another node whose role is switched off. Treat the roles as a scheduling hint, and run every node with every role when every node must accept every grain.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/two-servers-two-clusters.CmBs6WgO_2fs88w.webp&quot; alt=&quot;A diagram titled Two servers, one database, two panels. Left panel, Localhost default: two nodes, each inside its own cluster, both with arrows to one MongoDB, and a note reading two clusters, no startup error. Right panel, Clustering type MongoDB: a load balancer on port 35000; one cluster with the same IDs, holding two nodes and Orleans ports 11111 and 30000; a shared MongoDB replica set and encryption certificate; and a row reading roles: a scheduling hint. A band underneath reads: Tested in one process. What a second node adds hasn’t been measured and published.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The default gives two servers two clusters; a shared cluster needs the MongoDB type on every node.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;What’s been shown about a cluster is limited. Chronicle’s integration tests run an Orleans cluster of two silos in one process, with event sequences on one and observers on the other, and check that a reactor, a reducer and a projection work across it. A clustering benchmark suite compares one silo, two silos and two silos with split roles, and it runs on demand with no published results. Its silos share one machine, so it wouldn’t show ideal scaling even when it runs. Chronicle can run as a multi-node Orleans cluster, and what a second node adds in throughput hasn’t been measured and published.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-the-benchmarks-measure&quot;&gt;What the benchmarks measure&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A single-node benchmark suite runs every night on a GitHub-hosted Ubuntu runner, against the development container with MongoDB inside it. The series on the &lt;a href=&quot;https://cratis.io/chronicle/benchmarks/&quot;&gt;benchmarks page&lt;/a&gt; ends at a run from August 2026, and the nightly runs since then haven’t been added to it. Each benchmark runs one to three warmups and five or ten measured iterations. The last entry in that series gives these mean times:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benchmark&lt;/th&gt;
&lt;th&gt;Mean&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Append one event&lt;/td&gt;
&lt;td&gt;8.98 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Append 10 / 100 / 1,000 events in one &lt;code dir=&quot;auto&quot;&gt;AppendMany&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;12.9 / 34.1 / 239.9 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read a materialized instance, 10 / 100 events of history&lt;/td&gt;
&lt;td&gt;4.30 / 3.49 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read a passive instance, 10 / 100 events of history&lt;/td&gt;
&lt;td&gt;7.77 / 11.67 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replay a projection over 1,000 / 10,000 events&lt;/td&gt;
&lt;td&gt;328.9 / 1,508.0 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;One &lt;code dir=&quot;auto&quot;&gt;AppendMany&lt;/code&gt; of ten events takes 12.9 ms, against 8.98 ms for a single append. In these runs a materialized read didn’t grow with the history, and a passive read did.&lt;/p&gt;
&lt;p&gt;The limits are as specific as the numbers. It’s one runner type on shared hosted hardware, and per-commit numbers from hosted runners are noisy. The observer benchmarks, projecting, reducing and reacting to events, append everything to one event source, so they never exercise partitions running in parallel, and their window includes the append and a completion poll that puts a floor of roughly 60 to 70 ms under every result. None of it is an events-per-second figure, and there are no benchmarks for comparing storage providers, for job step parallelism, or for what happens when a queue fills up.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;tuning-a-production-deployment&quot;&gt;Tuning a production deployment&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Start with the deployment and restore requirements in &lt;a href=&quot;https://blog.cratis.io/running-chronicle-in-production/&quot;&gt;&lt;em&gt;Running Chronicle and a Cratis application in production&lt;/em&gt;&lt;/a&gt;, and make every observer idempotent before tuning delivery.&lt;/p&gt;
&lt;p&gt;The tuning knobs are &lt;code dir=&quot;auto&quot;&gt;observers.subscriberTimeout&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;observers.maxRetryAttempts&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;events.queues&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;jobs.maxParallelSteps&lt;/code&gt;. None of them comes with a sizing rule. Chronicle’s configuration examples show 8 queues and a 5-second subscriber timeout as illustrations, and the right values for a given system come from measuring that system.&lt;/p&gt;
&lt;p&gt;Watch the &lt;code dir=&quot;auto&quot;&gt;Author&lt;/code&gt; replay as a job, allow for the batches a crash can repeat, and measure what a second server adds before relying on it.&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href=&quot;https://blog.cratis.io/running-chronicle-in-production/&quot;&gt;Part 18 — Running Chronicle and a Cratis application in production&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next: &lt;a href=&quot;https://blog.cratis.io/screenplay-an-event-model-you-can-compile/&quot;&gt;Part 20 — Screenplay: an event model you can compile&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>chronicle</category><category>operations</category><category>performance</category><category>clustering</category><category>event-sourcing</category></item><item><title>From script to stage · Part 18: Running Chronicle and a Cratis application in production</title><link>https://blog.cratis.io/running-chronicle-in-production/</link><guid isPermaLink="true">https://blog.cratis.io/running-chronicle-in-production/</guid><description>What Chronicle&apos;s production image won&apos;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.</description><pubDate>Wed, 30 Sep 2026 09:18:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt; · Part 18 of 26&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A Compose file that runs Chronicle happily on &lt;code dir=&quot;auto&quot;&gt;cratis/chronicle:latest-development&lt;/code&gt; stops at startup when you change the tag to &lt;code dir=&quot;auto&quot;&gt;latest&lt;/code&gt;. The development images generate their own TLS certificate and fall back to throwaway keys for the built-in OAuth authority, and &lt;code dir=&quot;auto&quot;&gt;latest-development&lt;/code&gt; bundles a database as well. The production image does none of that, and no environment variable changes it back, because the production code paths are chosen by a compile-time symbol that only the development builds define.&lt;/p&gt;
&lt;p&gt;On a laptop, the author feature ran on one development container. In production it runs on a kernel that someone configures and keeps running, next to an application that has to trust the right server and report what it does.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-the-production-image-wont-start-without&quot;&gt;What the production image won’t start without&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The &lt;a href=&quot;https://cratis.io/chronicle/hosting/production/&quot;&gt;production guide&lt;/a&gt; names three settings the production path needs, and a missing certificate stops it at startup. Every setting can come from &lt;code dir=&quot;auto&quot;&gt;/app/chronicle.json&lt;/code&gt; or from an environment variable with the &lt;code dir=&quot;auto&quot;&gt;Cratis__Chronicle__&lt;/code&gt; prefix, and the &lt;a href=&quot;https://cratis.io/chronicle/hosting/configuration/configuration-precedence/&quot;&gt;environment variable wins&lt;/a&gt; when both are set:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;35000:35000&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/path/to/chronicle.json:/app/chronicle.json:ro&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/path/to/certs:/certs:ro&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-e&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;Cratis__Chronicle__Storage__ConnectionDetails=&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;mongodb://mongodb:27017/?directConnection=true&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-e&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis__Chronicle__Tls__CertificatePath=/certs/chronicle.pfx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-e&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;Cratis__Chronicle__Tls__CertificatePassword=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;$CHRONICLE_CERTIFICATE_PASSWORD&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-e&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis__Chronicle__EncryptionCertificate__CertificatePath=/certs/encryption-cert.pfx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-e&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;Cratis__Chronicle__EncryptionCertificate__CertificatePassword=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;$ENCRYPTION_CERTIFICATE_PASSWORD&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;cratis/chronicle:19.23.1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The storage connection defaults to an empty string, so there’s nothing to fall back to. An unset storage type means MongoDB. The TLS certificate is for port 35000, where gRPC for the clients and HTTP/1.1 for the REST API, the Workbench, OAuth and the health endpoint share one TLS port. The &lt;a href=&quot;https://cratis.io/chronicle/hosting/encryption-certificate/&quot;&gt;encryption certificate&lt;/a&gt; protects the signing and encryption keys of the internal OAuth authority. You can do without it only when that authority isn’t in use, either because you’ve pointed Chronicle at an external authority or turned the &lt;code dir=&quot;auto&quot;&gt;oAuthAuthority&lt;/code&gt; feature off, and encrypting a value later still needs a certificate. A certificate path that points at a missing file stops startup with &lt;code dir=&quot;auto&quot;&gt;EncryptionCertificateFileNotFound&lt;/code&gt;, so a typo can’t leave you running without one.&lt;/p&gt;
&lt;p&gt;Pin the version, as above. &lt;code dir=&quot;auto&quot;&gt;latest&lt;/code&gt; floats, and so does the image &lt;a href=&quot;https://cratis.io/chronicle/hosting/aspire/&quot;&gt;Aspire&lt;/a&gt; picks for you. In publish mode, &lt;code dir=&quot;auto&quot;&gt;AddCratisChronicle()&lt;/code&gt; selects the production image whatever else you configure, so it needs &lt;code dir=&quot;auto&quot;&gt;WithTlsCertificate&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;WithEncryptionCertificate&lt;/code&gt; before it’ll start, and &lt;code dir=&quot;auto&quot;&gt;WithImageTag&lt;/code&gt; to stop two deployments of the same AppHost from landing on different versions.&lt;/p&gt;
&lt;p&gt;The production image is built on a chiseled .NET base with no shell, &lt;code dir=&quot;auto&quot;&gt;curl&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;wget&lt;/code&gt;. A Docker &lt;code dir=&quot;auto&quot;&gt;HEALTHCHECK&lt;/code&gt; runs inside the container, so it fails every time and reports a healthy Chronicle as unhealthy. Probe &lt;code dir=&quot;auto&quot;&gt;/health&lt;/code&gt; from your orchestrator instead. If the prober can’t handle the TLS certificate, &lt;code dir=&quot;auto&quot;&gt;Cratis__Chronicle__Health__Port=8080&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Cratis__Chronicle__Health__Tls=false&lt;/code&gt; give it a plain HTTP &lt;a href=&quot;https://cratis.io/chronicle/hosting/configuration/health-endpoint/&quot;&gt;health port&lt;/a&gt;. That port serves the other HTTP/1.1 endpoints as well unless you set &lt;code dir=&quot;auto&quot;&gt;health.exclusive&lt;/code&gt;, so keep it internal.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;choosing-where-the-events-live&quot;&gt;Choosing where the events live&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Chronicle has pluggable &lt;a href=&quot;https://cratis.io/chronicle/hosting/configuration/storage/&quot;&gt;storage providers&lt;/a&gt;: MongoDB, which is the default, PostgreSQL, SQL Server and SQLite. The kernel creates and migrates its own tables on the SQL databases, so there’s no schema for you to run. What changes with the choice:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Storage type&lt;/th&gt;
&lt;th&gt;What to know&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;MongoDB&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Must run as a replica set, a single-node one included, because appends use transactions. Read models are documents. The only storage that multi-node clustering keeps its membership in.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;PostgreSql&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read models become tables, one per read model type, with a typed column per scalar property and a &lt;code dir=&quot;auto&quot;&gt;jsonb&lt;/code&gt; column for each collection or nested object.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;MsSql&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The same table layout, with &lt;code dir=&quot;auto&quot;&gt;nvarchar(max)&lt;/code&gt; for the JSON columns.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;Sqlite&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Separate database files for stores and namespaces, with read models in their own files. Each uses WAL mode with a 30-second busy timeout because many grains write to it at once. The JSON columns are &lt;code dir=&quot;auto&quot;&gt;TEXT&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;InMemory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not durable, and scoped to one process. For tests and throwaway environments.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The MongoDB in the production guide’s Compose file has no authentication and is published on loopback only. Authentication and TLS for the database, or a managed replica set, are yours to add.&lt;/p&gt;
&lt;p&gt;On the SQL databases, the &lt;a href=&quot;https://cratis.io/chronicle/sinks/&quot;&gt;SQL sink&lt;/a&gt; creates each read model’s table on first use and updates only the columns that changed. It doesn’t create the indexes a read model declares with &lt;code dir=&quot;auto&quot;&gt;[Index]&lt;/code&gt;. Only the MongoDB sink does that today, so an &lt;code dir=&quot;auto&quot;&gt;[Index]&lt;/code&gt; on the &lt;code dir=&quot;auto&quot;&gt;Author&lt;/code&gt; read model isn’t created on PostgreSQL, and you add that index to the table yourself.&lt;/p&gt;
&lt;p&gt;The sink is chosen by the client, separately from the kernel’s storage, and every Chronicle client registers read models against the MongoDB sink unless you tell it otherwise. With the kernel on PostgreSQL, SQL Server or SQLite, set the default sink to SQL in the application as well:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-0&quot; id=&quot;tab-0-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-1&quot; id=&quot;tab-0-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-2&quot; id=&quot;tab-0-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-3&quot; id=&quot;tab-0-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-4&quot; id=&quot;tab-0-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-0&quot; aria-labelledby=&quot;tab-0-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Sinks&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;AddCratisChronicle&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;configureOptions&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; options &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;options&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;DefaultSinkTypeId&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WellKnownSinkTypes&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SQL&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-1&quot; aria-labelledby=&quot;tab-0-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; io.cratis.chronicle.ChronicleOptions&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; io.cratis.chronicle.connection.ChronicleConnectionString&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; io.cratis.chronicle.sinks.WellKnownSinkTypes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;optionsWithSqlSink&lt;/span&gt;&lt;span&gt;(): &lt;/span&gt;&lt;span&gt;ChronicleOptions&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChronicleOptions&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;connectionString &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ChronicleConnectionString.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;chronicle://my-server:35000&quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;defaultSinkTypeId &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; WellKnownSinkTypes.SQL&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-2&quot; aria-labelledby=&quot;tab-0-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ChronicleOptions&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;connection&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ChronicleConnectionString&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;sinks&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WellKnownSinkTypes&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChronicleSqlSinkOptions&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;ChronicleOptions&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChronicleOptions&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ChronicleConnectionString&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Companion&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;chronicle://my-server:35000&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Unknown&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;WellKnownSinkTypes&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SQL&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-3&quot; aria-labelledby=&quot;tab-0-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ChronicleOptions&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;WellKnownSinks&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/chronicle&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;createChronicleOptionsSqlSink&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChronicleOptions&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChronicleOptions&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;fromConnectionString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;chronicle://my-server:35000&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;defaultSinkTypeId&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WellKnownSinks&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SQL&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-4&quot; aria-labelledby=&quot;tab-0-4&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;defmodule&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ChronicleSqlSink&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;@moduledoc false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;start_link&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;children &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Client&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;       &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;connection_string&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;chronicle://my-server:35000&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;default_sink_type_id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;sql&lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Supervisor&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;start_link&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;children&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;strategy&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;one_for_one&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;The storage choice also decides whether you can add a second node. The &lt;a href=&quot;https://cratis.io/chronicle/hosting/configuration/clustering/&quot;&gt;clustering&lt;/a&gt; types are &lt;code dir=&quot;auto&quot;&gt;Localhost&lt;/code&gt;, the default, and &lt;code dir=&quot;auto&quot;&gt;MongoDB&lt;/code&gt;, which keeps cluster membership in MongoDB storage. PostgreSQL, SQL Server and SQLite have no clustering type of their own, so a cluster of more than one node needs MongoDB storage. The default has a trap of its own, two servers pointed at one database forming two clusters without an error, and &lt;a href=&quot;https://blog.cratis.io/chronicle-jobs-scale-and-performance/&quot;&gt;&lt;em&gt;Chronicle jobs, scale and performance&lt;/em&gt;&lt;/a&gt; walks through it.&lt;/p&gt;
&lt;p&gt;We’d pick MongoDB, since both a second node and declared indexes need it, unless your organization already runs one of the SQL databases and doesn’t run MongoDB. SQLite fits one kernel on one machine.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;who-gets-to-connect&quot;&gt;Who gets to connect&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/chronicle/hosting/configuration/authentication/&quot;&gt;Authentication&lt;/a&gt; is on by default, served by Chronicle’s built-in OAuth authority unless you configure an external one. The production image creates the administrator without a password and lets the first person to open the Workbench set it. Keep port 35000 unreachable until that’s done, or supply the password from your secret store through the &lt;code dir=&quot;auto&quot;&gt;Cratis__Chronicle__Authentication__AdminUser__*&lt;/code&gt; settings.&lt;/p&gt;
&lt;p&gt;Services connect as applications, which are OAuth clients with a client ID and secret. You can create them in the Workbench, with &lt;code dir=&quot;auto&quot;&gt;cratis chronicle applications add&lt;/code&gt;, or declare them in the &lt;a href=&quot;https://cratis.io/chronicle/hosting/configuration/client-bootstrap/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;clients&lt;/code&gt; section&lt;/a&gt; of the configuration, where the secret is hashed when it loads. A declared application whose client ID already exists is skipped, so changing its secret in the configuration doesn’t change the one Chronicle holds. Removing it in the Workbench or with the CLI lasts until the next restart, when the kernel creates it again from the configuration, so to revoke a declared application, take it out of the configuration as well.&lt;/p&gt;
&lt;p&gt;The application puts its own credentials in the connection string, &lt;code dir=&quot;auto&quot;&gt;chronicle://author-service:&amp;#x3C;secret&gt;@chronicle.internal:35000&lt;/code&gt;. Leave them out and the .NET, Java, Kotlin and TypeScript clients fill in the well-known development credentials, &lt;code dir=&quot;auto&quot;&gt;chronicle-dev-client&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;chronicle-dev-secret&lt;/code&gt;. The development images create an application with those credentials in storage at startup and the production image doesn’t, so a production kernel refuses them, unless its storage was used by a development image before and still holds that application. If it does, remove &lt;code dir=&quot;auto&quot;&gt;chronicle-dev-client&lt;/code&gt; from the production store. In the Workbench it’s on the Applications page under System, and from a terminal you pass its ID from &lt;code dir=&quot;auto&quot;&gt;cratis chronicle applications list&lt;/code&gt; to &lt;code dir=&quot;auto&quot;&gt;cratis chronicle applications remove&lt;/code&gt;. If the &lt;code dir=&quot;auto&quot;&gt;clients&lt;/code&gt; section of the configuration declares it, take it out there too, or the kernel creates it again at the next restart. The Elixir client fills nothing in and connects without authentication, and a kernel with authentication on accepts that connection but never lets the client finish registering.&lt;/p&gt;
&lt;p&gt;Chronicle has no operator roles. The kernel’s authorization asks one question, whether the caller is authenticated, so every user and every application you create can do everything the API and the Workbench allow. Create a user for each person and an application for each service, so you can revoke one without touching the rest.&lt;/p&gt;
&lt;p&gt;The client side has a default to change too. Every Chronicle client uses TLS unless told otherwise, and by default it &lt;a href=&quot;https://cratis.io/chronicle/configuration/tls/&quot;&gt;accepts any server certificate&lt;/a&gt;, which is what lets a development client talk to the self-signed certificate of the development image. Against a production kernel, turn validation on with &lt;code dir=&quot;auto&quot;&gt;skipTlsValidation=false&lt;/code&gt;:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-0&quot; id=&quot;tab-1-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-1&quot; id=&quot;tab-1-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-2&quot; id=&quot;tab-1-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-3&quot; id=&quot;tab-1-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-4&quot; id=&quot;tab-1-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-0&quot; aria-labelledby=&quot;tab-1-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChronicleTlsOptions&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChronicleOptions&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Create&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ChronicleOptions&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;FromConnectionString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;chronicle://my-server:35000?skipTlsValidation=false&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-1&quot; aria-labelledby=&quot;tab-1-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; io.cratis.chronicle.ChronicleOptions&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;optionsWithTlsValidationEnabled&lt;/span&gt;&lt;span&gt;(): &lt;/span&gt;&lt;span&gt;ChronicleOptions&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ChronicleOptions.&lt;/span&gt;&lt;span&gt;fromConnectionString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;chronicle://my-server:35000?skipTlsValidation=false&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-2&quot; aria-labelledby=&quot;tab-1-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ChronicleOptions&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChronicleTlsOptions&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;ChronicleOptions&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChronicleOptions&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;fromConnectionString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;chronicle://my-server:35000?skipTlsValidation=false&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-3&quot; aria-labelledby=&quot;tab-1-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ChronicleOptions&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/chronicle&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;createOptionsWithTlsValidation&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChronicleOptions&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChronicleOptions&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;fromConnectionString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;chronicle://my-server:35000?skipTlsValidation=false&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-4&quot; aria-labelledby=&quot;tab-1-4&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;defmodule&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;TlsValidationEnabled&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;@moduledoc false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;connection_string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Connections&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ConnectionString&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;chronicle://my-server:35000?skipTlsValidation=false&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;Two settings look like shortcuts and aren’t meant for a network. &lt;code dir=&quot;auto&quot;&gt;auth=none&lt;/code&gt; in a .NET connection string only works against a server with authentication turned off, and a server like that answers every call from anyone who can reach its port, with every event and every read model. It exists for a kernel embedded next to its own client in one throwaway process. &lt;a href=&quot;https://cratis.io/chronicle/hosting/configuration/tls/&quot;&gt;TLS on the server&lt;/a&gt; can be switched off only for a private backend behind an HTTPS proxy that forwards everything as cleartext HTTP/2, and that setup needs an external authority, because the internal one doesn’t serve its token endpoint over cleartext. The .NET client’s client-credentials mode can’t use an external authority, so it doesn’t work behind such a proxy.&lt;/p&gt;
&lt;p&gt;Values in events that are secret without being personal, such as a partner’s API key, get &lt;code dir=&quot;auto&quot;&gt;[Encrypted]&lt;/code&gt;, which encrypts them at rest under keys that no erasure can reach. &lt;a href=&quot;https://cratis.io/chronicle/security/&quot;&gt;Security&lt;/a&gt; explains how it differs from &lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt;, and &lt;a href=&quot;https://blog.cratis.io/personal-data-in-an-event-log/&quot;&gt;&lt;em&gt;Personal data in an event log&lt;/em&gt;&lt;/a&gt; covers the personal side.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-you-can-watch&quot;&gt;What you can watch&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The kernel is instrumented whether or not anything collects it. Set &lt;code dir=&quot;auto&quot;&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/code&gt; and it exports &lt;a href=&quot;https://cratis.io/chronicle/hosting/configuration/open-telemetry/&quot;&gt;OpenTelemetry&lt;/a&gt; metrics, traces and logs over OTLP, with &lt;code dir=&quot;auto&quot;&gt;OTEL_EXPORTER_OTLP_PROTOCOL&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;OTEL_EXPORTER_OTLP_HEADERS&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;OTEL_SERVICE_NAME&lt;/code&gt; (default &lt;code dir=&quot;auto&quot;&gt;Chronicle&lt;/code&gt;) for the rest. Without the endpoint, nothing leaves the process.&lt;/p&gt;
&lt;p&gt;The metrics come from four meters: &lt;code dir=&quot;auto&quot;&gt;Cratis.Chronicle&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Microsoft.Orleans&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Grpc.AspNetCore.Server&lt;/code&gt; and the .NET runtime. The server also records further internal counters that the configuration reference doesn’t list.&lt;/p&gt;
&lt;p&gt;The application has telemetry of its own. In .NET, Arc publishes command, query and identity spans under the &lt;a href=&quot;https://cratis.io/arc/backend/csharp/observability/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;Cratis.Arc&lt;/code&gt;&lt;/a&gt; source, and the Chronicle client publishes its append, unit-of-work, reactor and reducer spans under &lt;code dir=&quot;auto&quot;&gt;Cratis.Chronicle.Client&lt;/code&gt;. Neither reaches an exporter until the application subscribes to it. The Chronicle client’s ASP.NET Core package, &lt;code dir=&quot;auto&quot;&gt;Cratis.Chronicle.AspNetCore&lt;/code&gt;, has one helper for this, &lt;code dir=&quot;auto&quot;&gt;AddCratisChronicleInstrumentation()&lt;/code&gt;, which registers the client’s source on the tracer. Arc has no counterpart and no single call sets up both, so the application names &lt;code dir=&quot;auto&quot;&gt;Cratis.Arc&lt;/code&gt; itself. One that registers only &lt;code dir=&quot;auto&quot;&gt;Cratis.Arc&lt;/code&gt; never exports a client append span:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-0&quot; id=&quot;tab-2-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-1&quot; id=&quot;tab-2-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-2&quot; id=&quot;tab-2-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-3&quot; id=&quot;tab-2-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-4&quot; id=&quot;tab-2-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-0&quot; aria-labelledby=&quot;tab-2-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;AspNetCore&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;OpenTelemetry&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OpenTelemetry&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OpenTelemetry&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Trace&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Services&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;AddOpenTelemetry&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WithTracing&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tracing &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tracing&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;AddSource&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Cratis.Arc&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;AddCratisChronicleInstrumentation&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;AddAspNetCoreInstrumentation&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;UseOtlpExporter&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-1&quot; aria-labelledby=&quot;tab-2-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;Arc for Kotlin and Java &lt;a href=&quot;https://cratis.io/arc/backend/kotlin/guides/observability/&quot;&gt;reports through Micrometer&lt;/a&gt; once you add its observability starter. The Chronicle client for the JVM reports spans under the &lt;code dir=&quot;auto&quot;&gt;io.cratis.chronicle&lt;/code&gt; scope through whatever OpenTelemetry the application registered globally, with no setup of its own.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dependencies&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;implementation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;io.cratis:arc-observability-spring-boot-starter:7.7.0&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-2&quot; aria-labelledby=&quot;tab-2-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;Arc for Kotlin and Java &lt;a href=&quot;https://cratis.io/arc/backend/kotlin/guides/observability/&quot;&gt;reports through Micrometer&lt;/a&gt; once you add its observability starter. The Chronicle client for the JVM reports spans under the &lt;code dir=&quot;auto&quot;&gt;io.cratis.chronicle&lt;/code&gt; scope through whatever OpenTelemetry the application registered globally, with no setup of its own.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dependencies&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;implementation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;io.cratis:arc-observability-spring-boot-starter:7.7.0&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-3&quot; aria-labelledby=&quot;tab-2-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;The Chronicle TypeScript client reports spans and metrics under &lt;code dir=&quot;auto&quot;&gt;@cratis/chronicle&lt;/code&gt; through the OpenTelemetry API, so starting the Node SDK before the client is enough.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;NodeSDK&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@opentelemetry/sdk-node&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;OTLPTraceExporter&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@opentelemetry/exporter-trace-otlp-http&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sdk&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;NodeSDK&lt;/span&gt;&lt;span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt; traceExporter&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OTLPTraceExporter&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-4&quot; aria-labelledby=&quot;tab-2-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;The Elixir client doesn’t emit traces or metrics of its own.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;Arc’s pipelines in .NET emit spans and no counters, so command and query rates come from the spans. Its &lt;code dir=&quot;auto&quot;&gt;Cratis.Arc&lt;/code&gt; meter records MongoDB client metrics.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;where-a-trace-stops&quot;&gt;Where a trace stops&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Follow a &lt;code dir=&quot;auto&quot;&gt;RegisterAuthor&lt;/code&gt; command through a trace in .NET and it stops at the append. The HTTP request, Arc’s &lt;code dir=&quot;auto&quot;&gt;cratis.arc.command.execute&lt;/code&gt; span and the client’s &lt;code dir=&quot;auto&quot;&gt;client.event_sequence.append&lt;/code&gt; span nest in one trace, and the kernel receives the append through ASP.NET Core, which reads the trace context the call carries. From there the event is stored, and nothing that carries it on to observers carries the trace with it. The kernel’s &lt;code dir=&quot;auto&quot;&gt;cratis.chronicle.observer.handle&lt;/code&gt; span runs in a separate trace, and &lt;code dir=&quot;auto&quot;&gt;client.reactor.handle&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;client.reducer.handle&lt;/code&gt; start with no parent and no link back to the command. The correlation ID travels with the event, but it’s an ID, and a tracing backend can’t join spans on it. The JVM client’s tracing guide says the same of its own spans. The gap is &lt;a href=&quot;https://github.com/Cratis/Chronicle/issues/4416&quot;&gt;Cratis/Chronicle#4416&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/where-the-trace-stops.BM49o9pn_Zfon3z.webp&quot; alt=&quot;A diagram titled Where a trace stops today, with a small tag that reads .NET. On the left, a box labeled One trace holds four rows, each indented further than the one above: HTTP request, Arc command, Client append and Kernel append. A dashed vertical line follows, labeled no trace context past the append. On the right, a box labeled New traces holds three rows: Observer, Reactor and Reducer. Two bands run along the bottom. The first reads: new traces have no parent and no link. The second reads: the correlation ID travels with the event, but a tracing backend can’t join on it.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2000&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;In .NET, the command, the client append and the kernel append share a trace; observers start new ones.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For the author feature, that means a slow registration shows up in a trace up to the append, and the projection that updates the &lt;code dir=&quot;auto&quot;&gt;Author&lt;/code&gt; list shows up on its own. The design discussion about carrying the trace past the append, and about a one-line setup and domain-named spans, is open in &lt;a href=&quot;https://github.com/Cratis/Chronicle/issues/4417&quot;&gt;Cratis/Chronicle#4417&lt;/a&gt;, &lt;a href=&quot;https://github.com/Cratis/Arc/issues/2914&quot;&gt;Cratis/Arc#2914&lt;/a&gt; and &lt;a href=&quot;https://github.com/Cratis/Arc/issues/2915&quot;&gt;Cratis/Arc#2915&lt;/a&gt;. None of it has shipped.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;backups-that-can-be-restored&quot;&gt;Backups that can be restored&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A Chronicle deployment can’t be restored from a database backup alone. The database carries the event store’s data, the Data Protection key ring, webhook definitions with their encrypted credentials, and OAuth applications and tokens. It can’t carry the certificates that decrypt the key ring, and those have to be the ones that were in use when the backup was taken.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/restore-order.CUIwC4GN_Z24Umyy.webp&quot; alt=&quot;A diagram titled Restore in this order, three numbered boxes from left to right with arrows between them. 1, Certificate ring: every certificate, as it was at backup time. 2, Storage: with the Data Protection key ring inside it. 3, Compliance key store, tagged when configured: same point in time as the storage. Under the first box, a note reads: keep each certificate until no backup needs it. An arrow from the third box leads to a box that reads: then start one node, read /diagnostics/encryption-certificates, then start the rest.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2000&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A database backup is the second of three things to restore.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Restore the &lt;a href=&quot;https://cratis.io/chronicle/hosting/encryption-certificate/&quot;&gt;certificate ring&lt;/a&gt; first, in the shape it had at backup time, including every previous certificate that was live then. Restore the storage second. The &lt;a href=&quot;https://cratis.io/chronicle/hosting/configuration/compliance-storage/&quot;&gt;compliance key store&lt;/a&gt; comes third, when one is configured, restored to the storage’s point in time and never to an earlier one. The fence that records an erasure is kept in the key store beside the keys, so a key store restored from earlier than the storage still holds the key of anyone erased in between, with no fence to refuse it. Their personal data reads again, and an erasure already reported as complete is undone. The same key store is missing the keys of subjects created since its backup, and their personal data reads back as empty strings, the same as a completed erasure. Restored from later than the storage, it keeps the deletions and fences of the erasures made in between, and those erasures hold. &lt;a href=&quot;https://blog.cratis.io/personal-data-in-an-event-log/&quot;&gt;&lt;em&gt;Personal data in an event log&lt;/em&gt;&lt;/a&gt; covers the key store itself. Then start one node and read &lt;code dir=&quot;auto&quot;&gt;GET /diagnostics/encryption-certificates&lt;/code&gt;. A key reported with the role &lt;code dir=&quot;auto&quot;&gt;Retired&lt;/code&gt; means the ring is missing a certificate the restored data needs.&lt;/p&gt;
&lt;p&gt;Nothing about a wrong order reports itself at restore time. The data is there, and nothing can read it. So a certificate stays in the backup set for as long as the oldest backup you’d still restore, which is longer than it stays in the ring. Losing the certificates can’t be recovered from.&lt;/p&gt;
&lt;p&gt;Rotating the certificate is forward only. You make the new certificate active, list the old one under &lt;code dir=&quot;auto&quot;&gt;previous&lt;/code&gt;, restart every node with the same ring, and remove the old entry once the diagnostic shows nothing depends on it. Overwriting the file at the certificate path with a new key pair isn’t a rotation, and it makes everything the old one protected unreadable.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;upgrades&quot;&gt;Upgrades&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Chronicle raises its major version for a breaking change to any published surface, whether the wire contract, a .NET API or observable behavior, and &lt;a href=&quot;https://cratis.io/chronicle/upgrading/major-versions/&quot;&gt;Every major version&lt;/a&gt; lists what each boundary changed. Most boundaries are compile-time changes you fix once. Three concerned data already written, 6 to 7, 10 to 11 and 13 to 14, and skipping past one doesn’t skip its consequence. Before crossing one, take a backup of a kind you’ve already restored from once, and rehearse the upgrade on a copy. Two releases are there to step over, 14.0.0 and 17.0.0, where the &lt;code dir=&quot;auto&quot;&gt;.0.1&lt;/code&gt; is the real start of the major.&lt;/p&gt;
&lt;p&gt;Within a major, each build is checked against every released minor of that major, so a .NET client and a kernel that differ only in minor are expected to work together. The Java, Kotlin, TypeScript and Elixir clients have version numbers of their own. Across a major, &lt;a href=&quot;https://cratis.io/chronicle/upgrading/&quot;&gt;upgrade the kernel and the clients together&lt;/a&gt;. The .NET, Java, Kotlin and TypeScript clients check at connect that their wire contract and the server’s are compatible, and a mismatch fails the connection. The Elixir client runs the check before it sends an append, and a mismatch fails the append. In .NET, &lt;code dir=&quot;auto&quot;&gt;skipCompatibilityCheck=true&lt;/code&gt; turns the check off, as a temporary escape hatch that trades a clear failure at connect for a less obvious one later.&lt;/p&gt;
&lt;p&gt;The kernel upgrades its own stored state with patches it applies at startup, in version order, and only forward. Starting an older kernel against patched storage doesn’t undo them, so rolling back means restoring the backup you took before the upgrade. Upgrading doesn’t rewrite the events you’ve already appended.&lt;/p&gt;
&lt;p&gt;Two operations don’t mix with a rolling upgrade. Finish the upgrade before you rotate the encryption certificate or change webhook credentials, because a credential written by an upgraded node can’t be read by one that isn’t. And while an erasure of personal data is in force, upgrade every node together, since a node from before the erasure fence can provision a key over it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-author-features-first-day&quot;&gt;The author feature’s first day&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Acme registers Jane Austen in the morning. Contoso registers her too, and both succeed, because the uniqueness constraint holds per event sequence and namespace, and each organization has its own namespace, as &lt;a href=&quot;https://blog.cratis.io/tenancy-and-identity-end-to-end/&quot;&gt;&lt;em&gt;Tenancy and identity end to end&lt;/em&gt;&lt;/a&gt; sets up. When a second Acme librarian tries the same name, the kernel refuses the append.&lt;/p&gt;
&lt;p&gt;The number to watch is failed partitions. If the projection behind the &lt;code dir=&quot;auto&quot;&gt;Author&lt;/code&gt; list throws for one author, that author’s partition stops and is retried, and every other author keeps updating. From a terminal, for Acme:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diagnose&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;acme&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;failed-partitions&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;acme&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Leave out &lt;code dir=&quot;auto&quot;&gt;-n&lt;/code&gt; and these commands look at the active context’s namespace, or &lt;code dir=&quot;auto&quot;&gt;Default&lt;/code&gt; when the context doesn’t set one, and Acme’s authors aren’t in &lt;code dir=&quot;auto&quot;&gt;Default&lt;/code&gt;. &lt;a href=&quot;https://blog.cratis.io/the-cratis-cli/&quot;&gt;&lt;em&gt;The Cratis CLI, from diagnose to llm-context&lt;/em&gt;&lt;/a&gt; follows a failed partition from &lt;a href=&quot;https://cratis.io/cli/chronicle/diagnose/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;diagnose&lt;/code&gt;&lt;/a&gt; to the retry, and &lt;a href=&quot;https://blog.cratis.io/chronicle-workbench/&quot;&gt;&lt;em&gt;Chronicle Workbench, screen by screen&lt;/em&gt;&lt;/a&gt; shows the same partition in the &lt;a href=&quot;https://cratis.io/chronicle/workbench/&quot;&gt;Workbench&lt;/a&gt;, labelled with the author’s ID.&lt;/p&gt;
&lt;p&gt;The operator can’t go from that failed partition to the trace of the command that registered the author, because that trace stopped at the append.&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href=&quot;https://blog.cratis.io/the-cratis-cli/&quot;&gt;Part 17 — The Cratis CLI, from diagnose to llm-context&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next: &lt;a href=&quot;https://blog.cratis.io/chronicle-jobs-scale-and-performance/&quot;&gt;Part 19 — Chronicle jobs, scale and performance&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>chronicle</category><category>operations</category><category>hosting</category><category>security</category><category>observability</category></item><item><title>From script to stage · Part 17: The Cratis CLI, from diagnose to llm-context</title><link>https://blog.cratis.io/the-cratis-cli/</link><guid isPermaLink="true">https://blog.cratis.io/the-cratis-cli/</guid><description>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.</description><pubDate>Wed, 30 Sep 2026 09:17:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt; · Part 17 of 26&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;cratis chronicle diagnose&lt;/code&gt; exits nonzero when the server is unreachable or when a failed partition exists. An observer that’s far behind the tail leaves the exit code at zero, and that’s deliberate.&lt;/p&gt;
&lt;p&gt;The Cratis &lt;a href=&quot;https://cratis.io/cli/&quot;&gt;CLI&lt;/a&gt; inspects and diagnoses Chronicle from a terminal, full-screen workbench included, and it also hosts &lt;code dir=&quot;auto&quot;&gt;cratis new&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;cratis ai&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;cratis screenplay&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;cratis render&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;installing-it&quot;&gt;Installing it&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The release binaries for macOS and Linux, on arm64 and x64, are self-contained and need no runtime. The .NET tool needs the runtime its package declares, .NET 10 or later.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tap&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cratis/cratis&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# macOS and Linux&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dotnet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tool&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-g&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis.Cli&lt;/span&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;# or, with .NET 10+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;completions&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Against a Chronicle on your own machine there’s nothing to configure. The first run writes a &lt;code dir=&quot;auto&quot;&gt;default&lt;/code&gt; context pointing at &lt;code dir=&quot;auto&quot;&gt;chronicle://localhost:35000&lt;/code&gt;, and the first &lt;code dir=&quot;auto&quot;&gt;chronicle&lt;/code&gt; command asks which event store to use by default and remembers the answer. Other servers get contexts of their own, with &lt;code dir=&quot;auto&quot;&gt;cratis context create&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;cratis context set&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Completion asks the running server. &lt;code dir=&quot;auto&quot;&gt;cratis completions install&lt;/code&gt; sets it up for bash, zsh, fish or PowerShell, and pressing Tab after a command that takes an observer, a read model or an event type asks the live server what it has registered right now.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;when-a-view-stops-updating&quot;&gt;When a view stops updating&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;We built the CLI so you can ask the running system instead of comparing sequence numbers by hand. Start with &lt;a href=&quot;https://cratis.io/cli/chronicle/diagnose/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;diagnose&lt;/code&gt;&lt;/a&gt;. Run against a bookshop store (the sample is from an older server), it prints this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;❯ cratis chronicle diagnose&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;── Chronicle Diagnostics  14:18:21 ─────────────────────────────────────────────&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;server:      chronicle://chronicle-dev-client:***@localhost:35100/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;event store: Bookshop  /  Default&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;✓  Connection            connected&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;✓  Server version        16.7.0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;✓  Event stores          2 stores: System, Bookshop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;✓  Observers             9 active&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;✗  Failed partitions     1 need attention  → cratis chronicle failed-partitions list&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;✓  Recommendations       none&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;✓  Event sequence        tail: 22&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;✗ Issues detected — review items above&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/cli-10-diagnose.oa6zfqe9_7XhfN.webp&quot; alt=&quot;Terminal output of cratis chronicle diagnose against the Authors event store. Connection, server version 19.23.1, event stores and observers pass. The failed partitions row reports 1 needing attention and names the command cratis chronicle failed-partitions list. The exit code printed below is 3.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1600&quot; height=&quot;1000&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The same summary from a current server, with the author feature’s store. The exit code is 3 while a partition has failed.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The failing row names the next command, and &lt;code dir=&quot;auto&quot;&gt;diagnose&lt;/code&gt; exits nonzero when failed partitions exist, so it works as a check in a script too. &lt;a href=&quot;https://cratis.io/cli/chronicle/failed-partitions/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;failed-partitions show&lt;/code&gt;&lt;/a&gt; then gives the exception and a short stack trace for each attempt, shown here trimmed:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;FailedPartition: caadc869-1251-41d0-9063-6947eaf74043&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Observer:        Bookshop.OverdueNotices&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Partition:       978-0131177055&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Attempts:        5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;--- Attempt at 2026-07-28T12:17:56.6680000+00:00 (Seq# 22) ---&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Exception has been thrown by the target of an invocation.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;smtp.bookshop.local: connection refused&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;StackTrace:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs(…)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;at System.Reflection.RuntimeMethodInfo.Invoke(…)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;at Cratis.Chronicle.Reactors.ReactorInvoker.Invoke(Object content, EventContext eventContext)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The partition is a book’s ISBN, because that’s the event source ID the application uses. For us it would be the author’s ID. Failures are addressed by your own keys, so you can go straight from “this author is missing” to “this author’s partition failed, and here’s why”.&lt;/p&gt;
&lt;p&gt;Behind that is Chronicle’s failure model. When one partition fails, it stops and is retried with backoff while every other partition keeps going. After the configured number of attempts it’s quarantined until someone looks. For a failed partition that isn’t quarantined, fix the cause, then ask the &lt;a href=&quot;https://cratis.io/cli/chronicle/observers/&quot;&gt;observer commands&lt;/a&gt; for exactly one more attempt:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;observers&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;retry-partition&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Bookshop.OverdueNotices&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;978-0131177055&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-y&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A quarantined partition refuses an ordinary retry, as &lt;a href=&quot;https://blog.cratis.io/four-changes-in-year-two/&quot;&gt;&lt;em&gt;Year two&lt;/em&gt;&lt;/a&gt; explains. A quarantined observer also blocks retries, and the CLI exits nonzero with a suggestion when the kernel refuses. Even a success only says the retry started, so run &lt;code dir=&quot;auto&quot;&gt;diagnose&lt;/code&gt; again.&lt;/p&gt;
&lt;p&gt;Retry the partition before you replay anything. &lt;code dir=&quot;auto&quot;&gt;observers replay&lt;/code&gt; is the bigger hammer. It reprocesses the observer from sequence zero and rebuilds its read model, and on a large store that takes time and costs something.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/failed-partition-loop.DXn8xMac_M9k6Q.webp&quot; alt=&quot;A diagram titled Following a failed partition, numbered steps stacked from top to bottom. 1 diagnose: one failed partition; next command named. 2 failed-partitions show: error per attempt, by your key. 3 Fix the cause. 4 retry-partition: one more attempt for that partition. From step 4, one arrow leads to 5 diagnose again, and a second arrow, labeled bigger hammer, leads to observers replay: rebuilds from sequence zero.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2300&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Inspect, fix the cause, retry one partition, inspect again.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/cli-20-failed-partition-loop.Dwql2BUU_1zQK3J.webp&quot; alt=&quot;Terminal recording: diagnose reports one failed partition, the failed-partitions list names it, the partition is retried, and a second diagnose reports the system is healthy.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1200&quot; height=&quot;675&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The same loop in a terminal. The fix itself isn’t shown; a comment stands in for it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The rest of the debugging kit depends on where you are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;In a terminal on a server.&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;cratis chronicle workbench&lt;/code&gt; opens a full-screen terminal dashboard over the same connection. &lt;code dir=&quot;auto&quot;&gt;Ctrl+P&lt;/code&gt; searches observers, event types, projections, read models and failed partitions at once, which helps because “author” isn’t a name in one list. It’s a thread through five of them. Actions such as replaying an observer or retrying a partition sit in place, behind a confirmation dialog. Tab completion asks the live server for observer and read-model names.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;In a browser.&lt;/strong&gt; &lt;a href=&quot;https://blog.cratis.io/chronicle-workbench/&quot;&gt;&lt;em&gt;Chronicle Workbench, screen by screen&lt;/em&gt;&lt;/a&gt; covers the browser tool’s views, projection preview and permissions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;In your editor.&lt;/strong&gt; &lt;a href=&quot;https://github.com/Cratis/Narrator&quot;&gt;Narrator&lt;/a&gt;, a &lt;a href=&quot;https://cratis.io/tools/vscode-extension/&quot;&gt;VS Code extension&lt;/a&gt; we still treat as experimental, browses event stores, event types, read models and observers, and pages through events. It only reads.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;reading-an-observer-row&quot;&gt;Reading an observer row&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;cratis chronicle observers list&lt;/code&gt; is the table you’ll spend the most time in. From the same bookshop store:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Id                       Type        State   Quarantined  Next#  LastHandled#  Subscribed&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Bookshop.Members         Reducer     Active  False        23     2             False&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Bookshop.Books           Reducer     Active  False        23     10            False&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Bookshop.BorrowedBooks   Projection  Active  False        23     18            False&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Bookshop.OverdueBooks    Projection  Active  False        23     22            False&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Bookshop.OverdueNotices  Reactor     Active  False        23     22            False&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Two of its columns are easy to misread. &lt;code dir=&quot;auto&quot;&gt;Next#&lt;/code&gt; is the next sequence number the observer will look at, and &lt;code dir=&quot;auto&quot;&gt;LastHandled#&lt;/code&gt; is the last event it actually processed. The tail here is 22, so every observer with &lt;code dir=&quot;auto&quot;&gt;Next#&lt;/code&gt; 23 has read everything there is. &lt;code dir=&quot;auto&quot;&gt;Members&lt;/code&gt; last handled sequence 2 because nobody has registered as a member since, and nothing between 3 and 22 was addressed to it. &lt;code dir=&quot;auto&quot;&gt;Next#&lt;/code&gt; is how far an observer has read. &lt;code dir=&quot;auto&quot;&gt;LastHandled#&lt;/code&gt; is the last thing it cared about.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/cli-31-events-tail-and-observer.C-4TVzFf_1UEwr4.webp&quot; alt=&quot;Terminal recording: events tail prints sequence number 3 and observers list shows Next# 4 and LastHandled# 3. After one more name is registered, events tail prints 4 and the same observers show Next# 5 and LastHandled# 4.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1200&quot; height=&quot;560&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;One more event moves the tail, &lt;code dir=&quot;auto&quot;&gt;Next#&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;LastHandled#&lt;/code&gt; by one. The register line stands in for registering a name in the app.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;State&lt;/code&gt; is one of &lt;code dir=&quot;auto&quot;&gt;Active&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Replaying&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Suspended&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Disconnected&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Quarantined&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;Unknown&lt;/code&gt;. &lt;code dir=&quot;auto&quot;&gt;Disconnected&lt;/code&gt; means no client is attached, which usually means the application isn’t running. For a store whose application is stopped, that’s the normal state.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;past-the-first-retry&quot;&gt;Past the first retry&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Two flags go further than the loop above. &lt;code dir=&quot;auto&quot;&gt;failed-partitions show --detailed&lt;/code&gt; prints the full stack trace for every attempt, and &lt;code dir=&quot;auto&quot;&gt;failed-partitions list --observer&lt;/code&gt; narrows the list to one observer. The &lt;a href=&quot;https://cratis.io/cli/scenarios/fix-a-stuck-observer/&quot;&gt;stuck-observer scenario&lt;/a&gt; puts them in order:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diagnose&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;failed-partitions&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;show&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;OBSERVER_ID&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;PARTITION&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--detailed&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;observers&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;retry-partition&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;OBSERVER_ID&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;PARTITION&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.cratis.io/inside-chronicle/&quot;&gt;&lt;em&gt;Inside Chronicle&lt;/em&gt;&lt;/a&gt; explains failure kinds, retry defaults and quarantine thresholds. &lt;code dir=&quot;auto&quot;&gt;failed-partitions show&lt;/code&gt; in CLI 3.21.0 prints the messages and stack trace, not the kind. &lt;a href=&quot;https://blog.cratis.io/chronicle-workbench/&quot;&gt;&lt;em&gt;Workbench&lt;/em&gt;&lt;/a&gt; shows the same failed partitions from the browser.&lt;/p&gt;
&lt;p&gt;A quarantined observer needs &lt;code dir=&quot;auto&quot;&gt;observers clear-quarantine&lt;/code&gt;, or Clear quarantine on the browser Workbench’s Observers page. The terminal workbench has no key for it.&lt;/p&gt;
&lt;p&gt;When the partition’s state is wrong and not merely stuck, &lt;code dir=&quot;auto&quot;&gt;observers replay-partition&lt;/code&gt; discards that partition’s state and rebuilds it, and leaves every other partition alone. It’s the step between one retry and a full replay.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/three-sizes-of-fix.CancEfQk_2fyR1v.webp&quot; alt=&quot;A diagram titled Three sizes of fix, three cards from smallest to largest. retry-partition: one more attempt for one partition; state kept; tagged mutating. replay-partition: that partition’s state discarded and rebuilt; other partitions untouched; tagged destructive. observers replay: the whole observer from sequence zero; read model rebuilt; tagged destructive. A band along the bottom reads: Reach for the smallest one that fixes it.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;1800&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Retry one partition, rebuild one partition, or rebuild the observer.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The loop is finished when &lt;code dir=&quot;auto&quot;&gt;diagnose&lt;/code&gt; shows no failed partitions and the observer’s &lt;code dir=&quot;auto&quot;&gt;Next#&lt;/code&gt; has reached the tail plus one. &lt;code dir=&quot;auto&quot;&gt;diagnose --watch --interval &amp;#x3C;seconds&gt;&lt;/code&gt; keeps the summary refreshing while you wait, and &lt;code dir=&quot;auto&quot;&gt;diagnose -o json&lt;/code&gt; gives a pipeline the same summary with the exit code intact.&lt;/p&gt;
&lt;p&gt;Every command that asks for confirmation defaults to &lt;strong&gt;No&lt;/strong&gt;. In a script or another non-interactive shell, it fails with a validation error unless you pass &lt;code dir=&quot;auto&quot;&gt;--yes&lt;/code&gt;, so nothing in a pipeline replays an observer because a prompt went unanswered.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;after-changing-a-projection&quot;&gt;After changing a projection&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A deployment can leave a replay recommendation, depending on the definition change and policy described in &lt;a href=&quot;https://blog.cratis.io/from-event-to-read-model/&quot;&gt;&lt;em&gt;From event to read model&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A filed recommendation appears in &lt;code dir=&quot;auto&quot;&gt;recommendations list&lt;/code&gt;, in the Recommendations row of &lt;code dir=&quot;auto&quot;&gt;diagnose&lt;/code&gt; and on the Workbench’s Recommendations page. &lt;a href=&quot;https://cratis.io/cli/chronicle/recommendations/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;recommendations perform&lt;/code&gt;&lt;/a&gt; carries it out, which means replaying the observer, and &lt;code dir=&quot;auto&quot;&gt;recommendations ignore&lt;/code&gt; drops it. You can also replay directly, following &lt;a href=&quot;https://cratis.io/cli/scenarios/replay-a-projection/&quot;&gt;Replay a projection&lt;/a&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;observers&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--type&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;projection&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-q&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;observers&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;replay&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;OBSERVER_ID&gt;&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;# asks for confirmation&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diagnose&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--watch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The replay runs as a job, and &lt;code dir=&quot;auto&quot;&gt;jobs list&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;jobs get&lt;/code&gt; follow it. When it’s done, &lt;code dir=&quot;auto&quot;&gt;read-models instances&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;occurrences&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;snapshots&lt;/code&gt; show what the rebuilt &lt;code dir=&quot;auto&quot;&gt;Author&lt;/code&gt; read model holds. &lt;a href=&quot;https://blog.cratis.io/chronicle-jobs-scale-and-performance/&quot;&gt;&lt;em&gt;Jobs, scale and performance&lt;/em&gt;&lt;/a&gt; covers what the replay does in between.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-terminal-workbench&quot;&gt;The terminal workbench&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;cratis chronicle workbench&lt;/code&gt; exists for one situation in particular. The browser Workbench is served by the server, behind the server’s authentication, and you’re on a box you reached over SSH.&lt;/p&gt;
&lt;p&gt;It has fifteen views: Overview on its own, then Observation (Observers, Failures, Jobs, Recommendations), Events (Event Sequences, Event Types), Projections (Projections, Read Models) and Server (Event Stores, Namespaces, Applications, Users, Identities, Subscriptions). The views refresh on an interval, and it reopens on the view you left.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/cli-30-terminal-workbench-tour.xfNu051Q_Z1aIaLl.webp&quot; alt=&quot;Terminal recording of the terminal workbench. The Overview shows a connected server and one failed partition. The view switches to Observers, Event Sequences and Read Models, and then the command palette, searched for author, lists the observers, the AuthorRegistered event type, the projection, the read model and the failed partition.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1200&quot; height=&quot;720&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The terminal workbench, from Overview to the palette.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The actions sit on keys in the view where they apply. &lt;code dir=&quot;auto&quot;&gt;R&lt;/code&gt; replays the selected observer, &lt;code dir=&quot;auto&quot;&gt;T&lt;/code&gt; retries and &lt;code dir=&quot;auto&quot;&gt;P&lt;/code&gt; replays a failed partition, &lt;code dir=&quot;auto&quot;&gt;S&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;U&lt;/code&gt; stop and resume a job, and &lt;code dir=&quot;auto&quot;&gt;A&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;I&lt;/code&gt; apply or ignore a recommendation. &lt;code dir=&quot;auto&quot;&gt;F&lt;/code&gt; filters the current view, and &lt;code dir=&quot;auto&quot;&gt;Ctrl+E&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Ctrl+N&lt;/code&gt; switch event store and namespace.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/tui-03-failures.C5XVXGdT_2iHI3O.webp&quot; alt=&quot;The Failures view of the terminal workbench listing the failed partition of the welcome-mail reactor with its attempt count and last error. The toolbar offers Retry partition (T) and Replay partition (P).&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1600&quot; height=&quot;900&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Failures view, with the retry and replay keys on its toolbar.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For the author feature, typing “author” into the &lt;code dir=&quot;auto&quot;&gt;Ctrl+P&lt;/code&gt; palette would bring up the projection’s observer, the &lt;code dir=&quot;auto&quot;&gt;AuthorRegistered&lt;/code&gt; event type, the projection declaration and the &lt;code dir=&quot;auto&quot;&gt;Author&lt;/code&gt; read model in one list, and picking one jumps to its view with the filter already applied.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/tui-07-command-palette.Ck4hdj7u_Z1IkRRk.webp&quot; alt=&quot;The command palette in the terminal workbench with the search author. It lists two observers, the AuthorRegistered event type, the projection, the read model and the failed partition.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1600&quot; height=&quot;900&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;code dir=&quot;auto&quot;&gt;Ctrl+P&lt;/code&gt; with “author”: five kinds of result in one list.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Each action opens a confirmation dialog that shows only its target. The event store and namespace are in the status bar, so read them and the target before confirming, because the dialog confirms whatever is selected. The terminal workbench covers observation and jobs. It has no screens for appending, revising or redacting events, and no projection editor.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;output-for-scripts&quot;&gt;Output for scripts&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The CLI picks its output format from where it’s running. An interactive terminal gets a table, redirected output gets JSON, and &lt;code dir=&quot;auto&quot;&gt;NO_COLOR&lt;/code&gt; gets tab-separated plain text. &lt;code dir=&quot;auto&quot;&gt;-o table|plain|json|json-compact&lt;/code&gt; chooses explicitly, and &lt;code dir=&quot;auto&quot;&gt;-q&lt;/code&gt; prints only identifiers, for piping into the next command.&lt;/p&gt;
&lt;p&gt;For anything automated, pick the format with &lt;code dir=&quot;auto&quot;&gt;-o&lt;/code&gt; and pin the CLI version the script was written against. The &lt;a href=&quot;https://cratis.io/cli/reference/output-formats/&quot;&gt;output formats&lt;/a&gt; are current behavior, and they aren’t declared as a stable machine contract between versions.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-command-catalog-for-agents&quot;&gt;A command catalog for agents&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Once the feature runs, an assistant that helps operate it needs to know which of its actions change anything. &lt;a href=&quot;https://cratis.io/cli/reference/llm-context/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;cratis llm-context&lt;/code&gt;&lt;/a&gt; prints the CLI’s command catalog as JSON, and every command states its effect, &lt;code dir=&quot;auto&quot;&gt;read-only&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;local&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;mutating&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;destructive&lt;/code&gt;, and whether it requires confirmation. An agent can tell observing commands from state-changing ones without keeping its own list. &lt;a href=&quot;https://cratis.io/cli/getting-started/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;cratis init&lt;/code&gt;&lt;/a&gt; writes that catalog into context files for the tools you use, and you refresh it after upgrading. When the CLI recognizes it’s running under an agent tool, it defaults to compact JSON. Only the effect and confirmation fields are meant to stay stable between versions, so pin the CLI version if you rely on anything else.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/cli-effects-for-agents.c6T2OYZ7_ZV5DWW.webp&quot; alt=&quot;A diagram titled What a command does, four rows stacked from top to bottom, each naming an effect and an example: read-only, diagnose and failed-partitions show; local, context set; mutating, retry-partition; destructive, observers replay. Arrows from the first two rows lead to a bracket reading Observes or stays local. Arrows from the last two lead to a bracket reading Changes the server.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;1800&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The catalog marks each command’s effect, so an agent can tell observing from changing.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Besides the effects, the catalog lists every group, command, argument and option, with connection details and guidance on output formats. An &lt;code dir=&quot;auto&quot;&gt;effect&lt;/code&gt; is the strongest change a command can make, and an option such as &lt;code dir=&quot;auto&quot;&gt;--dry-run&lt;/code&gt; can lower it for one run but never raise it. The Chronicle commands in CLI 3.21.0 carry these effects:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;&lt;code dir=&quot;auto&quot;&gt;read-only&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code dir=&quot;auto&quot;&gt;mutating&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code dir=&quot;auto&quot;&gt;destructive&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;observers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;list&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;show&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;retry-partition&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;clear-quarantine&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;replay&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;replay-partition&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;remove&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;failed-partitions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;list&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;show&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;jobs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;list&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;get&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;stop&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;resume&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;recommendations&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;ignore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;perform&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;events&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;get&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;tail&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;read-models&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;list&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;get&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;instances&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;occurrences&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;snapshots&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;users&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;applications&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;subscriptions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;add&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;remove&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;diagnose&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;diagnose&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Every &lt;code dir=&quot;auto&quot;&gt;mutating&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;destructive&lt;/code&gt; command in that table also requires confirmation, except &lt;code dir=&quot;auto&quot;&gt;add&lt;/code&gt;. &lt;code dir=&quot;auto&quot;&gt;recommendations perform&lt;/code&gt; is classed destructive because it replays an observer. &lt;code dir=&quot;auto&quot;&gt;local&lt;/code&gt; covers commands that change only your machine, such as &lt;code dir=&quot;auto&quot;&gt;context set&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;login&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;init&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;ai install&lt;/code&gt;. &lt;code dir=&quot;auto&quot;&gt;cratis chronicle workbench&lt;/code&gt; is classed &lt;code dir=&quot;auto&quot;&gt;destructive&lt;/code&gt; as a whole, because actions can be run from inside it, and it confirms each one in its own dialog.&lt;/p&gt;
&lt;p&gt;Some things aren’t in the catalog because the CLI can’t do them. No CLI command appends, revises or redacts an event, and there’s no &lt;code dir=&quot;auto&quot;&gt;jobs delete&lt;/code&gt;. &lt;code dir=&quot;auto&quot;&gt;cratis llm-context --schema&lt;/code&gt; prints the JSON Schema for the catalog itself, and &lt;code dir=&quot;auto&quot;&gt;effect&lt;/code&gt; should be parsed as a string, with any value an agent doesn’t recognize treated as a state change.&lt;/p&gt;
&lt;p&gt;The label informs an agent, and it enforces nothing. An agent that can run &lt;code dir=&quot;auto&quot;&gt;cratis&lt;/code&gt; in a shell with production credentials can run &lt;code dir=&quot;auto&quot;&gt;observers replay&lt;/code&gt; there, whatever the catalog says. The confirmation default helps, since a non-interactive replay needs an explicit &lt;code dir=&quot;auto&quot;&gt;--yes&lt;/code&gt;. The Cratis AI corpus adds a guard that blocks the store-changing &lt;code dir=&quot;auto&quot;&gt;cratis chronicle&lt;/code&gt; commands it can see in an assistant’s shell commands, in Claude Code and Pi, the two assistants its hooks are wired for. &lt;a href=&quot;https://blog.cratis.io/ai-across-the-cratis-stack/&quot;&gt;&lt;em&gt;AI across the stack&lt;/em&gt;&lt;/a&gt; covers the hooks, and Chronicle MCP, a separate way for an assistant to reach a running Chronicle.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;cratis-ai-and-the-other-groups&quot;&gt;cratis ai and the other groups&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;cratis ai&lt;/code&gt; installs the &lt;a href=&quot;https://cratis.io/ai/&quot;&gt;Cratis AI corpus&lt;/a&gt;, rules, skills, agents, prompts and hooks for building with Cratis, into a repository. The corpus is still in preview. It goes into &lt;code dir=&quot;auto&quot;&gt;.cratis/ai/&lt;/code&gt; once, and each supported assistant (Claude Code, Codex, Copilot, Cursor, OpenCode and pi) gets a thin adapter that links back to that one copy. The selection is recorded in &lt;code dir=&quot;auto&quot;&gt;.cratis/ai.json&lt;/code&gt; and the installed files in a manifest with hashes. &lt;code dir=&quot;auto&quot;&gt;cratis ai install&lt;/code&gt; runs once per repository, &lt;code dir=&quot;auto&quot;&gt;cratis ai update&lt;/code&gt; re-synchronizes the recorded choice after that, and &lt;code dir=&quot;auto&quot;&gt;cratis ai uninstall&lt;/code&gt; removes the managed content you haven’t changed.&lt;/p&gt;
&lt;p&gt;The profile is the choice that matters. &lt;code dir=&quot;auto&quot;&gt;cratis/application/*&lt;/code&gt; is for building an application on Cratis, and &lt;code dir=&quot;auto&quot;&gt;cratis/engineering/*&lt;/code&gt; is for working on a Cratis framework repository itself. &lt;code dir=&quot;auto&quot;&gt;install&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;update&lt;/code&gt; never touch a file you own, and they leave a managed file you’ve edited alone unless you pass &lt;code dir=&quot;auto&quot;&gt;--force&lt;/code&gt;. &lt;code dir=&quot;auto&quot;&gt;cratis ai status&lt;/code&gt; exits nonzero when managed files were modified locally, which makes it usable as a CI check. &lt;a href=&quot;https://cratis.io/ai/getting-started/&quot;&gt;Getting started&lt;/a&gt; has the full selection.&lt;/p&gt;
&lt;p&gt;The CLI doesn’t call a language model itself. &lt;code dir=&quot;auto&quot;&gt;cratis llm use&lt;/code&gt; stores model-provider settings, for Anthropic, OpenAI or a local model, that tools such as Prologue read.&lt;/p&gt;
&lt;p&gt;Several other groups live in the same binary. &lt;code dir=&quot;auto&quot;&gt;cratis new&lt;/code&gt; scaffolds a project from the Cratis templates, in C#, Kotlin or Java, without a .NET SDK installed, and &lt;a href=&quot;https://blog.cratis.io/getting-started-with-cratis/&quot;&gt;&lt;em&gt;Getting started with Cratis, and the tools around it&lt;/em&gt;&lt;/a&gt; walks through it. &lt;code dir=&quot;auto&quot;&gt;cratis screenplay&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;cratis render&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;cratis run&lt;/code&gt; work on &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; models and belong to the modeling tools in &lt;a href=&quot;https://blog.cratis.io/screenplay-an-event-model-you-can-compile/&quot;&gt;&lt;em&gt;Screenplay&lt;/em&gt;&lt;/a&gt; and the experimental &lt;a href=&quot;https://blog.cratis.io/stage-and-scene-after-a-model-compiles/&quot;&gt;&lt;em&gt;Stage and Scene&lt;/em&gt;&lt;/a&gt;. &lt;code dir=&quot;auto&quot;&gt;cratis prologue&lt;/code&gt; belongs to Prologue, experimental as well. &lt;code dir=&quot;auto&quot;&gt;cratis arc&lt;/code&gt; inspects the commands and queries a running Arc application has registered, and it doesn’t need Chronicle.&lt;/p&gt;
&lt;p&gt;After fixing the missing author’s partition, use &lt;code dir=&quot;auto&quot;&gt;retry-partition&lt;/code&gt; for one more attempt and inspect the result. The catalog decides nothing about who runs that command. It tells a person or an agent, before they run it, that it changes the server.&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href=&quot;https://blog.cratis.io/chronicle-workbench/&quot;&gt;Part 16 — Chronicle Workbench, screen by screen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next: &lt;a href=&quot;https://blog.cratis.io/running-chronicle-in-production/&quot;&gt;Part 18 — Running Chronicle and a Cratis application in production&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>cli</category><category>chronicle</category><category>operations</category><category>ai</category><category>event-sourcing</category></item><item><title>From script to stage · Part 16: Chronicle Workbench, screen by screen</title><link>https://blog.cratis.io/chronicle-workbench/</link><guid isPermaLink="true">https://blog.cratis.io/chronicle-workbench/</guid><description>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.</description><pubDate>Wed, 30 Sep 2026 09:16:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt; · Part 16 of 26&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Chronicle Workbench has nothing to install. The Chronicle server serves it on port 35000, the same TLS port your clients connect to, so starting Chronicle starts the Workbench with it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/chronicle/workbench/&quot;&gt;Chronicle Workbench&lt;/a&gt; ships with Chronicle and runs locally in your browser. Once you’re authorized, it shows what the running Chronicle is doing, and previews how a projection will behave for the projection features it supports.&lt;/p&gt;
&lt;p&gt;Say an author registers without an error and never appears in the list of authors. The command succeeded, so the event should be in the log, and the interesting part is what happened after the append. Most of the Workbench’s screens answer a piece of that.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;starting-one-locally&quot;&gt;Starting one locally&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The quickest Workbench is the development image on your own machine, published on localhost only:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--pull=always&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--rm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;127.0.0.1:35000:35000&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;cratis/chronicle:latest-development&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--insecure&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--fail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--retry&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;30&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--retry-all-errors&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--retry-delay&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;https://localhost:35000/health&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;When the health check prints &lt;code dir=&quot;auto&quot;&gt;Healthy&lt;/code&gt;, open &lt;code dir=&quot;auto&quot;&gt;https://localhost:35000&lt;/code&gt;. The development image generates a self-signed certificate in memory, so the browser warns about it the first time. Log in as &lt;code dir=&quot;auto&quot;&gt;Admin&lt;/code&gt; with the password &lt;code dir=&quot;auto&quot;&gt;ChangeMeNow!&lt;/code&gt;. That account is built into the development images for local use, and a development image with those credentials shouldn’t be reachable from a staging or production network.&lt;/p&gt;
&lt;p&gt;A production image behaves differently at exactly this point. It needs a real TLS certificate, or the server throws at startup. It also creates the administrator without a password, and the first person to open the Workbench sets one. Supply the initial password from your secret store through &lt;code dir=&quot;auto&quot;&gt;Authentication:AdminUser&lt;/code&gt;, or keep port 35000 unreachable until setup is done. The &lt;a href=&quot;https://cratis.io/chronicle/hosting/production/&quot;&gt;production guide&lt;/a&gt; covers the certificates and the rest of what that image needs.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/chronicle/hosting/configuration/authentication/&quot;&gt;Authentication&lt;/a&gt; is on by default, with Chronicle’s built-in OAuth authority unless you configure an external one. Setting &lt;code dir=&quot;auto&quot;&gt;authentication.enabled&lt;/code&gt; to &lt;code dir=&quot;auto&quot;&gt;false&lt;/code&gt; removes it altogether. There’s no login and no token endpoint, and every gRPC service and HTTP endpoint answers anonymously. That suits an instance embedded in one container or process, talking to its own client over loopback and thrown away with it, and nothing reachable as a server.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;store-first-then-namespace&quot;&gt;Store first, then namespace&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Most screens belong to one event store and one namespace, and the address carries both. A few belong to the store as a whole, among them event types, projections and the list of namespaces. User and application management sits above any single store.&lt;/p&gt;
&lt;p&gt;With Arc’s Chronicle integration, each tenant maps to its own &lt;a href=&quot;https://cratis.io/chronicle/concepts/namespaces/&quot;&gt;namespace&lt;/a&gt;. The author who’s missing for tenant Acme is visible only in Acme’s namespace, so pick the store and the namespace before you read anything else. An empty page in the wrong namespace looks exactly like an empty page in the right one.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;is-the-event-there&quot;&gt;Is the event there?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The Sequences page is a query editor over an &lt;a href=&quot;https://cratis.io/chronicle/concepts/event-sequence/&quot;&gt;event sequence&lt;/a&gt;, with filters, saved queries and a detail view for each event. For the author feature, filter on &lt;code dir=&quot;auto&quot;&gt;AuthorRegistered&lt;/code&gt; and look for the author’s ID.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/workbench-02-event-log.D5x7SJUJ_Z2cCgX4.webp&quot; alt=&quot;The Sequences page for the acme namespace of the Authors event store, showing six AuthorRegistered events with sequence numbers 0 to 5, their event source ids and times.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2880&quot; height=&quot;1800&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The event log of one namespace.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/workbench-03-event-context.CY_u8fPZ_2qEgzq.webp&quot; alt=&quot;The same Sequences page with the event at sequence number 3 selected. Its Context tab lists the event type AuthorRegistered, the event source id, sequence number 3, the time it occurred and its causation.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2880&quot; height=&quot;1800&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;One event’s context, with a Content tab beside it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If the event isn’t in the log, the problem sits on the write side, and the command result or the append result carries the reason. If it is there, the next question is who should have handled it.&lt;/p&gt;
&lt;p&gt;Event types is one store-level list. Selecting a type opens a side panel with its schema and an Observers tab that lists the observers reading it, in every namespace. For &lt;code dir=&quot;auto&quot;&gt;AuthorRegistered&lt;/code&gt;, that list should include the projection behind the &lt;code dir=&quot;auto&quot;&gt;Author&lt;/code&gt; read model. An event type that no observer reads is recorded and then ignored, which is sometimes the intent and sometimes the bug.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;who-should-have-handled-it&quot;&gt;Who should have handled it&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The Observers page lists every &lt;a href=&quot;https://cratis.io/chronicle/concepts/observers/&quot;&gt;observer&lt;/a&gt; in the namespace, projections, reducers and reactors alike. Each row has the observer’s ID, type, owner and state, the next sequence number it will look at and how many events it has handled.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/workbench-06-observers-list.ei8z2GXG_ZDdCBP.webp&quot; alt=&quot;The Observers page for the acme namespace, filtered to Authors. It lists a projection, Authors.Author, and a reactor, Authors.AuthorWelcomeMail, with their sequence, owner, next event sequence number, handled events and state. Both are Active with next sequence number 6. The projection has handled 6 events and the reactor 5.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2880&quot; height=&quot;1800&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Both rows read Active. The reactor has a failed partition, and the list doesn’t show it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;An observer’s position doesn’t say whether it’s healthy, because one that has stopped consuming looks exactly like one with nothing to do. The Failed partitions page answers that.&lt;/p&gt;
&lt;p&gt;The observer’s detail view has a connected clients section in its Summary tab, with each client’s connection, type, version, machine, process ID and process. When a client was last seen is on the Connected Clients page. An observer with no connected client has nobody to deliver events to. That’s the normal state while the application is stopped, and the first thing to check when it’s supposed to be running.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-it-failed&quot;&gt;Why it failed&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;When a &lt;a href=&quot;https://cratis.io/chronicle/concepts/observers/&quot;&gt;partition&lt;/a&gt; fails, Chronicle stops that partition and retries it with backoff, and the observer’s other partitions carry on. A partition is the events of one event source, so for the author feature it’s one author, and the failed partition is labelled with the author’s ID. You can go straight from “this author is missing” to the partition that holds the reason.&lt;/p&gt;
&lt;p&gt;The Failed partitions page lists each one with its status, the partition, the number of attempts and the last attempt, and it has a detail panel per partition. Status says whether the partition has been quarantined. When the status value is missing, the page shows &lt;strong&gt;Unknown&lt;/strong&gt;, and that’s a gap in what the page knows about the partition. It doesn’t mean the partition is fine.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.cratis.io/inside-chronicle/&quot;&gt;&lt;em&gt;Inside Chronicle&lt;/em&gt;&lt;/a&gt; explains the failure kinds that separate a handler bug from congestion. The detail panel lists each attempt’s time, sequence number, messages and stack trace. As of Chronicle 19.23.1 it doesn’t show the kind.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/workbench-09-failed-partition-detail.Ddkd0p6n_Z2qL6eT.webp&quot; alt=&quot;The Failed partitions page for the acme namespace with one failed partition selected. The detail panel shows the observer Authors.AuthorWelcomeMail, the status Failed and 6 attempts. Each attempt lists its time, sequence number 3, its messages, including Welcome mail could not be sent: smtp.authors.local: connection refused, and a stack trace.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2880&quot; height=&quot;1800&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A failed partition’s detail: one entry per attempt, with its messages and stack trace.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/failed-attempt-kinds.BmJjrx4c_GBlBv.webp&quot; alt=&quot;A diagram titled What a failed attempt says, four cards in a two-by-two grid. Handling: the handler threw; something is wrong in the code. Timeout: no answer in time; the system was congested; doesn’t count toward quarantine thresholds. Disconnected: the subscriber was gone when the events arrived. Unknown: nothing classified the failure. A band underneath reads: One failed partition per event source, such as one author, while the other partitions keep going.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2000&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The kind of an attempt separates a bug from congestion.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The retry policy and the timeout exception to observer quarantine are covered in &lt;a href=&quot;https://blog.cratis.io/inside-chronicle/&quot;&gt;&lt;em&gt;Inside Chronicle&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The page itself has one action, Retry, which asks for one more attempt at that partition. Fixing the cause comes first, because a retry against the same bug fails the same way.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/workbench-31-failed-partition-recovers.BjORrz7c_br15h.webp&quot; alt=&quot;Screen recording of the Workbench Failed partitions page. A failed partition with its attempt messages is selected, Retry is pressed after the cause has been fixed off screen, and the list becomes empty. The Observers page then shows both Authors observers Active with six handled events.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1200&quot; height=&quot;750&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fix the cause, press Retry, and the partition leaves the list.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-the-view-actually-holds&quot;&gt;What the view actually holds&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The Read models page lists the read-model definitions in the namespace, their occurrences by generation, and a paged list of the instances in each. For the missing author, look for the author’s ID among the &lt;code dir=&quot;auto&quot;&gt;Author&lt;/code&gt; instances. When an instance exists but holds old content, the time machine shows the snapshots of that one instance, so you can see how its state got to where it is.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/workbench-10-read-model-instances.DiE7ZfrJ_UeJFx.webp&quot; alt=&quot;The Read Models page for the acme namespace with the Authors read model selected, listing six instances with their ids and names.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2880&quot; height=&quot;1800&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The instances of a read model in one namespace.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/workbench-30-event-to-read-model.jv5ddwmi_ZGbtnf.webp&quot; alt=&quot;Screen recording of the Chronicle Workbench. The event log of the acme namespace lists six AuthorRegistered events. One is opened to show its context and its content, the name Corwin Ashdown. The Read Models page then lists the Author instance with the same id and name.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1200&quot; height=&quot;750&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;From one event to the read-model instance built from it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/chronicle/projections/&quot;&gt;Projection&lt;/a&gt; definitions live at store level, on the projections page, with an editor and a preview. The preview shows how a projection will behave for the features it supports, so a change to the &lt;code dir=&quot;auto&quot;&gt;Author&lt;/code&gt; projection can be tried there first, within what the preview covers. What happens when a changed definition is deployed, and why Chronicle sometimes replays and sometimes only recommends it, is in &lt;a href=&quot;https://blog.cratis.io/chronicle-jobs-scale-and-performance/&quot;&gt;&lt;em&gt;Jobs, scale and performance&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/workbench-15-projection.qG1Ymd_4_Z2gjkvf.webp&quot; alt=&quot;The Projections page with Authors.Author selected. The editor shows its declaration: projection Authors.Author to Author, from AuthorRegistered.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2880&quot; height=&quot;1800&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A projection’s declaration in the editor.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;around-the-edges&quot;&gt;Around the edges&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The dashboard gives key figures, a distribution of events by type and a breakdown by namespace. The Jobs page shows replays, catch-ups, migrations and failed-partition retries with their type, status and progress. The Recommendations page shows the replays Chronicle has filed for a person to decide on, each with a name, a description and when it occurred.&lt;/p&gt;
&lt;p&gt;The other store-level pages aren’t on the missing author’s route: read-model types, known identities, &lt;a href=&quot;https://cratis.io/chronicle/webhooks/&quot;&gt;webhooks&lt;/a&gt;, external services, captures and a page that seeds events into a store.&lt;/p&gt;
&lt;p&gt;Above the stores, the Servers page lists each server’s address, status, CPU and memory. It’s where a cluster with more than one node shows up, and it doesn’t show which node runs which partition. The other system screens list users, OAuth applications and connected clients, and development builds add a page of development tools.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/workbench-areas.B5BlILTE_Z10Er5L.webp&quot; alt=&quot;A diagram titled Workbench, area by area, four framed groups. Events: sequences, event types. Views: read models, projections. Processing: observers, failed partitions, jobs, recommendations. System: users, applications, connected clients, servers. Sequences, projections, observers, failed partitions, jobs, recommendations, users and applications are highlighted with a tag reading actions. A band underneath reads: Anyone who can sign in sees every screen, actions included.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Workbench’s main screens, and which ones carry actions.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;screens-that-change-things&quot;&gt;Screens that change things&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Several screens have buttons that change the store. On Sequences you can append an event, &lt;a href=&quot;https://cratis.io/chronicle/events/revision/&quot;&gt;revise&lt;/a&gt; one, or &lt;a href=&quot;https://cratis.io/chronicle/events/redaction/&quot;&gt;redact&lt;/a&gt; a single event or every event of one event source. Observers has Replay, Clear quarantine and Remove. Jobs has Stop, Resume and Delete. On Recommendations, Perform replays the observer the recommendation names, and Ignore drops it. The seed-data page writes events, the projections page saves definitions, the system screens add and remove users and OAuth applications, and the store-level pages remove webhooks and external services and stop or delete captures.&lt;/p&gt;
&lt;p&gt;Some of those buttons reach further than they look. Removing an observer is refused while any client is subscribed to it in any namespace. Once it goes through, it applies to every namespace and leaves the read-model data where it is, and if the observer registers again later, it replays from the start of the log. A replay rebuilds from sequence zero, which on a large store takes time. A revision keeps the original next to the correction, and redaction replaces an event’s content with a marker in the same slot. &lt;a href=&quot;https://blog.cratis.io/four-changes-in-year-two/&quot;&gt;&lt;em&gt;Year two&lt;/em&gt;&lt;/a&gt; goes through what each kind of correction means.&lt;/p&gt;
&lt;p&gt;The Workbench is built for looking at a running Chronicle and previewing projections. It isn’t a full administration console, and nothing about its buttons makes a change on a production store reviewed or safe. Behind the screens there’s also no role model. Anyone who can sign in gets every screen, the ones that change things included, and the password-change prompt on first login doesn’t restrict anything else they can call. Workbench has screens for revising and redacting events. Being able to open a tool or run a command isn’t permission to use it on a production system.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-browser-or-the-terminal&quot;&gt;The browser or the terminal&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The Workbench is served by the server, behind the server’s authentication. On a machine you reached over SSH, &lt;code dir=&quot;auto&quot;&gt;cratis chronicle workbench&lt;/code&gt; opens a full-screen terminal dashboard over the CLI’s connection instead. It has views for observers, failed partitions, jobs, recommendations, event sequences, event types, projections and read models, plus the server-level lists, and each action goes through a confirmation dialog. It has no screens for appending, revising or redacting events, and no projection editor. &lt;a href=&quot;https://blog.cratis.io/the-cratis-cli/&quot;&gt;&lt;em&gt;The Cratis CLI&lt;/em&gt;&lt;/a&gt; covers it with the rest of the CLI.&lt;/p&gt;
&lt;p&gt;The CLI’s &lt;code dir=&quot;auto&quot;&gt;cratis chronicle diagnose&lt;/code&gt; reports failed partitions in one summary and names the next command to run. The two work well side by side. The terminal says that a partition failed, and the Workbench shows the observer, its clients and the instance it was supposed to update.&lt;/p&gt;
&lt;p&gt;From VS Code, &lt;a href=&quot;https://github.com/Cratis/Narrator&quot;&gt;Narrator&lt;/a&gt;, which we still treat as experimental, browses the same stores and only reads.&lt;/p&gt;
&lt;p&gt;Follow the missing author from the event on the Sequences page to the observers listed on its event type, then to the failed partition labeled with the author’s ID and the error its attempts recorded. The &lt;code dir=&quot;auto&quot;&gt;Author&lt;/code&gt; instance on the Read models page shows what the list was actually reading. The fix goes into the code, and whether anyone presses Retry afterwards, on a production system, is a decision for whoever runs that system.&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href=&quot;https://blog.cratis.io/arc-gotchas/&quot;&gt;Part 15 — Arc gotchas: where a convention decides for you&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next: &lt;a href=&quot;https://blog.cratis.io/the-cratis-cli/&quot;&gt;Part 17 — The Cratis CLI, from diagnose to llm-context&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>chronicle</category><category>workbench</category><category>operations</category><category>event-sourcing</category></item><item><title>From script to stage · Part 15: Arc gotchas: where a convention decides for you</title><link>https://blog.cratis.io/arc-gotchas/</link><guid isPermaLink="true">https://blog.cratis.io/arc-gotchas/</guid><description>Three places Arc&apos;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.</description><pubDate>Wed, 30 Sep 2026 09:15:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt; · Part 15 of 26&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A validation rule that applies only sometimes on the server can apply every time in the browser. Arc’s proxy generator copies the rules it recognizes from a C# validator into the generated TypeScript, and it doesn’t copy &lt;code dir=&quot;auto&quot;&gt;When&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;Unless&lt;/code&gt; conditions. A rule that sat inside a condition can arrive on the client &lt;a href=&quot;https://cratis.io/arc/backend/csharp/proxy-generation/validation/&quot;&gt;without it&lt;/a&gt;, and a form will then refuse input the server would have accepted.&lt;/p&gt;
&lt;p&gt;Most of what Arc saves you comes from conventions. A namespace becomes a route, a validator becomes browser rules, a property becomes a field in the generated TypeScript. Each convention makes one decision in the place that owns it, and each has an edge that’s documented but easy to miss until it costs an afternoon.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.cratis.io/arc-commands-and-queries/&quot;&gt;&lt;em&gt;Arc: commands and queries without the plumbing&lt;/em&gt;&lt;/a&gt; covers the pipeline and the proxies in general, and &lt;a href=&quot;https://blog.cratis.io/validation-all-the-way-down/&quot;&gt;&lt;em&gt;Validation all the way down&lt;/em&gt;&lt;/a&gt; follows a rule through every layer it can live in.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;validation-that-runs-in-two-places&quot;&gt;Validation that runs in two places&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The server is where validation counts. For a command, Arc runs authorization first, then &lt;a href=&quot;https://cratis.io/arc/backend/csharp/commands/validation/&quot;&gt;validation&lt;/a&gt;, from data annotations, FluentValidation validators and concept validators, and only then &lt;code dir=&quot;auto&quot;&gt;Provide()&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Handle()&lt;/code&gt;. &lt;code dir=&quot;auto&quot;&gt;POST &amp;#x3C;command-route&gt;/validate&lt;/code&gt; runs the same authorization and validation without calling the handler.&lt;/p&gt;
&lt;p&gt;The browser gets a copy. At build time the proxy generator loads the compiled assembly, finds the validator for each command, creates an instance and inspects its rules, then writes the ones it can express into the generated TypeScript. In the Arc and React sample the idea title is a concept with a validator of its own:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-0&quot; id=&quot;tab-0-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-1&quot; id=&quot;tab-0-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-2&quot; id=&quot;tab-0-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-3&quot; id=&quot;tab-0-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-4&quot; id=&quot;tab-0-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-0&quot; aria-labelledby=&quot;tab-0-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IdeaTitleValidator&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ConceptValidator&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IdeaTitle&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IdeaTitleValidator&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RuleFor&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_ &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;NotEmpty&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;MaximumLength&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;72&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-1&quot; aria-labelledby=&quot;tab-0-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;These rules run on the server only. Unlike the .NET generator described on this page, Arc for Kotlin doesn’t copy them into the generated TypeScript.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@Component&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IdeaTitleValidator&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;ConceptValidator&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IdeaTitle&lt;/span&gt;&lt;span&gt;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; conceptType: &lt;/span&gt;&lt;span&gt;Class&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IdeaTitle&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; IdeaTitle::&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;.java&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;validate&lt;/span&gt;&lt;span&gt;(concept: &lt;/span&gt;&lt;span&gt;IdeaTitle&lt;/span&gt;&lt;span&gt;): &lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;ValidationResult&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;buildList&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (concept.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;isBlank&lt;/span&gt;&lt;span&gt;()) &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(ValidationResult.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;A title is required.&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (concept.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;().length &lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;72&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(ValidationResult.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;A title can be at most 72 characters.&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-2&quot; aria-labelledby=&quot;tab-0-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;These rules run on the server only. Unlike the .NET generator described on this page, Arc for Kotlin doesn’t copy them into the generated TypeScript.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Component&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;final&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IdeaTitleValidator&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;implements&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ConceptValidator&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IdeaTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Class&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IdeaTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;getConceptType&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IdeaTitle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;ValidationResult&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;validate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IdeaTitle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;concept&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;results&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ArrayList&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;ValidationResult&lt;/span&gt;&lt;span&gt;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;concept&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;isBlank&lt;/span&gt;&lt;span&gt;&lt;span&gt;())&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;results&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ValidationResult&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;A title is required.&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;concept&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;72&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;results&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ValidationResult&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;A title can be at most 72 characters.&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; results&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-3&quot; aria-labelledby=&quot;tab-0-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;validator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IdeaTitle&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IdeaTitleValidator&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ConceptValidator&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IdeaTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;constructor&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;super&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ruleFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;notEmpty&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;maxLength&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;72&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-4&quot; aria-labelledby=&quot;tab-0-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Arc for Elixir doesn’t exist; use the Chronicle Elixir client directly.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;and the generated command proxy carries one statement per rule:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ruleFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;notEmpty&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ruleFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;maxLength&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;72&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The copy covers a fixed set. From FluentValidation that’s &lt;code dir=&quot;auto&quot;&gt;NotEmpty&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;NotNull&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;EmailAddress&lt;/code&gt;, the length rules, &lt;code dir=&quot;auto&quot;&gt;Matches&lt;/code&gt; and the four numeric comparisons against constants. From data annotations it’s &lt;code dir=&quot;auto&quot;&gt;[Required]&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;[EmailAddress]&lt;/code&gt;, the length attributes, &lt;code dir=&quot;auto&quot;&gt;[Range]&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;[RegularExpression]&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;[Url]&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;[Phone]&lt;/code&gt;. Per member, an explicit FluentValidation validator wins, a concept validator’s rules add to it, and data annotations fill in only where neither contributed anything.&lt;/p&gt;
&lt;p&gt;Everything else stays on the server. &lt;code dir=&quot;auto&quot;&gt;Must&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;MustAsync&lt;/code&gt;, custom validators and any check that needs a service are never turned into browser logic. The author feature’s name-uniqueness rule never reaches the browser either, because it’s a Chronicle constraint checked when the event is appended, as &lt;a href=&quot;https://blog.cratis.io/rules-that-hold-when-you-write/&quot;&gt;&lt;em&gt;Rules that hold when you write&lt;/em&gt;&lt;/a&gt; explains. Comparisons between two properties and comparisons against a constant that isn’t a number, a date for example, aren’t projected. A .NET regular expression is copied as it is, so it has to mean the same thing in JavaScript.&lt;/p&gt;
&lt;p&gt;Conditions are the sharp edge. &lt;code dir=&quot;auto&quot;&gt;When&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Unless&lt;/code&gt;, rule sets, cascade behavior and severity that depends on runtime values aren’t reproduced. Say the author feature lets people register under a pen name, and the validator says &lt;code dir=&quot;auto&quot;&gt;PenName&lt;/code&gt; must not be empty &lt;code dir=&quot;auto&quot;&gt;When&lt;/code&gt; the author chose to use one. The browser can receive the &lt;code dir=&quot;auto&quot;&gt;notEmpty()&lt;/code&gt; rule without its condition and block every registration that leaves the pen name blank. A blocking result in the browser can stop the request, so the server never gets to say otherwise.&lt;/p&gt;
&lt;p&gt;A few quieter cases pass the .NET build:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The generator runs the validator’s constructor. It prefers a parameterless one, and otherwise passes &lt;code dir=&quot;auto&quot;&gt;null&lt;/code&gt; for reference dependencies and defaults for values. If construction or reflection fails, it can extract no FluentValidation rules at all, and a green build is not proof that the rules were emitted.&lt;/li&gt;
&lt;li&gt;A message built with a &lt;code dir=&quot;auto&quot;&gt;.WithMessage(x =&gt; ...)&lt;/code&gt; factory isn’t evaluated, so the browser shows its default message for that rule. A literal message is copied.&lt;/li&gt;
&lt;li&gt;Rules at Error severity block in the browser by default. Warning and Info block only with &lt;code dir=&quot;auto&quot;&gt;[BlockOnValidationSeverity]&lt;/code&gt;, and a severity decided at runtime is left out of the client so it can’t block by mistake. Extraction happens at build time, so a change to FluentValidation’s global default severity isn’t seen.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;[CreditCard]&lt;/code&gt; emits a &lt;code dir=&quot;auto&quot;&gt;creditCard()&lt;/code&gt; call the client rule builder doesn’t implement, so the generated file fails TypeScript checking. &lt;code dir=&quot;auto&quot;&gt;Required.AllowEmptyStrings&lt;/code&gt; isn’t projected, and messages from resource files aren’t resolved.&lt;/li&gt;
&lt;li&gt;On a positional record, the server’s data-annotations filter reads property attributes, not constructor-parameter ones. Write &lt;code dir=&quot;auto&quot;&gt;[property: Required]&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After changing a validator, open the generated file, then try one accepted and one rejected value against both the form and the server. Never patch the generated validator. When a rule must stay server-only, write it in a form the generator doesn’t translate, a &lt;code dir=&quot;auto&quot;&gt;Must&lt;/code&gt; predicate for example. The browser rules are a convenience copy, and the server decides. &lt;a href=&quot;https://blog.cratis.io/live-uis-with-arc/&quot;&gt;&lt;em&gt;Live UIs&lt;/em&gt;&lt;/a&gt; shows how forms use them, and &lt;a href=&quot;https://cratis.io/arc/understanding-the-proxy-boundary/&quot;&gt;the proxy boundary&lt;/a&gt; explains what crosses from C# into TypeScript.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/validation-twice.CoaQJJpm_67UQT.webp&quot; alt=&quot;A diagram titled Validation, twice. A shared node at the top, One validator, has an arrow into each side. On the left, the server: Authorize, then Validate: every rule, including Must, When / Unless and checks that need a service, then Handle. On the right, the browser: the fixed subset of NotEmpty, NotNull, length, Matches, email and numeric comparisons, and a notEmpty() rule marked can become unconditional, with an arrow into it from the server’s validate step. A band at the bottom reads The server decides.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2100&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;One validator, two programs.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-folder-the-generator-owns&quot;&gt;A folder the generator owns&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every generated TypeScript file starts with a header in the form &lt;code dir=&quot;auto&quot;&gt;// @generated by Cratis. Source: &amp;#x3C;type&gt;. Time: &amp;#x3C;utc&gt;. Hash: &amp;#x3C;sha256&gt;&lt;/code&gt;, followed by a DO NOT EDIT banner. In incremental mode, that header decides what the generator may delete.&lt;/p&gt;
&lt;p&gt;There are two ways to run the generator, and their defaults differ. The MSBuild integration keeps the output directory, updates it incrementally and skips a write when the content hash matches. The generator executable run directly deletes the output directory first, unless you pass &lt;code dir=&quot;auto&quot;&gt;--skip-output-deletion&lt;/code&gt;. You can make the build do the same:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;PropertyGroup&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CratisProxiesSkipOutputDeletion&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;/span&gt;&lt;span&gt;CratisProxiesSkipOutputDeletion&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;PropertyGroup&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;With that &lt;a href=&quot;https://cratis.io/arc/backend/csharp/proxy-generation/configuration/output-behavior/&quot;&gt;output behavior&lt;/a&gt;, the generator recursively deletes the entire output directory, handwritten files and indexes included, before it writes the proxies. Never point it at your frontend source root.&lt;/p&gt;
&lt;p&gt;Incremental mode deletes too. After each run the generator scans the &lt;code dir=&quot;auto&quot;&gt;.ts&lt;/code&gt; files below its output path, finds the ones with valid generated metadata that this run didn’t produce, and removes them. That’s how renaming &lt;code dir=&quot;auto&quot;&gt;UpdateOrder&lt;/code&gt; to &lt;code dir=&quot;auto&quot;&gt;ModifyOrder&lt;/code&gt; gets rid of the old file. The scan covers the whole output tree, not one input assembly, so two independent generation runs writing into the same tree can each take the other’s proxies for orphans. &lt;code dir=&quot;auto&quot;&gt;CratisProxiesSkipFileIndexTracking&lt;/code&gt; is still forwarded by the build package, and the executable ignores it. There’s no supported way to turn &lt;a href=&quot;https://cratis.io/arc/backend/csharp/proxy-generation/file-index-tracking/&quot;&gt;orphan cleanup&lt;/a&gt; off.&lt;/p&gt;
&lt;p&gt;Only files with the header are eligible for that cleanup, so a handwritten file in the same folder isn’t swept by it. Even so, incremental mode doesn’t guarantee to preserve a mixed source tree, and a separate cleanup phase for emptied directories can remove an &lt;code dir=&quot;auto&quot;&gt;index.ts&lt;/code&gt;. The generator treats &lt;code dir=&quot;auto&quot;&gt;index.ts&lt;/code&gt; differently from its other files. It never carries the header, because it’s a file you edit. The generator adds exports for the files it generated and removes exports whose target is gone, and it leaves exports for your own files for you to write.&lt;/p&gt;
&lt;p&gt;Two side effects follow from the header. It embeds the generation time, so a full regeneration that deletes files defeats the hash check and can produce Git diffs for contracts that didn’t change. And tools can recognize generated files by it. The &lt;code dir=&quot;auto&quot;&gt;skip-generated-proxies&lt;/code&gt; processor in &lt;code dir=&quot;auto&quot;&gt;@cratis/eslint-plugin-arc&lt;/code&gt; &lt;a href=&quot;https://cratis.io/arc/backend/csharp/proxy-generation/linting/&quot;&gt;skips linting them&lt;/a&gt;, and the Cratis AI write guard refuses to let an assistant edit them, as &lt;a href=&quot;https://blog.cratis.io/ai-across-the-cratis-stack/&quot;&gt;&lt;em&gt;AI across the Cratis stack&lt;/em&gt;&lt;/a&gt; describes.&lt;/p&gt;
&lt;p&gt;Arc’s recommendation is to “use a dedicated, generated-only directory with one generation owner.” The &lt;code dir=&quot;auto&quot;&gt;cratis&lt;/code&gt; template, from &lt;a href=&quot;https://blog.cratis.io/getting-started-with-cratis/&quot;&gt;&lt;em&gt;Getting started with Cratis, and the tools around it&lt;/em&gt;&lt;/a&gt;, and the Arc and React sample both do the opposite on purpose. They set &lt;code dir=&quot;auto&quot;&gt;CratisProxiesOutputPath&lt;/code&gt; to the project directory, write each proxy beside its C# source, run in incremental mode and rely on the header. It works, and it isn’t the recommended arrangement. In a project set up that way, full regeneration would delete the project folder, so leave &lt;code dir=&quot;auto&quot;&gt;CratisProxiesSkipOutputDeletion&lt;/code&gt; at &lt;code dir=&quot;auto&quot;&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/generator-owns-the-folder.CtwKPExn_Z1MHmje.webp&quot; alt=&quot;A diagram titled Who owns the folder. Two folders side by side. One, marked generated-only, one owner, holds generated files and an index.ts. The other, a mixed folder, holds a generated file, a handwritten CaptureIdeaDialog.tsx and an index.ts. Below, a generator row, what each run deletes, has an arrow to each of three cards. Full regeneration: everything, handwritten files included. Incremental run: generated files it didn’t produce. Second run, same tree: the first run’s files.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2200&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Give the generator a folder of its own.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;names-that-do-the-mapping&quot;&gt;Names that do the mapping&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Several things in Arc and Chronicle are resolved by name, with defaults that are usually right. Each default is a decision made somewhere you may not be looking.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;routes-are-computed-twice&quot;&gt;Routes are computed twice&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A conventional route is the API prefix, the artifact’s namespace with a configured number of leading segments skipped, in kebab case, and the artifact’s name. A query uses its method name, not its read-model type, as the last segment. For namespace &lt;code dir=&quot;auto&quot;&gt;MyApp.Orders.Registration&lt;/code&gt; and command &lt;code dir=&quot;auto&quot;&gt;CreateOrder&lt;/code&gt;, with one segment skipped, the &lt;a href=&quot;https://cratis.io/arc/backend/csharp/proxy-generation/configuration/routing/&quot;&gt;route&lt;/a&gt; is &lt;code dir=&quot;auto&quot;&gt;/api/orders/registration/create-order&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The generator and the server each compute that route from their own settings. The Arc and React sample sets both sides, in the project file and in &lt;code dir=&quot;auto&quot;&gt;Program.cs&lt;/code&gt;, and the generated client for its &lt;code dir=&quot;auto&quot;&gt;CaptureIdea&lt;/code&gt; command in &lt;code dir=&quot;auto&quot;&gt;Arc.React.Ideas.Board&lt;/code&gt; carries &lt;code dir=&quot;auto&quot;&gt;route = &apos;/api/ideas/board&apos;&lt;/code&gt;:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-0&quot; id=&quot;tab-1-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-1&quot; id=&quot;tab-1-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-2&quot; id=&quot;tab-1-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-3&quot; id=&quot;tab-1-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-4&quot; id=&quot;tab-1-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-0&quot; aria-labelledby=&quot;tab-1-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CratisProxiesSegmentsToSkip&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;/span&gt;&lt;span&gt;CratisProxiesSegmentsToSkip&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CratisProxiesSkipCommandNameInRoute&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;/span&gt;&lt;span&gt;CratisProxiesSkipCommandNameInRoute&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-1&quot; aria-labelledby=&quot;tab-1-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratisArc&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;endpoints&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;segmentsToSkip.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;includeCommandNames.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-2&quot; aria-labelledby=&quot;tab-1-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratisArc&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;endpoints&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;segmentsToSkip.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;includeCommandNames.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-3&quot; aria-labelledby=&quot;tab-1-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;arc-proxygenerator&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--project&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tsconfig.json&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--artifacts&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--output&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--use-proxy-file-suffix&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--segments-to-skip&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--skip-command-name-in-route&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-4&quot; aria-labelledby=&quot;tab-1-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Arc for Elixir doesn’t exist; use the Chronicle Elixir client directly.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-0&quot; id=&quot;tab-2-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-1&quot; id=&quot;tab-2-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-2&quot; id=&quot;tab-2-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-3&quot; id=&quot;tab-2-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-4&quot; id=&quot;tab-2-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-0&quot; aria-labelledby=&quot;tab-2-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;AddCratisArc&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;options &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;options&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GeneratedApis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;RoutePrefix&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;api&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;options&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GeneratedApis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;IncludeCommandNameInRoute&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;options&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GeneratedApis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SegmentsToSkipForRoute&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-1&quot; aria-labelledby=&quot;tab-2-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis.arc.endpoints.route-prefix&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;api&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis.arc.endpoints.include-command-name-in-route&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis.arc.endpoints.segments-to-skip-for-route&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-2&quot; aria-labelledby=&quot;tab-2-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis.arc.endpoints.route-prefix&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;api&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis.arc.endpoints.include-command-name-in-route&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis.arc.endpoints.segments-to-skip-for-route&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-3&quot; aria-labelledby=&quot;tab-2-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ArcApplication&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;createBuilder&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;generatedApis&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;routePrefix&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;api&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;includeCommandNameInRoute&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;segmentsToSkipForRoute&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-4&quot; aria-labelledby=&quot;tab-2-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Arc for Elixir doesn’t exist; use the Chronicle Elixir client directly.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;The build properties configure the generator only and don’t change the running application’s endpoints. &lt;code dir=&quot;auto&quot;&gt;IncludeCommandNameInRoute&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;IncludeQueryNameInRoute&lt;/code&gt; are the inverse of the generator’s skip properties, and both sides default to skipping no segments. The templates skip one and put the server side in &lt;code dir=&quot;auto&quot;&gt;appsettings.json&lt;/code&gt;. Change one side without the other and the generated client calls a route the backend doesn’t map.&lt;/p&gt;
&lt;p&gt;With command-name skipping on, the generator restores command names when more than one command shares a namespace after skipping. A namespace with one command gets a route without the command’s name. Add a second command to that namespace and the first one’s generated route moves, from &lt;code dir=&quot;auto&quot;&gt;/api/orders/registration&lt;/code&gt; to &lt;code dir=&quot;auto&quot;&gt;/api/orders/registration/create-order&lt;/code&gt;. An explicit &lt;code dir=&quot;auto&quot;&gt;[Path]&lt;/code&gt; pins a route, and the generator uses it as written, without the API prefix.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/two-settings-one-route.CQM0G_Zt_1M8ovn.webp&quot; alt=&quot;A diagram titled Two settings, one route. At the top, the build property for the generator: CratisProxiesSegmentsToSkip = 2 and CratisProxiesSkipCommandNameInRoute = true. Lower down, ArcOptions.GeneratedApis for the server: SegmentsToSkipForRoute = 2 and IncludeCommandNameInRoute = false. Both point to one rewrite between them: the namespace Arc.React.Ideas.Board becomes /api/ideas/board. Below, a panel headed When they disagree: the generator skips 2 and gets /api/ideas/board, the server skips 1 and gets /api/react/ideas/board, and a red cross marks that the client calls a route the backend doesn’t map.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The generator and the server have to agree.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;which-stream-a-command-appends-to&quot;&gt;Which stream a command appends to&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A command that appends through Arc’s &lt;a href=&quot;https://cratis.io/arc/backend/csharp/chronicle/commands/events/&quot;&gt;Chronicle integration&lt;/a&gt; needs an event source ID. &lt;code dir=&quot;auto&quot;&gt;ICanProvideEventSourceId&lt;/code&gt; wins if the command implements it. Otherwise Arc takes the first property that is an &lt;code dir=&quot;auto&quot;&gt;EventSourceId&lt;/code&gt;, derives from &lt;code dir=&quot;auto&quot;&gt;EventSourceId&amp;#x3C;T&gt;&lt;/code&gt; or carries &lt;code dir=&quot;auto&quot;&gt;[Key]&lt;/code&gt;, with no preference for the typed one, so a command should have exactly one candidate. If no property matches, a new &lt;code dir=&quot;auto&quot;&gt;EventSourceId&lt;/code&gt; is created so the append still succeeds. A command meant to add to an existing author’s stream then starts a new stream, and nothing reports an error.&lt;/p&gt;
&lt;p&gt;Returning the identity with the event, as the &lt;code dir=&quot;auto&quot;&gt;cratis&lt;/code&gt; template’s &lt;code dir=&quot;auto&quot;&gt;Register&lt;/code&gt; command does with &lt;code dir=&quot;auto&quot;&gt;(SomeId, Registered)&lt;/code&gt;, works only for a type that derives from &lt;code dir=&quot;auto&quot;&gt;EventSourceId&lt;/code&gt;. A raw &lt;code dir=&quot;auto&quot;&gt;Guid&lt;/code&gt; in the same tuple is just a response value, and the event goes to the fallback stream. The &lt;a href=&quot;https://cratis.io/arc/backend/csharp/chronicle/code-analysis/arcchr0010/&quot;&gt;ARCCHR0010&lt;/a&gt; analyzer flags that shape, and ARCCHR0002 flags a command with more than one candidate property.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;an-events-name-is-its-identity&quot;&gt;An event’s name is its identity&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;[EventType]&lt;/code&gt; takes an optional ID, and without one the &lt;a href=&quot;https://cratis.io/chronicle/concepts/event-type/&quot;&gt;event type&lt;/a&gt; is identified by its type name:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-0&quot; id=&quot;tab-3-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-1&quot; id=&quot;tab-3-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-2&quot; id=&quot;tab-3-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-3&quot; id=&quot;tab-3-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-4&quot; id=&quot;tab-3-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-0&quot; aria-labelledby=&quot;tab-3-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;/// &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;param&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;Optional identifier of the event type, if not used it will default to the type name.&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;/span&gt;&lt;span&gt;param&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;/// &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;param&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;generation&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;see&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cref&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;EventTypeGeneration&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;span&gt; represented as &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;see&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cref&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;uint&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;/span&gt;&lt;span&gt;param&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sealed&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EventTypeAttribute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; id &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;uint&lt;/span&gt;&lt;span&gt; generation &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EventTypeGeneration&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;FirstValue&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Attribute&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-1&quot; aria-labelledby=&quot;tab-3-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;* &lt;/span&gt;&lt;span&gt;@property&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; The UUID string identifier for this event type. If empty, the class simple name is used.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;* &lt;/span&gt;&lt;span&gt;@property&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;generation&lt;/span&gt;&lt;span&gt; The generation number of this event type. Defaults to 1.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;annotation&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; id: &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; generation: &lt;/span&gt;&lt;span&gt;Int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-2&quot; aria-labelledby=&quot;tab-3-2&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Java uses the Kotlin client’s &lt;code dir=&quot;auto&quot;&gt;@EventType&lt;/code&gt; annotation, and the class’s simple name is the identity when &lt;code dir=&quot;auto&quot;&gt;id&lt;/code&gt; is empty.&lt;/p&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-3&quot; aria-labelledby=&quot;tab-3-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;* - `@eventType()` -&gt; uses class name as id, generation 1, tombstone false.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;eventType&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChronicleClassDecorator&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;eventType&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChronicleClassDecorator&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;eventType&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;generation&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChronicleClassDecorator&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-4&quot; aria-labelledby=&quot;tab-3-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;The Elixir client has no default: &lt;code dir=&quot;auto&quot;&gt;id:&lt;/code&gt; is required in &lt;code dir=&quot;auto&quot;&gt;use Chronicle.Events.EventType&lt;/code&gt;.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;Rename &lt;code dir=&quot;auto&quot;&gt;AuthorRegistered&lt;/code&gt; and the events you append from then on carry the new identity, while the ones already stored keep the old one. Give an event that has to last an explicit ID. When you add a &lt;a href=&quot;https://cratis.io/chronicle/concepts/event-type-migrations/&quot;&gt;generation&lt;/a&gt;, put &lt;code dir=&quot;auto&quot;&gt;[EventTypeGenerationFor&amp;#x3C;T&gt;]&lt;/code&gt; on the previous one. It takes the ID from the current generation’s own &lt;code dir=&quot;auto&quot;&gt;[EventType]&lt;/code&gt;, so the two can’t drift apart the way hand-typed IDs on both can. Analyzer &lt;a href=&quot;https://cratis.io/chronicle/code-analysis/chr0037/&quot;&gt;CHR0037&lt;/a&gt; warns when the generations a migration names don’t resolve to the same event type differing only by generation. It looks at migrations, so a rename without one gets no warning from it. &lt;a href=&quot;https://blog.cratis.io/four-changes-in-year-two/&quot;&gt;&lt;em&gt;Year two&lt;/em&gt;&lt;/a&gt; goes further into changing events.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;projections-and-proxies-match-by-name&quot;&gt;Projections and proxies match by name&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Declarative projections &lt;a href=&quot;https://cratis.io/chronicle/projections/declarative/auto-map/&quot;&gt;map matching names&lt;/a&gt; by default. An event property and a read-model property map when their names match after the client’s serialization naming policy is applied and the value can be assigned to the read-model property. Where they differ, you write the mapping. An event a projection only counts, increments or adds from contributes nothing to that automatic mapping. &lt;a href=&quot;https://blog.cratis.io/from-event-to-read-model/&quot;&gt;&lt;em&gt;From event to read model&lt;/em&gt;&lt;/a&gt; covers the rest of it.&lt;/p&gt;
&lt;p&gt;The proxy generator collects the public properties declared directly on a command type, so a property inherited from a base record doesn’t become a field in the TypeScript. Field names are the C# names in camel case.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-tenant-arrives-by-header-name&quot;&gt;The tenant arrives by header name&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;In .NET, Arc’s header resolver reads the header named in &lt;code dir=&quot;auto&quot;&gt;Tenancy.HttpHeader&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;x-cratis-tenant-id&lt;/code&gt; by default, and a missing header gives an empty tenant:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-0&quot; id=&quot;tab-4-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-1&quot; id=&quot;tab-4-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-2&quot; id=&quot;tab-4-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-3&quot; id=&quot;tab-4-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-4&quot; id=&quot;tab-4-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-0&quot; aria-labelledby=&quot;tab-4-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; headerName &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;options&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Tenancy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;HttpHeader&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Headers&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;TryGetValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;headerName&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;out&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;&lt;span&gt; tenantId&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tenantId&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Empty&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-1&quot; aria-labelledby=&quot;tab-4-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;In Kotlin a missing or blank header gives no tenant (&lt;code dir=&quot;auto&quot;&gt;null&lt;/code&gt;), not an empty string.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;context.&lt;/span&gt;&lt;span&gt;header&lt;/span&gt;&lt;span&gt;(options.headerName).&lt;/span&gt;&lt;span&gt;toTenantIdOrNull&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-2&quot; aria-labelledby=&quot;tab-4-2&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Java uses the Kotlin implementation, &lt;code dir=&quot;auto&quot;&gt;HeaderTenantIdResolver&lt;/code&gt;, which gives no tenant (&lt;code dir=&quot;auto&quot;&gt;null&lt;/code&gt;) for a missing or blank header.&lt;/p&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-3&quot; aria-labelledby=&quot;tab-4-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;In TypeScript a missing header gives &lt;code dir=&quot;auto&quot;&gt;undefined&lt;/code&gt;, not an empty string.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TenantResolverType&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Header&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;selected&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;header&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-4&quot; aria-labelledby=&quot;tab-4-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Arc for Elixir doesn’t exist; use the Chronicle Elixir client directly.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;AuthProxy forwards the tenant it resolved as &lt;code dir=&quot;auto&quot;&gt;Tenant-ID&lt;/code&gt;, so Arc has to be told that name, with &lt;code dir=&quot;auto&quot;&gt;options.UseHeaderTenancy(...)&lt;/code&gt; in .NET, or the tenant never arrives. A &lt;a href=&quot;https://cratis.io/arc/backend/csharp/tenancy/resolvers/&quot;&gt;resolver&lt;/a&gt; selects a tenant and doesn’t check that the user belongs to it. &lt;a href=&quot;https://blog.cratis.io/tenancy-and-identity-end-to-end/&quot;&gt;&lt;em&gt;Tenancy and identity end to end&lt;/em&gt;&lt;/a&gt; follows the header from AuthProxy to a Chronicle namespace.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;spotting-them-early&quot;&gt;Spotting them early&lt;/h2&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you see&lt;/th&gt;
&lt;th&gt;The convention behind it&lt;/th&gt;
&lt;th&gt;The fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The form rejects input the server accepts&lt;/td&gt;
&lt;td&gt;A condition wasn’t carried to the browser&lt;/td&gt;
&lt;td&gt;Test both layers; keep conditional rules server-only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No client rule for a validator, and a green build&lt;/td&gt;
&lt;td&gt;The validator’s constructor failed during extraction&lt;/td&gt;
&lt;td&gt;Don’t call services while building rules, and check the generated file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A handwritten file is gone after a build&lt;/td&gt;
&lt;td&gt;Full regeneration, or a second run into the same tree&lt;/td&gt;
&lt;td&gt;One generated-only folder with one owner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A generated call hits a route the backend doesn’t map&lt;/td&gt;
&lt;td&gt;Generator and server route settings disagree&lt;/td&gt;
&lt;td&gt;Change &lt;code dir=&quot;auto&quot;&gt;CratisProxies*&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;ArcOptions.GeneratedApis&lt;/code&gt; together&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A route moved after a new command&lt;/td&gt;
&lt;td&gt;Command-name fallback in a shared namespace&lt;/td&gt;
&lt;td&gt;Pin important routes with &lt;code dir=&quot;auto&quot;&gt;[Path]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Events land in a new stream&lt;/td&gt;
&lt;td&gt;No property matched the event source ID convention&lt;/td&gt;
&lt;td&gt;One unambiguous &lt;code dir=&quot;auto&quot;&gt;EventSourceId&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;[Key]&lt;/code&gt; property, or return an &lt;code dir=&quot;auto&quot;&gt;EventSourceId&lt;/code&gt;-derived identity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New events carry a different type after a class rename&lt;/td&gt;
&lt;td&gt;Event type identity came from the type name&lt;/td&gt;
&lt;td&gt;An explicit &lt;code dir=&quot;auto&quot;&gt;[EventType]&lt;/code&gt; ID on events that last&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Every request gets an empty tenant&lt;/td&gt;
&lt;td&gt;The header name isn’t the one the proxy sends&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;UseHeaderTenancy&lt;/code&gt; with the proxy’s header&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This part was checked against Arc v22.42.0 and Chronicle v19.25.1; other parts retain the versions they were checked against. The pages linked in each section have the complete rule lists. Before you rely on the browser to reject anything, read the validation page, and &lt;a href=&quot;https://blog.cratis.io/validation-all-the-way-down/&quot;&gt;&lt;em&gt;Validation all the way down&lt;/em&gt;&lt;/a&gt; for what each layer behind the browser holds.&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href=&quot;https://blog.cratis.io/four-changes-in-year-two/&quot;&gt;Part 14 — Year two&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next: &lt;a href=&quot;https://blog.cratis.io/chronicle-workbench/&quot;&gt;Part 16 — Chronicle Workbench, screen by screen&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>arc</category><category>proxy-generation</category><category>validation</category><category>chronicle</category><category>typescript</category></item><item><title>From script to stage · Part 14: Year two</title><link>https://blog.cratis.io/four-changes-in-year-two/</link><guid isPermaLink="true">https://blog.cratis.io/four-changes-in-year-two/</guid><description>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&apos;t. What each one changes in Chronicle, what the kernel works out on its own, and what an operator still decides.</description><pubDate>Wed, 30 Sep 2026 09:14:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt; · Part 14 of 26&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When a Chronicle client appends an event in the old shape of its type, the kernel stores the new shape next to it. Append the new shape and the kernel stores the old one as well, translated back. Two services written a year apart can share an event type, each reading the generation it was built against, and nobody runs a migration script over the store.&lt;/p&gt;
&lt;p&gt;That’s one of four kinds of change a system meets after launch. Take a made-up but ordinary year for the author feature and the book catalog beside it. The catalog team wants each book’s subtitle in a field of its own. Someone wants a new list of authors on a screen that didn’t exist at launch. Support finds one author registered with a misspelled name. And the reactor that sends welcome mail has stopped on one author and stays stopped.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;four-kinds-of-change&quot;&gt;Four kinds of change&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A year in, four kinds of change show up that look alike: a new shape for an event, a new view over old events, a correction to something recorded, and a processor that stopped. Chronicle has a separate mechanism for each, so each is an operation you choose instead of a script you write against the store.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/day-400-four-changes.xAoKVZIs_Z1LaOXt.webp&quot; alt=&quot;A diagram titled Four kinds of change, as four cards. A new shape for an event: generations; the stored one is kept. A new view over old events. A processor that stopped. A correction, as four options: reversal, a new event; revision, the original kept; redaction, a marker in the slot; key erasure, values unreadable. A band at the bottom reads: The event log. Only corrections change what it shows.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2000&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Four kinds of change, and which one touches the log.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A new generation changes how an event is represented and leaves the stored fact as it was. A new view only touches derived data. A correction is the one change that alters what the log shows. That can reach one event, an event source or a subject, depending on the operation. A stopped processor is about how far an observer has got in the log, and nothing stored is wrong at all. Treat the misspelled name like a new view and you end up replaying projections that were fine. Treat the stuck reactor like a correction and you touch the log to fix a mail server.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-new-shape-for-an-event&quot;&gt;A new shape for an event&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Chronicle keeps &lt;a href=&quot;https://cratis.io/chronicle/understanding-event-evolution/&quot;&gt;generations of an event type&lt;/a&gt; side by side. An event type is identified by a stable id, and a new shape is a new generation of that same id. In the &lt;a href=&quot;https://cratis.io/chronicle/migrations/dotnet-client/&quot;&gt;.NET client&lt;/a&gt;, the current generation carries the id and its generation number, and the previous generation points at the current one:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-0&quot; id=&quot;tab-0-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-1&quot; id=&quot;tab-0-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-2&quot; id=&quot;tab-0-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-3&quot; id=&quot;tab-0-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-4&quot; id=&quot;tab-0-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-0&quot; aria-labelledby=&quot;tab-0-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Generation 2 (current) — the subtitle has a property of its own&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;book-cataloged&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;generation&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BookTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt; Title&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;BookSubtitle&lt;/span&gt;&lt;span&gt;&lt;span&gt; Subtitle&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Generation 1 (original) — marked as a previous generation of the current record above,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// instead of carrying its own [EventType]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;EventTypeGenerationFor&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BookTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt; Title&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-1&quot; aria-labelledby=&quot;tab-0-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;The Kotlin client repeats the explicit id on every generation.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; io.cratis.chronicle.events.EventType&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Generation 2 (current) - the subtitle has a property of its own&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@EventType&lt;/span&gt;&lt;span&gt;(id &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;book-cataloged&quot;&lt;/span&gt;&lt;span&gt;, generation &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; title: &lt;/span&gt;&lt;span&gt;BookTitle&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; subtitle: &lt;/span&gt;&lt;span&gt;BookSubtitle&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Generation 1 (original) - same explicit id as the current generation, only the generation differs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@EventType&lt;/span&gt;&lt;span&gt;(id &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;book-cataloged&quot;&lt;/span&gt;&lt;span&gt;, generation &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; title: &lt;/span&gt;&lt;span&gt;BookTitle&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-2&quot; aria-labelledby=&quot;tab-0-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;The Java client repeats the explicit id on every generation.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;events&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Generation 2 (current) - the subtitle has a property of its own&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;book-cataloged&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;generation&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BookTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt; title&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;BookSubtitle&lt;/span&gt;&lt;span&gt;&lt;span&gt; subtitle&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Generation 1 (original) - same explicit id as the current generation, only the generation differs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;book-cataloged&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;generation&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BookTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt; title&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-3&quot; aria-labelledby=&quot;tab-0-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;The TypeScript client repeats the explicit id on every generation.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;eventType&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/chronicle&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/fundamentals&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Generation 2 (current) — the subtitle has a property of its own&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;eventType&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;book-cataloged&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BookTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookTitle&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BookSubtitle&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;subtitle&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookSubtitle&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;subtitle&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookSubtitle&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;subtitle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;subtitle&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Generation 1 (original) — same id, generation 1, kept only so the migration below can&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// upcast from it. It is not the &quot;current&quot; shape of the event any more.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;eventType&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;book-cataloged&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BookTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookTitle&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-4&quot; aria-labelledby=&quot;tab-0-4&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;The Elixir client repeats the explicit id on every generation.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;defmodule&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;book-cataloged&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;generation&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;defstruct&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; %&lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;BookTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt;{},&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;subtitle&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; %&lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;BookSubtitle&lt;/span&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;defmodule&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;book-cataloged&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;generation&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;defstruct&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; %&lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;BookTitle&lt;/span&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;In the .NET client, the older record has no id of its own, and that’s deliberate. Its event type id is resolved from the current type. A previous generation that simply leaves the id out would fall back to its CLR name and quietly become a different event type, which is the trap this shape closes. The older style, with the same explicit id string on every generation and a different &lt;code dir=&quot;auto&quot;&gt;generation:&lt;/code&gt; value, still works, and it’s the style the other clients use. The naming rule behind that trap is covered in &lt;a href=&quot;https://blog.cratis.io/events-are-facts/&quot;&gt;&lt;em&gt;Events are facts&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You &lt;a href=&quot;https://cratis.io/chronicle/migrations/&quot;&gt;declare a migration&lt;/a&gt; between adjacent generations. &lt;code dir=&quot;auto&quot;&gt;Upcast&lt;/code&gt; describes how the old shape becomes the new one, and &lt;code dir=&quot;auto&quot;&gt;Downcast&lt;/code&gt; describes the way back:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-0&quot; id=&quot;tab-1-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-1&quot; id=&quot;tab-1-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-2&quot; id=&quot;tab-1-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-3&quot; id=&quot;tab-1-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-4&quot; id=&quot;tab-1-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-0&quot; aria-labelledby=&quot;tab-1-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookCatalogedMigration&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;EventTypeMigration&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Upcast&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IEventMigrationBuilder&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; builder&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Properties&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pb &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pb&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Split&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;m &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; e &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;SplitPartIndex&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;First&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Split&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;m &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Subtitle&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; e &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;SplitPartIndex&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Second&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Downcast&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IEventMigrationBuilder&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; builder&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Properties&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pb &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pb&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Combine&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;m &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; e &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; e &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Subtitle&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-1&quot; aria-labelledby=&quot;tab-1-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;The Kotlin client takes property references and records their names as written.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookCatalogedMigration&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;EventTypeMigration&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;BookCataloged::&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;BookCatalogedV1::&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;upcast&lt;/span&gt;&lt;span&gt;(builder: &lt;/span&gt;&lt;span&gt;EventTypeMigrationBuilder&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(BookCataloged::&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;, BookCatalogedV1::&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;: &quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(BookCataloged::&lt;/span&gt;&lt;span&gt;subtitle&lt;/span&gt;&lt;span&gt;, BookCatalogedV1::&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;: &quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;downcast&lt;/span&gt;&lt;span&gt;(builder: &lt;/span&gt;&lt;span&gt;EventTypeMigrationBuilder&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;builder.&lt;/span&gt;&lt;span&gt;combine&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;BookCatalogedV1::&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;: &quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;BookCataloged::&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;BookCataloged::&lt;/span&gt;&lt;span&gt;subtitle&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-2&quot; aria-labelledby=&quot;tab-1-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;The Java client takes property names as strings and records them as written.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookCatalogedMigration&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EventTypeMigration&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                   &lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;BookCatalogedMigration&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;super&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;upcast&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;EventTypeMigrationBuilder&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                                 &lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;subtitle&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;downcast&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;EventTypeMigrationBuilder&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                                   &lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;combine&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;subtitle&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-3&quot; aria-labelledby=&quot;tab-1-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;The TypeScript client takes property names as strings, typed as keys of the event class.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;eventTypeMigration&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookCatalogedMigration&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;implements&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IEventTypeMigration&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;upcast&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IEventMigrationBuilder&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt;&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;properties&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;propertyBuilder&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;propertyBuilder&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;subtitle&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;downcast&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IEventMigrationBuilder&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;properties&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;propertyBuilder&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;propertyBuilder&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;combine&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;subtitle&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-4&quot; aria-labelledby=&quot;tab-1-4&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;The Elixir client takes property names as atoms or strings and writes a snake_case name in camelCase. Migrations need &lt;code dir=&quot;auto&quot;&gt;cratis_chronicle&lt;/code&gt; 3.8.1 or later when the event modules compile separately; on an earlier version, &lt;code dir=&quot;auto&quot;&gt;require&lt;/code&gt; both event modules at the top of the migration.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;defmodule&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Migrations&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;BookCatalogedMigration&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Migration&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;BookCatalogedV1&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;generation&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;BookCataloged&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;generation&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;alias&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;MigrationBuilder&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;impl&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;upcast&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;|&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MigrationBuilder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;split_property&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;|&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MigrationBuilder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;split_property&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;subtitle&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;impl&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;downcast&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;MigrationBuilder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;combine_properties&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;subtitle&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;],&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;The builder has a small vocabulary. &lt;code dir=&quot;auto&quot;&gt;Split&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Combine&lt;/code&gt; handle the title, &lt;code dir=&quot;auto&quot;&gt;RenamedFrom&lt;/code&gt; covers a renamed property and &lt;code dir=&quot;auto&quot;&gt;DefaultValue&lt;/code&gt; fills a new one. &lt;code dir=&quot;auto&quot;&gt;MapValues&lt;/code&gt; translates values, applies the map forward and inverts it on the way back. Values the map doesn’t mention pass through. If two old values collapse onto one new value, the way back takes the first pair you declared, so a map like that forces you to choose what the reverse means. &lt;code dir=&quot;auto&quot;&gt;MapValues&lt;/code&gt; exists in the C# client only. Migrations run on the content after Chronicle has encrypted personal data, so a migration that splits, combines or maps a value marked &lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt; works on its ciphertext (&lt;a href=&quot;https://github.com/Cratis/Chronicle/issues/4456&quot;&gt;Cratis/Chronicle#4456&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;In the .NET client, the property expressions are typed, and they resolve to the JSON names Chronicle stores, &lt;code dir=&quot;auto&quot;&gt;[JsonPropertyName]&lt;/code&gt; first and then the client’s naming policy. An expression that points at nothing is rejected at registration with &lt;code dir=&quot;auto&quot;&gt;InvalidMigrationPropertyForEventType&lt;/code&gt;. The other clients don’t check the names at registration. Adding or removing an optional property still needs a migration class, with no operations inside, so that the chain from generation 1 to the current one has no holes.&lt;/p&gt;
&lt;p&gt;The base class refuses a migration whose two generations don’t share an event type ID, or that skips a generation. It’s a check in the constructor:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;protected&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EventTypeMigration&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; previousEventType &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TPrevious&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;GetEventType&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; upgradeEventType &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TUpgrade&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;GetEventType&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;previousEventType&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Id&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;upgradeEventType&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Id&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MigrationGenerationsMustShareEventTypeId&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TPrevious&lt;/span&gt;&lt;span&gt;&lt;span&gt;),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TUpgrade&lt;/span&gt;&lt;span&gt;&lt;span&gt;),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;previousEventType&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Id&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;upgradeEventType&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Id&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;From&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;previousEventType&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Generation&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;To&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;upgradeEventType&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Generation&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;To&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;From&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;InvalidMigrationGenerationGap&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TPrevious&lt;/span&gt;&lt;span&gt;&lt;span&gt;),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TUpgrade&lt;/span&gt;&lt;span&gt;&lt;span&gt;),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;From&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;To&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The kernel &lt;a href=&quot;https://cratis.io/chronicle/migrations/validation/&quot;&gt;validates the chain again&lt;/a&gt;, whichever client registered it. Generation 1 has to exist, generations have to be sequential, and every consecutive pair needs a migrator. A chain that breaks one of those rules fails at startup with &lt;code dir=&quot;auto&quot;&gt;MissingFirstGenerationForEventType&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;MissingMigrationForEventTypeGeneration&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;MissingEventTypeMigrators&lt;/code&gt;, and two migrators that claim the same pair throw &lt;code dir=&quot;auto&quot;&gt;MultipleMigratorsForSameEventTypeGeneration&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;what-the-kernel-does-with-it&quot;&gt;What the kernel does with it&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;At registration the client sends the whole event type to the kernel, every generation and every migration, with the migrations expressed as JmesPath. From then on the kernel applies them on every append. Append generation 1 and Chronicle stores generations 1 and 2. Append generation 2 and it stores generation 2 plus the downcast generation 1. Chains of three or more generations extend the same way. Chronicle produces upcast and downcast representations while keeping the originally stored generation, so old and new code can each read the shape they expect.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/generation-map.CSpn-eQb_Z1vIewp.webp&quot; alt=&quot;A diagram titled One event, every generation. At the top, two cards: Append generation 1 stores 1, and 2 by upcast; Append generation 2 stores 2, and 1 by downcast. Both lead to one stored BookCataloged event whose content is keyed by generation, 1 and 2. Beside it, a background job fills in generation 2 for older events in the default namespace’s event log, with an arrow into the stored event. From the stored event, arrows lead down to a service on generation 1, which reads Title, and a service on generation 2, which reads Title and Subtitle. A band at the bottom reads: A migration changes representation. It can’t change meaning or fix a wrong value.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;One stored event, readable in every generation of its type.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Events stored before the new generation existed get the same treatment later. Registering a new generation makes the kernel append an &lt;code dir=&quot;auto&quot;&gt;EventTypeGenerationAdded&lt;/code&gt; event to its system sequence. A built-in reactor observes it and starts a job that walks the default namespace’s event log for events of that type still stored in the older generation, computes every generation for each one and writes the result back into the stored event. The job covers the default namespace’s event log only. The application stays usable while the job runs, and a consumer reading the new generation sees the events migrated so far.&lt;/p&gt;
&lt;p&gt;A migration changes representation. It can split a title that was stored as “Persuasion: A Novel” into two fields, and it has no way to know that the colon in some other title was never meant as a separator. If what the event &lt;em&gt;means&lt;/em&gt; has changed, that’s a new event, and a migration is the wrong tool. A migration can’t fix a wrong value either. The misspelled author gets a correction, further down.&lt;/p&gt;
&lt;p&gt;Upgrading Chronicle itself is a separate matter. A package or server upgrade doesn’t rewrite, reshape or reinterpret stored events, and event schema evolution only happens through the mechanism above. &lt;a href=&quot;https://cratis.io/chronicle/upgrading/&quot;&gt;Wire compatibility&lt;/a&gt; is checked within a major version, across every released minor of it, and crossing a major is where a protocol change is allowed.&lt;/p&gt;
&lt;p&gt;On the application side, nothing in the event mechanism depends on Arc, and Arc needs no change when a generation is added. A command that returns the current generation’s type just writes it. A changed C# command or read model reaches the browser only after the &lt;a href=&quot;https://cratis.io/arc/backend/csharp/proxy-generation/&quot;&gt;proxy generator&lt;/a&gt; has run at build time and the frontend has type-checked against the new files, which &lt;a href=&quot;https://blog.cratis.io/arc-commands-and-queries/&quot;&gt;&lt;em&gt;Arc: commands and queries without the plumbing&lt;/em&gt;&lt;/a&gt; follows in detail.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-new-view-over-old-events&quot;&gt;A new view over old events&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;When a projection is wrong, fix it and &lt;a href=&quot;https://cratis.io/chronicle/scenarios/rebuild-a-read-model/&quot;&gt;rebuild the read model&lt;/a&gt; from the retained history. Replay reads the events as they stand now, with any revisions and redaction markers that apply. The distinction between rebuilding state and delivering side effects is covered in &lt;a href=&quot;https://blog.cratis.io/from-event-to-read-model/&quot;&gt;&lt;em&gt;From event to read model&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.cratis.io/from-event-to-read-model/&quot;&gt;&lt;em&gt;From event to read model&lt;/em&gt;&lt;/a&gt; covers how Chronicle classifies a changed definition, when it can use a partial replay, and how &lt;code dir=&quot;auto&quot;&gt;definitionEvolution&lt;/code&gt; chooses between running the work and recommending it. Reactor and webhook definition changes use their separate replay switch, covered in &lt;a href=&quot;https://blog.cratis.io/reacting-to-facts/&quot;&gt;&lt;em&gt;Reacting to facts&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A new projection, such as the list of authors on that new screen, is a different case. Registering it against a store that already holds events starts a catch-up job, and the registration call doesn’t wait for it. Registered and caught up are two different states, and the gap between them shows in Chronicle Workbench’s observer job view, which &lt;a href=&quot;https://blog.cratis.io/chronicle-workbench/&quot;&gt;&lt;em&gt;Workbench&lt;/em&gt;&lt;/a&gt; walks through. The client retries a registration with exponential backoff, so a busy kernel doesn’t fail the host. A kernel that keeps refusing still fails it once the attempts are spent, and a definition the kernel rejects fails on its own while the others still land.&lt;/p&gt;
&lt;p&gt;When you do want a replay by hand, &lt;code dir=&quot;auto&quot;&gt;cratis chronicle observers replay &amp;#x3C;observer-id&gt;&lt;/code&gt; starts one from &lt;a href=&quot;https://cratis.io/cli/chronicle/observers/&quot;&gt;the CLI&lt;/a&gt;. The CLI marks the command as destructive in its own effect metadata and asks for confirmation unless you pass &lt;code dir=&quot;auto&quot;&gt;--yes&lt;/code&gt;. Replays, catch-ups and event migrations run as &lt;a href=&quot;https://cratis.io/chronicle/jobs/&quot;&gt;jobs&lt;/a&gt; you can inspect, stop and resume, with a setting that caps how many steps run in parallel. A replay takes time in proportion to the history behind the observer, and &lt;a href=&quot;https://blog.cratis.io/chronicle-jobs-scale-and-performance/&quot;&gt;&lt;em&gt;Jobs, scale and performance&lt;/em&gt;&lt;/a&gt; covers what that means on a large store.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;when-a-replay-reaches-a-reactor&quot;&gt;When a replay reaches a reactor&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Before replaying the welcome mailer, apply the replay markers and delivery-identity limits from &lt;a href=&quot;https://blog.cratis.io/reacting-to-facts/&quot;&gt;&lt;em&gt;Reacting to facts&lt;/em&gt;&lt;/a&gt;; replay and recovery need different treatment.&lt;/p&gt;
&lt;p&gt;Personal data puts one more limit on replay. A replay that covers an &lt;a href=&quot;https://cratis.io/chronicle/compliance/erasing-a-subject/&quot;&gt;erased subject&lt;/a&gt; is refused for that subject’s partition, and a rebuild doesn’t clear ciphertext that’s already stored. &lt;a href=&quot;https://blog.cratis.io/personal-data-in-an-event-log/&quot;&gt;&lt;em&gt;Personal data in an event log&lt;/em&gt;&lt;/a&gt; explains why, and what to do before such a rebuild.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-correction-to-something-recorded&quot;&gt;A correction to something recorded&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Choose a compensation, revision, redaction or key erasure by what needs correcting and what must remain, using the boundaries in &lt;a href=&quot;https://blog.cratis.io/personal-data-in-an-event-log/&quot;&gt;&lt;em&gt;Personal data in an event log&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The misspelled author is a revision. The revised content has to use the same event type id as the original, consumers see the latest revision, and the original stays in the event’s history.&lt;/p&gt;
&lt;p&gt;Neither revision nor redaction is free for observers. Both rewind the affected event source’s partition for every replayable observer of that event type, so the read models built from that one author are rebuilt from that author’s events. The rest of each observer stays where it was. A reactor on that event type replays that author’s events too, unless it’s marked &lt;code dir=&quot;auto&quot;&gt;[OnceOnly]&lt;/code&gt;, which is the case the marker exists for.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-processor-that-stopped&quot;&gt;A processor that stopped&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The fourth change doesn’t touch anything stored. The welcome mailer threw on one author, and Chronicle has been holding that author’s events ever since.&lt;/p&gt;
&lt;p&gt;The failed partition pauses without skipping the event while every other partition keeps going. &lt;a href=&quot;https://blog.cratis.io/inside-chronicle/&quot;&gt;&lt;em&gt;Inside Chronicle&lt;/em&gt;&lt;/a&gt; covers the failure kinds, retry defaults and the quarantine that stops automatic attempts.&lt;/p&gt;
&lt;p&gt;A timeout ends the kernel’s wait and nothing else. The subscriber keeps processing the batch it was handed while the kernel has already written it off. The events are delivered again on retry, so observers have to be idempotent.&lt;/p&gt;
&lt;p&gt;Once the cause is fixed, someone asks for another attempt. The kernel decides whether it can start one, and it has four answers:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;PartitionRecoveryOutcome&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;TryStartRecoverJobForFailedPartition&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Key&lt;/span&gt;&lt;span&gt;&lt;span&gt; partition&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;State&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;RunningState&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ObserverRunningState&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Quarantined&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PartitionRecoveryOutcome&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ObserverQuarantined&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;Failures&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;TryGet&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;partition&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;out&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;&lt;span&gt; failure&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PartitionRecoveryOutcome&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PartitionNotFound&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;failure&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;IsQuarantined&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PartitionRecoveryOutcome&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PartitionQuarantined&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;StartRecoverJobForFailedPartition&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;failure&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PartitionRecoveryOutcome&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Started&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A quarantined observer has to be cleared with &lt;code dir=&quot;auto&quot;&gt;clear-quarantine&lt;/code&gt; before any of its partitions can be retried. A partition that isn’t failing has nothing to retry, and a quarantined partition refuses an ordinary retry.&lt;/p&gt;
&lt;p&gt;The CLI passes those answers on without softening them. &lt;code dir=&quot;auto&quot;&gt;cratis chronicle observers retry-partition&lt;/code&gt; turns the response into an exit code:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Outcome&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PartitionRecoveryOutcome&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Started&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;OutputFormatter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WriteMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;$&quot;&lt;/span&gt;&lt;span&gt;Retry started for partition &apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;settings&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Partition&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&apos; of observer &apos;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;settings&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ObserverId&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&apos;. Use &apos;cratis chronicle observers show &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;settings&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ObserverId&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos; to check progress.&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ExitCodes&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Success&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; suggestion&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;DescribeRefusal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Outcome&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;settings&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;OutputFormatter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WriteError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;suggestion&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ExitCodes&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ValidationErrorCode&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ExitCodes&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ValidationError&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Only &lt;code dir=&quot;auto&quot;&gt;Started&lt;/code&gt; exits successfully, and even that message says the retry &lt;em&gt;started&lt;/em&gt;. Every refusal becomes a validation error with a suggested next step. A script that runs the retry gets a nonzero exit code when nothing happened, and a human gets told to check progress. A started retry says nothing yet about whether the partition recovered, so look at the partition again afterwards. A retry also gives no exactly-once guarantee for a reactor’s side effects, and the welcome mail may go out twice if the handler sent it before it threw.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/retry-is-not-recovery.TcKlAXEe_kWOFW.webp&quot; alt=&quot;A diagram titled Retry is not recovery. At the top, a Failing partition, with Handling or Timeout, retried with backoff, has an arrow labeled attempts used up to a Quarantined partition that doesn’t resume by itself. Below, a CLI retry-partition request goes to a card reading Kernel answers, with four arrows out. Three lead into a group labeled Refused: ObserverQuarantined, with the note clear the observer’s quarantine first; PartitionNotFound; and PartitionQuarantined. The fourth leads to Started, with the note check the partition again. A note beside the request reads: A refusal exits with a validation error and a suggestion.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A retry the kernel can’t start is reported as a refusal. One it starts still has to be checked.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A retry isn’t a replay. &lt;code dir=&quot;auto&quot;&gt;retry-partition&lt;/code&gt; asks for one more attempt from where the partition failed. &lt;code dir=&quot;auto&quot;&gt;replay-partition&lt;/code&gt; replays the partition from the start, for when its derived state is wrong. A typed reactor can also list its own failed partitions and retry one from application code. The &lt;a href=&quot;https://cratis.io/chronicle/troubleshooting/&quot;&gt;troubleshooting guide&lt;/a&gt; walks the loop end to end, and &lt;a href=&quot;https://blog.cratis.io/the-cratis-cli/&quot;&gt;&lt;em&gt;The Cratis CLI&lt;/em&gt;&lt;/a&gt; follows it from &lt;code dir=&quot;auto&quot;&gt;diagnose&lt;/code&gt; onwards.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-four-changes&quot;&gt;The four changes&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Back to the made-up year. The subtitle is a second generation of &lt;code dir=&quot;auto&quot;&gt;BookCataloged&lt;/code&gt; and one migrator, and books already cataloged in the default namespace’s event log get their second generation from a background job. The new screen is a new projection that catches up from the log. The misspelled name is a revision, which rewinds that one author’s partition in the observers that care. The stuck welcome mailer is a failed partition, a fixed mail configuration and one retry, followed by a look at whether the partition is current.&lt;/p&gt;
&lt;p&gt;Chronicle checks the chain of generations, translates on every append and classifies a changed projection before it replays anything. It also refuses a retry it can’t start, and says so.&lt;/p&gt;
&lt;p&gt;Some decisions stay with whoever runs the system. A migration can’t tell that a field changed meaning, so that call is a modeling one. The definition-evolution setting decides whether a full replay runs straight away or waits as a recommendation. Picking between a reversal, a revision, a redaction and a key erasure is a judgment about what the mistake was. And a retry that started is only a request until someone has checked that the partition caught up.&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href=&quot;https://blog.cratis.io/personal-data-in-an-event-log/&quot;&gt;Part 13 — Personal data in an event log&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next: &lt;a href=&quot;https://blog.cratis.io/arc-gotchas/&quot;&gt;Part 15 — Arc gotchas: where a convention decides for you&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>chronicle</category><category>event-sourcing</category><category>migrations</category><category>replay</category><category>operations</category></item><item><title>From script to stage · Part 13: Personal data in an event log</title><link>https://blog.cratis.io/personal-data-in-an-event-log/</link><guid isPermaLink="true">https://blog.cratis.io/personal-data-in-an-event-log/</guid><description>Chronicle erases a person by destroying their key. The events stay, their marked values read as empty, and the fence stops the key from coming back.</description><pubDate>Wed, 30 Sep 2026 09:13:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt; · Part 13 of 26&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Erasing a person in Chronicle is one call that deletes their key. Every event they appear in stays in the log at the same sequence number, and the values marked as personal data come back as empty strings. If something then tries to append new personal data for that person, the append fails on purpose instead of quietly starting a fresh key.&lt;/p&gt;
&lt;p&gt;The author’s name travels further than the event: into the command’s audit trail, into read models, into other event stores and into backups.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;personal-data-marked-once&quot;&gt;Personal data, marked once&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The author’s name is personal data. One attribute on its type makes Chronicle encrypt the value with the key of the event’s subject, and with Arc’s Chronicle integration the same attribute keeps it out of the &lt;a href=&quot;https://cratis.io/arc/backend/csharp/chronicle/commands/causation/&quot;&gt;causation record&lt;/a&gt;. &lt;a href=&quot;https://blog.cratis.io/the-log-remembers-pii-crypto-shredding-in-chronicle/&quot;&gt;The log remembers&lt;/a&gt; shows the Chronicle side in five languages.&lt;/p&gt;
&lt;p&gt;The name’s &lt;a href=&quot;https://cratis.io/chronicle/compliance/pii-with-concepts/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;ConceptAs&amp;#x3C;T&gt;&lt;/code&gt; type is marked &lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt;&lt;/a&gt;, and every event property of that type is encrypted for the event’s subject. A person-name type marked that way:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-0&quot; id=&quot;tab-0-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-1&quot; id=&quot;tab-0-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-2&quot; id=&quot;tab-0-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-3&quot; id=&quot;tab-0-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-4&quot; id=&quot;tab-0-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-0&quot; aria-labelledby=&quot;tab-0-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Compliance&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GDPR&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Concepts&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;PII&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PersonName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt; Value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ConceptAs&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;implicit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;operator&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PersonName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt; value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-1&quot; aria-labelledby=&quot;tab-0-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; io.cratis.chronicle.compliance.Pii&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; io.cratis.chronicle.concepts.ConceptAs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@Pii&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PersonName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;) : &lt;/span&gt;&lt;span&gt;ConceptAs&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-2&quot; aria-labelledby=&quot;tab-0-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;compliance&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Pii&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;concepts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ConceptAs&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Pii&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PersonName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&lt;span&gt; value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;implements&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ConceptAs&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;getValue&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; value&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-3&quot; aria-labelledby=&quot;tab-0-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pii&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/chronicle&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ConceptAs&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/fundamentals&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;pii&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PersonName&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ConceptAs&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;valueType&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;super&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-4&quot; aria-labelledby=&quot;tab-0-4&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;defmodule&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PersonName&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Concept&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;pii&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;Projection-backed read models track the protected value’s subject through their mappings. Reducer read models need &lt;a href=&quot;https://cratis.io/chronicle/compliance/read-models/&quot;&gt;their own PII marking&lt;/a&gt;, because the kernel can’t infer arbitrary transformations.&lt;/p&gt;
&lt;p&gt;Pick the attribute by what the value is. Personal data you may have to erase gets &lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt;. A value that should never reach the causation record gets &lt;code dir=&quot;auto&quot;&gt;[NotAudited]&lt;/code&gt;, which excludes it without encrypting it, and an operational secret you have to keep gets &lt;code dir=&quot;auto&quot;&gt;[Encrypted]&lt;/code&gt;, whose separate key erasure doesn’t touch, as &lt;a href=&quot;#what-a-command-leaves-behind&quot;&gt;what a command leaves behind&lt;/a&gt; explains.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/chronicle/compliance/erasing-a-subject/&quot;&gt;Erasure&lt;/a&gt; is one call:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-0&quot; id=&quot;tab-1-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-1&quot; id=&quot;tab-1-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-2&quot; id=&quot;tab-1-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-3&quot; id=&quot;tab-1-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-4&quot; id=&quot;tab-1-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-0&quot; aria-labelledby=&quot;tab-1-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;eventStore&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PII&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;DeleteEncryptionKeyFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;person-42&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-1&quot; aria-labelledby=&quot;tab-1-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;eventStore.compliance.&lt;/span&gt;&lt;span&gt;deleteEncryptionKey&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;person-42&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-2&quot; aria-labelledby=&quot;tab-1-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ComplianceServiceJavaBridge&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;deleteEncryptionKey&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;eventStore&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;unwrap&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getCompliance&lt;/span&gt;&lt;span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;person-42&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-3&quot; aria-labelledby=&quot;tab-1-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;eventStore&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;pii&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;deleteEncryptionKey&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;person-42&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-4&quot; aria-labelledby=&quot;tab-1-4&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Compliance&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;delete_encryption_key&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;person-42&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;Once the &lt;a href=&quot;https://cratis.io/chronicle/compliance/key-lifecycle/&quot;&gt;key&lt;/a&gt; has been destroyed, Chronicle can’t decrypt anything encrypted with it. The event keeps its place in the log, so the log still shows that something happened.&lt;/p&gt;
&lt;p&gt;Erasure stops at these points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The key belongs to a subject within a namespace. A person who exists in two tenants needs two erasures.&lt;/li&gt;
&lt;li&gt;Values that were never marked stay readable, and so do causation values recorded in clear before anyone marked them.&lt;/li&gt;
&lt;li&gt;Copies outside Chronicle, including exported keys, messages sent to another system and values already shown in a browser, need separate handling.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.cratis.io/personal-data-in-an-event-log/#erasure-step-by-step&quot;&gt;&lt;em&gt;Erasure, step by step&lt;/em&gt;&lt;/a&gt; covers the rebuild limit, and &lt;a href=&quot;https://blog.cratis.io/personal-data-in-an-event-log/#where-the-keys-live-and-how-they-come-back&quot;&gt;&lt;em&gt;Where the keys live, and how they come back&lt;/em&gt;&lt;/a&gt; covers the restore that can undo an erasure.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/erasure-boundary.DFBL3yAD_1zskbr.webp&quot; alt=&quot;A diagram titled What deleting a key reaches. A panel labeled Inside Chronicle, namespace Acme holds two cards. Event log: the encrypted value is unreadable; the record remains. Managed read model: the protected field reads empty. A second panel, Deleting the key doesn’t reach, holds three cards: backups taken before erasure, copies sent elsewhere, and values already shown. A bottom card, Another namespace, reads: The same person in another tenant needs their own erasure.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deleting a person’s key reaches the event log and managed read models, not copies.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;An application that handles personal data still needs its own privacy review. Redaction and revision, which change an event instead of a key, come up again in &lt;a href=&quot;https://blog.cratis.io/four-changes-in-year-two/&quot;&gt;&lt;em&gt;Year two&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-the-marker-lands&quot;&gt;Where the marker lands&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The &lt;a href=&quot;https://cratis.io/chronicle/compliance/pii/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt; marker&lt;/a&gt; is written into the event type’s schema as compliance metadata. Chronicle looks for it in four places, in order: on the property, on the declaring type, on the property’s own type, which is the concept case above, and on a matching constructor parameter. It lands on individual leaf values. A marked concept nested inside a value object is encrypted while its sibling stays readable, and the document keeps its shape. A marked collection is encrypted as one value, and geospatial values are never looked inside.&lt;/p&gt;
&lt;p&gt;Some shapes are refused outright. A polymorphic base type or a dictionary carrying &lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt; makes the append fail, so the value is never stored unprotected. &lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt; on an &lt;code dir=&quot;auto&quot;&gt;EventSourceId&lt;/code&gt; type throws &lt;code dir=&quot;auto&quot;&gt;PIINotSupportedOnEventSourceId&lt;/code&gt;. The fix is a surrogate id that says nothing about the person, with the personal value in a marked property. The author feature already has that shape, since &lt;code dir=&quot;auto&quot;&gt;AuthorId&lt;/code&gt; identifies the author and &lt;code dir=&quot;auto&quot;&gt;AuthorName&lt;/code&gt; carries the name.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whose-key-the-subject&quot;&gt;Whose key: the subject&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Keys are held per &lt;a href=&quot;https://cratis.io/chronicle/concepts/subject/&quot;&gt;subject&lt;/a&gt;. The subject defaults to the event source id, and it can be set on append, by an event property marked &lt;code dir=&quot;auto&quot;&gt;[Subject]&lt;/code&gt; (C# only), or from an Arc command. &lt;code dir=&quot;auto&quot;&gt;ShippingAddressChanged&lt;/code&gt; lives on the order’s stream, but the address belongs to the customer, so the customer is the subject, and every event about that customer, from any stream, shares one key.&lt;/p&gt;
&lt;p&gt;An Arc command sets the subject by &lt;a href=&quot;https://cratis.io/arc/backend/csharp/chronicle/compliance/subject/&quot;&gt;returning it next to the event&lt;/a&gt;:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-0&quot; id=&quot;tab-2-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-1&quot; id=&quot;tab-2-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-2&quot; id=&quot;tab-2-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-3&quot; id=&quot;tab-2-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-4&quot; id=&quot;tab-2-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-0&quot; aria-labelledby=&quot;tab-2-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Arc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Commands&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Arc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Commands&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ModelBound&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Command&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PlaceCustomerOrder&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;OrderId&lt;/span&gt;&lt;span&gt;&lt;span&gt; OrderId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CustomerId&lt;/span&gt;&lt;span&gt;&lt;span&gt; CustomerId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Money&lt;/span&gt;&lt;span&gt;&lt;span&gt; Total&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ICanProvideEventSourceId&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EventSourceId&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GetEventSourceId&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OrderId&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;OrderPlaced&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Subject&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Handle&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OrderPlaced&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CustomerId&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Total&lt;/span&gt;&lt;span&gt;&lt;span&gt;),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Subject&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CustomerId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ToString&lt;/span&gt;&lt;span&gt;()));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OrderPlaced&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CustomerId&lt;/span&gt;&lt;span&gt;&lt;span&gt; CustomerId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Money&lt;/span&gt;&lt;span&gt;&lt;span&gt; Total&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-1&quot; aria-labelledby=&quot;tab-2-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;In Arc for Kotlin the command implements &lt;code dir=&quot;auto&quot;&gt;CommandEventSubjectProvider&lt;/code&gt; instead of returning the subject next to the event.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; io.cratis.arc.artifacts.Command&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; io.cratis.arc.artifacts.CommandEventSubjectProvider&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; io.cratis.arc.artifacts.CommandKey&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; io.cratis.chronicle.events.EventType&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@Command&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PlaceCustomerOrder&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;@CommandKey&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; orderId: &lt;/span&gt;&lt;span&gt;OrderId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; customerId: &lt;/span&gt;&lt;span&gt;CustomerId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; total: &lt;/span&gt;&lt;span&gt;Money&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;) : &lt;/span&gt;&lt;span&gt;CommandEventSubjectProvider&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;eventSubject&lt;/span&gt;&lt;span&gt;(): &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; customerId.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;(): &lt;/span&gt;&lt;span&gt;OrderPlaced&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OrderPlaced&lt;/span&gt;&lt;span&gt;(customerId, total)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@EventType&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OrderPlaced&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; customerId: &lt;/span&gt;&lt;span&gt;CustomerId&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; total: &lt;/span&gt;&lt;span&gt;Money&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-2&quot; aria-labelledby=&quot;tab-2-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;In Arc for Java the command implements &lt;code dir=&quot;auto&quot;&gt;CommandEventSubjectProvider&lt;/code&gt; instead of returning the subject next to the event.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;arc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;artifacts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Command&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;arc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;artifacts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;CommandEventSubjectProvider&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;arc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;artifacts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;CommandKey&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;events&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Command&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PlaceCustomerOrder&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;/span&gt;&lt;span&gt;CommandKey&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OrderId&lt;/span&gt;&lt;span&gt;&lt;span&gt; orderId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CustomerId&lt;/span&gt;&lt;span&gt;&lt;span&gt; customerId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Money&lt;/span&gt;&lt;span&gt;&lt;span&gt; total&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;implements&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CommandEventSubjectProvider&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;eventSubject&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;customerId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OrderPlaced&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OrderPlaced&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;customerId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; total&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OrderPlaced&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CustomerId&lt;/span&gt;&lt;span&gt;&lt;span&gt; customerId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Money&lt;/span&gt;&lt;span&gt;&lt;span&gt; total&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-3&quot; aria-labelledby=&quot;tab-2-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;In Arc for TypeScript the command computes the subject in &lt;code dir=&quot;auto&quot;&gt;getSubject()&lt;/code&gt; instead of returning it next to the event.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Guid&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/fundamentals&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;eventType&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/chronicle/events&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/arc.core&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PlaceCustomerOrder&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;OrderId&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;orderId&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OrderId&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CustomerId&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;customerId&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CustomerId&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Money&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Money&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;getSubject&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;customerId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;&lt;span&gt;();&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OrderPlaced&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OrderPlaced&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;customerId&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;eventType&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OrderPlaced&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CustomerId&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;customerId&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CustomerId&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Money&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Money&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;customerId&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CustomerId&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Guid&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;empty&lt;/span&gt;&lt;span&gt;&lt;span&gt;),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Money&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;customerId&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;customerId&lt;/span&gt;&lt;span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-4&quot; aria-labelledby=&quot;tab-2-4&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;Arc for Elixir doesn’t exist; with the Chronicle Elixir client you pass the subject as an append option.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;defmodule&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;OrderPlaced&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;order-placed&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;defstruct&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;customer_id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; %&lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;CustomerId&lt;/span&gt;&lt;span&gt;&lt;span&gt;{},&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; %&lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Money&lt;/span&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;defmodule&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PlaceCustomerOrder&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;place&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;OrderId&lt;/span&gt;&lt;span&gt;&lt;span&gt;{}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; order_id&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; %&lt;/span&gt;&lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;CustomerId&lt;/span&gt;&lt;span&gt;&lt;span&gt;{}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; customer_id&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; total&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;order_id&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;OrderPlaced&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&lt;span&gt;customer_id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; customer_id&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; total&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;subject&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; customer_id&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;A command can also implement &lt;code dir=&quot;auto&quot;&gt;ICanProvideSubject&lt;/code&gt;. The &lt;code dir=&quot;auto&quot;&gt;Subject&lt;/code&gt; is append metadata and never becomes the command’s response. Arc doesn’t infer the subject from the signed-in user, and an aggregate’s &lt;code dir=&quot;auto&quot;&gt;Apply()&lt;/code&gt; doesn’t forward it.&lt;/p&gt;
&lt;p&gt;A key belongs to one event store and one namespace. The subject also travels on the event as &lt;code dir=&quot;auto&quot;&gt;EventContext.Subject&lt;/code&gt;, so an observer that forwards an event and passes &lt;code dir=&quot;auto&quot;&gt;context.Subject&lt;/code&gt; along keeps its compliance identity. An event store subscription copies the subject’s key into the target store and never across namespaces.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;on-the-way-in-and-on-the-way-out&quot;&gt;On the way in and on the way out&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The cryptographic contract for one value fits in two methods of the kernel’s &lt;code dir=&quot;auto&quot;&gt;PIICompliancePropertyValueHandler&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;JsonNode&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Apply&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;EventStoreName&lt;/span&gt;&lt;span&gt;&lt;span&gt; eventStore&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;EventStoreNamespaceName&lt;/span&gt;&lt;span&gt;&lt;span&gt; eventStoreNamespace&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt; identifier&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;JsonNode&lt;/span&gt;&lt;span&gt;&lt;span&gt; value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; key &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;provisioner&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;EnsureKeyFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;eventStore&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;eventStoreNamespace&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;identifier&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ProtectedValueCodec&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Encrypt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;encryption&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;JsonNode&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Release&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;EventStoreName&lt;/span&gt;&lt;span&gt;&lt;span&gt; eventStore&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;EventStoreNamespaceName&lt;/span&gt;&lt;span&gt;&lt;span&gt; eventStoreNamespace&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt; identifier&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;JsonNode&lt;/span&gt;&lt;span&gt;&lt;span&gt; value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;ProtectedValueCodec&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;TryDecodeCipherText&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;encryption&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ToString&lt;/span&gt;&lt;span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;out&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;&lt;span&gt; encrypted&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; key &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;encryptionKeyStore&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;TryGetFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;eventStore&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;eventStoreNamespace&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;identifier&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;is&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;JsonValue&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Empty&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ProtectedValueCodec&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Decrypt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;encryption&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;encrypted&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;Apply&lt;/code&gt; makes sure the subject has a key, minting one on first use, and replaces the value with ciphertext. &lt;code dir=&quot;auto&quot;&gt;Release&lt;/code&gt; decodes only values in the shape of ciphertext it produced. A value that was never encrypted passes through, and an encrypted value whose key is gone comes back as an empty string. Nothing throws, so a query over an erased person keeps working. The asymmetry is deliberate. If protecting a value fails on the way in, the operation fails. A value that can’t be read on the way out is blanked per property and never fails the whole read.&lt;/p&gt;
&lt;p&gt;Reactors, webhooks, reducers and projections all receive decrypted events, from a single decryption point in the observer.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-a-read-model-holds&quot;&gt;What a read model holds&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Projection-backed &lt;a href=&quot;https://cratis.io/chronicle/compliance/read-models/&quot;&gt;read models&lt;/a&gt; get their PII lineage automatically. The kernel knows which read model properties map from PII event properties and encrypts them before the sink write, with no attribute on the read model. A reducer is opaque code, so its read model needs &lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt; on its own properties.&lt;/p&gt;
&lt;p&gt;Every managed document stores a reserved &lt;code dir=&quot;auto&quot;&gt;__subject&lt;/code&gt;. When a projection joins personal values from different subjects, only the exceptions go into &lt;code dir=&quot;auto&quot;&gt;__subjects&lt;/code&gt;, as in &lt;code dir=&quot;auto&quot;&gt;{&quot;advisorName&quot;: &quot;advisor-17&quot;}&lt;/code&gt;. That one value is released and erased under &lt;code dir=&quot;auto&quot;&gt;advisor-17&lt;/code&gt;, while the rest of the document follows its own subject, &lt;code dir=&quot;auto&quot;&gt;employee-42&lt;/code&gt;. Don’t name your own properties &lt;code dir=&quot;auto&quot;&gt;__subject&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;__subjects&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Decryption happens per property. A value encrypted under the document’s subject is released, a value whose key was deleted reads as empty, and a value that was never encrypted passes through. A value encrypted under a different subject with no ownership metadata, from legacy data or data written outside the managed sinks, reads as empty, and the kernel logs an error naming the property and the subject.&lt;/p&gt;
&lt;p&gt;Queries through &lt;code dir=&quot;auto&quot;&gt;IReadModels&lt;/code&gt; release values automatically. An instance that arrived another way, from raw storage, a cache or &lt;code dir=&quot;auto&quot;&gt;Watch&lt;/code&gt;, which streams without releasing, needs an explicit &lt;code dir=&quot;auto&quot;&gt;IReadModels.Release&lt;/code&gt;. Arc calls &lt;code dir=&quot;auto&quot;&gt;Release&lt;/code&gt; on the query paths that use its read-model interception, before the response or the WebSocket or SSE emission. Observable HTTP snapshots currently bypass that interception, so each response path that exposes a read model has to be chosen and authorized deliberately. Release isn’t authorization, and it can’t bring back an erased key.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-a-command-leaves-behind&quot;&gt;What a command leaves behind&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;With Arc’s Chronicle integration in .NET and TypeScript, the &lt;a href=&quot;https://cratis.io/arc/backend/csharp/chronicle/commands/causation/&quot;&gt;causation&lt;/a&gt; of every event a command appends records the command’s name and its property values. Keys are camelCased, concepts render as their inner value, and nested objects become compact JSON, truncated at 1,024 characters with a marker. &lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt; keeps a top-level value off that chain, whether it sits on the property, the command, the positional parameter or the concept. Arc’s own &lt;code dir=&quot;auto&quot;&gt;[NotAudited]&lt;/code&gt; does the same for secrets that aren’t personal data:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-0&quot; id=&quot;tab-3-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-1&quot; id=&quot;tab-3-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-2&quot; id=&quot;tab-3-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-3&quot; id=&quot;tab-3-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-4&quot; id=&quot;tab-3-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-0&quot; aria-labelledby=&quot;tab-3-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;NotAudited&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Password&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt; Value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ConceptAs&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Command&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChangePassword&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;UserId&lt;/span&gt;&lt;span&gt;&lt;span&gt; User&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Password&lt;/span&gt;&lt;span&gt;&lt;span&gt; OldPassword&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Password&lt;/span&gt;&lt;span&gt;&lt;span&gt; NewPassword&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Result&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;PasswordChanged&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ValidationResult&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;UserCredentials&lt;/span&gt;&lt;span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; credentials&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;IPasswordHasher&lt;/span&gt;&lt;span&gt;&lt;span&gt; hasher&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;credentials&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;is&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;not&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;hasher&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Verify&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;OldPassword&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;credentials&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PasswordHash&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PasswordChanged&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;hasher&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Hash&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;NewPassword&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ValidationResult&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;The old password is wrong.&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;oldPassword&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-1&quot; aria-labelledby=&quot;tab-3-1&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Arc for Kotlin records the command’s type and key in the causation chain, not its property values, so there’s no &lt;code dir=&quot;auto&quot;&gt;NotAudited&lt;/code&gt; marker.&lt;/p&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-2&quot; aria-labelledby=&quot;tab-3-2&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Arc for Java records the command’s type and key in the causation chain, not its property values, so there’s no &lt;code dir=&quot;auto&quot;&gt;NotAudited&lt;/code&gt; marker.&lt;/p&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-3&quot; aria-labelledby=&quot;tab-3-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;In Arc for TypeScript, &lt;code dir=&quot;auto&quot;&gt;@notAudited()&lt;/code&gt; marks a field, not a type.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Password&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ConceptAs&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;valueType&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChangePassword&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;UserId&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;UserId&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Password&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;/span&gt;&lt;span&gt;notAudited&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;oldPassword&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Password&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Password&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;/span&gt;&lt;span&gt;notAudited&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;newPassword&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Password&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;inject&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;commandReadModel&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;UserCredentials&lt;/span&gt;&lt;span&gt;&lt;span&gt;),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;PasswordHasher&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;credentials&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;UserCredentials&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;hasher&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PasswordHasher&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Outcome&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;PasswordChanged&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PasswordChanged&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;hasher&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;verify&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;oldPassword&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;credentials&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;passwordHash&lt;/span&gt;&lt;span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rejected&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;validation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;The old password is wrong.&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;oldPassword&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;]));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PasswordChanged&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;hasher&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;hash&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;newPassword&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-4&quot; aria-labelledby=&quot;tab-3-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Arc for Elixir doesn’t exist; use the Chronicle Elixir client directly.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;The decision fails closed, so a property whose exclusion can’t be decided is left out. Exclusion isn’t recursive, and a marked member inside a nested object still ends up in that JSON. Arc’s analyzer ARCCHR0009 warns when a command property’s name reads like a secret and carries no marker.&lt;/p&gt;
&lt;p&gt;Causation values aren’t encrypted under the subject’s key, so deleting the key doesn’t erase them. Setting &lt;a href=&quot;https://cratis.io/chronicle/hosting/configuration/events/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;events.causationPropertyRetention&lt;/code&gt;&lt;/a&gt; to &lt;code dir=&quot;auto&quot;&gt;Omit&lt;/code&gt; stops new appends, revisions and redactions from storing causation property values, while keeping their types and times. Chains already stored stay as they are.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt; on an Arc command keeps a value out of causation and encrypts nothing. The event you build from the command needs its own marker, on the property or on the concept.&lt;/p&gt;
&lt;p&gt;For an operational secret you must store, Chronicle has &lt;a href=&quot;https://cratis.io/chronicle/security/encrypted-values/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;[Encrypted]&lt;/code&gt;&lt;/a&gt;, which uses a separate key. Erasing a subject’s &lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt; key leaves an &lt;code dir=&quot;auto&quot;&gt;[Encrypted]&lt;/code&gt; value readable, and Chronicle’s subject-erasure operation refuses to delete its key. &lt;code dir=&quot;auto&quot;&gt;[Encrypted]&lt;/code&gt; keys are scoped to the subject by default, or to the namespace or globally, and no operation deletes one. Combining &lt;code dir=&quot;auto&quot;&gt;[Encrypted]&lt;/code&gt; with &lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt; on one property makes the schema pass throw, and analyzer CHR0053 catches the direct cases.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;four-ways-to-change-the-record-of-an-event&quot;&gt;Four ways to change the record of an event&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A request to “delete it” can mean four different operations, and each answers a different question.&lt;/p&gt;
&lt;p&gt;A compensation is a new domain event. A cancelled registration is recorded as a cancellation, and nothing earlier changes. Redaction and revision aren’t meant for undoing a business decision.&lt;/p&gt;
&lt;p&gt;A &lt;a href=&quot;https://cratis.io/chronicle/events/revision/&quot;&gt;revision&lt;/a&gt; corrects content. It has to keep the same event type id, or it fails with &lt;code dir=&quot;auto&quot;&gt;InvalidRevisionEventType&lt;/code&gt;. The corrected payload is stored beside the original with the requester’s causation and identity, consumers see the latest revision from then on, and the original stays in the event’s history. Chronicle then rewinds that event source’s partition for replayable observers of the type. A revision removes nothing.&lt;/p&gt;
&lt;p&gt;A &lt;a href=&quot;https://cratis.io/chronicle/events/redaction/&quot;&gt;redaction&lt;/a&gt; removes content. Requesting one appends a system event, &lt;code dir=&quot;auto&quot;&gt;EventRedactionRequested&lt;/code&gt; for a single event or &lt;code dir=&quot;auto&quot;&gt;EventsRedactedForEventSource&lt;/code&gt; for a whole event source, optionally limited to some event types, and both take a &lt;code dir=&quot;auto&quot;&gt;RedactionReason&lt;/code&gt;. A built-in reactor then replaces the event in place. It keeps its sequence number and becomes an &lt;code dir=&quot;auto&quot;&gt;EventRedacted&lt;/code&gt; that holds the reason, the original event type and occurred time, the correlation id and caused-by chain, and the types and times of the original causation entries without their property values. The payload, the revision history and the content hashes are gone from sequence storage. The kernel rewinds the affected partition for observers of that type, and a repeated redaction skips the rewind.&lt;/p&gt;
&lt;p&gt;An observer hears about a redaction only if it subscribes to the original event type and also handles &lt;code dir=&quot;auto&quot;&gt;EventRedacted&lt;/code&gt;:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-0&quot; id=&quot;tab-4-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-1&quot; id=&quot;tab-4-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-2&quot; id=&quot;tab-4-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-3&quot; id=&quot;tab-4-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-4&quot; id=&quot;tab-4-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-0&quot; aria-labelledby=&quot;tab-4-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PersonRegistered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;PersonName&lt;/span&gt;&lt;span&gt;&lt;span&gt; Name&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PersonRegistrations&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;IReactor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Registered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;PersonRegistered&lt;/span&gt;&lt;span&gt;&lt;span&gt; @event&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;EventContext&lt;/span&gt;&lt;span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;CompletedTask&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Redacted&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;EventRedacted&lt;/span&gt;&lt;span&gt;&lt;span&gt; @event&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;EventContext&lt;/span&gt;&lt;span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// Called only when a PersonRegistered event is redacted.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;CompletedTask&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-1&quot; aria-labelledby=&quot;tab-4-1&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Not available in the Kotlin client.&lt;/p&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-2&quot; aria-labelledby=&quot;tab-4-2&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Not available in the Java client.&lt;/p&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-3&quot; aria-labelledby=&quot;tab-4-3&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Not available in the TypeScript client.&lt;/p&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-4&quot; aria-labelledby=&quot;tab-4-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Not available in the Elixir client.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;That handler is where a read model gets cleaned up or a downstream system gets told. Redaction doesn’t reach database oplogs, backups, or copies already forwarded to another event store through outbox and inbox, and it doesn’t clean read models by itself.&lt;/p&gt;
&lt;p&gt;Erasing a key is the fourth operation. Every event stays intact, and only the marked values become unreadable.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/four-operations.CwAQt5qN_1D0o3L.webp&quot; alt=&quot;A diagram titled Four questions, four operations, in two columns headed Changes and Stays, one row per operation. Compensate: a new event at N+k; event N untouched. Revise: a correction stored beside it, and the partition rewinds; the original is kept in history. Redact: a marker replaces the payload; type, time, correlation and causation types stay, and oplogs, backups and forwarded copies keep the original. Erase a key: marked values read empty; the event stays intact, and causation values, ids and backups remain.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Four ways to change what an event shows, and what each one leaves.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;erasure-step-by-step&quot;&gt;Erasure, step by step&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;DeleteEncryptionKeyFor&lt;/code&gt; on &lt;code dir=&quot;auto&quot;&gt;eventStore.PII&lt;/code&gt; runs three phases in the kernel:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;DeleteEncryptionKeyFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;EncryptionKeyIdentifier&lt;/span&gt;&lt;span&gt;&lt;span&gt; identifier&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;ThrowIfEncryptedValueIdentifier&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;identifier&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; key &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GetKey&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Exception&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; failures &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[];&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; eventStores &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EventStoresToReach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;failures&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ForEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;eventStores&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;failures&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; eventStore &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;keyStore&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;RecordErasureFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;eventStore&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Namespace&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;identifier&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ForEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;eventStores&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;failures&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; eventStore &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;keyStore&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;DeleteFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;eventStore&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Namespace&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;identifier&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ForEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;eventStores&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;failures&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; eventStore &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cacheClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Evict&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;eventStore&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Namespace&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;identifier&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;failures&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Count&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;logger&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SubjectErasureIncomplete&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BindingFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;identifier&lt;/span&gt;&lt;span&gt;&lt;span&gt;),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Namespace&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;failures&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Count&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;eventStores&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Count&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Names&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;eventStores&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EncryptionKeyErasureIncomplete&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;identifier&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;failures&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;logger&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ErasedSubject&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BindingFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;identifier&lt;/span&gt;&lt;span&gt;&lt;span&gt;),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Namespace&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;eventStores&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Count&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Names&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;eventStores&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;It refuses an identifier that belongs to an &lt;code dir=&quot;auto&quot;&gt;[Encrypted]&lt;/code&gt; value, with &lt;code dir=&quot;auto&quot;&gt;EncryptionKeyIsNotErasable&lt;/code&gt;. It lists every event store in the namespace, always including the store the caller named, even if the listing fails. It records an erasure fence in each store, and only after that destroys the key in each of them. Then it evicts the key from every silo’s cache, whether or not the earlier phases succeeded. Every phase tries every store, and if any of them failed, the call throws &lt;code dir=&quot;auto&quot;&gt;EncryptionKeyErasureIncomplete&lt;/code&gt; listing all the failures.&lt;/p&gt;
&lt;p&gt;The order closes a window. Erasing store by store left an interval where one store still held the key and another didn’t, and an event forwarded in that interval copied the surviving key into the store that had just been cleared.&lt;/p&gt;
&lt;p&gt;The fence is a record kept beside the keys, with three fields. &lt;code dir=&quot;auto&quot;&gt;ErasedThrough&lt;/code&gt; is the highest key revision it covers, &lt;code dir=&quot;auto&quot;&gt;ErasedKeyFingerprints&lt;/code&gt; holds SHA-256 fingerprints of the destroyed public keys, and &lt;code dir=&quot;auto&quot;&gt;NewKeyAllowed&lt;/code&gt; is false after every erasure. While a fence exists, the store refuses to provision a key for the subject, to save any revision at or below the floor, or to accept a key whose fingerprint matches a destroyed one at any revision. The fingerprint check is what stops a cross-store copy from bringing the old key back. Reads are untouched, which is why the erased values read as empty.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Appending a &lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt; value for an erased subject fails with &lt;code dir=&quot;auto&quot;&gt;EncryptionKeyErased&lt;/code&gt;. It doesn’t mint a key and doesn’t blank the value.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;AllowNewEncryptionKeyFor(subject)&lt;/code&gt; sets &lt;code dir=&quot;auto&quot;&gt;NewKeyAllowed&lt;/code&gt; in every store of the namespace and creates no key. The next protected append mints one at &lt;code dir=&quot;auto&quot;&gt;ErasedThrough + 1&lt;/code&gt;. That key decrypts nothing written before the erasure, and the destroyed material is still refused.&lt;/li&gt;
&lt;li&gt;A forwarded event carrying &lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt; for an erased subject fails to append in the target store, and that event source’s partition fails while the rest of the subscription keeps flowing.&lt;/li&gt;
&lt;li&gt;Replaying a projection or reducer into a stored read model applies protection again, so the replay is refused for the erased subject’s partition. A rebuild doesn’t remove ciphertext already in the read model store. Before such a rebuild, authorize a new key for anyone you intend to keep protecting.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Erasure doesn’t touch the constraint index. A &lt;code dir=&quot;auto&quot;&gt;[Unique]&lt;/code&gt; constraint over a &lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt; value keeps a SHA-256 hash of the plaintext, and &lt;code dir=&quot;auto&quot;&gt;DeleteEncryptionKeyFor&lt;/code&gt; removes keys and caches, not that entry. The erased author’s name therefore stays taken in that namespace, and because the hash isn’t salted, anyone who can read the index can still confirm a guessed name. The entry goes when an event marked &lt;code dir=&quot;auto&quot;&gt;[RemoveConstraint]&lt;/code&gt; with the constraint’s name is appended for the same event source, so give the author feature a removal event that carries no personal data, such as &lt;code dir=&quot;auto&quot;&gt;AuthorForgotten(AuthorId)&lt;/code&gt;, and append it as part of the erasure. A constraint reindex, which runs when a constraint’s definition changes, also drops entries whose value now reads as empty. A fix is tracked in &lt;a href=&quot;https://github.com/Cratis/Chronicle/issues/4389&quot;&gt;Chronicle issue #4389&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The completion is logged, as in “Erased the encryption key for the subject bound to ‘a3f1…’ in namespace ‘Default’, across all 3 event stores”. The binding is a SHA-256 hex of the identifier, a pseudonym that can be brute-forced when the space of identifiers is small. The log records that an erasure completed and where. Who asked, and on what legal basis, isn’t recorded. An incomplete run logs at error level.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/the-fence.DjFb6wra_1nuF58.webp&quot; alt=&quot;A diagram titled The fence. A frame labeled Namespace Acme holds three event stores, Sales, Support and Billing, and three numbered steps across all of them: 1, record the fence in every store; 2, destroy the key in every store; 3, evict every cache. A row reads: Fence: ErasedThrough, key fingerprints, NewKeyAllowed = false. A red row reads: a forwarded PII append fails: EncryptionKeyErased. Below the frame, a separate row reads: Namespace Globex: untouched, needs its own erasure.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fence first, destroy second, evict always.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Erasures performed before the fence shipped aren’t fenced and can be resurrected until they’re run again. The call is idempotent, one per subject. Every silo has to run a version with the fence, and mixed versions aren’t supported while an erasure is in force. A custom &lt;code dir=&quot;auto&quot;&gt;IEncryptionKeyStorage&lt;/code&gt; without the fence fails on the first erasure. In a composed store, an unreachable member is skipped on reads, so a fence held only there is invisible while that member is down, and a key provisioning already in flight when the fence lands can still persist its key. Erase again after forwarding and appends for the subject have stopped.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-the-keys-live-and-how-they-come-back&quot;&gt;Where the keys live, and how they come back&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;By default keys sit in the general storage backend. &lt;code dir=&quot;auto&quot;&gt;compliance.encryption.storage&lt;/code&gt; selects a &lt;a href=&quot;https://cratis.io/chronicle/hosting/configuration/compliance-storage/&quot;&gt;dedicated store&lt;/a&gt;, either HashiCorp Vault (KV v2, with the token from &lt;code dir=&quot;auto&quot;&gt;VAULT_TOKEN&lt;/code&gt;) or Azure Key Vault, and both keep every key revision as its own secret. What those services do with a deleted secret follows their own retention and recovery policies.&lt;/p&gt;
&lt;p&gt;Switching to a dedicated store on a running system with keys already stored makes those keys unreachable, and their values read as empty strings with no error. &lt;code dir=&quot;auto&quot;&gt;migrateFromDefaultStorage: true&lt;/code&gt; composes both stores, moves each key on its first read, mirrors new keys back and erases from both. A key nobody has read since the switch still lives only in the default store, so turn the flag off only after confirming that the dedicated store holds every key.&lt;/p&gt;
&lt;p&gt;A backup has three parts: the encryption-certificate ring, the storage backend and the key store. The key store has to be restored to the same point in time as the storage. Restore it from earlier, and newer subjects’ values read as empty, indistinguishable from an erasure. The same backup, if it’s from before an erasure, brings the key back and removes the fence, so an erasure you’ve already reported as complete is silently undone, and nothing inside Chronicle can see that it happened. A lost key store can’t be rebuilt, because there’s no escrow and no recovery key.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-same-markers-in-other-clients&quot;&gt;The same markers in other clients&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The C# client validates the most. The Kotlin/Java, TypeScript and Elixir clients accept the markers without the same checks. Every client takes a subject as an append option, but only C# reads it from an event’s &lt;code dir=&quot;auto&quot;&gt;[Subject]&lt;/code&gt; property. The other clients’ subject markers only pick the key a read model is released under. Elixir can’t mark a whole multi-field struct &lt;code dir=&quot;auto&quot;&gt;[PII]&lt;/code&gt;, and the TypeScript decorator doesn’t read constructor parameters. &lt;code dir=&quot;auto&quot;&gt;[Encrypted]&lt;/code&gt; exists in all four. Every client can delete a subject’s key, and only .NET and TypeScript can authorize a new one afterward. &lt;a href=&quot;https://cratis.io/chronicle/clients/where-the-clients-differ/&quot;&gt;Where the clients differ&lt;/a&gt; has the full list.&lt;/p&gt;
&lt;p&gt;Erasing an author takes &lt;code dir=&quot;auto&quot;&gt;DeleteEncryptionKeyFor&lt;/code&gt; with the author’s subject, once in each namespace the author appears in, and because the name is &lt;code dir=&quot;auto&quot;&gt;[Unique]&lt;/code&gt;, the erasure also appends a removal event so the name’s hash leaves the constraint index. The name then reads as empty in the log and in the author list, &lt;code dir=&quot;auto&quot;&gt;AuthorRegistered&lt;/code&gt; keeps its sequence number, and whatever the application already sent elsewhere is still the application’s to clean up.&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href=&quot;https://blog.cratis.io/beyond-dotnet/&quot;&gt;Part 12 — Beyond .NET&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next: &lt;a href=&quot;https://blog.cratis.io/four-changes-in-year-two/&quot;&gt;Part 14 — Year two&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>chronicle</category><category>arc</category><category>pii</category><category>privacy</category><category>event-sourcing</category></item><item><title>From script to stage · Part 12: Beyond .NET</title><link>https://blog.cratis.io/beyond-dotnet/</link><guid isPermaLink="true">https://blog.cratis.io/beyond-dotnet/</guid><description>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.</description><pubDate>Wed, 30 Sep 2026 09:12:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt; · Part 12 of 26&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Chronicle has clients for .NET, Kotlin and Java, TypeScript and Elixir, and all of them append to the same kernel through the same contract. Only one of them checks concurrency on an append unless you ask it to. The .NET client reads the tail of the event source and sends the sequence number it expects, so an append that lands between that read and its write fails. The Kotlin and Java client appends with no concurrency scope, and the TypeScript and Elixir clients send no check at all.&lt;/p&gt;
&lt;p&gt;That’s one line in a table of differences, and it matters the day a second team picks a second language. The author feature is written in C#, and nothing in its uniqueness rule needs C#. A JVM or Node team can build the same feature on Cratis, once they know what exists for their language and where it departs from the .NET path.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;one-contract-several-ecosystems&quot;&gt;One contract, several ecosystems&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Not every team is on .NET, and not every service in one system should have to be. Arc’s &lt;a href=&quot;https://cratis.io/arc/http-contract/&quot;&gt;HTTP contract&lt;/a&gt; is shared across three server implementations, and each generates TypeScript proxies for the frontend, though each has gaps of its own.&lt;/p&gt;
&lt;p&gt;On the JVM, &lt;a href=&quot;https://cratis.io/arc/backend/kotlin/&quot;&gt;Arc for Kotlin&lt;/a&gt; is a Spring Boot implementation for Kotlin and Java, published to Maven Central. This command and its event come from its Chronicle Spring Boot sample, trimmed and with the id and title as concepts. The C# and TypeScript tabs have the same command, the TypeScript one from Arc for TypeScript’s Chronicle guide:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-0&quot; id=&quot;tab-0-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-1&quot; id=&quot;tab-0-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-2&quot; id=&quot;tab-0-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-3&quot; id=&quot;tab-0-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-4&quot; id=&quot;tab-0-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-0&quot; aria-labelledby=&quot;tab-0-0&quot; role=&quot;tabpanel&quot;&gt;&lt;p&gt;The same command in C#, where &lt;code dir=&quot;auto&quot;&gt;TaskId&lt;/code&gt;, an &lt;code dir=&quot;auto&quot;&gt;EventSourceId&amp;#x3C;Guid&gt;&lt;/code&gt;, names the event source.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Arc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Authorization&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Arc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Commands&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ModelBound&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskCreated&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt; Title&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Command&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;AllowAnonymous&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreateTask&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TaskId&lt;/span&gt;&lt;span&gt;&lt;span&gt; Id&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt; Title&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskCreated&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Handle&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-1&quot; aria-labelledby=&quot;tab-0-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; io.cratis.arc.artifacts.Command&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; io.cratis.arc.artifacts.CommandKey&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; io.cratis.arc.authorization.AllowAnonymous&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; io.cratis.chronicle.events.EventType&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@EventType&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskCreated&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; title: &lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@Command&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@AllowAnonymous&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreateTask&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;@CommandKey&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; id: &lt;/span&gt;&lt;span&gt;TaskId&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; title: &lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;(): &lt;/span&gt;&lt;span&gt;TaskCreated&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskCreated&lt;/span&gt;&lt;span&gt;(title)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-2&quot; aria-labelledby=&quot;tab-0-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;arc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;artifacts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Command&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;arc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;artifacts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;CommandKey&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;arc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;authorization&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;AllowAnonymous&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;events&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskCreated&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt; title&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Command&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;AllowAnonymous&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreateTask&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;/span&gt;&lt;span&gt;CommandKey&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskId&lt;/span&gt;&lt;span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt; title&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskCreated&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskCreated&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-3&quot; aria-labelledby=&quot;tab-0-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/fundamentals&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;eventType&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/chronicle/events&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;allowAnonymous&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/arc.core&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;eventType&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskCreated&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;allowAnonymous&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreateTask&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TaskId&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskId&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskCreated&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskCreated&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-4&quot; aria-labelledby=&quot;tab-0-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Arc for Elixir doesn’t exist; use the Chronicle Elixir client directly.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;The command returns the event, and Arc’s Chronicle integration, which the sample uses, appends it to the event source named by &lt;code dir=&quot;auto&quot;&gt;@CommandKey&lt;/code&gt;. Arc for Kotlin also has validation, authorization, tenancy, observable queries and in-process command scenarios. It isn’t at parity with .NET. Command operations and aggregates aren’t in it, and it runs only on Spring Boot.&lt;/p&gt;
&lt;p&gt;The Node.js command is a decorated class, here from the source-only Tasks sample described in &lt;a href=&quot;https://blog.cratis.io/beyond-dotnet/#arc-for-typescript-from-source&quot;&gt;&lt;em&gt;Arc for TypeScript, from source&lt;/em&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RegisterTask&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TaskId&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskId&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tasks&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Tasks&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskId&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;tasks&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;register&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Chronicle has its own set of &lt;a href=&quot;https://cratis.io/chronicle-clients/&quot;&gt;clients&lt;/a&gt;: a first-class .NET SDK, and clients for TypeScript, Kotlin/Java (JVM) and Elixir, over whichever storage provider each deployment chooses. Services in one system can use different clients against the same kernel. The clients aren’t interchangeable, though. The default concurrency check, &lt;code dir=&quot;auto&quot;&gt;ExpectingNoMatchingEvent&lt;/code&gt;, decision reads and some erasure options differ, and &lt;a href=&quot;https://cratis.io/chronicle/clients/where-the-clients-differ/&quot;&gt;where the clients differ&lt;/a&gt; lists the gaps. &lt;a href=&quot;https://blog.cratis.io/event-sourcing-in-any-language/&quot;&gt;Event sourcing in any language&lt;/a&gt; explains the shared contract underneath.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/one-contract-many-ecosystems.BnO-DEEp_BqkcN.webp&quot; alt=&quot;A diagram titled One contract, several ecosystems. On the left, three server boxes: Arc on ASP.NET Core, Arc on Spring Boot, and Arc on Node.js with Express, Fastify and Hono. All three connect to one central box, the Arc HTTP contract, which connects to Generated TypeScript proxies and then React and Components on the right. Below, in a frame labeled Chronicle, a Chronicle kernel box sits beside four client chips: .NET, TypeScript, Kotlin and Java, and Elixir.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2200&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The contract is shared; each implementation states its own gaps.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Two things are shared and everything else belongs to a language. The Chronicle kernel runs the observers, the constraints and the storage, and a client registers its artifacts with it over gRPC and streams events to and from it. Arc’s contract fixes the command and query envelopes, the observable transports and the validation rules. For the frontend that means very little changes. The React code from &lt;a href=&quot;https://blog.cratis.io/live-uis-with-arc/&quot;&gt;&lt;em&gt;Live UIs&lt;/em&gt;&lt;/a&gt; compiles against generated proxies, and &lt;code dir=&quot;auto&quot;&gt;@cratis/arc&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;@cratis/arc.react&lt;/code&gt; don’t care which server the proxies came from. What changes is how the backend declares its commands and queries, and how its events reach the log.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;arc-on-the-jvm&quot;&gt;Arc on the JVM&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/arc/backend/kotlin/&quot;&gt;Arc for Kotlin&lt;/a&gt; is MIT-licensed and published to Maven Central as &lt;code dir=&quot;auto&quot;&gt;io.cratis:arc&lt;/code&gt;. It runs on the Spring Boot servlet stack, where auto-configuration registers the HTTP, Server-Sent Events and optional WebSocket endpoints. Java gets the same treatment as Kotlin, with plain-Java samples and Java tabs in the docs for the same features. A handler can be an ordinary function, a Kotlin &lt;code dir=&quot;auto&quot;&gt;suspend&lt;/code&gt; function or a method returning a Java &lt;code dir=&quot;auto&quot;&gt;CompletionStage&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A command is a &lt;code dir=&quot;auto&quot;&gt;@Command&lt;/code&gt; class with a &lt;code dir=&quot;auto&quot;&gt;handle()&lt;/code&gt; method, and a query is a companion or static method on a &lt;code dir=&quot;auto&quot;&gt;@ReadModel&lt;/code&gt; type. KSP generates the handlers at compile time, so nothing is found by reflection when the application runs. Routes come from names, and &lt;code dir=&quot;auto&quot;&gt;CreateTask&lt;/code&gt; becomes &lt;code dir=&quot;auto&quot;&gt;POST /api/create-task&lt;/code&gt;. The package segments to leave out of a route are set twice, once in the Gradle plugin as &lt;code dir=&quot;auto&quot;&gt;cratisArc.endpoints.segmentsToSkip&lt;/code&gt; and once at runtime as &lt;code dir=&quot;auto&quot;&gt;cratis.arc.endpoints.segments-to-skip-for-route&lt;/code&gt;. If the two disagree, the starter refuses to start. &lt;a href=&quot;https://blog.cratis.io/arc-gotchas/&quot;&gt;&lt;em&gt;Arc gotchas&lt;/em&gt;&lt;/a&gt; covers names doing the mapping on .NET. On the JVM, a mismatch is caught at startup.&lt;/p&gt;
&lt;p&gt;A query stays live by returning something that emits. In Kotlin that’s a &lt;code dir=&quot;auto&quot;&gt;Flow&lt;/code&gt; or a &lt;code dir=&quot;auto&quot;&gt;StateFlow&lt;/code&gt;. In Java it’s a JDK &lt;code dir=&quot;auto&quot;&gt;Flow.Publisher&lt;/code&gt; or Arc’s &lt;code dir=&quot;auto&quot;&gt;ObservableState&amp;#x3C;T&gt;&lt;/code&gt;, and with the optional &lt;code dir=&quot;auto&quot;&gt;arc-rxjava3&lt;/code&gt; artifact it can be an RxJava 3 &lt;code dir=&quot;auto&quot;&gt;Observable&lt;/code&gt;. The Kotlin and Java tabs follow the &lt;a href=&quot;https://cratis.io/arc/backend/kotlin/guides/observable-queries/&quot;&gt;observable queries guide&lt;/a&gt;, the TypeScript tab follows Arc for TypeScript’s guide to observable queries, and the C# tab is the same query for .NET. Like the guides, each keeps the tasks in one value for the whole process, not one per tenant:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-0&quot; id=&quot;tab-1-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-1&quot; id=&quot;tab-1-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-2&quot; id=&quot;tab-1-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-3&quot; id=&quot;tab-1-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-4&quot; id=&quot;tab-1-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-0&quot; aria-labelledby=&quot;tab-1-0&quot; role=&quot;tabpanel&quot;&gt;&lt;p&gt;The same query in C#, with a &lt;code dir=&quot;auto&quot;&gt;BehaviorSubject&lt;/code&gt; as the &lt;code dir=&quot;auto&quot;&gt;ISubject&lt;/code&gt;.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;ReadModel&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;AllowAnonymous&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskView&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TaskId&lt;/span&gt;&lt;span&gt;&lt;span&gt; Id&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt; Title&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BehaviorSubject&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IEnumerable&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TaskView&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;span&gt; Tasks &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;([]);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ISubject&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IEnumerable&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TaskView&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;All&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Tasks&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-1&quot; aria-labelledby=&quot;tab-1-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; _tasks &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MutableStateFlow&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TaskView&lt;/span&gt;&lt;span&gt;&gt;&gt;(&lt;/span&gt;&lt;span&gt;emptyList&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@ReadModel&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@AllowAnonymous&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskView&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; id: &lt;/span&gt;&lt;span&gt;TaskId&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; title: &lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;companion&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;@JvmStatic&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;all&lt;/span&gt;&lt;span&gt;(): &lt;/span&gt;&lt;span&gt;Flow&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TaskView&lt;/span&gt;&lt;span&gt;&gt;&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; _tasks&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-2&quot; aria-labelledby=&quot;tab-1-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;ReadModel&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;AllowAnonymous&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskView&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TaskId&lt;/span&gt;&lt;span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt; title&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;final&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ObservableState&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TaskView&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tasks&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ObservableState&lt;/span&gt;&lt;span&gt;&amp;#x3C;&gt;(&lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;of&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Flow&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Publisher&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TaskView&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;all&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; tasks&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-3&quot; aria-labelledby=&quot;tab-1-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tasks&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BehaviorSubject&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TaskView&lt;/span&gt;&lt;span&gt;[]&gt;([]);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;readModel&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;allowAnonymous&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskView&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TaskId&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskId&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt; observable&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;all&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Observable&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TaskView&lt;/span&gt;&lt;span&gt;&lt;span&gt;[]&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tasks&lt;/span&gt;&lt;span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-4&quot; aria-labelledby=&quot;tab-1-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Arc for Elixir doesn’t exist; use the Chronicle Elixir client directly.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;MutableStateFlow&lt;/code&gt; does the job &lt;code dir=&quot;auto&quot;&gt;ISubject&amp;#x3C;T&gt;&lt;/code&gt; does on .NET, and the state it holds matters. A source that only emits has nothing to answer a plain HTTP request with, so a snapshot GET against it returns &lt;code dir=&quot;auto&quot;&gt;202 Not Ready&lt;/code&gt; unless the caller passes &lt;code dir=&quot;auto&quot;&gt;waitForFirstResult=true&lt;/code&gt;. The transports are the ones .NET uses, Server-Sent Events, WebSocket and an HTTP snapshot.&lt;/p&gt;
&lt;p&gt;Validation is Jakarta Bean Validation, with reusable concept and model validators and shared fluent validators whose rules carry over into the generated client. The Gradle plugin, &lt;code dir=&quot;auto&quot;&gt;io.cratis.arc&lt;/code&gt;, generates strict-mode TypeScript for commands, queries, observables and models, validation metadata included, and the frontend uses it the way it would against .NET.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;events-from-a-command&quot;&gt;Events from a command&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Chronicle is optional on the JVM as well. &lt;code dir=&quot;auto&quot;&gt;io.cratis:arc&lt;/code&gt; has no Chronicle dependency, and an application can sit on JPA, MongoDB or plain services. For an event-sourced application the one dependency to take is &lt;code dir=&quot;auto&quot;&gt;io.cratis:cratis&lt;/code&gt;, which pulls in Arc, its Spring wiring and the &lt;a href=&quot;https://cratis.io/arc/backend/kotlin/guides/chronicle/&quot;&gt;Chronicle integration&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With it, a command returns an event and Arc appends it, as &lt;code dir=&quot;auto&quot;&gt;CreateTask&lt;/code&gt; does above. &lt;code dir=&quot;auto&quot;&gt;@EventType&lt;/code&gt; on the event is required. Arc’s Chronicle integration treats only &lt;code dir=&quot;auto&quot;&gt;@EventType&lt;/code&gt; values as events. A collection or routed wrapper that contains anything else fails before anything is appended, and a single unannotated value isn’t appended at all. Arc for Kotlin’s plain example returns a &lt;code dir=&quot;auto&quot;&gt;TaskCreated&lt;/code&gt; with no annotation, which is fine there because that sample has no event log, and wrong the moment Chronicle is involved. The Chronicle sample also gives every event property a default, as &lt;code dir=&quot;auto&quot;&gt;TaskCreated&lt;/code&gt; above does with &lt;code dir=&quot;auto&quot;&gt;val title: TaskTitle = TaskTitle(&quot;&quot;)&lt;/code&gt;, and the Chronicle client’s own examples declare events the same way.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;@CommandKey&lt;/code&gt; is required too. It names the event source, and a command without one gets back a validation result with &lt;code dir=&quot;auto&quot;&gt;reasonDetail: &quot;commandKey&quot;&lt;/code&gt;, because Arc won’t guess which event source you meant. Events returned inside one outermost command are staged and committed with a single &lt;code dir=&quot;auto&quot;&gt;appendMany&lt;/code&gt; call, which fails closed if the event store, the namespace or the correlation doesn’t match. That makes it a commit barrier for the event log. Chronicle, JPA and MongoDB can’t join one atomic transaction, and cross-store transactions aren’t planned.&lt;/p&gt;
&lt;p&gt;Failures come back as validation results. A broken &lt;a href=&quot;https://cratis.io/chronicle/constraints/&quot;&gt;constraint&lt;/a&gt; has the reason &lt;code dir=&quot;auto&quot;&gt;constraintViolation&lt;/code&gt;, and a &lt;a href=&quot;https://cratis.io/chronicle/events/concurrency/&quot;&gt;concurrency&lt;/a&gt; failure has &lt;code dir=&quot;auto&quot;&gt;concurrencyViolation&lt;/code&gt; with the expected and actual sequence numbers. When a decision depends on something the command read, &lt;code dir=&quot;auto&quot;&gt;EventsWithConcurrencyScopes&lt;/code&gt; sets an exact scope per event source. A Chronicle read model can be a handler parameter with no annotation, resolved by the command key and the tenant’s store, and read-model values go through Chronicle’s release step before a query hands them out.&lt;/p&gt;
&lt;p&gt;A reactor can pass a registered command to Arc’s real pipeline through &lt;code dir=&quot;auto&quot;&gt;ChronicleCommandSideEffectHandler&lt;/code&gt;, and gets no roles unless it declares &lt;code dir=&quot;auto&quot;&gt;@ExecuteCommandsAsSystem&lt;/code&gt;. For specs, &lt;code dir=&quot;auto&quot;&gt;CommandScenario&lt;/code&gt; finds an in-memory event log through &lt;code dir=&quot;auto&quot;&gt;ServiceLoader&lt;/code&gt;, so a command spec runs with no kernel. &lt;a href=&quot;https://blog.cratis.io/testing-with-arc-and-chronicle/&quot;&gt;&lt;em&gt;Testing with Arc and Chronicle&lt;/em&gt;&lt;/a&gt; follows the same idea on .NET.&lt;/p&gt;
&lt;p&gt;The Chronicle sample is strict about tenants. Every request has to carry the &lt;code dir=&quot;auto&quot;&gt;x-cratis-tenant-id&lt;/code&gt; header, and the sample resolves the event store in exactly that namespace, with no default tenant to fall back on. Where that header should come from in a real deployment is the subject of &lt;a href=&quot;https://blog.cratis.io/tenancy-and-identity-end-to-end/&quot;&gt;&lt;em&gt;Tenancy and identity end to end&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;what-the-jvm-doesnt-have&quot;&gt;What the JVM doesn’t have&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Arc for Kotlin runs on Spring Boot’s servlet stack only, so WebFlux and hosts outside Spring aren’t supported. Controller-based commands, static-file and SPA-fallback hosting, &lt;code dir=&quot;auto&quot;&gt;@FromRequest&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;@GenerateOneOf&lt;/code&gt; aren’t planned. Command operations and aggregate roots aren’t in the source, and Screenplay generation doesn’t target the JVM. Agreement with .NET is checked only in part: generated proxies against a captured .NET fixture, and HTTP behavior over nine common cases. MongoDB observables need a replica set, and Java’s delegated filters and validators need adapter beans.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;chronicle-on-the-jvm&quot;&gt;Chronicle on the JVM&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;io.cratis:chronicle&lt;/code&gt; is the Kotlin and Java client, and its model is the one .NET developers know. &lt;code dir=&quot;auto&quot;&gt;@EventType&lt;/code&gt; goes on a data class or a record. A read model is a &lt;code dir=&quot;auto&quot;&gt;@ReadModel&lt;/code&gt;, and a &lt;code dir=&quot;auto&quot;&gt;@Reducer&lt;/code&gt; has one method per event. There’s no schema file and no registry to fill in. When the client connects, it finds the annotated artifacts on the classpath and registers them with the kernel in order.&lt;/p&gt;
&lt;p&gt;In Kotlin every call to the kernel suspends. Java gets blocking bridges over the same surface and configures the client through &lt;code dir=&quot;auto&quot;&gt;ChronicleOptions&lt;/code&gt;. The Spring Boot starter, &lt;code dir=&quot;auto&quot;&gt;io.cratis:chronicle-spring-boot-starter&lt;/code&gt;, connects the client, registers the artifacts at startup, handles tenancy, identity and units of work per request, and gives you an injectable &lt;code dir=&quot;auto&quot;&gt;IEventStore&lt;/code&gt;. It targets Spring Boot 4 on the servlet stack, and its per-request features don’t apply to WebFlux.&lt;/p&gt;
&lt;p&gt;By default the client constructs an artifact by calling a constructor with no parameters, or one where every parameter has a default. A reactor that needs a repository, or anything else from the container, needs an &lt;code dir=&quot;auto&quot;&gt;IArtifactActivator&lt;/code&gt;, and the starter supplies one.&lt;/p&gt;
&lt;p&gt;A successful append means the fact is stored. The read model catches up asynchronously, so a read on the very next line can return &lt;code dir=&quot;auto&quot;&gt;null&lt;/code&gt;, and the client’s guide shows a bounded wait for the places that need one. Reactors take &lt;code dir=&quot;auto&quot;&gt;@OnceOnly&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;@Replay&lt;/code&gt; as they do on .NET. The client checks the kernel’s version when it connects and refuses a kernel it isn’t compatible with, and Arc for Kotlin’s Chronicle guide names a matching client and kernel pair. &lt;a href=&quot;https://blog.cratis.io/running-chronicle-in-production/&quot;&gt;&lt;em&gt;Running Chronicle in production&lt;/em&gt;&lt;/a&gt; covers the certificate-validation setup before connecting any client to a shared server.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;chronicle-in-typescript&quot;&gt;Chronicle in TypeScript&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;@cratis/chronicle&lt;/code&gt; is on npm. Its artifacts are decorators, such as &lt;code dir=&quot;auto&quot;&gt;@eventType&lt;/code&gt; for an event and &lt;code dir=&quot;auto&quot;&gt;@projection&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;@reducer&lt;/code&gt; for a read model. An append goes from a &lt;code dir=&quot;auto&quot;&gt;ChronicleClient&lt;/code&gt; to an &lt;code dir=&quot;auto&quot;&gt;EventStore&lt;/code&gt; to its &lt;code dir=&quot;auto&quot;&gt;EventLog&lt;/code&gt;. The &lt;a href=&quot;https://cratis.io/chronicle/events/appending/&quot;&gt;appending example&lt;/a&gt;, without its service wrapper and with &lt;code dir=&quot;auto&quot;&gt;CustomerId&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Money&lt;/code&gt; in place of a string and a number:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;eventType&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OrderPlaced&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CustomerId&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;customerId&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CustomerId&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Money&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Money&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;customerId&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CustomerId&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Money&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;customerId&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;customerId&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;store&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;eventLog&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;orderId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OrderPlaced&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;customerId&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;isSuccess&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;/* decide whether to retry or surface a conflict */&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;eventType&lt;/code&gt; comes from &lt;code dir=&quot;auto&quot;&gt;@cratis/chronicle&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;field&lt;/code&gt; from &lt;code dir=&quot;auto&quot;&gt;@cratis/fundamentals&lt;/code&gt;. The append sends no concurrency check, so when the event depends on something you read first, give the append an explicit scope.&lt;/p&gt;
&lt;p&gt;The client needs Node.js 22.19 or later, because it’s built on &lt;code dir=&quot;auto&quot;&gt;undici&lt;/code&gt;, and it fails to import on Node 20. Standard decorators and top-level &lt;code dir=&quot;auto&quot;&gt;await&lt;/code&gt; need TypeScript 5.2 or later and an ES module build. A read model is inferred from a &lt;code dir=&quot;auto&quot;&gt;@projection&lt;/code&gt;, a &lt;code dir=&quot;auto&quot;&gt;@reducer&lt;/code&gt; or an exported model with &lt;code dir=&quot;auto&quot;&gt;@fromEvent&lt;/code&gt;, its schema comes from the model type, and its id defaults to the class name. &lt;code dir=&quot;auto&quot;&gt;skipTlsValidation&lt;/code&gt; defaults to &lt;code dir=&quot;auto&quot;&gt;true&lt;/code&gt;, as on every client, so set it to &lt;code dir=&quot;auto&quot;&gt;false&lt;/code&gt; for any shared server.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;chronicle-in-elixir&quot;&gt;Chronicle in Elixir&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;cratis_chronicle&lt;/code&gt; is on Hex, and it’s written for OTP. An event is a struct that uses &lt;code dir=&quot;auto&quot;&gt;Chronicle.Events.EventType&lt;/code&gt; with an id. A read model uses &lt;code dir=&quot;auto&quot;&gt;Chronicle.ReadModels.ReadModel&lt;/code&gt; with a &lt;code dir=&quot;auto&quot;&gt;from Event, set: [...]&lt;/code&gt; mapping, a model-bound projection the kernel runs. Reactors, reducers and seeders follow the same pattern, there are model-bound unique and unique-event-type constraints, and identity, correlation and causation are metadata scoped to the process. The same append:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;defmodule&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;OrderPlaced&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;order-placed&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;defstruct&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;customer_id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; %&lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;CustomerId&lt;/span&gt;&lt;span&gt;&lt;span&gt;{},&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; %&lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Money&lt;/span&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;case&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;order_id&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; %&lt;/span&gt;&lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;OrderPlaced&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;       &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;customer_id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; customer_id&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;       &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; total&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;     &lt;/span&gt;&lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;ok&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;ok&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;_reason&lt;/span&gt;&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; error &lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt; error&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;Chronicle.append&lt;/code&gt; returns &lt;code dir=&quot;auto&quot;&gt;:ok&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;{:error, reason}&lt;/code&gt;, so there’s no sequence number to read back from a successful append. The client is a supervised child of your application, &lt;code dir=&quot;auto&quot;&gt;{Chronicle.Client, connection_string: ..., event_store: ..., otp_app: ...}&lt;/code&gt;. It connects and registers in the background with backoff, and until registration has finished, calls return &lt;code dir=&quot;auto&quot;&gt;{:error, :not_connected}&lt;/code&gt;. &lt;code dir=&quot;auto&quot;&gt;Lifecycle.wait_until(..., :registered)&lt;/code&gt; waits for it.&lt;/p&gt;
&lt;p&gt;The client needs Elixir 1.18 or later and a kernel with contracts 19.19 or later. The Mint gRPC adapter needs &lt;code dir=&quot;auto&quot;&gt;enable_push: false&lt;/code&gt;, among other caveats. TLS has the same default as in TypeScript. Version 3.5.0 had defects, so take 3.5.1 or later.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;arc-for-typescript-from-source&quot;&gt;Arc for TypeScript, from source&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/arc/backend/typescript/getting-started/&quot;&gt;Arc for TypeScript&lt;/a&gt; is an early source preview of a TypeScript command and query server on Node.js. Its packages aren’t on npm, its version starts with 0, and its APIs can change. You run it from a clone, for example with &lt;code dir=&quot;auto&quot;&gt;yarn workspace @cratis/arc.core.sample.tasks start&lt;/code&gt;. Next to the &lt;code dir=&quot;auto&quot;&gt;RegisterTask&lt;/code&gt; command above, the Tasks sample has a live query:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;readModel&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskItem&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TaskId&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskId&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TaskTitle&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;observeAllTasks&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tasks&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Tasks&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BehaviorSubject&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TaskItem&lt;/span&gt;&lt;span&gt;&lt;span&gt;[]&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tasks&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;observeAll&lt;/span&gt;&lt;span&gt;&lt;span&gt;();&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;An observable query returns an RxJS &lt;code dir=&quot;auto&quot;&gt;BehaviorSubject&lt;/code&gt;, an &lt;code dir=&quot;auto&quot;&gt;Observable&lt;/code&gt; or an async iterable. The older &lt;code dir=&quot;auto&quot;&gt;defineCommand&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;defineQuery&lt;/code&gt; functions with Zod schemas still work.&lt;/p&gt;
&lt;p&gt;Besides Express 5, Fastify 5 and Hono 4 there’s a standalone Node host, and a Fetch API entry, &lt;code dir=&quot;auto&quot;&gt;@cratis/arc.core/fetch&lt;/code&gt;, for Next.js route handlers, Bun and Deno. Next.js App Router route handlers run on the Node runtime. Cloudflare Workers and the Next.js Edge runtime aren’t supported, and there’s no NestJS adapter. Data comes from tenant-scoped MongoDB, which observes changes through a replica set, or from Drizzle over SQLite or PostgreSQL, with no change tracking and no SQL observation. There are no automatic migrations.&lt;/p&gt;
&lt;p&gt;The Chronicle integration, &lt;code dir=&quot;auto&quot;&gt;@cratis/arc.chronicle&lt;/code&gt;, is experimental. It appends returned events and resolves read models by key. Returned events and command operations can’t be combined, and immediate appends, aggregates and reactor command effects don’t join the single event-log batch. Wire behavior is compared with Arc on .NET in a paired suite for a bounded set of HTTP cases, which doesn’t amount to parity. A source-based &lt;code dir=&quot;auto&quot;&gt;arc-proxygenerator&lt;/code&gt; writes a bounded client that compiles against the published &lt;code dir=&quot;auto&quot;&gt;@cratis/arc&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;@cratis/arc.react&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-the-clients-differ&quot;&gt;Where the clients differ&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;These are the &lt;a href=&quot;https://cratis.io/chronicle/clients/where-the-clients-differ/&quot;&gt;differences between the clients&lt;/a&gt; that change what your code has to do:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;.NET&lt;/th&gt;
&lt;th&gt;Kotlin and Java&lt;/th&gt;
&lt;th&gt;TypeScript&lt;/th&gt;
&lt;th&gt;Elixir&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Concurrency on append, by default&lt;/td&gt;
&lt;td&gt;Reads the tail, sends the expected sequence number&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;ConcurrencyScope.none&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No check&lt;/td&gt;
&lt;td&gt;No check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expect no matching event&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;ExpectingNoMatchingEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;withExpectsNoMatchingEvent()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;EventSequenceNumber.beforeFirst&lt;/code&gt; as the scope’s sequence number&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What an append returns&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;AppendResult&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;AppendResult&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;AppendResult&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;:ok&lt;/code&gt; or an error tuple&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subject for personal data&lt;/td&gt;
&lt;td&gt;Resolves &lt;code dir=&quot;auto&quot;&gt;[Subject]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The event source id&lt;/td&gt;
&lt;td&gt;The event source id&lt;/td&gt;
&lt;td&gt;The event source id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;AllowNewEncryptionKeyFor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reactors and replay&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;[OnceOnly]&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;[Replay]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;@OnceOnly&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;@Replay&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;@onceOnly()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;@replay()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Replayable, no once-only marker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delivery identity for reactors&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;ReactorDelivery.Id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Build your own key&lt;/td&gt;
&lt;td&gt;Build your own key&lt;/td&gt;
&lt;td&gt;Build your own key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;An append that depends on an earlier read needs an explicit scope on every client. The .NET default only catches an append that lands between its own read of the tail and its write, and the first append into an empty scope isn’t checked unless you opt in. Outside .NET, an append without a scope isn’t checked at all. The subject row matters for &lt;a href=&quot;https://cratis.io/chronicle/concepts/subject/&quot;&gt;personal data&lt;/a&gt;: outside .NET, pass the subject explicitly whenever it isn’t the event source. &lt;a href=&quot;https://blog.cratis.io/personal-data-in-an-event-log/&quot;&gt;&lt;em&gt;Personal data in an event log&lt;/em&gt;&lt;/a&gt; explains what the subject decides. A reactor that needs a stable key for its side effects has one on .NET, and elsewhere you build it from the event store, the namespace, the sequence, the reactor and the position, as &lt;a href=&quot;https://blog.cratis.io/reacting-to-facts/&quot;&gt;&lt;em&gt;Reacting to facts&lt;/em&gt;&lt;/a&gt; describes. Seeding behaves differently per client as well, in what it targets and how it handles duplicates.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/same-fact-four-languages.COS5jcsu_ZAKPXS.webp&quot; alt=&quot;A diagram titled The same fact in four languages, four cards in a two-by-two grid: C#, Kotlin, TypeScript and Elixir. Each card shows the event-type declaration: [EventType] on a record AuthorRegistered in C#, @EventType on a data class TaskCreated in Kotlin, @eventType() on a class OrderPlaced in TypeScript, and use Chronicle.Events.EventType with the label defstruct with typed defaults in Elixir. Under each card a chip names the default concurrency on append: optimistic tail check for C#, none for Kotlin, TypeScript and Elixir. A band along the bottom reads: Concurrency on append, by default.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2200&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;One kernel contract, four client defaults.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;storage-is-the-servers-choice&quot;&gt;Storage is the server’s choice&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Chronicle Server’s &lt;a href=&quot;https://cratis.io/chronicle/hosting/configuration/storage/&quot;&gt;storage provider&lt;/a&gt; is &lt;code dir=&quot;auto&quot;&gt;MongoDB&lt;/code&gt; by default. The others are &lt;code dir=&quot;auto&quot;&gt;PostgreSql&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;MsSql&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Sqlite&lt;/code&gt;, which is embedded and file-based, and &lt;code dir=&quot;auto&quot;&gt;InMemory&lt;/code&gt;, which is ephemeral and non-durable. The provider is a server setting, and no client, in any language, sees which one it is.&lt;/p&gt;
&lt;p&gt;The client chooses the read-model sink, not the server’s provider. Every client registers read models against the MongoDB sink unless you set the default sink type, and the sinks don’t behave the same. On the SQL sink each read model gets a table, with a JSON column for collections, and &lt;code dir=&quot;auto&quot;&gt;[Index]&lt;/code&gt; creates no index there. Only MongoDB creates them. A live Chronicle read model in the UI is documented on MongoDB, where Arc watches it through change streams. Arc’s EF Core observation sees writes from other processes only on PostgreSQL and SQL Server, with provider setup, and not on SQLite.&lt;/p&gt;
&lt;p&gt;On .NET, Arc’s queries can read from MongoDB, or from a relational database through EF Core. Arc’s guide to adding event sourcing follows the MongoDB branch, where a Chronicle projection fills the collection an unchanged query already reads, and doesn’t cover projecting into the EF Core branch’s tables.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/one-kernel-four-clients.DQ0_r1U7_9Lt8d.webp&quot; alt=&quot;A diagram titled One kernel, four clients. Four client boxes, each with its registry, connect to a Chronicle kernel box: .NET from NuGet, Kotlin and Java as io.cratis on Maven Central, TypeScript as @cratis/chronicle on npm, and Elixir as cratis_chronicle on Hex. The kernel box lists observers, constraints and storage, with a gRPC  port chip. An arrow from the kernel leads to a storage box, marked as a server setting, which lists MongoDB as the default, PostgreSQL, SQL Server and SQLite. A band along the bottom reads: One contract. The client APIs are not interchangeable.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Four clients, one kernel, storage chosen by the server.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;starting-on-the-jvm&quot;&gt;Starting on the JVM&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The &lt;a href=&quot;https://cratis.io/templates/&quot;&gt;templates&lt;/a&gt; create JVM applications too, with &lt;code dir=&quot;auto&quot;&gt;dotnet new cratis --language Kotlin&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;--language Java&lt;/code&gt;. &lt;a href=&quot;https://cratis.io/cli/new/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;cratis new&lt;/code&gt;&lt;/a&gt; does the same without the .NET SDK. Either way the JVM path needs JDK 17.&lt;/p&gt;
&lt;p&gt;In a clone of the Arc for Kotlin repository, &lt;code dir=&quot;auto&quot;&gt;./Samples/run.sh&lt;/code&gt; starts the Kotlin sample with in-memory storage and the React frontend on port 5173. &lt;code dir=&quot;auto&quot;&gt;--language java&lt;/code&gt; switches to Java, &lt;code dir=&quot;auto&quot;&gt;--database mongodb&lt;/code&gt; to MongoDB, and &lt;code dir=&quot;auto&quot;&gt;--chronicle&lt;/code&gt; starts a development kernel pinned by digest. The Chronicle sample runs backend-only. There are four samples, Kotlin and Java, each with and without Chronicle, and they’re applications you run from source.&lt;/p&gt;
&lt;p&gt;The author feature carries over. On Spring Boot, &lt;code dir=&quot;auto&quot;&gt;RegisterAuthor&lt;/code&gt; is a &lt;code dir=&quot;auto&quot;&gt;@Command&lt;/code&gt; that returns an &lt;code dir=&quot;auto&quot;&gt;@EventType&lt;/code&gt; event, keyed by a &lt;code dir=&quot;auto&quot;&gt;@CommandKey&lt;/code&gt;, and the generated TypeScript feeds the same React form. What doesn’t carry over is the defaults. Set a concurrency scope wherever a decision depends on something you read, and pass the subject when it isn’t the event source. Every client, .NET included, skips certificate validation by default, allowing the development image’s self-signed certificate. Turn it on (&lt;code dir=&quot;auto&quot;&gt;skipTlsValidation=false&lt;/code&gt;) before connecting to anything shared, following &lt;a href=&quot;https://blog.cratis.io/running-chronicle-in-production/&quot;&gt;&lt;em&gt;Running Chronicle in production&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href=&quot;https://blog.cratis.io/testing-with-arc-and-chronicle/&quot;&gt;Part 11 — Testing with Arc and Chronicle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next: &lt;a href=&quot;https://blog.cratis.io/personal-data-in-an-event-log/&quot;&gt;Part 13 — Personal data in an event log&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>arc</category><category>chronicle</category><category>kotlin</category><category>typescript</category><category>elixir</category></item><item><title>From script to stage · Part 11: Testing with Arc and Chronicle</title><link>https://blog.cratis.io/testing-with-arc-and-chronicle/</link><guid isPermaLink="true">https://blog.cratis.io/testing-with-arc-and-chronicle/</guid><description>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.</description><pubDate>Wed, 30 Sep 2026 09:11:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt; · Part 11 of 26&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A spec for “a second author with the same name is rejected” can pass without the first author ever reaching the event log. On an Arc &lt;code dir=&quot;auto&quot;&gt;CommandScenario&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Given&lt;/code&gt; seeds an in-memory read model, and nothing it seeds is appended to the log that Chronicle checks a &lt;code dir=&quot;auto&quot;&gt;[Unique]&lt;/code&gt; constraint against. The spec sits in the right folder and carries the right name, and the constraint it’s named after is never exercised.&lt;/p&gt;
&lt;p&gt;That’s one of a handful of ways a spec in an event-sourced application can go green and prove nothing, and they all come from losing track of what a given kind of test actually runs. Cratis offers several kinds, from calling a method to running a real Chronicle in Docker, and for most rules the cheapest one is enough.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;start-with-the-function&quot;&gt;Start with the function&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The decision in &lt;code dir=&quot;auto&quot;&gt;RegisterAuthor&lt;/code&gt; is a method. Call &lt;code dir=&quot;auto&quot;&gt;Handle()&lt;/code&gt;, compare the event it returns, and the rule is tested without starting a server or faking an event log. &lt;a href=&quot;https://cratis.io/arc/backend/csharp/testing/&quot;&gt;Arc’s testing guide&lt;/a&gt; makes that direct call the first choice for a deterministic decision:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-0&quot; id=&quot;tab-0-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-1&quot; id=&quot;tab-0-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-2&quot; id=&quot;tab-0-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-3&quot; id=&quot;tab-0-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-4&quot; id=&quot;tab-0-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-0&quot; aria-labelledby=&quot;tab-0-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; DEBUG&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;when_deciding_to_register_an_author&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Specification&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt; _name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Jane Austen&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt; _event &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Because&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_event&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;New&lt;/span&gt;&lt;span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;_name&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Handle&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Fact&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;should_record_the_name&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_event&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Name&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ShouldEqual&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_name&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-1&quot; aria-labelledby=&quot;tab-0-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;This spec uses JUnit; Cratis Specifications is a .NET library.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RegisterAuthorTest&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Jane Austen&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;@Test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;`records the name`&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; event &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;(AuthorId.&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(), name).&lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;assertEquals&lt;/span&gt;&lt;span&gt;(name, event.name)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-2&quot; aria-labelledby=&quot;tab-0-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;This spec uses JUnit; Cratis Specifications is a .NET library.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RegisterAuthorTest&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;final&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Jane Austen&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;recordsTheName&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;newId&lt;/span&gt;&lt;span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;assertEquals&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-3&quot; aria-labelledby=&quot;tab-0-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;This spec uses Vitest.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;describe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;when deciding to register an author&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Jane Austen&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;beforeEach&lt;/span&gt;&lt;span&gt;&lt;span&gt;(()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Object&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;assign&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;should record the name&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;should&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;equal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-4&quot; aria-labelledby=&quot;tab-0-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Arc for Elixir doesn’t exist; use the Chronicle Elixir client directly.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;The C# spec is written with &lt;a href=&quot;https://cratis.io/specifications/&quot;&gt;Cratis Specifications&lt;/a&gt;, where &lt;code dir=&quot;auto&quot;&gt;Because()&lt;/code&gt; is the “when” and each &lt;code dir=&quot;auto&quot;&gt;[Fact]&lt;/code&gt; is a “then”. It needs no container and no fake.&lt;/p&gt;
&lt;p&gt;An event is a record, so expecting one is an equality check on a value. The event doesn’t even repeat its own event source ID, because Chronicle keeps that in the event context, so the value &lt;code dir=&quot;auto&quot;&gt;Handle()&lt;/code&gt; returns is all there is to compare. The &lt;a href=&quot;https://cratis.io/arc/backend/csharp/testing/event-sourced-commands/&quot;&gt;event-sourced commands lesson&lt;/a&gt; puts every branch of a decision in direct specs like this one.&lt;/p&gt;
&lt;p&gt;Arc also splits getting data from deciding with it. &lt;code dir=&quot;auto&quot;&gt;Provide()&lt;/code&gt; fetches what a command needs, and &lt;code dir=&quot;auto&quot;&gt;Handle()&lt;/code&gt; decides with what it was given, so &lt;code dir=&quot;auto&quot;&gt;Handle&lt;/code&gt; can be a pure function of its arguments. Arc’s loan example gets a credit score in &lt;code dir=&quot;auto&quot;&gt;Provide()&lt;/code&gt; and rejects an applicant with no credit history, as in &lt;a href=&quot;https://blog.cratis.io/validation-all-the-way-down/&quot;&gt;&lt;em&gt;Validation all the way down&lt;/em&gt;&lt;/a&gt;. In each language below, the direct spec supplies the score to &lt;code dir=&quot;auto&quot;&gt;Handle()&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;handle()&lt;/code&gt; and doesn’t exercise that rejection:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-0&quot; id=&quot;tab-1-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-1&quot; id=&quot;tab-1-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-2&quot; id=&quot;tab-1-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-3&quot; id=&quot;tab-1-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-4&quot; id=&quot;tab-1-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-0&quot; aria-labelledby=&quot;tab-1-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Command&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AssessLoan&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;LoanId&lt;/span&gt;&lt;span&gt;&lt;span&gt; LoanId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ApplicantId&lt;/span&gt;&lt;span&gt;&lt;span&gt; Applicant&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Result&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CreditScore&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ValidationResult&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Provide&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ICreditBureau&lt;/span&gt;&lt;span&gt;&lt;span&gt; bureau&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; score &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bureau&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GetScore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Applicant&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;score&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;is&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ValidationResult&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;No credit history&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;applicant&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;score&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoanAssessment&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CreditScore&lt;/span&gt;&lt;span&gt;&lt;span&gt; creditScore&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;LoanId&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;creditScore&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; DEBUG&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;when_assessing_a_loan&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Specification&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoanId&lt;/span&gt;&lt;span&gt; _loanId &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoanId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;New&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;LoanAssessment&lt;/span&gt;&lt;span&gt; _assessment &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Because&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_assessment&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AssessLoan&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_loanId&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ApplicantId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;New&lt;/span&gt;&lt;span&gt;&lt;span&gt;())&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreditScore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;800&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Fact&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;should_return_the_assessment&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;_assessment&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ShouldEqual&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoanAssessment&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_loanId&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreditScore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;800&lt;/span&gt;&lt;span&gt;)));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-1&quot; aria-labelledby=&quot;tab-1-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@Command&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AssessLoan&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; loanId: &lt;/span&gt;&lt;span&gt;LoanId&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; applicant: &lt;/span&gt;&lt;span&gt;ApplicantId&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;suspend&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;provide&lt;/span&gt;&lt;span&gt;(bureau: &lt;/span&gt;&lt;span&gt;CreditBureau&lt;/span&gt;&lt;span&gt;): &lt;/span&gt;&lt;span&gt;Any&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bureau.&lt;/span&gt;&lt;span&gt;score&lt;/span&gt;&lt;span&gt;(applicant)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;?: ValidationResult.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;No credit history&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;listOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;applicant&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;(creditScore: &lt;/span&gt;&lt;span&gt;CreditScore&lt;/span&gt;&lt;span&gt;): &lt;/span&gt;&lt;span&gt;LoanAssessment&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoanAssessment&lt;/span&gt;&lt;span&gt;(loanId, creditScore)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AssessLoanTest&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;@Test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;`handle returns the assessment`&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; loanId &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoanId&lt;/span&gt;&lt;span&gt;(UUID.&lt;/span&gt;&lt;span&gt;randomUUID&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; command &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AssessLoan&lt;/span&gt;&lt;span&gt;(loanId, &lt;/span&gt;&lt;span&gt;ApplicantId&lt;/span&gt;&lt;span&gt;(UUID.&lt;/span&gt;&lt;span&gt;randomUUID&lt;/span&gt;&lt;span&gt;()))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; assessment &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; command.&lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CreditScore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;800&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;assertEquals&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;LoanAssessment&lt;/span&gt;&lt;span&gt;(loanId, &lt;/span&gt;&lt;span&gt;CreditScore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;800&lt;/span&gt;&lt;span&gt;)), assessment)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-2&quot; aria-labelledby=&quot;tab-1-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Command&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AssessLoan&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;LoanId&lt;/span&gt;&lt;span&gt;&lt;span&gt; loanId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ApplicantId&lt;/span&gt;&lt;span&gt;&lt;span&gt; applicant&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Object&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;provide&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CreditBureau&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bureau&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;score&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bureau&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;score&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;applicant&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; score &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ValidationResult&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;No credit history&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;of&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;applicant&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; score&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoanAssessment&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CreditScore&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;creditScore&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoanAssessment&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;loanId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; creditScore&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AssessLoanTest&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;handleReturnsTheAssessment&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;loanId&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoanId&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;UUID&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;randomUUID&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AssessLoan&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;loanId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ApplicantId&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;UUID&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;randomUUID&lt;/span&gt;&lt;span&gt;()));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;assessment&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreditScore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;800&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;assertEquals&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoanAssessment&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;loanId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreditScore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;800&lt;/span&gt;&lt;span&gt;&lt;span&gt;)),&lt;/span&gt;&lt;span&gt; assessment&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-3&quot; aria-labelledby=&quot;tab-1-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;From Arc for TypeScript, an early source preview whose packages aren’t on npm yet:&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AssessLoan&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;LoanId&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;loanId&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoanId&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ApplicantId&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;applicant&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ApplicantId&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// provide() takes no parameters, so it resolves its services from the execution scope.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;provide&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CreditScore&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Outcome&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;never&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bureau&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;currentServices&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;resolve&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CreditBureau&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;score&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bureau&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;findScore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;applicant&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;score&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rejected&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;validation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;No credit history&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;applicant&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;]));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;creditScore&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreditScore&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoanAssessment&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoanAssessment&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;loanId&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;creditScore&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;describe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;when assessing a loan&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;should return the assessment&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AssessLoan&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;loanId&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoanId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;applicant&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ApplicantId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreditScore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;800&lt;/span&gt;&lt;span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;should&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;deep&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;equal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoanAssessment&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;loanId&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreditScore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;800&lt;/span&gt;&lt;span&gt;)));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-4&quot; aria-labelledby=&quot;tab-1-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Arc for Elixir doesn’t exist; use the Chronicle Elixir client directly.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;The &lt;a href=&quot;https://cratis.io/arc/backend/csharp/testing/command-decisions/&quot;&gt;command decisions lesson&lt;/a&gt; tests one command both ways, once directly and once through the pipeline. A &lt;code dir=&quot;auto&quot;&gt;Handle()&lt;/code&gt; that calls a service or reads the clock is still testable, but it isn’t a pure function just because it lives on a command.&lt;/p&gt;
&lt;p&gt;Reducers work the same way. A reducer takes the event, the current state and the event context, and returns the next state. In .NET, the &lt;a href=&quot;https://github.com/Cratis/Samples/tree/3c1ee46a4b8e86bac9623fcf9faace208a9551d9/Chronicle/Processing&quot;&gt;Processing sample&lt;/a&gt; tests its progress reducer by folding four events by hand, trimmed here to the action:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-0&quot; id=&quot;tab-2-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-1&quot; id=&quot;tab-2-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-2&quot; id=&quot;tab-2-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-3&quot; id=&quot;tab-2-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-2-4&quot; id=&quot;tab-2-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-0&quot; aria-labelledby=&quot;tab-2-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; reducer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkItemProgressReducer&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;_result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reducer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Opened&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkItemOpened&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Prepare release&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;&lt;span&gt;),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ContextFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;_result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reducer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Recorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ProgressRecorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;&lt;span&gt;),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;_result&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ContextFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;_result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reducer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Recorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ProgressRecorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;&lt;span&gt;),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;_result&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ContextFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;_result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reducer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Recorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ProgressRecorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;&lt;span&gt;),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;_result&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ContextFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// then: _result.ShouldEqual(new WorkItemProgress(_workItemId, 10, 10, WorkPoints.NotSet, new EventSequenceNumber(3)));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-1&quot; aria-labelledby=&quot;tab-2-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;The same fold against a Kotlin reducer, which takes the event, the current state and the event context.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; reducer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkItemProgressReducer&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; reducer.&lt;/span&gt;&lt;span&gt;opened&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;WorkItemOpened&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;WorkItemTitle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Prepare release&quot;&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;WorkPoints&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)), &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;contextFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; reducer.&lt;/span&gt;&lt;span&gt;recorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ProgressRecorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;WorkPoints&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;)), result, &lt;/span&gt;&lt;span&gt;contextFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; reducer.&lt;/span&gt;&lt;span&gt;recorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ProgressRecorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;WorkPoints&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;)), result, &lt;/span&gt;&lt;span&gt;contextFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; reducer.&lt;/span&gt;&lt;span&gt;recorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ProgressRecorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;WorkPoints&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;)), result, &lt;/span&gt;&lt;span&gt;contextFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// then: assertEquals(WorkItemProgress(workItemId, WorkPoints(10), WorkPoints(10), WorkPoints.NOT_SET, 3), result)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-2&quot; aria-labelledby=&quot;tab-2-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;The same fold against a Java reducer class.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reducer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkItemProgressReducer&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reducer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;opened&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkItemOpened&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkItemTitle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Prepare release&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkPoints&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;&lt;span&gt;)),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;contextFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reducer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;recorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ProgressRecorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkPoints&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;&lt;span&gt;)),&lt;/span&gt;&lt;span&gt; result&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;contextFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reducer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;recorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ProgressRecorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkPoints&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;&lt;span&gt;)),&lt;/span&gt;&lt;span&gt; result&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;contextFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reducer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;recorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ProgressRecorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkPoints&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;&lt;span&gt;)),&lt;/span&gt;&lt;span&gt; result&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;contextFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// then: assertEquals(new WorkItemProgress(workItemId, new WorkPoints(10), new WorkPoints(10), WorkPoints.NOT_SET, 3), result);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-3&quot; aria-labelledby=&quot;tab-2-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;The same fold against a TypeScript reducer, whose method is named for its event class in camelCase.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reducer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkItemProgressReducer&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reducer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;workItemOpened&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkItemOpened&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkItemTitle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Prepare release&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkPoints&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;&lt;span&gt;)),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;contextFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reducer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;progressRecorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ProgressRecorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkPoints&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;&lt;span&gt;)),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;contextFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reducer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;progressRecorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ProgressRecorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkPoints&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;&lt;span&gt;)),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;contextFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reducer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;progressRecorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ProgressRecorded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkPoints&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;&lt;span&gt;)),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;contextFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// then: result.should.deep.equal(new WorkItemProgress(workItemId, new WorkPoints(10), new WorkPoints(10), WorkPoints.notSet, 3))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-2-4&quot; aria-labelledby=&quot;tab-2-4&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;The same fold through the &lt;code dir=&quot;auto&quot;&gt;reduce/3&lt;/code&gt; callback of an Elixir reducer.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkItemProgressReducer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reduce&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;/span&gt;&lt;span&gt;WorkItemOpened&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Prepare release&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;points&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;&lt;span&gt;},&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;context_for&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkItemProgressReducer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reduce&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;/span&gt;&lt;span&gt;ProgressRecorded&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&lt;span&gt;points&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;&lt;span&gt;},&lt;/span&gt;&lt;span&gt; result&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;context_for&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkItemProgressReducer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reduce&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;/span&gt;&lt;span&gt;ProgressRecorded&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&lt;span&gt;points&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;&lt;span&gt;},&lt;/span&gt;&lt;span&gt; result&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;context_for&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WorkItemProgressReducer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reduce&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;/span&gt;&lt;span&gt;ProgressRecorded&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&lt;span&gt;points&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;&lt;span&gt;},&lt;/span&gt;&lt;span&gt; result&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;context_for&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# then: assert result == %WorkItemProgress{work_item_id: work_item_id, target: 10, recorded: 10, sequence_number: 3}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;In .NET, the same sample calls its reactor directly. &lt;code dir=&quot;auto&quot;&gt;new CompletionSummaryReactor().Completed(new WorkItemCompleted(8, 8))&lt;/code&gt; returns a &lt;code dir=&quot;auto&quot;&gt;CompletionSummarized&lt;/code&gt; event, with no event log and no command pipeline injected into the reactor. Chronicle’s &lt;a href=&quot;https://cratis.io/chronicle/testing/reactors/&quot;&gt;reactor testing guide&lt;/a&gt; makes that split the default: the logic goes into a function that returns its side effect as a value, and the framework contract gets its own scenario. The default holds for as long as your reactors return their side effects instead of performing them.&lt;/p&gt;
&lt;p&gt;Projections are the exception. A projection is attributes or a fluent definition, and there’s no method to call. The cheapest honest test of a projection runs the real projection engine, in memory.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;one-process-the-real-pipeline&quot;&gt;One process, the real pipeline&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A &lt;a href=&quot;https://cratis.io/arc/backend/csharp/testing/chronicle/&quot;&gt;spec&lt;/a&gt; for registering an author looks like this. Here &lt;code dir=&quot;auto&quot;&gt;RegisterAuthor&lt;/code&gt; takes the author’s id and name:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-0&quot; id=&quot;tab-3-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-1&quot; id=&quot;tab-3-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-2&quot; id=&quot;tab-3-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-3&quot; id=&quot;tab-3-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-3-4&quot; id=&quot;tab-3-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-0&quot; aria-labelledby=&quot;tab-3-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; DEBUG&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;when_registering_author&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Specification&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt; _authorId &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;New&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CommandScenario&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; _scenario &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;CommandResult&lt;/span&gt;&lt;span&gt; _result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default!&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Because&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;_result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_scenario&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Execute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_authorId&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Jane Austen&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Fact&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;should_succeed&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;_result&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ShouldBeSuccessful&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Fact&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;should_have_appended_registered_event&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_scenario&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ShouldHaveAppendedEvent&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;_authorId&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; e &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Jane Austen&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-1&quot; aria-labelledby=&quot;tab-3-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;In Arc for Kotlin and Java, &lt;code dir=&quot;auto&quot;&gt;CommandScenario&lt;/code&gt; from &lt;code dir=&quot;auto&quot;&gt;io.cratis:arc-testing&lt;/code&gt; runs the real command pipeline, and the Chronicle integration gives it an in-memory event log.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RegisterAuthorScenarioTest&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; authorId &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AuthorId.&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;@Test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;`registers the author`&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;runBlocking&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; scenario &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CommandScenario&lt;/span&gt;&lt;span&gt;(module, RegisterAuthor::&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;.java)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; scenario.&lt;/span&gt;&lt;span&gt;execute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;(authorId, &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Jane Austen&quot;&lt;/span&gt;&lt;span&gt;)))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;result.&lt;/span&gt;&lt;span&gt;shouldSucceed&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;scenario.&lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;shouldHaveAppendedEvent&lt;/span&gt;&lt;span&gt;(authorId.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;(), AuthorRegistered::&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;.java)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-2&quot; aria-labelledby=&quot;tab-3-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;In Arc for Kotlin and Java, &lt;code dir=&quot;auto&quot;&gt;CommandScenario&lt;/code&gt; from &lt;code dir=&quot;auto&quot;&gt;io.cratis:arc-testing&lt;/code&gt; runs the real command pipeline, and the Chronicle integration gives it an in-memory event log.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RegisterAuthorScenarioTest&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;final&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;authorId&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;newId&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;registersTheAuthor&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;CommandScenario&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;configured&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CommandScenario&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&gt;(&lt;/span&gt;&lt;span&gt;module&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;ChronicleCommandScenario&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChronicleCommandScenarios&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;configured&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;BlockingCommandScenario&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;scenario&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BlockingCommandScenario&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&gt;(&lt;/span&gt;&lt;span&gt;configured&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;scenario&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;execute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;authorId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Jane Austen&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;)))&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;shouldSucceed&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;shouldHaveAppendedEvent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;authorId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-3&quot; aria-labelledby=&quot;tab-3-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;In Arc for TypeScript, &lt;code dir=&quot;auto&quot;&gt;ChronicleCommandScenario&lt;/code&gt; from &lt;code dir=&quot;auto&quot;&gt;@cratis/arc.chronicle/testing&lt;/code&gt; runs the real command pipeline over an in-memory event log.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;describe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;when registering author&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;authorId&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scenario&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChronicleCommandScenario&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Awaited&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;ReturnType&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scenario&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;execute&lt;/span&gt;&lt;span&gt;&gt;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;beforeEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;scenario&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChronicleCommandScenario&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scenario&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;execute&lt;/span&gt;&lt;span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;authorId&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Jane Austen&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;afterEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scenario&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;dispose&lt;/span&gt;&lt;span&gt;&lt;span&gt;();&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;should succeed&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;shouldBeSuccessful&lt;/span&gt;&lt;span&gt;&lt;span&gt;();&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;should have appended registered event&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;shouldHaveAppendedEvent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;authorId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-3-4&quot; aria-labelledby=&quot;tab-3-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Arc for Elixir doesn’t exist; use the Chronicle Elixir client directly.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;It runs Arc’s &lt;a href=&quot;https://cratis.io/arc/backend/csharp/commands/command-pipeline/&quot;&gt;command pipeline&lt;/a&gt; and an in-memory Chronicle event log in one process, without a Chronicle server, and checks the decision and the recorded fact. HTTP routing, the auth middleware, a real read-model store and redelivery need a different kind of test. In .NET, &lt;a href=&quot;https://cratis.io/specifications/&quot;&gt;Specifications&lt;/a&gt; supplies the given/when/then base on xUnit or NUnit. Synopsis can turn recognized specification shapes into navigable HTML documentation, though it doesn’t run them.&lt;/p&gt;
&lt;p&gt;In .NET, &lt;a href=&quot;https://cratis.io/arc/backend/csharp/testing/command-scenario/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;CommandScenario&amp;#x3C;TCommand&gt;&lt;/code&gt;&lt;/a&gt; runs authorization, the validators, &lt;code dir=&quot;auto&quot;&gt;Provide()&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Handle()&lt;/code&gt;, and builds the command result, which is the path a request takes once routing has found the command. &lt;a href=&quot;https://cratis.io/arc/backend/csharp/testing/query-scenario/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;QueryScenario&amp;#x3C;TReadModel&gt;&lt;/code&gt;&lt;/a&gt; does the same for a static query on a read model. With &lt;code dir=&quot;auto&quot;&gt;Cratis.Arc.Chronicle.Testing&lt;/code&gt; referenced, a command scenario gains &lt;code dir=&quot;auto&quot;&gt;Given&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;EventLog&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;AppendedEvents&lt;/code&gt;, and that’s where &lt;code dir=&quot;auto&quot;&gt;ShouldHaveAppendedEvent&lt;/code&gt; comes from. Arc for Kotlin and Java have a &lt;code dir=&quot;auto&quot;&gt;CommandScenario&lt;/code&gt; and a &lt;code dir=&quot;auto&quot;&gt;QueryScenario&lt;/code&gt; in &lt;code dir=&quot;auto&quot;&gt;io.cratis:arc-testing&lt;/code&gt;, and the source preview of Arc for TypeScript has a &lt;code dir=&quot;auto&quot;&gt;CommandScenario&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A direct spec and a scenario spec answer different questions about the same command. The direct one says the decision is right for the inputs you chose. The scenario says the command still reaches that decision when authorization, validation and &lt;code dir=&quot;auto&quot;&gt;Provide()&lt;/code&gt; run in front of it, and that the event it returned was appended.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;chronicles-own-scenarios&quot;&gt;Chronicle’s own scenarios&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;In .NET, Chronicle’s &lt;a href=&quot;https://cratis.io/chronicle/testing/&quot;&gt;testing package&lt;/a&gt;, &lt;code dir=&quot;auto&quot;&gt;Cratis.Chronicle.Testing&lt;/code&gt;, runs the real projection and reducer engines, constraint evaluation and reactor dispatch over in-memory storage. Three scenarios divide the work:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Runs&lt;/th&gt;
&lt;th&gt;Doesn’t reach&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;EventScenario&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Appends, constraints and sequencing&lt;/td&gt;
&lt;td&gt;Network serialization, the kernel connection, storage writes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;ReadModelScenario&amp;#x3C;T&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A projection or reducer building state from events&lt;/td&gt;
&lt;td&gt;Sink writes, MongoDB queries, change streams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;ReactorScenario&amp;#x3C;T&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dispatch, dependency injection, read-model materialization, routing of side effects&lt;/td&gt;
&lt;td&gt;Observer registration, kernel dispatch, redelivery&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In .NET, the &lt;code dir=&quot;auto&quot;&gt;Author&lt;/code&gt; read model from the hub declares its projection with &lt;code dir=&quot;auto&quot;&gt;[FromEvent&amp;#x3C;AuthorRegistered&gt;]&lt;/code&gt;, and a &lt;a href=&quot;https://cratis.io/chronicle/testing/read-models/scenario/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;ReadModelScenario&lt;/code&gt;&lt;/a&gt; picks up that model-bound form on its own, as it does a fluent projection or a reducer:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-0&quot; id=&quot;tab-4-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-1&quot; id=&quot;tab-4-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-2&quot; id=&quot;tab-4-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-3&quot; id=&quot;tab-4-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-4-4&quot; id=&quot;tab-4-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-0&quot; aria-labelledby=&quot;tab-4-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; DEBUG&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;when_an_author_is_registered&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Specification&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt; _authorId &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;New&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt; _name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Jane Austen&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ReadModelScenario&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Author&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; _scenario &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Because&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_scenario&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Given&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ForEventSource&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_authorId&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_name&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Fact&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;should_have_the_name&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_scenario&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Instance&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Name&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ShouldEqual&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_name&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-1&quot; aria-labelledby=&quot;tab-4-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;The Kotlin client’s &lt;code dir=&quot;auto&quot;&gt;ReadModelScenario&lt;/code&gt; folds a reducer.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorReducerTest&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; authorId &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AuthorId.&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Jane Austen&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;@Test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;`the author has the name`&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;runBlocking&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; scenario &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ReadModelScenario&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Author&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;AuthorReducer&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; author &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; scenario.&lt;/span&gt;&lt;span&gt;fold&lt;/span&gt;&lt;span&gt;(authorId.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;(), &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;(name))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;assertEquals&lt;/span&gt;&lt;span&gt;(name, author&lt;/span&gt;&lt;span&gt;!!&lt;/span&gt;&lt;span&gt;.name)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-2&quot; aria-labelledby=&quot;tab-4-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;The Java client calls the reducer method directly.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorReducerTest&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;final&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Jane Austen&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;theAuthorHasTheName&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reducer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorReducer&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reducer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;registered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;assertEquals&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-3&quot; aria-labelledby=&quot;tab-4-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;The TypeScript client’s &lt;code dir=&quot;auto&quot;&gt;ReadModelScenario&lt;/code&gt; evaluates a &lt;code dir=&quot;auto&quot;&gt;@fromEvent&lt;/code&gt; model-bound projection. It refuses protected fields, including fields whose concept is marked &lt;code dir=&quot;auto&quot;&gt;@pii()&lt;/code&gt;. The series’ &lt;code dir=&quot;auto&quot;&gt;AuthorName&lt;/code&gt; keeps that marking and needs a kernel-backed test. This spec uses separate, unprotected test fixtures and checks only projection behavior, not personal-data handling.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TestAuthorName&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ConceptAs&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;valueType&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;eventType&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TestAuthorRegistered&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TestAuthorName&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;/span&gt;&lt;span&gt;unique&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;UniqueAuthorName&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TestAuthorName&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TestAuthorName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;fromEvent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TestAuthorRegistered&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TestAuthor&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TestAuthorName&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TestAuthorName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;describe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;when an author is registered&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;authorId&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TestAuthorName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Jane Austen&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scenario&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ReadModelScenario&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TestAuthor&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;beforeEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scenario&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;given&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;forEventSource&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;authorId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;&lt;span&gt;())&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;events&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TestAuthorRegistered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;should have the name&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scenario&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;instance&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;should&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;deep&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;equal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-4-4&quot; aria-labelledby=&quot;tab-4-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Not available in the Elixir client yet.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;This is the uniqueness rule’s natural home too. In .NET, an &lt;code dir=&quot;auto&quot;&gt;EventScenario&lt;/code&gt; appends to its in-memory event log, so a first registration seeded through its &lt;code dir=&quot;auto&quot;&gt;Given&lt;/code&gt; is in the log the constraint is checked against, and a second append with the same name is rejected there. Assert the violation by the constraint’s name with &lt;code dir=&quot;auto&quot;&gt;ShouldHaveConstraintViolationFor&lt;/code&gt;:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-5-0&quot; id=&quot;tab-5-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-5-1&quot; id=&quot;tab-5-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-5-2&quot; id=&quot;tab-5-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-5-3&quot; id=&quot;tab-5-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-5-4&quot; id=&quot;tab-5-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-5-0&quot; aria-labelledby=&quot;tab-5-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;property&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Unique&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;UniqueAuthorName&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;&lt;span&gt; Name&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; DEBUG&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;when_registering_a_taken_author_name&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Specification&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EventScenario&lt;/span&gt;&lt;span&gt; _scenario &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;AppendResult&lt;/span&gt;&lt;span&gt; _result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default!&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Establish&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;_scenario&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Given&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ForEventSource&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;New&lt;/span&gt;&lt;span&gt;&lt;span&gt;())&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Jane Austen&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Because&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;_result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_scenario&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;When&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ForEventSource&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;New&lt;/span&gt;&lt;span&gt;&lt;span&gt;())&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Jane Austen&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Fact&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;should_be_rejected&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_result&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ShouldBeFailed&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Fact&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;should_report_the_constraint&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_result&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ShouldHaveConstraintViolationFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;UniqueAuthorName&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Destroy&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_scenario&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Dispose&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-5-1&quot; aria-labelledby=&quot;tab-5-1&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;The Kotlin client’s &lt;code dir=&quot;auto&quot;&gt;EventScenario&lt;/code&gt; doesn’t check constraints.&lt;/p&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-5-2&quot; aria-labelledby=&quot;tab-5-2&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;The Java client has no in-memory scenario that checks constraints.&lt;/p&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-5-3&quot; aria-labelledby=&quot;tab-5-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;This spec uses Vitest and the same &lt;code dir=&quot;auto&quot;&gt;TestAuthorName&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;TestAuthorRegistered&lt;/code&gt; fixtures. Since &lt;code dir=&quot;auto&quot;&gt;@cratis/chronicle&lt;/code&gt; 6.31.4, &lt;code dir=&quot;auto&quot;&gt;EventScenario&lt;/code&gt; accepts string and boolean concepts, comparing their serialized scalar values. Its content still has to fit the supported flat, unprotected string/boolean schema; numeric, &lt;code dir=&quot;auto&quot;&gt;Guid&lt;/code&gt;, date and nested object fields remain unsupported. It also refuses the series’ &lt;code dir=&quot;auto&quot;&gt;@pii()&lt;/code&gt;-marked &lt;code dir=&quot;auto&quot;&gt;AuthorName&lt;/code&gt;, so testing the real &lt;code dir=&quot;auto&quot;&gt;AuthorRegistered&lt;/code&gt; still needs a kernel. This fixture tests uniqueness without personal-data handling.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TestAuthorName&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ConceptAs&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;valueType&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;eventType&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TestAuthorRegistered&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TestAuthorName&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;/span&gt;&lt;span&gt;unique&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;UniqueAuthorName&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TestAuthorName&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TestAuthorName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;describe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;when registering a taken author name&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AppendResult&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;beforeEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scenario&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EventScenario&lt;/span&gt;&lt;span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt; artifacts&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; eventTypes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;TestAuthorRegistered&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scenario&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;given&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;forEventSource&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;&lt;span&gt;())&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;events&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TestAuthorRegistered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TestAuthorName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Jane Austen&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scenario&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;when&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;forEventSource&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;&lt;span&gt;())&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TestAuthorRegistered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TestAuthorName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Jane Austen&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;should be rejected&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;isSuccess&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;should&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;be&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;should report the constraint&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;constraintViolations&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;violation&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;violation&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;constraintId&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;should&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;contain&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;UniqueAuthorName&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-5-4&quot; aria-labelledby=&quot;tab-5-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Not available in the Elixir client yet.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;The same helper exists on a &lt;code dir=&quot;auto&quot;&gt;CommandResult&lt;/code&gt; and on an append result.&lt;/p&gt;
&lt;p&gt;In .NET, &lt;code dir=&quot;auto&quot;&gt;ReactorScenario&lt;/code&gt; delivers each event once. To check that a reactor is idempotent, deliver the same event twice yourself and look at what happened the second time.&lt;/p&gt;
&lt;p&gt;Outside .NET the scenarios are fewer. The Kotlin client has &lt;code dir=&quot;auto&quot;&gt;EventScenario&lt;/code&gt; and a &lt;code dir=&quot;auto&quot;&gt;ReadModelScenario&lt;/code&gt; for reducers. TypeScript has all three, with &lt;code dir=&quot;auto&quot;&gt;ReadModelScenario&lt;/code&gt; covering reducers and the projections it supports. Java tests appends and calls reducers directly, and Elixir has no scenarios. &lt;a href=&quot;https://blog.cratis.io/beyond-dotnet/&quot;&gt;&lt;em&gt;Beyond .NET&lt;/em&gt;&lt;/a&gt; covers the clients themselves.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;green-and-proving-nothing&quot;&gt;Green, and proving nothing&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;In-process scenarios are fast, so they’re trusted, and a few mistakes make them pass without testing anything. Three are easy to make with the author feature.&lt;/p&gt;
&lt;p&gt;The first is a discarded assertion. Chronicle’s &lt;code dir=&quot;auto&quot;&gt;ShouldHaveAppendedEvent&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;ShouldHaveTailSequenceNumber&lt;/code&gt; on an event sequence are genuinely asynchronous. In a &lt;code dir=&quot;auto&quot;&gt;void&lt;/code&gt; fact that doesn’t await them, a failure lands on a &lt;code dir=&quot;auto&quot;&gt;Task&lt;/code&gt; that nobody observes, and the spec passes whatever the code did. The compiler doesn’t warn about it outside an &lt;code dir=&quot;auto&quot;&gt;async&lt;/code&gt; method. Make the fact &lt;code dir=&quot;auto&quot;&gt;async Task&lt;/code&gt; and await every &lt;code dir=&quot;auto&quot;&gt;Should*&lt;/code&gt; call that returns a task. Chronicle’s analyzer, &lt;code dir=&quot;auto&quot;&gt;CHR0039&lt;/code&gt;, reports the ones you miss, for Chronicle’s and Arc’s testing packages alike.&lt;/p&gt;
&lt;p&gt;The second is a read model nobody seeded. When a validator, &lt;code dir=&quot;auto&quot;&gt;Provide()&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;Handle()&lt;/code&gt; asks for a read model that doesn’t exist for the command’s key, no rule runs at all, and the command is still rejected, with a single &lt;code dir=&quot;auto&quot;&gt;DependencyUnavailable&lt;/code&gt; validation result. &lt;code dir=&quot;auto&quot;&gt;ShouldNotBeSuccessful()&lt;/code&gt; passes, and it keeps passing after the rule the spec is named after has been deleted. &lt;code dir=&quot;auto&quot;&gt;ShouldHaveValidationErrors()&lt;/code&gt; refuses a result whose only errors carry that reason. An unhappy-path spec asserts both &lt;code dir=&quot;auto&quot;&gt;ShouldNotBeSuccessful()&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;ShouldHaveValidationErrors()&lt;/code&gt;, and a spec about authorization asserts &lt;code dir=&quot;auto&quot;&gt;ShouldNotBeAuthorized()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The third is the one from the opening. &lt;code dir=&quot;auto&quot;&gt;Given&lt;/code&gt; on a &lt;code dir=&quot;auto&quot;&gt;CommandScenario&lt;/code&gt; materializes read models. A precondition the constraint has to see goes through the scenario’s &lt;code dir=&quot;auto&quot;&gt;EventScenario.Given&lt;/code&gt;, which appends it to the in-memory log before the command runs.&lt;/p&gt;
&lt;p&gt;Two more are worth knowing. &lt;code dir=&quot;auto&quot;&gt;Validate(command)&lt;/code&gt; runs the command filters, and authorization comes before validation, so a validate-only spec on an &lt;code dir=&quot;auto&quot;&gt;[Authorize]&lt;/code&gt; command with no identity registered is denied before its rule runs. And an expected value computed in the spec with the same expression as the code under test agrees with it by construction. Write the literal.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/green-proving-nothing.BtOiOOkQ_SJmd.webp&quot; alt=&quot;A diagram titled Green, and proving nothing, three rows, each with a green Passes chip beside a card. A discarded assertion: a void fact calls ShouldHaveAppendedEvent without await; fix, async Task and await, and CHR0039 reports it. A read model nobody seeded: DependencyUnavailable rejects the command, so ShouldNotBeSuccessful passes with the rule deleted; fix, also assert ShouldHaveValidationErrors. A precondition not in the log: CommandScenario Given seeds a read model, so the Unique constraint is never checked; fix, seed via EventScenario.Given. A band along the bottom reads: None reached the rule it’s named after.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Three green specs, and what each one never reached.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;examples-in-the-model&quot;&gt;Examples in the model&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; slice in &lt;a href=&quot;https://cratis.io/screenplay/specifications/&quot;&gt;Screenplay&lt;/a&gt; can carry given/when/then examples of its own, written against the model. Adapted from the Screenplay docs’ example, with &lt;code dir=&quot;auto&quot;&gt;for&lt;/code&gt; naming the event source instead of a property on the event:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;specification RegisteringADraftInvoice&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;given CustomerRegistered&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;for &quot;3fa85f64-5717-4562-b3fc-2c963f66afa6&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name = &quot;Acme Corp&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;when RegisterInvoice&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;invoiceId  = &quot;9c858901-8a57-4791-81fe-4c455b099bc9&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;customerId = &quot;3fa85f64-5717-4562-b3fc-2c963f66afa6&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;then InvoiceRegistered&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;for &quot;9c858901-8a57-4791-81fe-4c455b099bc9&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;customerId = &quot;3fa85f64-5717-4562-b3fc-2c963f66afa6&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A &lt;code dir=&quot;auto&quot;&gt;then&lt;/code&gt; can also name a read model, a query result, an error or a denial. The experimental &lt;a href=&quot;https://github.com/Cratis/Stage&quot;&gt;Stage&lt;/a&gt; specification runner, a container job, compiles the &lt;code dir=&quot;auto&quot;&gt;.play&lt;/code&gt; files, checks each specification against the model and writes the outcome to &lt;code dir=&quot;auto&quot;&gt;results.json&lt;/code&gt;. With the default engine, nothing else starts: no Arc, no Chronicle, no database and no network. The opt-in semantic engine instead runs commands through Arc’s in-memory command pipeline, against an in-memory Chronicle event log. It shows that the example fits the model, and it says nothing about whether the C# behaves that way. Reactions aren’t executed at the model level either. &lt;a href=&quot;https://blog.cratis.io/screenplay-an-event-model-you-can-compile/&quot;&gt;&lt;em&gt;Screenplay&lt;/em&gt;&lt;/a&gt; and &lt;a href=&quot;https://blog.cratis.io/stage-and-scene-after-a-model-compiles/&quot;&gt;&lt;em&gt;Stage and Scene&lt;/em&gt;&lt;/a&gt; go into the model side.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-real-chronicle-and-a-running-host&quot;&gt;A real Chronicle and a running host&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Some behavior only exists with a real host. Routing, authentication middleware, real storage writes, change streams, redelivery and tenant routing from AuthProxy’s headers all sit outside one process with in-memory storage.&lt;/p&gt;
&lt;p&gt;For .NET, &lt;code dir=&quot;auto&quot;&gt;Cratis.Chronicle.XUnit.Integration&lt;/code&gt; has two fixtures. &lt;code dir=&quot;auto&quot;&gt;ChronicleInProcessFixture&lt;/code&gt; runs the kernel inside the test process with MongoDB in a container through Testcontainers, and &lt;code dir=&quot;auto&quot;&gt;ChronicleOutOfProcessFixture&lt;/code&gt; runs the kernel in a container of its own. Both need Docker. A real kernel processes asynchronously, so &lt;a href=&quot;https://cratis.io/chronicle/testing/event-append-collection/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;IEventAppendCollection&lt;/code&gt;&lt;/a&gt; collects appends as they happen, and &lt;code dir=&quot;auto&quot;&gt;WaitForCount&lt;/code&gt; waits for the number you expect.&lt;/p&gt;
&lt;p&gt;Arc has no packaged test host. A hosted spec is an HTTP client pointed at a running application, next to a Chronicle fixture that a project defines as an xUnit collection. The &lt;a href=&quot;https://github.com/Cratis/Samples/tree/3c1ee46a4b8e86bac9623fcf9faace208a9551d9/Library&quot;&gt;Library sample&lt;/a&gt; does this for the author feature. Its &lt;code dir=&quot;auto&quot;&gt;and_there_already_exists_one_with_same_name&lt;/code&gt; spec appends an existing author straight to the event log, posts a &lt;code dir=&quot;auto&quot;&gt;RegisterAuthor&lt;/code&gt; with the same name to &lt;code dir=&quot;auto&quot;&gt;/api/authors/register&lt;/code&gt;, and asserts that the command failed. It expects the application to be running on &lt;code dir=&quot;auto&quot;&gt;localhost:8080&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That spec covers the whole uniqueness rule end to end, and it costs Docker, a kernel shared across tests and a slower run.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-frontend&quot;&gt;The frontend&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The generated proxies aren’t something to test. They’re output, and a change in the backend shows up in them on the next build. What’s worth a spec on the frontend is the code you wrote around them.&lt;/p&gt;
&lt;p&gt;The convention Cratis recommends for application frontends mirrors the backend. Specs use Vitest, Sinon and Chai’s &lt;code dir=&quot;auto&quot;&gt;should&lt;/code&gt;, and files follow &lt;code dir=&quot;auto&quot;&gt;for_&amp;#x3C;Subject&gt;/when_&amp;#x3C;context&gt;/and_&amp;#x3C;extra&gt;.ts&lt;/code&gt;. View models are plain classes you can construct without React, pure helpers get tested first, and there are no snapshots.&lt;/p&gt;
&lt;p&gt;Test generated validation for the forms that depend on it, especially the omissions and dropped conditions in &lt;a href=&quot;https://blog.cratis.io/validation-all-the-way-down/&quot;&gt;&lt;em&gt;Validation all the way down&lt;/em&gt;&lt;/a&gt;. On the server side of TypeScript, Arc for TypeScript has a testing package, &lt;code dir=&quot;auto&quot;&gt;@cratis/arc.testing&lt;/code&gt;, and like the rest of Arc for TypeScript it’s a source preview.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;specs-that-read-as-sentences&quot;&gt;Specs that read as sentences&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Given, when and then map straight onto event sourcing. Given is the facts that already happened, or the read-model state that follows from them. When is the command, the append or the event under test. Then is the events that were appended, the command result, the read model or the side effect.&lt;/p&gt;
&lt;p&gt;Folder names carry the same structure. A spec at &lt;code dir=&quot;auto&quot;&gt;for_RegisterAuthor/when_registering/and_the_name_is_taken.cs&lt;/code&gt; reads as a sentence about the feature, and a folder of them reads as its behavior. The word &lt;code dir=&quot;auto&quot;&gt;when&lt;/code&gt; appears only in &lt;code dir=&quot;auto&quot;&gt;when_&lt;/code&gt; folder names, and each &lt;code dir=&quot;auto&quot;&gt;should_&lt;/code&gt; fact finishes the sentence. One behavior goes in each spec. Logging, trivial getters and plain delegation don’t get specs of their own.&lt;/p&gt;
&lt;p&gt;Specifications gives that shape to .NET on xUnit or NUnit, in the style of Machine.Specifications: &lt;code dir=&quot;auto&quot;&gt;Establish()&lt;/code&gt; is the given, &lt;code dir=&quot;auto&quot;&gt;Because()&lt;/code&gt; the when, the facts the then, and &lt;code dir=&quot;auto&quot;&gt;Destroy()&lt;/code&gt; cleans up. The methods are optional, can return &lt;code dir=&quot;auto&quot;&gt;void&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;Task&lt;/code&gt;, and run across the inheritance chain. None of it is required. Chronicle’s scenarios work with any test framework, and a plain xUnit test can call &lt;code dir=&quot;auto&quot;&gt;Handle()&lt;/code&gt; just as well.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-much-of-each&quot;&gt;How much of each&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/testing-ladder.BQIOt8N4_Z1zcYRC.webp&quot; alt=&quot;A diagram titled The testing ladder. Three rising steps from left to right, with cost along the bottom and confidence in the wiring up the side. Step 1, Call the function: Handle, a reducer, a reactor. Step 2, In-process scenario: CommandScenario, ReadModelScenario, EventScenario, ReactorScenario. Step 3, Integration fixture: a real Chronicle and a running host. A bracket over steps 1 and 2 reads: Most tests here. Beside the ladder at ground level, a separate card reads: Model specs, Screenplay given, when, then, run by Stage: the example fits the model.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Cheapest first. Most specs belong on the first two steps.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Start the author suite with direct specs for each branch of the &lt;code dir=&quot;auto&quot;&gt;RegisterAuthor&lt;/code&gt; decision. One &lt;code dir=&quot;auto&quot;&gt;CommandScenario&lt;/code&gt; spec shows the command still decides that way with validation and authorization in front of it and appends what it returned. A &lt;code dir=&quot;auto&quot;&gt;ReadModelScenario&lt;/code&gt; covers the &lt;code dir=&quot;auto&quot;&gt;Author&lt;/code&gt; projection, and an &lt;code dir=&quot;auto&quot;&gt;EventScenario&lt;/code&gt; covers the &lt;code dir=&quot;auto&quot;&gt;[Unique]&lt;/code&gt; constraint with the first author actually in the log. One hosted spec sends the same name twice through a running application.&lt;/p&gt;
&lt;p&gt;All but the last run in one process with nothing to start. The hosted spec is the only one that needs Docker, a kernel and a running application.&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href=&quot;https://blog.cratis.io/tenancy-and-identity-end-to-end/&quot;&gt;Part 10 — Tenancy and identity end to end&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next: &lt;a href=&quot;https://blog.cratis.io/beyond-dotnet/&quot;&gt;Part 12 — Beyond .NET&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>testing</category><category>arc</category><category>chronicle</category><category>specifications</category><category>event-sourcing</category></item><item><title>From script to stage · Part 10: Tenancy and identity end to end</title><link>https://blog.cratis.io/tenancy-and-identity-end-to-end/</link><guid isPermaLink="true">https://blog.cratis.io/tenancy-and-identity-end-to-end/</guid><description>AuthProxy signs the user in, picks a tenant and forwards it as Tenant-ID. Arc reads a header called x-cratis-tenant-id until you tell it otherwise, 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.</description><pubDate>Wed, 30 Sep 2026 09:10:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt; · Part 10 of 26&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;AuthProxy forwards the tenant it resolved in a header named &lt;code dir=&quot;auto&quot;&gt;Tenant-ID&lt;/code&gt;. Arc, left on its defaults, reads the tenant from a header named &lt;code dir=&quot;auto&quot;&gt;x-cratis-tenant-id&lt;/code&gt;. Put the two together without configuring either, and Arc ignores the tenant AuthProxy resolved. A request with no &lt;code dir=&quot;auto&quot;&gt;x-cratis-tenant-id&lt;/code&gt; header gets no tenant, and Arc’s Chronicle integration sends it to Chronicle’s &lt;code dir=&quot;auto&quot;&gt;Default&lt;/code&gt; namespace. Nothing fails, and every organization ends up in one event log. A request that carries &lt;code dir=&quot;auto&quot;&gt;x-cratis-tenant-id&lt;/code&gt; gets whatever tenant the caller put there, because AuthProxy passes that header through.&lt;/p&gt;
&lt;p&gt;The workaround is one line, &lt;code dir=&quot;auto&quot;&gt;options.UseHeaderTenancy(&quot;Tenant-ID&quot;)&lt;/code&gt;. The fix on the proxy side is tracked in the public issue &lt;a href=&quot;https://github.com/Cratis/AuthProxy/issues/157&quot;&gt;Cratis/AuthProxy#157&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Keeping author names unique within an organization rests on a tenant ID that travels from the edge to the database. Take a made-up week with the mismatch in place. Contoso tries to register Jane Austen and is told the name is taken, because Acme registered her on Monday and both organizations are writing to &lt;code dir=&quot;auto&quot;&gt;Default&lt;/code&gt;. On Friday, Acme’s admin asks why their author list shows names they’ve never seen. Every step behind that week is a default, and each one is sensible on its own. Follow one tenant, &lt;code dir=&quot;auto&quot;&gt;acme&lt;/code&gt;, from sign-in to storage, and past the edge a missing tenant turns into &lt;code dir=&quot;auto&quot;&gt;Default&lt;/code&gt; at each hop, without an error.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/four-hops.d5HNdluQ_Z1kornO.webp&quot; alt=&quot;A diagram titled Four hops of a tenant ID. Four boxes from top to bottom, each with an arrow to the next: AuthProxy resolves acme and forwards it as Tenant-ID; Arc’s default header is x-cratis-tenant-id; Chronicle namespace acme, mapped from the tenant; MongoDB storage, with Ada+es+acme for events and Ada+acme for read models. From each of the first three boxes an arrow goes to a Default namespace box on the right, holding Ada+es+Default and Ada, labeled with what sends a request there: no strategies, no header, and NotSet or Default. From storage, an arrow points to an Ada+Default box, which does not exist and reads empty.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2200&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;At every hop, find out what “no tenant” turns into.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;at-the-edge-sign-in-tenant-and-onboarding&quot;&gt;At the edge: sign-in, tenant and onboarding&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/authproxy/&quot;&gt;AuthProxy&lt;/a&gt; sits in front of the backend and the frontend. It handles &lt;a href=&quot;https://cratis.io/authproxy/configuration/authentication/&quot;&gt;OIDC and OAuth2 sign-in, plus JWT bearer authentication&lt;/a&gt;, resolves a tenant for each request and forwards it downstream in a header named &lt;code dir=&quot;auto&quot;&gt;Tenant-ID&lt;/code&gt;. &lt;a href=&quot;https://cratis.io/authproxy/configuration/tenancy/&quot;&gt;Tenant resolution&lt;/a&gt; is a list of strategies under &lt;code dir=&quot;auto&quot;&gt;TenantResolutions&lt;/code&gt;, and resolving from a token claim is one entry in it:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;AuthProxy&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;TenantResolutions&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Strategy&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Claim&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Options&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;ClaimType&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;tid&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;AuthProxy matches the claim’s value against the source identifiers you configure for each tenant. It can also &lt;a href=&quot;https://cratis.io/authproxy/configuration/authorization/&quot;&gt;require configured claims&lt;/a&gt;, such as a role or a group, before it forwards a request. Let the backend accept traffic from the proxy and nothing else, because a service reachable directly believes whatever headers a caller sends it.&lt;/p&gt;
&lt;p&gt;A user who has signed in but belongs to no tenant yet needs somewhere to go. &lt;a href=&quot;https://github.com/Cratis/Ante&quot;&gt;Ante&lt;/a&gt; is a separate invitation and onboarding lobby for that moment, where they can join an existing tenant, create one by invitation, or self-register. It records the acceptance and publishes it for your application to act on. Membership, provisioning and email stay yours.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;how-the-edge-picks-a-tenant&quot;&gt;How the edge picks a tenant&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The strategies run in order until one succeeds, so a list with &lt;code dir=&quot;auto&quot;&gt;Host&lt;/code&gt; first and &lt;code dir=&quot;auto&quot;&gt;Claim&lt;/code&gt; second tries the request host before the token. For &lt;code dir=&quot;auto&quot;&gt;Host&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Claim&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Route&lt;/code&gt;, a strategy finds a source identifier and looks it up in the &lt;code dir=&quot;auto&quot;&gt;Tenants&lt;/code&gt; registry, and what travels downstream is the registry key, &lt;code dir=&quot;auto&quot;&gt;acme&lt;/code&gt;, instead of the raw value of the &lt;code dir=&quot;auto&quot;&gt;tid&lt;/code&gt; claim. &lt;code dir=&quot;auto&quot;&gt;SubHost&lt;/code&gt; takes the subdomain as it is, with no lookup. For that strategy, AuthProxy’s &lt;a href=&quot;https://cratis.io/authproxy/configuration/tenancy/&quot;&gt;tenant verification&lt;/a&gt; can ask your backend whether the tenant exists, and a tenant that exists still isn’t proof that this caller belongs to it.&lt;/p&gt;
&lt;p&gt;Before any of that runs, AuthProxy’s tenancy middleware removes inbound identity headers and any inbound &lt;code dir=&quot;auto&quot;&gt;Tenant-ID&lt;/code&gt;. A caller can’t hand the backend a tenant of its own choosing under that name. The resolved value is then added to the proxied request.&lt;/p&gt;
&lt;p&gt;A request can also leave the edge with no tenant at all. If strategies are configured and none resolves, a signed-in user gets a 403 page explaining that they belong to no organization, an anonymous caller gets a 401, and with a lobby configured for it the user is redirected there instead. If &lt;code dir=&quot;auto&quot;&gt;TenantResolutions&lt;/code&gt; is empty, the request goes through with no tenant and no &lt;code dir=&quot;auto&quot;&gt;Tenant-ID&lt;/code&gt; header. Sign-in, invitation, registration and paths declared anonymous go through without a tenant as well.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;who-the-user-is&quot;&gt;Who the user is&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;AuthProxy authenticates before a request reaches your code, and it forwards the signed-in identity downstream in the &lt;code dir=&quot;auto&quot;&gt;x-ms-client-principal&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;x-ms-client-principal-id&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;x-ms-client-principal-name&lt;/code&gt; headers, with the principal itself as base64-encoded JSON. It removes inbound copies of those headers first, so a caller can’t pass a principal of its own through the proxy.&lt;/p&gt;
&lt;p&gt;Arc doesn’t sign anyone in. The host establishes the principal, and Arc’s &lt;a href=&quot;https://cratis.io/arc/backend/csharp/authorizing-commands-and-queries/&quot;&gt;authorization attributes&lt;/a&gt; check it on each command and query: &lt;code dir=&quot;auto&quot;&gt;[Authorize]&lt;/code&gt; for any authenticated user, &lt;code dir=&quot;auto&quot;&gt;[Roles]&lt;/code&gt; for any of the listed roles, a named policy, or &lt;code dir=&quot;auto&quot;&gt;[AllowAnonymous]&lt;/code&gt;. With no requirement declared, the built-in evaluator allows the call. A role isn’t ownership either. “Only the librarian who registered an author may rename it” is a rule the application writes, by deriving the owner from the principal and constraining the query, and Arc has no universal mapping from a claim to an owner.&lt;/p&gt;
&lt;p&gt;For the frontend, an &lt;a href=&quot;https://cratis.io/arc/understanding-identity-and-access/&quot;&gt;identity details provider&lt;/a&gt; composes application-specific details from the authenticated principal, and Arc serves them at &lt;code dir=&quot;auto&quot;&gt;/.cratis/me&lt;/code&gt;. The endpoint exists only when a provider is registered. The &lt;code dir=&quot;auto&quot;&gt;.cratis-identity&lt;/code&gt; cookie that carries those details to the browser is unsigned presentation data the client controls, so nothing on the backend should ever authorize on it. &lt;code dir=&quot;auto&quot;&gt;/.cratis/me&lt;/code&gt; doesn’t authorize commands or queries.&lt;/p&gt;
&lt;p&gt;Identity and tenant arrive by the same route, as headers the proxy sets. Arc trusts both, and the difference is in what Arc does with them. The principal goes through authorization. The tenant only selects where data goes.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-header-hop-into-arc&quot;&gt;The header hop into Arc&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Arc reads the tenant from a header too, but its default header name is &lt;code dir=&quot;auto&quot;&gt;x-cratis-tenant-id&lt;/code&gt;, so point it at the proxy’s header. The &lt;a href=&quot;https://cratis.io/arc/backend/csharp/tenancy/resolvers/&quot;&gt;tenant resolver docs&lt;/a&gt; show the setting with a custom name, and in .NET the extension method lives in &lt;code dir=&quot;auto&quot;&gt;Cratis.Arc.Tenancy&lt;/code&gt;:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-0&quot; id=&quot;tab-0-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-1&quot; id=&quot;tab-0-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-2&quot; id=&quot;tab-0-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-3&quot; id=&quot;tab-0-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-4&quot; id=&quot;tab-0-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-0&quot; aria-labelledby=&quot;tab-0-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Arc&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Arc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Tenancy&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;AddCratisArc&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;options &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;options&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;UseHeaderTenancy&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Tenant-ID&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-1&quot; aria-labelledby=&quot;tab-0-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;In Arc for Kotlin and Java this is a Spring property. A request without the header has no tenant (&lt;code dir=&quot;auto&quot;&gt;null&lt;/code&gt;) unless &lt;code dir=&quot;auto&quot;&gt;cratis.arc.tenancy.required&lt;/code&gt; is set, which rejects it with a 400, and the Chronicle integration uses the default event store for a null tenant.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis.arc.tenancy.header-name&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;Tenant-ID&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-2&quot; aria-labelledby=&quot;tab-0-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;In Arc for Kotlin and Java this is a Spring property. A request without the header has no tenant (&lt;code dir=&quot;auto&quot;&gt;null&lt;/code&gt;) unless &lt;code dir=&quot;auto&quot;&gt;cratis.arc.tenancy.required&lt;/code&gt; is set, which rejects it with a 400, and the Chronicle integration uses the default event store for a null tenant.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis.arc.tenancy.header-name&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;Tenant-ID&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-3&quot; aria-labelledby=&quot;tab-0-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;In Arc for TypeScript this is the &lt;code dir=&quot;auto&quot;&gt;tenancy.httpHeader&lt;/code&gt; option. A request without the header has no &lt;code dir=&quot;auto&quot;&gt;tenantId&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;undefined&lt;/code&gt;) unless &lt;code dir=&quot;auto&quot;&gt;tenancy.required&lt;/code&gt; is set, which answers 400, and the Chronicle integration then appends in the &lt;code dir=&quot;auto&quot;&gt;Default&lt;/code&gt; namespace.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ArcApplication&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/arc.core&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ArcApplication&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;createBuilder&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tenancy&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; httpHeader&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Tenant-ID&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-4&quot; aria-labelledby=&quot;tab-0-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Arc for Elixir doesn’t exist; use the Chronicle Elixir client directly.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;In .NET, with the default resolvers, a missing header doesn’t reject the request. It selects the unset tenant and then Chronicle’s default namespace, so decide deliberately whether that’s allowed.&lt;/p&gt;
&lt;p&gt;Nothing sets the name for you, and putting AuthProxy in front of Arc doesn’t change which header Arc reads. That’s the mismatch in &lt;a href=&quot;https://github.com/Cratis/AuthProxy/issues/157&quot;&gt;Cratis/AuthProxy#157&lt;/a&gt;, and the one line above is the workaround.&lt;/p&gt;
&lt;p&gt;Setting the name closes a second gap as well. AuthProxy removes the inbound headers it owns, and &lt;code dir=&quot;auto&quot;&gt;x-cratis-tenant-id&lt;/code&gt; isn’t one of them. An Arc left on its default reads a tenant from a header the proxy doesn’t remove. Configure &lt;code dir=&quot;auto&quot;&gt;Tenant-ID&lt;/code&gt;, and strip &lt;code dir=&quot;auto&quot;&gt;x-cratis-tenant-id&lt;/code&gt; at any ingress that could pass it through, the same treatment Arc recommends for the client-supplied fallback header of its subdomain resolver.&lt;/p&gt;
&lt;p&gt;In .NET, when the header Arc reads is missing, the resolver returns an empty string and the &lt;a href=&quot;https://cratis.io/arc/backend/csharp/tenancy/tenant-context/&quot;&gt;tenant context&lt;/a&gt; becomes &lt;code dir=&quot;auto&quot;&gt;TenantId.NotSet&lt;/code&gt;. The tenant is resolved once per async execution flow and cached, so every service in a request sees the same value. An explicit tenant scope wins over the resolver, which matters for background work further down.&lt;/p&gt;
&lt;p&gt;Non-ASCII tenant IDs have a seam of their own. AuthProxy sends a tenant ID that isn’t plain ASCII in RFC 8187 encoded form, and Arc’s header resolver passes the value through as it arrives. Tenant IDs that are plain lowercase labels, such as &lt;code dir=&quot;auto&quot;&gt;acme&lt;/code&gt;, never meet it, and the next hop gives a second reason to keep them that way.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-tenant-becomes-a-namespace&quot;&gt;The tenant becomes a namespace&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A Chronicle &lt;a href=&quot;https://cratis.io/chronicle/concepts/namespaces/&quot;&gt;namespace&lt;/a&gt; is a logical partition within one event store. Events, projections, reducers and read models are processed per namespace, and when no namespace is given, Chronicle uses &lt;code dir=&quot;auto&quot;&gt;Default&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Arc’s Chronicle integration is optional, and Arc runs without it. Once you add it, with &lt;code dir=&quot;auto&quot;&gt;WithChronicle&lt;/code&gt; or the all-in-one &lt;code dir=&quot;auto&quot;&gt;AddCratis&lt;/code&gt;, it registers &lt;code dir=&quot;auto&quot;&gt;TenantNamespaceResolver&lt;/code&gt; as Chronicle’s namespace resolver, and your own options callback runs afterwards, so you can replace it. The resolver is short:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EventStoreNamespaceName&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Resolve&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; tenantId &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tenantIdAccessor&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Current&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tenantId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;IsDefault&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EventStoreNamespaceName&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Default&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EventStoreNamespaceName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tenantId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;IsDefault&lt;/code&gt; is true for two values, &lt;code dir=&quot;auto&quot;&gt;TenantId.NotSet&lt;/code&gt; and a tenant actually named &lt;code dir=&quot;auto&quot;&gt;Default&lt;/code&gt;. Both land in Chronicle’s &lt;code dir=&quot;auto&quot;&gt;Default&lt;/code&gt; namespace, so a tenant called “Default” and a request with no tenant read and write the same data. Any other tenant ID becomes the namespace name exactly as it is.&lt;/p&gt;
&lt;p&gt;That cuts both ways. A wrong but accepted tenant ID sends the write to that tenant’s namespace, faithfully. &lt;a href=&quot;https://cratis.io/arc/backend/csharp/chronicle/tenancy/&quot;&gt;Isolation follows the tenant resolver&lt;/a&gt;, and the mapping selects a namespace without authorizing anything. Caller-controlled headers, query strings and hosts don’t prove membership. Check membership before any tenant-scoped work, and test the no-tenant case.&lt;/p&gt;
&lt;p&gt;On MongoDB the namespace also becomes part of a database name, so it has to be a legal one. MongoDB rejects &lt;code dir=&quot;auto&quot;&gt;/ \ . &quot; $ * &amp;#x3C; &gt; : | ?&lt;/code&gt;, the space and the null character, and caps a database name at 63 bytes. A display-style tenant ID such as &lt;code dir=&quot;auto&quot;&gt;Acme Books Ltd.&lt;/code&gt; won’t fit. Chronicle checks the composed name and throws &lt;code dir=&quot;auto&quot;&gt;InvalidDatabaseName&lt;/code&gt;, naming the event store, the namespace and the offending character, before the driver fails somewhere later. Arc’s subdomain resolver already insists on a letter-digit-hyphen label of up to 63 characters. AuthProxy registry keys in the same shape keep all three products in agreement about what a tenant ID can look like.&lt;/p&gt;
&lt;p&gt;This hop is where the author feature’s rule becomes per organization. A &lt;a href=&quot;https://cratis.io/chronicle/constraints/&quot;&gt;constraint&lt;/a&gt; is scoped to its event sequence and namespace, so with a namespace per tenant, Acme and Contoso can each register Jane Austen. In the made-up week, they were sharing one namespace.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/tenant-boundaries.BUP0PcGe_1z13Dd.webp&quot; alt=&quot;A diagram titled Three tenant boundaries, three columns from left to right. Edge, AuthProxy: selects the tenant and forwards it as Tenant-ID. App, Arc: reads a header, configured for Tenant-ID. Store, Chronicle: a namespace per tenant, mapped from Arc’s tenant. Below, under Read-model storage: MongoDB, a database per tenant via Arc; EF Core, a connection you configure. A full-width card, Membership: your rule, reads: Check membership before tenant-scoped work. A header or tenant ID is not proof of membership.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Selecting a tenant isn’t authorizing it.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-the-read-models-land&quot;&gt;Where the read models land&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Events and read models aren’t stored under the same names, and the default namespace has one asymmetry. From Chronicle’s &lt;a href=&quot;https://cratis.io/chronicle/concepts/namespaces/&quot;&gt;namespace naming&lt;/a&gt;, for an event store named &lt;code dir=&quot;auto&quot;&gt;Ada&lt;/code&gt; on MongoDB:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Holds&lt;/th&gt;
&lt;th&gt;Namespace &lt;code dir=&quot;auto&quot;&gt;Default&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Namespace &lt;code dir=&quot;auto&quot;&gt;acme&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Event store wide state&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;Ada+es&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;Ada+es&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event sequences&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;Ada+es+Default&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;Ada+es+acme&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read models&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;Ada&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;Ada+acme&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Read models of the default namespace go into the bare event store name, while event sequences carry a suffix for every namespace, &lt;code dir=&quot;auto&quot;&gt;Default&lt;/code&gt; included. Query read models from outside Chronicle with a database name you composed by hand, and &lt;code dir=&quot;auto&quot;&gt;Ada+Default&lt;/code&gt; is a database nothing writes to. Reads from it come back empty instead of failing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/default-namespace-storage.DDWubVyz_Z2pCWHX.webp&quot; alt=&quot;A table-style diagram titled Default is stored differently, for the event store Ada on MongoDB, with columns for namespace Default and namespace acme. Store-wide state is Ada+es for both. Event sequences are Ada+es+Default and Ada+es+acme. Read models are Ada, highlighted, for Default and Ada+acme for acme. An arrow from Ada points to a note: Not Ada+Default. Composed by hand, that name reads empty. A strip below says Arc’s MongoDB resolver uses base for NotSet and Default and base+tenant otherwise, and that base must be aligned with Chronicle’s sink.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2200&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Read models of the default namespace use the bare store name.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Arc’s MongoDB integration uses the same shape, the configured base database for a default tenant and &lt;code dir=&quot;auto&quot;&gt;base+tenant&lt;/code&gt; for any other, and both &lt;code dir=&quot;auto&quot;&gt;NotSet&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Default&lt;/code&gt; resolve to the base name. For Arc’s direct MongoDB queries to find what Chronicle materialized, the base database and Chronicle’s read-model sink have to line up, and checking that alignment is your job. With EF Core, Arc uses the connection string you configured, pooled contexts included, and appends no tenant. Separate databases, schemas or enforced filters are yours to build and test.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;work-that-has-no-request&quot;&gt;Work that has no request&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A scheduled job, or a reactor that starts work later, has no header to read, so it names its tenant. Arc’s &lt;a href=&quot;https://cratis.io/arc/backend/csharp/commands/command-pipeline/&quot;&gt;command pipeline&lt;/a&gt; shows the pattern with a cart:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-0&quot; id=&quot;tab-1-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-1&quot; id=&quot;tab-1-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-2&quot; id=&quot;tab-1-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-3&quot; id=&quot;tab-1-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-4&quot; id=&quot;tab-1-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-0&quot; aria-labelledby=&quot;tab-1-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TenantCartJob&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ITenantScope&lt;/span&gt;&lt;span&gt;&lt;span&gt; tenants&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ICommandPipeline&lt;/span&gt;&lt;span&gt;&lt;span&gt; pipeline&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CommandResult&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CartLineId&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TenantId&lt;/span&gt;&lt;span&gt;&lt;span&gt; tenant&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Sku&lt;/span&gt;&lt;span&gt;&lt;span&gt; sku&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Quantity&lt;/span&gt;&lt;span&gt;&lt;span&gt; quantity&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt;&lt;span&gt; stoppingToken&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;tenants&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Begin&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tenant&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pipeline&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Execute&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CartLineId&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AddItemToCart&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sku&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;quantity&lt;/span&gt;&lt;span&gt;&lt;span&gt;),&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;stoppingToken&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-1&quot; aria-labelledby=&quot;tab-1-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;In Arc for Kotlin and Java the tenant travels in the &lt;code dir=&quot;auto&quot;&gt;CommandExecutionOptions&lt;/code&gt; passed to the pipeline.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TenantCartJob&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; pipeline: &lt;/span&gt;&lt;span&gt;CommandPipeline&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; services: &lt;/span&gt;&lt;span&gt;ServiceResolver&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;suspend&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(tenant: &lt;/span&gt;&lt;span&gt;TenantId&lt;/span&gt;&lt;span&gt;, sku: &lt;/span&gt;&lt;span&gt;Sku&lt;/span&gt;&lt;span&gt;, quantity: &lt;/span&gt;&lt;span&gt;Quantity&lt;/span&gt;&lt;span&gt;, principal: &lt;/span&gt;&lt;span&gt;ArcPrincipal&lt;/span&gt;&lt;span&gt;): &lt;/span&gt;&lt;span&gt;CommandResult&lt;/span&gt;&lt;span&gt;&amp;#x3C;*&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pipeline.&lt;/span&gt;&lt;span&gt;execute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;AddItemToCart&lt;/span&gt;&lt;span&gt;(sku, quantity),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;CommandExecutionOptions&lt;/span&gt;&lt;span&gt;(UUID.&lt;/span&gt;&lt;span&gt;randomUUID&lt;/span&gt;&lt;span&gt;(), principal, services, tenant.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-2&quot; aria-labelledby=&quot;tab-1-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;In Arc for Kotlin and Java the tenant travels in the &lt;code dir=&quot;auto&quot;&gt;CommandExecutionOptions&lt;/code&gt; passed to the pipeline.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TenantCartJob&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;final&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BlockingCommandPipeline&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pipeline&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;final&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ServiceResolver&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;services&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TenantCartJob&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BlockingCommandPipeline&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pipeline&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ServiceResolver&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;services&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;pipeline&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; pipeline&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;services&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; services&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CommandResult&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TenantId&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tenant&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Sku&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sku&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Quantity&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;quantity&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ArcPrincipal&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;principal&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pipeline&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;execute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AddItemToCart&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sku&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; quantity&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CommandExecutionOptions&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;UUID&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;randomUUID&lt;/span&gt;&lt;span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;span&gt; principal&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; services&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tenant&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;()));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-3&quot; aria-labelledby=&quot;tab-1-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;In Arc for TypeScript the tenant is the &lt;code dir=&quot;auto&quot;&gt;tenantId&lt;/code&gt; of the execution context passed to &lt;code dir=&quot;auto&quot;&gt;executeCommand&lt;/code&gt;, and no tenant is resolved for a direct call.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TenantCartJob&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;constructor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ArcApplication&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tenant&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;sku&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Sku&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;quantity&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Quantity&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;principal&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Principal&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;signal&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AbortSignal&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CommandResult&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;server&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;executeCommand&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;AddItemToCart&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; sku&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;sku&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; quantity&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;quantity&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;correlationId&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;randomUUID&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;principal&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tenantId&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tenant&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;signal&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;allowedSeverity&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Severity&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Warning&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-4&quot; aria-labelledby=&quot;tab-1-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Arc for Elixir doesn’t exist; use the Chronicle Elixir client directly.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;In .NET, &lt;code dir=&quot;auto&quot;&gt;Begin&lt;/code&gt; refuses an empty tenant, and the explicit scope wins over the configured resolver, even during an HTTP request. Dependency injection is where it goes wrong. A tenant-scoped service, such as Chronicle’s event store or a &lt;code dir=&quot;auto&quot;&gt;DbContext&lt;/code&gt;, that was already resolved in an existing DI scope stays bound to that scope’s original tenant after the current tenant changes. Create the DI scope inside the tenant scope. The &lt;code dir=&quot;auto&quot;&gt;Execute&lt;/code&gt; call above takes no scope of its own and creates one for you. Arc for Kotlin and Java take the tenant in &lt;code dir=&quot;auto&quot;&gt;CommandExecutionOptions&lt;/code&gt;, and Arc for TypeScript in the execution context passed to &lt;code dir=&quot;auto&quot;&gt;executeCommand&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-tools-you-look-through&quot;&gt;The tools you look through&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every &lt;code dir=&quot;auto&quot;&gt;cratis chronicle&lt;/code&gt; command that works on a namespace takes &lt;code dir=&quot;auto&quot;&gt;-n&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;--namespace&lt;/code&gt;. Leave it out, and the &lt;a href=&quot;https://cratis.io/cli/chronicle/namespaces/&quot;&gt;CLI&lt;/a&gt; uses the active context’s namespace, or &lt;code dir=&quot;auto&quot;&gt;Default&lt;/code&gt; when the context doesn’t set one.&lt;/p&gt;
&lt;p&gt;So when Acme reports that a view stopped updating, &lt;code dir=&quot;auto&quot;&gt;cratis chronicle failed-partitions list&lt;/code&gt; lists the &lt;a href=&quot;https://cratis.io/cli/chronicle/failed-partitions/&quot;&gt;failed partitions&lt;/a&gt; in &lt;code dir=&quot;auto&quot;&gt;Default&lt;/code&gt;, and an empty list tells you nothing about Acme. Ask for the namespace you mean:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;failed-partitions&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;acme&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Add &lt;a href=&quot;https://cratis.io/cli/reference/global-options/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;--debug&lt;/code&gt;&lt;/a&gt; to any command and the CLI prints the event store and namespace it resolved, each with where the value came from: the option, the context or the built-in default. A context with a namespace saves typing, and it also means the same command answers for a different tenant depending on which context is active. State-changing commands such as &lt;code dir=&quot;auto&quot;&gt;observers replay&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;observers retry-partition&lt;/code&gt; take the same &lt;code dir=&quot;auto&quot;&gt;-n&lt;/code&gt; with the same fallback. They ask for confirmation first, and the target namespace is worth reading before you answer.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/chronicle/workbench/&quot;&gt;Workbench&lt;/a&gt; asks you to select the event store and a namespace you’re authorized to inspect, and to confirm both before reading anything into what it shows. Being able to open a namespace in a tool isn’t permission to change anything in it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-not-a-deployment-per-tenant&quot;&gt;Why not a deployment per tenant?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The strongest case against all of this is to share nothing. Give each tenant its own deployment and its own database, and no header mismatch can mix their data. For a handful of large tenants with contractual separation, that’s a reasonable choice, and Arc supports it: &lt;code dir=&quot;auto&quot;&gt;UseFixedTenancy&lt;/code&gt; resolves every request to one tenant ID, decided once for the whole deployment.&lt;/p&gt;
&lt;p&gt;The cost is that every tenant becomes another deployment to upgrade, migrate, back up and watch. Namespaces put many tenants on one Chronicle kernel and one event store, each with its own data. In exchange, the selection path has to be right at every hop, which is why it deserves tests.&lt;/p&gt;
&lt;p&gt;A second objection is that the defaults should reject a missing tenant. Arc does fail early in one place. Configure the subdomain resolver without a usable base domain and the application refuses to start, because otherwise every request would take its tenant from a header any client can set. For a missing tenant, though, the resolvers select &lt;code dir=&quot;auto&quot;&gt;Default&lt;/code&gt; on purpose, so single-tenant applications work with no tenant configuration at all. Arc for .NET has no switch that rejects a request without a tenant, although Arc for Kotlin and Java (&lt;code dir=&quot;auto&quot;&gt;cratis.arc.tenancy.required&lt;/code&gt;) and Arc for TypeScript (&lt;code dir=&quot;auto&quot;&gt;tenancy.required&lt;/code&gt;) do. In .NET that check is yours, as middleware or an authorization rule, next to the membership check.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;seven-tests-before-the-second-tenant-signs-up&quot;&gt;Seven tests before the second tenant signs up&lt;/h2&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Send a request with no tenant header. Decide whether default-namespace access is allowed, then assert either that the request is rejected or that it lands in &lt;code dir=&quot;auto&quot;&gt;Default&lt;/code&gt; by design.&lt;/li&gt;
&lt;li&gt;Make sure nothing in your tenant registry can resolve to a tenant named &lt;code dir=&quot;auto&quot;&gt;Default&lt;/code&gt;, or that you’re content with it sharing data with requests that carry no tenant.&lt;/li&gt;
&lt;li&gt;Through the proxy, as an Acme user, send &lt;code dir=&quot;auto&quot;&gt;Tenant-ID: contoso&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;x-cratis-tenant-id: contoso&lt;/code&gt;, and assert that the write lands in &lt;code dir=&quot;auto&quot;&gt;acme&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Register the same author name in &lt;code dir=&quot;auto&quot;&gt;acme&lt;/code&gt; and in &lt;code dir=&quot;auto&quot;&gt;contoso&lt;/code&gt;. Both should succeed, and if the second is rejected, your namespaces aren’t per tenant.&lt;/li&gt;
&lt;li&gt;Run a background job under &lt;code dir=&quot;auto&quot;&gt;ITenantScope.Begin&lt;/code&gt;, with the DI scope created inside the tenant scope, and check which namespace its events land in.&lt;/li&gt;
&lt;li&gt;Query read models directly for the default tenant and for &lt;code dir=&quot;auto&quot;&gt;acme&lt;/code&gt;, and assert that both return data. That one catches &lt;code dir=&quot;auto&quot;&gt;Ada+Default&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run your usual diagnostic command with &lt;code dir=&quot;auto&quot;&gt;--debug&lt;/code&gt;, and check that the namespace it reports is the one you meant.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;None of these proves that a caller belongs to a tenant. Membership is a separate rule, checked against your own data, and its spec belongs in the same suite as these seven.&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href=&quot;https://blog.cratis.io/cratis-components-the-react-layer/&quot;&gt;Part 09 — Cratis Components, the React layer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next: &lt;a href=&quot;https://blog.cratis.io/testing-with-arc-and-chronicle/&quot;&gt;Part 11 — Testing with Arc and Chronicle&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>tenancy</category><category>identity</category><category>authproxy</category><category>arc</category><category>chronicle</category></item><item><title>From script to stage · Part 09: Cratis Components, the React layer</title><link>https://blog.cratis.io/cratis-components-the-react-layer/</link><guid isPermaLink="true">https://blog.cratis.io/cratis-components-the-react-layer/</guid><description>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.</description><pubDate>Wed, 30 Sep 2026 09:09:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt; · Part 09 of 26&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The dialog that registers a name in the &lt;code dir=&quot;auto&quot;&gt;cratis&lt;/code&gt; template has one field and no event handlers. It still keeps its button disabled while validation fails, shows a spinner while the command runs, puts a server’s rejection under the field it names and closes only when the command succeeds. Two packages share that work. Arc’s React package runs the command and holds the form state, and &lt;a href=&quot;https://cratis.io/components/&quot;&gt;Cratis Components&lt;/a&gt; draws the dialog, the field and the buttons around it.&lt;/p&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;cratis&lt;/code&gt; template, which &lt;code dir=&quot;auto&quot;&gt;dotnet new cratis&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;cratis new&lt;/code&gt; both scaffold (see &lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/#start-it-from-nothing-to-a-running-skeleton&quot;&gt;&lt;em&gt;Cratis: from script to stage, and the long run&lt;/em&gt;&lt;/a&gt;), already has the two screens the author feature needs, a list page and a register dialog, and both are built with Components 4. &lt;a href=&quot;https://blog.cratis.io/live-uis-with-arc/&quot;&gt;&lt;em&gt;Live UIs&lt;/em&gt;&lt;/a&gt; covered the live query behind the list and the props of &lt;code dir=&quot;auto&quot;&gt;CommandDialog&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;arc-underneath-components-on-top&quot;&gt;Arc underneath, Components on top&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Arc’s &lt;code dir=&quot;auto&quot;&gt;@cratis/arc.react&lt;/code&gt; owns the runtime and the state. That covers the &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;Arc&gt;&lt;/code&gt; provider with the command and query runtime, &lt;code dir=&quot;auto&quot;&gt;CommandForm&lt;/code&gt; with its field registry and validation state, the dialog host behind &lt;code dir=&quot;auto&quot;&gt;useDialog&lt;/code&gt;, and the TypeScript the proxy generator writes for each command and query. Components 4 owns the React markup, its public types, the design tokens and the stable part names that style hooks target. &lt;code dir=&quot;auto&quot;&gt;CommandDialog&lt;/code&gt; shows the split directly. Its body is Arc’s &lt;code dir=&quot;auto&quot;&gt;CommandForm&lt;/code&gt; wrapped around a Components &lt;code dir=&quot;auto&quot;&gt;Dialog&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Components 4 renders its own HTML. Its package manifest declares no PrimeReact, PrimeIcons or PrimeUI dependency, and a release gate checks the emitted JavaScript and declarations for Prime imports. React Aria supplies focus, keyboard, overlay and date behavior inside some components, and none of its types or class names are part of the public contract.&lt;/p&gt;
&lt;p&gt;The package root is for setup only. Every component comes from its own subpath, such as &lt;code dir=&quot;auto&quot;&gt;@cratis/components/CommandDialog&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;@cratis/components/DataPage&lt;/code&gt;, and the subpaths fall into three groups. Foundation covers forms, dialogs, tables, pages, filters and notifications. Advanced React covers chat, schema and object editors, a time scrubber and a toolbar. Spatial is &lt;code dir=&quot;auto&quot;&gt;Canvas&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;PivotViewer&lt;/code&gt;, the only two that need the optional &lt;code dir=&quot;auto&quot;&gt;pixi.js&lt;/code&gt; peer. The groups describe dependencies, and they don’t rank stability. Next to React 19 and the Arc range that &lt;a href=&quot;https://blog.cratis.io/live-uis-with-arc/&quot;&gt;&lt;em&gt;Live UIs&lt;/em&gt;&lt;/a&gt; lists, Components needs &lt;code dir=&quot;auto&quot;&gt;@cratis/fundamentals&lt;/code&gt; from 7.10.3 within 7.&lt;/p&gt;
&lt;p&gt;An application mounts both providers and tells Arc which dialogs to use for confirmations and busy states:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;reflect-metadata&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Arc&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/arc.react&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;DialogComponents&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/arc.react/dialogs&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CratisComponentsProvider&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/components&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;BusyIndicatorDialog&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ConfirmationDialog&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/components/Dialogs&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Authors&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;./Authors/Authors&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Arc&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CratisComponentsProvider&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;span&gt; locale&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;en-US&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;toaster&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;DialogComponents&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;confirmation&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;ConfirmationDialog&lt;/span&gt;&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;busyIndicator&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BusyIndicatorDialog&lt;/span&gt;&lt;span&gt;}&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Authors&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;DialogComponents&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;CratisComponentsProvider&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;Arc&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;Arc&gt;&lt;/code&gt; is what the generated proxies run against, and &lt;a href=&quot;https://cratis.io/components/getting-started/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;CratisComponentsProvider&lt;/code&gt;&lt;/a&gt; doesn’t replace it. The Components provider holds the locale, the labels Components renders itself, an optional renderer and, with &lt;code dir=&quot;auto&quot;&gt;toaster&lt;/code&gt;, the region where notifications appear. &lt;code dir=&quot;auto&quot;&gt;DialogComponents&lt;/code&gt; is Arc’s, and it registers Components’ &lt;code dir=&quot;auto&quot;&gt;ConfirmationDialog&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;BusyIndicatorDialog&lt;/code&gt; as the renderers behind Arc’s &lt;code dir=&quot;auto&quot;&gt;useConfirmationDialog&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;useBusyIndicator&lt;/code&gt;, because Arc &lt;a href=&quot;https://cratis.io/arc/frontend/react/dialogs/&quot;&gt;draws no dialog itself&lt;/a&gt;. The template splits this between &lt;code dir=&quot;auto&quot;&gt;.frontend/main.tsx&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;App.tsx&lt;/code&gt;, and its &lt;code dir=&quot;auto&quot;&gt;main.tsx&lt;/code&gt; puts &lt;code dir=&quot;auto&quot;&gt;CratisComponentsProvider&lt;/code&gt; outside &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;Arc&gt;&lt;/code&gt;, which works as well, since both still wrap the whole app. That provider has no &lt;code dir=&quot;auto&quot;&gt;toaster&lt;/code&gt;, so the toasts further down never show in a template app. They go into a queue that no region displays, and nothing reports it. Adding the prop in &lt;code dir=&quot;auto&quot;&gt;main.tsx&lt;/code&gt; is the one change they need:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ReactDOM&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;createRoot&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;document&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;root&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;React.StrictMode&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CratisComponentsProvider&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;toaster&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Arc&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;Arc&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;CratisComponentsProvider&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;React.StrictMode&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The stylesheets go in the CSS entry. The template’s starts like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;tailwindcss&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/components/tokens&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/components/styles&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/components/theme&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &apos;../&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Keeping them in CSS also keeps &lt;code dir=&quot;auto&quot;&gt;tsc&lt;/code&gt; quiet. TypeScript 6 checks side-effect imports by default, and imported from a &lt;code dir=&quot;auto&quot;&gt;.tsx&lt;/code&gt; file, those three stylesheet paths fail the build with TS2882.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-registration-dialog&quot;&gt;The registration dialog&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The template’s dialog, with the names changed for authors. In the C# template, its &lt;code dir=&quot;auto&quot;&gt;Register&lt;/code&gt; command creates the new id on the server, the shape &lt;a href=&quot;https://blog.cratis.io/arc-commands-and-queries/&quot;&gt;&lt;em&gt;Arc: commands and queries without the plumbing&lt;/em&gt;&lt;/a&gt; calls &lt;code dir=&quot;auto&quot;&gt;RegisterNewAuthor&lt;/code&gt;, so that’s its name here:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CommandDialog&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/components/CommandDialog&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;InputTextField&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/components/CommandForm&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;MdEdit&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;react-icons/md&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;RegisterNewAuthor&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;./RegisterNewAuthor&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RegisterAuthorDialog&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CommandDialog&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;RegisterNewAuthor&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;RegisterNewAuthor&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Register author&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;okLabel&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Register&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;cancelLabel&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Cancel&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;InputTextField&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;RegisterNewAuthor&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Name&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;icon&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&amp;#x3C;&lt;/span&gt;&lt;span&gt;MdEdit&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;CommandDialog&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;RegisterNewAuthor&lt;/code&gt; isn’t written by hand. After each build, Arc’s &lt;a href=&quot;https://cratis.io/arc/backend/csharp/proxy-generation/&quot;&gt;proxy generator&lt;/a&gt; writes a class for the C# &lt;code dir=&quot;auto&quot;&gt;[Command]&lt;/code&gt; record, and the template ships the one for its &lt;code dir=&quot;auto&quot;&gt;Register&lt;/code&gt; command. Trimmed, and with the names changed, it looks like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RegisterNewAuthor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Command&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IRegisterNewAuthor&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Guid&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;implements&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IRegisterNewAuthor&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;route&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;/api/authors/registration&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;propertyDescriptors&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PropertyDescriptor&lt;/span&gt;&lt;span&gt;&lt;span&gt;[]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PropertyDescriptor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_name&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_name&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;propertyChanged&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;CommandDialog&lt;/code&gt; takes that class, and Arc’s &lt;code dir=&quot;auto&quot;&gt;CommandForm&lt;/code&gt; creates an instance of it and tracks its values. The dialog walks its children. Each child that’s a command form field, such as &lt;code dir=&quot;auto&quot;&gt;InputTextField&lt;/code&gt;, gets wrapped so it binds to the command, and anything else, a heading or a layout &lt;code dir=&quot;auto&quot;&gt;div&lt;/code&gt;, renders as it is, with its own children walked the same way.&lt;/p&gt;
&lt;p&gt;The compiler checks &lt;code dir=&quot;auto&quot;&gt;command =&gt; command.name&lt;/code&gt; against the generated class. At runtime, Arc reads the accessor a second time, as source text, and takes the first identifier after a dot as the property name. A plain property read like &lt;code dir=&quot;auto&quot;&gt;command =&gt; command.name&lt;/code&gt; is all it can infer. An accessor like &lt;code dir=&quot;auto&quot;&gt;command =&gt; command[key]&lt;/code&gt; can’t be read that way, and the field needs an explicit &lt;code dir=&quot;auto&quot;&gt;fieldName&lt;/code&gt;. In development, Arc warns in the console when a field stays unbound.&lt;/p&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;Guid&lt;/code&gt; in &lt;code dir=&quot;auto&quot;&gt;Command&amp;#x3C;IRegisterNewAuthor, Guid&gt;&lt;/code&gt; is the response type. In C#, the template’s handler creates the new id and returns it with the event, so the command responds with the new id as a &lt;code dir=&quot;auto&quot;&gt;Guid&lt;/code&gt; from &lt;code dir=&quot;auto&quot;&gt;@cratis/fundamentals&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;onSuccess&lt;/code&gt; receives it. The Kotlin and Java templates take the id from the client instead, as a &lt;code dir=&quot;auto&quot;&gt;@CommandKey&lt;/code&gt; on the command, the way &lt;code dir=&quot;auto&quot;&gt;RegisterAuthor&lt;/code&gt; does. Their dialog adds &lt;code dir=&quot;auto&quot;&gt;initialValues={{ id: crypto.randomUUID() }}&lt;/code&gt;, a UUID string made in the browser, and imports the command from &lt;code dir=&quot;auto&quot;&gt;Api/&lt;/code&gt;, where the Gradle plugin writes the proxies.&lt;/p&gt;
&lt;p&gt;The confirm button is focused when the dialog opens. With a required field the button starts disabled, so a held Enter key can’t confirm an empty form. A command with no input, such as a delete, has nothing to keep the button disabled, and &lt;a href=&quot;https://cratis.io/components/commanddialog/&quot;&gt;the &lt;code dir=&quot;auto&quot;&gt;CommandDialog&lt;/code&gt; guide&lt;/a&gt; recommends &lt;code dir=&quot;auto&quot;&gt;initialFocus={DialogInitialFocus.Cancel}&lt;/code&gt; for those.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;when-the-name-is-taken&quot;&gt;When the name is taken&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A registration that reuses a taken name passes every client rule, so the rejection comes from the server, after &lt;strong&gt;Register&lt;/strong&gt; is clicked. The template has no uniqueness rule. The author feature needs one, and in the template it’s a Chronicle &lt;a href=&quot;https://cratis.io/chronicle/constraints/&quot;&gt;constraint&lt;/a&gt; on the event, &lt;code dir=&quot;auto&quot;&gt;[property: Unique(message: &quot;That name is already registered.&quot;)]&lt;/code&gt; on the &lt;code dir=&quot;auto&quot;&gt;Name&lt;/code&gt; of the &lt;code dir=&quot;auto&quot;&gt;Registered&lt;/code&gt; record. &lt;a href=&quot;https://blog.cratis.io/rules-that-hold-when-you-write/&quot;&gt;&lt;em&gt;Rules that hold when you write&lt;/em&gt;&lt;/a&gt; explains why the check has to live at the append. A rule that spans more than one event type goes in a class that implements &lt;code dir=&quot;auto&quot;&gt;IConstraint&lt;/code&gt;, the &lt;a href=&quot;https://cratis.io/chronicle/constraints/declarative/&quot;&gt;declarative form&lt;/a&gt;, where the constraint takes the class name unless &lt;code dir=&quot;auto&quot;&gt;WithName&lt;/code&gt; gives it another.&lt;/p&gt;
&lt;p&gt;With that attribute added, registering “Anselm Brackenridge” a second time through the template’s dialog goes like this in .NET:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Chronicle refuses the append. The violation carries the constraint’s name, a message, and details that name the property and the value that clashed.&lt;/li&gt;
&lt;li&gt;The .NET client replaces the kernel’s message with the one the constraint declared. Without a declared message, the text reads like “Event ‘…’ on member ‘name’ violated a unique constraint on sequence number …”, which is written for a developer. A declared message can use &lt;code dir=&quot;auto&quot;&gt;{PropertyName}&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;{PropertyValue}&lt;/code&gt; placeholders.&lt;/li&gt;
&lt;li&gt;Arc’s Chronicle integration turns each violation into a &lt;a href=&quot;https://cratis.io/arc/frontend/core/validation/results/&quot;&gt;validation result&lt;/a&gt;. The command route answers with HTTP 400 and one result, with the message “That name is already registered.”, the member &lt;code dir=&quot;auto&quot;&gt;name&lt;/code&gt;, the reason &lt;code dir=&quot;auto&quot;&gt;constraintViolation&lt;/code&gt; and the reason detail &lt;code dir=&quot;auto&quot;&gt;Name&lt;/code&gt;, which is the constraint’s name. An unnamed &lt;code dir=&quot;auto&quot;&gt;[Unique]&lt;/code&gt; takes the property’s name. Code that needs to know which rule failed compares &lt;code dir=&quot;auto&quot;&gt;reasonDetail&lt;/code&gt; and leaves the message alone.&lt;/li&gt;
&lt;li&gt;The command result has &lt;code dir=&quot;auto&quot;&gt;isSuccess&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;isValid&lt;/code&gt; both false. &lt;code dir=&quot;auto&quot;&gt;CommandDialog&lt;/code&gt; calls &lt;code dir=&quot;auto&quot;&gt;onFailed&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;onValidationFailure&lt;/code&gt; if you passed them, puts the result on the form and stays open.&lt;/li&gt;
&lt;li&gt;The form matches the result’s members against each field’s property name. The member is &lt;code dir=&quot;auto&quot;&gt;name&lt;/code&gt;, the field is bound to &lt;code dir=&quot;auto&quot;&gt;name&lt;/code&gt;, and the message appears under the Name field.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/template-duplicate-rejected.m8KYBo40_1bCCGk.webp&quot; alt=&quot;The cratis template’s Register dialog in the dark theme. The Name field holds Anselm Brackenridge, a name that’s already registered. The field has a red outline, and under it dark red text reads That name is already registered. The Register and Cancel buttons are enabled.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;960&quot; height=&quot;600&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The second registration of the same name, rejected by the constraint and shown under the Name field.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The field also gets &lt;code dir=&quot;auto&quot;&gt;aria-invalid=&quot;true&quot;&lt;/code&gt; and an &lt;code dir=&quot;auto&quot;&gt;aria-describedby&lt;/code&gt; that points at the message, which ties the message to the field for assistive technology. The dialog stays open with both buttons enabled, and editing the name clears the message. The list behind it still has two rows, because nothing was appended.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/a-taken-name-back-to-the-field.BzDpGoVY_1nx1dp.webp&quot; alt=&quot;A diagram titled A taken name, back to the field, with a small tag that reads .NET path. Six numbered boxes in two rows, joined by arrows, the second row running right to left. 1 Chronicle: append refused, constraint Name. 2 .NET client: message swapped in, the declared one. 3 Arc: validation result, member name. 4 Arc: command result, isValid false. 5 Components: dialog stays open, result on the form. 6 CommandForm: the Name field shows the message. A note along the bottom reads: the command property must share the event property’s name.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2000&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The path a constraint violation takes from the kernel to the Name field, in .NET.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Step 5 works because two names match. The member comes from the event’s property, and it reaches a field only when the command has a property with the same name, as &lt;code dir=&quot;auto&quot;&gt;Register.Name&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Registered.Name&lt;/code&gt; do. A validation result that matches no field has no built-in place on the form. &lt;a href=&quot;https://cratis.io/arc/frontend/react/command-form/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;CommandForm&lt;/code&gt;&lt;/a&gt; shows field errors and a form-level panel for exceptions, and that panel shows a safe default sentence, never the exception text. For a rule that doesn’t map to one field, handle &lt;code dir=&quot;auto&quot;&gt;onValidationFailure&lt;/code&gt; yourself, or show it as a notification.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;autoServerValidate&lt;/code&gt; calls the &lt;code dir=&quot;auto&quot;&gt;/validate&lt;/code&gt; route; &lt;a href=&quot;https://blog.cratis.io/validation-all-the-way-down/&quot;&gt;&lt;em&gt;Validation all the way down&lt;/em&gt;&lt;/a&gt; explains why it can’t catch this duplicate before the user confirms.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-author-list&quot;&gt;The author list&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The template’s list page, again with author names:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Column&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;DataPage&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;MenuItem&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/components/DataPage&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;MdAdd&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;react-icons/md&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AllAuthors&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;./AllAuthors&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorsPageProps&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;onRegister&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorsPage&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;onRegister&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorsPageProps&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;DataPage&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Authors&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;AllAuthors&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;dataKey&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;emptyMessage&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;No authors registered yet.&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;DataPage.MenuItems&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;MenuItem&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Register&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;icon&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;MdAdd&lt;/span&gt;&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;onRegister&lt;/span&gt;&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;DataPage.MenuItems&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;DataPage.Columns&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Column&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;header&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Name&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;DataPage.Columns&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;DataPage&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/components/datapage/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;DataPage&lt;/code&gt;&lt;/a&gt; checks what kind of query it got. A snapshot query gets a table that fetches when the page mounts and again when an argument or the page changes. An observable query gets a table that subscribes and updates as the server pushes results. &lt;code dir=&quot;auto&quot;&gt;DataPage&lt;/code&gt; doesn’t let you change the page size of 20 rows. The C# template’s listing is observable, so its list picks up a new name without a reload. The Kotlin and Java templates list with a plain query, &lt;code dir=&quot;auto&quot;&gt;All&lt;/code&gt;, so their list shows new names on its next fetch.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/template-author-list.UjECZaUO_1h49yJ.webp&quot; alt=&quot;A screenshot of the cratis template’s Demo page in the dark theme. An action bar with a plus icon and the word Register, then a table with Name and Id columns and two rows, Anselm Brackenridge and Odalys Verhoeven, each with a GUID in the Id column.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2880&quot; height=&quot;1800&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The template’s list page, a DataPage over its observable listing query. The template keeps the Id column that the author version drops.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The two &lt;code dir=&quot;auto&quot;&gt;icon&lt;/code&gt; props differ in kind. &lt;code dir=&quot;auto&quot;&gt;InputTextField&lt;/code&gt; takes an element, &lt;code dir=&quot;auto&quot;&gt;icon={&amp;#x3C;MdEdit /&gt;}&lt;/code&gt;, and &lt;a href=&quot;https://cratis.io/components/datapage/menu-items/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;MenuItem&lt;/code&gt;&lt;/a&gt; takes the component, &lt;code dir=&quot;auto&quot;&gt;icon={MdAdd}&lt;/code&gt;. &lt;code dir=&quot;auto&quot;&gt;MenuItem&lt;/code&gt;’s &lt;code dir=&quot;auto&quot;&gt;command&lt;/code&gt; is called with no arguments. The action bar is flat. A &lt;code dir=&quot;auto&quot;&gt;MenuItem&lt;/code&gt; nested in another one is ignored without an error, and so is any child of &lt;code dir=&quot;auto&quot;&gt;DataPage.MenuItems&lt;/code&gt; that isn’t a &lt;code dir=&quot;auto&quot;&gt;MenuItem&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The table covers the states a list goes through. The first load shows a loading row, a refetch keeps the old rows and marks the table busy, and an empty result shows &lt;code dir=&quot;auto&quot;&gt;emptyMessage&lt;/code&gt;. A failed or unauthorized query shows an alert row with &lt;code dir=&quot;auto&quot;&gt;failureMessage&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;unauthorizedMessage&lt;/code&gt;. The server’s exception text never reaches the page.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;DataPage&lt;/code&gt; needs an ancestor with a definite height, and the template’s CSS gives &lt;code dir=&quot;auto&quot;&gt;#root&lt;/code&gt; a height of &lt;code dir=&quot;auto&quot;&gt;100vh&lt;/code&gt; for it. Without one, the table grows with its rows and pushes the paginator off the page, and a page with no height above it at all falls back to a minimum of &lt;code dir=&quot;auto&quot;&gt;20rem&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The page renders inside a &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;main&gt;&lt;/code&gt; element, so a shell that already has one ends up with two. Each row is a tab stop that Enter or Space selects, and there’s no arrow-key movement between rows. The table has no accessible name until you give it one through &lt;code dir=&quot;auto&quot;&gt;tablePt={{ table: { &apos;aria-label&apos;: &apos;Authors&apos; } }}&lt;/code&gt;. With a details panel, the divider only moves with a pointer, and the panel opens beside the table at every screen width.&lt;/p&gt;
&lt;p&gt;The feature component puts the page and the dialog together with Arc’s &lt;code dir=&quot;auto&quot;&gt;useDialog&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;useDialog&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/arc.react/dialogs&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;RegisterAuthorDialog&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;./Registration/RegisterAuthorDialog&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AuthorsPage&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;./Listing/AuthorsPage&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Authors&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;RegistrationDialog&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;showRegistrationDialog&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;useDialog&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;RegisterAuthorDialog&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;AuthorsPage&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;onRegister&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;{()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;showRegistrationDialog&lt;/span&gt;&lt;span&gt;&lt;span&gt;();&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;RegistrationDialog&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The wrapper from Arc’s &lt;code dir=&quot;auto&quot;&gt;useDialog&lt;/code&gt; renders once, and the function next to it opens the dialog and resolves with a &lt;code dir=&quot;auto&quot;&gt;DialogResult&lt;/code&gt; and an optional response when it closes.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;dialogs-and-notifications&quot;&gt;Dialogs and notifications&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Components’ &lt;a href=&quot;https://cratis.io/components/dialogs/&quot;&gt;dialog guide&lt;/a&gt; sorts the choices by what the user does, and &lt;a href=&quot;https://blog.cratis.io/live-uis-with-arc/&quot;&gt;&lt;em&gt;Live UIs&lt;/em&gt;&lt;/a&gt; covers the choice between &lt;code dir=&quot;auto&quot;&gt;CommandDialog&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Dialog&lt;/code&gt;. &lt;code dir=&quot;auto&quot;&gt;StepperCommandDialog&lt;/code&gt; runs a command after several steps. A yes-or-no question goes through &lt;code dir=&quot;auto&quot;&gt;useConfirmationDialog&lt;/code&gt; and renders as &lt;code dir=&quot;auto&quot;&gt;ConfirmationDialog&lt;/code&gt;, and a blocking spinner goes through &lt;code dir=&quot;auto&quot;&gt;useBusyIndicator&lt;/code&gt; and renders as &lt;code dir=&quot;auto&quot;&gt;BusyIndicatorDialog&lt;/code&gt;. On a &lt;code dir=&quot;auto&quot;&gt;Dialog&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;placement=&apos;start&apos;&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;&apos;end&apos;&lt;/code&gt; turns it into a side panel.&lt;/p&gt;
&lt;p&gt;Anything the user doesn’t have to answer is a toast. With &lt;code dir=&quot;auto&quot;&gt;toaster&lt;/code&gt; set on the provider, &lt;code dir=&quot;auto&quot;&gt;toast.success&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;toast.warn&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;toast.error&lt;/code&gt; and the rest can be called from components and from plain modules. Outside a dialog, &lt;a href=&quot;https://cratis.io/components/notifications/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;toastCommandResult&lt;/code&gt;&lt;/a&gt; turns a command result into the matching toast:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;execute&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;toastCommandResult&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; successTitle&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Author registered&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}))&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;close&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Success gives a success toast, a rejected authorization a warning, and a failed validation an error toast that lists the validation messages. Exceptions get a generic error toast, and their messages and stack traces are left for you to log. Components runs its own toast queue and region, because React Aria’s toast API isn’t stable.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;tokens-parts-and-layers&quot;&gt;Tokens, parts and layers&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The three stylesheets are separate on purpose. &lt;code dir=&quot;auto&quot;&gt;tokens&lt;/code&gt; defines the semantic &lt;code dir=&quot;auto&quot;&gt;--cratis-*&lt;/code&gt; variables with light defaults. &lt;code dir=&quot;auto&quot;&gt;styles&lt;/code&gt; is the component structure, in low-priority cascade layers, with no reset and no copy of the tokens. &lt;code dir=&quot;auto&quot;&gt;theme&lt;/code&gt; is optional and adds dark mode, automatic through &lt;code dir=&quot;auto&quot;&gt;prefers-color-scheme&lt;/code&gt; or explicit with a &lt;code dir=&quot;auto&quot;&gt;cratis-dark&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;cratis-light&lt;/code&gt; class, as well as forced-colors support and independently themed subtrees.&lt;/p&gt;
&lt;p&gt;A product with its own design system leaves out &lt;code dir=&quot;auto&quot;&gt;theme&lt;/code&gt; and maps its values onto the Cratis tokens:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;root&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;--cratis-primary-color&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--product-accent&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;--cratis-surface-card&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--product-surface&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;--cratis-text-color&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--product-text&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;--cratis-focus-ring&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--product-focus-ring&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;For one component, &lt;a href=&quot;https://cratis.io/components/styling/pass-through/&quot;&gt;stable parts&lt;/a&gt; are the hook. Documented parts carry a &lt;code dir=&quot;auto&quot;&gt;data-cratis-part&lt;/code&gt; attribute, and components that support it take a typed &lt;code dir=&quot;auto&quot;&gt;pt&lt;/code&gt; prop with ordinary HTML attributes per part, such as a &lt;code dir=&quot;auto&quot;&gt;className&lt;/code&gt; for a dialog’s &lt;code dir=&quot;auto&quot;&gt;root&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;backdrop&lt;/code&gt;. React Aria’s class names and the undocumented DOM around the parts aren’t styling hooks, and Components tells you not to target them.&lt;/p&gt;
&lt;p&gt;In a Tailwind host such as the template, the layer order works against &lt;code dir=&quot;auto&quot;&gt;pt&lt;/code&gt;. &lt;code dir=&quot;auto&quot;&gt;styles&lt;/code&gt; declares its layers as &lt;code dir=&quot;auto&quot;&gt;cratis-theme&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;cratis-components&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;cratis-utilities&lt;/code&gt;, and cascade layers rank in the order they’re first declared. Tailwind declares its own layers first, so the Cratis layers land after them and a component’s own rule outranks a Tailwind utility you pass through &lt;code dir=&quot;auto&quot;&gt;pt&lt;/code&gt;. &lt;a href=&quot;https://cratis.io/components/styling/&quot;&gt;Components’ styling guide&lt;/a&gt; fixes the order with one statement parsed before any stylesheet:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;layer&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; properties, theme, base, cratis-theme, cratis-components, cratis-utilities, components, utilities&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;An application that uses a few surfaces can import &lt;code dir=&quot;auto&quot;&gt;@cratis/components/styles/base&lt;/code&gt; and one stylesheet per subpath, such as &lt;code dir=&quot;auto&quot;&gt;@cratis/components/DataPage/styles&lt;/code&gt;, in place of the whole &lt;code dir=&quot;auto&quot;&gt;styles&lt;/code&gt; bundle.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;swapping-the-primitives&quot;&gt;Swapping the primitives&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;By default, Components renders everything itself. Three optional packages, &lt;a href=&quot;https://cratis.io/components/renderers/&quot;&gt;renderer adapters&lt;/a&gt;, let a set of primitives come from another library, one for MUI 9, one for PrimeReact 11 and one for PrimeReact 10 from 10.9.9. You pick one with the provider’s &lt;code dir=&quot;auto&quot;&gt;library&lt;/code&gt; prop:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CratisComponentsProvider&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/components&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;muiUiLibrary&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@cratis/components.mui&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Application&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CratisComponentsProvider&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;span&gt; locale&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;en-US&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;library&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;muiUiLibrary&lt;/span&gt;&lt;span&gt;}&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;Application content&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;CratisComponentsProvider&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Each adapter covers the same nine slots: button, icon button, text input, text area, checkbox, radio, switch, progress and surface. Tooltips, dropdowns, dialogs, date pickers and the paginator stay with Components, and so do the composites. Under the MUI adapter, a &lt;code dir=&quot;auto&quot;&gt;DataPage&lt;/code&gt; is still a &lt;code dir=&quot;auto&quot;&gt;DataPage&lt;/code&gt;, and only its buttons and other primitives can come from MUI. A slot the adapter doesn’t declare falls back to the built-in one without a warning, and &lt;code dir=&quot;auto&quot;&gt;rendererFallback=&apos;throw&apos;&lt;/code&gt; turns that into an error. Each adapter release requires exactly its own version of &lt;code dir=&quot;auto&quot;&gt;@cratis/components&lt;/code&gt;, so the two upgrade together. The PrimeReact 11 adapter needs the application’s own &lt;code dir=&quot;auto&quot;&gt;PrimeReactProvider&lt;/code&gt; with the application’s license key, and installing the adapter doesn’t move key handling into it.&lt;/p&gt;
&lt;p&gt;When a screen needs what a vendor grid does, such as grouping, row expansion, inline editing, virtualization or sorting controlled on the server, &lt;a href=&quot;https://cratis.io/components/renderers/unsupported/&quot;&gt;the adapter guide&lt;/a&gt; points to a custom composition next to Components, and not to anything inside &lt;code dir=&quot;auto&quot;&gt;DataPage&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-a-view-model-fits&quot;&gt;Where a view model fits&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Everything above takes props, so it works the same inside a view model’s view. Arc’s &lt;a href=&quot;https://cratis.io/arc/frontend/react/mvvm/&quot;&gt;MVVM package&lt;/a&gt;, &lt;code dir=&quot;auto&quot;&gt;@cratis/arc.react.mvvm&lt;/code&gt;, keeps screen behavior in a class that &lt;code dir=&quot;auto&quot;&gt;withViewModel&lt;/code&gt; binds to a component, and &lt;a href=&quot;https://blog.cratis.io/live-uis-with-arc/&quot;&gt;&lt;em&gt;Live UIs&lt;/em&gt;&lt;/a&gt; walks through it with a Components dialog. The template doesn’t install the package. Arc’s advice is plain hooks unless a screen has substantial behavior, and a list with a register button doesn’t.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;upgrading-from-components-3&quot;&gt;Upgrading from Components 3&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Components 3 to 4 is a breaking upgrade. Root namespace imports, rendered markup, style parts and the provider changed. The &lt;a href=&quot;https://cratis.io/components/migration/3-to-4/&quot;&gt;migration guide&lt;/a&gt; comes with &lt;code dir=&quot;auto&quot;&gt;@cratis/components.migrator&lt;/code&gt;, a development CLI that rewrites imports and a few props, and &lt;code dir=&quot;auto&quot;&gt;@cratis/eslint-plugin-components&lt;/code&gt;, which stops the old imports from coming back.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-components-stops&quot;&gt;Where Components stops&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The contract is React in a browser. There’s no React Native, Vue, Angular or framework-neutral target.&lt;/p&gt;
&lt;p&gt;Sorting and filtering in the tables work on the page that’s loaded, and the table doesn’t forward its state to the server for you. A filter over the whole result is a query argument that the server applies before paging.&lt;/p&gt;
&lt;p&gt;The repository runs automated checks on its built-in renderer, including axe, form and event behavior in jsdom, and server rendering. Those checks aren’t an accessibility conformance result, and they don’t replace a review with assistive technology in your application. Some generated labels and plural text are still outside the provider’s message set, so the provider can’t translate every string a page shows.&lt;/p&gt;
&lt;p&gt;Arc React’s published type declarations report TS2503 errors when an application type-checks libraries with &lt;code dir=&quot;auto&quot;&gt;skipLibCheck: false&lt;/code&gt;. The template sets &lt;code dir=&quot;auto&quot;&gt;skipLibCheck: true&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href=&quot;https://blog.cratis.io/live-uis-with-arc/&quot;&gt;Part 08 — Live UIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next: &lt;a href=&quot;https://blog.cratis.io/tenancy-and-identity-end-to-end/&quot;&gt;Part 10 — Tenancy and identity end to end&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>components</category><category>react</category><category>arc</category><category>typescript</category><category>frontend</category></item><item><title>From script to stage · Part 08: Live UIs</title><link>https://blog.cratis.io/live-uis-with-arc/</link><guid isPermaLink="true">https://blog.cratis.io/live-uis-with-arc/</guid><description>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.</description><pubDate>Wed, 30 Sep 2026 09:08:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://blog.cratis.io/from-the-edge-to-the-event-log/&quot;&gt;Cratis: from script to stage, and the long run&lt;/a&gt; · Part 08 of 26&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In Arc, the difference between a query that loads a list once and a query that keeps pushing new results to a React screen is the return type of one C# method, as long as the data it returns comes from a source that notifies on change. Return a list and the browser gets a snapshot. Return an &lt;code dir=&quot;auto&quot;&gt;ISubject&lt;/code&gt; of that list and the generated hook subscribes, and the server pushes later results to it over Server-Sent Events or WebSocket. The C# record stays the same. The generator writes an observable query class instead of a plain one, and the component still calls &lt;code dir=&quot;auto&quot;&gt;.use()&lt;/code&gt;, whose tuple is &lt;code dir=&quot;auto&quot;&gt;[result, setSorting]&lt;/code&gt; instead of &lt;code dir=&quot;auto&quot;&gt;[result, perform, setSorting]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The author screen needs two things: a list of authors that shows a new one without a reload, and a form that registers one. Both sit on the TypeScript that Arc generates from the backend, and both are about the same question, which is what the browser knows and when it learns it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-live-query-is-a-return-type&quot;&gt;A live query is a return type&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A C# &lt;a href=&quot;https://cratis.io/arc/backend/csharp/queries/model-bound/&quot;&gt;model-bound query&lt;/a&gt; is a static method on a &lt;code dir=&quot;auto&quot;&gt;[ReadModel]&lt;/code&gt; record. Returning &lt;code dir=&quot;auto&quot;&gt;ISubject&amp;#x3C;IEnumerable&amp;#x3C;T&gt;&gt;&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;ISubject&amp;#x3C;T&gt;&lt;/code&gt; for one item, makes it an &lt;a href=&quot;https://cratis.io/arc/backend/csharp/queries/model-bound/observable-queries/&quot;&gt;observable query&lt;/a&gt;. The Ideas board in the Cratis Samples repository keeps its ideas in memory, in one store for the whole process and not one per tenant, and observes them:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-0&quot; id=&quot;tab-0-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-1&quot; id=&quot;tab-0-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-2&quot; id=&quot;tab-0-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-3&quot; id=&quot;tab-0-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-0-4&quot; id=&quot;tab-0-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-0&quot; aria-labelledby=&quot;tab-0-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;ReadModel&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AllowAnonymous&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IdeaId&lt;/span&gt;&lt;span&gt;&lt;span&gt; Id&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;IdeaTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt; Title&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;IdeaSummary&lt;/span&gt;&lt;span&gt;&lt;span&gt; Summary&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ISubject&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IEnumerable&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ObserveIdeas&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IdeaStore&lt;/span&gt;&lt;span&gt;&lt;span&gt; store&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;store&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Observe&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sealed&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IdeaStore&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;IDisposable&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Lock&lt;/span&gt;&lt;span&gt; _gate &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BehaviorSubject&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IEnumerable&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;span&gt; _ideas &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;([]);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Capture&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;&lt;span&gt; idea&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;lock&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;_gate&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;_ideas&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;OnNext&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;idea&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;..&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_ideas&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ISubject&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IEnumerable&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Observe&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_ideas&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Dispose&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_ideas&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Dispose&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-1&quot; aria-labelledby=&quot;tab-0-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;Arc for Kotlin returns a &lt;code dir=&quot;auto&quot;&gt;Flow&lt;/code&gt; from the query, and a &lt;code dir=&quot;auto&quot;&gt;MutableStateFlow&lt;/code&gt; holds the current value for snapshot reads.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@ReadModel&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@AllowAnonymous&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; id: &lt;/span&gt;&lt;span&gt;IdeaId&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; title: &lt;/span&gt;&lt;span&gt;IdeaTitle&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; summary: &lt;/span&gt;&lt;span&gt;IdeaSummary&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;companion&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;@JvmStatic&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;observeIdeas&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;@FromServices&lt;/span&gt;&lt;span&gt; store: &lt;/span&gt;&lt;span&gt;IdeaStore&lt;/span&gt;&lt;span&gt;): &lt;/span&gt;&lt;span&gt;Flow&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;&gt;&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;observe&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@Component&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IdeaStore&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; ideas &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MutableStateFlow&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;&gt;&gt;(&lt;/span&gt;&lt;span&gt;emptyList&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;capture&lt;/span&gt;&lt;span&gt;(idea: &lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ideas.&lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;listOf&lt;/span&gt;&lt;span&gt;(idea) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; it }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;observe&lt;/span&gt;&lt;span&gt;(): &lt;/span&gt;&lt;span&gt;Flow&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;&gt;&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ideas&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-2&quot; aria-labelledby=&quot;tab-0-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;Arc for Java returns a JDK &lt;code dir=&quot;auto&quot;&gt;Flow.Publisher&lt;/code&gt; from the query, and &lt;code dir=&quot;auto&quot;&gt;ObservableState&lt;/code&gt; holds the current value for snapshot reads.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;ReadModel&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;AllowAnonymous&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IdeaId&lt;/span&gt;&lt;span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;IdeaTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt; title&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;IdeaSummary&lt;/span&gt;&lt;span&gt;&lt;span&gt; summary&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Flow&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Publisher&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;observeIdeas&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;/span&gt;&lt;span&gt;FromServices&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IdeaStore&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;store&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;store&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;observe&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Component&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;final&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IdeaStore&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;final&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ObservableState&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ideas&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ObservableState&lt;/span&gt;&lt;span&gt;&amp;#x3C;&gt;(&lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;of&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;synchronized&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;capture&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;idea&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;updated&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ArrayList&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;updated&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;idea&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;updated&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;addAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ideas&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ideas&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;copyOf&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;updated&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Flow&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Publisher&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;observe&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; ideas&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-3&quot; aria-labelledby=&quot;tab-0-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;Arc for TypeScript returns an RxJS &lt;code dir=&quot;auto&quot;&gt;Observable&lt;/code&gt; from a &lt;code dir=&quot;auto&quot;&gt;@query({ observable: true })&lt;/code&gt; method, and a &lt;code dir=&quot;auto&quot;&gt;BehaviorSubject&lt;/code&gt; holds the current value.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IdeaStore&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;#ideas&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BehaviorSubject&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;[]&gt;([]);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;capture&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;idea&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;#ideas&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;next&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;idea&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;#ideas&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;observe&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Observable&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;&lt;span&gt;[]&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;#ideas&lt;/span&gt;&lt;span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;readModel&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;allowAnonymous&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IdeaId&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IdeaId&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IdeaTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IdeaTitle&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IdeaSummary&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;summary&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IdeaSummary&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt; observable&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;service&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IdeaStore&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;observeIdeas&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;store&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IdeaStore&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Observable&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;&lt;span&gt;[]&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;store&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;observe&lt;/span&gt;&lt;span&gt;&lt;span&gt;();&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-0-4&quot; aria-labelledby=&quot;tab-0-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Arc for Elixir doesn’t exist; use the Chronicle Elixir client directly.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;In .NET, &lt;code dir=&quot;auto&quot;&gt;Task&amp;#x3C;ISubject&amp;#x3C;T&gt;&gt;&lt;/code&gt; is allowed as well, and an arbitrary &lt;code dir=&quot;auto&quot;&gt;IObservable&amp;#x3C;T&gt;&lt;/code&gt; isn’t something Arc discovers as a query. You can compose with Rx operators such as &lt;code dir=&quot;auto&quot;&gt;Select&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;CombineLatest&lt;/code&gt;, as long as the subscription you return releases every upstream subscription when it’s disposed and passes terminal errors on. The direct transports dispose the subscription they receive.&lt;/p&gt;
&lt;p&gt;The return type alone doesn’t open a connection. The client decides whether it wants a one-off snapshot, a direct SSE or WebSocket connection, or a subscription on a shared hub, and the default is the hub.&lt;/p&gt;
&lt;p&gt;For &lt;code dir=&quot;auto&quot;&gt;ObserveIdeas&lt;/code&gt;, the proxy generator writes an &lt;code dir=&quot;auto&quot;&gt;ObservableQueryFor&amp;#x3C;Idea[]&gt;&lt;/code&gt; class. A parameterless query over a list gets a &lt;code dir=&quot;auto&quot;&gt;use(sorting?)&lt;/code&gt; hook that returns the result and a function to change the sorting. It also gets &lt;code dir=&quot;auto&quot;&gt;useWithPaging&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;useSuspense&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;useSuspenseWithPaging&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;useChangeStream&lt;/code&gt; and a &lt;code dir=&quot;auto&quot;&gt;when(condition)&lt;/code&gt; helper. Rename a property in C#, rebuild, and the generated types and every field accessor that used the old name stop compiling until you fix them, the same way commands behave in &lt;a href=&quot;https://blog.cratis.io/arc-commands-and-queries/&quot;&gt;&lt;em&gt;Arc: commands and queries without the plumbing&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Arc’s &lt;a href=&quot;https://cratis.io/arc/tutorial/real-time/&quot;&gt;real-time tutorial&lt;/a&gt; includes a two-tab test. Open the app in two browser tabs connected to the same backend process, add a book in one, and the other updates without a reload. That test covers the tutorial’s own path. A write from a second backend process is a separate case, and it depends on where the data lives.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/app-40-observable-query-live.D-hzqlS4_1rhkML.webp&quot; alt=&quot;Screen recording of two windows showing the same name list. In the left window a name is typed into the Register dialog. As soon as it is submitted, the new row appears in the right window too, without a reload. A second name follows the same way.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1200&quot; height=&quot;470&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The same check on the Cratis web template’s name list, with two windows side by side.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;from-a-write-to-the-screen&quot;&gt;From a write to the screen&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The samples show two paths, and they differ in what sits between the command and the subject the query returns.&lt;/p&gt;
&lt;p&gt;In the .NET Ideas board there’s no event store. The command’s &lt;code dir=&quot;auto&quot;&gt;Handle(IdeaStore store)&lt;/code&gt; calls &lt;code dir=&quot;auto&quot;&gt;store.Capture(...)&lt;/code&gt;, which pushes a new list into the &lt;code dir=&quot;auto&quot;&gt;BehaviorSubject&lt;/code&gt;. The query returns that same subject, so every subscriber gets the new list, the hub sends it to the browser and the hook re-renders. The command writes to a current-state store, and the query observes that same store.&lt;/p&gt;
&lt;p&gt;With Chronicle and MongoDB, the chain is longer. In .NET, this is the author list from Cratis’s &lt;a href=&quot;https://cratis.io/build-a-full-app/&quot;&gt;full-app walkthrough&lt;/a&gt;, with the name typed as &lt;code dir=&quot;auto&quot;&gt;AuthorName&lt;/code&gt;:&lt;/p&gt;
&lt;starlight-tabs data-sync-key=&quot;language&quot;&gt;&lt;div&gt;&lt;ul role=&quot;tablist&quot;&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-0&quot; id=&quot;tab-1-0&quot; aria-selected=&quot;true&quot; tabindex=&quot;0&quot;&gt;C#&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-1&quot; id=&quot;tab-1-1&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-2&quot; id=&quot;tab-1-2&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Java&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-3&quot; id=&quot;tab-1-3&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;&lt;li role=&quot;presentation&quot;&gt;&lt;a role=&quot;tab&quot; href=&quot;#tab-panel-1-4&quot; id=&quot;tab-1-4&quot; aria-selected=&quot;false&quot; tabindex=&quot;-1&quot;&gt;Elixir&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-0&quot; aria-labelledby=&quot;tab-1-0&quot; role=&quot;tabpanel&quot;&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;ReadModel&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;FromEvent&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Author&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;&lt;span&gt; Id&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;&lt;span&gt; Name&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ISubject&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IEnumerable&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Author&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AllAuthors&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IMongoCollection&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Author&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; collection&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;collection&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Observe&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-1&quot; aria-labelledby=&quot;tab-1-1&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;Arc for Kotlin and Java observe the collection with &lt;code dir=&quot;auto&quot;&gt;MongoObservableQuery&lt;/code&gt;, which resumes its MongoDB change stream after transient failures.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@ReadModel&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@io.cratis.chronicle.readModels.ReadModel&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@FromEvent&lt;/span&gt;&lt;span&gt;(AuthorRegistered::&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Author&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;@FromEventSourceId&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; id: &lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;UUID&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; name: &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;companion&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;@JvmStatic&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allAuthors&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;@FromServices&lt;/span&gt;&lt;span&gt; queries: &lt;/span&gt;&lt;span&gt;MongoObservableQuery&lt;/span&gt;&lt;span&gt;): &lt;/span&gt;&lt;span&gt;Flow&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Author&lt;/span&gt;&lt;span&gt;&gt;&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;queries.&lt;/span&gt;&lt;span&gt;observe&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Author&lt;/span&gt;&lt;span&gt;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-2&quot; aria-labelledby=&quot;tab-1-2&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;Arc for Kotlin and Java observe the collection with &lt;code dir=&quot;auto&quot;&gt;MongoObservableQuery&lt;/code&gt;, which resumes its MongoDB change stream after transient failures.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;ReadModel&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;readModels&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ReadModel&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;FromEvent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;eventType&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Author&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;FromEventSourceId&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;UUID&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Flow&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Publisher&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Author&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;allAuthors&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;/span&gt;&lt;span&gt;FromServices&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MongoObservableQuery&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;queries&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;queries&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;observePublisher&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Author&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-3&quot; aria-labelledby=&quot;tab-1-3&quot; role=&quot;tabpanel&quot; hidden&gt;&lt;p&gt;In Arc for TypeScript, &lt;code dir=&quot;auto&quot;&gt;ChronicleReadModels&lt;/code&gt; watches the read model through Chronicle. &lt;code dir=&quot;auto&quot;&gt;mongoCollection(...).observe()&lt;/code&gt; is the MongoDB change-stream form.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;readModel&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;fromEvent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AuthorRegistered&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Author&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorId&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;!:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuthorName&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt; observable&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;service&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ChronicleReadModels&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allAuthors&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;models&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChronicleReadModels&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Observable&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Author&lt;/span&gt;&lt;span&gt;&lt;span&gt;[]&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;observeAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Author&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/div&gt;&lt;div id=&quot;tab-panel-1-4&quot; aria-labelledby=&quot;tab-1-4&quot; role=&quot;tabpanel&quot; tabindex=&quot;0&quot; hidden&gt;&lt;p&gt;Arc for Elixir doesn’t exist; use the Chronicle Elixir client directly.&lt;/p&gt;&lt;/div&gt;&lt;starlight-tabs-restore&gt;&lt;/starlight-tabs-restore&gt;&lt;/starlight-tabs&gt;
&lt;p&gt;The command returns an &lt;code dir=&quot;auto&quot;&gt;AuthorRegistered&lt;/code&gt; event and Chronicle appends it. In .NET, the projection declared by &lt;code dir=&quot;auto&quot;&gt;[FromEvent&amp;#x3C;AuthorRegistered&gt;]&lt;/code&gt; writes the &lt;code dir=&quot;auto&quot;&gt;Author&lt;/code&gt; document to MongoDB. &lt;code dir=&quot;auto&quot;&gt;collection.Observe()&lt;/code&gt; sees that document change through a MongoDB change stream, and Arc pushes the new result to every subscribed client.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/the-live-hop.m3OYDcZc_ZWOOPS.webp&quot; alt=&quot;A diagram titled The live hop, seven numbered cards in two rows joined by arrows. 1 Command, Arc. 2 Event appended, Chronicle. 3 Projection, declared by you and run by Chronicle, writes to MongoDB. The arrow from 3 turns down to the second row and is labeled eventually consistent. 4 Change stream, Arc Observe. 5 SSE hub at slash dot cratis slash queries slash sse. 6 The use hook. 7 Screen. A band at the bottom reads: You write steps 1 and 6 and the query’s ISubject return type. Arc and Chronicle run steps 2 to 5.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Chronicle path to the screen. The UI observes the document, not the event log.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In .NET, &lt;code dir=&quot;auto&quot;&gt;Observe()&lt;/code&gt; is database observation. It watches the MongoDB collection the projection writes to, and it isn’t a Chronicle projection or a Chronicle API. Change streams need a MongoDB replica set or a supported sharded deployment. If the MongoDB watcher fails, Arc logs the failure and completes the stream instead of raising an error, so a stream that completed doesn’t prove the data is current. Arc for Kotlin and Java observe the collection with &lt;code dir=&quot;auto&quot;&gt;MongoObservableQuery&lt;/code&gt;, which resumes its MongoDB change stream after transient failures, and Arc for TypeScript’s experimental Chronicle integration watches the read model through Chronicle.&lt;/p&gt;
&lt;p&gt;The read model catches up after the append, asynchronously. A command that returns success has recorded the event. The projection may not have written the document yet, and the screen may not have changed yet either. There’s nothing to refresh by hand, and also nothing that promises the list has updated by the time the command’s promise resolves.&lt;/p&gt;
&lt;p&gt;For EF Core in .NET, an observed &lt;code dir=&quot;auto&quot;&gt;DbSet&lt;/code&gt; gets in-process notifications from Arc’s &lt;code dir=&quot;auto&quot;&gt;SaveChanges&lt;/code&gt; interceptor. SQLite relies on that path only. SQL Server and PostgreSQL can pick up writes from other processes once their database notification setup is in place. If that setup fails, EF observation can fall back to in-process only. Without a notification source, an observable query can stay connected and never send fresh data.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-the-result-travels&quot;&gt;How the result travels&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Two settings on &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;Arc&gt;&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;queryTransportMethod&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;queryDirectMode&lt;/code&gt;, choose the &lt;a href=&quot;https://cratis.io/arc/frontend/react/queries/observable-query-multiplexing/&quot;&gt;transport&lt;/a&gt;, and they’re independent of each other:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Transport&lt;/th&gt;
&lt;th&gt;Direct mode&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SSE (React default)&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;One shared EventSource at &lt;code dir=&quot;auto&quot;&gt;/.cratis/queries/sse&lt;/code&gt;; subscribe and unsubscribe are POSTs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebSocket&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;One shared socket at &lt;code dir=&quot;auto&quot;&gt;/.cratis/queries/ws&lt;/code&gt;; subscribe and unsubscribe are socket messages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSE or WebSocket&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;One connection per query URL, bypassing the hub&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;On the SSE hub, &lt;code dir=&quot;auto&quot;&gt;GET /.cratis/queries/sse&lt;/code&gt; answers with a &lt;code dir=&quot;auto&quot;&gt;connected&lt;/code&gt; message and a connection id. The client then POSTs each subscription with the connection id, a subscription id, the query name and its arguments, and results come back tagged with the subscription id. Many subscriptions share one EventSource, and the client handles reconnecting and subscribing again.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;Arc&gt;&lt;/code&gt; defaults to the SSE hub with one connection slot. SSE hub connections are capped at four, so that the browser’s per-origin connection limit under HTTP/1.1 still leaves room for the control POSTs. HTTP/2 doesn’t lift Arc’s cap, and direct SSE connections are outside it. The multiplexer is module-global, shared by every &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;Arc&gt;&lt;/code&gt; on the page, so nesting a second &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;Arc&gt;&lt;/code&gt; doesn’t give you an isolated hub.&lt;/p&gt;
&lt;p&gt;An observable query is also an ordinary &lt;code dir=&quot;auto&quot;&gt;GET&lt;/code&gt;. If the observable hasn’t emitted yet, the server answers 202 with &lt;code dir=&quot;auto&quot;&gt;isReady: false&lt;/code&gt;. For MongoDB’s &lt;code dir=&quot;auto&quot;&gt;Observe()&lt;/code&gt;, a snapshot without &lt;code dir=&quot;auto&quot;&gt;waitForFirstResult&lt;/code&gt; always answers 202 and can leave its watcher running, so use &lt;code dir=&quot;auto&quot;&gt;waitForFirstResult=true&lt;/code&gt; there. &lt;code dir=&quot;auto&quot;&gt;waitForFirstResult=true&lt;/code&gt; makes it wait for the first emission, 30 seconds by default, adjustable with &lt;code dir=&quot;auto&quot;&gt;waitForFirstResultTimeout&lt;/code&gt;, and a timeout is a 408. HTTP snapshots bypass read-model interception and emission guards, so masking or guarding that only happens on the streaming path doesn’t protect the snapshot endpoint.&lt;/p&gt;
&lt;p&gt;For collection subscriptions on the hub, each item needs a stable, unique &lt;code dir=&quot;auto&quot;&gt;Id&lt;/code&gt; for Arc to send change sets in &lt;code dir=&quot;auto&quot;&gt;delta&lt;/code&gt; mode. Without one, Arc sends the whole collection every time and logs a warning once per subscription. When something looks wrong, &lt;code dir=&quot;auto&quot;&gt;arc.observableQueryDiagnostics.getSnapshot()&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;snapshots$&lt;/code&gt; show the transport, the multiplexer’s state, the cache and a health summary.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;one-hook-and-its-states&quot;&gt;One hook and its states&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The board’s React component is short. Adapted from the sample, without its header and styling, it reads:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Board&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;withViewModel&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BoardViewModel&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;viewModel&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;ideasResult&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ObserveIdeas&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;CaptureDialog&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;showCaptureDialog&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;useDialog&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CaptureIdeaDialog&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ideas&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;viewModel&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ideasResult&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[]);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Button&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Capture idea&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;{()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;showCaptureDialog&lt;/span&gt;&lt;span&gt;&lt;span&gt;();&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;viewModel&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;searchTerm&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                   &lt;/span&gt;&lt;span&gt;onChange&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;viewModel&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setSearchTerm&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;&lt;span&gt;)}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;ideasResult&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;isPerforming&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;ideasResult&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;hasData&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;Connecting to the live board…&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;ideas&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;idea&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;IdeaCard&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;idea&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;&lt;span&gt;()}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;idea&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;idea&lt;/span&gt;&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;sequence&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&gt;)}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CaptureDialog&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;ObserveIdeas.use()&lt;/code&gt; returns a tuple, &lt;code dir=&quot;auto&quot;&gt;[result, setSorting]&lt;/code&gt;, and an observable query for a single item returns &lt;code dir=&quot;auto&quot;&gt;[result]&lt;/code&gt;. The first line destructures it and keeps the result, which is the element the component reads.&lt;/p&gt;
&lt;p&gt;The result carries &lt;code dir=&quot;auto&quot;&gt;data&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;isSuccess&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;isReady&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;isAuthorized&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;isValid&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;hasExceptions&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;paging&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;isPerforming&lt;/code&gt;. Two of those are easy to confuse. &lt;code dir=&quot;auto&quot;&gt;isReady&lt;/code&gt; says a result has been produced. &lt;code dir=&quot;auto&quot;&gt;isPerforming&lt;/code&gt; says the client is fetching or subscribing right now. The initial state isn’t ready, and &lt;code dir=&quot;auto&quot;&gt;isReady: false&lt;/code&gt; after a 202 is transient and isn’t a failure. The board shows its “Connecting” line only while it’s performing and has no data yet.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;ObserveSingle&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;ObserveById&lt;/code&gt; emit &lt;code dir=&quot;auto&quot;&gt;null&lt;/code&gt; when the document is gone, including when it was removed through &lt;code dir=&quot;auto&quot;&gt;[RemovedWith&amp;#x3C;T&gt;]&lt;/code&gt;, and the subscription stays open. Guard with &lt;code dir=&quot;auto&quot;&gt;hasData&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;isReady&lt;/code&gt; before you read a property.&lt;/p&gt;
&lt;p&gt;Each &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;Arc&gt;&lt;/code&gt; mount keeps a &lt;a href=&quot;https://cratis.io/arc/frontend/react/queries/query-instance-caching/&quot;&gt;query instance cache&lt;/a&gt;, keyed by the query name and its arguments. Components that use the same query with the same arguments share one subscription. When the last of them unmounts, the subscription stays open for &lt;code dir=&quot;auto&quot;&gt;queryCacheRetentionMs&lt;/code&gt;, 30,000 ms by default, so going back to a screen doesn’t start from empty. It isn’t an isolation boundary between users.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cratis.io/arc/frontend/react/queries/change-stream/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;useChangeStream(getKey)&lt;/code&gt;&lt;/a&gt; gives you what was added, replaced and removed since the last result. Arc computes it by comparing snapshots, on the server or in the client, so a write that happened between two emissions and was overwritten before the second one can be missing from the delta. It’s a view of the difference between results. When every operation matters, read the event log. A reconnect creates a new subscription, and it doesn’t resume from where the old one stopped.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-subscription-that-stays-open&quot;&gt;A subscription that stays open&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Authorization for an observable query runs once, when the subscription is established. The attributes from &lt;a href=&quot;https://blog.cratis.io/arc-commands-and-queries/&quot;&gt;&lt;em&gt;Arc: commands and queries without the plumbing&lt;/em&gt;&lt;/a&gt; are checked then, and the verdict doesn’t expire on its own when a token does or when a role is revoked. Keep-alive pings stop proxies from dropping the connection, and the client reconnects by itself, so a subscription can outlive the session that opened it.&lt;/p&gt;
&lt;p&gt;For re-checking during a long subscription, Arc has &lt;a href=&quot;https://cratis.io/arc/backend/csharp/queries/observable-query-emission-guards/&quot;&gt;emission guards&lt;/a&gt;. A class implementing &lt;code dir=&quot;auto&quot;&gt;IGuardObservableQueryEmission&lt;/code&gt; is discovered by convention, and Arc asks it about every emission on every transport, the hub, direct WebSocket and direct SSE alike. It answers &lt;code dir=&quot;auto&quot;&gt;Allow&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Suppress&lt;/code&gt; for a single emission, or &lt;code dir=&quot;auto&quot;&gt;DenyAndTerminate&lt;/code&gt;. Guards cover streaming emissions only, not the HTTP snapshot. On a WebSocket the principal is the one from the handshake. A guard that throws counts as &lt;code dir=&quot;auto&quot;&gt;DenyAndTerminate&lt;/code&gt;. A filter can capture a serializable &lt;code dir=&quot;auto&quot;&gt;QueryContext.SubscriptionScope&lt;/code&gt; after the filters succeed and before the query runs, and the guard gets a fresh copy of it for each emission. An application without a guard pays nothing per emission. Controller-based queries always get a null subscription scope.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-form&quot;&gt;The form&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;After each build, Arc’s &lt;a href=&quot;https://cratis.io/arc/backend/csharp/proxy-generation/&quot;&gt;proxy generator&lt;/a&gt; writes a TypeScript version of the command, and &lt;a href=&quot;https://cratis.io/components/&quot;&gt;Components&lt;/a&gt; binds a form to it. Following the Components &lt;a href=&quot;https://cratis.io/components/building-a-form/&quot;&gt;form recipe&lt;/a&gt;, the form looks like this. &lt;code dir=&quot;auto&quot;&gt;RegisterAuthor&lt;/code&gt; is the generated proxy class, not something you write:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CommandDialog&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Add author&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;okLabel&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Add&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;initialValues&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;validateOn&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;change&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;InputTextField&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;RegisterAuthor&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{(&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Name&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;placeholder&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Sample Author&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;CommandDialog&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The field accessor is typed against the command. Rename &lt;code dir=&quot;auto&quot;&gt;Name&lt;/code&gt; in C#, rebuild, and that line stops compiling. That’s why we generate the proxy at all, so drift between frontend and backend shows up as a compile error in your own build. &lt;strong&gt;Add&lt;/strong&gt; stays disabled until the validation rules the backend shared with the proxy pass, and server messages land on their fields.&lt;/p&gt;
&lt;p&gt;The generated validator is a partial copy, with the omissions and dropped conditions covered in &lt;a href=&quot;https://blog.cratis.io/validation-all-the-way-down/&quot;&gt;&lt;em&gt;Validation all the way down&lt;/em&gt;&lt;/a&gt;. Keep its output in a folder of its own; &lt;a href=&quot;https://blog.cratis.io/arc-gotchas/&quot;&gt;&lt;em&gt;Arc gotchas&lt;/em&gt;&lt;/a&gt; covers what a full regeneration deletes.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;what-commanddialog-does-for-you&quot;&gt;What CommandDialog does for you&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A &lt;a href=&quot;https://cratis.io/components/commanddialog/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;CommandDialog&lt;/code&gt;&lt;/a&gt; takes the command’s constructor. It creates the command, validates it and keeps the confirm button disabled until client validation passes. On confirm it runs the command and shows a busy state, with the buttons and fields disabled and a spinner. Server field errors land on their fields, and a success closes the dialog with &lt;code dir=&quot;auto&quot;&gt;DialogResult.Ok&lt;/code&gt;. Cancel, the close button, Escape and a click on the backdrop close it with &lt;code dir=&quot;auto&quot;&gt;Cancelled&lt;/code&gt; and fire no callback.&lt;/p&gt;
&lt;p&gt;The Ideas board’s dialog seeds a value that no field shows, the new idea’s id.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CaptureIdeaDialog&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CommandDialog&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CaptureIdea&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;CaptureIdea&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Capture an idea&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;okLabel&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Add to board&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;initialValues&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Guid&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;span&gt; title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; summary&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}}&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;InputTextField&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CaptureIdea&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;placeholder&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Make local setup self-explanatory&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;CommandDialog&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A generated proxy starts with every property unset, and a required &lt;code dir=&quot;auto&quot;&gt;Guid&lt;/code&gt; isn’t filled in for you. A value that no field renders, such as the new idea’s id, goes in &lt;code dir=&quot;auto&quot;&gt;initialValues&lt;/code&gt;, which counts toward validity. The sample calls &lt;code dir=&quot;auto&quot;&gt;Guid.create()&lt;/code&gt; inline, which makes a new id on every render. The &lt;code dir=&quot;auto&quot;&gt;CommandDialog&lt;/code&gt; guide recommends creating it once, for example with &lt;code dir=&quot;auto&quot;&gt;useState(() =&gt; Guid.create())&lt;/code&gt;. &lt;code dir=&quot;auto&quot;&gt;onBeforeExecute&lt;/code&gt; can transform values too, but it runs after confirm, which is too late to make an invalid command valid.&lt;/p&gt;
&lt;p&gt;With &lt;code dir=&quot;auto&quot;&gt;autoServerValidate&lt;/code&gt;, the dialog also calls the command’s &lt;code dir=&quot;auto&quot;&gt;/validate&lt;/code&gt; route once local checks have settled, after 500 ms by default. The option has &lt;code dir=&quot;auto&quot;&gt;Throttle&lt;/code&gt; in its name and behaves as a debounce. The preflight’s limits, including the name race it can’t prevent, are in &lt;a href=&quot;https://blog.cratis.io/validation-all-the-way-down/&quot;&gt;&lt;em&gt;Validation all the way down&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The rule for choosing is in Components’ own &lt;a href=&quot;https://cratis.io/components/choosing-a-component/&quot;&gt;guide&lt;/a&gt;. If confirming the dialog executes a generated command, use &lt;code dir=&quot;auto&quot;&gt;CommandDialog&lt;/code&gt;. If the dialog only gathers values and hands them back, use &lt;code dir=&quot;auto&quot;&gt;Dialog&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Arc also has &lt;a href=&quot;https://cratis.io/arc/frontend/react/dialogs/&quot;&gt;dialogs without Components&lt;/a&gt;. &lt;code dir=&quot;auto&quot;&gt;useDialog&amp;#x3C;TResponse, TInput&gt;(Component)&lt;/code&gt; returns three elements: the wrapper to render, a function that shows the dialog and the dialog’s context. Showing it resolves to a &lt;code dir=&quot;auto&quot;&gt;DialogResult&lt;/code&gt; and an optional response. &lt;code dir=&quot;auto&quot;&gt;DialogResult&lt;/code&gt; has &lt;code dir=&quot;auto&quot;&gt;None&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Ok&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Yes&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;No&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Cancelled&lt;/code&gt;. &lt;code dir=&quot;auto&quot;&gt;useConfirmationDialog&lt;/code&gt; resolves to a bare &lt;code dir=&quot;auto&quot;&gt;DialogResult&lt;/code&gt;. Arc draws no dialog itself, and &lt;code dir=&quot;auto&quot;&gt;DialogComponents&lt;/code&gt; is where you supply the confirmation and busy renderers. One dialog hook stores one pending result, so don’t open the same one twice at once.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;when-a-screen-has-behavior&quot;&gt;When a screen has behavior&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The board wraps its component in &lt;code dir=&quot;auto&quot;&gt;withViewModel&lt;/code&gt;. &lt;a href=&quot;https://cratis.io/arc/frontend/react/mvvm/using-view-model/&quot;&gt;Arc’s MVVM support&lt;/a&gt; makes the view model an &lt;code dir=&quot;auto&quot;&gt;@injectable()&lt;/code&gt; class from tsyringe, observable through MobX, and the view re-renders when a property it read changes.&lt;/p&gt;
&lt;p&gt;In the Ideas board, the whole view model is this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;injectable&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;tsyringe&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;./Idea&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;injectable&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BoardViewModel&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;searchTerm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;setSearchTerm&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;searchTerm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ideas&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;[])&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Idea&lt;/span&gt;&lt;span&gt;&lt;span&gt;[]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;normalizedSearch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;searchTerm&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;toLowerCase&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;normalizedSearch&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ideas&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ideas&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;idea&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;idea&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toLowerCase&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;normalizedSearch&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;idea&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;summary&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toLowerCase&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;normalizedSearch&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Filtering the list that’s already loaded is view behavior, so it lives in the view model, and a spec in the sample tests the filter by title and summary without rendering anything. The live data still comes from the hook in the component.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;withViewModel&lt;/code&gt; observes only the render of the component it wraps. A child component that reads observable view-model state directly sits outside that boundary and won’t re-render when the state changes. Pass plain values down as props, or wrap the child in &lt;code dir=&quot;auto&quot;&gt;observer()&lt;/code&gt; from &lt;code dir=&quot;auto&quot;&gt;@cratis/arc.react.mvvm&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A view model can ask the user something through &lt;code dir=&quot;auto&quot;&gt;IDialogs&lt;/code&gt;, with &lt;code dir=&quot;auto&quot;&gt;show&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;showConfirmation&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;showBusyIndicator&lt;/code&gt;, and the view registers the dialogs it can show. The &lt;a href=&quot;https://cratis.io/arc/frontend/react/mvvm/dialogs/&quot;&gt;MVVM &lt;code dir=&quot;auto&quot;&gt;useDialog&lt;/code&gt;&lt;/a&gt; takes a request type and a component and returns two elements, the wrapper and the function that shows it, which is one fewer than the base hook. Arc’s own advice is to stay with plain hooks unless a screen has substantial behavior.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;tables-and-pages&quot;&gt;Tables and pages&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;For the author list, a table is often the right screen. &lt;code dir=&quot;auto&quot;&gt;DataTableForObservableQuery&lt;/code&gt; from Components subscribes when it mounts and unsubscribes when it unmounts, and rows appear, change and disappear as the server pushes results:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LiveAuthors&lt;/span&gt;&lt;span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;span&gt; height&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;480px&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;}}&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;DataTableForObservableQuery&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;AllAuthors&lt;/span&gt;&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;emptyMessage&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;No authors yet&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dataKey&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Column&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;header&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Name&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sortable&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;DataTableForObservableQuery&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;AllAuthors&lt;/code&gt; is the generated proxy for the Chronicle-backed query above. The &lt;a href=&quot;https://cratis.io/components/datatables/data-table-for-observable-query/&quot;&gt;table&lt;/a&gt; needs a parent with a definite height, which is why the example wraps it in a 480-pixel box. Set &lt;code dir=&quot;auto&quot;&gt;dataKey&lt;/code&gt;, or the selection is lost when an update arrives. Server paging is 20 rows per page. Sorting, column filters and the global search all work on the page that’s loaded, so filtering across the whole result belongs in query arguments, before paging.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;DataPage&lt;/code&gt; puts a table like that into a page with an action bar and a resizable details panel. &lt;code dir=&quot;auto&quot;&gt;DataPage.MenuItems&lt;/code&gt; holds the actions, and &lt;code dir=&quot;auto&quot;&gt;disableOnUnselected&lt;/code&gt; ties an action to having a row selected. Components’ &lt;a href=&quot;https://cratis.io/components/list-screen-with-actions/&quot;&gt;list-screen recipe&lt;/a&gt; wires a live table to two &lt;code dir=&quot;auto&quot;&gt;CommandDialog&lt;/code&gt; actions, and with Chronicle the new row arrives once the projection has updated the read model. Grouping, row expansion and controlled server-side sorting aren’t part of the Components tables.&lt;/p&gt;
&lt;p&gt;Components needs React 19, and &lt;code dir=&quot;auto&quot;&gt;@cratis/arc&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;@cratis/arc.react&lt;/code&gt; in the range from 20.3.1 up to, but not including, 23.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.cratis.io/_astro/one-proxy-four-consumers.Ctaa5DBc_Z1kmJ7R.webp&quot; alt=&quot;A diagram titled One proxy, four consumers. At the top left, a C# query, ObserveIdeas, returning an ISubject of IEnumerable of Idea, with an arrow labeled build down to the generated ObserveIdeas class, an ObservableQueryFor of Idea array. Four arrows lead from it to the right: use, giving the list; useChangeStream, giving added, replaced and removed; DataTableForObservableQuery, giving live rows, 20 per page; and DataPage, giving a table, actions and a details panel. A note at the top right reads: Components needs React 19 and @cratis/arc from 20.3.1 up to, but not including, 23.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3200&quot; height=&quot;2400&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The same generated query class feeds a hook, a change stream, a table and a page.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;which-hop-is-live&quot;&gt;Which hop is live&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;For the author screen, the list and the form meet in one place. The form’s command appends &lt;code dir=&quot;auto&quot;&gt;AuthorRegistered&lt;/code&gt;, the projection writes the document, the change stream notices, and the hub pushes a new list to every tab that subscribed. The screen code is a hook call and a dialog, and on the backend it’s a return type and an &lt;code dir=&quot;auto&quot;&gt;Observe()&lt;/code&gt; call, over a projection you declared.&lt;/p&gt;
&lt;p&gt;Each hop has its own terms. The projection writes the document after the append, asynchronously, and the change stream reports changes to that document. A snapshot &lt;code dir=&quot;auto&quot;&gt;GET&lt;/code&gt; skips read-model interception and emission guards, and for MongoDB it needs &lt;code dir=&quot;auto&quot;&gt;waitForFirstResult&lt;/code&gt; to return data. Deltas compare snapshots and can miss a write between two results. Authorization is checked at subscribe time unless a guard checks each emission. A live screen built on Arc tells the browser about new results of a query, and the event log remains the place to look when each individual change matters.&lt;/p&gt;
&lt;p&gt;The Kotlin and TypeScript servers expose observable queries too, each with its own shape on the backend and its own differences from .NET, which &lt;a href=&quot;https://blog.cratis.io/beyond-dotnet/&quot;&gt;&lt;em&gt;Beyond .NET&lt;/em&gt;&lt;/a&gt; goes through.&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href=&quot;https://blog.cratis.io/validation-all-the-way-down/&quot;&gt;Part 07 — Validation all the way down&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next: &lt;a href=&quot;https://blog.cratis.io/cratis-components-the-react-layer/&quot;&gt;Part 09 — Cratis Components, the React layer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>arc</category><category>components</category><category>react</category><category>observable-queries</category><category>typescript</category></item></channel></rss>