@cratis/pi 7.67.0 → 7.68.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. package/package/corpus/hooks/scripts/cratis-check-pr.mjs +23 -3
  2. package/package/corpus/product-sync.json +1 -1
  3. package/package/corpus/skills/cratis-screenplay-model-authoring/references/state-and-recovery.md +1 -0
  4. package/package/corpus/skills/cratis-screenplay-model-review/references/report-template.md +2 -2
  5. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/SKILL.md +1 -1
  6. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/phases.md +1 -1
  7. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/stop-or-assume.md +1 -1
  8. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/verdicts-and-modes.md +6 -1
  9. package/package/corpus/skills/cratis-screenplay-projections/SKILL.md +3 -1
  10. package/package/corpus/skills/cratis-screenplay-projections/references/reducer-identity.md +88 -0
  11. package/package/corpus/skills/cratis-screenplay-projections/verification.json +2 -1
  12. package/package/corpus/skills/cratis-screenplay-read-surface/SKILL.md +5 -0
  13. package/package/corpus/skills/cratis-screenplay-slice-design/SKILL.md +3 -5
  14. package/package/corpus/skills/cratis-screenplay-slice-design/references/read-model-design.md +4 -1
  15. package/package/corpus/skills/cratis-screenplay-toolchain/SKILL.md +5 -0
  16. package/package/corpus/skills/cratis-screenplay-toolchain/references/cheat-sheet.md +15 -0
  17. package/package/corpus/skills/cratis-screenplay-toolchain/references/diagnostics.md +13 -2
  18. package/package/corpus/skills/cratis-screenplay-toolchain/references/traps.md +1 -0
  19. package/package/corpus/skills/cratis-screenplay-toolchain/references/verdicts.md +64 -3
  20. package/package/corpus/skills/cratis-screenplay-toolchain/verification.json +7 -1
  21. package/package.json +1 -1
@@ -4,9 +4,9 @@
4
4
 
5
5
  import { spawnSync } from 'node:child_process';
6
6
  import { createHash } from 'node:crypto';
7
- import { mkdirSync, readFileSync, readdirSync, statSync, writeFileSync, renameSync } from 'node:fs';
7
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync, renameSync } from 'node:fs';
8
8
  import { homedir } from 'node:os';
9
- import { isAbsolute, join, resolve } from 'node:path';
9
+ import { dirname, isAbsolute, join, resolve } from 'node:path';
10
10
  import { parseArgs } from 'node:util';
11
11
  import { Script } from 'node:vm';
12
12
  import { fileURLToPath } from 'node:url';
@@ -16,6 +16,7 @@ const commandTimeout = 15_000;
16
16
  const reviewedRulesRef = '40125b4fcae388e62ed9471edfffd0e991422f0a';
17
17
  const hookMode = process.argv[2] === '--hook';
18
18
  let claudeHook = false;
19
+ let invocation = {};
19
20
  const hookWarnings = [];
20
21
  const warningLine = line => /^(::warning\b|::notice title=Release notes (?:drift )?not checked|Warning:)/.test(line);
