@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.cjs CHANGED
@@ -20,6 +20,28 @@ var v__namespace = /* @__PURE__ */ _interopNamespaceCompat(v);
20
20
  function findOpenStageEntry(host) {
21
21
  return host.stages.find((s) => s.name === host.currentStage && s.exitedAt === void 0);
22
22
  }
23
+ function findCurrentActivityEntry(host, activityName) {
24
+ return findOpenStageEntry(host)?.activities.find((a) => a.name === activityName);
25
+ }
26
+ function errorMessage(err) {
27
+ return err instanceof Error ? err.message : String(err);
28
+ }
29
+ function rethrowWithContext(err, context) {
30
+ throw new Error(`${context}: ${errorMessage(err)}`, { cause: err });
31
+ }
32
+ function mapJsonStrings(value, transform) {
33
+ const walk = (val, key) => {
34
+ if (typeof val == "string") return transform(val, key);
35
+ if (Array.isArray(val)) return val.map((item) => walk(item, key));
36
+ if (val !== null && typeof val == "object") {
37
+ const out = {};
38
+ for (const [k, v2] of Object.entries(val)) out[k] = walk(v2, k);
39
+ return out;
40
+ }
41
+ return val;
42
+ };
43
+ return walk(value, void 0);
44
+ }
23
45
  const KNOWN_SCHEMES = /* @__PURE__ */ new Set(["dataset", "canvas", "media-library", "dashboard"]);
24
46
  function parseGdr(uri) {
25
47
  const colon = uri.indexOf(":");
@@ -59,22 +81,56 @@ function parseGdr(uri) {
59
81
  documentId: parts[1]
60
82
  };
61
83
  }
84
+ function tryParseGdr(uri) {
85
+ try {
86
+ return parseGdr(uri);
87
+ } catch {
88
+ return;
89
+ }
90
+ }
62
91
  function gdrUri(parts) {
63
92
  return parts.scheme === "dataset" ? `dataset:${parts.projectId}:${parts.dataset}:${parts.documentId}` : `${parts.scheme}:${parts.resourceId}:${parts.documentId}`;
64
93
  }
65
94
  function extractDocumentId(gdrUriString) {
66
95
  return parseGdr(gdrUriString).documentId;
67
96
  }
