Canonical corpus conformance gate
Source/Conformance.Specs/for_CanonicalCorpusConformance runs every vector of the pinned Cratis.Screenplay.CanonicalCorpus package through the strict model loader and three surfaces, and compares each result with a checked-in table, conformance-matrix.json. The table is the conformance matrix: a vector without a row fails, a row missing a column fails, and a cell that changes fails until you update the table.
AnonymousPoliciesCorpus.V31 and EsmGoldenVectors.V31 are supported on all four
columns. The renderer column includes a Debug C# compilation of the anonymous output
and its specification sources; it does not substitute for a generated application or
kernel run.
What each column measures
Section titled “What each column measures”| Column | What runs | Cell is supported when |
|---|---|---|
loader | SemanticExecutionPlan.Compile(model), the strict load the model loader applies | The plan compiles with no issues |
executor | SemanticSpecificationExecutor over every specification of the vector | Every specification passes, as the corpus expects |
host | SemanticRuntimeAdmission (internal; the Host grants this assembly InternalsVisibleTo in Debug) | Every admission entry is supported |
renderer | CratisRendering.Plan, then an in-memory Debug C# compilation of every published render | The plan and its emitted backend, Program.cs, and Debug specifications have no errors |
The loader outcome is its own column. A model the loader refuses has outcome refused and one record per distinct (STAGE-PLAN-004, issue kind, full artifact identity). Several unsupported validation rules or projection transitions can share that identity. The loader uses set semantics: it keeps one fingerprint and writes its occurrence count plus all distinct details, ordered ordinally, to the details sidecar. The three surfaces still run on the partial plan, SemanticExecutionPlan.Compile(model, []), so each refuses per artifact instead of hiding behind the loader. Only when even the partial plan does not compile are they n/a:not-loaded.
The renderer column alone renames the application. The ESM goldens carry a prose application name (Canonical Golden Application), and the Cratis scaffold requires a C# identifier, so it throws on the name before it reads a single construct. The renderer column therefore runs a copy of the model whose application is named after the vector; every other construct is unchanged. The loader, executor and host columns use the model as the corpus ships it.
Every published render is compiled, including one whose specification refusals already make the renderer cell refused. Compilation uses the emitted bytes without rewriting or skipping specifications, the pinned Cratis client references, and the renderer’s implicit usings with DEBUG enabled. The test host’s Chronicle kernel assembly is excluded because generated applications do not reference it. This is a compile check, not a generated application build, test run, or kernel run.
Every cell is one of:
supportedrefused, with arefusalsarray containing the exact fingerprints and dispositions described belowexpected-refusal, when the corpus itself expects an unsupported executor result; these cells are exempt from tracking and must carry no recordsn/a:no-specifications, only for a vector whose plan has no specificationsn/a:not-loaded, when no plan exists at allgap:<what>, which always fails: an exception, an untyped refusal, a partial result, duplicate, unknown or missing executor result IDs against the plan’s exact specification ID set (even without corpus expectations), a corpus expectation that matches no result, or a render that blocked yet emitted artifacts
Enumeration
Section titled “Enumeration”Vectors are found by reflection over the corpus assembly: every public static CanonicalCorpusVector property, and every full ESM golden (EsmGoldenVectors.V<n>, plus the extension vectors). The spec fails if the count drops below minimumVectorCount, if corpusVersion differs from the resolved package, or if a corpus type yields no vector and is not listed in excludedTypes with a reason.
Every refusal has one disposition
Section titled “Every refusal has one disposition”A refused cell has one record per fingerprint in refusals. Each record contains:
| Field | Contract |
|---|---|
code | A typed STAGE-… code, capability:<Name>[<reason key>], or a Roslyn compiler code such as CS0246 |
kind | Loader plan-issue kind, Host admission-entry kind, or compile for a renderer compiler error; otherwise omitted |
artifact | Full artifact identity, or the exact emitted relative file path for compile; never a shortened hash, kind, name, prefix, or wildcard |
issue | Positive integer identifying the Stage issue that tracks this exact refusal |
byDesign | Non-blank reason for an explicitly allowed by-design refusal, instead of issue |
A compiler failure adds one fingerprint for the first error, sorted ordinally by path, then line, code, and message. Its artifact is the emitted relative path. The line, message, and remaining error count appear in the failure output and details sidecar, not the matrix fingerprint. A compile record requires a positive issue and may never use byDesign; otherwise it reports compile-record-needs-issue. Errors in the synthetic GlobalUsings.g.cs or without a source location fail as harness problems, not tracked model refusals. A published render must contain exactly one .csproj; multi-project grouping is not supported by this check.
The executor collapses repeated refusals across specifications only when they share the same code and full construct identity. The Host keeps every artifact and kind, including separate specification artifacts. Renderer fingerprints retain the full semantic ID from each diagnostic.
Matching is ordinal and exact on (vector, surface, code, kind, artifact). A record on another vector or surface grants no coverage. Changing a code, kind, or artifact reports both unrecorded-refusal and stale-record; duplicates report duplicate-record. Empty or wildcard record artifacts report code-only-record. Missing observed artifacts report refusal-without-artifact.
Every record needs exactly one disposition. A null or non-positive issue without a reason reports missing-disposition; supplying both fields reports conflicting-disposition. Issue 500 may track only STAGE-ESM-011, STAGE-IMPL-003 records are tracked only by Stage#891, and no record may point at issue 513. Records on supported, n/a, gap, or expected-refusal cells fail as records-on-unrefused-cell.
The loader rejects legacy cell-level issue, issueRefusals, and byDesign fields, unknown fields, duplicate fields, and non-integer issue values with schema diagnostics. There is no compatibility fallback to cell-level ownership.
Refusals that are correct by design
Section titled “Refusals that are correct by design”byDesign requires a non-blank reason and an exact match in the allow-list:
| Vectors | Surface | Code | Full artifact identities |
|---|---|---|---|
GenerationFixturesCorpus.V21 | renderer | STAGE-ESM-019 | sem1:25bdf5d2a8f97601c41d6bc30d4da1d2fd4123e1ebf894d79f96b9103b85caf4 |
EsmGoldenVectors.V8–EsmGoldenVectors.V24 (inclusive) | renderer | STAGE-ESM-030 | sem1:000000000000000000000000000000000000000000000000000000000000232c, sem1:0000000000000000000000000000000000000000000000000000000000002333 |
The first entry is the intentionally invalid TenantOrderId generation fixture. The second entry covers exactly 34 refusals: the two listed artifacts on each of the 17 frozen golden vectors. Chronicle reserves their stored names (All/Default); Screenplay#893 added authoring error PLAY0802 to forbid those names in current models. By the decision on Stage#536, Stage keeps refusing these frozen fixtures rather than mapping their stored names.
Another artifact, vector, surface, code, or kind reports unauthorized-by-design, even if another part of the fingerprint is identical. Both entries require an omitted (null) kind. A new refusal on an otherwise allowed golden vector is not automatically exempt. A Stage limitation outside this allow-list still needs an issue.
When an exempt artifact no longer appears, its record becomes stale and must be removed.
Invocation expectations
Section titled “Invocation expectations”The Cratis specification renderer compares authored then invokes expectations against the command objects returned by the reactor scenario. It checks the exact count, ordered command types and only the stated property values; omitted values remain unspecified. Screenplay does not allow then invokes alongside a rejection outcome; rejection specifications remain separate from invocation expectations. Existing event and outcome assertions remain separate. A foreign command contributes an invocation decision, not its foreign slice’s facts.
Support is bounded to one fired declarative, invoke-only reaction entered by a direct append or a lowerable command cascade. The cascade keeps invocation expectations on its reaction half, not its trigger half. Scalar and concept values, nulls and scalar collections are comparable; absent or empty expectations keep the existing output path.
Other shapes remain explicit STAGE-ESM-016 refusals on the original specification: Translate, capture and trigger entry points, redelivery, native bodies, fan-out, mixed production/invocation, refusal branches, context mappings, deeper effects and non-comparable expected values. Admitting the V34 version pair does not imply blanket support for V34 invocation scenarios.
Update the table
Section titled “Update the table”-
Run the Debug specs. Failure messages name the vector, surface, and affected fingerprint or outcome.
-
Capture the actual table into an existing ignored work directory:
Terminal window CRATIS_STAGE_WRITE_CONFORMANCE_MATRIX="$PWD/.ai-work/matrix.json" dotnet test Source/Conformance.Specs/Conformance.Specs.csproj --configuration Debug --no-build --filter FullyQualifiedName~when_running_every_canonical_vectorThis writes
matrix.jsonandmatrix.details.json. The sidecar contains the full observed details for diagnosis; details never enter the matrix. The run still validates the checked-in expectations, so writing an actual table is not a passing verdict. -
Review changed outcomes and fingerprints before replacing
conformance-matrix.json. The writer carries a disposition only from one identical prior(vector, surface, fingerprint)record. A new or ambiguous fingerprint gets"issue": null, even if another artifact has the same code or the cell outcome is unchanged. -
Replace each null with the issue that covers that exact refusal, or the authorized by-design reason. Do not infer coverage from another record in the cell.
-
Rebuild to copy the reviewed matrix into the test output directory, then rerun the specs. Removed refusals must leave no stale records.