@cratis/pi 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/package.json +24 -0
- package/skills/cratis-application-react-specifications/LICENSE +2 -0
- package/skills/cratis-application-react-specifications/SKILL.md +192 -0
- package/skills/cratis-application-slice-specifications/LICENSE +2 -0
- package/skills/cratis-application-slice-specifications/SKILL.md +222 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/LICENSE +2 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/SKILL.md +233 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/authentication.md +140 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/authorization.md +112 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/frontend.md +102 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/local-development.md +85 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/tenancy.md +87 -0
- package/skills/cratis-arc-command/LICENSE +2 -0
- package/skills/cratis-arc-command/SKILL.md +286 -0
- package/skills/cratis-arc-command/references/command-result.md +111 -0
- package/skills/cratis-arc-command/references/handler-shapes.md +135 -0
- package/skills/cratis-arc-command/references/proxy-generation.md +87 -0
- package/skills/cratis-arc-command/references/read-model-injection.md +97 -0
- package/skills/cratis-arc-command-execution/LICENSE +2 -0
- package/skills/cratis-arc-command-execution/SKILL.md +148 -0
- package/skills/cratis-arc-command-validation/LICENSE +2 -0
- package/skills/cratis-arc-command-validation/SKILL.md +230 -0
- package/skills/cratis-arc-ef-core-migration/LICENSE +2 -0
- package/skills/cratis-arc-ef-core-migration/SKILL.md +231 -0
- package/skills/cratis-arc-observable-query-http/LICENSE +2 -0
- package/skills/cratis-arc-observable-query-http/SKILL.md +157 -0
- package/skills/cratis-arc-query-paging/LICENSE +2 -0
- package/skills/cratis-arc-query-paging/SKILL.md +173 -0
- package/skills/cratis-arc-react-page/LICENSE +2 -0
- package/skills/cratis-arc-react-page/SKILL.md +291 -0
- package/skills/cratis-arc-react-page/references/data-page.md +125 -0
- package/skills/cratis-arc-react-page/references/data-tables.md +136 -0
- package/skills/cratis-arc-react-page/references/dialogs.md +238 -0
- package/skills/cratis-arc-react-page/references/mvvm.md +150 -0
- package/skills/cratis-arc-react-page/references/queries-and-commands.md +200 -0
- package/skills/cratis-chronicle-cli-operations/LICENSE +2 -0
- package/skills/cratis-chronicle-cli-operations/SKILL.md +199 -0
- package/skills/cratis-chronicle-client-dotnet/LICENSE +2 -0
- package/skills/cratis-chronicle-client-dotnet/SKILL.md +433 -0
- package/skills/cratis-chronicle-client-elixir/LICENSE +2 -0
- package/skills/cratis-chronicle-client-elixir/SKILL.md +429 -0
- package/skills/cratis-chronicle-client-kotlin/LICENSE +2 -0
- package/skills/cratis-chronicle-client-kotlin/SKILL.md +498 -0
- package/skills/cratis-chronicle-client-typescript/LICENSE +2 -0
- package/skills/cratis-chronicle-client-typescript/SKILL.md +396 -0
- package/skills/cratis-chronicle-compliance/LICENSE +2 -0
- package/skills/cratis-chronicle-compliance/SKILL.md +246 -0
- package/skills/cratis-chronicle-event-constraints/LICENSE +2 -0
- package/skills/cratis-chronicle-event-constraints/SKILL.md +227 -0
- package/skills/cratis-chronicle-event-modeling/LICENSE +2 -0
- package/skills/cratis-chronicle-event-modeling/SKILL.md +157 -0
- package/skills/cratis-chronicle-event-specifications/LICENSE +2 -0
- package/skills/cratis-chronicle-event-specifications/SKILL.md +200 -0
- package/skills/cratis-chronicle-event-type-migration/LICENSE +2 -0
- package/skills/cratis-chronicle-event-type-migration/SKILL.md +235 -0
- package/skills/cratis-chronicle-mcp-inspection/LICENSE +2 -0
- package/skills/cratis-chronicle-mcp-inspection/SKILL.md +79 -0
- package/skills/cratis-chronicle-mcp-inspection/references/blocked-tools.md +12 -0
- package/skills/cratis-chronicle-mcp-inspection/references/observational-tools.md +8 -0
- package/skills/cratis-chronicle-multi-tenancy/LICENSE +2 -0
- package/skills/cratis-chronicle-multi-tenancy/SKILL.md +198 -0
- package/skills/cratis-chronicle-projection/LICENSE +2 -0
- package/skills/cratis-chronicle-projection/SKILL.md +162 -0
- package/skills/cratis-chronicle-projection/references/fluent-builder.md +126 -0
- package/skills/cratis-chronicle-projection/references/model-bound-attributes.md +118 -0
- package/skills/cratis-chronicle-reactor/LICENSE +2 -0
- package/skills/cratis-chronicle-reactor/SKILL.md +344 -0
- package/skills/cratis-chronicle-read-model/LICENSE +2 -0
- package/skills/cratis-chronicle-read-model/SKILL.md +193 -0
- package/skills/cratis-chronicle-read-model/references/queries.md +142 -0
- package/skills/cratis-chronicle-read-model-specifications/LICENSE +2 -0
- package/skills/cratis-chronicle-read-model-specifications/SKILL.md +176 -0
- package/skills/cratis-chronicle-reducer/LICENSE +2 -0
- package/skills/cratis-chronicle-reducer/SKILL.md +173 -0
- package/skills/cratis-chronicle-web-workbench/LICENSE +2 -0
- package/skills/cratis-chronicle-web-workbench/SKILL.md +177 -0
- package/skills/cratis-cli-terminal-workbench/LICENSE +2 -0
- package/skills/cratis-cli-terminal-workbench/SKILL.md +156 -0
- package/skills/cratis-code-review/LICENSE +2 -0
- package/skills/cratis-code-review/SKILL.md +177 -0
- package/skills/cratis-components-accessibility/LICENSE +2 -0
- package/skills/cratis-components-accessibility/SKILL.md +176 -0
- package/skills/cratis-components-schema-editor/LICENSE +2 -0
- package/skills/cratis-components-schema-editor/SKILL.md +238 -0
- package/skills/cratis-components-stepper-command-dialog/LICENSE +2 -0
- package/skills/cratis-components-stepper-command-dialog/SKILL.md +209 -0
- package/skills/cratis-components-styling/LICENSE +2 -0
- package/skills/cratis-components-styling/SKILL.md +221 -0
- package/skills/cratis-components-toolbar/LICENSE +2 -0
- package/skills/cratis-components-toolbar/SKILL.md +259 -0
- package/skills/cratis-documentation-writing/LICENSE +2 -0
- package/skills/cratis-documentation-writing/SKILL.md +121 -0
- package/skills/cratis-event-model-diagram/LICENSE +2 -0
- package/skills/cratis-event-model-diagram/SKILL.md +142 -0
- package/skills/cratis-fundamentals-concept/LICENSE +2 -0
- package/skills/cratis-fundamentals-concept/SKILL.md +198 -0
- package/skills/cratis-fundamentals-type-discovery/LICENSE +2 -0
- package/skills/cratis-fundamentals-type-discovery/SKILL.md +152 -0
- package/skills/cratis-governed-release-methodology/LICENSE +2 -0
- package/skills/cratis-governed-release-methodology/SKILL.md +297 -0
- package/skills/cratis-lens-browser-extension/LICENSE +2 -0
- package/skills/cratis-lens-browser-extension/SKILL.md +239 -0
- package/skills/cratis-performance-review/LICENSE +2 -0
- package/skills/cratis-performance-review/SKILL.md +139 -0
- package/skills/cratis-screenplay-model-authoring/LICENSE +2 -0
- package/skills/cratis-screenplay-model-authoring/SKILL.md +231 -0
- package/skills/cratis-security-review/LICENSE +2 -0
- package/skills/cratis-security-review/SKILL.md +141 -0
- package/skills/cratis-specification-by-example/LICENSE +2 -0
- package/skills/cratis-specification-by-example/SKILL.md +188 -0
- package/skills/cratis-specifications-csharp/LICENSE +2 -0
- package/skills/cratis-specifications-csharp/SKILL.md +230 -0
- package/skills/cratis-specifications-csharp/references/application-scenarios.md +191 -0
- package/skills/cratis-specifications-csharp/references/csharp-patterns.md +142 -0
- package/skills/cratis-specifications-csharp/references/integration-specs.md +126 -0
- package/skills/cratis-specifications-typescript/LICENSE +2 -0
- package/skills/cratis-specifications-typescript/SKILL.md +202 -0
- package/skills/cratis-specifications-typescript/references/typescript-patterns.md +168 -0
- package/skills/cratis-stage-rendering-and-sandbox/LICENSE +2 -0
- package/skills/cratis-stage-rendering-and-sandbox/SKILL.md +230 -0
- package/skills/cratis-studio-mcp-safety-guidance/LICENSE +2 -0
- package/skills/cratis-studio-mcp-safety-guidance/SKILL.md +69 -0
- package/skills/cratis-studio-mcp-safety-guidance/references/blocked-tools.md +12 -0
- package/skills/cratis-studio-mcp-safety-guidance/references/observational-tools.md +8 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cratis-lens-browser-extension
|
|
3
|
+
description: Use the Cratis Lens browser extension against a running Cratis Arc application — building and side-loading it, what the Arc app must already serve for Lens to work, switching the active user and tenant, and executing a command or query from the popup. Use when setting Lens up, when Lens does not detect an Arc app, or when a switched identity or tenant does not take effect. Do not use for production authentication or authorization design, and do not use for Chronicle inspection.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Use Lens against a running Arc application
|
|
8
|
+
|
|
9
|
+
Lens is a Manifest V3 browser extension that impersonates a user and a tenant
|
|
10
|
+
against an Arc application you are developing, and executes that application's
|
|
11
|
+
commands and queries from a popup. It changes nothing in the application: it
|
|
12
|
+
rewrites request headers at the network boundary and reads Arc's own
|
|
13
|
+
introspection endpoints.
|
|
14
|
+
|
|
15
|
+
⚠️ Lens is a **development** tool that can present any identity to any origin
|
|
16
|
+
the developer points it at. Nothing in it is production tooling.
|
|
17
|
+
|
|
18
|
+
## Verified product sources
|
|
19
|
+
|
|
20
|
+
| Source | Version | Purpose |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| `Cratis/Lens` `Source/` | manifest `1.0.0`, repository revision `3d0df87` | The extension itself |
|
|
23
|
+
| `Cratis.Arc.Core` | `22.10.4` | The `/.cratis/*` endpoints, the forwarded-identity contract, and the tenancy header Lens depends on |
|
|
24
|
+
|
|
25
|
+
⚠️ **There is no published store listing.** Nothing in the Lens repository links
|
|
26
|
+
a Chrome Web Store, Edge Add-ons, AMO, or App Store entry — the only store URLs
|
|
27
|
+
present are developer-console links inside its own publishing guide. Building
|
|
28
|
+
from source and loading unpacked is the only install path that exists.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cd Source
|
|
34
|
+
yarn install
|
|
35
|
+
yarn build # writes the unpacked extension to Source/dist/
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Then open `chrome://extensions`, enable **Developer mode**, choose **Load
|
|
39
|
+
unpacked**, and select `Source/dist`. Chrome lists it as **Cratis Lens** — the
|
|
40
|
+
name in `manifest.json`; the repository's own tutorial calls it "Lens - Cratis
|
|
41
|
+
Developer Tools", which is stale.
|
|
42
|
+
|
|
43
|
+
`yarn dev` is `vite build --watch`; the extension still has to be reloaded in
|
|
44
|
+
Chrome after each rebuild. `yarn ci` runs `typecheck`, `test` and `build` — the
|
|
45
|
+
same three the pull-request workflow runs. There is no lint script.
|
|
46
|
+
|
|
47
|
+
The code uses the `chrome.*` namespace throughout with no polyfill. The release
|
|
48
|
+
workflow also packages for Edge, Firefox and Safari, but no cross-browser
|
|
49
|
+
behavior is tested; treat Chromium as the supported target.
|
|
50
|
+
|
|
51
|
+
## What the Arc application must already provide
|
|
52
|
+
|
|
53
|
+
Lens is not self-contained. It reads endpoints Arc maps for itself, and it sends
|
|
54
|
+
headers Arc's shipped identity handler already reads. **No NuGet package,
|
|
55
|
+
middleware, `Program.cs` change, or configuration key exists for "Lens support"
|
|
56
|
+
— nothing in either repository names one.**
|
|
57
|
+
|
|
58
|
+
| Route | Mapped by | Lens uses it for |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| `GET /.cratis/commands` | `MapIntrospectionEndpoints`, `AllowAnonymous` | The Commands tree |
|
|
61
|
+
| `GET /.cratis/queries` | `MapIntrospectionEndpoints`, `AllowAnonymous` | The Queries tree |
|
|
62
|
+
| `GET /.cratis/users` | every `ICanProvideUsers`, `AllowAnonymous` | The importable user roster |
|
|
63
|
+
| `GET /.cratis/tenants` | every `ICanProvideTenants`, `AllowAnonymous` | The importable tenant list |
|
|
64
|
+
| `GET /.cratis/identity-details/schema` | the identity endpoint mapper | The schema-driven editor for a user's `identityDetails` |
|
|
65
|
+
|
|
66
|
+
`MapIntrospectionEndpoints` is called from Arc's own application startup, so the
|
|
67
|
+
two introspection routes exist without being asked for. The user and tenant
|
|
68
|
+
routes return whatever the application implements — with **no implementation
|
|
69
|
+
registered they return an empty list**, and Lens treats a `404` on either as
|
|
70
|
+
"empty", not "broken". Users and tenants can also be created by hand in Lens's
|
|
71
|
+
Settings, so an application that provides neither still works.
|
|
72
|
+
|
|
73
|
+
⚠️ `/.cratis/users` and `/.cratis/tenants` are mapped `AllowAnonymous` and
|
|
74
|
+
**unconditionally**. Register `ICanProvideUsers`/`ICanProvideTenants` only for a
|
|
75
|
+
development environment and return fixtures — see
|
|
76
|
+
`cratis-arc-authentication-authorization-and-identity`.
|
|
77
|
+
|
|
78
|
+
### Identity
|
|
79
|
+
|
|
80
|
+
Lens sends exactly the three forwarded client-principal headers Arc's shipped
|
|
81
|
+
handler reads:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
X-MS-CLIENT-PRINCIPAL-ID
|
|
85
|
+
X-MS-CLIENT-PRINCIPAL-NAME
|
|
86
|
+
X-MS-CLIENT-PRINCIPAL # base64 of the principal JSON
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The principal document it base64-encodes is
|
|
90
|
+
`{ identityProvider, userId, userDetails, userRoles, claims: [{ typ, val }], … }`
|
|
91
|
+
with the user's `identityDetails` and `applicationProperties` spread over it.
|
|
92
|
+
`identityProvider` defaults to `aad` and `userRoles` falls back to
|
|
93
|
+
`["authenticated", "anonymous"]` when the user has no roles.
|
|
94
|
+
|
|
95
|
+
⚠️ That is Lens's **only** identity mode. There is no bearer token, no OIDC, no
|
|
96
|
+
sign-in. The application has to be running on the Microsoft-Identity-Platform
|
|
97
|
+
forwarded-header path, and the reverse proxy in front of it in production must
|
|
98
|
+
be the only thing that can set those headers.
|
|
99
|
+
|
|
100
|
+
### Tenancy
|
|
101
|
+
|
|
102
|
+
Lens sets one header, named by its `tenantHeaderName` setting, whose default is
|
|
103
|
+
`x-cratis-tenant-id`. That matches `TenancyOptions.HttpHeader`'s default, and
|
|
104
|
+
`TenantResolverType.Header` is Arc's default resolver — so a default Arc
|
|
105
|
+
application needs no change.
|
|
106
|
+
|
|
107
|
+
⚠️ **`tenantHeaderName` has no user interface.** It is a stored setting with a
|
|
108
|
+
default and nothing binds it to an input. An application that resolves tenancy
|
|
109
|
+
by query parameter, claim, subdomain, or a renamed header **cannot be driven by
|
|
110
|
+
Lens today** without editing `chrome.storage.local` by hand. Guidance describing
|
|
111
|
+
a "Tenant Header Name" field on an options page is describing something that
|
|
112
|
+
does not exist — there is no `options_page` in the manifest at all.
|
|
113
|
+
|
|
114
|
+
### The frontend
|
|
115
|
+
|
|
116
|
+
Detection is not header-level and is not invisible. Lens injects a function into
|
|
117
|
+
the page's main world and looks for:
|
|
118
|
+
|
|
119
|
+
1. an element with `id="root"`;
|
|
120
|
+
2. a React fiber or container key on it (`__reactFiber…` / `__reactContainer…`);
|
|
121
|
+
3. a context value on the fiber tree carrying either a `reconnectQueries`
|
|
122
|
+
property, or a `configuration` object with one of `baseUrl`, `apiBaseUrl`,
|
|
123
|
+
`apiSurface`, `apiSurfaceBaseUrl`, `baseUri` or `baseAddress`.
|
|
124
|
+
|
|
125
|
+
If that fails it falls back to matching the path
|
|
126
|
+
`/projects/{guid}/application/{guid}`, and otherwise to the host being
|
|
127
|
+
`localhost` or `127.0.0.1`.
|
|
128
|
+
|
|
129
|
+
⚠️ A non-React frontend, a root element with another id, or a React version that
|
|
130
|
+
renames its internal fiber keys leaves Lens undetected — the Commands, Queries
|
|
131
|
+
and Query Diagnostics tabs are then **disabled**, and only Context and Settings
|
|
132
|
+
work. The detected base URL is also what the Commands and Queries tabs call, so
|
|
133
|
+
without detection there is nothing to call.
|
|
134
|
+
|
|
135
|
+
## Switch the active user or tenant
|
|
136
|
+
|
|
137
|
+
Pick both on the **Context** tab. Two things then happen, in this order:
|
|
138
|
+
|
|
139
|
+
1. The service worker rebuilds its `declarativeNetRequest` **dynamic** rules —
|
|
140
|
+
at most two, one for the identity headers and one for the tenant header.
|
|
141
|
+
2. It deletes the Arc identity cookie `.cratis-identity` on the detected base URL
|
|
142
|
+
and page origins, then reloads every open tab on those origins.
|
|
143
|
+
|
|
144
|
+
Step 2 is the one that matters. Headers alone do not change who you are once Arc
|
|
145
|
+
has issued its identity cookie; the application keeps presenting the identity it
|
|
146
|
+
was first given. Dropping the cookie and reloading is what makes the switch take
|
|
147
|
+
effect.
|
|
148
|
+
|
|
149
|
+
The rules are scoped, deliberately. When a page origin is known the condition is
|
|
150
|
+
`{ urlFilter: '*', initiatorDomains: [<page host>] }`; otherwise it is
|
|
151
|
+
`||<base URL host>`. **With neither resolvable, no rules are installed at all** —
|
|
152
|
+
`host_permissions` is `<all_urls>`, so a wildcard filter would attach
|
|
153
|
+
impersonation headers to every XHR on every site the developer browses.
|
|
154
|
+
|
|
155
|
+
⚠️ The rules match `XMLHTTPREQUEST` **only**. Document navigations, WebSocket
|
|
156
|
+
connections and EventSource/SSE streams carry no injected headers. Arc's
|
|
157
|
+
observable-query transports (`/.cratis/queries/ws`, `/.cratis/queries/sse`) are
|
|
158
|
+
therefore **not** covered by header injection.
|
|
159
|
+
|
|
160
|
+
## Execute a command or query
|
|
161
|
+
|
|
162
|
+
The Commands and Queries tabs read the introspection lists and group them into a
|
|
163
|
+
tree by splitting the namespace on `.`.
|
|
164
|
+
|
|
165
|
+
A command is always `POST <base URL><route>` with `Content-Type: application/json`
|
|
166
|
+
and the active context headers, and a body built by the schema-driven editor. A
|
|
167
|
+
query is always `GET`, with `{name}` segments in the route substituted from the
|
|
168
|
+
path-parameter inputs and the same context headers.
|
|
169
|
+
|
|
170
|
+
⚠️ **The schema-driven payload form does not currently receive a schema from
|
|
171
|
+
Arc.** Lens reads an optional property named `schema` off each introspection
|
|
172
|
+
item, and falls back to trying `/.cratis/types/{type}`, `/.cratis/schema/{type}`,
|
|
173
|
+
`/.cratis/schemas/{type}` and `/.cratis/types?type={type}` in turn. Arc names the
|
|
174
|
+
property **`payloadSchema`** on a command and **`argumentsSchema`** on a query,
|
|
175
|
+
and maps none of those four fallback routes. Every branch therefore misses, and
|
|
176
|
+
the panel shows *"No payload schema available. Lens will execute this command
|
|
177
|
+
with an empty payload object."*
|
|
178
|
+
|
|
179
|
+
There is **no raw-JSON editor** behind that empty state, so a command reached
|
|
180
|
+
this way can only be fired with `{}`. Expect that for every command until the two
|
|
181
|
+
sides agree on a property name; do not read it as a broken application. Queries
|
|
182
|
+
are unaffected — their inputs come from the `{name}` segments of the route, not
|
|
183
|
+
from a schema.
|
|
184
|
+
|
|
185
|
+
Both result panels parse defensively rather than against a contract: success is
|
|
186
|
+
`isSuccess` when present and HTTP 2xx otherwise, messages are harvested from
|
|
187
|
+
`exceptionMessages`/`errors`/`message`/`title`/`detail`, and a query payload is
|
|
188
|
+
unwrapped from the first of `data`, `result`, `items`, `results`, `value`,
|
|
189
|
+
`payload` or `content` that is present.
|
|
190
|
+
|
|
191
|
+
⚠️ **The two introspection fetches send no headers and no credentials**, while
|
|
192
|
+
the Settings user/tenant refresh does send both. So an application that puts
|
|
193
|
+
authorization in front of `/.cratis/commands` breaks the Commands and Queries
|
|
194
|
+
tabs while Settings keeps working — which reads as a mysterious partial failure.
|
|
195
|
+
Leave those two routes anonymous.
|
|
196
|
+
|
|
197
|
+
Queries are path-parameter GETs only: no query-string arguments, no paging or
|
|
198
|
+
sorting inputs, and no observable subscription from the popup.
|
|
199
|
+
|
|
200
|
+
## Query diagnostics
|
|
201
|
+
|
|
202
|
+
The Query Diagnostics tab is read-only and polls every 2 seconds. It requires the
|
|
203
|
+
detected Arc context to expose `observableQueryDiagnostics.getSnapshot()`; when
|
|
204
|
+
it does not, the panel reports no data. Nothing else in Lens depends on it.
|
|
205
|
+
|
|
206
|
+
## Safety
|
|
207
|
+
|
|
208
|
+
The manifest asks for `storage`, `scripting`, `cookies`, `declarativeNetRequest`,
|
|
209
|
+
`declarativeNetRequestWithHostAccess` and `<all_urls>`. That combination lets the
|
|
210
|
+
extension present a chosen identity to any origin, read and delete cookies, and
|
|
211
|
+
inject into any page.
|
|
212
|
+
|
|
213
|
+
- Keep it out of a browser profile used for anything but development.
|
|
214
|
+
- The user roster, including claims, is stored unencrypted in
|
|
215
|
+
`chrome.storage.local`.
|
|
216
|
+
- An application reachable outside a developer's machine while trusting the
|
|
217
|
+
forwarded headers is impersonatable by anyone who can set them. Lens does not
|
|
218
|
+
create that exposure — it demonstrates it.
|
|
219
|
+
|
|
220
|
+
## Verify
|
|
221
|
+
|
|
222
|
+
- `yarn ci` is clean, and Chrome loaded `Source/dist` without errors.
|
|
223
|
+
- The application answers `/.cratis/commands` and `/.cratis/queries`
|
|
224
|
+
anonymously.
|
|
225
|
+
- Tenancy resolves from the header and the header is named
|
|
226
|
+
`x-cratis-tenant-id`.
|
|
227
|
+
- Opening the popup on the app's tab enables the Commands tab — that is the
|
|
228
|
+
detection signal.
|
|
229
|
+
- After switching a user, the tab reloaded and the application reports the new
|
|
230
|
+
identity, not the previous one.
|
|
231
|
+
- Development user and tenant providers, if registered at all, return fixtures.
|
|
232
|
+
|
|
233
|
+
## Route near misses
|
|
234
|
+
|
|
235
|
+
- Designing authentication, authorization, identity details or tenancy in the
|
|
236
|
+
application: `cratis-arc-authentication-authorization-and-identity`.
|
|
237
|
+
- Calling a command over HTTP without the extension: `cratis-arc-command-execution`.
|
|
238
|
+
- Observable query transports and their HTTP shapes: `cratis-arc-observable-query-http`.
|
|
239
|
+
- Inspecting a Chronicle event store: the Chronicle CLI or Workbench guidance.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cratis-performance-review
|
|
3
|
+
description: Perform a focused scalability review of changed code in a Cratis application — Chronicle observers and replay, read-model query shape, command and query payloads, .NET enumeration, and React render cost — and report findings by risk. Use when asked to check for performance or scalability problems. Do not use for ordinary implementation and do not override the authoritative paging guidance.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Cratis performance review
|
|
8
|
+
|
|
9
|
+
Most performance findings in a Cratis application are shape problems, not
|
|
10
|
+
hot-loop problems: a projection that has to re-read, a query that materializes
|
|
11
|
+
before it filters, a payload nobody uses. They are cheap to fix while the change
|
|
12
|
+
is open and expensive once data has grown behind them.
|
|
13
|
+
|
|
14
|
+
Two things make this stack different. **A projection must be able to replay the
|
|
15
|
+
entire history** — a cost that is invisible at development volumes and fatal at
|
|
16
|
+
production ones. And **an event is permanent**, so an oversized event is a
|
|
17
|
+
storage and replay cost that never goes away.
|
|
18
|
+
|
|
19
|
+
## Verified product sources
|
|
20
|
+
|
|
21
|
+
| Package | Version | Purpose |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| `Cratis.Chronicle` | `16.45.2` | Observers, projections, reducers, reactors, replay |
|
|
24
|
+
| `Cratis.Arc.Core` | `22.10.4` | Query return shapes and server-side paging |
|
|
25
|
+
| `@cratis/components` | current | `DataTable` paging surface |
|
|
26
|
+
|
|
27
|
+
Reverify against the owning product repository before asserting a framework
|
|
28
|
+
behavior this file does not already state.
|
|
29
|
+
|
|
30
|
+
## Route near misses
|
|
31
|
+
|
|
32
|
+
- General correctness and maintainability: use `cratis-code-review`. Its
|
|
33
|
+
performance section is the pass-by list; this skill is the focused one. When
|
|
34
|
+
both have run, do not restate the same finding twice.
|
|
35
|
+
- Adding paging to a query rather than judging one: use
|
|
36
|
+
`cratis-arc-query-paging`. That skill is authoritative on the mechanism; this
|
|
37
|
+
one does not override it.
|
|
38
|
+
- Security or data exposure: use `cratis-security-review`. An over-fetching
|
|
39
|
+
payload is a finding for both — say which lens produced it.
|
|
40
|
+
|
|
41
|
+
## Step 1 — Chronicle and event sourcing
|
|
42
|
+
|
|
43
|
+
- **A projection joins on events, never on a read model.** A read-model join
|
|
44
|
+
forces a re-read the projection engine cannot optimize, and it makes the
|
|
45
|
+
projection depend on another observer's position.
|
|
46
|
+
- **A reactor does not re-query the event log inside its handler.** The event it
|
|
47
|
+
received already carries what it needs. (Reactor dispatch is by the handler's
|
|
48
|
+
first parameter type — the method name is free, so look for the event-typed
|
|
49
|
+
parameter, not for a method called `On`.)
|
|
50
|
+
- **A new projection can replay all historical events without failing.** Ask it
|
|
51
|
+
explicitly: at production volume, does this projection complete a full replay?
|
|
52
|
+
- **Events are small.** No large blob and no base64 payload embedded in an
|
|
53
|
+
event; put the content elsewhere and carry a reference.
|
|
54
|
+
- No eager load of a whole event sequence without paging or filtering.
|
|
55
|
+
- AutoMap is on by default. Hand-mapping every property is both a maintenance
|
|
56
|
+
and a cost problem — and, since AutoMap runs anyway, it does not save the work
|
|
57
|
+
it appears to.
|
|
58
|
+
|
|
59
|
+
## Step 2 — Read models and their store
|
|
60
|
+
|
|
61
|
+
- Queries filter on indexed fields. An unintentional full-collection scan is the
|
|
62
|
+
most common finding here.
|
|
63
|
+
- **A list that can grow returns `IQueryable<T>`**, so Arc applies server-side
|
|
64
|
+
paging and sorting. Materializing and then slicing in memory reads the whole
|
|
65
|
+
collection every request.
|
|
66
|
+
- No N+1: one query returns what the caller needs.
|
|
67
|
+
- A count is a count. Hydrating the collection to measure its length is the
|
|
68
|
+
same finding as an unpaged list, wearing different clothes.
|
|
69
|
+
- A read model does not embed a large nested collection nothing fully iterates.
|
|
70
|
+
|
|
71
|
+
## Step 3 — Commands and queries
|
|
72
|
+
|
|
73
|
+
- A response payload carries only fields the client uses.
|
|
74
|
+
- Command validators are synchronous and in-memory. Validation is on the hot
|
|
75
|
+
path of every attempt, including the ones that will be rejected — an I/O call
|
|
76
|
+
there is paid on every request.
|
|
77
|
+
- No `await Task.Run(() => syncWork)` wrapping for work that is naturally
|
|
78
|
+
asynchronous.
|
|
79
|
+
|
|
80
|
+
## Step 4 — .NET
|
|
81
|
+
|
|
82
|
+
- No `.ToList()` before `.Where()`. Filter before materializing.
|
|
83
|
+
- An `IEnumerable<T>` is not enumerated more than once — materialize once when
|
|
84
|
+
it must be reused.
|
|
85
|
+
- Large-object logging uses the destructuring form only at `Debug` level, so a
|
|
86
|
+
production log level does not pay to serialize it.
|
|
87
|
+
|
|
88
|
+
## Step 5 — React
|
|
89
|
+
|
|
90
|
+
- A `DataTable` over a growable collection uses lazy loading and a paginator
|
|
91
|
+
rather than rendering every row.
|
|
92
|
+
- No inline object or array literal passed as a prop: it is a new identity every
|
|
93
|
+
render and defeats memoization downstream.
|
|
94
|
+
- `useEffect` dependencies are correct — neither missing nor over-broad. An
|
|
95
|
+
over-broad dependency array is a re-run per render, which reads as a
|
|
96
|
+
correctness bug and behaves as a performance one.
|
|
97
|
+
- Components over large collections hold stable references or are memoized.
|
|
98
|
+
- No `JSON.parse(JSON.stringify(x))` deep cloning.
|
|
99
|
+
|
|
100
|
+
## Step 6 — Report
|
|
101
|
+
|
|
102
|
+
Open with one line:
|
|
103
|
+
|
|
104
|
+
> **Performance review: No issues / Minor findings / Blocking issues found**
|
|
105
|
+
|
|
106
|
+
Group findings by the section that produced them, and classify each:
|
|
107
|
+
|
|
108
|
+
| Risk | Meaning |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| **High** | Measurable degradation at moderate load — fix before merge |
|
|
111
|
+
| **Medium** | Degrades under load or as data grows |
|
|
112
|
+
| **Low** | Minor inefficiency |
|
|
113
|
+
|
|
114
|
+
Close with a per-section summary table.
|
|
115
|
+
|
|
116
|
+
**Say what the finding costs and at what scale.** "This is slow" is not
|
|
117
|
+
actionable; "this scans the whole collection on every page load, so it is fine
|
|
118
|
+
at hundreds of rows and not at tens of thousands" is. And **name what you did
|
|
119
|
+
not measure** — a review of code shape is not a benchmark, and reporting it as
|
|
120
|
+
one overstates the evidence.
|
|
121
|
+
|
|
122
|
+
## What breaks
|
|
123
|
+
|
|
124
|
+
- **A shape finding is reported as a measurement.** Reading code tells you the
|
|
125
|
+
shape; only running it tells you the cost. Say which one you have.
|
|
126
|
+
- **The paging advice contradicts the paging skill.** `cratis-arc-query-paging`
|
|
127
|
+
is authoritative on the mechanism. Report the missing paging; do not invent an
|
|
128
|
+
alternative to it.
|
|
129
|
+
- **A replay cost is judged at development volume.** A projection over a hundred
|
|
130
|
+
seeded events proves nothing about a replay over a production history.
|
|
131
|
+
- **Every finding is High.** The classification is the value; when all findings
|
|
132
|
+
are urgent, none are.
|
|
133
|
+
|
|
134
|
+
## How it is proven
|
|
135
|
+
|
|
136
|
+
The build and specifications are green before the report is written; each
|
|
137
|
+
finding cites the file and line and states the scale at which it bites; and the
|
|
138
|
+
report says plainly that it reviewed code shape rather than measured behavior,
|
|
139
|
+
naming anything that would need a benchmark to settle.
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cratis-screenplay-model-authoring
|
|
3
|
+
description: Author and verify a Cratis Screenplay `.play` event model — the file shape, the constructs the compiler actually accepts, the `screenplay` dotnet tool, and the gap between what parses and what any downstream runtime admits. Use when writing, reviewing, or compiling a `.play` file, or when deciding whether a construct is safe to model. Do not use for rendering a model into an application, and do not use for hand-written Arc or Chronicle C#.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Author a Screenplay `.play` model
|
|
8
|
+
|
|
9
|
+
Screenplay is a declarative, indentation-based language that describes a whole
|
|
10
|
+
event-sourced CQRS system in one document set: concepts, events, commands,
|
|
11
|
+
queries, projections, screens and specifications. This repository owns the
|
|
12
|
+
language and its compiler front end.
|
|
13
|
+
|
|
14
|
+
⚠️ **Screenplay is experimental, and it is a front end.** It parses, validates
|
|
15
|
+
and prints `.play` source, and binds a deliberately narrow subset to a versioned
|
|
16
|
+
executable semantic model. **It does not generate, render or run an
|
|
17
|
+
application.** Nothing in it emits C#, TypeScript or any target artifact — that
|
|
18
|
+
is Stage's job, and Stage admits less than Screenplay parses. Author against that
|
|
19
|
+
reality, not against the ecosystem diagram.
|
|
20
|
+
|
|
21
|
+
## Verified product sources
|
|
22
|
+
|
|
23
|
+
| Package | Version | Purpose |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `Cratis.Screenplay` | `4.12.1` | The compiler: parser, syntax tree, validator, diagnostics, printer, semantic model |
|
|
26
|
+
| `Cratis.Screenplay.Tool` | `4.12.1` | The `screenplay` dotnet tool |
|
|
27
|
+
| `Cratis.Screenplay.CanonicalCorpus` | `4.12.1` | The frozen `RegisterProject` conformance vector downstream tools verify against |
|
|
28
|
+
|
|
29
|
+
Behavior below is read from the repository at revision `7e2d91d`. Reverify before
|
|
30
|
+
claiming another version behaves the same.
|
|
31
|
+
|
|
32
|
+
## Verify a model
|
|
33
|
+
|
|
34
|
+
```shell
|
|
35
|
+
dotnet tool install -g Cratis.Screenplay.Tool
|
|
36
|
+
screenplay # or: screenplay path/to/model
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The tool takes **one positional argument** — a directory or a single `.play`
|
|
40
|
+
file, defaulting to the current directory — plus `--no-color` and
|
|
41
|
+
`--warnaserror`. That is the entire surface: no verbs, no `--help`, no
|
|
42
|
+
`--version`, and no output or emit flag.
|
|
43
|
+
|
|
44
|
+
⚠️ **It only verifies. It never writes anything.** A tool invocation that appears
|
|
45
|
+
to have "generated" something did not.
|
|
46
|
+
|
|
47
|
+
A directory is compiled as **one application**, recursively over `**/*.play`, so
|
|
48
|
+
a name declared in one file resolves from another. A single file is compiled
|
|
49
|
+
alone. Exit code is `1` when there is any error, or any warning under
|
|
50
|
+
`--warnaserror`; otherwise `0`. Diagnostics print compiler-style with the
|
|
51
|
+
offending line and a caret:
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
<file>(<line>,<column>): error PLAY0042: <message>
|
|
55
|
+
42 | <the offending line>
|
|
56
|
+
| ^
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Codes run `PLAY0001` through `PLAY0276`. Embed the same compiler with
|
|
60
|
+
`dotnet add package Cratis.Screenplay`.
|
|
61
|
+
|
|
62
|
+
## The file shape
|
|
63
|
+
|
|
64
|
+
Plain text, UTF-8, `.play`. Nesting is by **indentation** — no braces, no
|
|
65
|
+
terminators. A construct owns everything indented beneath it. `//` starts a
|
|
66
|
+
comment. Reserved words inside a block are escaped with a leading `@`.
|
|
67
|
+
|
|
68
|
+
## What the compiler accepts
|
|
69
|
+
|
|
70
|
+
These are the constructs the parser dispatches on, not a documentation summary.
|
|
71
|
+
|
|
72
|
+
**Top level:** `domain`, `import`, `concept`, `type`, `policy`, `persona`,
|
|
73
|
+
`authentication`, `module`, `seed`, `trigger`, `ui profile`, `theme`, `layout`.
|
|
74
|
+
Anything else is `Unexpected '<word>' at the top level`.
|
|
75
|
+
|
|
76
|
+
**Inside `module`:** `description`, `screen` (a screen template), `dialog` (a
|
|
77
|
+
dialog template), `form`, `contribute`, `feature`.
|
|
78
|
+
|
|
79
|
+
**Inside `feature`:** `description`, `feature` (nested to any depth), `slice`,
|
|
80
|
+
`contribute`.
|
|
81
|
+
|
|
82
|
+
**`slice <Type> <Name>`** takes one of four types — an unknown one is
|
|
83
|
+
`Unknown slice type '<x>' - expected StateChange, StateView, Automation or
|
|
84
|
+
Translate`:
|
|
85
|
+
|
|
86
|
+
| Slice type | What it models | Constructs it carries |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| `StateChange` | something changes the system | `command` → `event` via `produces`, with `validate`, `authorize`, `constraint` |
|
|
89
|
+
| `StateView` | something reads the system | `readmodel`, `projection`, `query`, `screen` |
|
|
90
|
+
| `Automation` | something runs when something happens | `reaction` |
|
|
91
|
+
| `Translate` | outside data becomes events | `capture` |
|
|
92
|
+
|
|
93
|
+
**Inside any slice:** `description`, `event`, `command`, `query`, `projection`,
|
|
94
|
+
`capture`, `reaction`, `screen`, `constraint`, `specification`, `readmodel`,
|
|
95
|
+
`reducer`. An unrecognised word here is a **warning**, not an error, and its
|
|
96
|
+
block is skipped — a typo can silently drop a whole construct, so treat slice
|
|
97
|
+
warnings as failures.
|
|
98
|
+
|
|
99
|
+
A `concept` is based on one of `Uuid`, `String`, `Int`, `Decimal`, `Bool`,
|
|
100
|
+
`Date`, `DateTime`, or `Enum` with its values indented beneath. Concepts carry
|
|
101
|
+
`@pii` and `@sensitive` attributes and their own `validate` block, and every use
|
|
102
|
+
of the concept inherits them.
|
|
103
|
+
|
|
104
|
+
Projections are written in the Projection Declaration Language and captures in
|
|
105
|
+
the Change Data Capture Language; both are sub-grammars of the same file, parsed
|
|
106
|
+
by dedicated parsers rather than passed through as text.
|
|
107
|
+
|
|
108
|
+
## A complete model
|
|
109
|
+
|
|
110
|
+
This is the canonical conformance vector — the smallest complete program the
|
|
111
|
+
whole toolchain is verified against, quoted from
|
|
112
|
+
`Screenplay.CanonicalCorpus`'s `RegisterProject` source:
|
|
113
|
+
|
|
114
|
+
```screenplay
|
|
115
|
+
concept ProjectId : Uuid
|
|
116
|
+
concept ProjectName : String
|
|
117
|
+
module Projects
|
|
118
|
+
feature Registration
|
|
119
|
+
slice StateChange RegisterProject
|
|
120
|
+
command RegisterProject
|
|
121
|
+
projectId ProjectId identifier
|
|
122
|
+
name ProjectName
|
|
123
|
+
validate
|
|
124
|
+
name not empty message "Project name is required"
|
|
125
|
+
produces ProjectRegistered
|
|
126
|
+
for projectId
|
|
127
|
+
projectId = projectId
|
|
128
|
+
name = name
|
|
129
|
+
event ProjectRegistered
|
|
130
|
+
projectId ProjectId
|
|
131
|
+
name ProjectName
|
|
132
|
+
specification RegisteringAProject
|
|
133
|
+
when RegisterProject
|
|
134
|
+
projectId = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
|
|
135
|
+
name = "Screenplay"
|
|
136
|
+
then ProjectRegistered
|
|
137
|
+
projectId = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
|
|
138
|
+
name = "Screenplay"
|
|
139
|
+
specification RejectingAnEmptyProjectName
|
|
140
|
+
when RegisterProject
|
|
141
|
+
projectId = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
|
|
142
|
+
name = ""
|
|
143
|
+
then error "Project name is required"
|
|
144
|
+
slice StateView ProjectLookup
|
|
145
|
+
readmodel ProjectSummary
|
|
146
|
+
projectId ProjectId
|
|
147
|
+
name ProjectName
|
|
148
|
+
query ProjectById => ProjectSummary?
|
|
149
|
+
by projectId ProjectId
|
|
150
|
+
projection ProjectSummaryProjection => ProjectSummary
|
|
151
|
+
from ProjectRegistered key projectId
|
|
152
|
+
name = name
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The same corpus ships as a NuGet package precisely so downstream tools verify
|
|
156
|
+
against these exact bytes. When in doubt about a construct's shape, start from
|
|
157
|
+
this file rather than from prose.
|
|
158
|
+
|
|
159
|
+
The repository's own showcase, `invoicing.play`, is a 981-line file that parses
|
|
160
|
+
with zero diagnostics and is pinned by 120 assertions. It is the
|
|
161
|
+
best available reference for the wider language — policies, personas,
|
|
162
|
+
authentication providers, composite `type` declarations, PDL joins, CDL captures,
|
|
163
|
+
screens and triggers.
|
|
164
|
+
|
|
165
|
+
## The boundary that matters: parsed is not admitted
|
|
166
|
+
|
|
167
|
+
The parser accepts far more than anything can execute or render. Between the
|
|
168
|
+
syntax tree and any runtime sits the **executable semantic model (ESM v1)**, and
|
|
169
|
+
it fails closed.
|
|
170
|
+
|
|
171
|
+
⚠️ `SemanticSliceKind` has exactly two members, `StateChange` and `StateView`.
|
|
172
|
+
An `Automation` or `Translate` slice is rejected outright with
|
|
173
|
+
`Slice '<name>' of type '<type>' is not admitted by ESM v1.` It parses, it
|
|
174
|
+
validates, and it binds to nothing.
|
|
175
|
+
|
|
176
|
+
Around forty further constructs bind to an `UnsupportedSemanticSyntax` error
|
|
177
|
+
rather than a weaker model — imports, policies, personas, triggers, `authorize`,
|
|
178
|
+
imperative `handler` bodies, code-backed `validate` rules, conditional
|
|
179
|
+
`produces … when`, query filters and performers, non-`by` queries, non-scalar
|
|
180
|
+
query results, `@pii`/`@sensitive` concepts, non-direct projection mappings,
|
|
181
|
+
parent keys and `sequence` among them.
|
|
182
|
+
|
|
183
|
+
The repository states the admitted set plainly: *"The minimum evaluator currently
|
|
184
|
+
admits the RegisterProject-style vertical: `not empty` validation, unconditional
|
|
185
|
+
event production, one affected read-model instance, optional snapshot lookup, and
|
|
186
|
+
exact ordered specification results. Unsupported reachable capabilities block
|
|
187
|
+
plan creation rather than producing a partial or stubbed execution."*
|
|
188
|
+
|
|
189
|
+
So: model the wider language when the `.play` file is the artifact you want —
|
|
190
|
+
documentation, review, a shared description of a system. Stay inside the
|
|
191
|
+
RegisterProject vertical when the model has to reach a runtime. Never assume a
|
|
192
|
+
construct works downstream because `screenplay` reported no diagnostics.
|
|
193
|
+
|
|
194
|
+
## Editor support
|
|
195
|
+
|
|
196
|
+
The language service ships two ways: the Monaco package
|
|
197
|
+
`@cratis/screenplay-language`, and the VS Code extension `cratis.screenplay`
|
|
198
|
+
(marketplace id `cratis.screenplay`), which is the same service plus a `.play`
|
|
199
|
+
file icon.
|
|
200
|
+
|
|
201
|
+
⚠️ Its keyword list has drifted from the parser. `theme`, `ui`, `form`,
|
|
202
|
+
`contribute`, `dialog` and `reducer` appear nowhere in the Monaco package,
|
|
203
|
+
although all six are real constructs the compiler accepts. They get no
|
|
204
|
+
highlighting and no completion. Absent highlighting is not evidence that a
|
|
205
|
+
construct is wrong — check `screenplay` instead.
|
|
206
|
+
|
|
207
|
+
The repository's TypeScript workspaces are **not** built, linted or tested by any
|
|
208
|
+
pull-request workflow; they run only inside the release job. Do not read a green
|
|
209
|
+
pull request as evidence the language service still compiles.
|
|
210
|
+
|
|
211
|
+
## Verify
|
|
212
|
+
|
|
213
|
+
- `screenplay <model>` reports zero errors **and** zero warnings — an unknown
|
|
214
|
+
slice construct is only a warning and silently drops its block.
|
|
215
|
+
- Every name referenced across files resolves when the whole folder is compiled,
|
|
216
|
+
not just the file being edited.
|
|
217
|
+
- Concepts carrying personal data declare `@pii` with a reason, so the
|
|
218
|
+
classification travels with every use.
|
|
219
|
+
- If the model is meant to reach a runtime, every slice is `StateChange` or
|
|
220
|
+
`StateView` and stays inside the admitted vertical.
|
|
221
|
+
- Specifications express the intended behavior, including the rejection cases.
|
|
222
|
+
|
|
223
|
+
## Route near misses
|
|
224
|
+
|
|
225
|
+
- Turning a model into an application, or the runtime sandbox:
|
|
226
|
+
`cratis-stage-rendering-and-sandbox`.
|
|
227
|
+
- Designing an event model as a diagram rather than as a `.play` file:
|
|
228
|
+
`cratis-event-model-diagram`.
|
|
229
|
+
- Writing the Chronicle projections, reactors or read models by hand: the
|
|
230
|
+
Chronicle guidance.
|
|
231
|
+
- Writing the Arc commands and queries by hand: `cratis-arc-command`.
|