68
- function refDataset(projectId, dataset, documentId, type) {
97
+ const RESOURCE_ALIAS_NAME_SOURCE = "[a-z0-9][a-z0-9-]*", RESOURCE_ALIAS_NAME_RE = new RegExp(`^${RESOURCE_ALIAS_NAME_SOURCE}$`);
98
+ function validateResourceAliasName(name) {
99
+ if (!RESOURCE_ALIAS_NAME_RE.test(name))
100
+ throw new Error(
101
+ `Invalid resource alias name "${name}": expected lowercase letters, digits, and dashes (no leading dash).`
102
+ );
103
+ }
104
+ function toPhysicalGdr(id, home) {
105
+ return isGdrUri(id) ? id : gdrFromResource(home, id);
106
+ }
107
+ function refDataset({
108
+ projectId,
109
+ dataset,
110
+ documentId,
111
+ type
112
+ }) {
69
113
  return { id: gdrUri({ scheme: "dataset", projectId, dataset, documentId }), type };
70
114
  }
71
- function refCanvas(resourceId, documentId, type) {
115
+ function refCanvas({
116
+ resourceId,
117
+ documentId,
118
+ type
119
+ }) {
72
120
  return { id: gdrUri({ scheme: "canvas", resourceId, documentId }), type };
73
121
  }
74
- function refMediaLibrary(resourceId, documentId, type) {
122
+ function refMediaLibrary({
123
+ resourceId,
124
+ documentId,
125
+ type
126
+ }) {
75
127
  return { id: gdrUri({ scheme: "media-library", resourceId, documentId }), type };
76
128
  }
77
- function refDashboard(resourceId, documentId, type) {
129
+ function refDashboard({
130
+ resourceId,
131
+ documentId,
132
+ type
133
+ }) {
78
134
  return { id: gdrUri({ scheme: "dashboard", resourceId, documentId }), type };
79
135
  }
80
136
  function datasetResourceParts(id) {
@@ -90,17 +146,16 @@ function gdrFromResource(res, documentId) {
90
146
  }
91
147
  return gdrUri({ scheme: res.type, resourceId: res.id, documentId });
92
148
  }
149
+ function gdrResourcePrefix(res) {
150
+ const full = gdrFromResource(res, "x");
151
+ return full.slice(0, full.length - 1);
152
+ }
153
+ function resourceFromParsed(parsed) {
154
+ return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
155
+ }
93
156
  function resourceFromGdrUri(uri) {
94
- let parsed;
95
- try {
96
- parsed = parseGdr(uri);
97
- } catch {
98
- return;
99
- }
100
- if (parsed.scheme === "dataset")
101
- return parsed.projectId === void 0 || parsed.dataset === void 0 ? void 0 : { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` };
102
- if (parsed.resourceId !== void 0)
103
- return { type: parsed.scheme, id: parsed.resourceId };
157
+ const parsed = tryParseGdr(uri);
158
+ return parsed === void 0 ? void 0 : resourceFromParsed(parsed);
104
159
  }
105
160
  function sameResource(a, b) {
106
161
  return a.type === b.type && a.id === b.id;
@@ -108,15 +163,24 @@ function sameResource(a, b) {
108
163
  function selfGdr(doc) {
109
164
  return gdrFromResource(doc.workflowResource, doc._id);
110
165
  }
166
+ function definitionDocId({
167
+ tag,
168
+ definition,
169
+ version
170
+ }) {
171
+ return `${tag}.${definition}.v${version}`;
172
+ }
111
173
  function isGdrUri(value) {
112
- if (typeof value != "string") return !1;
113
- try {
114
- return parseGdr(value), !0;
115
- } catch {
116
- return !1;
117
- }
174
+ return typeof value == "string" && tryParseGdr(value) !== void 0;
175
+ }
176
+ function toBareId(id) {
177
+ return isGdrUri(id) ? extractDocumentId(id) : id;
118
178
  }
119
- function gdrRef(res, documentId, type) {
179
+ function gdrRef({
180
+ res,
181
+ documentId,
182
+ type
183
+ }) {
120
184
  return { id: gdrFromResource(res, documentId), type };
121
185
  }
122
186
  function isGdr(value) {
@@ -151,17 +215,26 @@ function renderedFields(entries, snapshot) {
151
215
  function renderedValue(entry, snapshot) {
152
216
  return entry._type !== "doc.ref" || entry.value === null || snapshot === void 0 ? entry.value : snapshot.docs.find((d) => d._id === entry.value.id) ?? entry.value;
153
217
  }
154
- function scopedFieldOverlay(instance, snapshot, activityName) {
218
+ function scopedFieldOverlay({
219
+ instance,
220
+ snapshot,
221
+ activityName
222
+ }) {
155
223
  const stageEntry = findOpenStageEntry(instance);
156
224
  if (stageEntry === void 0) return {};
157
225
  const stageFields = renderedFields(stageEntry.fields ?? [], snapshot), activity = activityName ? stageEntry.activities.find((t) => t.name === activityName) : void 0;
158
226
  return { ...stageFields, ...renderedFields(activity?.fields ?? [], snapshot) };
159
227
  }
160
- function assignedFor(instance, activityName, actor, roleAliases) {
228
+ function assignedFor({
229
+ instance,
230
+ activityName,
231
+ actor,
232
+ roleAliases
233
+ }) {
161
234
  if (actor === void 0) return !1;
162
- const entry = findOpenStageEntry(instance)?.activities.find((t) => t.name === activityName)?.fields?.find((s) => s._type === "assignees");
235
+ const entry = findCurrentActivityEntry(instance, activityName)?.fields?.find((s) => s._type === "assignees");
163
236
  return entry === void 0 ? !1 : entry.value.some(
164
- (a) => a.type === "user" ? a.id === actor.id : schema.actorFulfillsRole(actor.roles, a.role, roleAliases)
237
+ (a) => a.type === "user" ? a.id === actor.id : schema.actorFulfillsRole({ actorRoles: actor.roles, required: a.role, aliases: roleAliases })
165
238
  );
166
239
  }
167
240
  function effectsContextMap(instance) {
@@ -174,35 +247,20 @@ function parseJsonContextEntry(entry) {
174
247
  try {
175
248
  return JSON.parse(entry.value);
176
249
  } catch (err) {
177
- throw new Error(
178
- `effectsContext entry "${entry.name}" holds unparseable JSON: ${err instanceof Error ? err.message : String(err)}`,
179
- { cause: err }
180
- );
250
+ rethrowWithContext(err, `effectsContext entry "${entry.name}" holds unparseable JSON`);
181
251
  }
182
252
  }
183
253
  function paramsForLake(params) {
184
254
  return {
185
255
  ...params,
186
- self: typeof params.self == "string" ? bareId(params.self) : params.self,
187
- parent: typeof params.parent == "string" ? bareId(params.parent) : params.parent,
188
- ancestors: Array.isArray(params.ancestors) ? params.ancestors.map((a) => typeof a == "string" ? bareId(a) : a) : params.ancestors,
256
+ self: typeof params.self == "string" ? toBareId(params.self) : params.self,
257
+ parent: typeof params.parent == "string" ? toBareId(params.parent) : params.parent,
258
+ ancestors: Array.isArray(params.ancestors) ? params.ancestors.map((a) => typeof a == "string" ? toBareId(a) : a) : params.ancestors,
189
259
  fields: stripFieldsForLake(params.fields)
190
260
  };
191
261
  }
192
262
  function stripFieldsForLake(value) {
193
- if (value == null) return value;
194
- if (typeof value == "string") return bareId(value);
195
- if (Array.isArray(value)) return value.map(stripFieldsForLake);
196
- if (typeof value == "object") {
197
- const out = {};
198
- for (const [k, v2] of Object.entries(value))
199
- out[k] = stripFieldsForLake(v2);
200
- return out;
201
- }
202
- return value;
203
- }
204
- function bareId(id) {
205
- return isGdrUri(id) ? extractDocumentId(id) : id;
263
+ return mapJsonStrings(value, (s) => toBareId(s));
206
264
  }
207
265
  function getPath(value, path) {
208
266
  let current = value;
@@ -212,9 +270,6 @@ function getPath(value, path) {
212
270
  }
213
271
  return current;
214
272
  }
215
- function resourceOf(p) {
216
- return p.scheme === "dataset" ? { type: "dataset", id: `${p.projectId}.${p.dataset}` } : { type: p.scheme, id: p.resourceId ?? "" };
217
- }
218
273
  const FIELD_READ = /^\$fields\.(\w+)(?:\.(.+))?$/, EFFECTS_READ = /^\$effects\['([^']+)'\](?:\.(.+))?$/;
219
274
  function isGuardReadExpr(expr) {
220
275
  return expr === "$self" || expr === "$now" || FIELD_READ.test(expr) || EFFECTS_READ.test(expr);
@@ -257,9 +312,9 @@ function resolveIdRefTargets(idRefs, ctx) {
257
312
  return targets.length === 0 ? null : targets;
258
313
  }
259
314
  function assertSingleResource(targets) {
260
- const resource = resourceOf(targets[0].parsed);
315
+ const resource = resourceFromParsed(targets[0].parsed);
261
316
  for (const g of targets) {
262
- const r = resourceOf(g.parsed);
317
+ const r = resourceFromParsed(g.parsed);
263
318
  if (r.type !== resource.type || r.id !== resource.id)
264
319
  throw new Error(
265
320
  `Guard targets span multiple resources (${resource.type}:${resource.id} vs ${r.type}:${r.id}); a guard is single-resource.`
@@ -323,23 +378,31 @@ function validateStage(v2, stage) {
323
378
  for (const entry of stage.fields ?? [])
324
379
  v2.checkEntry(entry, `stage "${stage.name}".fields "${entry.name}"`);
325
380
  for (const guard of stage.guards ?? [])
326
- validateGuardReads(v2, stage.name, guard);
381
+ validateGuardReads({ v: v2, stageName: stage.name, guard });
327
382
  for (const t of stage.transitions ?? []) {
328
383
  v2.checkCondition(t.filter, `transition "${t.name}" (${stage.name} \u2192 ${t.to}) filter`);
329
384
  for (const [key, groq] of effectBindings(t.effects))
330
385
  v2.tryParse(groq, `transition "${t.name}" effect binding "${key}"`);
331
386
  }
332
387
  for (const activity of stage.activities ?? [])
333
- validateActivity(v2, stage.name, activity);
388
+ validateActivity({ v: v2, stageName: stage.name, activity });
334
389
  }
335
- function validateGuardReads(v2, stageName, guard) {
390
+ function validateGuardReads({
391
+ v: v2,
392
+ stageName,
393
+ guard
394
+ }) {
336
395
  const where = `stage "${stageName}" guard "${guard.name}"`;
337
396
  for (const expr of guard.match.idRefs ?? [])
338
397
  v2.checkGuardRead(expr, `${where} match.idRefs`);
339
398
  for (const [key, expr] of Object.entries(guard.metadata ?? {}))
340
399
  v2.checkGuardRead(expr, `${where} metadata "${key}"`);
341
400
  }
342
- function validateActivity(v2, stageName, activity) {
401
+ function validateActivity({
402
+ v: v2,
403
+ stageName,
404
+ activity
405
+ }) {
343
406
  const where = `stage "${stageName}" activity "${activity.name}"`;
344
407
  for (const entry of activity.fields ?? [])
345
408
  v2.checkEntry(entry, `${where}.fields "${entry.name}"`);
@@ -348,7 +411,7 @@ function validateActivity(v2, stageName, activity) {
348
411
  v2.checkCondition(groq, `${where}.requirements "${name}"`);
349
412
  for (const [key, groq] of effectBindings(activity.effects))
350
413
  v2.tryParse(groq, `${where} effect binding "${key}"`);
351
- validateActivityActions(v2, activity), activity.subworkflows !== void 0 && validateSubworkflows(v2, where, activity.subworkflows);
414
+ validateActivityActions(v2, activity), activity.subworkflows !== void 0 && validateSubworkflows({ v: v2, where, sub: activity.subworkflows });
352
415
  }
353
416
  function validateActivityActions(v2, activity) {
354
417
  for (const a of activity.actions ?? []) {
@@ -357,7 +420,11 @@ function validateActivityActions(v2, activity) {
357
420
  v2.tryParse(groq, `activity "${activity.name}" action "${a.name}" effect binding "${key}"`);
358
421
  }
359
422
  }
360
- function validateSubworkflows(v2, where, sub) {
423
+ function validateSubworkflows({
424
+ v: v2,
425
+ where,
426
+ sub
427
+ }) {
361
428
  v2.tryParse(sub.forEach, `${where}.subworkflows.forEach`);
362
429
  for (const [key, groq] of Object.entries(sub.with ?? {}))
363
430
  v2.tryParse(groq, `${where}.subworkflows.with "${key}"`);
@@ -436,7 +503,11 @@ function globMatch(pattern, value) {
436
503
  const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
437
504
  return new RegExp(`^${escaped}$`).test(value);
438
505
  }
439
- function guardMatches(guard, doc, action) {
506
+ function guardMatches({
507
+ guard,
508
+ doc,
509
+ action
510
+ }) {
440
511
  const m = guard.match;
441
512
  if (!m.actions.includes(action) || m.types && m.types.length > 0 && !(doc.type !== void 0 && m.types.includes(doc.type)))
442
513
  return !1;
@@ -465,7 +536,7 @@ async function evaluateMutationGuard(args) {
465
536
  async function denyingGuards(args) {
466
537
  const { guards, doc, context } = args, denied = [];
467
538
  for (const guard of guards)
468
- guardMatches(guard, doc, context.action) && (await evaluateMutationGuard({ guard, context }) || denied.push(guard));
539
+ guardMatches({ guard, doc, action: context.action }) && (await evaluateMutationGuard({ guard, context }) || denied.push(guard));
469
540
  return denied;
470
541
  }
471
542
  async function instanceWriteDenials(args) {
@@ -636,6 +707,16 @@ ${lines}`
636
707
  ), this.name = "ActionParamsInvalidError", this.action = args.action, this.activity = args.activity, this.issues = args.issues;
637
708
  }
638
709
  }
710
+ class EffectOpsInvalidError extends Error {
711
+ effect;
712
+ issues;
713
+ constructor(args) {
714
+ const lines = args.issues.map((i) => ` - ${i}`).join(`
715
+ `);
716
+ super(`Effect "${args.effect}" returned invalid completion ops:
717
+ ${lines}`), this.name = "EffectOpsInvalidError", this.effect = args.effect, this.issues = args.issues;
718
+ }
719
+ }
639
720
  class RequiredFieldNotProvidedError extends Error {
640
721
  definition;
641
722
  missing;
@@ -712,38 +793,39 @@ class CascadeLimitError extends Error {
712
793
  function buildSnapshot(args) {
713
794
  const docs = [], knownIds = /* @__PURE__ */ new Set();
714
795
  for (const { doc, resource } of args.docs) {
715
- const uri = gdrFromResource(resource, doc._id), restamped = restampForSnapshot(doc, uri, resource);
796
+ const uri = gdrFromResource(resource, doc._id), restamped = restampForSnapshot({ doc, gdrUri: uri, resource });
716
797
  docs.push(restamped), knownIds.add(uri);
717
798
  }
718
799
  return { docs, knownIds };
719
800
  }
720
- function restampForSnapshot(doc, gdrUri2, resource) {
801
+ function restampForSnapshot({
802
+ doc,
803
+ gdrUri: gdrUri2,
804
+ resource
805
+ }) {
721
806
  return { ...rewriteRefsRecursive(doc, resource), _id: gdrUri2 };
722
807
  }
723
808
  function rewriteRefsRecursive(value, resource) {
724
- if (Array.isArray(value))
725
- return value.map((v2) => rewriteRefsRecursive(v2, resource));
726
- if (value === null || typeof value != "object") return value;
727
- const obj = value, out = {};
728
- for (const [k, v2] of Object.entries(obj))
729
- k === "_ref" && typeof v2 == "string" ? out[k] = v2.includes(":") ? v2 : gdrFromResource(resource, v2) : out[k] = rewriteRefsRecursive(v2, resource);
730
- return out;
809
+ return mapJsonStrings(
810
+ value,
811
+ (str, key) => (
812
+ // Only `_ref` string values become URIs; already-qualified ones pass through.
813
+ key === "_ref" && !str.includes(":") ? gdrFromResource(resource, str) : str
814
+ )
815
+ );
731
816
  }
732
817
  const WORKFLOW_INSTANCE_TYPE = "sanity.workflow.instance";
733
818
  function parseDefinitionSnapshot(instance) {
734
819
  try {
735
820
  return JSON.parse(instance.definitionSnapshot);
736
821
  } catch (err) {
737
- throw new Error(
738
- `Failed to parse definitionSnapshot on instance "${instance._id}": ${err instanceof Error ? err.message : String(err)}`,
739
- { cause: err }
740
- );
822
+ rethrowWithContext(err, `Failed to parse definitionSnapshot on instance "${instance._id}"`);
741
823
  }
742
824
  }
743
825
  function collectWatchRefs(instance) {
744
826
  const stage = findOpenStageEntry(instance);
745
827
  return [
746
- gdrRef(instance.workflowResource, instance._id, instance._type),
828
+ gdrRef({ res: instance.workflowResource, documentId: instance._id, type: instance._type }),
747
829
  ...instance.ancestors,
748
830
  ...entryDocRefs(instance.fields),
749
831
  ...entryDocRefs(stage?.fields),
@@ -801,13 +883,13 @@ async function hydrateSnapshot(args) {
801
883
  loaded.push(held), visited.add(uri);
802
884
  return;
803
885
  }
804
- const fetched = await loadByGdr(
805
- client,
886
+ const fetched = await loadByGdr({
887
+ defaultClient: client,
806
888
  clientForGdr,
807
- instance.workflowResource,
889
+ defaultResource: instance.workflowResource,
808
890
  uri,
809
891
  perspective
810
- );
892
+ });
811
893
  fetched && (loaded.push(fetched), visited.add(uri));
812
894
  };
813
895
  loaded.push({ doc: instance, resource: instance.workflowResource }), visited.add(selfGdr(instance));
@@ -818,23 +900,28 @@ async function hydrateSnapshot(args) {
818
900
  );
819
901
  return buildSnapshot({ docs: loaded });
820
902
  }
821
- async function loadByGdr(defaultClient, clientForGdr, defaultResource, uri, perspective) {
822
- let parsed;
823
- try {
824
- parsed = parseGdr(uri);
825
- } catch {
826
- const doc2 = await readDoc(defaultClient, uri, perspective);
903
+ async function loadByGdr({
904
+ defaultClient,
905
+ clientForGdr,
906
+ defaultResource,
907
+ uri,
908
+ perspective
909
+ }) {
910
+ const parsed = tryParseGdr(uri);
911
+ if (parsed === void 0) {
912
+ const doc2 = await readDoc({ client: defaultClient, id: uri, perspective });
827
913
  return doc2 ? { doc: doc2, resource: defaultResource } : null;
828
914
  }
829
- const routed = clientForGdr(parsed), doc = await readDoc(routed, parsed.documentId, perspective);
915
+ const routed = clientForGdr(parsed), doc = await readDoc({ client: routed, id: parsed.documentId, perspective });
830
916
  return doc ? { doc, resource: resourceFromParsed(parsed) } : null;
831
917
  }
832
- async function readDoc(client, id, perspective) {
918
+ async function readDoc({
919
+ client,
920
+ id,
921
+ perspective
922
+ }) {
833
923
  return perspective === "raw" ? await client.getDocument(id) ?? null : await client.fetch("*[_id == $id][0]", { id }, { perspective }) ?? null;
834
924
  }
835
- function resourceFromParsed(parsed) {
836
- return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
837
- }
838
925
  function collectEntryDocUris(resolvedFieldEntries) {
839
926
  return entryDocRefs(resolvedFieldEntries).map((ref) => ref.id);
840
927
  }
@@ -856,13 +943,13 @@ async function guardsForInstance(args) {
856
943
  const { client, clientForGdr, instance } = args, stateUris = [
857
944
  ...collectEntryDocUris(instance.fields),
858
945
  ...instance.stages.flatMap((stage) => collectEntryDocUris(stage.fields))
859
- ], clients = resourceClientMap(
860
- instance.workflowResource,
861
- client,
946
+ ], clients = resourceClientMap({
947
+ base: instance.workflowResource,
948
+ baseClient: client,
862
949
  clientForGdr,
863
- stateUris.map((uri) => tryParseGdr(uri))
864
- ), { query, params } = instanceGuardQuery(instance._id);
865
- return queryGuardsAcross(clients.values(), query, params);
950
+ gdrs: stateUris.map((uri) => tryParseGdr(uri))
951
+ }), { query, params } = instanceGuardQuery(instance._id);
952
+ return queryGuardsAcross({ clients: clients.values(), query, params });
866
953
  }
867
954
  async function guardsForDefinition(args) {
868
955
  const perClient = await guardsForDefinitionByClient(args);
@@ -870,8 +957,13 @@ async function guardsForDefinition(args) {
870
957
  }
871
958
  async function guardsForDefinitionByClient(args) {
872
959
  const { client, clientForGdr, workflowResource, definition, definitions } = args, gdrs = definitions.flatMap(
873
- (def) => guardIdRefs(def).map(({ idRef, stage }) => staticIdRefGdr(idRef, stage, def))
874
- ), clients = resourceClientMap(workflowResource, client, clientForGdr, gdrs), query = "*[_type == $t && sourceDefinition == $definition]", params = { t: GUARD_DOC_TYPE, definition };
960
+ (def) => guardIdRefs(def).map(({ idRef, stage }) => staticIdRefGdr({ idRef, stage, definition: def }))
961
+ ), clients = resourceClientMap({
962
+ base: workflowResource,
963
+ baseClient: client,
964
+ clientForGdr,
965
+ gdrs
966
+ }), query = "*[_type == $t && sourceDefinition == $definition]", params = { t: GUARD_DOC_TYPE, definition };
875
967
  return Promise.all(
876
968
  [...clients.values()].map(async (resourceClient) => ({
877
969
  client: resourceClient,
@@ -886,7 +978,11 @@ function guardIdRefs(definition) {
886
978
  )
887
979
  );
888
980
  }
889
- function staticIdRefGdr(idRef, stage, definition) {
981
+ function staticIdRefGdr({
982
+ idRef,
983
+ stage,
984
+ definition
985
+ }) {
890
986
  const read = /^\$fields\.([\w-]+)/.exec(idRef);
891
987
  if (read === null) return;
892
988
  const seed = entriesInScope(stage, definition).find((e) => e.name === read[1])?.initialValue;
@@ -906,14 +1002,12 @@ function gdrFromValue(value) {
906
1002
  return typeof id == "string" ? tryParseGdr(id) : void 0;
907
1003
  }
908
1004
  }
909
- function tryParseGdr(uri) {
910
- try {
911
- return parseGdr(uri);
912
- } catch {
913
- return;
914
- }
915
- }
916
- function resourceClientMap(base, baseClient, clientForGdr, gdrs) {
1005
+ function resourceClientMap({
1006
+ base,
1007
+ baseClient,
1008
+ clientForGdr,
1009
+ gdrs
1010
+ }) {
917
1011
  const map = /* @__PURE__ */ new Map([[resourceKey(base), baseClient]]);
918
1012
  for (const parsed of gdrs) {
919
1013
  if (parsed === void 0) continue;
@@ -922,7 +1016,11 @@ function resourceClientMap(base, baseClient, clientForGdr, gdrs) {
922
1016
  }
923
1017
  return map;
924
1018
  }
925
- async function queryGuardsAcross(clients, query, params) {
1019
+ async function queryGuardsAcross({
1020
+ clients,
1021
+ query,
1022
+ params
1023
+ }) {
926
1024
  const perResource = await Promise.all(
927
1025
  [...clients].map((c) => c.fetch(query, params))
928
1026
  );
@@ -932,7 +1030,12 @@ function dedupById(guards) {
932
1030
  return [...new Map(guards.map((g) => [g._id, g])).values()].sort((a, b) => a._id.localeCompare(b._id));
933
1031
  }
934
1032
  const SYNC_COMMIT = { visibility: "sync" }, GUARD_OWNER = "robot:workflow-engine", GUARD_LIFTED_PREDICATE = "true";
935
- function resolveGuard(guard, instance, stageName, now) {
1033
+ function resolveGuard({
1034
+ guard,
1035
+ instance,
1036
+ stageName,
1037
+ now
1038
+ }) {
936
1039
  const ctx = { instance, now }, targets = resolveIdRefTargets(guard.match.idRefs, ctx);
937
1040
  if (targets === null) return null;
938
1041
  const resource = assertSingleResource(targets), types = resolveMatchTypes(targets, guard.match.types);
@@ -967,7 +1070,12 @@ async function upsertGuard(client, doc) {
967
1070
  function resolvedStageGuards(args) {
968
1071
  const stage = args.definition.stages.find((s) => s.name === args.stageName), out = [];
969
1072
  for (const guard of stage?.guards ?? []) {
970
- const resolved = resolveGuard(guard, args.instance, args.stageName, args.now);
1073
+ const resolved = resolveGuard({
1074
+ guard,
1075
+ instance: args.instance,
1076
+ stageName: args.stageName,
1077
+ now: args.now
1078
+ });
971
1079
  resolved !== null && out.push({ client: args.clientForGdr(resolved.routeGdr), doc: resolved.doc });
972
1080
  }
973
1081
  return out;
@@ -1016,13 +1124,16 @@ function randomKey(length = 12) {
1016
1124
  const bytes = new Uint8Array(length);
1017
1125
  return globalThis.crypto.getRandomValues(bytes), [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, length);
1018
1126
  }
1127
+ function instanceDocId(tag) {
1128
+ return `${tag}.wf-instance.${randomKey()}`;
1129
+ }
1019
1130
  function isUnevaluable(result) {
1020
1131
  return result == null;
1021
1132
  }
1022
1133
  async function evaluateConditionOutcome(args) {
1023
1134
  const { condition, snapshot, params } = args;
1024
1135
  if (condition === void 0) return "satisfied";
1025
- const result = await runGroq(condition, params, snapshot);
1136
+ const result = await runGroq({ groq: condition, params, snapshot });
1026
1137
  return isUnevaluable(result) ? "unevaluable" : result ? "satisfied" : "unsatisfied";
1027
1138
  }
1028
1139
  async function evaluateCondition(args) {
@@ -1031,7 +1142,7 @@ async function evaluateCondition(args) {
1031
1142
  async function evaluatePredicates(args) {
1032
1143
  const out = {};
1033
1144
  for (const [name, groq] of Object.entries(args.predicates ?? {})) {
1034
- const result = await runGroq(groq, args.params, args.snapshot);
1145
+ const result = await runGroq({ groq, params: args.params, snapshot: args.snapshot });
1035
1146
  out[name] = isUnevaluable(result) ? null : !!result;
1036
1147
  }
1037
1148
  return out;
@@ -1042,7 +1153,11 @@ async function evaluateRequirements(args) {
1042
1153
  await evaluateCondition({ condition, snapshot: args.snapshot, params: args.params }) || unmet.push(name);
1043
1154
  return unmet;
1044
1155
  }
1045
- async function runGroq(groq, params, snapshot) {
1156
+ async function runGroq({
1157
+ groq,
1158
+ params,
1159
+ snapshot
1160
+ }) {
1046
1161
  const tree = groqJs.parse(groq, { params });
1047
1162
  return (await groqJs.evaluate(tree, { dataset: snapshot.docs, params })).get();
1048
1163
  }
@@ -1172,15 +1287,19 @@ function derivePerspectiveFromFields(entries) {
1172
1287
  async function resolveDeclaredFields(args) {
1173
1288
  const { entryDefs, initialFields, ctx, randomKey: randomKey2 } = args;
1174
1289
  if (entryDefs === void 0 || entryDefs.length === 0) return [];
1175
- assertRequiredInputProvided(entryDefs, initialFields, ctx.definitionName);
1290
+ assertRequiredInputProvided({ entryDefs, initialFields, definitionName: ctx.definitionName });
1176
1291
  const out = [];
1177
1292
  for (const entry of entryDefs) {
1178
1293
  const scopedCtx = { ...ctx, resolvedFields: out };
1179
- out.push(await resolveOneEntry(entry, initialFields, scopedCtx, randomKey2));
1294
+ out.push(await resolveOneEntry({ entry, initialFields, ctx: scopedCtx, randomKey: randomKey2 }));
1180
1295
  }
1181
1296
  return out;
1182
1297
  }
1183
- function assertRequiredInputProvided(entryDefs, initialFields, definitionName) {
1298
+ function assertRequiredInputProvided({
1299
+ entryDefs,
1300
+ initialFields,
1301
+ definitionName
1302
+ }) {
1184
1303
  const missing = entryDefs.filter((entry) => entry.required === !0 && entry.initialValue?.type === "input").filter((entry) => {
1185
1304
  const match = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
1186
1305
  return match === void 0 || match.value === void 0 || match.value === null;
@@ -1195,15 +1314,29 @@ const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set(["doc.refs", "array", "assignee
1195
1314
  function defaultEntryValue(entryType) {
1196
1315
  return ARRAY_SLOT_TYPES.has(entryType) ? [] : null;
1197
1316
  }
1198
- function resolveInputValue(entry, initialFields, defaultValue) {
1317
+ function resolveInputValue({
1318
+ entry,
1319
+ initialFields,
1320
+ defaultValue
1321
+ }) {
1199
1322
  const initMatch = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
1200
1323
  return initMatch === void 0 ? defaultValue : (assertInputValueShape(entry, initMatch.value), initMatch.value);
1201
1324
  }
1202
- function resolveFieldReadValue(source, ctx, defaultValue) {
1325
+ function resolveFieldReadValue({
1326
+ source,
1327
+ ctx,
1328
+ defaultValue
1329
+ }) {
1203
1330
  const target = (source.scope === "workflow" ? ctx.workflowFields ?? [] : ctx.resolvedFields ?? []).find((s) => s.name === source.field), targetValue = target === void 0 ? void 0 : target.value;
1204
1331
  return (source.path !== void 0 ? getPath(targetValue, source.path) : targetValue) ?? defaultValue;
1205
1332
  }
1206
- async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
1333
+ async function resolveQueryValue({
1334
+ entry,
1335
+ source,
1336
+ ctx,
1337
+ client,
1338
+ defaultValue
1339
+ }) {
1207
1340
  const params = paramsForLake({
1208
1341
  self: ctx.selfId ?? null,
1209
1342
  fields: fieldMapFromResolved(ctx.resolvedFields ?? []),
@@ -1214,19 +1347,30 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
1214
1347
  });
1215
1348
  try {
1216
1349
  const fetchOptions = { perspective: ctx.perspective ?? DEFAULT_CONTENT_PERSPECTIVE }, result = await client.fetch(source.query, params, fetchOptions);
1217
- return normalizeQueryResult(entry.type, result, ctx.workflowResource) ?? defaultValue;
1350
+ return normalizeQueryResult({
1351
+ entryType: entry.type,
1352
+ raw: result,
1353
+ workflowResource: ctx.workflowResource
1354
+ }) ?? defaultValue;
1218
1355
  } catch (err) {
1219
- throw new Error(
1220
- `Failed to resolve query entry "${entry.name}" (${entry.type}): ${err instanceof Error ? err.message : String(err)}`,
1221
- { cause: err }
1222
- );
1356
+ rethrowWithContext(err, `Failed to resolve query entry "${entry.name}" (${entry.type})`);
1223
1357
  }
1224
1358
  }
1225
- async function resolveEntryValue(entry, initialFields, ctx, defaultValue) {
1359
+ async function resolveEntryValue({
1360
+ entry,
1361
+ initialFields,
1362
+ ctx,
1363
+ defaultValue
1364
+ }) {
1226
1365
  const source = entry.initialValue;
1227
- 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;
1228
- }
1229
- function buildResolvedEntry(entry, value, _key, now) {
1366
+ 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;
1367
+ }
1368
+ function buildResolvedEntry({
1369
+ entry,
1370
+ value,
1371
+ _key,
1372
+ now
1373
+ }) {
1230
1374
  return {
1231
1375
  _key,
1232
1376
  _type: entry.type,
@@ -1239,8 +1383,13 @@ function buildResolvedEntry(entry, value, _key, now) {
1239
1383
  ...entry.type === "array" ? { of: entry.of ?? [] } : {}
1240
1384
  };
1241
1385
  }
1242
- async function resolveOneEntry(entry, initialFields, ctx, randomKey2) {
1243
- const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue(entry, initialFields, ctx, defaultValue);
1386
+ async function resolveOneEntry({
1387
+ entry,
1388
+ initialFields,
1389
+ ctx,
1390
+ randomKey: randomKey2
1391
+ }) {
1392
+ const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue({ entry, initialFields, ctx, defaultValue });
1244
1393
  if (entry.initialValue?.type === "query") {
1245
1394
  const issues = checkFieldValue({
1246
1395
  entryType: entry.type,
@@ -1248,7 +1397,7 @@ async function resolveOneEntry(entry, initialFields, ctx, randomKey2) {
1248
1397
  fields: entry.fields,
1249
1398
  of: entry.of
1250
1399
  });
1251
- return issues !== void 0 ? (ctx.recordDiscard?.({ field: entry.name, detail: issues.join("; ") }), buildResolvedEntry(entry, defaultValue, randomKey2(), ctx.now)) : buildResolvedEntry(entry, value, randomKey2(), ctx.now);
1400
+ 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 });
1252
1401
  }
1253
1402
  return validateFieldValue({
1254
1403
  entryType: entry.type,
@@ -1256,7 +1405,7 @@ async function resolveOneEntry(entry, initialFields, ctx, randomKey2) {
1256
1405
  value,
1257
1406
  fields: entry.fields,
1258
1407
  of: entry.of
1259
- }), buildResolvedEntry(entry, value, randomKey2(), ctx.now);
1408
+ }), buildResolvedEntry({ entry, value, _key: randomKey2(), now: ctx.now });
1260
1409
  }
1261
1410
  function fieldMapFromResolved(entries) {
1262
1411
  const out = {};
@@ -1301,35 +1450,57 @@ function assertGdrShape(value, context) {
1301
1450
  `Invalid GDR for ${context}: \`type\` (schema name) must be a non-empty string. Got ${JSON.stringify(v2.type)}.`
1302
1451
  );
1303
1452
  }
1304
- function normalizeQueryResult(entryType, raw, workflowResource) {
1453
+ function normalizeQueryResult({
1454
+ entryType,
1455
+ raw,
1456
+ workflowResource
1457
+ }) {
1305
1458
  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;
1306
1459
  }
1307
- function toGdrUri(docId, workflowResource) {
1308
- return isGdrUri(docId) ? docId : gdrFromResource(workflowResource, docId);
1309
- }
1310
1460
  function coerceGdrShape(raw, workflowResource) {
1311
1461
  if (!("id" in raw) || !("type" in raw)) return null;
1312
1462
  const r = raw;
1313
- 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;
1463
+ 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;
1314
1464
  }
1315
1465
  function coerceRefEnvelope(raw, workflowResource) {
1316
1466
  if (!("_ref" in raw)) return null;
1317
1467
  const r = raw;
1318
- return typeof r._ref != "string" || !workflowResource ? null : { id: toGdrUri(r._ref, workflowResource), type: "document" };
1468
+ return typeof r._ref != "string" || !workflowResource ? null : { id: toPhysicalGdr(r._ref, workflowResource), type: "document" };
1319
1469
  }
1320
1470
  function coerceToGdr(raw, workflowResource) {
1321
- return raw == null ? null : typeof raw == "object" ? coerceGdrShape(raw, workflowResource) ?? coerceRefEnvelope(raw, workflowResource) : typeof raw == "string" && workflowResource ? { id: toGdrUri(raw, workflowResource), type: "document" } : null;
1322
- }
1323
- function gdrToBareId(uri) {
1324
- return uri.includes(":") ? extractDocumentId(uri) : uri;
1325
- }
1326
- function loadCallContext(client, instanceId, options) {
1327
- return loadContext(client, instanceId, {
1328
- ...options?.clock ? { clock: options.clock } : {},
1329
- ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
1471
+ return raw == null ? null : typeof raw == "object" ? coerceGdrShape(raw, workflowResource) ?? coerceRefEnvelope(raw, workflowResource) : typeof raw == "string" && workflowResource ? { id: toPhysicalGdr(raw, workflowResource), type: "document" } : null;
1472
+ }
1473
+ function loadCallContext({
1474
+ client,
1475
+ instanceId,
1476
+ options
1477
+ }) {
1478
+ return loadContext({
1479
+ client,
1480
+ instanceId,
1481
+ options: {
1482
+ ...options?.clock ? { clock: options.clock } : {},
1483
+ ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
1484
+ }
1330
1485
  });
1331
1486
  }
1332
- async function loadContext(client, instanceId, options) {
1487
+ async function retryOnRevisionConflict(args) {
1488
+ const { client, instanceId, options, commit, onExhausted } = args;
1489
+ for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
1490
+ const ctx = await loadCallContext({ client, instanceId, options });
1491
+ try {
1492
+ return await commit(ctx);
1493
+ } catch (error) {
1494
+ if (!isRevisionConflict(error)) throw error;
1495
+ }
1496
+ }
1497
+ throw onExhausted();
1498
+ }
1499
+ async function loadContext({
1500
+ client,
1501
+ instanceId,
1502
+ options
1503
+ }) {
1333
1504
  const instance = await client.getDocument(instanceId);
1334
1505
  if (!instance)
1335
1506
  throw new Error(`Workflow instance ${instanceId} not found`);
@@ -1344,12 +1515,21 @@ async function loadContext(client, instanceId, options) {
1344
1515
  async function ctxConditionParams(ctx, opts) {
1345
1516
  const base = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), fields = {
1346
1517
  ...base.fields,
1347
- ...scopedFieldOverlay(ctx.instance, ctx.snapshot, opts?.activityName)
1518
+ ...scopedFieldOverlay({
1519
+ instance: ctx.instance,
1520
+ snapshot: ctx.snapshot,
1521
+ activityName: opts?.activityName
1522
+ })
1348
1523
  }, params = {
1349
1524
  ...base,
1350
1525
  fields,
1351
1526
  actor: opts?.actor,
1352
- assigned: opts?.activityName !== void 0 ? assignedFor(ctx.instance, opts.activityName, opts?.actor, ctx.definition.roleAliases) : !1,
1527
+ assigned: opts?.activityName !== void 0 ? assignedFor({
1528
+ instance: ctx.instance,
1529
+ activityName: opts.activityName,
1530
+ actor: opts?.actor,
1531
+ roleAliases: ctx.definition.roleAliases
1532
+ }) : !1,
1353
1533
  ...opts?.vars
1354
1534
  };
1355
1535
  return { ...await evaluatePredicates({
@@ -1358,15 +1538,23 @@ async function ctxConditionParams(ctx, opts) {
1358
1538
  params
1359
1539
  }), ...params };
1360
1540
  }
1361
- async function ctxEvaluateConditionOutcome(ctx, condition, opts) {
1541
+ async function ctxEvaluateConditionOutcome({
1542
+ ctx,
1543
+ condition,
1544
+ opts
1545
+ }) {
1362
1546
  return condition === void 0 ? "satisfied" : evaluateConditionOutcome({
1363
1547
  condition,
1364
1548
  snapshot: ctx.snapshot,
1365
1549
  params: await ctxConditionParams(ctx, opts)
1366
1550
  });
1367
1551
  }
1368
- async function ctxEvaluateCondition(ctx, condition, opts) {
1369
- return await ctxEvaluateConditionOutcome(ctx, condition, opts) === "satisfied";
1552
+ async function ctxEvaluateCondition({
1553
+ ctx,
1554
+ condition,
1555
+ opts
1556
+ }) {
1557
+ return await ctxEvaluateConditionOutcome({ ctx, condition, opts }) === "satisfied";
1370
1558
  }
1371
1559
  async function buildEngineContext(args) {
1372
1560
  const { client, clientForGdr, instance, definition } = args, clock = args.clock ?? wallClock;
@@ -1430,10 +1618,14 @@ async function resolveActivityFieldEntries(args) {
1430
1618
  async function resolveBindings(args) {
1431
1619
  const resolved = {};
1432
1620
  for (const [key, groq] of Object.entries(args.bindings ?? {}))
1433
- resolved[key] = await runGroq(groq, args.params, args.snapshot);
1621
+ resolved[key] = await runGroq({ groq, params: args.params, snapshot: args.snapshot });
1434
1622
  return { ...resolved, ...args.staticInput };
1435
1623
  }
1436
- async function reload(client, instanceId, tag) {
1624
+ async function reload({
1625
+ client,
1626
+ instanceId,
1627
+ tag
1628
+ }) {
1437
1629
  const doc = await client.getDocument(instanceId);
1438
1630
  if (!doc)
1439
1631
  throw new Error(`Workflow instance ${instanceId} not found`);
@@ -1542,13 +1734,25 @@ async function persist(ctx, mutation) {
1542
1734
  const actorForPriming = void 0;
1543
1735
  for (const body of pendingCreates)
1544
1736
  try {
1545
- await primeInitialStage(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock), await cascadeAutoTransitions(
1546
- ctx.client,
1547
- body._id,
1548
- actorForPriming,
1549
- ctx.clientForGdr,
1550
- ctx.clock
1551
- ), await propagateToAncestors(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock);
1737
+ await primeInitialStage({
1738
+ client: ctx.client,
1739
+ instanceId: body._id,
1740
+ actor: actorForPriming,
1741
+ clientForGdr: ctx.clientForGdr,
1742
+ clock: ctx.clock
1743
+ }), await cascadeAutoTransitions({
1744
+ client: ctx.client,
1745
+ instanceId: body._id,
1746
+ actor: actorForPriming,
1747
+ clientForGdr: ctx.clientForGdr,
1748
+ clock: ctx.clock
1749
+ }), await propagateToAncestors({
1750
+ client: ctx.client,
1751
+ instanceId: body._id,
1752
+ actor: actorForPriming,
1753
+ clientForGdr: ctx.clientForGdr,
1754
+ clock: ctx.clock
1755
+ });
1552
1756
  } catch (cause) {
1553
1757
  throw cause instanceof WorkflowStateDivergedError ? cause : new WorkflowStateDivergedError({
1554
1758
  instanceId: ctx.instance._id,
@@ -1592,159 +1796,16 @@ function findCurrentStageEntry(instance) {
1592
1796
  function findCurrentActivities(instance) {
1593
1797
  return findCurrentStageEntry(instance)?.activities ?? [];
1594
1798
  }
1595
- async function deployOrRollback(args) {
1596
- try {
1597
- await args.deploy();
1598
- } catch (guardError) {
1599
- if (!args.reversible)
1600
- throw new WorkflowStateDivergedError({
1601
- instanceId: args.instanceId,
1602
- guardError,
1603
- reason: "the move created child instances a parent-only rollback cannot undo"
1604
- });
1605
- try {
1606
- let patch = args.client.patch(args.instanceId).set(args.restore);
1607
- 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);
1608
- } catch (rollbackError) {
1609
- throw new WorkflowStateDivergedError({
1610
- instanceId: args.instanceId,
1611
- guardError,
1612
- rollbackError,
1613
- reason: "rollback of the committed move failed"
1614
- });
1615
- }
1616
- throw guardError instanceof PartialGuardDeployError ? new WorkflowStateDivergedError({
1617
- instanceId: args.instanceId,
1618
- guardError,
1619
- reason: "a multi-guard deploy partially applied; the rolled-back move left orphaned guard locks"
1620
- }) : guardError;
1621
- }
1622
- }
1623
- async function persistThenDeploy(ctx, mutation, deploy) {
1624
- const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
1625
- await deployOrRollback({
1626
- client: ctx.client,
1627
- instanceId: ctx.instance._id,
1628
- committedRev: committed._rev,
1629
- restore: instanceStateFields(ctx.instance),
1630
- unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
1631
- reversible: !spawned,
1632
- deploy
1633
- });
1634
- }
1635
- async function refreshStageGuards(ctx, mutation, stageName) {
1636
- await deployStageGuards({
1637
- client: ctx.client,
1638
- clientForGdr: ctx.clientForGdr,
1639
- instance: materializeInstance(ctx.instance, mutation),
1640
- definition: ctx.definition,
1641
- stageName,
1642
- now: ctx.now
1643
- });
1644
- }
1645
- async function completeEffect(args) {
1646
- const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
1647
- ...options?.clock ? { clock: options.clock } : {},
1648
- ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
1649
- });
1650
- return commitCompleteEffect(
1651
- ctx,
1652
- effectKey,
1653
- status,
1654
- outputs,
1655
- detail,
1656
- error,
1657
- durationMs,
1658
- options?.actor
1659
- );
1660
- }
1661
- function buildEffectHistoryEntry(pending, outcome) {
1662
- const { status, ranAt, actor, detail, error, durationMs, outputs } = outcome, resolvedActor = actor ?? pending.actor;
1663
- return {
1664
- _key: pending._key,
1665
- name: pending.name,
1666
- ...pending.title !== void 0 ? { title: pending.title } : {},
1667
- ...pending.description !== void 0 ? { description: pending.description } : {},
1668
- params: pending.params,
1669
- origin: pending.origin,
1670
- ...resolvedActor !== void 0 ? { actor: resolvedActor } : {},
1671
- ranAt,
1672
- ...durationMs !== void 0 ? { durationMs } : {},
1673
- status,
1674
- ...detail !== void 0 ? { detail } : {},
1675
- ...error !== void 0 ? { error } : {},
1676
- ...outputs !== void 0 ? { outputs } : {}
1677
- };
1678
- }
1679
- function upsertEffectsContext(mutation, effectName, outputs) {
1680
- const existingIndex = mutation.effectsContext.findIndex((e) => e.name === effectName), entry = effectsContextJsonEntry(effectName, outputs);
1681
- existingIndex >= 0 ? mutation.effectsContext[existingIndex] = entry : mutation.effectsContext.push(entry);
1682
- }
1683
- async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, error, durationMs, actor) {
1684
- const pending = ctx.instance.pendingEffects.find((e) => e._key === effectKey);
1685
- if (pending === void 0)
1686
- throw new Error(`Pending effect "${effectKey}" not found on instance ${ctx.instance._id}`);
1687
- const mutation = startMutation(ctx.instance);
1688
- mutation.pendingEffects = mutation.pendingEffects.filter((e) => e._key !== effectKey);
1689
- const ranAt = ctx.now;
1690
- mutation.effectHistory.push(
1691
- buildEffectHistoryEntry(pending, { status, ranAt, actor, detail, error, durationMs, outputs })
1692
- );
1693
- const wroteEffectsContext = status === "done" && outputs !== void 0;
1694
- return wroteEffectsContext && upsertEffectsContext(mutation, pending.name, outputs), mutation.history.push({
1695
- _key: randomKey(),
1696
- _type: "effectCompleted",
1697
- at: ranAt,
1698
- effectKey,
1699
- effect: pending.name,
1700
- status,
1701
- ...outputs !== void 0 ? { outputs } : {},
1702
- ...detail !== void 0 ? { detail } : {},
1703
- ...actor !== void 0 ? { actor } : {}
1704
- }), await persistThenDeploy(
1705
- ctx,
1706
- mutation,
1707
- () => wroteEffectsContext ? refreshStageGuards(ctx, mutation, ctx.instance.currentStage) : Promise.resolve()
1708
- ), { effectKey, status };
1709
- }
1710
- function buildQueuedEffect(effect, origin, params, actor, now) {
1711
- const key = randomKey(), pending = {
1712
- _key: key,
1713
- _type: "pendingEffect",
1714
- name: effect.name,
1715
- ...effect.title !== void 0 ? { title: effect.title } : {},
1716
- ...effect.description !== void 0 ? { description: effect.description } : {},
1717
- ...effect.bindings !== void 0 ? { bindings: effect.bindings } : {},
1718
- params,
1719
- origin,
1720
- ...actor !== void 0 ? { actor } : {},
1721
- queuedAt: now
1722
- }, history = {
1723
- _key: randomKey(),
1724
- _type: "effectQueued",
1725
- at: now,
1726
- effectKey: key,
1727
- effect: effect.name,
1728
- origin
1729
- };
1730
- return { pending, history };
1731
- }
1732
- async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
1733
- if (!effects || effects.length === 0) return;
1734
- const now = ctx.now, liveCtx = { ...ctx, instance: materializeInstance(ctx.instance, mutation) }, params = await ctxConditionParams(liveCtx, {
1735
- ...opts?.activityName !== void 0 ? { activityName: opts.activityName } : {},
1736
- ...actor !== void 0 ? { actor } : {},
1737
- // Caller-supplied action params, readable in bindings as `$params.<name>`.
1738
- vars: { params: opts?.callerParams ?? {} }
1739
- });
1740
- for (const effect of effects) {
1741
- const resolved = await resolveBindings({
1742
- bindings: effect.bindings,
1743
- staticInput: effect.input,
1744
- snapshot: ctx.snapshot,
1745
- params
1746
- }), { pending, history } = buildQueuedEffect(effect, origin, resolved, actor, now);
1747
- mutation.pendingEffects.push(pending), mutation.history.push(history);
1799
+ function resolveStaticValueExpr(src, ctx) {
1800
+ switch (src.type) {
1801
+ case "literal":
1802
+ return src.value;
1803
+ case "param":
1804
+ return ctx.params?.[src.param];
1805
+ case "actor":
1806
+ return ctx.actor;
1807
+ case "now":
1808
+ return ctx.now;
1748
1809
  }
1749
1810
  }
1750
1811
  function fieldQueryDiscardedEntry(args) {
@@ -1757,7 +1818,11 @@ function fieldQueryDiscardedEntry(args) {
1757
1818
  detail: args.detail
1758
1819
  };
1759
1820
  }
1760
- function recordFieldDiscards(target, scope, at) {
1821
+ function recordFieldDiscards({
1822
+ target,
1823
+ scope,
1824
+ at
1825
+ }) {
1761
1826
  return (discard) => target.push(fieldQueryDiscardedEntry({ scope, field: discard.field, detail: discard.detail, at }));
1762
1827
  }
1763
1828
  function applyActivityStatusChange(args) {
@@ -1805,18 +1870,6 @@ function stageTransitionHistory(args) {
1805
1870
  }
1806
1871
  ];
1807
1872
  }
1808
- function resolveStaticValueExpr(src, ctx) {
1809
- switch (src.type) {
1810
- case "literal":
1811
- return src.value;
1812
- case "param":
1813
- return ctx.params?.[src.param];
1814
- case "actor":
1815
- return ctx.actor;
1816
- case "now":
1817
- return ctx.now;
1818
- }
1819
- }
1820
1873
  const FIELD_OP_TYPES = [
1821
1874
  "field.set",
1822
1875
  "field.unset",
@@ -1827,7 +1880,11 @@ const FIELD_OP_TYPES = [
1827
1880
  function isFieldOp(summary) {
1828
1881
  return FIELD_OP_TYPES.includes(summary.opType);
1829
1882
  }
1830
- function validateActionParams(action, activityName, callerParams) {
1883
+ function validateActionParams({
1884
+ action,
1885
+ activityName,
1886
+ callerParams
1887
+ }) {
1831
1888
  const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
1832
1889
  for (const decl of declared) {
1833
1890
  const value = params[decl.name], present = decl.name in params && value !== void 0 && value !== null;
@@ -1862,7 +1919,7 @@ function checkParamType(value, decl) {
1862
1919
  case "doc.ref":
1863
1920
  return hasStringField(value, "id");
1864
1921
  case "doc.refs":
1865
- return Array.isArray(value) && value.every((v2) => checkParamType(v2, { type: "doc.ref" }));
1922
+ return Array.isArray(value) && value.every((ref) => checkParamType(ref, { type: "doc.ref" }));
1866
1923
  case "json":
1867
1924
  return !0;
1868
1925
  default:
@@ -1898,12 +1955,27 @@ function opAppliedEntry(args) {
1898
1955
  ...origin.action !== void 0 ? { action: origin.action } : {},
1899
1956
  ...origin.transition !== void 0 ? { transition: origin.transition } : {},
1900
1957
  ...origin.edit === !0 ? { edit: !0 } : {},
1958
+ ...origin.effect !== void 0 ? { effect: origin.effect } : {},
1901
1959
  opType: summary.opType,
1902
1960
  ...summary.target !== void 0 ? { target: summary.target } : {},
1903
1961
  ...summary.resolved !== void 0 ? { resolved: summary.resolved } : {},
1904
1962
  ...actor !== void 0 ? { actor } : {}
1905
1963
  };
1906
1964
  }
1965
+ function validateEffectOps(ops, effectName) {
1966
+ const result = v__namespace.safeParse(v__namespace.array(schema.StoredFieldOpSchema), ops);
1967
+ if (!result.success)
1968
+ throw new EffectOpsInvalidError({ effect: effectName, issues: formatIssues(result.issues) });
1969
+ const activityScoped = result.output.find((op) => op.target.scope === "activity");
1970
+ if (activityScoped !== void 0)
1971
+ throw new EffectOpsInvalidError({
1972
+ effect: effectName,
1973
+ issues: [
1974
+ `${activityScoped.type} targets an activity-scope field \u2014 an effect's completion ops write workflow- or stage-scope fields`
1975
+ ]
1976
+ });
1977
+ return result.output;
1978
+ }
1907
1979
  function applyOp(op, ctx) {
1908
1980
  switch (op.type) {
1909
1981
  case "field.set":
@@ -1959,7 +2031,7 @@ function applyFieldUpdateWhere(op, ctx) {
1959
2031
  return setEntryValue(
1960
2032
  entry,
1961
2033
  rows.map(
1962
- (row) => evalOpPredicate(op.where, row, ctx) ? { ...row, ...merge } : row
2034
+ (row) => evalOpPredicate({ p: op.where, row, ctx }) ? { ...row, ...merge } : row
1963
2035
  )
1964
2036
  ), { opType: op.type, target: op.target, resolved: { merge } };
1965
2037
  }
@@ -1967,7 +2039,7 @@ function applyFieldRemoveWhere(op, ctx) {
1967
2039
  const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op);
1968
2040
  return setEntryValue(
1969
2041
  entry,
1970
- rows.filter((row) => !evalOpPredicate(op.where, row, ctx))
2042
+ rows.filter((row) => !evalOpPredicate({ p: op.where, row, ctx }))
1971
2043
  ), { opType: op.type, target: op.target };
1972
2044
  }
1973
2045
  function requireArrayValue(entry, op) {
@@ -1978,7 +2050,7 @@ function requireArrayValue(entry, op) {
1978
2050
  return entry.value;
1979
2051
  }
1980
2052
  function applyStatusSet(op, ctx) {
1981
- const entry = findOpenStageEntry(ctx.mutation)?.activities.find((t) => t.name === op.activity);
2053
+ const entry = findCurrentActivityEntry(ctx.mutation, op.activity);
1982
2054
  if (entry === void 0)
1983
2055
  throw new Error(
1984
2056
  `status.set targets activity "${op.activity}" which has no entry in the open stage`
@@ -2033,24 +2105,247 @@ function resolveOpValue(src, ctx) {
2033
2105
  }
2034
2106
  }
2035
2107
  }
2036
- function entryValue(entries, name) {
2037
- return entries?.find((s) => s.name === name)?.value;
2108
+ function entryValue(entries, name) {
2109
+ return entries?.find((s) => s.name === name)?.value;
2110
+ }
2111
+ function readEntryFromMutation(ctx, src) {
2112
+ const scopes = src.scope !== void 0 ? [src.scope] : ["stage", "workflow"], stageEntry = findOpenStageEntry(ctx.mutation);
2113
+ for (const scope of scopes) {
2114
+ const host = scope === "workflow" ? ctx.mutation.fields : stageEntry?.fields, value = entryValue(host, src.field);
2115
+ if (value !== void 0) return value;
2116
+ }
2117
+ }
2118
+ function evalOpPredicate({
2119
+ p,
2120
+ row,
2121
+ ctx
2122
+ }) {
2123
+ switch (p.type) {
2124
+ case "all":
2125
+ return p.of.every((sub) => evalOpPredicate({ p: sub, row, ctx }));
2126
+ case "any":
2127
+ return p.of.some((sub) => evalOpPredicate({ p: sub, row, ctx }));
2128
+ case "field":
2129
+ return row[p.field] === resolveOpValue(p.equals, ctx);
2130
+ }
2131
+ }
2132
+ async function deployOrRollback(args) {
2133
+ try {
2134
+ await args.deploy();
2135
+ } catch (guardError) {
2136
+ if (!args.reversible)
2137
+ throw new WorkflowStateDivergedError({
2138
+ instanceId: args.instanceId,
2139
+ guardError,
2140
+ reason: "the move created child instances a parent-only rollback cannot undo"
2141
+ });
2142
+ try {
2143
+ let patch = args.client.patch(args.instanceId).set(args.restore);
2144
+ 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);
2145
+ } catch (rollbackError) {
2146
+ throw new WorkflowStateDivergedError({
2147
+ instanceId: args.instanceId,
2148
+ guardError,
2149
+ rollbackError,
2150
+ reason: "rollback of the committed move failed"
2151
+ });
2152
+ }
2153
+ throw guardError instanceof PartialGuardDeployError ? new WorkflowStateDivergedError({
2154
+ instanceId: args.instanceId,
2155
+ guardError,
2156
+ reason: "a multi-guard deploy partially applied; the rolled-back move left orphaned guard locks"
2157
+ }) : guardError;
2158
+ }
2159
+ }
2160
+ async function persistThenDeploy({
2161
+ ctx,
2162
+ mutation,
2163
+ deploy
2164
+ }) {
2165
+ const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
2166
+ await deployOrRollback({
2167
+ client: ctx.client,
2168
+ instanceId: ctx.instance._id,
2169
+ committedRev: committed._rev,
2170
+ restore: instanceStateFields(ctx.instance),
2171
+ unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
2172
+ reversible: !spawned,
2173
+ deploy
2174
+ });
2175
+ }
2176
+ async function persistThenMaybeRefresh({
2177
+ ctx,
2178
+ mutation,
2179
+ stageName,
2180
+ didChangeState
2181
+ }) {
2182
+ await persistThenDeploy({
2183
+ ctx,
2184
+ mutation,
2185
+ deploy: () => didChangeState ? refreshStageGuards({ ctx, mutation, stageName }) : Promise.resolve()
2186
+ });
2187
+ }
2188
+ async function refreshStageGuards({
2189
+ ctx,
2190
+ mutation,
2191
+ stageName
2192
+ }) {
2193
+ await deployStageGuards({
2194
+ client: ctx.client,
2195
+ clientForGdr: ctx.clientForGdr,
2196
+ instance: materializeInstance(ctx.instance, mutation),
2197
+ definition: ctx.definition,
2198
+ stageName,
2199
+ now: ctx.now
2200
+ });
2201
+ }
2202
+ async function completeEffect(args) {
2203
+ const { client, instanceId, effectKey, status, outputs, ops, detail, error, durationMs, options } = args, ctx = await loadCallContext({ client, instanceId, options });
2204
+ return commitCompleteEffect({
2205
+ ctx,
2206
+ effectKey,
2207
+ status,
2208
+ outputs,
2209
+ ops,
2210
+ detail,
2211
+ error,
2212
+ durationMs,
2213
+ actor: options?.actor
2214
+ });
2215
+ }
2216
+ function buildEffectHistoryEntry(pending, outcome) {
2217
+ const { status, ranAt, actor, detail, error, durationMs, outputs } = outcome, resolvedActor = actor ?? pending.actor;
2218
+ return {
2219
+ _key: pending._key,
2220
+ name: pending.name,
2221
+ ...pending.title !== void 0 ? { title: pending.title } : {},
2222
+ ...pending.description !== void 0 ? { description: pending.description } : {},
2223
+ params: pending.params,
2224
+ origin: pending.origin,
2225
+ ...resolvedActor !== void 0 ? { actor: resolvedActor } : {},
2226
+ ranAt,
2227
+ ...durationMs !== void 0 ? { durationMs } : {},
2228
+ status,
2229
+ ...detail !== void 0 ? { detail } : {},
2230
+ ...error !== void 0 ? { error } : {},
2231
+ ...outputs !== void 0 ? { outputs } : {}
2232
+ };
2038
2233
  }
2039
- function readEntryFromMutation(ctx, src) {
2040
- const scopes = src.scope !== void 0 ? [src.scope] : ["stage", "workflow"], stageEntry = findOpenStageEntry(ctx.mutation);
2041
- for (const scope of scopes) {
2042
- const host = scope === "workflow" ? ctx.mutation.fields : stageEntry?.fields, value = entryValue(host, src.field);
2043
- if (value !== void 0) return value;
2044
- }
2234
+ function upsertEffectsContext({
2235
+ mutation,
2236
+ effectName,
2237
+ outputs
2238
+ }) {
2239
+ const existingIndex = mutation.effectsContext.findIndex((e) => e.name === effectName), entry = effectsContextJsonEntry(effectName, outputs);
2240
+ existingIndex >= 0 ? mutation.effectsContext[existingIndex] = entry : mutation.effectsContext.push(entry);
2045
2241
  }
2046
- function evalOpPredicate(p, row, ctx) {
2047
- switch (p.type) {
2048
- case "all":
2049
- return p.of.every((sub) => evalOpPredicate(sub, row, ctx));
2050
- case "any":
2051
- return p.of.some((sub) => evalOpPredicate(sub, row, ctx));
2052
- case "field":
2053
- return row[p.field] === resolveOpValue(p.equals, ctx);
2242
+ async function commitCompleteEffect({
2243
+ ctx,
2244
+ effectKey,
2245
+ status,
2246
+ outputs,
2247
+ ops,
2248
+ detail,
2249
+ error,
2250
+ durationMs,
2251
+ actor
2252
+ }) {
2253
+ const pending = ctx.instance.pendingEffects.find((e) => e._key === effectKey);
2254
+ if (pending === void 0)
2255
+ throw new Error(`Pending effect "${effectKey}" not found on instance ${ctx.instance._id}`);
2256
+ if (status === "failed" && ops !== void 0 && ops.length > 0)
2257
+ throw new EffectOpsInvalidError({
2258
+ effect: pending.name,
2259
+ issues: [
2260
+ "ops cannot accompany a failed completion \u2014 field.set the outcome on a done completion instead"
2261
+ ]
2262
+ });
2263
+ const validatedOps = ops !== void 0 ? validateEffectOps(ops, pending.name) : [], mutation = startMutation(ctx.instance);
2264
+ mutation.pendingEffects = mutation.pendingEffects.filter((e) => e._key !== effectKey);
2265
+ const ranAt = ctx.now;
2266
+ mutation.effectHistory.push(
2267
+ buildEffectHistoryEntry(pending, { status, ranAt, actor, detail, error, durationMs, outputs })
2268
+ );
2269
+ const wroteEffectsContext = status === "done" && outputs !== void 0;
2270
+ wroteEffectsContext && upsertEffectsContext({ mutation, effectName: pending.name, outputs }), mutation.history.push({
2271
+ _key: randomKey(),
2272
+ _type: "effectCompleted",
2273
+ at: ranAt,
2274
+ effectKey,
2275
+ effect: pending.name,
2276
+ status,
2277
+ ...outputs !== void 0 ? { outputs } : {},
2278
+ ...detail !== void 0 ? { detail } : {},
2279
+ ...actor !== void 0 ? { actor } : {}
2280
+ });
2281
+ const ranOps = runOps({
2282
+ ops: validatedOps,
2283
+ mutation,
2284
+ stage: ctx.instance.currentStage,
2285
+ origin: { effect: pending.name },
2286
+ params: pending.params,
2287
+ actor,
2288
+ self: selfGdr(ctx.instance),
2289
+ now: ranAt
2290
+ }), needsGuardRefresh = wroteEffectsContext || ranOps.some(isFieldOp);
2291
+ return await persistThenMaybeRefresh({
2292
+ ctx,
2293
+ mutation,
2294
+ stageName: ctx.instance.currentStage,
2295
+ didChangeState: needsGuardRefresh
2296
+ }), { effectKey, status };
2297
+ }
2298
+ function buildQueuedEffect({
2299
+ effect,
2300
+ origin,
2301
+ params,
2302
+ actor,
2303
+ now
2304
+ }) {
2305
+ const key = randomKey(), pending = {
2306
+ _key: key,
2307
+ _type: "pendingEffect",
2308
+ name: effect.name,
2309
+ ...effect.title !== void 0 ? { title: effect.title } : {},
2310
+ ...effect.description !== void 0 ? { description: effect.description } : {},
2311
+ ...effect.bindings !== void 0 ? { bindings: effect.bindings } : {},
2312
+ params,
2313
+ origin,
2314
+ ...actor !== void 0 ? { actor } : {},
2315
+ queuedAt: now
2316
+ }, history = {
2317
+ _key: randomKey(),
2318
+ _type: "effectQueued",
2319
+ at: now,
2320
+ effectKey: key,
2321
+ effect: effect.name,
2322
+ origin
2323
+ };
2324
+ return { pending, history };
2325
+ }
2326
+ async function queueEffects({
2327
+ ctx,
2328
+ mutation,
2329
+ effects,
2330
+ origin,
2331
+ actor,
2332
+ opts
2333
+ }) {
2334
+ if (!effects || effects.length === 0) return;
2335
+ const now = ctx.now, liveCtx = { ...ctx, instance: materializeInstance(ctx.instance, mutation) }, params = await ctxConditionParams(liveCtx, {
2336
+ ...opts?.activityName !== void 0 ? { activityName: opts.activityName } : {},
2337
+ ...actor !== void 0 ? { actor } : {},
2338
+ // Caller-supplied action params, readable in bindings as `$params.<name>`.
2339
+ vars: { params: opts?.callerParams ?? {} }
2340
+ });
2341
+ for (const effect of effects) {
2342
+ const resolved = await resolveBindings({
2343
+ bindings: effect.bindings,
2344
+ staticInput: effect.input,
2345
+ snapshot: ctx.snapshot,
2346
+ params
2347
+ }), { pending, history } = buildQueuedEffect({ effect, origin, params: resolved, actor, now });
2348
+ mutation.pendingEffects.push(pending), mutation.history.push(history);
2054
2349
  }
2055
2350
  }
2056
2351
  const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
@@ -2071,13 +2366,14 @@ async function discoverItems(args) {
2071
2366
  const { client, groq, params, perspective } = args, fetchOptions = perspective !== void 0 ? { perspective } : void 0;
2072
2367
  return client.fetch(groq, paramsForLake(params), fetchOptions);
2073
2368
  }
2074
- function clientForDiscovery(defaultClient, clientForGdr, subjectGdrUri) {
2075
- if (!subjectGdrUri || !subjectGdrUri.includes(":")) return defaultClient;
2076
- try {
2077
- return clientForGdr(parseGdr(subjectGdrUri));
2078
- } catch {
2079
- return defaultClient;
2080
- }
2369
+ function clientForDiscovery({
2370
+ defaultClient,
2371
+ clientForGdr,
2372
+ subjectGdrUri
2373
+ }) {
2374
+ if (subjectGdrUri === void 0) return defaultClient;
2375
+ const parsed = tryParseGdr(subjectGdrUri);
2376
+ return parsed !== void 0 ? clientForGdr(parsed) : defaultClient;
2081
2377
  }
2082
2378
  const ENGINE_ACTOR_ID_PREFIX = "engine.";
2083
2379
  function engineSystemActor(name) {
@@ -2090,25 +2386,49 @@ const MAX_SPAWN_DEPTH = 6, SUBWORKFLOWS_ALL_DONE = "count($subworkflows[status !
2090
2386
  function gateActor(outcome) {
2091
2387
  return engineSystemActor(outcome === "failed" ? "failWhen" : "completeWhen");
2092
2388
  }
2389
+ function applyGateOutcome(args) {
2390
+ applyActivityStatusChange({
2391
+ entry: args.entry,
2392
+ history: args.history,
2393
+ stage: args.stage,
2394
+ to: args.outcome,
2395
+ at: args.at,
2396
+ actor: gateActor(args.outcome)
2397
+ });
2398
+ }
2093
2399
  function effectiveCompleteWhen(activity) {
2094
2400
  return activity.completeWhen ?? (activity.subworkflows !== void 0 ? SUBWORKFLOWS_ALL_DONE : void 0);
2095
2401
  }
2096
- async function evaluateResolutionGate(ctx, activity, vars) {
2402
+ async function evaluateResolutionGate({
2403
+ ctx,
2404
+ activity,
2405
+ vars
2406
+ }) {
2097
2407
  const scope = { activityName: activity.name, vars };
2098
- if (activity.failWhen !== void 0 && await ctxEvaluateCondition(ctx, activity.failWhen, scope))
2408
+ if (activity.failWhen !== void 0 && await ctxEvaluateCondition({ ctx, condition: activity.failWhen, opts: scope }))
2099
2409
  return "failed";
2100
2410
  const completeWhen = effectiveCompleteWhen(activity);
2101
- if (completeWhen !== void 0 && await ctxEvaluateCondition(ctx, completeWhen, scope))
2411
+ if (completeWhen !== void 0 && await ctxEvaluateCondition({ ctx, condition: completeWhen, opts: scope }))
2102
2412
  return "done";
2103
2413
  }
2104
- async function spawnSubworkflows(ctx, mutation, activity, sub, actor) {
2414
+ async function spawnSubworkflows({
2415
+ ctx,
2416
+ mutation,
2417
+ activity,
2418
+ sub,
2419
+ actor
2420
+ }) {
2105
2421
  if (ctx.instance.ancestors.length + 1 > MAX_SPAWN_DEPTH) {
2106
2422
  const chain = [...ctx.instance.ancestors.map((a) => a.id), selfGdr(ctx.instance)].join(" \u2192 ");
2107
2423
  throw new Error(
2108
2424
  `Subworkflow depth limit (${MAX_SPAWN_DEPTH}) exceeded on activity "${activity.name}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
2109
2425
  );
2110
2426
  }
2111
- const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tag);
2427
+ const now = ctx.now, definition = await resolveDefinitionRef({
2428
+ client: ctx.client,
2429
+ ref: sub.definition,
2430
+ tag: ctx.instance.tag
2431
+ });
2112
2432
  if (definition === null) {
2113
2433
  const versionLabel = sub.definition.version === void 0 || sub.definition.version === "latest" ? "latest" : `v${sub.definition.version}`;
2114
2434
  throw new Error(
@@ -2118,16 +2438,16 @@ async function spawnSubworkflows(ctx, mutation, activity, sub, actor) {
2118
2438
  const parentScope = await ctxConditionParams(ctx, {
2119
2439
  activityName: activity.name,
2120
2440
  ...actor ? { actor } : {}
2121
- }), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
2441
+ }), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff({ ctx, sub, parentScope }), refs = [];
2122
2442
  for (const row of rows) {
2123
- const initialFields = await projectRowFields(
2443
+ const initialFields = await projectRowFields({
2124
2444
  ctx,
2125
2445
  sub,
2126
- definition,
2446
+ childDefinition: definition,
2127
2447
  parentScope,
2128
2448
  row,
2129
2449
  discoveryResource
2130
- ), { ref, body } = await prepareChildInstance({
2450
+ }), { ref, body } = await prepareChildInstance({
2131
2451
  client: ctx.client,
2132
2452
  parent: ctx.instance,
2133
2453
  definition,
@@ -2144,7 +2464,11 @@ async function resolveSpawnRows(ctx, sub) {
2144
2464
  const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
2145
2465
  let value, discoveryResource;
2146
2466
  if (groq.includes("*")) {
2147
- const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
2467
+ const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery({
2468
+ defaultClient: ctx.client,
2469
+ clientForGdr: ctx.clientForGdr,
2470
+ subjectGdrUri: routingUri
2471
+ });
2148
2472
  client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
2149
2473
  client,
2150
2474
  groq,
@@ -2160,7 +2484,14 @@ async function resolveSpawnRows(ctx, sub) {
2160
2484
  }
2161
2485
  return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
2162
2486
  }
2163
- async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
2487
+ async function projectRowFields({
2488
+ ctx,
2489
+ sub,
2490
+ childDefinition,
2491
+ parentScope,
2492
+ row,
2493
+ discoveryResource
2494
+ }) {
2164
2495
  const out = [];
2165
2496
  for (const [name, groq] of Object.entries(sub.with ?? {})) {
2166
2497
  const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
@@ -2168,11 +2499,15 @@ async function projectRowFields(ctx, sub, childDefinition, parentScope, row, dis
2168
2499
  throw new Error(
2169
2500
  `subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
2170
2501
  );
2171
- const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
2172
- parentResource: ctx.instance.workflowResource,
2173
- discoveryResource,
2174
- entryName: name,
2175
- childName: childDefinition.name
2502
+ const raw = await runGroq({ groq, params: { ...parentScope, row }, snapshot: ctx.snapshot }), value = canonicaliseSpawnValue({
2503
+ kind: declared.type,
2504
+ value: raw,
2505
+ cx: {
2506
+ parentResource: ctx.instance.workflowResource,
2507
+ discoveryResource,
2508
+ entryName: name,
2509
+ childName: childDefinition.name
2510
+ }
2176
2511
  });
2177
2512
  value != null && out.push({
2178
2513
  type: declared.type,
@@ -2182,15 +2517,23 @@ async function projectRowFields(ctx, sub, childDefinition, parentScope, row, dis
2182
2517
  }
2183
2518
  return out;
2184
2519
  }
2185
- async function resolveContextHandoff(ctx, sub, parentScope) {
2520
+ async function resolveContextHandoff({
2521
+ ctx,
2522
+ sub,
2523
+ parentScope
2524
+ }) {
2186
2525
  const out = {};
2187
2526
  for (const [name, groq] of Object.entries(sub.context ?? {})) {
2188
- const v2 = await runGroq(groq, parentScope, ctx.snapshot);
2527
+ const v2 = await runGroq({ groq, params: parentScope, snapshot: ctx.snapshot });
2189
2528
  v2 != null && (typeof v2 == "string" || typeof v2 == "number" || typeof v2 == "boolean" || typeof v2 == "object" && "id" in v2 && "type" in v2) && (out[name] = v2);
2190
2529
  }
2191
2530
  return out;
2192
2531
  }
2193
- function canonicaliseSpawnValue(kind, value, cx) {
2532
+ function canonicaliseSpawnValue({
2533
+ kind,
2534
+ value,
2535
+ cx
2536
+ }) {
2194
2537
  return kind === "doc.ref" ? coerceSpawnGdr(value, cx) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, cx)).filter((v2) => v2 !== null) : value;
2195
2538
  }
2196
2539
  function assertBareIdRootsCorrectly(id, cx) {
@@ -2213,7 +2556,11 @@ function coerceSpawnGdr(raw, cx) {
2213
2556
  }
2214
2557
  return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
2215
2558
  }
2216
- async function resolveDefinitionRef(client, ref, tag) {
2559
+ async function resolveDefinitionRef({
2560
+ client,
2561
+ ref,
2562
+ tag
2563
+ }) {
2217
2564
  const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
2218
2565
  return wantsExplicit && (params.version = ref.version), await client.fetch(
2219
2566
  definitionLookupGroq(wantsExplicit),
@@ -2221,7 +2568,7 @@ async function resolveDefinitionRef(client, ref, tag) {
2221
2568
  ) ?? null;
2222
2569
  }
2223
2570
  async function prepareChildInstance(args) {
2224
- 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 = [
2571
+ 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 = [
2225
2572
  ...parent.ancestors,
2226
2573
  {
2227
2574
  id: selfGdr(parent),
@@ -2238,7 +2585,7 @@ async function prepareChildInstance(args) {
2238
2585
  workflowResource,
2239
2586
  definitionName: definition.name,
2240
2587
  ...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {},
2241
- recordDiscard: recordFieldDiscards(fieldDiscards, "workflow", now)
2588
+ recordDiscard: recordFieldDiscards({ target: fieldDiscards, scope: "workflow", at: now })
2242
2589
  },
2243
2590
  randomKey
2244
2591
  }), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
@@ -2273,7 +2620,7 @@ async function prepareChildInstance(args) {
2273
2620
  }
2274
2621
  async function subworkflowRows(ctx, refs) {
2275
2622
  if (refs.length === 0) return [];
2276
- const ids = refs.map((r) => gdrToBareId(r.id));
2623
+ const ids = refs.map((r) => toBareId(r.id));
2277
2624
  return (await ctx.client.fetch("*[_id in $ids]{_id, currentStage, completedAt}", { ids })).map((c) => ({
2278
2625
  _id: c._id,
2279
2626
  stage: c.currentStage,
@@ -2281,13 +2628,14 @@ async function subworkflowRows(ctx, refs) {
2281
2628
  }));
2282
2629
  }
2283
2630
  async function invokeActivity(args) {
2284
- const { client, instanceId, activity: activityName, options } = args, ctx = await loadContext(client, instanceId, {
2285
- ...options?.clock ? { clock: options.clock } : {},
2286
- ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
2287
- });
2288
- return commitInvoke(ctx, activityName, options?.actor);
2289
- }
2290
- async function commitInvoke(ctx, activityName, actor) {
2631
+ const { client, instanceId, activity: activityName, options } = args, ctx = await loadCallContext({ client, instanceId, options });
2632
+ return commitInvoke({ ctx, activityName, actor: options?.actor });
2633
+ }
2634
+ async function commitInvoke({
2635
+ ctx,
2636
+ activityName,
2637
+ actor
2638
+ }) {
2291
2639
  const { activity } = findActivityInCurrentStage(ctx, activityName), entry = findCurrentActivities(ctx.instance).find((t) => t.name === activityName);
2292
2640
  if (entry === void 0)
2293
2641
  throw new Error(
@@ -2296,9 +2644,15 @@ async function commitInvoke(ctx, activityName, actor) {
2296
2644
  if (entry.status !== "pending")
2297
2645
  return { invoked: !1, activity: activityName };
2298
2646
  const mutation = startMutation(ctx.instance), mutEntry = requireMutationActivityEntry(mutation, activityName);
2299
- return await activateActivity(ctx, mutation, activity, mutEntry, actor), await persist(ctx, mutation), { invoked: !0, activity: activityName };
2300
- }
2301
- async function activateActivity(ctx, mutation, activity, entry, actor) {
2647
+ return await activateActivity({ ctx, mutation, activity, entry: mutEntry, actor }), await persist(ctx, mutation), { invoked: !0, activity: activityName };
2648
+ }
2649
+ async function activateActivity({
2650
+ ctx,
2651
+ mutation,
2652
+ activity,
2653
+ entry,
2654
+ actor
2655
+ }) {
2302
2656
  const now = ctx.now;
2303
2657
  if (entry.status = "active", entry.startedAt = now, activity.fields !== void 0 && activity.fields.length > 0) {
2304
2658
  const stage = findStage(ctx.definition, mutation.currentStage);
@@ -2308,7 +2662,7 @@ async function activateActivity(ctx, mutation, activity, entry, actor) {
2308
2662
  stage,
2309
2663
  activity,
2310
2664
  now,
2311
- recordDiscard: recordFieldDiscards(mutation.history, "activity", now)
2665
+ recordDiscard: recordFieldDiscards({ target: mutation.history, scope: "activity", at: now })
2312
2666
  });
2313
2667
  }
2314
2668
  runOps({
@@ -2327,19 +2681,25 @@ async function activateActivity(ctx, mutation, activity, entry, actor) {
2327
2681
  stage: mutation.currentStage,
2328
2682
  activity: activity.name,
2329
2683
  ...actor !== void 0 ? { actor } : {}
2330
- }), await queueEffects(
2684
+ }), await queueEffects({
2331
2685
  ctx,
2332
2686
  mutation,
2333
- activity.effects,
2334
- { kind: "activity", name: activity.name },
2687
+ effects: activity.effects,
2688
+ origin: { kind: "activity", name: activity.name },
2335
2689
  actor,
2336
- {
2690
+ opts: {
2337
2691
  activityName: activity.name
2338
2692
  }
2339
- );
2693
+ });
2340
2694
  let pendingChildren;
2341
2695
  if (activity.subworkflows !== void 0) {
2342
- const createsBefore = mutation.pendingCreates.length, refs = await spawnSubworkflows(ctx, mutation, activity, activity.subworkflows, actor);
2696
+ const createsBefore = mutation.pendingCreates.length, refs = await spawnSubworkflows({
2697
+ ctx,
2698
+ mutation,
2699
+ activity,
2700
+ sub: activity.subworkflows,
2701
+ actor
2702
+ });
2343
2703
  entry.spawnedInstances = refs, pendingChildren = mutation.pendingCreates.slice(createsBefore).map((c) => ({ _id: c._id, stage: c.currentStage, status: "active" }));
2344
2704
  for (const ref of refs)
2345
2705
  mutation.history.push({
@@ -2350,24 +2710,42 @@ async function activateActivity(ctx, mutation, activity, entry, actor) {
2350
2710
  instanceRef: ref
2351
2711
  });
2352
2712
  }
2353
- await autoResolveOnActivate(ctx, mutation, activity, entry, now, pendingChildren) || resolveMachineStep(mutation, activity, entry, now);
2354
- }
2355
- async function autoResolveOnActivate(ctx, mutation, activity, entry, now, pendingChildren) {
2713
+ await autoResolveOnActivate({
2714
+ ctx,
2715
+ mutation,
2716
+ activity,
2717
+ entry,
2718
+ now,
2719
+ ...pendingChildren !== void 0 ? { pendingChildren } : {}
2720
+ }) || resolveMachineStep({ mutation, activity, entry, now });
2721
+ }
2722
+ async function autoResolveOnActivate({
2723
+ ctx,
2724
+ mutation,
2725
+ activity,
2726
+ entry,
2727
+ now,
2728
+ pendingChildren
2729
+ }) {
2356
2730
  if (activity.failWhen === void 0 && effectiveCompleteWhen(activity) === void 0) return !1;
2357
- const vars = pendingChildren !== void 0 ? { subworkflows: pendingChildren } : await activityConditionVars(ctx, entry), outcome = await evaluateResolutionGate(ctx, activity, vars);
2358
- return outcome === void 0 ? !1 : (applyActivityStatusChange({
2731
+ const vars = pendingChildren !== void 0 ? { subworkflows: pendingChildren } : await activityConditionVars(ctx, entry), outcome = await evaluateResolutionGate({ ctx, activity, vars });
2732
+ return outcome === void 0 ? !1 : (applyGateOutcome({
2359
2733
  entry,
2360
2734
  history: mutation.history,
2361
2735
  stage: mutation.currentStage,
2362
- to: outcome,
2363
2736
  at: now,
2364
- actor: gateActor(outcome)
2737
+ outcome
2365
2738
  }), !0);
2366
2739
  }
2367
2740
  async function activityConditionVars(ctx, entry) {
2368
2741
  return entry.spawnedInstances === void 0 || entry.spawnedInstances.length === 0 ? { subworkflows: [] } : { subworkflows: await subworkflowRows(ctx, entry.spawnedInstances) };
2369
2742
  }
2370
- function resolveMachineStep(mutation, activity, entry, now) {
2743
+ function resolveMachineStep({
2744
+ mutation,
2745
+ activity,
2746
+ entry,
2747
+ now
2748
+ }) {
2371
2749
  const waitsForActor = activity.actions !== void 0 && activity.actions.length > 0, waitsForCondition = activity.completeWhen !== void 0 || activity.subworkflows !== void 0;
2372
2750
  waitsForActor || waitsForCondition || applyActivityStatusChange({
2373
2751
  entry,
@@ -2381,19 +2759,25 @@ function resolveMachineStep(mutation, activity, entry, now) {
2381
2759
  async function buildStageActivities(ctx, stage) {
2382
2760
  const entries = [];
2383
2761
  for (const activity of stage.activities ?? []) {
2384
- const outcome = await ctxEvaluateConditionOutcome(ctx, activity.filter, {
2385
- activityName: activity.name
2762
+ const outcome = await ctxEvaluateConditionOutcome({
2763
+ ctx,
2764
+ condition: activity.filter,
2765
+ opts: { activityName: activity.name }
2386
2766
  }), inScope = outcome !== "unsatisfied";
2387
2767
  entries.push({
2388
2768
  _key: randomKey(),
2389
2769
  name: activity.name,
2390
2770
  status: inScope ? "pending" : "skipped",
2391
- ...activity.filter !== void 0 ? { filterEvaluation: buildFilterEvaluation(ctx.now, activity.filter, outcome) } : {}
2771
+ ...activity.filter !== void 0 ? { filterEvaluation: buildFilterEvaluation({ at: ctx.now, filter: activity.filter, outcome }) } : {}
2392
2772
  });
2393
2773
  }
2394
2774
  return entries;
2395
2775
  }
2396
- function buildFilterEvaluation(at, filter, outcome) {
2776
+ function buildFilterEvaluation({
2777
+ at,
2778
+ filter,
2779
+ outcome
2780
+ }) {
2397
2781
  return outcome === "unevaluable" ? {
2398
2782
  at,
2399
2783
  truthy: !1,
@@ -2405,13 +2789,16 @@ function isTerminalStage(stage) {
2405
2789
  return (stage.transitions ?? []).length === 0;
2406
2790
  }
2407
2791
  async function setStage(args) {
2408
- const { client, instanceId, targetStage, reason, options } = args, ctx = await loadContext(client, instanceId, {
2409
- ...options?.clock ? { clock: options.clock } : {},
2410
- ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
2411
- });
2412
- return commitSetStage(ctx, targetStage, reason, options?.actor);
2413
- }
2414
- async function enterStage(ctx, mutation, nextStage, actor, at) {
2792
+ const { client, instanceId, targetStage, reason, options } = args, ctx = await loadCallContext({ client, instanceId, options });
2793
+ return commitSetStage({ ctx, targetStage, reason, actor: options?.actor });
2794
+ }
2795
+ async function enterStage({
2796
+ ctx,
2797
+ mutation,
2798
+ nextStage,
2799
+ actor,
2800
+ at
2801
+ }) {
2415
2802
  mutation.currentStage = nextStage.name;
2416
2803
  const nextStageEntry = {
2417
2804
  _key: randomKey(),
@@ -2422,7 +2809,7 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
2422
2809
  instance: ctx.instance,
2423
2810
  stage: nextStage,
2424
2811
  now: at,
2425
- recordDiscard: recordFieldDiscards(mutation.history, "stage", at)
2812
+ recordDiscard: recordFieldDiscards({ target: mutation.history, scope: "stage", at })
2426
2813
  }),
2427
2814
  activities: await buildStageActivities(ctx, nextStage)
2428
2815
  };
@@ -2430,14 +2817,19 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
2430
2817
  for (const activity of nextStage.activities ?? []) {
2431
2818
  if (activity.activation !== "auto") continue;
2432
2819
  const entry = nextStageEntry.activities.find((t) => t.name === activity.name);
2433
- entry && entry.status === "pending" && await activateActivity(ctx, mutation, activity, entry, actor);
2820
+ entry && entry.status === "pending" && await activateActivity({ ctx, mutation, activity, entry, actor });
2434
2821
  }
2435
2822
  isTerminalStage(nextStage) && (mutation.completedAt = at);
2436
2823
  }
2437
2824
  function isTerminal(ctx) {
2438
2825
  return ctx.instance.completedAt !== void 0;
2439
2826
  }
2440
- async function commitSetStage(ctx, targetStage, reason, actor) {
2827
+ async function commitSetStage({
2828
+ ctx,
2829
+ targetStage,
2830
+ reason,
2831
+ actor
2832
+ }) {
2441
2833
  if (isTerminal(ctx))
2442
2834
  return { fired: !1 };
2443
2835
  const currentStage = findStage(ctx.definition, ctx.instance.currentStage), nextStage = findStage(ctx.definition, targetStage);
@@ -2456,18 +2848,28 @@ async function commitSetStage(ctx, targetStage, reason, actor) {
2456
2848
  })
2457
2849
  );
2458
2850
  const priorEntry = findOpenStageEntry(mutation);
2459
- return priorEntry !== void 0 && (priorEntry.exitedAt = at), await enterStage(ctx, mutation, nextStage, actor, at), await persistStageMove(ctx, mutation, currentStage.name, nextStage.name), {
2851
+ return priorEntry !== void 0 && (priorEntry.exitedAt = at), await enterStage({ ctx, mutation, nextStage, actor, at }), await persistStageMove({
2852
+ ctx,
2853
+ mutation,
2854
+ exitedStage: currentStage.name,
2855
+ enteredStage: nextStage.name
2856
+ }), {
2460
2857
  fired: !0,
2461
2858
  fromStage: currentStage.name,
2462
2859
  toStage: nextStage.name,
2463
2860
  transition: transitionName
2464
2861
  };
2465
2862
  }
2466
- async function persistStageMove(ctx, mutation, exitedStage, enteredStage) {
2467
- await persistThenDeploy(
2863
+ async function persistStageMove({
2864
+ ctx,
2865
+ mutation,
2866
+ exitedStage,
2867
+ enteredStage
2868
+ }) {
2869
+ await persistThenDeploy({
2468
2870
  ctx,
2469
2871
  mutation,
2470
- () => deployStageGuards({
2872
+ deploy: () => deployStageGuards({
2471
2873
  client: ctx.client,
2472
2874
  clientForGdr: ctx.clientForGdr,
2473
2875
  instance: materializeInstance(ctx.instance, mutation),
@@ -2475,7 +2877,7 @@ async function persistStageMove(ctx, mutation, exitedStage, enteredStage) {
2475
2877
  stageName: enteredStage,
2476
2878
  now: ctx.now
2477
2879
  })
2478
- ), await retractStageGuards({
2880
+ }), await retractStageGuards({
2479
2881
  client: ctx.client,
2480
2882
  clientForGdr: ctx.clientForGdr,
2481
2883
  instance: ctx.instance,
@@ -2500,16 +2902,16 @@ async function commitTransition(ctx, actor) {
2500
2902
  actor,
2501
2903
  self: selfGdr(ctx.instance),
2502
2904
  now: at
2503
- }), await queueEffects(
2905
+ }), await queueEffects({
2504
2906
  ctx,
2505
2907
  mutation,
2506
- transition.effects,
2507
- {
2908
+ effects: transition.effects,
2909
+ origin: {
2508
2910
  kind: "transition",
2509
2911
  name: transition.name
2510
2912
  },
2511
2913
  actor
2512
- ), mutation.history.push(
2914
+ }), mutation.history.push(
2513
2915
  ...stageTransitionHistory({
2514
2916
  fromStage: currentStage.name,
2515
2917
  toStage: transition.to,
@@ -2521,7 +2923,12 @@ async function commitTransition(ctx, actor) {
2521
2923
  const priorEntry = findOpenStageEntry(mutation);
2522
2924
  priorEntry !== void 0 && (priorEntry.exitedAt = at);
2523
2925
  const nextStage = findStage(ctx.definition, transition.to);
2524
- return await enterStage(ctx, mutation, nextStage, actor, at), await persistStageMove(ctx, mutation, currentStage.name, nextStage.name), {
2926
+ return await enterStage({ ctx, mutation, nextStage, actor, at }), await persistStageMove({
2927
+ ctx,
2928
+ mutation,
2929
+ exitedStage: currentStage.name,
2930
+ enteredStage: nextStage.name
2931
+ }), {
2525
2932
  fired: !0,
2526
2933
  fromStage: currentStage.name,
2527
2934
  toStage: transition.to,
@@ -2530,20 +2937,30 @@ async function commitTransition(ctx, actor) {
2530
2937
  }
2531
2938
  async function pickTransition(ctx, stage) {
2532
2939
  for (const candidate of stage.transitions ?? []) {
2533
- const outcome = await ctxEvaluateConditionOutcome(ctx, candidate.filter);
2940
+ const outcome = await ctxEvaluateConditionOutcome({ ctx, condition: candidate.filter });
2534
2941
  if (outcome === "satisfied") return candidate;
2535
2942
  if (outcome === "unevaluable") return;
2536
2943
  }
2537
2944
  }
2538
2945
  async function evaluateAutoTransitions(args) {
2539
- const { client, instanceId, options, clientForGdr, clock, overlay } = args, ctx = await loadContext(client, instanceId, {
2540
- ...clientForGdr ? { clientForGdr } : {},
2541
- ...clock ? { clock } : {},
2542
- ...overlay ? { overlay } : {}
2946
+ const { client, instanceId, options, clientForGdr, clock, overlay } = args, ctx = await loadContext({
2947
+ client,
2948
+ instanceId,
2949
+ options: {
2950
+ ...clientForGdr ? { clientForGdr } : {},
2951
+ ...clock ? { clock } : {},
2952
+ ...overlay ? { overlay } : {}
2953
+ }
2543
2954
  });
2544
2955
  return commitTransition(ctx, options?.actor);
2545
2956
  }
2546
- async function primeInitialStage(client, instanceId, actor, clientForGdr, clock) {
2957
+ async function primeInitialStage({
2958
+ client,
2959
+ instanceId,
2960
+ actor,
2961
+ clientForGdr,
2962
+ clock
2963
+ }) {
2547
2964
  const instance = await client.getDocument(instanceId);
2548
2965
  if (!instance || instance.stages.length > 0) return;
2549
2966
  const definition = parseDefinitionSnapshot(instance), stage = definition.stages.find((s) => s.name === instance.currentStage);
@@ -2563,7 +2980,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
2563
2980
  instance,
2564
2981
  stage,
2565
2982
  now,
2566
- recordDiscard: recordFieldDiscards(discards, "stage", now)
2983
+ recordDiscard: recordFieldDiscards({ target: discards, scope: "stage", at: now })
2567
2984
  }),
2568
2985
  activities: await buildStageActivities(ctx, stage)
2569
2986
  }, committed = await client.patch(instance._id).set({
@@ -2588,7 +3005,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
2588
3005
  stageName: stage.name,
2589
3006
  now
2590
3007
  })
2591
- }), await autoActivatePrimedActivities(
3008
+ }), await autoActivatePrimedActivities({
2592
3009
  client,
2593
3010
  instanceId,
2594
3011
  definition,
@@ -2596,9 +3013,17 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
2596
3013
  actor,
2597
3014
  clientForGdr,
2598
3015
  clock
2599
- );
3016
+ });
2600
3017
  }
2601
- async function autoActivatePrimedActivities(client, instanceId, definition, stage, actor, clientForGdr, clock) {
3018
+ async function autoActivatePrimedActivities({
3019
+ client,
3020
+ instanceId,
3021
+ definition,
3022
+ stage,
3023
+ actor,
3024
+ clientForGdr,
3025
+ clock
3026
+ }) {
2602
3027
  const resolvedClientForGdr = clientForGdr ?? (() => client);
2603
3028
  for (const activity of stage.activities ?? []) {
2604
3029
  if (activity.activation !== "auto") continue;
@@ -2611,7 +3036,7 @@ async function autoActivatePrimedActivities(client, instanceId, definition, stag
2611
3036
  definition,
2612
3037
  ...clock ? { clock } : {}
2613
3038
  }), mutation = startMutation(updated), mutEntry = currentActivities(mutation).find((t) => t.name === activity.name);
2614
- mutEntry && mutEntry.status === "pending" && (await activateActivity(updatedCtx, mutation, activity, mutEntry, actor), await persist(updatedCtx, mutation));
3039
+ mutEntry && mutEntry.status === "pending" && (await activateActivity({ ctx: updatedCtx, mutation, activity, entry: mutEntry, actor }), await persist(updatedCtx, mutation));
2615
3040
  }
2616
3041
  }
2617
3042
  const CASCADE_LIMIT = 100;
@@ -2620,20 +3045,30 @@ async function gatherAutoResolveCandidates(ctx, activities) {
2620
3045
  for (const activity of activities) {
2621
3046
  const entry = currentActivitiesList.find((e) => e.name === activity.name);
2622
3047
  if (entry?.status !== "active") continue;
2623
- const outcome = await evaluateResolutionGate(
3048
+ const outcome = await evaluateResolutionGate({
2624
3049
  ctx,
2625
3050
  activity,
2626
- await activityConditionVars(ctx, entry)
2627
- );
3051
+ vars: await activityConditionVars(ctx, entry)
3052
+ });
2628
3053
  outcome !== void 0 && candidates.push({ activity, outcome });
2629
3054
  }
2630
3055
  return candidates;
2631
3056
  }
2632
- async function resolveCompleteWhen(client, instanceId, clientForGdr, clock, overlay) {
2633
- const ctx = await loadContext(client, instanceId, {
2634
- ...clientForGdr ? { clientForGdr } : {},
2635
- ...clock ? { clock } : {},
2636
- ...overlay ? { overlay } : {}
3057
+ async function resolveCompleteWhen({
3058
+ client,
3059
+ instanceId,
3060
+ clientForGdr,
3061
+ clock,
3062
+ overlay
3063
+ }) {
3064
+ const ctx = await loadContext({
3065
+ client,
3066
+ instanceId,
3067
+ options: {
3068
+ ...clientForGdr ? { clientForGdr } : {},
3069
+ ...clock ? { clock } : {},
3070
+ ...overlay ? { overlay } : {}
3071
+ }
2637
3072
  }), stage = findStage(ctx.definition, ctx.instance.currentStage), gatedActivities = (stage.activities ?? []).filter(
2638
3073
  (t) => effectiveCompleteWhen(t) !== void 0 || t.failWhen !== void 0
2639
3074
  );
@@ -2644,21 +3079,27 @@ async function resolveCompleteWhen(client, instanceId, clientForGdr, clock, over
2644
3079
  let count = 0;
2645
3080
  for (const { activity, outcome } of candidates) {
2646
3081
  const mutEntry = currentActivities(mutation).find((t) => t.name === activity.name);
2647
- mutEntry === void 0 || mutEntry.status !== "active" || (applyActivityStatusChange({
3082
+ mutEntry === void 0 || mutEntry.status !== "active" || (applyGateOutcome({
2648
3083
  entry: mutEntry,
2649
3084
  history: mutation.history,
2650
3085
  stage: stage.name,
2651
- to: outcome,
2652
3086
  at: ctx.now,
2653
- actor: gateActor(outcome)
3087
+ outcome
2654
3088
  }), count++);
2655
3089
  }
2656
3090
  return count > 0 && await persist(ctx, mutation), count;
2657
3091
  }
2658
- async function cascadeAutoTransitions(client, instanceId, actor, clientForGdr, clock, overlay) {
3092
+ async function cascadeAutoTransitions({
3093
+ client,
3094
+ instanceId,
3095
+ actor,
3096
+ clientForGdr,
3097
+ clock,
3098
+ overlay
3099
+ }) {
2659
3100
  let count = 0;
2660
3101
  for (; ; ) {
2661
- if (await resolveCompleteWhen(client, instanceId, clientForGdr, clock, overlay), !(await evaluateAutoTransitions({
3102
+ if (await resolveCompleteWhen({ client, instanceId, clientForGdr, clock, overlay }), !(await evaluateAutoTransitions({
2662
3103
  client,
2663
3104
  instanceId,
2664
3105
  ...actor !== void 0 ? { options: { actor } } : {},
@@ -2670,14 +3111,19 @@ async function cascadeAutoTransitions(client, instanceId, actor, clientForGdr, c
2670
3111
  throw new CascadeLimitError({ instanceId, limit: CASCADE_LIMIT });
2671
3112
  }
2672
3113
  }
2673
- async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
3114
+ async function findResolvedParentSpawn({
3115
+ client,
3116
+ child,
3117
+ clientForGdr,
3118
+ clock
3119
+ }) {
2674
3120
  const parentRef = child.ancestors.at(-1);
2675
3121
  if (parentRef === void 0) return;
2676
- const parent = await client.getDocument(gdrToBareId(parentRef.id));
3122
+ const parent = await client.getDocument(toBareId(parentRef.id));
2677
3123
  if (!parent || parent._type !== WORKFLOW_INSTANCE_TYPE || typeof parent.definitionSnapshot != "string") return;
2678
3124
  const definition = parseDefinitionSnapshot(parent), stage = definition.stages.find((s) => s.name === parent.currentStage);
2679
3125
  if (stage === void 0) return;
2680
- 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);
3126
+ 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);
2681
3127
  if (activity?.subworkflows === void 0) return;
2682
3128
  const entry = parentCurrentActivities.find((e) => e.name === activity.name);
2683
3129
  if (entry === void 0 || entry.status !== "active") return;
@@ -2687,18 +3133,29 @@ async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
2687
3133
  instance: parent,
2688
3134
  definition,
2689
3135
  ...clock ? { clock } : {}
2690
- }), outcome = await evaluateResolutionGate(
3136
+ }), outcome = await evaluateResolutionGate({
2691
3137
  ctx,
2692
3138
  activity,
2693
- await activityConditionVars(ctx, entry)
2694
- );
3139
+ vars: await activityConditionVars(ctx, entry)
3140
+ });
2695
3141
  if (outcome !== void 0)
2696
3142
  return { parent, definition, stage, activity, outcome };
2697
3143
  }
2698
- async function propagateToAncestors(client, instanceId, actor, clientForGdr, clock) {
3144
+ async function propagateToAncestors({
3145
+ client,
3146
+ instanceId,
3147
+ actor,
3148
+ clientForGdr,
3149
+ clock
3150
+ }) {
2699
3151
  const instance = await client.getDocument(instanceId);
2700
3152
  if (!instance) return;
2701
- const resolvedClientForGdr = clientForGdr ?? (() => client), found = await findResolvedParentSpawn(client, instance, resolvedClientForGdr, clock);
3153
+ const resolvedClientForGdr = clientForGdr ?? (() => client), found = await findResolvedParentSpawn({
3154
+ client,
3155
+ child: instance,
3156
+ clientForGdr: resolvedClientForGdr,
3157
+ clock
3158
+ });
2702
3159
  if (found === void 0) return;
2703
3160
  const { parent, definition, stage, activity, outcome } = found, ctx = await buildEngineContext({
2704
3161
  client,
@@ -2707,14 +3164,13 @@ async function propagateToAncestors(client, instanceId, actor, clientForGdr, clo
2707
3164
  definition,
2708
3165
  ...clock ? { clock } : {}
2709
3166
  }), mutation = startMutation(parent), mutEntry = currentActivities(mutation).find((e) => e.name === activity.name);
2710
- mutEntry !== void 0 && (applyActivityStatusChange({
3167
+ mutEntry !== void 0 && (applyGateOutcome({
2711
3168
  entry: mutEntry,
2712
3169
  history: mutation.history,
2713
3170
  stage: stage.name,
2714
- to: outcome,
2715
3171
  at: ctx.now,
2716
- actor: gateActor(outcome)
2717
- }), await persist(ctx, mutation), await cascadeAutoTransitions(client, parent._id, actor, clientForGdr, clock), await propagateToAncestors(client, parent._id, actor, clientForGdr, clock));
3172
+ outcome
3173
+ }), await persist(ctx, mutation), await cascadeAutoTransitions({ client, instanceId: parent._id, actor, clientForGdr, clock }), await propagateToAncestors({ client, instanceId: parent._id, actor, clientForGdr, clock }));
2718
3174
  }
2719
3175
  const parsedFilters = /* @__PURE__ */ new Map();
2720
3176
  async function matchesFilter(args) {
@@ -2755,7 +3211,12 @@ async function resolveAccess(client, args = {}) {
2755
3211
  );
2756
3212
  return { actor: overrideActor };
2757
3213
  }
2758
- 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]);
3214
+ const actorPromise = overrideActor !== void 0 ? Promise.resolve(overrideActor) : cachedActor(client, requestFn), grantsPromise = resolveGrants({
3215
+ overrideGrants,
3216
+ grantsFromPath: args.grantsFromPath,
3217
+ client,
3218
+ requestFn
3219
+ }), [actor, grants] = await Promise.all([actorPromise, grantsPromise]);
2759
3220
  if (actor === void 0)
2760
3221
  throw new Error(
2761
3222
  "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."
@@ -2770,10 +3231,19 @@ function cachedActor(client, requestFn) {
2770
3231
  });
2771
3232
  return actorCache.set(client, pending), pending;
2772
3233
  }
2773
- function resolveGrants(overrideGrants, grantsFromPath, client, requestFn) {
2774
- return overrideGrants !== void 0 ? Promise.resolve(overrideGrants) : grantsFromPath !== void 0 ? cachedGrants(client, requestFn, grantsFromPath) : Promise.resolve(void 0);
2775
- }
2776
- function cachedGrants(client, requestFn, resourcePath) {
3234
+ function resolveGrants({
3235
+ overrideGrants,
3236
+ grantsFromPath,
3237
+ client,
3238
+ requestFn
3239
+ }) {
3240
+ return overrideGrants !== void 0 ? Promise.resolve(overrideGrants) : grantsFromPath !== void 0 ? cachedGrants({ client, requestFn, resourcePath: grantsFromPath }) : Promise.resolve(void 0);
3241
+ }
3242
+ function cachedGrants({
3243
+ client,
3244
+ requestFn,
3245
+ resourcePath
3246
+ }) {
2777
3247
  let byPath = grantsCache.get(client);
2778
3248
  byPath === void 0 && (byPath = /* @__PURE__ */ new Map(), grantsCache.set(client, byPath));
2779
3249
  let cached = byPath.get(resourcePath);
@@ -2873,9 +3343,7 @@ function slotWindowOpen(instance, slot) {
2873
3343
  if (instance.completedAt !== void 0) return { open: !1, detail: "instance completed" };
2874
3344
  if (instance.abortedAt !== void 0) return { open: !1, detail: "instance aborted" };
2875
3345
  if (slot.scope !== "activity") return { open: !0 };
2876
- const status = findOpenStageEntry(instance)?.activities.find(
2877
- (t) => t.name === slot.activity
2878
- )?.status;
3346
+ const status = findCurrentActivityEntry(instance, slot.activity)?.status;
2879
3347
  return status === "active" ? { open: !0 } : { open: !1, detail: `activity "${slot.activity}" is ${status ?? "not active"}` };
2880
3348
  }
2881
3349
  function readSlotValue(instance, slot) {
@@ -2910,7 +3378,7 @@ async function evaluateInstance(args) {
2910
3378
  const { actor, grants } = await resolveAccess(client, {
2911
3379
  ...args.access !== void 0 ? { override: args.access } : {},
2912
3380
  ...args.grantsFromPath !== void 0 ? { grantsFromPath: args.grantsFromPath } : {}
2913
- }), 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);
3381
+ }), 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);
2914
3382
  return evaluateFromSnapshot({
2915
3383
  instance,
2916
3384
  definition,
@@ -2927,7 +3395,7 @@ async function evaluateFromSnapshot(args) {
2927
3395
  throw new Error(
2928
3396
  `Instance "${instance._id}" currentStage "${instance.currentStage}" not in definition`
2929
3397
  );
2930
- const scope = await renderScope({ instance, definition, actor, grants, snapshot, now }), currentActivityEntries = findOpenStageEntry(instance)?.activities ?? [], guardDenial = await instanceGuardReason(instance, actor, args.guards), activityEvaluations = [];
3398
+ const scope = await renderScope({ instance, definition, actor, grants, snapshot, now }), currentActivityEntries = findOpenStageEntry(instance)?.activities ?? [], guardDenial = await instanceGuardReason({ instance, actor, guards: args.guards }), activityEvaluations = [];
2931
3399
  for (const activity of stage.activities ?? [])
2932
3400
  activityEvaluations.push(
2933
3401
  await evaluateActivity({
@@ -3030,7 +3498,7 @@ async function renderScope(args) {
3030
3498
  ...buildParams({ instance, now, snapshot }),
3031
3499
  actor,
3032
3500
  assigned: !1,
3033
- can: await advisoryCan(instance, actor, grants)
3501
+ can: await advisoryCan({ instance, actor, grants })
3034
3502
  };
3035
3503
  return { ...await evaluatePredicates({
3036
3504
  predicates: definition.predicates,
@@ -3038,7 +3506,11 @@ async function renderScope(args) {
3038
3506
  params
3039
3507
  }), ...params };
3040
3508
  }
3041
- async function advisoryCan(instance, actor, grants) {
3509
+ async function advisoryCan({
3510
+ instance,
3511
+ actor,
3512
+ grants
3513
+ }) {
3042
3514
  if (grants === void 0) return;
3043
3515
  const can = {};
3044
3516
  for (const permission of schema.DOCUMENT_VALUE_PERMISSIONS)
@@ -3056,9 +3528,9 @@ function activityScopeFor(args) {
3056
3528
  ...scope,
3057
3529
  fields: {
3058
3530
  ...scope.fields,
3059
- ...scopedFieldOverlay(instance, snapshot, activityName)
3531
+ ...scopedFieldOverlay({ instance, snapshot, activityName })
3060
3532
  },
3061
- assigned: assignedFor(instance, activityName, actor, roleAliases)
3533
+ assigned: assignedFor({ instance, activityName, actor, roleAliases })
3062
3534
  };
3063
3535
  }
3064
3536
  async function evaluateActivity(args) {
@@ -3120,14 +3592,18 @@ async function evaluateAction(args) {
3120
3592
  stageHasExits,
3121
3593
  guardDenial,
3122
3594
  requirementsReason
3123
- } = args, lifecycle = lifecycleReason(instance, status, stageHasExits);
3595
+ } = args, lifecycle = lifecycleReason({ instance, status, stageHasExits });
3124
3596
  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({
3125
3597
  condition: action.filter,
3126
3598
  snapshot,
3127
3599
  params: activityScope
3128
3600
  }) ? disabled(action, { kind: "filter-failed", filter: action.filter }) : { action, allowed: !0 };
3129
3601
  }
3130
- async function instanceGuardReason(instance, actor, guards) {
3602
+ async function instanceGuardReason({
3603
+ instance,
3604
+ actor,
3605
+ guards
3606
+ }) {
3131
3607
  if (guards === void 0 || guards.length === 0) return;
3132
3608
  const denied = await instanceWriteDenials({ instance, guards, identity: actor.id });
3133
3609
  if (denied.length !== 0)
@@ -3137,7 +3613,11 @@ async function instanceGuardReason(instance, actor, guards) {
3137
3613
  detail: denied.map((g) => g.name ?? g._id).join(", ")
3138
3614
  };
3139
3615
  }
3140
- function lifecycleReason(instance, status, stageHasExits) {
3616
+ function lifecycleReason({
3617
+ instance,
3618
+ status,
3619
+ stageHasExits
3620
+ }) {
3141
3621
  if (instance.completedAt !== void 0)
3142
3622
  return { kind: "instance-completed", completedAt: instance.completedAt };
3143
3623
  if (!stageHasExits)
@@ -3148,10 +3628,11 @@ function lifecycleReason(instance, status, stageHasExits) {
3148
3628
  function disabled(action, reason) {
3149
3629
  return { action, allowed: !1, disabledReason: reason };
3150
3630
  }
3151
- function definitionDocId(_workflowResource, tag, definition, version) {
3152
- return `${tag}.${definition}.v${version}`;
3153
- }
3154
- async function sortByDependencies(client, definitions, tag) {
3631
+ async function sortByDependencies({
3632
+ client,
3633
+ definitions,
3634
+ tag
3635
+ }) {
3155
3636
  const byName = /* @__PURE__ */ new Map();
3156
3637
  for (const def of definitions) {
3157
3638
  if (byName.has(def.name))
@@ -3161,18 +3642,22 @@ async function sortByDependencies(client, definitions, tag) {
3161
3642
  byName.set(def.name, def);
3162
3643
  }
3163
3644
  const findInBatch = (ref) => findRefInBatch(byName, ref);
3164
- return await assertCrossBatchRefsDeployed(client, definitions, { tag, findInBatch }), topoSortDefinitions(definitions, { findInBatch });
3645
+ return await assertCrossBatchRefsDeployed({ client, definitions, ctx: { tag, findInBatch } }), topoSortDefinitions(definitions, { findInBatch });
3165
3646
  }
3166
3647
  function findRefInBatch(byName, ref) {
3167
3648
  if (typeof ref.version != "number")
3168
3649
  return byName.get(ref.name);
3169
3650
  }
3170
- async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
3651
+ async function assertCrossBatchRefsDeployed({
3652
+ client,
3653
+ definitions,
3654
+ ctx
3655
+ }) {
3171
3656
  const missing = [];
3172
3657
  for (const def of definitions)
3173
3658
  for (const ref of refsOf(def)) {
3174
3659
  if (ctx.findInBatch(ref) !== void 0) continue;
3175
- const label = await resolveDeployedRefLabel(client, ref, ctx.tag);
3660
+ const label = await resolveDeployedRefLabel({ client, ref, tag: ctx.tag });
3176
3661
  label !== void 0 && missing.push({ from: def.name, ref: label });
3177
3662
  }
3178
3663
  if (missing.length === 0) return;
@@ -3183,7 +3668,11 @@ async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
3183
3668
  `)
3184
3669
  );
3185
3670
  }
3186
- async function resolveDeployedRefLabel(client, ref, tag) {
3671
+ async function resolveDeployedRefLabel({
3672
+ client,
3673
+ ref,
3674
+ tag
3675
+ }) {
3187
3676
  const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
3188
3677
  if (wantsExplicit && (params.version = ref.version), !await client.fetch(
3189
3678
  definitionLookupGroq(wantsExplicit),
@@ -3226,9 +3715,17 @@ function hashDefinitionContent(def) {
3226
3715
  hash ^= BigInt(canonical.charCodeAt(i)), hash = hash * 0x100000001b3n & 0xffffffffffffffffn;
3227
3716
  return hash.toString(16).padStart(16, "0");
3228
3717
  }
3229
- function planDefinitionDeploy(def, latest, target) {
3230
- 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 = {
3231
- ...def,
3718
+ function planDefinitionDeploy({
3719
+ def,
3720
+ latest,
3721
+ target
3722
+ }) {
3723
+ 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({
3724
+ tag: target.tag,
3725
+ definition: expanded.name,
3726
+ version
3727
+ }), document = {
3728
+ ...expanded,
3232
3729
  _id: docId,
3233
3730
  _type: schema.WORKFLOW_DEFINITION_TYPE,
3234
3731
  tag: target.tag,
@@ -3237,6 +3734,38 @@ function planDefinitionDeploy(def, latest, target) {
3237
3734
  };
3238
3735
  return { status: unchanged ? "unchanged" : "create", version, contentHash, docId, document };
3239
3736
  }
3737
+ 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"]);
3738
+ function expandResourceAliases(definition, resourceAliases) {
3739
+ assertResourceAliasNamesValid(resourceAliases);
3740
+ const map = resourceAliases ?? {};
3741
+ return mapJsonStrings(
3742
+ definition,
3743
+ (value, key) => key !== void 0 && PROSE_KEYS.has(key) ? value : expandAliasString({ value, map, definitionName: definition.name })
3744
+ );
3745
+ }
3746
+ function expandAliasString({
3747
+ value,
3748
+ map,
3749
+ definitionName
3750
+ }) {
3751
+ const expanded = value.replace(RESOURCE_ALIAS_REF_RE, (_match, alias) => {
3752
+ if (!Object.hasOwn(map, alias))
3753
+ throw new Error(
3754
+ `workflow.deployDefinitions: definition "${definitionName}" references unbound resource alias "@${alias}". Bind it in the deploy target's resource map, or remove the reference.`
3755
+ );
3756
+ return gdrResourcePrefix(map[alias]);
3757
+ });
3758
+ if (expanded === value) return value;
3759
+ if (STANDALONE_RESOURCE_ALIAS_RE.test(value) && !isGdrUri(expanded))
3760
+ throw new Error(
3761
+ `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.`
3762
+ );
3763
+ return expanded;
3764
+ }
3765
+ function assertResourceAliasNamesValid(resourceAliases) {
3766
+ for (const name of Object.keys(resourceAliases ?? {}))
3767
+ validateResourceAliasName(name);
3768
+ }
3240
3769
  function stripSystemFields(doc) {
3241
3770
  const out = {};
3242
3771
  for (const [k, v2] of Object.entries(doc))
@@ -3248,7 +3777,13 @@ function stableStringify(value) {
3248
3777
  ([a], [b]) => a.localeCompare(b)
3249
3778
  ).map(([k, v2]) => `${JSON.stringify(k)}:${stableStringify(v2)}`).join(",")}}`;
3250
3779
  }
3251
- async function loadDefinition(client, definition, version, tag) {
3780
+ const noDeployedDefinitionMessage = (definition) => `No deployed definition for workflow ${definition}`;
3781
+ async function loadDefinition({
3782
+ client,
3783
+ definition,
3784
+ version,
3785
+ tag
3786
+ }) {
3252
3787
  if (version !== void 0) {
3253
3788
  const doc = await client.fetch(definitionLookupGroq(!0), {
3254
3789
  definition,
@@ -3259,31 +3794,50 @@ async function loadDefinition(client, definition, version, tag) {
3259
3794
  throw new Error(`Workflow definition ${definition} v${version} not deployed`);
3260
3795
  return doc;
3261
3796
  }
3262
- const latest = await loadLatestDeployed(client, definition, tag);
3797
+ const latest = await loadLatestDeployed({ client, definition, tag });
3263
3798
  if (!latest)
3264
- throw new Error(`No deployed definition for workflow ${definition}`);
3799
+ throw new Error(noDeployedDefinitionMessage(definition));
3265
3800
  return latest;
3266
3801
  }
3267
- async function loadLatestDeployed(client, definition, tag) {
3802
+ async function loadLatestDeployed({
3803
+ client,
3804
+ definition,
3805
+ tag
3806
+ }) {
3268
3807
  return await client.fetch(definitionLookupGroq(!1), {
3269
3808
  definition,
3270
3809
  tag
3271
3810
  }) ?? void 0;
3272
3811
  }
3273
- async function loadDefinitionVersions(client, definition, tag) {
3812
+ async function loadDefinitionVersions({
3813
+ client,
3814
+ definition,
3815
+ tag
3816
+ }) {
3274
3817
  return client.fetch(
3275
3818
  `*[_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}] | order(version desc)`,
3276
3819
  { definition, tag }
3277
3820
  );
3278
3821
  }
3279
- async function abortInstance(args) {
3280
- const { client, instanceId, reason, options } = args, ctx = await loadContext(client, instanceId, {
3281
- ...options?.clock ? { clock: options.clock } : {},
3282
- ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
3283
- });
3284
- return commitAbort(ctx, reason, options?.actor);
3822
+ async function loadDefinitionVersionsOrThrow({
3823
+ client,
3824
+ definition,
3825
+ tag
3826
+ }) {
3827
+ const versions = await loadDefinitionVersions({ client, definition, tag });
3828
+ if (versions.length === 0)
3829
+ throw new Error(noDeployedDefinitionMessage(definition));
3830
+ return versions;
3285
3831
  }
3286
- async function commitAbort(ctx, reason, actor) {
3832
+ async function abortInstance(args) {
3833
+ const { client, instanceId, reason, options } = args, ctx = await loadCallContext({ client, instanceId, options });
3834
+ return commitAbort({ ctx, reason, actor: options?.actor });
3835
+ }
3836
+ async function commitAbort({
3837
+ ctx,
3838
+ reason,
3839
+ actor
3840
+ }) {
3287
3841
  if (ctx.instance.completedAt !== void 0)
3288
3842
  return { fired: !1 };
3289
3843
  const stage = findStage(ctx.definition, ctx.instance.currentStage), mutation = startMutation(ctx.instance), at = ctx.now, openEntry = findOpenStageEntry(mutation);
@@ -3317,30 +3871,44 @@ async function commitAbort(ctx, reason, actor) {
3317
3871
  }
3318
3872
  async function fireAction(args) {
3319
3873
  const { client, instanceId, activity, action, params, options } = args;
3320
- for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
3321
- const ctx = await loadCallContext(client, instanceId, options);
3322
- try {
3323
- return await commitAction(ctx, activity, action, params, options);
3324
- } catch (error) {
3325
- if (!isRevisionConflict(error)) throw error;
3326
- }
3327
- }
3328
- throw new ConcurrentFireActionError({
3874
+ return retryOnRevisionConflict({
3875
+ client,
3329
3876
  instanceId,
3330
- activity,
3331
- action,
3332
- attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
3877
+ options,
3878
+ commit: (ctx) => commitAction({
3879
+ ctx,
3880
+ activityName: activity,
3881
+ actionName: action,
3882
+ callerParams: params,
3883
+ options
3884
+ }),
3885
+ onExhausted: () => new ConcurrentFireActionError({
3886
+ instanceId,
3887
+ activity,
3888
+ action,
3889
+ attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
3890
+ })
3333
3891
  });
3334
3892
  }
3335
- async function resolveActionCommit(ctx, activityName, actionName, callerParams, options) {
3893
+ async function resolveActionCommit({
3894
+ ctx,
3895
+ activityName,
3896
+ actionName,
3897
+ callerParams,
3898
+ options
3899
+ }) {
3336
3900
  const actor = options?.actor, { stage, activity } = findActivityInCurrentStage(ctx, activityName), action = (activity.actions ?? []).find((a) => a.name === actionName);
3337
3901
  if (action === void 0)
3338
3902
  throw new Error(`Action "${actionName}" not declared on activity "${activityName}"`);
3339
- const can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan(ctx.instance, actor, options.grants) : void 0;
3340
- if (!await ctxEvaluateCondition(ctx, action.filter, {
3341
- activityName,
3342
- ...actor !== void 0 ? { actor } : {},
3343
- ...can !== void 0 ? { vars: { can } } : {}
3903
+ const can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan({ instance: ctx.instance, actor, grants: options.grants }) : void 0;
3904
+ if (!await ctxEvaluateCondition({
3905
+ ctx,
3906
+ condition: action.filter,
3907
+ opts: {
3908
+ activityName,
3909
+ ...actor !== void 0 ? { actor } : {},
3910
+ ...can !== void 0 ? { vars: { can } } : {}
3911
+ }
3344
3912
  }))
3345
3913
  throw new Error(`Action "${actionName}" filter rejected on activity "${activityName}"`);
3346
3914
  const entry = findCurrentActivities(ctx.instance).find((t) => t.name === activityName);
@@ -3348,17 +3916,23 @@ async function resolveActionCommit(ctx, activityName, actionName, callerParams,
3348
3916
  throw new Error(
3349
3917
  `Activity "${activityName}" must be active to fire action "${actionName}"; status is ${entry?.status ?? "missing"}`
3350
3918
  );
3351
- const params = validateActionParams(action, activityName, callerParams);
3919
+ const params = validateActionParams({ action, activityName, callerParams });
3352
3920
  return { stage, activity, action, params };
3353
3921
  }
3354
- async function commitAction(ctx, activityName, actionName, callerParams, options) {
3355
- const actor = options?.actor, { stage, action, params } = await resolveActionCommit(
3922
+ async function commitAction({
3923
+ ctx,
3924
+ activityName,
3925
+ actionName,
3926
+ callerParams,
3927
+ options
3928
+ }) {
3929
+ const actor = options?.actor, { stage, action, params } = await resolveActionCommit({
3356
3930
  ctx,
3357
3931
  activityName,
3358
3932
  actionName,
3359
3933
  callerParams,
3360
3934
  options
3361
- ), mutation = startMutation(ctx.instance), mutEntry = requireMutationActivityEntry(mutation, activityName), now = ctx.now;
3935
+ }), mutation = startMutation(ctx.instance), mutEntry = requireMutationActivityEntry(mutation, activityName), now = ctx.now;
3362
3936
  mutation.history.push({
3363
3937
  _key: randomKey(),
3364
3938
  _type: "actionFired",
@@ -3378,14 +3952,19 @@ async function commitAction(ctx, activityName, actionName, callerParams, options
3378
3952
  self: selfGdr(ctx.instance),
3379
3953
  now
3380
3954
  }), newStatus = mutEntry.status !== statusBefore ? mutEntry.status : void 0;
3381
- return await queueEffects(ctx, mutation, action.effects, { kind: "action", name: actionName }, actor, {
3382
- callerParams: params,
3383
- activityName
3384
- }), await persistThenDeploy(
3955
+ return await queueEffects({
3956
+ ctx,
3957
+ mutation,
3958
+ effects: action.effects,
3959
+ origin: { kind: "action", name: actionName },
3960
+ actor,
3961
+ opts: { callerParams: params, activityName }
3962
+ }), await persistThenMaybeRefresh({
3385
3963
  ctx,
3386
3964
  mutation,
3387
- () => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
3388
- ), {
3965
+ stageName: stage.name,
3966
+ didChangeState: ranOps.some(isFieldOp)
3967
+ }), {
3389
3968
  fired: !0,
3390
3969
  activity: activityName,
3391
3970
  action: actionName,
@@ -3398,7 +3977,7 @@ class ActionDisabledError extends Error {
3398
3977
  activity;
3399
3978
  action;
3400
3979
  constructor(args) {
3401
- super(formatDisabledReason(args.activity, args.action, args.reason)), this.name = "ActionDisabledError", this.reason = args.reason, this.activity = args.activity, this.action = args.action;
3980
+ 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;
3402
3981
  }
3403
3982
  }
3404
3983
  const disabledReasonDetail = {
@@ -3409,7 +3988,11 @@ const disabledReasonDetail = {
3409
3988
  "mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`,
3410
3989
  "requirements-unmet": (r) => `unmet requirement(s): ${r.unmetRequirements.join(", ")}`
3411
3990
  };
3412
- function formatDisabledReason(activity, action, reason) {
3991
+ function formatDisabledReason({
3992
+ activity,
3993
+ action,
3994
+ reason
3995
+ }) {
3413
3996
  const detail = disabledReasonDetail[reason.kind](reason);
3414
3997
  return `Action "${activity}:${action}" is not allowed: ${detail}`;
3415
3998
  }
@@ -3435,28 +4018,32 @@ function formatEditDisabledReason(target, reason) {
3435
4018
  }
3436
4019
  async function editField(args) {
3437
4020
  const { client, instanceId, target, mode = "set", value, options } = args;
3438
- for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
3439
- const ctx = await loadCallContext(client, instanceId, options);
3440
- try {
3441
- return await commitEdit(ctx, target, mode, value, options);
3442
- } catch (error) {
3443
- if (!isRevisionConflict(error)) throw error;
3444
- }
3445
- }
3446
- throw new ConcurrentEditFieldError({
4021
+ return retryOnRevisionConflict({
4022
+ client,
3447
4023
  instanceId,
3448
- target: labelTarget(target),
3449
- attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
4024
+ options,
4025
+ commit: (ctx) => commitEdit({ ctx, target, mode, value, options }),
4026
+ onExhausted: () => new ConcurrentEditFieldError({
4027
+ instanceId,
4028
+ target: labelTarget(target),
4029
+ attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
4030
+ })
3450
4031
  });
3451
4032
  }
3452
- async function commitEdit(ctx, target, mode, value, options) {
4033
+ async function commitEdit({
4034
+ ctx,
4035
+ target,
4036
+ mode,
4037
+ value,
4038
+ options
4039
+ }) {
3453
4040
  const actor = options?.actor, stage = findStage(ctx.definition, ctx.instance.currentStage), slot = resolveEditTarget({ definition: ctx.definition, stage, target });
3454
4041
  if (slot === void 0)
3455
4042
  throw new Error(
3456
4043
  `No field slot "${describeTarget(target)}" resolves in current stage "${stage.name}" of ${ctx.definition.name}`
3457
4044
  );
3458
- await assertSlotEditable(ctx, slot, options);
3459
- const op = buildEditOp(slot, mode, value), mutation = startMutation(ctx.instance), ranOps = runOps({
4045
+ await assertSlotEditable({ ctx, slot, options });
4046
+ const op = buildEditOp({ slot, mode, value }), mutation = startMutation(ctx.instance), ranOps = runOps({
3460
4047
  ops: [op],
3461
4048
  mutation,
3462
4049
  stage: stage.name,
@@ -3469,21 +4056,30 @@ async function commitEdit(ctx, target, mode, value, options) {
3469
4056
  self: selfGdr(ctx.instance),
3470
4057
  now: ctx.now
3471
4058
  });
3472
- return await persistThenDeploy(
4059
+ return await persistThenMaybeRefresh({
3473
4060
  ctx,
3474
4061
  mutation,
3475
- () => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
3476
- ), {
4062
+ stageName: stage.name,
4063
+ didChangeState: ranOps.some(isFieldOp)
4064
+ }), {
3477
4065
  edited: !0,
3478
4066
  target: slotTarget(slot),
3479
4067
  ...ranOps.length > 0 ? { ranOps } : {}
3480
4068
  };
3481
4069
  }
3482
- async function assertSlotEditable(ctx, slot, options) {
3483
- 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, {
3484
- ...slot.activity !== void 0 ? { activityName: slot.activity } : {},
3485
- ...actor !== void 0 ? { actor } : {},
3486
- ...can !== void 0 ? { vars: { can } } : {}
4070
+ async function assertSlotEditable({
4071
+ ctx,
4072
+ slot,
4073
+ options
4074
+ }) {
4075
+ 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({
4076
+ ctx,
4077
+ condition: slot.effective,
4078
+ opts: {
4079
+ ...slot.activity !== void 0 ? { activityName: slot.activity } : {},
4080
+ ...actor !== void 0 ? { actor } : {},
4081
+ ...can !== void 0 ? { vars: { can } } : {}
4082
+ }
3487
4083
  }) : slot.effective === !0, reason = editDisabledReason({
3488
4084
  effective: slot.effective,
3489
4085
  instance: ctx.instance,
@@ -3493,7 +4089,11 @@ async function assertSlotEditable(ctx, slot, options) {
3493
4089
  });
3494
4090
  if (reason !== void 0) throw new EditFieldDeniedError({ target: slotTarget(slot), reason });
3495
4091
  }
3496
- function buildEditOp(slot, mode, value) {
4092
+ function buildEditOp({
4093
+ slot,
4094
+ mode,
4095
+ value
4096
+ }) {
3497
4097
  if (mode === "unset") return { type: "field.unset", target: slot.ref };
3498
4098
  if (value === void 0)
3499
4099
  throw new Error(`editField mode "${mode}" requires a value for slot "${slot.name}"`);
@@ -3541,25 +4141,45 @@ async function resolveOperationContext(args) {
3541
4141
  clientForGdr: buildClientForGdr(args.client, args.resourceClients)
3542
4142
  };
3543
4143
  }
3544
- async function cascade(client, instanceId, actor, clientForGdr, clock, overlay) {
3545
- const count = await cascadeAutoTransitions(
4144
+ async function cascade({
4145
+ client,
4146
+ instanceId,
4147
+ actor,
4148
+ clientForGdr,
4149
+ clock,
4150
+ overlay
4151
+ }) {
4152
+ const count = await cascadeAutoTransitions({
3546
4153
  client,
3547
4154
  instanceId,
3548
4155
  actor,
3549
- clientForGdr,
3550
- clock,
3551
- overlay
3552
- );
3553
- return await propagateToAncestors(client, instanceId, actor, clientForGdr, clock), count;
4156
+ ...clientForGdr !== void 0 ? { clientForGdr } : {},
4157
+ ...clock !== void 0 ? { clock } : {},
4158
+ ...overlay !== void 0 ? { overlay } : {}
4159
+ });
4160
+ return await propagateToAncestors({
4161
+ client,
4162
+ instanceId,
4163
+ actor,
4164
+ ...clientForGdr !== void 0 ? { clientForGdr } : {},
4165
+ ...clock !== void 0 ? { clock } : {}
4166
+ }), count;
4167
+ }
4168
+ function engineOptionsForActor({
4169
+ actor,
4170
+ clock,
4171
+ clientForGdr
4172
+ }) {
4173
+ return { ...actor !== void 0 ? { actor } : {}, clock, clientForGdr };
3554
4174
  }
3555
4175
  async function abortAndPropagate(args) {
3556
4176
  const { client, instanceId, reason, actor, clientForGdr, clock } = args, result = await abortInstance({
3557
4177
  client,
3558
4178
  instanceId,
3559
4179
  ...reason !== void 0 ? { reason } : {},
3560
- options: { ...actor !== void 0 ? { actor } : {}, clock, clientForGdr }
4180
+ options: engineOptionsForActor({ actor, clock, clientForGdr })
3561
4181
  });
3562
- return result.fired && await propagateToAncestors(client, instanceId, actor, clientForGdr, clock), result.fired;
4182
+ return result.fired && await propagateToAncestors({ client, instanceId, actor, clientForGdr, clock }), result.fired;
3563
4183
  }
3564
4184
  function buildClientForGdr(defaultClient, resolver) {
3565
4185
  return resolver === void 0 ? () => defaultClient : (parsed) => resolver(parsed) ?? defaultClient;
@@ -3575,15 +4195,25 @@ async function dispatchGatedWrite(args) {
3575
4195
  access,
3576
4196
  clock: args.clock,
3577
4197
  ...resourceClients !== void 0 ? { resourceClients } : {}
3578
- }), ranOps = await apply(args.before, evaluation), cascaded = await cascade(client, instanceId, args.actor, args.clientForGdr, args.clock);
4198
+ }), ranOps = await apply(args.before, evaluation), cascaded = await cascade({
4199
+ client,
4200
+ instanceId,
4201
+ actor: args.actor,
4202
+ clientForGdr: args.clientForGdr,
4203
+ clock: args.clock
4204
+ });
3579
4205
  return {
3580
- instance: await reload(client, instanceId, tag),
4206
+ instance: await reload({ client, instanceId, tag }),
3581
4207
  cascaded,
3582
4208
  fired: !0,
3583
4209
  ...ranOps !== void 0 ? { ranOps } : {}
3584
4210
  };
3585
4211
  }
3586
- function assertActionAllowed(evaluation, activity, action) {
4212
+ function assertActionAllowed({
4213
+ evaluation,
4214
+ activity,
4215
+ action
4216
+ }) {
3587
4217
  const actionEval = evaluation.currentStage.activities.find((t) => t.activity.name === activity)?.actions.find((a) => a.action.name === action);
3588
4218
  if (actionEval?.allowed === !1 && actionEval.disabledReason)
3589
4219
  throw new ActionDisabledError({ activity, action, reason: actionEval.disabledReason });
@@ -3621,7 +4251,7 @@ function buildEngineCallOptions(args) {
3621
4251
  }
3622
4252
  async function applyAction(args) {
3623
4253
  const { client, instance, activity, action, params, actor, grants, clock, clientForGdr } = args, options = buildEngineCallOptions({ actor, grants, clock, clientForGdr });
3624
- return findOpenStageEntry(instance)?.activities.find((t) => t.name === activity)?.status === "pending" && await invokeActivity({ client, instanceId: instance._id, activity, options }), (await fireAction({
4254
+ return findCurrentActivityEntry(instance, activity)?.status === "pending" && await invokeActivity({ client, instanceId: instance._id, activity, options }), (await fireAction({
3625
4255
  client,
3626
4256
  instanceId: instance._id,
3627
4257
  activity,
@@ -3631,15 +4261,12 @@ async function applyAction(args) {
3631
4261
  })).ranOps;
3632
4262
  }
3633
4263
  async function deleteDefinition(args) {
3634
- const { client, tag, definition, version, cascade: cascade2, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, versions = await loadDefinitionVersions(client, definition, tag);
3635
- if (versions.length === 0)
3636
- throw new Error(`No deployed definition for workflow ${definition}`);
3637
- const targets = version === void 0 ? versions : versions.filter((d) => d.version === version);
4264
+ 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);
3638
4265
  if (targets.length === 0)
3639
4266
  throw new Error(`Workflow definition ${definition} v${version} not deployed`);
3640
4267
  const lastVersionGoes = targets.length === versions.length;
3641
- await assertNoSpawnReferrers(client, tag, definition, targets, lastVersionGoes);
3642
- const instanceIds = await nonTerminalInstanceIds(client, tag, definition, version);
4268
+ await assertNoSpawnReferrers({ client, tag, definition, targets, lastVersionGoes });
4269
+ const instanceIds = await nonTerminalInstanceIds({ client, tag, definition, version });
3643
4270
  if (instanceIds.length > 0 && cascade2 !== !0)
3644
4271
  throw new Error(
3645
4272
  `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.`
@@ -3669,7 +4296,13 @@ async function deleteDefinition(args) {
3669
4296
  deletedGuardCount
3670
4297
  };
3671
4298
  }
3672
- async function assertNoSpawnReferrers(client, tag, definition, targets, lastVersionGoes) {
4299
+ async function assertNoSpawnReferrers({
4300
+ client,
4301
+ tag,
4302
+ definition,
4303
+ targets,
4304
+ lastVersionGoes
4305
+ }) {
3673
4306
  const deployed = await client.fetch(
3674
4307
  `*[_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && ${tagScopeFilter()}]`,
3675
4308
  { tag }
@@ -3685,7 +4318,12 @@ async function assertNoSpawnReferrers(client, tag, definition, targets, lastVers
3685
4318
  );
3686
4319
  }
3687
4320
  }
3688
- async function nonTerminalInstanceIds(client, tag, definition, version) {
4321
+ async function nonTerminalInstanceIds({
4322
+ client,
4323
+ tag,
4324
+ definition,
4325
+ version
4326
+ }) {
3689
4327
  const versionFilter = version === void 0 ? "" : " && pinnedVersion == $version";
3690
4328
  return (await client.fetch(
3691
4329
  `*[_type == "${WORKFLOW_INSTANCE_TYPE}" && definition == $definition && ${tagScopeFilter()} && !defined(completedAt)${versionFilter}] | order(_id asc){_id}`,
@@ -3722,14 +4360,19 @@ const workflow = {
3722
4360
  * Cycles in the dependency graph error before any write happens.
3723
4361
  */
3724
4362
  deployDefinitions: async (args) => {
3725
- const { client, tag, workflowResource, definitions } = args;
3726
- validateTag(tag);
3727
- for (const def of definitions)
3728
- validateDefinition(def);
3729
- const ordered = await sortByDependencies(client, definitions, tag), results = [], tx = client.transaction();
4363
+ const { client, tag, resourceAliases, definitions } = args;
4364
+ validateTag(tag), definitions.forEach(validateDefinition);
4365
+ const ordered = await sortByDependencies({ client, definitions, tag }), results = [], tx = client.transaction();
3730
4366
  let hasWrites = !1;
3731
4367
  for (const def of ordered) {
3732
- const latest = await loadLatestDeployed(client, def.name, tag), plan = planDefinitionDeploy(def, latest, { tag, workflowResource });
4368
+ const latest = await loadLatestDeployed({ client, definition: def.name, tag }), plan = planDefinitionDeploy({
4369
+ def,
4370
+ latest,
4371
+ target: {
4372
+ tag,
4373
+ ...resourceAliases !== void 0 ? { resourceAliases } : {}
4374
+ }
4375
+ });
3733
4376
  if (plan.status === "unchanged") {
3734
4377
  results.push({ definition: def.name, version: plan.version, status: "unchanged" });
3735
4378
  continue;
@@ -3766,7 +4409,7 @@ const workflow = {
3766
4409
  effectsContext,
3767
4410
  instanceId,
3768
4411
  perspective
3769
- } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, definition = await loadDefinition(client, definitionName, version, tag), id = instanceId ?? `${tag}.wf-instance.${randomKey()}`;
4412
+ } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, definition = await loadDefinition({ client, definition: definitionName, version, tag }), id = instanceId ?? instanceDocId(tag);
3770
4413
  if (definition.stages.find((s) => s.name === definition.initialStage) === void 0)
3771
4414
  throw new Error(
3772
4415
  `Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
@@ -3782,7 +4425,7 @@ const workflow = {
3782
4425
  workflowResource,
3783
4426
  definitionName: definition.name,
3784
4427
  ...perspective !== void 0 ? { perspective } : {},
3785
- recordDiscard: recordFieldDiscards(fieldDiscards, "workflow", now)
4428
+ recordDiscard: recordFieldDiscards({ target: fieldDiscards, scope: "workflow", at: now })
3786
4429
  },
3787
4430
  randomKey
3788
4431
  }), effectivePerspective = perspective ?? derivePerspectiveFromFields(resolvedFields), base = buildInstanceBase({
@@ -3802,7 +4445,7 @@ const workflow = {
3802
4445
  actor,
3803
4446
  ...fieldDiscards.length > 0 ? { extraHistory: fieldDiscards } : {}
3804
4447
  });
3805
- 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);
4448
+ 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 });
3806
4449
  },
3807
4450
  /**
3808
4451
  * Fire an action against an activity. If the activity is pending, it is
@@ -3824,8 +4467,8 @@ const workflow = {
3824
4467
  params,
3825
4468
  idempotent,
3826
4469
  resourceClients
3827
- } = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
3828
- return findOpenStageEntry(before)?.activities.find((t) => t.name === activity) === void 0 && idempotent === !0 ? { instance: before, cascaded: 0, fired: !1 } : dispatchGatedWrite({
4470
+ } = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload({ client, instanceId, tag });
4471
+ return findCurrentActivityEntry(before, activity) === void 0 && idempotent === !0 ? { instance: before, cascaded: 0, fired: !1 } : dispatchGatedWrite({
3829
4472
  client,
3830
4473
  tag,
3831
4474
  instanceId,
@@ -3835,7 +4478,7 @@ const workflow = {
3835
4478
  clock,
3836
4479
  before,
3837
4480
  ...resourceClients !== void 0 ? { resourceClients } : {},
3838
- apply: (instance, evaluation) => (assertActionAllowed(evaluation, activity, action), applyAction({
4481
+ apply: (instance, evaluation) => (assertActionAllowed({ evaluation, activity, action }), applyAction({
3839
4482
  client,
3840
4483
  instance,
3841
4484
  activity,
@@ -3864,7 +4507,7 @@ const workflow = {
3864
4507
  * never an `onChange` per keystroke.
3865
4508
  */
3866
4509
  editField: async (args) => {
3867
- 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);
4510
+ 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 });
3868
4511
  return dispatchGatedWrite({
3869
4512
  client,
3870
4513
  tag,
@@ -3893,24 +4536,27 @@ const workflow = {
3893
4536
  * appends an `effectHistory` entry, and (if `outputs` are supplied
3894
4537
  * and the effect succeeded) upserts those values into
3895
4538
  * `effectsContext` by key — so downstream effect bindings can
3896
- * reference them. Cascades after.
4539
+ * reference them. Any `ops` the handler returned (`field.*`) are
4540
+ * validated and applied to the instance in the same commit, through the
4541
+ * same op applier an action's field ops use. Cascades after.
3897
4542
  */
3898
4543
  completeEffect: async (args) => {
3899
- const { client, tag, instanceId, effectKey, status, outputs, detail, error, durationMs } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
4544
+ const { client, tag, instanceId, effectKey, status, outputs, ops, detail, error, durationMs } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
3900
4545
  await completeEffect({
3901
4546
  client,
3902
4547
  instanceId,
3903
4548
  effectKey,
3904
4549
  status,
3905
4550
  ...outputs !== void 0 ? { outputs } : {},
4551
+ ...ops !== void 0 ? { ops } : {},
3906
4552
  ...detail !== void 0 ? { detail } : {},
3907
4553
  ...error !== void 0 ? { error } : {},
3908
4554
  ...durationMs !== void 0 ? { durationMs } : {},
3909
- options: { ...actor !== void 0 ? { actor } : {}, clock, clientForGdr }
4555
+ options: engineOptionsForActor({ actor, clock, clientForGdr })
3910
4556
  });
3911
- const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
4557
+ const cascaded = await cascade({ client, instanceId, actor, clientForGdr, clock });
3912
4558
  return {
3913
- instance: await reload(client, instanceId, tag),
4559
+ instance: await reload({ client, instanceId, tag }),
3914
4560
  cascaded,
3915
4561
  fired: !0
3916
4562
  };
@@ -3925,11 +4571,11 @@ const workflow = {
3925
4571
  * affected instances and the engine re-evaluates.
3926
4572
  */
3927
4573
  tick: async (args) => {
3928
- 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);
4574
+ 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);
3929
4575
  await assertInstanceWriteAllowed({ instance: current, guards, identity: actor.id });
3930
- const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
4576
+ const cascaded = await cascade({ client, instanceId, actor, clientForGdr, clock });
3931
4577
  return {
3932
- instance: await reload(client, instanceId, tag),
4578
+ instance: await reload({ client, instanceId, tag }),
3933
4579
  cascaded,
3934
4580
  fired: cascaded > 0
3935
4581
  };
@@ -3941,16 +4587,16 @@ const workflow = {
3941
4587
  */
3942
4588
  setStage: async (args) => {
3943
4589
  const { client, tag, instanceId, targetStage, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
3944
- await reload(client, instanceId, tag);
4590
+ await reload({ client, instanceId, tag });
3945
4591
  const result = await setStage({
3946
4592
  client,
3947
4593
  instanceId,
3948
4594
  targetStage,
3949
4595
  ...reason !== void 0 ? { reason } : {},
3950
- options: { ...actor !== void 0 ? { actor } : {}, clock, clientForGdr }
3951
- }), cascaded = result.fired ? await cascade(client, instanceId, actor, clientForGdr, clock) : 0;
4596
+ options: engineOptionsForActor({ actor, clock, clientForGdr })
4597
+ }), cascaded = result.fired ? await cascade({ client, instanceId, actor, clientForGdr, clock }) : 0;
3952
4598
  return {
3953
- instance: await reload(client, instanceId, tag),
4599
+ instance: await reload({ client, instanceId, tag }),
3954
4600
  cascaded,
3955
4601
  fired: result.fired
3956
4602
  };
@@ -3963,10 +4609,10 @@ const workflow = {
3963
4609
  */
3964
4610
  abortInstance: async (args) => {
3965
4611
  const { client, tag, instanceId, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
3966
- await reload(client, instanceId, tag);
4612
+ await reload({ client, instanceId, tag });
3967
4613
  const fired = await abortAndPropagate({ client, instanceId, reason, actor, clientForGdr, clock });
3968
4614
  return {
3969
- instance: await reload(client, instanceId, tag),
4615
+ instance: await reload({ client, instanceId, tag }),
3970
4616
  cascaded: 0,
3971
4617
  fired
3972
4618
  };
@@ -3978,12 +4624,12 @@ const workflow = {
3978
4624
  */
3979
4625
  getInstance: async (args) => {
3980
4626
  const { client, tag, instanceId } = args;
3981
- return validateTag(tag), reload(client, instanceId, tag);
4627
+ return validateTag(tag), reload({ client, instanceId, tag });
3982
4628
  },
3983
4629
  guardsForInstance: async (args) => {
3984
4630
  const { client, tag, instanceId, resourceClients } = args;
3985
4631
  validateTag(tag);
3986
- const instance = await reload(client, instanceId, tag);
4632
+ const instance = await reload({ client, instanceId, tag });
3987
4633
  return guardsForInstance({
3988
4634
  client,
3989
4635
  clientForGdr: buildClientForGdr(client, resourceClients),
@@ -3993,9 +4639,7 @@ const workflow = {
3993
4639
  guardsForDefinition: async (args) => {
3994
4640
  const { client, tag, workflowResource, definition, resourceClients } = args;
3995
4641
  validateTag(tag);
3996
- const definitions = await loadDefinitionVersions(client, definition, tag);
3997
- if (definitions.length === 0)
3998
- throw new Error(`No deployed definition for workflow ${definition}`);
4642
+ const definitions = await loadDefinitionVersionsOrThrow({ client, definition, tag });
3999
4643
  return guardsForDefinition({
4000
4644
  client,
4001
4645
  clientForGdr: buildClientForGdr(client, resourceClients),
@@ -4040,7 +4684,7 @@ const workflow = {
4040
4684
  queryInScope: async (args) => {
4041
4685
  const { client, tag, instanceId, groq, params, resourceClients } = args, clock = args.clock ?? wallClock;
4042
4686
  validateTag(tag);
4043
- const instance = await reload(client, instanceId, tag), clientForGdr = buildClientForGdr(client, resourceClients), snapshot = await hydrateSnapshot({ client, clientForGdr, instance }), reserved = buildParams({ instance, now: clock(), snapshot }), tree = groqJs.parse(groq, { params: { ...reserved, ...params } });
4687
+ const instance = await reload({ client, instanceId, tag }), clientForGdr = buildClientForGdr(client, resourceClients), snapshot = await hydrateSnapshot({ client, clientForGdr, instance }), reserved = buildParams({ instance, now: clock(), snapshot }), tree = groqJs.parse(groq, { params: { ...reserved, ...params } });
4044
4688
  return await (await groqJs.evaluate(tree, {
4045
4689
  dataset: snapshot.docs,
4046
4690
  params: { ...reserved, ...params }
@@ -4050,13 +4694,13 @@ const workflow = {
4050
4694
  * List every pending effect on the instance. Returns the same entries
4051
4695
  * the runtime would see — claimed and unclaimed alike.
4052
4696
  */
4053
- listPendingEffects: async (args) => (await reload(args.client, args.instanceId, args.tag)).pendingEffects,
4697
+ listPendingEffects: async (args) => (await reload({ client: args.client, instanceId: args.instanceId, tag: args.tag })).pendingEffects,
4054
4698
  /**
4055
4699
  * Filter pending effects on the instance by criteria. `claimed`
4056
4700
  * filters on claim presence; `names` restricts to specific effect
4057
4701
  * names. Both filters compose (AND).
4058
4702
  */
4059
- 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))),
4703
+ 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))),
4060
4704
  /**
4061
4705
  * Project the instance from a given actor's perspective. Returns a
4062
4706
  * `WorkflowEvaluation` with per-action verdicts (`allowed` + a
@@ -4105,7 +4749,7 @@ const workflow = {
4105
4749
  children: async (args) => {
4106
4750
  const { client, tag, instanceId, activity } = args;
4107
4751
  validateTag(tag);
4108
- 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));
4752
+ 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));
4109
4753
  return ids.length === 0 ? [] : client.fetch(
4110
4754
  `*[_id in $ids && ${tagScopeFilter()}] | order(startedAt asc)`,
4111
4755
  { ids, tag }
@@ -4201,7 +4845,7 @@ async function verifyDeployedDefinitionsInternal(args) {
4201
4845
  locations: m.locations,
4202
4846
  definitionId: m.definitionId
4203
4847
  };
4204
- if (await applyMissingHandler(missingHandler, info, log) === "fail")
4848
+ if (await applyMissingHandler({ policy: missingHandler, info, log }) === "fail")
4205
4849
  throw new Error(
4206
4850
  `verifyDeployedDefinitions: missing handler for "${m.name}" referenced by ${m.definitionId} at ${m.locations.join(", ")}`
4207
4851
  );
@@ -4242,21 +4886,30 @@ async function drainEffectsInternal(args) {
4242
4886
  validateTag(tag);
4243
4887
  const log = logger("drainEffects"), drained = [], failed = [], skipped = [], skippedKeys = /* @__PURE__ */ new Set();
4244
4888
  for (; ; ) {
4245
- const before = await reload(client, instanceId, tag), candidate = before.pendingEffects.find(
4889
+ const before = await reload({ client, instanceId, tag }), candidate = before.pendingEffects.find(
4246
4890
  (e) => e.claim === void 0 && !skippedKeys.has(e._key)
4247
4891
  );
4248
4892
  if (candidate === void 0) break;
4249
4893
  const handler = effectHandlers[candidate.name];
4250
4894
  if (handler === void 0) {
4251
- await assertSkippableOrThrow(missingHandler, candidate, instanceId, log), skipped.push(candidate), skippedKeys.add(candidate._key);
4895
+ await assertSkippableOrThrow({ missingHandler, candidate, instanceId, log }), skipped.push(candidate), skippedKeys.add(candidate._key);
4252
4896
  continue;
4253
4897
  }
4254
- if (!await claimPendingEffect(client, before, candidate._key, drainerActor)) continue;
4255
- const { outputs, dispatchError } = await dispatchEffect(handler, candidate, {
4898
+ if (!await claimPendingEffect({
4256
4899
  client,
4257
- clientFor,
4258
- instanceId,
4259
- logger
4900
+ instance: before,
4901
+ effectKey: candidate._key,
4902
+ drainerActor
4903
+ })) continue;
4904
+ const { outputs, ops, dispatchError } = await dispatchEffect({
4905
+ handler,
4906
+ candidate,
4907
+ ctx: {
4908
+ client,
4909
+ clientFor,
4910
+ instanceId,
4911
+ logger
4912
+ }
4260
4913
  });
4261
4914
  await reportEffectOutcome({
4262
4915
  client,
@@ -4267,32 +4920,44 @@ async function drainEffectsInternal(args) {
4267
4920
  effectKey: candidate._key,
4268
4921
  access: completionAccess,
4269
4922
  outputs,
4923
+ ops,
4270
4924
  dispatchError
4271
4925
  }), dispatchError === void 0 ? drained.push(candidate) : failed.push(candidate);
4272
4926
  }
4273
4927
  return { drained, failed, skipped };
4274
4928
  }
4275
4929
  async function reportEffectOutcome(args) {
4276
- const { outputs, dispatchError, resourceClients, ...rest } = args;
4930
+ const { outputs, ops, dispatchError, resourceClients, ...rest } = args;
4277
4931
  await workflow.completeEffect({
4278
4932
  ...rest,
4279
4933
  ...resourceClients !== void 0 ? { resourceClients } : {},
4280
4934
  status: dispatchError === void 0 ? "done" : "failed",
4281
4935
  ...outputs !== void 0 ? { outputs } : {},
4936
+ ...ops !== void 0 ? { ops } : {},
4282
4937
  ...dispatchError !== void 0 ? { error: dispatchError } : {}
4283
4938
  });
4284
4939
  }
4285
- async function assertSkippableOrThrow(missingHandler, candidate, instanceId, log) {
4286
- if (await applyMissingHandler(
4287
- missingHandler,
4288
- { phase: "drain", name: candidate.name, effectKey: candidate._key, instanceId },
4940
+ async function assertSkippableOrThrow({
4941
+ missingHandler,
4942
+ candidate,
4943
+ instanceId,
4944
+ log
4945
+ }) {
4946
+ if (await applyMissingHandler({
4947
+ policy: missingHandler,
4948
+ info: { phase: "drain", name: candidate.name, effectKey: candidate._key, instanceId },
4289
4949
  log
4290
- ) === "fail")
4950
+ }) === "fail")
4291
4951
  throw new Error(
4292
4952
  `drainEffects: no handler registered for "${candidate.name}" (effectKey=${candidate._key}, instanceId=${instanceId})`
4293
4953
  );
4294
4954
  }
4295
- async function claimPendingEffect(client, instance, effectKey, drainerActor) {
4955
+ async function claimPendingEffect({
4956
+ client,
4957
+ instance,
4958
+ effectKey,
4959
+ drainerActor
4960
+ }) {
4296
4961
  const claim = {
4297
4962
  _type: "pendingEffect.claim",
4298
4963
  claimedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4305,7 +4970,11 @@ async function claimPendingEffect(client, instance, effectKey, drainerActor) {
4305
4970
  return !1;
4306
4971
  }
4307
4972
  }
4308
- async function dispatchEffect(handler, candidate, ctx) {
4973
+ async function dispatchEffect({
4974
+ handler,
4975
+ candidate,
4976
+ ctx
4977
+ }) {
4309
4978
  try {
4310
4979
  const result = await handler(candidate.params, {
4311
4980
  client: ctx.client,
@@ -4314,13 +4983,20 @@ async function dispatchEffect(handler, candidate, ctx) {
4314
4983
  effectKey: candidate._key,
4315
4984
  log: (message, extra) => ctx.logger(`effect.${candidate.name}`).info(message, extra)
4316
4985
  });
4317
- return result?.outputs !== void 0 ? { outputs: result.outputs } : {};
4986
+ return {
4987
+ ...result?.outputs !== void 0 ? { outputs: result.outputs } : {},
4988
+ ...result?.ops !== void 0 ? { ops: result.ops } : {}
4989
+ };
4318
4990
  } catch (err) {
4319
4991
  const message = err instanceof Error ? err.message : String(err), stack = err instanceof Error && err.stack !== void 0 ? err.stack : void 0;
4320
4992
  return { dispatchError: stack !== void 0 ? { message, stack } : { message } };
4321
4993
  }
4322
4994
  }
4323
- async function applyMissingHandler(policy, info, log) {
4995
+ async function applyMissingHandler({
4996
+ policy,
4997
+ info,
4998
+ log
4999
+ }) {
4324
5000
  if (policy === "fail") return "fail";
4325
5001
  if (policy === "skip")
4326
5002
  return log.warn(`Missing effect handler "${info.name}" \u2014 skipping`, { info }), "skip";
@@ -4358,9 +5034,20 @@ function createInstanceSession(args) {
4358
5034
  ...clock !== void 0 ? { now: clock() } : {},
4359
5035
  ...grants !== void 0 ? { grants } : {}
4360
5036
  });
4361
- }, settleAfterApply = async (actor, held, ranOps) => {
4362
- const cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
4363
- return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: !0, ...ranOps !== void 0 ? { ranOps } : {} };
5037
+ }, settleAfterApply = async ({
5038
+ actor,
5039
+ held,
5040
+ ranOps
5041
+ }) => {
5042
+ const cascaded = await cascade({
5043
+ client,
5044
+ instanceId: instance._id,
5045
+ actor,
5046
+ clientForGdr,
5047
+ ...clock !== void 0 ? { clock } : {},
5048
+ overlay: held
5049
+ });
5050
+ return instance = await reload({ client, instanceId: instance._id, tag }), { instance, cascaded, fired: !0, ...ranOps !== void 0 ? { ranOps } : {} };
4364
5051
  }, commit = async (run) => {
4365
5052
  committing = !0;
4366
5053
  const held = new Map(overlay);
@@ -4394,13 +5081,20 @@ function createInstanceSession(args) {
4394
5081
  tick() {
4395
5082
  return commit(async (actor, held) => {
4396
5083
  await assertInstanceWriteAllowed({ instance, guards: heldGuards, identity: actor.id });
4397
- const cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
4398
- return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: cascaded > 0 };
5084
+ const cascaded = await cascade({
5085
+ client,
5086
+ instanceId: instance._id,
5087
+ actor,
5088
+ clientForGdr,
5089
+ ...clock !== void 0 ? { clock } : {},
5090
+ overlay: held
5091
+ });
5092
+ return instance = await reload({ client, instanceId: instance._id, tag }), { instance, cascaded, fired: cascaded > 0 };
4399
5093
  });
4400
5094
  },
4401
5095
  fireAction({ activity, action, params }) {
4402
5096
  return commit(async (actor, held) => {
4403
- assertActionAllowed(await evaluateWith(held, heldGuards), activity, action);
5097
+ assertActionAllowed({ evaluation: await evaluateWith(held, heldGuards), activity, action });
4404
5098
  const { grants } = await access(), ranOps = await applyAction({
4405
5099
  client,
4406
5100
  instance,
@@ -4412,7 +5106,7 @@ function createInstanceSession(args) {
4412
5106
  ...clock !== void 0 ? { clock } : {},
4413
5107
  ...params !== void 0 ? { params } : {}
4414
5108
  });
4415
- return settleAfterApply(actor, held, ranOps);
5109
+ return settleAfterApply({ actor, held, ranOps });
4416
5110
  });
4417
5111
  },
4418
5112
  editField({ target, mode, value }) {
@@ -4429,7 +5123,7 @@ function createInstanceSession(args) {
4429
5123
  ...grants !== void 0 ? { grants } : {},
4430
5124
  ...clock !== void 0 ? { clock } : {}
4431
5125
  });
4432
- return settleAfterApply(actor, held, ranOps);
5126
+ return settleAfterApply({ actor, held, ranOps });
4433
5127
  });
4434
5128
  }
4435
5129
  };
@@ -4564,8 +5258,12 @@ function createEngine(args) {
4564
5258
  })
4565
5259
  };
4566
5260
  }
4567
- function diffEntry(def, latestRaw, target) {
4568
- const plan = planDefinitionDeploy(def, asLatest(latestRaw), target), existing = latestRaw ? stripSystemFields(latestRaw) : void 0;
5261
+ function diffEntry({
5262
+ def,
5263
+ latestRaw,
5264
+ target
5265
+ }) {
5266
+ const plan = planDefinitionDeploy({ def, latest: asLatest(latestRaw), target }), existing = latestRaw ? stripSystemFields(latestRaw) : void 0;
4569
5267
  return {
4570
5268
  name: def.name,
4571
5269
  version: plan.version,
@@ -4578,11 +5276,15 @@ function diffEntry(def, latestRaw, target) {
4578
5276
  function asLatest(raw) {
4579
5277
  return raw === void 0 ? void 0 : { version: typeof raw.version == "number" ? raw.version : 0, ...typeof raw.contentHash == "string" ? { contentHash: raw.contentHash } : {} };
4580
5278
  }
4581
- async function computeDiffEntries(client, defs, target) {
5279
+ async function computeDiffEntries({
5280
+ client,
5281
+ defs,
5282
+ target
5283
+ }) {
4582
5284
  const entries = [];
4583
5285
  for (const def of defs) {
4584
- const latest = await loadLatestDeployed(client, def.name, target.tag);
4585
- entries.push(diffEntry(def, latest, target));
5286
+ const latest = await loadLatestDeployed({ client, definition: def.name, tag: target.tag });
5287
+ entries.push(diffEntry({ def, latestRaw: latest, target }));
4586
5288
  }
4587
5289
  return entries;
4588
5290
  }
@@ -4818,6 +5520,7 @@ exports.DISPLAY = DISPLAY;
4818
5520
  exports.DRIVER_KIND_DISPLAY = DRIVER_KIND_DISPLAY;
4819
5521
  exports.EFFECTS_CONTEXT_DISPLAY = EFFECTS_CONTEXT_DISPLAY;
4820
5522
  exports.EditFieldDeniedError = EditFieldDeniedError;
5523
+ exports.EffectOpsInvalidError = EffectOpsInvalidError;
4821
5524
  exports.FIELD_SLOT_DISPLAY = FIELD_SLOT_DISPLAY;
4822
5525
  exports.GUARD_DOC_TYPE = GUARD_DOC_TYPE;
4823
5526
  exports.GUARD_LIFTED_PREDICATE = GUARD_LIFTED_PREDICATE;