@sanity/workflow-cli 0.32.0 → 0.33.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,150 @@
1
1
  # @sanity/workflow-cli
2
2
 
3
+ ## 0.33.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 225e0fb: **BREAKING:** Workflows App SDK and Studio integrations now require `@sanity/sdk` 3.1 or later in the 3.x line, and `@sanity/workflow-sdk` requires the matching `@sanity/sdk-react` 3.1 line when its React entry is used. The previous SDK 2 peer contract is no longer supported. The exported `WorkflowClient`, `TelemetryIntakeClient`, `ProjectUserProfileClient`, and `StudioUserClient` request contracts now pass their target in `url`; the previous `uri` request target is no longer used. The engine's effective client config also accepts the broader `{type: string, id: string}` resource descriptors returned by Sanity client 8.
8
+
9
+ Before upgrading Workflows, upgrade `@sanity/sdk` and `@sanity/sdk-react` together to 3.1 or later. Applications that stay on SDK 2 must stay on an earlier Workflows release. If you implement any of the request contracts named above, update it to read the request target from `url` instead of `uri`; otherwise its request-backed reads will fail. Existing `WorkflowClient.config()` implementations need no change when their resource descriptor already has string `type` and `id` fields. No separate CLI upgrade action is required; its definition-sharing and telemetry requests adopt `url` internally.
10
+
11
+ Before installing, override SDK 3's `@sanity/mutate` dependency to `0.18.2` in your application's root package-manager configuration, reinstall, and commit the updated lockfile. SDK 3.1.0 allows Mutate 0.18.1, which can leave document reads pending with client 8. For npm, set `overrides["@sanity/sdk"]["@sanity/mutate"]` to `"0.18.2"` in `package.json`. For pnpm, set `overrides['@sanity/sdk@3>@sanity/mutate']` to `0.18.2` in `pnpm-workspace.yaml`. Keep the override until your SDK release requires Mutate 0.18.2 or later. Complete examples and verification steps are in the Installation section of the published `@sanity/workflow-sdk` README.
12
+
13
+ Malformed project-member responses now report an inaccessible directory and can recover on a later lookup, instead of being cached as a missing user. No additional upgrade action is required for this correction.
14
+
15
+ **Docs impact:** Update the App SDK and Studio integration installation guidance, package compatibility references, and examples to require Sanity App SDK 3.1 and the consumer Mutate override described in the published `@sanity/workflow-sdk` README; update the client API references for `request({url})`, effective resource descriptors, and directory-response failures.
16
+
17
+ - ba031fe: **Experimental, and not ready for production use in this release.** Both commands are available so you can try the generated runtime and give feedback; their flags and their output may still change. What you would hit today:
18
+ - The start watcher runs on every create and update of a subject type, not only on writes that can actually start a workflow, so it wakes for documents that already have an instance or match no definition. The heartbeat is scheduled every minute and evaluates every in-flight instance in the tag when it runs; how often the platform runs it is a plan limit.
19
+ - `sanity-workflows deploy` does not check the generated tree, so drift between your definitions and the tree on disk is caught only by `blueprint generate --check` in CI.
20
+ - Definitions and functions deploy in two steps, definitions first, until the Blueprints service registers the `sanity.workflow` resource.
21
+ - A generated file cannot be taken over: editing one is drift, and the next generation rewrites it.
22
+ - A retry policy is not checked against its function's timeout at generation, so an effect can be configured to retry for longer than its function may run.
23
+
24
+ All of these are tracked, and the next release is the bar for using this in production.
25
+
26
+ `sanity-workflows blueprint generate` generates the Sanity Blueprints runtime your definitions require and writes it next to `sanity.workflow.ts`. It prints the needs analysis first — which effects force a drain function, which `$now` condition forces a scheduled heartbeat, which autonomous definition forces a start watcher, and the explicit "no functions needed" answer for a purely interactive workflow — then writes `workflows.blueprint.ts`, one function module and manifest per derived need, the effect-handler registry, and one handler stub per declared effect name. Handler stubs are yours: they are scaffolded once and never overwritten. Your own `sanity.blueprint.ts` receives the `workflowResources` import and spread once. The command deletes nothing; a handler for an effect you removed, a scaffold blocked by a path differing only in case, and a blueprint it could not wire are reported as warnings for you to resolve. The generated tree is one artifact for the whole config, so the command always covers every deployment.
27
+
28
+ `sanity-workflows blueprint generate --check` is the CI drift gate. It renders the same runtime in memory, compares it with the files on disk, writes nothing, and exits zero when they match. Otherwise it exits 1 and lists every file with its reason — `missing`, `differs`, an orphaned handler, or a blueprint that is not wired — so a CI log says what to fix without anyone reading source. A declared effect with no handler file on disk reports as `missing`, which is the handler-coverage check.
29
+
30
+ **No upgrade action required.** Both commands are new and nothing else changes. To use them, your `sanity.workflow.ts` must export each deployment under its own `name`, and that export must be the deployment object itself — `export const production = {name: 'production', …} satisfies WorkflowDeploymentInput`, then `export default defineWorkflowConfig({deployments: [production]})` — because every generated module imports its deployment from `./sanity.workflow` by that name; a config that only default-exports fails the command with the export to add rather than writing a tree that cannot compile. The command checks the name only, so make sure each export is the deployment the config lists: a typecheck of the generated modules catches a wrong export only where a generated function uses it, and a deployment that needs no functions emits none. Your `sanity.workflow.ts` must also be importable inside a deployed function, because every generated module imports it and each function bundles it: reading an absent variable there yields `undefined`, but a config that throws when a variable is missing, or requires one to build the deployment, fails every deployed function on init. Keep the deployment's resource coordinates literal in that file and read secrets inside your handlers. Deploy your definitions with `sanity-workflows deploy` before `npx sanity blueprints deploy`, because the generated blueprint carries the definitions entry commented out until the Blueprints service accepts the `sanity.workflow` resource type, and a function that starts or advances an instance resolves its definition from the Content Lake. A generated heartbeat is a scheduled function, which the platform only accepts on an organization-scoped stack, and how often it may run is a plan limit. Also install `@sanity/blueprints`, `@sanity/functions`, and `@sanity/workflow-blueprint` in your project: each generated function's manifest copies the specifications your project declares for `@sanity/functions`, `@sanity/workflow-blueprint`, and `@sanity/workflow-engine`, and a package you declare nowhere fails the command instead of being stamped with a guessed range. `typescript` is now a dependency of the CLI, because parsing `sanity.blueprint.ts` to insert the wiring uses the TypeScript compiler.
31
+
32
+ **Docs impact:** Rewrite the cookbook chapter on running workflows on a server so it drives the runtime through these two commands rather than describing hand-written functions, and cover the CI drift check there. Update the CLI command reference with both commands, and the Blueprints concept page where it explains who writes the generated tree.
33
+
34
+ ### Patch Changes
35
+
36
+ - 393ac71: CLI and MCP telemetry now includes `context.surface` (`cli` or `mcp`) and
37
+ execution mode in each event's `context.environment`. Dashboards can include
38
+ shell and engine activity while distinguishing it from SDK events marked
39
+ `sdk`. Existing command trace context is preserved.
40
+
41
+ All three surfaces report `production` for `NODE_ENV=production`, and
42
+ `development` for `development` or `test`. Unset, empty, and unrecognized values
43
+ default to `production` for CLI and MCP execution mode, and `development` for
44
+ SDK build mode. SDK environment classification is unchanged. When comparing
45
+ activity across surfaces, group or filter by surface alongside environment.
46
+ Environment does not identify a production dataset or deployment; API host,
47
+ dataset name, and workflow tag do not set it.
48
+
49
+ **No upgrade action required.** Existing telemetry consent and opt-out
50
+ settings still apply. Historical events are unchanged.
51
+
52
+ **Docs impact:** Update CLI, MCP, and SDK telemetry guidance to explain surface
53
+ context, build versus execution mode, the explicit defaults, and how to
54
+ interpret environment when comparing activity across surfaces.
55
+
56
+ - 8874c50: **BREAKING:** A `subject`, `doc.ref`, or `doc.refs` field marked `required: true`
57
+ now requires its selected documents to remain readable after initialization.
58
+ Previously, the flag checked only that an initial value was supplied. Missing
59
+ required targets now produce a fault and prevent normal actions, triggered
60
+ actions, and transitions from advancing, even without an activity requirement
61
+ reading those targets. Abort and direct edits to editable fields remain
62
+ available. Optional references fault only when an unmet runtime condition needs
63
+ their content; completed workflows remain completed.
64
+
65
+ `StuckCause` adds `document-missing`. Update exhaustive handlers before upgrading,
66
+ or their typechecks fail and renderers have no matching branch. Evaluation
67
+ identifies the affected field and reference in `missingDocuments`, with its title
68
+ and completed availability evidence; `blockingMissingDocuments` selects the
69
+ references that prevent progress. The engine checks draft, published, and release
70
+ representations before distinguishing deletion from inaccessible content or
71
+ content outside the workflow perspective. Studio, CLI, and MCP use that same
72
+ result. Failed availability checks report unreadable content and log the cause;
73
+ optional references still permit unrelated actions. Incomplete reads do not
74
+ claim deletion, and an existing task fault keeps
75
+ its recovery controls. The fault clears when the required content becomes
76
+ readable; this release adds no undo-delete operation and does not automatically
77
+ abort workflows.
78
+
79
+ Upgrade every Workflows runtime sharing affected data, including Studio, CLI,
80
+ MCP servers, and deployed Functions, before relying on continued required-target
81
+ availability. Then set the deployment's reviewed `expectedMinReaderModel` to
82
+ `10` before deploying definitions with required content references. Writers stamp
83
+ model 10; this feature requires reader model 10, while documents without it keep
84
+ the floor required by their other features, normally 4, 8, or 9. Existing
85
+ instances remain readable without a backfill and adopt the rule under upgraded
86
+ engines. Their stored floor rises on the next full write, so an older runtime
87
+ can still advance an unstamped existing instance until the fleet is upgraded.
88
+ See `packages/workflow-engine/DATAMODEL.md` for the complete rollout contract.
89
+
90
+ The rendered CLI `show` command evaluates running instances. If evaluation fails,
91
+ it warns and displays stored state; terminal instances and `show --json` retain
92
+ their stored-state behavior.
93
+
94
+ **Docs impact:** Update field requiredness and initialization guidance, required
95
+ reference repair examples, the model-10 readers-first rollout, the diagnostics
96
+ reference for `StuckCause` and `MissingDocument`, CLI `show` and `diagnose`, MCP
97
+ workflow-state guidance, and the Workflows tool and document-view guides for
98
+ loading, deletion, permissions, and perspectives.
99
+
100
+ - 3b42bdc: The Workflows CLI command-trace now sets the oclif command id as trace context (`groupOrCommand`) when the built-in telemetry shell starts, so telemetry reporting can break invocations down by command the same way it can for other Sanity CLI commands. The id remains in the log payload as `command` for config-provided telemetry loggers.
101
+
102
+ **No upgrade action required.** Existing CLI installs begin emitting the queryable context after upgrading; historical rows are unchanged.
103
+
104
+ **Docs impact:** None — internal telemetry envelope shape for an existing event; no public API or user-facing CLI behavior change.
105
+
106
+ - 7eb9eca: Correct the API references for field initialization and edits, start requirements,
107
+ transitions, reference IDs, effect handling, reactive state, member controls,
108
+ Studio mappings, test helpers, and GROQ condition outcomes. The references state
109
+ caller constraints and defaults that were missing or incorrect. Package setup
110
+ guidance identifies the public npm packages and supported deployment command;
111
+ the MCP validation description distinguishes validation from deployment checks.
112
+ Runtime behavior and API signatures are unchanged.
113
+
114
+ **No upgrade action required.**
115
+
116
+ **Docs impact:** After release and reference sync, reconcile the modeling,
117
+ runtime, reactive UI, Studio, testing, deployment, MCP, and evaluation-insight
118
+ guides and references with the corrected contracts. Fix affected examples and
119
+ replace redundant API inventories with verified symbol links while preserving
120
+ useful teaching and the CLI/MCP reference material not exposed by TypeDoc.
121
+
122
+ - 2cef086: Internal maintenance consolidates engine action/edit handling and Studio document
123
+ and workflow title sorting. Action and field-edit calls retain their inputs and
124
+ results, and document and workflow titles retain their ordering. Action
125
+ availability, telemetry, and persisted document formats are unchanged.
126
+
127
+ **No upgrade action required.**
128
+
129
+ **Docs impact: None** because public APIs, configuration, and workflow behavior
130
+ are unchanged.
131
+
132
+ - Updated dependencies [8874c50]
133
+ - Updated dependencies [b3b2797]
134
+ - Updated dependencies [393ac71]
135
+ - Updated dependencies [8874c50]
136
+ - Updated dependencies [ba031fe]
137
+ - Updated dependencies [0555271]
138
+ - Updated dependencies [b3b2797]
139
+ - Updated dependencies [225e0fb]
140
+ - Updated dependencies [b7510cc]
141
+ - Updated dependencies [7eb9eca]
142
+ - Updated dependencies [2cef086]
143
+ - Updated dependencies [232f811]
144
+ - Updated dependencies [ba031fe]
145
+ - @sanity/workflow-blueprint@0.33.0
146
+ - @sanity/workflow-engine@0.33.0
147
+
3
148
  ## 0.32.0
