@cratis/pi 7.92.0 → 7.94.0

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 (41) hide show
  1. package/package/corpus/harnesses/copilot/instructions/discoverability.instructions.md +34 -0
  2. package/package/corpus/hooks/scripts/cratis-patterns.json +24 -0
  3. package/package/corpus/prompts/pre-publish-check.prompt.md +26 -0
  4. package/package/corpus/rules/discoverability.md +33 -0
  5. package/package/corpus/rules/general.md +1 -0
  6. package/package/corpus/skills/cratis-discoverability/LICENSE +2 -0
  7. package/package/corpus/skills/cratis-discoverability/SKILL.md +73 -0
  8. package/package/corpus/skills/cratis-discoverability/references/entities.md +68 -0
  9. package/package/corpus/skills/cratis-discoverability/references/package-metadata.md +38 -0
  10. package/package/corpus/skills/cratis-discoverability/references/page-checklist.md +88 -0
  11. package/package/corpus/skills/cratis-discoverability/references/structured-data.md +62 -0
  12. package/package/corpus/skills/cratis-discoverability/scripts/verify-discoverability.mjs +107 -0
  13. package/package/corpus/skills/cratis-discoverability/verification.json +13 -0
  14. package/package/corpus/skills/cratis-screenplay-command-surface/references/text-and-match-rules.md +17 -0
  15. package/package/corpus/skills/cratis-screenplay-model-authoring/references/language-reference.md +1 -1
  16. package/package/corpus/skills/cratis-screenplay-model-authoring/references/mcp-tools.md +14 -2
  17. package/package/corpus/skills/cratis-screenplay-model-review/SKILL.md +2 -2
  18. package/package/corpus/skills/cratis-screenplay-model-review/references/checklist.md +3 -3
  19. package/package/corpus/skills/cratis-screenplay-model-review/references/model-inventory-report.md +2 -1
  20. package/package/corpus/skills/cratis-screenplay-model-review/references/verified-sources.md +1 -1
  21. package/package/corpus/skills/cratis-screenplay-read-surface/SKILL.md +3 -3
  22. package/package/corpus/skills/cratis-screenplay-read-surface/references/equality-filters.md +1 -1
  23. package/package/corpus/skills/cratis-screenplay-read-surface/references/named-keys.md +1 -1
  24. package/package/corpus/skills/cratis-screenplay-render-and-gap-fill/references/renderability-gate.md +1 -1
  25. package/package/corpus/skills/cratis-screenplay-scenario-coverage/SKILL.md +8 -2
  26. package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/scenario-examples.md +2 -2
  27. package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/spec-forms-by-mode.md +4 -3
  28. package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/view-and-story-specs.md +1 -1
  29. package/package/corpus/skills/cratis-screenplay-slice-design/SKILL.md +1 -1
  30. package/package/corpus/skills/cratis-screenplay-slice-design/references/read-model-design.md +3 -2
  31. package/package/corpus/skills/cratis-screenplay-specifications/SKILL.md +6 -2
  32. package/package/corpus/skills/cratis-screenplay-specifications/references/examples-and-routes.md +19 -0
  33. package/package/corpus/skills/cratis-screenplay-streams-and-consistency/references/stream-id-rules.md +4 -0
  34. package/package/corpus/skills/cratis-screenplay-toolchain/SKILL.md +1 -1
  35. package/package/corpus/skills/cratis-screenplay-toolchain/references/diagnostics.md +9 -0
  36. package/package/corpus/skills/cratis-screenplay-toolchain/references/executable-subset.md +10 -8
  37. package/package/corpus/skills/cratis-screenplay-toolchain/references/renderable-subset.md +2 -2
  38. package/package/corpus/skills/cratis-screenplay-toolchain/references/traps.md +1 -1
  39. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/admission.md +1 -1
  40. package/package/profile-catalog.json +2 -0
  41. package/package.json +1 -1
@@ -0,0 +1,34 @@
1
+ ---
2
+ applyTo: "**/Documentation/**/*.md,**/Documentation/**/*.mdx,**/src/pages/**/*.astro,**/src/pages/**/*.md,**/src/pages/**/*.mdx,**/src/pages/**/*.html,**/src/content/**/*.md,**/src/content/**/*.mdx"
3
+ paths:
4
+ - "**/Documentation/**/*.md"
5
+ - "**/Documentation/**/*.mdx"
6
+ - "**/src/pages/**/*.astro"
7
+ - "**/src/pages/**/*.md"
8
+ - "**/src/pages/**/*.mdx"
9
+ - "**/src/pages/**/*.html"
10
+ - "**/src/content/**/*.md"
11
+ - "**/src/content/**/*.mdx"
12
+ ---
13
+ <!-- cratis-ai: generated Copilot instructions. Do not edit; rules/ is the source. -->
14
+
15
+ # Writing content that people and assistants can find
16
+
17
+ Readability comes first. If a change for search engines or AI assistants makes a page harder to read, drop the change. Voice, structure and examples stay with [writing-cratis-docs.md](./writing-cratis-docs.instructions.md), [editing-cratis-docs.md](./editing-cratis-docs.instructions.md) and [documentation-structure-and-formatting.md](./documentation-structure-and-formatting.instructions.md); this rule adds only what they do not cover. Before publishing a page, post or package, use **cratis-discoverability**.
18
+
19
+ ## Every page, post and README
20
+
21
+ - **Define first, where a definition belongs.** Landing pages, product index pages, glossary entries, READMEs and package descriptions open with one self-contained sentence: "X is a <category> for <audience> that <does>."
22
+ - **Docs tutorials, getting-started pages and explanations follow [writing-cratis-docs.md](./writing-cratis-docs.instructions.md)**: scenario first, pain before relief. Their answer-first, self-contained definition goes in the frontmatter `description`, which serves search and AI. Where it reads naturally, one definition sentence may follow the opening scenario. writing-cratis-docs wins on voice and structure.
23
+ - **Title and description are written by hand.** Title with the distinguishing term first: 50-60 characters for blog and landing pages, 20-60 for docs and reference pages; a README H1 that is the product name is fine. Description is one sentence of 60-160 characters, never over 160, and not a copy of the title or of another page's description.
24
+ - **Name things one way.** The first mention is the full name ("Cratis Chronicle"), later mentions may shorten it. Use the canonical names in the **cratis-discoverability** entity reference, and do not call the platform "Cratis.io".
25
+ - **Links name their destination.** Link text says what is behind it. Outside the docs site, link docs with the absolute canonical host `https://www.cratis.io/...`, never the bare `cratis.io`.
26
+ - **Images carry alt text.** Describe the content. Use an empty `alt=""` only for decoration.
27
+ - **No keyword stuffing, no filler for length, no text hidden from readers.** A page is done when it is complete.
28
+
29
+ ## Sites and packages
30
+
31
+ - Public pages need a canonical link, one `<h1>`, Open Graph tags, valid JSON-LD that describes visible content, a sitemap entry and a `robots.txt` that allows crawling. Add no new `HowTo` or `FAQPage` markup; keep the visible Q&A.
32
+ - `llms.txt` is an optional index. Never claim it improves ranking. See **cratis-llm-friendly-documentation**.
33
+ - Package metadata (`Description`, `PackageProjectUrl`, `PackageTags`, npm `description`, `homepage`, `keywords`) and the README opener use the same definition sentence, and the project URL points at the docs page `https://www.cratis.io/<product>/`.
34
+ - State only facts the repository supports: versions, licenses, limits. The claim gate in `BRAND.md`, `MESSAGING.md` and `SITE.md` still applies.
@@ -87,6 +87,30 @@
87
87
  "paths": ["**/*.ts", "**/*.tsx"],
88
88
  "exclude_paths": ["**/node_modules/**", "**/dist/**", "**/wwwroot/**", "**/obj/**", "**/bin/**"],
89
89
  "message": "Cratis Components 4 has no PrimeReact underneath — a `primereact/dialog` import is a leftover from Components 3. Use `CommandDialog` from `@cratis/components/CommandDialog` or `Dialog` from `@cratis/components/Dialogs`."
90
+ },
91
+ {
92
+ "id": "cratis-apex-docs-link",
93
+ "rule": "discoverability.md — canonical links",
94
+ "pattern": "https?://cratis\\.io([/)\"' <]|$)",
95
+ "paths": ["**/*.md", "**/*.mdx", "**/*.html", "**/*.astro"],
96
+ "exclude_paths": ["**/node_modules/**", "**/dist/**", "**/obj/**", "**/bin/**", "**/.ai-work/**"],
97
+ "message": "Discoverability (advisory): the docs host is `https://www.cratis.io`. Link the canonical host, not the bare `cratis.io`."
98
+ },
99
+ {
100
+ "id": "cratis-empty-image-alt",
101
+ "rule": "discoverability.md — images carry alt text",
102
+ "pattern": "!\\[\\]\\(",
103
+ "paths": ["**/*.md", "**/*.mdx"],
104
+ "exclude_paths": ["**/node_modules/**", "**/dist/**", "**/obj/**", "**/bin/**", "**/.ai-work/**"],
105
+ "message": "Discoverability (advisory): an image with empty alt text. Describe the content, or keep it empty only when the image is purely decorative."
106
+ },
107
+ {
108
+ "id": "cratis-package-url-github-only",
109
+ "rule": "discoverability.md — package metadata",
110
+ "pattern": "<PackageProjectUrl>[[:space:]]*https?://github\\.com",
111
+ "paths": ["**/*.csproj", "**/*.props"],
112
+ "exclude_paths": ["**/node_modules/**", "**/obj/**", "**/bin/**"],
113
+ "message": "Discoverability (advisory): `PackageProjectUrl` should point at the product's docs page `https://www.cratis.io/<product>/`, not only at GitHub."
90
114
  }
91
115
  ]
92
116
  }
