@sanity/workflow-engine 0.17.0 → 0.19.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 +53 -0
- package/DATAMODEL.md +193 -13
- package/README.md +23 -0
- package/dist/_chunks-cjs/invariants.cjs +696 -122
- package/dist/_chunks-es/invariants.js +643 -123
- package/dist/define.cjs +6 -1
- package/dist/define.d.cts +397 -40
- package/dist/define.d.ts +397 -40
- package/dist/define.js +7 -2
- package/dist/index.cjs +3089 -2159
- package/dist/index.d.cts +1199 -205
- package/dist/index.d.ts +1199 -205
- package/dist/index.js +3012 -2132
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
1
|
# @sanity/workflow-engine
|
|
2
2
|
|
|
3
|
+
## 0.19.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 24b11dd: Effect handlers can report intermediate field state mid-dispatch. The handler context gains `ctx.commitOps({ops, idempotencyKey})` — field ops only, validated like completion ops, executed through the new `workflow.commitEffectOps` verb as a standard engine commit (gate, history, idempotency ledger, compare-and-swap persist, guard refresh, cascade) — and `ctx.setProgress(field, value)`, sugar that coalesces pending absolute sets per field. Calls enqueue synchronously into a bounded per-dispatch queue (depth 32, 200 commits per dispatch) that serializes commits in call order; overflow throws synchronously at the call site, and settlement drains the queue before completion so an un-awaited failed report still fails the effect. Pending-effect claims now carry a `claimToken` minted on every claim and takeover: a report from an expired, released, or superseded dispatch is rejected before writing (`StaleEffectClaimError`), and a successful report renews the claim's lease. Completion semantics are unchanged and remain claim-blind first-writer-wins. Each report emits the sampled `Editorial Workflows Effect State Reported` telemetry event (adoption signal, max one per minute). Persisted-model note: the claim token rides persisted model 3 additively (see `DATAMODEL.md`).
|
|
8
|
+
- 2005ab7: Action evaluations carry a `firing` consequence (`FiringConsequence` `{exitsStage, transition?}`): what firing the action would do to the flow right now, computed by replaying the fire in memory through the same machinery a real commit runs — ops, the triggered fixpoint, then transition selection — against the projection's state. The answer is total for the current state and conditional on the commit landing; it is omitted where the replay can't be faithful (caller params, a spawn's dataset-driven fan-out in the hop, a terminal instance, a stage visit whose entries have diverged from the pinned definition) and on cascade-fired actions. Advisory, like every derived verdict. Wrong-shape `field.updateWhere` aborts (a merge resolving to a non-object; a resolved merge carrying reserved row keys) now throw the typed `FieldValueShapeError` on the commit path, joining the other wrong-shape write aborts.
|
|
9
|
+
- 24b11dd: Add the `progress` field kind: an elevated `number` carrying application-defined 0–100 completion, so Studio and SDK surfaces can identify and elevate it without guessing which number field means "how far along". Every non-null value must be finite and within 0–100 inclusive (fractions allowed), enforced by one value schema at every write boundary — declaration seeds, start values, edits, ops, nested sub-fields, and effect outputs. Choice lists are rejected on the kind at authoring time. Mints persisted model 3 additively (see `DATAMODEL.md`).
|
|
10
|
+
- 59b40fd: **BREAKING:** a deployment's `name` is now its identity in `defineWorkflowConfig`: enforced unique across the config and constrained to the same grammar as `tag` (lowercase letters, digits, dashes — free text like "My cool workflow" is rejected). Tags may now repeat across deployments; the enforced storage invariant is instead the `(workflowResource, tag)` pair — two deployments sharing both are rejected with each entry named, since they would write into the same partition and fight over definition versions.
|
|
11
|
+
|
|
12
|
+
## 0.18.0
|
|
13
|
+
|
|
14
|
+
### Minor Changes
|
|
15
|
+
|
|
16
|
+
- 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.
|
|
17
|
+
- 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.
|
|
18
|
+
- 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.
|
|
19
|
+
- 00c089d: **BREAKING:** Require every definition deployment and workflow deployment configuration to state an `expectedMinReaderModel` literal that exactly matches the installed engine's writer capability.
|
|
20
|
+
|
|
21
|
+
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.
|
|
22
|
+
|
|
23
|
+
- cd03973: **BREAKING:** Reject initial workflow field values that cannot be consumed and expose structured preflight issues.
|
|
24
|
+
|
|
25
|
+
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.
|
|
26
|
+
|
|
27
|
+
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.
|
|
28
|
+
|
|
29
|
+
- 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.
|
|
30
|
+
- 9126299: **BREAKING:** Delete persisted stage guard documents when their stage exits, and remove the lifted-record `GUARD_LIFTED_PREDICATE` and `isGuardLifted` exports.
|
|
31
|
+
|
|
32
|
+
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.
|
|
33
|
+
|
|
34
|
+
- 72018af: Add an extensible action semantics array with decision.accept and decision.decline as the initial engine-owned vocabulary.
|
|
35
|
+
- 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.
|
|
36
|
+
- 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.
|
|
37
|
+
- 55a54b3: **BREAKING:** the subject is now a first-class field kind, and the subject-entry rule keys on it.
|
|
38
|
+
|
|
39
|
+
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`.
|
|
40
|
+
|
|
41
|
+
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.
|
|
42
|
+
|
|
43
|
+
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).
|
|
44
|
+
|
|
45
|
+
- b82bda4: Add the opt-in `$subjectHasInFlightInstance` start variable for composing subject-exclusive visibility and permission rules.
|
|
46
|
+
- 4490442: Add inclusive validation.min/max bounds for workflow string, text, and number declarations, with Studio input constraints and MCP authoring guidance.
|
|
47
|
+
- 1540dce: **BREAKING:** Definitions and instances written by this release require a model-2 engine reader; upgrade every reader before deploying its writers.
|
|
48
|
+
|
|
49
|
+
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.
|
|
50
|
+
|
|
51
|
+
### Patch Changes
|
|
52
|
+
|
|
53
|
+
- 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.
|
|
54
|
+
- 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`.
|
|
55
|
+
|
|
3
56
|
## 0.17.0
|
|
4
57
|
|
|
5
58
|
### 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,156 @@ 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
|
+
|
|
448
|
+
### Model 3 — the `progress` field kind + effect claim tokens (reader floor: 2)
|
|
449
|
+
|
|
450
|
+
Two additive growths ship together as model 3 — model 2 was released (npm
|
|
451
|
+
`@sanity/workflow-engine@0.18.0`) before either landed, so they mint the next
|
|
452
|
+
version rather than amending a shipped one. Neither raises the reader floor;
|
|
453
|
+
the writer maximum stays 2.
|
|
454
|
+
|
|
455
|
+
**The `progress` field kind.** An elevated `number` (same stored numeric
|
|
456
|
+
value) carrying application-defined 0–100 completion, so surfaces can
|
|
457
|
+
identify and elevate it without guessing which number field means "how far
|
|
458
|
+
along". Every non-null value is finite and within 0–100 inclusive (fractions
|
|
459
|
+
allowed), enforced at every write boundary by the kind's one value schema.
|
|
460
|
+
Model 2's `validation` tree composes with it as NARROWING only: each declared
|
|
461
|
+
bound must itself sit within 0–100, gated at authoring. Both stamped trees
|
|
462
|
+
grow the value:
|
|
463
|
+
|
|
464
|
+
- **Definition tree** — `fields[].type` (and nested `FieldShape.type`)
|
|
465
|
+
admits `'progress'`.
|
|
466
|
+
- **Instance tree** — resolved `fields[]` entries admit `_type: 'progress'`
|
|
467
|
+
(the same arm shape as `number`).
|
|
468
|
+
|
|
469
|
+
Manifest feature: `progress-field-kind` (definition + instance, additive,
|
|
470
|
+
detectable, floor 0).
|
|
471
|
+
|
|
472
|
+
Would an old reader misread the kind (rule 5)? No — the same loud refusal as
|
|
473
|
+
the `subject` kind: an older engine (model 2 and below) that loaded a
|
|
474
|
+
progress-carrying instance fails the strict `fields[]` variant with
|
|
475
|
+
`PersistedDocShapeError` before interpreting anything, and cannot deploy or
|
|
476
|
+
evaluate a progress-declaring definition. No older code path can write a
|
|
477
|
+
progress field while skipping its bound, so there is no silent bypass to
|
|
478
|
+
fence — the kind leaves the floor at 0.
|
|
479
|
+
|
|
480
|
+
**Effect claim tokens.** `pendingEffects[].claim` grows an optional
|
|
481
|
+
`claimToken` — an opaque identifier minted fresh on every claim and
|
|
482
|
+
takeover. It is the exact-claim identity a mid-dispatch state report
|
|
483
|
+
(`commitEffectOps`) must present: `effectKey` cannot serve because a lease
|
|
484
|
+
takeover keeps the same key. A successful report also renews the claim's
|
|
485
|
+
`leaseExpiresAt` in the same commit.
|
|
486
|
+
|
|
487
|
+
Manifest feature: `effect-claim-tokens` (instance, additive, detectable,
|
|
488
|
+
floor 0).
|
|
489
|
+
|
|
490
|
+
Tolerant read: an older engine's claim parse admits unknown keys, so the
|
|
491
|
+
token rides through untouched and means nothing to it — older claim
|
|
492
|
+
semantics (presence + lease) are unchanged. Round-trip survival: full
|
|
493
|
+
persists derive from the complete loaded post-state, so an older writer
|
|
494
|
+
carries the token along; the claim-REPLACING writes (takeover, sweep
|
|
495
|
+
release, completion drain) discard it deliberately, which is the token's
|
|
496
|
+
own lifecycle — a report presenting a discarded token is rejected before
|
|
497
|
+
writing, failing closed. A claim without a token (older writer's takeover)
|
|
498
|
+
simply cannot authorise mid-dispatch reports; dispatch and completion are
|
|
499
|
+
unaffected.
|
|
500
|
+
|
|
321
501
|
## Pending governed changes
|
|
322
502
|
|
|
323
503
|
- **`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)
|