@sanity/workflow-engine-test 0.12.0 → 0.13.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,40 @@
1
1
  # @sanity/workflow-engine-test
2
2
 
3
+ ## 0.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 00c089d: **BREAKING:** Require every definition deployment and workflow deployment configuration to state an `expectedMinReaderModel` literal that exactly matches the installed engine's writer capability.
8
+
9
+ 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.
10
+
11
+ - 55a54b3: Surfaces understand the new `subject` field kind. Studio plugin: the start dialog's default subject seed lands on a definition's `subject`-kind entry whatever it is named (the doc.ref-named-`subject` convention stays the fallback), subject entries get the document picker / read-only face / state-panel preview / document pill, and mapping validation checks the subject entry's `types` facet. MCP: the subject lookup behind title enrichment prefers the `subject`-kind entry and keeps the name convention as fallback; the authoring guide teaches the kind. Test bench — **BREAKING:** `subjectField` now builds a `subject`-KIND entry value (matching its name and the engine's subject rule); a definition declaring the doc.ref convention needs the new `docRefField(documentId, {name})` instead. `instancesForSubject` matches both single-ref kinds.
12
+
13
+ ### Patch Changes
14
+
15
+ - 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`.
16
+ - 9126299: **BREAKING:** Delete persisted stage guard documents when their stage exits, and remove the lifted-record `GUARD_LIFTED_PREDICATE` and `isGuardLifted` exports.
17
+
18
+ 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.
19
+
20
+ - Updated dependencies [239d9f6]
21
+ - Updated dependencies [3af2ca0]
22
+ - Updated dependencies [7276702]
23
+ - Updated dependencies [58f8211]
24
+ - Updated dependencies [00c089d]
25
+ - Updated dependencies [71cd58e]
26
+ - Updated dependencies [cd03973]
27
+ - Updated dependencies [d26cc1a]
28
+ - Updated dependencies [9126299]
29
+ - Updated dependencies [72018af]
30
+ - Updated dependencies [5cd8ffd]
31
+ - Updated dependencies [b82bda4]
32
+ - Updated dependencies [55a54b3]
33
+ - Updated dependencies [b82bda4]
34
+ - Updated dependencies [4490442]
35
+ - Updated dependencies [1540dce]
36
+ - @sanity/workflow-engine@0.18.0
37
+
3
38
  ## 0.12.0
4
39
 
5
40
  ### Minor Changes
package/README.md CHANGED
@@ -37,7 +37,7 @@ test reads as the workflow it exercises:
37
37
 
38
38
  ```ts
39
39
  const bench = createBench({now: '2026-01-01T00:00:00Z'})
40
- await bench.deployDefinitions({definitions: [reviewWorkflow]})
40
+ await bench.deployDefinitions({expectedMinReaderModel: 2, definitions: [reviewWorkflow]})
41
41
 
42
42
  const {instance} = await bench.startInstance({
43
43
  definition: 'review-workflow',
@@ -72,7 +72,7 @@ raw `workflow.*` calls, and the default tag ships as the `BENCH_TAG` constant.
72
72
  ## Mutation guards
73
73
 
74
74
  A workflow stage can deploy **mutation guards** — `temp.system.guard` documents
75
- that lock the documents it governs while the stage is active, and lift when it
75
+ that lock the documents it governs while the stage is active, and are deleted when it
76
76
  exits. The lake does not enforce this doc type yet: in production, guard
77
77
  enforcement is the engine's own **optimistic** evaluation (advisory verdicts
78
78
  and engine-side write denials), and the lake ACL is the only hard boundary.
package/dist/index.cjs CHANGED
@@ -304,7 +304,7 @@ function createReadHelpers(scope) {
304
304
  ...engineScope,
305
305
  ...args
306
306
  }),