@@ -0,0 +1,26 @@
1
+ ---
2
+ agent: agent
3
+ description: "Run the discoverability check on content about to be published."
4
+ ---
5
+
6
+ # Pre-publish check
7
+
8
+ Run the discoverability check on what is about to be published: a docs page, blog
9
+ post, landing page, README or package metadata. Follow
10
+ `.cratis/ai/rules/discoverability.md` and invoke **cratis-discoverability**.
11
+
12
+ ## Identify the content
13
+
14
+ Find the files in the change, the content type of each, and the owning source (never
15
+ a synced copy). Ask only when the content type is unclear.
16
+
17
+ ## Check and fix
18
+
19
+ Apply the skill's checks for each content type to the draft: opening definition,
20
+ title and description, canonical names, canonical `https://www.cratis.io/...` links,
21
+ alt text and, for sites and packages, the structured data and metadata. Make the
22
+ fixes in the owning files. Where a fix would hurt readability, keep the text and
23
+ report it. For a built site, run the skill's checker on the build output.
24
+
25
+ Report what changed, what was left alone and why, and any check that could not run.
26
+ Publishing, merging and releasing stay with a person.
@@ -0,0 +1,33 @@
1
+ ---
2
+ applyTo: "**/Documentation/**/*.md,**/Documentation/**/*.mdx,**/src/pages/**/*.astro,**/src/pages/**/*.md,**/src/pages/**/*.mdx,**/src/pages/**/*.html,**/src/content/**/*.md,**/src/content/**/*.mdx"
3
+ paths:
4
+ - "**/Documentation/**/*.md"
5
+ - "**/Documentation/**/*.mdx"
6
+ - "**/src/pages/**/*.astro"
7
+ - "**/src/pages/**/*.md"
8
+ - "**/src/pages/**/*.mdx"
9
+ - "**/src/pages/**/*.html"
10
+ - "**/src/content/**/*.md"
11
+ - "**/src/content/**/*.mdx"
12
+ ---
13
+
14
+ # Writing content that people and assistants can find
15
+
16
+ Readability comes first. If a change for search engines or AI assistants makes a page harder to read, drop the change. Voice, structure and examples stay with [writing-cratis-docs.md](./writing-cratis-docs.md), [editing-cratis-docs.md](./editing-cratis-docs.md) and [documentation-structure-and-formatting.md](./documentation-structure-and-formatting.md); this rule adds only what they do not cover. Before publishing a page, post or package, use **cratis-discoverability**.
17
+
18
+ ## Every page, post and README
19
+
20
+ - **Define first, where a definition belongs.** Landing pages, product index pages, glossary entries, READMEs and package descriptions open with one self-contained sentence: "X is a <category> for <audience> that <does>."
21
+ - **Docs tutorials, getting-started pages and explanations follow [writing-cratis-docs.md](./writing-cratis-docs.md)**: scenario first, pain before relief. Their answer-first, self-contained definition goes in the frontmatter `description`, which serves search and AI. Where it reads naturally, one definition sentence may follow the opening scenario. writing-cratis-docs wins on voice and structure.
22
+ - **Title and description are written by hand.** Title with the distinguishing term first: 50-60 characters for blog and landing pages, 20-60 for docs and reference pages; a README H1 that is the product name is fine. Description is one sentence of 60-160 characters, never over 160, and not a copy of the title or of another page's description.
23
+ - **Name things one way.** The first mention is the full name ("Cratis Chronicle"), later mentions may shorten it. Use the canonical names in the **cratis-discoverability** entity reference, and do not call the platform "Cratis.io".
24
+ - **Links name their destination.** Link text says what is behind it. Outside the docs site, link docs with the absolute canonical host `https://www.cratis.io/...`, never the bare `cratis.io`.
25
+ - **Images carry alt text.** Describe the content. Use an empty `alt=""` only for decoration.
26
+ - **No keyword stuffing, no filler for length, no text hidden from readers.** A page is done when it is complete.
27
+
28
+ ## Sites and packages
29
+
30
+ - Public pages need a canonical link, one `<h1>`, Open Graph tags, valid JSON-LD that describes visible content, a sitemap entry and a `robots.txt` that allows crawling. Add no new `HowTo` or `FAQPage` markup; keep the visible Q&A.
31
+ - `llms.txt` is an optional index. Never claim it improves ranking. See **cratis-llm-friendly-documentation**.
32
+ - Package metadata (`Description`, `PackageProjectUrl`, `PackageTags`, npm `description`, `homepage`, `keywords`) and the README opener use the same definition sentence, and the project URL points at the docs page `https://www.cratis.io/<product>/`.
33
+ - State only facts the repository supports: versions, licenses, limits. The claim gate in `BRAND.md`, `MESSAGING.md` and `SITE.md` still applies.
@@ -131,6 +131,7 @@ A claim is only as good as the signal behind it — a build result, a test run,
131
131
  | C# / TypeScript style and code-quality principles | `csharp.md`, `typescript.md`, `code-quality.md`, `code-quality.csharp.md`, `code-quality.typescript.md` |
132
132
  | Kotlin / Java / Elixir style; Orleans grains; reactors; GitHub Actions workflows | `kotlin.md`, `java.md`, `elixir.md`, `orleans.md`, `reactors.md`, `github-actions.md` |
133
133
  | Documentation pages (`Documentation/`): structure, voice, examples, editing | `documentation.md`, `documentation-structure-and-formatting.md`, `writing-cratis-docs.md`, `editing-cratis-docs.md`, `writing-correct-examples.md` |
134
+ | Findability of published pages, READMEs and package metadata | `discoverability.md` |
134
135
  | Go style, source and module files | `go.md`; framework contributors also read `go-engineering.md`; product APIs use `cratis-chronicle-client-go`, `cratis-arc-go` and `cratis-fundamentals-concept-go` |
135
136
  | Service lifetimes — why anything taking a scoped dependency is scoped or transient, never a singleton | `csharp.md` |
136
137
  | React + Arc + Cratis Components + MVVM + dialogs | `react.md`, `components.md`, `dialogs.md` |
