@sanity/workflow-engine 0.1.0 → 0.2.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
@@ -1,18 +1,34 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: !0 });
3
- var groqJs = require("groq-js"), zod = require("zod");
3
+ var groqJs = require("groq-js"), v = require("valibot");
4
+ function _interopNamespaceCompat(e) {
5
+ if (e && typeof e == "object" && "default" in e) return e;
6
+ var n = /* @__PURE__ */ Object.create(null);
7
+ return e && Object.keys(e).forEach(function(k) {
8
+ if (k !== "default") {
9
+ var d = Object.getOwnPropertyDescriptor(e, k);
10
+ Object.defineProperty(n, k, d.get ? d : {
11
+ enumerable: !0,
12
+ get: function() {
13
+ return e[k];
14
+ }
15
+ });
16
+ }
17
+ }), n.default = e, Object.freeze(n);
18
+ }
19
+ var v__namespace = /* @__PURE__ */ _interopNamespaceCompat(v);
4
20
  function validateDefinition(definition) {
5
- const v = createDefinitionValidator();
21
+ const v2 = createDefinitionValidator();
6
22
  for (const slot of definition.state ?? [])
7
- v.checkSlot(slot, "workflow.state", `workflow.state "${slot.id}"`);
23
+ v2.checkSlot(slot, `workflow.state "${slot.id}"`);
8
24
  for (const p of definition.predicates ?? [])
9
- p.groq && v.checkFilter(p.groq, `predicate "${p.id}"`);
25
+ p.groq && v2.checkFilter(p.groq, `predicate "${p.id}"`);
10
26
  for (const stage of definition.stages)
11
- validateStage(v, stage);
12
- if (v.errors.length > 0)
27
+ validateStage(v2, stage);
28
+ if (v2.errors.length > 0)
13
29
  throw new Error(
14
- `defineWorkflow("${definition.workflowId}", v${definition.version}): ${v.errors.length} validation error${v.errors.length === 1 ? "" : "s"}:
15
- ` + v.errors.join(`
30
+ `defineWorkflow("${definition.workflowId}", v${definition.version}): ${v2.errors.length} validation error${v2.errors.length === 1 ? "" : "s"}:
31
+ ` + v2.errors.join(`
16
32
  `)
17
33
  );
18
34
  }
@@ -31,31 +47,29 @@ function createDefinitionValidator() {
31
47
  };
32
48
  return { errors, tryParse, checkFilter: (groq, where) => {
33
49
  tryParse(groq, where), rejectTypeScan(groq, where);
34
- }, checkSlot: (slot, scopeLabel, slotLabel) => {
35
- slot.source.kind === "computed" && errors.push(
36
- ` \xB7 ${scopeLabel} slot "${slot.id}": source.kind="computed" is reserved (not implemented in v0.1)`
37
- ), slot.source.kind === "query" && tryParse(slot.source.query, `${slotLabel}.query`);
50
+ }, checkSlot: (slot, slotLabel) => {
51
+ slot.source.kind === "query" && tryParse(slot.source.query, `${slotLabel}.query`);
38
52
  } };
39
53
  }
40
- function validateStage(v, stage) {
54
+ function validateStage(v2, stage) {
41
55
  for (const slot of stage.state ?? [])
42
- v.checkSlot(slot, `stage "${stage.id}".state`, `stage "${stage.id}".state "${slot.id}"`);
43
- typeof stage.completion == "string" && v.tryParse(stage.completion, `stage "${stage.id}".completion`);
56
+ v2.checkSlot(slot, `stage "${stage.id}".state "${slot.id}"`);
57
+ typeof stage.completion == "string" && v2.tryParse(stage.completion, `stage "${stage.id}".completion`);
44
58
  for (const t of stage.transitions ?? [])
45
- typeof t.filter == "string" && v.checkFilter(t.filter, `transition ${stage.id} \u2192 ${t.to} filter`);
59
+ typeof t.filter == "string" && v2.checkFilter(t.filter, `transition ${stage.id} \u2192 ${t.to} filter`);
46
60
  for (const task of stage.tasks ?? [])
47
- validateTask(v, stage.id, task);
61
+ validateTask(v2, stage.id, task);
48
62
  }
49
- function validateTask(v, stageId, task) {
63
+ function validateTask(v2, stageId, task) {
50
64
  const where = `stage "${stageId}" task "${task.id}"`;
51
65
  for (const slot of task.state ?? [])
52
- v.checkSlot(slot, `${where}.state`, `${where}.state "${slot.id}"`);
53
- typeof task.filter == "string" && v.checkFilter(task.filter, `${where}.filter`), typeof task.completeWhen == "string" && v.checkFilter(task.completeWhen, `${where}.completeWhen`);
66
+ v2.checkSlot(slot, `${where}.state "${slot.id}"`);
67
+ typeof task.filter == "string" && v2.checkFilter(task.filter, `${where}.filter`), typeof task.completeWhen == "string" && v2.checkFilter(task.completeWhen, `${where}.completeWhen`);
54
68
  for (const a of task.actions ?? [])
55
- typeof a.filter == "string" && v.checkFilter(a.filter, `task "${task.id}" action "${a.id}".filter`);
56
- task.spawns?.forEach?.groq && v.tryParse(task.spawns.forEach.groq, `task "${task.id}".spawns.forEach.groq`);
69
+ typeof a.filter == "string" && v2.checkFilter(a.filter, `task "${task.id}" action "${a.id}".filter`);
70
+ task.spawns?.forEach?.groq && v2.tryParse(task.spawns.forEach.groq, `task "${task.id}".spawns.forEach.groq`);
57
71
  }
58
- const KNOWN_SCHEMES = /* @__PURE__ */ new Set(["dataset", "canvas", "media-library", "dashboard"]);
72
+ const wallClock = () => (/* @__PURE__ */ new Date()).toISOString(), KNOWN_SCHEMES = /* @__PURE__ */ new Set(["dataset", "canvas", "media-library", "dashboard"]);
59
73
  function parseGdr(uri) {
60
74
  const colon = uri.indexOf(":");
61
75
  if (colon < 0)
@@ -122,6 +136,9 @@ function gdrFromResource(res, documentId) {
122
136
  }
123
137
  return gdrUri({ scheme: res.type, resourceId: res.id, documentId });
124
138
  }
139
+ function selfGdr(doc) {
140
+ return gdrFromResource(doc.workflowResource, doc._id);
141
+ }
125
142
  function isGdrUri(value) {
126
143
  if (typeof value != "string") return !1;
127
144
  try {
@@ -136,15 +153,15 @@ function gdrRef(res, documentId, type) {
136
153
  function isGdr(value) {
137
154
  return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
138
155
  }
139
- function buildParams(instance, extra) {
156
+ function buildParams(instance, now, extra) {
140
157
  return {
141
- self: gdrFromResource(instance.workflowResource, instance._id),
158
+ self: selfGdr(instance),
142
159
  state: stateMap(instance.state ?? []),
143
160
  parent: instance.ancestors.at(-1)?.id ?? null,
144
161
  ancestors: instance.ancestors.map((a) => a.id),
145
162
  /** Current stage id — bind for filters that scope to the current stage. */
146
163
  stage: instance.currentStageId,
147
- now: (/* @__PURE__ */ new Date()).toISOString(),
164
+ now,
148
165
  ...extra
149
166
  };
150
167
  }
@@ -168,8 +185,8 @@ function stripStateForLake(value) {
168
185
  if (Array.isArray(value)) return value.map(stripStateForLake);
169
186
  if (typeof value == "object") {
170
187
  const out = {};
171
- for (const [k, v] of Object.entries(value))
172
- out[k] = stripStateForLake(v);
188
+ for (const [k, v2] of Object.entries(value))
189
+ out[k] = stripStateForLake(v2);
173
190
  return out;
174
191
  }
175
192
  return value;
@@ -227,7 +244,7 @@ function matchesParamType(type, value) {
227
244
  case "boolean":
228
245
  return typeof value == "boolean";
229
246
  case "string[]":
230
- return Array.isArray(value) && value.every((v) => typeof v == "string");
247
+ return Array.isArray(value) && value.every((v2) => typeof v2 == "string");
231
248
  case "reference":
232
249
  return isGdr(value);
233
250
  }
@@ -248,11 +265,11 @@ function restampForSnapshot(doc, gdrUri2, resource) {
248
265
  }
249
266
  function rewriteRefsRecursive(value, resource) {
250
267
  if (Array.isArray(value))
251
- return value.map((v) => rewriteRefsRecursive(v, resource));
268
+ return value.map((v2) => rewriteRefsRecursive(v2, resource));
252
269
  if (value === null || typeof value != "object") return value;
253
270
  const obj = value, out = {};
254
- for (const [k, v] of Object.entries(obj))
255
- k === "_ref" && typeof v == "string" ? out[k] = v.includes(":") ? v : gdrFromResource(resource, v) : out[k] = rewriteRefsRecursive(v, resource);
271
+ for (const [k, v2] of Object.entries(obj))
272
+ k === "_ref" && typeof v2 == "string" ? out[k] = v2.includes(":") ? v2 : gdrFromResource(resource, v2) : out[k] = rewriteRefsRecursive(v2, resource);
256
273
  return out;
257
274
  }
258
275
  async function hydrateSnapshot(args) {
@@ -261,7 +278,7 @@ async function hydrateSnapshot(args) {
261
278
  const fetched = await loadByGdr(client, clientForGdr, instance.workflowResource, uri);
262
279
  fetched && (loaded.push(fetched), visited.add(uri));
263
280
  };
264
- loaded.push({ doc: instance, resource: instance.workflowResource }), visited.add(gdrFromResource(instance.workflowResource, instance._id));
281
+ loaded.push({ doc: instance, resource: instance.workflowResource }), visited.add(selfGdr(instance));
265
282
  for (const ancestor of instance.ancestors)
266
283
  await loadInto(ancestor.id);
267
284
  for (const uri of collectSlotDocUris(instance.state))
@@ -296,12 +313,12 @@ function slotDocUris(slot) {
296
313
  if (!slot || typeof slot != "object") return [];
297
314
  const s = slot;
298
315
  if (s._type === "workflow.state.doc.ref") {
299
- const v = s.value, id = refId(v);
316
+ const v2 = s.value, id = refId(v2);
300
317
  return id !== void 0 ? [id] : [];
301
318
  }
302
319
  if (s._type === "workflow.state.doc.refs") {
303
- const v = s.value;
304
- return Array.isArray(v) ? v.map(refId).filter((id) => id !== void 0) : [];
320
+ const v2 = s.value;
321
+ return Array.isArray(v2) ? v2.map(refId).filter((id) => id !== void 0) : [];
305
322
  }
306
323
  return [];
307
324
  }
@@ -325,50 +342,61 @@ class SlotValueShapeError extends Error {
325
342
  super(`Slot ${args.mode} shape invalid for "${args.slotId}" (${args.slotType}): ${issueText}`), this.name = "SlotValueShapeError", this.slotType = args.slotType, this.slotId = args.slotId, this.issues = args.issues;
326
343
  }
327
344
  }
328
- const GdrShape = zod.z.object({
329
- id: zod.z.string().refine((s) => isGdrUri(s), { message: "must be a GDR URI" }),
330
- type: zod.z.string().min(1)
331
- }).passthrough(), ReleaseRefShape = zod.z.object({
332
- id: zod.z.string().refine((s) => isGdrUri(s), { message: "must be a GDR URI" }),
333
- type: zod.z.literal("system.release"),
334
- releaseName: zod.z.string().min(1)
335
- }).passthrough(), ActorShape = zod.z.object({
336
- kind: zod.z.enum(["user", "ai", "system"]),
337
- id: zod.z.string().min(1),
338
- roles: zod.z.array(zod.z.string()).optional(),
339
- onBehalfOf: zod.z.string().optional()
340
- }).passthrough(), AssigneeShape = zod.z.union([
341
- zod.z.object({ kind: zod.z.literal("user"), id: zod.z.string().min(1) }).passthrough(),
342
- zod.z.object({ kind: zod.z.literal("role"), role: zod.z.string().min(1) }).passthrough()
343
- ]), ChecklistItemShape = zod.z.object({
344
- label: zod.z.string().min(1),
345
- done: zod.z.boolean(),
346
- doneBy: zod.z.string().optional(),
347
- doneAt: zod.z.string().optional(),
348
- _key: zod.z.string().min(1).optional()
349
- }).passthrough(), NoteItemShape = zod.z.object({
350
- _key: zod.z.string().min(1).optional()
351
- }).passthrough().refine((v) => typeof v == "object" && v !== null && !Array.isArray(v), {
352
- message: "must be an object"
353
- }), NullableString = zod.z.union([zod.z.null(), zod.z.string()]), NullableNumber = zod.z.union([zod.z.null(), zod.z.number()]), NullableBoolean = zod.z.union([zod.z.null(), zod.z.boolean()]), NullableDateTime = zod.z.union([
354
- zod.z.null(),
355
- zod.z.string().refine((s) => !Number.isNaN(Date.parse(s)), {
356
- message: "must be an ISO-8601 datetime string"
357
- })
345
+ const GdrShape = v__namespace.looseObject({
346
+ id: v__namespace.pipe(
347
+ v__namespace.string(),
348
+ v__namespace.check((s) => isGdrUri(s), "must be a GDR URI")
349
+ ),
350
+ type: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1))
351
+ }), ReleaseRefShape = v__namespace.looseObject({
352
+ id: v__namespace.pipe(
353
+ v__namespace.string(),
354
+ v__namespace.check((s) => isGdrUri(s), "must be a GDR URI")
355
+ ),
356
+ type: v__namespace.literal("system.release"),
357
+ releaseName: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1))
358
+ }), ActorShape = v__namespace.looseObject({
359
+ kind: v__namespace.picklist(["user", "ai", "system"]),
360
+ id: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)),
361
+ roles: v__namespace.optional(v__namespace.array(v__namespace.string())),
362
+ onBehalfOf: v__namespace.optional(v__namespace.string())
363
+ }), AssigneeShape = v__namespace.union([
364
+ v__namespace.looseObject({ kind: v__namespace.literal("user"), id: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)) }),
365
+ v__namespace.looseObject({ kind: v__namespace.literal("role"), role: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)) })
366
+ ]), ChecklistItemShape = v__namespace.looseObject({
367
+ label: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)),
368
+ done: v__namespace.boolean(),
369
+ doneBy: v__namespace.optional(v__namespace.string()),
370
+ doneAt: v__namespace.optional(v__namespace.string()),
371
+ _key: v__namespace.optional(v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)))
372
+ }), NoteItemShape = v__namespace.pipe(
373
+ v__namespace.looseObject({
374
+ _key: v__namespace.optional(v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)))
375
+ }),
376
+ v__namespace.check(
377
+ (val) => typeof val == "object" && val !== null && !Array.isArray(val),
378
+ "must be an object"
379
+ )
380
+ ), NullableString = v__namespace.union([v__namespace.null(), v__namespace.string()]), NullableNumber = v__namespace.union([v__namespace.null(), v__namespace.number()]), NullableBoolean = v__namespace.union([v__namespace.null(), v__namespace.boolean()]), NullableDateTime = v__namespace.union([
381
+ v__namespace.null(),
382
+ v__namespace.pipe(
383
+ v__namespace.string(),
384
+ v__namespace.check((s) => !Number.isNaN(Date.parse(s)), "must be an ISO-8601 datetime string")
385
+ )
358
386
  ]), NullableUrl = NullableString, valueSchemas = {
359
- "workflow.state.doc.ref": zod.z.union([zod.z.null(), GdrShape]),
360
- "workflow.state.doc.refs": zod.z.array(GdrShape),
361
- "workflow.state.release.ref": zod.z.union([zod.z.null(), ReleaseRefShape]),
362
- "workflow.state.query": zod.z.any(),
387
+ "workflow.state.doc.ref": v__namespace.union([v__namespace.null(), GdrShape]),
388
+ "workflow.state.doc.refs": v__namespace.array(GdrShape),
389
+ "workflow.state.release.ref": v__namespace.union([v__namespace.null(), ReleaseRefShape]),
390
+ "workflow.state.query": v__namespace.any(),
363
391
  "workflow.state.value.string": NullableString,
364
392
  "workflow.state.value.url": NullableUrl,
365
393
  "workflow.state.value.number": NullableNumber,
366
394
  "workflow.state.value.boolean": NullableBoolean,
367
395
  "workflow.state.value.dateTime": NullableDateTime,
368
- "workflow.state.value.actor": zod.z.union([zod.z.null(), ActorShape]),
369
- "workflow.state.checklist": zod.z.array(ChecklistItemShape),
370
- "workflow.state.notes": zod.z.array(NoteItemShape),
371
- "workflow.state.assignees": zod.z.array(AssigneeShape)
396
+ "workflow.state.value.actor": v__namespace.union([v__namespace.null(), ActorShape]),
397
+ "workflow.state.checklist": v__namespace.array(ChecklistItemShape),
398
+ "workflow.state.notes": v__namespace.array(NoteItemShape),
399
+ "workflow.state.assignees": v__namespace.array(AssigneeShape)
372
400
  }, itemSchemas = {
373
401
  "workflow.state.doc.refs": GdrShape,
374
402
  "workflow.state.checklist": ChecklistItemShape,
@@ -387,12 +415,12 @@ function validateSlotValue(args) {
387
415
  issues: [`unknown slot type ${args.slotType}`],
388
416
  mode: "value"
389
417
  });
390
- const result = schema.safeParse(args.value);
418
+ const result = v__namespace.safeParse(schema, args.value);
391
419
  if (!result.success)
392
420
  throw new SlotValueShapeError({
393
421
  slotType: args.slotType,
394
422
  slotId: args.slotId,
395
- issues: formatIssues(result.error.issues),
423
+ issues: formatIssues(result.issues),
396
424
  mode: "value"
397
425
  });
398
426
  }
@@ -404,17 +432,20 @@ function validateSlotAppendItem(args) {
404
432
  issues: [`slot type ${args.slotType} does not support append`],
405
433
  mode: "item"
406
434
  });
407
- const result = itemSchemas[args.slotType].safeParse(args.item);
435
+ const schema = itemSchemas[args.slotType], result = v__namespace.safeParse(schema, args.item);
408
436
  if (!result.success)
409
437
  throw new SlotValueShapeError({
410
438
  slotType: args.slotType,
411
439
  slotId: args.slotId,
412
- issues: formatIssues(result.error.issues),
440
+ issues: formatIssues(result.issues),
413
441
  mode: "item"
414
442
  });
415
443
  }
416
444
  function formatIssues(issues) {
417
- return issues.map((i) => `${i.path.length > 0 ? `at ${i.path.join(".")}: ` : ""}${i.message}`);
445
+ return issues.map((i) => {
446
+ const keys = i.path?.map((p) => p.key) ?? [];
447
+ return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${i.message}`;
448
+ });
418
449
  }