4
149
 
5
150
  ### Minor Changes
package/README.md CHANGED
@@ -3,23 +3,31 @@
3
3
  Command-line tool for deploying, inspecting, and administering Sanity workflow
4
4
  definitions and instances.
5
5
 
6
- > [!WARNING]
7
- > Early access, restricted. The commands below talk to a real Sanity dataset,
8
- > configured by a `sanity.workflow.ts` file and authenticated with
9
- > `sanity login`.
6
+ The package is in early access and is publicly available on npm. Commands that
7
+ access Sanity use the resources in `sanity.workflow.ts` and authenticate with a
8
+ login session or an API token. `deploy --check` validates definitions offline.
10
9
 
11
10
  ## Run
12
11
 
13
- ```bash
14
- pnpm --filter @sanity/workflow-cli dev <command> [...args]
12
+ Use Node.js 20.12 or later. Install the CLI and its engine peer from the same
13
+ Workflows release in your project:
15
14
 
16
- # Examples:
17
- pnpm --filter @sanity/workflow-cli dev --help
18
- pnpm --filter @sanity/workflow-cli dev deploy --dry-run
19
- pnpm --filter @sanity/workflow-cli dev list --include-completed
20
- pnpm --filter @sanity/workflow-cli dev show wf-instance.abc123
15
+ ```sh
16
+ npm install --save-dev @sanity/workflow-cli @sanity/workflow-engine
17
+ npx @sanity/workflow-cli --help
21
18
  ```
22
19
 
20
+ After authentication and [configuration](#configuration), run commands through
21
+ the project-local package:
22
+
23
+ ```sh
24
+ npx @sanity/workflow-cli deploy --dry-run
25
+ npx @sanity/workflow-cli list --include-completed
26
+ npx @sanity/workflow-cli show INSTANCE_ID
27
+ ```
28
+
29
+ Replace `INSTANCE_ID` with the workflow instance ID returned by `list`.
30
+
23
31
  The package is an [oclif](https://oclif.io) plugin: every command's canonical
24
32
  id nests under the `workflows` topic
25
33
  (`sanity-workflows workflows deploy`), so mounting the package into
@@ -32,9 +40,9 @@ binary's stable surface — its production and development entrypoints
32
40
  resolves the command. Those short forms are **not** registered as oclif aliases,
33
41
  so a host mount does not pollute the host root.
34
42
 
35
- Authenticate once with `sanity login` (the CLI reads that session token); for
36
- CI, set `SANITY_AUTH_TOKEN` instead. Then create a `sanity.workflow.ts` in the
37
- directory you run from see [Configuration](#configuration).
43
+ Authenticate with `npx sanity@latest login`; the CLI reads that session token.
44
+ For CI, set `SANITY_AUTH_TOKEN` instead. Create a `sanity.workflow.ts` in the
45
+ directory you run from, as described in [Configuration](#configuration).
38
46
 
39
47
  The token must span **every resource the workflow references**, not just the
40
48
  workflow resource. Runtime commands build one client from the deployment
@@ -52,28 +60,29 @@ the directory you run from. It exports a config built with
52
60
  environment):
53
61
 
54
62
  ```ts
