@sanity/workflow-engine 0.31.0 → 0.32.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,135 @@
1
1
  # @sanity/workflow-engine
2
2
 
3
+ ## 0.32.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a8ed312: **BREAKING:** Assignment is now one ordered user-or-role member-list model: a direct user holder shadows every role in the same activity, role-only values route work to a pool, and singular `assignee` fields allow at most one user while retaining any number of roles. The `claim` field and action sugars have been removed from the authoring DSL; authors using them must replace each pair with an `assignee` field, a literal role seed where the work starts in a pool, ordinary field edits for take/release, a guarded `editable` predicate when second-taker exclusion is required, and `$assigned` on holder-only actions. Definitions that still submit `type: 'claim'` now fail validation.
8
+
9
+ Upgrade every engine, Studio, CLI, MCP, and adapter runtime sharing a workflow resource before deploying a definition containing a singular `assignee`, then acknowledge reader model 9 on deployment. Existing model-8-and-earlier instances remain readable and keep their object/null singular representation; no stored-document backfill is required. Deploy now rejects project-role references absent from the target project's live role catalog and warns when a referenced role has no current human holder, so the deploying identity must be able to read the project role and member directories.
10
+
11
+ Studio assignment matching and holder explanations now follow direct-user shadowing. The CLI adds viewer-scoped assignment list flags and counts, MCP instance listing adds corresponding assignment inputs and counts, and the public waiting `Diagnosis` adds a required `waitingFor` discriminant that distinguishes caller-actionable, manual-but-unavailable, and automation waits without implying that unassigned work is freely actionable.
12
+
13
+ `@sanity/workflow-components` now exports `roleMemberCount`, which reports the distinct people who can fulfill any supplied project role using the workflow definition's aliases. Pass the current member directory, required role names, and normalized `roleAliases`; literal members and alias-only fulfillers are deduplicated by account-global user id. No upgrade action is required unless a custom assignment surface wants alias-aware pool sizes.
14
+
15
+ **Docs impact:** Update the assignment model and authoring references, the reader-model rollout guide, guarded role-pool and take/release examples, the `@sanity/workflow-components` member-selection reference for `roleMemberCount`, Studio task-holder explanations, CLI instance-list flags, MCP list tool reference, deploy role-validation requirements, and migration guidance from removed claim sugar.
16
+
17
+ - 2de38fd: **BREAKING:** `createEngine` takes its effect settings as one `effects` group. The top-level `effectHandlers`, `effectLeaseMs`, and `missingHandler` options are removed, with no alias and no deprecation shim, so everyone who builds an engine — a Sanity Function drainer, a hosted runtime, a script, a test — must move them into `effects: {handlers, leaseMs, missingHandler}`. An unmigrated TypeScript call stops compiling. An unmigrated plain-JavaScript call still builds an engine, but one with no handlers and the default `fail` policy, so its first drain throws `MissingHandlerError` instead of dispatching.
18
+
19
+ Migrate every construction site: `createEngine({client, workflowResource, tag, effectHandlers: H, effectLeaseMs: L, missingHandler: P})` becomes `createEngine({client, workflowResource, tag, effects: {handlers: H, leaseMs: L, missingHandler: P}})`. The resolved values read back off the engine under the same word, so `engine.effectHandlers` and `engine.missingHandler` become `engine.effects.handlers` and `engine.effects.missingHandler`. Nothing about draining changes: the same `fail` default, the same five-minute default lease, and the same claim, dispatch, and deploy-verification semantics. `@sanity/workflow-engine-test`'s `createBenchEngine(bench, overrides)` forwards `CreateEngineArgs` unchanged, so bench engines move their handler and policy overrides into the same group.
20
+
21
+ `@sanity/workflow-studio`'s `useWorkflowEngine` follows the engine: its `effectHandlers` and `missingHandler` props are replaced by one `effects` prop taking the engine's exported `EngineEffectsArgs`, so a Studio-side drainer passes `effects: {handlers, missingHandler}`. Keep that object referentially stable, at module scope or through `useMemo` — the hook memoizes the engine on it, and a new object every render rebuilds the engine every render. The `@sanity/workflow-studio-plugin` `effectHandlers` config key is unchanged; the plugin translates it into the group where it builds the engine.
22
+
23
+ **Docs impact:** Update the `createEngine` options reference and the effects concepts page for the new group, the missing-handler and claim-lease reference entries that named the old keys, the `useWorkflowEngine` adapter reference, the Studio plugin README's drain-function example, and every cookbook or runtime example that constructs an engine with handlers; carry the migration into the release notes.
24
+
25
+ - a2ce4a7: **BREAKING:** Mutation guards now have one public compilation path:
26
+ `compileGuards` replaces the singular `compileGuard` export and returns every
27
+ Lake document required for the authored guard. Callers using `compileGuard`
28
+ must switch to `compileGuards` and persist every returned document; retaining
29
+ the old call would either fail to compile after upgrade or omit required
30
+ ID-space siblings. Literal field-seed document IDs now also enforce the Lake's
31
+ 128-character limit and reject double dots; replace an invalid seed with a
32
+ valid resource-local document ID before upgrading. `MutationContext.action` now admits only the
33
+ Lake's `create`, `update`, and `delete` operations, and
34
+ `documentActionDenials` takes their concrete `before` / `after` mutation image
35
+ instead of a prospective document plus authored lifecycle action. Callers of
36
+ that helper must construct the same mutation the Lake will evaluate; otherwise
37
+ their preview can disagree with enforcement. Pure `evaluateFromSnapshot`
38
+ callers that supply dereference-bearing guards must also pass a
39
+ `guardDereference` resolver for stored resource reads; the engine and reactive
40
+ session supply their token-bound client resolver automatically.
41
+
42
+ Mutation guards now deploy edit locks against draft IDs and publish or
43
+ unpublish gates against published IDs using the create, update, and delete
44
+ operations Content Lake evaluates. A guard combining both action classes
45
+ emits separate temporary guard documents, preventing either match from leaking
46
+ into the other ID space. Guard previews bind `document.before` and
47
+ `document.after`, follow stored resource-local references, continue to
48
+ understand temporary guards emitted with the older lifecycle-action vocabulary,
49
+ and reject invalid actions, ID patterns, or predicates before deployment.
50
+
51
+ Existing guard definitions keep their authored shape. Definitions whose guards
52
+ stay within one emitted ID space require no rollout change and retain their
53
+ existing reader floor. A guard that combines direct create/delete actions,
54
+ content updates, or publish/unpublish actions across ID spaces is a detectable
55
+ model-9 feature on both its definition and instances.
56
+
57
+ Before deploying such a definition, upgrade every Studio, CLI, MCP server,
58
+ Function, and other engine runtime sharing its workflow resource, then change
59
+ that deployment's reviewed `expectedMinReaderModel` literal to `9`. This
60
+ readers-first order is required because pre-model-9 engines only know the base
61
+ guard document ID: after a newer engine emits an ID-space sibling, an older
62
+ engine could otherwise advance or abort the instance, retract only the base,
63
+ and leave an advisory lock behind. The affected instance commits reader floor
64
+ 9 before split guards deploy, so an old runtime fails explicitly instead of
65
+ stranding a sibling. Existing affected instances need no data migration; their
66
+ first model-9 commit raises the floor before it can create a split guard.
67
+
68
+ **Docs impact:** Update `docs/reference.md` and the public mutation-guard
69
+ reference to explain publish and unpublish action translation, predicate
70
+ document bindings and dereferencing,
71
+ deploy-time validation errors, and why mixed-ID-space guards emit separate
72
+ documents. Update the prerelease reader-model rollout guide with the conditional
73
+ model-9 adoption sequence, including every shared runtime, the
74
+ `expectedMinReaderModel: 9` change, the old-retractor failure mode, and the fact
75
+ that unaffected definitions retain their existing floor and require no data
76
+ migration. Keep the `@sanity/workflow-engine-test` README's draft-ID examples
77
+ and the root live-parity credential guidance aligned with those contracts.
78
+
79
+ - 2ddd3d7: **BREAKING:** `WorkflowErrorKind` can now be `concurrent-cascade`, paired with
80
+ the new exported `ConcurrentCascadeError`, when an automatic cascade exhausts
81
+ its revision-conflict retry budget. Consumers with exhaustive switches or
82
+ `Record<WorkflowErrorKind, …>` mappings over the previous union must add the
83
+ new member before upgrading; otherwise their TypeScript build will fail.
84
+
85
+ Automatic cascade hops now recover from concurrent same-instance commits by
86
+ reloading and re-evaluating state before retrying. Bounded retries for actions,
87
+ field edits, and effect writes now also classify conflicts only at the parent
88
+ instance commit, so a later guard or child-settlement 409 surfaces immediately
89
+ instead of being mislabeled as instance contention. Update exhaustive error
90
+ handling as described above; workflow definitions and stored instances require
91
+ no migration.
92
+
93
+ **Docs impact:** Add `ConcurrentCascadeError` to the engine error reference and
94
+ describe automatic cascade conflict recovery.
95
+
96
+ - 6035672: Every mutating verb's `OperationResult` and `evaluate`'s `WorkflowEvaluation` now carry `nextEvaluationAt`, an optional ISO 8601 instant: the earliest future moment at which the passage of time alone could change what an instance evaluates to. It is the nearest `$now` boundary across every clock-reading condition site in the instance's current stage — transitions, activity filters, requirements, action filters and triggers, and editable-field gates — computed from the instance's current field values and verified by re-evaluating each site at the candidate instant. A runtime that polls on a cron can wait exactly instead (`tick`, read `nextEvaluationAt`, sleep until it, `tick` again), and a live session can re-render on the same instant.
97
+
98
+ Read the absence of the field precisely: it is absent for two reasons and no others. Either no clock-reading site the engine can judge there has a boundary — the stage declares none, or none has one the instance's current values pin, since date arithmetic and dataset scans report nothing rather than a guess — or the instance is terminal and will never re-evaluate. A result with `changed: false` still carries it, which is exactly when a scheduler needs it: a tick that moved nothing because the deadline has not arrived yet still reports when it will. What the engine can judge differs between the two surfaces: it derives the operation-side copy caller-free, so an action filter, requirement, or editable predicate combining `$now` with `$actor`, `$assigned`, `$can`, or `$attributes` is unevaluable there and contributes nothing to a verb result. Such a site answers per actor and a verb result carries no actor, so `evaluate` — projected for one — is the surface that answers it; deploy already keeps caller-bound reads out of the cascade gates the engine acts on.
99
+
100
+ The instant is a pure function of the evaluated instance state, its definition, and the operation's clock reading. It is never persisted and never derived after a write: a cascading verb derives it from the settled in-memory state before that hop persists, a verb returning the instance unchanged derives it from the state it loaded, and a derivation failure fails the call rather than following a commit that already landed.
101
+
102
+ **No upgrade action required.** The field is optional and additive; existing callers, the reactive session, and the Studio and App SDK adapters compile and behave unchanged, and consumers that already return these result types expose the new field without any change of their own.
103
+
104
+ **Docs impact:** The repository reference (`docs/reference.md`) is updated in this change; the published documentation is not. Update the published engine reference where it lists the `OperationResult` and `WorkflowEvaluation` shapes and where the verb entries describe what `tick`, `fireAction`, and `evaluate` return, so both shapes name `nextEvaluationAt` and carry the absence rule above, and correct the `createEngine.clock` entry, which says one reading per operation where the engine takes one per pass. Update the published concepts page where it explains that the engine never runs a clock and that a caller must `tick` when a deadline passes, so the scheduling story says how a runtime learns when that is.
105
+
106
+ - 26dd4e5: Generated API references now include every type used by a public signature and link cross-package symbols to their authoritative package entry. Caller-facing helper contracts such as field mutation operations, engine operation context types, test-bench argument types, member avatar data, MCP client policy, and Studio user clients are now available as named exports where their public APIs already expose those shapes. GROQ condition consumers can import the documented `COMPARISON_OPS` list alongside its `ComparisonOp` type, while the shared cross-package exhaustiveness helper is an unsupported `@internal` export and is omitted from the reference.
107
+
108
+ **No upgrade action required.** Existing imports and runtime behavior remain compatible; consumers may adopt the new named type exports instead of reconstructing those shapes locally.
109
+
110
+ **Docs impact:** Refresh the generated API references for the affected packages; no conceptual guides, examples, or migration guidance need changes.
111
+
112
+ - b04580d: **BREAKING:** The in-memory bench now exposes `GuardDeniedError` when Content Lake-style mutation guards reject client writes, replacing the engine-specific `MutationGuardDeniedError` previously thrown by the fake seam. Bench consumers that catch or assert the old class must import `GuardDeniedError` from `@sanity/workflow-engine-test` and match it instead; without this migration, denial assertions will fail even though the write remains rejected.
113
+
114
+ Publish and unpublish tests must also express guards in terms of the lake mutations they commit: publish updates or creates the published document and deletes its draft, while unpublish creates the draft and deletes the published document. Replace synthetic `publish` and `unpublish` guard-action expectations with the applicable `create`, `update`, and `delete` actions.
115
+
116
+ The engine's `DocumentValuePermission` and `Grant` types now include the lake's `manage` permission so access configurations can represent identities allowed to author system documents. `DOCUMENT_VALUE_PERMISSIONS` is now exported for consumers that need the complete runtime vocabulary.
117
+
118
+ The bench also re-exports `WriteAccessDeniedError`, giving access-control denial assertions the same stable package entry point as mutation-guard denial assertions. No upgrade action is required. Consumers may move existing imports of this class from `@sanity-labs/client-fake-for-test` to `@sanity/workflow-engine-test`.
119
+
120
+ **Docs impact:** Update the engine authorization API reference for the `manage` permission and `DOCUMENT_VALUE_PERMISSIONS`, plus the engine test-bench guard and access-denial guidance for the re-exported error classes and underlying publish/unpublish mutations.
121
+
122
+ ### Patch Changes
123
+
124
+ - 2ba0c09: Generated API references keep navigation between public authoring, resource-alias, and observer helpers while no longer presenting private implementation helpers or private-package README targets as broken links.
125
+
126
+ **No upgrade action required.** Runtime behavior and public TypeScript contracts are unchanged.
127
+
128
+ **Docs impact:** Regenerate the API reference entries for these packages so their corrected TSDoc and README content is visible.
129
+
130
+ - Updated dependencies [26dd4e5]
131
+ - @sanity/groq-condition-describe@0.5.0
132
+
3
133
  ## 0.31.0