@@ -0,0 +1,2 @@
1
+ Copyright (c) Cratis. All rights reserved.
2
+ Licensed under the MIT license. See LICENSE file in the project root for full license information.
@@ -0,0 +1,73 @@
1
+ ---
2
+ name: cratis-discoverability
3
+ description: Run the pre-publish check that makes Cratis content easy to find in search and AI answers without making it harder to read - a docs page, blog post, landing page, README or package metadata (title, description, opening definition, canonical links, entity names, alt text, structured data). Use when about to publish or review content for discoverability, SEO, AEO or GEO, or when writing package descriptions and README openers. Not for page voice and structure (cratis-documentation-writing), llms.txt design (cratis-llm-friendly-documentation), distribution plans (cratis-developer-marketing) or length decisions (cratis-content-length).
4
+ license: MIT
5
+ ---
6
+
7
+ # Discoverability before you publish
8
+
9
+ Discoverability is a property of good content: a clear definition, an honest title, one name per
10
+ thing and working links. The always-on rule is [discoverability.md](../../rules/discoverability.md);
11
+ this skill is the procedure to run on the finished draft. If a fix makes the page worse to read,
12
+ drop the fix and say so.
13
+
14
+ ## Procedure
15
+
16
+ 1. Name the content type: docs page, blog post, landing page, README or package metadata.
17
+ 2. Name the reader's one job and, for docs, the page type (tutorial, how-to, reference, explanation).
18
+ 3. Apply the checks for that type below. Fix the draft; do not annotate it.
19
+ 4. For a built site, run the checker and fix errors; warnings are judgment calls to report.
20
+ 5. Report what was changed, what was left alone and why.
21
+
22
+ Read `references/page-checklist.md` for the full per-type checklist and the field lengths.
23
+
24
+ ## Checks by content type
25
+
26
+ - **Docs page.** The `description` is written by hand as one self-contained
27
+ definition or outcome sentence. Tutorials, getting-started pages and explanations follow
28
+ `writing-cratis-docs` (scenario first); the definition lives in the `description`, and one
29
+ definition sentence may follow the opening scenario where it reads naturally. `writing-cratis-docs`
30
+ wins on voice and structure. Product index pages open with the definition sentence.
31
+ Reference pages stay terse: title is the exact name plus its product, no question headings, no
32
+ intro. Titles are unique across the site and name the product ("Chronicle projections").
33
+ - **Blog post.** The title states the topic in the words a reader would search. The intro gives the
34
+ conclusion in two sentences. Show the named author and date, add `lastUpdated` only for real
35
+ changes, link the owning docs pages with `https://www.cratis.io/...`, keep `description` apart
36
+ from the card `excerpt`.
37
+ - **Landing page.** One primary term in the title, the `<h1>` or the first paragraph, and a
38
+ one-sentence definition above the fold. Brand lines stay; pair them with a category term in the
39
+ title and description. Respect the claim gate in `BRAND.md`, `MESSAGING.md` and `SITE.md`.
40
+ - **README.** The H1 may be the product name ("Cratis <Product>"). The first sentence defines the product in 15-35 plain
41
+ nouns, with no logo-only or metaphor opener. Links are absolute; link the docs page.
42
+ - **Package metadata.** One sentence naming what the package is and which Cratis product it belongs
43
+ to, identical to the README opener. `PackageProjectUrl` and `homepage` point at the docs page, not
44
+ only at GitHub. Read `references/package-metadata.md` for the field map.
45
+
46
+ ## Names
47
+
48
+ Use the canonical names, one per concept, and the first-mention rule. Read
49
+ `references/entities.md` when naming a product, writing a definition sentence or filling `sameAs`.
50
+ Do not invent a name, URL, maturity word or claim that the references do not state.
51
+
52
+ ## Structured data
53
+
54
+ Describe only what is visible on the page, one `@graph` per page, stable `@id`s. Add no `HowTo` and
55
+ no new `FAQPage`. Read `references/structured-data.md` for the patterns and what each page type
56
+ gets. Do not name people in structured data.
57
+
58
+ ## Checking a built site
59
+
60
+ For a built static site (Astro `dist/` or a plain-HTML repository root), run with Node.js:
61
+ `node <skill-directory>/scripts/verify-discoverability.mjs --root <dir> --site <https://host>`.
62
+ It reports `<html lang>`, title, description, canonical host, `<h1>` count, JSON-LD parse errors,
63
+ `robots.txt`, the sitemap and `llms.txt` structure. Errors exit 1; warnings do not. Pass
64
+ `--warn-only` to downgrade errors and `--require-llms` where the site ships `llms.txt`.
65
+ Read `scripts/verify-discoverability.mjs` only when changing the checker.
66
+
67
+ ## Pitfalls
68
+
69
+ - Question headings everywhere: use them only where they read naturally (concept pages, FAQs,
70
+ comparisons, blog), never on reference pages or tutorial steps.
71
+ - Padding to a word count: there is no minimum length; a complete page is done.
72
+ - Apex `https://cratis.io/` links: the docs host is `https://www.cratis.io`.
73
+ - A description that repeats the title or a sibling page's description.
@@ -0,0 +1,68 @@
1
+ # Entity names and definitions
2
+
3
+ ## Contents
4
+
5
+ - Naming rules
6
+ - Shared identifiers
7
+ - Definition sentences
8
+ - Disambiguation
9
+
10
+ Only facts the Cratis repositories state are listed. If a product, URL or identifier is not here,
11
+ find it in the product's own README or docs before writing it; do not guess.
12
+
13
+ ## Naming rules
14
+
15
+ - First mention on a page: "Cratis Chronicle". Later mentions may be "Chronicle". In package
16
+ descriptions, README H1s and GitHub About text, always the prefixed form.
17
+ - The product is Cratis Direct. "Direct" and "Stagehand" are internal or older names.
18
+ - Cratis Build, Cratis Studio and Cratis Assurance are the three offers on cratis.no. In technical
19
+ copy and metadata, name the product (Chronicle, Arc), not the offer.
20
+ - The product is not "Cratis.io". `cratis.io` is the docs host, canonical `https://www.cratis.io`.
21
+ - "Cratis" alone is both the company and the platform. Say "the Cratis platform" where the company
22
+ could be meant.
23
+ - Cratis Studio maturity: follow `BRAND.md` (Preview) until the owner decides otherwise.
24
+
25
+ ## Shared identifiers
26
+
27
+ | Item | Value |
28
+ | --- | --- |
29
+ | Organization `@id` | `https://cratis.no/#organization` |
30
+ | Company site | `https://cratis.no` |
31
+ | Docs (canonical host) | `https://www.cratis.io` |
32
+ | Blog | `https://blog.cratis.io` |
33
+ | GitHub organization | `https://github.com/Cratis` |
34
+ | NuGet profile | `https://www.nuget.org/profiles/Cratis` |
35
+ | npm organization | `https://www.npmjs.com/org/cratis` |
36
+ | YouTube | `https://www.youtube.com/@CratisStack` |
37
+
38
+ Product docs follow `https://www.cratis.io/<product>/`.
39
+
40
+ ## Definition sentences
41
+
42
+ Use these as the opening sentence of a definitional page, README or package description. Adjust only
43
+ to fit the page; keep the facts.
44
+
45
+ | Name | Sentence | Docs page (under `https://www.cratis.io`) |
46
+ | --- | --- | --- |
47
+ | Cratis (platform) | Cratis is an open-source (MIT) event sourcing and CQRS platform for .NET, built around the Chronicle event-sourcing database. | `https://www.cratis.io/` |
48
+ | Cratis Chronicle | Cratis Chronicle is an open-source (MIT) event-sourcing database and runtime with a .NET kernel and clients for several languages. | `/chronicle/` |
49
+ | Cratis Arc | Cratis Arc is an open-source (MIT) CQRS application framework for ASP.NET Core with generated TypeScript proxies. | `/arc/` |
50
+ | Cratis Fundamentals | Cratis Fundamentals is the set of shared building blocks beneath the Cratis stack for .NET and TypeScript. | `/fundamentals/` |
51
+ | Cratis Studio | Cratis Studio is a collaborative environment for designing, visualizing and editing Screenplay event models. | `/studio/` |
52
+ | Cratis Direct | Cratis Direct is the managed control plane for the Cratis software factory: it turns GitHub signals into prioritized, governed AI work and reviewed pull requests. | none confirmed |
53
+ | Cratis Cast | Cratis Cast is a profile-site engine and directory for the people who build with Cratis, with verified, event-sourced profile pages. | `https://cast.cratis.io` |
54
+ | Cratis Screenplay | Cratis Screenplay is a business-oriented declarative language for specifying the desired functionality of an information system. | `/screenplay/` |
55
+ | Cratis Stage | Cratis Stage compiles Screenplay source, verifies modeled specifications, and renders or partially performs Cratis applications. | none confirmed |
56
+ | Cratis Prologue | Cratis Prologue captures selected signals from an existing system and interprets them into a provisional event model for review. | `/prologue/` |
57
+ | Cratis CLI | The Cratis CLI is a terminal tool for inspecting and diagnosing Cratis Chronicle. | `/cli/` |
58
+ | Chronicle Workbench | Chronicle Workbench is the web dashboard bundled with Cratis Chronicle for browsing events and operating a deployment. | `/chronicle/workbench/` |
59
+ | Chronicle MCP | The Chronicle MCP server connects an AI agent to a running Cratis Chronicle database over the Model Context Protocol. | `/chronicle-mcp/` |
60
+
61
+ Chronicle storage is pluggable (MongoDB by default, PostgreSQL, SQL Server, SQLite, in-memory).
62
+ Arc works without event sourcing; the Chronicle integration is optional.
63
+
64
+ ## Disambiguation
65
+
66
+ - `@cratis/arc` is the browser client of Arc .NET; `@cratis/arc.core` is the Node server package.
67
+ - "Cast" alone is not the streaming protocol: write "Cratis Cast".
68
+ - A docs slug not in the table is unconfirmed. Check the docs site before linking it.
@@ -0,0 +1,38 @@
1
+ # Package metadata
2
+
3
+ ## Contents
4
+
5
+ - Field map
6
+ - Writing the description
7
+ - Pitfalls
8
+
9
+ ## Field map
10
+
11
+ | Field | NuGet (`.csproj`, `Directory.Build.props`) | npm (`package.json`) |
12
+ | --- | --- | --- |
13
+ | Description | `<Description>`, 80-250 characters; the first 100 are what shows | `description`, 80-160 characters |
14
+ | Tags | `<PackageTags>`, 5-10, `cratis` plus category terms | `keywords`, the same list |
15
+ | Project URL | `<PackageProjectUrl>` = `https://www.cratis.io/<product>/` | `homepage`, the same |
16
+ | Repository | `RepositoryUrl` | `repository` |
17
+ | Authors | `Cratis` | `author` `Cratis` |
18
+ | License | `PackageLicenseExpression` `MIT` | `license` `MIT` |
19
+ | Readme | A packed README with the same opener | `README.md` |
20
+
21
+ Use a `https://www.cratis.io/<product>/` page that exists; check the docs site rather than guessing
22
+ a slug. If none exists, leave the existing URL and report it.
23
+
24
+ ## Writing the description
25
+
26
+ One sentence: what it is, for whom, and which Cratis product it belongs to - "<what it is> for
27
+ <audience>, part of Cratis <Parent>". Name the parent so the package disambiguates ("the client for
28
+ Cratis Chronicle, the open-source event-sourcing database"). The text equals the README opener and
29
+ the GitHub About text. Tags are specific ("event-sourcing", "cqrs", "dotnet"); never "utilities".
30
+
31
+ ## Pitfalls
32
+
33
+ - An empty `Description`.
34
+ - Mixed `Authors` values across packages of one product.
35
+ - `PackageProjectUrl` pointing only at GitHub.
36
+ - Two packages with near-identical descriptions. Where `@cratis/arc` (the browser client of Arc
37
+ .NET) and `@cratis/arc.core` (the Node server package) could be confused, each description says
38
+ which one it is.
@@ -0,0 +1,88 @@
1
+ # Page checklist
2
+
3
+ ## Contents
4
+
5
+ - Field targets
6
+ - Every page
7
+ - Docs pages
8
+ - Blog posts
9
+ - Landing pages
10
+ - READMEs
11
+ - Before publishing a site change
12
+
13
+ ## Field targets
14
+
15
+ Lengths are targets, counted in characters including any site suffix.
16
+
17
+ | Field | Target |
18
+ | --- | --- |
19
+ | Title | Distinguishing term first. Blog and landing pages 50-60; docs and reference pages 20-60; a README H1 that is the product name is fine |
20
+ | Description | One sentence, 60-160, never over 160; the point comes first |
21
+ | Lead answer passage | 40-60 words, names its subject, one key property, one limit |
22
+ | Heading | Under about 70 characters |
23
+ | `og:image` | 1200x630, under 200 KB, with `og:image:alt` |
24
+
25
+ ## Every page
26
+
27
+ - [ ] One reader job. For docs, one page type: tutorial, how-to, reference or explanation.
28
+ - [ ] Title unique, distinguishing term near the front.
29
+ - [ ] Description written by hand; not the title, not another page's description.
30
+ - [ ] The `description` answers what the page is or achieves in one self-contained sentence; the first paragraph does too on landing, index, glossary and README pages.
31
+ - [ ] Canonical names; first mention uses the full "Cratis X" form.
32
+ - [ ] Facts, numbers and comparisons carry a source or a date; nothing beyond the claim gate.
33
+ - [ ] Links have descriptive text. Root-relative inside the docs site, absolute
34
+ `https://www.cratis.io/...` elsewhere.
35
+ - [ ] Images have alt text (empty only when decorative) and sensible size.
36
+ - [ ] Linked from at least two other pages and from the right hub; no orphan.
37
+ - [ ] Read aloud once: if it sounds written for a crawler, rewrite it.
38
+
39
+ ## Docs pages
40
+
41
+ - Voice and structure follow `writing-cratis-docs`, which wins on any conflict: tutorials,
42
+ getting-started pages and explanations open with a concrete scenario (pain, then relief). Put the
43
+ answer-first definition in the frontmatter `description`; one definition sentence may follow the
44
+ scenario where it reads naturally.
45
+ - Product index page: opens with the one-sentence definition, then the without-versus-with framing.
46
+ - Concept page: scenario first; then why it exists, when to use it, when not to, how it works.
47
+ H2s such as "What is X" and "When to use X" only where they fit. No H1 in the body.
48
+ - Tutorial: title names the outcome and starts with a verb; the scenario, then what the reader will
49
+ have running; prerequisites and versions up front; one visible result per step; "Last updated" and the
50
+ version it applies to.
51
+ - How-to: the task as the title; goal, prerequisites, numbered steps, a done check, the two likely
52
+ failures; versions stated.
53
+ - Reference: the title is the exact name with its product; first sentence says what it is for;
54
+ tables and signatures; stable anchors; no intro, no pitch. Merge or enrich near-empty stub pages.
55
+
56
+ ## Blog posts
57
+
58
+ - [ ] Title states the topic; series name goes in a `series` field or at the end.
59
+ - [ ] Two-sentence conclusion up front; "when this is the wrong fit" where it applies.
60
+ - [ ] Named author where agreed (`cratis-team` only for company announcements), date, `lastUpdated`
61
+ only on real changes.
62
+ - [ ] At least one internal link to the owning docs page; tags reused from the existing list, no
63
+ singleton tags; a cover image with alt text for posts likely to be shared.
64
+
65
+ ## Landing pages
66
+
67
+ - [ ] One primary term in title, `<h1>` or first paragraph; one-sentence definition above the fold.
68
+ - [ ] Description differs per page and is not a 300-character paragraph.
69
+ - [ ] No testimonials, numbers, customers, prices or maturity claims the claim gate does not allow.
70
+ - [ ] Norwegian pages are written natively, keep established English technical terms, link to the
71
+ English page with `hreflang`, and are not machine-translated and published unreviewed.
72
+
73
+ ## READMEs
74
+
75
+ - [ ] H1 may be the product name ("Cratis <Product>"); first sentence is the definition (15-35 words, no metaphor).
76
+ - [ ] Then install, a minimal working example, a link to `https://www.cratis.io/<product>/`,
77
+ license and how it relates to the rest of Cratis.
78
+ - [ ] GitHub About text equals the README opener (80-160 characters); website is the docs page;
79
+ topics include `cratis` plus category terms.
80
+
81
+ ## Before publishing a site change
82
+
83
+ - [ ] Title, description, canonical, Open Graph and JSON-LD present and unique on changed pages.
84
+ - [ ] Sitemap lists new pages; `robots.txt` unchanged unless intended.
85
+ - [ ] JSON-LD validates; no `HowTo`; no new `FAQPage`.
86
+ - [ ] The content is in the raw HTML the server returns, not only rendered by JavaScript.
87
+ - [ ] Internal links resolve; no apex `cratis.io` links.
88
+ - [ ] A real HTTP 404 for unknown URLs; redirect stubs keep a canonical and stay out of the sitemap.
@@ -0,0 +1,62 @@
1
+ # Structured data patterns
2
+
3
+ ## Contents
4
+
5
+ - Rules
6
+ - Types per page type
7
+ - Pattern
8
+
9
+ ## Rules
10
+
11
+ - One `@graph` per page with stable `@id` values. The organization `@id` is
12
+ `https://cratis.no/#organization`.
13
+ - Markup describes what is visible on the page, and every date is a real one.
14
+ - Add no `HowTo`. Add no new `FAQPage`; keep the visible question and answer text. Use `QAPage` only
15
+ for real user questions and answers.
16
+ - Do not name people (`Person`, `author` name) in structured data unless the owner has approved it.
17
+ Reference the organization by `@id`.
18
+ - Do not add `offers`, an address or contact data without approval (see `SITE.md`).
19
+ - Validate with the schema.org validator or Google's Rich Results Test. Invalid markup is worse than
20
+ none. Static sites hand-write `<script type="application/ld+json">`; Astro sites generate it in
21
+ the head component, so change it there rather than per page.
22
+
23
+ ## Types per page type
24
+
25
+ | Page type | Types |
26
+ | --- | --- |
27
+ | Company home | `Organization` (name, url, logo, `sameAs`), `WebSite` |
28
+ | Product marketing page | `SoftwareApplication` (name, applicationCategory, license, url, `sameAs`) |
29
+ | Docs concept, tutorial, how-to, reference | `TechArticle` with `headline`, `dateModified`, plus `BreadcrumbList` |
30
+ | Docs hub and landing page | `CollectionPage` or `WebPage`, not `TechArticle` |
31
+ | Blog post | `BlogPosting` (`datePublished`, `dateModified`, `publisher`, `image`) |
32
+ | Repository or sample code | `SoftwareSourceCode` |
33
+ | Legal pages | `WebPage` |
34
+ | 404 and app hosts | none |
35
+
36
+ ## Pattern
37
+
38
+ ```json
39
+ {
40
+ "@context": "https://schema.org",
41
+ "@graph": [
42
+ {
43
+ "@type": "TechArticle",
44
+ "@id": "https://www.cratis.io/chronicle/projections/#article",
45
+ "headline": "Chronicle projections",
46
+ "description": "A projection declares how Chronicle builds a read model from events.",
47
+ "dateModified": "YYYY-MM-DD",
48
+ "publisher": { "@id": "https://cratis.no/#organization" },
49
+ "mainEntityOfPage": "https://www.cratis.io/chronicle/projections/"
50
+ },
51
+ {
52
+ "@type": "BreadcrumbList",
53
+ "itemListElement": [
54
+ { "@type": "ListItem", "position": 1, "name": "Chronicle", "item": "https://www.cratis.io/chronicle/" },
55
+ { "@type": "ListItem", "position": 2, "name": "Projections" }
56
+ ]
57
+ }
58
+ ]
59
+ }
60
+ ```
61
+
62
+ The values are illustrative: take the title, description and dates from the page itself.
@@ -0,0 +1,107 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+ //
4
+ // Checks a built static site (Astro dist/ or a plain-HTML repo root) before it is published:
5
+ // every indexable page has <html lang>, one <title>, a meta description, a canonical on the
6
+ // expected host and parseable JSON-LD; the site has robots.txt, a sitemap and (optionally) a
7
+ // well-formed llms.txt. Node standard library only.
8
+ //
9
+ // Usage: node verify-discoverability.mjs --root dist --site https://blog.cratis.io [--require-llms] [--warn-only]
10
+ // Exit codes: 0 clean or warnings only, 1 errors, 2 could not run.
11
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
12
+ import { join, relative } from 'node:path';
13
+
14
+ const args = process.argv.slice(2);
15
+ const option = (name, fallback) => { const i = args.indexOf(name); return i < 0 ? fallback : args[i + 1]; };
16
+ const root = option('--root', 'dist');
17
+ const site = option('--site');
18
+ const requireLlms = args.includes('--require-llms');
19
+ const warnOnly = args.includes('--warn-only');
20
+ const ignore = (option('--ignore', 'node_modules,.git,.cratis,.github,404.html,_astro,storybook') ?? '').split(',');
21
+
22
+ if (!site || !existsSync(root)) {
23
+ console.error(`could not run: need --site and an existing --root (got ${root})`);
24
+ process.exit(2);
25
+ }
26
+ const host = new URL(site).host;
27
+ let errors = 0;
28
+ let warnings = 0;
29
+ const report = (level, file, message) => {
30
+ if (level === 'error' && !warnOnly) errors++; else warnings++;
31
+ console.log(`::${level === 'error' && !warnOnly ? 'error' : 'warning'} file=${file}::${message}`);
32
+ };
33
+
34
+ function* htmlFiles(dir) {
35
+ for (const name of readdirSync(dir)) {
36
+ if (ignore.includes(name)) continue;
37
+ const path = join(dir, name);
38
+ if (statSync(path).isDirectory()) yield* htmlFiles(path);
39
+ else if (name.endsWith('.html')) yield path;
40
+ }
41
+ }
42
+
43
+ const attr = (tag, name) => tag.match(new RegExp(`${name}\\s*=\\s*["']([^"']*)["']`, 'i'))?.[1];
44
+ const tags = (html, pattern) => [...html.matchAll(pattern)].map(match => match[0]);
45
+ const titles = new Map();
46
+
47
+ for (const path of htmlFiles(root)) {
48
+ const file = relative(process.cwd(), path);
49
+ const html = readFileSync(path, 'utf8');
50
+ const metas = tags(html, /<meta\b[^>]*>/gi);
51
+ const robots = metas.find(tag => /name\s*=\s*["']robots["']/i.test(tag));
52
+ if (robots && /noindex/i.test(attr(robots, 'content') ?? '')) continue; // deliberately not indexed
53
+ if (/http-equiv\s*=\s*["']refresh["']/i.test(html)) continue; // redirect stub
54
+
55
+ if (!/<html\b[^>]*\blang\s*=/i.test(html)) report('error', file, 'missing <html lang>');
56
+ const title = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1]?.trim();
57
+ if (!title) report('error', file, 'missing or empty <title>');
58
+ else {
59
+ if (title.length > 65) report('warning', file, `title is ${title.length} characters; keep the distinguishing words in the first 60`);
60
+ titles.set(title, [...(titles.get(title) ?? []), file]);
61
+ }
62
+ const description = metas.find(tag => /name\s*=\s*["']description["']/i.test(tag));
63
+ const descriptionText = description ? attr(description, 'content') ?? '' : '';
64
+ if (!descriptionText) report('error', file, 'missing meta description');
65
+ else if (descriptionText.length < 60 || descriptionText.length > 160) report('warning', file, `meta description is ${descriptionText.length} characters (aim for 60-160)`);
66
+ const canonical = tags(html, /<link\b[^>]*>/gi).find(tag => /rel\s*=\s*["']canonical["']/i.test(tag));
67
+ const href = canonical ? attr(canonical, 'href') : undefined;
68
+ if (!href) report('error', file, 'missing <link rel="canonical">');
69
+ else {
70
+ try {
71
+ const url = new URL(href);
72
+ if (url.host !== host) report('error', file, `canonical points at ${url.host}, expected ${host}`);
73
+ } catch {
74
+ report('error', file, `canonical is not an absolute URL: ${href}`);
75
+ }
76
+ }
77
+ if ((html.match(/<h1\b/gi) ?? []).length !== 1) report('warning', file, 'expected exactly one <h1>');
78
+ for (const block of html.matchAll(/<script\b[^>]*type\s*=\s*["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi)) {
79
+ try {
80
+ const data = JSON.parse(block[1]);
81
+ const nodes = Array.isArray(data) ? data : data['@graph'] ?? [data];
82
+ if (!Array.isArray(data) && !String(data['@context'] ?? '').includes('schema.org')) report('error', file, 'JSON-LD without a schema.org @context');
83
+ for (const node of nodes) if (!node['@type']) report('warning', file, 'JSON-LD node without @type');
84
+ } catch (error) {
85
+ report('error', file, `JSON-LD does not parse: ${error.message}`);
86
+ }
87
+ }
88
+ }
89
+
90
+ for (const [title, files] of titles) if (files.length > 1) report('warning', files[0], `title "${title}" is shared by ${files.length} pages`);
91
+
92
+ const robotsPath = join(root, 'robots.txt');
93
+ if (!existsSync(robotsPath)) report('error', root, 'no robots.txt');
94
+ else if (!/^\s*sitemap\s*:/im.test(readFileSync(robotsPath, 'utf8'))) report('warning', robotsPath, 'robots.txt does not declare a Sitemap');
95
+ if (!readdirSync(root).some(name => /^sitemap.*\.xml$/.test(name))) report('error', root, 'no sitemap*.xml at the site root');
96
+
97
+ const llmsPath = join(root, 'llms.txt');
98
+ if (existsSync(llmsPath)) {
99
+ // Same structure rules as vendor/claude-seo/scripts/agentic_check.py evaluate_llms_txt (Lighthouse llms-txt audit).
100
+ const llms = readFileSync(llmsPath, 'utf8').replace(/^\uFEFF/, '');
101
+ if (!/^\s*#\s+.+/m.test(llms)) report('error', llmsPath, 'llms.txt has no H1');
102
+ if (!/\[.+\]\(.+\)/.test(llms)) report('error', llmsPath, 'llms.txt has no Markdown links');
103
+ if (!/^\s*>\s+\S/m.test(llms)) report('warning', llmsPath, "llms.txt has no '>' summary");
104
+ } else if (requireLlms) report('error', root, 'no llms.txt');
105
+
106
+ console.log(`verify-discoverability: ${errors} error(s), ${warnings} warning(s)`);
107
+ process.exit(errors ? 1 : 0);
@@ -0,0 +1,13 @@
1
+ {
2
+ "skill": "cratis-discoverability",
3
+ "input": "Run the pre-publish discoverability check on a new blog post, a package README and the package metadata for a Cratis library.",
4
+ "fixtures": [],
5
+ "assertions": [
6
+ { "kind": "skill-contains", "value": "If a fix makes the page worse to read," },
7
+ { "kind": "skill-contains", "value": "Add no `HowTo` and" },
8
+ { "kind": "skill-contains", "value": "Do not name people in structured data." },
9
+ { "kind": "skill-contains", "value": "Apex `https://cratis.io/` links: the docs host is `https://www.cratis.io`." },
10
+ { "kind": "skill-contains", "value": "no logo-only or metaphor opener" },
11
+ { "kind": "skill-contains", "value": "Padding to a word count: there is no minimum length" }
12
+ ]
13
+ }
@@ -63,6 +63,23 @@ settings. Syntax remains `matches "^INV-[0-9]{6}$"` or `matches email`.
63
63
 
64
64
  `\s` includes U+00A0, U+FEFF and Unicode line/paragraph separators; it excludes
65
65
  U+0085. Dot excludes LF, CR, U+2028 and U+2029. `\w` excludes U+0130.
66
+ ### Write unambiguous patterns (PLAY0803)
67
+
68
+ Requires Screenplay 4.218.2 or later (`screenplay --version`); older compilers do not warn.
69
+
70
+ Generated clients run the pattern in a browser's backtracking engine, so an exponentially
71
+ ambiguous pattern can hang a tab on a crafted value even though Screenplay's own automaton
72
+ stays linear. The warning `PLAY0803` (`AmbiguousMatchPattern`) names the ambiguous
73
+ subexpression and a simpler rewrite where one exists; C#, TypeScript, Monaco and VS Code
74
+ report it identically. Very large patterns are not analyzed, so keep them small anyway.
75
+
76
+ | Ambiguous | Write instead |
77
+ | --- | --- |
78
+ | `(a+)+$`, `(a*)*` | `a+`, `a*` (never quantify a group whose body already repeats) |
79
+ | `(a\|a)*` | `a*` (alternatives must not match the same text) |
80
+
81
+ Prefer a character class such as `[ab]*` over alternatives that overlap. Fix the pattern rather than suppressing the warning; validate with `--warnings-as-errors`.
82
+
66
83
  `matches email` is conservative, not RFC 5322: one `@`, nonempty local part without
67
84
  whitespace/`@`, and at least two nonempty domain parts without whitespace/`@`.
68
85
  A no-break space therefore fails email validation. Other bare names report PLAY0366.
@@ -278,7 +278,7 @@ See read-surface `references/named-keys.md` for the collection-key exclusion alg
278
278
  | Protected command reads (ESM v25) | Root `command reads protected`; eligible read paths in requirements and production mappings, absence Validation, concurrent matching facts Conflict; Stage admits v25 and, in a new enough bundle, renders protected reads as Arc read protection, refusing unpreservable shapes and every older bundle with STAGE-ESM-044; semantic Host/executor execute admitted reads with non-Error protected requirements refused STAGE-ESM-045 |
279
279
  | Chronological command dates (ESM v26) | Same-primitive Date/DateTime `==`, `!=`, `>`, `>=`, `<`, `<=` in require/produces-when (property or literal RHS) and command/property rules (literal RHS only). DateTime uses UTC ticks, participating offset-less inputs reject Validation before rules; then matching stays ordinal. Requires Screenplay 4.199.0 (`screenplay --version`); older binders refuse PLAY0268. The Stage Host and executor run v26 (older Stage refuses STAGE-ESM-016); the renderer and legacy engines refuse STAGE-ESM-046 (toolchain `language-admission.md`). Read command-surface `references/date-comparisons.md`. |
280
280
  | Current routing and actor admission | Sources/streams and command/specification routes bind and reference-execute as v8; reaction `runs as system role` supplies the invocation actor as v10. Whole-workspace binding determines executable availability; CLI/Stage realization remains separate |
281
- | Blocks binding (`PLAY0268`) | enum/identity compliance and events combining personal or explicitly subject-scoped secret values with explicit subject marks; a compiler that stops before v13 refuses all `pii`/`secret` concepts ("compliance attributes require portable data-subject semantics"), while v13 binds them and Stage renders protection; bare `rule <Name>`, read paths without protected opt-in or in unsupported positions such as `produces when`, date comparisons with mixed primitives, optional condition operands, `$context`, protected read paths or `today`, date rules on concepts or reaction `where`, `$context.tenant`/claims/roles/causation in `produces`, `$env` conditions, `file` constraints, unquoted `import` of an external contract, a read model with neither explicit `key` nor unambiguous query-based identity as described above (several key marks bind as v27; older binders refuse PLAY0268; toolchain `language-admission.md`), queries with context `from` sources (typed query context is unadmitted), single-result filters (collections only), `scoped to` (opaque scope), `performer` (opaque implementation) or a `by` block on a list query or non-optional single result; optional keyed and collection queries, including caller-supplied equality filters and observable delivery, bind, `$causedBy` and templates in projections |
281
+ | Blocks binding (`PLAY0268`) | enum/identity compliance and events combining personal or explicitly subject-scoped secret values with explicit subject marks; a compiler that stops before v13 refuses all `pii`/`secret` concepts ("compliance attributes require portable data-subject semantics"), while v13 binds them and Stage renders protection; bare `rule <Name>`, read paths without protected opt-in or in unsupported positions such as `produces when`, date comparisons with mixed primitives, optional condition operands, `$context`, protected read paths or `today`, date rules on concepts or reaction `where`, `$context.tenant`/claims/roles/causation in `produces`, `$env` conditions, `file` constraints, unquoted `import` of an external contract, a read model with neither explicit `key` nor unambiguous query-based identity as described above (several key marks bind as v27; older binders refuse PLAY0268; toolchain `language-admission.md`), queries with context `from` sources (typed query context is unadmitted), single-result filters (collections only), `scoped to` (opaque scope) or a `by` block on a list query or non-optional single result (a `performer` query is not refused: it is an opaque read, PLAY0270, left out of the executable model; [version gate](../../cratis-screenplay-toolchain/references/executable-subset.md#code-attachments-what-binds-what-never-does)); optional keyed and collection queries, including caller-supplied equality filters and observable delivery, bind, `$causedBy` and templates in projections |
282
282
  | Blocks binding (`PLAY0800`, permanent attachment requirement) | Attached file/inline command `handler`: gap-fill the slice with a constrained implementation attachment, not by waiting for ESM admission. PLAY0035 beside productions; pending described intent instead binds v23. Read required typed `arguments.subject` for the command, never parse the message. Compiler prerequisite and older PLAY0268 attribution: [diagnostic arguments](../../cratis-screenplay-toolchain/references/diagnostic-arguments.md) |
283
283
  | Blocks binding (`PLAY0271`, legacy meaning) | Command reads without protected opt-in and `concurrency`; combined protected reads/concurrency is PLAY0783 |
284
284
  | Deferred (`PLAY0269`, information) | Screens, layouts, templates, forms, contributions, UI profiles, themes, behaviors and `on`/`uses` |
@@ -451,7 +451,14 @@ correctness repair; PLAY0667–0670 require an author decision, with no automati
451
451
 
452
452
  Sources/streams and command/specification routes bind as ESM v8 and have
453
453
  catalog identities; `executionAvailable` still requires whole-workspace binding.
454
- Source/stream `propose-rename` repairs bound route references. Operations and
454
+ Source/stream `propose-rename` repairs bound route references. `propose-rename` also
455
+ renames a composite stream-id part: it rewrites the part declaration and every
456
+ `part = ...` mapping in command routes, specification routes, examples and redelivery
457
+ locators, keeps part order and stored stream ids, and is refused
458
+ when the new name collides with another part of the same stream. Use it instead of a
459
+ hand-written `propose-ast` batch.
460
+ Requires Screenplay 4.220.0 (`screenplay --version`) for part renames; older servers
461
+ cannot rename a part through `propose-rename`. Operations and
455
462
  exact numeric mode remain unadmitted. Check installed schemas, especially when
456
463
  the CLI bundle predates the standalone tool.
457
464
 
@@ -626,7 +633,12 @@ Upgrade before treating those rows as a coverage gate.
626
633
  `find-modeling-smells` asks information-level questions, never changes compilation
627
634
  or `--warnaserror`: SMELL001 generic event endings, SMELL002 generic command verbs,
628
635
  SMELL003 duplicate nonempty event shapes from different commands, SMELL004 event
629
- fan-out, SMELL005 read-model property fan-in. Optional scope/document and
636
+ fan-out, SMELL005 read-model property fan-in (counts only the events that actually
637
+ feed the property, not every known event through a projection `all` block), SMELL006
638
+ an event property that no mapping, AutoMap, reaction, condition or specification
639
+ reads (worded as a question: dead data or a missing reader?).
640
+ Requires Screenplay 4.221.0 (`screenplay --version`) for SMELL006 and the corrected
641
+ SMELL005 count; older servers over-report SMELL005 and never report SMELL006. Optional scope/document and
630
642
  `eventFanOutThreshold`/`propertyFanInThreshold` (default 5, integers 1–200) bound
631
643
  selection; equal to threshold is not flagged. Similar shape does not establish
632
644
  similar meaning: report a defect only with a domain consequence. No per-declaration
@@ -53,8 +53,8 @@ model's behalf.
53
53
 
54
54
  3. **Element sweep** (phase 1, mechanical, before any judgement). One line per gated element, event,
55
55
  name, command, event property and slice; `references/checklist.md` S1-S7 gives each line's
56
- format. Scoped `find-modeling-smells` supplies advisory generic-name, duplicate-shape
57
- and wide-flow questions, never compiler defects or a substitute for the sweep. Each `NONE` and each form name is its own finding naming that element. A repeated
56
+ format. Scoped `find-modeling-smells` supplies advisory generic-name, duplicate-shape,
57
+ wide-flow and unread-event-property questions, never compiler defects or a substitute for the sweep. Each `NONE` and each form name is its own finding naming that element. A repeated
58
58
  payload or generic verb is a signal to investigate: report it only when the name or payload
59
59
  hides the business reason or a consequence is shown (distinct facts that share a shape and
60
60
  earlier generations are not findings). A description, comment or spec name never excuses a flagged line.
@@ -52,8 +52,8 @@ not why: a signal, so confirm the hidden reason before reporting. The fix names
52
52
  change (`BerthReassigned`, `BerthLengthLimitRaised`), one event per reason, each with its own
53
53
  rules and consumers. A shared payload shape alone is not a defect: full generations repeat the
54
54
  prior shape on purpose and different facts may look alike. Screenplay 4.127.0 MCP
55
- `find-modeling-smells` reports advisory questions (SMELL001–SMELL005) for generic
56
- names, duplicate shapes and wide flows. It supplements the sweep, never changes
55
+ `find-modeling-smells` reports advisory questions (SMELL001–SMELL005; SMELL006 needs a newer server, see [MCP tools](../../cratis-screenplay-model-authoring/references/mcp-tools.md)) for generic
56
+ names, duplicate shapes, wide flows and event properties nothing reads. It supplements the sweep, never changes
57
57
  a compiler verdict or proves a domain defect.
58
58
 
59
59
  ## Phase 2. Entity walk (5 checks, T1-T5; feeds F1-F3 and scenario coverage)
@@ -180,7 +180,7 @@ Nebulit (`provenance.md`).
180
180
  | Id | Check | Evidence | Tier |
181
181
  |---|---|---|---|
182
182
  | H1 | Query shape follows the business view (list where people scan; lookup where they open one) | queries, screens | D |
183
- | H2 | Executable scope: a keyed `=> RM optional` query with `by` named like a read-model property, or a collection (`=> RM[]`, observable too) with caller-supplied `by` and/or equality filters. `from`, single-result filters, opaque `scoped to`/`performer` and a list-query `by` block remain PLAY0268. A read model with several key parts binds as v27 (Requires Screenplay 4.204.0). The projection routes by the same identity (a `from` key or an event property, or `$eventSourceId`; `$eventSourceId` is not mandated). Filtered models: supported v14 equality filters render within the CLI bundle's ESM ceiling. Renderable scope only: Stage-specific identity/filter limits; keyed optional snapshot lookups and snapshot/observable collections render (Requires Stage 4.90.0 for snapshot lists; older renderers report `STAGE-ESM-010`). Check query concept-validation parity separately; primitive-argument targets answer absence instead of Validation | queries, projections | C (executable); C (render admission, renderable only) |
183
+ | H2 | Executable scope: a keyed `=> RM optional` query with `by` named like a read-model property, or a collection (`=> RM[]`, observable too) with caller-supplied `by` and/or equality filters. `from`, single-result filters, opaque `scoped to` and a list-query `by` block remain PLAY0268; a `performer` query is an opaque read (PLAY0270) left out of the executable model ([version gate](../../cratis-screenplay-toolchain/references/executable-subset.md#code-attachments-what-binds-what-never-does)). A read model with several key parts binds as v27 (Requires Screenplay 4.204.0). The projection routes by the same identity (a `from` key or an event property, or `$eventSourceId`; `$eventSourceId` is not mandated). Filtered models: supported v14 equality filters render within the CLI bundle's ESM ceiling. Renderable scope only: Stage-specific identity/filter limits; keyed optional snapshot lookups and snapshot/observable collections render (Requires Stage 4.90.0 for snapshot lists; older renderers report `STAGE-ESM-010`). Check query concept-validation parity separately; primitive-argument targets answer absence instead of Validation | queries, projections | C (executable); C (render admission, renderable only) |
184
184
  | H3 | One builder per read model; projection `key` only on `from` | projections | C |
185
185
  | H4 | Screens show one actor's point of view; a form submits one command | screens, forms | D |
186
186
 
@@ -32,7 +32,8 @@ the Screenplay MCP views.
32
32
  for obligations with no matching specification. These read authored examples, not executed
33
33
  coverage (`../../cratis-screenplay-model-authoring/references/mcp-tools.md`).
34
34
  5. Gaps and orphans: `find-references` and `dependencies` for declarations referenced by nothing;
35
- `find-modeling-smells` for the candidate shapes.
35
+ `find-modeling-smells` for the candidate shapes; SMELL006 names event properties no
36
+ mapping, reaction, condition or specification reads, a question to answer, not a defect.
36
37
 
37
38
  ## Report
38
39
  Asked conversationally, give the summary and the most important gap and offer the full lists on request.
@@ -9,7 +9,7 @@ reference execution.
9
9
  |---|---|
10
10
  | The MCP tool catalog (`describe-application`, `search-declarations`, `dependency-graph`, `find-assertion-gaps`, `read-workspace`, `declaration-details`) | `Screenplay`: `Source/DotNET/Screenplay.Mcp/McpToolCatalog.cs`, `Documentation/screenplay/mcp/reference.md` |
11
11
  | Diagnostics PLAY0029, PLAY0191, PLAY0268, PLAY0271, PLAY0350, PLAY0381, PLAY0469; one data subject per event | `Screenplay`: `Source/DotNET/Screenplay/Diagnostics/DiagnosticCodes.cs`, the decision record on data subjects |
12
- | `find-specification-obligations` (SPEC001 to SPEC008, authored presence) and `find-modeling-smells` (SMELL001 to SMELL005, advisory questions); neither proves executed behavior or complete lineage | `Screenplay`: `Documentation/screenplay/mcp/reference.md` |
12
+ | `find-specification-obligations` (SPEC001 to SPEC008, authored presence) and `find-modeling-smells` (SMELL001 to SMELL006, advisory questions); neither proves executed behavior or complete lineage | `Screenplay`: `Documentation/screenplay/mcp/reference.md` |
13
13
  | `semantic-diff` source objects and revision-pinned pages | `Screenplay`: `Source/DotNET/Screenplay.Mcp/` (`McpRevisionDiff`, `McpWorkspaces.Comparison`), `Documentation/screenplay/mcp/reference.md` |
14
14
  | Model notes: anchors, kinds, the committed sidecar, the open-question gate | `Screenplay`: `Documentation/screenplay/model-notes.md`; notes tools in model-authoring `references/model-notes.md` |
15
15
  | Property links and their roles for lineage and journey tracing | `Screenplay`: `Documentation/screenplay/mcp/reference.md`; model-authoring `references/property-links.md` |
@@ -85,7 +85,7 @@ is not the same thing. Rules and diagnostics:
85
85
  **Name the compiler and renderer.** Optional keyed queries and collection queries
86
86
  bind, including `observable`. Caller-supplied equality
87
87
  filters on collections bind as ESM v14; `from`, single-result filters, `scoped to`,
88
- `performer` and a `by` block on a list query (or on a non-optional single result) remain PLAY0268, each with its reason. Read
88
+ and a `by` block on a list query (or on a non-optional single result) remain PLAY0268, each with its reason. A `performer` query is not refused: it is an opaque read (PLAY0270 information) left out of the executable model ([version gate](../cratis-screenplay-toolchain/references/executable-subset.md#code-attachments-what-binds-what-never-does)). Read
89
89
  [equality filters](references/equality-filters.md) for the admitted shape, a complete
90
90
  compiled specification model and the Stage/CLI ceilings. Keep a list the domain needs.
91
91
 
@@ -166,7 +166,7 @@ precondition. It takes a `file` reference or an inline block in a tagged fence
166
166
  (` ```csharp `, ` ```sql `), and is the query's counterpart to a command's
167
167
  `handler`. The parser accepts any registered inline language and does not reject
168
168
  a nonsensical one, so the choice is yours to get right. Performer code is opaque:
169
- Screenplay never runs it.
169
+ Screenplay never runs it. It is reported as information `PLAY0270` and left out of the executable model; a specification that reads it is left out while every other specification still runs. Older compilers refuse it with PLAY0268: see the [version gate](../cratis-screenplay-toolchain/references/executable-subset.md#code-attachments-what-binds-what-never-does).
170
170
 
171
171
  A read model a performer composes needs no projection, and no event builds it.
172
172
  Specify it from state with `given readmodel`, then `when query <Query>` with the
@@ -183,7 +183,7 @@ Caller-supplied equality filters on collections select ESM v14 (Requires Screenp
183
183
  primitive/concept/enum properties, same nominal type ignoring optionality, no
184
184
  `from`, distinct names not repeating `by`. `by` and supplied filters AND together;
185
185
  optional filters may be omitted, but supplied null is refused. PLAY0268 still
186
- rejects context sources, single-result filters, opaque scope/performer and
186
+ rejects context sources, single-result filters, opaque scope and
187
187
  a `by` block on a list query (or on a non-optional single result).
188
188
  Authorization runs before lookup, ANDed with module/feature gates; policies see
189
189
  only `by`, never filters. Pin denial with `given caller` and `then denied`.
@@ -39,7 +39,7 @@ Each binding refusal reports PLAY0268 with its query/filter and reason:
39
39
  | `filter ... from ...` or context-derived `by` | Typed query context sources are not admitted; a caller argument must not stand in for trusted context |
40
40
  | Filter on a single-result query | Equality filters are admitted only for collections |
41
41
  | `scoped to` | The named scope has no portable executable scope contract |
42
- | `performer` | Query implementation code is opaque, not a portable declarative lookup |
42
+ | `performer` | Query implementation code is opaque. Requires Screenplay 4.220.1 (`screenplay --version`) for it to be an opaque read (PLAY0270 information) left out of the executable model; older compilers refuse it with PLAY0268 and leave the model unbound |
43
43
  | `by` block on a collection query | A composite lookup is admitted only for an optional single-instance query; narrow a list by composite-keyed read-model parts with equality filters instead |
44
44
  | Filter on a collection/composite property, missing property or mismatched nominal type | The v14 filter contract requires a same-named scalar primitive, concept or enum property with the same type |
45
45
 
@@ -25,7 +25,7 @@ selection is:
25
25
  (case-sensitive). It first excludes collection-query `by` properties: if exactly
26
26
  one candidate remains, that is the identifier; otherwise it uses all `*Id`
27
27
  candidates and requires exactly one. An admitted list query here is a collection
28
- query without context-derived `by`, filters, scope or performer.
28
+ query without context-derived `by`, filters or scope.
29
29
 
30
30
  A projection-only/queryless read model needs an explicit `key`: a lone `*Id`
31
31
  property alone does not infer identity. Never use the command's `identifier`
@@ -35,7 +35,7 @@ ESM). Only the probe answers renderability. `PLAY0270` entries are information,
35
35
  | Class | Meaning | Typical examples (each verified in `admission.md`) | Action |
36
36
  | --- | --- | --- | --- |
37
37
  | **Model fix (in mode)** | the model is in, or the user chose, renderable mode and breaks its own subset in a way that has a renderable equivalent with the same meaning | a `produces` whose `for` is not the command identifier; two projections for one read model (`-007`); a policy Stage cannot express portably (`-015`; role-only and opaque pure policies render) | edit request to the modeler session with address, change and rationale |
38
- | **Capability gap** | the intended meaning needs a construct Stage does not render yet | DateTime equality filters and required parameters after optional filters (`-010`); context/single-result filters, opaque `scoped to`/`performer` and a list-query `by` block remain PLAY0268; a read model with several key parts or a projection keyed by parts (v27) refuses `STAGE-ESM-048` and a single-instance query by parts `STAGE-ESM-047` (Chronicle#4265); Translate, clocks, captures, non-event triggers and opaque reaction bodies (`-024`); claim-reading/opaque `runs as` gates (`-034`), legacy `from` (`-035`), mixed effects in one handler (`-037`, retries can duplicate events; keep it whole), unsupported reaction mappings (`-039`); unsupported generated types/result combinations (`-028`/`-038`); `then no readmodel` (`-027`); evolved events (`STAGE-ESM-026`); unsupported expression/enum production conditions (`-006`; supported conditional productions render); legacy reads (`PLAY0271`), protected reads the renderer cannot preserve, or any protected read in an older CLI bundle (STAGE-ESM-044 despite v25 admission; semantic Host/executor support is separate); code validation (`-005`); protected identities/enums (`-040`) or unadmitted explicit subject lineage (`PLAY0268`); described handlers (v23, STAGE-IMPL-002 even Stub mode), atomic mode (STAGE-ESM-043; v22 admission Requires Stage 4.139.0, older Stage reports -016) | keep the model; fallback record entry; options a to c in `SKILL.md`; link the Stage issue |
38
+ | **Capability gap** | the intended meaning needs a construct Stage does not render yet | DateTime equality filters and required parameters after optional filters (`-010`); context/single-result filters, opaque `scoped to` and a list-query `by` block remain PLAY0268; a query `performer` (opaque read, PLAY0270) is left out of the model so render emits nothing for it: write the code the model needs by hand as a gap-fill item; a read model with several key parts or a projection keyed by parts (v27) refuses `STAGE-ESM-048` and a single-instance query by parts `STAGE-ESM-047` (Chronicle#4265); Translate, clocks, captures, non-event triggers and opaque reaction bodies (`-024`); claim-reading/opaque `runs as` gates (`-034`), legacy `from` (`-035`), mixed effects in one handler (`-037`, retries can duplicate events; keep it whole), unsupported reaction mappings (`-039`); unsupported generated types/result combinations (`-028`/`-038`); `then no readmodel` (`-027`); evolved events (`STAGE-ESM-026`); unsupported expression/enum production conditions (`-006`; supported conditional productions render); legacy reads (`PLAY0271`), protected reads the renderer cannot preserve, or any protected read in an older CLI bundle (STAGE-ESM-044 despite v25 admission; semantic Host/executor support is separate); code validation (`-005`); protected identities/enums (`-040`) or unadmitted explicit subject lineage (`PLAY0268`); described handlers (v23, STAGE-IMPL-002 even Stub mode), atomic mode (STAGE-ESM-043; v22 admission Requires Stage 4.139.0, older Stage reports -016) | keep the model; fallback record entry; options a to c in `SKILL.md`; link the Stage issue |
39
39
  | **Implementation attachment** | a permanent limitation, not future ESM admission | file/inline command handlers (PLAY0800; typed `subject` names the command) | keep the model contract and gap-fill the slice in code with a constrained implementation attachment; do not wait for a new ESM version |
40
40
  | **Tool skew** | the bundled compiler disagrees with documented semantics | the false `PLAY0285` on reaction cascades from an older bundle; codes the standalone compiler does not raise | record in the session state and the fallback record; never "fix" a correct model |
41
41
  | **Environment** | not the model | restore failures, missing SDK, a destination that is not writable | fix the environment or report blocked |
@@ -131,7 +131,11 @@ proved and keep other guarantees as target requirements; recovery cases are in
131
131
  - One behaviour per spec; names read as sentences: `Reserving...`, `Rejecting...`, `Refusing...`
132
132
  (denial), `<View>LifecycleN<State>` for families.
133
133
  - `for` on every given, appended and expected event (plain `produces <Name>` too; inline `produces event` omits it), and on the `when` command where the
134
- destination matters. A competing-claim given needs a *different* `for`.
134
+ destination matters. A competing-claim given needs a *different* `for`. A given without `for`
135
+ belongs to the event source the specification acts on; state `for` when the command appends to
136
+ several event sources or an assertion depends on the given's source, and never assert
137
+ `$eventSourceId` of a sourceless given. Read
138
+ [examples and routes](../cratis-screenplay-specifications/references/examples-and-routes.md#givens-without-an-event-source).
135
139
  Foreign event fixtures feeding a capture may omit `for`; supplied `for` is
136
140
  opaque source text, not the capture's content key.
137
141
  - Format rules belong on a `concept`: give each concept rule representative acceptance,
@@ -147,7 +151,9 @@ proved and keep other guarantees as target requirements; recovery cases are in
147
151
  - Descriptions are not rendered: a rule only in prose is unenforced; make it a rule layer, a
148
152
  spec or a recorded target requirement.
149
153
  - Prove projections with events (`given`/`when append`), not `given readmodel`; seed read models
150
- only for performer-backed views or commands that decide on view state.
154
+ only for performer-backed views or commands that decide on view state. A query `performer` is an opaque read
155
+ (information `PLAY0270`, not `PLAY0268`): it is left out of the executable model, a spec that reads it is
156
+ left out too and the other specs still run, so mark it `gap` for the executable modes ([version gate](../cratis-screenplay-toolchain/references/executable-subset.md#code-attachments-what-binds-what-never-does)).
151
157
  - No contradiction between a family and single-transition specs of the same view.
152
158
 
153
159
  ### Quality checklist (adapted from the TrogonStack scenario checklist; `references/provenance.md`)
@@ -219,6 +219,6 @@ empty-list intent is a separate claim from a keyed row being absent (`view-and-s
219
219
  queries are executable too (`ListOpenInvoices` in the backing example); caller-supplied equality filters on collections bind as ESM v14.
220
220
  Pass `by` and filters in query arguments; PLAY0468 reports unknown, repeated or
221
221
  missing required arguments. Optional filters may be omitted, supplied null is refused.
222
- Context-sourced/single-result filters, opaque `scoped to`/`performer`, and a list-query
223
- `by` block remain PLAY0268. Compiled coverage and renderer ceilings:
222
+ Context-sourced/single-result filters, opaque `scoped to`, and a list-query
223
+ `by` block remain PLAY0268; a `performer` query is an opaque read left out of the model (PLAY0270; [version gate](../../cratis-screenplay-toolchain/references/executable-subset.md#code-attachments-what-binds-what-never-does)). Compiled coverage and renderer ceilings:
224
224
  read-surface [equality filters](../../cratis-screenplay-read-surface/references/equality-filters.md).
@@ -24,7 +24,7 @@ Typed example/override forms: `cratis-screenplay-specifications`
24
24
  | `then readmodel` / `then query` (keyed `ById`, `arguments`, `result`) | yes | yes | yes |
25
25
  | `then query` on an unfiltered list or a list narrowed by one `by` key (observable too) | yes | yes: no `arguments` for an unkeyed list, one `result` per row stating the read-model identifier, no `result` = empty list | snapshot and observable lists render |
26
26
  | Collection query with caller-supplied equality `filter`, including observable | yes | yes, ESM v14: same-named scalar property/type, AND with `by`; optional omission allowed, supplied null refused; PLAY0468 checks arguments; row order does not matter | yes on supported snapshot/observable collections; DateTime/filter parameter order refuse -010; independent Stage executor still refuses filtered assertions (-010) |
27
- | Context `from`, single-result filters, `scoped to`, `performer` or a list-query `by` block | yes | PLAY0268: query context, collection-only filtering, opaque scope/implementation or a multipart list lookup are unadmitted | no |
27
+ | Context `from`, single-result filters, `scoped to` or a list-query `by` block | yes | PLAY0268: query context, collection-only filtering, opaque scope or a multipart list lookup are unadmitted | no |
28
28
  | `then no readmodel <RM> for "<key>"` | yes | yes | no |
29
29
  | `when query` -> `then result` / `then no result` | yes | yes | same as `then query`: only the query-only cases below |
30
30
  | `given clock` / `when clock`, `when trigger`, record-source `given`/`when capture` | yes | yes (standalone and any cratis whose compiler admits v6; an older bundle does not, see toolchain `references/executable-subset.md`) | no |
@@ -45,7 +45,8 @@ Typed example/override forms: `cratis-screenplay-specifications`
45
45
  | Bodied rules, code policies | spec them | spec reported **unsupported**, never passed | no |
46
46
  | Pending described command handlers (v23) | specify intent | binds; denial/validation execute, body-reaching specs Unsupported ImplementationBody; all-Unsupported is not green | STAGE-IMPL-002 even Stub mode |
47
47
  | Attached command handlers | spec them in design | blocked: PLAY0800, no spec runs (V3 blocked); permanent constrained-attachment requirement, gap-fill in code | no |
48
- | `file` constraints, query `performer`s | spec them in design | blocked: PLAY0268 at binding, no spec runs (V3 blocked) | no |
48
+ | `file` constraints | spec them in design | blocked: PLAY0268 at binding, no spec runs (V3 blocked) | no |
49
+ | Query `performer`s | spec them in design | opaque read: PLAY0270 information, left out of the executable model; a spec that reads it is left out, every other spec still runs ([version gate](../../cratis-screenplay-toolchain/references/executable-subset.md#code-attachments-what-binds-what-never-does); older compilers report PLAY0268) | no |
49
50
  | Atomic reaction mode (v22) | declare for new models | failed batch adds no facts or view changes; earlier batches/commands remain; `then error` alone is insufficient | v22 STAGE-ESM-016; mode in admitted v23–v27 STAGE-ESM-043 |
50
51
  | Protected command reads (v25) | declare opt-in for eligible reads | absence Validation; matching intervening facts Conflict; race vectors are not a new spec construct | Cratis C# rendering in a new enough bundle (STAGE-ESM-044 before, or for unpreservable shapes); semantic Host/executor execute admitted reads, non-Error protected requirements STAGE-ESM-045; v25 pair admitted |
51
52
 
@@ -58,7 +59,7 @@ Requires Stage 4.90.0 for snapshot/live lists, 4.101.0 for no-event specificatio
58
59
 
59
60
  ## Design mode
60
61
  Write the full intended behaviour, including scenarios the executable subset cannot run
61
- (filtered, scoped and performer-backed views; clock, trigger and capture scenarios when only a
62
+ (filtered, scoped and performer-backed views, the last left out of the executable model rather than refused; clock, trigger and capture scenarios when only a
62
63
  older cratis bundle is available). Mark each such spec in the coverage matrix as
63
64
  `gap: <reason>` for the narrower modes so the verification and render phases know what will not execute.
64
65
 
@@ -61,7 +61,7 @@ own full givens. The excerpt is copied from `invoicing-dues-example.md`; the las
61
61
  both that the stored row is gone and that the keyed query returns nothing. The whole-list query
62
62
  (no key) is executable too (Requires Screenplay 4.105.0 or later): `invoicing-dues-example.md` adds
63
63
  `ListOpenInvoices => OpenInvoice[]` with one spec that expects one `result` row and one that
64
- expects the empty list after `PaymentReceived`. Only `filter`, `scoped to` and `performer` queries stay design-mode.
64
+ expects the empty list after `PaymentReceived`. Only `filter`, `scoped to` and `performer` queries stay design-mode (a performer query is left out of the executable model, not refused).
65
65
  The source document also asserts the other terminal outcomes (`InvoiceVoided`, `InvoiceSettled`) the same way; they are not copied here.
66
66
  ```screenplay excerpt parent=references/invoicing-dues-example.md#1
67
67
  specification OpenInvoicesLifecycle1Empty
@@ -120,7 +120,7 @@ Ask the batch below only for what the request leaves open: (1) origin of each ac
120
120
 
121
121
  - At most one `identifier` per command; never on an event property or read-model property (use `key`). At most one builder per read model (`PLAY0191`). Read `references/read-model-design.md` ("Identifier and key contract") for the exact rules.
122
122
 
123
- - A query's `by` goes on its own body line; `from`, single-result filters, scope, performer and a list-query `by` block remain `PLAY0268`; equality filters on collections bind as ESM v14. Read `references/read-model-design.md` ("Query binding") for the binding rules.
123
+ - A query's `by` goes on its own body line; `from`, single-result filters, scope and a list-query `by` block remain `PLAY0268` (a `performer` query is an opaque read left out of the executable model, [version gate](../cratis-screenplay-toolchain/references/executable-subset.md#code-attachments-what-binds-what-never-does)); equality filters on collections bind as ESM v14. Read `references/read-model-design.md` ("Query binding") for the binding rules.
124
124
 
125
125
  - A `unique` constraint names a property the event declares directly; the constraint name is
126
126
  its identity (renaming starts an empty index).
@@ -330,8 +330,9 @@ module Berths
330
330
  - A query's `by` goes on its own body line. Optional keyed and collection queries
331
331
  bind, including observable delivery. Caller-supplied equality filters on collections
332
332
  bind as ESM v14; `by` ANDs with supplied filters. `from`, single-result
333
- filters, scope, performer and a `by` block on a list query remain PLAY0268 for their
334
- respective context/cardinality/opaque/multipart gaps. A composite-keyed optional
333
+ filters, scope and a `by` block on a list query remain PLAY0268 for their
334
+ respective context/cardinality/opaque/multipart gaps. A `performer` query is an opaque read left
335
+ out of the executable model, not PLAY0268 ([version gate](../../cratis-screenplay-toolchain/references/executable-subset.md#code-attachments-what-binds-what-never-does)). A composite-keyed optional
335
336
  single-instance query takes a `by` block naming every part (ESM v27). Read read-surface
336
337
  [equality filters](../../cratis-screenplay-read-surface/references/equality-filters.md).
337
338
 
@@ -238,7 +238,10 @@ For command specifications:
238
238
  - [ ] `given caller` is present whenever the command is authorized, with a
239
239
  `then denied` case for a caller who must be refused.
240
240
  - [ ] `then` contains **either** events **or** an error, never both.
241
- - [ ] A collision between two event sources uses `for` on the `given` event.
241
+ - [ ] A collision between two event sources uses `for` on the `given` event. A `given` without
242
+ `for` belongs to the event source the specification acts on; state `for` when the command
243
+ appends to several event sources or an assertion depends on it (see
244
+ [examples and routes](references/examples-and-routes.md#givens-without-an-event-source)).
242
245
  - [ ] Error cases test business rules; each concept rule has one rejection
243
246
  specification through one command, not one per use.
244
247
  - [ ] A localized rejection is pinned by its quoted `$strings` key.
@@ -248,7 +251,8 @@ For command specifications:
248
251
  For view specifications:
249
252
 
250
253
  - [ ] A view a `performer` composes is specified with `given readmodel`,
251
- `when query` and `then result` or `then no result`.
254
+ `when query` and `then result` or `then no result`; the reference runner leaves such a
255
+ spec out (`PLAY0270` information), so it is design intent, not executed proof.
252
256
  - [ ] `given readmodel` is a complete instance with its identifier.
253
257
  - [ ] `then readmodel` states the identifier and the properties that matter; add
254
258
  `exactly` only when extra properties must fail the assertion.
@@ -11,6 +11,7 @@
11
11
  - Event routes
12
12
  - Redelivery locators
13
13
  - Operation and recovery intent
14
+ - Givens without an event source
14
15
 
15
16
  ## Typed examples
16
17
 
@@ -223,3 +224,21 @@ future external-work behavior. Use `then no events` to assert explicitly that th
223
224
  action and cascade produced no new facts; after append it excludes the input fact.
224
225
  It may accompany view/query/response assertions, not events, event-order assertions,
225
226
  errors or denial (PLAY0545). An omitted event list is not this explicit assertion.
227
+
228
+ ## Givens without an event source
229
+
230
+ A `given` event with no `for` belongs to the event source the specification acts on: the
231
+ event source each fact the command appends goes to, or the `for` of `when append`.
232
+
233
+ - A unique-event constraint counts a sourceless given as already on that event source. A
234
+ unique-value constraint treats its value as the action's own claim.
235
+ - When a projection needs the given's event-source identity, the reference evaluator reports
236
+ `Unsupported` (Projection) and does not invent one. This holds whatever the action,
237
+ because givens are projected before it runs.
238
+ - Runners that append to real event streams, such as Stage, append sourceless givens to the
239
+ action's event source.
240
+ - State `for` when the command appends to more than one event source, or when an assertion
241
+ depends on the given's event source. Never assert `$eventSourceId` of a sourceless given.
242
+
243
+ Requires Screenplay 4.229.0 (`screenplay --version`) for the documented, vector-pinned
244
+ placement; older versions leave a sourceless given's placement unspecified.
@@ -13,4 +13,8 @@ Rules for stream-id literals, composite ids and the reserved `All` stream name.
13
13
  PLAY0273 as a backstop. Streams `all`, `ALL` and `Default`
14
14
  remain valid. Rename via `propose-rename`; a pin retaining `All` cannot fix it.
15
15
  Existing `All` history reads as source-only appends, not the renamed stream.
16
+ Rename a composite part with `propose-rename` too: it rewrites the part and every
17
+ `part = ...` mapping together and refuses a name that collides with another part;
18
+ never hand-write a `propose-ast` batch for it (needs a server that supports part
19
+ renames, see model-authoring `references/mcp-tools.md`).
16
20
  Source evidence and migration limits are in that reference.
@@ -103,7 +103,7 @@ not the source identity.
103
103
  - **Executable** (binds, V3): `references/executable-subset.md`. Optional keyed lookups, unfiltered
104
104
  lists and lists narrowed by one `by` key (observable too) bind; caller-supplied equality
105
105
  filters on collections bind as ESM v14. Context `from`, single-result
106
- filters, opaque `scoped to`/`performer` and a `by` block on a list query remain PLAY0268. Read
106
+ filters, opaque `scoped to` and a `by` block on a list query remain PLAY0268; a `performer` query is an opaque read left out of the executable model (PLAY0270; [version gate](references/executable-subset.md#code-attachments-what-binds-what-never-does)). Read
107
107
  read-surface [equality filters](../cratis-screenplay-read-surface/references/equality-filters.md).
108
108
  Pending handlers beside unconditional productions bind as described commands (v23),
109
109
  but reached bodies are Unsupported; attached file/inline handlers refuse PLAY0800
@@ -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
+ - Ambiguous match patterns (PLAY0803)
11
12
  - Projection event scope (PLAY0801)
12
13
  - Reducer instance identity (PLAY0690)
13
14
  - Caller identity, compliance and routes
@@ -149,6 +150,14 @@ Overrides/filters select released ESM v10; malformed routes also use PLAY0504.
149
150
  Read read-surface `references/named-keys.md` and
150
151
  [production routes and filters](production-routes-and-filters.md).
151
152
 
153
+ ## Ambiguous match patterns (PLAY0803)
154
+
155
+ Requires Screenplay 4.218.2 (`screenplay --version`); older compilers do not report it.
156
+
157
+ | Code | Severity | Meaning / fix |
158
+ | --- | --- | --- |
159
+ | PLAY0803 | W | `AmbiguousMatchPattern`: a `matches` pattern in the portable dialect, such as `(a+)+$`, `(a\|a)*` or `(a*)*`, can backtrack exponentially in a browser engine. Reported by both compilers and editors with the ambiguous subexpression; very large patterns are not analyzed. Rewrite it unambiguously (`a+`, `[ab]*`); see command-surface `text-and-match-rules.md`. |
160
+
152
161
  ## Projection event scope (PLAY0801)
153
162
 
154
163
  Requires Screenplay 4.212.3 (`screenplay --version`) for this ordinary compilation
@@ -91,7 +91,7 @@ Requires Screenplay 4.141.0 or later (`screenplay --version`) to bind concept co
91
91
  equivalent inferred ESM bytes. Flat composite read-model keys bind as v27
92
92
  (see below). Collections and observable delivery bind.
93
93
  Caller-supplied equality filters on collections also bind as ESM v14; context
94
- sources, single-result filters, scope, performer and a list-query `by` block remain blocked.
94
+ sources, single-result filters, scope and a list-query `by` block remain blocked; a `performer` query is an opaque read left out of the executable model.
95
95
  - One projection per read model: `from`, `every`, `all` (one block per level), `join`,
96
96
  `children`, `nested`, `remove with`, `remove via join` (binds; blocks the execution plan at the projection level, see below), `clear with`, counters, `add`,
97
97
  `subtract`, `set`, `clear`, variants, and the identifier mapped from the projection's key (`xId = $eventSourceId` only for an event-source key).
@@ -316,11 +316,13 @@ v22–v28 and [described handlers](../../cratis-screenplay-command-surface/refer
316
316
  for the result contract. Attached handlers remain unbound.
317
317
 
318
318
  Inline fences (`csharp`, `typescript`, `react`, `html`, `sql`) and `file <path>` attach
319
- code to an owner. Three kinds block binding (V3 blocked, no specification runs):
320
- the attached command `handler` (PLAY0800), the `file` constraint (PLAY0268, see below)
321
- and a query `performer` (PLAY0268)
322
- ("uses delivery, filtering, scope, or implementation behavior outside the first ESM v1
323
- vertical"). Everything else binds as an **opaque requirement** that needs a target to
319
+ code to an owner. Two kinds block binding (V3 blocked, no specification runs):
320
+ the attached command `handler` (PLAY0800) and the `file` constraint (PLAY0268, see below).
321
+ A query `performer` does not block: it is an opaque read, reported as information
322
+ `PLAY0270` and left out of the executable model; a specification that reads it is left
323
+ out, and every other specification still runs with `screenplay test`.
324
+ Requires Screenplay 4.220.1 (`screenplay --version`); older versions refuse a performer
325
+ query with PLAY0268 and leave the whole model unbound. Everything else binds as an **opaque requirement** that needs a target to
324
326
  execute: reducer transitions, rule predicates, command and concept code validation, policy
325
327
  predicates and reaction effects. Reaction effects bind only when the trigger has no
326
328
  `reads`; a trigger with `reads` plus a `file` or inline effect fails binding. Reaction
@@ -350,7 +352,7 @@ bodies still leave their owner non-executable.
350
352
  code validation is refused (STAGE-ESM-005). Opaque pure policies render.
351
353
  - The reference runner returns Unsupported for the opaque bodies that do bind (rule and
352
354
  policy predicates, code validation, reducer transitions); a specification that needs one
353
- never passes. Attached handlers, file constraints and query performers (and reaction effects on a trigger with
355
+ never passes. Attached handlers and file constraints (and reaction effects on a trigger with
354
356
  `reads`) are different: they leave the whole model unbound, so no specification runs at
355
357
  all.
356
358
 
@@ -362,7 +364,7 @@ bodies still leave their owner non-executable.
362
364
  | Unquoted `import Other.Contract` | PLAY0268 | external contract import is not bound |
363
365
  | `pii` or `secret` on a concept | PLAY0268 | Older compilers refuse it outright. Current compilers admit v13 except enum/identity compliance and events mixing personal or explicitly subject-scoped secret values with explicit subject marks; see `compliance.md` in command-surface references |
364
366
  | `query` with a `by` block over a list, or `by ... from` context-derived arguments | PLAY0268 | a `by` block binds only on an optional single-instance query (v27). An optional scalar snapshot lookup, an unfiltered list (`RM[]`) and a list narrowed by one caller-supplied `by` key bind, in their `observable` form too |
365
- | Context `from`, single-result filters, `scoped to`, `performer` queries | PLAY0268 | Typed query context, filtering of single results, opaque scope and query implementation remain unadmitted; each refusal names its reason. Caller-supplied collection equality filters bind as v14. `observable` goes in the return position (`=> observable RM[]`); as a body line it is PLAY0048 |
367
+ | Context `from`, single-result filters, `scoped to` | PLAY0268 | Typed query context, filtering of single results and opaque scope remain unadmitted; a `performer` query is not refused (PLAY0270 information, left out of the executable model); each refusal names its reason. Caller-supplied collection equality filters bind as v14. `observable` goes in the return position (`=> observable RM[]`); as a body line it is PLAY0048 |
366
368
  | `contains` or `starts with` in conditions only | PLAY0268 | conditions admit scalar equality (Uuid from v28) and numeric ordering, plus same-primitive command date comparisons from v26; property/concept text rules bind as v24 |
367
369
  | Date comparisons with mixed primitives, `$context` (including occurred), `today`, protected read paths, optional condition operands, concept rules or reaction `where` | PLAY0268 | v26 admits only command conditions and command/property literal validation rules |
368
370
  | Rule on a nested path (`lines.qty all > 0`) | PLAY0268 | put the rule on the nested value's concept |
@@ -53,7 +53,7 @@ This table is a reading of the renderer's admission, not a render result: run th
53
53
  | Area | Renders | Refused (code) |
54
54
  | --- | --- | --- |
55
55
  | Slice kinds | `StateChange`, `StateView`, supported event-triggered `Automation` with Chronicle reactors | Translate, captures, non-event triggers, opaque bodies, clock/trigger/capture specs (`-024`); claim-reading/opaque `runs as` gates (`-034`), mixed effects from one handler (`-037`, redelivery can duplicate events), unsupported conditions/destinations/mappings (`-039`). Keep refused reactions whole |
56
- | Queries | optional keyed snapshot lookups; snapshot or observable collections, keyed or unkeyed, with v14 equality filters | context/single-result filters, opaque `scoped to`/`performer` and a list-query `by` block: PLAY0268; v27 composite-key lookups by parts: STAGE-ESM-047, read models and projections keyed by parts: STAGE-ESM-048. DateTime filters and required parameters after optional filters refuse `-010`; filter-name collisions refuse `-012`; concept arguments retain Arc validation, including nested members/elements; nullable keys or optional concept filters with `not empty` refuse `-010` rather than changing null behavior |
56
+ | Queries | optional keyed snapshot lookups; snapshot or observable collections, keyed or unkeyed, with v14 equality filters | context/single-result filters, opaque `scoped to` and a list-query `by` block: PLAY0268; a `performer` query is an opaque read (PLAY0270) left out of the model ([version gate](executable-subset.md#code-attachments-what-binds-what-never-does)); v27 composite-key lookups by parts: STAGE-ESM-047, read models and projections keyed by parts: STAGE-ESM-048. DateTime filters and required parameters after optional filters refuse `-010`; filter-name collisions refuse `-012`; concept arguments retain Arc validation, including nested members/elements; nullable keys or optional concept filters with `not empty` refuse `-010` rather than changing null behavior |
57
57
  | Authorization | role-only, authenticated, opaque pure policies, claim conditions with Uuid, Date, DateTime or text targets (numeric and Boolean claim matches render as always false), `not`/and/or; roles read through the identity's `RoleClaimType` | role-claim URIs, nested opaque policies, unresolved or non-portable policies, no single identifier, opaque-policy subjects that are not text, Uuid, Date or DateTime (`STAGE-ESM-015`, a restriction separate from declarative claim targets) |
58
58
  | Event sources, streams, routes (ESM v8) | rendered, including routed read-model/query companion replay | reserved stored names, unformattable routed fixtures (`STAGE-ESM-030`) |
59
59
  | Numbers, case tables | whole numbers as `long` from ESM v8; case tables | out-of-`int` literals below v8 (`STAGE-ESM-031`) |
@@ -99,7 +99,7 @@ ids into routing to fit a renderer (PLAY0515).
99
99
  observable collections, including supported equality filters;
100
100
  Requires Stage 4.90.0 for snapshot lists and live `IReadModels.Observe` streams;
101
101
  older renderers refuse snapshot lists with STAGE-ESM-010.
102
- context/single-result filters, opaque `scoped to` and `performer` block binding. Keep a list the domain needs and report
102
+ context/single-result filters, and opaque `scoped to` block binding; a `performer` query is left out of the executable model ([version gate](executable-subset.md#code-attachments-what-binds-what-never-does)). Keep a list the domain needs and report
103
103
  the capability gap; marking it `observable` or keying it is a modeling decision for the user.
104
104
  - Attached file/inline handlers refuse PLAY0800 and require constrained implementation
105
105
  attachments: gap-fill in code, not by waiting for ESM admission.
@@ -3,7 +3,7 @@
3
3
  Compiler syntax: `cheat-sheet.md`. Negative capability claims depend on the tool you ran; name it in every verdict.
4
4
 
5
5
  1. Clean V1 is not executable: binding errors appear through MCP (V2), `render` or `cratis screenplay validate --executable` (bundled compiler, writes nothing). Neither `screenplay <folder>` nor plain `cratis screenplay validate` binds.
6
- 2. The Screenplay samples validate clean but are not all executable; copying their idioms (context-filtered queries, enum/identity compliance, `today`, `reads`, `$causedBy`, templates, performers) can block V3. Caller-supplied collection equality filters bind; read read-surface `references/equality-filters.md`.
6
+ 2. The Screenplay samples validate clean but are not all executable; copying their idioms (context-filtered queries, enum/identity compliance, `today`, `reads`, `$causedBy`, templates) can block V3. Caller-supplied collection equality filters bind; read read-surface `references/equality-filters.md`.
7
7
  3. Know the target mode (design/executable/renderable) before writing; state it.
8
8
  4. Plain `produces` without `for` means allocation in the ESM but "the identifier" in rendered code. Always write `for <identifier>` on a plain `produces <Name>`; inline `produces event <Name>` omits `for` and defaults to the identifier (PLAY0478).
9
9
  5. PLAY0029 is a warning: a misspelled keyword (or `type` inside a slice) drops the construct silently. Use `--warnings-as-errors`.
@@ -157,7 +157,7 @@ Requires Stage 4.90.0 for snapshot/live lists, 4.96.0 for non-text secrets and 4
157
157
  | `STAGE-ESM-005` | Code validation or rule predicate bodies; or a command beyond the first capability (optional event property, event revision other than the first, validation or requirement that cannot be rendered, neither `produces` nor a response) | Use declarative validation; non-initial event generations are not rendered |
158
158
  | `STAGE-ESM-006` | `produces` that cannot render without changing its destination or mappings (expression/enum conditions, literal or computed mappings, `for` not a command identifier, mappings not one-to-one with event properties, duplicate tags) | Map properties one to one; supported conditional productions render as guarded events, not separate commands |
159
159
  | `STAGE-ESM-007`, `-008`, `-009` | Projection count per read model (exactly one projection or reducer each); unresolvable properties or no single read-model transition (**flat** projections only); affected instance not one, not keyed by an event property, or not the event-source identity (flat projections only) | One projection per read model; see "Projections" below |
160
- | `STAGE-ESM-010` | A query is neither an optional snapshot lookup by the read-model identifier nor a supported snapshot/observable collection query; each query is checked on its own. Both collection modes render. Concept-typed keys and filters retain Arc validation, including composite members and collection elements; nullable concept keys or optional concept filters with `not empty` cannot preserve null semantics | Keyed `XById => X optional` queries or snapshot/observable lists. Equality filters render on both collections; DateTime filters, required filters after optional ones and unsupported null-validation shapes refuse 010. Filter-name collisions refuse 012. Context/single-result filters, opaque scope/performer and a list-query `by` block remain PLAY0268; a read model with several key parts (v27) refuses `STAGE-ESM-048` |
160
+ | `STAGE-ESM-010` | A query is neither an optional snapshot lookup by the read-model identifier nor a supported snapshot/observable collection query; each query is checked on its own. Both collection modes render. Concept-typed keys and filters retain Arc validation, including composite members and collection elements; nullable concept keys or optional concept filters with `not empty` cannot preserve null semantics | Keyed `XById => X optional` queries or snapshot/observable lists. Equality filters render on both collections; DateTime filters, required filters after optional ones and unsupported null-validation shapes refuse 010. Filter-name collisions refuse 012. Context/single-result filters, opaque scope and a list-query `by` block remain PLAY0268; a performer query is left out of the model (PLAY0270, Requires Screenplay 4.220.1), so render emits nothing for it and it is a gap-fill item; a read model with several key parts (v27) refuses `STAGE-ESM-048` |
161
161
  | `STAGE-ESM-011` | A specification cannot render (see the rules below) | Reshape it only if the assertion keeps its meaning; otherwise keep the specification, record the gap, and gap-fill with the specification as the oracle. Never weaken or delete it to get a render |
162
162
  | `STAGE-ESM-012` | Generated C# names collide (properties, types, namespaces, constraints) | Rename |
163
163
  | `STAGE-ESM-013` | An event-context value other than `$context.occurred` mapped in `produces` | Map a command property when that preserves the meaning; otherwise gap-fill |
@@ -657,6 +657,7 @@
657
657
  "availableTargets": [
658
658
  "cratis-content-length",
659
659
  "cratis-developer-marketing",
660
+ "cratis-discoverability",
660
661
  "cratis-release-notes",
661
662
  "cratis-social-feed-post",
662
663
  "cratis-technical-examples",
@@ -670,6 +671,7 @@
670
671
  {
671
672
  "id": "cratis/documentation",
672
673
  "availableTargets": [
674
+ "cratis-discoverability",
673
675
  "cratis-documentation-writing",
674
676
  "cratis-engineering-docs-authoring",
675
677
  "cratis-llm-friendly-documentation",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/pi",
3
- "version": "7.92.0",
3
+ "version": "7.94.0",
4
4
  "description": "Configuration-aware Cratis AI integration for Pi",
5
5
  "type": "module",
6
6
  "files": [