@sanity/workflow-engine 0.2.0 → 0.3.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
@@ -517,6 +517,24 @@ declare type Bound<
517
517
  },
518
518
  > = Omit<Args, "client" | "tags" | "workflowResource">;
519
519
 
520
+ /**
521
+ * Build a snapshot from a set of loaded docs. Pure transform.
522
+ *
523
+ * For each `LoadedDoc { doc, resource }`:
524
+ * - The doc's `_id` is rewritten to `gdrFromResource(resource, _id)`.
525
+ * - Every nested `_ref` value (recursively, through objects and arrays)
526
+ * is rewritten the same way: bare ref → URI in the parent's resource.
527
+ * Already-qualified `_ref` URIs (containing `:`) are left alone.
528
+ *
529
+ * Plain string fields, numbers, booleans — anything that isn't an
530
+ * object with a `_ref` field — pass through untouched. The rewriter is
531
+ * structural, not schema-aware: only the `_ref` field on an object is
532
+ * treated as a Sanity reference. Everything else is user data.
533
+ */
534
+ export declare function buildSnapshot(args: {
535
+ docs: LoadedDoc[];
536
+ }): HydratedSnapshot;
537
+
520
538
  export declare function canonicalTag(tags: string[]): string;
521
539
 
522
540
  /**
@@ -627,6 +645,29 @@ export declare class ConcurrentFireActionError extends Error {
627
645
  });
628
646
  }
629
647
 
648
+ /**
649
+ * The Content Release a watched doc resolves under, or `undefined` for a raw
650
+ * read — how a reactive adapter turns the watch-set's perspective into the
651
+ * per-doc release its store needs. Instance / ancestor / `system.release` docs
652
+ * read raw ({@link readsRaw}); a content doc resolves under the **leading**
653
+ * release in the stack. The engine never forms the `versions.<release>.<id>`
654
+ * id — the adapter does (Studio's `editState` version arg, the SDK handle
655
+ * `{releaseName}`). A non-array perspective (`raw`/`published`/`drafts`)
656
+ * carries no release, so content falls back to draft/published.
657
+ *
658
+ * Note: this resolves a **single** release — the documented `instance.perspective`
659
+ * shapes (`[release]` / `[release, "drafts"]`). The stores' per-doc reads take
660
+ * one release, so a multi-release stack can't be observed reactively here; the
661
+ * engine's own fetch path ({@link hydrateSnapshot}) honours the full stack via
662
+ * `client.fetch({perspective})`.
663
+ */
664
+ export declare function contentReleaseName(args: {
665
+ ref: {
666
+ type: string;
667
+ };
668
+ perspective: WorkflowPerspective | undefined;
669
+ }): string | undefined;
670
+
630
671
  /**
631
672
  * Construct an engine bound to a workflow resource + tags. The returned
632
673
  * object exposes the same verbs as the `workflow.*` namespace, minus
@@ -994,6 +1035,28 @@ export declare interface Engine {
994
1035
  setStage: (args: Bound<SetStageArgs>) => Promise<DispatchResult>;
995
1036
  /** Fetch a workflow instance by id. */
996
1037
  getInstance: (args: { instanceId: string }) => Promise<WorkflowInstance>;
1038
+ /** The reactive {@link WatchSet} for an instance — every document whose
1039
+ * change should re-evaluate it (the instance, its ancestors, and the docs
1040
+ * named by `doc.ref`/`doc.refs`/`release.ref` slots on the workflow scope
1041
+ * + current stage) as exploded {@link SubscriptionDocument}s, plus the
1042
+ * instance's read perspective. Fetches the instance, then derives. A
1043
+ * reactive adapter that already holds the live instance calls the pure
1044
+ * `subscriptionDocumentsForInstance` directly instead, to avoid the re-fetch. */
1045
+ subscriptionDocumentsForInstance: (args: {
1046
+ instanceId: string;
1047
+ }) => Promise<WatchSet>;
1048
+ /** Opt into reactivity: bind the engine to an instance doc and get a stateful
1049
+ * {@link InstanceSession}. Push the docs it lists in `subscriptionDocuments`
1050
+ * via `update`, then `evaluate`/`tick`/`fireAction` against the held state —
1051
+ * pushed content is never refetched. The session never observes or ticks on
1052
+ * its own; the consumer drives it. */
1053
+ instance: (
1054
+ instanceDoc: WorkflowInstance,
1055
+ opts?: {
1056
+ access?: WorkflowAccessOverride;
1057
+ grantsFromPath?: string;
1058
+ },
1059
+ ) => InstanceSession;
997
1060
  /** Every lake mutation guard this instance registered, unioned across the
998
1061
  * instance's own resource and the resource of each `doc.ref`/`doc.refs`
999
1062
  * GDR it holds in state. For coherency refresh and housekeeping. */