21
22
  const warning = message => {
@@ -25,6 +26,23 @@ const warning = message => {
25
26
  const run = (command, args, options = {}) => spawnSync(command, args, {
26
27
  encoding: 'utf8', timeout: commandTimeout, maxBuffer: 32 * 1024 * 1024, ...options,
27
28
  });
29
+ // A bare ENOENT cannot be told apart from a path resolved against the wrong directory, a copy of this
30
+ // checker from another checkout, or a file that is not there yet. Naming all of those lets the next
31
+ // occurrence be diagnosed instead of retried.
32
+ function readBodyFile(value) {
33
+ const resolved = resolve(value);
34
+ try {
35
+ return readFileSync(resolved, 'utf8');
36
+ } catch (error) {
37
+ const parent = dirname(resolved);
38
+ const details = [`--body-file value: ${value}`, `path tried: ${resolved}`,
39
+ `parent directory exists: ${existsSync(parent)}`, `file exists now: ${existsSync(resolved)}`,
40
+ `checker working directory: ${process.cwd()}`,
41
+ ...Object.entries(invocation).map(([name, detail]) => `${name}: ${detail}`),
42
+ `checker script: ${fileURLToPath(import.meta.url)}`];
43
+ throw new Error(`cannot read the pull-request body file (${error.code || error.message}); ${details.join('; ')}`, { cause: error });
44
+ }
45
+ }
28
46
  const gh = (args, options = {}) => {
29
47
  const result = run('gh', args, options);
30
48
  if (result.error || result.status !== 0) throw new Error(`gh ${args[0]} failed: ${result.stderr?.trim() || result.error?.message || result.status}`);
@@ -156,7 +174,7 @@ function check(argv, repositoryCallers, lookupOptions) {
156
174
  for (const label of split(values['add-label'])) labels.add(label);
157
175
  const intent = intents.filter(label => labels.has(label));
158
176
  if (intent.length !== 1) throw new Error(`Exactly one release-intent label is required (major/minor/patch/no-release); found ${intent.join(', ') || 'none'}.`);
159
- const body = values['body-file'] ? readFileSync(resolve(values['body-file']), 'utf8') : pull.body || '';
177
+ const body = values['body-file'] ? readBodyFile(values['body-file']) : pull.body || '';
160
178
  let repository = values.repo;
161
179
  let defaultBranch = git(['symbolic-ref', '--short', 'refs/remotes/origin/HEAD']).replace(/^origin\//, '') || 'main';
162
180
  try {
@@ -324,6 +342,7 @@ function hook() {
324
342
  if (typeof text !== 'string' || !/\bgh\s+pr\s+(create|edit)\b/.test(text.replace(/\\\r?\n/g, ''))) return 0;
325
343
  // Match each simple command, never the same text inside an echo/grep argument.
326
344
  let cwd = payload.cwd || process.cwd();
345
+ invocation = { 'hook payload directory': payload.cwd || '(none)' };
327
346
  let knownDirectory = true;
328
347
  let parsed;
329
348
  try { parsed = commands(text); } catch (error) { if (!optedIn(cwd)) return 0; throw error; }
@@ -386,6 +405,7 @@ function hook() {
386
405
  if (targetedRepository) lookupEnvironment.GH_REPO = targetedRepository;
387
406
  else delete lookupEnvironment.GH_REPO;
388
407
  process.chdir(cwd);
408
+ invocation['command directory'] = cwd;
389
409
  const code = check(args, repositoryCallers, { env: lookupEnvironment });
390
410
  if (code !== 0 && code !== 3) throw new Error('The pull-request body or release intent failed cratis-check-pr. Fix the reported violations before retrying.');
391
411
  }
@@ -201,7 +201,7 @@
201
201
  ],
202
202
  "rules": []
203
203
  },
204
- "notes": "Standalone 4.204.0 and byte-identical compiler contract pinned. Cratis/AI#743: flat composite read-model keys (ESM v27): untyped projection key blocks on root from and remove with, equality-filter lists, single-instance by blocks and per-part fixtures, with PLAY0661/PLAY0662/PLAY0268 refusals checked against the 4.204.0 compiler; Stage origin/main still refuses v27 with STAGE-ESM-016 (stage-rendering.md); Stage v4.135.0 v26 admission (Host and executor run date comparisons; renderer STAGE-ESM-046) was source-checked for this sync; no render or runtime probe. Prior: Standalone 4.201.0 and byte-identical compiler contract pinned. Cratis/AI#733: the ui profile root template, shell options, appearance and new screens default (PLAY0788-PLAY0799, quick fix for the legacy layout line, ui-profiles MCP tool, 44 tools). Cratis/AI#735: chronological Date/DateTime ordering in command conditions and command/property literal validation (ESM v26), UTC tick precision and pre-rule offset validation; then matching remains ordinal. Source-checked Screenplay v4.199.0 commands, diagnostics, interoperability and specifications documentation plus decision 0087. Stage v4.127.1 still refuses v26 with STAGE-ESM-016 (stage-rendering.md and SemanticVersionFeatures.Supports); no render/runtime probe. Cratis/AI#576: prior mechanical compiler catch-up. Language batch #717/#722/#723/#724/#720/#728/#734 teaches atomic reaction batches (v22), described payload handlers (v23), portable linear matching, ordinal text rules (v24), protected command reads (v25), diagnostic arguments and event-source invokes mappings. Facts read at Screenplay v4.198.0 and introducing tags; Stage v4.127.0 admits exact v1-v21 and v23-v25 pairs but refuses v22/v26 and mixed pairs (STAGE-ESM-016), protected reads (044), atomic mode (043) and described handlers (STAGE-IMPL-002), even Stub mode. Stage text rules execute/render; no new renderer/runtime probe or Stage pin uplift. Cratis/AI#706: application navigation regions (module inherit, extend or override), toolbar slice buttons, blueprint template references and sample data packs in cratis-screenplay-ui-composition (references/application-shell.md), with the canonical shell vector and the rejection codes PLAY0710-PLAY0722 and PLAY0740-PLAY0765. Examples moved to the executable-state rules since 4.164: translator reactions in an Automation slice beside the Translate slice (PLAY0694) and the first record of a capture key appended with `when added`. Earlier: Standalone 4.164.0 and byte-identical compiler contract pinned. Cratis/AI#663: one origin per capture (PLAY0687). #668: executable no-event/redelivery v17. #684: concept query-argument validation in every version and query-error assertions v18. #681: executable refusal branches and refusal values v19. #666: proposal summary/severity counts and exact structural source proposals. Generated-concept rules select v20 with PLAY0688/0689 proofs and derived-matches repair; generated secrets and preview strategies remain refused. Release-baseline from-esm admission reads storedName. Standalone binding is not renderer admission: Stage 4.103.0 admits v1-v17 and v19; CLI 3.62.0 bundles Screenplay 4.159.0 / Stage 4.99.0 and renders through v16. Prior evidence remains unless superseded; no new renderer, Docker or browser probe."
204
+ "notes": "Standalone 4.204.0 and byte-identical compiler contract pinned. Cratis/AI#702 and #691: PLAY0690 reducer identity (Screenplay 4.170.0, native compiler only) and the screenplay.json validation gate (Screenplay 4.165.0; cratis screenplay validate reads it from CLI 3.65.0) are taught and probed against the 4.204.0 compiler. Cratis/AI#743: flat composite read-model keys (ESM v27): untyped projection key blocks on root from and remove with, equality-filter lists, single-instance by blocks and per-part fixtures, with PLAY0661/PLAY0662/PLAY0268 refusals checked against the 4.204.0 compiler; Stage origin/main still refuses v27 with STAGE-ESM-016 (stage-rendering.md); Stage v4.135.0 v26 admission (Host and executor run date comparisons; renderer STAGE-ESM-046) was source-checked for this sync; no render or runtime probe. Prior: Standalone 4.201.0 and byte-identical compiler contract pinned. Cratis/AI#733: the ui profile root template, shell options, appearance and new screens default (PLAY0788-PLAY0799, quick fix for the legacy layout line, ui-profiles MCP tool, 44 tools). Cratis/AI#735: chronological Date/DateTime ordering in command conditions and command/property literal validation (ESM v26), UTC tick precision and pre-rule offset validation; then matching remains ordinal. Source-checked Screenplay v4.199.0 commands, diagnostics, interoperability and specifications documentation plus decision 0087. Stage v4.127.1 still refuses v26 with STAGE-ESM-016 (stage-rendering.md and SemanticVersionFeatures.Supports); no render/runtime probe. Cratis/AI#576: prior mechanical compiler catch-up. Language batch #717/#722/#723/#724/#720/#728/#734 teaches atomic reaction batches (v22), described payload handlers (v23), portable linear matching, ordinal text rules (v24), protected command reads (v25), diagnostic arguments and event-source invokes mappings. Facts read at Screenplay v4.198.0 and introducing tags; Stage v4.127.0 admits exact v1-v21 and v23-v25 pairs but refuses v22/v26 and mixed pairs (STAGE-ESM-016), protected reads (044), atomic mode (043) and described handlers (STAGE-IMPL-002), even Stub mode. Stage text rules execute/render; no new renderer/runtime probe or Stage pin uplift. Cratis/AI#706: application navigation regions (module inherit, extend or override), toolbar slice buttons, blueprint template references and sample data packs in cratis-screenplay-ui-composition (references/application-shell.md), with the canonical shell vector and the rejection codes PLAY0710-PLAY0722 and PLAY0740-PLAY0765. Examples moved to the executable-state rules since 4.164: translator reactions in an Automation slice beside the Translate slice (PLAY0694) and the first record of a capture key appended with `when added`. Earlier: Standalone 4.164.0 and byte-identical compiler contract pinned. Cratis/AI#663: one origin per capture (PLAY0687). #668: executable no-event/redelivery v17. #684: concept query-argument validation in every version and query-error assertions v18. #681: executable refusal branches and refusal values v19. #666: proposal summary/severity counts and exact structural source proposals. Generated-concept rules select v20 with PLAY0688/0689 proofs and derived-matches repair; generated secrets and preview strategies remain refused. Release-baseline from-esm admission reads storedName. Standalone binding is not renderer admission: Stage 4.103.0 admits v1-v17 and v19; CLI 3.62.0 bundles Screenplay 4.159.0 / Stage 4.99.0 and renders through v16. Prior evidence remains unless superseded; no new renderer, Docker or browser probe."
205
205
  },
206
206
  {
207
207
  "id": "scene",
@@ -4,6 +4,7 @@ This reference holds identity-state, journal and recovery rules and the readines
4
4
 
5
5
  ## Preserve state and recover explicitly
6
6
 
7
+ `.screenplay/` is tool-owned; the only authored file beside it is `screenplay.json`, the validation gate, which no MCP operation writes.
7
8
  Commit `.screenplay/identities.json` with the model. Only `apply` writes it;
8
9
  `open-workspace` never creates it; text-only models have none. Never delete or
9
10
  overwrite conflicting identity state, silently mint IDs on restart, or text-rename
@@ -167,8 +167,8 @@ it. Verify the answer became a specification, a description or a project-owned
167
167
  decision record; a resolved note is not behavioral proof. Ask the owner to reanchor
168
168
  or resolve affected notes before source apply; resolution alone leaves an orphan.
169
169
 
170
- The optional `--open-questions fail` gate is a standalone `screenplay` option; the
171
- CLI-bundled compiler can lack it. Report it separately from V1–V5. Evidence: Screenplay
170
+ The optional `--open-questions fail` gate exists on the standalone `screenplay` tool and on
171
+ `cratis screenplay validate` (CLI 3.60.0 or later). Report it separately from V1–V5. Evidence: Screenplay
172
172
  `Documentation/screenplay/model-notes.md`,
173
173
  `Documentation/screenplay/mcp/reference.md`; source lines in the linked guide.
174
174
 
@@ -75,7 +75,7 @@ or **critic** (P4-P5: strict, report first, fix in a separate batch).
75
75
  ## Verdicts (independent evidence, not a ladder)
76
76
  | Verdict | Meaning | Does not prove |
77
77
  |---|---|---|
78
- | V1 authorable | compiles, warnings as errors | meaning, binding |
78
+ | V1 authorable | compiles, warnings as errors (declared gate) | meaning, binding |
79
79
  | V2 executable diagnostics | the executable model produces none | that specs run |
80
80
  | V3 binding-ready | the model binds | that any spec passes |
81
81
  | V4 reference specs run | engine, expected vs discovered | rendering or build health |
@@ -109,7 +109,7 @@ phase, a decision important enough that a later reader could misread the model (
109
109
  ## P6 Accept (the user)
110
110
  - **Input**: the reviewed source identity.
111
111
  - **Carry-forward**: the accepted **source identity** (`verdicts-and-modes.md`).
112
- - **Commit at P6**: the accepted `.play` files and `.screenplay/identities.json` when present.
112
+ - **Commit at P6**: the accepted `.play` files, `screenplay.json` (the authored validation gate) when the repository has one, and `.screenplay/identities.json` when present.
113
113
  The user commits, or explicitly authorizes the owning session to commit; the modeler never
114
114
  commits on its own. Run `cratis-screenplay-modeling-lifecycle`'s `scripts/model-first-status.mjs` to check acceptance, drafts and model changes in progress.
115
115
  - **Gate**: explicit acceptance and the model commit before P7-P9. After committing, recompute
@@ -64,7 +64,7 @@ consequential policy. Resolving is not implementation; a known refusal still
64
64
  needs `then error` or `then denied`. Reopen when the evidence ceases to answer it.
65
65
 
66
66
  Question gating is separate from V1: standalone Screenplay 4.144.0 has
67
- `--open-questions fail`; Cratis CLI 3.53.1 does not. Read toolchain
67
+ `--open-questions fail`, and `cratis screenplay validate` has it from CLI 3.60.0. Read toolchain
68
68
  `references/verdicts.md` "Model notes gate" when a phase requires no open questions.
69
69
  Product evidence: Screenplay `Documentation/screenplay/model-notes.md`,
70
70
  `decisions/0045-model-notes-sidecar.md` and the model-authoring source table.
@@ -48,6 +48,11 @@ V5 rules:
48
48
  ## Commands (enough to check without loading the toolchain skill)
49
49
  - V1, preferred: `screenplay <model-folder> --warnaserror --no-color` (check "N file(s) compiled").
50
50
  - V1, fallback: `cratis screenplay validate <model-folder> --warnings-as-errors`.
51
+ - The repository's gate file: V1 runs through a `screenplay.json` at the model root when one exists
52
+ (checks, warnings as errors, scope, open questions), so it is part of the verification contract.
53
+ Read the effective gate with `screenplay <model-folder> --print-gate`, name it on the V1 line, and
54
+ never bypass it with `--no-gate-file` without saying so. A refused file is `V1 not run: gate file
55
+ refused`, not a pass. Commands and precedence: toolchain `references/verdicts.md`.
51
56
  - V2, V3: the MCP workspace readiness and executable diagnostics.
52
57
  - Name tool and version on each line. A tool older than the model's constructs reports false
53
58
  failures; see the toolchain skill.
@@ -56,7 +61,7 @@ V5 rules:
56
61
 
57
62
  ```text
58
63
  Mode: executable Source: <commit+digest from the source-identity helper> / ws <revision> cat <catalogRevision>
59
- V1 authorable: pass (N files) | fail: PLAYnnnn at file:line - meaning
64
+ V1 authorable: pass (N files, gate: <screenplay.json or flags>) | fail: PLAYnnnn at file:line - meaning | not run: gate file refused
60
65
  V2 diagnostics: none (executable model) | n diagnostics | not run: <reason>
61
66
  V3 binding: ready | blocked: PLAYnnnn ... | not run: design mode
62
67
  V4 specs: 12 expected / 12 discovered, 11 passed, 1 unsupported (route) | not run: no route
@@ -261,7 +261,9 @@ What the executable model does with it (ESM v3):
261
261
  rules is `PLAY0398`; two rules for one event is `PLAY0399`; a reducer with no
262
262
  bodies is rejected with a hint to write a projection.
263
263
  - The key is **always the event source id** — a reducer cannot route by an event
264
- property. State starts at null; returning null deletes the instance.
264
+ property. State starts at null; returning null deletes the instance. A query
265
+ `by` another identity type warns `PLAY0690`; use a projection keyed on each `from`
266
+ or a join ([reducer identity](references/reducer-identity.md)).
265
267
  - The body is an opaque attachment identified by a content hash, never compiled
266
268
  or run by Screenplay. The reference runner cannot compute reducer state, so a
267
269
  specification that reads a reducer-built read model reports unsupported and
@@ -0,0 +1,88 @@
1
+ # Reducer instance identity (PLAY0690)
2
+
3
+ A reducer instance is keyed by the **event-source id** of the event it reduces. A reducer cannot be re-keyed by a payload value, a read-model `key` or a stream partition. A view that must be looked up by anything other than that event-source identity needs a projection.
4
+
5
+ Requires Screenplay 4.170.0 or later (`screenplay --version`) for PLAY0690; an older compiler does not report it, so a clean run there does not clear the mismatch.
6
+
7
+ ## What PLAY0690 reports
8
+
9
+ A warning (it fails `--warnaserror`) in two cases:
10
+
11
+ - A reducer-backed query returns one instance `by` a scalar whose nominal type differs from the identity type of an observed event whose producers are all known, for example `by month Month` while the events live on `ResourceId` streams.
12
+ - The producers of one reducer's events use several different nominal identity types, so no single identity can key the instances. One reducer warning replaces the repeated query warnings.
13
+
14
+ This example reports PLAY0690 on `ByMonth`: the instances follow `ResourceId`, not `Month`.
15
+
16
+ ```screenplay expect PLAY0690
17
+ concept ResourceId : String
18
+ concept Month : String
19
+ module Work
20
+ feature Reporting
21
+ slice StateView Totals
22
+ event Recorded
23
+ month Month
24
+ hours Decimal
25
+ readmodel Totals
26
+ month Month key
27
+ hours Decimal
28
+ reducer Fold => Totals
29
+ on Recorded
30
+ ```csharp
31
+ return context.State is null
32
+ ? new(context.Event.month, context.Event.hours)
33
+ : context.State with { hours = context.State.hours + context.Event.hours };
34
+ ```
35
+ query ByMonth => Totals optional
36
+ by month Month
37
+ slice StateChange Record
38
+ command Record
39
+ resource ResourceId identifier
40
+ month Month
41
+ hours Decimal
42
+ produces Recorded
43
+ for resource
44
+ month = month
45
+ hours = hours
46
+ ```
47
+
48
+ ## Fix
49
+
50
+ To aggregate by a payload value, replace the reducer with a projection that declares a property key on each `from`. The read-model `key` and the `from` key then agree, and the warning is gone:
51
+
52
+ ```screenplay
53
+ concept ResourceId : String
54
+ concept Month : String
55
+ module Work
56
+ feature Reporting
57
+ slice StateView Totals
58
+ event Recorded
59
+ month Month
60
+ hours Decimal
61
+ readmodel Totals
62
+ month Month key
63
+ hours Decimal
64
+ projection Fold => Totals
65
+ from Recorded key month
66
+ add hours by hours
67
+ query ByMonth => Totals optional
68
+ by month Month
69
+ slice StateChange Record
70
+ command Record
71
+ resource ResourceId identifier
72
+ month Month
73
+ hours Decimal
74
+ produces Recorded
75
+ for resource
76
+ month = month
77
+ hours = hours
78
+ ```
79
+
80
+ When instances already exist under their own identity and another event only enriches them, use a [join](joins.md) instead: it updates the existing instances and never creates one. There is no automatic repair from a reducer to a projection.
81
+
82
+ ## What the check compares
83
+
84
+ - It compares declared **nominal** identity types (`Month` and `ResourceId` differ even though both wrap text), never property names, primitive backing, stream ids or fixture values. Equal types do not prove equal runtime ids, and distinct concepts can hold equal values, so a finding is a likely modeling mistake rather than proof.
85
+ - It compares only where the producer is known: a command production with `for`, an implicit or generated command identifier, and a reaction production with an explicit `for`. All producers across the application count, after source and stream filters and production-route overrides.
86
+ - Not compared (unknown provenance): a plain `produces` without `for` (it lands on an identity the request allocates), a reaction without `for`, captures, seeds and code. An unknown producer suppresses the query comparison for that event.
87
+ - Collection queries, performer-served or unbuilt views, projection-backed views and public-event reducer targets do not trigger it.
88
+ - It is a native C# compiler diagnostic: the standalone tool, `cratis screenplay validate` (when its bundled compiler is new enough) and MCP diagnostics report it. The TypeScript compiler and editors only pass through findings the host supplies, so a clean editor is not evidence.
@@ -16,6 +16,7 @@
16
16
  { "kind": "skill-contains", "value": "**routes nothing** (`PLAY0381`)" },
17
17
  { "kind": "skill-contains", "value": "The key is **always the event source id**" },
18
18
  { "kind": "skill-contains", "value": "**A join never creates an" },
19
- { "kind": "skill-contains", "value": "Inline code uses a tagged fence" }
19
+ { "kind": "skill-contains", "value": "Inline code uses a tagged fence" },
20
+ { "kind": "skill-contains", "value": "PLAY0690", "file": "references/reducer-identity.md" }
20
21
  ]
21
22
  }
@@ -186,6 +186,10 @@ Specs pass `by` and filters by name (PLAY0468 for unknown, repeated or missing
186
186
  required arguments); collection results match by read model/key regardless of
187
187
  row order. Details and renderer limits: [equality filters](references/equality-filters.md).
188
188
 
189
+ A reducer-built view is keyed by event-source id, so a single-instance `by` of another
190
+ nominal type warns `PLAY0690`. Rebuild it as a projection keyed on each `from`: projections
191
+ [reducer identity](../cratis-screenplay-projections/references/reducer-identity.md).
192
+
189
193
  ## `screen` — three levels
190
194
 
191
195
  The examples below are excerpts: each screen sits in a slice, and the queries,
@@ -303,6 +307,7 @@ binding failures are errors.
303
307
  - [ ] Every authorized query has a specification with `given caller`, including a
304
308
  `then denied` case.
305
309
  - [ ] Each read model has exactly one builder and every field traces to an event.
310
+ - [ ] A reducer-built view's `by` type matches its events' event-source identity (`PLAY0690`).
306
311
  - [ ] No screen reference is left ambiguous or unresolved.
307
312
  - [ ] Every UI binding that consumes screen data (component context, toolbar
308
313
  parameter, dialog input, outlet route) has a source in the screen contract
@@ -38,8 +38,7 @@ Ask the batch below only for what the request leaves open: (1) origin of each ac
38
38
  ## Procedure
39
39
 
40
40
  1. **Fix the mode** for the scope (`cratis-screenplay-modeling-lifecycle`): design, executable
41
- or renderable. It decides which shapes you may use (steps 6, 9). Read the existing model
42
- before adding to it.
41
+ or renderable. It decides which shapes you may use (steps 6, 9). Read the existing model first.
43
42
  2. **Find each event's origin.** For every event ask who or what makes it happen and through
44
43
  which path: a persona's screen `action` or form, a reaction's `invokes`, a capture. The
45
44
  origin must be **reachable** (some screen, form, reaction or integration leads to the
@@ -54,8 +53,7 @@ Ask the batch below only for what the request leaves open: (1) origin of each ac
54
53
  separate reasons change these values, then split (`references/slicing.md` *Generic edits*).
55
54
  One input per thing the caller decides or supplies; `identifier` on the property naming the
56
55
  instance (omit only for allocation); inline `produces event <Name>` (no `for`) when the command owns the event; plain `produces <Name>` needs `for <identifier>`. Trace each input
57
- (`references/field-lineage.md`) and list the values each rule reads: input, state or context
58
- (`references/command-inventory.md`). A conditional input is a granularity question: two
56
+ (`references/field-lineage.md`) and list the values each rule reads: input, state or context. A conditional input is a granularity question: two
59
57
  decisions are two commands; one decision with a dependent rule is an implication
60
58
  (`require a == false or b > c`). Never a boolean attestation input (`confirmsX == true`)
61
59
  standing in for a rule. Place each rule in its layer (`references/rule-layers.md`). A rule
@@ -89,7 +87,7 @@ Ask the batch below only for what the request leaves open: (1) origin of each ac
89
87
  6. **Design the read side** (`references/read-model-design.md`). Enumerate every screen and
90
88
  automation first: one read model per screen component or automation need; the typical
91
89
  slice is `read model -> screen -> command -> event`. Pick the builder (declarative projection first, reducer when
92
- guards cannot be expressed). The query follows the business view: a list where people scan a
90
+ guards cannot be expressed; a lookup `by` another identity type warns `PLAY0690`). The query follows the business view: a list where people scan a
93
91
  list, a keyed lookup where they open one thing. For **executable or renderable scope**, every
94
92
  read model needs one unambiguous identity: explicit trailing `key` first. Read `references/read-model-design.md` ("Executable identity of a read model") for the fallback order, the key decision table and the Stage rule.
95
93
 
@@ -130,7 +130,10 @@ Observable delivery does not make a materialized projection decision-consistent;
130
130
  decision needs a guarded read (`rule-layers.md`, `cratis-screenplay-streams-and-consistency`). Prefer a
131
131
  declarative projection (mappings, event joins, counters, bounded children, removals); a join
132
132
  consumes event facts, not another read model. Use a reducer when prior-state, attempt or
133
- ordering guards cannot be expressed declaratively. Review every writer of a property: AutoMap
133
+ ordering guards cannot be expressed declaratively, and the view is only ever looked up by the
134
+ event-source identity: a reducer cannot be re-keyed, so a query `by` another identity type warns
135
+ `PLAY0690`; aggregate by a payload value with a projection keyed on each `from`, or a join
136
+ (projections [reducer identity](../../cratis-screenplay-projections/references/reducer-identity.md)). Review every writer of a property: AutoMap
134
137
  can introduce updates from other events. Use variants only when lifecycle stages have different
135
138
  shapes. Keep growing histories in a separate view; never combine several people's PII under
136
139
  one read-model instance (compose queries across person-scoped views instead).
@@ -39,6 +39,10 @@ superseded statement needs context; exceptions do not change the contract.
39
39
  2. **Check the folder, warnings as errors.** `screenplay <folder> --warnaserror` or
40
40
  `cratis screenplay validate <folder> --warnings-as-errors`. Warnings hide defects:
41
41
  PLAY0029 (a misspelt keyword or a `type` inside a slice) drops a construct silently.
42
+ A committed `screenplay.json` at the model root declares the repository's gate (checks, warnings
43
+ as errors, scope, open questions): run without repeating those flags (confirm with
44
+ `--print-gate` before dropping `--warnaserror`), read it with
45
+ `--print-gate`, and treat a refused file as "could not run" (`references/verdicts.md`).
42
46
  3. **Pick the compiler deliberately and name it.** Standalone `screenplay` for
43
47
  language checks and reference specifications; `cratis` for rendering
44
48
  and generation. Report both tool and version, rather than transferring a verdict across
@@ -63,6 +67,7 @@ superseded statement needs context; exceptions do not change the contract.
63
67
  | --- | --- | --- |
64
68
  | Check root | `screenplay <root> --warnaserror --no-color` | `cratis screenplay validate <root> --warnings-as-errors -o json-compact` |
65
69
  | Scope/checks | `--scope <address>`, `--check <names>`; a scoped pass is not whole-application success | `validate --executable` (bundled binder, no write), `--check <names>`, `--scope`; `render --scope`/`--check`; `cratis screenplay conform` |
70
+ | Declared gate | `screenplay <root> --print-gate`, `--format json`, `--no-gate-file` | `validate <root> --print-gate`, `--no-gate-file`; the same `screenplay.json` |
66
71
  | Reference execution | `screenplay test <root>` or MCP `run-specifications` | Discover bundled MCP capability; target tests are not V4 |
67
72
  | MCP | `screenplay mcp <root>`; discover schemas | `cratis screenplay mcp [root]`; registered by `cratis ai install` |
68
73
  | Render boundary | Binder admission is not Stage admission | The CLI renders only ESM versions it knows; a newer bound model gets `CLI-RENDER-004` |
@@ -2,6 +2,7 @@
2
2
 
3
3
  ## Contents
4
4
 
5
+ - Validation commands and the gate file
5
6
  - Samples and preview fixtures
6
7
  - Lexical and structural rules
7
8
  - Exact numeric source mode
@@ -57,6 +58,20 @@ by `for_Documentation/when_comparing_the_grammar_against_the_parsers.cs`),
57
58
  Screenplay CI (`when_compiling_every_example.cs`) against repository main: the standalone
58
59
  compiler; the compiler bundled in `cratis` can be older and lack the newest ESM versions.
59
60
 
61
+ ## Validation commands and the gate file
62
+
63
+ ```shell
64
+ screenplay <root> --warnaserror --no-color # V1; also reads <root>/screenplay.json
65
+ screenplay <root> --print-gate # effective gate and each value's source, no compile
66
+ screenplay <root> --format json # one JSON report: diagnostics, gate, exitCode
67
+ screenplay <root> --no-gate-file --check all # bypass the file for one run
68
+ screenplay test <root> # V4
69
+ ```
70
+
71
+ `screenplay.json` (authored, beside `.screenplay/`) holds `validation.checks`, `warningsAsErrors`, `scope`
72
+ and `openQuestions`; unknown keys or check names are refused (exit 2), never ignored. Details and
73
+ precedence: [verdicts](verdicts.md).
74
+
60
75
  ## Samples and preview fixtures
61
76
 
62
77
  Invoicing is a warning-free syntax showcase, not a runnable whole-language proof.
@@ -8,6 +8,7 @@
8
8
  - Explicit enum member numbers
9
9
  - Reserved stored names and event pins
10
10
  - Named keys, production routes and observer filters
11
+ - Reducer instance identity (PLAY0690)
11
12
  - Caller identity, compliance and routes
12
13
  - Specification metadata, callers, cases and locators
13
14
  - Guarded interaction diagnostics
@@ -146,6 +147,16 @@ Overrides/filters select released ESM v10; malformed routes also use PLAY0504.
146
147
  Read read-surface `references/named-keys.md` and
147
148
  [production routes and filters](production-routes-and-filters.md).
148
149
 
150
+ ## Reducer instance identity (PLAY0690)
151
+
152
+ | Code | Severity | Meaning / fix |
153
+ | --- | --- | --- |
154
+ | PLAY0690 | W | A reducer instance is keyed by the event-source id, so a reducer-backed scalar query `by` another nominal type, or a reducer whose complete producers use several identity types, warns. Reducers cannot be re-keyed: use a projection with a property key on each `from`, or a join for existing instances |
155
+
156
+ Fails `--warnaserror`. Compares declared nominal types only where producers are known (command `for`, implicit or generated identifiers, reaction `for`); plain `produces` without `for`, reactions without `for`, captures, seeds and code are not compared. Native C# only: a clean editor proves nothing. Example and limits: projections [reducer identity](../../cratis-screenplay-projections/references/reducer-identity.md).
157
+
158
+ Requires Screenplay 4.170.0 or later (`screenplay --version`); an older compiler does not report it, and `cratis screenplay validate` reports it only when its bundled compiler is that new.
159
+
149
160
  ## Caller identity, compliance and routes
150
161
 
151
162
  | Code | Severity | Meaning / fix |
@@ -309,8 +320,8 @@ identity details (0633–0646), compliance/subject checks, reaction identity
309
320
  A shared catalog code is not proof that a check is implemented in both compilers.
310
321
 
311
322
  C# CLI/MCP owns whole-application scoped diagnostics, opt-in completeness
312
- (including PLAY0530–0537, PLAY0602–0606 and PLAY0652), bound reaction gate
313
- checks PLAY0650/0651, semantic binding and PLAY0546 (a negated claim targets optional,
323
+ (including PLAY0530–0537, PLAY0602–0606 and PLAY0652), reducer identity
324
+ PLAY0690, bound reaction gate checks PLAY0650/0651, semantic binding and PLAY0546 (a negated claim targets optional,
314
325
  unavailable-subject or non-text data). The VS Code repair bridge is not a general
315
326
  C# validator. A clean editor is not V1 for the complete application, V3 binding
316
327
  or V4 execution. Run the CLI or MCP for those verdicts.
@@ -69,3 +69,4 @@ Traps 39 to 44 are tool traps.
69
69
  42. A symlinked model path is refused by the MCP server (macOS `/tmp` is a symlink); pass the physical path. Proposals are connection-local (at most 16); `apply` persists identities to `.screenplay/identities.json` and is not crash-atomic across files; never retry `apply` after `ApplyOutcomeUnknown`.
70
70
  43. `modelRevision` is canonical semantic (line positions and descriptions do not change it; a file move only if it changes logical placement or application identity) and, without `identities.json`, changes with the root folder name; it is not comparable with a render's `semanticRevision`. Source identity for verdicts is the commit plus the digest from the source-identity helper (`cratis-screenplay-modeling-lifecycle` `references/verdicts-and-modes.md` "Source identity").
71
71
  44. `cratis render` neither builds nor tests; `--name` is required for plain source; `.cratis-render/` is control state and is never staged; rendered apps pin their own Arc and Chronicle versions (`[ProtectedDecision]` is available to gap-fill, not automatically emitted). `cratis` file mode versus folder mode (trap 6) and a stale `cratis` on `PATH` (`which -a cratis`) both silently change what a verdict means.
72
+ 45. A reducer cannot be re-keyed: its instances follow the event-source id, so a query `by` another identity type warns PLAY0690 (`--warnaserror` fails it; editors only show it when the host supplies native findings). Aggregate by a payload value with a projection keyed on each `from`, or a join: projections [reducer identity](../../cratis-screenplay-projections/references/reducer-identity.md).
@@ -3,6 +3,7 @@
3
3
  ## Contents
4
4
 
5
5
  - V1: authorable
6
+ - Declared validation gate (`screenplay.json`)
6
7
  - Release-baseline gate (independent of V1–V5)
7
8
  - V2: executable diagnostics
8
9
  - V3: binding-ready
@@ -51,8 +52,67 @@ render, V1 and V5 from the CLIs. Read installed versions with `screenplay --vers
51
52
  - Warnings are failures here. PLAY0029 (a construct dropped silently) and the unresolved-name
52
53
  warnings are the defects this verdict exists to catch.
53
54
  - Syntax and consistency checks (PLAY0282 to PLAY0294) run; binding does not.
55
+ - A `screenplay.json` at the model root supplies checks, warnings-as-errors, scope and open-questions
56
+ mode to both commands above, so they need not repeat those flags; drop `--warnaserror` only when
57
+ `--print-gate` shows `warningsAsErrors: true`. Read the effective gate before
58
+ reporting (next section).
54
59
  - Report: `V1 pass (screenplay <version>, 3 files)`, `V1 fail: PLAY0029 in a.play, line 12`.
55
60
 
61
+ ## Declared validation gate (`screenplay.json`)
62
+
63
+ A repository declares its V1 gate once, in an authored `screenplay.json` at the model root,
64
+ beside the tool-owned `.screenplay/`. Prefer it to repeating `--check`, `--warnaserror`,
65
+ `--scope` and `--open-questions` in every script: every run then enforces the same gate.
66
+ `screenplay.json` is the only file there people write; no tool creates or edits it.
67
+
68
+ ```json
69
+ {
70
+ "$schema": "https://raw.githubusercontent.com/Cratis/Screenplay/main/Documentation/screenplay/screenplay-v1.schema.json",
71
+ "validation": {
72
+ "checks": ["field-origins", "query-keys", "navigation"],
73
+ "warningsAsErrors": true,
74
+ "scope": "Workspaces.Tracking",
75
+ "openQuestions": "fail"
76
+ }
77
+ }
78
+ ```
79
+
80
+ - Every member is optional. A `checks` item is one check name, diagnostic code or `all`
81
+ (never a comma list); `all` expands to every check the running tool supports, so an upgrade can
82
+ strengthen the gate, while an explicit list pins it. `openQuestions` is `report` or `fail`;
83
+ omitted means notes are not evaluated.
84
+ - The model root is the nearest folder at or above the target (a file's parent) that holds
85
+ `screenplay.json` or `.screenplay/`. The search includes but never crosses the first folder with
86
+ `.git`; with no match the target folder is the root and the defaults apply. Nested models keep
87
+ their own gates; imports never affect the search.
88
+ - A command-line option replaces only its own setting. Omitting `--warnaserror` keeps
89
+ `warningsAsErrors: true`, and any `--check` replaces the file's whole list. `--no-gate-file`
90
+ ignores the file, even an invalid one; a relaxed run is not the repository's gate, so say so.
91
+
92
+ | Need | Standalone | `cratis screenplay validate` |
93
+ | --- | --- | --- |
94
+ | Effective gate and the source of each value, no compile (exit 0 unless the file is refused) | `screenplay <root> --print-gate` (text) | `--print-gate` (text, or the gate object under `-o json`) |
95
+ | Machine-readable verdict | `--format json`: one document with `diagnostics`, `openQuestions`, `refusal`, `exitCode`, `gate` | `-o json-compact`: the summary carries `gate` |
96
+ | Ignore the file | `--no-gate-file` | `--no-gate-file` |
97
+
98
+ Each gate value reports `commandLine`, `file` or `default` as its source. Report the gate with
99
+ the verdict: `V1 pass (screenplay <version>, gate screenplay.json: field-origins, query-keys,
100
+ warnings as errors, 3 files)`.
101
+
102
+ **A refused gate file is "could not run", never a pass or a fail.** Malformed JSON, a duplicate
103
+ member, an unknown key, an unknown check name or a wrong type is refused as invalid, and a
104
+ directory, symlink or unreadable file is refused as unreadable; neither is ignored or repaired. The
105
+ standalone tool exits **2** and the CLI exits **1**, each with `path:line:column`. Report
106
+ `V1 not run: gate file refused (<path:line:column>)`, fix the file and rerun.
107
+ A scope declared in the file combined with `cratis screenplay validate --executable` also exits 1:
108
+ check binding separately or pass `--no-gate-file` and name it.
109
+
110
+ Requires Screenplay 4.165.0 or later (`screenplay --version`) for `screenplay.json`, `--format`,
111
+ `--print-gate` and `--no-gate-file`; an older tool does not read the file and runs without the
112
+ gate, so a clean result there says nothing about it. `cratis screenplay validate` reads the file
113
+ from Cratis CLI 3.65.0 or later (`cratis --version`); an older CLI ignores it, so use the standalone
114
+ tool for the gate.
115
+
56
116
  ## Release-baseline gate (independent of V1–V5)
57
117
 
58
118
  Run `screenplay baseline check <model-root>` before accepting edits to released event
@@ -180,9 +240,10 @@ What Stage admits: `renderable-subset.md`.
180
240
 
181
241
  | Layer | Route | Result |
182
242
  | --- | --- | --- |
183
- | Source validation | `screenplay <root> --warnaserror --no-color` | 0 clean; 1 reported defects; 2 could not run or invalid arguments or input. `--scope` limits the reported set, not compilation |
243
+ | Source validation | `screenplay <root> --warnaserror --no-color` | 0 clean; 1 reported defects; 2 could not run or invalid arguments or input, including a refused `screenplay.json`. `--scope` limits the reported set, not compilation |
244
+ | Declared gate | `screenplay <root> --print-gate`; `--format json`; `--no-gate-file` | Reads `screenplay.json`; `--print-gate` exits 0 without compiling unless the file is refused; a gate-promoted warning or an open question in `fail` mode exits 1 |
184
245
  | Completeness | add `--check <name>[,<name>]` (repeatable) or `--check all` | Opt-in warnings; whole-source errors skip checks, not pass them |
185
- | CLI source validation | `cratis screenplay validate <root> --warnings-as-errors -o json-compact` | Read the producer exit and the JSON errors, warnings and file count |
246
+ | CLI source validation | `cratis screenplay validate <root> --warnings-as-errors -o json-compact` | Read the producer exit and the JSON errors, warnings and file count; 5 validation error (including gate-promoted warnings), 1 invalid input (including a refused `screenplay.json`) |
186
247
  | Binding | MCP workspace readiness and `executable-diagnostics` | V2 reads diagnostics; V3 requires binding with no blocking diagnostics |
187
248
  | Reference specifications | `screenplay test <root> [--filter <address>] [--format text\|json]`, or MCP `run-specifications` (`specification`, `scope`, `offset`, `limit`, `expectedSourceRevision`, `proposalId`) | 0 all selected passed; 1 failed; 2 invalid arguments, input, selection or IO; 3 unsupported or unbound. Counts of zero do not prove coverage |
188
249
  | Release baseline | `screenplay baseline admit <root> --release <label>`; `screenplay baseline check <root>` | 0 success; 1 changed or missing revision; 2 unavailable, unreadable or invalid input or write failure |
@@ -221,7 +282,7 @@ change ESM or V1–V5 verdicts. Read model-authoring
221
282
  [model notes](../../cratis-screenplay-model-authoring/references/model-notes.md) when
222
283
  recording, resolving, paging or reviewing orphan impact.
223
284
 
224
- Requires Screenplay 4.144.0 or later (`screenplay --version`) for `--open-questions`; `cratis screenplay validate` has no such option, so use the standalone tool for this gate.
285
+ Requires Screenplay 4.144.0 or later (`screenplay --version`) for `--open-questions`. `cratis screenplay validate` accepts the same option from Cratis CLI 3.60.0 or later (exit 5 when a question remains in `fail` mode, 1 when notes are unreadable); with an older CLI use the standalone tool. `validation.openQuestions` in `screenplay.json` selects the mode without the flag.
225
286
 
226
287
  ## Revisions and descriptions
227
288
 
@@ -16,7 +16,13 @@
16
16
  { "kind": "skill-contains", "value": "the **whole source, stream and stream id**", "file": "references/production-routes-and-filters.md" },
17
17
  { "kind": "play-compiles" },
18
18
  { "kind": "skill-contains", "value": "Requires Screenplay 4.144.0", "file": "references/verdicts.md" },
19
- { "kind": "skill-contains", "value": "`cratis screenplay validate` has no such option", "file": "references/verdicts.md" },
19
+ { "kind": "skill-contains", "value": "accepts the same option from Cratis CLI 3.60.0", "file": "references/verdicts.md" },
20
+ { "kind": "skill-contains", "value": "A refused gate file is \"could not run\"", "file": "references/verdicts.md" },
21
+ { "kind": "skill-contains", "value": "Requires Screenplay 4.165.0 or later", "file": "references/verdicts.md" },
22
+ { "kind": "skill-contains", "value": "Cratis CLI 3.65.0 or later", "file": "references/verdicts.md" },
23
+ { "kind": "skill-contains", "value": "--print-gate", "file": "references/cheat-sheet.md" },
24
+ { "kind": "skill-contains", "value": "PLAY0690", "file": "references/diagnostics.md" },
25
+ { "kind": "skill-contains", "value": "Requires Screenplay 4.170.0 or later", "file": "references/diagnostics.md" },
20
26
  { "kind": "skill-contains", "value": "--open-questions fail", "file": "references/verdicts.md" },
21
27
  { "kind": "skill-contains", "value": "unreadable notes exit **2**", "file": "references/verdicts.md" },
22
28
  { "kind": "skill-contains", "value": "MCP/CLI only, **no PLAY code**", "file": "references/verdicts.md" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/pi",
3
- "version": "7.67.0",
3
+ "version": "7.68.1",
4
4
  "description": "Configuration-aware Cratis AI integration for Pi",
5
5
  "type": "module",
6
6
  "files": [