Hands an authored event model a stage and lets it perform — a live, running Cratis application at runtime. No code generation, no compilation: the model is the application.
A script isn't a show until someone performs it. Hand Stage an authored event model — the intermediate JSON produced by Cratis Studio, the Cratis CLI, or any other tool — and it puts the model on its feet: dynamically generated commands and queries (Arc), read models and projections (Chronicle), and a Scalar/OpenAPI surface, all materialized at runtime. Nothing is generated to disk and compiled; the model is interpreted and performed. Change the model, and the performance changes with it.
Stage is self-contained — it has no dependency on Studio. It's consumed as containers (the host and the specification runner) and as a NuGet package (the contracts), from Studio, the Cratis CLI, or your own tooling.
Three reasons, and they all line up:
- The stage is where the script becomes a live show. A screenplay is just paper until it's staged; the performance happens in front of a live audience — here, real HTTP callers hitting a running API. Stage is where the model stops being a document and starts behaving.
- It performs, it doesn't print. No code generation, no build step, no artifacts to check in — the model runs as-is. The stage is bare until the model steps onto it, and it leaves nothing behind when the curtain falls.
- The Cratis storytelling family. Cratis names its products after telling a story: Chronicle records what happened, Arc shapes the plot, Screenplay is the script, Studio storyboards it, Narrator reads it back… Stage is where the cast performs the script live. It joins the ensemble.
Hand the same model to Studio and it storyboards it — visualizing and generating. Hand it to Stage and it performs it. One model; no meaning lost between the whiteboard and the running app.
The only thing Stage needs is a serialized EventModel. The types and the exact serialization live in
Cratis.Stage.Contracts, so consumers write the file with EventModelFile.Write(model) and never hand-roll
JSON:
flowchart LR
Author["🎨 Studio · CLI<br/>authors the model"] -->|"event-model.json"| Model[["📄 EventModel"]]
Model -->|"cratis/stage"| Host["▶️ Stage host"]
Model -->|"cratis/stage-specrunner"| Spec["🧪 spec runner"]
Host --> Api["🌐 live HTTP API<br/>Arc + Chronicle · Scalar/OpenAPI · :9090"]
Host --> Workbench["🔍 Chronicle Workbench<br/>events · observers · read models · :35000"]
Spec --> Results["📋 results.json"]
At startup the host loads the model, stands up its commands, queries, read models, and projections, and registers the projections with Chronicle so they run and populate the read-model store — a real event-sourced application, materialized from JSON.
| Project | Package / Image | Purpose |
|---|---|---|
Source/Contracts |
Cratis.Stage.Contracts (NuGet) |
The event model intermediate format (with its embedded JSON schema), specification run results, and the serialization for both (EventModelFile, SpecificationRunResultsFile, StageJson). |
Source/Stage |
Cratis.Stage (NuGet) |
The engine — synthesizes commands, queries, validators, read models, and projections from a deserialized EventModel at runtime. |
Source/Host |
cratis/stage (Docker) |
Self-contained play sandbox: in-memory Chronicle kernel + the Stage engine in one container. Mount a model at /eventmodel, get a live API on port 9090 and the Chronicle Workbench on port 35000. |
Source/SpecRunner |
cratis/stage-specrunner (Docker) |
Run-to-completion job that compiles the Screenplay files in the mounted /model folder, runs their specifications, and writes results.json to the mounted /output folder. |
Studio / CLI ──(event-model.json)──▶ cratis/stage ⇒ live HTTP API (9090) + Workbench (35000)
Studio / CLI ──(event-model.json)──▶ cratis/stage-specrunner ⇒ results.json
Full documentation — what is inside the images, how they boot, and every URL a play session exposes — lives in Documentation.
- The host takes the model file path as its first argument (the container entrypoint finds it in
/eventmodel). Deployment configuration is supplied through a dedicatedcratis-stage.jsonfile (path overridable with theSTAGE_CONFIGenvironment variable) instead ofappsettings.json. - The spec runner takes
--model <folder>and--output <file>, with optional--slice <guid>/--spec <guid>filters; the container defaults to/modeland/output/results.json.
dotnet build # Debug
dotnet test # run the specs
dotnet build -c Release # Release — warnings are errorsBoth Dockerfiles expect a prebuilt, framework-dependent dotnet publish output rather than compiling inside
the Docker build — ./dockerize.sh publishes both apps and builds both images from the repository root:
./dockerize.shdotnet build -c Release # zero warnings, zero errors
dotnet test # all specs greenPart of the Cratis platform · Licensed under the MIT license