@@ -1100,6 +1163,43 @@ export declare interface EvaluateArgs {
1100
1163
  resourceClients?: (parsed: ParsedGdr) => WorkflowClient | undefined;
1101
1164
  }
1102
1165
 
1166
+ /**
1167
+ * The pure projection at the heart of {@link evaluateInstance}: given an
1168
+ * instance, its definition, the resolved actor/grants, and a snapshot,
1169
+ * compute "what can this actor do right now, and why not the rest." No
1170
+ * I/O — feed it a fresh snapshot (e.g. rebuilt from a live store on
1171
+ * change) for reactive re-evaluation. Best-effort by design: the lake's
1172
+ * mutation guards + ACL are the real enforcement.
1173
+ */
1174
+ export declare function evaluateFromSnapshot(
1175
+ args: EvaluateFromSnapshotArgs,
1176
+ ): Promise<WorkflowEvaluation>;
1177
+
1178
+ export declare interface EvaluateFromSnapshotArgs {
1179
+ instance: WorkflowInstance;
1180
+ definition: WorkflowDefinition;
1181
+ actor: Actor;
1182
+ /**
1183
+ * Resolved grants for the actor. Omit to skip the `permission-denied`
1184
+ * gate (graceful degradation — the real lake write boundary still
1185
+ * enforces).
1186
+ */
1187
+ grants?: Grant[];
1188
+ /**
1189
+ * The in-memory snapshot to evaluate against. The caller assembles it
1190
+ * from whatever source — a fetch (see {@link evaluateInstance}) or a
1191
+ * live store. The `_id` of every doc must be in GDR-URI form, as
1192
+ * {@link buildSnapshot} produces.
1193
+ */
1194
+ snapshot: HydratedSnapshot;
1195
+ /**
1196
+ * The `$now` reading every filter in this projection shares. Omit to
1197
+ * use {@link wallClock}; a reactive consumer (or the bench) passes its
1198
+ * own clock reading for a stable/deterministic timeline.
1199
+ */
1200
+ now?: string;
1201
+ }
1202
+
1103
1203
  declare function evaluateInstance(
1104
1204
  args: Clocked<EvaluateArgs>,
1105
1205
  ): Promise<WorkflowEvaluation>;
@@ -1239,7 +1339,7 @@ export declare type GdrScheme =
1239
1339
  * type + runtime guarantee that no bare-string id reaches the
1240
1340
  * snapshot, slot, or filter layer.
1241
1341
  */
1242
- declare type GdrUri = `${GdrScheme}:${string}`;
1342
+ export declare type GdrUri = `${GdrScheme}:${string}`;
1243
1343
 
1244
1344
  /** Compose a GDR URI from parts. Inverse of `parseGdr`. */
1245
1345
  export declare function gdrUri(
@@ -1603,6 +1703,22 @@ export declare type HistoryEntry =
1603
1703
  actor?: Actor;
1604
1704
  };
1605
1705
 