63
+ import type {WorkflowDeploymentInput} from '@sanity/workflow-engine'
55
64
  import {defineWorkflowConfig} from '@sanity/workflow-engine/define'
56
65
 
57
66
  import {articleReview, urlDraft} from './src/workflows.ts'
58
67
 
59
- export default defineWorkflowConfig({
60
- deployments: [
61
- {
62
- expectedMinReaderModel: 4,
63
- name: 'production', // the deployment's unique identity (lowercase letters, digits, dashes)
64
- tag: 'prod', // the environment partition the engine's docs are scoped to
65
- workflowResource: {type: 'dataset', id: 'acme.workflows'}, // where those docs live
66
- resourceAliases: [
67
- // binds each `@<handle>:` a definition references to a physical resource
68
- // in a DIFFERENT dataset from `workflowResource` (same-resource content
69
- // needs no alias — see below)
70
- {name: 'content', resource: {type: 'dataset', id: 'acme.content'}},
71
- {name: 'assets', resource: {type: 'dataset', id: 'acme.assets'}},
72
- ],
73
- definitions: [articleReview, urlDraft], // the batch this deployment ships
74
- },
68
+ // Export each deployment under its own `name`, then list the exports in the
69
+ // config. `blueprint generate` requires these named exports; nothing else does.
70
+ export const production = {
71
+ expectedMinReaderModel: 4,
72
+ name: 'production', // the deployment's unique identity (lowercase letters, digits, dashes)
73
+ tag: 'prod', // the environment partition the engine's docs are scoped to
74
+ workflowResource: {type: 'dataset', id: 'acme.workflows'}, // where those docs live
75
+ resourceAliases: [
76
+ // binds each `@<handle>:` a definition references to a physical resource
77
+ // in a DIFFERENT dataset from `workflowResource` (same-resource content
78
+ // needs no alias — see below)
79
+ {name: 'content', resource: {type: 'dataset', id: 'acme.content'}},
80
+ {name: 'assets', resource: {type: 'dataset', id: 'acme.assets'}},
75
81
  ],
76
- })
82
+ definitions: [articleReview, urlDraft], // the batch this deployment ships
83
+ } satisfies WorkflowDeploymentInput
84
+
85
+ export default defineWorkflowConfig({deployments: [production]})
77
86
  ```
78
87
 
79
88
  `name` is the deployment's identity: unique across the config and constrained
@@ -82,16 +91,20 @@ repeat, as long as no two deployments share both a `workflowResource` and a
82
91
  `tag` — that pair is the storage partition, and the config rejects the
83
92
  collision naming both entries.
84
93
 
85
- Pick a deployment with `--deployment <name>`; with a single deployment configured you
86
- can omit it. `--tag <tag>` also works on single-deployment commands while the
87
- tag names exactly one deployment on `deploy` it targets every deployment
88
- carrying the tag (a tag is an environment group). With several configured, a
89
- bare interactive run presents a keyboard-driven deployment selector (by name,
90
- tag alongside) on `deploy`, `start`, and `definition diff`/`delete`, and on
91
- the instance-targeted commands (`abort`, `set-stage`, `reset-activity`,
92
- `fire-action`, `diagnose`) when the config spans several resources. In CI or another
93
- non-interactive shell, the command fails asking for `--deployment` or `--tag`
94
- (`deploy` also suggests `--all-tags`) instead of blocking for input.
94
+ For `deploy`, `start`, `definition diff`, and `definition delete`, select a
95
+ deployment with `--deployment <name>`. With one deployment configured, you can
96
+ omit the selector. `--tag <tag>` selects one deployment when the tag is unique;
97
+ on `deploy`, it targets every deployment carrying the tag. With several
98
+ deployments and no selector, these commands prompt for a deployment in an
99
+ interactive terminal. In a non-interactive shell, they fail asking for
100
+ `--deployment` or `--tag`; `deploy` also suggests `--all-tags`.
101
+
102
+ The instance-targeted commands `abort`, `set-stage`, `reset-activity`,
103
+ `fire-action`, and `diagnose` locate the instance by ID across configured
104
+ resources. They do not prompt for a deployment. Their optional `--deployment`
105
+ and `--tag` flags narrow the resources searched; the operation's tag comes from
106
+ the loaded instance.
107
+
95
108
  `--all-tags` deploys every deployment in the config in one run: a failure in one doesn't
96
109
  stop the rest — the run continues, prints a summary of what failed, and exits
97
110
  non-zero. The client's project + dataset are derived from the deployment's
@@ -173,6 +186,8 @@ Invoke as `sanity-workflows <command>` (or `sanity workflows <command>` once the
173
186
  | `set-stage <instance-id> --to <stage>` | Move an instance to a stage, skipping declared transitions. The target stage's enter lifecycle still runs. |
174
187
  | `reset-activity <instance-id> <activity>` | Re-run a failed activity, or `--skip` it so a gated transition can fire. |
175
188
  | `fire-action <instance-id>` | Fire an action on a waiting activity. Omit `--action` to list what can be fired. |
189
+ | `blueprint generate` | Write the Sanity Blueprints runtime the definitions require next to `sanity.workflow.ts`. |
190
+ | `blueprint generate --check` | Verify that runtime still matches the definitions. Writes nothing; exits non-zero on any difference. |
176
191
  | `definition list` | List deployed definitions. |
177
192
  | `definition show <name>` | Show a deployed definition (latest version, or `--version`). |
178
193
  | `definition diff <name>` | Diff the in-code definition against what is deployed. |
@@ -180,6 +195,182 @@ Invoke as `sanity-workflows <command>` (or `sanity workflows <command>` once the
180
195
  | `nuke --deployment <name>` / `nuke --tag <tag>` | Dev reset: delete every engine-owned document for that tag (instances, definitions, guards). Prints a plan, then asks for confirmation. Content documents are never touched. |
181
196
  | `nuke --instance <id>` | Dev reset: delete one finished instance and its guards. Refuses in-flight instances (abort first). |
182
197
 
198
+ ## Generating the Blueprints runtime
199
+
200
+ **Experimental, and not ready for production use in this release.** Both
201
+ commands are available so you can try the generated runtime and give feedback;
202
+ their flags and their output may still change. What you would hit today:
203
+
204
+ - The start watcher runs on every create and update of a subject type, not only
205
+ on writes that can actually start a workflow, so it wakes for documents that
206
+ already have an instance or match no definition. The heartbeat is scheduled
207
+ every minute and evaluates every in-flight instance in the tag when it runs;
208
+ how often the platform runs it is the plan limit described below.
209
+ - `sanity-workflows deploy` does not check the generated tree, so drift between
210
+ your definitions and the tree on disk is caught only by
211
+ `blueprint generate --check` in CI.
212
+ - Definitions and functions deploy in two steps, definitions first, until the
213
+ Blueprints service registers the `sanity.workflow` resource.
214
+ - A generated file cannot be taken over: editing one is drift, and the next
215
+ generation rewrites it.
216
+ - A retry policy is not checked against its function's timeout at generation, so
217
+ an effect can be configured to retry for longer than its function may run.
218
+
219
+ All of these are tracked, and the next release is the bar for using this in
220
+ production.
221
+
222
+ A definition that declares an effect, reads `$now`, or starts autonomously needs
223
+ server-side code: something has to run the effect handlers, sample deadlines, and
224
+ start instances from document writes. `blueprint generate` derives that runtime
225
+ from the definitions and writes it next to `sanity.workflow.ts`, so
226
+ `npx sanity blueprints deploy` has something to deploy. A purely interactive
227
+ workflow needs none of it, and the command says so.
228
+
229
+ Five prerequisites:
230
+
231
+ - **Each deployment is exported under its own `name`, and the export is the
232
+ deployment object itself**, as the [Configuration](#configuration) example
233
+ does: every generated module imports its deployment from `./sanity.workflow`
234
+ by that name. The command refuses to write anything until each name is
235
+ exported, and names the export to add. It checks the name only, so make sure
236
+ each export is the deployment your config lists. A typecheck of the generated
237
+ modules catches a wrong export only where a generated function uses it, and a
238
+ deployment that needs no functions emits none.
239
+ - **`sanity.workflow.ts` is importable inside a deployed function.** Every
240
+ generated module imports it, and each function bundles it, so the file runs
241
+ where your shell and your `.env` do not exist. Reading an absent variable
242
+ there yields `undefined`; what fails is a config that **throws** when a
243
+ variable is missing, or requires one to build the deployment, because that
244
+ runs on init before your handler is reached. Keep the deployment's resource
245
+ coordinates literal in the file and read secrets inside your handlers.
246
+ - **The Blueprints packages the generated modules import are installed**,
247
+ alongside the CLI and its peers.
248
+ - **Your definitions are deployed before the functions.** The generated
249
+ `workflows.blueprint.ts` carries the definitions entry commented out, because
250
+ the Blueprints service does not accept the `sanity.workflow` resource type
251
+ yet, so `npx sanity blueprints deploy` does not deploy them. Run
252
+ `npx @sanity/workflow-cli deploy` first: a function that starts or advances an
253
+ instance resolves its definition from the Content Lake, and finds nothing
254
+ until that deploy has run.
255
+ - **A generated heartbeat needs an organization-scoped stack.** The heartbeat is
256
+ a scheduled function, and the platform only accepts one on a stack in
257
+ organization scope. A project-scoped stack refuses it, and with it the whole
258
+ deploy. See
259
+ [Promote a stack to organization scope](https://www.sanity.io/docs/blueprints/promote-stack-to-organization-scope).
260
+ How often it may run is a plan limit rather than something the generator
261
+ chooses — see [the schedule](#the-generated-heartbeats-schedule) below.
262
+
263
+ ```sh
264
+ npm install --save-dev @sanity/blueprints @sanity/functions @sanity/workflow-blueprint
265
+ npx @sanity/workflow-cli blueprint generate
266
+ npx @sanity/workflow-cli deploy
267
+ npx sanity blueprints deploy
268
+ ```
269
+
270
+ Each generated function's `package.json` copies the specifications your project
271
+ declares for `@sanity/functions`, `@sanity/workflow-blueprint`, and
272
+ `@sanity/workflow-engine`. A package you declare nowhere fails the command
273
+ rather than being stamped with a guessed range.
274
+
275
+ The command prints what the definitions need and why, then every file it wrote:
276
+
277
+ ```
278
+ $ npx @sanity/workflow-cli blueprint generate
279
+ Runtime needs:
280
+
281
+ ▸ prod (prod)
282
+ hosting: this deployment declares function, and each level below inherits it
283
+ → function workflow(s): article-review
284
+ → function effect(s): notify-reviewer
285
+ effects: 1 declared — notify-reviewer
286
+ → drain function wf-prod-drain-effects runs notify-reviewer
287
+ triggered by instance writes matching _type == "sanity.workflow.instance" && tag == "prod" && count(pendingEffects[!defined(claim)]) > 0
288
+ clock: samples $now deadlines; sweeps stale effect claims left by a dead drain
289
+ article-review · working · action-when · $fields.subject.embargoAt <= $now
290
+ → scheduled function wf-prod-heartbeat on * * * * *
291
+ a scheduled function runs at most as often as your organization's plan allows — every minute on Enterprise, hourly on Growth, daily on Free (https://www.sanity.io/docs/functions/functions-introduction) — and a schedule below that threshold is deployed and never invoked
292
+ autonomous starts: 1 definition(s) — article-review
293
+ → start watcher wf-prod-start-workflows on acme.workflows matching _type in ["article"]
294
+
295
+ Generated tree:
296
+ created workflows.blueprint.ts
297
+ created functions/wf-prod-drain-effects/index.ts
298
+ created functions/wf-prod-drain-effects/package.json
299
+ created functions/wf-prod-heartbeat/index.ts
300
+ created functions/wf-prod-heartbeat/package.json
301
+ created functions/wf-prod-start-workflows/index.ts
302
+ created functions/wf-prod-start-workflows/package.json
303
+ created effect-handlers/all.ts
304
+ created effect-handlers/notify-reviewer.ts yours now — implement the effect
305
+ created sanity.blueprint.ts the workflow resources spread
306
+ ```
307
+
308
+ Fill in each `effect-handlers/<effect name>.ts`: they are yours, scaffolded once
309
+ and never overwritten. Everything marked `created`, `updated`, or `unchanged`
310
+ under `workflows.blueprint.ts`, `functions/`, and `effect-handlers/all.ts` is
311
+ regenerated on every run, so edits there do not survive. Your own
312
+ `sanity.blueprint.ts` receives the `workflowResources` import and spread once;
313
+ after that the command leaves it alone. The command never deletes a file. A
314
+ handler for an effect you removed, a scaffold blocked by a path that differs
315
+ only in case, and a blueprint it could not wire are reported for you to resolve.
316
+
317
+ The command covers every deployment in the config, because the emitted
318
+ `workflows.blueprint.ts` declares them all.
319
+
320
+ ### Where each workflow runs
321
+
322
+ The report opens with the hosting each `runtime` block resolved to, after every
323
+ level has inherited from the one above it. It matters because a name listed
324
+ under `durableFunction` or `selfHosted` has no emitted function to point at:
325
+
326
+ - `function` — the drain, heartbeat, and start watcher above.
327
+ - `durableFunction` — nothing is emitted yet. The rest of the deployment emits
328
+ normally around it.
329
+ - `selfHosted` — nothing is emitted, and the report ends with what your own
330
+ process must do instead: which deadlines to tick, which effects to drain,
331
+ which parents to re-evaluate when a child settles, and which autonomous
332
+ definitions to start. A self-hosted deadline is never a reason to emit a
333
+ heartbeat, so it is listed there and not under `clock`; an emitted heartbeat
334
+ ticks every in-flight instance in the tag, whatever hosts each workflow.
335
+
336
+ ### The generated heartbeat's schedule
337
+
338
+ The heartbeat is emitted on `* * * * *`, one run a minute, and how often the
339
+ platform will actually run it is a plan limit rather than something the
340
+ generator can choose: Sanity documents the threshold as
341
+ [Free daily, Growth hourly, Enterprise minutely](https://www.sanity.io/docs/functions/functions-introduction).
342
+ Below Enterprise the platform accepts the scheduled function and does not run
343
+ it, so a workflow whose deadlines depend on the heartbeat does not advance and
344
+ nothing reports an error. Check your plan's cadence before relying on a `$now`
345
+ deadline in a deployed workflow.
346
+
347
+ ### Checking for drift in CI
348
+
349
+ `blueprint generate --check` renders the same runtime in memory and compares it
350
+ with the files on disk. It writes nothing and exits zero when they match:
351
+
352
+ ```
353
+ $ npx @sanity/workflow-cli blueprint generate --check
354
+ ✔ the generated tree matches the definitions
355
+ ```
356
+
357
+ Otherwise it exits `1` and names every file, so the log alone says what to fix:
358
+
359
+ ```
360
+ $ npx @sanity/workflow-cli blueprint generate --check
361
+ ✖ The generated tree does not match the definitions (1):
362
+ differs effect-handlers/all.ts
363
+ Run `sanity-workflows blueprint generate` to bring it back in line.
364
+ $ echo $?
365
+ 1
366
+ ```
367
+
368
+ A file reports `missing` when nothing is at its path, `differs` when the file
369
+ there has other contents, and a handler for a declared effect reports `missing`
370
+ when you have not written it yet. Add the command to the job that builds your
371
+ blueprint and a definition change can no longer ship without the runtime it
372
+ needs.
373
+
183
374
  ## Telemetry
184
375
 
185
376
  The CLI collects usage telemetry through Sanity's standard pipeline: a
@@ -193,6 +384,17 @@ is passed — see [Definition sharing](#definition-sharing)),
193
384
  and a session that isn't logged in sends nothing. A one-time notice on stderr
194
385
  discloses collection on first use.
195
386
 
387
+ Each event includes `context.surface: 'cli'` and its process execution mode
388
+ in `context.environment`. `NODE_ENV=production` reports `production`;
389
+ `development` and `test` report `development`. Unset, empty, and unrecognized
390
+ values default to `production` for installed CLI and MCP runs. The SDK reports
391
+ build mode and defaults to `development` instead.
392
+
393
+ When comparing activity across surfaces, group or filter by `context.surface`
394
+ alongside `context.environment`. Environment does not identify a production
395
+ dataset or deployment; the API host, dataset name, and deployment tag do not
396
+ set it. Existing telemetry consent and opt-out settings still apply.
397
+
196
398
  One extension point: supply your own logger as `telemetry` in
197
399
  `sanity.workflow.ts` and the built-in pipeline is not constructed at all —
198
400
  every event flows to your implementation unconditionally (CI included), and
@@ -0,0 +1,9 @@
1
+ import { WorkflowCommand } from '../../../lib/base-command.ts';
2
+ export default class BlueprintGenerate extends WorkflowCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ check: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ };
8
+ run(): Promise<void>;
9
+ }
@@ -0,0 +1,28 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { WorkflowCommand } from "../../../lib/base-command.js";
3
+ import { checkBlueprint, generateBlueprint } from "../../../lib/blueprint-emit.js";
4
+ import { loadWorkflowConfigModule } from "../../../lib/load-config.js";
5
+ export default class BlueprintGenerate extends WorkflowCommand {
6
+ static description = 'Experimental: generate the Sanity Blueprints runtime the definitions require, next to sanity.workflow.ts. Writes the workflow resources, one function per derived need, the effect-handler registry, and a handler stub per declared effect. Covers every deployment in the config, because the emitted resources module declares them all. These flags and this output may change before the Blueprints backend accepts the sanity.workflow resource.';
7
+ static examples = [
8
+ '<%= config.bin %> workflows blueprint generate',
9
+ '<%= config.bin %> workflows blueprint generate --check',
10
+ ];
11
+ static flags = {
12
+ check: Flags.boolean({
13
+ description: 'Experimental: verify the tree on disk still matches the definitions; write nothing and exit non-zero on any difference. The CI drift gate.',
14
+ default: false,
15
+ }),
16
+ };
17
+ async run() {
18
+ const { flags } = await this.parse(BlueprintGenerate);
19
+ const root = process.cwd();
20
+ const { config, configFile, exportedNames } = await loadWorkflowConfigModule(root);
21
+ const log = (line) => this.log(line);
22
+ if (flags.check) {
23
+ checkBlueprint({ root, config, configFile, exportedNames, log });
24
+ return;
25
+ }
26
+ generateBlueprint({ root, config, configFile, exportedNames, log });
27
+ }
28
+ }
@@ -1,4 +1,4 @@
1
- import { type Diagnosis, type DiagnoseInput, type SuggestedRemediation, type WorkflowEvaluation } from '@sanity/workflow-engine';
1
+ import { type Diagnosis, type DiagnoseInput, type MissingDocument, type SuggestedRemediation, type WorkflowEvaluation } from '@sanity/workflow-engine';
2
2
  import { WorkflowCommand } from '../../lib/base-command.ts';
3
3
  /** The `--json` transitions payload: raw derived state (atom GROQ, negation,
4
4
  * pivotality, solvable requirement) — structure for scripts, never baked
@@ -23,10 +23,12 @@ export declare function rawTransitionProjection(evaluation: WorkflowEvaluation):
23
23
  * {@link SuggestedRemediation}s rather than re-deriving them, so the rendered
24
24
  * fix block and the `--json` output stay one computation.
25
25
  */
26
- export declare function renderDiagnosis({ diagnosis, input, remediations, explanations, }: {
26
+ export declare function renderDiagnosis({ diagnosis, input, remediations, explanations, allMissingDocuments, }: {
27
27
  diagnosis: Diagnosis;
28
28
  input: DiagnoseInput;
29
29
  remediations: SuggestedRemediation[];
30
+ /** Full evaluation evidence, including references outside the blocking subset. */
31
+ allMissingDocuments?: MissingDocument[] | undefined;
30
32
  /** Insight summaries per unsatisfied transition name — rendered under the
31
33
  * transition line so "stuck" reads as "stuck until X". */
32
34
  explanations?: ReadonlyMap<string, string> | undefined;