Skip to content

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.

ColumnWhat runsCell is supported when
loaderSemanticExecutionPlan.Compile(model), the strict load the model loader appliesThe plan compiles with no issues
executorSemanticSpecificationExecutor over every specification of the vectorEvery specification passes, as the corpus expects
hostSemanticRuntimeAdmission (internal; the Host grants this assembly InternalsVisibleTo in Debug)Every admission entry is supported
rendererCratisRendering.Plan, then an in-memory Debug C# compilation of every published renderThe 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

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.

A refused cell has one record per fingerprint in refusals. Each record contains:

FieldContract
codeA typed STAGE-… code, capability:<Name>[<reason key>], or a Roslyn compiler code such as CS0246
kindLoader plan-issue kind, Host admission-entry kind, or compile for a renderer compiler error; otherwise omitted
artifactFull artifact identity, or the exact emitted relative file path for compile; never a shortened hash, kind, name, prefix, or wildcard
issuePositive integer identifying the Stage issue that tracks this exact refusal
byDesignNon-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.

byDesign requires a non-blank reason and an exact match in the allow-list:

VectorsSurfaceCodeFull artifact identities
GenerationFixturesCorpus.V21rendererSTAGE-ESM-019sem1:25bdf5d2a8f97601c41d6bc30d4da1d2fd4123e1ebf894d79f96b9103b85caf4
EsmGoldenVectors.V8–EsmGoldenVectors.V24 (inclusive)rendererSTAGE-ESM-030sem1: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.

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.

  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:

    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_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.