1706
+ /**
1707
+ * The in-memory groq-js dataset filters evaluate against. Built once
1708
+ * per cascade entry (in the shell), passed through to `evaluateFilter`
1709
+ * (in the core) for every filter check.
1710
+ */
1711
+ export declare interface HydratedSnapshot {
1712
+ /** All hydrated docs, keyed by GDR URI as `_id`. */
1713
+ docs: SanityDocument[];
1714
+ /**
1715
+ * The set of GDR URIs present in `docs`. Helpful for tests and shells
1716
+ * that want to assert "is this doc in scope?" — the core eval pipeline
1717
+ * itself doesn't read it.
1718
+ */
1719
+ knownIds: Set<string>;
1720
+ }
1721
+
1606
1722
  /**
1607
1723
  * Initial value for an `init`-sourced slot — what a caller supplies at
1608
1724
  * `startInstance` (or `setStage`). Same discriminator shape as
@@ -1618,6 +1734,28 @@ export declare type InitialStateValue = {
1618
1734
  };
1619
1735
  }[Exclude<StateSlotKind, "workflow.state.query">];
1620
1736
 
1737
+ export declare interface InstanceSession {
1738
+ /** The watch-set to feed via {@link InstanceSession.update}, derived from the
1739
+ * held instance. Recompute after the instance changes (a new stage changes it). */
1740
+ readonly subscriptionDocuments: WatchSet;
1741
+ /** Replace the held content with the current values the consumer observed
1742
+ * (last-write-wins, scoped). A doc whose id is the instance itself updates
1743
+ * the held instance. Buffered if a commit is in flight. */
1744
+ update(docs: LoadedDoc[]): void;
1745
+ /** Best-effort projection against the held content. No network (async only
1746
+ * because filter evaluation runs on groq-js). */
1747
+ evaluate(): Promise<WorkflowEvaluation>;
1748
+ /** Advance the instance against the held content: cascade auto-transitions,
1749
+ * deploy guards, queue effects, commit with `ifRevisionId`. */
1750
+ tick(): Promise<DispatchResult>;
1751
+ /** Fire an action against a task, gated on the held content, then cascade. */
1752
+ fireAction(args: {
1753
+ taskId: string;
1754
+ action: string;
1755
+ params?: Record<string, unknown>;
1756
+ }): Promise<DispatchResult>;
1757
+ }
1758
+
1621
1759
  /**
1622
1760
  * Compare a deployed definition document to what we'd write next. Skip
1623
1761
  * Sanity-managed system fields (`_rev`, `_createdAt`, `_updatedAt`) —
@@ -1639,6 +1777,16 @@ export declare function lakeGuardId(args: {
1639
1777
  index: number;
1640
1778
  }): string;
1641
1779
 
1780
+ /**
1781
+ * One loaded doc plus the resource it came from. The shell builds
1782
+ * this list by routing reads to the right client for each doc, then
1783
+ * hands it to `buildSnapshot`.
1784
+ */
1785
+ export declare interface LoadedDoc {
1786
+ doc: SanityDocument;
1787
+ resource: WorkflowResource;
1788
+ }
1789
+
1642
1790
  export declare type LoggerFactory = (name: string) => EngineLogger;
1643
1791
 
1644
1792
  /**
@@ -2122,6 +2270,17 @@ declare const PredicateSchema: v.StrictObjectSchema<
2122
2270
  undefined
2123
2271
  >;
2124
2272
 
2273
+ /**
2274
+ * Whether a watched ref reads RAW — never perspective-scoped. The
2275
+ * instance, its ancestors (both `workflow.instance`), and `release.ref`
2276
+ * system docs (`system.release`) are not versioned by a release
2277
+ * perspective; everything else is content and resolves to its
2278
+ * version/draft/published form. This is the single encoding of the rule
2279
+ * the {@link WatchSet} `perspective` contract describes to consumers, and
2280
+ * the rule {@link hydrateSnapshot} applies on the fetch side.
2281
+ */
2282
+ export declare function readsRaw(ref: { type: string }): boolean;
2283
+
2125
2284
  /** Make a GDR pointer to a Canvas-resource doc. */
