@sanity/workflow-engine 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,32 @@
1
1
  import { parse, evaluate } from "groq-js";
2
- import { actorFulfillsRole, isTerminalActivityStatus, WORKFLOW_DEFINITION_TYPE, andConditions, DOCUMENT_VALUE_PERMISSIONS } from "./_chunks-es/schema.js";
2
+ import { actorFulfillsRole, isTerminalActivityStatus, StoredFieldOpSchema, WORKFLOW_DEFINITION_TYPE, andConditions, DOCUMENT_VALUE_PERMISSIONS } from "./_chunks-es/schema.js";
3
3
  import { ACTIVITY_KINDS, DRIVER_KINDS, isStartableDefinition } from "./_chunks-es/schema.js";
4
4
  import * as v from "valibot";
5
5
  function findOpenStageEntry(host) {
6
6
  return host.stages.find((s) => s.name === host.currentStage && s.exitedAt === void 0);
7
7
  }
8
+ function findCurrentActivityEntry(host, activityName) {
9
+ return findOpenStageEntry(host)?.activities.find((a) => a.name === activityName);
10
+ }
11
+ function errorMessage(err) {
12
+ return err instanceof Error ? err.message : String(err);
13
+ }
14
+ function rethrowWithContext(err, context) {
15
+ throw new Error(`${context}: ${errorMessage(err)}`, { cause: err });
16
+ }
17
+ function mapJsonStrings(value, transform) {
18
+ const walk = (val, key) => {
19
+ if (typeof val == "string") return transform(val, key);
20
+ if (Array.isArray(val)) return val.map((item) => walk(item, key));
21
+ if (val !== null && typeof val == "object") {
22
+ const out = {};
23
+ for (const [k, v2] of Object.entries(val)) out[k] = walk(v2, k);
24
+ return out;
25
+ }
26
+ return val;
27
+ };
28
+ return walk(value, void 0);
29
+ }
8
30
  const KNOWN_SCHEMES = /* @__PURE__ */ new Set(["dataset", "canvas", "media-library", "dashboard"]);
9
31
  function parseGdr(uri) {
10
32
  const colon = uri.indexOf(":");
@@ -44,22 +66,56 @@ function parseGdr(uri) {
44
66
  documentId: parts[1]
45
67
  };
46
68
  }
69
+ function tryParseGdr(uri) {
70
+ try {
71
+ return parseGdr(uri);
72
+ } catch {
73
+ return;
74
+ }
75
+ }
47
76
  function gdrUri(parts) {
48
77
  return parts.scheme === "dataset" ? `dataset:${parts.projectId}:${parts.dataset}:${parts.documentId}` : `${parts.scheme}:${parts.resourceId}:${parts.documentId}`;
49
78
  }
50
79
  function extractDocumentId(gdrUriString) {
51
80
  return parseGdr(gdrUriString).documentId;
52
81
  }
