@sanity/workflow-engine 0.32.0 → 0.34.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 +195 -0
- package/DATAMODEL.md +144 -8
- package/README.md +2 -2
- package/dist/_chunks-cjs/invariants.cjs +2017 -1891
- package/dist/_chunks-es/invariants.js +2047 -1923
- package/dist/define.cjs +6 -3
- package/dist/define.d.cts +431 -255
- package/dist/define.d.ts +431 -255
- package/dist/define.js +7 -4
- package/dist/index.cjs +745 -210
- package/dist/index.d.cts +845 -429
- package/dist/index.d.ts +845 -429
- package/dist/index.js +737 -214
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,200 @@
|
|
|
1
1
|
# @sanity/workflow-engine
|
|
2
2
|
|
|
3
|
+
## 0.34.0
|
|
4
|
+
|
|
5
|
+
## 0.33.0
|
|
6
|
+
|
|
7
|
+
### Minor Changes
|
|
8
|
+
|
|
9
|
+
- 8874c50: **BREAKING:** A `subject`, `doc.ref`, or `doc.refs` field marked `required: true`
|
|
10
|
+
now requires its selected documents to remain readable after initialization.
|
|
11
|
+
Previously, the flag checked only that an initial value was supplied. Missing
|
|
12
|
+
required targets now produce a fault and prevent normal actions, triggered
|
|
13
|
+
actions, and transitions from advancing, even without an activity requirement
|
|
14
|
+
reading those targets. Abort and direct edits to editable fields remain
|
|
15
|
+
available. Optional references fault only when an unmet runtime condition needs
|
|
16
|
+
their content; completed workflows remain completed.
|
|
17
|
+
|
|
18
|
+
`StuckCause` adds `document-missing`. Update exhaustive handlers before upgrading,
|
|
19
|
+
or their typechecks fail and renderers have no matching branch. Evaluation
|
|
20
|
+
identifies the affected field and reference in `missingDocuments`, with its title
|
|
21
|
+
and completed availability evidence; `blockingMissingDocuments` selects the
|
|
22
|
+
references that prevent progress. The engine checks draft, published, and release
|
|
23
|
+
representations before distinguishing deletion from inaccessible content or
|
|
24
|
+
content outside the workflow perspective. Studio, CLI, and MCP use that same
|
|
25
|
+
result. Failed availability checks report unreadable content and log the cause;
|
|
26
|
+
optional references still permit unrelated actions. Incomplete reads do not
|
|
27
|
+
claim deletion, and an existing task fault keeps
|
|
28
|
+
its recovery controls. The fault clears when the required content becomes
|
|
29
|
+
readable; this release adds no undo-delete operation and does not automatically
|
|
30
|
+
abort workflows.
|
|
31
|
+
|
|
32
|
+
Upgrade every Workflows runtime sharing affected data, including Studio, CLI,
|
|
33
|
+
MCP servers, and deployed Functions, before relying on continued required-target
|
|
34
|
+
availability. Then set the deployment's reviewed `expectedMinReaderModel` to
|
|
35
|
+
`10` before deploying definitions with required content references. Writers stamp
|
|
36
|
+
model 10; this feature requires reader model 10, while documents without it keep
|
|
37
|
+
the floor required by their other features, normally 4, 8, or 9. Existing
|
|
38
|
+
instances remain readable without a backfill and adopt the rule under upgraded
|
|
39
|
+
engines. Their stored floor rises on the next full write, so an older runtime
|
|
40
|
+
can still advance an unstamped existing instance until the fleet is upgraded.
|
|
41
|
+
See `packages/workflow-engine/DATAMODEL.md` for the complete rollout contract.
|
|
42
|
+
|
|
43
|
+
The rendered CLI `show` command evaluates running instances. If evaluation fails,
|
|
44
|
+
it warns and displays stored state; terminal instances and `show --json` retain
|
|
45
|
+
their stored-state behavior.
|
|
46
|
+
|
|
47
|
+
**Docs impact:** Update field requiredness and initialization guidance, required
|
|
48
|
+
reference repair examples, the model-10 readers-first rollout, the diagnostics
|
|
49
|
+
reference for `StuckCause` and `MissingDocument`, CLI `show` and `diagnose`, MCP
|
|
50
|
+
workflow-state guidance, and the Workflows tool and document-view guides for
|
|
51
|
+
loading, deletion, permissions, and perspectives.
|
|
52
|
+
|
|
53
|
+
- 0555271: An effect node can now declare a bounded retry policy the engine enforces on every runtime:
|
|
54
|
+
`retry: {kind: 'engine', attempts, backoff?: {kind: 'fixed' | 'exponential', delayMs}, expiryMs?}`.
|
|
55
|
+
The block's `kind` names who runs the policy and `engine` is the only accepted member; authoring may
|
|
56
|
+
omit it, as with `start.kind`, and `defineWorkflow` fills it in while desugaring, so a stored block
|
|
57
|
+
without it fails to parse at deploy. `attempts` is the total number of attempts
|
|
58
|
+
including the first, `backoff` is the wait between two attempts
|
|
59
|
+
(`delayMs` every time under `fixed`, doubled per attempt already made under `exponential`), and
|
|
60
|
+
`expiryMs` decides whether a further attempt may start: the engine stops rather than begin a wait
|
|
61
|
+
that would carry the run past it, so an attempt can go unused. It is not a handler timeout: an
|
|
62
|
+
attempt already running is never interrupted, so a run can finish after the window and a success
|
|
63
|
+
then still counts.
|
|
64
|
+
|
|
65
|
+
The whole policy runs inside one `drainEffects` call, under one claim whose lease is renewed across
|
|
66
|
+
each wait, and **every attempt invokes the handler again**. That is a third cause of the
|
|
67
|
+
at-least-once repetition handlers already tolerate, and no `effectHistory` row sits between two
|
|
68
|
+
attempts, so derive external-system identifiers from `ctx.effectKey` rather than checking history
|
|
69
|
+
to deduplicate. Two
|
|
70
|
+
things stop a run without settling it, both reported in the drain's `lost` bucket. A lease this
|
|
71
|
+
drainer can no longer renew leaves the entry pending, for a later drain to pick up once the lease
|
|
72
|
+
lapses. An entry another party completed or cancelled during a backoff is already settled by that
|
|
73
|
+
party, and the handler is not invoked again.
|
|
74
|
+
Otherwise the run settles in the single `effectCompleted` row the drain has always written: `failed`
|
|
75
|
+
with a `detail` naming the attempts used and the last error (`failed after 3 of 3 attempts: gateway
|
|
76
|
+
timeout`), or `done` with a `detail` naming the attempt that succeeded when a later one did. A
|
|
77
|
+
policy changes no routing rule, since `$effectStatus['<name>'] == 'failed'` routes the instance
|
|
78
|
+
exactly as a single failure does. It does change which outcome those rules see, because an attempt
|
|
79
|
+
after the first that succeeds makes the status `done`. Adding a policy to an effect a `'failed'`
|
|
80
|
+
branch already watches is therefore a behaviour change, not only a latency change. Nothing
|
|
81
|
+
about an attempt is persisted, so no scheduler is involved and no per-attempt history rows appear.
|
|
82
|
+
An effect that declares no `retry` is unchanged: its handler's first failure settles it.
|
|
83
|
+
|
|
84
|
+
The waits are the engine's only deliberate pause, so `createEngine({sleep})` now takes a `Sleeper`
|
|
85
|
+
that decides how they are spent. Production omits it and spends real time; a deterministic harness
|
|
86
|
+
passes one that moves its `clock` forward instead, which is what the engine test bench does, so a
|
|
87
|
+
paced policy runs in a test without waiting.
|
|
88
|
+
|
|
89
|
+
**No upgrade action required** to keep existing definitions working; `sweepStaleClaims` and every
|
|
90
|
+
history shape are unchanged, and `drainEffects` keeps its signature and result shape. Two things to
|
|
91
|
+
weigh before adopting it. The
|
|
92
|
+
waits are real time inside the drain's invocation, so on a Functions host the policy has to fit
|
|
93
|
+
that function's timeout. Handing the policy to a Durable Functions host's own retry strategy
|
|
94
|
+
instead is future work in the generated runtime, not part of this contract; today every host waits
|
|
95
|
+
inside the invocation. And **declaring `retry` raises the definition's reader floor to model 10**:
|
|
96
|
+
`DATA_MODEL_VERSION` and `DATA_MODEL_MAX_READER` both move to 10, so upgrade every Studio, CLI, MCP
|
|
97
|
+
server, Function, and other runtime sharing that workflow resource, then raise the reviewed
|
|
98
|
+
`expectedMinReaderModel` literal to `10`, then deploy. A pre-model-10 reader would ignore the
|
|
99
|
+
policy and settle the effect on its first failure. Deploy refuses a `retry` definition against an
|
|
100
|
+
acknowledgement below 10, one whose backoff cannot fit its declared `attempts` inside `expiryMs`,
|
|
101
|
+
and one whose `expiryMs` or accumulated backoff exceeds **366 days**, since the whole policy runs
|
|
102
|
+
inside one `drainEffects` invocation and no host offers a single execution longer than a year.
|
|
103
|
+
|
|
104
|
+
**Docs impact:** Update the effects concept in the Workflows concepts guide to describe retry as an
|
|
105
|
+
in-invocation loop and name the per-host cost (the functions timeout, and durables delegation as future work), add `retry`
|
|
106
|
+
and its one-year span ceiling to the effect-node reference, note the loop in the `drainEffects` protocol entry along with what
|
|
107
|
+
the `lost` bucket now covers, list the `sleep` option in the `createEngine` reference, and add the retry
|
|
108
|
+
feature to the crossing-to-model-10 section of the reader-model rollout guide. A cookbook recipe for a bounded
|
|
109
|
+
external call should show the `retry` node instead of the counter-field retry loop.
|
|
110
|
+
|
|
111
|
+
- b3b2797: Where the generated unattended runtime hosts a workflow or an effect is declared as one `runtime` block on three authoring nodes: the deployment in `sanity.workflow.ts`, the workflow in `defineWorkflow`, and the effect node. `kind` is `'function'` (a plain Sanity Function), `'durableFunction'` (a durable one), or `'selfHosted'` (a process you run yourself). Each level inherits from the one above, so a workflow that declares no block takes its deployment's kind and an effect takes its workflow's. Omit it everywhere and everything hosts on `'function'`. `kind` is the whole block at the deployment and workflow levels; an effect's `'function'` block also carries the function budget, `timeout` in seconds and `memory` in megabytes, and an effect that declares either gets its own drain function so an expensive handler's budget does not size every other handler's. An unknown kind fails at `defineWorkflow` or `defineWorkflowConfig`, before any deploy runs, naming the three accepted members; a budget on any other kind is rejected there too.
|
|
112
|
+
|
|
113
|
+
`@sanity/workflow-blueprint`'s emission plan groups by resolved kind. A `'function'` workflow gets the drain, the heartbeat, and the start watcher it always got. A `'durableFunction'` workflow or effect is listed under `planned.hosting` and emits nothing yet, while the rest of the deployment emits normally, with no error and no whole-deployment refusal. A `'selfHosted'` workflow or effect emits nothing, is skipped by the generated drains, is left out of the generated handler registry, and is reported under `planned.selfHosted`, which names the deadlines that process must tick, the effects it must drain, the parents it must re-evaluate when a child settles, and the autonomous definitions it must start. Two definitions in one deployment that host the same effect name differently fail generation, because one effect name has one handler and one budget. A start watcher is emitted for a durable workflow as well as a function-hosted one, because starting an instance does not depend on the host.
|
|
114
|
+
|
|
115
|
+
The block is authoring-only. `defineWorkflow` returns the stored definition plus the `runtime` the generator reads, and the deploy strips it: it never reaches the Content Lake, the stored definition schema rejects it, and two definitions that differ only in a hosting kind fingerprint identically, so changing a kind never mints a definition version. It does change the emitted tree, so regenerate afterwards and delete the function directories the new tree no longer contains.
|
|
116
|
+
|
|
117
|
+
**No upgrade action required.** Nothing this release replaces has been published: the deployment `runtime` block and the `@sanity/workflow-blueprint/generate` entry point that exposed the earlier planning helpers both ship for the first time here. Declare a block only where you want to move a workflow or an effect off the default.
|
|
118
|
+
|
|
119
|
+
**Docs impact:** Document the `runtime` block once, at all three levels, in the deployment configuration reference beside `tag`, `workflowResource`, and `resourceAliases`: each kind, what the generator emits for it, and the effect-level `timeout` and `memory` units. Add the four hosting shapes (function hosted, durable hosted, self hosted with one workflow kept on functions, and mixed inside one workflow) to the generated-runtime guide, and state there that hosting decides whether a `$now` deadline causes a heartbeat to be emitted rather than which instances an emitted heartbeat ticks. State in the data-model concepts that `runtime` is the authoring-only block tooling reads and the deploy never stores.
|
|
120
|
+
|
|
121
|
+
- 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.
|
|
122
|
+
|
|
123
|
+
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.
|
|
124
|
+
|
|
125
|
+
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.
|
|
126
|
+
|
|
127
|
+
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.
|
|
128
|
+
|
|
129
|
+
**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.
|
|
130
|
+
|
|
131
|
+
### Patch Changes
|
|
132
|
+
|
|
133
|
+
- 393ac71: CLI and MCP telemetry now includes `context.surface` (`cli` or `mcp`) and
|
|
134
|
+
execution mode in each event's `context.environment`. Dashboards can include
|
|
135
|
+
shell and engine activity while distinguishing it from SDK events marked
|
|
136
|
+
`sdk`. Existing command trace context is preserved.
|
|
137
|
+
|
|
138
|
+
All three surfaces report `production` for `NODE_ENV=production`, and
|
|
139
|
+
`development` for `development` or `test`. Unset, empty, and unrecognized values
|
|
140
|
+
default to `production` for CLI and MCP execution mode, and `development` for
|
|
141
|
+
SDK build mode. SDK environment classification is unchanged. When comparing
|
|
142
|
+
activity across surfaces, group or filter by surface alongside environment.
|
|
143
|
+
Environment does not identify a production dataset or deployment; API host,
|
|
144
|
+
dataset name, and workflow tag do not set it.
|
|
145
|
+
|
|
146
|
+
**No upgrade action required.** Existing telemetry consent and opt-out
|
|
147
|
+
settings still apply. Historical events are unchanged.
|
|
148
|
+
|
|
149
|
+
**Docs impact:** Update CLI, MCP, and SDK telemetry guidance to explain surface
|
|
150
|
+
context, build versus execution mode, the explicit defaults, and how to
|
|
151
|
+
interpret environment when comparing activity across surfaces.
|
|
152
|
+
|
|
153
|
+
- 7eb9eca: Correct the API references for field initialization and edits, start requirements,
|
|
154
|
+
transitions, reference IDs, effect handling, reactive state, member controls,
|
|
155
|
+
Studio mappings, test helpers, and GROQ condition outcomes. The references state
|
|
156
|
+
caller constraints and defaults that were missing or incorrect. Package setup
|
|
157
|
+
guidance identifies the public npm packages and supported deployment command;
|
|
158
|
+
the MCP validation description distinguishes validation from deployment checks.
|
|
159
|
+
Runtime behavior and API signatures are unchanged.
|
|
160
|
+
|
|
161
|
+
**No upgrade action required.**
|
|
162
|
+
|
|
163
|
+
**Docs impact:** After release and reference sync, reconcile the modeling,
|
|
164
|
+
runtime, reactive UI, Studio, testing, deployment, MCP, and evaluation-insight
|
|
165
|
+
guides and references with the corrected contracts. Fix affected examples and
|
|
166
|
+
replace redundant API inventories with verified symbol links while preserving
|
|
167
|
+
useful teaching and the CLI/MCP reference material not exposed by TypeDoc.
|
|
168
|
+
|
|
169
|
+
- 2cef086: Internal maintenance consolidates engine action/edit handling and Studio document
|
|
170
|
+
and workflow title sorting. Action and field-edit calls retain their inputs and
|
|
171
|
+
results, and document and workflow titles retain their ordering. Action
|
|
172
|
+
availability, telemetry, and persisted document formats are unchanged.
|
|
173
|
+
|
|
174
|
+
**No upgrade action required.**
|
|
175
|
+
|
|
176
|
+
**Docs impact: None** because public APIs, configuration, and workflow behavior
|
|
177
|
+
are unchanged.
|
|
178
|
+
|
|
179
|
+
- 232f811: Workflows can resolve reviewer attributes from self-hosted studios without
|
|
180
|
+
sending the attributes request to a global API host that rejects their origin.
|
|
181
|
+
Actor resolution uses the account-global identity carried by the project user
|
|
182
|
+
response when available, avoiding an unnecessary global request.
|
|
183
|
+
|
|
184
|
+
Custom clients without `withConfig` can also resolve organization attributes
|
|
185
|
+
when they already serve the engine's required API version.
|
|
186
|
+
|
|
187
|
+
**No upgrade action required.** Existing project CORS settings and workflow
|
|
188
|
+
definitions continue to apply.
|
|
189
|
+
|
|
190
|
+
**Docs impact:** Update Workflows prerelease troubleshooting guidance to describe
|
|
191
|
+
the fix for assignee loading on self-hosted studios and advise upgrading the
|
|
192
|
+
Workflows packages together. Document the version requirement for custom clients
|
|
193
|
+
in the engine client reference.
|
|
194
|
+
|
|
195
|
+
- Updated dependencies [7eb9eca]
|
|
196
|
+
- @sanity/groq-condition-describe@0.5.1
|
|
197
|
+
|
|
3
198
|
## 0.32.0
|
|
4
199
|
|
|
5
200
|
### Minor Changes
|
package/DATAMODEL.md
CHANGED
|
@@ -66,7 +66,9 @@ never matches the new type).
|
|
|
66
66
|
|
|
67
67
|
- **`modelVersion` — provenance ("conforms-to").** The value of
|
|
68
68
|
`DATA_MODEL_VERSION` at write time: "this document conforms to model N
|
|
69
|
-
now".
|
|
69
|
+
now". Advances for **every** declared shape change, additive included,
|
|
70
|
+
though several changes merged into one unreleased model share its number
|
|
71
|
+
(rule 9).
|
|
70
72
|
Instances are re-stamped on every full persist (and rollback restore).
|
|
71
73
|
Partial patches deliberately leave the pair alone — they don't normalize
|
|
72
74
|
the shape, so restamping there would lie.
|
|
@@ -210,6 +212,13 @@ these principles before the change-process rules below apply.
|
|
|
210
212
|
definition grammar has no kebab-case values, and no addition introduces
|
|
211
213
|
the first.
|
|
212
214
|
|
|
215
|
+
One block sits outside the persisted language entirely: `runtime` declares where
|
|
216
|
+
the generated unattended runtime hosts a workflow or an effect, tooling reads it
|
|
217
|
+
at authoring and generation time, and the deploy strips it so nothing reaches the
|
|
218
|
+
Content Lake. One rule decides the category for any new property: it is stored
|
|
219
|
+
if the engine reads it while running, and authoring-only if only tooling reads it
|
|
220
|
+
before deploy.
|
|
221
|
+
|
|
213
222
|
## Rules for changing the model
|
|
214
223
|
|
|
215
224
|
`DATA_MODEL_CHANGES` is the append-only, machine-readable counterpart of the
|
|
@@ -234,11 +243,12 @@ retained model-4 baseline.
|
|
|
234
243
|
log below, and decide — in the same change — whether `DATA_MODEL_VERSION`
|
|
235
244
|
moves **and whether the reader floor moves with it**. Nothing lands
|
|
236
245
|
"incidentally".
|
|
237
|
-
2. **Additive optional fields are allowed** without machinery:
|
|
238
|
-
`DATA_MODEL_VERSION
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
246
|
+
2. **Additive optional fields are allowed** without machinery: land on the
|
|
247
|
+
current model per rule 9 — bumping `DATA_MODEL_VERSION` when that model has
|
|
248
|
+
shipped and joining it when it has not — leave the reader constants
|
|
249
|
+
untouched, and declare the tolerant read (what an absent value means on
|
|
250
|
+
docs written before the field existed) plus why the field survives an old
|
|
251
|
+
writer's round-trip.
|
|
242
252
|
3. **Renames, removals, and semantic changes are not allowed yet.** They
|
|
243
253
|
require versioned upgrade machinery (per-model normalization steps applied
|
|
244
254
|
on read) that does not exist; until it does, the model may only grow
|
|
@@ -246,8 +256,9 @@ retained model-4 baseline.
|
|
|
246
256
|
documents' reader floor — through `DATA_MODEL_MIN_READER` when no reliable
|
|
247
257
|
marker exists — and ships only after the fleet story is written.
|
|
248
258
|
4. **`DATA_MODEL_VERSION` moves when compatibility reasoning changes** — when
|
|
249
|
-
a reader would need to know which shape it is looking at
|
|
250
|
-
internal refactor that provably writes an
|
|
259
|
+
a reader would need to know which shape it is looking at, and that model has
|
|
260
|
+
already shipped (rule 9). A purely internal refactor that provably writes an
|
|
261
|
+
identical tree does not move it.
|
|
251
262
|
5. **Classify by misinterpretation, not parseability.** The snapshot gate
|
|
252
263
|
catches structural drift only. A change can be structurally additive yet
|
|
253
264
|
change the MEANING of existing fields — old readers parse it fine and
|
|
@@ -272,6 +283,11 @@ retained model-4 baseline.
|
|
|
272
283
|
existing slot, value, or variable could carry the behavior. "The existing
|
|
273
284
|
mechanism is illegible or undiscoverable to surfaces" is an argument for
|
|
274
285
|
restructuring that mechanism, not for a sibling property beside it.
|
|
286
|
+
9. **A model number is assigned per release, not per merged change.** A change
|
|
287
|
+
merged before its model has shipped joins that model's log entry and its
|
|
288
|
+
ledger instead of opening a new number, so one release carries one model.
|
|
289
|
+
Reworking a ledger that has not shipped is the one exception to
|
|
290
|
+
append-only; once a model is released its entry and ledger are frozen.
|
|
275
291
|
|
|
276
292
|
## Model log
|
|
277
293
|
|
|
@@ -932,6 +948,126 @@ instance floor.
|
|
|
932
948
|
Manifest feature: `singular-assignee-lists` (definition + instance,
|
|
933
949
|
reader-floor, detectable, floor 9).
|
|
934
950
|
|
|
951
|
+
### Model 10 — required content-reference availability and bounded effect retry (reader floor: 10)
|
|
952
|
+
|
|
953
|
+
#### Required content-reference availability
|
|
954
|
+
|
|
955
|
+
A workflow-scope `subject`, `doc.ref`, or `doc.refs` field marked `required: true`
|
|
956
|
+
requires its selected targets to remain readable in the workflow's perspective
|
|
957
|
+
after initialization. Missing targets produce a derived fault and prevent normal
|
|
958
|
+
actions, triggered actions, and transitions from advancing. Abort and direct
|
|
959
|
+
field repair remain available. Other field kinds retain the input-presence
|
|
960
|
+
contract; optional references retain their authored runtime requirements.
|
|
961
|
+
|
|
962
|
+
This is an explicitly approved extension of the existing `required` contract.
|
|
963
|
+
It changes interpretation without changing stored field shapes. Existing
|
|
964
|
+
reference values and frozen definition snapshots remain readable without
|
|
965
|
+
normalization or backfill. The behavior applies when the upgraded engine reads
|
|
966
|
+
an existing instance as well as when it starts a new one. The same word and
|
|
967
|
+
shape carry requiredness; no parallel definition property is introduced.
|
|
968
|
+
|
|
969
|
+
Manifest feature: `required-content-references` (definition + instance,
|
|
970
|
+
reader-floor, detectable, floor 10). The marker is a workflow-scope required
|
|
971
|
+
content-reference declaration, including one in an instance's frozen definition
|
|
972
|
+
snapshot. New writes stamp model 10. Documents without this feature retain the
|
|
973
|
+
floor required by their other features, normally 4, 8, or 9.
|
|
974
|
+
|
|
975
|
+
Would an old reader misread (rule 5)? Yes. Older engines interpret `required`
|
|
976
|
+
only at initialization and can advance after a required target disappears.
|
|
977
|
+
Upgrade all runtimes sharing affected workflows before relying on this rule,
|
|
978
|
+
then acknowledge reader model 10 before deploying affected definitions. A new
|
|
979
|
+
engine's next full instance write raises the stored floor to 10. Existing
|
|
980
|
+
instances are not backfilled, so until that write the stored floor alone cannot
|
|
981
|
+
prevent an older runtime from advancing them; the readers-first rollout is
|
|
982
|
+
required for those existing workflows too. Unknown fields and frozen snapshots
|
|
983
|
+
survive full persists unchanged, and the floor remains raise-only.
|
|
984
|
+
|
|
985
|
+
#### Bounded effect retry policy
|
|
986
|
+
|
|
987
|
+
An effect node may declare `retry: {kind, attempts, backoff?: {kind, delayMs},
|
|
988
|
+
expiryMs?}`. The block's `kind` says who runs the policy, and `engine` is its
|
|
989
|
+
only member: the stored schema requires the discriminator and rejects any
|
|
990
|
+
other value, and an authored block may omit it because desugar fills `engine`
|
|
991
|
+
in, the same shape `start.kind` has. `attempts` is the total number of
|
|
992
|
+
attempts, the first included. `backoff.kind` is `fixed` or `exponential`, and
|
|
993
|
+
`delayMs` is the wait between two attempts: the same every time under `fixed`,
|
|
994
|
+
doubled per attempt already made under `exponential`. `expiryMs` decides
|
|
995
|
+
whether a further attempt may start rather than bounding the run, so an
|
|
996
|
+
attempt already running is never interrupted.
|
|
997
|
+
|
|
998
|
+
The accepted range is bounded on one side: `expiryMs` and the backoff a policy
|
|
999
|
+
accumulates are each capped at 366 days. The whole policy runs inside one
|
|
1000
|
+
`drainEffects` invocation and no host offers a single execution longer than a
|
|
1001
|
+
year, so a policy declaring more than that could never run as written wherever
|
|
1002
|
+
it ran. The caps weigh declared waits alone, because a handler's
|
|
1003
|
+
duration is unknown at deploy; at runtime every elapsed millisecond counts
|
|
1004
|
+
against `expiryMs`, handler time included. This
|
|
1005
|
+
is a tightening of the accepted values in a new field, not a reshape: no
|
|
1006
|
+
previously valid stored tree becomes invalid, because nothing before model 10
|
|
1007
|
+
could carry a `retry` block at all. Bounding the span also keeps every deadline
|
|
1008
|
+
the engine stamps from it inside the range a timestamp can carry.
|
|
1009
|
+
|
|
1010
|
+
The discriminator is there from the start because who runs the policy is the
|
|
1011
|
+
one thing about it that will vary, and a later member is then a new value in
|
|
1012
|
+
this slot rather than a second mechanism beside it. A `caller` member — the
|
|
1013
|
+
engine holding the claim and the expiry ceiling while surfacing each failure
|
|
1014
|
+
to whoever called `drainEffects` — is a separate, additive change under its
|
|
1015
|
+
own ticket.
|
|
1016
|
+
|
|
1017
|
+
This is a definition-tree addition only. The policy runs inside one
|
|
1018
|
+
`drainEffects` call: the drain dispatches, waits the declared backoff on a
|
|
1019
|
+
failure that has attempts left, renews the claim's lease across the wait, and
|
|
1020
|
+
dispatches again, stopping when `attempts` are used up, or rather than start
|
|
1021
|
+
a wait that would carry the run past `expiryMs`. Nothing about an attempt is
|
|
1022
|
+
persisted, because nothing needs to survive the call — the run is one dispatch
|
|
1023
|
+
as far as the instance is concerned, and it ends in the `effectCompleted` row
|
|
1024
|
+
the engine already writes.
|
|
1025
|
+
That row's `detail` names how the run ended: the attempts used and the last
|
|
1026
|
+
error on a failure, or the attempt that succeeded when a later one did. No
|
|
1027
|
+
instance field, history row, or vocabulary value is added or changed.
|
|
1028
|
+
|
|
1029
|
+
An absent `retry` preserves the model-9 meaning exactly: a failing handler
|
|
1030
|
+
completes the effect as failed on its first attempt, and a claim abandoned by
|
|
1031
|
+
a dead drainer is redispatched without limit under `effects.leaseMs`. That
|
|
1032
|
+
recovery path is also unchanged for an effect that declares a policy — a
|
|
1033
|
+
drainer that dies mid-run leaves a lapsed claim, the sweep releases it, and
|
|
1034
|
+
the next drain starts a fresh run.
|
|
1035
|
+
|
|
1036
|
+
Why the existing vocabulary could not carry this (rule 8). The engine's only
|
|
1037
|
+
retry control was `effects.leaseMs`, a runtime construction option and one
|
|
1038
|
+
global number for every effect a deployment drains; it cannot say that a
|
|
1039
|
+
payout gets five paced attempts and a notification gets one. The definition
|
|
1040
|
+
could already express a retry LOOP — a counter field, an incrementing op, and
|
|
1041
|
+
transitions reading `$effectStatus` — but that re-enters a stage and queues a
|
|
1042
|
+
new effect entry each pass, which is a different thing: it retries the
|
|
1043
|
+
workflow step, not the dispatch. This policy governs how one queued entry is
|
|
1044
|
+
dispatched, and no existing slot speaks about a dispatch. `retry` is a new
|
|
1045
|
+
value in the existing effect node rather than a sibling mechanism: it
|
|
1046
|
+
introduces no error type, no ordering rule against an existing gate, and no
|
|
1047
|
+
verdict leg. Its failure is the effect-failure outcome that already exists,
|
|
1048
|
+
and its routing is the `$effectStatus` variable that already exists.
|
|
1049
|
+
|
|
1050
|
+
Would an old reader misread (rule 5)? Yes. A pre-model-10 engine reading a
|
|
1051
|
+
definition preserves the unknown `retry` property and ignores it, so a handler
|
|
1052
|
+
failure completes the effect on the first attempt: an author who declared five
|
|
1053
|
+
attempts silently gets one, and the instance takes its failure branch four
|
|
1054
|
+
attempts early. That is a silent wrong outcome rather than a refused read,
|
|
1055
|
+
which is exactly what the floor exists for. Instances need no separate
|
|
1056
|
+
judgment — the policy writes no instance shape a model-9 reader has not seen.
|
|
1057
|
+
|
|
1058
|
+
The marker is an effect node carrying `retry`, detectable in a definition
|
|
1059
|
+
document's own tree and in an instance's embedded `definitionSnapshot`. An
|
|
1060
|
+
instance derives the floor from its pinned definition, so an instance of a
|
|
1061
|
+
policy-bearing definition is held to the same reader as the definition itself.
|
|
1062
|
+
Definitions and instances without a `retry` effect retain the floor their
|
|
1063
|
+
other features derive, normally model 4, 8 or 9, or model 10 through the
|
|
1064
|
+
required-reference feature beside it.
|
|
1065
|
+
|
|
1066
|
+
Manifest feature: `effect-retry-policy` (definition + instance, reader-floor,
|
|
1067
|
+
detectable, floor 10). The instance document type is listed because an
|
|
1068
|
+
instance inherits its pinned definition's floor, not because the policy adds
|
|
1069
|
+
an instance shape.
|
|
1070
|
+
|
|
935
1071
|
## Pending governed changes
|
|
936
1072
|
|
|
937
1073
|
- **`temp.system.guard` → `system.guard`** — the guard doc type's `temp.`
|
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@ Workflow / BPM engine for Sanity content. Define workflows as data, run them as
|
|
|
4
4
|
instances against a Sanity client, gate transitions on GROQ filters, and queue
|
|
5
5
|
effects for runtimes to drain.
|
|
6
6
|
|
|
7
|
-
> **Status:** 0
|
|
8
|
-
>
|
|
7
|
+
> **Status:** Pre-1.0 and publicly available on npm. The API may change between
|
|
8
|
+
> minor versions.
|
|
9
9
|
|
|
10
10
|
## Installation
|
|
11
11
|
|