2126
2285
  export declare function refCanvas(
2127
2286
  resourceId: string,
@@ -2241,6 +2400,8 @@ declare type ResourceClientResolver = (
2241
2400
  parsed: ParsedGdr,
2242
2401
  ) => WorkflowClient | undefined;
2243
2402
 
2403
+ export declare function resourceFromParsed(parsed: ParsedGdr): WorkflowResource;
2404
+
2244
2405
  /**
2245
2406
  * Retract every guard for a stage being exited (lift predicate to allow), but
2246
2407
  * only once the instance has genuinely moved off that stage. Skip if it is
@@ -4404,6 +4565,36 @@ export declare function stripSystemFields(
4404
4565
  doc: Record<string, unknown>,
4405
4566
  ): Record<string, unknown>;
4406
4567
 
4568
+ /**
4569
+ * A document the reactive layer should subscribe to, with its GDR
4570
+ * exploded so consumers never re-parse: the resource-addressing parts
4571
+ * (from {@link ParsedGdr}), the round-trip `globalDocumentId` URI, and
4572
+ * the target doc's schema `_type`. The resource parts let an adapter
4573
+ * detect refs pointing at a project/dataset/resource it isn't
4574
+ * configured for; `type` feeds per-doc handles (`useDocument` /
4575
+ * `editState`).
4576
+ */
4577
+ export declare interface SubscriptionDocument extends ParsedGdr {
4578
+ /** Full GDR URI — resource-qualified identity, for round-trip / snapshot keying. */
4579
+ globalDocumentId: GdrUri;
4580
+ /** Target doc's schema `_type`. */
4581
+ type: string;
4582
+ }
4583
+
4584
+ export declare function subscriptionDocumentsForInstance(
4585
+ instance: WorkflowInstance,
4586
+ ): WatchSet;
4587
+
4588
+ /**
4589
+ * The engine's read-partition invariant as a GROQ predicate: a document is
4590
+ * visible when its `tags[]` intersect the caller's tag set.
4591
+ *
4592
+ * @param param - GROQ parameter holding the caller's tags. Defaults to
4593
+ * `engineTags`, matching the `workflow.query` guard so any query built with
4594
+ * it stays tag-scoped.
4595
+ */
4596
+ export declare function tagScopeFilter(param?: string): string;
4597
+
4407
4598
  export declare type Task = v.InferOutput<typeof TaskSchema>;
4408
4599
 
4409
4600
  /**
@@ -5541,6 +5732,35 @@ export declare interface VerifyDeployedDefinitionsResult {
5541
5732
  /** The default clock: real wall-clock time as an ISO-8601 string. */
5542
5733
  export declare const wallClock: Clock;
5543
5734
 
5735
+ /**
5736
+ * The reactive subscription target for an instance: the exploded,
5737
+ * deduped watch-set plus the instance's read {@link WorkflowPerspective}.
5738
+ */
5739
+ export declare interface WatchSet {
5740
+ /**
5741
+ * Docs to subscribe to — instance + ancestors + the docs named by
5742
+ * `doc.ref`/`doc.refs`/`release.ref` slots — deduped by
5743
+ * `globalDocumentId`. Refs that aren't resource-qualified GDR URIs are
5744
+ * skipped (without a resource there's nothing to subscribe against).
5745
+ */
5746
+ documents: SubscriptionDocument[];
5747
+ /**
5748
+ * The instance's effective read perspective (a release stack like
5749
+ * `[releaseName]` or `[releaseName, "drafts"]`), already resolved at
5750
+ * `startInstance`. `undefined` means raw/published.
5751
+ *
5752
+ * The engine does not form `versions.<release>.<id>` ids — it has no
5753
+ * such concept (it reads content through `client.fetch({perspective})`
5754
+ * for queries). A reactive consumer applies this perspective when it
5755
+ * subscribes: a **content** doc must be resolved to its version /
5756
+ * draft / published form (e.g. Studio `editState`'s version param, or
5757
+ * the SDK's `getVersionId`), while instance / ancestor / `system.release`
5758
+ * docs are always raw. The `type` on each {@link SubscriptionDocument}
5759
+ * tells the consumer which is which.
5760
+ */
5761
+ perspective?: WorkflowPerspective;
5762
+ }
5763
+
5544
5764
  /**
5545
5765
  * The four-method workflow API.
5546
5766
  */
@@ -5740,6 +5960,20 @@ export declare const workflow: {
5740
5960
  };
5741
5961
  };
5742
5962
 
5963
+ /**
5964
+ * The lake document type for a deployed workflow definition. Single source of
5965
+ * truth — every tag-scoped lookup query and the deploy write derive their
5966
+ * `_type` from here, mirroring {@link WORKFLOW_INSTANCE_TYPE}.
5967
+ */
5968
+ export declare const WORKFLOW_DEFINITION_TYPE = "workflow.definition";
5969
+
5970
+ /**
5971
+ * The lake document type for a workflow instance. Single source of truth — the
5972
+ * {@link WorkflowInstance} `_type`, every tag-scoped query, and the create
5973
+ * write all derive from here, mirroring {@link WORKFLOW_DEFINITION_TYPE}.
5974
+ */
5975
+ export declare const WORKFLOW_INSTANCE_TYPE = "workflow.instance";
5976
+
5743
5977
  /**
5744
5978
  * The engine's view of "who am I, what can I do?". `actor` is who
5745
5979
  * the engine stamps onto history / `completedBy` / `Source.actor`.
@@ -8284,7 +8518,7 @@ export declare interface WorkflowFetchOptions {
8284
8518
  }
8285
8519
 
8286
8520
  export declare interface WorkflowInstance extends SanityDocument {
8287
- _type: "workflow.instance";
8521
+ _type: typeof WORKFLOW_INSTANCE_TYPE;
8288
8522
  /**
8289
8523
  * Engine-scope tags stamped on the instance at create time. Engines
8290
8524
  * with disjoint tag sets cannot read this instance; engines sharing
package/dist/index.d.ts CHANGED
@@ -517,6 +517,24 @@ declare type Bound<
517
517
  },
518
518
  > = Omit<Args, "client" | "tags" | "workflowResource">;
519
519
 
520
+ /**
521
+ * Build a snapshot from a set of loaded docs. Pure transform.
522
+ *
523
+ * For each `LoadedDoc { doc, resource }`:
524
+ * - The doc's `_id` is rewritten to `gdrFromResource(resource, _id)`.
525
+ * - Every nested `_ref` value (recursively, through objects and arrays)
526
+ * is rewritten the same way: bare ref → URI in the parent's resource.
527
+ * Already-qualified `_ref` URIs (containing `:`) are left alone.
528
+ *
529
+ * Plain string fields, numbers, booleans — anything that isn't an
530
+ * object with a `_ref` field — pass through untouched. The rewriter is
531
+ * structural, not schema-aware: only the `_ref` field on an object is
532
+ * treated as a Sanity reference. Everything else is user data.
533
+ */
534
+ export declare function buildSnapshot(args: {
535
+ docs: LoadedDoc[];
536
+ }): HydratedSnapshot;
537
+
520
538
  export declare function canonicalTag(tags: string[]): string;
