Summary
Cratis.Stage.Contracts is the intermediate representation the whole authoring pipeline passes through, and it is now narrower than the language it carries. Screenplay 2.x added constructs the contract cannot hold, so they are lost on import and unrenderable on output — no matter what the compiler, the importer or the renderer do.
The pipeline
.play → Screenplay compiler → Studio importer → Stage.Contracts.EventModel → Stage renderer → C#
Studio does not generate code itself: Source/Core/Program.cs:148 registers
Cratis.Stage.Rendering.Cratis.CratisRenderer.CreateDefault() as the IRenderer. Every construct has to
survive a round trip through EventModel in both directions.
What the contract cannot express
Source/Contracts/Slice.cs:
public record Slice(
Guid Id, string Name, SliceType SliceType,
IReadOnlyList<EventDefinition> Events,
CommandDefinition? Command, // one
ReadModelDefinition? ReadModel, // one
IReadOnlyList<Specification> Specifications);
Missing entirely — no type in Source/Contracts mentions any of them:
| Screenplay 2.x |
Contract |
readmodel <Name> standing alone |
— |
reducer <Name> => <ReadModel> with on rules |
— |
reads <ReadModel> [by <property>] |
— |
require <condition> |
— |
invokes <Command> on a reactor |
— |
scoped to <scope> on a query |
— |
for <expression> in a produces body |
— |
authorize |
no authorization member on CommandDefinition |
Two cardinality limits also now diverge: a slice may declare several commands and several
projections in Screenplay, and the contract holds one of each.
Why this is the gating item
Everything else in the chain is either done or mechanical:
- Screenplay 2.1.0 is released and expresses all of the above.
- Studio's importer already warns about what it drops (Cratis/Studio#1074) — it knows the gap exists.
- Stage's renderer can be extended once there is something to render from.
But a construct that cannot be held in EventModel cannot survive the round trip, so extending the
importer or the renderer first would have nowhere to put the data.
Suggested direction
Design the contract first, once, and let both sides encode against it. If the importer and the renderer
each grow their own understanding of a construct, they will disagree, and a round trip is exactly where
that surfaces — there is already one such asymmetry today: specifications and constraints are exported
by Studio but dropped on import (noted on Cratis/StudioIssues#52).
Worth deciding explicitly, as part of that design, which Screenplay constructs are deliberately not
carried to generated code. That is a product decision, and making it explicit is better than discovering
it as a silent drop later.
Sequencing
This is blocked behind a Cratis.Stage release built against Screenplay 2.x — 1.2.0 still bakes
Cratis.Screenplay.Secrets.ISecretsFiles into its generated convention-binding table, which throws
TypeLoadException on every AddCratisArcCore when Screenplay 2.x is on the graph. That release also
unblocks Cratis/Studio's absorption.
Summary
Cratis.Stage.Contractsis the intermediate representation the whole authoring pipeline passes through, and it is now narrower than the language it carries. Screenplay 2.x added constructs the contract cannot hold, so they are lost on import and unrenderable on output — no matter what the compiler, the importer or the renderer do.The pipeline
Studio does not generate code itself:
Source/Core/Program.cs:148registersCratis.Stage.Rendering.Cratis.CratisRenderer.CreateDefault()as theIRenderer. Every construct has tosurvive a round trip through
EventModelin both directions.What the contract cannot express
Source/Contracts/Slice.cs:Missing entirely — no type in
Source/Contractsmentions any of them:readmodel <Name>standing alonereducer <Name> => <ReadModel>withonrulesreads <ReadModel> [by <property>]require <condition>invokes <Command>on a reactorscoped to <scope>on a queryfor <expression>in a produces bodyauthorizeCommandDefinitionTwo cardinality limits also now diverge: a slice may declare several commands and several
projections in Screenplay, and the contract holds one of each.
Why this is the gating item
Everything else in the chain is either done or mechanical:
But a construct that cannot be held in
EventModelcannot survive the round trip, so extending theimporter or the renderer first would have nowhere to put the data.
Suggested direction
Design the contract first, once, and let both sides encode against it. If the importer and the renderer
each grow their own understanding of a construct, they will disagree, and a round trip is exactly where
that surfaces — there is already one such asymmetry today: specifications and constraints are exported
by Studio but dropped on import (noted on Cratis/StudioIssues#52).
Worth deciding explicitly, as part of that design, which Screenplay constructs are deliberately not
carried to generated code. That is a product decision, and making it explicit is better than discovering
it as a silent drop later.
Sequencing
This is blocked behind a
Cratis.Stagerelease built against Screenplay 2.x — 1.2.0 still bakesCratis.Screenplay.Secrets.ISecretsFilesinto its generated convention-binding table, which throwsTypeLoadExceptionon everyAddCratisArcCorewhen Screenplay 2.x is on the graph. That release alsounblocks Cratis/Studio's absorption.