Interoperability and extensions
Screenplay-family integrations move in two independent directions: authored source can be recovered into Screenplay, and Screenplay can be compiled and realized as target artifacts. Syntax-tree tools sit beside both directions when they only need to inspect or export the written language.
Choose the narrowest extension level that owns the decision you need to make. This keeps source-framework interpretation out of Screenplay, target realization out of Generation, and orchestration out of render planners.
Two-direction architecture
Section titled “Two-direction architecture”The two main directions are not inverses:
- A source adapter explains what authored source proves and reports what it cannot recover.
- A render planner makes explicit, versioned choices for one target and rejects semantics it cannot realize.
Recovering generated artifacts through a source adapter can provide a useful regression check, but it does not prove behavioral equivalence or a lossless round trip.
Level 1: syntax tools and exporters
Section titled “Level 1: syntax tools and exporters”Use ScreenplaySyntaxWalker when the integration consumes Screenplay syntax directly: documentation tools, linters, indexes, visualizers, format-aware exporters, and other tree analyses.
The type ships in the Cratis.Screenplay package under Cratis.Screenplay.Syntax. Its general and root entry points are:
| Member | Purpose |
|---|---|
public virtual void VisitNode(SyntaxNode node) | Observe every node before node-specific traversal |
public virtual void VisitApplication(ApplicationSyntax syntax) | Walk a complete application |
public virtual void VisitProjection(ProjectionSyntax syntax) | Walk a standalone projection |
public virtual void VisitSpecification(SpecificationSyntax syntax) | Walk a standalone specification |
public virtual void VisitCapture(CaptureSyntax syntax) | Walk a standalone capture |
Every concrete node kind also has a public virtual Visit... method. The walk is pre-order: VisitNode() runs before the node-specific method descends into children. An override that calls its base implementation continues into that subtree; an override that does not call base prunes it.
This is the compatibility-oriented extension point for syntax consumers. It is not the source-recovery contract and it is not the target-rendering contract. A generator that must make decisions from resolved semantic identity should consume the executable semantic model through a render planner instead of inferring meaning from syntax names.
See Visitors and traversal and Syntax tree compatibility for traversal and versioning details.
Level 2: source-to-Screenplay recovery
Section titled “Level 2: source-to-Screenplay recovery”Use IDotNetScreenplayAdapter when an integration recovers authored .NET source into Screenplay. The adapter interprets framework or library evidence in Roslyn compilations and contributes neutral facts, evidence, and diagnostics.
The contract ships in Cratis.Screenplay.Generation.DotNet under Cratis.Screenplay.Generation.DotNet:
| Member | Contract |
|---|---|
AdapterIdentity Identity { get; } | Stable adapter identity and version |
bool CanAnalyze(DotNetAnalysisContext context) | Whether the adapter recognizes evidence it can analyze |
AdapterContribution Analyze(DotNetAnalysisContext context, DotNetAdapterOptions options) | Neutral facts and diagnostics from one analysis |
The surrounding host owns workspace loading, the authoritative authored syntax trees, options, and explicit adapter selection. CanAnalyze() decides whether an already-selected adapter recognizes semantic evidence in that context. Analyze() returns one AdapterContribution; it does not construct syntax or print .play text.
Generation owns the framework-neutral pipeline after analysis. ScreenplayDefinitionGenerator.Generate(...) resolves all contributions together, lowers the resolved graph to Screenplay syntax, prints canonical source, and verifies that source with the Screenplay compiler.
Generation does not discover adapter packages automatically. A package, catalog entry, or implementation of the interface does not make an adapter active; a host must select and compose it.
For implementation details, source authority, evidence strength, deterministic identity, diagnostics, and verification, follow the canonical Generation source adapter guide.
Level 3: Screenplay-to-artifact rendering
Section titled “Level 3: Screenplay-to-artifact rendering”Use IArtifactRenderPlanner when an integration turns compiled Screenplay semantics into target artifacts. Raw .play input is compiled first; the planner receives an immutable executable semantic model and its capability-admitted execution plan.
The contract ships in Cratis.Stage.Contracts under Cratis.Stage.Contracts.Rendering. Its public operation is ArtifactRenderPlan Plan(ArtifactRenderRequest request).
ArtifactRenderRequest is a sealed record with this exact positional contract:
| Parameter | Type | Purpose |
|---|---|---|
Model | ExecutableSemanticModel | Immutable executable semantic model |
ExecutionPlan | SemanticExecutionPlan | Capability-admitted plan for that model |
Profile | ArtifactRenderProfile | Fully resolved target and renderer profile |
Scope | ArtifactRenderScope | Semantic scope to render |
A scope identifies the application, one module, one feature, or one slice. The fully resolved profile identifies the target, renderer, versions, and immutable inputs.
Execution plan compilation
Section titled “Execution plan compilation”SemanticExecutionPlan and SemanticExecutionPlanCompilation ship in Cratis.Screenplay.Semantics.Execution.
| Operation or member | Contract |
|---|---|
SemanticExecutionPlan.Compile(ExecutableSemanticModel model) | Compiles the whole model; any admission issue blocks plan creation. |
SemanticExecutionPlan.Compile(ExecutableSemanticModel model, IEnumerable<SemanticId> selectedSlices) | Compiles for a selection of slice identities, including an empty selection. Unknown identities produce InvalidSliceSelection issues. |
SemanticExecutionPlanCompilation.Plan | The indexed plan, or null when blocking issues exist. |
SemanticExecutionPlanCompilation.Issues | Blocking admission issues: selected-slice issues and all application-level issues. |
SemanticExecutionPlanCompilation.UnselectedIssues | Non-blocking issues owned by unselected slices; empty for whole-model compilation. |
SemanticExecutionPlanCompilation.Success | true only when a plan exists and Issues is empty. |
SemanticExecutionPlan.Model / Revision | The original model and its unchanged semantic revision, including during scoped compilation. |
Use the selection overload for scoped rendering when an unrelated slice fails portable execution admission. Expand module or feature selections to their slice identities before calling it; nested features are traversed, but only explicitly selected slices are selected. The plan retains every declaration, rather than manufacturing a subset model with a different revision. Application-level issues always block because selected declarations may depend on shared concepts or types. An empty selection blocks only on application-level issues and reports every slice-owned issue in UnselectedIssues; use it when a chosen scope has no slices left. The same evaluator guards apply to its deferred artifacts.
A successful scoped compilation does not admit the deferred artifacts for execution. The reference evaluator returns SemanticUnsupported with the existing Command, Query, or Projection capability when a deferred command or query is requested, a query reads a deferred projection’s read model, or appended/replayed facts would reach a deferred projection. This includes event-target projections and all facts that would be committed, both original and published. Reachability is conservative: any every or all block is treated as observing every event, regardless of whether a read-model instance exists. Deferred projections never enter either evaluation engine; refusal returns the input world without committing facts or read-model state. Reaction settlement likewise rolls back cascading effects when an invoked command is deferred. Inspect UnselectedIssues to report these limitations to the caller.
Executable semantic model versions
Section titled “Executable semantic model versions”The table below is the authoritative version allocation under decision 0025, as amended by 0066. Versions are cumulative and select only when their constructs are used. Read, reference-execute, render and reverse-recover admission are separate consumer choices; a package update admits none of them automatically.
Released versions
Section titled “Released versions”| Version | Selecting constructs | schemaVersion | Decision | Golden file | Corpus vector |
|---|---|---|---|---|---|
| 1.0 | Baseline portable model without later constructs | 1 | 0001, 0004 | full-esm-v1.json | RegisterProjectCorpus.LegacyV1 |
| 2.0 | Typed command destinations, specification event sources, scalar event context | 2 | 0004 | full-esm-v2.json | RegisterProjectCorpus.V2 (scenario now evolved to v4) |
| 3.0 | Implementation attachments: reducers, validation, policy predicates | 3 | 0002, 0005 | full-esm-v3.json | No dedicated source-backed v3 corpus yet |
| 4.0 | Multiple event generations | 4 | 0015 | full-esm-v4.json | RegisterProjectCorpus.V2 |
| 5.0 | Keyed read-model absence assertions | 5 | 0020 | full-esm-v5.json | ReadModelAbsenceCorpus.V5 |
| 6.0 | Triggers, automation/translate slices, reactions, captures, clock and capture specifications | 6 | 0022 | full-esm-v6.json | ReactionsCorpus.V6 |
| 7.0 | Generated command properties, responses, generated fixtures, return expectations; policy negation (0027 extension) | 7 | 0025, 0026, 0027 | full-esm-v7.json | RegisterProjectCorpus.V7, PolicyNegationCorpus.V7 |
| 8.0 | Event sources and streams, scalar/composite command routes, specification routes and no-stream assertions | 8 | 0036, 0031, 0033 | full-esm-v8.json | EventRoutesCorpus.V8, EventRoutesCorpus.SpecificationsV8, EventRoutesCorpus.CompositesV8, EventRoutesCorpus.CompositeCommandsV8 |
| 9.0 | Public events and event origins, translation direction, event-target projections and reducers, and event-source captures | 9 | 0051, 0050, 0049 | full-esm-v9.json | PublicEventsCorpus.V9 |
| 10.0 | Reaction system identity (runs as system), production route overrides and observer from filters | 10 | 0043, 0004, 0025, 0063, 0061, 0062, 0066 | full-esm-v10.json | ReactionIdentityCorpus.V10, EventRoutesCorpus.ProductionRoutesV10, EventRoutesCorpus.ObserverFiltersV10 |
| 11.0 | Exact Double-mode lowering of a retained non-route number literal | 11 | 0064, 0066, 0025 | full-esm-v11.json | NumberLiteralsCorpus.ExactLiteralsV11 |
| 12.0 | Non-positional enum member numbers | 12 | 0065, 0025, 0066 | full-esm-v12.json | EnumMemberNumbersCorpus.V12 |
| 13.0 | Concept compliance markers (pii, secret), personal qualifiers and secret scope | 13 | 0068, 0025, 0066 | full-esm-v13.json | ConceptComplianceCorpus.V13 |
| 14.0 | Caller-supplied equality filters on collection queries | 14 | 0069 | full-esm-v14.json | QueryFiltersCorpus.V14 |
| 15.0 | Concept generation strategies and Guarded freshness | 15 | 0054 | full-esm-v15.json | GeneratedValuesCorpus.V15 |
| 16.0 | Event identity pins and stored event type names | 16 | 0075 | full-esm-v16.json | StoredEventNamesCorpus.V16 |
| 17.0 | Explicit no-event assertions and recovery redelivery | 17 | 0074 | full-esm-v17.json | RecoverySpecificationsCorpus.V17 |
| 18.0 | Actionless query validation rejection specifications | 18 | 0072 | full-esm-v18.json | QueryValidationCorpus.V18 |
| 19.0 | Reaction invocation refusal branches and String refusal values | 19 | 0030 | full-esm-v19.json | InvocationRefusalsCorpus.V19 |
Claimed, unreleased version
Section titled “Claimed, unreleased version”| Version | Selecting constructs | schemaVersion | Decision | Golden file | Corpus vector |
|---|---|---|---|---|---|
| 20.0 | Rules on generated concepts, including implicit UUID generated properties | 20 | 0054 | full-esm-v20.json | GeneratedConceptRulesCorpus.V20 |
2026-10-06 — v7 extension accepted. Decision 0027 admits unary policy-condition
notas a byte-preserving extension under 0004. Models using negation select v7; models without it keep their existing canonical bytes and revisions. Pre-extension v7 strict readers reject the new condition variant, so consumers must admit negation explicitly.
ESM v10 was released in Screenplay 4.125.0 under 0043. Production routes and observer filters extended it under 0063 in Screenplay 4.129.0; 0066 records that shipped extension as a retroactive exception, now part of frozen v10. Consumers admitting v10 must include those bytes. A model selects v10 when it uses any of these three features. Any release or prerelease NuGet or npm package exposing a version freezes it: later changes take a new version unless they are byte-preserving extensions under 0004. A claimed entry moves to the released table with its first publishing merge, in practice the admitting pull request’s release. ESM v11 (exact number literals, under 0064) was released in Screenplay 4.135.0. ESM v16 admits stored event type names under 0075. ESM v17 admits explicit no-event assertions and recovery redelivery under 0074; its admission merge publishes the contract. ESM v19 was released in Screenplay 4.160.0 for reaction refusal branches and $refusal values under 0030. ESM v18 admits query validation rejection specifications under 0072; it was admitted after v19 was released, so v19 includes v18’s version pairs. ESM v20 is claimed and unreleased for rules on generated concepts under 0054; its admission merge publishes the contract. ESM v7 was released in Screenplay 4.68.0, v8 in 4.101.0, and v9 in 4.117.0. Operations, decision reads and exact source numeric mode have no numbered claim here; they receive a number only at admission. ESM v12 admits enum member numbers under 0065; its admission merge publishes the contract. Golden files live in Source/DotNET/Screenplay/Semantics/Serialization/Golden/. The v3 corpus gap is recorded by 0004; the table does not imply that a golden is a source-backed corpus.
Enum member numbers that differ from declaration positions select language/semantic 12.0 and schemaVersion: 12. The syntax AST carries optional valueNumbers; SemanticConcept.Numbers is an optional immutable int32 array serialized as numbers, after validations and before optional v13 compliance, parallel to values. Absence means zero-based positional identity. Explicit pins equal to 0..n-1 bind as absence and keep the earlier version, canonical bytes and revision. The strict reader and programmatic validator refuse numbers below v12, without enum values, on non-text concepts, with mismatched cardinality, with duplicates, or when positional; JSON also refuses non-integers and values outside int32. V12 requires at least one non-positional array and includes every v11 and earlier contract, including v11 literal lowering, without requiring an exact literal to activate v12. Specifications and conditions compare member names, not numbers. Consumers must explicitly admit v12 and preserve stored numbers, never guess or silently discard them. full-esm-v12.json and EnumMemberNumbersCorpus.V12 pin the contract and name-based outcomes across equivalent source forms. Released v1–v11 vectors are unchanged. See Enum concepts.
Concept compliance selects language/semantic 13.0 and schemaVersion: 13 only when a concept has a marker. SemanticConcept.Compliance is optional and serialized as compliance after numbers (or after validations when numbers are absent). Its optional personal object carries one special category and optional criminal: true; bare pii writes {"personal":{}}. Its optional secret object carries scope: "subject", "namespace" or "global", omitted when unspecified. pii secret writes {"personal":{},"secret":{}}: personal takes precedence, and the binder drops secret scope next to personal. Reasons are report-only and not written. Classification propagates through type references, including composite members and collection elements, without per-property bytes.
V13 includes v11 exact-number lowering, v12 enum member numbers and every earlier contract. Unmarked models retain their version, bytes and revision. The validator and strict reader refuse compliance below v13, unknown categories or scopes, secret scope next to personal, and compliance on event-source/stream identities or enum concepts. The binder additionally refuses an event holding personal or explicitly subject-scoped secret values together with an explicit subject mark; subject marks stay report-only and have no ESM bytes. The event source is the default data subject. Reference specifications execute with unchanged outcomes: encryption, storage and causation are outside that evaluator. Consumers must explicitly admit v13 rather than drop protection. full-esm-v13.json and ConceptComplianceCorpus.V13 pin bytes, revision and outcomes across equivalent source forms; released v1–v12 vectors remain unchanged. See Concepts.
Explicit then no events or when redelivered selects language/semantic 17.0 and schemaVersion: 17. A specification writes optional whenRedelivered: {"reaction":"<semantic id>","given":<zero-based index>} after its other command/append action members and before thenEvents. Optional thenNoEvents: true follows thenEvents, before thenEventsInAnyOrder. Both members are omitted when unused. Redelivery must identify an existing given fact and a reaction with an event trigger on its event; only that reaction receives the existing fact, then ordinary cascades settle. No-event assertions compare all newly accepted facts, excluding an append action’s own fact, and count as a success outcome. They conflict with event, event-order, error and denial expectations. Strict readers below v17 reject both members. Refusal branches and $refusal values select ESM v19. full-esm-v17.json and RecoverySpecificationsCorpus.V17 pin bytes, revisions and reference outcomes across equivalent source forms; prior vectors are unchanged. V17 includes stored event type names and every earlier contract. Consumers must explicitly admit v17 rather than omit either action or assertion.
Invocation refusal branches select language/semantic 19.0 and schemaVersion: 19. An invocation’s optional ordered onRefused array follows mappings; each branch writes selector (any, validation, constraint or authorization), optional constraint (the constraint name, its executable identity), then produces. Empty produces means acknowledge. Refusal expressions write kind: "refusal", member (reason, constraint or message) and primitive String type. They are available only in branch event mappings; constraint requires a constraint selector. Named constraints must resolve and target one of the invoked command’s events. Event triggers default branch destinations to the observed event source; other triggers require for. Earlier strict readers reject these members and expression variants. The first matching branch handles the refusal and stops the remaining invocations of that trigger. Authorization wins over constraint, then validation; bare refusal excludes authorization. Messages are joined with \n in result order, without trimming or resolving symbolic keys. Contract, concurrency and Unsupported outcomes never match. A branch append failure ends the scenario. full-esm-v19.json and InvocationRefusalsCorpus.V19 pin canonical bytes and reference outcomes; all prior golden and corpus bytes remain unchanged. Consumers must explicitly admit v19. See Refusal branches.
An actionless specification with exactly one queried call, one error and no other outcome selects language/semantic 18.0 and schemaVersion: 18. Both when query … then error and then query … arguments followed by an error lower to one existing thenQueries entry with results: [] plus thenErrors. A result block, explicit then no result, denial, another call or another outcome is refused. The runner requires exactly Validation; Contract, Unauthorized and Unsupported do not pass. V18 requires this shape and earlier versions refuse it. Query concept rules on the key and supplied filters run after authorization in every ESM version without changing earlier bytes; omitted optional filters are not validated. full-esm-v18.json and QueryValidationCorpus.V18 pin the new contract. Consumers must explicitly admit v18. See Query validation rejections.
Concept generation selects language/semantic 15.0 and schemaVersion: 15 when any concept serializes its optional last member generation, after compliance. A strategy object carries kind, its size/count/encoding arguments, and only declared non-default alphabet/prefix/case modifiers. Unmodified Uuid uuid generation omits the member, preserving earlier bytes and revision. V15 includes v11–v14. The validator refuses generation below v15, v15 without generation, unsupported strategies, malformed modifiers, compliance on explicitly generated concepts, rules below V20, and identifiers below 64 random bits. When the model declares a serialized concept generation strategy, the evaluator checks exact fixture shapes (including implicit UUIDv4) and rejects an append to a generated source with prior facts as Concurrency; it inspects pre-invocation history across all streams and event types. Models without such a strategy remain Unguarded, even when another construct selects a later version. Consumers must explicitly admit v15 and preserve its freshness policy. See Generated values.
Rules on an explicitly generated concept, or on a concept used by a generated command property (including implicit UUID), select language/semantic 20.0 and schemaVersion: 20. V20 builds on V19 and includes every earlier contract, including v18. There are no new serialized members: existing validations carry the rules, and proof status is derived, never stored. Binding admits Proven rules silently, reports Refuted rules as PLAY0688 with a quoted witness, and warns for Unproven rules with PLAY0689 and a reason. Below V20 the validator and strict reader refuse these rules; at V20 the validator re-runs the proof and throws InvalidSemanticContract on Refuted programmatic models. Generation compliance and preview strategies remain refused. Every generated fixture is shape-checked, then rule-checked; failure is Rejected(Contract), never a redraw. Opaque rules remain Unsupported. Feature use, not a version comparison, drives reference execution. full-esm-v20.json and GeneratedConceptRulesCorpus.V20 pin proven matches, unproven opaque validation and a rule-failing fixture. Prior ESM golden and corpus bytes are unchanged. Consumers must explicitly admit V20 and validate generated values, or refuse it.
An event id pin that differs from the event name selects language/semantic 16.0 and schemaVersion: 16; an equal-name pin keeps the earlier version, canonical bytes and revision. Each event contract’s optional storedName string follows name, before properties; absence means the display name. The value is the pin, omitted when equal to name (including an explicitly equal pin). Prior generations share the current contract’s stored name. Binding rejects invalid pins, inconsistent generation pins and duplicate effective stored names across the application. A valid pin uses the same identifier rule as an event name. Strict readers and programmatic models below v16 refuse the member. V16 requires at least one event contract with storedName; unpinned events and equal-name pins in the same model omit the member. A source without differing pins selects its earlier version and retains its bytes and revision. Reference evaluation is unchanged: facts are addressed by contract and display name, not by stored type. Consumers must explicitly admit v16 and preserve the name or refuse the model. full-esm-v16.json and StoredEventNamesCorpus.V16 pin mixed pinned/unpinned bytes and outcomes; v1–v15 goldens remain unchanged. See Events.
Caller-supplied equality filters on collection queries select language/semantic 14.0 and schemaVersion: 14. A query’s optional filters array follows argument and keyProperty (or readModel when unkeyed), before cardinality, in declaration order. Each entry carries id, name, type and the same-named read-model property identity. The type reference alone carries optionality: there is no separate optional flag. Each specification query result, evaluator request and evaluator result carries only supplied {filter, value} entries, after key, in query filter order. Empty arrays are omitted.
V14 includes v11 exact-number lowering, v12 enum member numbers and v13 concept compliance. Filter-free models keep their earlier version, bytes and revision. The validator refuses filters below v14 and v14 without filters. Filters require collection results, distinct names not equal to by, scalar primitive/concept/enum properties and matching nominal types ignoring optionality. Context-sourced filters, explicit scopes, performers and single-result filters remain unadmitted. The strict reader and evaluator refuse unknown or repeated filter values, missing required filters, supplied null and mismatched types. Optional omission does not narrow; by and supplied equality filters combine with AND. Policies still see only by. Collection specifications match rows by read model and key without regard to order, with exact row count; query call identity includes supplied filters. full-esm-v14.json and QueryFiltersCorpus.V14 pin bytes, revision and execution across equivalent source forms. Consumers must explicitly admit v14 rather than drop filters; Stage rendering is a separate admission. See Queries.
A reaction declaring runs as system, with zero or more roles, selects language/semantic 10.0 and schemaVersion: 10 only when used. Its optional runsAs member follows triggers, carries the closed kind system and an ordinally sorted roles array, and is omitted when absent. The reference invocation authenticates the system principal with exactly those roles; every claim condition is unknown, including under not, and a final unknown denies. This authorization identity does not supply audit identity. Models without it retain their earlier bytes and revisions unless another v10 feature is used. Earlier strict readers reject runsAs; consumers must refuse v10 until they explicitly admit it. ReactionIdentityCorpus.V10 pins accepted role-gated invocation and unauthorized invocation without identity across single-file and folder forms; the shared full-esm-v10.json also pins empty roles alongside routes, filters and earlier constructs.
EventRoutesCorpus.ProductionRoutesV10 pins scalar and composite overrides, an override without a command route, and atomic failure from a skipped production’s route. EventRoutesCorpus.ObserverFiltersV10 pins routed-only reaction delivery and an opaque reducer reached only through matching source metadata. Both preserve single-file, folder, reordered and relocated canonical bytes. Versions 1 through 9 retain their bytes and outcomes.
A retained non-route Double-mode number literal selects language/semantic 11.0 and schemaVersion: 11 when its lowered decimal differs detectably from legacy lowering (0064). Lowering is pure: parse the invariant shortest round-tripping Double representation into a decimal, then use it only when converting that decimal through Double with the legacy conversion produces a different value (or overflows); otherwise keep the legacy value. Decimal scale can round away digits, so 0.00000000000000012345678901235 keeps the legacy 0.0000000000000001234567890123 and does not select v11. Selection follows the retained graph: unused examples, overridden values and validation-only binding do not count. Already-lossless route literals and route-feeding inputs do not select v11. Every literal in a v11 model uses this lowering; v11 includes every v10 feature. A v11 model without an exact non-route number is refused. Programmatic v1–v10 models remain valid with any decimal permitted by their existing contracts, including precise decimals in the frozen v10 golden. Models not selecting v11 retain their existing bytes, revisions and outcomes. This does not admit numbers exact or add numericMode. Consumers must explicitly admit v11; NumberLiteralsCorpus.ExactLiteralsV11 pins exact integers, a 17-digit fraction and reference outcomes across equivalent source forms, and full-esm-v11.json adds exact event payloads to the full v10 model without changing its golden.
PublicEventsCorpus.V9 pins the source-backed single-file form of an outbound translation that publishes one public event through an event-target projection and an inbound translation that consumes a foreign public event with a source-events capture, the v9 canonical bytes and revision, and the accepted outcome of the publication specification. Semantics/Serialization/Golden/full-esm-v9.json additionally pins event-target reducers. Consumers must reject either vector until they explicitly support v9.
The event routes implementation follows 0036: source declarations, scalar/composite command routes and specification routes select its ESM v8 contract. Models without routes retain v1–v7 bytes and outcomes. Catalog identities survive renames and relocation, but an unpinned rename changes the stored route name; pin before renaming declarations with stored events. Consumers must explicitly admit the strict-read, reference-execution, rendering and recovery contracts they support, and refuse the rest. Arc’s earlier stream-id computation is not equivalent to the portable post-validation route phase. ESM v8 was released in Screenplay 4.101.0.
Screenplay writes a model as language/semantic 1.0 and canonical JSON schemaVersion: 1 unless the model uses a v2 construct. A typed command destination from produces … for without duplicating the identity in the event payload, a specification event-source for assertion, or an admitted scalar $context value in produces selects language/semantic 2.0 and schemaVersion: 2. The strict reader accepts these paired versions; v1 canonical bytes and revisions do not change. Historical v1 sources that already used produces … for while copying the same identifier into event payloads keep their v1 contract; v2 models promote the typed command destination independently of that legacy payload. A target pinned to v1, including Stage until it explicitly bumps its ESM admission, must reject v2 rather than treating the new context and destination as payload or ignoring them. See Specifications and Contexts for the admitted syntax.
An implementation attachment admitted by the ESM (a bodied reducer transition, code validation, or policy predicate) selects language/semantic 3.0 and schemaVersion: 3; v3 includes all v2 semantics, including typed command destination promotion, and sources without a v3 construct retain their v1/v2 bytes and revisions. Its ESM reducer entry names the built read model, observed event contracts, event-source key, null initial state, state-or-delete result, and each rule’s implementation requirement id. The body is not portable executable code. Screenplay names the pure capability; a target provider owns the allowlist and must reject code it cannot admit. An unresolved file attachment has no content hash until its host supplies contents; the authored path never serves as its revision. Rule predicates on command properties and concepts bind their guarded value, name, message, severity and requirement id; whole-command code validation binds ordered requirement ids and yields zero or more rejection messages. These attachments require context/result contract version 1 and capability pure. An opaque policy’s condition is {"kind":"opaque"} with a requirementId on its policy entry; portable policy entries retain their v1/v2 bytes. The documented RuleContext supplies the value and, for a command predicate, the command artifact; a predicate returns accept or reject. A strict v1/v2 consumer must reject v3 explicitly. A specification that reads reducer-built state gets SemanticUnsupported naming the reducer rather than synthesized reducer state. An executed command that carries code validation, including through nested or collection concepts, gets SemanticUnsupported naming the rule rather than a guessed validation result. A command or query whose authorization depends on an opaque policy returns SemanticUnsupported with the Authorization capability and the policy name rather than claiming a denial or permission. In authored order, a portable left-hand and denial or or allowance short-circuits; an opaque operand reached first makes the result unsupported even if a later operand could decide it (see decision 0001 and Policies). Unrelated specifications remain runnable.
An event with multiple declared generations selects ESM language/semantic 4.0 and schemaVersion: 4, including when the same document uses v2 destinations or v3 attachments. A lone generation-1 marker does not select v4. Only evolved event records carry predecessor and priorRevisions; ordinary single-generation events in a v4 model omit both fields. Preceding revisions appear in numeric order, with revision-specific property identities and tags. History changes canonical bytes and SemanticRevision. In v4, flat projection transitions carry no affected-instance cardinality: they always affect one instance. A v4 model carrying zeroOrOne or many there is invalid; v1–v3 bytes and query cardinality are unchanged. The strict reader rejects noncanonical versions/bytes and revision mismatches. Deployment to Chronicle requires an admitted transformation chain; a target lacking one must reject evolution, never guess a conversion. See Events and decision 0015.
A then no readmodel <View> for <key> assertion selects ESM language/semantic 5.0 and schemaVersion: 5 only when used. Each v5 specification carries thenAbsentReadModels (an array of typed view-id/key pairs, empty for specifications with no absence assertion). Its key is required and type-checked against the read-model identifier; an assertion of presence and absence for one pair is contradictory. Projection-backed absence executes by semantic key equality, while reducer-dependent absence returns typed Unsupported. ESM v5 retains v4’s event generation history and prohibition of legacy flat transition cardinalities. Existing v1–v4 canonical bytes and golden vectors stay unchanged. Stage, CLI, Studio and Generation must admit v5 explicitly; until then each must reject it rather than silently ignore thenAbsentReadModels. See decision 0020.
An application trigger, an automation or translate slice, a reaction, a capture, or a specification given clock, when clock, when trigger, given capture or when capture selects ESM language/semantic 6.0 and schemaVersion: 6 only when used (decision 0022). The application carries triggers; a slice carries reactions and captures; a reaction trigger names its kind (event, applicationTrigger, startup, shutdown, interval or schedule), its source, its schedule in seconds (an interval counts from the Unix epoch, a time of day is UTC), its where, the events it produces - with a destinationType when it appends to an event source of its own - the commands it invokes, and a requirementId when its body is code. A capture carries its key, its ordered map operations, its guarded appends with their event-source type, and its children and nested records; a specification carries round-trip UTC clock instants and typed capture records. Each v6 member is written only when present, so v1–v5 canonical bytes and golden vectors are unchanged. ESM v6 retains v5 absence, v4 lineage and every earlier construct. The reference evaluator runs reactions after every accepted fact until they settle, fires clock occurrences once each in time order, and evaluates captures against the record they last saw; an opaque reaction body returns typed Unsupported. Stage, CLI, Studio and Generation must admit v6 explicitly; until then each must reject it rather than drop reactions, captures or clocks. See Specifications, Reactions and Captures.
A generated command property, command response, generated fixture or then returns expectation selects ESM language/semantic 7.0 and schemaVersion: 7 only when used (decision 0026). Request inputs exclude generated properties; generation follows authorization and validation. Generated properties are required scalar UUID-backed concepts without validation rules. Pre-generation policies, rules and requirements cannot reference them. A generated identifier’s when … for supplies its generation fixture, not a legacy allocated destination; when … generated supplies other generated values. Missing reached fixtures return Unsupported(IdentityAllocation), never guessed identifiers. Responses are scalar or ordered named records over complete command values and exist only on successful acceptance; a later scenario failure keeps accepted facts but no response. Record then returns asserts a non-empty named subset. The strict reader admits the new members only at v7 or later and still rejects numericMode; exact-mode binding remains unsupported. Models without these constructs retain their v1–v6 bytes, revisions and outcomes. Consumers must explicitly admit v7 or reject it. The v7 contract does not deliver rendering or UI continuations.
A policy condition using unary not also selects v7 under 0027. Its canonical variant is {"kind":"not","operand":<condition>}; it is refused in earlier versions and cannot contain an opaque predicate. not binds tighter than and, then or; parentheses override precedence. The reference runner uses three-valued Kleene logic: a claim comparison against a missing or null target, an absent subject, or a non-text target is unknown; not unknown remains unknown. For and, false decides the result and true passes through the other operand; for or, true decides the result and false passes through the other operand, in either operand order. Other combinations involving unknown remain unknown. Only a final true result allows access; false and unknown deny. Existing absent-caller refusal and comparison rules remain unchanged, and authored-order short-circuiting applies when the left operand decides the result. See Policies: portable evaluation. policy-negation-v7.json and PolicyNegationCorpus.V7 pin role/claim exclusions and allowed/denied outcomes across single, split, reordered and relocated sources without altering the original v1–v7 fixtures.
The planner owns target admission and realization. It returns a complete deterministic ArtifactRenderPlan with planned paths, bytes, hashes, and typed diagnostics. It must not write files, start processes, use the network, read the clock, or inspect ambient dependency state. Publication belongs to the caller after a successful plan.
A target must fail closed when it cannot realize reachable semantics. It must not emit guessed defaults, thinner behavior, placeholders, or to-do blocks.
Canonical conformance corpus
Section titled “Canonical conformance corpus”For ESM-version admission checks, use the released golden bytes from Cratis.Screenplay.CanonicalCorpus
instead of vendoring serializer fixtures. EsmGoldenVectors.Versions lists the full ESM vectors in ascending
order. EsmGoldenVectors.Get(version) returns an EsmGoldenVector containing Version, immutable Bytes,
and SemanticRevision verified by the strict ESM reader; an unavailable version throws EsmGoldenVectorNotFound.
EsmGoldenVectors.PolicyNegationV7 provides the separate byte-preserving v7 policy-negation extension.
These resources are linked from the compiler’s Golden folder in Debug and Release packages. New
full-esm-v<N>.json files are included automatically at build time. Expression, identity-catalog and
typed-context sidecars are not ESM and are excluded from this API. Availability does not grant admission:
your consumer still decides which ESM versions and extensions it supports.
Cratis.Screenplay.CanonicalCorpus provides source-backed vectors for consumers that compile Screenplay independently of their rendering pipeline. RegisterProjectCorpus.LegacyV1 contains single-file, folder, reverse-ordered, and relocated forms of the same application. Each form carries exact UTF-8 bytes, stable document keys, portable display paths, and identity-catalog bytes; all compile to the pinned semantic revision, canonical ESM bytes, and normalized specification outcomes. A relocated path changes the workspace transport revision, not the semantic revision.
RegisterProjectCorpus.V2 names the evolved scenario, not the ESM schema. It pins new ESM v4 bytes and revision: generation 1 of ProjectRegistered carries projectId and name, while generation 2 carries only name; the command and expected event specify the stream with for, and the projection keys from the event source. The single-file and folder forms have distinct persisted catalogs; reordered and relocated forms reuse the folder catalog. All four produce the same bytes, identities and outcomes. Consumers pinned to v1–v3 must reject this v4 vector. The identity catalog retains schemaVersion: 1 while its typed property addresses can now include a Generation part and its event assignments can carry revisions above 1. Readers without support for those address parts must reject the catalog rather than drop lineage. The legacy v1 corpus and v1–v3 serializer golden bytes remain unchanged. Semantics/Serialization/Golden/full-esm-v4.json is the source-backed mixed v4 serializer vector.
ReadModelAbsenceCorpus.V5 pins source-backed single-file, folder, reordered and relocated forms, the v5 canonical bytes and revision, and the accepted outcome for a projection removal that leaves the other key intact. Semantics/Serialization/Golden/full-esm-v5.json additionally pins v4 lineage alongside a negative read-model assertion in a mixed v5 model. Consumers must reject either vector until they explicitly support v5.
ReactionsCorpus.V6 pins source-backed single-file, folder, reordered and relocated forms of an application with an application trigger, reactions that produce and invoke, a weekly clock reaction and a capture, together with the v6 canonical bytes and revision and normalized positive and negative outcomes: accepted command cascades, trigger invocations, clock schedules and capture transitions, validation and invoked-command constraint rejections, and a reached opaque Startup effect that is unsupported. Semantics/Serialization/Golden/full-esm-v6.json additionally pins every reaction trigger kind, map operation, capture condition and record shape in one model. Consumers must reject either vector until they explicitly support v6.
RegisterProjectCorpus.V7 pins single-file, folder, reordered and relocated forms of a command with a generated identifier and a second generated UUID-backed concept, inline event production and an ordered record response. All forms share canonical v7 bytes and revision and three normalized outcomes: accepted with a matching response, missing-allocation Unsupported(IdentityAllocation), and accepted execution with a mismatched return assertion. A failed assertion does not undo the accepted fact or response. CanonicalCorpusSpecificationExpectation.Response carries canonical response JSON in contract field order, or null when no response exists. Generated request input is not an execution vector in this corpus: the syntax validator refuses it before binding. full-esm-v7.json additionally pins scalar and record response contracts, generated fixtures and return assertions. Consumers must reject both vectors until they explicitly admit v7.
ScreenCompositionCorpus.V1 pins the source-owned screens-release authoring fixture: single-file, folder, reordered and relocated forms of the same master/detail workspace with command forms, nested sections, navigation contribution, dialog template, package profile, and dialog-opening interaction probes. Its positive behavior contract now points at the real source/folder form: master/detail selection, query rebinding and clearing, native validation and command submission, navigation/outlet/dialog routing, package profile/fallback behavior and browser control behavior all carry authored machine-checkable assertions. It also carries the positive typed source case for the screen-release authoring grammar, typed-source MCP edit invariants, folder-source MCP import/edit/export assertions, and harness entry points with explicit executed vs pending status. The browser, folder-MCP and Studio harnesses keep their pending reasons; the CLI/Stage harness records the exact refusals observed on the released CLI 3.39.0 + Stage 4.43.0 vector - the bundled compiler 4.93.0 predates the screens authoring syntax and refuses template slots (PLAY0025), screen directives (PLAY0103), profile icons (PLAY0207), form columns (PLAY0210) and slice templates (PLAY0029), while the pre-4.94 corpus form parses but the renderer’s ESM vertical refuses its observable list queries (PLAY0268) and publishes zero artifacts. Those red vectors are preserved, not regenerated into green. The current backend ESM deliberately rejects UI declarations with pinned PLAY0268/PLAY0269 diagnostics. Consumers use it to prove authoring, import/export, renderer admission and runtime behavior without pretending screens are executable backend semantics.
ScreenCompositionCorpus.V1.WorkingBranchHarnesses records the pre-release full-app assertion runs. It names exact branch heads for Stage PR #260, Studio PR #1615/#1617, the local Scene form-geometry branch and the Screenplay MCP transcript, together with the command that ran, the release-switch rule and the assertions each harness must satisfy before final packages publish. Stage PR #260 currently passes the focused screen-corpus planning specs, Studio’s Screenplay MCP transport specs pass, and Scene form-geometry model specs pass; browser-native controls, production Studio save/export/import/Play and folder-source MCP revision editing remain pending with their blockers recorded.
ScreenCompositionCorpus.V1.ReleasedVectorResults records exact released-vector execution, one entry per public CLI and Stage pair, and keeps every earlier red or partial result rather than overwriting it. The CLI renders the folder source as authored Scene composition (WorkItemList, WorkItemDetails, CommentThread, AppShell, MasterDetail, Navigation, EditDialog and the Desktop profile) with two guarded-action warnings for the Close action and the double-click interaction. The committed browser harness starts cratis run, drives Chromium through two seeded work items, and asserts scoped selection, query identity, stale-response handling, deep links, native form validation and submission, persistence and theme effect. run-public-acceptance.sh reruns it, the MCP stdio transcript and the Studio production harness against the installed CLI and the Stage image it defaults to. The current per-criterion status of every screens-release issue, with the open runtime blockers, is in the screens release acceptance matrix.
RegisterProjectCorpus.ExecutedStagePlan is an additional compatibility vector, not screens acceptance: the legacy v1 source form rendered end to end on the exact released screens vector (Screenplay 4.97.0 + CLI 3.39.0 with bundled compiler 4.93.0 + Stage 4.43.0 + Scene 4.10.0 + Studio 0.135.1). It pins the produced .cratis-render.json manifest, scene.json plan and generated src/bindings.ts as embedded evidence whose semantic revision equals the corpus’s pinned revision, an ordered 28-artifact expectation with SHA-256 hashes, and authored assertions over manifest metadata, the native Cratis.Components:commandForm/queryInputForm markers, typed-binding resolvers and zero placeholder components. A specification evaluates every assertion against the pinned bytes, so a future toolchain regression fails instead of silently drifting. Consumers prove deterministic CLI/Stage compatibility through it without requiring the released toolchain in CI, but full screens parity remains owned by ScreenCompositionCorpus.V1.
RegisterProjectCorpus.UnsupportedSequence is a CanonicalCorpusRejectionVector: compilation returns no ESM, its expected PLAY0268 diagnostic is explicit, and ArtifactPaths is empty. Treat failed binding as zero publishable artifacts; an empty manifest alone is not evidence that a renderer ran. RegisterProjectCorpus.GeneratedPropertySuppliedAsInput is a rejection vector of the same shape for ESM v7: its specification supplies a generated property as input, so compilation returns no ESM, the expected diagnostic is PLAY0485, and ArtifactPaths is empty. These vectors are conformance inputs, not renderer plans or a CLI activation policy.
For profiles, admission, deterministic planning, scope behavior, publication boundaries, and verification, follow the canonical Stage renderer target guide.
Ownership boundaries
Section titled “Ownership boundaries”| Owner | Semantic responsibility | Does not own |
|---|---|---|
| Screenplay | The language, syntax tree, compiler, printer, semantic identities, executable semantic model, and portable execution plan | Source-framework interpretation, target-specific realization, or CLI admission |
| Generation adapters | Interpretation of authored source into neutral facts, evidence, and diagnostics | Workspace authority, Screenplay language semantics, syntax printing, or target artifacts |
| Generation core | Deterministic resolution, lowering, canonical printing, and compiler verification across adapter contributions | Adapter discovery or framework-specific source interpretation |
| Stage renderers | Target capability admission and pure, deterministic artifact planning from the executable semantic model | Source recovery, file publication, process execution, or CLI command policy |
| CLI orchestration | Command-line selection, fully resolved profiles, planner invocation, diagnostics, staging, and publication | Screenplay semantics or target realization rules |
The source-recovery host and the rendering CLI are composition boundaries. They decide which trusted integrations to invoke; implementing an extension contract does not grant that trust.
Neutral extension catalog
Section titled “Neutral extension catalog”An extension catalog describes interoperability options. It is documentation, not a plugin loader, package-discovery mechanism, trust decision, compatibility guarantee, or CLI allowlist.
| Extension level | Public entry point | Input | Output | Activation model |
|---|---|---|---|---|
| Syntax consumer | ScreenplaySyntaxWalker | Screenplay syntax root or subtree | Consumer-defined analysis or export | The consuming tool constructs and invokes its walker |
| .NET source recovery | IDotNetScreenplayAdapter | DotNetAnalysisContext and DotNetAdapterOptions | AdapterContribution | A source host explicitly selects and composes adapters |
| Target rendering | IArtifactRenderPlanner | ArtifactRenderRequest | ArtifactRenderPlan | A caller explicitly constructs the planner and profile; a CLI may separately bundle a reviewed target |
Clearly labeled ecosystem examples include the Vogen source adapter in Screenplay Generation, the Critter Stack source adapter as a framework-specific integration, and the Cratis artifact render planner in Stage. Ecosystem-specific adapters and renderers are owned separately from the core extension contracts. Catalog membership documents an integration; it does not imply admission to or support by the Cratis CLI.
The Cratis CLI currently uses a static, reviewed renderer-target roster. A renderer appearing in this catalog, being installed beside a workspace, or implementing IArtifactRenderPlanner does not admit it to that roster. CLI support requires a separate orchestration change, explicit profile construction, publication wiring, and verification.