521
539
 
522
540
  /**
@@ -627,6 +645,29 @@ export declare class ConcurrentFireActionError extends Error {
627
645
  });
628
646
  }
629
647
 
648
+ /**
649
+ * The Content Release a watched doc resolves under, or `undefined` for a raw
650
+ * read — how a reactive adapter turns the watch-set's perspective into the
651
+ * per-doc release its store needs. Instance / ancestor / `system.release` docs
652
+ * read raw ({@link readsRaw}); a content doc resolves under the **leading**
653
+ * release in the stack. The engine never forms the `versions.<release>.<id>`
654
+ * id — the adapter does (Studio's `editState` version arg, the SDK handle
655
+ * `{releaseName}`). A non-array perspective (`raw`/`published`/`drafts`)
656
+ * carries no release, so content falls back to draft/published.
657
+ *
658
+ * Note: this resolves a **single** release — the documented `instance.perspective`
659
+ * shapes (`[release]` / `[release, "drafts"]`). The stores' per-doc reads take
660
+ * one release, so a multi-release stack can't be observed reactively here; the
661
+ * engine's own fetch path ({@link hydrateSnapshot}) honours the full stack via
662
+ * `client.fetch({perspective})`.
663
+ */
664
+ export declare function contentReleaseName(args: {
665
+ ref: {
666
+ type: string;
667
+ };
668
+ perspective: WorkflowPerspective | undefined;
669
+ }): string | undefined;
670
+
630
671
  /**
631
672
  * Construct an engine bound to a workflow resource + tags. The returned
632
673
  * object exposes the same verbs as the `workflow.*` namespace, minus
@@ -994,6 +1035,28 @@ export declare interface Engine {
994
1035
  setStage: (args: Bound<SetStageArgs>) => Promise<DispatchResult>;
995
1036
  /** Fetch a workflow instance by id. */
996
1037
  getInstance: (args: { instanceId: string }) => Promise<WorkflowInstance>;
1038
+ /** The reactive {@link WatchSet} for an instance — every document whose
1039
+ * change should re-evaluate it (the instance, its ancestors, and the docs
1040
+ * named by `doc.ref`/`doc.refs`/`release.ref` slots on the workflow scope
1041
+ * + current stage) as exploded {@link SubscriptionDocument}s, plus the
1042
+ * instance's read perspective. Fetches the instance, then derives. A
1043
+ * reactive adapter that already holds the live instance calls the pure
1044
+ * `subscriptionDocumentsForInstance` directly instead, to avoid the re-fetch. */
1045
+ subscriptionDocumentsForInstance: (args: {
1046
+ instanceId: string;
1047
+ }) => Promise<WatchSet>;
1048
+ /** Opt into reactivity: bind the engine to an instance doc and get a stateful
1049
+ * {@link InstanceSession}. Push the docs it lists in `subscriptionDocuments`
1050
+ * via `update`, then `evaluate`/`tick`/`fireAction` against the held state —
1051
+ * pushed content is never refetched. The session never observes or ticks on
1052
+ * its own; the consumer drives it. */
1053
+ instance: (
1054
+ instanceDoc: WorkflowInstance,
1055
+ opts?: {
1056
+ access?: WorkflowAccessOverride;
1057
+ grantsFromPath?: string;
1058
+ },
1059
+ ) => InstanceSession;
997
1060
  /** Every lake mutation guard this instance registered, unioned across the
998
1061
  * instance's own resource and the resource of each `doc.ref`/`doc.refs`
999
1062
  * GDR it holds in state. For coherency refresh and housekeeping. */
