@sanity/workflow-engine 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { analyzeCondition } from "@sanity/groq-condition-describe";
2
2
  import { AtomInsight } from "@sanity/groq-condition-describe";
3
+ import { atomReadsDataset } from "@sanity/groq-condition-describe";
3
4
  import { AtomRequirement } from "@sanity/groq-condition-describe";
4
5
  import { checklistLines as checklistLines_2 } from "@sanity/groq-condition-describe";
5
6
  import { ComparisonOp } from "@sanity/groq-condition-describe";
@@ -39,17 +40,15 @@ export declare function abortReason(
39
40
  ): string | undefined;
40
41
 
41
42
  /**
42
- * The structured half of applicability: does a required subject entry
43
- * ({@link isSubjectEntry} — `doc.ref` / `doc.refs`, workflow scope) accept
44
- * `documentType`? Name-blind and EXISTENTIAL: ANY required ref entry counts
45
- * a multi-input definition (say, contract + counterparty) surfaces from
46
- * either document's picker, and the start dialog collects the remaining
47
- * required entries (their fail-hard validation backstops). An entry without
48
- * `types` accepts any type; a definition with NO required ref entry takes no
49
- * subject and never matches. Cheap and indexable — no GROQ evaluation — so a
50
- * consumer can pre-filter before loading document content. (`required` is
51
- * pinned to caller-filled `input` entries by a deploy invariant, so it alone
52
- * identifies the handoff.)
43
+ * The structured half of applicability: does the definition's subject entry
44
+ * ({@link isSubjectEntry} — the `subject` kind, workflow scope, at most one
45
+ * by deploy invariant) accept `documentType`? The kind is the discriminator,
46
+ * so a definition surfaces from its SUBJECT's document picker and nowhere
47
+ * else; the start dialog collects the remaining required entries (their
48
+ * fail-hard validation backstops). A subject without `types` accepts any
49
+ * type; a definition with NO subject entry takes no subject and never
50
+ * matches. Cheap and indexable — no GROQ evaluation — so a consumer can
51
+ * pre-filter before loading document content.
53
52
  */
