From b6831c39513c2076ce8534efc948dffb0351400b Mon Sep 17 00:00:00 2001 From: woksin Date: Mon, 12 Oct 2026 22:53:39 +0200 Subject: [PATCH 1/5] Update bundled Stage and Screenplay and admit ESM v11 through v13 --- Directory.Packages.props | 4 ++-- Documentation/reference/screenplay-mcp.md | 4 ++-- Documentation/reference/screenplay.md | 22 +++++++++---------- .../and_a_sibling_is_not_admitted.cs | 4 +++- .../when_planning_a_supported_application.cs | 4 ++-- .../when_checking_admitted_versions.cs | 21 +++++++++++++----- ...with_a_folder_and_the_default_stage_tag.cs | 4 ++-- ...h_a_play_file_and_the_default_stage_tag.cs | 4 ++-- .../when_planning_an_esm_v7_model.cs | 6 ++--- ...an_unselected_slice_is_refused_by_stage.cs | 4 +++- .../when_resolving_the_default_tag.cs | 2 +- .../Render/RenderedSemanticVersions.cs | 6 +++-- 12 files changed, 50 insertions(+), 35 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index db5dd04d..432114d8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,9 +5,9 @@ 22.54.0 0.24.0 0.18.0 - 4.137.0 + 4.145.1 - 4.73.1 + 4.79.1 diff --git a/Documentation/reference/screenplay-mcp.md b/Documentation/reference/screenplay-mcp.md index 479dcab7..42ce4281 100644 --- a/Documentation/reference/screenplay-mcp.md +++ b/Documentation/reference/screenplay-mcp.md @@ -3,9 +3,9 @@ title: Screenplay MCP description: Run the embedded Screenplay MCP server and configure project-local AI host registrations safely. --- -`cratis screenplay mcp` hosts the embedded Screenplay 4.137.0 Model Context Protocol (MCP) server over standard input and output. `cratis version` shows the bundled compiler version; JSON output includes `screenplay.version`. It does not install tools, download packages, or check for CLI updates at startup. Native and Homebrew CLI distributions include the runtime; this command does not require a separate .NET installation. +`cratis screenplay mcp` hosts the embedded Screenplay 4.145.1 Model Context Protocol (MCP) server over standard input and output. `cratis version` shows the bundled compiler version; JSON output includes `screenplay.version`. It does not install tools, download packages, or check for CLI updates at startup. Native and Homebrew CLI distributions include the runtime; this command does not require a separate .NET installation. -The server can author and bind every ESM version the bundled Screenplay compiler reads; this does not establish renderability. The CLI's bundled Stage 4.73.1 renderer admits ESM v1 through v10, including event sources, streams and routes, and renders policy negation and authored XML documentation, but refuses generated command values (`STAGE-ESM-028`), command responses (`STAGE-ESM-029`), reactions, captures, triggers and Automation/Translate slices (`STAGE-ESM-024`), read-model absence assertions (`STAGE-ESM-027`), public events and event-target projections and reducers (`STAGE-ESM-032`), foreign origins and event-source captures (`STAGE-ESM-033`), reaction system identity (`STAGE-ESM-034`), reaction observer filters (`STAGE-ESM-035`) and per-production route overrides (`STAGE-ESM-036`); reducer observer filters render. A specification whose unauthenticated caller carries roles or claims is refused with `STAGE-ESM-011`, including the unchanged canonical negation example. See [Screenplay rendering](screenplay.md#executable-model-versions) for command-specific compatibility. +The server can author and bind every ESM version the bundled Screenplay compiler reads; this does not establish renderability. The CLI's bundled Stage 4.79.1 renderer admits ESM v1 through v13, including event sources, streams and routes, and renders policy negation, authored XML documentation, non-optional UUID-backed generated concepts as Arc generated-value parameters, scalar and named scalar command responses, and supported event-triggered Automation slices. Unsupported generated types (`STAGE-ESM-028`), unsupported generation/response combinations (`STAGE-ESM-038`), mixed reaction effects (`STAGE-ESM-037`), captures, non-event triggers and Translate slices (`STAGE-ESM-024`) remain refused, as do read-model absence assertions (`STAGE-ESM-027`), public events and event-target projections and reducers (`STAGE-ESM-032`), foreign origins and event-source captures (`STAGE-ESM-033`), reaction system identity (`STAGE-ESM-034`), reaction observer filters (`STAGE-ESM-035`) and per-production route overrides (`STAGE-ESM-036`); reducer observer filters render. A specification whose unauthenticated caller carries roles or claims is refused with `STAGE-ESM-011`, including the unchanged canonical negation example. See [Screenplay rendering](screenplay.md#executable-model-versions) for command-specific compatibility. For Claude Desktop and ChatGPT Desktop **installation**, see [Screenplay desktop MCP](screenplay-desktop-mcp.md). Desktop management uses diff --git a/Documentation/reference/screenplay.md b/Documentation/reference/screenplay.md index adf4ef2c..aecf9a97 100644 --- a/Documentation/reference/screenplay.md +++ b/Documentation/reference/screenplay.md @@ -12,7 +12,7 @@ cratis screenplay mcp [PATH] cratis screenplay desktop install ``` -The CLI bundles Screenplay 4.137.0 (including its MCP server), Stage 4.73.1, and the Arc source adapter 22.54.0. `cratis render` admits ESM v1 through v10, including the canonical screen-composition corpus observable query and query-only specification shapes, and preserves authored Scene layouts, profiles, templates, navigation and forms when a model declares screens. Guarded screen actions render as runnable Scene guards: the generated frontend evaluates the authored guard, including enum-typed comparisons against the read model's declared members, and shows the action only when it passes. The renderer still refuses unsupported constructs within those versions: reactions, captures, triggers and Automation/Translate slices report `STAGE-ESM-024`, read-model absence assertions report `STAGE-ESM-027`, and the v9 and v10 constructs report their own codes (`STAGE-ESM-032` to `STAGE-ESM-036`, listed in the admission table below). Reducer observer filters render. It also refuses an evolved event until Stage can render its event-type migrations. Validation rejects `numbers exact` and `implementation` attachments on concept, built-in, and whole-command rules. +The CLI bundles Screenplay 4.145.1 (including its MCP server), Stage 4.79.1, and the Arc source adapter 22.54.0. `cratis render` admits ESM v1 through v13, including the canonical screen-composition corpus observable query and query-only specification shapes, and preserves authored Scene layouts, profiles, templates, navigation and forms when a model declares screens. Guarded screen actions render as runnable Scene guards: the generated frontend evaluates the authored guard, including enum-typed comparisons against the read model's declared members, and shows the action only when it passes. The renderer still refuses unsupported constructs within those versions: captures, non-event triggers and Translate slices report `STAGE-ESM-024`; supported event-triggered Automation slices render, while reactions mixing event production and command invocation report `STAGE-ESM-037`, read-model absence assertions report `STAGE-ESM-027`, and the v9 and v10 constructs report their own codes (`STAGE-ESM-032` to `STAGE-ESM-036`, listed in the admission table below). Reducer observer filters render. It also refuses an evolved event until Stage can render its event-type migrations. Exact numbers are admitted by the bundled compiler and renderer. Validation rejects `implementation` attachments on concept, built-in, and whole-command rules. For the embedded stdio server, the `visualize-model` MCP App, and native AI host registration, see [Screenplay MCP](screenplay-mcp.md). For Claude Desktop and ChatGPT Desktop installation, see [Screenplay desktop MCP](screenplay-desktop-mcp.md). @@ -27,25 +27,25 @@ A newer bundled compiler can produce a newer executable semantic model (ESM) ver | Command | ESM v7 (generated command values, command responses and policy negation with `not`) | |---|---| | `cratis screenplay validate` | Admitted. Without `--executable` it parses and merges v7 syntax and runs the source checks only; with `--executable` it also binds the model and reports binding errors. | -| `cratis screenplay mcp` | Admitted for authoring and reading. The embedded server binds v7 models, pages their executable model (generated properties, responses, generation fixtures and return expectations) and reports execution readiness. Reference execution is not offered: neither the CLI nor the embedded server runs specifications. | -| `cratis render` | Admitted. Policy negation renders. Generated command values and command responses are refused by the renderer with `STAGE-ESM-028` and `STAGE-ESM-029`, respectively; see [Cratis/Stage#175](https://github.com/Cratis/Stage/issues/175). Stage refuses a specification whose unauthenticated caller carries roles or claims with `STAGE-ESM-011`; this includes the unchanged canonical negation example. A refusal leaves no publishable artifacts. Versions newer than v10 are still blocked by `CLI-RENDER-004`, naming the version. | -| `cratis screenplay generate` | From Arc 22.52.0, the Arc adapter recovers supported generated UUID values and command responses by default for commands without successful scenarios. Output containing these constructs compiles to ESM v7, which `cratis render` admits, but the renderer refuses generated values with `STAGE-ESM-028` and responses with `STAGE-ESM-029`, even without `--authoring-only-constructs`; see [Cratis/Stage#175](https://github.com/Cratis/Stage/issues/175). Unsupported shapes and values needed by pre-generation protection remain in code with `SP0052`. Marten and Critter Stack retain their existing recovery surface. | -| `cratis run` | The Stage 4.73.1 container compiles the mounted `.play` files with its own Screenplay release, 4.137.0. Passing `cratis screenplay validate` does not establish Stage support for every construct. | +| `cratis screenplay mcp` | Admitted for authoring and reading. The embedded server binds v7 models, pages their executable model (generated properties, responses, generation fixtures and return expectations) and reports execution readiness. The embedded server offers deterministic reference execution through `run-specifications`, including retained proposal candidates. | +| `cratis render` | Admitted. Policy negation renders. Server-generated, non-optional UUID-backed concepts render as Arc generated-value parameters. Scalar responses and named responses whose fields come from scalar command properties render. Unsupported generated types report `STAGE-ESM-028`; unsupported generation/response combinations report `STAGE-ESM-038`. Stage refuses a specification whose unauthenticated caller carries roles or claims with `STAGE-ESM-011`; this includes the unchanged canonical negation example. A refusal leaves no publishable artifacts. Versions newer than v13 are still blocked by `CLI-RENDER-004`, naming the version. | +| `cratis screenplay generate` | From Arc 22.52.0, the Arc adapter recovers supported generated UUID values and command responses by default for commands without successful scenarios. Output containing these constructs compiles to ESM v7, which `cratis render` admits. The renderer supports non-optional UUID-backed generated concepts and scalar or named scalar command responses; unsupported types or combinations still receive typed refusals. Unsupported shapes and values needed by pre-generation protection remain in code with `SP0052`. Marten and Critter Stack retain their existing recovery surface. | +| `cratis run` | The Stage 4.79.1 container compiles the mounted `.play` files with its own bundled Screenplay compiler. Passing `cratis screenplay validate` does not establish Stage support for every construct. | -The bundled Screenplay reader supports ESM schemas through v10. ESM v5 adds keyed read-model absence assertions; v8 adds event sources, streams, command routes and specification routes. ESM v9 adds public events, foreign event origins, translation direction, event-target projections/reducers and event-source captures; v10 adds reaction system identity (`runsAs`), per-production route overrides (`SemanticProducedEvent.Route`) and observer filters (`SemanticReaction.From` and `SemanticReducer.From`). Version reading and command admission are separate: +The bundled Screenplay reader supports ESM schemas through v14. ESM v5 adds keyed read-model absence assertions; v8 adds event sources, streams, command routes and specification routes. ESM v9 adds public events, foreign event origins, translation direction, event-target projections/reducers and event-source captures; v10 adds reaction system identity (`runsAs`), per-production route overrides (`SemanticProducedEvent.Route`) and observer filters (`SemanticReaction.From` and `SemanticReducer.From`). Version reading and command admission are separate: | Command | ESM v8 admission | ESM v9 admission | ESM v10 admission | |---|---|---|---| | `cratis screenplay validate` | Admitted for source validation and `--executable` binding. | Admitted for source validation and binding; not proof of renderer or runtime support. | Admitted for source validation and binding; not proof of renderer or runtime support. | -| `cratis screenplay mcp` | Admitted for authoring, reading and binding, including event-source, stream and route views. | Admitted by the bundled Screenplay server. No reference specification execution is offered by the CLI. | Admitted by the bundled Screenplay server. No reference specification execution is offered by the CLI. | +| `cratis screenplay mcp` | Admitted for authoring, reading and binding, including event-source, stream and route views. | Admitted by the bundled Screenplay server, including reference specification execution. | Admitted by the bundled Screenplay server, including reference specification execution. | | `cratis screenplay conform` | Admitted for structural comparison. Coverage gaps and declaration-level fallback remain explicit. | Admitted by the bundled Screenplay comparison API; a pass does not establish Stage support. | Admitted by the bundled Screenplay comparison API; a pass does not establish Stage support. | | `cratis screenplay generate` | Checks recovered source with the bundled compiler. The Arc adapter's output remains ESM v7; recovery is provider-owned. | Does not execute or render these constructs. Recovery remains provider-owned. | Does not execute or render these constructs. Recovery remains provider-owned. | | `cratis view` | Serves embedded or generated source through the bundled event-model explorer. | Viewing is not renderer admission. | Viewing is not renderer admission. | | Generation statistics | Count declarations in compiled source, including documents with event routes. | Counts do not establish executable or renderer support. | Counts do not establish executable or renderer support. | -| `cratis render` | Admitted through Stage 4.73.1, including routed commands and supported routed specifications. Unsupported constructs still block publication. | Admitted for construct-level planning. Public events and event-target projections/reducers report `STAGE-ESM-032`; foreign origins and event-source captures report `STAGE-ESM-033`; translation direction reports `STAGE-ESM-024`. | Admitted for construct-level planning. Reaction `runsAs` reports `STAGE-ESM-034`; reaction `from` filters (`SemanticReaction.From`) report `STAGE-ESM-035`; per-production route overrides (`SemanticProducedEvent.Route`) report `STAGE-ESM-036`. Reducer `from` filters (`SemanticReducer.From`) render as Chronicle source/stream attributes; specifications requiring filtered read-model replay remain refused. | -| `cratis run` | Refused by the Stage 4.73.1 container: `cratis run` starts Stage's default `eventmodel` engine, which reports `STAGE-ESM-030` for named event sources, streams and routes because only Stage's semantic engine runs them. Render the model with `cratis render` instead. | Stage applies its own runtime admission; CLI validation is not Stage admission. | Stage applies its own runtime admission; CLI validation is not Stage admission. | +| `cratis render` | Admitted through Stage 4.79.1, including routed commands and supported routed specifications. Unsupported constructs still block publication. | Admitted for construct-level planning. Public events and event-target projections/reducers report `STAGE-ESM-032`; foreign origins and event-source captures report `STAGE-ESM-033`; translation direction reports `STAGE-ESM-024`. | Admitted for construct-level planning. Reaction `runsAs` reports `STAGE-ESM-034`; reaction `from` filters (`SemanticReaction.From`) report `STAGE-ESM-035`; per-production route overrides (`SemanticProducedEvent.Route`) report `STAGE-ESM-036`. Reducer `from` filters (`SemanticReducer.From`) render as Chronicle source/stream attributes; specifications requiring filtered read-model replay remain refused. | +| `cratis run` | Refused by the Stage 4.79.1 container: `cratis run` starts Stage's default `eventmodel` engine, which reports `STAGE-ESM-030` for named event sources, streams and routes because only Stage's semantic engine runs them. Render the model with `cratis render` instead. | Stage applies its own runtime admission; CLI validation is not Stage admission. | Stage applies its own runtime admission; CLI validation is not Stage admission. | -Version admission does not admit every construct. A selected scope without unsupported constructs can plan normally at v9 or v10; a typed renderer refusal leaves no publishable artifacts. ESM v11 and newer remain blocked by `CLI-RENDER-004` before target planning or publication. +Version admission does not admit every construct. A selected scope without unsupported constructs can plan normally at v9 or v10; a typed renderer refusal leaves no publishable artifacts. ESM v11 exact numbers, v12 enum member numbers and v13 concept compliance are admitted by the bundled renderer. ESM v14 query filters and newer versions remain blocked by `CLI-RENDER-004` before target planning or publication. ### Routed command example @@ -430,7 +430,7 @@ This adds readable operations with their external systems and implementation fil The generated document still compiles, but while authoring-only constructs are present it has **no executable model** (`PLAY0268`). Legacy reads also report `PLAY0271`: they do not imply decision consistency. The flag does not execute operations, generate implementation code, or bypass executable-model admission. Leave it unset when you do not need these additional constructs; it defaults to false. Marten and Critter Stack report `CLI0014` rather than silently applying an Arc-only option. -Supported generated UUID values and responses do not need this flag. From Arc 22.52.0, output containing them compiles to ESM v7, which `cratis render` admits, but the renderer refuses generated values with `STAGE-ESM-028` and responses with `STAGE-ESM-029`, even without `--authoring-only-constructs`; see [Cratis/Stage#175](https://github.com/Cratis/Stage/issues/175). Arc emits them by default where admitted; commands with successful scenarios retain their legacy representation until deterministic generation fixtures and response expectations can be recovered. See [Arc's generator reference](https://github.com/Cratis/Arc/blob/main/Documentation/backend/csharp/generating-a-screenplay.md) for the supported shapes and diagnostics. +Supported generated UUID values and responses do not need this flag. From Arc 22.52.0, output containing them compiles to ESM v7, which `cratis render` admits. Non-optional UUID-backed generated concepts render as Arc generated-value parameters; scalar and named scalar responses from command properties also render. Unsupported generation/response combinations remain blocking diagnostics. Arc emits them by default where admitted; commands with successful scenarios retain their legacy representation until deterministic generation fixtures and response expectations can be recovered. See [Arc's generator reference](https://github.com/Cratis/Arc/blob/main/Documentation/backend/csharp/generating-a-screenplay.md) for the supported shapes and diagnostics. ### Naming the modules diff --git a/Source/Cli.Specs/for_ArtifactPublisher/when_publishing_scopes/and_a_sibling_is_not_admitted.cs b/Source/Cli.Specs/for_ArtifactPublisher/when_publishing_scopes/and_a_sibling_is_not_admitted.cs index 201deffa..0acea27e 100644 --- a/Source/Cli.Specs/for_ArtifactPublisher/when_publishing_scopes/and_a_sibling_is_not_admitted.cs +++ b/Source/Cli.Specs/for_ArtifactPublisher/when_publishing_scopes/and_a_sibling_is_not_admitted.cs @@ -19,9 +19,11 @@ feature Unsupported slice StateChange RegisterOtherProject command RegisterOtherProject projectId ProjectId generated identifier + occurred ProjectId generated name String produces event OtherProjectRegistered name String = name + timestamp DateTime = $context.occurred """); async Task Because() @@ -32,7 +34,7 @@ async Task Because() } [Fact] void should_keep_full_render_refused() => _full.Success.ShouldBeFalse(); - [Fact] void should_name_the_unrenderable_construct() => _full.Diagnostics.Select(diagnostic => diagnostic.Code).ShouldContain("STAGE-ESM-028"); + [Fact] void should_name_the_unrenderable_construct() => _full.Diagnostics.Select(diagnostic => diagnostic.Code).ShouldContain("STAGE-ESM-038"); [Fact] void should_render_the_admitted_feature() => _scoped.Success.ShouldBeTrue(); [Fact] void should_publish_a_scaffolded_application() => File.Exists(Path.Combine(_destination, "Shop.csproj")).ShouldBeTrue(); [Fact] void should_make_the_remainder_visible() => _published.Receipt.OutsideScope!.ShouldContain("Tasks.Unsupported.RegisterOtherProject"); diff --git a/Source/Cli.Specs/for_CratisRenderTarget/when_planning_a_supported_application.cs b/Source/Cli.Specs/for_CratisRenderTarget/when_planning_a_supported_application.cs index 0f1861e7..ef4b6a04 100644 --- a/Source/Cli.Specs/for_CratisRenderTarget/when_planning_a_supported_application.cs +++ b/Source/Cli.Specs/for_CratisRenderTarget/when_planning_a_supported_application.cs @@ -31,8 +31,8 @@ Task Because() [Fact] void should_plan_the_exact_facade_target_version() => _result.TargetVersion.ShouldEqual(_expected.TargetVersion); [Fact] void should_plan_the_exact_facade_renderer() => _result.Renderer.ShouldEqual(_expected.Renderer); [Fact] void should_plan_the_exact_facade_renderer_version() => _result.RendererVersion.ShouldEqual(_expected.RendererVersion); - [Fact] void should_load_the_released_stage_44910_renderer_package() => - typeof(CratisRendering).Assembly.GetCustomAttribute()!.InformationalVersion.Split('+')[0].ShouldEqual("4.73.1"); + [Fact] void should_load_the_released_stage_4791_renderer_package() => + typeof(CratisRendering).Assembly.GetCustomAttribute()!.InformationalVersion.Split('+')[0].ShouldEqual("4.79.1"); [Fact] void should_plan_the_same_application_name() => _result.ApplicationName.ShouldEqual(_expected.ApplicationName); [Fact] void should_plan_the_same_semantic_revision() => _result.SemanticRevision.ShouldEqual(_expected.SemanticRevision); [Fact] void should_plan_the_exact_facade_artifacts() => diff --git a/Source/Cli.Specs/for_RenderedSemanticVersions/when_checking_admitted_versions.cs b/Source/Cli.Specs/for_RenderedSemanticVersions/when_checking_admitted_versions.cs index 3bf0285b..88970fa8 100644 --- a/Source/Cli.Specs/for_RenderedSemanticVersions/when_checking_admitted_versions.cs +++ b/Source/Cli.Specs/for_RenderedSemanticVersions/when_checking_admitted_versions.cs @@ -8,17 +8,26 @@ namespace Cratis.Cli.for_RenderedSemanticVersions; public class when_checking_admitted_versions : Specification { ScreenplayDiagnostic? _newer; - ScreenplayDiagnostic? _admitted; + ScreenplayDiagnostic? _v10; + ScreenplayDiagnostic? _v11; + ScreenplayDiagnostic? _v12; + ScreenplayDiagnostic? _v13; void Because() { - _newer = RenderedSemanticVersions.Check(new SemanticVersion(11, 0)); - _admitted = RenderedSemanticVersions.Check(SemanticVersion.V10); + _newer = RenderedSemanticVersions.Check(SemanticVersion.V14); + _v10 = RenderedSemanticVersions.Check(SemanticVersion.V10); + _v11 = RenderedSemanticVersions.Check(SemanticVersion.V11); + _v12 = RenderedSemanticVersions.Check(SemanticVersion.V12); + _v13 = RenderedSemanticVersions.Check(SemanticVersion.V13); } - [Fact] void should_admit_v10() => _admitted.ShouldBeNull(); - [Fact] void should_refuse_versions_newer_than_v10() => _newer!.Code.ShouldEqual("CLI-RENDER-004"); - [Fact] void should_name_the_refused_version() => _newer!.Message.ShouldContain("ESM v11.0"); + [Fact] void should_keep_v10_admitted() => _v10.ShouldBeNull(); + [Fact] void should_admit_v11_exact_numbers() => _v11.ShouldBeNull(); + [Fact] void should_admit_v12_enum_member_numbers() => _v12.ShouldBeNull(); + [Fact] void should_admit_v13_concept_compliance() => _v13.ShouldBeNull(); + [Fact] void should_refuse_v14_query_filters() => _newer!.Code.ShouldEqual("CLI-RENDER-004"); + [Fact] void should_name_the_refused_version() => _newer!.Message.ShouldContain("ESM v14.0"); [Fact] void should_block_rendering_newer_versions() => _newer!.Severity.ShouldEqual(ScreenplayDiagnosticSeverity.Error); [Fact] void should_describe_the_explicit_version_gate() => _newer!.Message.ShouldContain("Newer model versions require explicit renderer admission"); } diff --git a/Source/Cli.Specs/for_RunCommand/when_running/with_a_folder_and_the_default_stage_tag.cs b/Source/Cli.Specs/for_RunCommand/when_running/with_a_folder_and_the_default_stage_tag.cs index c55f7771..d5adad17 100644 --- a/Source/Cli.Specs/for_RunCommand/when_running/with_a_folder_and_the_default_stage_tag.cs +++ b/Source/Cli.Specs/for_RunCommand/when_running/with_a_folder_and_the_default_stage_tag.cs @@ -15,8 +15,8 @@ public class with_a_folder_and_the_default_stage_tag : given.a_run_command [Unix.Fact] void should_start_the_released_stage_image_that_matches_the_renderer_packages() => DockerArguments.ShouldContain($"cratis/stage:{StageContainer.DefaultTag}"); - [Unix.Fact] void should_run_stage_44910_by_default() => - DockerArguments.ShouldContain("cratis/stage:4.73.1"); + [Unix.Fact] void should_run_stage_4791_by_default() => + DockerArguments.ShouldContain("cratis/stage:4.79.1"); [Unix.Fact] void should_mount_only_the_selected_folder_read_only() => DockerArguments.ShouldContain($"{_folder}:/eventmodel:ro"); diff --git a/Source/Cli.Specs/for_RunCommand/when_running/with_a_play_file_and_the_default_stage_tag.cs b/Source/Cli.Specs/for_RunCommand/when_running/with_a_play_file_and_the_default_stage_tag.cs index cb92d218..21858dd8 100644 --- a/Source/Cli.Specs/for_RunCommand/when_running/with_a_play_file_and_the_default_stage_tag.cs +++ b/Source/Cli.Specs/for_RunCommand/when_running/with_a_play_file_and_the_default_stage_tag.cs @@ -20,8 +20,8 @@ void Establish() [Unix.Fact] void should_start_the_released_stage_image_that_matches_the_renderer_packages() => DockerArguments.ShouldContain($"cratis/stage:{StageContainer.DefaultTag}"); - [Unix.Fact] void should_run_stage_44910_by_default() => - DockerArguments.ShouldContain("cratis/stage:4.73.1"); + [Unix.Fact] void should_run_stage_4791_by_default() => + DockerArguments.ShouldContain("cratis/stage:4.79.1"); [Unix.Fact] void should_mount_only_the_selected_file_read_only() => DockerArguments.ShouldContain($"{_path}:/eventmodel/input.play:ro"); diff --git a/Source/Cli.Specs/for_ScreenplayPlanning/when_planning_an_esm_v7_model.cs b/Source/Cli.Specs/for_ScreenplayPlanning/when_planning_an_esm_v7_model.cs index 725b869f..d42c21e3 100644 --- a/Source/Cli.Specs/for_ScreenplayPlanning/when_planning_an_esm_v7_model.cs +++ b/Source/Cli.Specs/for_ScreenplayPlanning/when_planning_an_esm_v7_model.cs @@ -24,9 +24,9 @@ void Establish() async Task Because() => _result = await Plan(_path); - [Fact] void should_not_be_successful() => _result.Success.ShouldBeFalse(); - [Fact] void should_not_plan_any_publishable_artifacts() => _result.Artifacts!.Artifacts.ShouldBeEmpty(); + [Fact] void should_be_successful() => _result.Success.ShouldBeTrue(); + [Fact] void should_plan_publishable_artifacts() => _result.Artifacts!.Artifacts.ShouldNotBeEmpty(); [Fact] void should_leave_version_admission_to_the_renderer() => _result.Diagnostics.Select(_ => _.Code).ShouldNotContain(RenderedSemanticVersions.NotAdmittedCode); - [Fact] void should_report_the_renderers_generated_value_and_response_refusals() => _result.Diagnostics.Where(_ => _.Severity == ScreenplayDiagnosticSeverity.Error).Select(_ => _.Code).Distinct().Order(StringComparer.Ordinal).ShouldEqual("STAGE-ESM-028", "STAGE-ESM-029"); + [Fact] void should_admit_generated_values_and_responses() => _result.Diagnostics.Where(_ => _.Severity == ScreenplayDiagnosticSeverity.Error).ShouldBeEmpty(); [Fact] void should_compile_without_source_errors() => _result.Diagnostics.Where(_ => _.Severity == ScreenplayDiagnosticSeverity.Error && _.Code.StartsWith("PLAY", StringComparison.Ordinal)).ShouldBeEmpty(); } diff --git a/Source/Cli.Specs/for_ScreenplayPlanning/when_planning_scopes/and_an_unselected_slice_is_refused_by_stage.cs b/Source/Cli.Specs/for_ScreenplayPlanning/when_planning_scopes/and_an_unselected_slice_is_refused_by_stage.cs index a5eb7d9a..8683d9f7 100644 --- a/Source/Cli.Specs/for_ScreenplayPlanning/when_planning_scopes/and_an_unselected_slice_is_refused_by_stage.cs +++ b/Source/Cli.Specs/for_ScreenplayPlanning/when_planning_scopes/and_an_unselected_slice_is_refused_by_stage.cs @@ -18,9 +18,11 @@ feature Unsupported slice StateChange RegisterOtherProject command RegisterOtherProject projectId ProjectId generated identifier + occurred ProjectId generated name String produces event OtherProjectRegistered name String = name + timestamp DateTime = $context.occurred """); _full = await Plan(); } @@ -28,7 +30,7 @@ produces event OtherProjectRegistered async Task Because() => _scoped = await Plan("Projects.Registration.RegisterProject"); [Fact] void should_refuse_the_full_plan() => _full.Success.ShouldBeFalse(); - [Fact] void should_report_the_generated_value_renderer_refusal() => _full.Diagnostics.ShouldContain(diagnostic => diagnostic.Severity == ScreenplayDiagnosticSeverity.Error && diagnostic.Code == "STAGE-ESM-028"); + [Fact] void should_report_the_generated_occurrence_name_collision() => _full.Diagnostics.ShouldContain(diagnostic => diagnostic.Severity == ScreenplayDiagnosticSeverity.Error && diagnostic.Code == "STAGE-ESM-038"); [Fact] void should_admit_the_independent_selected_slice() => _scoped.Success.ShouldBeTrue(); [Fact] void should_have_no_scoped_error_diagnostics() => _scoped.Diagnostics.Where(diagnostic => diagnostic.Severity == ScreenplayDiagnosticSeverity.Error).ShouldBeEmpty(); [Fact] void should_include_the_selected_slice() => _scoped.Artifacts!.Artifacts.ShouldContain(artifact => artifact.RelativePath == "Projects/Registration/RegisterProject/RegisterProject.cs"); diff --git a/Source/Cli.Specs/for_StageContainer/when_resolving_the_default_tag.cs b/Source/Cli.Specs/for_StageContainer/when_resolving_the_default_tag.cs index 6c9cb8db..282344e5 100644 --- a/Source/Cli.Specs/for_StageContainer/when_resolving_the_default_tag.cs +++ b/Source/Cli.Specs/for_StageContainer/when_resolving_the_default_tag.cs @@ -26,7 +26,7 @@ void Establish() => _renderer = typeof(ArtifactRenderPlan).Assembly [Fact] void should_run_the_stage_the_renderer_came_from() => _tag.ShouldEqual(_renderer); - [Fact] void should_run_the_released_stage_4731_image() => _tag.ShouldEqual("4.73.1"); + [Fact] void should_run_the_released_stage_4791_image() => _tag.ShouldEqual("4.79.1"); [Fact] void should_not_run_a_moving_tag() => _tag.ShouldNotEqual(StageContainer.FallbackTag); diff --git a/Source/Cli/Commands/Render/RenderedSemanticVersions.cs b/Source/Cli/Commands/Render/RenderedSemanticVersions.cs index c10e87b5..a9434158 100644 --- a/Source/Cli/Commands/Render/RenderedSemanticVersions.cs +++ b/Source/Cli/Commands/Render/RenderedSemanticVersions.cs @@ -16,7 +16,9 @@ namespace Cratis.Cli.Commands.Render; /// reviewed; the renderer diagnoses unsupported constructs within admitted versions. ESM v8 event sources, /// streams, command routes and specification routes are admitted by the bundled Stage. ESM v9 and v10 /// reach its construct-level admission: unsupported public/foreign events, captures, reaction identity, -/// reaction filters and production route overrides receive typed refusals; reducer filters render. +/// reaction filters and production route overrides receive typed refusals; reducer filters render. ESM v11 +/// exact numbers, v12 enum member numbers and v13 concept compliance are admitted by Stage 4.79.1. +/// ESM v14 query filters remain refused until the renderer admits them. /// public static class RenderedSemanticVersions { @@ -28,7 +30,7 @@ public static class RenderedSemanticVersions /// /// Gets the newest model version cratis render admits. /// - public static SemanticVersion Newest { get; } = SemanticVersion.V10; + public static SemanticVersion Newest { get; } = SemanticVersion.V13; /// /// Reports a model version cratis render does not admit. From 06aeda58434c21cef922672e2fcdba4d413c4f9b Mon Sep 17 00:00:00 2001 From: woksin Date: Mon, 12 Oct 2026 22:54:11 +0200 Subject: [PATCH 2/5] Compile empty render scopes without unrelated slice admission --- Documentation/reference/screenplay.md | 2 +- ...cope_has_an_invalid_application_concept.cs | 37 +++++++++++++++++++ ..._empty_scope_has_an_unsupported_sibling.cs | 6 +-- .../and_the_only_slice_was_deleted.cs | 16 ++++++++ .../Cli/Commands/Render/ScreenplayPlanning.cs | 2 +- 5 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 Source/Cli.Specs/for_ScreenplayPlanning/when_planning_scopes/and_an_empty_scope_has_an_invalid_application_concept.cs diff --git a/Documentation/reference/screenplay.md b/Documentation/reference/screenplay.md index aecf9a97..d1f8cf5f 100644 --- a/Documentation/reference/screenplay.md +++ b/Documentation/reference/screenplay.md @@ -112,7 +112,7 @@ cratis render ./plays --name Shop --destination ./out \ cratis render ./plays --name Shop --destination ./out --scope Orders.Placing --check ``` -The entire source set must compile and bind. Execution-plan (`PLAN-*`) problems in unselected slices are reported as warnings; problems in selected slices and application-level declarations still block a scoped render. When the selected module or feature no longer contains any slices, execution admission covers the whole model. Renderer (`STAGE-ESM-*`) refusals in unselected slices do not block a scoped render: Stage admits and renders only the selected slices. Shared concepts and composite types referenced by those slices are included. Authored XML documentation and shared policy artifacts have the same bytes as in a full render. Unknown addresses report `CLI-RENDER-011`, including existing sibling names at the first unmatched level. An address that names both a feature and a slice reports `CLI-RENDER-012`. Both exit with 5. +The entire source set must compile and bind. Execution-plan (`PLAN-*`) problems in unselected slices are reported as warnings; problems in selected slices and application-level declarations still block a scoped render. When the selected module or feature no longer contains any slices, execution admission covers application-level declarations only; unrelated slice problems remain warnings, so they do not prevent removing that scope's previously owned artifacts. Renderer (`STAGE-ESM-*`) refusals in unselected slices do not block a scoped render: Stage admits and renders only the selected slices. Shared concepts and composite types referenced by those slices are included. Authored XML documentation and shared policy artifacts have the same bytes as in a full render. Unknown addresses report `CLI-RENDER-011`, including existing sibling names at the first unmatched level. An address that names both a feature and a slice reports `CLI-RENDER-012`. Both exit with 5. The application scaffold and default Scene belong to Application scope. A scoped plan excludes both. When the destination has **no ownership manifest**, publication adds the plain application scaffold, using the same application name, project name and root namespace as a full render; it does not add a default Scene. Once a manifest exists, scoped publication leaves the existing scaffold alone. diff --git a/Source/Cli.Specs/for_ScreenplayPlanning/when_planning_scopes/and_an_empty_scope_has_an_invalid_application_concept.cs b/Source/Cli.Specs/for_ScreenplayPlanning/when_planning_scopes/and_an_empty_scope_has_an_invalid_application_concept.cs new file mode 100644 index 00000000..d53a3aec --- /dev/null +++ b/Source/Cli.Specs/for_ScreenplayPlanning/when_planning_scopes/and_an_empty_scope_has_an_invalid_application_concept.cs @@ -0,0 +1,37 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Cratis.Cli.for_ScreenplayPlanning.when_planning_scopes; + +#pragma warning disable MA0136 // Fixture line endings do not affect normalized rendered artifacts. + +public class and_an_empty_scope_has_an_invalid_application_concept : given.a_scoped_model +{ + ScreenplayRenderPlan _plan = null!; + string _ownedPath = null!; + byte[] _ownedBytes = null!; + byte[] _manifestBytes = null!; + + async Task Establish() + { + await Publish(await Plan()); + _ownedPath = Directory.GetFiles(Path.Combine(_destination, "Tasks", "Backlog", "Intake"), "*.cs", SearchOption.AllDirectories)[0]; + _ownedBytes = await File.ReadAllBytesAsync(_ownedPath); + _manifestBytes = await File.ReadAllBytesAsync(Path.Combine(_destination, ".cratis-render.json")); + await File.AppendAllTextAsync(_projectFile, "\nconcept Broken : MissingType\n"); + await File.WriteAllTextAsync(_taskFile, """ + module Tasks + feature Backlog + feature Intake + description "Tasks have been removed" + """); + } + + async Task Because() => _plan = await Plan("Tasks.Backlog.Intake"); + + [Fact] void should_refuse_the_empty_scope() => _plan.Success.ShouldBeFalse(); + [Fact] void should_report_the_application_error() => _plan.Diagnostics.Any(diagnostic => diagnostic.Severity == ScreenplayDiagnosticSeverity.Error).ShouldBeTrue(); + [Fact] void should_have_no_publishable_plan() => _plan.Artifacts.ShouldBeNull(); + [Fact] void should_preserve_previously_owned_artifacts() => File.ReadAllBytes(_ownedPath).SequenceEqual(_ownedBytes).ShouldBeTrue(); + [Fact] void should_preserve_the_manifest() => File.ReadAllBytes(Path.Combine(_destination, ".cratis-render.json")).SequenceEqual(_manifestBytes).ShouldBeTrue(); +} diff --git a/Source/Cli.Specs/for_ScreenplayPlanning/when_planning_scopes/and_an_empty_scope_has_an_unsupported_sibling.cs b/Source/Cli.Specs/for_ScreenplayPlanning/when_planning_scopes/and_an_empty_scope_has_an_unsupported_sibling.cs index e536d6ba..da938d0f 100644 --- a/Source/Cli.Specs/for_ScreenplayPlanning/when_planning_scopes/and_an_empty_scope_has_an_unsupported_sibling.cs +++ b/Source/Cli.Specs/for_ScreenplayPlanning/when_planning_scopes/and_an_empty_scope_has_an_unsupported_sibling.cs @@ -18,8 +18,8 @@ feature Intake async Task Because() => _results = [await Plan("Tasks"), await Plan("Tasks.Backlog.Intake")]; - [Fact] void should_keep_whole_model_admission_for_empty_scopes() => _results.All(result => !result.Success).ShouldBeTrue(); - [Fact] void should_report_the_unsupported_sibling_as_an_error() => _results.All(result => result.Diagnostics.Any(diagnostic => diagnostic.Code == "PLAN-UNSUPPORTEDEVENTCONTEXT" && diagnostic.Severity == ScreenplayDiagnosticSeverity.Error)).ShouldBeTrue(); + [Fact] void should_admit_the_empty_scopes() => _results.All(result => result.Success).ShouldBeTrue(); + [Fact] void should_report_the_unsupported_sibling_as_a_warning() => _results.All(result => result.Diagnostics.Any(diagnostic => diagnostic.Code == "PLAN-UNSUPPORTEDEVENTCONTEXT" && diagnostic.Severity == ScreenplayDiagnosticSeverity.Warning)).ShouldBeTrue(); [Fact] void should_not_report_invalid_slice_selection() => _results.SelectMany(result => result.Diagnostics).Where(diagnostic => diagnostic.Code == "PLAN-INVALIDSLICESELECTION").ShouldBeEmpty(); - [Fact] void should_not_plan_artifacts() => _results.All(result => result.Artifacts is null).ShouldBeTrue(); + [Fact] void should_have_no_slice_artifacts() => _results.All(result => result.Artifacts!.Artifacts.IsEmpty).ShouldBeTrue(); } diff --git a/Source/Cli.Specs/for_ScreenplayPlanning/when_planning_scopes/and_the_only_slice_was_deleted.cs b/Source/Cli.Specs/for_ScreenplayPlanning/when_planning_scopes/and_the_only_slice_was_deleted.cs index f51dbd1d..a52edfb1 100644 --- a/Source/Cli.Specs/for_ScreenplayPlanning/when_planning_scopes/and_the_only_slice_was_deleted.cs +++ b/Source/Cli.Specs/for_ScreenplayPlanning/when_planning_scopes/and_the_only_slice_was_deleted.cs @@ -30,6 +30,21 @@ feature Backlog feature Intake description "Tasks have been removed" """); + await File.WriteAllTextAsync(_lookupFile, """ + module Projects + feature Registration + slice StateView ProjectLookup + readmodel ProjectSummary + projectId ProjectId + name String + lastSeen DateTime? + query ProjectById => ProjectSummary optional + by projectId ProjectId + projection ProjectSummaryProjection => ProjectSummary + from ProjectRegistered key projectId + name = name + lastSeen = $eventContext.occurred + """); } async Task Because() @@ -40,6 +55,7 @@ async Task Because() } [Fact] void should_admit_the_empty_feature() => _plan.Success.ShouldBeTrue(); + [Fact] void should_report_the_unrelated_issue_as_a_warning() => _plan.Diagnostics.Any(diagnostic => diagnostic.Code == "PLAN-UNSUPPORTEDEVENTCONTEXT" && diagnostic.Severity == ScreenplayDiagnosticSeverity.Warning).ShouldBeTrue(); [Fact] void should_have_no_slice_artifacts() => _plan.Artifacts!.Artifacts.ShouldBeEmpty(); [Fact] void should_not_report_an_invalid_slice_selection() => _plan.Diagnostics.Where(diagnostic => diagnostic.Code == "PLAN-INVALIDSLICESELECTION").ShouldBeEmpty(); [Fact] void should_have_previously_owned_files() => _stalePaths.ShouldNotBeEmpty(); diff --git a/Source/Cli/Commands/Render/ScreenplayPlanning.cs b/Source/Cli/Commands/Render/ScreenplayPlanning.cs index 9a442015..2588d809 100644 --- a/Source/Cli/Commands/Render/ScreenplayPlanning.cs +++ b/Source/Cli/Commands/Render/ScreenplayPlanning.cs @@ -127,7 +127,7 @@ public ScreenplayRenderPlan PlanDocuments(ScreenplayDocumentRenderRequest reques var selectedSlices = selection is null ? [] : addresses.Slices.Where(slice => selection.Contains(slice.Id.ToString(), addresses)).Select(slice => slice.Id).ToArray(); - var execution = selectedSlices.Length == 0 + var execution = selection is null ? SemanticExecutionPlan.Compile(compilation.Value.Model) : SemanticExecutionPlan.Compile(compilation.Value.Model, selectedSlices); diagnostics.AddRange(execution.Issues.Select(issue => From 5e0dbd923cb111c2d31ad94614d0facd609067d4 Mon Sep 17 00:00:00 2001 From: woksin Date: Mon, 12 Oct 2026 22:54:21 +0200 Subject: [PATCH 3/5] Document bundled model notes and standalone open-question checks --- Documentation/reference/screenplay-mcp.md | 14 ++++++++++++++ Documentation/reference/screenplay.md | 11 +++++++++++ .../when_listing_bundled_authoring_tools.cs | 3 +++ 3 files changed, 28 insertions(+) diff --git a/Documentation/reference/screenplay-mcp.md b/Documentation/reference/screenplay-mcp.md index 42ce4281..f16f4fda 100644 --- a/Documentation/reference/screenplay-mcp.md +++ b/Documentation/reference/screenplay-mcp.md @@ -38,8 +38,22 @@ With an explicit path or project configuration, the CLI never searches parent pr Use `open-workspace` to obtain a revision-bound workspace, then discover the current contract with `syntax-schema`. For whole `.play` documents, use `propose-source`: it parses source server-side and supports creating, replacing, removing, moving, or renaming document keys. For typed edits, use `read-ast` handles and `propose-ast`. Both proposal tools default to Authoring validation; select Executable validation when the proposal must also bind. +`run-specifications` executes specifications through Screenplay's deterministic in-memory reference evaluator. Its optional `proposalId` selects a retained candidate without applying it, including when the proposal's disk base has become stale. It reports passed, failed or unsupported results; it does not run the generated application. Pin `expectedSourceRevision` when continuing a paged result. + Proposals require explicit formatting consent and identity continuity. Parse failures return `SourceParseFailed` with located `authoringDiagnostics` and no proposal. Review a successful proposal with `read-proposal` before `apply`; proposing never writes source files. The older `propose` tool remains executable-only. Source validity, executable readiness, and renderer admission are separate verdicts. +## Model notes + +Questions, tasks and review notes live in the committed, server-owned `.screenplay/notes.json` sidecar beneath the model root, outside `.play` syntax and executable revisions. Use the notes proposal/apply workflow rather than editing the format by hand. Do not put secrets or personal data in notes, author names, references or resolutions. + +| Tool | Contract | +|---|---| +| `list-model-notes` | Pages notes by anchor, scope, kind, state or reference. The default state is `open`; `view: "findings"` reports unresolved questions as host-only `model-note.open-question` information. Pin `expectedNotesRevision` when continuing pages. | +| `propose-model-notes` | Proposes an atomic add, edit, resolve, reopen, reanchor or remove batch. Requires workspace, catalog and notes revisions; the server supplies note IDs and UTC instants. | +| `read-proposal` with `view: "notes"` | Reviews the before/after notes before `apply`. Pin the proposal's `afterNotesRevision` as `expectedNotesRevision` on continuation. Source proposals also report newly orphaned note IDs in `orphanedNotes`. | + +Malformed or unreadable notes never block opening a model. Note tools refuse with `UnreadableNotes` without repairing or rewriting the sidecar; restore known-good content or resolve the conflict before reading notes again. Stable identities keep notes anchored across model renames and moves; removed identities leave visible orphans rather than deleting notes. A resolution records an answer, not an implementation of that answer. + ## Event model board The bundled server includes the `visualize-model` MCP App. In a host that renders MCP Apps, it draws your Screenplay application as an event model board beside the conversation. You can also pass a `proposalId` to inspect a proposed change before applying it, or `sketch` documents to visualize a what-if without writing files. diff --git a/Documentation/reference/screenplay.md b/Documentation/reference/screenplay.md index d1f8cf5f..6af0fefc 100644 --- a/Documentation/reference/screenplay.md +++ b/Documentation/reference/screenplay.md @@ -697,6 +697,17 @@ cratis screenplay validate ./plays --check all # opt into structural completenes cratis screenplay validate ./plays --check navigation --check field-origins --executable ``` +### Model questions + +`cratis screenplay validate` checks source and optional executable binding; it does not gate model notes. Use the check command in Screenplay's standalone CLI to report or fail on unresolved questions after [installing the Screenplay tool](/screenplay/tool/#install-the-cli): + +```bash +screenplay ./plays --open-questions report +screenplay ./plays --scope Billing.Invoices.SendInvoice --open-questions fail +``` + +`report` lists open questions without failing because they remain. `fail` exits `1` when an open question remains in the selected set; unreadable notes exit `2`. Question scope includes descendants and ancestor notes. Without persisted identity state, Screenplay reports questions unscoped as orphaned rather than hiding them. Tasks and ordinary notes do not fail this gate. + ### Scoped validation Keep the whole application as `PATH` and select the part to report with `--scope [.[.]]`: diff --git a/Source/Cli.Specs/for_ScreenplayMcpRunner/when_listing_bundled_authoring_tools.cs b/Source/Cli.Specs/for_ScreenplayMcpRunner/when_listing_bundled_authoring_tools.cs index 2c18ebf1..369e7fe7 100644 --- a/Source/Cli.Specs/for_ScreenplayMcpRunner/when_listing_bundled_authoring_tools.cs +++ b/Source/Cli.Specs/for_ScreenplayMcpRunner/when_listing_bundled_authoring_tools.cs @@ -18,6 +18,9 @@ void Because() .Single(tool => tool.GetProperty("name").GetString() == "read-workspace").GetProperty("description").GetString(); } + [Fact] void should_offer_model_note_reads() => _tools.ShouldContain("list-model-notes"); + [Fact] void should_offer_model_note_proposals() => _tools.ShouldContain("propose-model-notes"); + [Fact] void should_offer_reference_specification_execution() => _tools.ShouldContain("run-specifications"); [Fact] void should_offer_whole_source_proposals() => _tools.ShouldContain("propose-source"); [Fact] void should_retain_typed_ast_proposals() => _tools.ShouldContain("propose-ast"); [Fact] void should_explain_whole_source_authoring_to_clients() => _instructions.ShouldContain("propose-source"); From f396e95b3818118b48a9629c98dd47abf926337e Mon Sep 17 00:00:00 2001 From: woksin Date: Mon, 12 Oct 2026 22:54:33 +0200 Subject: [PATCH 4/5] Link Copilot instructions to native corpus adapters --- Documentation/ai/index.md | 2 +- .../given/a_copilot_corpus.cs | 20 ++++++++++++++++ ...pdate_adds_copilot_instruction_adapters.cs | 24 +++++++++++++++++++ ...pus_has_no_copilot_instruction_adapters.cs | 12 ++++++++++ ...rpus_ships_copilot_instruction_adapters.cs | 14 +++++++++++ .../Cli/Commands/Ai/AiHarnessIntegrations.cs | 3 ++- 6 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 Source/Cli.Specs/for_AiCorpusSynchronizer/given/a_copilot_corpus.cs create mode 100644 Source/Cli.Specs/for_AiCorpusSynchronizer/when_synchronizing/and_an_update_adds_copilot_instruction_adapters.cs create mode 100644 Source/Cli.Specs/for_AiCorpusSynchronizer/when_synchronizing/and_the_corpus_has_no_copilot_instruction_adapters.cs create mode 100644 Source/Cli.Specs/for_AiCorpusSynchronizer/when_synchronizing/and_the_corpus_ships_copilot_instruction_adapters.cs diff --git a/Documentation/ai/index.md b/Documentation/ai/index.md index 5d2e45f5..e98f83a6 100644 --- a/Documentation/ai/index.md +++ b/Documentation/ai/index.md @@ -38,7 +38,7 @@ AGENTS.md, CLAUDE.md links to the project instructions .opencode/ .pi/ .agents/ ``` -Guidance adapters are symbolic links into `.cratis/ai/`, not copies. A user-owned file already sitting at one of those paths is left exactly as it is — install never replaces your own content. +Guidance adapters are symbolic links into `.cratis/ai/`, not copies. Copilot's `.github/instructions` links to `.cratis/ai/harnesses/copilot/instructions` when the selected corpus supplies native `*.instructions.md` files; older corpora fall back to `.cratis/ai/rules`. `cratis ai update` relinks an unchanged Cratis-owned instructions link when that target changes. A user-owned file already sitting at one of those paths is left exactly as it is — install never replaces your own content. Selected profiles can also supply MCP servers. [Screenplay MCP](../reference/screenplay-mcp.md) uses native project configuration entries, **not symlinks**: unrelated server definitions, comments, and settings remain intact. Only the bundled `cratis screenplay mcp` command is registered; a corpus descriptor that changes its executable or arguments is rejected before installation writes anything. Install/update creates the selected empty model directory (normally `.cratis/screenplay`), but does not start the server or create source files. Unsupported MCP adapters are named in `unsupportedMcpServers`; guidance installation does not imply every host has MCP support. diff --git a/Source/Cli.Specs/for_AiCorpusSynchronizer/given/a_copilot_corpus.cs b/Source/Cli.Specs/for_AiCorpusSynchronizer/given/a_copilot_corpus.cs new file mode 100644 index 00000000..c561d556 --- /dev/null +++ b/Source/Cli.Specs/for_AiCorpusSynchronizer/given/a_copilot_corpus.cs @@ -0,0 +1,20 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Cratis.Cli.for_AiCorpusSynchronizer.given; + +public class a_copilot_corpus : a_corpus_with_profile_scoped_rules +{ + protected SyncResult _result = null!; + protected static AiConfiguration Configuration { get; } = new(["copilot"], ["cratis/example"], []); + protected string InstructionsTarget => new DirectoryInfo(Path.Combine(_project, ".github", "instructions")).LinkTarget!; + + void Establish() => File.WriteAllText(Path.Combine(_corpus, ".cratis", "ai", "manifest.json"), "{\"harnesses\":[\"copilot\"],\"profiles\":[\"cratis/example\"],\"languages\":[]}"); + + protected void AddAdapter() + { + var instructions = Path.Combine(_corpus, ".cratis", "ai", "harnesses", "copilot", "instructions"); + Directory.CreateDirectory(instructions); + File.WriteAllText(Path.Combine(instructions, "general.instructions.md"), "---\napplyTo: \"**/*\"\n---\n# General rule"); + } +} diff --git a/Source/Cli.Specs/for_AiCorpusSynchronizer/when_synchronizing/and_an_update_adds_copilot_instruction_adapters.cs b/Source/Cli.Specs/for_AiCorpusSynchronizer/when_synchronizing/and_an_update_adds_copilot_instruction_adapters.cs new file mode 100644 index 00000000..97110b57 --- /dev/null +++ b/Source/Cli.Specs/for_AiCorpusSynchronizer/when_synchronizing/and_an_update_adds_copilot_instruction_adapters.cs @@ -0,0 +1,24 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Cratis.Cli.for_AiCorpusSynchronizer.when_synchronizing; + +public class and_an_update_adds_copilot_instruction_adapters : given.a_copilot_corpus +{ + string _previousTarget = null!; + + void Establish() + { + AiCorpusSynchronizer.Synchronize(_project, _corpus, Configuration); + _previousTarget = InstructionsTarget; + AddAdapter(); + } + + void Because() => _result = AiCorpusSynchronizer.Synchronize(_project, _corpus, Configuration); + + [Fact] void should_start_with_the_legacy_rules_link() => _previousTarget.ShouldEqual("../.cratis/ai/rules"); + [Fact] void should_relink_the_owned_instructions() => InstructionsTarget.ShouldEqual("../.cratis/ai/harnesses/copilot/instructions"); + [Fact] void should_record_the_relink() => _result.Actions.ShouldContain("Configured .github/instructions"); + [Fact] void should_not_report_owned_link_drift() => AiCorpusSynchronizer.Status(_project).ModifiedFiles.ShouldBeEmpty(); + [Fact] void should_have_no_conflicts() => _result.Conflicts.ShouldBeEmpty(); +} diff --git a/Source/Cli.Specs/for_AiCorpusSynchronizer/when_synchronizing/and_the_corpus_has_no_copilot_instruction_adapters.cs b/Source/Cli.Specs/for_AiCorpusSynchronizer/when_synchronizing/and_the_corpus_has_no_copilot_instruction_adapters.cs new file mode 100644 index 00000000..134d58b6 --- /dev/null +++ b/Source/Cli.Specs/for_AiCorpusSynchronizer/when_synchronizing/and_the_corpus_has_no_copilot_instruction_adapters.cs @@ -0,0 +1,12 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Cratis.Cli.for_AiCorpusSynchronizer.when_synchronizing; + +public class and_the_corpus_has_no_copilot_instruction_adapters : given.a_copilot_corpus +{ + void Because() => _result = AiCorpusSynchronizer.Synchronize(_project, _corpus, Configuration); + + [Fact] void should_fall_back_to_canonical_rules() => InstructionsTarget.ShouldEqual("../.cratis/ai/rules"); + [Fact] void should_have_no_conflicts() => _result.Conflicts.ShouldBeEmpty(); +} diff --git a/Source/Cli.Specs/for_AiCorpusSynchronizer/when_synchronizing/and_the_corpus_ships_copilot_instruction_adapters.cs b/Source/Cli.Specs/for_AiCorpusSynchronizer/when_synchronizing/and_the_corpus_ships_copilot_instruction_adapters.cs new file mode 100644 index 00000000..9aefc77f --- /dev/null +++ b/Source/Cli.Specs/for_AiCorpusSynchronizer/when_synchronizing/and_the_corpus_ships_copilot_instruction_adapters.cs @@ -0,0 +1,14 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Cratis.Cli.for_AiCorpusSynchronizer.when_synchronizing; + +public class and_the_corpus_ships_copilot_instruction_adapters : given.a_copilot_corpus +{ + void Establish() => AddAdapter(); + void Because() => _result = AiCorpusSynchronizer.Synchronize(_project, _corpus, Configuration); + + [Fact] void should_link_the_native_instructions() => InstructionsTarget.ShouldEqual("../.cratis/ai/harnesses/copilot/instructions"); + [Fact] void should_install_the_adapter() => File.Exists(Path.Combine(_project, ".github", "instructions", "general.instructions.md")).ShouldBeTrue(); + [Fact] void should_have_no_conflicts() => _result.Conflicts.ShouldBeEmpty(); +} diff --git a/Source/Cli/Commands/Ai/AiHarnessIntegrations.cs b/Source/Cli/Commands/Ai/AiHarnessIntegrations.cs index f2450caa..1c38afc6 100644 --- a/Source/Cli/Commands/Ai/AiHarnessIntegrations.cs +++ b/Source/Cli/Commands/Ai/AiHarnessIntegrations.cs @@ -70,7 +70,8 @@ static void PlanCodex(Planner planner) static void PlanCopilot(Planner planner) { planner.Add(".github/copilot-instructions.md", planner.NestedInstructionsTarget, false, preserveExisting: true); - planner.Add(".github/instructions", "../.cratis/ai/rules", true); + var hasCopilotInstructions = planner.Files.Any(file => file.StartsWith("harnesses/copilot/instructions/", StringComparison.Ordinal)); + planner.Add(".github/instructions", hasCopilotInstructions ? "../.cratis/ai/harnesses/copilot/instructions" : "../.cratis/ai/rules", true); planner.Add(".github/prompts", "../.cratis/ai/prompts", true); planner.Add(".github/skills", "../.cratis/ai/skills", true); planner.AddAgents(".github/agents", ".agent.md"); From 7bc1b48b5fa6792954ea167510c0ade78c0c021a Mon Sep 17 00:00:00 2001 From: woksin Date: Mon, 12 Oct 2026 23:01:38 +0200 Subject: [PATCH 5/5] Describe ESM v11 as lossless number lowering and refresh README bundle notes --- Documentation/reference/screenplay.md | 4 ++-- README.md | 2 +- Source/Cli/Commands/Render/RenderedSemanticVersions.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/reference/screenplay.md b/Documentation/reference/screenplay.md index 6af0fefc..d4e8b45d 100644 --- a/Documentation/reference/screenplay.md +++ b/Documentation/reference/screenplay.md @@ -12,7 +12,7 @@ cratis screenplay mcp [PATH] cratis screenplay desktop install ``` -The CLI bundles Screenplay 4.145.1 (including its MCP server), Stage 4.79.1, and the Arc source adapter 22.54.0. `cratis render` admits ESM v1 through v13, including the canonical screen-composition corpus observable query and query-only specification shapes, and preserves authored Scene layouts, profiles, templates, navigation and forms when a model declares screens. Guarded screen actions render as runnable Scene guards: the generated frontend evaluates the authored guard, including enum-typed comparisons against the read model's declared members, and shows the action only when it passes. The renderer still refuses unsupported constructs within those versions: captures, non-event triggers and Translate slices report `STAGE-ESM-024`; supported event-triggered Automation slices render, while reactions mixing event production and command invocation report `STAGE-ESM-037`, read-model absence assertions report `STAGE-ESM-027`, and the v9 and v10 constructs report their own codes (`STAGE-ESM-032` to `STAGE-ESM-036`, listed in the admission table below). Reducer observer filters render. It also refuses an evolved event until Stage can render its event-type migrations. Exact numbers are admitted by the bundled compiler and renderer. Validation rejects `implementation` attachments on concept, built-in, and whole-command rules. +The CLI bundles Screenplay 4.145.1 (including its MCP server), Stage 4.79.1, and the Arc source adapter 22.54.0. `cratis render` admits ESM v1 through v13, including the canonical screen-composition corpus observable query and query-only specification shapes, and preserves authored Scene layouts, profiles, templates, navigation and forms when a model declares screens. Guarded screen actions render as runnable Scene guards: the generated frontend evaluates the authored guard, including enum-typed comparisons against the read model's declared members, and shows the action only when it passes. The renderer still refuses unsupported constructs within those versions: captures, non-event triggers and Translate slices report `STAGE-ESM-024`; supported event-triggered Automation slices render, while reactions mixing event production and command invocation report `STAGE-ESM-037`, read-model absence assertions report `STAGE-ESM-027`, and the v9 and v10 constructs report their own codes (`STAGE-ESM-032` to `STAGE-ESM-036`, listed in the admission table below). Reducer observer filters render. It also refuses an evolved event until Stage can render its event-type migrations. Validation rejects `numbers exact` (`PLAY0268`) and `implementation` attachments on concept, built-in, and whole-command rules. For the embedded stdio server, the `visualize-model` MCP App, and native AI host registration, see [Screenplay MCP](screenplay-mcp.md). For Claude Desktop and ChatGPT Desktop installation, see [Screenplay desktop MCP](screenplay-desktop-mcp.md). @@ -45,7 +45,7 @@ The bundled Screenplay reader supports ESM schemas through v14. ESM v5 adds keye | `cratis render` | Admitted through Stage 4.79.1, including routed commands and supported routed specifications. Unsupported constructs still block publication. | Admitted for construct-level planning. Public events and event-target projections/reducers report `STAGE-ESM-032`; foreign origins and event-source captures report `STAGE-ESM-033`; translation direction reports `STAGE-ESM-024`. | Admitted for construct-level planning. Reaction `runsAs` reports `STAGE-ESM-034`; reaction `from` filters (`SemanticReaction.From`) report `STAGE-ESM-035`; per-production route overrides (`SemanticProducedEvent.Route`) report `STAGE-ESM-036`. Reducer `from` filters (`SemanticReducer.From`) render as Chronicle source/stream attributes; specifications requiring filtered read-model replay remain refused. | | `cratis run` | Refused by the Stage 4.79.1 container: `cratis run` starts Stage's default `eventmodel` engine, which reports `STAGE-ESM-030` for named event sources, streams and routes because only Stage's semantic engine runs them. Render the model with `cratis render` instead. | Stage applies its own runtime admission; CLI validation is not Stage admission. | Stage applies its own runtime admission; CLI validation is not Stage admission. | -Version admission does not admit every construct. A selected scope without unsupported constructs can plan normally at v9 or v10; a typed renderer refusal leaves no publishable artifacts. ESM v11 exact numbers, v12 enum member numbers and v13 concept compliance are admitted by the bundled renderer. ESM v14 query filters and newer versions remain blocked by `CLI-RENDER-004` before target planning or publication. +Version admission does not admit every construct. A selected scope without unsupported constructs can plan normally at v9 or v10; a typed renderer refusal leaves no publishable artifacts. ESM v11 (lossless lowering of Double-mode number literals within ±(2^53−1); `numbers exact` documents still do not bind), v12 enum member numbers and v13 concept compliance are admitted by the bundled renderer. ESM v14 query filters and newer versions remain blocked by `CLI-RENDER-004` before target planning or publication. ### Routed command example diff --git a/README.md b/README.md index b72bf9e6..3377365e 100644 --- a/README.md +++ b/README.md @@ -459,7 +459,7 @@ The CLI repository carries additional command groups whose exact behavior and st - `cratis arc` inspects registered commands and queries in a running [Arc](https://github.com/Cratis/Arc) application. - `cratis view` opens a .NET application as an event model in the browser, from the Screenplay documents it embeds or ones generated from its source. See the [View command reference](https://github.com/Cratis/cli/blob/main/Documentation/reference/view.md). -- `cratis screenplay` and `cratis render` work with Cratis Screenplay (`.play`) documents — generation, validation, and rendering from files, with nothing running. The CLI bundles Screenplay 4.137.0 and Stage 4.73.1; rendering admits ESM v8, including event sources, streams, routed commands and policy negation, and carries authored `description` and event `documentation` into XML doc comments. Generated values and command responses remain renderer refusals (`STAGE-ESM-028`/`STAGE-ESM-029`); specifications whose unauthenticated callers carry roles or claims are refused with `STAGE-ESM-011`. See the [Screenplay command reference](https://github.com/Cratis/cli/blob/main/Documentation/reference/screenplay.md) for the supported constructs and other renderer limits. +- `cratis screenplay` and `cratis render` work with Cratis Screenplay (`.play`) documents — generation, validation, and rendering from files, with nothing running. The CLI bundles Screenplay 4.145.1 and Stage 4.79.1; rendering admits ESM v1 through v13, including event sources, streams, routed commands and policy negation, and carries authored `description` and event `documentation` into XML doc comments. Server-generated UUID values and scalar or named command responses render; unsupported generated types report `STAGE-ESM-028` and unsupported generation/response combinations `STAGE-ESM-038`; specifications whose unauthenticated callers carry roles or claims are refused with `STAGE-ESM-011`. See the [Screenplay command reference](https://github.com/Cratis/cli/blob/main/Documentation/reference/screenplay.md) for the supported constructs and other renderer limits. - `cratis screenplay desktop install --clients claude,chatgpt` acquires verified, self-contained local Screenplay desktop packages. Complete installation/enabling in each host's UI; use `status`, `update`, and `uninstall` to manage Cratis-owned sources safely. See [desktop MCP installation](https://github.com/Cratis/cli/blob/main/Documentation/reference/screenplay-desktop-mcp.md). - The [canonical CLI page](https://cratis.io/cli/) carries the currently admitted command-group documentation. diff --git a/Source/Cli/Commands/Render/RenderedSemanticVersions.cs b/Source/Cli/Commands/Render/RenderedSemanticVersions.cs index a9434158..84e7fd2f 100644 --- a/Source/Cli/Commands/Render/RenderedSemanticVersions.cs +++ b/Source/Cli/Commands/Render/RenderedSemanticVersions.cs @@ -17,7 +17,7 @@ namespace Cratis.Cli.Commands.Render; /// streams, command routes and specification routes are admitted by the bundled Stage. ESM v9 and v10 /// reach its construct-level admission: unsupported public/foreign events, captures, reaction identity, /// reaction filters and production route overrides receive typed refusals; reducer filters render. ESM v11 -/// exact numbers, v12 enum member numbers and v13 concept compliance are admitted by Stage 4.79.1. +/// lossless Double-mode number literals, v12 enum member numbers and v13 concept compliance are admitted by Stage 4.79.1. /// ESM v14 query filters remain refused until the renderer admits them. /// public static class RenderedSemanticVersions