@@ -1100,6 +1163,43 @@ export declare interface EvaluateArgs {
1100
1163
  resourceClients?: (parsed: ParsedGdr) => WorkflowClient | undefined;
1101
1164
  }
1102
1165
 
1166
+ /**
1167
+ * The pure projection at the heart of {@link evaluateInstance}: given an
1168
+ * instance, its definition, the resolved actor/grants, and a snapshot,
1169
+ * compute "what can this actor do right now, and why not the rest." No
1170
+ * I/O — feed it a fresh snapshot (e.g. rebuilt from a live store on
1171
+ * change) for reactive re-evaluation. Best-effort by design: the lake's
1172
+ * mutation guards + ACL are the real enforcement.
1173
+ */
1174
+ export declare function evaluateFromSnapshot(
1175
+ args: EvaluateFromSnapshotArgs,
1176
+ ): Promise<WorkflowEvaluation>;
1177
+
1178
+ export declare interface EvaluateFromSnapshotArgs {
1179
+ instance: WorkflowInstance;
1180
+ definition: WorkflowDefinition;
1181
+ actor: Actor;
1182
+ /**
1183
+ * Resolved grants for the actor. Omit to skip the `permission-denied`
1184
+ * gate (graceful degradation — the real lake write boundary still
1185
+ * enforces).
1186
+ */
1187
+ grants?: Grant[];
1188
+ /**
1189
+ * The in-memory snapshot to evaluate against. The caller assembles it
1190
+ * from whatever source — a fetch (see {@link evaluateInstance}) or a
1191
+ * live store. The `_id` of every doc must be in GDR-URI form, as
1192
+ * {@link buildSnapshot} produces.
1193
+ */
1194
+ snapshot: HydratedSnapshot;
1195
+ /**
1196
+ * The `$now` reading every filter in this projection shares. Omit to
1197
+ * use {@link wallClock}; a reactive consumer (or the bench) passes its
1198
+ * own clock reading for a stable/deterministic timeline.
1199
+ */
1200
+ now?: string;
1201
+ }
1202
+
1103
1203
  declare function evaluateInstance(
1104
1204
  args: Clocked<EvaluateArgs>,
1105
1205
  ): Promise<WorkflowEvaluation>;
@@ -1239,7 +1339,7 @@ export declare type GdrScheme =
1239
1339
  * type + runtime guarantee that no bare-string id reaches the
1240
1340
  * snapshot, slot, or filter layer.
1241
1341
  */
1242
- declare type GdrUri = `${GdrScheme}:${string}`;
1342
+ export declare type GdrUri = `${GdrScheme}:${string}`;
1243
1343
 
1244
1344
  /** Compose a GDR URI from parts. Inverse of `parseGdr`. */
1245
1345
  export declare function gdrUri(
@@ -1603,6 +1703,22 @@ export declare type HistoryEntry =
1603
1703
  actor?: Actor;
1604
1704
  };
1605
1705
 