54
53
  export declare function acceptsDocumentType(
55
54
  definition: Pick<ApplicabilitySource, "fields">,
@@ -82,6 +81,11 @@ export declare type Action = ActionFields<Op, string[]> & {
82
81
  roles?: string[] | undefined;
83
82
  };
84
83
 
84
+ export declare const ACTION_SEMANTICS: readonly [
85
+ "decision.accept",
86
+ "decision.decline",
87
+ ];
88
+
85
89
  /**
86
90
  * The engine's per-reason detail fragment — the same wording
87
91
  * {@link ActionDisabledError} embeds in its message. Exported so a dev-facing
@@ -123,6 +127,8 @@ export declare type ActionDisabledReason = Exclude<
123
127
 
124
128
  export declare interface ActionEvaluation {
125
129
  action: Action;
130
+ /** The action's advisory workflow meaning, unchanged from its definition. */
131
+ semantics?: ActionSemantic[] | undefined;
126
132
  allowed: boolean;
127
133
  /**
128
134
  * The action is cascade-fired (`when`): the engine fires it on truth, it
@@ -146,6 +152,7 @@ export declare interface ActionEvaluation {
146
152
  * group-membership grammars. */
147
153
  declare type ActionFields<TOp, TGroup> = {
148
154
  name: string;
155
+ semantics?: ActionSemantic[] | undefined;
149
156
  title?: string | undefined;
150
157
  description?: string | undefined;
151
158
  group?: TGroup | undefined;
@@ -167,36 +174,131 @@ export declare type ActionParam = v.InferOutput<typeof ActionParamSchema>;
167
174
  * effects: missing required params → ActionParamsInvalidError, action
168
175
  * does not commit. Resolved values feed `ValueExpr.param` lookups.
169
176
  */
170
- declare const ActionParamSchema: v.StrictObjectSchema<
171
- {
172
- readonly type: v.PicklistSchema<
173
- readonly [
174
- "string",
175
- "number",
176
- "boolean",
177
- "url",
178
- "dateTime",
179
- "actor",
180
- "doc.ref",
181
- "doc.refs",
182
- "json",
183
- ],
184
- `Invalid option: expected one of ${string}`
185
- >;
186
- readonly name: v.SchemaWithPipe<
187
- readonly [
188
- v.StringSchema<undefined>,
189
- v.MinLengthAction<string, 1, "must be a non-empty string">,
190
- ]
191
- >;
192
- readonly title: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
193
- readonly description: v.OptionalSchema<
194
- v.StringSchema<undefined>,
177
+ declare const ActionParamSchema: v.SchemaWithPipe<
178
+ readonly [
179
+ v.StrictObjectSchema<
180
+ {
181
+ readonly type: v.PicklistSchema<
182
+ readonly [
183
+ "string",
184
+ "number",
185
+ "boolean",
186
+ "url",
187
+ "dateTime",
188
+ "actor",
189
+ "doc.ref",
190
+ "doc.refs",
191
+ "json",
192
+ ],
193
+ `Invalid option: expected one of ${string}`
194
+ >;
195
+ readonly name: v.SchemaWithPipe<
196
+ readonly [
197
+ v.StringSchema<undefined>,
198
+ v.MinLengthAction<string, 1, "must be a non-empty string">,
199
+ ]
200
+ >;
201
+ readonly title: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
202
+ readonly description: v.OptionalSchema<
203
+ v.StringSchema<undefined>,
204
+ undefined
205
+ >;
206
+ readonly required: v.OptionalSchema<
207
+ v.BooleanSchema<undefined>,
208
+ undefined
209
+ >;
210
+ readonly options: v.OptionalSchema<
211
+ v.GenericSchema<ChoiceOptions>,
212
+ undefined
213
+ >;
214
+ readonly validation: v.OptionalSchema<
215
+ v.GenericSchema<ScalarValidation>,
216
+ undefined
217
+ >;
218
+ },
195
219
  undefined
196
- >;
197
- readonly required: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
198
- },
199
- undefined
220
+ >,
221
+ v.CheckAction<
222
+ {
223
+ type:
224
+ | "string"
225
+ | "number"
226
+ | "boolean"
227
+ | "doc.ref"
228
+ | "doc.refs"
229
+ | "url"
230
+ | "actor"
231
+ | "dateTime"
232
+ | "json";
233
+ name: string;
234
+ title?: string | undefined;
235
+ description?: string | undefined;
236
+ required?: boolean | undefined;
237
+ options?: ChoiceOptions | undefined;
238
+ validation?: ScalarValidation | undefined;
239
+ },
240
+ (
241
+ issue: v.CheckIssue<{
242
+ type:
243
+ | "string"
244
+ | "number"
245
+ | "boolean"
246
+ | "doc.ref"
247
+ | "doc.refs"
248
+ | "url"
249
+ | "actor"
250
+ | "dateTime"
251
+ | "json";
252
+ name: string;
253
+ title?: string | undefined;
254
+ description?: string | undefined;
255
+ required?: boolean | undefined;
256
+ options?: ChoiceOptions | undefined;
257
+ validation?: ScalarValidation | undefined;
258
+ }>,
259
+ ) => string
260
+ >,
261
+ v.CheckAction<
262
+ {
263
+ type:
264
+ | "string"
265
+ | "number"
266
+ | "boolean"
267
+ | "doc.ref"
268
+ | "doc.refs"
269
+ | "url"
270
+ | "actor"
271
+ | "dateTime"
272
+ | "json";
273
+ name: string;
274
+ title?: string | undefined;
275
+ description?: string | undefined;
276
+ required?: boolean | undefined;
277
+ options?: ChoiceOptions | undefined;
278
+ validation?: ScalarValidation | undefined;
279
+ },
280
+ (
281
+ issue: v.CheckIssue<{
282
+ type:
283
+ | "string"
284
+ | "number"
285
+ | "boolean"
286
+ | "doc.ref"
287
+ | "doc.refs"
288
+ | "url"
289
+ | "actor"
290
+ | "dateTime"
291
+ | "json";
292
+ name: string;
293
+ title?: string | undefined;
294
+ description?: string | undefined;
295
+ required?: boolean | undefined;
296
+ options?: ChoiceOptions | undefined;
297
+ validation?: ScalarValidation | undefined;
298
+ }>,
299
+ ) => string
300
+ >,
301
+ ]
200
302
  >;
201
303
 
202
304
  /**
@@ -241,6 +343,8 @@ export declare function actionRendering(action: {
241
343
  | undefined;
242
344
  }): "absent" | "automation" | "button";
243
345
 
346
+ export declare type ActionSemantic = (typeof ACTION_SEMANTICS)[number];
347
+
244
348
  /** The fireable-action verdict for one action on an activity — its `allowed`
245
349
  * state, structured `disabledReason`, and declared params, tagged with the
246
350
  * owning activity. The per-action atom both projections share:
@@ -312,6 +416,13 @@ declare const ACTIVITY_STATUSES: readonly [
312
416
  "failed",
313
417
  ];
314
418
 
419
+ /** The activity slice of a stage rollup — total by construction, like
420
+ * {@link stageAutonomyOf}. */
421
+ export declare function activityAutonomyOf(
422
+ stageAutonomy: StageAutonomy,
423
+ activityName: string,
424
+ ): AutonomyAnswer;
425
+
315
426
  /** An activity's gates, each described — absent keys mirror undeclared gates. */
316
427
  export declare interface ActivityDescription {
317
428
  requirements?: Record<string, ConditionDescription>;
@@ -360,11 +471,19 @@ export declare interface ActivityEvaluation {
360
471
  */
361
472
  kind: ActivityKind;
362
473
  /**
363
- * Who, if anyone, the activity waits on — derived from the definition
474
+ * Who, if anyone, fires the activity's actions — derived from its shape
364
475
  * alone: `autonomous` (every action cascade-fired), `interactive` (only
365
476
  * fireAction-fired actions), `off-system` (`target` present), or `hybrid`.
366
477
  */
367
478
  classification: ExecutorClassification;
479
+ /**
480
+ * The causal refinement of {@link ActivityEvaluation.classification}:
481
+ * whether this activity completes without a caller, derived by
482
+ * {@link deriveWorkflowAutonomy}'s dataflow over the definition — a
483
+ * mechanically `autonomous` activity whose triggers only read
484
+ * caller-written state still reports the caller it waits on.
485
+ */
486
+ autonomy: AutonomyAnswer;
368
487
  /** Whether this activity is the current actor's responsibility right now. */
369
488
  pendingOnActor: boolean;
370
489
  /**
@@ -443,11 +562,31 @@ export declare const ACTOR_KINDS: readonly ["person", "agent", "system"];
443
562
 
444
563
  export declare type ActorKind = (typeof ACTOR_KINDS)[number];
445
564
 
565
+ export declare type ActorResolution<User> =
566
+ | {
567
+ readonly status: "resolved";
568
+ readonly actor: PersonActor;
569
+ readonly user: User;
570
+ }
571
+ | {
572
+ readonly status: "missing";
573
+ readonly actor: PersonActor;
574
+ }
575
+ | {
576
+ readonly status: "inaccessible";
577
+ readonly actor: PersonActor;
578
+ readonly cause?: unknown;
579
+ }
580
+ | {
581
+ readonly status: "not-person";
582
+ readonly actor: Actor;
583
+ };
584
+
446
585
  export { analyzeCondition };
447
586
 
448
- /** The definition surface applicability reads — structural, so authored,
587
+ /** The definition surface the start contexts read — structural, so authored,
449
588
  * stored, and deployed definition shapes all fit. `name` binds
450
- * `$definition` and names the definition in a broken-filter error. */
589
+ * `$definition` and names the definition in a broken-predicate error. */
451
590
  export declare interface ApplicabilitySource {
452
591
  name?: string | undefined;
453
592
  lifecycle?: WorkflowLifecycle | undefined;
@@ -456,6 +595,7 @@ export declare interface ApplicabilitySource {
456
595
  | {
457
596
  kind?: StartKind | undefined;
458
597
  filter?: string | undefined;
598
+ allowed?: string | undefined;
459
599
  }
460
600
  | undefined;
461
601
  }
@@ -470,7 +610,7 @@ export declare function applicableDefinitions<
470
610
  definitions: readonly T[];
471
611
  document: CandidateDocument;
472
612
  /** Caller-side filter bindings, shared by every definition's evaluation. */
473
- scope?: StartFilterScope;
613
+ scope?: StartScope;
474
614
  }): Promise<T[]>;
475
615
 
476
616
  /**
@@ -491,6 +631,11 @@ export declare function assertReadableModel<
491
631
  },
492
632
  >(doc: T): T;
493
633
 
634
+ export declare function assertReaderModelAcknowledgement(
635
+ expectedMinReaderModel: unknown,
636
+ context?: string,
637
+ ): asserts expectedMinReaderModel is typeof DATA_MODEL_MIN_READER;
638
+
494
639
  /**
495
640
  * One member of an `assignees`-kind entry's value — and the value of the
496
641
  * singular `assignee` kind. The WHO-FOR spec the inbox reverse-query and the
@@ -511,6 +656,8 @@ export declare type Assignee =
511
656
 
512
657
  export { AtomInsight };
513
658
 
659
+ export { atomReadsDataset };
660
+
514
661
  export { AtomRequirement };
515
662
 
516
663
  /**
@@ -607,6 +754,11 @@ export declare type AuthoringGuard = v.InferOutput<typeof AuthoringGuardSchema>;
607
754
 
608
755
  declare const AuthoringGuardSchema: v.StrictObjectSchema<
609
756
  {
757
+ /**
758
+ * Lake-id-segment grammar (`^[a-z0-9][a-z0-9-]*$`, deploy-enforced): the
759
+ * guard's lake `_id` derives from `(instanceId, name)` at stage entry.
760
+ * Unique per definition.
761
+ */
610
762
  name: v.SchemaWithPipe<
611
763
  readonly [
612
764
  v.StringSchema<undefined>,
@@ -768,8 +920,9 @@ declare const AuthoringGuardSchema: v.StrictObjectSchema<
768
920
  /**
769
921
  * Lake GROQ predicate — a distinct eval context: delta-mode GROQ
770
922
  * reading the `before()`/`after()` natives, `mutation`, `guard`, and
771
- * `identity()`. Bare ids/fields only. Omitted or empty means
772
- * UNCONDITIONAL DENY.
923
+ * `identity()`. Bare ids/fields only. Polarity: a result of strictly
924
+ * `true` ALLOWS the matched mutation; anything else (false, null, an
925
+ * evaluation error) DENIES. Omitted or empty means UNCONDITIONAL DENY.
773
926
  */
774
927
  predicate: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
775
928
  /**
@@ -1221,6 +1374,85 @@ export declare type AuthoringWorkflow = WorkflowFields<
1221
1374
  AuthoringStartBlock
1222
1375
  >;
1223
1376
 
1377
+ /** The narratable answer at one rollup level: the verdict plus what it waits
1378
+ * on. `waitsOn` may be non-empty on a `yes` verdict (an effect settle is a
1379
+ * wait the engine resolves itself) — consumers choose what to surface. */
1380
+ export declare interface AutonomyAnswer {
1381
+ completesWithoutCaller: AutonomyVerdict;
1382
+ waitsOn: AutonomyWait[];
1383
+ }
1384
+
1385
+ /** The workflow-level autonomy narrative: fully self-running, or the stages
1386
+ * where a run is held. */
1387
+ export declare function autonomySummary(
1388
+ autonomy: WorkflowAutonomy,
1389
+ ctx: DescribeContext,
1390
+ ): InsightPhrase;
1391
+
1392
+ /**
1393
+ * Whether a node resolves without a caller: `yes` — engine machinery alone
1394
+ * (the cascade plus the host's effect drain/ticker) gets it there; `no` —
1395
+ * someone outside the engine must act (a caller fires an action, an editor
1396
+ * writes a field or content); `conditional` — it may resolve either way
1397
+ * depending on values the analysis can't see (start-time seeds, mixed
1398
+ * alternative routes, unresolvable children, cyclic gates).
1399
+ *
1400
+ * `yes` still leans on the host: effects settle only if a drain runs, and
1401
+ * time only passes for a ticker. A cascade-fired action's `roles` pin is a
1402
+ * DEPENDENCY, not an assumption — the runtime leaves a pinned trigger armed
1403
+ * until a capable token cascades, and whether the deployment's tokens fulfil
1404
+ * the pin is configuration the analysis can't see, so a pinned trigger
1405
+ * reports `conditional` with the pin as its wait.
1406
+ */
1407
+ export declare type AutonomyVerdict = "yes" | "no" | "conditional";
1408
+
1409
+ /** One narratable dependency a non-`yes` verdict rests on. */
1410
+ export declare type AutonomyWait =
1411
+ | {
1412
+ kind: "caller-action";
1413
+ stage: string;
1414
+ activity: string;
1415
+ action: string;
1416
+ roles?: string[];
1417
+ }
1418
+ | {
1419
+ kind: "execute-pin";
1420
+ stage: string;
1421
+ activity: string;
1422
+ action: string;
1423
+ roles: string[];
1424
+ }
1425
+ | {
1426
+ kind: "editor-field";
1427
+ field: string;
1428
+ }
1429
+ | {
1430
+ kind: "editor-content";
1431
+ condition: Condition;
1432
+ }
1433
+ | {
1434
+ kind: "effect";
1435
+ effect: string;
1436
+ }
1437
+ | {
1438
+ kind: "start-input";
1439
+ field: string;
1440
+ }
1441
+ | {
1442
+ kind: "context";
1443
+ key?: string;
1444
+ }
1445
+ | {
1446
+ kind: "subworkflow";
1447
+ definition: string;
1448
+ resolved: boolean;
1449
+ }
1450
+ | {
1451
+ kind: "unwritten-field";
1452
+ field: string;
1453
+ handlerPossible: boolean;
1454
+ };
1455
+
1224
1456
  export declare interface AvailableAction {
1225
1457
  activity: string;
1226
1458
  activityStatus: ActivityStatus;
@@ -1251,10 +1483,10 @@ export declare interface AvailableActionsResult {
1251
1483
  /**
1252
1484
  * Type each caller-supplied name→value against the workflow's declared field
1253
1485
  * entries — the engine takes typed {@link InitialFieldValue}s, and a value's
1254
- * type IS its declared entry's kind. Only `input`-sourced entries read
1255
- * caller values (the engine silently ignores the rest), so anything else
1256
- * fails here, naming the fields that ARE settable; value validation stays
1257
- * in the engine.
1486
+ * type IS its declared entry's kind. Only `input`-sourced entries read caller
1487
+ * values, so anything else fails here, naming the fields that ARE settable.
1488
+ * The engine repeats this structural validation at its contract boundary;
1489
+ * value validation also stays there.
1258
1490
  */
1259
1491
  export declare function buildInitialFields({
1260
1492
  declared,
@@ -1318,6 +1550,17 @@ export declare interface ChildrenArgs extends InstanceRefArgs {
1318
1550
  activity?: string;
1319
1551
  }
1320
1552
 
1553
+ export declare interface ChoiceOption {
1554
+ title: string;
1555
+ value: ChoiceValue;
1556
+ }
1557
+
1558
+ export declare interface ChoiceOptions {
1559
+ list: ChoiceOption[];
1560
+ }
1561
+
1562
+ export declare type ChoiceValue = string | number;
1563
+
1321
1564
  /**
1322
1565
  * The action half of the mirrored claim pair. `field` references an
1323
1566
  * author-declared actor-valued entry (the pair's other half), resolved
@@ -1378,6 +1621,21 @@ export declare function clientConfigFromResource(res: WorkflowResource):
1378
1621
  * resolves to a client, or the router throws. */
1379
1622
  declare type ClientForGdr = (parsed: ParsedGdr) => WorkflowClient;
1380
1623
 
1624
+ /** Native project-user response returned by Sanity's project API. */
1625
+ export declare interface ClientProjectUser {
1626
+ readonly id: string;
1627
+ readonly displayName?: string;
1628
+ readonly email?: string;
1629
+ readonly imageUrl?: string | null;
1630
+ readonly [key: string]: unknown;
1631
+ }
1632
+
1633
+ /** Project-user directory for CLI, MCP, and server runtimes using the engine client. */
1634
+ export declare function clientProjectUserDirectory(
1635
+ client: WorkflowClient,
1636
+ projectId: string,
1637
+ ): ProjectUserDirectory<ClientProjectUser>;
1638
+
1381
1639
  /**
1382
1640
  * The engine's single source of "now".
1383
1641
  *
@@ -1648,11 +1906,14 @@ export declare interface ConditionVar {
1648
1906
  * the rest evaluate to `undefined` — and deploy rejects them at these
1649
1907
  * sites; a cascade-fired action's per-token gate is `roles`, never its
1650
1908
  * conditions.
1651
- * 3. **The start-filter context** — a definition's `start.filter` evaluates
1652
- * against a CANDIDATE (no instance exists yet): the candidate document is
1653
- * the root, {@link START_FILTER_VARS} are the only bound vars, and
1654
- * `*[...]` reads the WORKFLOW resource's dataset. None of the rendered
1655
- * condition vars exist there.
1909
+ * 3. **The start contexts** — a definition's `start.filter` and
1910
+ * `start.allowed` evaluate against a CANDIDATE (no instance exists yet):
1911
+ * `*[...]` reads the WORKFLOW resource's dataset and none of the rendered
1912
+ * condition vars exist. The two split on what a surface can know:
1913
+ * `filter` is browse-time-pure (candidate document as root,
1914
+ * {@link START_FILTER_VARS} — no `$fields`, which cannot exist before
1915
+ * inputs do), `allowed` is the start-time permission predicate
1916
+ * ({@link START_ALLOWED_VARS} — `$fields` bound, never a root).
1656
1917
  * 4. **Guard predicates** — NOT conditions. A lake mutation guard's
1657
1918
  * `predicate` is groq-js **delta-mode** GROQ over a document mutation:
1658
1919
  * `before()`/`after()`/`identity()` are dialect natives, and the wire
@@ -1666,7 +1927,7 @@ export declare interface ConditionVar {
1666
1927
  * instance and its snapshot.
1667
1928
  * - `'caller'` — rides the acting caller; without one the var is `undefined`
1668
1929
  * (conditions referencing it fail closed). Author predicates may not read
1669
- * these — they pre-evaluate once per instance, caller-free.
1930
+ * these — they pre-evaluate caller-free, per evaluation context.
1670
1931
  * - `'spawn'` — bound only at spawn sites (the per-row `$row`).
1671
1932
  */
1672
1933
  export declare type ConditionVarBinding = "always" | "caller" | "spawn";
@@ -1808,10 +2069,11 @@ export declare function contextMap(
1808
2069
 
1809
2070
  /**
1810
2071
  * A caller broke a public-API contract — a call the engine rejects
1811
- * before touching the lake: an invalid `tag`, a `workflow.query` GROQ that
2072
+ * without writing anything: an invalid `tag`, a `workflow.query` GROQ that
1812
2073
  * never binds `$tag`, a bare id where a GDR URI is required, an action,
1813
- * activity, or field the definition never declared. Fix the call site;
1814
- * retrying cannot succeed.
2074
+ * activity, or field the definition never declared, or a `startInstance`
2075
+ * `instanceId` reused for a different start (or a discarded one). Fix the
2076
+ * call site; retrying cannot succeed.
1815
2077
  */
1816
2078
  export declare class ContractViolationError extends WorkflowError<"contract-violation"> {
1817
2079
  constructor(message: string);
@@ -1841,7 +2103,9 @@ export declare class ContractViolationError extends WorkflowError<"contract-viol
1841
2103
  * `completeEffect` — the runtime still decides when to drain and
1842
2104
  * reports outcomes via `completeEffect`.
1843
2105
  */
1844
- export declare function createEngine(args: CreateEngineArgs): Engine;
2106
+ export declare function createEngine<Client extends WorkflowClient>(
2107
+ args: CreateEngineArgs<Client>,
2108
+ ): Engine;
1845
2109
 
1846
2110
  /**
1847
2111
  * The {@link EngineScopeArgs} scope pinned at construction, plus the
@@ -1851,8 +2115,11 @@ export declare function createEngine(args: CreateEngineArgs): Engine;
1851
2115
  * partition is the only thing keeping reads and writes off the wrong
1852
2116
  * environment.
1853
2117
  */
1854
- export declare interface CreateEngineArgs extends EngineScopeArgs {
1855
- effectHandlers?: Record<string, EffectHandler>;
2118
+ export declare interface CreateEngineArgs<
2119
+ Client extends WorkflowClient = WorkflowClient,
2120
+ > extends EngineScopeArgs {
2121
+ client: Client;
2122
+ effectHandlers?: Record<string, EffectHandler<Client>>;
1856
2123
  missingHandler?: MissingHandlerPolicy;
1857
2124
  loggerFactory?: LoggerFactory;
1858
2125
  /**
@@ -1904,19 +2171,70 @@ export declare function createTelemetryIntake(args: {
1904
2171
  }): TelemetryIntake;
1905
2172
 
1906
2173
  /**
1907
- * The reader floor the current model imposes — the compatibility half of
1908
- * {@link MODEL_STAMP}, stamped as `minReaderModel`: the oldest engine model
1909
- * that can safely interpret a document written at {@link DATA_MODEL_VERSION}.
1910
- * An additive change leaves it untouched (older engines read newer docs
1911
- * fine mixed fleets of Functions, Studios, and CLIs are the steady state);
1912
- * ONLY a breaking shape change raises it, and doing so is a declared,
1913
- * DATAMODEL.md-logged decision to fence out older readers.
2174
+ * The append-only, machine-readable counterpart of the model log in
2175
+ * `DATAMODEL.md`. It records compatibility decisions; the prose log retains
2176
+ * the reasoning, absent-value semantics, and old-writer round-trip proof.
2177
+ */
2178
+ export declare const DATA_MODEL_CHANGES: readonly [
2179
+ Readonly<{
2180
+ id: "governed-model-stamps";
2181
+ introducedInModel: 1;
2182
+ minReaderModel: 0;
2183
+ documentTypes: readonly ["definition", "instance"];
2184
+ compatibility: "additive";
2185
+ applicability: "unconditional";
2186
+ summary: "Definition and instance documents carry model provenance and reader-floor stamps.";
2187
+ }>,
2188
+ Readonly<{
2189
+ id: "subject-field-kind";
2190
+ introducedInModel: 2;
2191
+ minReaderModel: 0;
2192
+ documentTypes: readonly ["definition", "instance"];
2193
+ compatibility: "additive";
2194
+ applicability: "detectable";
2195
+ summary: "A workflow-level subject field identifies the document a workflow is about.";
2196
+ }>,
2197
+ Readonly<{
2198
+ id: "typed-scalar-choice-lists";
2199
+ introducedInModel: 2;
2200
+ minReaderModel: 2;
2201
+ documentTypes: readonly ["definition", "instance"];
2202
+ compatibility: "reader-floor";
2203
+ applicability: "detectable";
2204
+ summary: "Scalar fields may constrain writes to a persisted typed choice list.";
2205
+ }>,
2206
+ Readonly<{
2207
+ id: "action-semantics";
2208
+ introducedInModel: 2;
2209
+ minReaderModel: 0;
2210
+ documentTypes: readonly ["definition"];
2211
+ compatibility: "additive";
2212
+ applicability: "detectable";
2213
+ summary: "Ordinary actions may carry a closed bag of advisory workflow semantics.";
2214
+ }>,
2215
+ Readonly<{
2216
+ id: "inclusive-scalar-bounds";
2217
+ introducedInModel: 2;
2218
+ minReaderModel: 2;
2219
+ documentTypes: readonly ["definition", "instance"];
2220
+ compatibility: "reader-floor";
2221
+ applicability: "detectable";
2222
+ summary: "String, text, and number values may carry persisted inclusive bounds.";
2223
+ }>,
2224
+ ];
2225
+
2226
+ /**
2227
+ * The maximum reader floor this writer can emit. Individual documents derive
2228
+ * their `minReaderModel` from the compatibility-bearing features actually
2229
+ * present; a document written at {@link DATA_MODEL_VERSION} may therefore
2230
+ * carry a lower floor. Raising this maximum is a declared, DATAMODEL.md-logged
2231
+ * decision that requires readers-first fleet sequencing.
1914
2232
  */
1915
- export declare const DATA_MODEL_MIN_READER = 0;
2233
+ export declare const DATA_MODEL_MIN_READER = 2;
1916
2234
 
1917
2235
  /**
1918
2236
  * The engine's persisted data-model version — the provenance half of
1919
- * {@link MODEL_STAMP}, stamped as `modelVersion` on every engine-owned
2237
+ * the model stamp, written as `modelVersion` on every engine-owned
1920
2238
  * document at its construction and persist choke-points. Conforms-to
1921
2239
  * semantics: the stamp means "this document conforms to model N now" —
1922
2240
  * instances are re-stamped on every full persist, so mixed-version fleets
@@ -1927,7 +2245,19 @@ export declare const DATA_MODEL_MIN_READER = 0;
1927
2245
  * job. Declare every bump in `DATAMODEL.md`; the model-surface snapshot test
1928
2246
  * keeps undeclared drift red.
1929
2247
  */
1930
- export declare const DATA_MODEL_VERSION = 1;
2248
+ export declare const DATA_MODEL_VERSION = 2;
2249
+
2250
+ export declare interface DataModelChange {
2251
+ readonly id: string;
2252
+ readonly introducedInModel: number;
2253
+ readonly minReaderModel: number;
2254
+ readonly documentTypes: readonly DataModelDocumentType[];
2255
+ readonly compatibility: "additive" | "reader-floor";
2256
+ readonly applicability: "unconditional" | "detectable";
2257
+ readonly summary: string;
2258
+ }
2259
+
2260
+ export declare type DataModelDocumentType = "definition" | "instance";
1931
2261
 
1932
2262
  /**
1933
2263
  * Split a dataset resource id (`<projectId>.<dataset>`) into its parts — the
@@ -2103,6 +2433,10 @@ export declare interface DefinitionsForDocumentArgs {
2103
2433
  * perspective the caller loaded the document with.
2104
2434
  */
2105
2435
  document: CandidateDocument;
2436
+ /** Resource-qualified identity of `document`. Required when an applicable
2437
+ * definition's `start.filter` reads `$subjectHasInFlightInstance`; the
2438
+ * loaded value's bare `_id` cannot distinguish resources. */
2439
+ subject?: GdrUri;
2106
2440
  }
2107
2441
 
2108
2442
  /** A definition-level site address: every runtime {@link InsightSite}, plus
@@ -2227,13 +2561,17 @@ export declare interface DeployDefinitionResult {
2227
2561
  export declare interface DeployDefinitionsArgs<
2228
2562
  T extends WorkflowDefinitionInput<T> = WorkflowDefinition,
2229
2563
  > {
2564
+ /** Reviewed literal acknowledging the installed writer's maximum reader-floor capability. */
2565
+ expectedMinReaderModel: typeof DATA_MODEL_MIN_READER;
2230
2566
  /**
2231
2567
  * Resource-alias bindings for this deploy (alias name → physical resource).
2232
- * Every `@<alias>:` reference in a definition is expanded to its bound
2233
- * physical resource at deploy, so the deployed definition holds only physical
2234
- * references. Deploy fails closed when a definition references an alias this
2235
- * map doesn't bind. Omit for single-resource workflows (definitions that
2236
- * reference no aliases).
2568
+ * A deploy-time abstraction ONLY: every `@<alias>:` reference in a
2569
+ * definition is expanded to its bound physical resource before the
2570
+ * deployed definition is fingerprinted and stored, and deploy fails closed
2571
+ * on an unbound alias. Aliases never exist past deploy — no stored
2572
+ * document carries one, the runtime never sees the map, and an
2573
+ * alias-shaped ref reaching a runtime boundary is rejected as malformed.
2574
+ * Omit for single-resource workflows (definitions referencing no aliases).
2237
2575
  */
2238
2576
  resourceAliases?: ResourceAliases;
2239
2577
  /** Authored definitions (the default — full compile-time checking) or
@@ -2282,7 +2620,7 @@ export declare type DeployedDefinition = WorkflowDefinition & {
2282
2620
  *
2283
2621
  * An aborted instance also no-ops: it parks on its stage forever, so a stale
2284
2622
  * deploy landing after abort's retract would re-lock the subjects with no
2285
- * remaining path to lift them. The gate is `abortedAt`, not `completedAt` —
2623
+ * remaining path to delete them. The gate is `abortedAt`, not `completedAt` —
2286
2624
  * a normal move *into* a terminal stage stamps `completedAt` in the same
2287
2625
  * persist and must still deploy that stage's guards.
2288
2626
  */
@@ -2294,6 +2632,7 @@ export declare function deployStageGuards(args: StageGuardArgs): Promise<void>;
2294
2632
  * definitions themselves — carrying `resourceAliases` here is what keeps a diff
2295
2633
  * fingerprinting the same physical content `deployDefinitions` would. */
2296
2634
  export declare interface DeployTarget {
2635
+ expectedMinReaderModel: typeof DATA_MODEL_MIN_READER;
2297
2636
  tag: string;
2298
2637
  workflowResource: WorkflowResource;
2299
2638
  resourceAliases?: ResourceAliases;
@@ -2308,18 +2647,37 @@ export declare interface DeployTarget {
2308
2647
  */
2309
2648
  export declare function deriveActivityKind(activity: Activity): ActivityKind;
2310
2649
 
2650
+ export declare interface DeriveAutonomyOptions {
2651
+ /** Child definitions for `spawn` recursion, by definition `name` — the
2652
+ * deploy batch or previously deployed set. An unresolvable child reports
2653
+ * `conditional` with an unresolved wait. */
2654
+ children?: ReadonlyMap<string, WorkflowDefinition>;
2655
+ }
2656
+
2311
2657
  /**
2312
- * Who, if anyone, the activity waits on — derived ahead-of-time from the
2313
- * definition alone: `off-system` when `target` is present; else `autonomous`
2314
- * (every action cascade-fired — no caller ever needed), `interactive` (only
2315
- * fireAction-fired actions), or `hybrid` (mixed). An actionless activity
2316
- * classifies `autonomous` vacuously — nothing waits on a caller (deploy's
2317
- * terminal-reachability invariant rejects it anyway).
2658
+ * Who, if anyone, fires the activity's actions — derived ahead-of-time from
2659
+ * the activity's shape alone: `off-system` when `target` is present; else
2660
+ * `autonomous` (every action cascade-fired — no caller fires anything),
2661
+ * `interactive` (only fireAction-fired actions), or `hybrid` (mixed). An
2662
+ * actionless activity classifies `autonomous` vacuously (deploy's
2663
+ * terminal-reachability invariant rejects it anyway). Shape-only: whether
2664
+ * the activity actually RESOLVES without a caller is the causal question
2665
+ * `deriveWorkflowAutonomy` answers.
2318
2666
  */
2319
2667
  export declare function deriveExecutorClassification(
2320
2668
  activity: Activity,
2321
2669
  ): ExecutorClassification;
2322
2670
 
2671
+ /**
2672
+ * Derive the causal autonomy of a stored definition. Pure and instance-free;
2673
+ * conditions must parse (they did at define/deploy time — an unparseable one
2674
+ * throws groq-js's syntax error rather than guessing).
2675
+ */
2676
+ export declare function deriveWorkflowAutonomy(
2677
+ definition: WorkflowDefinition,
2678
+ options?: DeriveAutonomyOptions,
2679
+ ): WorkflowAutonomy;
2680
+
2323
2681
  export declare type Describable =
2324
2682
  | ConditionInsight
2325
2683
  | FieldInsight
@@ -2337,6 +2695,12 @@ export declare function describeAtom(
2337
2695
  fallback: boolean;
2338
2696
  };
2339
2697
 
2698
+ /** One {@link AutonomyWait} as a phrase in the workflow vocabulary. */
2699
+ export declare function describeAutonomyWait(
2700
+ wait: AutonomyWait,
2701
+ ctx: DescribeContext,
2702
+ ): InsightPhrase;
2703
+
2340
2704
  /** Render one condition's insight as a checklist + frontier summary, in the
2341
2705
  * workflow vocabulary. */
2342
2706
  export declare function describeCondition(
@@ -2611,7 +2975,7 @@ export declare type DisabledReason =
2611
2975
  /**
2612
2976
  * The actor's grants on an involved subject's resource don't allow the
2613
2977
  * content write the action's effects would perform there. Forecast
2614
- * per-resource: each subject named by a `doc.ref`/`doc.refs`/`release.ref`
2978
+ * per-resource: each subject named by a `doc.ref`/`subject`/`doc.refs`/`release.ref`
2615
2979
  * field entry outside the workflow's own resource is checked against
2616
2980
  * THAT resource's ACL. Advisory like every engine verdict — the subject's
2617
2981
  * lake still enforces; and it degrades open: a resource whose grants
@@ -2806,6 +3170,7 @@ export declare interface EditableFieldEvaluation {
2806
3170
  name: string;
2807
3171
  type: FieldKind;
2808
3172
  title?: string;
3173
+ validation?: ScalarValidation;
2809
3174
  /** Current resolved value; `undefined` until the field is first resolved. */
2810
3175
  value: unknown;
2811
3176
  /** Whether THIS actor may edit the field right now (window + predicate + guard). */
@@ -2920,6 +3285,15 @@ export declare type EditMode = "set" | "append" | "unset";
2920
3285
 
2921
3286
  export declare type Effect = v.InferOutput<typeof EffectSchema>;
2922
3287
 
3288
+ /**
3289
+ * Every terminal state an effect run can record. `done` and `failed` are
3290
+ * reported through completion ({@link EffectCompletionStatus}); `cancelled`
3291
+ * is engine-stamped only — an abort cancelling the entry before dispatch.
3292
+ * A cancellation is not a failure: anything counting failures (dashboards,
3293
+ * retry tooling) must not count aborted-away effects among them.
3294
+ */
3295
+ declare const EFFECT_RUN_STATUSES: readonly ["done", "failed", "cancelled"];
3296
+
2923
3297
  /** The outcomes a completer may report through `completeEffect` — a run
2924
3298
  * either succeeded or failed. `cancelled` is not reportable: only an abort
2925
3299
  * stamps it, on entries that never dispatched. */
@@ -2928,6 +3302,20 @@ export declare type EffectCompletionStatus = Exclude<
2928
3302
  "cancelled"
2929
3303
  >;
2930
3304
 
3305
+ export declare type EffectHandler<
3306
+ Client extends WorkflowClient = WorkflowClient,
3307
+ > = {
3308
+ /** Bivariant so a concretely typed handler registry remains readable from
3309
+ * the non-generic Engine surface; only the typed drain invokes handlers. */
3310
+ bivarianceHack(
3311
+ params: Record<string, unknown>,
3312
+ ctx: EffectHandlerContext<Client>,
3313
+ ): Promise<{
3314
+ outputs?: Record<string, unknown>;
3315
+ ops?: FieldOp[];
3316
+ } | void>;
3317
+ }["bivarianceHack"];
3318
+
2931
3319
  /**
2932
3320
  * External effect handler — invoked at drain time with the resolved
2933
3321
  * `params` and a context. Returning `outputs` records them on the run's
@@ -2938,8 +3326,10 @@ export declare type EffectCompletionStatus = Exclude<
2938
3326
  * applier as an action's field ops (so a created doc's ref enters `$fields`, or
2939
3327
  * a screened outcome lands in a field the activity/stage gate reads). Effects
2940
3328
  * report results as field state, never by flipping an activity status, so `ops`
2941
- * excludes `status.set`. Throwing marks the effect as failed (and returns no
2942
- * `ops`).
3329
+ * excludes `status.set`. Unlike definition-authored field ops, completion ops
3330
+ * have no authoring location from which to infer a target scope: every returned
3331
+ * op must explicitly set `target.scope` to `'workflow'` or `'stage'`. Throwing
3332
+ * marks the effect as failed (and returns no `ops`).
2943
3333
  *
2944
3334
  * **Delivery is at-least-once — a handler MAY run more than once for the
2945
3335
  * same effect.** Two overlaps produce a double-run: the dispatching process
@@ -2957,38 +3347,38 @@ export declare type EffectCompletionStatus = Exclude<
2957
3347
  * already completed; derive external-system identifiers from `ctx.effectKey`
2958
3348
  * so the receiving system can dedupe the overlap the ledger can't see.
2959
3349
  */
2960
- export declare type EffectHandler = (
2961
- params: Record<string, unknown>,
2962
- ctx: {
2963
- /** The engine's own client — bound to the workflow resource the
2964
- * instance lives in. Use it for same-resource subjects; for a
2965
- * cross-resource subject, route through {@link clientFor} instead. */
2966
- client: WorkflowClient;
2967
- /**
2968
- * Resolve the client bound to a subject doc's own resource. A handler
2969
- * patches the SUBJECT (which may live in a different Sanity resource
2970
- * than the instance split-dataset GDR deploys); the drainer's
2971
- * `completeEffect` writes the INSTANCE through {@link client}. One
2972
- * client can't address both, so a handler that patches a foreign
2973
- * subject must route its write here.
2974
- *
2975
- * Pass the subject's GDR the URI a binding like `$fields.subject._id`
2976
- * resolves to (the hydrated doc's `_id`), or a full
2977
- * {@link GlobalDocumentReference}. Returns the `resourceClients` client
2978
- * for that resource when one is mapped, {@link client} for the workflow
2979
- * resource itself, and a sibling derived from {@link client}'s
2980
- * credentials otherwise. Throws if `ref` isn't a GDR a bare id can't
2981
- * be routed, so failing loud beats silently patching the wrong dataset.
2982
- */
2983
- clientFor: (ref: GdrUri | GlobalDocumentReference) => WorkflowClient;
2984
- instanceId: string;
2985
- effectKey: string;
2986
- log: (message: string, extra?: Record<string, unknown>) => void;
2987
- },
2988
- ) => Promise<{
2989
- outputs?: Record<string, unknown>;
2990
- ops?: FieldOp[];
2991
- } | void>;
3350
+ declare type EffectHandlerContext<Client extends WorkflowClient> = {
3351
+ /**
3352
+ * The exact concrete client supplied to `createEngine`, bound to the
3353
+ * workflow resource rather than an
3354
+ * engine wrapper. Its class identity, namespaces, configuration, and
3355
+ * credentials are preserved. Handler-owned requests are not automatically
3356
+ * tagged by the engine.
3357
+ */
3358
+ client: Client;
3359
+ /**
3360
+ * Resolve the client bound to a subject doc's own resource. A handler
3361
+ * patches the SUBJECT (which may live in a different Sanity resource
3362
+ * than the instance split-dataset GDR deploys); the drainer's
3363
+ * `completeEffect` writes the INSTANCE through {@link client}. One
3364
+ * client can't address both, so a handler that patches a foreign
3365
+ * subject must route its write here.
3366
+ *
3367
+ * Pass the subject's GDR — the URI a binding like `$fields.subject._id`
3368
+ * resolves to (the hydrated doc's `_id`), or a full
3369
+ * {@link GlobalDocumentReference}. Returns the `resourceClients` client
3370
+ * for that resource when one is mapped, {@link client} for the workflow
3371
+ * resource itself, and a sibling derived from {@link client}'s
3372
+ * credentials otherwise. Mapped clients are returned unchanged; an
3373
+ * unmapped foreign resource necessarily returns a configured sibling.
3374
+ * Throws if `ref` isn't a GDR — a bare id can't
3375
+ * be routed, so failing loud beats silently patching the wrong dataset.
3376
+ */
3377
+ clientFor: (ref: GdrUri | GlobalDocumentReference) => WorkflowClient;
3378
+ instanceId: string;
3379
+ effectKey: string;
3380
+ log: (message: string, extra?: Record<string, unknown>) => void;
3381
+ };
2992
3382
 
2993
3383
  export declare interface EffectHistoryEntry {
2994
3384
  _key: string;
@@ -3095,14 +3485,7 @@ export declare function effectOutputsMap(
3095
3485
  instance: Pick<WorkflowInstance, "effectHistory">,
3096
3486
  ): Record<string, unknown>;
3097
3487
 
3098
- /**
3099
- * Every terminal state an effect run can record. `done` and `failed` are
3100
- * reported through completion ({@link EffectCompletionStatus}); `cancelled`
3101
- * is engine-stamped only — an abort cancelling the entry before dispatch.
3102
- * A cancellation is not a failure: anything counting failures (dashboards,
3103
- * retry tooling) must not count aborted-away effects among them.
3104
- */
3105
- export declare type EffectRunStatus = "done" | "failed" | "cancelled";
3488
+ export declare type EffectRunStatus = (typeof EFFECT_RUN_STATUSES)[number];
3106
3489
 
3107
3490
  declare const EffectSchema: v.StrictObjectSchema<
3108
3491
  {
@@ -3183,6 +3566,10 @@ export declare interface Engine {
3183
3566
  /** The resolved telemetry logger ({@link noopTelemetry} unless injected) —
3184
3567
  * exposed so adapters built on the engine log through the same seam. */
3185
3568
  readonly telemetry: WorkflowTelemetryLogger;
3569
+ /** Resolve durable actor provenance through this engine's project client. */
3570
+ resolveActor: (
3571
+ args: ResolveClientActorArgs,
3572
+ ) => Promise<ActorResolution<ClientProjectUser>>;
3186
3573
  deployDefinitions: <T extends WorkflowDefinitionInput<T>>(
3187
3574
  args: DeployDefinitionsArgs<T>,
3188
3575
  ) => Promise<DeployDefinitionsResult>;
@@ -3214,7 +3601,7 @@ export declare interface Engine {
3214
3601
  getInstance: (args: InstanceRefArgs) => Promise<WorkflowInstance>;
3215
3602
  /** The reactive {@link WatchSet} for an instance — every document whose
3216
3603
  * change should re-evaluate it (the instance, its ancestors, and the docs
3217
- * named by `doc.ref`/`doc.refs`/`release.ref` field entries on the workflow scope
3604
+ * named by `doc.ref`/`subject`/`doc.refs`/`release.ref` field entries on the workflow scope
3218
3605
  * + current stage) as exploded {@link SubscriptionDocument}s, plus the
3219
3606
  * instance's read perspective. Fetches the instance, then derives. A
3220
3607
  * reactive adapter that already holds the live instance calls the pure
@@ -3229,8 +3616,9 @@ export declare interface Engine {
3229
3616
  * its own; the consumer drives it. */
3230
3617
  session: (args: SessionArgs) => InstanceSession;
3231
3618
  /** Every lake mutation guard this instance registered, unioned across the
3232
- * instance's own resource and the resource of each `doc.ref`/`doc.refs`
3233
- * GDR it holds in state. For coherency refresh and housekeeping. */
3619
+ * instance's own resource and the resource of each
3620
+ * `doc.ref`/`subject`/`doc.refs` GDR it holds in state. For coherency
3621
+ * refresh and housekeeping. */
3234
3622
  guardsForInstance: (args: InstanceRefArgs) => Promise<MutationGuardDoc[]>;
3235
3623
  /** Every lake mutation guard a workflow deployed (any version), across the
3236
3624
  * datasources its guards statically name — the workflow resource plus any
@@ -3249,26 +3637,39 @@ export declare interface Engine {
3249
3637
  * in-flight instance whose watch-set includes `document` (a resource-qualified
3250
3638
  * GDR URI). For a non-reactive, content-change-driven runtime deciding which
3251
3639
  * instances a changed doc should `tick`. Matches the same ref set the forward
3252
- * watch-set uses (self, ancestors, current-stage `doc.ref`/`doc.refs`/`release.ref`)
3640
+ * watch-set uses (self, ancestors, current-stage `doc.ref`/`subject`/`doc.refs`/`release.ref`)
3253
3641
  * via the shared `collectWatchRefs`. Sorted by `startedAt` asc. */
3254
3642
  instancesForDocument: (
3255
3643
  args: InstancesForDocumentArgs,
3256
3644
  ) => Promise<WorkflowInstance[]>;
3257
3645
  /** The startable half of {@link Engine.instancesForDocument}: the latest
3258
3646
  * deployed version of every definition that applies to the LOADED candidate
3259
- * document — startable, a required subject entry accepts its `_type`, and
3260
- * `start.filter` passes. All matches, name ascending; advisory — a start
3261
- * picker's filter, never enforcement. */
3647
+ * document — startable, the `subject`-kind entry accepts its `_type`, and
3648
+ * `start.filter` (browse-time-pure `$fields` never binds) passes. All
3649
+ * matches, name ascending; advisory — a start picker's filter, never
3650
+ * enforcement. */
3262
3651
  definitionsForDocument: (
3263
3652
  args: DefinitionsForDocumentArgs,
3264
3653
  ) => Promise<DeployedDefinition[]>;
3654
+ /** Pre-flight `startInstance`'s gates for a definition + the
3655
+ * `initialFields` gathered so far: structurally invalid rows, the
3656
+ * `start.allowed` verdict with its insight, and still-missing required
3657
+ * inputs. `allowed` is the overall startability signal; `outcome` describes
3658
+ * only the condition. Bindability-aware — a
3659
+ * predicate reading a not-yet-supplied entry reports `'unevaluable'`
3660
+ * with the entries named in `unboundReads`, never a collapsed verdict.
3661
+ * Pure read; the enforcement moment is `startInstance` itself. */
3662
+ evaluateStart: (args: EvaluateStartArgs) => Promise<StartEvaluation>;
3265
3663
  /** GROQ query against the engine's workflow resource. `$tag`
3266
3664
  * is bound for tag-scoped filtering. */
3267
3665
  query: <T = unknown>(args: QueryArgs) => Promise<T>;
3268
3666
  /** Snapshot-aware GROQ — runs against the same in-memory view the
3269
- * engine's filters see for the supplied instance. The rendered scope's
3270
- * instance-derived vars ({@link FILTER_SCOPE_VARS}) are bound, ids in
3271
- * GDR URI form to match the snapshot's keying. */
3667
+ * engine's filters see for the supplied instance. The caller-free
3668
+ * rendered scope cascade gates evaluate in is bound the
3669
+ * instance-derived vars ({@link FILTER_SCOPE_VARS}) with the open
3670
+ * stage's overlay merged into `$fields`, `$assigned` at its caller-free
3671
+ * `false`, plus the author's pre-evaluated `$<predicate>` booleans —
3672
+ * ids in GDR URI form to match the snapshot's keying. */
3272
3673
  queryInScope: <T = unknown>(args: QueryInScopeArgs) => Promise<T>;
3273
3674
  /** List every pending effect on an instance. */
3274
3675
  listPendingEffects: (args: InstanceRefArgs) => Promise<PendingEffect[]>;
@@ -3303,8 +3704,9 @@ export declare interface Engine {
3303
3704
  * housekeeping exports — derives its working client onto this version via
3304
3705
  * {@link WorkflowClient.withConfig}, and `resourceClients`-resolved clients
3305
3706
  * are rebound the same way. A caller's configured `apiVersion` therefore
3306
- * never reaches engine traffic (the caller's own client instance is
3307
- * untouched). The one exception is a client that lacks `withConfig` and so
3707
+ * never reaches engine-owned traffic (the caller's own client instance is
3708
+ * untouched). Effect handlers are host traffic and deliberately receive the
3709
+ * caller's configuration unchanged. The other exception is a client that lacks `withConfig` and so
3308
3710
  * cannot be rebound — it must be built to serve this version; see
3309
3711
  * {@link WorkflowClient.withConfig}.
3310
3712
  */
@@ -3347,8 +3749,9 @@ export declare interface EngineScopeArgs {
3347
3749
  * docs that live in a different Sanity resource than the workflow). Called
3348
3750
  * with a parsed GDR; return a client for that resource, or undefined to let
3349
3751
  * the engine route it — `client` for the workflow resource itself, a
3350
- * sibling derived from `client`'s credentials for anything else. Engine-scope
3351
- * configuration see `CreateEngineArgs`.
3752
+ * sibling derived from `client`'s credentials for anything else. A served
3753
+ * resource is also part of the declared surface for runtime-supplied refs.
3754
+ * Engine-scope configuration — see `CreateEngineArgs`.
3352
3755
  */
3353
3756
  resourceClients?: ResourceClientResolver;
3354
3757
  /**
@@ -3368,6 +3771,16 @@ export declare interface EngineScopeArgs {
3368
3771
  idempotencyTtlMs?: number;
3369
3772
  }
3370
3773
 
3774
+ /**
3775
+ * Pull the {@link GlobalDocumentReference} values out of `doc.ref` /
3776
+ * `subject` / `doc.refs` (content) field entries. Content only — release
3777
+ * field entries come from {@link entryReleaseRefs}, so guard discovery (which
3778
+ * reads this via `collectEntryDocUris`) stays scoped to content docs.
3779
+ */
3780
+ export declare function entryDocRefs(
3781
+ entries: unknown,
3782
+ ): GlobalDocumentReference[];
3783
+
3371
3784
  /**
3372
3785
  * Pure error helpers — no client, no I/O. The one home for the engine's
3373
3786
  * "coerce an unknown caught value" + "rethrow with context" idioms, so the
@@ -3465,24 +3878,45 @@ export declare function evaluateMutationGuard(args: {
3465
3878
  context: MutationContext;
3466
3879
  }): Promise<boolean>;
3467
3880
 
3881
+ /** Args for `evaluateStart` — the pre-flight read of `startInstance`'s gates
3882
+ * for a definition + the `initialFields` gathered so far (possibly none). */
3883
+ export declare interface EvaluateStartArgs {
3884
+ /** The definition's `name` — which deployed workflow would be started. */
3885
+ definition: string;
3886
+ /** Optional explicit version. Defaults to the highest deployed version. */
3887
+ version?: number;
3888
+ /**
3889
+ * The candidate seed, in `StartInstanceArgs.initialFields` shape. Partial
3890
+ * mid-form input is expected and safe: the verdict is BINDABILITY-AWARE —
3891
+ * when the predicate reads a declared entry these inputs don't supply,
3892
+ * `outcome` reports `'unevaluable'` and {@link StartEvaluation.unboundReads}
3893
+ * names the entries, instead of the collapsed answer GROQ equality would
3894
+ * otherwise produce (`null == x` is false, not null — a definitive-looking
3895
+ * verdict one more input could overturn, in either direction).
3896
+ */
3897
+ initialFields?: InitialFieldValue[];
3898
+ }
3899
+
3468
3900
  /**
3469
- * Evaluate one `start.filter` in the start-filter context: `document` (may be
3470
- * absent — root reads then fail closed) as the GROQ root, the
3471
- * {@link StartFilterScope} bindings plus `$definition`, and — only when
3472
- * `analyzeCondition` says the filter reads the dataset — the scope's fetched
3473
- * slice as `*`. Cheap pure evaluation otherwise: no I/O rides a filter that
3474
- * never scans. GROQ null ("can't decide") is `false` every consumer of
3901
+ * Evaluate one `start.filter` in the start-filter context: `document` (may
3902
+ * be absent — a root read is then GROQ null, fail-closed or vacuous-pass by
3903
+ * shape) as the GROQ root, the {@link StartScope} bindings plus
3904
+ * `$definition`, and — when `analyzeCondition` says the filter reads the
3905
+ * dataset or the filter reads `$subjectHasInFlightInstance` the scope's
3906
+ * fetched slice as `*`. Cheap pure evaluation otherwise: no I/O rides a
3907
+ * filter that needs neither. GROQ null ("can't decide") is `false` — every consumer of
3475
3908
  * this verdict fails closed; a parse/evaluation THROW is a malformed
3476
- * predicate, not an unevaluable one — rethrown loud, naming the definition
3477
- * (only writable by bypassing deploy validation), so every read surface that
3478
- * evaluates the filter reports the same context. A failed slice FETCH
3909
+ * predicate, not an unevaluable one — rethrown loud, naming the definition,
3910
+ * so every read surface that evaluates the filter reports the same context.
3911
+ * A caller that omits the prospective subject required by the synthetic
3912
+ * variable also throws as a scope-contract error. A failed slice FETCH
3479
3913
  * propagates as itself: transport trouble, never definition blame.
3480
3914
  */
3481
3915
  export declare function evaluateStartFilter(args: {
3482
3916
  filter: string;
3483
3917
  definition: Pick<ApplicabilitySource, "name">;
3484
3918
  document?: CandidateDocument | undefined;
3485
- scope?: StartFilterScope | undefined;
3919
+ scope?: StartScope | undefined;
3486
3920
  }): Promise<boolean>;
3487
3921
 
3488
3922
  /**
@@ -3532,8 +3966,8 @@ export declare interface ExecutionContext {
3532
3966
  }
3533
3967
 
3534
3968
  /**
3535
- * Executor classifications — who, if anyone, an activity waits on
3536
- * (derived; see `./activity-kind.ts`).
3969
+ * Executor classifications — who, if anyone, fires an activity's actions
3970
+ * (shape-derived; see `./activity-kind.ts`).
3537
3971
  */
3538
3972
  export declare const EXECUTOR_CLASSIFICATION_DISPLAY: {
3539
3973
  autonomous: {
@@ -3564,10 +3998,63 @@ export declare const EXECUTOR_CLASSIFICATIONS: readonly [
3564
3998
  export declare type ExecutorClassification =
3565
3999
  (typeof EXECUTOR_CLASSIFICATIONS)[number];
3566
4000
 
4001
+ /**
4002
+ * Expand every `@<alias>:` reference in a definition to the physical GDR prefix
4003
+ * its alias binds to, returning a definition that carries only physical
4004
+ * references. Fails closed — naming the definition + alias — when a referenced
4005
+ * alias isn't bound: the check that stops a portable definition from deploying
4006
+ * against the wrong (or no) resource.
4007
+ *
4008
+ * Runs at deploy (inside {@link planDefinitionDeploy}), BEFORE the content
4009
+ * fingerprint, so a deployed definition never carries a logical alias. The
4010
+ * stored references are physical, and a rebind (same source, a different alias
4011
+ * map) surfaces as changed content — a new version, never a silent shift in what
4012
+ * the workflow reads. A no-op when the definition references no aliases.
4013
+ *
4014
+ * Rewrites string VALUES only (via the shared {@link mapJsonStrings} deep-walk),
4015
+ * skipping the prose fields in {@link PROSE_KEYS}. A reference that stands alone
4016
+ * (the whole value is `@<alias>:<id>`, e.g. a literal `doc.ref`) must expand to a
4017
+ * well-formed GDR — a malformed one (`@content:a:b`, an empty id) is rejected
4018
+ * here rather than failing when an instance later reads it.
4019
+ */
4020
+ export declare function expandResourceAliases(
4021
+ definition: WorkflowDefinition,
4022
+ resourceAliases: ResourceAliases | undefined,
4023
+ ): WorkflowDefinition;
4024
+
3567
4025
  export { explainCondition };
3568
4026
 
3569
4027
  export { ExplainConditionArgs };
3570
4028
 
4029
+ /**
4030
+ * Explain one `start.allowed` in the start-allowed context: no root (the
4031
+ * subject rides `$fields.<entry>.id` — deploy rejects a root read), the
4032
+ * {@link StartScope} bindings plus `$definition` and the caller's `$fields`
4033
+ * map, and the scope's fetched slice as `*` when the predicate reads the
4034
+ * dataset. Returns the full {@link ConditionInsight} — `outcome` is the
4035
+ * verdict (`'satisfied'` allows; `'unsatisfied'`/`'unevaluable'` refuse,
4036
+ * fail-closed) and the atom breakdown is what lets a surface disable its
4037
+ * start affordance AND say why. One evaluator serves both moments:
4038
+ * `startInstance` enforces the outcome, the `evaluateStart` verb pre-flights
4039
+ * it.
4040
+ *
4041
+ * A dataset-reading predicate with no slice provider THROWS instead of
4042
+ * failing closed: unlike a picker that merely hides a row, this verdict
4043
+ * refuses starts — evaluating over an empty `*` would let a `count()`-style
4044
+ * rule pass vacuously, and a silent "not allowed" would misreport a caller
4045
+ * bug as an author verdict. Parse/evaluation throws rethrow naming the
4046
+ * definition, like the filter's; a failed slice FETCH propagates as itself.
4047
+ */
4048
+ export declare function explainStartAllowed(args: {
4049
+ allowed: string;
4050
+ definition: Pick<ApplicabilitySource, "name" | "fields">;
4051
+ /** The caller's input entries as a `$fields` map — the engine verbs build
4052
+ * it with `startFieldsParam` (field resolution's projection), so the
4053
+ * predicate can only see values the resolution would persist. */
4054
+ fields: Record<string, unknown>;
4055
+ scope?: StartScope | undefined;
4056
+ }): Promise<ConditionInsight>;
4057
+
3571
4058
  /**
3572
4059
  * Extract the bare document `_id` from a GDR URI — what
3573
4060
  * `*[_id == $docId]` in GROQ needs.
@@ -3610,6 +4097,10 @@ export declare const FIELD_KIND_DISPLAY: {
3610
4097
  title: string;
3611
4098
  description: string;
3612
4099
  };
4100
+ subject: {
4101
+ title: string;
4102
+ description: string;
4103
+ };
3613
4104
  "release.ref": {
3614
4105
  title: string;
3615
4106
  description: string;
@@ -3678,6 +4169,7 @@ declare const FIELD_SCOPES: readonly ["workflow", "stage", "activity"];
3678
4169
  declare const FIELD_VALUE_KINDS: readonly [
3679
4170
  "doc.ref",
3680
4171
  "doc.refs",
4172
+ "subject",
3681
4173
  "release.ref",
3682
4174
  "string",
3683
4175
  "text",
@@ -3721,6 +4213,8 @@ declare type FieldEntryFields<TEditable, TGroup> = FieldBase<
3721
4213
  TGroup
3722
4214
  > & {
3723
4215
  type: FieldValueKind;
4216
+ options?: ChoiceOptions | undefined;
4217
+ validation?: ScalarValidation | undefined;
3724
4218
  types?: string[] | undefined;
3725
4219
  fields?: FieldShape[] | undefined;
3726
4220
  of?: FieldShape[] | undefined;
@@ -3776,6 +4270,8 @@ export declare interface FieldShape {
3776
4270
  name: string;
3777
4271
  title?: string | undefined;
3778
4272
  description?: string | undefined;
4273
+ options?: ChoiceOptions | undefined;
4274
+ validation?: ScalarValidation | undefined;
3779
4275
  fields?: FieldShape[] | undefined;
3780
4276
  of?: FieldShape[] | undefined;
3781
4277
  }
@@ -3809,6 +4305,10 @@ declare type FieldValueKind = (typeof FIELD_VALUE_KINDS)[number];
3809
4305
  export declare interface FieldValueMap {
3810
4306
  "doc.ref": GlobalDocumentReference | null;
3811
4307
  "doc.refs": GlobalDocumentReference[];
4308
+ /** THE document the workflow is about — same value as
4309
+ * {@link FieldValueMap."doc.ref"}, distinct kind so the runtime and UI
4310
+ * identify the subject deterministically (workflow scope, at most one). */
4311
+ subject: GlobalDocumentReference | null;
3812
4312
  "release.ref": ReleaseRef | null;
3813
4313
  string: string | null;
3814
4314
  /** Multiline string — same value as {@link FieldValueMap.string}, distinct kind for rendering. */
@@ -3987,6 +4487,35 @@ export declare function gdrUri(
3987
4487
  ): GdrUri;
3988
4488
 
3989
4489
  /**
4490
+ * The engine's reference value — deliberately NOT a lake reference, and
4491
+ * spelled differently on purpose (`id`/`type`, never `_ref`/`_id`):
4492
+ *
4493
+ * - **`id` is a URI, not a bare doc id**, because workflow references cross
4494
+ * resources (another dataset, a media library) and a bare `_id` is only
4495
+ * meaningful inside one dataset. Everything resource-aware — client
4496
+ * routing, spawn discovery, guard target resolution — reads the resource
4497
+ * out of this URI. It is stripped to a bare lake id at exactly one
4498
+ * boundary: `paramsForLake` (core/params.ts), when a query actually runs
4499
+ * against a lake.
4500
+ * - **`type` carries the target's schema type so consumers don't need a
4501
+ * fetch — as stamped by the ref's constructor, never verified against the
4502
+ * target.** Engine-built refs stamp it from real data (a projection row's
4503
+ * `_type`, the `releaseRef` constructor); a caller-supplied ref's `type`
4504
+ * is a trusted claim. Three advisory surfaces read it: the declared-subject-type
4505
+ * contract (an entry's `types: [...]` accepts/rejects incoming refs —
4506
+ * `refTypeIssues` at start `initialFields` and spawn `with`), start
4507
+ * applicability ("which definitions accept a doc of this `_type`" —
4508
+ * `acceptsDocumentType`), and guard `match.types` inference from resolved
4509
+ * targets (`resolveMatchTypes`). All must work when the target lives in a
4510
+ * resource the reader can't (or shouldn't) fetch from.
4511
+ *
4512
+ * In rendered GROQ this shape is what a reference-held value exposes
4513
+ * (`$fields.<entry>.id` / `.type`) — whereas `_id`/`_type` belong to
4514
+ * documents you hold (lake rows, `$row`, a dereferenced singular `doc.ref`).
4515
+ * The spelling is not a namespace guarantee: a content document can carry
4516
+ * its own `id`/`type` fields — what you're reading follows what the entry's
4517
+ * declared kind puts in your hand, never the key's spelling.
4518
+ *
3990
4519
  * `TType` preserves the `type` literal the constructors ({@link refDataset},
3991
4520
  * {@link gdrRef}, …) were called with, so a constructed ref satisfies
3992
4521
  * literal-typed targets (e.g. a `release.ref` value's `"system.release"`).
@@ -4116,9 +4645,6 @@ export declare type Guard = v.InferOutput<typeof GuardSchema>;
4116
4645
  */
4117
4646
  export declare const GUARD_DOC_TYPE = "temp.system.guard";
4118
4647
 
4119
- /** Retracted predicate — unconditionally allows, lifting the guard. */
4120
- export declare const GUARD_LIFTED_PREDICATE = "true";
4121
-
4122
4648
  export declare const GUARD_OWNER = "robot:workflow-engine";
4123
4649
 
4124
4650
  /**
@@ -4243,6 +4769,11 @@ declare const GuardReadSchema: v.VariantSchema<
4243
4769
  /** Stored guards carry the printed string reads (the deploy resolver's input). */
4244
4770
  declare const GuardSchema: v.StrictObjectSchema<
4245
4771
  {
4772
+ /**
4773
+ * Lake-id-segment grammar (`^[a-z0-9][a-z0-9-]*$`, deploy-enforced): the
4774
+ * guard's lake `_id` derives from `(instanceId, name)` at stage entry.
4775
+ * Unique per definition.
4776
+ */
4246
4777
  name: v.SchemaWithPipe<
4247
4778
  readonly [
4248
4779
  v.StringSchema<undefined>,
@@ -4314,8 +4845,9 @@ declare const GuardSchema: v.StrictObjectSchema<
4314
4845
  /**
4315
4846
  * Lake GROQ predicate — a distinct eval context: delta-mode GROQ
4316
4847
  * reading the `before()`/`after()` natives, `mutation`, `guard`, and
4317
- * `identity()`. Bare ids/fields only. Omitted or empty means
4318
- * UNCONDITIONAL DENY.
4848
+ * `identity()`. Bare ids/fields only. Polarity: a result of strictly
4849
+ * `true` ALLOWS the matched mutation; anything else (false, null, an
4850
+ * evaluation error) DENIES. Omitted or empty means UNCONDITIONAL DENY.
4319
4851
  */
4320
4852
  predicate: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
4321
4853
  /**
@@ -4711,6 +5243,67 @@ export declare interface HydratedSnapshot {
4711
5243
  */
4712
5244
  export declare function inFlightFilter(): string;
4713
5245
 
5246
+ /** Why one caller-supplied initial-field row cannot be consumed. */
5247
+ export declare type InitialFieldIssue = {
5248
+ index: number;
5249
+ name: string;
5250
+ type: FieldKind;
5251
+ } & (
5252
+ | {
5253
+ reason: "duplicate";
5254
+ duplicateIndexes: number[];
5255
+ }
5256
+ | {
5257
+ reason: "undeclared";
5258
+ }
5259
+ | {
5260
+ reason: "wrong-kind";
5261
+ expectedTypes: FieldKind[];
5262
+ }
5263
+ | {
5264
+ reason: "not-input";
5265
+ source:
5266
+ | "working-memory"
5267
+ | Exclude<NonNullable<FieldEntry["initialValue"]>["type"], "input">;
5268
+ }
5269
+ | ({
5270
+ reason: "invalid-scope";
5271
+ stage: string;
5272
+ declaredType: FieldKind;
5273
+ } & (
5274
+ | {
5275
+ scope: "stage";
5276
+ }
5277
+ | {
5278
+ scope: "activity";
5279
+ activity: string;
5280
+ }
5281
+ ))
5282
+ );
5283
+
5284
+ /**
5285
+ * Explain every structural reason caller-supplied initial-field rows cannot
5286
+ * be consumed. Value-shape validation remains in the resolver because it
5287
+ * needs the matched declaration's complete schema.
5288
+ */
5289
+ export declare function initialFieldIssues(
5290
+ args: ScopedInitialFieldDeclarations & {
5291
+ initialFields: readonly InitialFieldValue[];
5292
+ },
5293
+ ): InitialFieldIssue[];
5294
+
5295
+ /**
5296
+ * Thrown before start writes when one or more caller-supplied `initialFields`
5297
+ * rows cannot be consumed by workflow-scope, input-sourced declarations.
5298
+ * `issues` is stable machine-readable remediation context; the message renders
5299
+ * the same complete set for logs and command-line callers.
5300
+ */
5301
+ export declare class InitialFieldsInvalidError extends WorkflowError<"initial-fields-invalid"> {
5302
+ readonly definition?: string;
5303
+ readonly issues: InitialFieldIssue[];
5304
+ constructor(args: { definition?: string; issues: InitialFieldIssue[] });
5305
+ }
5306
+
4714
5307
  /**
4715
5308
  * Initial value for an `input`-sourced entry — what a caller supplies at
4716
5309
  * `startInstance` (or `setStage`). Same discriminator shape as
@@ -4760,14 +5353,24 @@ export declare type InsightSite =
4760
5353
  activity?: string;
4761
5354
  };
4762
5355
 
5356
+ /**
5357
+ * Mint the Sanity document `_id` for a workflow instance — a fresh
5358
+ * {@link randomKey} suffix, so every instance (root or spawned child) gets a
5359
+ * unique doc id under its tag. Lives in the shell, not `core/`, because it
5360
+ * draws randomness; the deterministic {@link definitionDocId} is the pure-core
5361
+ * counterpart. Bare form — Sanity rejects `:` in document IDs, so this is
5362
+ * never a GDR URI.
5363
+ */
5364
+ export declare function instanceDocId(tag: string): string;
5365
+
4763
5366
  /**
4764
5367
  * The per-instance guard filter — the single definition of "this instance's
4765
5368
  * guards in one datasource". The engine's verdict load
4766
5369
  * ({@link verdictGuardsForInstance}) fetches it once against the engine
4767
5370
  * datasource; the reactive adapters feed the same query/params to their
4768
5371
  * stores as a live subscription; {@link guardsForInstance} unions it across
4769
- * datasources for housekeeping. Matches lifted guards too (predicate
4770
- * `"true"`) the consumer decides how to render a lifted guard.
5372
+ * datasources for housekeeping. Ordinary stage retraction deletes guards, so
5373
+ * results represent active persisted guard documents.
4771
5374
  */
4772
5375
  export declare function instanceGuardQuery(instanceId: string): CompiledQuery;
4773
5376
 
@@ -4815,7 +5418,10 @@ export declare interface InstanceSession {
4815
5418
  /** Replace the held content with the current values the consumer observed
4816
5419
  * (last-write-wins, scoped). A self-doc updates the held instance only when
4817
5420
  * its `_updatedAt` is strictly newer — an older or timestamp-equal echo is
4818
- * ignored. Buffered if a commit is in flight. */
5421
+ * ignored. Buffered if a commit is in flight; a BUFFERED push that fails
5422
+ * validation surfaces on the NEXT `update` call (after that call's own
5423
+ * docs are processed), never from the commit itself — the commit's
5424
+ * outcome is never masked by a store echo's failure. */
4819
5425
  update(docs: LoadedDoc[]): void;
4820
5426
  /** Replace the held live guards (the consumer's guard stream,
4821
5427
  * last-write-wins). Guards are a separate stream from the watch-set:
@@ -4858,10 +5464,11 @@ export declare interface InstanceSession {
4858
5464
  * replace the target's staged rows (last write wins); appends accumulate.
4859
5465
  * Tolerant where commits are loud: a target that doesn't resolve in the
4860
5466
  * current stage (it moved under a mounted editor), a closed edit window
4861
- * (a skipped activity's field), or a value that doesn't
4862
- * fit the field's shape yet (a half-typed date) stages an INERT preview
4863
- * no echo, no throw; the commit at the semantic boundary is the loud
4864
- * surface. Never persisted — commit via {@link InstanceSession.editField}. */
5467
+ * (a skipped activity's field), a value that doesn't fit the field's
5468
+ * shape yet (a half-typed date), or a ref outside the declared resource
5469
+ * surface stages an INERT preview — no echo, no throw; the commit at the
5470
+ * semantic boundary is the loud surface. Never persisted — commit via
5471
+ * {@link InstanceSession.editField}. */
4865
5472
  previewField(args: {
4866
5473
  target: EditFieldTarget;
4867
5474
  mode?: EditMode;
@@ -4879,8 +5486,10 @@ export declare interface InstancesForDocumentArgs {
4879
5486
 
4880
5487
  /**
4881
5488
  * The instance-list GROQ for a {@link InstancesQueryFilter}, ordered by
4882
- * `startedAt` ascending. Adapters subscribe to it; {@link workflow.query}
4883
- * -style one-shot reads fetch it — both see the same rows.
5489
+ * `startedAt` ascending (descending + sliced under
5490
+ * {@link InstancesQueryFilter.limit}). Adapters subscribe to it;
5491
+ * {@link workflow.query}-style one-shot reads fetch it — both see the same
5492
+ * rows.
4884
5493
  */
4885
5494
  export declare function instancesQuery(args: {
4886
5495
  tag: string;
@@ -4924,6 +5533,14 @@ export declare interface InstancesQueryFilter {
4924
5533
  stage?: string;
4925
5534
  /** Include completed/aborted instances (default: in-flight only). */
4926
5535
  includeCompleted?: boolean;
5536
+ /**
5537
+ * Cap the read to the NEWEST `limit` instances — the query flips to
5538
+ * `startedAt desc` and slices, so a bounded consumer (a dashboard over an
5539
+ * unbounded dataset) reads the most recent rows instead of the oldest.
5540
+ * Unlimited reads keep the ascending order adapters index by. Must be a
5541
+ * positive integer.
5542
+ */
5543
+ limit?: number;
4927
5544
  }
4928
5545
 
4929
5546
  /**
@@ -4978,7 +5595,7 @@ export declare function isDefinitionApplicable(args: {
4978
5595
  definition: ApplicabilitySource;
4979
5596
  document: CandidateDocument;
4980
5597
  /** Caller-side filter bindings — omit for the bare candidate-doc context. */
4981
- scope?: StartFilterScope;
5598
+ scope?: StartScope;
4982
5599
  }): Promise<boolean>;
4983
5600
 
4984
5601
  /**
@@ -4997,13 +5614,14 @@ export declare function isFilterScopedOut(entry: {
4997
5614
  export declare function isGdr(value: unknown): value is GlobalDocumentReference;
4998
5615
 
4999
5616
  /**
5000
- * Whether a guard has been lifted (retracted to the unconditional-allow
5001
- * predicate). A lift patches the predicate and keeps the doc, so lifted
5002
- * guards still appear in guard queries and streams — this is the
5003
- * discriminator a consumer filters or renders by.
5617
+ * Whether an entry is caller-filled at start/spawn the only source that
5618
+ * reads `initialFields`. Every other source (query/literal/fieldRead, or
5619
+ * working memory) resolves itself at materialisation, so a `$fields.<name>`
5620
+ * read of it can never bind on any read surface that evaluates a
5621
+ * `start.filter`. The deploy read-check keys on this.
5004
5622
  */
5005
- export declare function isGuardLifted(
5006
- guard: Pick<MutationGuardDoc, "predicate">,
5623
+ export declare function isInputSourced(
5624
+ entry: Pick<FieldEntry, "initialValue">,
5007
5625
  ): boolean;
5008
5626
 
5009
5627
  /**
@@ -5027,6 +5645,32 @@ export declare function isNotesEntry(
5027
5645
  }
5028
5646
  >;
5029
5647
 
5648
+ /** Whether a project-user API failure explicitly means the user is absent. */
5649
+ export declare function isProjectUserNotFoundError(error: unknown): boolean;
5650
+
5651
+ /** Entry-level {@link isSingleDocRefKind}: narrows a resolved entry to the
5652
+ * single-GDR arms (`doc.ref` / `subject`) — the value is one GDR (or null)
5653
+ * and the entry may carry the accepted-target `types`. */
5654
+ export declare function isSingleDocRefEntry(
5655
+ entry: ResolvedFieldEntry,
5656
+ ): entry is Extract<
5657
+ ResolvedFieldEntry,
5658
+ {
5659
+ _type: "doc.ref" | "subject";
5660
+ }
5661
+ >;
5662
+
5663
+ /**
5664
+ * Kinds whose value is a single content-document GDR — `doc.ref` and its
5665
+ * elevated alias `subject`. The one predicate behind every "this entry is one
5666
+ * document reference" branch (deref reads, query/spawn ref coercion, input
5667
+ * shape checks, watch-set collection), so the alias can't drift out of a
5668
+ * site.
5669
+ */
5670
+ export declare function isSingleDocRefKind(
5671
+ kind: string,
5672
+ ): kind is "doc.ref" | "subject";
5673
+
5030
5674
  /**
5031
5675
  * Whether a human may start this definition standalone (the default). A
5032
5676
  * `lifecycle: 'child'` definition is spawn-only — instantiated by a parent via
@@ -5040,15 +5684,16 @@ export declare function isStartableDefinition(definition: {
5040
5684
  }): boolean;
5041
5685
 
5042
5686
  /**
5043
- * The SUBJECT-ENTRY RULE: a required `doc.ref` / `doc.refs` input entry is
5044
- * what makes a definition "about" a subject document. One predicate shared by
5045
- * applicability's type matching (`acceptsDocumentType`), the autonomous-start
5046
- * deploy invariant, and the root-read deploy check (a root-reading
5047
- * `start.filter` needs a subject entry to bind a candidate root) so
5048
- * "counts as a subject" can't drift between them.
5687
+ * The SUBJECT-ENTRY RULE: the `subject`-kind entry is what makes a definition
5688
+ * "about" a subject document the kind is the discriminator, never a
5689
+ * heuristic over required refs. One predicate shared by applicability's type
5690
+ * matching (`acceptsDocumentType`), the autonomous-start deploy invariant,
5691
+ * and the root-read deploy check (a root-reading `start.filter` needs a
5692
+ * subject entry to bind a candidate root) — so "counts as a subject" can't
5693
+ * drift between them.
5049
5694
  */
5050
5695
  export declare function isSubjectEntry(
5051
- entry: Pick<FieldEntry, "required" | "type">,
5696
+ entry: Pick<FieldEntry, "type">,
5052
5697
  ): boolean;
5053
5698
 
5054
5699
  /**
@@ -5098,6 +5743,19 @@ export declare function isTodoListEntry(
5098
5743
  * as null). The remaining columns stay loose (field validation owns them). */
5099
5744
  export declare function isTodoListItem(row: unknown): row is TodoListItem;
5100
5745
 
5746
+ /**
5747
+ * True when the instance document exists but its start never finished: the
5748
+ * `create` committed, yet priming (stage entry, activities, guard deploy)
5749
+ * did not. The shape is legal and resumable — `startInstance` with the same
5750
+ * `instanceId` completes the outstanding commits. Readers should present it
5751
+ * as an incomplete start, never as a normal run sitting at its initial
5752
+ * stage. Terminal instances are excluded: an aborted never-primed husk
5753
+ * reads as aborted, not as still-resumable.
5754
+ */
5755
+ export declare function isUnprimed(
5756
+ instance: Pick<WorkflowInstance, "stages" | "completedAt" | "abortedAt">,
5757
+ ): boolean;
5758
+
5101
5759
  /**
5102
5760
  * Deterministic id so deploy/retract are query-free and idempotent. Derived
5103
5761
  * from the guard's authored `name` — an author-controlled handle that stays
@@ -5164,6 +5822,15 @@ export declare interface LoadedDoc {
5164
5822
 
5165
5823
  export declare type LoggerFactory = (name: string) => EngineLogger;
5166
5824
 
5825
+ /** A by-name reference to another workflow definition (an
5826
+ * `action.spawn.definition`). No `version` (or `'latest'`) means "the
5827
+ * highest deployed version at spawn time"; a numeric version pins one
5828
+ * deployed version permanently. */
5829
+ export declare interface LogicalRef {
5830
+ name: string;
5831
+ version?: number | "latest";
5832
+ }
5833
+
5167
5834
  export declare type ManualTarget = v.InferOutput<
5168
5835
  typeof StoredManualTargetSchema
5169
5836
  >;
@@ -5190,7 +5857,7 @@ export { MAX_COUNTERFACTUAL_INDEX };
5190
5857
 
5191
5858
  /**
5192
5859
  * The oldest engine model that can safely read a persisted engine document.
5193
- * The engine always writes the {@link MODEL_STAMP} pair, so a `modelVersion`
5860
+ * The engine always writes the stamp pair, so a `modelVersion`
5194
5861
  * with no `minReaderModel` is malformed foreign data — read conservatively:
5195
5862
  * the stamp itself is the floor. A doc with neither is model 0 (floor 0).
5196
5863
  */
@@ -5396,6 +6063,8 @@ export declare interface MutationGuardDoc extends MutationGuardBody {
5396
6063
  _updatedAt?: string;
5397
6064
  }
5398
6065
 
6066
+ export declare const MutationGuardDocSchema: v.GenericSchema<MutationGuardDoc>;
6067
+
5399
6068
  export declare interface MutationGuardMatch {
5400
6069
  types?: string[];
5401
6070
  /** Bare document ids (resource-local; both published and `drafts.` forms). */
@@ -5404,6 +6073,14 @@ export declare interface MutationGuardMatch {
5404
6073
  actions: MutationGuardAction[];
5405
6074
  }
5406
6075
 
6076
+ /** Every wait narrated, de-duplicated by phrase: same-named actions on
6077
+ * sibling activities are distinct waits that tell one story — consumers
6078
+ * want one line per story, not one per site. */
6079
+ export declare function narrateAutonomyWaits(
6080
+ waits: readonly AutonomyWait[],
6081
+ ctx: DescribeContext,
6082
+ ): string[];
6083
+
5407
6084
  /** The default logger: emit nothing. The engine's twin of `@sanity/telemetry`'s `noopLogger`. */
5408
6085
  export declare const noopTelemetry: WorkflowTelemetryLogger;
5409
6086
 
@@ -5527,12 +6204,7 @@ export declare function parseDefinitionInput(
5527
6204
  caller: string,
5528
6205
  ): WorkflowDefinition;
5529
6206
 
5530
- /**
5531
- * The ONE way to read an instance's frozen {@link WorkflowInstance.definitionSnapshot}
5532
- * back into a {@link WorkflowDefinition} — wraps the parse so a corrupt
5533
- * snapshot fails with the instance id attached instead of a bare
5534
- * `SyntaxError`.
5535
- */
6207
+ /** Parse the frozen definition from a complete persisted instance. */
5536
6208
  export declare function parseDefinitionSnapshot(
5537
6209
  instance: WorkflowInstance,
5538
6210
  ): WorkflowDefinition;
@@ -5549,12 +6221,34 @@ export declare interface ParsedGdr {
5549
6221
  documentId: string;
5550
6222
  }
5551
6223
 
6224
+ declare type ParsedWorkflowConfig = v.InferOutput<typeof WorkflowConfigSchema>;
6225
+
6226
+ declare type ParsedWorkflowDeployment =
6227
+ ParsedWorkflowConfig["deployments"][number];
6228
+
5552
6229
  /**
5553
6230
  * Parse a GDR URI into its scheme + addressing parts. Throws on
5554
6231
  * unknown scheme or malformed shape.
5555
6232
  */
5556
6233
  export declare function parseGdr(uri: string): ParsedGdr;
5557
6234
 
6235
+ /**
6236
+ * Parse a fetched guard document, failing hard with a
6237
+ * {@link PersistedDocShapeError} naming the document and every offending
6238
+ * field. No model-version gate: the guard doc carries no stamp by design —
6239
+ * its `_type` cutover is its versioning event.
6240
+ */
6241
+ export declare function parseGuardDocument(doc: unknown): MutationGuardDoc;
6242
+
6243
+ /**
6244
+ * Parse a fetched `sanity.workflow.instance` document, failing hard with a
6245
+ * {@link PersistedDocShapeError} naming the document and every offending
6246
+ * field. The caller's model-version gate (`assertReadableModel`) comes
6247
+ * FIRST — a doc from beyond the reader floor is a governed
6248
+ * `ModelVersionAheadError`, not a shape error.
6249
+ */
6250
+ export declare function parseInstanceDocument(doc: unknown): WorkflowInstance;
6251
+
5558
6252
  /**
5559
6253
  * Parse a resource-shaped GDR (`<type>:<id>`, no document part) into the
5560
6254
  * {@link WorkflowResource} it names. The one grammar for resource addresses —
@@ -5643,6 +6337,30 @@ export declare interface PendingEffectClaim {
5643
6337
  leaseExpiresAt?: string;
5644
6338
  }
5645
6339
 
6340
+ /**
6341
+ * A persisted engine-owned document failed its shape parse — the stored
6342
+ * field tree does not match what this engine's data model declares for the
6343
+ * doc type. Distinct from {@link ModelVersionAheadError} (a declared,
6344
+ * governed fence: upgrade the engine); this is UNGOVERNED corruption — a
6345
+ * document no conforming engine wrote — and the read fails hard instead of
6346
+ * letting the malformed value corrupt behavior downstream. The issues name
6347
+ * every offending field path.
6348
+ */
6349
+ export declare class PersistedDocShapeError extends WorkflowError<"persisted-doc-shape"> {
6350
+ readonly documentId: string;
6351
+ readonly documentType: string;
6352
+ readonly issues: ValidationIssue[];
6353
+ constructor(args: {
6354
+ documentId: string;
6355
+ documentType: string;
6356
+ issues: ValidationIssue[];
6357
+ });
6358
+ }
6359
+
6360
+ export declare type PersonActor = Omit<Actor, "kind"> & {
6361
+ readonly kind: "person";
6362
+ };
6363
+
5646
6364
  /**
5647
6365
  * One row of the instance's idempotency ledger — a caller-supplied
5648
6366
  * `idempotencyKey` a state-changing verb already committed under. Recorded in
@@ -5702,6 +6420,26 @@ export declare function projectToWatchRef(args: {
5702
6420
  perspective: WorkflowPerspective | undefined;
5703
6421
  }): SanityDocument;
5704
6422
 
6423
+ /** Host integration for current project-user data. */
6424
+ export declare interface ProjectUserDirectory<User> {
6425
+ /** Return `inaccessible` for lookup failures; directory methods must not reject. */
6426
+ readonly findById: (id: string) => Promise<ProjectUserLookup<User>>;
6427
+ readonly findByEmail?: (email: string) => Promise<ProjectUserLookup<User>>;
6428
+ }
6429
+
6430
+ export declare type ProjectUserLookup<User> =
6431
+ | {
6432
+ readonly status: "resolved";
6433
+ readonly user: User;
6434
+ }
6435
+ | {
6436
+ readonly status: "missing";
6437
+ }
6438
+ | {
6439
+ readonly status: "inaccessible";
6440
+ readonly cause?: unknown;
6441
+ };
6442
+
5705
6443
  export declare interface QueryArgs {
5706
6444
  groq: string;
5707
6445
  params?: Record<string, unknown>;
@@ -5711,6 +6449,28 @@ export declare interface QueryInScopeArgs extends InstanceRefArgs, QueryArgs {}
5711
6449
 
5712
6450
  export { quoted };
5713
6451
 
6452
+ export declare const READER_MODEL_ROLLOUT_URL =
6453
+ "https://github.com/sanity-io/workflows/blob/main/docs/reader-model-rollout.md";
6454
+
6455
+ /** A producer has not explicitly acknowledged this engine's writer capability. */
6456
+ export declare class ReaderModelAcknowledgementError extends WorkflowError<"reader-model-acknowledgement"> {
6457
+ readonly code = "WORKFLOW_READER_MODEL_ACKNOWLEDGEMENT_MISMATCH";
6458
+ readonly expectedMinReaderModel: unknown;
6459
+ readonly engineMinReaderModel = 2;
6460
+ readonly engineModelVersion = 2;
6461
+ readonly documentationUrl =
6462
+ "https://github.com/sanity-io/workflows/blob/main/docs/reader-model-rollout.md";
6463
+ constructor(expectedMinReaderModel: unknown, context?: string);
6464
+ }
6465
+
6466
+ /**
6467
+ * The one spelling of the instance read discipline — model gate
6468
+ * ({@link assertReadableModel}) first, shape parse
6469
+ * ({@link parseInstanceDocument}) second — shared by the point-read funnel
6470
+ * above and the list-read sites that fetch instance rows in bulk.
6471
+ */
6472
+ export declare function readInstanceDoc(doc: SanityDocument): WorkflowInstance;
6473
+
5714
6474
  /**
5715
6475
  * Whether a watched ref reads RAW — never perspective-scoped. The
5716
6476
  * instance, its ancestors, and its spawned children (all instance docs), and
@@ -5761,6 +6521,15 @@ export declare function refDataset<TType extends string = string>({
5761
6521
  type: TType;
5762
6522
  }): GlobalDocumentReference<TType>;
5763
6523
 
6524
+ /**
6525
+ * Kinds whose entries carry the accepted-target `types` facet — the
6526
+ * content-document reference kinds. `release.ref` is excluded: it points at a
6527
+ * release system doc whose type is fixed.
6528
+ */
6529
+ export declare function refKindAcceptsTypes(
6530
+ kind: string,
6531
+ ): kind is "doc.ref" | "doc.refs" | "subject";
6532
+
5764
6533
  /** Make a GDR pointer to a Media-Library-resource doc. */
5765
6534
  export declare function refMediaLibrary<TType extends string = string>({
5766
6535
  resourceId,
@@ -5769,15 +6538,36 @@ export declare function refMediaLibrary<TType extends string = string>({
5769
6538
  }: ResourceRefArgs<TType>): GlobalDocumentReference<TType>;
5770
6539
 
5771
6540
  /**
5772
- * The GDR `type`s in a `doc.ref` / `doc.refs` value that the entry's declared
5773
- * accepted `types` reject empty when the value conforms, the entry declares
5774
- * no `types`, or the kind isn't a ref. A GDR's `type` names the target
5775
- * document's schema type, so this is the declared-subject-type contract.
5776
- * Skips anything that isn't GDR-shaped — the shape schemas scream about
5777
- * those. Boundaries that need their own error framing (the spawn `with`
5778
- * projection gates its remediation hint on the generic `"document"` type
5779
- * being among the rejects) read this; everything else goes through
5780
- * {@link refTypeIssues} / {@link checkFieldValue}.
6541
+ * Thrown when a field write carries a ref to a resource outside the
6542
+ * declared surface. The write does not commit; the engine has not mutated
6543
+ * state.
6544
+ */
6545
+ export declare class RefResourceUndeclaredError extends WorkflowError<"ref-resource-undeclared"> {
6546
+ readonly entryType: string;
6547
+ readonly entryName: string;
6548
+ readonly issues: string[];
6549
+ constructor(args: { entryName: string; entryType: string; issues: string[] });
6550
+ }
6551
+
6552
+ /**
6553
+ * Every spawn reference a definition carries, as logical
6554
+ * {@link LogicalRef} entries — the definition's outgoing edges in the
6555
+ * spawn-dependency graph. The one walk deploy ordering, delete's
6556
+ * referrer check, and external dependency tooling all share, so what
6557
+ * counts as a reference can never drift between them.
6558
+ */
6559
+ export declare function refsOf(def: WorkflowDefinition): LogicalRef[];
6560
+
6561
+ /**
6562
+ * The GDR `type`s in a reference-kind ({@link refKindAcceptsTypes}) value
6563
+ * that the entry's declared accepted `types` reject — empty when the value
6564
+ * conforms, the entry declares no `types`, or the kind isn't a ref. A GDR's
6565
+ * `type` names the target document's schema type, so this is the
6566
+ * declared-target-type contract. Skips anything that isn't GDR-shaped — the
6567
+ * shape schemas scream about those. Boundaries that need their own error
6568
+ * framing (the spawn `with` projection gates its remediation hint on the
6569
+ * generic `"document"` type being among the rejects) read this; everything
6570
+ * else goes through {@link refTypeIssues} / {@link checkFieldValue}.
5781
6571
  */
5782
6572
  export declare function rejectedRefTypes(args: {
5783
6573
  entryType: string;
@@ -5865,12 +6655,18 @@ export declare class RequiredFieldNotProvidedError extends WorkflowError<"requir
5865
6655
  });
5866
6656
  }
5867
6657
 
5868
- /**
5869
- * Names an author's predicate may not use — engine-owned and author names
5870
- * share one namespace, so a predicate redefining a binding would silently
5871
- * shadow engine behaviour. Rejected at deploy; derived from
5872
- * {@link CONDITION_VARS}.
5873
- */
6658
+ /** The known persisted-model requirements applicable to one canonical stored tree. */
6659
+ export declare function requiredModelFeatures(
6660
+ documentType: DataModelDocumentType,
6661
+ document: unknown,
6662
+ ): readonly DataModelChange[];
6663
+
6664
+ /** The oldest engine model that can safely interpret one canonical stored tree. */
6665
+ export declare function requiredReaderModel(
6666
+ documentType: DataModelDocumentType,
6667
+ document: unknown,
6668
+ ): number;
6669
+
5874
6670
  export declare const RESERVED_CONDITION_VARS: readonly string[];
5875
6671
 
5876
6672
  /**
@@ -5894,6 +6690,23 @@ export declare interface ResolveAccessArgs {
5894
6690
  grantsFromPath?: string;
5895
6691
  }
5896
6692
 
6693
+ /** Resolves person provenance without treating agent or system ids as user ids. */
6694
+ export declare function resolveActor<User>(
6695
+ directory: ProjectUserDirectory<User>,
6696
+ actor: Actor,
6697
+ ): Promise<ActorResolution<User>>;
6698
+
6699
+ /** Resolve an actor through a plain engine client, without a UI adapter. */
6700
+ export declare function resolveClientActor(
6701
+ client: WorkflowClient,
6702
+ args: ResolveClientActorArgs,
6703
+ ): Promise<ActorResolution<ClientProjectUser>>;
6704
+
6705
+ export declare interface ResolveClientActorArgs {
6706
+ readonly actor: Actor;
6707
+ readonly projectId: string;
6708
+ }
6709
+
5897
6710
  /**
5898
6711
  * A resolved field entry as the engine persists it on an instance.
5899
6712
  * Discriminated by `_type` (bare — unique within this union); the `value`
@@ -5901,9 +6714,9 @@ export declare interface ResolveAccessArgs {
5901
6714
  * `initialValue` is `{type:'query'}`, of any kind) additionally carries
5902
6715
  * `resolvedAt` — the lake-read time — so `resolvedAt` is provenance-driven,
5903
6716
  * not kind-specific. `object` / `array` entries carry their declared
5904
- * sub-field shape (`fields` / `of`) — and `doc.ref` / `doc.refs` entries
5905
- * their declared accepted `types` — so the instance is self-describing for
5906
- * op-time validation and rendering.
6717
+ * sub-field shape (`fields` / `of`) — and the reference kinds
6718
+ * ({@link refKindAcceptsTypes}) their declared accepted `types` — so the
6719
+ * instance is self-describing for op-time validation and rendering.
5907
6720
  */
5908
6721
  export declare type ResolvedFieldEntry = {
5909
6722
  [K in FieldKind]: {
@@ -5915,6 +6728,8 @@ export declare type ResolvedFieldEntry = {
5915
6728
  value: FieldValueMap[K];
5916
6729
  /** Lake-read time, present only on `query`-sourced entries. */
5917
6730
  resolvedAt?: string;
6731
+ options?: ChoiceOptions;
6732
+ validation?: ScalarValidation;
5918
6733
  } & (K extends "object"
5919
6734
  ? {
5920
6735
  fields: FieldShape[];
@@ -5925,7 +6740,7 @@ export declare type ResolvedFieldEntry = {
5925
6740
  of: FieldShape[];
5926
6741
  }
5927
6742
  : Record<never, never>) &
5928
- (K extends "doc.ref" | "doc.refs"
6743
+ (K extends "doc.ref" | "doc.refs" | "subject"
5929
6744
  ? {
5930
6745
  types?: string[];
5931
6746
  }
@@ -5959,9 +6774,10 @@ export declare function resolveFieldEntry(
5959
6774
  export declare type ResourceAliases = Record<string, WorkflowResource>;
5960
6775
 
5961
6776
  /**
5962
- * Collapse a deployment's `resourceAliases` bindings into the engine's
5963
- * {@link ResourceAliases} map (handle name → physical resource) — the shape
5964
- * {@link deployDefinitions} consumes.
6777
+ * Collapse a deployment's `resourceAliases` bindings into the
6778
+ * {@link ResourceAliases} map (handle name → physical resource) that
6779
+ * `deployDefinitions` expands `@<handle>:` references against. Deploy-time
6780
+ * only — the map never reaches any other verb.
5965
6781
  */
5966
6782
  export declare function resourceAliasesToMap(
5967
6783
  resourceAliases: WorkflowDeployment["resourceAliases"],
@@ -5974,10 +6790,13 @@ export declare function resourceAliasesToMap(
5974
6790
  * engine route it — its own client for the workflow resource, a derived
5975
6791
  * sibling ({@link WorkflowClient.withConfig}) for anything else.
5976
6792
  *
5977
- * Resolved clients ride the engine's API version like every other engine
5978
- * client: the verb scope rebinds them onto `ENGINE_API_VERSION` when they
5979
- * carry {@link WorkflowClient.withConfig} (one without it is used as
5980
- * returned and must be built to serve that version).
6793
+ * Serving a resource also DECLARES it on the written-ref surface: the write
6794
+ * boundaries probe this resolver per ref, so narrowing a resolver rejects
6795
+ * refs to the resources it stops serving.
6796
+ *
6797
+ * Engine-owned verb scopes rebind resolved clients onto
6798
+ * `ENGINE_API_VERSION`. Effect handlers receive resolver clients unchanged
6799
+ * because handler traffic belongs to the host.
5981
6800
  */
5982
6801
  export declare type ResourceClientResolver = (
5983
6802
  parsed: ParsedGdr,
@@ -6008,11 +6827,18 @@ declare interface ResourceRefArgs<TType extends string> {
6008
6827
  type: TType;
6009
6828
  }
6010
6829
 
6830
+ export declare interface ResourceSurface {
6831
+ /** True when the parsed GDR targets a declared resource. */
6832
+ allows: (parsed: ParsedGdr) => boolean;
6833
+ /** Prose rendering of the declared surface, for rejection messages. */
6834
+ description: string;
6835
+ }
6836
+
6011
6837
  /**
6012
- * Retract every guard for a stage being exited (lift predicate to allow), but
6838
+ * Retract every guard for a stage being exited by deleting it, but
6013
6839
  * only once the instance has genuinely moved off that stage — or was aborted
6014
6840
  * on it: an aborted instance keeps its `currentStage` (no stage move), so the
6015
- * `abortedAt` stamp is what licenses lifting the stage it still occupies. The
6841
+ * `abortedAt` stamp is what licenses retracting the stage it still occupies. The
6016
6842
  * gate is `abortedAt`, not `completedAt` — normal completion parks the
6017
6843
  * instance on a structurally terminal stage whose guards must stay live.
6018
6844
  * Skip if it is still live on the stage (a concurrent loop-back re-entered
@@ -6079,8 +6905,52 @@ declare const RoleAliasesSchema: v.RecordSchema<
6079
6905
  undefined
6080
6906
  >;
6081
6907
 
6908
+ /** Whether two workflow resources address the same place. */
6909
+ export declare function sameResource(
6910
+ a: WorkflowResource,
6911
+ b: WorkflowResource,
6912
+ ): boolean;
6913
+
6914
+ /** Inclusive scalar bounds. String/text bounds measure character length;
6915
+ * number bounds measure the numeric value. */
6916
+ export declare interface ScalarValidation {
6917
+ min?: number | undefined;
6918
+ max?: number | undefined;
6919
+ }
6920
+
6921
+ export declare function scalarValidationIssues(args: {
6922
+ entryType: string;
6923
+ validation: ScalarValidation | undefined;
6924
+ value: unknown;
6925
+ }): string[] | undefined;
6926
+
6927
+ /**
6928
+ * The DECLARED field tree of a runtime schema — every entry key (optional
6929
+ * keys marked `key?`), every enum vocabulary, every variant arm — walked out
6930
+ * of valibot's runtime shape. The declared twin of `fieldTreeShape` (which
6931
+ * walks a CONCRETE document): the model-surface gate pins persisted doc
6932
+ * types by what their schema declares, so an optional field no fixture
6933
+ * reaches is still ledger-visible. Recursive grammars (a field shape nesting
6934
+ * field shapes) close with a `'(circular)'` marker.
6935
+ */
6936
+ export declare function schemaTreeShape(schema: v.GenericSchema): unknown;
6937
+
6082
6938
  export { ScopeAssignment };
6083
6939
 
6940
+ export declare interface ScopedInitialFieldDeclarations {
6941
+ workflowFields: readonly FieldEntry[];
6942
+ stages?: readonly {
6943
+ name: string;
6944
+ fields?: readonly FieldEntry[] | undefined;
6945
+ activities?:
6946
+ | readonly {
6947
+ name: string;
6948
+ fields?: readonly FieldEntry[] | undefined;
6949
+ }[]
6950
+ | undefined;
6951
+ }[];
6952
+ }
6953
+
6084
6954
  export { sentenceCase };
6085
6955
 
6086
6956
  export declare interface SessionArgs {
@@ -6115,6 +6985,27 @@ export declare interface SiteConsequence {
6115
6985
  after: ConditionOutcome;
6116
6986
  }
6117
6987
 
6988
+ /** One statically invalid parent-to-child spawn contract found at deploy. */
6989
+ export declare type SpawnContractIssue =
6990
+ | {
6991
+ reason: "unresolved-definition";
6992
+ from: string;
6993
+ ref: string;
6994
+ }
6995
+ | {
6996
+ reason: "unconsumable-field";
6997
+ from: string;
6998
+ child: string;
6999
+ field: string;
7000
+ detail: string;
7001
+ };
7002
+
7003
+ /** Deploy refused one or more invalid static `spawn` contracts. */
7004
+ export declare class SpawnContractsInvalidError extends WorkflowError<"spawn-contracts-invalid"> {
7005
+ readonly issues: SpawnContractIssue[];
7006
+ constructor(args: { issues: SpawnContractIssue[]; message: string });
7007
+ }
7008
+
6118
7009
  export declare type Stage = StageFields<
6119
7010
  FieldEntry,
6120
7011
  Activity,
@@ -6123,6 +7014,21 @@ export declare type Stage = StageFields<
6123
7014
  Editable
6124
7015
  >;
6125
7016
 
7017
+ /** A stage's rollup: progress through any exit transition, plus each
7018
+ * activity's own completion answer. */
7019
+ export declare interface StageAutonomy extends AutonomyAnswer {
7020
+ stage: string;
7021
+ activities: Record<string, AutonomyAnswer>;
7022
+ }
7023
+
7024
+ /** The stage slice of a rollup. Total by construction — the rollup derives
7025
+ * from the same `definition.stages` consumers iterate, so a miss is an
7026
+ * engine bug worth failing loud on. */
7027
+ export declare function stageAutonomyOf(
7028
+ autonomy: WorkflowAutonomy,
7029
+ stageName: string,
7030
+ ): StageAutonomy;
7031
+
6126
7032
  export declare interface StageEntry {
6127
7033
  _key: string;
6128
7034
  name: StageName;
@@ -6138,6 +7044,9 @@ export declare interface StageEvaluation {
6138
7044
  stage: Stage;
6139
7045
  activities: ActivityEvaluation[];
6140
7046
  transitions: TransitionEvaluation[];
7047
+ /** The stage's causal-autonomy rollup — will it progress without a caller,
7048
+ * and what does it wait on. Definition-derived, identical for every actor. */
7049
+ autonomy: StageAutonomy;
6141
7050
  }
6142
7051
 
6143
7052
  /** Type-mirror of {@link stageFields}, parameterised over field/activity/transition/guard/editable. */
@@ -6170,18 +7079,35 @@ declare interface StageGuardArgs {
6170
7079
 
6171
7080
  export declare type StageName = string;
6172
7081
 
7082
+ /**
7083
+ * The vars a definition's `start.allowed` reads — the start-time permission
7084
+ * dialect: everything the filter context binds ({@link START_FILTER_VARS})
7085
+ * plus `$fields`, which start time always has. No candidate root ever binds
7086
+ * here (the subject rides `$fields.<entry>.id`; a root read is
7087
+ * deploy-rejected). Pre-flights can omit not-yet-collected input bindings and
7088
+ * report those reads as provisional. Bound in one place: `startContextParams`
7089
+ * in the applicability evaluator.
7090
+ */
7091
+ export declare const START_ALLOWED_VARS: readonly {
7092
+ name: string;
7093
+ label: string;
7094
+ description: string;
7095
+ }[];
7096
+
6173
7097
  /**
6174
7098
  * The vars a definition's `start.filter` reads — the start-filter dialect,
6175
7099
  * not the rendered condition scope (no {@link ConditionVarBinding}: these
6176
7100
  * bind only while the filter is evaluated on the READ side — the
6177
7101
  * `definitionsForDocument` derivation and the Studio start control).
6178
- * `startInstance` never evaluates the filter. An absent binding evaluates to
6179
- * GROQ null, so a read of it fails closed: a picker with no `initialFields`
6180
- * in hand hides a `$fields`-reading definition rather than guessing. Bound in
6181
- * one place: `startFilterParams` in the applicability evaluator.
7102
+ * `startInstance` never evaluates the filter. BROWSE-TIME-PURE: `$fields` is
7103
+ * deliberately absent a `$fields` read is deploy-rejected with a pointer
7104
+ * to `start.allowed`. The synthetic subject variable requires a prospective
7105
+ * subject; evaluation throws when the caller omits that required scope.
7106
+ * Bound in one place: `startContextParams` in the applicability evaluator.
6182
7107
  */
6183
7108
  export declare const START_FILTER_VARS: readonly {
6184
7109
  name: string;
7110
+ label: string;
6185
7111
  description: string;
6186
7112
  }[];
6187
7113
 
@@ -6203,44 +7129,72 @@ export declare type StartBlock = StartFields & {
6203
7129
  */
6204
7130
  export declare type StartContext = Record<string, unknown>;
6205
7131
 
7132
+ /**
7133
+ * What `evaluateStart` projects — the same gates `startInstance` enforces,
7134
+ * as renderable state. `allowed` and `missingRequired` are deliberately
7135
+ * orthogonal: a missing required input is a caller mistake (the verb throws
7136
+ * `RequiredFieldNotProvidedError` for it), never a `start.allowed` verdict.
7137
+ */
7138
+ export declare interface StartEvaluation {
7139
+ /** Overall preflight startability: every supplied initial-field row is
7140
+ * valid and the `start.allowed` outcome is satisfied. `outcome` describes
7141
+ * only the condition; structurally invalid rows can therefore produce
7142
+ * `allowed: false` with `outcome: 'satisfied'`. */
7143
+ allowed: boolean;
7144
+ /**
7145
+ * Three-valued, BINDABILITY-AWARE verdict: `'unsatisfied'` is a definitive
7146
+ * no for these inputs (disable and explain), `'unevaluable'` means the
7147
+ * predicate can't be decided yet — either a null operand reached an
7148
+ * ordered comparison, or the predicate reads an entry `initialFields`
7149
+ * doesn't supply ({@link StartEvaluation.unboundReads} non-empty). Keep
7150
+ * the affordance enabled on `'unevaluable'`; `startInstance` still
7151
+ * enforces the final verdict, where absence is final rather than
7152
+ * provisional.
7153
+ */
7154
+ outcome: ConditionOutcome;
7155
+ /** The `start.allowed` atom breakdown over the SUPPLIED inputs — what to
7156
+ * render next to a disabled start affordance. Present iff the definition
7157
+ * declares `allowed`. With `unboundReads` non-empty its own `outcome` may
7158
+ * be a collapsed provisional answer — the top-level `outcome` is the
7159
+ * trustworthy one. */
7160
+ insight?: ConditionInsight;
7161
+ /** The `$fields` entries the predicate reads that `initialFields` doesn't
7162
+ * supply — "fill these to decide". Non-empty forces `outcome:
7163
+ * 'unevaluable'`. Empty when the definition declares no `allowed`. */
7164
+ unboundReads: string[];
7165
+ /** Required input entries `initialFields` doesn't fill yet — the rows a
7166
+ * `startInstance` now would throw `RequiredFieldNotProvidedError` about. */
7167
+ missingRequired: {
7168
+ name: string;
7169
+ type: FieldEntry["type"];
7170
+ }[];
7171
+ /** Supplied rows `startInstance` would reject before writing. Empty means
7172
+ * every row targets a workflow-scope, input-sourced declaration exactly
7173
+ * once. Render these as corrections, not an authorization verdict. */
7174
+ invalidInitialFields: InitialFieldIssue[];
7175
+ }
7176
+
6206
7177
  /** Type-mirror of {@link startFields}: how standalone runs of this workflow
6207
7178
  * begin. Stored requires `kind` (desugar fills the `'interactive'` default);
6208
7179
  * authoring may omit it — so each variant declares it. */
6209
7180
  declare type StartFields = {
6210
7181
  filter?: string | undefined;
7182
+ allowed?: string | undefined;
6211
7183
  };
6212
7184
 
6213
7185
  /**
6214
- * The caller-side half of the start-filter context — everything the
6215
- * evaluating surface knows that the definition doesn't. Every member is
6216
- * optional because the surfaces genuinely differ (a picker holds no
6217
- * `initialFields`; a pure consumer may hold no clock): an absent binding
6218
- * evaluates to GROQ null, so a filter reading it FAILS CLOSED rather than
6219
- * guessing. The vars themselves are inventoried in `START_FILTER_VARS`.
7186
+ * Project caller-supplied `initialFields` into the `$fields` map the
7187
+ * start-allowed context binds: one key per declared `input`-sourced entry,
7188
+ * resolved through {@link suppliedFieldFor} the predicate can only ever see
7189
+ * a value the input resolution would persist, and an undeclared supplied name
7190
+ * never leaks in. Unsupplied (or null-supplied) entries stay unbound, so a
7191
+ * read of one is GROQ null. Document references bind as their GDR envelopes
7192
+ * (`$fields.<entry>.id` is the GDR URI).
6220
7193
  */
6221
- export declare interface StartFilterScope {
6222
- /** The engine's tag partition — binds `$tag`. */
6223
- tag?: string | undefined;
6224
- /** ISO clock reading — binds `$now`. */
6225
- now?: string | undefined;
6226
- /**
6227
- * The candidate initialFields by entry name — binds `$fields`. Document
6228
- * references bind as their GDR envelopes (`$fields.<entry>.id` is the GDR
6229
- * URI). A surface that holds the caller's inputs (the Studio start control)
6230
- * supplies them; a per-doc picker does not.
6231
- */
6232
- fields?: Record<string, unknown> | undefined;
6233
- /**
6234
- * The WORKFLOW resource's dataset, for filters that read it (`*[...]` or a
6235
- * deref) — invoked lazily, only when `analyzeCondition` says the filter
6236
- * needs it. A slice is fine as long as it covers what filters scan;
6237
- * `definitionsForDocument` supplies EVERY instance of the tag, completed
6238
- * included — `*` carries no hidden predicate, so authors qualify in-flight
6239
- * themselves (`!defined(completedAt)`). Absent ⇒ a dataset-reading filter
6240
- * fails closed (this surface cannot see the dataset, so it cannot decide).
6241
- */
6242
- fetchDataset?: (() => Promise<unknown[]>) | undefined;
6243
- }
7194
+ export declare function startFieldsParam(args: {
7195
+ entryDefs: readonly FieldEntry[];
7196
+ initialFields: readonly InitialFieldValue[];
7197
+ }): Record<string, unknown>;
6244
7198
 
6245
7199
  export declare interface StartInstanceArgs {
6246
7200
  /** The definition's `name` — which deployed workflow to instantiate. */
@@ -6256,10 +7210,11 @@ export declare interface StartInstanceArgs {
6256
7210
  * `initialValue` resolution for query/working-memory entries).
6257
7211
  *
6258
7212
  * To start an instance "about" a specific document, declare a
6259
- * `{ type: "doc.ref", name: "subject", initialValue: { type: "input" } }`
7213
+ * `{ type: "subject", name: "subject", initialValue: { type: "input" } }`
6260
7214
  * entry on the workflow and pass
6261
- * `{ type: "doc.ref", name: "subject", value: { id, type } }` here.
6262
- * Conditions then read it as `$fields.subject`.
7215
+ * `{ type: "subject", name: "subject", value: { id, type } }` here.
7216
+ * Conditions then read it as `$fields.subject`, and document pickers key
7217
+ * on the `subject` kind.
6263
7218
  */
6264
7219
  initialFields?: InitialFieldValue[];
6265
7220
  ancestors?: GlobalDocumentReference[];
@@ -6275,7 +7230,14 @@ export declare interface StartInstanceArgs {
6275
7230
  * entry, so all forms read back the same under `$context.<name>`.
6276
7231
  */
6277
7232
  context?: StartContext;
6278
- /** Caller-supplied id; auto-generated otherwise. */
7233
+ /**
7234
+ * Caller-supplied id; auto-generated otherwise (mint one with
7235
+ * `instanceDocId`). This is start's idempotency key: a retry carrying the
7236
+ * same id resumes the earlier attempt's outstanding commits instead of
7237
+ * creating a duplicate — the per-instance request ledger can't cover the
7238
+ * create (it lives on the instance document), so id identity is the rail.
7239
+ * Inputs bind on the create only; a resume never re-reads them.
7240
+ */
6279
7241
  instanceId?: string;
6280
7242
  /**
6281
7243
  * URL path on the supplied client where the engine fetches the
@@ -6329,6 +7291,55 @@ export declare function startKindOf(definition: {
6329
7291
  | undefined;
6330
7292
  }): StartKind;
6331
7293
 
7294
+ /**
7295
+ * Thrown by `startInstance` when the definition's `start.allowed` predicate
7296
+ * is not satisfied for the supplied `initialFields` — evaluated false, or
7297
+ * GROQ null ("can't decide"; fail-closed, `insight.outcome` tells the two
7298
+ * apart). Carries the full {@link ConditionInsight} so a consumer renders
7299
+ * the same explanation the `evaluateStart` pre-flight would have shown.
7300
+ * There is no override arg — like `fireAction`, you don't bypass a verdict,
7301
+ * you change what produces it. Advisory under races like every engine-side
7302
+ * check.
7303
+ */
7304
+ export declare class StartNotAllowedError extends WorkflowError<"start-not-allowed"> {
7305
+ readonly definition: string;
7306
+ readonly insight: ConditionInsight;
7307
+ constructor(args: { definition: string; insight: ConditionInsight });
7308
+ }
7309
+
7310
+ /**
7311
+ * Thrown when a start fails AFTER its create committed but BEFORE priming:
7312
+ * the instance document exists, unprimed (see {@link isUnprimed}) — a
7313
+ * genuinely failed start, but a RESUMABLE one. The error names the id so
7314
+ * every surface's failure message can point at the retry rail: retrying
7315
+ * `startInstance` with this `instanceId` finishes the start instead of
7316
+ * creating a duplicate; aborting the instance discards it.
7317
+ */
7318
+ export declare class StartNotPrimedError extends WorkflowError<"start-not-primed"> {
7319
+ readonly instanceId: string;
7320
+ constructor(args: { instanceId: string; cause: unknown });
7321
+ }
7322
+
7323
+ /**
7324
+ * Thrown when a start's first cascade fails AFTER the instance was created
7325
+ * and primed: the run exists — stage entered, activities active, guards
7326
+ * deployed — it just hasn't auto-advanced to a stable stage yet. Callers
7327
+ * must not present this as a failed start: the workflow IS running, and a
7328
+ * `tick` (or retrying `startInstance` with the same `instanceId`) resumes
7329
+ * the settling.
7330
+ */
7331
+ export declare class StartNotSettledError extends WorkflowError<"start-not-settled"> {
7332
+ readonly instanceId: string;
7333
+ /** The primed instance re-read after the cascade failure — absent when
7334
+ * that best-effort read also failed. */
7335
+ readonly instance?: WorkflowInstance;
7336
+ constructor(args: {
7337
+ instanceId: string;
7338
+ instance?: WorkflowInstance;
7339
+ cause: unknown;
7340
+ });
7341
+ }
7342
+
6332
7343
  /**
6333
7344
  * Why a definition can't be started standalone, or `undefined` when it can.
6334
7345
  * Advisory — the engine itself does not refuse ({@link isStartableDefinition})
@@ -6339,6 +7350,43 @@ export declare function startRefusal(definition: {
6339
7350
  lifecycle?: WorkflowLifecycle | undefined;
6340
7351
  }): string | undefined;
6341
7352
 
7353
+ /**
7354
+ * The caller-side half of the start contexts — everything the evaluating
7355
+ * surface knows that the definition doesn't. Every member is optional
7356
+ * because the surfaces genuinely differ (a pure consumer may hold no clock):
7357
+ * except for the subject identity required by `$subjectHasInFlightInstance`,
7358
+ * an absent binding evaluates each read of it to GROQ null, and where that
7359
+ * null lands decides the verdict — a predicate that can't decide without
7360
+ * the binding fails closed, while a count-of-matches clause over values
7361
+ * every row stores passes vacuously (in GROQ null equals only null, so the
7362
+ * unbound read matches no stored value). The vars themselves are
7363
+ * inventoried in `START_FILTER_VARS` / `START_ALLOWED_VARS`; the caller's
7364
+ * `$fields` map is NOT scope — `start.filter` never binds it, and
7365
+ * `explainStartAllowed` takes it as its own argument.
7366
+ */
7367
+ export declare interface StartScope {
7368
+ /** The engine's tag partition — binds `$tag`. */
7369
+ tag?: string | undefined;
7370
+ /** ISO clock reading — binds `$now`. */
7371
+ now?: string | undefined;
7372
+ /** Resource-qualified identity of the prospective subject. Required when
7373
+ * `start.filter` reads `$subjectHasInFlightInstance`; unlike a loaded
7374
+ * document's bare `_id`, this stays collision-free across resources. */
7375
+ subject?: GdrUri | undefined;
7376
+ /**
7377
+ * The WORKFLOW resource's dataset, for predicates that read it (`*[...]` or
7378
+ * a deref) or `$subjectHasInFlightInstance` — invoked lazily only when the
7379
+ * predicate needs it. A slice is fine as long as it covers what predicates
7380
+ * scan; the engine verbs supply EVERY instance of the tag, completed
7381
+ * included — `*` carries no hidden predicate, so authors qualify in-flight
7382
+ * themselves (`!defined(completedAt)`). Absent ⇒ a dataset-reading filter
7383
+ * fails closed (this surface cannot see the dataset, so it cannot decide),
7384
+ * while a dataset-reading `allowed` THROWS — see
7385
+ * {@link explainStartAllowed}.
7386
+ */
7387
+ fetchDataset?: (() => Promise<unknown[]>) | undefined;
7388
+ }
7389
+
6342
7390
  /**
6343
7391
  * Declared editability of a field — the generic edit seam's gate. Default
6344
7392
  * (absent) is NOT editable: a field is op-only engine working memory unless the
@@ -7181,6 +8229,29 @@ declare type TransitionFields = {
7181
8229
  */
7182
8230
  export declare function tryParseGdr(uri: string): ParsedGdr | undefined;
7183
8231
 
8232
+ /**
8233
+ * The `$fields` entries a `start.allowed` predicate reads that `fields` does
8234
+ * not bind — the BINDABILITY rule every pre-flight surface applies before
8235
+ * trusting a verdict over possibly-incomplete inputs. GROQ equality against
8236
+ * a missing operand collapses to a definitive-looking answer (`null == x` is
8237
+ * false, not null), so a verdict that read an unbound entry is PROVISIONAL in
8238
+ * both directions: supplying the entry could overturn a refusal AND an
8239
+ * approval. Non-empty ⇒ report "can't decide yet" instead of the collapsed
8240
+ * verdict (the `evaluateStart` verb does; the Studio start control does).
8241
+ *
8242
+ * The GATE never consults this: at `startInstance` the supplied inputs are
8243
+ * final, so evaluating over genuine absence — including rules that WANT it,
8244
+ * like `!defined($fields.rush)` — is the correct semantics there.
8245
+ *
8246
+ * `fields` is a produced `$fields` map (`startFieldsParam`, or a surface's
8247
+ * seed map) — producers bind only own keys with real values, so key presence
8248
+ * IS the supplied-ness rule.
8249
+ */
8250
+ export declare function unboundAllowedReads(
8251
+ allowed: string,
8252
+ fields: Record<string, unknown>,
8253
+ ): string[];
8254
+
7184
8255
  /**
7185
8256
  * The diagnose idiom shared by every consumer: each unsatisfied exit
7186
8257
  * transition with its insight summary. Consumers shape the result (the CLI
@@ -7221,27 +8292,13 @@ export declare function validateDefinition(
7221
8292
  definition: WorkflowDefinition,
7222
8293
  ): void;
7223
8294
 
7224
- /**
7225
- * Engine-scope tag — the environment partition primitive.
7226
- *
7227
- * Every engine operates against exactly one `tag` ("test", "prod", …).
7228
- * The tag partitions definitions and instances within a single workflow
7229
- * resource: `deploy(def, tag: "test")` and `deploy(def, tag: "prod")` are
7230
- * separate deployed workflows with independent lifecycles, and every read
7231
- * is scoped to one tag so test runs never surface in prod.
7232
- *
7233
- * The tag must be Sanity-ID-compatible since it's joined into IDs with
7234
- * `.` — ASCII lowercase + digits + dashes, no leading dash, no dots. It
7235
- * is also the ID prefix for every definition/instance the engine writes.
7236
- *
7237
- * There is no default. The tag selects which environment the engine reads
7238
- * and writes, and the engine enforces nothing — so the partition is the
7239
- * only thing keeping test runs out of prod. Every entry point
7240
- * (`createEngine`, the CLI, the MCP server) requires it explicitly and
7241
- * fails when it's absent rather than guessing an environment.
7242
- */
7243
8295
  export declare function validateTag(tag: string): void;
7244
8296
 
8297
+ declare interface ValidationIssue {
8298
+ path: ReadonlyArray<PropertyKey>;
8299
+ message: string;
8300
+ }
8301
+
7245
8302
  export declare type ValueExpr = ValueExprInternal;
7246
8303
 
7247
8304
  declare type ValueExprInternal =
@@ -7311,7 +8368,7 @@ export declare const wallClock: Clock;
7311
8368
  export declare interface WatchSet {
7312
8369
  /**
7313
8370
  * Docs to subscribe to — instance + ancestors + the docs named by
7314
- * `doc.ref`/`doc.refs`/`release.ref` field entries — deduped by
8371
+ * `doc.ref`/`subject`/`doc.refs`/`release.ref` field entries — deduped by
7315
8372
  * `globalDocumentId`. Refs that aren't resource-qualified GDR URIs are
7316
8373
  * skipped (without a resource there's nothing to subscribe against).
7317
8374
  */
@@ -7386,18 +8443,36 @@ export declare const workflow: {
7386
8443
  /**
7387
8444
  * Spawn a new workflow instance from a deployed definition.
7388
8445
  *
7389
- * Throws only on its own input contract a missing `required` input
7390
- * ({@link RequiredFieldNotProvidedError}) or a value that doesn't fit its
7391
- * declared kind. It does NOT evaluate the definition's `start.filter`: that
7392
- * is a read-side visibility rule (see `definitionsForDocument`), so a
7393
- * caller who supplies everything needed starts, full stop.
8446
+ * Two gates run before anything is written, in order: the required-input
8447
+ * check a missing `required` input is a CALLER mistake
8448
+ * ({@link RequiredFieldNotProvidedError}) then the definition's
8449
+ * `start.allowed` permission predicate, whose false/GROQ-null VERDICT
8450
+ * throws {@link StartNotAllowedError} (declaring the expression is the
8451
+ * opt-in; there is no override arg — pre-flight with `evaluateStart`).
8452
+ * Per-value SHAPE validation fires during field resolution, after the
8453
+ * verdict but still before any write. It does NOT evaluate `start.filter`:
8454
+ * that is a read-side visibility rule (see `definitionsForDocument`).
7394
8455
  *
7395
8456
  * Pins the snapshot at start-time, seeds the `context` bag, and enters
7396
8457
  * the initial stage — fields resolve and every in-scope activity is
7397
8458
  * born active. Then cascades until stable, so the initial stage's
7398
- * `when: 'true'` triggers have fired by the time this returns. Starting
7399
- * always changes state (`changed: true`) — `cascaded` reports how far
7400
- * the new instance auto-advanced.
8459
+ * `when: 'true'` triggers have fired by the time this returns.
8460
+ *
8461
+ * Start is three commits — create, prime, first cascade — and a supplied
8462
+ * `instanceId` is its idempotency key across them. When that id already
8463
+ * exists under this tag for the same start, the call RESUMES: input gates
8464
+ * and field resolution are skipped (those values were pinned at create)
8465
+ * and the outstanding commits run — the retry path for a start that
8466
+ * failed after its create landed (see `isUnprimed`). Reusing an id for a
8467
+ * DIFFERENT start (definition or explicit version mismatch) — or for an
8468
+ * unfinished start that was aborted (a discarded start) — throws
8469
+ * {@link ContractViolationError}. The mid-sequence failures are typed and
8470
+ * carry the retry id: a prime failure after the create committed throws
8471
+ * `StartNotPrimedError` (failed but resumable), and a cascade failure
8472
+ * after a successful prime throws `StartNotSettledError` — the run exists
8473
+ * by then and must not be reported as a failed start. `changed` is `true`
8474
+ * on a fresh start and rev-derived on a resume; `cascaded` reports how far
8475
+ * the instance auto-advanced.
7401
8476
  */
7402
8477
  startInstance: (
7403
8478
  rawArgs: Clocked<Telemetered<StartInstanceArgs & EngineScopeArgs>>,
@@ -7517,10 +8592,13 @@ export declare const workflow: {
7517
8592
  * filters see for a given instance.
7518
8593
  *
7519
8594
  * Hydrates the instance's snapshot (instance + ancestors + every doc
7520
- * declared by a `doc.ref` / `doc.refs` entry in scope), then
8595
+ * declared by a `doc.ref` / `subject` / `doc.refs` entry in scope), then
7521
8596
  * evaluates the supplied GROQ in groq-js against that dataset. The
7522
- * rendered scope's instance-derived vars ({@link FILTER_SCOPE_VARS}) are
7523
- * auto-bound ids in GDR URI form to match the snapshot's keying.
8597
+ * caller-free rendered scope cascade gates evaluate in is auto-bound —
8598
+ * the instance-derived vars ({@link FILTER_SCOPE_VARS}) with the open
8599
+ * stage's overlay merged into `$fields`, `$assigned` at its caller-free
8600
+ * `false`, plus the author's pre-evaluated `$<predicate>` booleans —
8601
+ * ids in GDR URI form to match the snapshot's keying.
7524
8602
  *
7525
8603
  * Use when an external consumer (a UI, a debug pane, a test) wants
7526
8604
  * to ask "what does the engine see for this workflow right now?"
@@ -7600,7 +8678,8 @@ export declare const workflow: {
7600
8678
  * Inngest/durable worker, any server) that holds no instances in memory: a
7601
8679
  * document changed; which instances should it `tick`? The watch-set covers
7602
8680
  * the instance itself, its ancestors, and the docs named by
7603
- * `doc.ref` / `doc.refs` / `release.ref` field entries on the workflow scope
8681
+ * `doc.ref` / `subject` / `doc.refs` / `release.ref` field entries on the
8682
+ * workflow scope
7604
8683
  * **and the current stage** — so a hand-rolled GROQ over `fields[]` gets it
7605
8684
  * subtly wrong (misses stage-scope refs, `release.ref`, ancestors).
7606
8685
  *
@@ -7629,21 +8708,44 @@ export declare const workflow: {
7629
8708
  * deployed definition that APPLIES to `document` — what a start picker for
7630
8709
  * it should offer. Loads the latest deployed version of each definition
7631
8710
  * visible to the engine's tag and filters it through the derivation
7632
- * ({@link applicableDefinitions}): startable ∧ a required subject entry
8711
+ * ({@link applicableDefinitions}): startable ∧ the `subject`-kind entry
7633
8712
  * accepts the doc's `_type` ∧ `start.filter` passes — evaluated in the
7634
- * start-filter context with `$tag`/`$definition`/`$now` bound and the
7635
- * tag's instance slice (completed included) backing dataset reads. A picker holds no
7636
- * `initialFields`, so `$fields` stays unbound here and a filter reading it
7637
- * fails closed (the definition is not surfaced).
8713
+ * browse-time-pure start-filter context with `$tag`/`$definition`/`$now`
8714
+ * bound and the tag's instance slice (completed included) backing dataset
8715
+ * reads. `start.allowed` never participates permission is a start-time
8716
+ * question; pre-flight it with {@link workflow.evaluateStart}.
7638
8717
  *
7639
8718
  * Takes the LOADED candidate document, not a ref — applicability evaluates
7640
- * its content, under whatever perspective the caller read it with. Surfaces
7641
- * ALL matches (name ascending), no engine ranking — presenting a picker or
7642
- * auto-picking is consumer policy. Advisory like every engine-side check.
8719
+ * its content, under whatever perspective the caller read it with. A
8720
+ * resource-qualified `subject` accompanies it when a filter reads
8721
+ * `$subjectHasInFlightInstance`; a bare document id cannot identify a
8722
+ * cross-resource subject. Surfaces ALL matches (name ascending), no engine
8723
+ * ranking — presenting a picker or auto-picking is consumer policy.
8724
+ * Advisory like every engine-side check.
7643
8725
  */
7644
8726
  definitionsForDocument: (
7645
8727
  rawArgs: Clocked<DefinitionsForDocumentArgs & EngineScopeArgs>,
7646
8728
  ) => Promise<DeployedDefinition[]>;
8729
+ /**
8730
+ * Pre-flight the start gates for a definition + candidate `initialFields` —
8731
+ * the read `startInstance` enforces, as a {@link StartEvaluation} a surface
8732
+ * can render: `missingRequired` mirrors the input contract
8733
+ * ({@link RequiredFieldNotProvidedError}'s rows), and `allowed` /
8734
+ * `outcome` / `insight` carry the `start.allowed` verdict with its atom
8735
+ * breakdown — disable the start affordance on a definitive `false` and say
8736
+ * why. BINDABILITY-AWARE for partial mid-form inputs: when the predicate
8737
+ * reads an entry `initialFields` doesn't supply, `outcome` is
8738
+ * `'unevaluable'` and `unboundReads` names the entries ("fill these to
8739
+ * decide") instead of the collapsed answer GROQ equality would give —
8740
+ * this is the ONE deliberate divergence from the gate, where absence is
8741
+ * final, not provisional (a rule like `!defined($fields.rush)` genuinely
8742
+ * passes there when `rush` is absent). A definition with no
8743
+ * `start.allowed` is vacuously allowed, exactly like the verb. Pure read;
8744
+ * advisory under races — the enforcement moment is `startInstance` itself.
8745
+ */
8746
+ evaluateStart: (
8747
+ rawArgs: Clocked<EvaluateStartArgs & EngineScopeArgs>,
8748
+ ) => Promise<StartEvaluation>;
7647
8749
  /**
7648
8750
  * Permission helpers — Sanity ACL grants evaluated against documents
7649
8751
  * via GROQ. Used by `workflow.evaluate` to soft-gate actions when the
@@ -7705,6 +8807,12 @@ export declare interface WorkflowAdminOverrideData extends InstanceScopedEventDa
7705
8807
  changed: boolean;
7706
8808
  }
7707
8809
 
8810
+ /** The whole-definition rollup: every stage must progress, so the workflow
8811
+ * verdict conjoins the stage verdicts. */
8812
+ export declare interface WorkflowAutonomy extends AutonomyAnswer {
8813
+ stages: StageAutonomy[];
8814
+ }
8815
+
7708
8816
  export declare interface WorkflowClient {
7709
8817
  fetch: <T = unknown>(
7710
8818
  query: string,
@@ -7854,7 +8962,12 @@ export declare interface WorkflowCommitOptions {
7854
8962
  tag?: string;
7855
8963
  }
7856
8964
 
7857
- export declare type WorkflowConfig = v.InferOutput<typeof WorkflowConfigSchema>;
8965
+ export declare type WorkflowConfig = Omit<
8966
+ ParsedWorkflowConfig,
8967
+ "deployments"
8968
+ > & {
8969
+ deployments: WorkflowDeployment[];
8970
+ };
7858
8971
 
7859
8972
  declare const WorkflowConfigSchema: v.ObjectSchema<
7860
8973
  {
@@ -7869,6 +8982,10 @@ declare const WorkflowConfigSchema: v.ObjectSchema<
7869
8982
  v.NonEmptyAction<string, "must not be empty">,
7870
8983
  ]
7871
8984
  >;
8985
+ readonly expectedMinReaderModel: v.OptionalSchema<
8986
+ v.CustomSchema<2, undefined>,
8987
+ undefined
8988
+ >;
7872
8989
  readonly tag: v.SchemaWithPipe<
7873
8990
  readonly [
7874
8991
  v.StringSchema<undefined>,
@@ -8130,6 +9247,7 @@ declare const WorkflowConfigSchema: v.ObjectSchema<
8130
9247
  v.MinLengthAction<
8131
9248
  {
8132
9249
  name: string;
9250
+ expectedMinReaderModel?: 2 | undefined;
8133
9251
  tag: string;
8134
9252
  workflowResource:
8135
9253
  | {
@@ -8190,6 +9308,7 @@ declare const WorkflowConfigSchema: v.ObjectSchema<
8190
9308
  v.CheckAction<
8191
9309
  {
8192
9310
  name: string;
9311
+ expectedMinReaderModel?: 2 | undefined;
8193
9312
  tag: string;
8194
9313
  workflowResource:
8195
9314
  | {
@@ -8351,7 +9470,12 @@ declare const WorkflowDefinitionSchema: v.GenericSchema<
8351
9470
  WorkflowFields<FieldEntry, Stage, StartBlock>
8352
9471
  >;
8353
9472
 
8354
- export declare type WorkflowDeployment = WorkflowConfig["deployments"][number];
9473
+ export declare type WorkflowDeployment = Omit<
9474
+ ParsedWorkflowDeployment,
9475
+ "expectedMinReaderModel"
9476
+ > & {
9477
+ expectedMinReaderModel: typeof DATA_MODEL_MIN_READER;
9478
+ };
8355
9479
 
8356
9480
  export declare const WorkflowEffectCompleted: WorkflowTelemetryEvent<WorkflowEffectCompletedData>;
8357
9481
 
@@ -8399,23 +9523,31 @@ export declare abstract class WorkflowError<
8399
9523
 
8400
9524
  export declare type WorkflowErrorKind =
8401
9525
  | "action-disabled"
9526
+ | "start-not-allowed"
8402
9527
  | "edit-field-denied"
8403
9528
  | "mutation-guard-denied"
8404
9529
  | "action-params-invalid"
8405
9530
  | "effect-ops-invalid"
8406
9531
  | "effect-outputs-invalid"
8407
9532
  | "required-field-not-provided"
9533
+ | "initial-fields-invalid"
8408
9534
  | "workflow-state-diverged"
8409
9535
  | "partial-guard-deploy"
9536
+ | "start-not-primed"
9537
+ | "start-not-settled"
8410
9538
  | "concurrent-fire-action"
8411
9539
  | "concurrent-edit-field"
8412
9540
  | "concurrent-complete-effect"
8413
9541
  | "cascade-limit"
8414
9542
  | "field-value-shape"
9543
+ | "ref-resource-undeclared"
8415
9544
  | "model-version-ahead"
9545
+ | "reader-model-acknowledgement"
9546
+ | "persisted-doc-shape"
8416
9547
  | "instance-not-found"
8417
9548
  | "definition-not-found"
8418
9549
  | "definition-in-use"
9550
+ | "spawn-contracts-invalid"
8419
9551
  | "effect-not-found"
8420
9552
  | "missing-effect-handler"
8421
9553
  | "contract-violation";
@@ -8443,6 +9575,14 @@ export declare interface WorkflowEvaluation {
8443
9575
  * Empty when no condition reads a field.
8444
9576
  */
8445
9577
  fieldInsights: FieldInsight[];
9578
+ /**
9579
+ * The whole definition's causal-autonomy rollup: per activity, per stage,
9580
+ * and workflow-wide — will each level resolve without a caller, and what
9581
+ * does it wait on. Static (no instance state), stamped here so every
9582
+ * consumer reads one derivation. Spawn children are not resolved at
9583
+ * evaluation time, so their legs report `conditional`.
9584
+ */
9585
+ autonomy: WorkflowAutonomy;
8446
9586
  }
8447
9587
 
8448
9588
  export declare interface WorkflowFetchOptions {
@@ -8472,6 +9612,12 @@ export declare interface WorkflowFieldEditedData extends InstanceScopedEventData
8472
9612
 
8473
9613
  /** Type-mirror of {@link workflowFields}, parameterised over field/stage/start. */
8474
9614
  declare type WorkflowFields<TField, TStage, TStart> = {
9615
+ /**
9616
+ * Lake-id-segment grammar (`^[a-z0-9][a-z0-9-]*$`, deploy-enforced): the
9617
+ * name interpolates into every deployed document id
9618
+ * (`<tag>.<name>.v<version>`). Stable identity — instances pin to it,
9619
+ * subworkflows resolve by it.
9620
+ */
8475
9621
  name: string;
8476
9622
  title: string;
8477
9623
  description?: string | undefined;
@@ -8497,9 +9643,9 @@ export declare interface WorkflowInstance extends SanityDocument {
8497
9643
  modelVersion?: number;
8498
9644
  /**
8499
9645
  * Reader floor — the oldest engine data model that can safely interpret
8500
- * this document (see {@link DATA_MODEL_MIN_READER}). Written alongside
8501
- * {@link WorkflowInstance.modelVersion}; additive model changes leave it,
8502
- * only breaking ones raise it.
9646
+ * this document. Derived from features actually present, bounded by
9647
+ * {@link DATA_MODEL_MIN_READER}, and written alongside
9648
+ * {@link WorkflowInstance.modelVersion}. Full persists never lower it.
8503
9649
  */
8504
9650
  minReaderModel?: number;
8505
9651
  /**
@@ -8606,6 +9752,13 @@ export declare interface WorkflowInstance extends SanityDocument {
8606
9752
 
8607
9753
  export declare const WorkflowInstanceAborted: WorkflowTelemetryEvent<WorkflowAdminOverrideData>;
8608
9754
 
9755
+ /**
9756
+ * The persisted instance document, root to leaf. Reused verbatim by every
9757
+ * engine read boundary through {@link parseInstanceDocument}; the
9758
+ * model-surface gate derives its shape ledger from this schema.
9759
+ */
9760
+ export declare const WorkflowInstanceSchema: v.GenericSchema<WorkflowInstance>;
9761
+
8609
9762
  export declare const WorkflowInstanceStarted: WorkflowTelemetryEvent<WorkflowInstanceStartedData>;
8610
9763
 
8611
9764
  export declare interface WorkflowInstanceStartedData extends InstanceScopedEventData {
@@ -8800,10 +9953,11 @@ export declare interface WorkflowTransaction {
8800
9953
  /**
8801
9954
  * Queue a document delete. Deliberately a transaction-only capability —
8802
9955
  * the top-level client surface stays delete-free so no engine code path
8803
- * can casually remove documents; the sole consumer is `deleteDefinition`
8804
- * housekeeping (definition docs + orphaned guard docs). Both the real
8805
- * `@sanity/client` Transaction and the test fake's TransactionHandle
8806
- * carry this shape.
9956
+ * can casually remove documents. Engine-side consumers are guard retraction
9957
+ * and `deleteDefinition` housekeeping; outside the engine the CLI's `nuke`
9958
+ * reset deletes raw engine-owned docs through it. Both the real
9959
+ * `@sanity/client` Transaction and the test fake's TransactionHandle carry
9960
+ * this shape.
8807
9961
  */
8808
9962
  delete: (id: string) => WorkflowTransaction;
8809
9963
  /**