---
title: Canonical corpus conformance gate
editUrl: https://github.com/Cratis/Stage/edit/main/Documentation/reference/corpus-conformance.md
description: The Cratis Stage corpus conformance gate compares loader, executor, host and renderer outcomes against a matrix with tracked refusal fingerprints.
lastUpdated: 2026-11-05T00:00:00.000Z
---


`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

| 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:

- `supported`
- `refused`, with a `refusals` array containing the exact fingerprints and dispositions described below
- `expected-refusal`, when the corpus itself expects an unsupported executor result; these cells are exempt from tracking and must carry no records
- `n/a:no-specifications`, only for a vector whose plan has no specifications
- `n/a:not-loaded`, when no plan exists at all
- `gap:<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

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

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

`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](https://github.com/Cratis/Screenplay/issues/893) added authoring error `PLAY0802` to forbid those names in current models. By the decision on [Stage#536](https://github.com/Cratis/Stage/issues/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

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

1. Run the Debug specs. Failure messages name the vector, surface, and affected fingerprint or outcome.
2. Capture the actual table into an existing ignored work directory:

   ```bash
   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_vector
   ```

   This writes `matrix.json` and `matrix.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.
3. 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.
4. 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.
5. Rebuild to copy the reviewed matrix into the test output directory, then rerun the specs. Removed refusals must leave no stale records.