419
450
  function derivePerspectiveFromState(slots) {
420
451
  if (slots !== void 0) {
@@ -459,7 +490,7 @@ async function resolveQueryValue(slot, source, ctx, client, defaultValue) {
459
490
  state: stateMapFromResolved(ctx.resolvedState ?? []),
460
491
  stage: ctx.stageId ?? null,
461
492
  task: ctx.taskId ?? null,
462
- now: (/* @__PURE__ */ new Date()).toISOString(),
493
+ now: ctx.now,
463
494
  engineTags: ctx.tags ?? []
464
495
  });
465
496
  try {
@@ -476,7 +507,7 @@ async function resolveSlotValue(slot, initialState, ctx, defaultValue) {
476
507
  const source = slot.source;
477
508
  return source.kind === "init" ? resolveInitValue(slot, initialState, defaultValue) : source.kind === "literal" ? source.value ?? defaultValue : source.kind === "stateRead" ? resolveStateReadValue(source, ctx, defaultValue) : source.kind === "query" && ctx.client !== void 0 ? resolveQueryValue(slot, source, ctx, ctx.client, defaultValue) : defaultValue;
478
509
  }
479
- function buildResolvedSlot(slot, value, _key) {
510
+ function buildResolvedSlot(slot, value, _key, now) {
480
511
  const titleProp = slot.title !== void 0 ? { title: slot.title } : {}, descriptionProp = slot.description !== void 0 ? { description: slot.description } : {};
481
512
  return slot.type === "workflow.state.query" ? {
482
513
  _key,
@@ -485,7 +516,7 @@ function buildResolvedSlot(slot, value, _key) {
485
516
  ...titleProp,
486
517
  ...descriptionProp,
487
518
  value,
488
- resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
519
+ resolvedAt: now
489
520
  } : {
490
521
  _key,
491
522
  _type: slot.type,
@@ -497,7 +528,7 @@ function buildResolvedSlot(slot, value, _key) {
497
528
  }
498
529
  async function resolveOneSlot(slot, initialState, ctx, randomKey2) {
499
530
  const defaultValue = defaultSlotValue(slot.type), value = await resolveSlotValue(slot, initialState, ctx, defaultValue);
500
- return validateSlotValue({ slotType: slot.type, slotId: slot.id, value }), buildResolvedSlot(slot, value, randomKey2());
531
+ return validateSlotValue({ slotType: slot.type, slotId: slot.id, value }), buildResolvedSlot(slot, value, randomKey2(), ctx.now);
501
532
  }
502
533
  function stateMapFromResolved(slots) {
503
534
  const out = {};
@@ -511,10 +542,10 @@ function assertInitValueShape(slot, value) {
511
542
  }
512
543
  if (slot.type === "workflow.state.release.ref") {
513
544
  assertGdrShape(value, `state slot "${slot.id}" (workflow.state.release.ref)`);
514
- const v = value;
515
- if (typeof v.releaseName != "string" || v.releaseName.length === 0)
545
+ const v2 = value;
546
+ if (typeof v2.releaseName != "string" || v2.releaseName.length === 0)
516
547
  throw new Error(
517
- `Invalid init value for state slot "${slot.id}" (workflow.state.release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(v.releaseName)}.`
548
+ `Invalid init value for state slot "${slot.id}" (workflow.state.release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(v2.releaseName)}.`
518
549
  );
519
550
  return;
520
551
  }
@@ -532,18 +563,18 @@ function assertGdrShape(value, context) {
532
563
  throw new Error(
533
564
  `Invalid GDR for ${context}: expected { id: "<scheme>:...", type: "<schema>" }, got ${typeof value}.`
534
565
  );
535
- const v = value;
536
- if (typeof v.id != "string" || !isGdrUri(v.id))
566
+ const v2 = value;
567
+ if (typeof v2.id != "string" || !isGdrUri(v2.id))
537
568
  throw new Error(
538
- `Invalid GDR for ${context}: \`id\` must be a GDR URI ("<scheme>:<...id-parts>" with scheme dataset|canvas|media-library|dashboard). Got ${JSON.stringify(v.id)}. Construct via \`gdrFromResource\` / \`refDataset\` / \`refCanvas\` etc. \u2014 bare document ids are not accepted.`
569
+ `Invalid GDR for ${context}: \`id\` must be a GDR URI ("<scheme>:<...id-parts>" with scheme dataset|canvas|media-library|dashboard). Got ${JSON.stringify(v2.id)}. Construct via \`gdrFromResource\` / \`refDataset\` / \`refCanvas\` etc. \u2014 bare document ids are not accepted.`
539
570
  );
540
- if (typeof v.type != "string" || v.type.length === 0)
571
+ if (typeof v2.type != "string" || v2.type.length === 0)
541
572
  throw new Error(
542
- `Invalid GDR for ${context}: \`type\` (schema name) must be a non-empty string. Got ${JSON.stringify(v.type)}.`
573
+ `Invalid GDR for ${context}: \`type\` (schema name) must be a non-empty string. Got ${JSON.stringify(v2.type)}.`
543
574
  );
544
575
  }
545
576
  function normalizeQueryResult(slotType, raw, workflowResource) {
546
- return raw == null ? raw : slotType === "workflow.state.doc.ref" ? coerceToGdr(raw, workflowResource) : slotType === "workflow.state.doc.refs" ? Array.isArray(raw) ? raw.map((item) => coerceToGdr(item, workflowResource)).filter((v) => v !== null) : [] : raw;
577
+ return raw == null ? raw : slotType === "workflow.state.doc.ref" ? coerceToGdr(raw, workflowResource) : slotType === "workflow.state.doc.refs" ? Array.isArray(raw) ? raw.map((item) => coerceToGdr(item, workflowResource)).filter((v2) => v2 !== null) : [] : raw;
547
578
  }
548
579
  function toGdrUri(docId, workflowResource) {
549
580
  return isGdrUri(docId) ? docId : gdrFromResource(workflowResource, docId);
@@ -568,22 +599,24 @@ async function loadContext(client, instanceId, options) {
568
599
  const instance = await client.getDocument(instanceId);
569
600
  if (!instance)
570
601
  throw new Error(`Workflow instance ${instanceId} not found`);
571
- const definition = JSON.parse(instance.definitionSnapshot), clientForGdr = options?.clientForGdr ?? (() => client), snapshot = await hydrateSnapshot({ client, clientForGdr, instance, definition });
572
- return { client, clientForGdr, instance, definition, snapshot };
602
+ const definition = JSON.parse(instance.definitionSnapshot), clientForGdr = options?.clientForGdr ?? (() => client), clock = options?.clock ?? wallClock, snapshot = await hydrateSnapshot({ client, clientForGdr, instance, definition });
603
+ return { client, clientForGdr, clock, now: clock(), instance, definition, snapshot };
573
604
  }
574
605
  function ctxEvaluateFilter(ctx, filter) {
575
606
  return evaluateFilter({
576
607
  filter,
577
608
  definition: ctx.definition,
578
609
  snapshot: ctx.snapshot,
579
- params: buildParams(ctx.instance)
610
+ params: buildParams(ctx.instance, ctx.now)
580
611
  });
581
612
  }
582
613
  async function buildEngineContext(args) {
583
- const { client, clientForGdr, instance, definition } = args;
614
+ const { client, clientForGdr, instance, definition } = args, clock = args.clock ?? wallClock;
584
615
  return {
585
616
  client,
586
617
  clientForGdr,
618
+ clock,
619
+ now: clock(),
587
620
  instance,
588
621
  definition,
589
622
  snapshot: await hydrateSnapshot({ client, clientForGdr, instance, definition })
@@ -596,12 +629,13 @@ function findStage(definition, stageId) {
596
629
  return stage;
597
630
  }
598
631
  async function resolveStageStateSlots(args) {
599
- const { client, instance, stage, initialState } = args;
632
+ const { client, instance, stage, now, initialState } = args;
600
633
  return resolveDeclaredState({
601
634
  slotDefs: stage.state,
602
635
  initialState: initialState ?? [],
603
636
  ctx: {
604
637
  client,
638
+ now,
605
639
  selfId: instance._id,
606
640
  tags: instance.tags ?? [],
607
641
  stageId: stage.id,
@@ -616,12 +650,13 @@ async function resolveStageStateSlots(args) {
616
650
  });
617
651
  }
618
652
  async function resolveTaskStateSlots(args) {
619
- const { client, instance, stage, task } = args;
653
+ const { client, instance, stage, task, now } = args;
620
654
  return resolveDeclaredState({
621
655
  slotDefs: task.state,
622
656
  initialState: [],
623
657
  ctx: {
624
658
  client,
659
+ now,
625
660
  selfId: instance._id,
626
661
  tags: instance.tags ?? [],
627
662
  stageId: stage.id,
@@ -649,16 +684,24 @@ function slotValue$1(slot) {
649
684
  if (slot != null && typeof slot == "object")
650
685
  return slot.value;
651
686
  }
652
- function resolveSource(src, ctx) {
687
+ function resolveStaticSource(src, ctx) {
653
688
  switch (src.source) {
654
689
  case "literal":
655
- return src.value;
690
+ return { handled: !0, value: src.value };
656
691
  case "param":
657
- return ctx.params?.[src.paramId];
692
+ return { handled: !0, value: ctx.params?.[src.paramId] };
658
693
  case "actor":
659
- return ctx.actor;
694
+ return { handled: !0, value: ctx.actor };
660
695
  case "now":
661
- return (/* @__PURE__ */ new Date()).toISOString();
696
+ return { handled: !0, value: ctx.now };
697
+ default:
698
+ return { handled: !1 };
699
+ }
700
+ }
701
+ function resolveSource(src, ctx) {
702
+ const staticValue = resolveStaticSource(src, ctx);
703
+ if (staticValue.handled) return staticValue.value;
704
+ switch (src.source) {
662
705
  case "self":
663
706
  return ctx.instance._id;
664
707
  case "stageId":
@@ -669,6 +712,11 @@ function resolveSource(src, ctx) {
669
712
  return resolveEffectOutput(src, ctx);
670
713
  case "object":
671
714
  return resolveObject(src, ctx);
715
+ case "row":
716
+ case "parentState":
717
+ return;
718
+ default:
719
+ return;
672
720
  }
673
721
  }
674
722
  function resolveStateRead(src, ctx) {
@@ -688,10 +736,11 @@ function resolveObject(src, ctx) {
688
736
  return out;
689
737
  }
690
738
  async function resolveBindings(args) {
691
- const { bindings, staticInput, instance, params, actor } = args, resolved = {};
739
+ const { bindings, staticInput, instance, now, params, actor } = args, resolved = {};
692
740
  if (bindings) {
693
741
  const ctx = {
694
742
  instance,
743
+ now,
695
744
  ...params !== void 0 ? { params } : {},
696
745
  ...actor !== void 0 ? { actor } : {}
697
746
  };
@@ -738,16 +787,29 @@ function buildInstanceBase(args) {
738
787
  lastChangedAt: now
739
788
  };
740
789
  }
790
+ const GUARD_DOC_TYPE = "temp.system.guard";
791
+ class MutationGuardDeniedError extends Error {
792
+ denied;
793
+ documentId;
794
+ action;
795
+ constructor(args) {
796
+ const ids = args.denied.map((d) => d.guardId).join(", ");
797
+ super(`Mutation on "${args.documentId}" (${args.action}) denied by guard(s) [${ids}]`), this.name = "MutationGuardDeniedError", this.denied = args.denied, this.documentId = args.documentId, this.action = args.action;
798
+ }
799
+ }
741
800
  function lakeGuardId(args) {
742
- return `temp.system.guard.${args.instanceDocId}.${args.stageId}.${args.index}`;
801
+ return `${GUARD_DOC_TYPE}.${args.instanceDocId}.${args.stageId}.${args.index}`;
743
802
  }
744
803
  function compileGuard(args) {
745
804
  return {
746
805
  _id: args.id,
747
- _type: "temp.system.guard",
806
+ _type: GUARD_DOC_TYPE,
748
807
  resourceType: args.resourceType,
749
808
  resourceId: args.resourceId,
750
809
  owner: args.owner,
810
+ sourceInstanceId: args.sourceInstanceId,
811
+ sourceDefinitionId: args.sourceDefinitionId,
812
+ sourceStageId: args.sourceStageId,
751
813
  ...args.name !== void 0 ? { name: args.name } : {},
752
814
  ...args.description !== void 0 ? { description: args.description } : {},
753
815
  match: args.match,
@@ -803,9 +865,9 @@ function resolveIdRefTarget(src, ctx) {
803
865
  if (typeof value == "string" && isGdrUri(value))
804
866
  return { parsed: parseGdr(value) };
805
867
  if (value && typeof value == "object") {
806
- const v = value;
807
- if (typeof v.id == "string" && isGdrUri(v.id))
808
- return typeof v.type == "string" ? { parsed: parseGdr(v.id), type: v.type } : { parsed: parseGdr(v.id) };
868
+ const v2 = value;
869
+ if (typeof v2.id == "string" && isGdrUri(v2.id))
870
+ return typeof v2.type == "string" ? { parsed: parseGdr(v2.id), type: v2.type } : { parsed: parseGdr(v2.id) };
809
871
  }
810
872
  return null;
811
873
  }
@@ -844,8 +906,8 @@ function resolveMetadata(metadata, ctx) {
844
906
  return out;
845
907
  }
846
908
  const GUARD_OWNER = "robot:workflow-engine", GUARD_LIFTED_PREDICATE = "true";
847
- function resolveGuard(guard, index, instance, stageId) {
848
- const ctx = { instance, stageId }, targets = resolveIdRefTargets(guard.match.idRefs, ctx);
909
+ function resolveGuard(guard, index, instance, stageId, now) {
910
+ const ctx = { instance, stageId, now }, targets = resolveIdRefTargets(guard.match.idRefs, ctx);
849
911
  if (targets === null) return null;
850
912
  const resource = assertSingleResource(targets), types = resolveMatchTypes(targets, guard.match.types);
851
913
  return { doc: compileGuard({
@@ -853,6 +915,9 @@ function resolveGuard(guard, index, instance, stageId) {
853
915
  resourceType: resource.type,
854
916
  resourceId: resource.id,
855
917
  owner: GUARD_OWNER,
918
+ sourceInstanceId: instance._id,
919
+ sourceDefinitionId: instance.workflowId,
920
+ sourceStageId: stageId,
856
921
  ...guard.name !== void 0 ? { name: guard.name } : {},
857
922
  ...guard.description !== void 0 ? { description: guard.description } : {},
858
923
  match: {
@@ -866,47 +931,108 @@ function resolveGuard(guard, index, instance, stageId) {
866
931
  }), routeGdr: targets[0].parsed };
867
932
  }
868
933
  async function upsertGuard(client, doc) {
869
- const existing = await client.getDocument(doc._id), set = {
870
- resourceType: doc.resourceType,
871
- resourceId: doc.resourceId,
872
- owner: doc.owner,
873
- match: doc.match,
874
- predicate: doc.predicate,
875
- metadata: doc.metadata
876
- };
877
- doc.name !== void 0 && (set.name = doc.name), doc.description !== void 0 && (set.description = doc.description), existing ? await client.patch(doc._id).set(set).commit() : await client.create(doc);
934
+ if (!await client.getDocument(doc._id)) {
935
+ await client.create(doc);
936
+ return;
937
+ }
938
+ const { _id, _type, _rev, _createdAt, _updatedAt, ...body } = doc;
939
+ await client.patch(doc._id).set(body).commit();
878
940
  }
879
941
  function resolvedStageGuards(args) {
880
942
  const stage = args.definition.stages.find((s) => s.id === args.stageId), out = [];
881
943
  for (const [index, guard] of (stage?.guards ?? []).entries()) {
882
- const resolved = resolveGuard(guard, index, args.instance, args.stageId);
944
+ const resolved = resolveGuard(guard, index, args.instance, args.stageId, args.now);
883
945
  resolved !== null && out.push({ client: args.clientForGdr(resolved.routeGdr), doc: resolved.doc });
884
946
  }
885
947
  return out;
886
948
  }
949
+ async function committedStageId(args) {
950
+ return (await args.client.getDocument(args.instance._id))?.currentStageId;
951
+ }
887
952
  async function deployStageGuards(args) {
888
- for (const { client, doc } of resolvedStageGuards(args))
889
- await upsertGuard(client, doc);
953
+ if (await committedStageId(args) === args.stageId)
954
+ for (const { client, doc } of resolvedStageGuards(args))
955
+ await upsertGuard(client, doc);
890
956
  }
891
957
  async function retractStageGuards(args) {
892
- for (const { client, doc } of resolvedStageGuards(args))
893
- await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit();
958
+ const live = await committedStageId(args);
959
+ if (!(live === void 0 || live === args.stageId))
960
+ for (const { client, doc } of resolvedStageGuards(args))
961
+ await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit();
894
962
  }
895
- async function reconcileStageGuards(args) {
896
- const { client, clientForGdr, instance, definition, exitedStageId, enteredStageId } = args;
897
- exitedStageId !== void 0 && await retractStageGuards({
898
- client,
899
- clientForGdr,
900
- instance,
901
- definition,
902
- stageId: exitedStageId
903
- }), enteredStageId !== void 0 && await deployStageGuards({
904
- client,
905
- clientForGdr,
906
- instance,
907
- definition,
908
- stageId: enteredStageId
909
- });
963
+ class ActionParamsInvalidError extends Error {
964
+ action;
965
+ taskId;
966
+ issues;
967
+ constructor(args) {
968
+ const lines = args.issues.map((i) => ` - ${i.paramId}: ${i.reason}`).join(`
969
+ `);
970
+ super(`Action "${args.action}" on task "${args.taskId}" rejected: invalid params
971
+ ${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.taskId = args.taskId, this.issues = args.issues;
972
+ }
973
+ }
974
+ class WorkflowStateDivergedError extends Error {
975
+ instanceId;
976
+ /** The guard-deploy failure that triggered the (attempted) rollback. */
977
+ guardError;
978
+ /** The rollback failure, when rollback was attempted and itself failed. */
979
+ rollbackError;
980
+ constructor(args) {
981
+ super(
982
+ `Workflow "${args.instanceId}" state committed but its guards could not be reconciled: ${args.reason}.`,
983
+ { cause: args.guardError }
984
+ ), this.name = "WorkflowStateDivergedError", this.instanceId = args.instanceId, this.guardError = args.guardError, args.rollbackError !== void 0 && (this.rollbackError = args.rollbackError);
985
+ }
986
+ }
987
+ const CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS = 3;
988
+ class ConcurrentFireActionError extends Error {
989
+ instanceId;
990
+ taskId;
991
+ action;
992
+ attempts;
993
+ constructor(args) {
994
+ super(
995
+ `Action "${args.action}" on task "${args.taskId}" (instance ${args.instanceId}) lost the optimistic-locking race ${args.attempts} attempts running \u2014 a concurrent writer kept committing first. Re-read and retry, or investigate a write storm on this instance.`
996
+ ), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.taskId = args.taskId, this.action = args.action, this.attempts = args.attempts;
997
+ }
998
+ }
999
+ function isRevisionConflict(error) {
1000
+ if (typeof error != "object" || error === null) return !1;
1001
+ const { statusCode, message } = error;
1002
+ return statusCode === 409 ? !0 : typeof message == "string" && message.includes("ifRevisionId check failed");
1003
+ }
1004
+ class CascadeLimitError extends Error {
1005
+ instanceId;
1006
+ limit;
1007
+ constructor(args) {
1008
+ super(
1009
+ `Cascade did not stabilise after ${args.limit} auto-transitions on ${args.instanceId} \u2014 likely a runaway loop (transition guards that stay mutually satisfied). Check that each transition's filter eventually goes false.`
1010
+ ), this.name = "CascadeLimitError", this.instanceId = args.instanceId, this.limit = args.limit;
1011
+ }
1012
+ }
1013
+ async function deployOrRollback(args) {
1014
+ try {
1015
+ await args.deploy();
1016
+ } catch (guardError) {
1017
+ if (!args.reversible)
1018
+ throw new WorkflowStateDivergedError({
1019
+ instanceId: args.instanceId,
1020
+ guardError,
1021
+ reason: "the move created child instances a parent-only rollback cannot undo"
1022
+ });
1023
+ try {
1024
+ let patch = args.client.patch(args.instanceId).set(args.restore);
1025
+ args.unset && args.unset.length > 0 && (patch = patch.unset(args.unset)), args.committedRev !== void 0 && (patch = patch.ifRevisionId(args.committedRev)), await patch.commit();
1026
+ } catch (rollbackError) {
1027
+ throw new WorkflowStateDivergedError({
1028
+ instanceId: args.instanceId,
1029
+ guardError,
1030
+ rollbackError,
1031
+ reason: "rollback of the committed move failed"
1032
+ });
1033
+ }
1034
+ throw guardError;
1035
+ }
910
1036
  }
911
1037
  async function discoverItems(args) {
912
1038
  const { client, groq, params, perspective } = args, fetchOptions = perspective !== void 0 ? { perspective } : void 0;
@@ -923,31 +1049,29 @@ function clientForDiscovery(defaultClient, clientForGdr, subjectGdrUri) {
923
1049
  const MAX_SPAWN_DEPTH = 6;
924
1050
  async function spawnChildren(ctx, mutation, task, spawn, actor) {
925
1051
  if (ctx.instance.ancestors.length + 1 > MAX_SPAWN_DEPTH) {
926
- const chain = [
927
- ...ctx.instance.ancestors.map((a) => a.id),
928
- gdrFromResource(ctx.instance.workflowResource, ctx.instance._id)
929
- ].join(" \u2192 ");
1052
+ const chain = [...ctx.instance.ancestors.map((a) => a.id), selfGdr(ctx.instance)].join(" \u2192 ");
930
1053
  throw new Error(
931
1054
  `Spawn depth limit (${MAX_SPAWN_DEPTH}) exceeded on task "${task.id}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
932
1055
  );
933
1056
  }
934
- const rows = await resolveSpawnRows(ctx, spawn), refs = [];
1057
+ const now = ctx.now, rows = await resolveSpawnRows(ctx, spawn), refs = [];
935
1058
  for (const row of rows) {
936
- const projected = projectSpawnRow(ctx.instance, spawn, row), { ref, body } = await prepareChildInstance({
1059
+ const projected = projectSpawnRow(ctx.instance, spawn, row, now), { ref, body } = await prepareChildInstance({
937
1060
  client: ctx.client,
938
1061
  parent: ctx.instance,
939
1062
  task,
940
1063
  spawn,
941
1064
  initialState: projected.initialState,
942
1065
  effectsContext: projected.effectsContext,
943
- actor
1066
+ actor,
1067
+ now
944
1068
  });
945
1069
  refs.push(ref), mutation.pendingCreates.push(body);
946
1070
  }
947
1071
  return refs;
948
1072
  }
949
1073
  async function resolveSpawnRows(ctx, spawn) {
950
- const params = buildParams(ctx.instance), groq = spawn.forEach.groq;
1074
+ const params = buildParams(ctx.instance, ctx.now), groq = spawn.forEach.groq;
951
1075
  let value;
952
1076
  if (groq.includes("*")) {
953
1077
  const routingUri = firstDocRefGdrUri(ctx.instance.state), client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
@@ -969,7 +1093,7 @@ function firstDocRefGdrUri(slots) {
969
1093
  if (s._type === "workflow.state.doc.ref" && s.value !== null) return s.value.id;
970
1094
  }
971
1095
  }
972
- function resolveSpawnSource(source, parent, row) {
1096
+ function resolveSpawnSource(source, parent, row, now) {
973
1097
  switch (source.source) {
974
1098
  case "literal":
975
1099
  return source.value;
@@ -980,15 +1104,15 @@ function resolveSpawnSource(source, parent, row) {
980
1104
  return slot === void 0 ? null : source.path !== void 0 ? getPath(slot, source.path) : slot;
981
1105
  }
982
1106
  case "self":
983
- return gdrFromResource(parent.workflowResource, parent._id);
1107
+ return selfGdr(parent);
984
1108
  case "stageId":
985
1109
  return parent.currentStageId;
986
1110
  case "now":
987
- return (/* @__PURE__ */ new Date()).toISOString();
1111
+ return now;
988
1112
  case "object": {
989
1113
  const out = {};
990
- for (const [k, v] of Object.entries(source.fields))
991
- out[k] = resolveSpawnSource(v, parent, row);
1114
+ for (const [k, v2] of Object.entries(source.fields))
1115
+ out[k] = resolveSpawnSource(v2, parent, row, now);
992
1116
  return out;
993
1117
  }
994
1118
  case "actor":
@@ -998,10 +1122,10 @@ function resolveSpawnSource(source, parent, row) {
998
1122
  return null;
999
1123
  }
1000
1124
  }
1001
- function projectSpawnRow(parent, spawn, row) {
1125
+ function projectSpawnRow(parent, spawn, row, now) {
1002
1126
  const initialState = [];
1003
1127
  for (const entry of spawn.forEach.as.initialState ?? []) {
1004
- const raw = resolveSpawnSource(entry.value, parent, row), value = canonicaliseSpawnValue(entry.type, raw, parent.workflowResource);
1128
+ const raw = resolveSpawnSource(entry.value, parent, row, now), value = canonicaliseSpawnValue(entry.type, raw, parent.workflowResource);
1005
1129
  initialState.push({
1006
1130
  type: entry.type,
1007
1131
  id: entry.id,
@@ -1010,13 +1134,13 @@ function projectSpawnRow(parent, spawn, row) {
1010
1134
  }
1011
1135
  const effectsContext = {};
1012
1136
  for (const [k, src] of Object.entries(spawn.forEach.as.effectsContext ?? {})) {
1013
- const v = resolveSpawnSource(src, parent, row);
1014
- v != null && (typeof v == "string" || typeof v == "number" || typeof v == "boolean" || typeof v == "object" && "id" in v && "type" in v) && (effectsContext[k] = v);
1137
+ const v2 = resolveSpawnSource(src, parent, row, now);
1138
+ v2 != null && (typeof v2 == "string" || typeof v2 == "number" || typeof v2 == "boolean" || typeof v2 == "object" && "id" in v2 && "type" in v2) && (effectsContext[k] = v2);
1015
1139
  }
1016
1140
  return { initialState, effectsContext };
1017
1141
  }
1018
1142
  function canonicaliseSpawnValue(slotType, value, workflowResource) {
1019
- return slotType === "workflow.state.doc.ref" ? coerceSpawnGdr(value, workflowResource) : slotType === "workflow.state.doc.refs" && Array.isArray(value) ? value.map((v) => coerceSpawnGdr(v, workflowResource)).filter((v) => v !== null) : value;
1143
+ return slotType === "workflow.state.doc.ref" ? coerceSpawnGdr(value, workflowResource) : slotType === "workflow.state.doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, workflowResource)).filter((v2) => v2 !== null) : value;
1020
1144
  }
1021
1145
  function coerceSpawnGdr(raw, workflowResource) {
1022
1146
  const toUri = (id) => isGdrUri(id) ? id : gdrFromResource(workflowResource, id);
@@ -1039,7 +1163,7 @@ async function resolveLogicalDefinition(client, ref, tags) {
1039
1163
  return await client.fetch(groq, params) ?? null;
1040
1164
  }
1041
1165
  async function prepareChildInstance(args) {
1042
- const { client, parent, task, spawn, initialState, effectsContext, actor } = args, definition = await resolveLogicalDefinition(client, spawn.definitionRef, parent.tags ?? []);
1166
+ const { client, parent, task, spawn, initialState, effectsContext, actor, now } = args, definition = await resolveLogicalDefinition(client, spawn.definitionRef, parent.tags ?? []);
1043
1167
  if (definition === null) {
1044
1168
  const versionLabel = spawn.definitionRef.version === void 0 || spawn.definitionRef.version === "latest" ? "latest" : `v${spawn.definitionRef.version}`;
1045
1169
  throw new Error(
@@ -1049,7 +1173,7 @@ async function prepareChildInstance(args) {
1049
1173
  const childTags = parent.tags ?? [], childPrefix = childTags[0] ?? "wf", workflowResource = parent.workflowResource, childDocId = `${childPrefix}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type: "workflow.instance" }, ancestors = [
1050
1174
  ...parent.ancestors,
1051
1175
  {
1052
- id: gdrFromResource(parent.workflowResource, parent._id),
1176
+ id: selfGdr(parent),
1053
1177
  type: "workflow.instance"
1054
1178
  }
1055
1179
  ], inheritedPerspective = parent.perspective, childState = await resolveDeclaredState({
@@ -1057,6 +1181,7 @@ async function prepareChildInstance(args) {
1057
1181
  initialState,
1058
1182
  ctx: {
1059
1183
  client,
1184
+ now,
1060
1185
  selfId: childDocId,
1061
1186
  tags: childTags,
1062
1187
  workflowResource,
@@ -1076,7 +1201,7 @@ async function prepareChildInstance(args) {
1076
1201
  }
1077
1202
  ), base = buildInstanceBase({
1078
1203
  id: childDocId,
1079
- now: (/* @__PURE__ */ new Date()).toISOString(),
1204
+ now,
1080
1205
  tags: childTags,
1081
1206
  workflowResource,
1082
1207
  workflowId: definition.workflowId,
@@ -1112,7 +1237,9 @@ async function checkSpawnCompletion(client, entry, spawn) {
1112
1237
  }
1113
1238
  }
1114
1239
  async function invokeTask(args) {
1115
- const { client, instanceId, taskId, options } = args, ctx = await loadContext(client, instanceId);
1240
+ const { client, instanceId, taskId, options } = args, ctx = await loadContext(client, instanceId, {
1241
+ ...options?.clock ? { clock: options.clock } : {}
1242
+ });
1116
1243
  return commitInvoke(ctx, taskId, options?.actor);
1117
1244
  }
1118
1245
  async function commitInvoke(ctx, taskId, actor) {
@@ -1124,41 +1251,41 @@ async function commitInvoke(ctx, taskId, actor) {
1124
1251
  const mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation, taskId);
1125
1252
  return await activateTask(ctx, mutation, task, mutEntry, actor), await persist(ctx, mutation), { invoked: !0, taskId };
1126
1253
  }
1127
- async function autoResolveOnActivate(ctx, mutation, task, entry) {
1254
+ async function autoResolveOnActivate(ctx, mutation, task, entry, now) {
1128
1255
  const checks = [
1129
1256
  { filter: task.failWhen, outcome: "failed", systemId: "engine.failWhen" },
1130
1257
  { filter: task.completeWhen, outcome: "done", systemId: "engine.completeWhen" }
1131
1258
  ];
1132
- for (const { filter, outcome, systemId } of checks) {
1133
- if (filter === void 0 || !await ctxEvaluateFilter(ctx, filter)) continue;
1134
- const completedAt = (/* @__PURE__ */ new Date()).toISOString();
1135
- return entry.status = outcome, entry.completedAt = completedAt, entry.completedBy = systemId, mutation.history.push(
1136
- taskStatusChangedEntry({
1137
- stageId: mutation.currentStageId,
1138
- taskId: task.id,
1139
- from: "active",
1140
- to: outcome,
1141
- actor: { kind: "system", id: systemId },
1142
- at: completedAt
1143
- })
1144
- ), !0;
1145
- }
1259
+ for (const { filter, outcome, systemId } of checks)
1260
+ if (filter !== void 0 && await ctxEvaluateFilter(ctx, filter))
1261
+ return entry.status = outcome, entry.completedAt = now, entry.completedBy = systemId, mutation.history.push(
1262
+ taskStatusChangedEntry({
1263
+ stageId: mutation.currentStageId,
1264
+ taskId: task.id,
1265
+ from: "active",
1266
+ to: outcome,
1267
+ at: now,
1268
+ actor: { kind: "system", id: systemId }
1269
+ })
1270
+ ), !0;
1146
1271
  return !1;
1147
1272
  }
1148
1273
  async function activateTask(ctx, mutation, task, entry, actor) {
1149
- if (entry.status = "active", entry.startedAt = (/* @__PURE__ */ new Date()).toISOString(), task.state !== void 0 && task.state.length > 0) {
1274
+ const now = ctx.now;
1275
+ if (entry.status = "active", entry.startedAt = now, task.state !== void 0 && task.state.length > 0) {
1150
1276
  const stage = findStage(ctx.definition, mutation.currentStageId);
1151
1277
  entry.state = await resolveTaskStateSlots({
1152
1278
  client: ctx.client,
1153
1279
  instance: ctx.instance,
1154
1280
  stage,
1155
- task
1281
+ task,
1282
+ now
1156
1283
  });
1157
1284
  }
1158
- if (!await autoResolveOnActivate(ctx, mutation, task, entry) && (mutation.history.push({
1285
+ if (!await autoResolveOnActivate(ctx, mutation, task, entry, now) && (mutation.history.push({
1159
1286
  _key: randomKey(),
1160
1287
  _type: "workflow.history.taskActivated",
1161
- at: (/* @__PURE__ */ new Date()).toISOString(),
1288
+ at: now,
1162
1289
  stageId: mutation.currentStageId,
1163
1290
  taskId: task.id,
1164
1291
  ...actor !== void 0 ? { actor } : {}
@@ -1178,18 +1305,19 @@ async function activateTask(ctx, mutation, task, entry, actor) {
1178
1305
  mutation.history.push({
1179
1306
  _key: randomKey(),
1180
1307
  _type: "workflow.history.spawned",
1181
- at: (/* @__PURE__ */ new Date()).toISOString(),
1308
+ at: now,
1182
1309
  taskId: task.id,
1183
1310
  instanceRef: ref
1184
1311
  });
1185
1312
  if (await checkSpawnCompletion(ctx.client, entry, task.spawns)) {
1186
1313
  const previousStatus = entry.status;
1187
- entry.status = "done", entry.completedAt = (/* @__PURE__ */ new Date()).toISOString(), mutation.history.push(
1314
+ entry.status = "done", entry.completedAt = now, mutation.history.push(
1188
1315
  taskStatusChangedEntry({
1189
1316
  stageId: mutation.currentStageId,
1190
1317
  taskId: task.id,
1191
1318
  from: previousStatus,
1192
1319
  to: "done",
1320
+ at: now,
1193
1321
  ...actor !== void 0 ? { actor } : {}
1194
1322
  })
1195
1323
  );
@@ -1206,7 +1334,7 @@ async function buildStageTasks(ctx, stage) {
1206
1334
  status: inScope ? "pending" : "skipped",
1207
1335
  ...task.filter !== void 0 ? {
1208
1336
  filterEvaluation: {
1209
- at: (/* @__PURE__ */ new Date()).toISOString(),
1337
+ at: ctx.now,
1210
1338
  truthy: inScope
1211
1339
  }
1212
1340
  } : {}
@@ -1218,7 +1346,9 @@ function activatesOnStageEnter(task) {
1218
1346
  return task.invokeOn === "stageEnter" || task.spawns !== void 0 || task.completeWhen !== void 0 || task.failWhen !== void 0;
1219
1347
  }
1220
1348
  async function setStage(args) {
1221
- const { client, instanceId, targetStageId, reason, options } = args, ctx = await loadContext(client, instanceId);
1349
+ const { client, instanceId, targetStageId, reason, options } = args, ctx = await loadContext(client, instanceId, {
1350
+ ...options?.clock ? { clock: options.clock } : {}
1351
+ });
1222
1352
  return commitSetStage(ctx, targetStageId, reason, options?.actor);
1223
1353
  }
1224
1354
  async function enterStage(ctx, mutation, nextStage, actor, at) {
@@ -1230,7 +1360,8 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
1230
1360
  state: await resolveStageStateSlots({
1231
1361
  client: ctx.client,
1232
1362
  instance: ctx.instance,
1233
- stage: nextStage
1363
+ stage: nextStage,
1364
+ now: at
1234
1365
  }),
1235
1366
  tasks: await buildStageTasks(ctx, nextStage)
1236
1367
  };
@@ -1249,7 +1380,7 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
1249
1380
  nextStage.kind === "terminal" && (mutation.completedAt = at);
1250
1381
  }
1251
1382
  async function beginStageMove(ctx, currentStage, actor) {
1252
- const mutation = startMutation(ctx.instance), at = (/* @__PURE__ */ new Date()).toISOString();
1383
+ const mutation = startMutation(ctx.instance), at = ctx.now;
1253
1384
  return await queueEffects(
1254
1385
  ctx,
1255
1386
  mutation,
@@ -1283,13 +1414,46 @@ async function commitSetStage(ctx, targetStageId, reason, actor) {
1283
1414
  };
1284
1415
  }
1285
1416
  async function persistStageMove(ctx, mutation, exitedStageId, enteredStageId) {
1286
- await persist(ctx, mutation), await reconcileStageGuards({
1417
+ await persistThenDeploy(
1418
+ ctx,
1419
+ mutation,
1420
+ () => deployStageGuards({
1421
+ client: ctx.client,
1422
+ clientForGdr: ctx.clientForGdr,
1423
+ instance: materializeInstance(ctx.instance, mutation),
1424
+ definition: ctx.definition,
1425
+ stageId: enteredStageId,
1426
+ now: ctx.now
1427
+ })
1428
+ ), await retractStageGuards({
1287
1429
  client: ctx.client,
1288
1430
  clientForGdr: ctx.clientForGdr,
1289
1431
  instance: ctx.instance,
1290
1432
  definition: ctx.definition,
1291
- exitedStageId,
1292
- enteredStageId
1433
+ stageId: exitedStageId,
1434
+ now: ctx.now
1435
+ });
1436
+ }
1437
+ async function persistThenDeploy(ctx, mutation, deploy) {
1438
+ const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
1439
+ await deployOrRollback({
1440
+ client: ctx.client,
1441
+ instanceId: ctx.instance._id,
1442
+ committedRev: committed._rev,
1443
+ restore: instanceStateFields(ctx.instance),
1444
+ unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
1445
+ reversible: !spawned,
1446
+ deploy
1447
+ });
1448
+ }
1449
+ async function refreshStageGuards(ctx, mutation, stageId) {
1450
+ await deployStageGuards({
1451
+ client: ctx.client,
1452
+ clientForGdr: ctx.clientForGdr,
1453
+ instance: materializeInstance(ctx.instance, mutation),
1454
+ definition: ctx.definition,
1455
+ stageId,
1456
+ now: ctx.now
1293
1457
  });
1294
1458
  }
1295
1459
  async function commitTransition(ctx, action, actor) {
@@ -1331,15 +1495,18 @@ async function pickTransition(ctx, stage, action) {
1331
1495
  if (await ctxEvaluateFilter(ctx, candidate.filter)) return candidate;
1332
1496
  }
1333
1497
  async function evaluateAutoTransitions(args) {
1334
- const { client, instanceId, options, clientForGdr } = args, ctx = await loadContext(client, instanceId, clientForGdr ? { clientForGdr } : void 0);
1498
+ const { client, instanceId, options, clientForGdr, clock } = args, ctx = await loadContext(client, instanceId, {
1499
+ ...clientForGdr ? { clientForGdr } : {},
1500
+ ...clock ? { clock } : {}
1501
+ });
1335
1502
  return commitTransition(ctx, void 0, options?.actor);
1336
1503
  }
1337
- async function primeInitialStage(client, instanceId, actor, clientForGdr) {
1504
+ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock) {
1338
1505
  const instance = await client.getDocument(instanceId);
1339
1506
  if (!instance || instance.stages.length > 0 || instance.pendingEffects.length > 0) return;
1340
1507
  const definition = JSON.parse(instance.definitionSnapshot), stage = definition.stages.find((s) => s.id === instance.currentStageId);
1341
1508
  if (stage === void 0) return;
1342
- const snapshot = await hydrateSnapshot({
1509
+ const now = (clock ?? wallClock)(), snapshot = await hydrateSnapshot({
1343
1510
  client,
1344
1511
  clientForGdr: clientForGdr ?? (() => client),
1345
1512
  instance,
@@ -1350,7 +1517,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr) {
1350
1517
  filter: task.filter,
1351
1518
  definition,
1352
1519
  snapshot,
1353
- params: buildParams(instance)
1520
+ params: buildParams(instance, now)
1354
1521
  });
1355
1522
  tasks.push({
1356
1523
  _key: randomKey(),
@@ -1361,8 +1528,8 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr) {
1361
1528
  const initialStageEntry = {
1362
1529
  _key: randomKey(),
1363
1530
  id: stage.id,
1364
- enteredAt: (/* @__PURE__ */ new Date()).toISOString(),
1365
- state: await resolveStageStateSlots({ client, instance, stage }),
1531
+ enteredAt: now,
1532
+ state: await resolveStageStateSlots({ client, instance, stage, now }),
1366
1533
  tasks
1367
1534
  }, pendingEffects = [], newHistory = [...instance.history];
1368
1535
  for (const effect of stage.onEnter ?? []) {
@@ -1370,29 +1537,44 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr) {
1370
1537
  bindings: effect.bindings,
1371
1538
  staticInput: effect.input,
1372
1539
  instance,
1540
+ now,
1373
1541
  ...actor !== void 0 ? { actor } : {}
1374
1542
  }), { pending, history } = buildQueuedEffect(
1375
1543
  effect,
1376
1544
  { kind: "stageEnter", id: stage.id },
1377
1545
  params,
1378
- actor
1546
+ actor,
1547
+ now
1379
1548
  );
1380
1549
  pendingEffects.push(pending), newHistory.push(history);
1381
1550
  }
1382
- await client.patch(instance._id).set({
1551
+ const committed = await client.patch(instance._id).set({
1383
1552
  stages: [initialStageEntry],
1384
1553
  pendingEffects,
1385
1554
  history: newHistory,
1386
- lastChangedAt: (/* @__PURE__ */ new Date()).toISOString()
1387
- }).ifRevisionId(instance._rev).commit(), await deployStageGuards({
1555
+ lastChangedAt: now
1556
+ }).ifRevisionId(instance._rev).commit();
1557
+ await deployOrRollback({
1388
1558
  client,
1389
- clientForGdr: clientForGdr ?? (() => client),
1390
- instance,
1391
- definition,
1392
- stageId: stage.id
1393
- }), await autoActivatePrimedTasks(client, instanceId, definition, stage, actor, clientForGdr);
1559
+ instanceId: instance._id,
1560
+ committedRev: committed._rev,
1561
+ restore: {
1562
+ stages: instance.stages,
1563
+ pendingEffects: instance.pendingEffects,
1564
+ history: instance.history
1565
+ },
1566
+ reversible: !0,
1567
+ deploy: () => deployStageGuards({
1568
+ client,
1569
+ clientForGdr: clientForGdr ?? (() => client),
1570
+ instance,
1571
+ definition,
1572
+ stageId: stage.id,
1573
+ now
1574
+ })
1575
+ }), await autoActivatePrimedTasks(client, instanceId, definition, stage, actor, clientForGdr, clock);
1394
1576
  }
1395
- async function autoActivatePrimedTasks(client, instanceId, definition, stage, actor, clientForGdr) {
1577
+ async function autoActivatePrimedTasks(client, instanceId, definition, stage, actor, clientForGdr, clock) {
1396
1578
  const resolvedClientForGdr = clientForGdr ?? (() => client);
1397
1579
  for (const task of stage.tasks ?? []) {
1398
1580
  if (!activatesOnStageEnter(task)) continue;
@@ -1402,7 +1584,8 @@ async function autoActivatePrimedTasks(client, instanceId, definition, stage, ac
1402
1584
  client,
1403
1585
  clientForGdr: resolvedClientForGdr,
1404
1586
  instance: updated,
1405
- definition
1587
+ definition,
1588
+ ...clock ? { clock } : {}
1406
1589
  }), mutation = startMutation(updated), mutEntry = currentTasks(mutation).find((t) => t.id === task.id);
1407
1590
  mutEntry && mutEntry.status === "pending" && (await activateTask(updatedCtx, mutation, task, mutEntry, actor), await persist(updatedCtx, mutation));
1408
1591
  }
@@ -1420,8 +1603,11 @@ async function gatherAutoResolveCandidates(ctx, tasks) {
1420
1603
  }
1421
1604
  return candidates;
1422
1605
  }
1423
- async function resolveCompleteWhen(client, instanceId, clientForGdr) {
1424
- const ctx = await loadContext(client, instanceId, clientForGdr ? { clientForGdr } : void 0), stage = findStage(ctx.definition, ctx.instance.currentStageId), tasksWithAutoPredicate = (stage.tasks ?? []).filter(
1606
+ async function resolveCompleteWhen(client, instanceId, clientForGdr, clock) {
1607
+ const ctx = await loadContext(client, instanceId, {
1608
+ ...clientForGdr ? { clientForGdr } : {},
1609
+ ...clock ? { clock } : {}
1610
+ }), stage = findStage(ctx.definition, ctx.instance.currentStageId), tasksWithAutoPredicate = (stage.tasks ?? []).filter(
1425
1611
  (t) => t.completeWhen !== void 0 || t.failWhen !== void 0
1426
1612
  );
1427
1613
  if (tasksWithAutoPredicate.length === 0) return 0;
@@ -1435,65 +1621,71 @@ async function resolveCompleteWhen(client, instanceId, clientForGdr) {
1435
1621
  const previousStatus = mutEntry.status, actor = {
1436
1622
  kind: "system",
1437
1623
  id: outcome === "failed" ? "engine.failWhen" : "engine.completeWhen"
1438
- };
1439
- mutEntry.status = outcome, mutEntry.completedAt = (/* @__PURE__ */ new Date()).toISOString(), mutEntry.completedBy = actor.id, mutation.history.push(
1624
+ }, now = ctx.now;
1625
+ mutEntry.status = outcome, mutEntry.completedAt = now, mutEntry.completedBy = actor.id, mutation.history.push(
1440
1626
  taskStatusChangedEntry({
1441
1627
  stageId: stage.id,
1442
1628
  taskId: task.id,
1443
1629
  from: previousStatus,
1444
1630
  to: outcome,
1631
+ at: now,
1445
1632
  actor
1446
1633
  })
1447
1634
  ), count++;
1448
1635
  }
1449
1636
  return count > 0 && await persist(ctx, mutation), count;
1450
1637
  }
1451
- async function cascadeAutoTransitions(client, instanceId, actor, clientForGdr) {
1638
+ async function cascadeAutoTransitions(client, instanceId, actor, clientForGdr, clock) {
1452
1639
  let count = 0;
1453
1640
  for (; ; ) {
1454
- if (await resolveCompleteWhen(client, instanceId, clientForGdr), !(await evaluateAutoTransitions({
1641
+ if (await resolveCompleteWhen(client, instanceId, clientForGdr, clock), !(await evaluateAutoTransitions({
1455
1642
  client,
1456
1643
  instanceId,
1457
1644
  ...actor !== void 0 ? { options: { actor } } : {},
1458
- ...clientForGdr ? { clientForGdr } : {}
1645
+ ...clientForGdr ? { clientForGdr } : {},
1646
+ ...clock ? { clock } : {}
1459
1647
  })).fired) return count;
1460
- if (count++, count > CASCADE_LIMIT)
1461
- throw new Error(
1462
- `Cascade did not stabilise after ${CASCADE_LIMIT} auto-transitions on ${instanceId} \u2014 possible infinite loop`
1463
- );
1648
+ if (count++, count >= CASCADE_LIMIT)
1649
+ throw new CascadeLimitError({ instanceId, limit: CASCADE_LIMIT });
1464
1650
  }
1465
1651
  }
1466
- async function propagateToAncestors(client, instanceId, actor, clientForGdr) {
1467
- const instance = await client.getDocument(instanceId);
1468
- if (!instance || instance.ancestors.length === 0) return;
1469
- const parentRef = instance.ancestors[instance.ancestors.length - 1];
1652
+ async function findSatisfiedParentSpawn(client, child) {
1653
+ const parentRef = child.ancestors.at(-1);
1470
1654
  if (parentRef === void 0) return;
1471
- const parentId = gdrToBareId(parentRef.id), parent = await client.getDocument(parentId);
1472
- if (!parent || parent._type !== "workflow.instance" || typeof parent.definitionSnapshot != "string")
1473
- return;
1474
- const parentDefinition = JSON.parse(parent.definitionSnapshot), parentStage = parentDefinition.stages.find((s) => s.id === parent.currentStageId);
1475
- if (parentStage === void 0) return;
1476
- const parentCurrentTasks = findCurrentTasks(parent), parentTask = (parentStage.tasks ?? []).find((t) => t.spawns === void 0 ? !1 : parentCurrentTasks.find((e) => e.id === t.id)?.spawnedInstances?.some((r) => gdrToBareId(r.id) === instance._id) ?? !1);
1477
- if (parentTask === void 0) return;
1478
- const parentEntry = parentCurrentTasks.find((e) => e.id === parentTask.id);
1479
- if (parentEntry === void 0 || parentEntry.status === "done" || !await checkSpawnCompletion(client, parentEntry, parentTask.spawns)) return;
1480
- const ctx = await buildEngineContext({
1655
+ const parent = await client.getDocument(gdrToBareId(parentRef.id));
1656
+ if (!parent || parent._type !== "workflow.instance" || typeof parent.definitionSnapshot != "string") return;
1657
+ const definition = JSON.parse(parent.definitionSnapshot), stage = definition.stages.find((s) => s.id === parent.currentStageId);
1658
+ if (stage === void 0) return;
1659
+ const parentCurrentTasks = findCurrentTasks(parent), task = (stage.tasks ?? []).find((t) => t.spawns === void 0 ? !1 : parentCurrentTasks.find((e) => e.id === t.id)?.spawnedInstances?.some((r) => gdrToBareId(r.id) === child._id) ?? !1);
1660
+ if (task?.spawns === void 0) return;
1661
+ const entry = parentCurrentTasks.find((e) => e.id === task.id);
1662
+ if (!(entry === void 0 || entry.status === "done") && await checkSpawnCompletion(client, entry, task.spawns))
1663
+ return { parent, definition, stage, task };
1664
+ }
1665
+ async function propagateToAncestors(client, instanceId, actor, clientForGdr, clock) {
1666
+ const instance = await client.getDocument(instanceId);
1667
+ if (!instance) return;
1668
+ const found = await findSatisfiedParentSpawn(client, instance);
1669
+ if (found === void 0) return;
1670
+ const { parent, definition, stage, task } = found, ctx = await buildEngineContext({
1481
1671
  client,
1482
1672
  clientForGdr: clientForGdr ?? (() => client),
1483
1673
  instance: parent,
1484
- definition: parentDefinition
1485
- }), mutation = startMutation(parent), mutEntry = currentTasks(mutation).find((e) => e.id === parentTask.id);
1674
+ definition,
1675
+ ...clock ? { clock } : {}
1676
+ }), mutation = startMutation(parent), mutEntry = currentTasks(mutation).find((e) => e.id === task.id);
1486
1677
  if (mutEntry === void 0) return;
1487
- const previousStatus = mutEntry.status;
1488
- mutEntry.status = "done", mutEntry.completedAt = (/* @__PURE__ */ new Date()).toISOString(), mutation.history.push(
1678
+ const previousStatus = mutEntry.status, now = ctx.now;
1679
+ mutEntry.status = "done", mutEntry.completedAt = now, mutation.history.push(
1489
1680
  taskStatusChangedEntry({
1490
- stageId: parentStage.id,
1491
- taskId: parentTask.id,
1681
+ stageId: stage.id,
1682
+ taskId: task.id,
1492
1683
  from: previousStatus,
1493
1684
  to: "done",
1685
+ at: now,
1494
1686
  ...actor !== void 0 ? { actor } : {}
1495
1687
  })
1496
- ), await persist(ctx, mutation), await cascadeAutoTransitions(client, parentId, actor, clientForGdr), await propagateToAncestors(client, parentId, actor, clientForGdr);
1688
+ ), await persist(ctx, mutation), await cascadeAutoTransitions(client, parent._id, actor, clientForGdr, clock), await propagateToAncestors(client, parent._id, actor, clientForGdr, clock);
1497
1689
  }
1498
1690
  function startMutation(instance) {
1499
1691
  return {
@@ -1522,7 +1714,7 @@ function taskStatusChangedEntry(args) {
1522
1714
  return {
1523
1715
  _key: randomKey(),
1524
1716
  _type: "workflow.history.taskStatusChanged",
1525
- at: args.at ?? (/* @__PURE__ */ new Date()).toISOString(),
1717
+ at: args.at,
1526
1718
  stageId: args.stageId,
1527
1719
  taskId: args.taskId,
1528
1720
  from: args.from,
@@ -1562,19 +1754,31 @@ function stageTransitionHistory(args) {
1562
1754
  }
1563
1755
  ];
1564
1756
  }
1565
- async function persist(ctx, mutation) {
1566
- const set = {
1757
+ function instanceStateFields(src) {
1758
+ const fields = {
1759
+ currentStageId: src.currentStageId,
1760
+ state: src.state,
1761
+ stages: src.stages,
1762
+ pendingEffects: src.pendingEffects,
1763
+ effectHistory: src.effectHistory,
1764
+ effectsContext: src.effectsContext,
1765
+ history: src.history
1766
+ };
1767
+ return src.completedAt !== void 0 && (fields.completedAt = src.completedAt), fields;
1768
+ }
1769
+ function materializeInstance(base, mutation) {
1770
+ return {
1771
+ ...base,
1567
1772
  currentStageId: mutation.currentStageId,
1568
1773
  state: mutation.state,
1569
- stages: mutation.stages,
1570
- pendingEffects: mutation.pendingEffects,
1571
- effectHistory: mutation.effectHistory,
1572
- effectsContext: mutation.effectsContext,
1573
- history: mutation.history,
1574
- lastChangedAt: (/* @__PURE__ */ new Date()).toISOString()
1774
+ stages: mutation.stages
1575
1775
  };
1576
- mutation.completedAt !== void 0 && (set.completedAt = mutation.completedAt);
1577
- const pendingCreates = mutation.pendingCreates;
1776
+ }
1777
+ async function persist(ctx, mutation) {
1778
+ const set = {
1779
+ ...instanceStateFields(mutation),
1780
+ lastChangedAt: ctx.now
1781
+ }, pendingCreates = mutation.pendingCreates;
1578
1782
  if (pendingCreates.length === 0)
1579
1783
  return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit();
1580
1784
  const tx = ctx.client.transaction();
@@ -1582,7 +1786,7 @@ async function persist(ctx, mutation) {
1582
1786
  tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)), await tx.commit(), mutation.pendingCreates = [];
1583
1787
  const actorForPriming = void 0;
1584
1788
  for (const body of pendingCreates)
1585
- await primeInitialStage(ctx.client, body._id, actorForPriming, ctx.clientForGdr), await cascadeAutoTransitions(ctx.client, body._id, actorForPriming, ctx.clientForGdr), await propagateToAncestors(ctx.client, body._id, actorForPriming, ctx.clientForGdr);
1789
+ await primeInitialStage(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock), await cascadeAutoTransitions(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock), await propagateToAncestors(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock);
1586
1790
  const reloaded = await ctx.client.getDocument(ctx.instance._id);
1587
1791
  if (!reloaded)
1588
1792
  throw new Error(`Instance ${ctx.instance._id} disappeared after transaction commit`);
@@ -1623,7 +1827,9 @@ function findCurrentTasks(instance) {
1623
1827
  return findCurrentStageEntry(instance)?.tasks ?? [];
1624
1828
  }
1625
1829
  async function completeEffect(args) {
1626
- const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId);
1830
+ const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
1831
+ ...options?.clock ? { clock: options.clock } : {}
1832
+ });
1627
1833
  return commitCompleteEffect(
1628
1834
  ctx,
1629
1835
  effectKey,
@@ -1665,7 +1871,7 @@ async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, err
1665
1871
  throw new Error(`Pending effect "${effectKey}" not found on instance ${ctx.instance._id}`);
1666
1872
  const mutation = startMutation(ctx.instance);
1667
1873
  mutation.pendingEffects = mutation.pendingEffects.filter((e) => e._key !== effectKey);
1668
- const ranAt = (/* @__PURE__ */ new Date()).toISOString();
1874
+ const ranAt = ctx.now;
1669
1875
  return mutation.effectHistory.push(
1670
1876
  buildEffectHistoryEntry(pending, { status, ranAt, actor, detail, error, durationMs, outputs })
1671
1877
  ), status === "done" && outputs !== void 0 && upsertEffectsContext(mutation, outputs), mutation.history.push({
@@ -1680,7 +1886,7 @@ async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, err
1680
1886
  ...actor !== void 0 ? { actor } : {}
1681
1887
  }), await persist(ctx, mutation), { effectKey, status };
1682
1888
  }
1683
- function buildQueuedEffect(effect, origin, params, actor) {
1889
+ function buildQueuedEffect(effect, origin, params, actor, now) {
1684
1890
  const key = randomKey(), pending = {
1685
1891
  _key: key,
1686
1892
  _type: "workflow.pendingEffect",
@@ -1691,11 +1897,11 @@ function buildQueuedEffect(effect, origin, params, actor) {
1691
1897
  params,
1692
1898
  origin,
1693
1899
  ...actor !== void 0 ? { actor } : {},
1694
- queuedAt: (/* @__PURE__ */ new Date()).toISOString()
1900
+ queuedAt: now
1695
1901
  }, history = {
1696
1902
  _key: randomKey(),
1697
1903
  _type: "workflow.history.effectQueued",
1698
- at: (/* @__PURE__ */ new Date()).toISOString(),
1904
+ at: now,
1699
1905
  effectKey: key,
1700
1906
  effectId: effect.id,
1701
1907
  origin
@@ -1704,7 +1910,7 @@ function buildQueuedEffect(effect, origin, params, actor) {
1704
1910
  }
1705
1911
  async function queueEffects(ctx, mutation, effects, origin, actor, callerParams) {
1706
1912
  if (!effects) return;
1707
- const liveInstance = {
1913
+ const now = ctx.now, liveInstance = {
1708
1914
  ...ctx.instance,
1709
1915
  state: mutation.state,
1710
1916
  stages: mutation.stages,
@@ -1715,22 +1921,21 @@ async function queueEffects(ctx, mutation, effects, origin, actor, callerParams)
1715
1921
  bindings: effect.bindings,
1716
1922
  staticInput: effect.input,
1717
1923
  instance: liveInstance,
1924
+ now,
1718
1925
  ...callerParams !== void 0 ? { params: callerParams } : {},
1719
1926
  ...actor !== void 0 ? { actor } : {}
1720
- }), { pending, history } = buildQueuedEffect(effect, origin, params, actor);
1927
+ }), { pending, history } = buildQueuedEffect(effect, origin, params, actor, now);
1721
1928
  mutation.pendingEffects.push(pending), mutation.history.push(history);
1722
1929
  }
1723
1930
  }
1724
- class ActionParamsInvalidError extends Error {
1725
- action;
1726
- taskId;
1727
- issues;
1728
- constructor(args) {
1729
- const lines = args.issues.map((i) => ` - ${i.paramId}: ${i.reason}`).join(`
1730
- `);
1731
- super(`Action "${args.action}" on task "${args.taskId}" rejected: invalid params
1732
- ${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.taskId = args.taskId, this.issues = args.issues;
1733
- }
1931
+ const STATE_OP_TYPES = [
1932
+ "workflow.op.state.set",
1933
+ "workflow.op.state.append",
1934
+ "workflow.op.state.updateWhere",
1935
+ "workflow.op.state.removeWhere"
1936
+ ];
1937
+ function isStateOp(summary) {
1938
+ return STATE_OP_TYPES.includes(summary.opType);
1734
1939
  }
1735
1940
  function validateActionParams(action, taskId, callerParams) {
1736
1941
  const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
@@ -1767,7 +1972,7 @@ function checkParamType(value, decl) {
1767
1972
  case "doc.ref":
1768
1973
  return hasStringField(value, "id");
1769
1974
  case "doc.refs":
1770
- return Array.isArray(value) && value.every((v) => checkParamType(v, { paramType: "doc.ref" }));
1975
+ return Array.isArray(value) && value.every((v2) => checkParamType(v2, { paramType: "doc.ref" }));
1771
1976
  case "json":
1772
1977
  return !0;
1773
1978
  default:
@@ -1775,15 +1980,15 @@ function checkParamType(value, decl) {
1775
1980
  }
1776
1981
  }
1777
1982
  async function runActionOps(args) {
1778
- const { ops, mutation, stageId, taskId, actionName, params, actor } = args;
1983
+ const { ops, mutation, stageId, taskId, actionName, params, actor, self, now } = args;
1779
1984
  if (ops === void 0 || ops.length === 0) return [];
1780
1985
  const summaries = [];
1781
1986
  for (const op of ops) {
1782
- const summary = applyOp(op, { mutation, stageId, taskId, params, actor });
1987
+ const summary = applyOp(op, { mutation, stageId, taskId, params, actor, self, now });
1783
1988
  summaries.push(summary), mutation.history.push({
1784
1989
  _key: randomKey(),
1785
1990
  _type: "workflow.history.opApplied",
1786
- at: (/* @__PURE__ */ new Date()).toISOString(),
1991
+ at: now,
1787
1992
  stageId,
1788
1993
  taskId,
1789
1994
  actionId: actionName,
@@ -1842,8 +2047,8 @@ function applyStateUpdateWhere(op, ctx) {
1842
2047
  /*createIfMissing*/
1843
2048
  !1
1844
2049
  ), resolvedMerge = {};
1845
- for (const [k, v] of Object.entries(op.merge))
1846
- resolvedMerge[k] = resolveOpSource(v, ctx);
2050
+ for (const [k, v2] of Object.entries(op.merge))
2051
+ resolvedMerge[k] = resolveOpSource(v2, ctx);
1847
2052
  return slot !== void 0 && Array.isArray(slot.value) && (slot.value = slot.value.map(
1848
2053
  (row) => evalOpPredicate(op.where, row, ctx) ? { ...row, ...resolvedMerge } : row
1849
2054
  )), { opType: op.type, target: op.target, resolved: { merge: resolvedMerge } };
@@ -1886,18 +2091,11 @@ function locateSlot(ctx, target, createIfMissing) {
1886
2091
  }, host.push(slot)), slot;
1887
2092
  }
1888
2093
  function resolveOpSource(src, ctx) {
2094
+ const staticValue = resolveStaticSource(src, ctx);
2095
+ if (staticValue.handled) return staticValue.value;
1889
2096
  switch (src.source) {
1890
- case "literal":
1891
- return src.value;
1892
- case "param":
1893
- return ctx.params[src.paramId];
1894
- case "actor":
1895
- return ctx.actor;
1896
- case "now":
1897
- return (/* @__PURE__ */ new Date()).toISOString();
1898
2097
  case "self":
1899
- return;
1900
- // Not applicable inside ops; reserved for higher-level binding.
2098
+ return ctx.self;
1901
2099
  case "stageId":
1902
2100
  return ctx.stageId;
1903
2101
  case "stateRead": {
@@ -1907,8 +2105,8 @@ function resolveOpSource(src, ctx) {
1907
2105
  case "effectOutput": {
1908
2106
  const entry = ctx.mutation.effectsContext.find((e) => e.id === src.contextId);
1909
2107
  if (entry === void 0) return null;
1910
- const v = src.path !== void 0 ? getPath(entry.value, src.path) : entry.value;
1911
- return v === void 0 ? null : v;
2108
+ const v2 = src.path !== void 0 ? getPath(entry.value, src.path) : entry.value;
2109
+ return v2 === void 0 ? null : v2;
1912
2110
  }
1913
2111
  case "object": {
1914
2112
  const out = {};
@@ -1916,6 +2114,11 @@ function resolveOpSource(src, ctx) {
1916
2114
  out[field] = resolveOpSource(fieldSrc, ctx);
1917
2115
  return out;
1918
2116
  }
2117
+ case "row":
2118
+ case "parentState":
2119
+ return;
2120
+ default:
2121
+ return;
1919
2122
  }
1920
2123
  }
1921
2124
  function slotValue(slots, slotId) {
@@ -1935,8 +2138,23 @@ function evalOpPredicate(p, row, ctx) {
1935
2138
  return row[p.field] === target;
1936
2139
  }
1937
2140
  async function fireAction(args) {
1938
- const { client, instanceId, taskId, action, params, options } = args, ctx = await loadContext(client, instanceId);
1939
- return commitAction(ctx, taskId, action, params, options?.actor);
2141
+ const { client, instanceId, taskId, action, params, options } = args;
2142
+ for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
2143
+ const ctx = await loadContext(client, instanceId, {
2144
+ ...options?.clock ? { clock: options.clock } : {}
2145
+ });
2146
+ try {
2147
+ return await commitAction(ctx, taskId, action, params, options?.actor);
2148
+ } catch (error) {
2149
+ if (!isRevisionConflict(error)) throw error;
2150
+ }
2151
+ }
2152
+ throw new ConcurrentFireActionError({
2153
+ instanceId,
2154
+ taskId,
2155
+ action,
2156
+ attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
2157
+ });
1940
2158
  }
1941
2159
  async function resolveActionCommit(ctx, taskId, actionName, callerParams) {
1942
2160
  const { stage, task } = findTaskInCurrentStage(ctx, taskId), action = (task.actions ?? []).find((a) => a.id === actionName);
@@ -1952,15 +2170,16 @@ async function resolveActionCommit(ctx, taskId, actionName, callerParams) {
1952
2170
  const params = validateActionParams(action, taskId, callerParams);
1953
2171
  return { stage, task, action, params };
1954
2172
  }
1955
- function applyActionSetStatus(action, mutation, mutEntry, stageId, taskId, actor) {
2173
+ function applyActionSetStatus(action, mutation, mutEntry, stageId, taskId, actor, now) {
1956
2174
  if (action.setStatus === void 0) return;
1957
2175
  const initialStatus = mutEntry.status, newStatus = action.setStatus;
1958
- return mutEntry.status = newStatus, mutEntry.completedAt = (/* @__PURE__ */ new Date()).toISOString(), actor && (mutEntry.completedBy = actor.id), mutation.history.push(
2176
+ return mutEntry.status = newStatus, mutEntry.completedAt = now, actor && (mutEntry.completedBy = actor.id), mutation.history.push(
1959
2177
  taskStatusChangedEntry({
1960
2178
  stageId,
1961
2179
  taskId,
1962
2180
  from: initialStatus,
1963
2181
  to: newStatus,
2182
+ at: now,
1964
2183
  ...actor !== void 0 ? { actor } : {}
1965
2184
  })
1966
2185
  ), newStatus;
@@ -1971,11 +2190,11 @@ async function commitAction(ctx, taskId, actionName, callerParams, actor) {
1971
2190
  taskId,
1972
2191
  actionName,
1973
2192
  callerParams
1974
- ), mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation, taskId);
2193
+ ), mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation, taskId), now = ctx.now;
1975
2194
  mutation.history.push({
1976
2195
  _key: randomKey(),
1977
2196
  _type: "workflow.history.actionFired",
1978
- at: (/* @__PURE__ */ new Date()).toISOString(),
2197
+ at: now,
1979
2198
  stageId: stage.id,
1980
2199
  taskId,
1981
2200
  actionId: actionName,
@@ -1988,8 +2207,10 @@ async function commitAction(ctx, taskId, actionName, callerParams, actor) {
1988
2207
  taskId,
1989
2208
  actionName,
1990
2209
  params,
1991
- actor
1992
- }), newStatus = applyActionSetStatus(action, mutation, mutEntry, stage.id, taskId, actor);
2210
+ actor,
2211
+ self: selfGdr(ctx.instance),
2212
+ now
2213
+ }), newStatus = applyActionSetStatus(action, mutation, mutEntry, stage.id, taskId, actor, now);
1993
2214
  return await queueEffects(
1994
2215
  ctx,
1995
2216
  mutation,
@@ -2000,7 +2221,11 @@ async function commitAction(ctx, taskId, actionName, callerParams, actor) {
2000
2221
  },
2001
2222
  actor,
2002
2223
  params
2003
- ), await persist(ctx, mutation), {
2224
+ ), await persistThenDeploy(
2225
+ ctx,
2226
+ mutation,
2227
+ () => ranOps.some(isStateOp) ? refreshStageGuards(ctx, mutation, stage.id) : Promise.resolve()
2228
+ ), {
2004
2229
  fired: !0,
2005
2230
  taskId,
2006
2231
  action: actionName,
@@ -2039,7 +2264,7 @@ async function resolveAccess(client, args = {}) {
2039
2264
  const overrideActor = args.override?.actor, overrideGrants = args.override?.grants;
2040
2265
  if (overrideActor !== void 0 && overrideGrants !== void 0)
2041
2266
  return { actor: overrideActor, grants: overrideGrants };
2042
- const requestFn = client.request;
2267
+ const requestFn = client.request === void 0 ? void 0 : (opts) => client.request(opts);
2043
2268
  if (requestFn === void 0) {
2044
2269
  if (overrideActor === void 0)
2045
2270
  throw new Error(
@@ -2114,7 +2339,7 @@ function canonicalTag(tags) {
2114
2339
  }
2115
2340
  const WILDCARD_ROLE = "*";
2116
2341
  async function evaluateInstance(args) {
2117
- const { client, tags, instanceId, resourceClients } = args;
2342
+ const { client, tags, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
2118
2343
  validateTags(tags);
2119
2344
  const { actor, grants } = await resolveAccess(client, {
2120
2345
  ...args.access !== void 0 ? { override: args.access } : {},
@@ -2146,7 +2371,8 @@ async function evaluateInstance(args) {
2146
2371
  definition,
2147
2372
  actor,
2148
2373
  grants,
2149
- snapshot
2374
+ snapshot,
2375
+ now
2150
2376
  })
2151
2377
  );
2152
2378
  const transitionEvaluations = [];
@@ -2157,7 +2383,7 @@ async function evaluateInstance(args) {
2157
2383
  filter: transition.filter,
2158
2384
  definition,
2159
2385
  snapshot,
2160
- params: buildParams(instance)
2386
+ params: buildParams(instance, now)
2161
2387
  })
2162
2388
  });
2163
2389
  const currentStage = {
@@ -2175,7 +2401,7 @@ async function evaluateInstance(args) {
2175
2401
  };
2176
2402
  }
2177
2403
  async function evaluateTask(args) {
2178
- const { task, statusEntry, instance, definition, actor, grants, snapshot } = args, status = statusEntry?.status ?? "pending", actions = [];
2404
+ const { task, statusEntry, instance, definition, actor, grants, snapshot, now } = args, status = statusEntry?.status ?? "pending", actions = [];
2179
2405
  for (const action of task.actions ?? [])
2180
2406
  actions.push(
2181
2407
  await evaluateAction({
@@ -2187,7 +2413,8 @@ async function evaluateTask(args) {
2187
2413
  definition,
2188
2414
  actor,
2189
2415
  ...grants !== void 0 ? { grants } : {},
2190
- snapshot
2416
+ snapshot,
2417
+ now
2191
2418
  })
2192
2419
  );
2193
2420
  return {
@@ -2201,9 +2428,9 @@ async function evaluateTask(args) {
2201
2428
  };
2202
2429
  }
2203
2430
  async function evaluateAction(args) {
2204
- const { action, task, status, instance, definition, actor, grants, snapshot } = args, syncReason = lifecycleReason(instance, definition, status) ?? actorReason(action, task, actor);
2431
+ const { action, task, status, instance, definition, actor, grants, snapshot, now } = args, syncReason = lifecycleReason(instance, definition, status) ?? actorReason(action, task, actor);
2205
2432
  if (syncReason !== void 0) return disabled(action, syncReason);
2206
- const asyncReason = await permissionReason(action, instance, actor, grants) ?? await filterReason(action, instance, definition, snapshot);
2433
+ const asyncReason = await permissionReason(action, instance, actor, grants) ?? await filterReason(action, instance, definition, snapshot, now);
2207
2434
  return asyncReason !== void 0 ? disabled(action, asyncReason) : { action, allowed: !0 };
2208
2435
  }
2209
2436
  function lifecycleReason(instance, definition, status) {
@@ -2232,12 +2459,12 @@ async function permissionReason(action, instance, actor, grants) {
2232
2459
  }))
2233
2460
  return { kind: "permission-denied", permission, matchingGrants: grants.length };
2234
2461
  }
2235
- async function filterReason(action, instance, definition, snapshot) {
2462
+ async function filterReason(action, instance, definition, snapshot, now) {
2236
2463
  if (!(action.filter === void 0 || await evaluateFilter({
2237
2464
  filter: action.filter,
2238
2465
  definition,
2239
2466
  snapshot,
2240
- params: buildParams(instance)
2467
+ params: buildParams(instance, now)
2241
2468
  })))
2242
2469
  return { kind: "filter-failed", filter: action.filter };
2243
2470
  }
@@ -2262,6 +2489,43 @@ function parseDefinitionSnapshot(instance) {
2262
2489
  );
2263
2490
  }
2264
2491
  }
2492
+ const resourceKey = (r) => `${r.type}.${r.id}`;
2493
+ function guardsForResource(client) {
2494
+ return client.fetch("*[_type == $t] | order(_id asc)", { t: GUARD_DOC_TYPE });
2495
+ }
2496
+ async function guardsForInstance(args) {
2497
+ const { client, clientForGdr, instance } = args, clientsByResource = /* @__PURE__ */ new Map([
2498
+ [resourceKey(instance.workflowResource), client]
2499
+ ]), stateUris = [
2500
+ ...collectSlotDocUris(instance.state),
2501
+ ...instance.stages.flatMap((stage) => collectSlotDocUris(stage.state))
2502
+ ];
2503
+ for (const uri of stateUris) {
2504
+ const parsed = tryParseGdr(uri);
2505
+ if (parsed === void 0) continue;
2506
+ const key = resourceKey(resourceFromParsed(parsed));
2507
+ clientsByResource.has(key) || clientsByResource.set(key, clientForGdr(parsed));
2508
+ }
2509
+ const perResource = await Promise.all(
2510
+ [...clientsByResource.values()].map(
2511
+ (c) => c.fetch("*[_type == $t && sourceInstanceId == $id]", {
2512
+ t: GUARD_DOC_TYPE,
2513
+ id: instance._id
2514
+ })
2515
+ )
2516
+ );
2517
+ return dedupById(perResource.flat());
2518
+ }
2519
+ function tryParseGdr(uri) {
2520
+ try {
2521
+ return parseGdr(uri);
2522
+ } catch {
2523
+ return;
2524
+ }
2525
+ }
2526
+ function dedupById(guards) {
2527
+ return [...new Map(guards.map((g) => [g._id, g])).values()].sort((a, b) => a._id.localeCompare(b._id));
2528
+ }
2265
2529
  class ActionDisabledError extends Error {
2266
2530
  reason;
2267
2531
  taskId;
@@ -2372,14 +2636,14 @@ function isDefinitionUnchanged(existing, expected) {
2372
2636
  }
2373
2637
  function stripSystemFields(doc) {
2374
2638
  const out = {};
2375
- for (const [k, v] of Object.entries(doc))
2376
- k === "_rev" || k === "_createdAt" || k === "_updatedAt" || (out[k] = v);
2639
+ for (const [k, v2] of Object.entries(doc))
2640
+ k === "_rev" || k === "_createdAt" || k === "_updatedAt" || (out[k] = v2);
2377
2641
  return out;
2378
2642
  }
2379
2643
  function stableStringify(value) {
2380
2644
  return value === null || typeof value != "object" ? JSON.stringify(value) : Array.isArray(value) ? `[${value.map(stableStringify).join(",")}]` : `{${Object.entries(value).toSorted(
2381
2645
  ([a], [b]) => a.localeCompare(b)
2382
- ).map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
2646
+ ).map(([k, v2]) => `${JSON.stringify(k)}:${stableStringify(v2)}`).join(",")}}`;
2383
2647
  }
2384
2648
  async function loadDefinition(client, workflowId, version, tags) {
2385
2649
  if (version !== void 0) {
@@ -2419,9 +2683,9 @@ async function resolveOperationContext(args) {
2419
2683
  clientForGdr: buildClientForGdr(args.client, args.resourceClients)
2420
2684
  };
2421
2685
  }
2422
- async function cascade(client, instanceId, actor, clientForGdr) {
2423
- const count = await cascadeAutoTransitions(client, instanceId, actor, clientForGdr);
2424
- return await propagateToAncestors(client, instanceId, actor, clientForGdr), count;
2686
+ async function cascade(client, instanceId, actor, clientForGdr, clock) {
2687
+ const count = await cascadeAutoTransitions(client, instanceId, actor, clientForGdr, clock);
2688
+ return await propagateToAncestors(client, instanceId, actor, clientForGdr, clock), count;
2425
2689
  }
2426
2690
  function buildClientForGdr(defaultClient, resolver) {
2427
2691
  return resolver === void 0 ? () => defaultClient : (parsed) => resolver(parsed) ?? defaultClient;
@@ -2513,16 +2777,17 @@ const workflow = {
2513
2777
  effectsContext,
2514
2778
  instanceId,
2515
2779
  perspective
2516
- } = args, { actor, clientForGdr } = await resolveOperationContext(args), definition = await loadDefinition(client, workflowId, version, tags), id = instanceId ?? `${canonicalTag(tags)}.wf-instance.${randomKey()}`;
2780
+ } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, definition = await loadDefinition(client, workflowId, version, tags), id = instanceId ?? `${canonicalTag(tags)}.wf-instance.${randomKey()}`;
2517
2781
  if (definition.stages.find((s) => s.id === definition.initialStageId) === void 0)
2518
2782
  throw new Error(
2519
2783
  `Initial stage "${definition.initialStageId}" missing in ${workflowId} v${definition.version}`
2520
2784
  );
2521
- const now = (/* @__PURE__ */ new Date()).toISOString(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedState = await resolveDeclaredState({
2785
+ const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedState = await resolveDeclaredState({
2522
2786
  slotDefs: definition.state ?? [],
2523
2787
  initialState: initialState ?? [],
2524
2788
  ctx: {
2525
2789
  client,
2790
+ now,
2526
2791
  selfId: id,
2527
2792
  tags,
2528
2793
  workflowResource,
@@ -2544,7 +2809,7 @@ const workflow = {
2544
2809
  initialStageId: definition.initialStageId,
2545
2810
  actor
2546
2811
  });
2547
- return await client.create(base), await primeInitialStage(client, id, actor, clientForGdr), await cascade(client, id, actor, clientForGdr), reload(client, id, tags);
2812
+ return await client.create(base), await primeInitialStage(client, id, actor, clientForGdr, clock), await cascade(client, id, actor, clientForGdr, clock), reload(client, id, tags);
2548
2813
  },
2549
2814
  /**
2550
2815
  * Fire an action against a task. If the task is pending, it is
@@ -2566,7 +2831,7 @@ const workflow = {
2566
2831
  params,
2567
2832
  idempotent,
2568
2833
  resourceClients
2569
- } = args, { access, actor, clientForGdr } = await resolveOperationContext(args), before = await reload(client, instanceId, tags), taskEntry = before.stages.find(
2834
+ } = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tags), taskEntry = before.stages.find(
2570
2835
  (s) => s.id === before.currentStageId && s.exitedAt === void 0
2571
2836
  )?.tasks.find((t) => t.id === taskId);
2572
2837
  if (taskEntry === void 0 && idempotent === !0)
@@ -2576,6 +2841,7 @@ const workflow = {
2576
2841
  tags,
2577
2842
  instanceId,
2578
2843
  access,
2844
+ clock,
2579
2845
  ...resourceClients !== void 0 ? { resourceClients } : {}
2580
2846
  })).currentStage.tasks.find((t) => t.task.id === taskId)?.actions.find((a) => a.action.id === action);
2581
2847
  if (actionEval !== void 0 && !actionEval.allowed && actionEval.disabledReason)
@@ -2588,7 +2854,7 @@ const workflow = {
2588
2854
  client,
2589
2855
  instanceId,
2590
2856
  taskId,
2591
- options: { actor }
2857
+ options: { actor, clock }
2592
2858
  });
2593
2859
  const actionResult = await fireAction({
2594
2860
  client,
@@ -2596,8 +2862,8 @@ const workflow = {
2596
2862
  taskId,
2597
2863
  action,
2598
2864
  ...params !== void 0 ? { params } : {},
2599
- options: { actor }
2600
- }), cascaded = await cascade(client, instanceId, actor, clientForGdr);
2865
+ options: { actor, clock }
2866
+ }), cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
2601
2867
  return {
2602
2868
  instance: await reload(client, instanceId, tags),
2603
2869
  cascaded,
@@ -2613,7 +2879,7 @@ const workflow = {
2613
2879
  * reference them. Cascades after.
2614
2880
  */
2615
2881
  completeEffect: async (args) => {
2616
- const { client, tags, instanceId, effectKey, status, outputs, detail, error, durationMs } = args, { actor, clientForGdr } = await resolveOperationContext(args);
2882
+ const { client, tags, instanceId, effectKey, status, outputs, detail, error, durationMs } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
2617
2883
  await completeEffect({
2618
2884
  client,
2619
2885
  instanceId,
@@ -2623,9 +2889,9 @@ const workflow = {
2623
2889
  ...detail !== void 0 ? { detail } : {},
2624
2890
  ...error !== void 0 ? { error } : {},
2625
2891
  ...durationMs !== void 0 ? { durationMs } : {},
2626
- ...actor !== void 0 ? { options: { actor } } : {}
2892
+ options: { ...actor !== void 0 ? { actor } : {}, clock }
2627
2893
  });
2628
- const cascaded = await cascade(client, instanceId, actor, clientForGdr);
2894
+ const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
2629
2895
  return {
2630
2896
  instance: await reload(client, instanceId, tags),
2631
2897
  cascaded,
@@ -2642,9 +2908,9 @@ const workflow = {
2642
2908
  * affected instances and the engine re-evaluates.
2643
2909
  */
2644
2910
  tick: async (args) => {
2645
- const { client, tags, instanceId } = args, { actor, clientForGdr } = await resolveOperationContext(args);
2911
+ const { client, tags, instanceId } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
2646
2912
  await reload(client, instanceId, tags);
2647
- const cascaded = await cascade(client, instanceId, actor, clientForGdr);
2913
+ const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
2648
2914
  return {
2649
2915
  instance: await reload(client, instanceId, tags),
2650
2916
  cascaded,
@@ -2657,15 +2923,15 @@ const workflow = {
2657
2923
  * upstream; this verb performs the mechanical move.
2658
2924
  */
2659
2925
  setStage: async (args) => {
2660
- const { client, tags, instanceId, targetStageId, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args);
2926
+ const { client, tags, instanceId, targetStageId, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
2661
2927
  await reload(client, instanceId, tags);
2662
2928
  const result = await setStage({
2663
2929
  client,
2664
2930
  instanceId,
2665
2931
  targetStageId,
2666
2932
  ...reason !== void 0 ? { reason } : {},
2667
- ...actor !== void 0 ? { options: { actor } } : {}
2668
- }), cascaded = result.fired ? await cascade(client, instanceId, actor, clientForGdr) : 0;
2933
+ options: { ...actor !== void 0 ? { actor } : {}, clock }
2934
+ }), cascaded = result.fired ? await cascade(client, instanceId, actor, clientForGdr, clock) : 0;
2669
2935
  return {
2670
2936
  instance: await reload(client, instanceId, tags),
2671
2937
  cascaded,
@@ -2681,6 +2947,16 @@ const workflow = {
2681
2947
  const { client, tags, instanceId } = args;
2682
2948
  return validateTags(tags), reload(client, instanceId, tags);
2683
2949
  },
2950
+ guardsForInstance: async (args) => {
2951
+ const { client, tags, instanceId, resourceClients } = args;
2952
+ validateTags(tags);
2953
+ const instance = await reload(client, instanceId, tags);
2954
+ return guardsForInstance({
2955
+ client,
2956
+ clientForGdr: buildClientForGdr(client, resourceClients),
2957
+ instance
2958
+ });
2959
+ },
2684
2960
  /**
2685
2961
  * Run a caller-supplied GROQ query with the engine's tags bound as
2686
2962
  * `$engineTags`. This does NOT rewrite the query — arbitrary GROQ
@@ -2715,9 +2991,9 @@ const workflow = {
2715
2991
  * without re-implementing hydration. Pure read — never writes.
2716
2992
  */
2717
2993
  queryInScope: async (args) => {
2718
- const { client, tags, instanceId, groq, params, resourceClients } = args;
2994
+ const { client, tags, instanceId, groq, params, resourceClients } = args, clock = args.clock ?? wallClock;
2719
2995
  validateTags(tags);
2720
- const instance = await reload(client, instanceId, tags), definition = JSON.parse(instance.definitionSnapshot), clientForGdr = buildClientForGdr(client, resourceClients), snapshot = await hydrateSnapshot({ client, clientForGdr, instance, definition }), reserved = buildParams(instance), tree = groqJs.parse(groq, { params: { ...reserved, ...params } });
2996
+ const instance = await reload(client, instanceId, tags), definition = JSON.parse(instance.definitionSnapshot), clientForGdr = buildClientForGdr(client, resourceClients), snapshot = await hydrateSnapshot({ client, clientForGdr, instance, definition }), reserved = buildParams(instance, clock()), tree = groqJs.parse(groq, { params: { ...reserved, ...params } });
2721
2997
  return await (await groqJs.evaluate(tree, {
2722
2998
  dataset: snapshot.docs,
2723
2999
  params: { ...reserved, ...params }
@@ -2901,7 +3177,8 @@ async function claimPendingEffect(client, instance, effectKey, drainerActor) {
2901
3177
  };
2902
3178
  try {
2903
3179
  return await client.patch(instance._id).ifRevisionId(instance._rev).set({ [`pendingEffects[_key=="${effectKey}"].claim`]: claim }).commit(), !0;
2904
- } catch {
3180
+ } catch (error) {
3181
+ if (!isRevisionConflict(error)) throw error;
2905
3182
  return !1;
2906
3183
  }
2907
3184
  }
@@ -2946,13 +3223,14 @@ const defaultLoggerFactory = (name) => ({
2946
3223
  }
2947
3224
  };
2948
3225
  function createEngine(args) {
2949
- const { client, workflowResource, tags, resourceClients } = args;
3226
+ const { client, workflowResource, tags, resourceClients, clock } = args;
2950
3227
  validateTags(tags);
2951
3228
  const effectHandlers = args.effectHandlers ?? {}, missingHandler = args.missingHandler ?? "fail", logger = args.loggerFactory ?? defaultLoggerFactory, bind = (rest) => ({
2952
3229
  client,
2953
3230
  tags,
2954
3231
  workflowResource,
2955
3232
  ...resourceClients !== void 0 ? { resourceClients } : {},
3233
+ ...clock !== void 0 ? { clock } : {},
2956
3234
  ...rest
2957
3235
  });
2958
3236
  return {
@@ -2970,6 +3248,13 @@ function createEngine(args) {
2970
3248
  evaluateInstance: (rest) => evaluateInstance(bind(rest)),
2971
3249
  setStage: (rest) => workflow.setStage(bind(rest)),
2972
3250
  getInstance: ({ instanceId }) => workflow.getInstance({ client, tags, workflowResource, instanceId }),
3251
+ guardsForInstance: ({ instanceId }) => workflow.guardsForInstance({
3252
+ client,
3253
+ tags,
3254
+ workflowResource,
3255
+ instanceId,
3256
+ ...resourceClients !== void 0 ? { resourceClients } : {}
3257
+ }),
2973
3258
  children: ({ instanceId, taskId }) => workflow.children({
2974
3259
  client,
2975
3260
  tags,
@@ -2990,7 +3275,8 @@ function createEngine(args) {
2990
3275
  workflowResource,
2991
3276
  instanceId,
2992
3277
  groq,
2993
- ...params !== void 0 ? { params } : {}
3278
+ ...params !== void 0 ? { params } : {},
3279
+ ...clock !== void 0 ? { clock } : {}
2994
3280
  }),
2995
3281
  listPendingEffects: ({ instanceId }) => workflow.listPendingEffects({ client, tags, workflowResource, instanceId }),
2996
3282
  findPendingEffects: ({ instanceId, claimed, names }) => workflow.findPendingEffects({
@@ -3021,15 +3307,6 @@ function createEngine(args) {
3021
3307
  })
3022
3308
  };
3023
3309
  }
3024
- class MutationGuardDeniedError extends Error {
3025
- denied;
3026
- documentId;
3027
- action;
3028
- constructor(args) {
3029
- const ids = args.denied.map((d) => d.guardId).join(", ");
3030
- super(`Mutation on "${args.documentId}" (${args.action}) denied by guard(s) [${ids}]`), this.name = "MutationGuardDeniedError", this.denied = args.denied, this.documentId = args.documentId, this.action = args.action;
3031
- }
3032
- }
3033
3310
  const HISTORY_DISPLAY = {
3034
3311
  "workflow.history.stageEntered": {
3035
3312
  title: "Stage entered",
@@ -3224,14 +3501,18 @@ function displayDescription(typeKey) {
3224
3501
  exports.AUTHORING_DISPLAY = AUTHORING_DISPLAY;
3225
3502
  exports.ActionDisabledError = ActionDisabledError;
3226
3503
  exports.ActionParamsInvalidError = ActionParamsInvalidError;
3504
+ exports.CascadeLimitError = CascadeLimitError;
3505
+ exports.ConcurrentFireActionError = ConcurrentFireActionError;
3227
3506
  exports.DISPLAY = DISPLAY;
3228
3507
  exports.EFFECTS_CONTEXT_DISPLAY = EFFECTS_CONTEXT_DISPLAY;
3508
+ exports.GUARD_DOC_TYPE = GUARD_DOC_TYPE;
3229
3509
  exports.GUARD_LIFTED_PREDICATE = GUARD_LIFTED_PREDICATE;
3230
3510
  exports.GUARD_OWNER = GUARD_OWNER;
3231
3511
  exports.HISTORY_DISPLAY = HISTORY_DISPLAY;
3232
3512
  exports.MutationGuardDeniedError = MutationGuardDeniedError;
3233
3513
  exports.OP_DISPLAY = OP_DISPLAY;
3234
3514
  exports.STATE_SLOT_DISPLAY = STATE_SLOT_DISPLAY;
3515
+ exports.WorkflowStateDivergedError = WorkflowStateDivergedError;
3235
3516
  exports.canonicalTag = canonicalTag;
3236
3517
  exports.compileGuard = compileGuard;
3237
3518
  exports.createEngine = createEngine;
@@ -3246,10 +3527,12 @@ exports.gdrFromResource = gdrFromResource;
3246
3527
  exports.gdrRef = gdrRef;
3247
3528
  exports.gdrUri = gdrUri;
3248
3529
  exports.guardMatches = guardMatches;
3530
+ exports.guardsForInstance = guardsForInstance;
3531
+ exports.guardsForResource = guardsForResource;
3532
+ exports.isDefinitionUnchanged = isDefinitionUnchanged;
3249
3533
  exports.isGdr = isGdr;
3250
3534
  exports.lakeGuardId = lakeGuardId;
3251
3535
  exports.parseGdr = parseGdr;
3252
- exports.reconcileStageGuards = reconcileStageGuards;
3253
3536
  exports.refCanvas = refCanvas;
3254
3537
  exports.refDashboard = refDashboard;
3255
3538
  exports.refDataset = refDataset;
@@ -3257,7 +3540,9 @@ exports.refMediaLibrary = refMediaLibrary;
3257
3540
  exports.resolveAccess = resolveAccess;
3258
3541
  exports.retractStageGuards = retractStageGuards;
3259
3542
  exports.silentLogger = silentLogger;
3543
+ exports.stripSystemFields = stripSystemFields;
3260
3544
  exports.validateDefinition = validateDefinition;
3261
3545
  exports.validateTags = validateTags;
3546
+ exports.wallClock = wallClock;
3262
3547
  exports.workflow = workflow;
3263
3548
  //# sourceMappingURL=index.cjs.map