1706
+ /**
1707
+ * The in-memory groq-js dataset filters evaluate against. Built once
1708
+ * per cascade entry (in the shell), passed through to `evaluateFilter`
1709
+ * (in the core) for every filter check.
1710
+ */
1711
+ export declare interface HydratedSnapshot {
1712
+ /** All hydrated docs, keyed by GDR URI as `_id`. */
1713
+ docs: SanityDocument[];
1714
+ /**
1715
+ * The set of GDR URIs present in `docs`. Helpful for tests and shells
1716
+ * that want to assert "is this doc in scope?" — the core eval pipeline
1717
+ * itself doesn't read it.
1718
+ */
1719
+ knownIds: Set<string>;
1720
+ }
1721
+
1606
1722
  /**
1607
1723
  * Initial value for an `init`-sourced slot — what a caller supplies at
1608
1724
  * `startInstance` (or `setStage`). Same discriminator shape as
@@ -1618,6 +1734,28 @@ export declare type InitialStateValue = {
1618
1734
  };
1619
1735
  }[Exclude<StateSlotKind, "workflow.state.query">];
1620
1736
 
1737
+ export declare interface InstanceSession {
1738
+ /** The watch-set to feed via {@link InstanceSession.update}, derived from the
1739
+ * held instance. Recompute after the instance changes (a new stage changes it). */
1740
+ readonly subscriptionDocuments: WatchSet;
1741
+ /** Replace the held content with the current values the consumer observed
1742
+ * (last-write-wins, scoped). A doc whose id is the instance itself updates
1743
+ * the held instance. Buffered if a commit is in flight. */
1744
+ update(docs: LoadedDoc[]): void;
1745
+ /** Best-effort projection against the held content. No network (async only
1746
+ * because filter evaluation runs on groq-js). */
1747
+ evaluate(): Promise<WorkflowEvaluation>;
1748
+ /** Advance the instance against the held content: cascade auto-transitions,
1749
+ * deploy guards, queue effects, commit with `ifRevisionId`. */
1750
+ tick(): Promise<DispatchResult>;
1751
+ /** Fire an action against a task, gated on the held content, then cascade. */
1752
+ fireAction(args: {
1753
+ taskId: string;
1754
+ action: string;
1755
+ params?: Record<string, unknown>;
1756
+ }): Promise<DispatchResult>;
1757
+ }
1758
+
1621
1759
  /**
1622
1760
  * Compare a deployed definition document to what we'd write next. Skip
1623
1761
  * Sanity-managed system fields (`_rev`, `_createdAt`, `_updatedAt`) —
@@ -1639,6 +1777,16 @@ export declare function lakeGuardId(args: {
1639
1777
  index: number;
1640
1778
  }): string;
1641
1779
 
1780
+ /**
1781
+ * One loaded doc plus the resource it came from. The shell builds
1782
+ * this list by routing reads to the right client for each doc, then
1783
+ * hands it to `buildSnapshot`.
1784
+ */
1785
+ export declare interface LoadedDoc {
1786
+ doc: SanityDocument;
1787
+ resource: WorkflowResource;
1788
+ }
1789
+
1642
1790
  export declare type LoggerFactory = (name: string) => EngineLogger;
1643
1791
 
1644
1792
  /**
@@ -2122,6 +2270,17 @@ declare const PredicateSchema: v.StrictObjectSchema<
2122
2270
  undefined
2123
2271
  >;
2124
2272
 
2273
+ /**
2274
+ * Whether a watched ref reads RAW — never perspective-scoped. The
2275
+ * instance, its ancestors (both `workflow.instance`), and `release.ref`
2276
+ * system docs (`system.release`) are not versioned by a release
2277
+ * perspective; everything else is content and resolves to its
2278
+ * version/draft/published form. This is the single encoding of the rule
2279
+ * the {@link WatchSet} `perspective` contract describes to consumers, and
2280
+ * the rule {@link hydrateSnapshot} applies on the fetch side.
2281
+ */
2282
+ export declare function readsRaw(ref: { type: string }): boolean;
2283
+
2125
2284
  /** Make a GDR pointer to a Canvas-resource doc. */