307
- instancesForSubject: async (subjectRef, entryName = "subject") => client.fetch(`*[_type == "${workflowEngine.WORKFLOW_INSTANCE_TYPE}"\n && fields[_type == "doc.ref" && name == $entry][0].value.id == $ref\n && ${workflowEngine.tagScopeFilter()}] | order(startedAt asc)`, {
307
+ instancesForSubject: async (subjectRef, entryName = "subject") => client.fetch(`*[_type == "${workflowEngine.WORKFLOW_INSTANCE_TYPE}"\n && fields[_type in ["doc.ref", "subject"] && name == $entry][0].value.id == $ref\n && ${workflowEngine.tagScopeFilter()}] | order(startedAt asc)`, {
308
308
  ref: subjectRef,
309
309
  entry: entryName,
310
310
  tag: tag
@@ -354,7 +354,7 @@ function createBenchEngine(bench, overrides = {}) {
354
354
 
355
355
  function subjectField(documentId, opts) {
356
356
  return {
357
- type: "doc.ref",
357
+ type: "subject",
358
358
  name: opts?.name ?? "subject",
359
359
  value: workflowEngine.gdrRef({
360
360
  res: DEFAULT_WORKFLOW_RESOURCE,
@@ -364,6 +364,18 @@ function subjectField(documentId, opts) {
364
364
  };
365
365
  }
366
366
 
367
+ function docRefField(documentId, opts) {
368
+ return {
369
+ type: "doc.ref",
370
+ name: opts.name,
371
+ value: workflowEngine.gdrRef({
372
+ res: DEFAULT_WORKFLOW_RESOURCE,
373
+ documentId: documentId,
374
+ type: opts.type ?? "document"
375
+ })
376
+ };
377
+ }
378
+
367
379
  function releaseField(releaseName, opts) {
368
380
  return {
369
381
  type: "release.ref",
@@ -392,6 +404,7 @@ async function startedInstanceFixture(bench, opts) {
392
404
  } ]
393
405
  });
394
406
  await bench.deployDefinitions({
407
+ expectedMinReaderModel: 2,
395
408
  definitions: [ flow ]
396
409
  });
397
410
  const {instance: instance} = await bench.startInstance({
@@ -505,6 +518,8 @@ exports.createBench = createBench;
505
518
 
506
519
  exports.createBenchEngine = createBenchEngine;
507
520
 
521
+ exports.docRefField = docRefField;
522
+
508
523
  exports.releaseField = releaseField;
509
524
 
510
525
  exports.startedInstanceFixture = startedInstanceFixture;
package/dist/index.d.cts CHANGED
@@ -276,6 +276,19 @@ export declare interface CreateBenchOptions {
276
276
  /** Default workflow resource for benches that don't pass one. */
277
277
  export declare const DEFAULT_WORKFLOW_RESOURCE: WorkflowResource;
278
278
 
279
+ /**
280
+ * A plain `doc.ref` entry value in the bench's default resource — for
281
+ * document-reference inputs that are NOT the workflow's subject (the
282
+ * `subject` kind is what marks THE subject; see {@link subjectField}).
283
+ */
284
+ export declare function docRefField(
285
+ documentId: string,
286
+ opts: {
287
+ name: string;
288
+ type?: string;
289
+ },
290
+ ): InitialFieldValue;
291
+
279
292
  /**
280
293
  * The write {@link GuardHelpers.editDocument} performs. Every arm is a real
281
294
  * distinct client write the guard seam vets under that action — `create` is
@@ -335,6 +348,7 @@ declare type EngineWrappers = {
335
348
  * so the fixture module needs no import of the bench itself. */
336
349
  declare interface FixtureBench {
337
350
  deployDefinitions: (args: {
351
+ expectedMinReaderModel: 2;
338
352
  definitions: WorkflowDefinition[];
339
353
  }) => Promise<unknown>;
340
354
  startInstance: (args: {
@@ -350,8 +364,8 @@ declare type GuardHelpers = {
350
364
  listGuards: () => Promise<MutationGuardDoc[]>;
351
365
  /**
352
366
  * Guards that would deny an arbitrary edit to `documentId` right now (i.e.
353
- * active locks). Probes by changing every non-system field; a lifted guard
354
- * (predicate `"true"`) returns nothing.
367
+ * active locks). Probes by changing every non-system field; a guard whose
368
+ * predicate is `"true"` returns nothing.
355
369
  */
356
370
  activeGuardsForDocument: (
357
371
  documentId: string,
@@ -447,10 +461,10 @@ declare type ReadHelpers = {
447
461
  */
448
462
  children: (args: ChildrenArgs) => Promise<WorkflowInstance[]>;
449
463
  /**
450
- * All workflow instances whose `doc.ref` field entry named
451
- * `entryName` (default `"subject"`) matches the given doc id. Useful
452
- * for "what workflow is running on this document?" questions. Pass
453
- * `entryName` for workflows whose subject entry uses a different name.
464
+ * All workflow instances whose single-ref field entry (`subject` kind or
465
+ * `doc.ref`) named `entryName` (default `"subject"`) matches the given doc
466
+ * id. Useful for "what workflow is running on this document?" questions.
467
+ * Pass `entryName` for workflows whose subject entry uses a different name.
454
468
  */
455
469
  instancesForSubject: (
456
470
  subjectRef: string,
@@ -478,7 +492,7 @@ declare type ReadHelpers = {
478
492
  /**
479
493
  * The startable half of {@link ReadHelpers.instancesForDocument}: the
480
494
  * latest deployed version of every definition applicable to the LOADED
481
- * candidate document — startable, a required subject entry accepts its
495
+ * candidate document — startable, the `subject`-kind entry accepts its
482
496
  * `_type`, `start.filter` (browse-time-pure) passes. All matches, name
483
497
  * ascending.
484
498
  */
@@ -530,9 +544,12 @@ declare type StoreDocument = {
530
544
  };
531
545
 
532
546
  /**
533
- * The conventional workflow-scope `subject` doc.ref entry, keyed by a bare
534
- * doc id in the bench's default resource. `type` defaults to `'document'`;
535
- * pass the subject's schema type when a filter or projection reads it.
547
+ * A workflow-scope `subject`-KIND entry value, keyed by a bare doc id in the
548
+ * bench's default resource what a definition declaring the first-class
549
+ * `subject` kind expects at start (applicability and document pickers key on
550
+ * that kind). `type` defaults to `'document'`; pass the subject's schema
551
+ * type when a filter or projection reads it. For a plain `doc.ref` input,
552
+ * use {@link docRefField}.
536
553
  */
537
554
  export declare function subjectField(
538
555
  documentId: string,
package/dist/index.d.ts CHANGED
@@ -276,6 +276,19 @@ export declare interface CreateBenchOptions {
276
276
  /** Default workflow resource for benches that don't pass one. */
277
277
  export declare const DEFAULT_WORKFLOW_RESOURCE: WorkflowResource;
278
278
 
279
+ /**
280
+ * A plain `doc.ref` entry value in the bench's default resource — for
281
+ * document-reference inputs that are NOT the workflow's subject (the
282
+ * `subject` kind is what marks THE subject; see {@link subjectField}).
283
+ */
284
+ export declare function docRefField(
285
+ documentId: string,
286
+ opts: {
287
+ name: string;
288
+ type?: string;
289
+ },
290
+ ): InitialFieldValue;
291
+
279
292
  /**
280
293
  * The write {@link GuardHelpers.editDocument} performs. Every arm is a real
281
294
  * distinct client write the guard seam vets under that action — `create` is
@@ -335,6 +348,7 @@ declare type EngineWrappers = {
335
348
  * so the fixture module needs no import of the bench itself. */
336
349
  declare interface FixtureBench {
337
350
  deployDefinitions: (args: {
351
+ expectedMinReaderModel: 2;
338
352
  definitions: WorkflowDefinition[];
339
353
  }) => Promise<unknown>;
340
354
  startInstance: (args: {
@@ -350,8 +364,8 @@ declare type GuardHelpers = {
350
364
  listGuards: () => Promise<MutationGuardDoc[]>;
351
365
  /**
352
366
  * Guards that would deny an arbitrary edit to `documentId` right now (i.e.
353
- * active locks). Probes by changing every non-system field; a lifted guard
354
- * (predicate `"true"`) returns nothing.
367
+ * active locks). Probes by changing every non-system field; a guard whose
368
+ * predicate is `"true"` returns nothing.
355
369
  */
356
370
  activeGuardsForDocument: (
357
371
  documentId: string,
@@ -447,10 +461,10 @@ declare type ReadHelpers = {
447
461
  */
448
462
  children: (args: ChildrenArgs) => Promise<WorkflowInstance[]>;
449
463
  /**
450
- * All workflow instances whose `doc.ref` field entry named
451
- * `entryName` (default `"subject"`) matches the given doc id. Useful
452
- * for "what workflow is running on this document?" questions. Pass
453
- * `entryName` for workflows whose subject entry uses a different name.
464
+ * All workflow instances whose single-ref field entry (`subject` kind or
465
+ * `doc.ref`) named `entryName` (default `"subject"`) matches the given doc
466
+ * id. Useful for "what workflow is running on this document?" questions.
467
+ * Pass `entryName` for workflows whose subject entry uses a different name.
454
468
  */
455
469
  instancesForSubject: (
456
470
  subjectRef: string,
@@ -478,7 +492,7 @@ declare type ReadHelpers = {
478
492
  /**
479
493
  * The startable half of {@link ReadHelpers.instancesForDocument}: the
480
494
  * latest deployed version of every definition applicable to the LOADED
481
- * candidate document — startable, a required subject entry accepts its
495
+ * candidate document — startable, the `subject`-kind entry accepts its
482
496
  * `_type`, `start.filter` (browse-time-pure) passes. All matches, name
483
497
  * ascending.
484
498
  */
@@ -530,9 +544,12 @@ declare type StoreDocument = {
530
544
  };
531
545
 
532
546
  /**
533
- * The conventional workflow-scope `subject` doc.ref entry, keyed by a bare
534
- * doc id in the bench's default resource. `type` defaults to `'document'`;
535
- * pass the subject's schema type when a filter or projection reads it.
547
+ * A workflow-scope `subject`-KIND entry value, keyed by a bare doc id in the
548
+ * bench's default resource what a definition declaring the first-class
549
+ * `subject` kind expects at start (applicability and document pickers key on
550
+ * that kind). `type` defaults to `'document'`; pass the subject's schema
551
+ * type when a filter or projection reads it. For a plain `doc.ref` input,
552
+ * use {@link docRefField}.
536
553
  */
537
554
  export declare function subjectField(
538
555
  documentId: string,
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { guardsForResource, denyingGuards, MutationGuardDeniedError, workflow, instancesQuery, WORKFLOW_INSTANCE_TYPE, tagScopeFilter, effectOutputsMap, contextMap, createEngine, releaseRef, gdrRef, validateTag, EXECUTION_KINDS, resourceFromParsed, sameResource, datasetResourceParts } from "@sanity/workflow-engine";
1
+ import { guardsForResource, denyingGuards, MutationGuardDeniedError, workflow, instancesQuery, WORKFLOW_INSTANCE_TYPE, tagScopeFilter, effectOutputsMap, contextMap, createEngine, gdrRef, releaseRef, validateTag, EXECUTION_KINDS, resourceFromParsed, sameResource, datasetResourceParts } from "@sanity/workflow-engine";
2
2
 
3
3
  import { createTestClient, defaultAclPath, isDraftId, isVersionId } from "@sanity-labs/client-fake-for-test";
4
4
 
@@ -302,7 +302,7 @@ function createReadHelpers(scope) {
302
302
  ...engineScope,
303
303
  ...args
304
304
  }),
305
- instancesForSubject: async (subjectRef, entryName = "subject") => client.fetch(`*[_type == "${WORKFLOW_INSTANCE_TYPE}"\n && fields[_type == "doc.ref" && name == $entry][0].value.id == $ref\n && ${tagScopeFilter()}] | order(startedAt asc)`, {
305
+ instancesForSubject: async (subjectRef, entryName = "subject") => client.fetch(`*[_type == "${WORKFLOW_INSTANCE_TYPE}"\n && fields[_type in ["doc.ref", "subject"] && name == $entry][0].value.id == $ref\n && ${tagScopeFilter()}] | order(startedAt asc)`, {
306
306
  ref: subjectRef,
307
307
  entry: entryName,
308
308
  tag: tag
@@ -352,7 +352,7 @@ function createBenchEngine(bench, overrides = {}) {
352
352
 
353
353
  function subjectField(documentId, opts) {
354
354
  return {
355
- type: "doc.ref",
355
+ type: "subject",
356
356
  name: opts?.name ?? "subject",
357
357
  value: gdrRef({
358
358
  res: DEFAULT_WORKFLOW_RESOURCE,
@@ -362,6 +362,18 @@ function subjectField(documentId, opts) {
362
362
  };
363
363
  }
364
364
 
365
+ function docRefField(documentId, opts) {
366
+ return {
367
+ type: "doc.ref",
368
+ name: opts.name,
369
+ value: gdrRef({
370
+ res: DEFAULT_WORKFLOW_RESOURCE,
371
+ documentId: documentId,
372
+ type: opts.type ?? "document"
373
+ })
374
+ };
375
+ }
376
+
365
377
  function releaseField(releaseName, opts) {
366
378
  return {
367
379
  type: "release.ref",
@@ -390,6 +402,7 @@ async function startedInstanceFixture(bench, opts) {
390
402
  } ]
391
403
  });
392
404
  await bench.deployDefinitions({
405
+ expectedMinReaderModel: 2,
393
406
  definitions: [ flow ]
394
407
  });
395
408
  const {instance: instance} = await bench.startInstance({
@@ -489,4 +502,4 @@ function assertIsoInstant(iso) {
489
502
  return new Date(ms).toISOString();
490
503
  }
491
504
 
492
- export { ALL_ACCESS, ALL_ACCESS_USER, BENCH_TAG, DEFAULT_WORKFLOW_RESOURCE, WILDCARD_GRANTS, createBench, createBenchEngine, releaseField, startedInstanceFixture, subjectField };
505
+ export { ALL_ACCESS, ALL_ACCESS_USER, BENCH_TAG, DEFAULT_WORKFLOW_RESOURCE, WILDCARD_GRANTS, createBench, createBenchEngine, docRefField, releaseField, startedInstanceFixture, subjectField };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/workflow-engine-test",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "In-memory test bench (createBench) for @sanity/workflow-engine.",
5
5
  "keywords": [
6
6
  "sanity",
@@ -43,7 +43,7 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@sanity-labs/client-fake-for-test": "^0.8.0",
46
- "@sanity/workflow-engine": "0.17.0"
46
+ "@sanity/workflow-engine": "0.18.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@sanity/client": "^7.22.1",