4
134
 
5
135
  ### Patch 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; revision-conditional delete on retract |
9
+ | `temp.system.guard` | `compileGuards` (`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
@@ -854,6 +854,84 @@ facet requires acknowledgement 8; a set without it may retain acknowledgement 4.
854
854
  instances whose canonical field tree carries a non-empty `roles` list stamp
855
855
  floor 8.
856
856
 
857
+ ### Model 9 — split guard ID spaces and singular assignment member lists (reader floor: 9)
858
+
859
+ #### Split guard ID spaces
860
+
861
+ A guard whose actions span more than one emitted ID space produces separate
862
+ temporary guard documents: direct create/delete actions preserve authored IDs,
863
+ content updates lock draft IDs, and publish/unpublish gates target published
864
+ IDs. The split prevents one match from applying every action to every ID, but
865
+ it also means retraction must reconstruct more than the historical base guard
866
+ ID. The definition and instance field trees do not grow; model 9 records the
867
+ new compatibility meaning of the existing guard action list, as required by
868
+ rule 4.
869
+
870
+ Manifest feature: `split-guard-id-spaces` (definition + instance,
871
+ reader-floor, detectable, floor 9). The stable marker is a guard whose actions
872
+ occupy at least two of the direct, edit, and lifecycle groups. Definitions and
873
+ instances without such a guard retain the floor derived from their other
874
+ features, normally model 4 or model 8.
875
+
876
+ Would an old reader misread (rule 5)? Yes. A pre-model-9 engine can read the
877
+ unchanged guard definition but only knows the base temporary-guard ID. If it
878
+ advances or aborts an instance after a model-9 engine emitted the sibling
879
+ document, it retracts the base and silently strands the sibling. That stale
880
+ advisory guard can keep an action locked after its stage exited. The instance's
881
+ embedded `definitionSnapshot` makes the feature detectable, so the commit that
882
+ can first deploy split guards stamps floor 9 before deployment; an older engine
883
+ then fails at the instance reader gate, and a concurrent old write loses its
884
+ revision compare-and-swap before it can retract anything. Full persists retain
885
+ the raise-only floor, so a newer engine cannot reopen the old-writer path.
886
+
887
+ Roll out readers first: upgrade every Studio, CLI, MCP server, Function, and
888
+ other engine runtime sharing the workflow resource, then raise the deployment's
889
+ reviewed `expectedMinReaderModel` literal to 9 before deploying a definition
890
+ with a split guard. Existing definitions and instances are safe to leave in
891
+ place: until a model-9 engine commits an affected instance, no split sibling
892
+ exists; that first commit establishes the floor before deploying one. The guard
893
+ document remains the unstamped foreign Content Lake shape—the compatibility
894
+ marker belongs to the engine-owned definition and instance trees.
895
+
896
+ The model-9 vocabulary ledger also records `document` beside `guard` and
897
+ `mutation` as a guard-predicate root. This pins the already-authored foreign
898
+ predicate dialect to the evaluator: `document.before` and `document.after`
899
+ must not disappear from a later reader while stored guard predicates still
900
+ reference them.
901
+
902
+ #### Singular assignment member lists
903
+
904
+ The singular `assignee` value becomes the same ordered user-or-role member
905
+ list as `assignees`, with one cardinality rule: it admits at most one user
906
+ member while role members do not consume the limit. This lets a singular slot
907
+ retain one or more routing roles while one person holds it. An empty value is
908
+ `[]`; append and remove-where ops apply to the list.
909
+
910
+ Like plural and composite assignment values, a singular replacement validates
911
+ new members against the current project directory while retaining already
912
+ stored members that have since left it. Taking or releasing routed work must
913
+ not become impossible merely because another retained member is now stale.
914
+
915
+ Readers permanently accept the model-8-and-earlier object/null representation
916
+ and normalize it to a zero-or-one-element list in memory. Writers partition by
917
+ the frozen definition snapshot's model stamp: instances pinned to definitions
918
+ below model 9 re-encode singular values as object/null for their whole lives,
919
+ while instances pinned to model-9 definitions write lists. A legacy instance
920
+ therefore never mixes representations, and no stored document is backfilled.
921
+ Redeploying is the opt-in migration for future instances; already-running
922
+ instances finish under their pinned definition model.
923
+
924
+ An older reader would interpret the list as an invalid singular value rather
925
+ than the assignment it represents, so this is a reader-floor change. The
926
+ definition tree gains no property: the existing `assignee` kind is the right
927
+ slot and its persisted value evolves. Definitions that declare that kind and
928
+ their instances require model 9. Definitions without it retain the highest
929
+ floor required by their other features, including the unconditional model-4
930
+ instance floor.
931
+
932
+ Manifest feature: `singular-assignee-lists` (definition + instance,
933
+ reader-floor, detectable, floor 9).
934
+
857
935
  ## Pending governed changes
858
936
 
859
937
  - **`temp.system.guard` → `system.guard`** — the guard doc type's `temp.`