2126
2285
  export declare function refCanvas(
2127
2286
  resourceId: string,
@@ -2241,6 +2400,8 @@ declare type ResourceClientResolver = (
2241
2400
  parsed: ParsedGdr,
2242
2401
  ) => WorkflowClient | undefined;
2243
2402
 
2403
+ export declare function resourceFromParsed(parsed: ParsedGdr): WorkflowResource;
2404
+
2244
2405
  /**
2245
2406
  * Retract every guard for a stage being exited (lift predicate to allow), but
2246
2407
  * only once the instance has genuinely moved off that stage. Skip if it is
@@ -4404,6 +4565,36 @@ export declare function stripSystemFields(
4404
4565
  doc: Record<string, unknown>,
4405
4566
  ): Record<string, unknown>;
4406
4567
 
4568
+ /**
4569
+ * A document the reactive layer should subscribe to, with its GDR
4570
+ * exploded so consumers never re-parse: the resource-addressing parts
4571
+ * (from {@link ParsedGdr}), the round-trip `globalDocumentId` URI, and
4572
+ * the target doc's schema `_type`. The resource parts let an adapter
4573
+ * detect refs pointing at a project/dataset/resource it isn't
4574
+ * configured for; `type` feeds per-doc handles (`useDocument` /
4575
+ * `editState`).
4576
+ */
4577
+ export declare interface SubscriptionDocument extends ParsedGdr {
4578
+ /** Full GDR URI — resource-qualified identity, for round-trip / snapshot keying. */
4579
+ globalDocumentId: GdrUri;
4580
+ /** Target doc's schema `_type`. */
4581
+ type: string;
4582
+ }
4583
+
4584
+ export declare function subscriptionDocumentsForInstance(
4585
+ instance: WorkflowInstance,
4586
+ ): WatchSet;
4587
+
4588
+ /**
4589
+ * The engine's read-partition invariant as a GROQ predicate: a document is
4590
+ * visible when its `tags[]` intersect the caller's tag set.
4591
+ *
4592
+ * @param param - GROQ parameter holding the caller's tags. Defaults to
4593
+ * `engineTags`, matching the `workflow.query` guard so any query built with
4594
+ * it stays tag-scoped.
4595
+ */
4596
+ export declare function tagScopeFilter(param?: string): string;
4597
+
4407
4598
  export declare type Task = v.InferOutput<typeof TaskSchema>;
4408
4599
 
4409
4600
  /**
@@ -5541,6 +5732,35 @@ export declare interface VerifyDeployedDefinitionsResult {
5541
5732
  /** The default clock: real wall-clock time as an ISO-8601 string. */
5542
5733
  export declare const wallClock: Clock;
5543
5734
 
5735
+ /**
5736
+ * The reactive subscription target for an instance: the exploded,
5737
+ * deduped watch-set plus the instance's read {@link WorkflowPerspective}.
5738
+ */
5739
+ export declare interface WatchSet {
5740
+ /**
5741
+ * Docs to subscribe to — instance + ancestors + the docs named by
5742
+ * `doc.ref`/`doc.refs`/`release.ref` slots — deduped by
5743
+ * `globalDocumentId`. Refs that aren't resource-qualified GDR URIs are
5744
+ * skipped (without a resource there's nothing to subscribe against).
5745
+ */
5746
+ documents: SubscriptionDocument[];
5747
+ /**
5748
+ * The instance's effective read perspective (a release stack like
5749
+ * `[releaseName]` or `[releaseName, "drafts"]`), already resolved at
5750
+ * `startInstance`. `undefined` means raw/published.
5751
+ *
5752
+ * The engine does not form `versions.<release>.<id>` ids — it has no
5753
+ * such concept (it reads content through `client.fetch({perspective})`
5754
+ * for queries). A reactive consumer applies this perspective when it
5755
+ * subscribes: a **content** doc must be resolved to its version /
5756
+ * draft / published form (e.g. Studio `editState`'s version param, or
5757
+ * the SDK's `getVersionId`), while instance / ancestor / `system.release`
5758
+ * docs are always raw. The `type` on each {@link SubscriptionDocument}
5759
+ * tells the consumer which is which.
5760
+ */
5761
+ perspective?: WorkflowPerspective;
5762
+ }
5763
+
5544
5764
  /**
5545
5765
  * The four-method workflow API.
5546
5766
  */
@@ -5740,6 +5960,20 @@ export declare const workflow: {
5740
5960
  };
5741
5961
  };
5742
5962
 
5963
+ /**
5964
+ * The lake document type for a deployed workflow definition. Single source of
5965
+ * truth — every tag-scoped lookup query and the deploy write derive their
5966
+ * `_type` from here, mirroring {@link WORKFLOW_INSTANCE_TYPE}.
5967
+ */
5968
+ export declare const WORKFLOW_DEFINITION_TYPE = "workflow.definition";
5969
+
5970
+ /**
5971
+ * The lake document type for a workflow instance. Single source of truth — the
5972
+ * {@link WorkflowInstance} `_type`, every tag-scoped query, and the create
5973
+ * write all derive from here, mirroring {@link WORKFLOW_DEFINITION_TYPE}.
5974
+ */
5975
+ export declare const WORKFLOW_INSTANCE_TYPE = "workflow.instance";
5976
+
5743
5977
  /**
5744
5978
  * The engine's view of "who am I, what can I do?". `actor` is who
5745
5979
  * the engine stamps onto history / `completedBy` / `Source.actor`.
@@ -8284,7 +8518,7 @@ export declare interface WorkflowFetchOptions {
8284
8518
  }
8285
8519
 
8286
8520
  export declare interface WorkflowInstance extends SanityDocument {
8287
- _type: "workflow.instance";
8521
+ _type: typeof WORKFLOW_INSTANCE_TYPE;
8288
8522
  /**
8289
8523
  * Engine-scope tags stamped on the instance at create time. Engines
8290
8524
  * with disjoint tag sets cannot read this instance; engines sharing