@sanity/workflow-engine 0.17.0 → 0.18.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 +44 -0
- package/DATAMODEL.md +140 -13
- package/README.md +23 -0
- package/dist/_chunks-cjs/invariants.cjs +620 -116
- package/dist/_chunks-es/invariants.js +567 -117
- package/dist/define.cjs +6 -1
- package/dist/define.d.cts +185 -36
- package/dist/define.d.ts +185 -36
- package/dist/define.js +7 -2
- package/dist/index.cjs +852 -392
- package/dist/index.d.cts +746 -198
- package/dist/index.d.ts +746 -198
- package/dist/index.js +752 -330
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# @sanity/workflow-engine
|
|
2
2
|
|
|
3
|
+
## 0.18.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3af2ca0: Derive each persisted definition and instance reader floor from the model features it actually contains. Export the append-only data-model change manifest and feature diagnostics so compatible model-2 documents remain readable by older engines while typed choice lists still fail closed.
|
|
8
|
+
- 7276702: Insight phrases resolve effect titles and drop the "settled" jargon. `$effectStatus` requirement phrases and autonomy effect waits now render the effect's declared `title` — an untitled effect keeps its registry name as-is — and the requirement verbs are plain outcomes: "must have succeeded" / "must have failed" / "must have finished". The `requirement.effect-*` and `autonomy-wait.effect` phrase params carry both `effect` (the registry name) and the resolved `title`, so consumers restyling by phrase id needn't re-derive either.
|
|
9
|
+
- 58f8211: Export `expandResourceAliases`, `refsOf`, and the `LogicalRef` type from the package root. Deploy-adjacent tooling can now run the alias expansion as an eager validation (fail-closed on unbound `@<handle>:` references before any write) and walk a definition's spawn references without re-implementing the walk.
|
|
10
|
+
- 00c089d: **BREAKING:** Require every definition deployment and workflow deployment configuration to state an `expectedMinReaderModel` literal that exactly matches the installed engine's writer capability.
|
|
11
|
+
|
|
12
|
+
Deploy and definition-diff paths now reject missing or stale acknowledgements before client access or telemetry, with a structured error and readers-first rollout guidance.
|
|
13
|
+
|
|
14
|
+
- cd03973: **BREAKING:** Reject initial workflow field values that cannot be consumed and expose structured preflight issues.
|
|
15
|
+
|
|
16
|
+
Calls that supplied unknown, wrong-kind, non-input, non-workflow-scope, or duplicate initial field rows previously succeeded while discarding those rows. They now fail before creating an instance with structured remediation details.
|
|
17
|
+
|
|
18
|
+
Deploy also rejects `spawn.with` keys that the resolved child definition cannot consume because the workflow field is missing or is not input-sourced. `SpawnContractsInvalidError` exposes every rejected parent/child row for machine-readable remediation.
|
|
19
|
+
|
|
20
|
+
- d26cc1a: Add the engine-owned Actor resolution contract with client-backed resolution for server, CLI, and MCP runtimes; add native Studio and App SDK project-user integrations; and migrate the Studio plugin member picker to the shared directory.
|
|
21
|
+
- 9126299: **BREAKING:** Delete persisted stage guard documents when their stage exits, and remove the lifted-record `GUARD_LIFTED_PREDICATE` and `isGuardLifted` exports.
|
|
22
|
+
|
|
23
|
+
Guard deletion now uses the observed document revision so stale exit reconciliation cannot delete a guard reactivated by a newer stage visit. Reactive guard streams contain only active persisted guards.
|
|
24
|
+
|
|
25
|
+
- 72018af: Add an extensible action semantics array with decision.accept and decision.decline as the initial engine-owned vocabulary.
|
|
26
|
+
- 5cd8ffd: **BREAKING:** Effect handlers now receive each host's concrete Sanity client unchanged, preserving its complete runtime and type surface. Handler traffic is no longer automatically tagged as `workflow.effect` or pinned to the engine API version; handlers own that configuration.
|
|
27
|
+
- b82bda4: **BREAKING:** `startInstance` rejects any `initialFields` row that would not land on an input-sourced declared entry — an undeclared name, a kind mismatch, or an entry that seeds itself (query/literal/fieldRead/working memory) — with `InitialFieldsInvalidError`. Previously such rows were silently ignored and the instance started without the caller's value.
|
|
28
|
+
- 55a54b3: **BREAKING:** the subject is now a first-class field kind, and the subject-entry rule keys on it.
|
|
29
|
+
|
|
30
|
+
New `subject` field kind: an elevated single `doc.ref` (same GDR value, validation, and `types` facet) that names THE document a workflow is about. Deploy enforces its placement — workflow scope only, at most one per workflow, never a nested sub-field. A subject entry dereferences in conditions, joins the reactive watch-set, routes spawn discovery, and canonicalises query/spawn refs exactly like a `doc.ref`. The persisted data model grows the value additively (`DATA_MODEL_VERSION` is now 2). The subject kind itself does not raise the reader floor — model 2's floor of 2 comes from the typed-choice-list change shipping alongside; see `DATAMODEL.md`.
|
|
31
|
+
|
|
32
|
+
The breaking half: `isSubjectEntry`, applicability (`acceptsDocumentType` / `definitionsForDocument` / document pickers), the autonomous-start deploy invariant, and the root-reading `start.filter` check now identify the subject by the `subject` kind — a required `doc.ref` / `doc.refs` input no longer counts. A deployed definition using that convention still parses and runs, but stops surfacing from document pickers (and stops satisfying the autonomous/root-read invariants on redeploy) until it declares a `subject` entry.
|
|
33
|
+
|
|
34
|
+
New exports: `isSingleDocRefKind` (single-GDR kinds — `doc.ref` and `subject`), `isSingleDocRefEntry` (its resolved-entry narrowing), and `refKindAcceptsTypes` (the kinds carrying the accepted-target `types` facet).
|
|
35
|
+
|
|
36
|
+
- b82bda4: Add the opt-in `$subjectHasInFlightInstance` start variable for composing subject-exclusive visibility and permission rules.
|
|
37
|
+
- 4490442: Add inclusive validation.min/max bounds for workflow string, text, and number declarations, with Studio input constraints and MCP authoring guidance.
|
|
38
|
+
- 1540dce: **BREAKING:** Definitions and instances written by this release require a model-2 engine reader; upgrade every reader before deploying its writers.
|
|
39
|
+
|
|
40
|
+
Workflow fields, nested field shapes, effect outputs, and action parameters can declare typed scalar choices with `options.list`. The engine validates authored lists and rejects runtime values outside them, Studio renders one shared select control and displays each selected choice by its title, and the MCP authoring guide explains the syntax.
|
|
41
|
+
|
|
42
|
+
### Patch Changes
|
|
43
|
+
|
|
44
|
+
- 239d9f6: Fix action operation values to follow the intended lexical lookup order by reading fields on their current activity before the stage and workflow, including writes made earlier in the same transaction.
|
|
45
|
+
- 71cd58e: Explain that effect-completion field operations require an explicit workflow or stage target scope when validation rejects a missing scope, and document the requirement on `EffectHandler`.
|
|
46
|
+
|
|
3
47
|
## 0.17.0
|
|
4
48
|
|
|
5
49
|
### Minor Changes
|
package/DATAMODEL.md
CHANGED
|
@@ -6,7 +6,7 @@ The engine owns three standalone document types in the Content Lake:
|
|
|
6
6
|
| ---------------------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
7
7
|
| `sanity.workflow.definition` | `planDefinitionDeploy` (`src/api/deploy.ts`) | never — deploys are create-only |
|
|
8
8
|
| `sanity.workflow.instance` | `buildInstanceBase` (`src/instance.ts`) | every persist writes the canonical state via `instanceStateFields` (`src/engine/mutation.ts`) |
|
|
9
|
-
| `temp.system.guard` | `compileGuard` (`src/core/guards.ts`) | full-body refresh on stage re-entry;
|
|
9
|
+
| `temp.system.guard` | `compileGuard` (`src/core/guards.ts`) | full-body refresh on stage re-entry; revision-conditional delete on retract |
|
|
10
10
|
|
|
11
11
|
These field trees are **our contract with every past and future engine
|
|
12
12
|
version reading the same dataset**. This file is where changes to them are
|
|
@@ -51,7 +51,8 @@ of the documents the engine itself writes.
|
|
|
51
51
|
## The stamp pair
|
|
52
52
|
|
|
53
53
|
The two doc types whose format the engine owns outright — definition and
|
|
54
|
-
instance — carry two numbers (`
|
|
54
|
+
instance — carry two numbers (built together by `modelStampFor` in
|
|
55
|
+
`src/core/model-version.ts`).
|
|
55
56
|
Provenance and compatibility are deliberately separate, because consumers
|
|
56
57
|
sharing a dataset upgrade months apart (Functions lag Studios lag CLIs) and
|
|
57
58
|
"newer" must not mean "incompatible".
|
|
@@ -69,13 +70,12 @@ never matches the new type).
|
|
|
69
70
|
Instances are re-stamped on every full persist (and rollback restore).
|
|
70
71
|
Partial patches deliberately leave the pair alone — they don't normalize
|
|
71
72
|
the shape, so restamping there would lie.
|
|
72
|
-
- **`minReaderModel` — the reader floor.** The
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
must upgrade readers before such writers deploy).
|
|
73
|
+
- **`minReaderModel` — the document's reader floor.** The oldest engine model
|
|
74
|
+
that can safely _interpret this particular document_. Writers derive it
|
|
75
|
+
from compatibility-bearing features present in the canonical stored tree.
|
|
76
|
+
`DATA_MODEL_MIN_READER` is the maximum floor the current writer can emit,
|
|
77
|
+
not a value stamped on every document. Additive features contribute floor
|
|
78
|
+
0; only an incompatible feature fences older readers out of that document.
|
|
79
79
|
- **Missing stamp = model 0** (floor 0) — the document was last written
|
|
80
80
|
before the stamp existed. A `modelVersion` with no `minReaderModel` is
|
|
81
81
|
malformed foreign data and gates conservatively: the stamp is the floor.
|
|
@@ -133,10 +133,9 @@ never matches the new type).
|
|
|
133
133
|
round-trip**. That is a load-bearing invariant: an additive change whose
|
|
134
134
|
fields would NOT survive an old writer's round-trip is not additive — it
|
|
135
135
|
is breaking, and must raise the floor.
|
|
136
|
-
- **The floor is raise-only.** A rewrite writes
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
model may still require it. (The failure mode this fences off is
|
|
136
|
+
- **The floor is raise-only.** A rewrite writes `max(stored minReaderModel, floor derived from the complete post-state)` — it never lowers or drops the
|
|
137
|
+
stored floor, because preserved unknown fields or a historical semantic
|
|
138
|
+
requirement may still require it. (The failure mode this fences off is
|
|
140
139
|
documented across the industry: a rewriter that drops the compatibility
|
|
141
140
|
gate it doesn't understand silently reopens the door the floor closed.)
|
|
142
141
|
- **Raising the floor is an availability decision, sequenced readers-first.**
|
|
@@ -155,6 +154,22 @@ never matches the new type).
|
|
|
155
154
|
|
|
156
155
|
## Rules for changing the model
|
|
157
156
|
|
|
157
|
+
`DATA_MODEL_CHANGES` is the append-only, machine-readable counterpart of the
|
|
158
|
+
model log. Every independently reasoned persisted-model feature records a
|
|
159
|
+
stable id, introduction model, affected document types, compatibility class,
|
|
160
|
+
reader floor, and summary. The manifest drives writer-side floor derivation
|
|
161
|
+
and diagnostics; this prose log remains authoritative for the reasoning,
|
|
162
|
+
absent-value behavior, and old-writer round-trip proof.
|
|
163
|
+
|
|
164
|
+
Feature applicability is explicit. A **detectable** feature contributes its
|
|
165
|
+
floor only when its stable marker is present in the canonical stored tree. An
|
|
166
|
+
**unconditional semantic requirement** applies to every newly written document
|
|
167
|
+
of its declared type when no reliable marker exists. Readers trust the
|
|
168
|
+
persisted floor; they do not recompute features they may be too old to know.
|
|
169
|
+
Every reader-floor increase is represented as a manifest feature and classified
|
|
170
|
+
this way; raising the package maximum never by itself raises every document's
|
|
171
|
+
floor.
|
|
172
|
+
|
|
158
173
|
1. **Any change to a persisted field tree is a model change** and must be
|
|
159
174
|
declared: update the model-surface snapshot, add an entry to the model
|
|
160
175
|
log below, and decide — in the same change — whether `DATA_MODEL_VERSION`
|
|
@@ -181,6 +196,18 @@ never matches the new type).
|
|
|
181
196
|
just fail to parse?) is exactly what decides whether
|
|
182
197
|
`DATA_MODEL_MIN_READER` moves, and no snapshot can make it — each model
|
|
183
198
|
log entry states it explicitly.
|
|
199
|
+
6. **Manifest and prose move together.** Every structural model change adds a
|
|
200
|
+
manifest entry and matching model-log declaration. The ledger snapshots
|
|
201
|
+
the stored grammar and manifest. A semantic-only implementation change may
|
|
202
|
+
have no observable schema delta; review must still classify and log any
|
|
203
|
+
change to how persisted values are interpreted.
|
|
204
|
+
7. **The PR compatibility callout is mandatory and explicit.** Near the top of
|
|
205
|
+
the PR body, state the writer's `modelVersion`, name every feature introduced
|
|
206
|
+
or changed by the PR that contributes a `minReaderModel`, and state the floor
|
|
207
|
+
for documents where those features are absent. Say plainly whether model-0
|
|
208
|
+
readers still work for those documents. Give the rollout order and scope:
|
|
209
|
+
readers-first before any writer can persist a floor-bearing feature, not a
|
|
210
|
+
misleading fleet-wide minimum when floor derivation is document-specific.
|
|
184
211
|
|
|
185
212
|
## Model log
|
|
186
213
|
|
|
@@ -202,6 +229,9 @@ rules, not folklore:
|
|
|
202
229
|
|
|
203
230
|
### Model 1 — stamping introduced; actions-only payload model (reader floor: 0)
|
|
204
231
|
|
|
232
|
+
Manifest feature: `governed-model-stamps` (definition + instance, additive,
|
|
233
|
+
floor 0).
|
|
234
|
+
|
|
205
235
|
The `modelVersion` + `minReaderModel` pair is stamped on the definition and
|
|
206
236
|
instance doc types; the guard doc deliberately carries none (foreign lake
|
|
207
237
|
contract — see "The stamp pair"). The floor is 0: model-0 documents keep
|
|
@@ -318,6 +348,103 @@ its shape. Redeploying one fails validation until the expression moves to
|
|
|
318
348
|
The released-engine caveat above covers this amendment identically: those
|
|
319
349
|
datasets are this-repo test data and are reset, not migrated.
|
|
320
350
|
|
|
351
|
+
### Model 2 — subject fields, scalar constraints, and action semantics (reader floor: 2)
|
|
352
|
+
|
|
353
|
+
Four additive growths — a new field kind and three optional facets — ship
|
|
354
|
+
together as model 2.
|
|
355
|
+
|
|
356
|
+
**The `subject` field kind.** An elevated single `doc.ref` (same GDR value
|
|
357
|
+
shape and validation, the `types` facet included) that names THE document a
|
|
358
|
+
workflow is about — workflow scope only, at most one, both deploy-enforced.
|
|
359
|
+
Both stamped trees grow the value:
|
|
360
|
+
|
|
361
|
+
- **Definition tree** — `fields[].type` admits `'subject'`, and a `subject`
|
|
362
|
+
entry may carry `types` like `doc.ref` / `doc.refs`.
|
|
363
|
+
- **Instance tree** — resolved `fields[]` entries admit `_type: 'subject'`
|
|
364
|
+
(the same arm shape as `doc.ref`, optional `types` included).
|
|
365
|
+
|
|
366
|
+
Manifest feature: `subject-field-kind` (definition + instance, additive,
|
|
367
|
+
detectable, floor 0).
|
|
368
|
+
|
|
369
|
+
Would an old reader misread the kind (rule 5)? No — it refuses loudly, never
|
|
370
|
+
silently: the definition document has no read-side content parse, and a
|
|
371
|
+
model-1 engine that loaded a subject-carrying **instance** would fail the
|
|
372
|
+
shape gate (the strict `fields[]` variant rejects the unknown discriminator
|
|
373
|
+
with `PersistedDocShapeError`) before interpreting anything — under the
|
|
374
|
+
document-specific floor derived below only fences it when another floor-bearing
|
|
375
|
+
feature (a choice list or scalar bounds) is also present. The subject kind
|
|
376
|
+
itself leaves the floor at 0.
|
|
377
|
+
|
|
378
|
+
One evaluation-semantics reconciliation rides the same change, declared
|
|
379
|
+
here under rule 5's judgment: the subject-entry rule (applicability's type
|
|
380
|
+
matching, the autonomous-start invariant, the root-read deploy check) now
|
|
381
|
+
keys on the `subject` kind, where it previously classified any required
|
|
382
|
+
`doc.ref` / `doc.refs` input as the subject. A stored pre-subject
|
|
383
|
+
definition still parses and runs everywhere, but no longer surfaces in
|
|
384
|
+
document pickers until redeployed with a `subject` entry — a deliberate
|
|
385
|
+
refuse-to-guess, not a misread.
|
|
386
|
+
|
|
387
|
+
**Typed scalar choice lists.** Definition value declarations and resolved
|
|
388
|
+
instance field entries gained an optional `options.list[]` tree. Each item
|
|
389
|
+
carries a display `title` and a typed scalar `value`; an absent `options`
|
|
390
|
+
retains the unconstrained behavior of every model-0/model-1 declaration and
|
|
391
|
+
instance entry.
|
|
392
|
+
|
|
393
|
+
Manifest feature: `typed-scalar-choice-lists` (definition + instance,
|
|
394
|
+
reader-floor, detectable, floor 2).
|
|
395
|
+
|
|
396
|
+
The field tree grows additively, and an absent `options` keeps every older
|
|
397
|
+
document's meaning unchanged. A document carrying `options.list` nevertheless
|
|
398
|
+
requires reader floor 2: an older engine would interpret the declaration as
|
|
399
|
+
unconstrained and could commit a value outside the list. That is semantic
|
|
400
|
+
misinterpretation, not a presentation downgrade. Writers of model 2 must
|
|
401
|
+
therefore deploy only after every engine reader has upgraded to model 2; the
|
|
402
|
+
floor makes an older reader fail explicitly instead of silently bypassing
|
|
403
|
+
the declaration. A model-2 definition or instance without a choice list retains
|
|
404
|
+
the floor derived from its other features (often 0). Resolved entries carry the
|
|
405
|
+
same optional tree so field writes remain self-validating. Full persists derive
|
|
406
|
+
from the complete post-state and preserve any higher stored floor.
|
|
407
|
+
|
|
408
|
+
**Action semantics.** Ordinary stored definition actions gained an optional
|
|
409
|
+
`semantics[]` bag whose initial closed vocabulary is `decision.accept` and
|
|
410
|
+
`decision.decline`. The values express advisory workflow meaning shared by
|
|
411
|
+
consumers; they change no execution, gate, transition, status, history, or
|
|
412
|
+
instance state. An absent `semantics` retains the generic behavior of every
|
|
413
|
+
older definition.
|
|
414
|
+
|
|
415
|
+
Manifest feature: `action-semantics` (definition, additive, detectable, floor
|
|
416
|
+
0).
|
|
417
|
+
|
|
418
|
+
Would an old reader misread (rule 5)? No. Definition content has no strict
|
|
419
|
+
read-side parse, and an engine predating the facet ignores the unknown optional
|
|
420
|
+
key, treating the action as generic. That loses advisory information but does
|
|
421
|
+
not change workflow execution. Definition documents are immutable after create,
|
|
422
|
+
so an old writer never rewrites and drops the array. The feature therefore
|
|
423
|
+
leaves the reader floor unchanged.
|
|
424
|
+
|
|
425
|
+
**Inclusive scalar bounds.** Workflow value declarations gained an optional
|
|
426
|
+
`validation` tree with finite `min` / `max` bounds. On `string` and `text`
|
|
427
|
+
declarations the bounds constrain character length and must be non-negative
|
|
428
|
+
integers; on `number` declarations they constrain the numeric value.
|
|
429
|
+
|
|
430
|
+
The same tree is available on action params, top-level fields, and nested field
|
|
431
|
+
shapes. Resolved instance field entries carry it so every later field write
|
|
432
|
+
validates against the declaration that created the instance. Definitions and
|
|
433
|
+
instances that omit `validation` retain their unconstrained behavior.
|
|
434
|
+
|
|
435
|
+
Manifest feature: `inclusive-scalar-bounds` (definition + instance,
|
|
436
|
+
reader-floor, detectable, floor 2).
|
|
437
|
+
|
|
438
|
+
The shape growth is additive, but a document carrying `validation` requires
|
|
439
|
+
reader floor 2. An older engine would preserve the unknown tree while
|
|
440
|
+
interpreting the value as unconstrained, and could therefore commit an
|
|
441
|
+
out-of-range value. That is semantic misinterpretation under rule 5, so older
|
|
442
|
+
readers must fail at the model gate. A model-2 definition or instance without
|
|
443
|
+
scalar bounds retains the floor derived from its other features (often 0).
|
|
444
|
+
Spread-based mutation copies preserve `validation` across every full persist;
|
|
445
|
+
the derived floor prevents older writers from reaching constrained model-2
|
|
446
|
+
instances.
|
|
447
|
+
|
|
321
448
|
## Pending governed changes
|
|
322
449
|
|
|
323
450
|
- **`temp.system.guard` → `system.guard`** — the guard doc type's `temp.`
|
package/README.md
CHANGED
|
@@ -43,6 +43,29 @@ different at each — read it by where it sits:
|
|
|
43
43
|
4. **Resolved runtime values** — `fields` on a workflow instance document: the
|
|
44
44
|
declared entries with their current values, written by ops and edits.
|
|
45
45
|
|
|
46
|
+
## Resolving actors to current project users
|
|
47
|
+
|
|
48
|
+
An `Actor` stores durable provenance, not mutable profile data. CLI, MCP, and
|
|
49
|
+
server consumers resolve through the client already bound to their engine:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
const result = await engine.resolveActor({actor: effect.actor, projectId})
|
|
53
|
+
|
|
54
|
+
if (result.status === 'resolved') {
|
|
55
|
+
await notify(result.user)
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The package also exports `resolveClientActor(client, {actor, projectId})` for
|
|
60
|
+
code that has a plain `WorkflowClient` but no constructed engine. UI adapters
|
|
61
|
+
implement `ProjectUserDirectory<User>` with their native user type and pass it
|
|
62
|
+
to `resolveActor(directory, actor)`.
|
|
63
|
+
|
|
64
|
+
The result distinguishes `resolved`, `missing`, `inaccessible`, and
|
|
65
|
+
`not-person`. Agent and system actor IDs are never sent to a project-user API.
|
|
66
|
+
An integration may also provide centralized `findByEmail`; consumers should
|
|
67
|
+
not page and scan a project directory independently.
|
|
68
|
+
|
|
46
69
|
## License
|
|
47
70
|
|
|
48
71
|
[MIT](./LICENSE)
|