53
- function refDataset(projectId, dataset, documentId, type) {
82
+ const RESOURCE_ALIAS_NAME_SOURCE = "[a-z0-9][a-z0-9-]*", RESOURCE_ALIAS_NAME_RE = new RegExp(`^${RESOURCE_ALIAS_NAME_SOURCE}$`);
83
+ function validateResourceAliasName(name) {
84
+ if (!RESOURCE_ALIAS_NAME_RE.test(name))
85
+ throw new Error(
86
+ `Invalid resource alias name "${name}": expected lowercase letters, digits, and dashes (no leading dash).`
87
+ );
88
+ }
89
+ function toPhysicalGdr(id, home) {
90
+ return isGdrUri(id) ? id : gdrFromResource(home, id);
91
+ }
92
+ function refDataset({
93
+ projectId,
94
+ dataset,
95
+ documentId,
96
+ type
97
+ }) {
54
98
  return { id: gdrUri({ scheme: "dataset", projectId, dataset, documentId }), type };
55
99
  }
56
- function refCanvas(resourceId, documentId, type) {
100
+ function refCanvas({
101
+ resourceId,
102
+ documentId,
103
+ type
104
+ }) {
57
105
  return { id: gdrUri({ scheme: "canvas", resourceId, documentId }), type };
58
106
  }
59
- function refMediaLibrary(resourceId, documentId, type) {
107
+ function refMediaLibrary({
108
+ resourceId,
109
+ documentId,
110
+ type
111
+ }) {
60
112
  return { id: gdrUri({ scheme: "media-library", resourceId, documentId }), type };
61
113
  }
62
- function refDashboard(resourceId, documentId, type) {
114
+ function refDashboard({
115
+ resourceId,
116
+ documentId,
117
+ type
118
+ }) {
63
119
  return { id: gdrUri({ scheme: "dashboard", resourceId, documentId }), type };
64
120
  }
65
121
  function datasetResourceParts(id) {
@@ -75,17 +131,16 @@ function gdrFromResource(res, documentId) {
75
131
  }
76
132
  return gdrUri({ scheme: res.type, resourceId: res.id, documentId });
77
133
  }
134
+ function gdrResourcePrefix(res) {
135
+ const full = gdrFromResource(res, "x");
136
+ return full.slice(0, full.length - 1);
137
+ }
138
+ function resourceFromParsed(parsed) {
139
+ return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
140
+ }
78
141
  function resourceFromGdrUri(uri) {
79
- let parsed;
80
- try {
81
- parsed = parseGdr(uri);
82
- } catch {
83
- return;
84
- }
85
- if (parsed.scheme === "dataset")
86
- return parsed.projectId === void 0 || parsed.dataset === void 0 ? void 0 : { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` };
87
- if (parsed.resourceId !== void 0)
88
- return { type: parsed.scheme, id: parsed.resourceId };
142
+ const parsed = tryParseGdr(uri);
143
+ return parsed === void 0 ? void 0 : resourceFromParsed(parsed);
89
144
  }
90
145
  function sameResource(a, b) {
91
146
  return a.type === b.type && a.id === b.id;
@@ -93,15 +148,24 @@ function sameResource(a, b) {
93
148
  function selfGdr(doc) {
94
149
  return gdrFromResource(doc.workflowResource, doc._id);
95
150
  }
151
+ function definitionDocId({
152
+ tag,
153
+ definition,
154
+ version
155
+ }) {
156
+ return `${tag}.${definition}.v${version}`;
157
+ }
96
158
  function isGdrUri(value) {
97
- if (typeof value != "string") return !1;
98
- try {
99
- return parseGdr(value), !0;
100
- } catch {
101
- return !1;
102
- }
159
+ return typeof value == "string" && tryParseGdr(value) !== void 0;
160
+ }
161
+ function toBareId(id) {
162
+ return isGdrUri(id) ? extractDocumentId(id) : id;
103
163
  }
104
- function gdrRef(res, documentId, type) {
164
+ function gdrRef({
165
+ res,
166
+ documentId,
167
+ type
168
+ }) {
105
169
  return { id: gdrFromResource(res, documentId), type };
106
170
  }
107
171
  function isGdr(value) {
@@ -136,17 +200,26 @@ function renderedFields(entries, snapshot) {
136
200
  function renderedValue(entry, snapshot) {
137
201
  return entry._type !== "doc.ref" || entry.value === null || snapshot === void 0 ? entry.value : snapshot.docs.find((d) => d._id === entry.value.id) ?? entry.value;
138
202
  }
139
- function scopedFieldOverlay(instance, snapshot, activityName) {
203
+ function scopedFieldOverlay({
204
+ instance,
205
+ snapshot,
206
+ activityName
207
+ }) {
140
208
  const stageEntry = findOpenStageEntry(instance);
141
209
  if (stageEntry === void 0) return {};
142
210
  const stageFields = renderedFields(stageEntry.fields ?? [], snapshot), activity = activityName ? stageEntry.activities.find((t) => t.name === activityName) : void 0;
143
211
  return { ...stageFields, ...renderedFields(activity?.fields ?? [], snapshot) };
144
212
  }
145
- function assignedFor(instance, activityName, actor, roleAliases) {
213
+ function assignedFor({
214
+ instance,
215
+ activityName,
216
+ actor,
217
+ roleAliases
218
+ }) {
146
219
  if (actor === void 0) return !1;
147
- const entry = findOpenStageEntry(instance)?.activities.find((t) => t.name === activityName)?.fields?.find((s) => s._type === "assignees");
220
+ const entry = findCurrentActivityEntry(instance, activityName)?.fields?.find((s) => s._type === "assignees");
148
221
  return entry === void 0 ? !1 : entry.value.some(
149
- (a) => a.type === "user" ? a.id === actor.id : actorFulfillsRole(actor.roles, a.role, roleAliases)
222
+ (a) => a.type === "user" ? a.id === actor.id : actorFulfillsRole({ actorRoles: actor.roles, required: a.role, aliases: roleAliases })
150
223
  );
151
224
  }
152
225
  function effectsContextMap(instance) {
@@ -159,35 +232,20 @@ function parseJsonContextEntry(entry) {
159
232
  try {
160
233
  return JSON.parse(entry.value);
161
234
  } catch (err) {
162
- throw new Error(
163
- `effectsContext entry "${entry.name}" holds unparseable JSON: ${err instanceof Error ? err.message : String(err)}`,
164
- { cause: err }
165
- );
235
+ rethrowWithContext(err, `effectsContext entry "${entry.name}" holds unparseable JSON`);
166
236
  }
167
237
  }
168
238
  function paramsForLake(params) {
169
239
  return {
170
240
  ...params,
171
- self: typeof params.self == "string" ? bareId(params.self) : params.self,
172
- parent: typeof params.parent == "string" ? bareId(params.parent) : params.parent,
173
- ancestors: Array.isArray(params.ancestors) ? params.ancestors.map((a) => typeof a == "string" ? bareId(a) : a) : params.ancestors,
241
+ self: typeof params.self == "string" ? toBareId(params.self) : params.self,
242
+ parent: typeof params.parent == "string" ? toBareId(params.parent) : params.parent,
243
+ ancestors: Array.isArray(params.ancestors) ? params.ancestors.map((a) => typeof a == "string" ? toBareId(a) : a) : params.ancestors,
174
244
  fields: stripFieldsForLake(params.fields)
175
245
  };
176
246
  }
177
247
  function stripFieldsForLake(value) {
178
- if (value == null) return value;
179
- if (typeof value == "string") return bareId(value);
180
- if (Array.isArray(value)) return value.map(stripFieldsForLake);
181
- if (typeof value == "object") {
182
- const out = {};
183
- for (const [k, v2] of Object.entries(value))
184
- out[k] = stripFieldsForLake(v2);
185
- return out;
186
- }
187
- return value;
188
- }
189
- function bareId(id) {
190
- return isGdrUri(id) ? extractDocumentId(id) : id;
248
+ return mapJsonStrings(value, (s) => toBareId(s));
191
249
  }
192
250
  function getPath(value, path) {
193
251
  let current = value;
@@ -197,9 +255,6 @@ function getPath(value, path) {
197
255
  }
198
256
  return current;
199
257
  }
200
- function resourceOf(p) {
201
- return p.scheme === "dataset" ? { type: "dataset", id: `${p.projectId}.${p.dataset}` } : { type: p.scheme, id: p.resourceId ?? "" };
202
- }
203
258
  const FIELD_READ = /^\$fields\.(\w+)(?:\.(.+))?$/, EFFECTS_READ = /^\$effects\['([^']+)'\](?:\.(.+))?$/;
204
259
  function isGuardReadExpr(expr) {
205
260
  return expr === "$self" || expr === "$now" || FIELD_READ.test(expr) || EFFECTS_READ.test(expr);
@@ -242,9 +297,9 @@ function resolveIdRefTargets(idRefs, ctx) {
242
297
  return targets.length === 0 ? null : targets;
243
298
  }
244
299
  function assertSingleResource(targets) {
245
- const resource = resourceOf(targets[0].parsed);
300
+ const resource = resourceFromParsed(targets[0].parsed);
246
301
  for (const g of targets) {
247
- const r = resourceOf(g.parsed);
302
+ const r = resourceFromParsed(g.parsed);
248
303
  if (r.type !== resource.type || r.id !== resource.id)
249
304
  throw new Error(
250
305
  `Guard targets span multiple resources (${resource.type}:${resource.id} vs ${r.type}:${r.id}); a guard is single-resource.`
@@ -308,23 +363,31 @@ function validateStage(v2, stage) {
308
363
  for (const entry of stage.fields ?? [])
309
364
  v2.checkEntry(entry, `stage "${stage.name}".fields "${entry.name}"`);
310
365
  for (const guard of stage.guards ?? [])
311
- validateGuardReads(v2, stage.name, guard);
366
+ validateGuardReads({ v: v2, stageName: stage.name, guard });
312
367
  for (const t of stage.transitions ?? []) {
313
368
  v2.checkCondition(t.filter, `transition "${t.name}" (${stage.name} \u2192 ${t.to}) filter`);
314
369
  for (const [key, groq] of effectBindings(t.effects))
315
370
  v2.tryParse(groq, `transition "${t.name}" effect binding "${key}"`);
316
371
  }
317
372
  for (const activity of stage.activities ?? [])
318
- validateActivity(v2, stage.name, activity);
373
+ validateActivity({ v: v2, stageName: stage.name, activity });
319
374
  }
320
- function validateGuardReads(v2, stageName, guard) {
375
+ function validateGuardReads({
376
+ v: v2,
377
+ stageName,
378
+ guard
379
+ }) {
321
380
  const where = `stage "${stageName}" guard "${guard.name}"`;
322
381
  for (const expr of guard.match.idRefs ?? [])
323
382
  v2.checkGuardRead(expr, `${where} match.idRefs`);
324
383
  for (const [key, expr] of Object.entries(guard.metadata ?? {}))
325
384
  v2.checkGuardRead(expr, `${where} metadata "${key}"`);
326
385
  }
327
- function validateActivity(v2, stageName, activity) {
386
+ function validateActivity({
387
+ v: v2,
388
+ stageName,
389
+ activity
390
+ }) {
328
391
  const where = `stage "${stageName}" activity "${activity.name}"`;
329
392
  for (const entry of activity.fields ?? [])
330
393
  v2.checkEntry(entry, `${where}.fields "${entry.name}"`);
@@ -333,7 +396,7 @@ function validateActivity(v2, stageName, activity) {
333
396
  v2.checkCondition(groq, `${where}.requirements "${name}"`);
334
397
  for (const [key, groq] of effectBindings(activity.effects))
335
398
  v2.tryParse(groq, `${where} effect binding "${key}"`);
336
- validateActivityActions(v2, activity), activity.subworkflows !== void 0 && validateSubworkflows(v2, where, activity.subworkflows);
399
+ validateActivityActions(v2, activity), activity.subworkflows !== void 0 && validateSubworkflows({ v: v2, where, sub: activity.subworkflows });
337
400
  }
338
401
  function validateActivityActions(v2, activity) {
339
402
  for (const a of activity.actions ?? []) {
@@ -342,7 +405,11 @@ function validateActivityActions(v2, activity) {
342
405
  v2.tryParse(groq, `activity "${activity.name}" action "${a.name}" effect binding "${key}"`);
343
406
  }
344
407
  }
345
- function validateSubworkflows(v2, where, sub) {
408
+ function validateSubworkflows({
409
+ v: v2,
410
+ where,
411
+ sub
412
+ }) {
346
413
  v2.tryParse(sub.forEach, `${where}.subworkflows.forEach`);
347
414
  for (const [key, groq] of Object.entries(sub.with ?? {}))
348
415
  v2.tryParse(groq, `${where}.subworkflows.with "${key}"`);
@@ -421,7 +488,11 @@ function globMatch(pattern, value) {
421
488
  const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
422
489
  return new RegExp(`^${escaped}$`).test(value);
423
490
  }
424
- function guardMatches(guard, doc, action) {
491
+ function guardMatches({
492
+ guard,
493
+ doc,
494
+ action
495
+ }) {
425
496
  const m = guard.match;
426
497
  if (!m.actions.includes(action) || m.types && m.types.length > 0 && !(doc.type !== void 0 && m.types.includes(doc.type)))
427
498
  return !1;
@@ -450,7 +521,7 @@ async function evaluateMutationGuard(args) {
450
521
  async function denyingGuards(args) {
451
522
  const { guards, doc, context } = args, denied = [];
452
523
  for (const guard of guards)
453
- guardMatches(guard, doc, context.action) && (await evaluateMutationGuard({ guard, context }) || denied.push(guard));
524
+ guardMatches({ guard, doc, action: context.action }) && (await evaluateMutationGuard({ guard, context }) || denied.push(guard));
454
525
  return denied;
455
526
  }
456
527
  async function instanceWriteDenials(args) {
@@ -621,6 +692,16 @@ ${lines}`
621
692
  ), this.name = "ActionParamsInvalidError", this.action = args.action, this.activity = args.activity, this.issues = args.issues;
622
693
  }
623
694
  }
695
+ class EffectOpsInvalidError extends Error {
696
+ effect;
697
+ issues;
698
+ constructor(args) {
699
+ const lines = args.issues.map((i) => ` - ${i}`).join(`
700
+ `);
701
+ super(`Effect "${args.effect}" returned invalid completion ops:
702
+ ${lines}`), this.name = "EffectOpsInvalidError", this.effect = args.effect, this.issues = args.issues;
703
+ }
704
+ }
624
705
  class RequiredFieldNotProvidedError extends Error {
625
706
  definition;
626
707
  missing;
@@ -697,38 +778,39 @@ class CascadeLimitError extends Error {
697
778
  function buildSnapshot(args) {
698
779
  const docs = [], knownIds = /* @__PURE__ */ new Set();
699
780
  for (const { doc, resource } of args.docs) {
700
- const uri = gdrFromResource(resource, doc._id), restamped = restampForSnapshot(doc, uri, resource);
781
+ const uri = gdrFromResource(resource, doc._id), restamped = restampForSnapshot({ doc, gdrUri: uri, resource });
701
782
  docs.push(restamped), knownIds.add(uri);
702
783
  }
703
784
  return { docs, knownIds };
704
785
  }
705
- function restampForSnapshot(doc, gdrUri2, resource) {
786
+ function restampForSnapshot({
787
+ doc,
788
+ gdrUri: gdrUri2,
789
+ resource
790
+ }) {
706
791
  return { ...rewriteRefsRecursive(doc, resource), _id: gdrUri2 };
707
792
  }
708
793
  function rewriteRefsRecursive(value, resource) {
709
- if (Array.isArray(value))
710
- return value.map((v2) => rewriteRefsRecursive(v2, resource));
711
- if (value === null || typeof value != "object") return value;
712
- const obj = value, out = {};
713
- for (const [k, v2] of Object.entries(obj))
714
- k === "_ref" && typeof v2 == "string" ? out[k] = v2.includes(":") ? v2 : gdrFromResource(resource, v2) : out[k] = rewriteRefsRecursive(v2, resource);
715
- return out;
794
+ return mapJsonStrings(
795
+ value,
796
+ (str, key) => (
797
+ // Only `_ref` string values become URIs; already-qualified ones pass through.
798
+ key === "_ref" && !str.includes(":") ? gdrFromResource(resource, str) : str
799
+ )
800
+ );
716
801
  }
717
802
  const WORKFLOW_INSTANCE_TYPE = "sanity.workflow.instance";
718
803
  function parseDefinitionSnapshot(instance) {
719
804
  try {
720
805
  return JSON.parse(instance.definitionSnapshot);
721
806
  } catch (err) {
722
- throw new Error(
723
- `Failed to parse definitionSnapshot on instance "${instance._id}": ${err instanceof Error ? err.message : String(err)}`,
724
- { cause: err }
725
- );
807
+ rethrowWithContext(err, `Failed to parse definitionSnapshot on instance "${instance._id}"`);
726
808
  }
727
809
  }
728
810
  function collectWatchRefs(instance) {
729
811
  const stage = findOpenStageEntry(instance);
730
812
  return [
731
- gdrRef(instance.workflowResource, instance._id, instance._type),
813
+ gdrRef({ res: instance.workflowResource, documentId: instance._id, type: instance._type }),
732
814
  ...instance.ancestors,
733
815
  ...entryDocRefs(instance.fields),
734
816
  ...entryDocRefs(stage?.fields),
@@ -786,13 +868,13 @@ async function hydrateSnapshot(args) {
786
868
  loaded.push(held), visited.add(uri);
787
869
  return;
788
870
  }
789
- const fetched = await loadByGdr(
790
- client,
871
+ const fetched = await loadByGdr({
872
+ defaultClient: client,
791
873
  clientForGdr,
792
- instance.workflowResource,
874
+ defaultResource: instance.workflowResource,
793
875
  uri,
794
876
  perspective
795
- );
877
+ });
796
878
  fetched && (loaded.push(fetched), visited.add(uri));
797
879
  };
798
880
  loaded.push({ doc: instance, resource: instance.workflowResource }), visited.add(selfGdr(instance));
@@ -803,23 +885,28 @@ async function hydrateSnapshot(args) {
803
885
  );
804
886
  return buildSnapshot({ docs: loaded });
805
887
  }
806
- async function loadByGdr(defaultClient, clientForGdr, defaultResource, uri, perspective) {
807
- let parsed;
808
- try {
809
- parsed = parseGdr(uri);
810
- } catch {
811
- const doc2 = await readDoc(defaultClient, uri, perspective);
888
+ async function loadByGdr({
889
+ defaultClient,
890
+ clientForGdr,
891
+ defaultResource,
892
+ uri,
893
+ perspective
894
+ }) {
895
+ const parsed = tryParseGdr(uri);
896
+ if (parsed === void 0) {
897
+ const doc2 = await readDoc({ client: defaultClient, id: uri, perspective });
812
898
  return doc2 ? { doc: doc2, resource: defaultResource } : null;
813
899
  }
814
- const routed = clientForGdr(parsed), doc = await readDoc(routed, parsed.documentId, perspective);
900
+ const routed = clientForGdr(parsed), doc = await readDoc({ client: routed, id: parsed.documentId, perspective });
815
901
  return doc ? { doc, resource: resourceFromParsed(parsed) } : null;
816
902
  }
817
- async function readDoc(client, id, perspective) {
903
+ async function readDoc({
904
+ client,
905
+ id,
906
+ perspective
907
+ }) {
818
908
  return perspective === "raw" ? await client.getDocument(id) ?? null : await client.fetch("*[_id == $id][0]", { id }, { perspective }) ?? null;
819
909
  }
820
- function resourceFromParsed(parsed) {
821
- return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
822
- }
823
910
  function collectEntryDocUris(resolvedFieldEntries) {
824
911
  return entryDocRefs(resolvedFieldEntries).map((ref) => ref.id);
825
912
  }
@@ -841,13 +928,13 @@ async function guardsForInstance(args) {
841
928
  const { client, clientForGdr, instance } = args, stateUris = [
842
929
  ...collectEntryDocUris(instance.fields),
843
930
  ...instance.stages.flatMap((stage) => collectEntryDocUris(stage.fields))
844
- ], clients = resourceClientMap(
845
- instance.workflowResource,
846
- client,
931
+ ], clients = resourceClientMap({
932
+ base: instance.workflowResource,
933
+ baseClient: client,
847
934
  clientForGdr,
848
- stateUris.map((uri) => tryParseGdr(uri))
849
- ), { query, params } = instanceGuardQuery(instance._id);
850
- return queryGuardsAcross(clients.values(), query, params);
935
+ gdrs: stateUris.map((uri) => tryParseGdr(uri))
936
+ }), { query, params } = instanceGuardQuery(instance._id);
937
+ return queryGuardsAcross({ clients: clients.values(), query, params });
851
938
  }
852
939
  async function guardsForDefinition(args) {
853
940
  const perClient = await guardsForDefinitionByClient(args);
@@ -855,8 +942,13 @@ async function guardsForDefinition(args) {
855
942
  }
856
943
  async function guardsForDefinitionByClient(args) {
857
944
  const { client, clientForGdr, workflowResource, definition, definitions } = args, gdrs = definitions.flatMap(
858
- (def) => guardIdRefs(def).map(({ idRef, stage }) => staticIdRefGdr(idRef, stage, def))
859
- ), clients = resourceClientMap(workflowResource, client, clientForGdr, gdrs), query = "*[_type == $t && sourceDefinition == $definition]", params = { t: GUARD_DOC_TYPE, definition };
945
+ (def) => guardIdRefs(def).map(({ idRef, stage }) => staticIdRefGdr({ idRef, stage, definition: def }))
946
+ ), clients = resourceClientMap({
947
+ base: workflowResource,
948
+ baseClient: client,
949
+ clientForGdr,
950
+ gdrs
951
+ }), query = "*[_type == $t && sourceDefinition == $definition]", params = { t: GUARD_DOC_TYPE, definition };
860
952
  return Promise.all(
861
953
  [...clients.values()].map(async (resourceClient) => ({
862
954
  client: resourceClient,
@@ -871,7 +963,11 @@ function guardIdRefs(definition) {
871
963
  )
872
964
  );
873
965
  }
874
- function staticIdRefGdr(idRef, stage, definition) {
966
+ function staticIdRefGdr({
967
+ idRef,
968
+ stage,
969
+ definition
970
+ }) {
875
971
  const read = /^\$fields\.([\w-]+)/.exec(idRef);
876
972
  if (read === null) return;
877
973
  const seed = entriesInScope(stage, definition).find((e) => e.name === read[1])?.initialValue;
@@ -891,14 +987,12 @@ function gdrFromValue(value) {
891
987
  return typeof id == "string" ? tryParseGdr(id) : void 0;
892
988
  }
893
989
  }
894
- function tryParseGdr(uri) {
895
- try {
896
- return parseGdr(uri);
897
- } catch {
898
- return;
899
- }
900
- }
901
- function resourceClientMap(base, baseClient, clientForGdr, gdrs) {
990
+ function resourceClientMap({
991
+ base,
992
+ baseClient,
993
+ clientForGdr,
994
+ gdrs
995
+ }) {
902
996
  const map = /* @__PURE__ */ new Map([[resourceKey(base), baseClient]]);
903
997
  for (const parsed of gdrs) {
904
998
  if (parsed === void 0) continue;
@@ -907,7 +1001,11 @@ function resourceClientMap(base, baseClient, clientForGdr, gdrs) {
907
1001
  }
908
1002
  return map;
909
1003
  }
910
- async function queryGuardsAcross(clients, query, params) {
1004
+ async function queryGuardsAcross({
1005
+ clients,
1006
+ query,
1007
+ params
1008
+ }) {
911
1009
  const perResource = await Promise.all(
912
1010
  [...clients].map((c) => c.fetch(query, params))
913
1011
  );
@@ -917,7 +1015,12 @@ function dedupById(guards) {
917
1015
  return [...new Map(guards.map((g) => [g._id, g])).values()].sort((a, b) => a._id.localeCompare(b._id));
918
1016
  }
919
1017
  const SYNC_COMMIT = { visibility: "sync" }, GUARD_OWNER = "robot:workflow-engine", GUARD_LIFTED_PREDICATE = "true";
920
- function resolveGuard(guard, instance, stageName, now) {
1018
+ function resolveGuard({
1019
+ guard,
1020
+ instance,
1021
+ stageName,
1022
+ now
1023
+ }) {
921
1024
  const ctx = { instance, now }, targets = resolveIdRefTargets(guard.match.idRefs, ctx);
922
1025
  if (targets === null) return null;
923
1026
  const resource = assertSingleResource(targets), types = resolveMatchTypes(targets, guard.match.types);
@@ -952,7 +1055,12 @@ async function upsertGuard(client, doc) {
952
1055
  function resolvedStageGuards(args) {
953
1056
  const stage = args.definition.stages.find((s) => s.name === args.stageName), out = [];
954
1057
  for (const guard of stage?.guards ?? []) {
955
- const resolved = resolveGuard(guard, args.instance, args.stageName, args.now);
1058
+ const resolved = resolveGuard({
1059
+ guard,
1060
+ instance: args.instance,
1061
+ stageName: args.stageName,
1062
+ now: args.now
1063
+ });
956
1064
  resolved !== null && out.push({ client: args.clientForGdr(resolved.routeGdr), doc: resolved.doc });
957
1065
  }
958
1066
  return out;
@@ -1001,13 +1109,16 @@ function randomKey(length = 12) {
1001
1109
  const bytes = new Uint8Array(length);
1002
1110
  return globalThis.crypto.getRandomValues(bytes), [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, length);
1003
1111
  }
1112
+ function instanceDocId(tag) {
1113
+ return `${tag}.wf-instance.${randomKey()}`;
1114
+ }
1004
1115
  function isUnevaluable(result) {
1005
1116
  return result == null;
1006
1117
  }
1007
1118
  async function evaluateConditionOutcome(args) {
1008
1119
  const { condition, snapshot, params } = args;
1009
1120
  if (condition === void 0) return "satisfied";
1010
- const result = await runGroq(condition, params, snapshot);
1121
+ const result = await runGroq({ groq: condition, params, snapshot });
1011
1122
  return isUnevaluable(result) ? "unevaluable" : result ? "satisfied" : "unsatisfied";
1012
1123
  }
1013
1124
  async function evaluateCondition(args) {
@@ -1016,7 +1127,7 @@ async function evaluateCondition(args) {
1016
1127
  async function evaluatePredicates(args) {
1017
1128
  const out = {};
1018
1129
  for (const [name, groq] of Object.entries(args.predicates ?? {})) {
1019
- const result = await runGroq(groq, args.params, args.snapshot);
1130
+ const result = await runGroq({ groq, params: args.params, snapshot: args.snapshot });
1020
1131
  out[name] = isUnevaluable(result) ? null : !!result;
1021
1132
  }
1022
1133
  return out;
@@ -1027,7 +1138,11 @@ async function evaluateRequirements(args) {
1027
1138
  await evaluateCondition({ condition, snapshot: args.snapshot, params: args.params }) || unmet.push(name);
1028
1139
  return unmet;
1029
1140
  }
1030
- async function runGroq(groq, params, snapshot) {
1141
+ async function runGroq({
1142
+ groq,
1143
+ params,
1144
+ snapshot
1145
+ }) {
1031
1146
  const tree = parse(groq, { params });
1032
1147
  return (await evaluate(tree, { dataset: snapshot.docs, params })).get();
1033
1148
  }
@@ -1157,15 +1272,19 @@ function derivePerspectiveFromFields(entries) {
1157
1272
  async function resolveDeclaredFields(args) {
1158
1273
  const { entryDefs, initialFields, ctx, randomKey: randomKey2 } = args;
1159
1274
  if (entryDefs === void 0 || entryDefs.length === 0) return [];
1160
- assertRequiredInputProvided(entryDefs, initialFields, ctx.definitionName);
1275
+ assertRequiredInputProvided({ entryDefs, initialFields, definitionName: ctx.definitionName });
1161
1276
  const out = [];
1162
1277
  for (const entry of entryDefs) {
1163
1278
  const scopedCtx = { ...ctx, resolvedFields: out };
1164
- out.push(await resolveOneEntry(entry, initialFields, scopedCtx, randomKey2));
1279
+ out.push(await resolveOneEntry({ entry, initialFields, ctx: scopedCtx, randomKey: randomKey2 }));
1165
1280
  }
1166
1281
  return out;
1167
1282
  }
1168
- function assertRequiredInputProvided(entryDefs, initialFields, definitionName) {
1283
+ function assertRequiredInputProvided({
1284
+ entryDefs,
1285
+ initialFields,
1286
+ definitionName
1287
+ }) {
1169
1288
  const missing = entryDefs.filter((entry) => entry.required === !0 && entry.initialValue?.type === "input").filter((entry) => {
1170
1289
  const match = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
1171
1290
  return match === void 0 || match.value === void 0 || match.value === null;
@@ -1180,15 +1299,29 @@ const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set(["doc.refs", "array", "assignee
1180
1299
  function defaultEntryValue(entryType) {
1181
1300
  return ARRAY_SLOT_TYPES.has(entryType) ? [] : null;
1182
1301
  }
1183
- function resolveInputValue(entry, initialFields, defaultValue) {
1302
+ function resolveInputValue({
1303
+ entry,
1304
+ initialFields,
1305
+ defaultValue
1306
+ }) {
1184
1307
  const initMatch = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
1185
1308
  return initMatch === void 0 ? defaultValue : (assertInputValueShape(entry, initMatch.value), initMatch.value);
1186
1309
  }
1187
- function resolveFieldReadValue(source, ctx, defaultValue) {
1310
+ function resolveFieldReadValue({
1311
+ source,
1312
+ ctx,
1313
+ defaultValue
1314
+ }) {
1188
1315
  const target = (source.scope === "workflow" ? ctx.workflowFields ?? [] : ctx.resolvedFields ?? []).find((s) => s.name === source.field), targetValue = target === void 0 ? void 0 : target.value;
1189
1316
  return (source.path !== void 0 ? getPath(targetValue, source.path) : targetValue) ?? defaultValue;
1190
1317
  }
1191
- async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
1318
+ async function resolveQueryValue({
1319
+ entry,
1320
+ source,
1321
+ ctx,
1322
+ client,
1323
+ defaultValue
1324
+ }) {
1192
1325
  const params = paramsForLake({
1193
1326
  self: ctx.selfId ?? null,
1194
1327
  fields: fieldMapFromResolved(ctx.resolvedFields ?? []),
@@ -1199,19 +1332,30 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
1199
1332
  });
1200
1333
  try {
1201
1334
  const fetchOptions = { perspective: ctx.perspective ?? DEFAULT_CONTENT_PERSPECTIVE }, result = await client.fetch(source.query, params, fetchOptions);
1202
- return normalizeQueryResult(entry.type, result, ctx.workflowResource) ?? defaultValue;
1335
+ return normalizeQueryResult({
1336
+ entryType: entry.type,
1337
+ raw: result,
1338
+ workflowResource: ctx.workflowResource
1339
+ }) ?? defaultValue;
1203
1340
  } catch (err) {
1204
- throw new Error(
1205
- `Failed to resolve query entry "${entry.name}" (${entry.type}): ${err instanceof Error ? err.message : String(err)}`,
1206
- { cause: err }
1207
- );
1341
+ rethrowWithContext(err, `Failed to resolve query entry "${entry.name}" (${entry.type})`);
1208
1342
  }
1209
1343
  }
1210
- async function resolveEntryValue(entry, initialFields, ctx, defaultValue) {
1344
+ async function resolveEntryValue({
1345
+ entry,
1346
+ initialFields,
1347
+ ctx,
1348
+ defaultValue
1349
+ }) {
1211
1350
  const source = entry.initialValue;
1212
- return source === void 0 ? defaultValue : source.type === "input" ? resolveInputValue(entry, initialFields, defaultValue) : source.type === "literal" ? source.value ?? defaultValue : source.type === "fieldRead" ? resolveFieldReadValue(source, ctx, defaultValue) : source.type === "query" && ctx.client !== void 0 ? resolveQueryValue(entry, source, ctx, ctx.client, defaultValue) : defaultValue;
1213
- }
1214
- function buildResolvedEntry(entry, value, _key, now) {
1351
+ return source === void 0 ? defaultValue : source.type === "input" ? resolveInputValue({ entry, initialFields, defaultValue }) : source.type === "literal" ? source.value ?? defaultValue : source.type === "fieldRead" ? resolveFieldReadValue({ source, ctx, defaultValue }) : source.type === "query" && ctx.client !== void 0 ? resolveQueryValue({ entry, source, ctx, client: ctx.client, defaultValue }) : defaultValue;
1352
+ }
1353
+ function buildResolvedEntry({
1354
+ entry,
1355
+ value,
1356
+ _key,
1357
+ now
1358
+ }) {
1215
1359
  return {
1216
1360
  _key,
1217
1361
  _type: entry.type,
@@ -1224,8 +1368,13 @@ function buildResolvedEntry(entry, value, _key, now) {
1224
1368
  ...entry.type === "array" ? { of: entry.of ?? [] } : {}
1225
1369
  };
1226
1370
  }
1227
- async function resolveOneEntry(entry, initialFields, ctx, randomKey2) {
1228
- const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue(entry, initialFields, ctx, defaultValue);
1371
+ async function resolveOneEntry({
1372
+ entry,
1373
+ initialFields,
1374
+ ctx,
1375
+ randomKey: randomKey2
1376
+ }) {
1377
+ const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue({ entry, initialFields, ctx, defaultValue });
1229
1378
  if (entry.initialValue?.type === "query") {
1230
1379
  const issues = checkFieldValue({
1231
1380
  entryType: entry.type,
@@ -1233,7 +1382,7 @@ async function resolveOneEntry(entry, initialFields, ctx, randomKey2) {
1233
1382
  fields: entry.fields,
1234
1383
  of: entry.of
1235
1384
  });
1236
- return issues !== void 0 ? (ctx.recordDiscard?.({ field: entry.name, detail: issues.join("; ") }), buildResolvedEntry(entry, defaultValue, randomKey2(), ctx.now)) : buildResolvedEntry(entry, value, randomKey2(), ctx.now);
1385
+ return issues !== void 0 ? (ctx.recordDiscard?.({ field: entry.name, detail: issues.join("; ") }), buildResolvedEntry({ entry, value: defaultValue, _key: randomKey2(), now: ctx.now })) : buildResolvedEntry({ entry, value, _key: randomKey2(), now: ctx.now });
1237
1386
  }
1238
1387
  return validateFieldValue({
1239
1388
  entryType: entry.type,
@@ -1241,7 +1390,7 @@ async function resolveOneEntry(entry, initialFields, ctx, randomKey2) {
1241
1390
  value,
1242
1391
  fields: entry.fields,
1243
1392
  of: entry.of
1244
- }), buildResolvedEntry(entry, value, randomKey2(), ctx.now);
1393
+ }), buildResolvedEntry({ entry, value, _key: randomKey2(), now: ctx.now });
1245
1394
  }
1246
1395
  function fieldMapFromResolved(entries) {
1247
1396
  const out = {};
@@ -1286,35 +1435,57 @@ function assertGdrShape(value, context) {
1286
1435
  `Invalid GDR for ${context}: \`type\` (schema name) must be a non-empty string. Got ${JSON.stringify(v2.type)}.`
1287
1436
  );
1288
1437
  }
1289
- function normalizeQueryResult(entryType, raw, workflowResource) {
1438
+ function normalizeQueryResult({
1439
+ entryType,
1440
+ raw,
1441
+ workflowResource
1442
+ }) {
1290
1443
  return raw == null ? raw : entryType === "doc.ref" ? coerceToGdr(raw, workflowResource) : entryType === "doc.refs" ? Array.isArray(raw) ? raw.map((item) => coerceToGdr(item, workflowResource)).filter((v2) => v2 !== null) : [] : raw;
1291
1444
  }
1292
- function toGdrUri(docId, workflowResource) {
1293
- return isGdrUri(docId) ? docId : gdrFromResource(workflowResource, docId);
1294
- }
1295
1445
  function coerceGdrShape(raw, workflowResource) {
1296
1446
  if (!("id" in raw) || !("type" in raw)) return null;
1297
1447
  const r = raw;
1298
- return typeof r.id != "string" || typeof r.type != "string" ? null : isGdrUri(r.id) ? { id: r.id, type: r.type } : workflowResource ? { id: gdrFromResource(workflowResource, r.id), type: r.type } : null;
1448
+ return typeof r.id != "string" || typeof r.type != "string" ? null : isGdrUri(r.id) ? { id: r.id, type: r.type } : workflowResource ? { id: toPhysicalGdr(r.id, workflowResource), type: r.type } : null;
1299
1449
  }
1300
1450
  function coerceRefEnvelope(raw, workflowResource) {
1301
1451
  if (!("_ref" in raw)) return null;
1302
1452
  const r = raw;
1303
- return typeof r._ref != "string" || !workflowResource ? null : { id: toGdrUri(r._ref, workflowResource), type: "document" };
1453
+ return typeof r._ref != "string" || !workflowResource ? null : { id: toPhysicalGdr(r._ref, workflowResource), type: "document" };
1304
1454
  }
1305
1455
  function coerceToGdr(raw, workflowResource) {
1306
- return raw == null ? null : typeof raw == "object" ? coerceGdrShape(raw, workflowResource) ?? coerceRefEnvelope(raw, workflowResource) : typeof raw == "string" && workflowResource ? { id: toGdrUri(raw, workflowResource), type: "document" } : null;
1307
- }
1308
- function gdrToBareId(uri) {
1309
- return uri.includes(":") ? extractDocumentId(uri) : uri;
1310
- }
1311
- function loadCallContext(client, instanceId, options) {
1312
- return loadContext(client, instanceId, {
1313
- ...options?.clock ? { clock: options.clock } : {},
1314
- ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
1456
+ return raw == null ? null : typeof raw == "object" ? coerceGdrShape(raw, workflowResource) ?? coerceRefEnvelope(raw, workflowResource) : typeof raw == "string" && workflowResource ? { id: toPhysicalGdr(raw, workflowResource), type: "document" } : null;
1457
+ }
1458
+ function loadCallContext({
1459
+ client,
1460
+ instanceId,
1461
+ options
1462
+ }) {
1463
+ return loadContext({
1464
+ client,
1465
+ instanceId,
1466
+ options: {
1467
+ ...options?.clock ? { clock: options.clock } : {},
1468
+ ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
1469
+ }
1315
1470
  });
1316
1471
  }
1317
- async function loadContext(client, instanceId, options) {
1472
+ async function retryOnRevisionConflict(args) {
1473
+ const { client, instanceId, options, commit, onExhausted } = args;
1474
+ for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
1475
+ const ctx = await loadCallContext({ client, instanceId, options });
1476
+ try {
1477
+ return await commit(ctx);
1478
+ } catch (error) {
1479
+ if (!isRevisionConflict(error)) throw error;
1480
+ }
1481
+ }
1482
+ throw onExhausted();
1483
+ }
1484
+ async function loadContext({
1485
+ client,
1486
+ instanceId,
1487
+ options
1488
+ }) {
1318
1489
  const instance = await client.getDocument(instanceId);
1319
1490
  if (!instance)
1320
1491
  throw new Error(`Workflow instance ${instanceId} not found`);
@@ -1329,12 +1500,21 @@ async function loadContext(client, instanceId, options) {
1329
1500
  async function ctxConditionParams(ctx, opts) {
1330
1501
  const base = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), fields = {
1331
1502
  ...base.fields,
1332
- ...scopedFieldOverlay(ctx.instance, ctx.snapshot, opts?.activityName)
1503
+ ...scopedFieldOverlay({
1504
+ instance: ctx.instance,
1505
+ snapshot: ctx.snapshot,
1506
+ activityName: opts?.activityName
1507
+ })
1333
1508
  }, params = {
1334
1509
  ...base,
1335
1510
  fields,
1336
1511
  actor: opts?.actor,
1337
- assigned: opts?.activityName !== void 0 ? assignedFor(ctx.instance, opts.activityName, opts?.actor, ctx.definition.roleAliases) : !1,
1512
+ assigned: opts?.activityName !== void 0 ? assignedFor({
1513
+ instance: ctx.instance,
1514
+ activityName: opts.activityName,
1515
+ actor: opts?.actor,
1516
+ roleAliases: ctx.definition.roleAliases
1517
+ }) : !1,
1338
1518
  ...opts?.vars
1339
1519
  };
1340
1520
  return { ...await evaluatePredicates({
@@ -1343,15 +1523,23 @@ async function ctxConditionParams(ctx, opts) {
1343
1523
  params
1344
1524
  }), ...params };
1345
1525
  }
1346
- async function ctxEvaluateConditionOutcome(ctx, condition, opts) {
1526
+ async function ctxEvaluateConditionOutcome({
1527
+ ctx,
1528
+ condition,
1529
+ opts
1530
+ }) {
1347
1531
  return condition === void 0 ? "satisfied" : evaluateConditionOutcome({
1348
1532
  condition,
1349
1533
  snapshot: ctx.snapshot,
1350
1534
  params: await ctxConditionParams(ctx, opts)
1351
1535
  });
1352
1536
  }
1353
- async function ctxEvaluateCondition(ctx, condition, opts) {
1354
- return await ctxEvaluateConditionOutcome(ctx, condition, opts) === "satisfied";
1537
+ async function ctxEvaluateCondition({
1538
+ ctx,
1539
+ condition,
1540
+ opts
1541
+ }) {
1542
+ return await ctxEvaluateConditionOutcome({ ctx, condition, opts }) === "satisfied";
1355
1543
  }
1356
1544
  async function buildEngineContext(args) {
1357
1545
  const { client, clientForGdr, instance, definition } = args, clock = args.clock ?? wallClock;
@@ -1415,10 +1603,14 @@ async function resolveActivityFieldEntries(args) {
1415
1603
  async function resolveBindings(args) {
1416
1604
  const resolved = {};
1417
1605
  for (const [key, groq] of Object.entries(args.bindings ?? {}))
1418
- resolved[key] = await runGroq(groq, args.params, args.snapshot);
1606
+ resolved[key] = await runGroq({ groq, params: args.params, snapshot: args.snapshot });
1419
1607
  return { ...resolved, ...args.staticInput };
1420
1608
  }
1421
- async function reload(client, instanceId, tag) {
1609
+ async function reload({
1610
+ client,
1611
+ instanceId,
1612
+ tag
1613
+ }) {
1422
1614
  const doc = await client.getDocument(instanceId);
1423
1615
  if (!doc)
1424
1616
  throw new Error(`Workflow instance ${instanceId} not found`);
@@ -1527,13 +1719,25 @@ async function persist(ctx, mutation) {
1527
1719
  const actorForPriming = void 0;
1528
1720
  for (const body of pendingCreates)
1529
1721
  try {
1530
- await primeInitialStage(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock), await cascadeAutoTransitions(
1531
- ctx.client,
1532
- body._id,
1533
- actorForPriming,
1534
- ctx.clientForGdr,
1535
- ctx.clock
1536
- ), await propagateToAncestors(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock);
1722
+ await primeInitialStage({
1723
+ client: ctx.client,
1724
+ instanceId: body._id,
1725
+ actor: actorForPriming,
1726
+ clientForGdr: ctx.clientForGdr,
1727
+ clock: ctx.clock
1728
+ }), await cascadeAutoTransitions({
1729
+ client: ctx.client,
1730
+ instanceId: body._id,
1731
+ actor: actorForPriming,
1732
+ clientForGdr: ctx.clientForGdr,
1733
+ clock: ctx.clock
1734
+ }), await propagateToAncestors({
1735
+ client: ctx.client,
1736
+ instanceId: body._id,
1737
+ actor: actorForPriming,
1738
+ clientForGdr: ctx.clientForGdr,
1739
+ clock: ctx.clock
1740
+ });
1537
1741
  } catch (cause) {
1538
1742
  throw cause instanceof WorkflowStateDivergedError ? cause : new WorkflowStateDivergedError({
1539
1743
  instanceId: ctx.instance._id,
@@ -1577,159 +1781,16 @@ function findCurrentStageEntry(instance) {
1577
1781
  function findCurrentActivities(instance) {
1578
1782
  return findCurrentStageEntry(instance)?.activities ?? [];
1579
1783
  }
1580
- async function deployOrRollback(args) {
1581
- try {
1582
- await args.deploy();
1583
- } catch (guardError) {
1584
- if (!args.reversible)
1585
- throw new WorkflowStateDivergedError({
1586
- instanceId: args.instanceId,
1587
- guardError,
1588
- reason: "the move created child instances a parent-only rollback cannot undo"
1589
- });
1590
- try {
1591
- let patch = args.client.patch(args.instanceId).set(args.restore);
1592
- args.unset && args.unset.length > 0 && (patch = patch.unset(args.unset)), args.committedRev !== void 0 && (patch = patch.ifRevisionId(args.committedRev)), await patch.commit(SYNC_COMMIT);
1593
- } catch (rollbackError) {
1594
- throw new WorkflowStateDivergedError({
1595
- instanceId: args.instanceId,
1596
- guardError,
1597
- rollbackError,
1598
- reason: "rollback of the committed move failed"
1599
- });
1600
- }
1601
- throw guardError instanceof PartialGuardDeployError ? new WorkflowStateDivergedError({
1602
- instanceId: args.instanceId,
1603
- guardError,
1604
- reason: "a multi-guard deploy partially applied; the rolled-back move left orphaned guard locks"
1605
- }) : guardError;
1606
- }
1607
- }
1608
- async function persistThenDeploy(ctx, mutation, deploy) {
1609
- const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
1610
- await deployOrRollback({
1611
- client: ctx.client,
1612
- instanceId: ctx.instance._id,
1613
- committedRev: committed._rev,
1614
- restore: instanceStateFields(ctx.instance),
1615
- unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
1616
- reversible: !spawned,
1617
- deploy
1618
- });
1619
- }
1620
- async function refreshStageGuards(ctx, mutation, stageName) {
1621
- await deployStageGuards({
1622
- client: ctx.client,
1623
- clientForGdr: ctx.clientForGdr,
1624
- instance: materializeInstance(ctx.instance, mutation),
1625
- definition: ctx.definition,
1626
- stageName,
1627
- now: ctx.now
1628
- });
1629
- }
1630
- async function completeEffect(args) {
1631
- const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
1632
- ...options?.clock ? { clock: options.clock } : {},
1633
- ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
1634
- });
1635
- return commitCompleteEffect(
1636
- ctx,
1637
- effectKey,
1638
- status,
1639
- outputs,
1640
- detail,
1641
- error,
1642
- durationMs,
1643
- options?.actor
1644
- );
1645
- }
1646
- function buildEffectHistoryEntry(pending, outcome) {
1647
- const { status, ranAt, actor, detail, error, durationMs, outputs } = outcome, resolvedActor = actor ?? pending.actor;
1648
- return {
1649
- _key: pending._key,
1650
- name: pending.name,
1651
- ...pending.title !== void 0 ? { title: pending.title } : {},
1652
- ...pending.description !== void 0 ? { description: pending.description } : {},
1653
- params: pending.params,
1654
- origin: pending.origin,
1655
- ...resolvedActor !== void 0 ? { actor: resolvedActor } : {},
1656
- ranAt,
1657
- ...durationMs !== void 0 ? { durationMs } : {},
1658
- status,
1659
- ...detail !== void 0 ? { detail } : {},
1660
- ...error !== void 0 ? { error } : {},
1661
- ...outputs !== void 0 ? { outputs } : {}
1662
- };
1663
- }
1664
- function upsertEffectsContext(mutation, effectName, outputs) {
1665
- const existingIndex = mutation.effectsContext.findIndex((e) => e.name === effectName), entry = effectsContextJsonEntry(effectName, outputs);
1666
- existingIndex >= 0 ? mutation.effectsContext[existingIndex] = entry : mutation.effectsContext.push(entry);
1667
- }
1668
- async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, error, durationMs, actor) {
1669
- const pending = ctx.instance.pendingEffects.find((e) => e._key === effectKey);
1670
- if (pending === void 0)
1671
- throw new Error(`Pending effect "${effectKey}" not found on instance ${ctx.instance._id}`);
1672
- const mutation = startMutation(ctx.instance);
1673
- mutation.pendingEffects = mutation.pendingEffects.filter((e) => e._key !== effectKey);
1674
- const ranAt = ctx.now;
1675
- mutation.effectHistory.push(
1676
- buildEffectHistoryEntry(pending, { status, ranAt, actor, detail, error, durationMs, outputs })
1677
- );
1678
- const wroteEffectsContext = status === "done" && outputs !== void 0;
1679
- return wroteEffectsContext && upsertEffectsContext(mutation, pending.name, outputs), mutation.history.push({
1680
- _key: randomKey(),
1681
- _type: "effectCompleted",
1682
- at: ranAt,
1683
- effectKey,
1684
- effect: pending.name,
1685
- status,
1686
- ...outputs !== void 0 ? { outputs } : {},
1687
- ...detail !== void 0 ? { detail } : {},
1688
- ...actor !== void 0 ? { actor } : {}
1689
- }), await persistThenDeploy(
1690
- ctx,
1691
- mutation,
1692
- () => wroteEffectsContext ? refreshStageGuards(ctx, mutation, ctx.instance.currentStage) : Promise.resolve()
1693
- ), { effectKey, status };
1694
- }
1695
- function buildQueuedEffect(effect, origin, params, actor, now) {
1696
- const key = randomKey(), pending = {
1697
- _key: key,
1698
- _type: "pendingEffect",
1699
- name: effect.name,
1700
- ...effect.title !== void 0 ? { title: effect.title } : {},
1701
- ...effect.description !== void 0 ? { description: effect.description } : {},
1702
- ...effect.bindings !== void 0 ? { bindings: effect.bindings } : {},
1703
- params,
1704
- origin,
1705
- ...actor !== void 0 ? { actor } : {},
1706
- queuedAt: now
1707
- }, history = {
1708
- _key: randomKey(),
1709
- _type: "effectQueued",
1710
- at: now,
1711
- effectKey: key,
1712
- effect: effect.name,
1713
- origin
1714
- };
1715
- return { pending, history };
1716
- }
1717
- async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
1718
- if (!effects || effects.length === 0) return;
1719
- const now = ctx.now, liveCtx = { ...ctx, instance: materializeInstance(ctx.instance, mutation) }, params = await ctxConditionParams(liveCtx, {
1720
- ...opts?.activityName !== void 0 ? { activityName: opts.activityName } : {},
1721
- ...actor !== void 0 ? { actor } : {},
1722
- // Caller-supplied action params, readable in bindings as `$params.<name>`.
1723
- vars: { params: opts?.callerParams ?? {} }
1724
- });
1725
- for (const effect of effects) {
1726
- const resolved = await resolveBindings({
1727
- bindings: effect.bindings,
1728
- staticInput: effect.input,
1729
- snapshot: ctx.snapshot,
1730
- params
1731
- }), { pending, history } = buildQueuedEffect(effect, origin, resolved, actor, now);
1732
- mutation.pendingEffects.push(pending), mutation.history.push(history);
1784
+ function resolveStaticValueExpr(src, ctx) {
1785
+ switch (src.type) {
1786
+ case "literal":
1787
+ return src.value;
1788
+ case "param":
1789
+ return ctx.params?.[src.param];
1790
+ case "actor":
1791
+ return ctx.actor;
1792
+ case "now":
1793
+ return ctx.now;
1733
1794
  }
1734
1795
  }
1735
1796
  function fieldQueryDiscardedEntry(args) {
@@ -1742,7 +1803,11 @@ function fieldQueryDiscardedEntry(args) {
1742
1803
  detail: args.detail
1743
1804
  };
1744
1805
  }
1745
- function recordFieldDiscards(target, scope, at) {
1806
+ function recordFieldDiscards({
1807
+ target,
1808
+ scope,
1809
+ at
1810
+ }) {
1746
1811
  return (discard) => target.push(fieldQueryDiscardedEntry({ scope, field: discard.field, detail: discard.detail, at }));
1747
1812
  }
1748
1813
  function applyActivityStatusChange(args) {
@@ -1790,18 +1855,6 @@ function stageTransitionHistory(args) {
1790
1855
  }
1791
1856
  ];
1792
1857
  }
1793
- function resolveStaticValueExpr(src, ctx) {
1794
- switch (src.type) {
1795
- case "literal":
1796
- return src.value;
1797
- case "param":
1798
- return ctx.params?.[src.param];
1799
- case "actor":
1800
- return ctx.actor;
1801
- case "now":
1802
- return ctx.now;
1803
- }
1804
- }
1805
1858
  const FIELD_OP_TYPES = [
1806
1859
  "field.set",
1807
1860
  "field.unset",
@@ -1812,7 +1865,11 @@ const FIELD_OP_TYPES = [
1812
1865
  function isFieldOp(summary) {
1813
1866
  return FIELD_OP_TYPES.includes(summary.opType);
1814
1867
  }
1815
- function validateActionParams(action, activityName, callerParams) {
1868
+ function validateActionParams({
1869
+ action,
1870
+ activityName,
1871
+ callerParams
1872
+ }) {
1816
1873
  const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
1817
1874
  for (const decl of declared) {
1818
1875
  const value = params[decl.name], present = decl.name in params && value !== void 0 && value !== null;
@@ -1847,7 +1904,7 @@ function checkParamType(value, decl) {
1847
1904
  case "doc.ref":
1848
1905
  return hasStringField(value, "id");
1849
1906
  case "doc.refs":
1850
- return Array.isArray(value) && value.every((v2) => checkParamType(v2, { type: "doc.ref" }));
1907
+ return Array.isArray(value) && value.every((ref) => checkParamType(ref, { type: "doc.ref" }));
1851
1908
  case "json":
1852
1909
  return !0;
1853
1910
  default:
@@ -1883,12 +1940,27 @@ function opAppliedEntry(args) {
1883
1940
  ...origin.action !== void 0 ? { action: origin.action } : {},
1884
1941
  ...origin.transition !== void 0 ? { transition: origin.transition } : {},
1885
1942
  ...origin.edit === !0 ? { edit: !0 } : {},
1943
+ ...origin.effect !== void 0 ? { effect: origin.effect } : {},
1886
1944
  opType: summary.opType,
1887
1945
  ...summary.target !== void 0 ? { target: summary.target } : {},
1888
1946
  ...summary.resolved !== void 0 ? { resolved: summary.resolved } : {},
1889
1947
  ...actor !== void 0 ? { actor } : {}
1890
1948
  };
1891
1949
  }
1950
+ function validateEffectOps(ops, effectName) {
1951
+ const result = v.safeParse(v.array(StoredFieldOpSchema), ops);
1952
+ if (!result.success)
1953
+ throw new EffectOpsInvalidError({ effect: effectName, issues: formatIssues(result.issues) });
1954
+ const activityScoped = result.output.find((op) => op.target.scope === "activity");
1955
+ if (activityScoped !== void 0)
1956
+ throw new EffectOpsInvalidError({
1957
+ effect: effectName,
1958
+ issues: [
1959
+ `${activityScoped.type} targets an activity-scope field \u2014 an effect's completion ops write workflow- or stage-scope fields`
1960
+ ]
1961
+ });
1962
+ return result.output;
1963
+ }
1892
1964
  function applyOp(op, ctx) {
1893
1965
  switch (op.type) {
1894
1966
  case "field.set":
@@ -1944,7 +2016,7 @@ function applyFieldUpdateWhere(op, ctx) {
1944
2016
  return setEntryValue(
1945
2017
  entry,
1946
2018
  rows.map(
1947
- (row) => evalOpPredicate(op.where, row, ctx) ? { ...row, ...merge } : row
2019
+ (row) => evalOpPredicate({ p: op.where, row, ctx }) ? { ...row, ...merge } : row
1948
2020
  )
1949
2021
  ), { opType: op.type, target: op.target, resolved: { merge } };
1950
2022
  }
@@ -1952,7 +2024,7 @@ function applyFieldRemoveWhere(op, ctx) {
1952
2024
  const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op);
1953
2025
  return setEntryValue(
1954
2026
  entry,
1955
- rows.filter((row) => !evalOpPredicate(op.where, row, ctx))
2027
+ rows.filter((row) => !evalOpPredicate({ p: op.where, row, ctx }))
1956
2028
  ), { opType: op.type, target: op.target };
1957
2029
  }
1958
2030
  function requireArrayValue(entry, op) {
@@ -1963,7 +2035,7 @@ function requireArrayValue(entry, op) {
1963
2035
  return entry.value;
1964
2036
  }
1965
2037
  function applyStatusSet(op, ctx) {
1966
- const entry = findOpenStageEntry(ctx.mutation)?.activities.find((t) => t.name === op.activity);
2038
+ const entry = findCurrentActivityEntry(ctx.mutation, op.activity);
1967
2039
  if (entry === void 0)
1968
2040
  throw new Error(
1969
2041
  `status.set targets activity "${op.activity}" which has no entry in the open stage`
@@ -2018,24 +2090,247 @@ function resolveOpValue(src, ctx) {
2018
2090
  }
2019
2091
  }
2020
2092
  }
2021
- function entryValue(entries, name) {
2022
- return entries?.find((s) => s.name === name)?.value;
2093
+ function entryValue(entries, name) {
2094
+ return entries?.find((s) => s.name === name)?.value;
2095
+ }
2096
+ function readEntryFromMutation(ctx, src) {
2097
+ const scopes = src.scope !== void 0 ? [src.scope] : ["stage", "workflow"], stageEntry = findOpenStageEntry(ctx.mutation);
2098
+ for (const scope of scopes) {
2099
+ const host = scope === "workflow" ? ctx.mutation.fields : stageEntry?.fields, value = entryValue(host, src.field);
2100
+ if (value !== void 0) return value;
2101
+ }
2102
+ }
2103
+ function evalOpPredicate({
2104
+ p,
2105
+ row,
2106
+ ctx
2107
+ }) {
2108
+ switch (p.type) {
2109
+ case "all":
2110
+ return p.of.every((sub) => evalOpPredicate({ p: sub, row, ctx }));
2111
+ case "any":
2112
+ return p.of.some((sub) => evalOpPredicate({ p: sub, row, ctx }));
2113
+ case "field":
2114
+ return row[p.field] === resolveOpValue(p.equals, ctx);
2115
+ }
2116
+ }
2117
+ async function deployOrRollback(args) {
2118
+ try {
2119
+ await args.deploy();
2120
+ } catch (guardError) {
2121
+ if (!args.reversible)
2122
+ throw new WorkflowStateDivergedError({
2123
+ instanceId: args.instanceId,
2124
+ guardError,
2125
+ reason: "the move created child instances a parent-only rollback cannot undo"
2126
+ });
2127
+ try {
2128
+ let patch = args.client.patch(args.instanceId).set(args.restore);
2129
+ args.unset && args.unset.length > 0 && (patch = patch.unset(args.unset)), args.committedRev !== void 0 && (patch = patch.ifRevisionId(args.committedRev)), await patch.commit(SYNC_COMMIT);
2130
+ } catch (rollbackError) {
2131
+ throw new WorkflowStateDivergedError({
2132
+ instanceId: args.instanceId,
2133
+ guardError,
2134
+ rollbackError,
2135
+ reason: "rollback of the committed move failed"
2136
+ });
2137
+ }
2138
+ throw guardError instanceof PartialGuardDeployError ? new WorkflowStateDivergedError({
2139
+ instanceId: args.instanceId,
2140
+ guardError,
2141
+ reason: "a multi-guard deploy partially applied; the rolled-back move left orphaned guard locks"
2142
+ }) : guardError;
2143
+ }
2144
+ }
2145
+ async function persistThenDeploy({
2146
+ ctx,
2147
+ mutation,
2148
+ deploy
2149
+ }) {
2150
+ const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
2151
+ await deployOrRollback({
2152
+ client: ctx.client,
2153
+ instanceId: ctx.instance._id,
2154
+ committedRev: committed._rev,
2155
+ restore: instanceStateFields(ctx.instance),
2156
+ unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
2157
+ reversible: !spawned,
2158
+ deploy
2159
+ });
2160
+ }
2161
+ async function persistThenMaybeRefresh({
2162
+ ctx,
2163
+ mutation,
2164
+ stageName,
2165
+ didChangeState
2166
+ }) {
2167
+ await persistThenDeploy({
2168
+ ctx,
2169
+ mutation,
2170
+ deploy: () => didChangeState ? refreshStageGuards({ ctx, mutation, stageName }) : Promise.resolve()
2171
+ });
2172
+ }
2173
+ async function refreshStageGuards({
2174
+ ctx,
2175
+ mutation,
2176
+ stageName
2177
+ }) {
2178
+ await deployStageGuards({
2179
+ client: ctx.client,
2180
+ clientForGdr: ctx.clientForGdr,
2181
+ instance: materializeInstance(ctx.instance, mutation),
2182
+ definition: ctx.definition,
2183
+ stageName,
2184
+ now: ctx.now
2185
+ });
2186
+ }
2187
+ async function completeEffect(args) {
2188
+ const { client, instanceId, effectKey, status, outputs, ops, detail, error, durationMs, options } = args, ctx = await loadCallContext({ client, instanceId, options });
2189
+ return commitCompleteEffect({
2190
+ ctx,
2191
+ effectKey,
2192
+ status,
2193
+ outputs,
2194
+ ops,
2195
+ detail,
2196
+ error,
2197
+ durationMs,
2198
+ actor: options?.actor
2199
+ });
2200
+ }
2201
+ function buildEffectHistoryEntry(pending, outcome) {
2202
+ const { status, ranAt, actor, detail, error, durationMs, outputs } = outcome, resolvedActor = actor ?? pending.actor;
2203
+ return {
2204
+ _key: pending._key,
2205
+ name: pending.name,
2206
+ ...pending.title !== void 0 ? { title: pending.title } : {},
2207
+ ...pending.description !== void 0 ? { description: pending.description } : {},
2208
+ params: pending.params,
2209
+ origin: pending.origin,
2210
+ ...resolvedActor !== void 0 ? { actor: resolvedActor } : {},
2211
+ ranAt,
2212
+ ...durationMs !== void 0 ? { durationMs } : {},
2213
+ status,
2214
+ ...detail !== void 0 ? { detail } : {},
2215
+ ...error !== void 0 ? { error } : {},
2216
+ ...outputs !== void 0 ? { outputs } : {}
2217
+ };
2023
2218
  }
2024
- function readEntryFromMutation(ctx, src) {
2025
- const scopes = src.scope !== void 0 ? [src.scope] : ["stage", "workflow"], stageEntry = findOpenStageEntry(ctx.mutation);
2026
- for (const scope of scopes) {
2027
- const host = scope === "workflow" ? ctx.mutation.fields : stageEntry?.fields, value = entryValue(host, src.field);
2028
- if (value !== void 0) return value;
2029
- }
2219
+ function upsertEffectsContext({
2220
+ mutation,
2221
+ effectName,
2222
+ outputs
2223
+ }) {
2224
+ const existingIndex = mutation.effectsContext.findIndex((e) => e.name === effectName), entry = effectsContextJsonEntry(effectName, outputs);
2225
+ existingIndex >= 0 ? mutation.effectsContext[existingIndex] = entry : mutation.effectsContext.push(entry);
2030
2226
  }
2031
- function evalOpPredicate(p, row, ctx) {
2032
- switch (p.type) {
2033
- case "all":
2034
- return p.of.every((sub) => evalOpPredicate(sub, row, ctx));
2035
- case "any":
2036
- return p.of.some((sub) => evalOpPredicate(sub, row, ctx));
2037
- case "field":
2038
- return row[p.field] === resolveOpValue(p.equals, ctx);
2227
+ async function commitCompleteEffect({
2228
+ ctx,
2229
+ effectKey,
2230
+ status,
2231
+ outputs,
2232
+ ops,
2233
+ detail,
2234
+ error,
2235
+ durationMs,
2236
+ actor
2237
+ }) {
2238
+ const pending = ctx.instance.pendingEffects.find((e) => e._key === effectKey);
2239
+ if (pending === void 0)
2240
+ throw new Error(`Pending effect "${effectKey}" not found on instance ${ctx.instance._id}`);
2241
+ if (status === "failed" && ops !== void 0 && ops.length > 0)
2242
+ throw new EffectOpsInvalidError({
2243
+ effect: pending.name,
2244
+ issues: [
2245
+ "ops cannot accompany a failed completion \u2014 field.set the outcome on a done completion instead"
2246
+ ]
2247
+ });
2248
+ const validatedOps = ops !== void 0 ? validateEffectOps(ops, pending.name) : [], mutation = startMutation(ctx.instance);
2249
+ mutation.pendingEffects = mutation.pendingEffects.filter((e) => e._key !== effectKey);
2250
+ const ranAt = ctx.now;
2251
+ mutation.effectHistory.push(
2252
+ buildEffectHistoryEntry(pending, { status, ranAt, actor, detail, error, durationMs, outputs })
2253
+ );
2254
+ const wroteEffectsContext = status === "done" && outputs !== void 0;
2255
+ wroteEffectsContext && upsertEffectsContext({ mutation, effectName: pending.name, outputs }), mutation.history.push({
2256
+ _key: randomKey(),
2257
+ _type: "effectCompleted",
2258
+ at: ranAt,
2259
+ effectKey,
2260
+ effect: pending.name,
2261
+ status,
2262
+ ...outputs !== void 0 ? { outputs } : {},
2263
+ ...detail !== void 0 ? { detail } : {},
2264
+ ...actor !== void 0 ? { actor } : {}
2265
+ });
2266
+ const ranOps = runOps({
2267
+ ops: validatedOps,
2268
+ mutation,
2269
+ stage: ctx.instance.currentStage,
2270
+ origin: { effect: pending.name },
2271
+ params: pending.params,
2272
+ actor,
2273
+ self: selfGdr(ctx.instance),
2274
+ now: ranAt
2275
+ }), needsGuardRefresh = wroteEffectsContext || ranOps.some(isFieldOp);
2276
+ return await persistThenMaybeRefresh({
2277
+ ctx,
2278
+ mutation,
2279
+ stageName: ctx.instance.currentStage,
2280
+ didChangeState: needsGuardRefresh
2281
+ }), { effectKey, status };
2282
+ }
2283
+ function buildQueuedEffect({
2284
+ effect,
2285
+ origin,
2286
+ params,
2287
+ actor,
2288
+ now
2289
+ }) {
2290
+ const key = randomKey(), pending = {
2291
+ _key: key,
2292
+ _type: "pendingEffect",
2293
+ name: effect.name,
2294
+ ...effect.title !== void 0 ? { title: effect.title } : {},
2295
+ ...effect.description !== void 0 ? { description: effect.description } : {},
2296
+ ...effect.bindings !== void 0 ? { bindings: effect.bindings } : {},
2297
+ params,
2298
+ origin,
2299
+ ...actor !== void 0 ? { actor } : {},
2300
+ queuedAt: now
2301
+ }, history = {
2302
+ _key: randomKey(),
2303
+ _type: "effectQueued",
2304
+ at: now,
2305
+ effectKey: key,
2306
+ effect: effect.name,
2307
+ origin
2308
+ };
2309
+ return { pending, history };
2310
+ }
2311
+ async function queueEffects({
2312
+ ctx,
2313
+ mutation,
2314
+ effects,
2315
+ origin,
2316
+ actor,
2317
+ opts
2318
+ }) {
2319
+ if (!effects || effects.length === 0) return;
2320
+ const now = ctx.now, liveCtx = { ...ctx, instance: materializeInstance(ctx.instance, mutation) }, params = await ctxConditionParams(liveCtx, {
2321
+ ...opts?.activityName !== void 0 ? { activityName: opts.activityName } : {},
2322
+ ...actor !== void 0 ? { actor } : {},
2323
+ // Caller-supplied action params, readable in bindings as `$params.<name>`.
2324
+ vars: { params: opts?.callerParams ?? {} }
2325
+ });
2326
+ for (const effect of effects) {
2327
+ const resolved = await resolveBindings({
2328
+ bindings: effect.bindings,
2329
+ staticInput: effect.input,
2330
+ snapshot: ctx.snapshot,
2331
+ params
2332
+ }), { pending, history } = buildQueuedEffect({ effect, origin, params: resolved, actor, now });
2333
+ mutation.pendingEffects.push(pending), mutation.history.push(history);
2039
2334
  }
2040
2335
  }
2041
2336
  const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
@@ -2056,13 +2351,14 @@ async function discoverItems(args) {
2056
2351
  const { client, groq, params, perspective } = args, fetchOptions = perspective !== void 0 ? { perspective } : void 0;
2057
2352
  return client.fetch(groq, paramsForLake(params), fetchOptions);
2058
2353
  }
2059
- function clientForDiscovery(defaultClient, clientForGdr, subjectGdrUri) {
2060
- if (!subjectGdrUri || !subjectGdrUri.includes(":")) return defaultClient;
2061
- try {
2062
- return clientForGdr(parseGdr(subjectGdrUri));
2063
- } catch {
2064
- return defaultClient;
2065
- }
2354
+ function clientForDiscovery({
2355
+ defaultClient,
2356
+ clientForGdr,
2357
+ subjectGdrUri
2358
+ }) {
2359
+ if (subjectGdrUri === void 0) return defaultClient;
2360
+ const parsed = tryParseGdr(subjectGdrUri);
2361
+ return parsed !== void 0 ? clientForGdr(parsed) : defaultClient;
2066
2362
  }
2067
2363
  const ENGINE_ACTOR_ID_PREFIX = "engine.";
2068
2364
  function engineSystemActor(name) {
@@ -2075,25 +2371,49 @@ const MAX_SPAWN_DEPTH = 6, SUBWORKFLOWS_ALL_DONE = "count($subworkflows[status !
2075
2371
  function gateActor(outcome) {
2076
2372
  return engineSystemActor(outcome === "failed" ? "failWhen" : "completeWhen");
2077
2373
  }
2374
+ function applyGateOutcome(args) {
2375
+ applyActivityStatusChange({
2376
+ entry: args.entry,
2377
+ history: args.history,
2378
+ stage: args.stage,
2379
+ to: args.outcome,
2380
+ at: args.at,
2381
+ actor: gateActor(args.outcome)
2382
+ });
2383
+ }
2078
2384
  function effectiveCompleteWhen(activity) {
2079
2385
  return activity.completeWhen ?? (activity.subworkflows !== void 0 ? SUBWORKFLOWS_ALL_DONE : void 0);
2080
2386
  }
2081
- async function evaluateResolutionGate(ctx, activity, vars) {
2387
+ async function evaluateResolutionGate({
2388
+ ctx,
2389
+ activity,
2390
+ vars
2391
+ }) {
2082
2392
  const scope = { activityName: activity.name, vars };
2083
- if (activity.failWhen !== void 0 && await ctxEvaluateCondition(ctx, activity.failWhen, scope))
2393
+ if (activity.failWhen !== void 0 && await ctxEvaluateCondition({ ctx, condition: activity.failWhen, opts: scope }))
2084
2394
  return "failed";
2085
2395
  const completeWhen = effectiveCompleteWhen(activity);
2086
- if (completeWhen !== void 0 && await ctxEvaluateCondition(ctx, completeWhen, scope))
2396
+ if (completeWhen !== void 0 && await ctxEvaluateCondition({ ctx, condition: completeWhen, opts: scope }))
2087
2397
  return "done";
2088
2398
  }
2089
- async function spawnSubworkflows(ctx, mutation, activity, sub, actor) {
2399
+ async function spawnSubworkflows({
2400
+ ctx,
2401
+ mutation,
2402
+ activity,
2403
+ sub,
2404
+ actor
2405
+ }) {
2090
2406
  if (ctx.instance.ancestors.length + 1 > MAX_SPAWN_DEPTH) {
2091
2407
  const chain = [...ctx.instance.ancestors.map((a) => a.id), selfGdr(ctx.instance)].join(" \u2192 ");
2092
2408
  throw new Error(
2093
2409
  `Subworkflow depth limit (${MAX_SPAWN_DEPTH}) exceeded on activity "${activity.name}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
2094
2410
  );
2095
2411
  }
2096
- const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tag);
2412
+ const now = ctx.now, definition = await resolveDefinitionRef({
2413
+ client: ctx.client,
2414
+ ref: sub.definition,
2415
+ tag: ctx.instance.tag
2416
+ });
2097
2417
  if (definition === null) {
2098
2418
  const versionLabel = sub.definition.version === void 0 || sub.definition.version === "latest" ? "latest" : `v${sub.definition.version}`;
2099
2419
  throw new Error(
@@ -2103,16 +2423,16 @@ async function spawnSubworkflows(ctx, mutation, activity, sub, actor) {
2103
2423
  const parentScope = await ctxConditionParams(ctx, {
2104
2424
  activityName: activity.name,
2105
2425
  ...actor ? { actor } : {}
2106
- }), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
2426
+ }), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff({ ctx, sub, parentScope }), refs = [];
2107
2427
  for (const row of rows) {
2108
- const initialFields = await projectRowFields(
2428
+ const initialFields = await projectRowFields({
2109
2429
  ctx,
2110
2430
  sub,
2111
- definition,
2431
+ childDefinition: definition,
2112
2432
  parentScope,
2113
2433
  row,
2114
2434
  discoveryResource
2115
- ), { ref, body } = await prepareChildInstance({
2435
+ }), { ref, body } = await prepareChildInstance({
2116
2436
  client: ctx.client,
2117
2437
  parent: ctx.instance,
2118
2438
  definition,
@@ -2129,7 +2449,11 @@ async function resolveSpawnRows(ctx, sub) {
2129
2449
  const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
2130
2450
  let value, discoveryResource;
2131
2451
  if (groq.includes("*")) {
2132
- const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
2452
+ const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery({
2453
+ defaultClient: ctx.client,
2454
+ clientForGdr: ctx.clientForGdr,
2455
+ subjectGdrUri: routingUri
2456
+ });
2133
2457
  client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
2134
2458
  client,
2135
2459
  groq,
@@ -2145,7 +2469,14 @@ async function resolveSpawnRows(ctx, sub) {
2145
2469
  }
2146
2470
  return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
2147
2471
  }
2148
- async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
2472
+ async function projectRowFields({
2473
+ ctx,
2474
+ sub,
2475
+ childDefinition,
2476
+ parentScope,
2477
+ row,
2478
+ discoveryResource
2479
+ }) {
2149
2480
  const out = [];
2150
2481
  for (const [name, groq] of Object.entries(sub.with ?? {})) {
2151
2482
  const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
@@ -2153,11 +2484,15 @@ async function projectRowFields(ctx, sub, childDefinition, parentScope, row, dis
2153
2484
  throw new Error(
2154
2485
  `subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
2155
2486
  );
2156
- const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
2157
- parentResource: ctx.instance.workflowResource,
2158
- discoveryResource,
2159
- entryName: name,
2160
- childName: childDefinition.name
2487
+ const raw = await runGroq({ groq, params: { ...parentScope, row }, snapshot: ctx.snapshot }), value = canonicaliseSpawnValue({
2488
+ kind: declared.type,
2489
+ value: raw,
2490
+ cx: {
2491
+ parentResource: ctx.instance.workflowResource,
2492
+ discoveryResource,
2493
+ entryName: name,
2494
+ childName: childDefinition.name
2495
+ }
2161
2496
  });
2162
2497
  value != null && out.push({
2163
2498
  type: declared.type,
@@ -2167,15 +2502,23 @@ async function projectRowFields(ctx, sub, childDefinition, parentScope, row, dis
2167
2502
  }
2168
2503
  return out;
2169
2504
  }
2170
- async function resolveContextHandoff(ctx, sub, parentScope) {
2505
+ async function resolveContextHandoff({
2506
+ ctx,
2507
+ sub,
2508
+ parentScope
2509
+ }) {
2171
2510
  const out = {};
2172
2511
  for (const [name, groq] of Object.entries(sub.context ?? {})) {
2173
- const v2 = await runGroq(groq, parentScope, ctx.snapshot);
2512
+ const v2 = await runGroq({ groq, params: parentScope, snapshot: ctx.snapshot });
2174
2513
  v2 != null && (typeof v2 == "string" || typeof v2 == "number" || typeof v2 == "boolean" || typeof v2 == "object" && "id" in v2 && "type" in v2) && (out[name] = v2);
2175
2514
  }
2176
2515
  return out;
2177
2516
  }
2178
- function canonicaliseSpawnValue(kind, value, cx) {
2517
+ function canonicaliseSpawnValue({
2518
+ kind,
2519
+ value,
2520
+ cx
2521
+ }) {
2179
2522
  return kind === "doc.ref" ? coerceSpawnGdr(value, cx) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, cx)).filter((v2) => v2 !== null) : value;
2180
2523
  }
2181
2524
  function assertBareIdRootsCorrectly(id, cx) {
@@ -2198,7 +2541,11 @@ function coerceSpawnGdr(raw, cx) {
2198
2541
  }
2199
2542
  return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
2200
2543
  }
2201
- async function resolveDefinitionRef(client, ref, tag) {
2544
+ async function resolveDefinitionRef({
2545
+ client,
2546
+ ref,
2547
+ tag
2548
+ }) {
2202
2549
  const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
2203
2550
  return wantsExplicit && (params.version = ref.version), await client.fetch(
2204
2551
  definitionLookupGroq(wantsExplicit),
@@ -2206,7 +2553,7 @@ async function resolveDefinitionRef(client, ref, tag) {
2206
2553
  ) ?? null;
2207
2554
  }
2208
2555
  async function prepareChildInstance(args) {
2209
- const { client, parent, definition, initialFields, effectsContext, actor, now } = args, childTag = parent.tag, workflowResource = parent.workflowResource, childDocId = `${childTag}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type: WORKFLOW_INSTANCE_TYPE }, ancestors = [
2556
+ const { client, parent, definition, initialFields, effectsContext, actor, now } = args, childTag = parent.tag, workflowResource = parent.workflowResource, childDocId = instanceDocId(childTag), childRef = { id: gdrFromResource(workflowResource, childDocId), type: WORKFLOW_INSTANCE_TYPE }, ancestors = [
2210
2557
  ...parent.ancestors,
2211
2558
  {
2212
2559
  id: selfGdr(parent),
@@ -2223,7 +2570,7 @@ async function prepareChildInstance(args) {
2223
2570
  workflowResource,
2224
2571
  definitionName: definition.name,
2225
2572
  ...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {},
2226
- recordDiscard: recordFieldDiscards(fieldDiscards, "workflow", now)
2573
+ recordDiscard: recordFieldDiscards({ target: fieldDiscards, scope: "workflow", at: now })
2227
2574
  },
2228
2575
  randomKey
2229
2576
  }), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
@@ -2258,7 +2605,7 @@ async function prepareChildInstance(args) {
2258
2605
  }
2259
2606
  async function subworkflowRows(ctx, refs) {
2260
2607
  if (refs.length === 0) return [];
2261
- const ids = refs.map((r) => gdrToBareId(r.id));
2608
+ const ids = refs.map((r) => toBareId(r.id));
2262
2609
  return (await ctx.client.fetch("*[_id in $ids]{_id, currentStage, completedAt}", { ids })).map((c) => ({
2263
2610
  _id: c._id,
2264
2611
  stage: c.currentStage,
@@ -2266,13 +2613,14 @@ async function subworkflowRows(ctx, refs) {
2266
2613
  }));
2267
2614
  }
2268
2615
  async function invokeActivity(args) {
2269
- const { client, instanceId, activity: activityName, options } = args, ctx = await loadContext(client, instanceId, {
2270
- ...options?.clock ? { clock: options.clock } : {},
2271
- ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
2272
- });
2273
- return commitInvoke(ctx, activityName, options?.actor);
2274
- }
2275
- async function commitInvoke(ctx, activityName, actor) {
2616
+ const { client, instanceId, activity: activityName, options } = args, ctx = await loadCallContext({ client, instanceId, options });
2617
+ return commitInvoke({ ctx, activityName, actor: options?.actor });
2618
+ }
2619
+ async function commitInvoke({
2620
+ ctx,
2621
+ activityName,
2622
+ actor
2623
+ }) {
2276
2624
  const { activity } = findActivityInCurrentStage(ctx, activityName), entry = findCurrentActivities(ctx.instance).find((t) => t.name === activityName);
2277
2625
  if (entry === void 0)
2278
2626
  throw new Error(
@@ -2281,9 +2629,15 @@ async function commitInvoke(ctx, activityName, actor) {
2281
2629
  if (entry.status !== "pending")
2282
2630
  return { invoked: !1, activity: activityName };
2283
2631
  const mutation = startMutation(ctx.instance), mutEntry = requireMutationActivityEntry(mutation, activityName);
2284
- return await activateActivity(ctx, mutation, activity, mutEntry, actor), await persist(ctx, mutation), { invoked: !0, activity: activityName };
2285
- }
2286
- async function activateActivity(ctx, mutation, activity, entry, actor) {
2632
+ return await activateActivity({ ctx, mutation, activity, entry: mutEntry, actor }), await persist(ctx, mutation), { invoked: !0, activity: activityName };
2633
+ }
2634
+ async function activateActivity({
2635
+ ctx,
2636
+ mutation,
2637
+ activity,
2638
+ entry,
2639
+ actor
2640
+ }) {
2287
2641
  const now = ctx.now;
2288
2642
  if (entry.status = "active", entry.startedAt = now, activity.fields !== void 0 && activity.fields.length > 0) {
2289
2643
  const stage = findStage(ctx.definition, mutation.currentStage);
@@ -2293,7 +2647,7 @@ async function activateActivity(ctx, mutation, activity, entry, actor) {
2293
2647
  stage,
2294
2648
  activity,
2295
2649
  now,
2296
- recordDiscard: recordFieldDiscards(mutation.history, "activity", now)
2650
+ recordDiscard: recordFieldDiscards({ target: mutation.history, scope: "activity", at: now })
2297
2651
  });
2298
2652
  }
2299
2653
  runOps({
@@ -2312,19 +2666,25 @@ async function activateActivity(ctx, mutation, activity, entry, actor) {
2312
2666
  stage: mutation.currentStage,
2313
2667
  activity: activity.name,
2314
2668
  ...actor !== void 0 ? { actor } : {}
2315
- }), await queueEffects(
2669
+ }), await queueEffects({
2316
2670
  ctx,
2317
2671
  mutation,
2318
- activity.effects,
2319
- { kind: "activity", name: activity.name },
2672
+ effects: activity.effects,
2673
+ origin: { kind: "activity", name: activity.name },
2320
2674
  actor,
2321
- {
2675
+ opts: {
2322
2676
  activityName: activity.name
2323
2677
  }
2324
- );
2678
+ });
2325
2679
  let pendingChildren;
2326
2680
  if (activity.subworkflows !== void 0) {
2327
- const createsBefore = mutation.pendingCreates.length, refs = await spawnSubworkflows(ctx, mutation, activity, activity.subworkflows, actor);
2681
+ const createsBefore = mutation.pendingCreates.length, refs = await spawnSubworkflows({
2682
+ ctx,
2683
+ mutation,
2684
+ activity,
2685
+ sub: activity.subworkflows,
2686
+ actor
2687
+ });
2328
2688
  entry.spawnedInstances = refs, pendingChildren = mutation.pendingCreates.slice(createsBefore).map((c) => ({ _id: c._id, stage: c.currentStage, status: "active" }));
2329
2689
  for (const ref of refs)
2330
2690
  mutation.history.push({
@@ -2335,24 +2695,42 @@ async function activateActivity(ctx, mutation, activity, entry, actor) {
2335
2695
  instanceRef: ref
2336
2696
  });
2337
2697
  }
2338
- await autoResolveOnActivate(ctx, mutation, activity, entry, now, pendingChildren) || resolveMachineStep(mutation, activity, entry, now);
2339
- }
2340
- async function autoResolveOnActivate(ctx, mutation, activity, entry, now, pendingChildren) {
2698
+ await autoResolveOnActivate({
2699
+ ctx,
2700
+ mutation,
2701
+ activity,
2702
+ entry,
2703
+ now,
2704
+ ...pendingChildren !== void 0 ? { pendingChildren } : {}
2705
+ }) || resolveMachineStep({ mutation, activity, entry, now });
2706
+ }
2707
+ async function autoResolveOnActivate({
2708
+ ctx,
2709
+ mutation,
2710
+ activity,
2711
+ entry,
2712
+ now,
2713
+ pendingChildren
2714
+ }) {
2341
2715
  if (activity.failWhen === void 0 && effectiveCompleteWhen(activity) === void 0) return !1;
2342
- const vars = pendingChildren !== void 0 ? { subworkflows: pendingChildren } : await activityConditionVars(ctx, entry), outcome = await evaluateResolutionGate(ctx, activity, vars);
2343
- return outcome === void 0 ? !1 : (applyActivityStatusChange({
2716
+ const vars = pendingChildren !== void 0 ? { subworkflows: pendingChildren } : await activityConditionVars(ctx, entry), outcome = await evaluateResolutionGate({ ctx, activity, vars });
2717
+ return outcome === void 0 ? !1 : (applyGateOutcome({
2344
2718
  entry,
2345
2719
  history: mutation.history,
2346
2720
  stage: mutation.currentStage,
2347
- to: outcome,
2348
2721
  at: now,
2349
- actor: gateActor(outcome)
2722
+ outcome
2350
2723
  }), !0);
2351
2724
  }
2352
2725
  async function activityConditionVars(ctx, entry) {
2353
2726
  return entry.spawnedInstances === void 0 || entry.spawnedInstances.length === 0 ? { subworkflows: [] } : { subworkflows: await subworkflowRows(ctx, entry.spawnedInstances) };
2354
2727
  }
2355
- function resolveMachineStep(mutation, activity, entry, now) {
2728
+ function resolveMachineStep({
2729
+ mutation,
2730
+ activity,
2731
+ entry,
2732
+ now
2733
+ }) {
2356
2734
  const waitsForActor = activity.actions !== void 0 && activity.actions.length > 0, waitsForCondition = activity.completeWhen !== void 0 || activity.subworkflows !== void 0;
2357
2735
  waitsForActor || waitsForCondition || applyActivityStatusChange({
2358
2736
  entry,
@@ -2366,19 +2744,25 @@ function resolveMachineStep(mutation, activity, entry, now) {
2366
2744
  async function buildStageActivities(ctx, stage) {
2367
2745
  const entries = [];
2368
2746
  for (const activity of stage.activities ?? []) {
2369
- const outcome = await ctxEvaluateConditionOutcome(ctx, activity.filter, {
2370
- activityName: activity.name
2747
+ const outcome = await ctxEvaluateConditionOutcome({
2748
+ ctx,
2749
+ condition: activity.filter,
2750
+ opts: { activityName: activity.name }
2371
2751
  }), inScope = outcome !== "unsatisfied";
2372
2752
  entries.push({
2373
2753
  _key: randomKey(),
2374
2754
  name: activity.name,
2375
2755
  status: inScope ? "pending" : "skipped",
2376
- ...activity.filter !== void 0 ? { filterEvaluation: buildFilterEvaluation(ctx.now, activity.filter, outcome) } : {}
2756
+ ...activity.filter !== void 0 ? { filterEvaluation: buildFilterEvaluation({ at: ctx.now, filter: activity.filter, outcome }) } : {}
2377
2757
  });
2378
2758
  }
2379
2759
  return entries;
2380
2760
  }
2381
- function buildFilterEvaluation(at, filter, outcome) {
2761
+ function buildFilterEvaluation({
2762
+ at,
2763
+ filter,
2764
+ outcome
2765
+ }) {
2382
2766
  return outcome === "unevaluable" ? {
2383
2767
  at,
2384
2768
  truthy: !1,
@@ -2390,13 +2774,16 @@ function isTerminalStage(stage) {
2390
2774
  return (stage.transitions ?? []).length === 0;
2391
2775
  }
2392
2776
  async function setStage(args) {
2393
- const { client, instanceId, targetStage, reason, options } = args, ctx = await loadContext(client, instanceId, {
2394
- ...options?.clock ? { clock: options.clock } : {},
2395
- ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
2396
- });
2397
- return commitSetStage(ctx, targetStage, reason, options?.actor);
2398
- }
2399
- async function enterStage(ctx, mutation, nextStage, actor, at) {
2777
+ const { client, instanceId, targetStage, reason, options } = args, ctx = await loadCallContext({ client, instanceId, options });
2778
+ return commitSetStage({ ctx, targetStage, reason, actor: options?.actor });
2779
+ }
2780
+ async function enterStage({
2781
+ ctx,
2782
+ mutation,
2783
+ nextStage,
2784
+ actor,
2785
+ at
2786
+ }) {
2400
2787
  mutation.currentStage = nextStage.name;
2401
2788
  const nextStageEntry = {
2402
2789
  _key: randomKey(),
@@ -2407,7 +2794,7 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
2407
2794
  instance: ctx.instance,
2408
2795
  stage: nextStage,
2409
2796
  now: at,
2410
- recordDiscard: recordFieldDiscards(mutation.history, "stage", at)
2797
+ recordDiscard: recordFieldDiscards({ target: mutation.history, scope: "stage", at })
2411
2798
  }),
2412
2799
  activities: await buildStageActivities(ctx, nextStage)
2413
2800
  };
@@ -2415,14 +2802,19 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
2415
2802
  for (const activity of nextStage.activities ?? []) {
2416
2803
  if (activity.activation !== "auto") continue;
2417
2804
  const entry = nextStageEntry.activities.find((t) => t.name === activity.name);
2418
- entry && entry.status === "pending" && await activateActivity(ctx, mutation, activity, entry, actor);
2805
+ entry && entry.status === "pending" && await activateActivity({ ctx, mutation, activity, entry, actor });
2419
2806
  }
2420
2807
  isTerminalStage(nextStage) && (mutation.completedAt = at);
2421
2808
  }
2422
2809
  function isTerminal(ctx) {
2423
2810
  return ctx.instance.completedAt !== void 0;
2424
2811
  }
2425
- async function commitSetStage(ctx, targetStage, reason, actor) {
2812
+ async function commitSetStage({
2813
+ ctx,
2814
+ targetStage,
2815
+ reason,
2816
+ actor
2817
+ }) {
2426
2818
  if (isTerminal(ctx))
2427
2819
  return { fired: !1 };
2428
2820
  const currentStage = findStage(ctx.definition, ctx.instance.currentStage), nextStage = findStage(ctx.definition, targetStage);
@@ -2441,18 +2833,28 @@ async function commitSetStage(ctx, targetStage, reason, actor) {
2441
2833
  })
2442
2834
  );
2443
2835
  const priorEntry = findOpenStageEntry(mutation);
2444
- return priorEntry !== void 0 && (priorEntry.exitedAt = at), await enterStage(ctx, mutation, nextStage, actor, at), await persistStageMove(ctx, mutation, currentStage.name, nextStage.name), {
2836
+ return priorEntry !== void 0 && (priorEntry.exitedAt = at), await enterStage({ ctx, mutation, nextStage, actor, at }), await persistStageMove({
2837
+ ctx,
2838
+ mutation,
2839
+ exitedStage: currentStage.name,
2840
+ enteredStage: nextStage.name
2841
+ }), {
2445
2842
  fired: !0,
2446
2843
  fromStage: currentStage.name,
2447
2844
  toStage: nextStage.name,
2448
2845
  transition: transitionName
2449
2846
  };
2450
2847
  }
2451
- async function persistStageMove(ctx, mutation, exitedStage, enteredStage) {
2452
- await persistThenDeploy(
2848
+ async function persistStageMove({
2849
+ ctx,
2850
+ mutation,
2851
+ exitedStage,
2852
+ enteredStage
2853
+ }) {
2854
+ await persistThenDeploy({
2453
2855
  ctx,
2454
2856
  mutation,
2455
- () => deployStageGuards({
2857
+ deploy: () => deployStageGuards({
2456
2858
  client: ctx.client,
2457
2859
  clientForGdr: ctx.clientForGdr,
2458
2860
  instance: materializeInstance(ctx.instance, mutation),
@@ -2460,7 +2862,7 @@ async function persistStageMove(ctx, mutation, exitedStage, enteredStage) {
2460
2862
  stageName: enteredStage,
2461
2863
  now: ctx.now
2462
2864
  })
2463
- ), await retractStageGuards({
2865
+ }), await retractStageGuards({
2464
2866
  client: ctx.client,
2465
2867
  clientForGdr: ctx.clientForGdr,
2466
2868
  instance: ctx.instance,
@@ -2485,16 +2887,16 @@ async function commitTransition(ctx, actor) {
2485
2887
  actor,
2486
2888
  self: selfGdr(ctx.instance),
2487
2889
  now: at
2488
- }), await queueEffects(
2890
+ }), await queueEffects({
2489
2891
  ctx,
2490
2892
  mutation,
2491
- transition.effects,
2492
- {
2893
+ effects: transition.effects,
2894
+ origin: {
2493
2895
  kind: "transition",
2494
2896
  name: transition.name
2495
2897
  },
2496
2898
  actor
2497
- ), mutation.history.push(
2899
+ }), mutation.history.push(
2498
2900
  ...stageTransitionHistory({
2499
2901
  fromStage: currentStage.name,
2500
2902
  toStage: transition.to,
@@ -2506,7 +2908,12 @@ async function commitTransition(ctx, actor) {
2506
2908
  const priorEntry = findOpenStageEntry(mutation);
2507
2909
  priorEntry !== void 0 && (priorEntry.exitedAt = at);
2508
2910
  const nextStage = findStage(ctx.definition, transition.to);
2509
- return await enterStage(ctx, mutation, nextStage, actor, at), await persistStageMove(ctx, mutation, currentStage.name, nextStage.name), {
2911
+ return await enterStage({ ctx, mutation, nextStage, actor, at }), await persistStageMove({
2912
+ ctx,
2913
+ mutation,
2914
+ exitedStage: currentStage.name,
2915
+ enteredStage: nextStage.name
2916
+ }), {
2510
2917
  fired: !0,
2511
2918
  fromStage: currentStage.name,
2512
2919
  toStage: transition.to,
@@ -2515,20 +2922,30 @@ async function commitTransition(ctx, actor) {
2515
2922
  }
2516
2923
  async function pickTransition(ctx, stage) {
2517
2924
  for (const candidate of stage.transitions ?? []) {
2518
- const outcome = await ctxEvaluateConditionOutcome(ctx, candidate.filter);
2925
+ const outcome = await ctxEvaluateConditionOutcome({ ctx, condition: candidate.filter });
2519
2926
  if (outcome === "satisfied") return candidate;
2520
2927
  if (outcome === "unevaluable") return;
2521
2928
  }
2522
2929
  }
2523
2930
  async function evaluateAutoTransitions(args) {
2524
- const { client, instanceId, options, clientForGdr, clock, overlay } = args, ctx = await loadContext(client, instanceId, {
2525
- ...clientForGdr ? { clientForGdr } : {},
2526
- ...clock ? { clock } : {},
2527
- ...overlay ? { overlay } : {}
2931
+ const { client, instanceId, options, clientForGdr, clock, overlay } = args, ctx = await loadContext({
2932
+ client,
2933
+ instanceId,
2934
+ options: {
2935
+ ...clientForGdr ? { clientForGdr } : {},
2936
+ ...clock ? { clock } : {},
2937
+ ...overlay ? { overlay } : {}
2938
+ }
2528
2939
  });
2529
2940
  return commitTransition(ctx, options?.actor);
2530
2941
  }
2531
- async function primeInitialStage(client, instanceId, actor, clientForGdr, clock) {
2942
+ async function primeInitialStage({
2943
+ client,
2944
+ instanceId,
2945
+ actor,
2946
+ clientForGdr,
2947
+ clock
2948
+ }) {
2532
2949
  const instance = await client.getDocument(instanceId);
2533
2950
  if (!instance || instance.stages.length > 0) return;
2534
2951
  const definition = parseDefinitionSnapshot(instance), stage = definition.stages.find((s) => s.name === instance.currentStage);
@@ -2548,7 +2965,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
2548
2965
  instance,
2549
2966
  stage,
2550
2967
  now,
2551
- recordDiscard: recordFieldDiscards(discards, "stage", now)
2968
+ recordDiscard: recordFieldDiscards({ target: discards, scope: "stage", at: now })
2552
2969
  }),
2553
2970
  activities: await buildStageActivities(ctx, stage)
2554
2971
  }, committed = await client.patch(instance._id).set({
@@ -2573,7 +2990,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
2573
2990
  stageName: stage.name,
2574
2991
  now
2575
2992
  })
2576
- }), await autoActivatePrimedActivities(
2993
+ }), await autoActivatePrimedActivities({
2577
2994
  client,
2578
2995
  instanceId,
2579
2996
  definition,
@@ -2581,9 +2998,17 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
2581
2998
  actor,
2582
2999
  clientForGdr,
2583
3000
  clock
2584
- );
3001
+ });
2585
3002
  }
2586
- async function autoActivatePrimedActivities(client, instanceId, definition, stage, actor, clientForGdr, clock) {
3003
+ async function autoActivatePrimedActivities({
3004
+ client,
3005
+ instanceId,
3006
+ definition,
3007
+ stage,
3008
+ actor,
3009
+ clientForGdr,
3010
+ clock
3011
+ }) {
2587
3012
  const resolvedClientForGdr = clientForGdr ?? (() => client);
2588
3013
  for (const activity of stage.activities ?? []) {
2589
3014
  if (activity.activation !== "auto") continue;
@@ -2596,7 +3021,7 @@ async function autoActivatePrimedActivities(client, instanceId, definition, stag
2596
3021
  definition,
2597
3022
  ...clock ? { clock } : {}
2598
3023
  }), mutation = startMutation(updated), mutEntry = currentActivities(mutation).find((t) => t.name === activity.name);
2599
- mutEntry && mutEntry.status === "pending" && (await activateActivity(updatedCtx, mutation, activity, mutEntry, actor), await persist(updatedCtx, mutation));
3024
+ mutEntry && mutEntry.status === "pending" && (await activateActivity({ ctx: updatedCtx, mutation, activity, entry: mutEntry, actor }), await persist(updatedCtx, mutation));
2600
3025
  }
2601
3026
  }
2602
3027
  const CASCADE_LIMIT = 100;
@@ -2605,20 +3030,30 @@ async function gatherAutoResolveCandidates(ctx, activities) {
2605
3030
  for (const activity of activities) {
2606
3031
  const entry = currentActivitiesList.find((e) => e.name === activity.name);
2607
3032
  if (entry?.status !== "active") continue;
2608
- const outcome = await evaluateResolutionGate(
3033
+ const outcome = await evaluateResolutionGate({
2609
3034
  ctx,
2610
3035
  activity,
2611
- await activityConditionVars(ctx, entry)
2612
- );
3036
+ vars: await activityConditionVars(ctx, entry)
3037
+ });
2613
3038
  outcome !== void 0 && candidates.push({ activity, outcome });
2614
3039
  }
2615
3040
  return candidates;
2616
3041
  }
2617
- async function resolveCompleteWhen(client, instanceId, clientForGdr, clock, overlay) {
2618
- const ctx = await loadContext(client, instanceId, {
2619
- ...clientForGdr ? { clientForGdr } : {},
2620
- ...clock ? { clock } : {},
2621
- ...overlay ? { overlay } : {}
3042
+ async function resolveCompleteWhen({
3043
+ client,
3044
+ instanceId,
3045
+ clientForGdr,
3046
+ clock,
3047
+ overlay
3048
+ }) {
3049
+ const ctx = await loadContext({
3050
+ client,
3051
+ instanceId,
3052
+ options: {
3053
+ ...clientForGdr ? { clientForGdr } : {},
3054
+ ...clock ? { clock } : {},
3055
+ ...overlay ? { overlay } : {}
3056
+ }
2622
3057
  }), stage = findStage(ctx.definition, ctx.instance.currentStage), gatedActivities = (stage.activities ?? []).filter(
2623
3058
  (t) => effectiveCompleteWhen(t) !== void 0 || t.failWhen !== void 0
2624
3059
  );
@@ -2629,21 +3064,27 @@ async function resolveCompleteWhen(client, instanceId, clientForGdr, clock, over
2629
3064
  let count = 0;
2630
3065
  for (const { activity, outcome } of candidates) {
2631
3066
  const mutEntry = currentActivities(mutation).find((t) => t.name === activity.name);
2632
- mutEntry === void 0 || mutEntry.status !== "active" || (applyActivityStatusChange({
3067
+ mutEntry === void 0 || mutEntry.status !== "active" || (applyGateOutcome({
2633
3068
  entry: mutEntry,
2634
3069
  history: mutation.history,
2635
3070
  stage: stage.name,
2636
- to: outcome,
2637
3071
  at: ctx.now,
2638
- actor: gateActor(outcome)
3072
+ outcome
2639
3073
  }), count++);
2640
3074
  }
2641
3075
  return count > 0 && await persist(ctx, mutation), count;
2642
3076
  }
2643
- async function cascadeAutoTransitions(client, instanceId, actor, clientForGdr, clock, overlay) {
3077
+ async function cascadeAutoTransitions({
3078
+ client,
3079
+ instanceId,
3080
+ actor,
3081
+ clientForGdr,
3082
+ clock,
3083
+ overlay
3084
+ }) {
2644
3085
  let count = 0;
2645
3086
  for (; ; ) {
2646
- if (await resolveCompleteWhen(client, instanceId, clientForGdr, clock, overlay), !(await evaluateAutoTransitions({
3087
+ if (await resolveCompleteWhen({ client, instanceId, clientForGdr, clock, overlay }), !(await evaluateAutoTransitions({
2647
3088
  client,
2648
3089
  instanceId,
2649
3090
  ...actor !== void 0 ? { options: { actor } } : {},
@@ -2655,14 +3096,19 @@ async function cascadeAutoTransitions(client, instanceId, actor, clientForGdr, c
2655
3096
  throw new CascadeLimitError({ instanceId, limit: CASCADE_LIMIT });
2656
3097
  }
2657
3098
  }
2658
- async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
3099
+ async function findResolvedParentSpawn({
3100
+ client,
3101
+ child,
3102
+ clientForGdr,
3103
+ clock
3104
+ }) {
2659
3105
  const parentRef = child.ancestors.at(-1);
2660
3106
  if (parentRef === void 0) return;
2661
- const parent = await client.getDocument(gdrToBareId(parentRef.id));
3107
+ const parent = await client.getDocument(toBareId(parentRef.id));
2662
3108
  if (!parent || parent._type !== WORKFLOW_INSTANCE_TYPE || typeof parent.definitionSnapshot != "string") return;
2663
3109
  const definition = parseDefinitionSnapshot(parent), stage = definition.stages.find((s) => s.name === parent.currentStage);
2664
3110
  if (stage === void 0) return;
2665
- const parentCurrentActivities = findCurrentActivities(parent), activity = (stage.activities ?? []).find((t) => t.subworkflows === void 0 ? !1 : parentCurrentActivities.find((e) => e.name === t.name)?.spawnedInstances?.some((r) => gdrToBareId(r.id) === child._id) ?? !1);
3111
+ const parentCurrentActivities = findCurrentActivities(parent), activity = (stage.activities ?? []).find((t) => t.subworkflows === void 0 ? !1 : parentCurrentActivities.find((e) => e.name === t.name)?.spawnedInstances?.some((r) => toBareId(r.id) === child._id) ?? !1);
2666
3112
  if (activity?.subworkflows === void 0) return;
2667
3113
  const entry = parentCurrentActivities.find((e) => e.name === activity.name);
2668
3114
  if (entry === void 0 || entry.status !== "active") return;
@@ -2672,18 +3118,29 @@ async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
2672
3118
  instance: parent,
2673
3119
  definition,
2674
3120
  ...clock ? { clock } : {}
2675
- }), outcome = await evaluateResolutionGate(
3121
+ }), outcome = await evaluateResolutionGate({
2676
3122
  ctx,
2677
3123
  activity,
2678
- await activityConditionVars(ctx, entry)
2679
- );
3124
+ vars: await activityConditionVars(ctx, entry)
3125
+ });
2680
3126
  if (outcome !== void 0)
2681
3127
  return { parent, definition, stage, activity, outcome };
2682
3128
  }
2683
- async function propagateToAncestors(client, instanceId, actor, clientForGdr, clock) {
3129
+ async function propagateToAncestors({
3130
+ client,
3131
+ instanceId,
3132
+ actor,
3133
+ clientForGdr,
3134
+ clock
3135
+ }) {
2684
3136
  const instance = await client.getDocument(instanceId);
2685
3137
  if (!instance) return;
2686
- const resolvedClientForGdr = clientForGdr ?? (() => client), found = await findResolvedParentSpawn(client, instance, resolvedClientForGdr, clock);
3138
+ const resolvedClientForGdr = clientForGdr ?? (() => client), found = await findResolvedParentSpawn({
3139
+ client,
3140
+ child: instance,
3141
+ clientForGdr: resolvedClientForGdr,
3142
+ clock
3143
+ });
2687
3144
  if (found === void 0) return;
2688
3145
  const { parent, definition, stage, activity, outcome } = found, ctx = await buildEngineContext({
2689
3146
  client,
@@ -2692,14 +3149,13 @@ async function propagateToAncestors(client, instanceId, actor, clientForGdr, clo
2692
3149
  definition,
2693
3150
  ...clock ? { clock } : {}
2694
3151
  }), mutation = startMutation(parent), mutEntry = currentActivities(mutation).find((e) => e.name === activity.name);
2695
- mutEntry !== void 0 && (applyActivityStatusChange({
3152
+ mutEntry !== void 0 && (applyGateOutcome({
2696
3153
  entry: mutEntry,
2697
3154
  history: mutation.history,
2698
3155
  stage: stage.name,
2699
- to: outcome,
2700
3156
  at: ctx.now,
2701
- actor: gateActor(outcome)
2702
- }), await persist(ctx, mutation), await cascadeAutoTransitions(client, parent._id, actor, clientForGdr, clock), await propagateToAncestors(client, parent._id, actor, clientForGdr, clock));
3157
+ outcome
3158
+ }), await persist(ctx, mutation), await cascadeAutoTransitions({ client, instanceId: parent._id, actor, clientForGdr, clock }), await propagateToAncestors({ client, instanceId: parent._id, actor, clientForGdr, clock }));
2703
3159
  }
2704
3160
  const parsedFilters = /* @__PURE__ */ new Map();
2705
3161
  async function matchesFilter(args) {
@@ -2740,7 +3196,12 @@ async function resolveAccess(client, args = {}) {
2740
3196
  );
2741
3197
  return { actor: overrideActor };
2742
3198
  }
2743
- const actorPromise = overrideActor !== void 0 ? Promise.resolve(overrideActor) : cachedActor(client, requestFn), grantsPromise = resolveGrants(overrideGrants, args.grantsFromPath, client, requestFn), [actor, grants] = await Promise.all([actorPromise, grantsPromise]);
3199
+ const actorPromise = overrideActor !== void 0 ? Promise.resolve(overrideActor) : cachedActor(client, requestFn), grantsPromise = resolveGrants({
3200
+ overrideGrants,
3201
+ grantsFromPath: args.grantsFromPath,
3202
+ client,
3203
+ requestFn
3204
+ }), [actor, grants] = await Promise.all([actorPromise, grantsPromise]);
2744
3205
  if (actor === void 0)
2745
3206
  throw new Error(
2746
3207
  "workflow: failed to resolve actor from `/users/me`. The client is configured but the endpoint returned no usable identity \u2014 check the token, or pass `access.actor` explicitly."
@@ -2755,10 +3216,19 @@ function cachedActor(client, requestFn) {
2755
3216
  });
2756
3217
  return actorCache.set(client, pending), pending;
2757
3218
  }
2758
- function resolveGrants(overrideGrants, grantsFromPath, client, requestFn) {
2759
- return overrideGrants !== void 0 ? Promise.resolve(overrideGrants) : grantsFromPath !== void 0 ? cachedGrants(client, requestFn, grantsFromPath) : Promise.resolve(void 0);
2760
- }
2761
- function cachedGrants(client, requestFn, resourcePath) {
3219
+ function resolveGrants({
3220
+ overrideGrants,
3221
+ grantsFromPath,
3222
+ client,
3223
+ requestFn
3224
+ }) {
3225
+ return overrideGrants !== void 0 ? Promise.resolve(overrideGrants) : grantsFromPath !== void 0 ? cachedGrants({ client, requestFn, resourcePath: grantsFromPath }) : Promise.resolve(void 0);
3226
+ }
3227
+ function cachedGrants({
3228
+ client,
3229
+ requestFn,
3230
+ resourcePath
3231
+ }) {
2762
3232
  let byPath = grantsCache.get(client);
2763
3233
  byPath === void 0 && (byPath = /* @__PURE__ */ new Map(), grantsCache.set(client, byPath));
2764
3234
  let cached = byPath.get(resourcePath);
@@ -2858,9 +3328,7 @@ function slotWindowOpen(instance, slot) {
2858
3328
  if (instance.completedAt !== void 0) return { open: !1, detail: "instance completed" };
2859
3329
  if (instance.abortedAt !== void 0) return { open: !1, detail: "instance aborted" };
2860
3330
  if (slot.scope !== "activity") return { open: !0 };
2861
- const status = findOpenStageEntry(instance)?.activities.find(
2862
- (t) => t.name === slot.activity
2863
- )?.status;
3331
+ const status = findCurrentActivityEntry(instance, slot.activity)?.status;
2864
3332
  return status === "active" ? { open: !0 } : { open: !1, detail: `activity "${slot.activity}" is ${status ?? "not active"}` };
2865
3333
  }
2866
3334
  function readSlotValue(instance, slot) {
@@ -2895,7 +3363,7 @@ async function evaluateInstance(args) {
2895
3363
  const { actor, grants } = await resolveAccess(client, {
2896
3364
  ...args.access !== void 0 ? { override: args.access } : {},
2897
3365
  ...args.grantsFromPath !== void 0 ? { grantsFromPath: args.grantsFromPath } : {}
2898
- }), instance = await reload(client, instanceId, tag), definition = parseDefinitionSnapshot(instance), snapshot = await hydrateSnapshot({ client, clientForGdr: resourceClients !== void 0 ? (parsed) => resourceClients(parsed) ?? client : () => client, instance }), guards = await verdictGuardsForInstance(client, instance._id);
3366
+ }), instance = await reload({ client, instanceId, tag }), definition = parseDefinitionSnapshot(instance), snapshot = await hydrateSnapshot({ client, clientForGdr: resourceClients !== void 0 ? (parsed) => resourceClients(parsed) ?? client : () => client, instance }), guards = await verdictGuardsForInstance(client, instance._id);
2899
3367
  return evaluateFromSnapshot({
2900
3368
  instance,
2901
3369
  definition,
@@ -2912,7 +3380,7 @@ async function evaluateFromSnapshot(args) {
2912
3380
  throw new Error(
2913
3381
  `Instance "${instance._id}" currentStage "${instance.currentStage}" not in definition`
2914
3382
  );
2915
- const scope = await renderScope({ instance, definition, actor, grants, snapshot, now }), currentActivityEntries = findOpenStageEntry(instance)?.activities ?? [], guardDenial = await instanceGuardReason(instance, actor, args.guards), activityEvaluations = [];
3383
+ const scope = await renderScope({ instance, definition, actor, grants, snapshot, now }), currentActivityEntries = findOpenStageEntry(instance)?.activities ?? [], guardDenial = await instanceGuardReason({ instance, actor, guards: args.guards }), activityEvaluations = [];
2916
3384
  for (const activity of stage.activities ?? [])
2917
3385
  activityEvaluations.push(
2918
3386
  await evaluateActivity({
@@ -3015,7 +3483,7 @@ async function renderScope(args) {
3015
3483
  ...buildParams({ instance, now, snapshot }),
3016
3484
  actor,
3017
3485
  assigned: !1,
3018
- can: await advisoryCan(instance, actor, grants)
3486
+ can: await advisoryCan({ instance, actor, grants })
3019
3487
  };
3020
3488
  return { ...await evaluatePredicates({
3021
3489
  predicates: definition.predicates,
@@ -3023,7 +3491,11 @@ async function renderScope(args) {
3023
3491
  params
3024
3492
  }), ...params };
3025
3493
  }
3026
- async function advisoryCan(instance, actor, grants) {
3494
+ async function advisoryCan({
3495
+ instance,
3496
+ actor,
3497
+ grants
3498
+ }) {
3027
3499
  if (grants === void 0) return;
3028
3500
  const can = {};
3029
3501
  for (const permission of DOCUMENT_VALUE_PERMISSIONS)
@@ -3041,9 +3513,9 @@ function activityScopeFor(args) {
3041
3513
  ...scope,
3042
3514
  fields: {
3043
3515
  ...scope.fields,
3044
- ...scopedFieldOverlay(instance, snapshot, activityName)
3516
+ ...scopedFieldOverlay({ instance, snapshot, activityName })
3045
3517
  },
3046
- assigned: assignedFor(instance, activityName, actor, roleAliases)
3518
+ assigned: assignedFor({ instance, activityName, actor, roleAliases })
3047
3519
  };
3048
3520
  }
3049
3521
  async function evaluateActivity(args) {
@@ -3105,14 +3577,18 @@ async function evaluateAction(args) {
3105
3577
  stageHasExits,
3106
3578
  guardDenial,
3107
3579
  requirementsReason
3108
- } = args, lifecycle = lifecycleReason(instance, status, stageHasExits);
3580
+ } = args, lifecycle = lifecycleReason({ instance, status, stageHasExits });
3109
3581
  return lifecycle !== void 0 ? disabled(action, lifecycle) : guardDenial !== void 0 ? disabled(action, guardDenial) : requirementsReason !== void 0 ? disabled(action, requirementsReason) : action.filter !== void 0 && !await evaluateCondition({
3110
3582
  condition: action.filter,
3111
3583
  snapshot,
3112
3584
  params: activityScope
3113
3585
  }) ? disabled(action, { kind: "filter-failed", filter: action.filter }) : { action, allowed: !0 };
3114
3586
  }
3115
- async function instanceGuardReason(instance, actor, guards) {
3587
+ async function instanceGuardReason({
3588
+ instance,
3589
+ actor,
3590
+ guards
3591
+ }) {
3116
3592
  if (guards === void 0 || guards.length === 0) return;
3117
3593
  const denied = await instanceWriteDenials({ instance, guards, identity: actor.id });
3118
3594
  if (denied.length !== 0)
@@ -3122,7 +3598,11 @@ async function instanceGuardReason(instance, actor, guards) {
3122
3598
  detail: denied.map((g) => g.name ?? g._id).join(", ")
3123
3599
  };
3124
3600
  }
3125
- function lifecycleReason(instance, status, stageHasExits) {
3601
+ function lifecycleReason({
3602
+ instance,
3603
+ status,
3604
+ stageHasExits
3605
+ }) {
3126
3606
  if (instance.completedAt !== void 0)
3127
3607
  return { kind: "instance-completed", completedAt: instance.completedAt };
3128
3608
  if (!stageHasExits)
@@ -3133,10 +3613,11 @@ function lifecycleReason(instance, status, stageHasExits) {
3133
3613
  function disabled(action, reason) {
3134
3614
  return { action, allowed: !1, disabledReason: reason };
3135
3615
  }
3136
- function definitionDocId(_workflowResource, tag, definition, version) {
3137
- return `${tag}.${definition}.v${version}`;
3138
- }
3139
- async function sortByDependencies(client, definitions, tag) {
3616
+ async function sortByDependencies({
3617
+ client,
3618
+ definitions,
3619
+ tag
3620
+ }) {
3140
3621
  const byName = /* @__PURE__ */ new Map();
3141
3622
  for (const def of definitions) {
3142
3623
  if (byName.has(def.name))
@@ -3146,18 +3627,22 @@ async function sortByDependencies(client, definitions, tag) {
3146
3627
  byName.set(def.name, def);
3147
3628
  }
3148
3629
  const findInBatch = (ref) => findRefInBatch(byName, ref);
3149
- return await assertCrossBatchRefsDeployed(client, definitions, { tag, findInBatch }), topoSortDefinitions(definitions, { findInBatch });
3630
+ return await assertCrossBatchRefsDeployed({ client, definitions, ctx: { tag, findInBatch } }), topoSortDefinitions(definitions, { findInBatch });
3150
3631
  }
3151
3632
  function findRefInBatch(byName, ref) {
3152
3633
  if (typeof ref.version != "number")
3153
3634
  return byName.get(ref.name);
3154
3635
  }
3155
- async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
3636
+ async function assertCrossBatchRefsDeployed({
3637
+ client,
3638
+ definitions,
3639
+ ctx
3640
+ }) {
3156
3641
  const missing = [];
3157
3642
  for (const def of definitions)
3158
3643
  for (const ref of refsOf(def)) {
3159
3644
  if (ctx.findInBatch(ref) !== void 0) continue;
3160
- const label = await resolveDeployedRefLabel(client, ref, ctx.tag);
3645
+ const label = await resolveDeployedRefLabel({ client, ref, tag: ctx.tag });
3161
3646
  label !== void 0 && missing.push({ from: def.name, ref: label });
3162
3647
  }
3163
3648
  if (missing.length === 0) return;
@@ -3168,7 +3653,11 @@ async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
3168
3653
  `)
3169
3654
  );
3170
3655
  }
3171
- async function resolveDeployedRefLabel(client, ref, tag) {
3656
+ async function resolveDeployedRefLabel({
3657
+ client,
3658
+ ref,
3659
+ tag
3660
+ }) {
3172
3661
  const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
3173
3662
  if (wantsExplicit && (params.version = ref.version), !await client.fetch(
3174
3663
  definitionLookupGroq(wantsExplicit),
@@ -3211,9 +3700,17 @@ function hashDefinitionContent(def) {
3211
3700
  hash ^= BigInt(canonical.charCodeAt(i)), hash = hash * 0x100000001b3n & 0xffffffffffffffffn;
3212
3701
  return hash.toString(16).padStart(16, "0");
3213
3702
  }
3214
- function planDefinitionDeploy(def, latest, target) {
3215
- const contentHash = hashDefinitionContent(def), unchanged = latest !== void 0 && latest.contentHash === contentHash, version = unchanged ? latest.version : (latest?.version ?? 0) + 1, docId = definitionDocId(target.workflowResource, target.tag, def.name, version), document = {
3216
- ...def,
3703
+ function planDefinitionDeploy({
3704
+ def,
3705
+ latest,
3706
+ target
3707
+ }) {
3708
+ const expanded = expandResourceAliases(def, target.resourceAliases), contentHash = hashDefinitionContent(expanded), unchanged = latest !== void 0 && latest.contentHash === contentHash, version = unchanged ? latest.version : (latest?.version ?? 0) + 1, docId = definitionDocId({
3709
+ tag: target.tag,
3710
+ definition: expanded.name,
3711
+ version
3712
+ }), document = {
3713
+ ...expanded,
3217
3714
  _id: docId,
3218
3715
  _type: WORKFLOW_DEFINITION_TYPE,
3219
3716
  tag: target.tag,
@@ -3222,6 +3719,38 @@ function planDefinitionDeploy(def, latest, target) {
3222
3719
  };
3223
3720
  return { status: unchanged ? "unchanged" : "create", version, contentHash, docId, document };
3224
3721
  }
3722
+ const RESOURCE_ALIAS_REF_RE = new RegExp(`@(${RESOURCE_ALIAS_NAME_SOURCE}):`, "g"), STANDALONE_RESOURCE_ALIAS_RE = new RegExp(`^@${RESOURCE_ALIAS_NAME_SOURCE}:`), PROSE_KEYS = /* @__PURE__ */ new Set(["title", "description"]);
3723
+ function expandResourceAliases(definition, resourceAliases) {
3724
+ assertResourceAliasNamesValid(resourceAliases);
3725
+ const map = resourceAliases ?? {};
3726
+ return mapJsonStrings(
3727
+ definition,
3728
+ (value, key) => key !== void 0 && PROSE_KEYS.has(key) ? value : expandAliasString({ value, map, definitionName: definition.name })
3729
+ );
3730
+ }
3731
+ function expandAliasString({
3732
+ value,
3733
+ map,
3734
+ definitionName
3735
+ }) {
3736
+ const expanded = value.replace(RESOURCE_ALIAS_REF_RE, (_match, alias) => {
3737
+ if (!Object.hasOwn(map, alias))
3738
+ throw new Error(
3739
+ `workflow.deployDefinitions: definition "${definitionName}" references unbound resource alias "@${alias}". Bind it in the deploy target's resource map, or remove the reference.`
3740
+ );
3741
+ return gdrResourcePrefix(map[alias]);
3742
+ });
3743
+ if (expanded === value) return value;
3744
+ if (STANDALONE_RESOURCE_ALIAS_RE.test(value) && !isGdrUri(expanded))
3745
+ throw new Error(
3746
+ `workflow.deployDefinitions: definition "${definitionName}" reference "${value}" expands to the malformed GDR "${expanded}". A resource alias reference must be "@<alias>:<documentId>" with a single document id.`
3747
+ );
3748
+ return expanded;
3749
+ }
3750
+ function assertResourceAliasNamesValid(resourceAliases) {
3751
+ for (const name of Object.keys(resourceAliases ?? {}))
3752
+ validateResourceAliasName(name);
3753
+ }
3225
3754
  function stripSystemFields(doc) {
3226
3755
  const out = {};
3227
3756
  for (const [k, v2] of Object.entries(doc))
@@ -3233,7 +3762,13 @@ function stableStringify(value) {
3233
3762
  ([a], [b]) => a.localeCompare(b)
3234
3763
  ).map(([k, v2]) => `${JSON.stringify(k)}:${stableStringify(v2)}`).join(",")}}`;
3235
3764
  }
3236
- async function loadDefinition(client, definition, version, tag) {
3765
+ const noDeployedDefinitionMessage = (definition) => `No deployed definition for workflow ${definition}`;
3766
+ async function loadDefinition({
3767
+ client,
3768
+ definition,
3769
+ version,
3770
+ tag
3771
+ }) {
3237
3772
  if (version !== void 0) {
3238
3773
  const doc = await client.fetch(definitionLookupGroq(!0), {
3239
3774
  definition,
@@ -3244,31 +3779,50 @@ async function loadDefinition(client, definition, version, tag) {
3244
3779
  throw new Error(`Workflow definition ${definition} v${version} not deployed`);
3245
3780
  return doc;
3246
3781
  }
3247
- const latest = await loadLatestDeployed(client, definition, tag);
3782
+ const latest = await loadLatestDeployed({ client, definition, tag });
3248
3783
  if (!latest)
3249
- throw new Error(`No deployed definition for workflow ${definition}`);
3784
+ throw new Error(noDeployedDefinitionMessage(definition));
3250
3785
  return latest;
3251
3786
  }
3252
- async function loadLatestDeployed(client, definition, tag) {
3787
+ async function loadLatestDeployed({
3788
+ client,
3789
+ definition,
3790
+ tag
3791
+ }) {
3253
3792
  return await client.fetch(definitionLookupGroq(!1), {
3254
3793
  definition,
3255
3794
  tag
3256
3795
  }) ?? void 0;
3257
3796
  }
3258
- async function loadDefinitionVersions(client, definition, tag) {
3797
+ async function loadDefinitionVersions({
3798
+ client,
3799
+ definition,
3800
+ tag
3801
+ }) {
3259
3802
  return client.fetch(
3260
3803
  `*[_type == "${WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}] | order(version desc)`,
3261
3804
  { definition, tag }
3262
3805
  );
3263
3806
  }
3264
- async function abortInstance(args) {
3265
- const { client, instanceId, reason, options } = args, ctx = await loadContext(client, instanceId, {
3266
- ...options?.clock ? { clock: options.clock } : {},
3267
- ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
3268
- });
3269
- return commitAbort(ctx, reason, options?.actor);
3807
+ async function loadDefinitionVersionsOrThrow({
3808
+ client,
3809
+ definition,
3810
+ tag
3811
+ }) {
3812
+ const versions = await loadDefinitionVersions({ client, definition, tag });
3813
+ if (versions.length === 0)
3814
+ throw new Error(noDeployedDefinitionMessage(definition));
3815
+ return versions;
3270
3816
  }
3271
- async function commitAbort(ctx, reason, actor) {
3817
+ async function abortInstance(args) {
3818
+ const { client, instanceId, reason, options } = args, ctx = await loadCallContext({ client, instanceId, options });
3819
+ return commitAbort({ ctx, reason, actor: options?.actor });
3820
+ }
3821
+ async function commitAbort({
3822
+ ctx,
3823
+ reason,
3824
+ actor
3825
+ }) {
3272
3826
  if (ctx.instance.completedAt !== void 0)
3273
3827
  return { fired: !1 };
3274
3828
  const stage = findStage(ctx.definition, ctx.instance.currentStage), mutation = startMutation(ctx.instance), at = ctx.now, openEntry = findOpenStageEntry(mutation);
@@ -3302,30 +3856,44 @@ async function commitAbort(ctx, reason, actor) {
3302
3856
  }
3303
3857
  async function fireAction(args) {
3304
3858
  const { client, instanceId, activity, action, params, options } = args;
3305
- for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
3306
- const ctx = await loadCallContext(client, instanceId, options);
3307
- try {
3308
- return await commitAction(ctx, activity, action, params, options);
3309
- } catch (error) {
3310
- if (!isRevisionConflict(error)) throw error;
3311
- }
3312
- }
3313
- throw new ConcurrentFireActionError({
3859
+ return retryOnRevisionConflict({
3860
+ client,
3314
3861
  instanceId,
3315
- activity,
3316
- action,
3317
- attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
3862
+ options,
3863
+ commit: (ctx) => commitAction({
3864
+ ctx,
3865
+ activityName: activity,
3866
+ actionName: action,
3867
+ callerParams: params,
3868
+ options
3869
+ }),
3870
+ onExhausted: () => new ConcurrentFireActionError({
3871
+ instanceId,
3872
+ activity,
3873
+ action,
3874
+ attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
3875
+ })
3318
3876
  });
3319
3877
  }
3320
- async function resolveActionCommit(ctx, activityName, actionName, callerParams, options) {
3878
+ async function resolveActionCommit({
3879
+ ctx,
3880
+ activityName,
3881
+ actionName,
3882
+ callerParams,
3883
+ options
3884
+ }) {
3321
3885
  const actor = options?.actor, { stage, activity } = findActivityInCurrentStage(ctx, activityName), action = (activity.actions ?? []).find((a) => a.name === actionName);
3322
3886
  if (action === void 0)
3323
3887
  throw new Error(`Action "${actionName}" not declared on activity "${activityName}"`);
3324
- const can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan(ctx.instance, actor, options.grants) : void 0;
3325
- if (!await ctxEvaluateCondition(ctx, action.filter, {
3326
- activityName,
3327
- ...actor !== void 0 ? { actor } : {},
3328
- ...can !== void 0 ? { vars: { can } } : {}
3888
+ const can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan({ instance: ctx.instance, actor, grants: options.grants }) : void 0;
3889
+ if (!await ctxEvaluateCondition({
3890
+ ctx,
3891
+ condition: action.filter,
3892
+ opts: {
3893
+ activityName,
3894
+ ...actor !== void 0 ? { actor } : {},
3895
+ ...can !== void 0 ? { vars: { can } } : {}
3896
+ }
3329
3897
  }))
3330
3898
  throw new Error(`Action "${actionName}" filter rejected on activity "${activityName}"`);
3331
3899
  const entry = findCurrentActivities(ctx.instance).find((t) => t.name === activityName);
@@ -3333,17 +3901,23 @@ async function resolveActionCommit(ctx, activityName, actionName, callerParams,
3333
3901
  throw new Error(
3334
3902
  `Activity "${activityName}" must be active to fire action "${actionName}"; status is ${entry?.status ?? "missing"}`
3335
3903
  );
3336
- const params = validateActionParams(action, activityName, callerParams);
3904
+ const params = validateActionParams({ action, activityName, callerParams });
3337
3905
  return { stage, activity, action, params };
3338
3906
  }
3339
- async function commitAction(ctx, activityName, actionName, callerParams, options) {
3340
- const actor = options?.actor, { stage, action, params } = await resolveActionCommit(
3907
+ async function commitAction({
3908
+ ctx,
3909
+ activityName,
3910
+ actionName,
3911
+ callerParams,
3912
+ options
3913
+ }) {
3914
+ const actor = options?.actor, { stage, action, params } = await resolveActionCommit({
3341
3915
  ctx,
3342
3916
  activityName,
3343
3917
  actionName,
3344
3918
  callerParams,
3345
3919
  options
3346
- ), mutation = startMutation(ctx.instance), mutEntry = requireMutationActivityEntry(mutation, activityName), now = ctx.now;
3920
+ }), mutation = startMutation(ctx.instance), mutEntry = requireMutationActivityEntry(mutation, activityName), now = ctx.now;
3347
3921
  mutation.history.push({
3348
3922
  _key: randomKey(),
3349
3923
  _type: "actionFired",
@@ -3363,14 +3937,19 @@ async function commitAction(ctx, activityName, actionName, callerParams, options
3363
3937
  self: selfGdr(ctx.instance),
3364
3938
  now
3365
3939
  }), newStatus = mutEntry.status !== statusBefore ? mutEntry.status : void 0;
3366
- return await queueEffects(ctx, mutation, action.effects, { kind: "action", name: actionName }, actor, {
3367
- callerParams: params,
3368
- activityName
3369
- }), await persistThenDeploy(
3940
+ return await queueEffects({
3941
+ ctx,
3942
+ mutation,
3943
+ effects: action.effects,
3944
+ origin: { kind: "action", name: actionName },
3945
+ actor,
3946
+ opts: { callerParams: params, activityName }
3947
+ }), await persistThenMaybeRefresh({
3370
3948
  ctx,
3371
3949
  mutation,
3372
- () => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
3373
- ), {
3950
+ stageName: stage.name,
3951
+ didChangeState: ranOps.some(isFieldOp)
3952
+ }), {
3374
3953
  fired: !0,
3375
3954
  activity: activityName,
3376
3955
  action: actionName,
@@ -3383,7 +3962,7 @@ class ActionDisabledError extends Error {
3383
3962
  activity;
3384
3963
  action;
3385
3964
  constructor(args) {
3386
- super(formatDisabledReason(args.activity, args.action, args.reason)), this.name = "ActionDisabledError", this.reason = args.reason, this.activity = args.activity, this.action = args.action;
3965
+ super(formatDisabledReason({ activity: args.activity, action: args.action, reason: args.reason })), this.name = "ActionDisabledError", this.reason = args.reason, this.activity = args.activity, this.action = args.action;
3387
3966
  }
3388
3967
  }
3389
3968
  const disabledReasonDetail = {
@@ -3394,7 +3973,11 @@ const disabledReasonDetail = {
3394
3973
  "mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`,
3395
3974
  "requirements-unmet": (r) => `unmet requirement(s): ${r.unmetRequirements.join(", ")}`
3396
3975
  };
3397
- function formatDisabledReason(activity, action, reason) {
3976
+ function formatDisabledReason({
3977
+ activity,
3978
+ action,
3979
+ reason
3980
+ }) {
3398
3981
  const detail = disabledReasonDetail[reason.kind](reason);
3399
3982
  return `Action "${activity}:${action}" is not allowed: ${detail}`;
3400
3983
  }
@@ -3420,28 +4003,32 @@ function formatEditDisabledReason(target, reason) {
3420
4003
  }
3421
4004
  async function editField(args) {
3422
4005
  const { client, instanceId, target, mode = "set", value, options } = args;
3423
- for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
3424
- const ctx = await loadCallContext(client, instanceId, options);
3425
- try {
3426
- return await commitEdit(ctx, target, mode, value, options);
3427
- } catch (error) {
3428
- if (!isRevisionConflict(error)) throw error;
3429
- }
3430
- }
3431
- throw new ConcurrentEditFieldError({
4006
+ return retryOnRevisionConflict({
4007
+ client,
3432
4008
  instanceId,
3433
- target: labelTarget(target),
3434
- attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
4009
+ options,
4010
+ commit: (ctx) => commitEdit({ ctx, target, mode, value, options }),
4011
+ onExhausted: () => new ConcurrentEditFieldError({
4012
+ instanceId,
4013
+ target: labelTarget(target),
4014
+ attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
4015
+ })
3435
4016
  });
3436
4017
  }
3437
- async function commitEdit(ctx, target, mode, value, options) {
4018
+ async function commitEdit({
4019
+ ctx,
4020
+ target,
4021
+ mode,
4022
+ value,
4023
+ options
4024
+ }) {
3438
4025
  const actor = options?.actor, stage = findStage(ctx.definition, ctx.instance.currentStage), slot = resolveEditTarget({ definition: ctx.definition, stage, target });
3439
4026
  if (slot === void 0)
3440
4027
  throw new Error(
3441
4028
  `No field slot "${describeTarget(target)}" resolves in current stage "${stage.name}" of ${ctx.definition.name}`
3442
4029
  );
3443
- await assertSlotEditable(ctx, slot, options);
3444
- const op = buildEditOp(slot, mode, value), mutation = startMutation(ctx.instance), ranOps = runOps({
4030
+ await assertSlotEditable({ ctx, slot, options });
4031
+ const op = buildEditOp({ slot, mode, value }), mutation = startMutation(ctx.instance), ranOps = runOps({
3445
4032
  ops: [op],
3446
4033
  mutation,
3447
4034
  stage: stage.name,
@@ -3454,21 +4041,30 @@ async function commitEdit(ctx, target, mode, value, options) {
3454
4041
  self: selfGdr(ctx.instance),
3455
4042
  now: ctx.now
3456
4043
  });
3457
- return await persistThenDeploy(
4044
+ return await persistThenMaybeRefresh({
3458
4045
  ctx,
3459
4046
  mutation,
3460
- () => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
3461
- ), {
4047
+ stageName: stage.name,
4048
+ didChangeState: ranOps.some(isFieldOp)
4049
+ }), {
3462
4050
  edited: !0,
3463
4051
  target: slotTarget(slot),
3464
4052
  ...ranOps.length > 0 ? { ranOps } : {}
3465
4053
  };
3466
4054
  }
3467
- async function assertSlotEditable(ctx, slot, options) {
3468
- const actor = options?.actor, window = slotWindowOpen(ctx.instance, slot), can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan(ctx.instance, actor, options.grants) : void 0, predicateSatisfied = window.open && slot.effective !== !0 && slot.effective !== void 0 ? await ctxEvaluateCondition(ctx, slot.effective, {
3469
- ...slot.activity !== void 0 ? { activityName: slot.activity } : {},
3470
- ...actor !== void 0 ? { actor } : {},
3471
- ...can !== void 0 ? { vars: { can } } : {}
4055
+ async function assertSlotEditable({
4056
+ ctx,
4057
+ slot,
4058
+ options
4059
+ }) {
4060
+ const actor = options?.actor, window = slotWindowOpen(ctx.instance, slot), can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan({ instance: ctx.instance, actor, grants: options.grants }) : void 0, predicateSatisfied = window.open && slot.effective !== !0 && slot.effective !== void 0 ? await ctxEvaluateCondition({
4061
+ ctx,
4062
+ condition: slot.effective,
4063
+ opts: {
4064
+ ...slot.activity !== void 0 ? { activityName: slot.activity } : {},
4065
+ ...actor !== void 0 ? { actor } : {},
4066
+ ...can !== void 0 ? { vars: { can } } : {}
4067
+ }
3472
4068
  }) : slot.effective === !0, reason = editDisabledReason({
3473
4069
  effective: slot.effective,
3474
4070
  instance: ctx.instance,
@@ -3478,7 +4074,11 @@ async function assertSlotEditable(ctx, slot, options) {
3478
4074
  });
3479
4075
  if (reason !== void 0) throw new EditFieldDeniedError({ target: slotTarget(slot), reason });
3480
4076
  }
3481
- function buildEditOp(slot, mode, value) {
4077
+ function buildEditOp({
4078
+ slot,
4079
+ mode,
4080
+ value
4081
+ }) {
3482
4082
  if (mode === "unset") return { type: "field.unset", target: slot.ref };
3483
4083
  if (value === void 0)
3484
4084
  throw new Error(`editField mode "${mode}" requires a value for slot "${slot.name}"`);
@@ -3526,25 +4126,45 @@ async function resolveOperationContext(args) {
3526
4126
  clientForGdr: buildClientForGdr(args.client, args.resourceClients)
3527
4127
  };
3528
4128
  }
3529
- async function cascade(client, instanceId, actor, clientForGdr, clock, overlay) {
3530
- const count = await cascadeAutoTransitions(
4129
+ async function cascade({
4130
+ client,
4131
+ instanceId,
4132
+ actor,
4133
+ clientForGdr,
4134
+ clock,
4135
+ overlay
4136
+ }) {
4137
+ const count = await cascadeAutoTransitions({
3531
4138
  client,
3532
4139
  instanceId,
3533
4140
  actor,
3534
- clientForGdr,
3535
- clock,
3536
- overlay
3537
- );
3538
- return await propagateToAncestors(client, instanceId, actor, clientForGdr, clock), count;
4141
+ ...clientForGdr !== void 0 ? { clientForGdr } : {},
4142
+ ...clock !== void 0 ? { clock } : {},
4143
+ ...overlay !== void 0 ? { overlay } : {}
4144
+ });
4145
+ return await propagateToAncestors({
4146
+ client,
4147
+ instanceId,
4148
+ actor,
4149
+ ...clientForGdr !== void 0 ? { clientForGdr } : {},
4150
+ ...clock !== void 0 ? { clock } : {}
4151
+ }), count;
4152
+ }
4153
+ function engineOptionsForActor({
4154
+ actor,
4155
+ clock,
4156
+ clientForGdr
4157
+ }) {
4158
+ return { ...actor !== void 0 ? { actor } : {}, clock, clientForGdr };
3539
4159
  }
3540
4160
  async function abortAndPropagate(args) {
3541
4161
  const { client, instanceId, reason, actor, clientForGdr, clock } = args, result = await abortInstance({
3542
4162
  client,
3543
4163
  instanceId,
3544
4164
  ...reason !== void 0 ? { reason } : {},
3545
- options: { ...actor !== void 0 ? { actor } : {}, clock, clientForGdr }
4165
+ options: engineOptionsForActor({ actor, clock, clientForGdr })
3546
4166
  });
3547
- return result.fired && await propagateToAncestors(client, instanceId, actor, clientForGdr, clock), result.fired;
4167
+ return result.fired && await propagateToAncestors({ client, instanceId, actor, clientForGdr, clock }), result.fired;
3548
4168
  }
3549
4169
  function buildClientForGdr(defaultClient, resolver) {
3550
4170
  return resolver === void 0 ? () => defaultClient : (parsed) => resolver(parsed) ?? defaultClient;
@@ -3560,15 +4180,25 @@ async function dispatchGatedWrite(args) {
3560
4180
  access,
3561
4181
  clock: args.clock,
3562
4182
  ...resourceClients !== void 0 ? { resourceClients } : {}
3563
- }), ranOps = await apply(args.before, evaluation), cascaded = await cascade(client, instanceId, args.actor, args.clientForGdr, args.clock);
4183
+ }), ranOps = await apply(args.before, evaluation), cascaded = await cascade({
4184
+ client,
4185
+ instanceId,
4186
+ actor: args.actor,
4187
+ clientForGdr: args.clientForGdr,
4188
+ clock: args.clock
4189
+ });
3564
4190
  return {
3565
- instance: await reload(client, instanceId, tag),
4191
+ instance: await reload({ client, instanceId, tag }),
3566
4192
  cascaded,
3567
4193
  fired: !0,
3568
4194
  ...ranOps !== void 0 ? { ranOps } : {}
3569
4195
  };
3570
4196
  }
3571
- function assertActionAllowed(evaluation, activity, action) {
4197
+ function assertActionAllowed({
4198
+ evaluation,
4199
+ activity,
4200
+ action
4201
+ }) {
3572
4202
  const actionEval = evaluation.currentStage.activities.find((t) => t.activity.name === activity)?.actions.find((a) => a.action.name === action);
3573
4203
  if (actionEval?.allowed === !1 && actionEval.disabledReason)
3574
4204
  throw new ActionDisabledError({ activity, action, reason: actionEval.disabledReason });
@@ -3606,7 +4236,7 @@ function buildEngineCallOptions(args) {
3606
4236
  }
3607
4237
  async function applyAction(args) {
3608
4238
  const { client, instance, activity, action, params, actor, grants, clock, clientForGdr } = args, options = buildEngineCallOptions({ actor, grants, clock, clientForGdr });
3609
- return findOpenStageEntry(instance)?.activities.find((t) => t.name === activity)?.status === "pending" && await invokeActivity({ client, instanceId: instance._id, activity, options }), (await fireAction({
4239
+ return findCurrentActivityEntry(instance, activity)?.status === "pending" && await invokeActivity({ client, instanceId: instance._id, activity, options }), (await fireAction({
3610
4240
  client,
3611
4241
  instanceId: instance._id,
3612
4242
  activity,
@@ -3616,15 +4246,12 @@ async function applyAction(args) {
3616
4246
  })).ranOps;
3617
4247
  }
3618
4248
  async function deleteDefinition(args) {
3619
- const { client, tag, definition, version, cascade: cascade2, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, versions = await loadDefinitionVersions(client, definition, tag);
3620
- if (versions.length === 0)
3621
- throw new Error(`No deployed definition for workflow ${definition}`);
3622
- const targets = version === void 0 ? versions : versions.filter((d) => d.version === version);
4249
+ const { client, tag, definition, version, cascade: cascade2, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, versions = await loadDefinitionVersionsOrThrow({ client, definition, tag }), targets = version === void 0 ? versions : versions.filter((d) => d.version === version);
3623
4250
  if (targets.length === 0)
3624
4251
  throw new Error(`Workflow definition ${definition} v${version} not deployed`);
3625
4252
  const lastVersionGoes = targets.length === versions.length;
3626
- await assertNoSpawnReferrers(client, tag, definition, targets, lastVersionGoes);
3627
- const instanceIds = await nonTerminalInstanceIds(client, tag, definition, version);
4253
+ await assertNoSpawnReferrers({ client, tag, definition, targets, lastVersionGoes });
4254
+ const instanceIds = await nonTerminalInstanceIds({ client, tag, definition, version });
3628
4255
  if (instanceIds.length > 0 && cascade2 !== !0)
3629
4256
  throw new Error(
3630
4257
  `Cannot delete ${definition}: ${instanceIds.length} non-terminal instance(s) exist (${previewIds(instanceIds)}). Pass cascade to abort them first \u2014 instances are aborted in place, never deleted.`
@@ -3654,7 +4281,13 @@ async function deleteDefinition(args) {
3654
4281
  deletedGuardCount
3655
4282
  };
3656
4283
  }
3657
- async function assertNoSpawnReferrers(client, tag, definition, targets, lastVersionGoes) {
4284
+ async function assertNoSpawnReferrers({
4285
+ client,
4286
+ tag,
4287
+ definition,
4288
+ targets,
4289
+ lastVersionGoes
4290
+ }) {
3658
4291
  const deployed = await client.fetch(
3659
4292
  `*[_type == "${WORKFLOW_DEFINITION_TYPE}" && ${tagScopeFilter()}]`,
3660
4293
  { tag }
@@ -3670,7 +4303,12 @@ async function assertNoSpawnReferrers(client, tag, definition, targets, lastVers
3670
4303
  );
3671
4304
  }
3672
4305
  }
3673
- async function nonTerminalInstanceIds(client, tag, definition, version) {
4306
+ async function nonTerminalInstanceIds({
4307
+ client,
4308
+ tag,
4309
+ definition,
4310
+ version
4311
+ }) {
3674
4312
  const versionFilter = version === void 0 ? "" : " && pinnedVersion == $version";
3675
4313
  return (await client.fetch(
3676
4314
  `*[_type == "${WORKFLOW_INSTANCE_TYPE}" && definition == $definition && ${tagScopeFilter()} && !defined(completedAt)${versionFilter}] | order(_id asc){_id}`,
@@ -3707,14 +4345,19 @@ const workflow = {
3707
4345
  * Cycles in the dependency graph error before any write happens.
3708
4346
  */
3709
4347
  deployDefinitions: async (args) => {
3710
- const { client, tag, workflowResource, definitions } = args;
3711
- validateTag(tag);
3712
- for (const def of definitions)
3713
- validateDefinition(def);
3714
- const ordered = await sortByDependencies(client, definitions, tag), results = [], tx = client.transaction();
4348
+ const { client, tag, resourceAliases, definitions } = args;
4349
+ validateTag(tag), definitions.forEach(validateDefinition);
4350
+ const ordered = await sortByDependencies({ client, definitions, tag }), results = [], tx = client.transaction();
3715
4351
  let hasWrites = !1;
3716
4352
  for (const def of ordered) {
3717
- const latest = await loadLatestDeployed(client, def.name, tag), plan = planDefinitionDeploy(def, latest, { tag, workflowResource });
4353
+ const latest = await loadLatestDeployed({ client, definition: def.name, tag }), plan = planDefinitionDeploy({
4354
+ def,
4355
+ latest,
4356
+ target: {
4357
+ tag,
4358
+ ...resourceAliases !== void 0 ? { resourceAliases } : {}
4359
+ }
4360
+ });
3718
4361
  if (plan.status === "unchanged") {
3719
4362
  results.push({ definition: def.name, version: plan.version, status: "unchanged" });
3720
4363
  continue;
@@ -3751,7 +4394,7 @@ const workflow = {
3751
4394
  effectsContext,
3752
4395
  instanceId,
3753
4396
  perspective
3754
- } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, definition = await loadDefinition(client, definitionName, version, tag), id = instanceId ?? `${tag}.wf-instance.${randomKey()}`;
4397
+ } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, definition = await loadDefinition({ client, definition: definitionName, version, tag }), id = instanceId ?? instanceDocId(tag);
3755
4398
  if (definition.stages.find((s) => s.name === definition.initialStage) === void 0)
3756
4399
  throw new Error(
3757
4400
  `Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
@@ -3767,7 +4410,7 @@ const workflow = {
3767
4410
  workflowResource,
3768
4411
  definitionName: definition.name,
3769
4412
  ...perspective !== void 0 ? { perspective } : {},
3770
- recordDiscard: recordFieldDiscards(fieldDiscards, "workflow", now)
4413
+ recordDiscard: recordFieldDiscards({ target: fieldDiscards, scope: "workflow", at: now })
3771
4414
  },
3772
4415
  randomKey
3773
4416
  }), effectivePerspective = perspective ?? derivePerspectiveFromFields(resolvedFields), base = buildInstanceBase({
@@ -3787,7 +4430,7 @@ const workflow = {
3787
4430
  actor,
3788
4431
  ...fieldDiscards.length > 0 ? { extraHistory: fieldDiscards } : {}
3789
4432
  });
3790
- return await client.create(base, SYNC_COMMIT), await primeInitialStage(client, id, actor, clientForGdr, clock), await cascade(client, id, actor, clientForGdr, clock), reload(client, id, tag);
4433
+ return await client.create(base, SYNC_COMMIT), await primeInitialStage({ client, instanceId: id, actor, clientForGdr, clock }), await cascade({ client, instanceId: id, actor, clientForGdr, clock }), reload({ client, instanceId: id, tag });
3791
4434
  },
3792
4435
  /**
3793
4436
  * Fire an action against an activity. If the activity is pending, it is
@@ -3809,8 +4452,8 @@ const workflow = {
3809
4452
  params,
3810
4453
  idempotent,
3811
4454
  resourceClients
3812
- } = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
3813
- return findOpenStageEntry(before)?.activities.find((t) => t.name === activity) === void 0 && idempotent === !0 ? { instance: before, cascaded: 0, fired: !1 } : dispatchGatedWrite({
4455
+ } = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload({ client, instanceId, tag });
4456
+ return findCurrentActivityEntry(before, activity) === void 0 && idempotent === !0 ? { instance: before, cascaded: 0, fired: !1 } : dispatchGatedWrite({
3814
4457
  client,
3815
4458
  tag,
3816
4459
  instanceId,
@@ -3820,7 +4463,7 @@ const workflow = {
3820
4463
  clock,
3821
4464
  before,
3822
4465
  ...resourceClients !== void 0 ? { resourceClients } : {},
3823
- apply: (instance, evaluation) => (assertActionAllowed(evaluation, activity, action), applyAction({
4466
+ apply: (instance, evaluation) => (assertActionAllowed({ evaluation, activity, action }), applyAction({
3824
4467
  client,
3825
4468
  instance,
3826
4469
  activity,
@@ -3849,7 +4492,7 @@ const workflow = {
3849
4492
  * never an `onChange` per keystroke.
3850
4493
  */
3851
4494
  editField: async (args) => {
3852
- const { client, tag, instanceId, target, mode, value, resourceClients } = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
4495
+ const { client, tag, instanceId, target, mode, value, resourceClients } = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload({ client, instanceId, tag });
3853
4496
  return dispatchGatedWrite({
3854
4497
  client,
3855
4498
  tag,
@@ -3878,24 +4521,27 @@ const workflow = {
3878
4521
  * appends an `effectHistory` entry, and (if `outputs` are supplied
3879
4522
  * and the effect succeeded) upserts those values into
3880
4523
  * `effectsContext` by key — so downstream effect bindings can
3881
- * reference them. Cascades after.
4524
+ * reference them. Any `ops` the handler returned (`field.*`) are
4525
+ * validated and applied to the instance in the same commit, through the
4526
+ * same op applier an action's field ops use. Cascades after.
3882
4527
  */
3883
4528
  completeEffect: async (args) => {
3884
- const { client, tag, instanceId, effectKey, status, outputs, detail, error, durationMs } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
4529
+ const { client, tag, instanceId, effectKey, status, outputs, ops, detail, error, durationMs } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
3885
4530
  await completeEffect({
3886
4531
  client,
3887
4532
  instanceId,
3888
4533
  effectKey,
3889
4534
  status,
3890
4535
  ...outputs !== void 0 ? { outputs } : {},
4536
+ ...ops !== void 0 ? { ops } : {},
3891
4537
  ...detail !== void 0 ? { detail } : {},
3892
4538
  ...error !== void 0 ? { error } : {},
3893
4539
  ...durationMs !== void 0 ? { durationMs } : {},
3894
- options: { ...actor !== void 0 ? { actor } : {}, clock, clientForGdr }
4540
+ options: engineOptionsForActor({ actor, clock, clientForGdr })
3895
4541
  });
3896
- const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
4542
+ const cascaded = await cascade({ client, instanceId, actor, clientForGdr, clock });
3897
4543
  return {
3898
- instance: await reload(client, instanceId, tag),
4544
+ instance: await reload({ client, instanceId, tag }),
3899
4545
  cascaded,
3900
4546
  fired: !0
3901
4547
  };
@@ -3910,11 +4556,11 @@ const workflow = {
3910
4556
  * affected instances and the engine re-evaluates.
3911
4557
  */
3912
4558
  tick: async (args) => {
3913
- const { client, tag, instanceId } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, current = await reload(client, instanceId, tag), guards = await verdictGuardsForInstance(client, current._id);
4559
+ const { client, tag, instanceId } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, current = await reload({ client, instanceId, tag }), guards = await verdictGuardsForInstance(client, current._id);
3914
4560
  await assertInstanceWriteAllowed({ instance: current, guards, identity: actor.id });
3915
- const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
4561
+ const cascaded = await cascade({ client, instanceId, actor, clientForGdr, clock });
3916
4562
  return {
3917
- instance: await reload(client, instanceId, tag),
4563
+ instance: await reload({ client, instanceId, tag }),
3918
4564
  cascaded,
3919
4565
  fired: cascaded > 0
3920
4566
  };
@@ -3926,16 +4572,16 @@ const workflow = {
3926
4572
  */
3927
4573
  setStage: async (args) => {
3928
4574
  const { client, tag, instanceId, targetStage, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
3929
- await reload(client, instanceId, tag);
4575
+ await reload({ client, instanceId, tag });
3930
4576
  const result = await setStage({
3931
4577
  client,
3932
4578
  instanceId,
3933
4579
  targetStage,
3934
4580
  ...reason !== void 0 ? { reason } : {},
3935
- options: { ...actor !== void 0 ? { actor } : {}, clock, clientForGdr }
3936
- }), cascaded = result.fired ? await cascade(client, instanceId, actor, clientForGdr, clock) : 0;
4581
+ options: engineOptionsForActor({ actor, clock, clientForGdr })
4582
+ }), cascaded = result.fired ? await cascade({ client, instanceId, actor, clientForGdr, clock }) : 0;
3937
4583
  return {
3938
- instance: await reload(client, instanceId, tag),
4584
+ instance: await reload({ client, instanceId, tag }),
3939
4585
  cascaded,
3940
4586
  fired: result.fired
3941
4587
  };
@@ -3948,10 +4594,10 @@ const workflow = {
3948
4594
  */
3949
4595
  abortInstance: async (args) => {
3950
4596
  const { client, tag, instanceId, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
3951
- await reload(client, instanceId, tag);
4597
+ await reload({ client, instanceId, tag });
3952
4598
  const fired = await abortAndPropagate({ client, instanceId, reason, actor, clientForGdr, clock });
3953
4599
  return {
3954
- instance: await reload(client, instanceId, tag),
4600
+ instance: await reload({ client, instanceId, tag }),
3955
4601
  cascaded: 0,
3956
4602
  fired
3957
4603
  };
@@ -3963,12 +4609,12 @@ const workflow = {
3963
4609
  */
3964
4610
  getInstance: async (args) => {
3965
4611
  const { client, tag, instanceId } = args;
3966
- return validateTag(tag), reload(client, instanceId, tag);
4612
+ return validateTag(tag), reload({ client, instanceId, tag });
3967
4613
  },
3968
4614
  guardsForInstance: async (args) => {
3969
4615
  const { client, tag, instanceId, resourceClients } = args;
3970
4616
  validateTag(tag);
3971
- const instance = await reload(client, instanceId, tag);
4617
+ const instance = await reload({ client, instanceId, tag });
3972
4618
  return guardsForInstance({
3973
4619
  client,
3974
4620
  clientForGdr: buildClientForGdr(client, resourceClients),
@@ -3978,9 +4624,7 @@ const workflow = {
3978
4624
  guardsForDefinition: async (args) => {
3979
4625
  const { client, tag, workflowResource, definition, resourceClients } = args;
3980
4626
  validateTag(tag);
3981
- const definitions = await loadDefinitionVersions(client, definition, tag);
3982
- if (definitions.length === 0)
3983
- throw new Error(`No deployed definition for workflow ${definition}`);
4627
+ const definitions = await loadDefinitionVersionsOrThrow({ client, definition, tag });
3984
4628
  return guardsForDefinition({
3985
4629
  client,
3986
4630
  clientForGdr: buildClientForGdr(client, resourceClients),
@@ -4025,7 +4669,7 @@ const workflow = {
4025
4669
  queryInScope: async (args) => {
4026
4670
  const { client, tag, instanceId, groq, params, resourceClients } = args, clock = args.clock ?? wallClock;
4027
4671
  validateTag(tag);
4028
- const instance = await reload(client, instanceId, tag), clientForGdr = buildClientForGdr(client, resourceClients), snapshot = await hydrateSnapshot({ client, clientForGdr, instance }), reserved = buildParams({ instance, now: clock(), snapshot }), tree = parse(groq, { params: { ...reserved, ...params } });
4672
+ const instance = await reload({ client, instanceId, tag }), clientForGdr = buildClientForGdr(client, resourceClients), snapshot = await hydrateSnapshot({ client, clientForGdr, instance }), reserved = buildParams({ instance, now: clock(), snapshot }), tree = parse(groq, { params: { ...reserved, ...params } });
4029
4673
  return await (await evaluate(tree, {
4030
4674
  dataset: snapshot.docs,
4031
4675
  params: { ...reserved, ...params }
@@ -4035,13 +4679,13 @@ const workflow = {
4035
4679
  * List every pending effect on the instance. Returns the same entries
4036
4680
  * the runtime would see — claimed and unclaimed alike.
4037
4681
  */
4038
- listPendingEffects: async (args) => (await reload(args.client, args.instanceId, args.tag)).pendingEffects,
4682
+ listPendingEffects: async (args) => (await reload({ client: args.client, instanceId: args.instanceId, tag: args.tag })).pendingEffects,
4039
4683
  /**
4040
4684
  * Filter pending effects on the instance by criteria. `claimed`
4041
4685
  * filters on claim presence; `names` restricts to specific effect
4042
4686
  * names. Both filters compose (AND).
4043
4687
  */
4044
- findPendingEffects: async (args) => (await reload(args.client, args.instanceId, args.tag)).pendingEffects.filter((e) => !(args.claimed === !0 && e.claim === void 0 || args.claimed === !1 && e.claim !== void 0 || args.names !== void 0 && !args.names.includes(e.name))),
4688
+ findPendingEffects: async (args) => (await reload({ client: args.client, instanceId: args.instanceId, tag: args.tag })).pendingEffects.filter((e) => !(args.claimed === !0 && e.claim === void 0 || args.claimed === !1 && e.claim !== void 0 || args.names !== void 0 && !args.names.includes(e.name))),
4045
4689
  /**
4046
4690
  * Project the instance from a given actor's perspective. Returns a
4047
4691
  * `WorkflowEvaluation` with per-action verdicts (`allowed` + a
@@ -4090,7 +4734,7 @@ const workflow = {
4090
4734
  children: async (args) => {
4091
4735
  const { client, tag, instanceId, activity } = args;
4092
4736
  validateTag(tag);
4093
- const parent = await reload(client, instanceId, tag), isSpawned = (h) => h._type === "spawned", ids = parent.history.filter(isSpawned).filter((h) => activity === void 0 || h.activity === activity).flatMap((h) => bareIdFromSpawnRef(h.instanceRef.id));
4737
+ const parent = await reload({ client, instanceId, tag }), isSpawned = (h) => h._type === "spawned", ids = parent.history.filter(isSpawned).filter((h) => activity === void 0 || h.activity === activity).flatMap((h) => bareIdFromSpawnRef(h.instanceRef.id));
4094
4738
  return ids.length === 0 ? [] : client.fetch(
4095
4739
  `*[_id in $ids && ${tagScopeFilter()}] | order(startedAt asc)`,
4096
4740
  { ids, tag }
@@ -4186,7 +4830,7 @@ async function verifyDeployedDefinitionsInternal(args) {
4186
4830
  locations: m.locations,
4187
4831
  definitionId: m.definitionId
4188
4832
  };
4189
- if (await applyMissingHandler(missingHandler, info, log) === "fail")
4833
+ if (await applyMissingHandler({ policy: missingHandler, info, log }) === "fail")
4190
4834
  throw new Error(
4191
4835
  `verifyDeployedDefinitions: missing handler for "${m.name}" referenced by ${m.definitionId} at ${m.locations.join(", ")}`
4192
4836
  );
@@ -4227,21 +4871,30 @@ async function drainEffectsInternal(args) {
4227
4871
  validateTag(tag);
4228
4872
  const log = logger("drainEffects"), drained = [], failed = [], skipped = [], skippedKeys = /* @__PURE__ */ new Set();
4229
4873
  for (; ; ) {
4230
- const before = await reload(client, instanceId, tag), candidate = before.pendingEffects.find(
4874
+ const before = await reload({ client, instanceId, tag }), candidate = before.pendingEffects.find(
4231
4875
  (e) => e.claim === void 0 && !skippedKeys.has(e._key)
4232
4876
  );
4233
4877
  if (candidate === void 0) break;
4234
4878
  const handler = effectHandlers[candidate.name];
4235
4879
  if (handler === void 0) {
4236
- await assertSkippableOrThrow(missingHandler, candidate, instanceId, log), skipped.push(candidate), skippedKeys.add(candidate._key);
4880
+ await assertSkippableOrThrow({ missingHandler, candidate, instanceId, log }), skipped.push(candidate), skippedKeys.add(candidate._key);
4237
4881
  continue;
4238
4882
  }
4239
- if (!await claimPendingEffect(client, before, candidate._key, drainerActor)) continue;
4240
- const { outputs, dispatchError } = await dispatchEffect(handler, candidate, {
4883
+ if (!await claimPendingEffect({
4241
4884
  client,
4242
- clientFor,
4243
- instanceId,
4244
- logger
4885
+ instance: before,
4886
+ effectKey: candidate._key,
4887
+ drainerActor
4888
+ })) continue;
4889
+ const { outputs, ops, dispatchError } = await dispatchEffect({
4890
+ handler,
4891
+ candidate,
4892
+ ctx: {
4893
+ client,
4894
+ clientFor,
4895
+ instanceId,
4896
+ logger
4897
+ }
4245
4898
  });
4246
4899
  await reportEffectOutcome({
4247
4900
  client,
@@ -4252,32 +4905,44 @@ async function drainEffectsInternal(args) {
4252
4905
  effectKey: candidate._key,
4253
4906
  access: completionAccess,
4254
4907
  outputs,
4908
+ ops,
4255
4909
  dispatchError
4256
4910
  }), dispatchError === void 0 ? drained.push(candidate) : failed.push(candidate);
4257
4911
  }
4258
4912
  return { drained, failed, skipped };
4259
4913
  }
4260
4914
  async function reportEffectOutcome(args) {
4261
- const { outputs, dispatchError, resourceClients, ...rest } = args;
4915
+ const { outputs, ops, dispatchError, resourceClients, ...rest } = args;
4262
4916
  await workflow.completeEffect({
4263
4917
  ...rest,
4264
4918
  ...resourceClients !== void 0 ? { resourceClients } : {},
4265
4919
  status: dispatchError === void 0 ? "done" : "failed",
4266
4920
  ...outputs !== void 0 ? { outputs } : {},
4921
+ ...ops !== void 0 ? { ops } : {},
4267
4922
  ...dispatchError !== void 0 ? { error: dispatchError } : {}
4268
4923
  });
4269
4924
  }
4270
- async function assertSkippableOrThrow(missingHandler, candidate, instanceId, log) {
4271
- if (await applyMissingHandler(
4272
- missingHandler,
4273
- { phase: "drain", name: candidate.name, effectKey: candidate._key, instanceId },
4925
+ async function assertSkippableOrThrow({
4926
+ missingHandler,
4927
+ candidate,
4928
+ instanceId,
4929
+ log
4930
+ }) {
4931
+ if (await applyMissingHandler({
4932
+ policy: missingHandler,
4933
+ info: { phase: "drain", name: candidate.name, effectKey: candidate._key, instanceId },
4274
4934
  log
4275
- ) === "fail")
4935
+ }) === "fail")
4276
4936
  throw new Error(
4277
4937
  `drainEffects: no handler registered for "${candidate.name}" (effectKey=${candidate._key}, instanceId=${instanceId})`
4278
4938
  );
4279
4939
  }
4280
- async function claimPendingEffect(client, instance, effectKey, drainerActor) {
4940
+ async function claimPendingEffect({
4941
+ client,
4942
+ instance,
4943
+ effectKey,
4944
+ drainerActor
4945
+ }) {
4281
4946
  const claim = {
4282
4947
  _type: "pendingEffect.claim",
4283
4948
  claimedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4290,7 +4955,11 @@ async function claimPendingEffect(client, instance, effectKey, drainerActor) {
4290
4955
  return !1;
4291
4956
  }
4292
4957
  }
4293
- async function dispatchEffect(handler, candidate, ctx) {
4958
+ async function dispatchEffect({
4959
+ handler,
4960
+ candidate,
4961
+ ctx
4962
+ }) {
4294
4963
  try {
4295
4964
  const result = await handler(candidate.params, {
4296
4965
  client: ctx.client,
@@ -4299,13 +4968,20 @@ async function dispatchEffect(handler, candidate, ctx) {
4299
4968
  effectKey: candidate._key,
4300
4969
  log: (message, extra) => ctx.logger(`effect.${candidate.name}`).info(message, extra)
4301
4970
  });
4302
- return result?.outputs !== void 0 ? { outputs: result.outputs } : {};
4971
+ return {
4972
+ ...result?.outputs !== void 0 ? { outputs: result.outputs } : {},
4973
+ ...result?.ops !== void 0 ? { ops: result.ops } : {}
4974
+ };
4303
4975
  } catch (err) {
4304
4976
  const message = err instanceof Error ? err.message : String(err), stack = err instanceof Error && err.stack !== void 0 ? err.stack : void 0;
4305
4977
  return { dispatchError: stack !== void 0 ? { message, stack } : { message } };
4306
4978
  }
4307
4979
  }
4308
- async function applyMissingHandler(policy, info, log) {
4980
+ async function applyMissingHandler({
4981
+ policy,
4982
+ info,
4983
+ log
4984
+ }) {
4309
4985
  if (policy === "fail") return "fail";
4310
4986
  if (policy === "skip")
4311
4987
  return log.warn(`Missing effect handler "${info.name}" \u2014 skipping`, { info }), "skip";
@@ -4343,9 +5019,20 @@ function createInstanceSession(args) {
4343
5019
  ...clock !== void 0 ? { now: clock() } : {},
4344
5020
  ...grants !== void 0 ? { grants } : {}
4345
5021
  });
4346
- }, settleAfterApply = async (actor, held, ranOps) => {
4347
- const cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
4348
- return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: !0, ...ranOps !== void 0 ? { ranOps } : {} };
5022
+ }, settleAfterApply = async ({
5023
+ actor,
5024
+ held,
5025
+ ranOps
5026
+ }) => {
5027
+ const cascaded = await cascade({
5028
+ client,
5029
+ instanceId: instance._id,
5030
+ actor,
5031
+ clientForGdr,
5032
+ ...clock !== void 0 ? { clock } : {},
5033
+ overlay: held
5034
+ });
5035
+ return instance = await reload({ client, instanceId: instance._id, tag }), { instance, cascaded, fired: !0, ...ranOps !== void 0 ? { ranOps } : {} };
4349
5036
  }, commit = async (run) => {
4350
5037
  committing = !0;
4351
5038
  const held = new Map(overlay);
@@ -4379,13 +5066,20 @@ function createInstanceSession(args) {
4379
5066
  tick() {
4380
5067
  return commit(async (actor, held) => {
4381
5068
  await assertInstanceWriteAllowed({ instance, guards: heldGuards, identity: actor.id });
4382
- const cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
4383
- return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: cascaded > 0 };
5069
+ const cascaded = await cascade({
5070
+ client,
5071
+ instanceId: instance._id,
5072
+ actor,
5073
+ clientForGdr,
5074
+ ...clock !== void 0 ? { clock } : {},
5075
+ overlay: held
5076
+ });
5077
+ return instance = await reload({ client, instanceId: instance._id, tag }), { instance, cascaded, fired: cascaded > 0 };
4384
5078
  });
4385
5079
  },
4386
5080
  fireAction({ activity, action, params }) {
4387
5081
  return commit(async (actor, held) => {
4388
- assertActionAllowed(await evaluateWith(held, heldGuards), activity, action);
5082
+ assertActionAllowed({ evaluation: await evaluateWith(held, heldGuards), activity, action });
4389
5083
  const { grants } = await access(), ranOps = await applyAction({
4390
5084
  client,
4391
5085
  instance,
@@ -4397,7 +5091,7 @@ function createInstanceSession(args) {
4397
5091
  ...clock !== void 0 ? { clock } : {},
4398
5092
  ...params !== void 0 ? { params } : {}
4399
5093
  });
4400
- return settleAfterApply(actor, held, ranOps);
5094
+ return settleAfterApply({ actor, held, ranOps });
4401
5095
  });
4402
5096
  },
4403
5097
  editField({ target, mode, value }) {
@@ -4414,7 +5108,7 @@ function createInstanceSession(args) {
4414
5108
  ...grants !== void 0 ? { grants } : {},
4415
5109
  ...clock !== void 0 ? { clock } : {}
4416
5110
  });
4417
- return settleAfterApply(actor, held, ranOps);
5111
+ return settleAfterApply({ actor, held, ranOps });
4418
5112
  });
4419
5113
  }
4420
5114
  };
@@ -4549,8 +5243,12 @@ function createEngine(args) {
4549
5243
  })
4550
5244
  };
4551
5245
  }
4552
- function diffEntry(def, latestRaw, target) {
4553
- const plan = planDefinitionDeploy(def, asLatest(latestRaw), target), existing = latestRaw ? stripSystemFields(latestRaw) : void 0;
5246
+ function diffEntry({
5247
+ def,
5248
+ latestRaw,
5249
+ target
5250
+ }) {
5251
+ const plan = planDefinitionDeploy({ def, latest: asLatest(latestRaw), target }), existing = latestRaw ? stripSystemFields(latestRaw) : void 0;
4554
5252
  return {
4555
5253
  name: def.name,
4556
5254
  version: plan.version,
@@ -4563,11 +5261,15 @@ function diffEntry(def, latestRaw, target) {
4563
5261
  function asLatest(raw) {
4564
5262
  return raw === void 0 ? void 0 : { version: typeof raw.version == "number" ? raw.version : 0, ...typeof raw.contentHash == "string" ? { contentHash: raw.contentHash } : {} };
4565
5263
  }
4566
- async function computeDiffEntries(client, defs, target) {
5264
+ async function computeDiffEntries({
5265
+ client,
5266
+ defs,
5267
+ target
5268
+ }) {
4567
5269
  const entries = [];
4568
5270
  for (const def of defs) {
4569
- const latest = await loadLatestDeployed(client, def.name, target.tag);
4570
- entries.push(diffEntry(def, latest, target));
5271
+ const latest = await loadLatestDeployed({ client, definition: def.name, tag: target.tag });
5272
+ entries.push(diffEntry({ def, latestRaw: latest, target }));
4571
5273
  }
4572
5274
  return entries;
4573
5275
  }
@@ -4802,6 +5504,7 @@ export {
4802
5504
  DRIVER_KIND_DISPLAY,
4803
5505
  EFFECTS_CONTEXT_DISPLAY,
4804
5506
  EditFieldDeniedError,
5507
+ EffectOpsInvalidError,
4805
5508
  FIELD_SLOT_DISPLAY,
4806
5509
  GUARD_DOC_TYPE,
4807
5510
  GUARD_LIFTED_PREDICATE,