@sanity/workflow-engine 0.10.0 → 0.12.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
@@ -123,7 +123,7 @@ function isGdr(value) {
123
123
  return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
124
124
  }
125
125
  function buildParams(args) {
126
- const { instance, now, snapshot, extra } = args, currentTasks2 = findOpenStageEntry(instance)?.tasks ?? [];
126
+ const { instance, now, snapshot, extra } = args, currentActivities2 = findOpenStageEntry(instance)?.activities ?? [];
127
127
  return {
128
128
  self: selfGdr(instance),
129
129
  fields: renderedFields(instance.fields ?? [], snapshot),
@@ -134,10 +134,12 @@ function buildParams(args) {
134
134
  /** `$effects` — parent context handoff by entry name, completed-effect
135
135
  * outputs namespaced under the effect's name (`$effects['x.y'].out`). */
136
136
  effects: effectsContextMap(instance),
137
- /** `$tasks` — the current stage's task rows, statuses included. */
138
- tasks: currentTasks2,
139
- allTasksDone: currentTasks2.every((t) => t.status === "done" || t.status === "skipped"),
140
- anyTaskFailed: currentTasks2.some((t) => t.status === "failed"),
137
+ /** `$activities` — the current stage's activity rows, statuses included. */
138
+ activities: currentActivities2,
139
+ allActivitiesDone: currentActivities2.every(
140
+ (activity) => activity.status === "done" || activity.status === "skipped"
141
+ ),
142
+ anyActivityFailed: currentActivities2.some((activity) => activity.status === "failed"),
141
143
  ...extra
142
144
  };
143
145
  }
@@ -149,15 +151,15 @@ function renderedFields(entries, snapshot) {
149
151
  function renderedValue(entry, snapshot) {
150
152
  return entry._type !== "doc.ref" || entry.value === null || snapshot === void 0 ? entry.value : snapshot.docs.find((d) => d._id === entry.value.id) ?? entry.value;
151
153
  }
152
- function scopedFieldOverlay(instance, snapshot, taskName) {
154
+ function scopedFieldOverlay(instance, snapshot, activityName) {
153
155
  const stageEntry = findOpenStageEntry(instance);
154
156
  if (stageEntry === void 0) return {};
155
- const stageFields = renderedFields(stageEntry.fields ?? [], snapshot), task = taskName ? stageEntry.tasks.find((t) => t.name === taskName) : void 0;
156
- return { ...stageFields, ...renderedFields(task?.fields ?? [], snapshot) };
157
+ const stageFields = renderedFields(stageEntry.fields ?? [], snapshot), activity = activityName ? stageEntry.activities.find((t) => t.name === activityName) : void 0;
158
+ return { ...stageFields, ...renderedFields(activity?.fields ?? [], snapshot) };
157
159
  }
158
- function assignedFor(instance, taskName, actor, roleAliases) {
160
+ function assignedFor(instance, activityName, actor, roleAliases) {
159
161
  if (actor === void 0) return !1;
160
- const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.fields?.find((s) => s._type === "assignees");
162
+ const entry = findOpenStageEntry(instance)?.activities.find((t) => t.name === activityName)?.fields?.find((s) => s._type === "assignees");
161
163
  return entry === void 0 ? !1 : entry.value.some(
162
164
  (a) => a.type === "user" ? a.id === actor.id : schema.actorFulfillsRole(actor.roles, a.role, roleAliases)
163
165
  );
@@ -289,7 +291,7 @@ function validateDefinition(definition) {
289
291
  validateStage(v2, stage);
290
292
  if (v2.errors.length > 0)
291
293
  throw new Error(
292
- `defineWorkflow("${definition.name}", v${definition.version}): ${v2.errors.length} validation error${v2.errors.length === 1 ? "" : "s"}:
294
+ `defineWorkflow("${definition.name}"): ${v2.errors.length} validation error${v2.errors.length === 1 ? "" : "s"}:
293
295
  ` + v2.errors.join(`
294
296
  `)
295
297
  );
@@ -310,7 +312,7 @@ function createDefinitionValidator() {
310
312
  return { errors, tryParse, checkCondition: (groq, where) => {
311
313
  tryParse(groq, where), rejectTypeScan(groq, where);
312
314
  }, checkEntry: (entry, label) => {
313
- entry.source.type === "query" && tryParse(entry.source.query, `${label}.query`);
315
+ entry.initialValue?.type === "query" && tryParse(entry.initialValue.query, `${label}.query`);
314
316
  }, checkGuardRead: (expr, where) => {
315
317
  isGuardReadExpr(expr) || errors.push(
316
318
  ` \xB7 ${where}: guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$fields.<name>[.path]", or "$effects['<name>'][.path]" (guards store resolved values; the lake cannot see $fields or $effects)`
@@ -327,8 +329,8 @@ function validateStage(v2, stage) {
327
329
  for (const [key, groq] of effectBindings(t.effects))
328
330
  v2.tryParse(groq, `transition "${t.name}" effect binding "${key}"`);
329
331
  }
330
- for (const task of stage.tasks ?? [])
331
- validateTask(v2, stage.name, task);
332
+ for (const activity of stage.activities ?? [])
333
+ validateActivity(v2, stage.name, activity);
332
334
  }
333
335
  function validateGuardReads(v2, stageName, guard) {
334
336
  const where = `stage "${stageName}" guard "${guard.name}"`;
@@ -337,22 +339,22 @@ function validateGuardReads(v2, stageName, guard) {
337
339
  for (const [key, expr] of Object.entries(guard.metadata ?? {}))
338
340
  v2.checkGuardRead(expr, `${where} metadata "${key}"`);
339
341
  }
340
- function validateTask(v2, stageName, task) {
341
- const where = `stage "${stageName}" task "${task.name}"`;
342
- for (const entry of task.fields ?? [])
342
+ function validateActivity(v2, stageName, activity) {
343
+ const where = `stage "${stageName}" activity "${activity.name}"`;
344
+ for (const entry of activity.fields ?? [])
343
345
  v2.checkEntry(entry, `${where}.fields "${entry.name}"`);
344
- task.filter !== void 0 && v2.checkCondition(task.filter, `${where}.filter`), task.completeWhen !== void 0 && v2.checkCondition(task.completeWhen, `${where}.completeWhen`), task.failWhen !== void 0 && v2.checkCondition(task.failWhen, `${where}.failWhen`);
345
- for (const [name, groq] of Object.entries(task.requirements ?? {}))
346
+ activity.filter !== void 0 && v2.checkCondition(activity.filter, `${where}.filter`), activity.completeWhen !== void 0 && v2.checkCondition(activity.completeWhen, `${where}.completeWhen`), activity.failWhen !== void 0 && v2.checkCondition(activity.failWhen, `${where}.failWhen`);
347
+ for (const [name, groq] of Object.entries(activity.requirements ?? {}))
346
348
  v2.checkCondition(groq, `${where}.requirements "${name}"`);
347
- for (const [key, groq] of effectBindings(task.effects))
349
+ for (const [key, groq] of effectBindings(activity.effects))
348
350
  v2.tryParse(groq, `${where} effect binding "${key}"`);
349
- validateTaskActions(v2, task), task.subworkflows !== void 0 && validateSubworkflows(v2, where, task.subworkflows);
351
+ validateActivityActions(v2, activity), activity.subworkflows !== void 0 && validateSubworkflows(v2, where, activity.subworkflows);
350
352
  }
351
- function validateTaskActions(v2, task) {
352
- for (const a of task.actions ?? []) {
353
- a.filter !== void 0 && v2.checkCondition(a.filter, `task "${task.name}" action "${a.name}".filter`);
353
+ function validateActivityActions(v2, activity) {
354
+ for (const a of activity.actions ?? []) {
355
+ a.filter !== void 0 && v2.checkCondition(a.filter, `activity "${activity.name}" action "${a.name}".filter`);
354
356
  for (const [key, groq] of effectBindings(a.effects))
355
- v2.tryParse(groq, `task "${task.name}" action "${a.name}" effect binding "${key}"`);
357
+ v2.tryParse(groq, `activity "${activity.name}" action "${a.name}" effect binding "${key}"`);
356
358
  }
357
359
  }
358
360
  function validateSubworkflows(v2, where, sub) {
@@ -367,10 +369,10 @@ function effectBindings(effects) {
367
369
  (e) => Object.entries(e.bindings ?? {}).map(([k, groq]) => [`${e.name}.${k}`, groq])
368
370
  );
369
371
  }
370
- function actionVerdict(task, action) {
372
+ function actionVerdict(activity, action) {
371
373
  return {
372
- task: task.task.name,
373
- taskStatus: task.status,
374
+ activity: activity.activity.name,
375
+ activityStatus: activity.status,
374
376
  action: action.action.name,
375
377
  title: action.action.title,
376
378
  allowed: action.allowed,
@@ -378,8 +380,8 @@ function actionVerdict(task, action) {
378
380
  params: action.action.params ?? []
379
381
  };
380
382
  }
381
- function availableActions(tasks) {
382
- return tasks.flatMap((t) => t.actions.map((a) => actionVerdict(t, a)));
383
+ function availableActions(activities) {
384
+ return activities.flatMap((t) => t.actions.map((a) => actionVerdict(t, a)));
383
385
  }
384
386
  const wallClock = () => (/* @__PURE__ */ new Date()).toISOString(), GUARD_DOC_TYPE = "temp.system.guard";
385
387
  class MutationGuardDeniedError extends Error {
@@ -501,60 +503,67 @@ function diagnoseInputFromEvaluation(evaluation) {
501
503
  const stage = openStage(evaluation.instance);
502
504
  return {
503
505
  instance: evaluation.instance,
504
- tasks: evaluation.currentStage.tasks,
506
+ activities: evaluation.currentStage.activities,
505
507
  transitions: evaluation.currentStage.transitions,
506
508
  assignees: Object.fromEntries(
507
- evaluation.currentStage.tasks.map((t) => [t.task.name, assigneesOf(stage, t.task.name)])
509
+ evaluation.currentStage.activities.map((t) => [
510
+ t.activity.name,
511
+ assigneesOf(stage, t.activity.name)
512
+ ])
508
513
  )
509
514
  };
510
515
  }
511
516
  function openStage(instance) {
512
517
  return findOpenStageEntry(instance);
513
518
  }
514
- function assigneesOf(stage, taskName) {
515
- return (stage?.tasks.find((t) => t.name === taskName)?.fields ?? []).filter((s) => s._type === "assignees").flatMap((s) => s.value);
519
+ function assigneesOf(stage, activityName) {
520
+ return (stage?.activities.find((t) => t.name === activityName)?.fields ?? []).filter((s) => s._type === "assignees").flatMap((s) => s.value);
516
521
  }
517
522
  function isResolved(status) {
518
523
  return status === "done" || status === "skipped";
519
524
  }
520
525
  function failedEffectCause(input) {
521
- const stalled = new Set(input.tasks.filter((t) => !isResolved(t.status)).map((t) => t.task.name)), effect = [...input.instance.effectHistory].reverse().find((e) => e.status === "failed" && e.origin.kind === "task" && stalled.has(e.origin.name));
526
+ const stalled = new Set(
527
+ input.activities.filter((t) => !isResolved(t.status)).map((t) => t.activity.name)
528
+ ), effect = [...input.instance.effectHistory].reverse().find(
529
+ (e) => e.status === "failed" && e.origin.kind === "activity" && stalled.has(e.origin.name)
530
+ );
522
531
  return effect ? { kind: "failed-effect", effect } : void 0;
523
532
  }
524
533
  function hungEffectCause(input) {
525
534
  const effect = input.instance.pendingEffects.find((e) => e.claim !== void 0);
526
535
  return effect ? { kind: "hung-effect", effect } : void 0;
527
536
  }
528
- function failedTaskCause(input) {
529
- const failed = input.tasks.find((t) => t.status === "failed");
530
- return failed ? { kind: "failed-task", task: failed.task.name } : void 0;
537
+ function failedActivityCause(input) {
538
+ const failed = input.activities.find((t) => t.status === "failed");
539
+ return failed ? { kind: "failed-activity", activity: failed.activity.name } : void 0;
531
540
  }
532
541
  function waitingState(input) {
533
- const active = input.tasks.find(
534
- (t) => t.status === "active" && (t.task.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length === 0
542
+ const active = input.activities.find(
543
+ (t) => t.status === "active" && (t.activity.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length === 0
535
544
  );
536
545
  if (active !== void 0)
537
546
  return {
538
547
  state: "waiting",
539
- task: active.task.name,
540
- assignees: input.assignees[active.task.name] ?? [],
541
- actions: (active.task.actions ?? []).map((a) => a.name)
548
+ activity: active.activity.name,
549
+ assignees: input.assignees[active.activity.name] ?? [],
550
+ actions: (active.activity.actions ?? []).map((a) => a.name)
542
551
  };
543
552
  }
544
553
  function blockedState(input) {
545
- const blocked = input.tasks.find(
546
- (t) => t.status === "active" && (t.task.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length > 0
554
+ const blocked = input.activities.find(
555
+ (t) => t.status === "active" && (t.activity.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length > 0
547
556
  );
548
557
  if (blocked !== void 0)
549
558
  return {
550
559
  state: "blocked",
551
- task: blocked.task.name,
560
+ activity: blocked.activity.name,
552
561
  requirements: blocked.unmetRequirements ?? [],
553
- assignees: input.assignees[blocked.task.name] ?? []
562
+ assignees: input.assignees[blocked.activity.name] ?? []
554
563
  };
555
564
  }
556
565
  function noTransitionFiresCause(input) {
557
- if (input.transitions.length === 0 || input.transitions.some((t) => t.filterSatisfied) || !input.tasks.every((t) => isResolved(t.status)))
566
+ if (input.transitions.length === 0 || input.transitions.some((t) => t.filterSatisfied) || !input.activities.every((t) => isResolved(t.status)))
558
567
  return;
559
568
  const undecidable = input.transitions.filter((t) => t.unevaluable);
560
569
  return undecidable.length > 0 ? { kind: "transition-unevaluable", transitions: undecidable.map((t) => t.transition.name) } : { kind: "no-transition-fires" };
@@ -567,7 +576,7 @@ function diagnoseInstance(input) {
567
576
  }
568
577
  if (instance.completedAt !== void 0)
569
578
  return { state: "completed", at: instance.completedAt };
570
- const cause = failedEffectCause(input) ?? failedTaskCause(input) ?? hungEffectCause(input) ?? noTransitionFiresCause(input);
579
+ const cause = failedEffectCause(input) ?? failedActivityCause(input) ?? hungEffectCause(input) ?? noTransitionFiresCause(input);
571
580
  return cause !== void 0 ? { state: "stuck", cause } : waitingState(input) ?? blockedState(input) ?? { state: "progressing" };
572
581
  }
573
582
  const RUNNABLE_VERBS = /* @__PURE__ */ new Set(["set-stage", "abort"]);
@@ -589,12 +598,12 @@ function remediationsForCause(cause) {
589
598
  },
590
599
  { verb: "abort", rationale: "Abort the instance if the effect never drains." }
591
600
  ];
592
- case "failed-task":
601
+ case "failed-activity":
593
602
  return [
594
- { verb: "reset-task", rationale: "Reset or skip the failed task." },
603
+ { verb: "reset-activity", rationale: "Reset or skip the failed activity." },
595
604
  {
596
605
  verb: "set-stage",
597
- rationale: "Move the instance past the failed task to the intended next stage."
606
+ rationale: "Move the instance past the failed activity to the intended next stage."
598
607
  }
599
608
  ];
600
609
  case "no-transition-fires":
@@ -616,13 +625,15 @@ function remediationsFor(diagnosis) {
616
625
  }
617
626
  class ActionParamsInvalidError extends Error {
618
627
  action;
619
- task;
628
+ activity;
620
629
  issues;
621
630
  constructor(args) {
622
631
  const lines = args.issues.map((i) => ` - ${i.param}: ${i.reason}`).join(`
623
632
  `);
624
- super(`Action "${args.action}" on task "${args.task}" rejected: invalid params
625
- ${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.task = args.task, this.issues = args.issues;
633
+ super(
634
+ `Action "${args.action}" on activity "${args.activity}" rejected: invalid params
635
+ ${lines}`
636
+ ), this.name = "ActionParamsInvalidError", this.action = args.action, this.activity = args.activity, this.issues = args.issues;
626
637
  }
627
638
  }
628
639
  class RequiredFieldNotProvidedError extends Error {
@@ -632,7 +643,7 @@ class RequiredFieldNotProvidedError extends Error {
632
643
  const lines = args.missing.map((m) => ` - ${m.name} (${m.type})`).join(`
633
644
  `), where = args.definition !== void 0 ? ` for workflow "${args.definition}"` : "";
634
645
  super(
635
- `Required init fields${where} was not provided:
646
+ `Required input fields${where} were not provided:
636
647
  ${lines}
637
648
  Provide each via \`initialFields\` when starting standalone, or via the parent's \`subworkflows.with\` when spawned.`
638
649
  ), this.name = "RequiredFieldNotProvidedError", args.definition !== void 0 && (this.definition = args.definition), this.missing = args.missing;
@@ -664,13 +675,13 @@ class PartialGuardDeployError extends Error {
664
675
  const CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS = 3;
665
676
  class ConcurrentFireActionError extends Error {
666
677
  instanceId;
667
- task;
678
+ activity;
668
679
  action;
669
680
  attempts;
670
681
  constructor(args) {
671
682
  super(
672
- `Action "${args.action}" on task "${args.task}" (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.`
673
- ), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.task = args.task, this.action = args.action, this.attempts = args.attempts;
683
+ `Action "${args.action}" on activity "${args.activity}" (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.`
684
+ ), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.activity = args.activity, this.action = args.action, this.attempts = args.attempts;
674
685
  }
675
686
  }
676
687
  function isRevisionConflict(error) {
@@ -683,7 +694,7 @@ class ConcurrentEditFieldError extends Error {
683
694
  target;
684
695
  attempts;
685
696
  constructor(args) {
686
- const where = args.target.task !== void 0 ? `${args.target.task}.${args.target.field}` : args.target.field;
697
+ const where = args.target.activity !== void 0 ? `${args.target.activity}.${args.target.field}` : args.target.field;
687
698
  super(
688
699
  `Edit of "${args.target.scope}:${where}" (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.`
689
700
  ), this.name = "ConcurrentEditFieldError", this.instanceId = args.instanceId, this.target = args.target, this.attempts = args.attempts;
@@ -761,6 +772,9 @@ function subscriptionDocumentsForInstance(instance) {
761
772
  ...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
762
773
  };
763
774
  }
775
+ function instanceWatchesDocument(instance, document) {
776
+ return collectWatchRefs(instance).some((ref) => ref.id === document);
777
+ }
764
778
  function entryDocRefs(entries) {
765
779
  return fieldEntries(entries).flatMap((entry) => entry._type === "doc.ref" ? isGdr(entry.value) ? [entry.value] : [] : entry._type === "doc.refs" ? Array.isArray(entry.value) ? entry.value.filter(isGdr) : [] : []);
766
780
  }
@@ -875,14 +889,14 @@ function guardIdRefs(definition) {
875
889
  function staticIdRefGdr(idRef, stage, definition) {
876
890
  const read = /^\$fields\.([\w-]+)/.exec(idRef);
877
891
  if (read === null) return;
878
- const source = entriesInScope(stage, definition).find((e) => e.name === read[1])?.source;
879
- return source?.type === "literal" ? gdrFromValue(source.value) : void 0;
892
+ const seed = entriesInScope(stage, definition).find((e) => e.name === read[1])?.initialValue;
893
+ return seed?.type === "literal" ? gdrFromValue(seed.value) : void 0;
880
894
  }
881
895
  function entriesInScope(stage, definition) {
882
896
  return [
883
897
  ...definition.fields ?? [],
884
898
  ...stage.fields ?? [],
885
- ...(stage.tasks ?? []).flatMap((task) => task.fields ?? [])
899
+ ...(stage.activities ?? []).flatMap((activity) => activity.fields ?? [])
886
900
  ];
887
901
  }
888
902
  function gdrFromValue(value) {
@@ -1064,76 +1078,73 @@ const GdrShape = v__namespace.looseObject({
1064
1078
  }), AssigneeShape = v__namespace.union([
1065
1079
  v__namespace.looseObject({ type: v__namespace.literal("user"), id: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)) }),
1066
1080
  v__namespace.looseObject({ type: v__namespace.literal("role"), role: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)) })
1067
- ]), ChecklistItemShape = v__namespace.looseObject({
1068
- label: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)),
1069
- done: v__namespace.boolean(),
1070
- doneBy: v__namespace.optional(v__namespace.string()),
1071
- doneAt: v__namespace.optional(v__namespace.string()),
1072
- _key: v__namespace.optional(v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)))
1073
- }), NoteItemShape = v__namespace.pipe(
1074
- v__namespace.looseObject({
1075
- _key: v__namespace.optional(v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)))
1076
- }),
1077
- v__namespace.check(
1078
- (val) => typeof val == "object" && val !== null && !Array.isArray(val),
1079
- "must be an object"
1080
- )
1081
- ), 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([
1081
+ ]), 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([
1082
1082
  v__namespace.null(),
1083
1083
  v__namespace.pipe(
1084
1084
  v__namespace.string(),
1085
1085
  v__namespace.check((s) => !Number.isNaN(Date.parse(s)), "must be an ISO-8601 datetime string")
1086
1086
  )
1087
+ ]), NullableDate = v__namespace.union([
1088
+ v__namespace.null(),
1089
+ v__namespace.pipe(v__namespace.string(), v__namespace.regex(/^\d{4}-\d{2}-\d{2}$/, "must be a `YYYY-MM-DD` date"))
1087
1090
  ]), NullableUrl = NullableString, valueSchemas = {
1088
1091
  "doc.ref": v__namespace.union([v__namespace.null(), GdrShape]),
1089
1092
  "doc.refs": v__namespace.array(GdrShape),
1090
1093
  "release.ref": v__namespace.union([v__namespace.null(), ReleaseRefShape]),
1091
1094
  query: v__namespace.any(),
1092
- "value.string": NullableString,
1093
- "value.url": NullableUrl,
1094
- "value.number": NullableNumber,
1095
- "value.boolean": NullableBoolean,
1096
- "value.dateTime": NullableDateTime,
1097
- "value.actor": v__namespace.union([v__namespace.null(), ActorShape]),
1098
- checklist: v__namespace.array(ChecklistItemShape),
1099
- notes: v__namespace.array(NoteItemShape),
1095
+ string: NullableString,
1096
+ text: NullableString,
1097
+ number: NullableNumber,
1098
+ boolean: NullableBoolean,
1099
+ date: NullableDate,
1100
+ datetime: NullableDateTime,
1101
+ url: NullableUrl,
1102
+ actor: v__namespace.union([v__namespace.null(), ActorShape]),
1103
+ assignee: v__namespace.union([v__namespace.null(), AssigneeShape]),
1100
1104
  assignees: v__namespace.array(AssigneeShape)
1101
- }, itemSchemas = {
1102
- "doc.refs": GdrShape,
1103
- checklist: ChecklistItemShape,
1104
- notes: NoteItemShape,
1105
- assignees: AssigneeShape
1106
1105
  };
1107
- function isAppendable(entryType) {
1108
- return entryType in itemSchemas;
1106
+ function shapeValueSchema(shape) {
1107
+ return shape.type === "object" ? objectSchema(shape.fields ?? []) : shape.type === "array" ? v__namespace.array(objectSchema(shape.of ?? [])) : valueSchemas[shape.type] ?? v__namespace.any();
1109
1108
  }
1110
- function validateFieldValue(args) {
1111
- const schema2 = valueSchemas[args.entryType];
1112
- if (schema2 === void 0)
1113
- throw new FieldValueShapeError({
1114
- entryType: args.entryType,
1115
- entryName: args.entryName,
1116
- issues: [`unknown field entry type ${args.entryType}`],
1117
- mode: "value"
1118
- });
1109
+ function objectSchema(fields) {
1110
+ const entries = /* @__PURE__ */ Object.create(null);
1111
+ for (const f of fields) entries[f.name] = v__namespace.optional(shapeValueSchema(f));
1112
+ return v__namespace.looseObject(entries);
1113
+ }
1114
+ function wholeValueSchema(entryType, shape) {
1115
+ return entryType === "object" ? v__namespace.union([v__namespace.null(), objectSchema(shape.fields ?? [])]) : entryType === "array" ? v__namespace.array(objectSchema(shape.of ?? [])) : valueSchemas[entryType];
1116
+ }
1117
+ function appendItemSchema(entryType, shape) {
1118
+ if (entryType === "array") return objectSchema(shape.of ?? []);
1119
+ if (entryType === "doc.refs") return GdrShape;
1120
+ if (entryType === "assignees") return AssigneeShape;
1121
+ }
1122
+ function checkFieldValue(args) {
1123
+ const schema2 = wholeValueSchema(args.entryType, args);
1124
+ if (schema2 === void 0) return [`unknown field entry type ${args.entryType}`];
1119
1125
  const result = v__namespace.safeParse(schema2, args.value);
1120
- if (!result.success)
1126
+ return result.success ? void 0 : formatIssues(result.issues);
1127
+ }
1128
+ function validateFieldValue(args) {
1129
+ const issues = checkFieldValue(args);
1130
+ if (issues !== void 0)
1121
1131
  throw new FieldValueShapeError({
1122
1132
  entryType: args.entryType,
1123
1133
  entryName: args.entryName,
1124
- issues: formatIssues(result.issues),
1134
+ issues,
1125
1135
  mode: "value"
1126
1136
  });
1127
1137
  }
1128
1138
  function validateFieldAppendItem(args) {
1129
- if (!isAppendable(args.entryType))
1139
+ const schema2 = appendItemSchema(args.entryType, args);
1140
+ if (schema2 === void 0)
1130
1141
  throw new FieldValueShapeError({
1131
1142
  entryType: args.entryType,
1132
1143
  entryName: args.entryName,
1133
1144
  issues: [`field entry type ${args.entryType} does not support append`],
1134
1145
  mode: "item"
1135
1146
  });
1136
- const schema2 = itemSchemas[args.entryType], result = v__namespace.safeParse(schema2, args.item);
1147
+ const result = v__namespace.safeParse(schema2, args.item);
1137
1148
  if (!result.success)
1138
1149
  throw new FieldValueShapeError({
1139
1150
  entryType: args.entryType,
@@ -1161,7 +1172,7 @@ function derivePerspectiveFromFields(entries) {
1161
1172
  async function resolveDeclaredFields(args) {
1162
1173
  const { entryDefs, initialFields, ctx, randomKey: randomKey2 } = args;
1163
1174
  if (entryDefs === void 0 || entryDefs.length === 0) return [];
1164
- assertRequiredInitProvided(entryDefs, initialFields, ctx.definitionName);
1175
+ assertRequiredInputProvided(entryDefs, initialFields, ctx.definitionName);
1165
1176
  const out = [];
1166
1177
  for (const entry of entryDefs) {
1167
1178
  const scopedCtx = { ...ctx, resolvedFields: out };
@@ -1169,8 +1180,8 @@ async function resolveDeclaredFields(args) {
1169
1180
  }
1170
1181
  return out;
1171
1182
  }
1172
- function assertRequiredInitProvided(entryDefs, initialFields, definitionName) {
1173
- const missing = entryDefs.filter((entry) => entry.required === !0 && entry.source.type === "init").filter((entry) => {
1183
+ function assertRequiredInputProvided(entryDefs, initialFields, definitionName) {
1184
+ const missing = entryDefs.filter((entry) => entry.required === !0 && entry.initialValue?.type === "input").filter((entry) => {
1174
1185
  const match = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
1175
1186
  return match === void 0 || match.value === void 0 || match.value === null;
1176
1187
  }).map((entry) => ({ name: entry.name, type: entry.type }));
@@ -1180,18 +1191,13 @@ function assertRequiredInitProvided(entryDefs, initialFields, definitionName) {
1180
1191
  missing
1181
1192
  });
1182
1193
  }
1183
- const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set([
1184
- "doc.refs",
1185
- "checklist",
1186
- "notes",
1187
- "assignees"
1188
- ]);
1194
+ const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set(["doc.refs", "array", "assignees"]);
1189
1195
  function defaultEntryValue(entryType) {
1190
1196
  return ARRAY_SLOT_TYPES.has(entryType) ? [] : null;
1191
1197
  }
1192
- function resolveInitValue(entry, initialFields, defaultValue) {
1198
+ function resolveInputValue(entry, initialFields, defaultValue) {
1193
1199
  const initMatch = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
1194
- return initMatch === void 0 ? defaultValue : (assertInitValueShape(entry, initMatch.value), initMatch.value);
1200
+ return initMatch === void 0 ? defaultValue : (assertInputValueShape(entry, initMatch.value), initMatch.value);
1195
1201
  }
1196
1202
  function resolveFieldReadValue(source, ctx, defaultValue) {
1197
1203
  const target = (source.scope === "workflow" ? ctx.workflowFields ?? [] : ctx.resolvedFields ?? []).find((s) => s.name === source.field), targetValue = target === void 0 ? void 0 : target.value;
@@ -1202,7 +1208,7 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
1202
1208
  self: ctx.selfId ?? null,
1203
1209
  fields: fieldMapFromResolved(ctx.resolvedFields ?? []),
1204
1210
  stage: ctx.stageName ?? null,
1205
- task: ctx.taskName ?? null,
1211
+ activity: ctx.activityName ?? null,
1206
1212
  now: ctx.now,
1207
1213
  tag: ctx.tag
1208
1214
  });
@@ -1217,38 +1223,47 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
1217
1223
  }
1218
1224
  }
1219
1225
  async function resolveEntryValue(entry, initialFields, ctx, defaultValue) {
1220
- const source = entry.source;
1221
- return source.type === "init" ? resolveInitValue(entry, initialFields, defaultValue) : source.type === "literal" ? source.value ?? defaultValue : source.type === "fieldRead" ? resolveFieldReadValue(source, ctx, defaultValue) : source.type === "query" && ctx.client !== void 0 ? resolveQueryValue(entry, source, ctx, ctx.client, defaultValue) : defaultValue;
1226
+ const source = entry.initialValue;
1227
+ return source === void 0 ? defaultValue : source.type === "input" ? resolveInputValue(entry, initialFields, defaultValue) : source.type === "literal" ? source.value ?? defaultValue : source.type === "fieldRead" ? resolveFieldReadValue(source, ctx, defaultValue) : source.type === "query" && ctx.client !== void 0 ? resolveQueryValue(entry, source, ctx, ctx.client, defaultValue) : defaultValue;
1222
1228
  }
1223
1229
  function buildResolvedEntry(entry, value, _key, now) {
1224
- const titleProp = entry.title !== void 0 ? { title: entry.title } : {}, descriptionProp = entry.description !== void 0 ? { description: entry.description } : {};
1225
- return entry.type === "query" ? {
1226
- _key,
1227
- _type: "query",
1228
- name: entry.name,
1229
- ...titleProp,
1230
- ...descriptionProp,
1231
- value,
1232
- resolvedAt: now
1233
- } : {
1230
+ return {
1234
1231
  _key,
1235
1232
  _type: entry.type,
1236
1233
  name: entry.name,
1237
- ...titleProp,
1238
- ...descriptionProp,
1239
- value
1234
+ ...entry.title !== void 0 ? { title: entry.title } : {},
1235
+ ...entry.description !== void 0 ? { description: entry.description } : {},
1236
+ value,
1237
+ ...entry.initialValue?.type === "query" ? { resolvedAt: now } : {},
1238
+ ...entry.type === "object" ? { fields: entry.fields ?? [] } : {},
1239
+ ...entry.type === "array" ? { of: entry.of ?? [] } : {}
1240
1240
  };
1241
1241
  }
1242
1242
  async function resolveOneEntry(entry, initialFields, ctx, randomKey2) {
1243
1243
  const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue(entry, initialFields, ctx, defaultValue);
1244
- return validateFieldValue({ entryType: entry.type, entryName: entry.name, value }), buildResolvedEntry(entry, value, randomKey2(), ctx.now);
1244
+ if (entry.initialValue?.type === "query") {
1245
+ const issues = checkFieldValue({
1246
+ entryType: entry.type,
1247
+ value,
1248
+ fields: entry.fields,
1249
+ of: entry.of
1250
+ });
1251
+ return issues !== void 0 ? (ctx.recordDiscard?.({ field: entry.name, detail: issues.join("; ") }), buildResolvedEntry(entry, defaultValue, randomKey2(), ctx.now)) : buildResolvedEntry(entry, value, randomKey2(), ctx.now);
1252
+ }
1253
+ return validateFieldValue({
1254
+ entryType: entry.type,
1255
+ entryName: entry.name,
1256
+ value,
1257
+ fields: entry.fields,
1258
+ of: entry.of
1259
+ }), buildResolvedEntry(entry, value, randomKey2(), ctx.now);
1245
1260
  }
1246
1261
  function fieldMapFromResolved(entries) {
1247
1262
  const out = {};
1248
1263
  for (const s of entries) out[s.name] = s.value;
1249
1264
  return out;
1250
1265
  }
1251
- function assertInitValueShape(entry, value) {
1266
+ function assertInputValueShape(entry, value) {
1252
1267
  if (entry.type === "doc.ref") {
1253
1268
  assertGdrShape(value, `field entry "${entry.name}" (doc.ref)`);
1254
1269
  return;
@@ -1258,14 +1273,14 @@ function assertInitValueShape(entry, value) {
1258
1273
  const v2 = value;
1259
1274
  if (typeof v2.releaseName != "string" || v2.releaseName.length === 0)
1260
1275
  throw new Error(
1261
- `Invalid init value for field entry "${entry.name}" (release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(v2.releaseName)}.`
1276
+ `Invalid input value for field entry "${entry.name}" (release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(v2.releaseName)}.`
1262
1277
  );
1263
1278
  return;
1264
1279
  }
1265
1280
  if (entry.type === "doc.refs") {
1266
1281
  if (!Array.isArray(value))
1267
1282
  throw new Error(
1268
- `Invalid init value for field entry "${entry.name}" (doc.refs): expected an array of GDRs, got ${typeof value}.`
1283
+ `Invalid input value for field entry "${entry.name}" (doc.refs): expected an array of GDRs, got ${typeof value}.`
1269
1284
  );
1270
1285
  for (const [i, item] of value.entries())
1271
1286
  assertGdrShape(item, `field entry "${entry.name}" (doc.refs) item [${i}]`);
@@ -1329,12 +1344,12 @@ async function loadContext(client, instanceId, options) {
1329
1344
  async function ctxConditionParams(ctx, opts) {
1330
1345
  const base = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), fields = {
1331
1346
  ...base.fields,
1332
- ...scopedFieldOverlay(ctx.instance, ctx.snapshot, opts?.taskName)
1347
+ ...scopedFieldOverlay(ctx.instance, ctx.snapshot, opts?.activityName)
1333
1348
  }, params = {
1334
1349
  ...base,
1335
1350
  fields,
1336
1351
  actor: opts?.actor,
1337
- assigned: opts?.taskName !== void 0 ? assignedFor(ctx.instance, opts.taskName, opts?.actor, ctx.definition.roleAliases) : !1,
1352
+ assigned: opts?.activityName !== void 0 ? assignedFor(ctx.instance, opts.activityName, opts?.actor, ctx.definition.roleAliases) : !1,
1338
1353
  ...opts?.vars
1339
1354
  };
1340
1355
  return { ...await evaluatePredicates({
@@ -1372,7 +1387,7 @@ function findStage(definition, stageName) {
1372
1387
  return stage;
1373
1388
  }
1374
1389
  async function resolveStageFieldEntries(args) {
1375
- const { client, instance, stage, now, initialFields } = args;
1390
+ const { client, instance, stage, now, initialFields, recordDiscard } = args;
1376
1391
  return resolveDeclaredFields({
1377
1392
  entryDefs: stage.fields,
1378
1393
  initialFields: initialFields ?? [],
@@ -1383,18 +1398,19 @@ async function resolveStageFieldEntries(args) {
1383
1398
  tag: instance.tag,
1384
1399
  stageName: stage.name,
1385
1400
  workflowResource: instance.workflowResource,
1386
- // Allow stage-scope entries' `source: { type: "fieldRead", scope:
1401
+ // Allow stage-scope entries' `initialValue: { type: "fieldRead", scope:
1387
1402
  // "workflow", ... }` to see the already-resolved workflow-scope fields.
1388
1403
  workflowFields: instance.fields ?? [],
1389
- ...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
1404
+ ...instance.perspective !== void 0 ? { perspective: instance.perspective } : {},
1405
+ ...recordDiscard !== void 0 ? { recordDiscard } : {}
1390
1406
  },
1391
1407
  randomKey
1392
1408
  });
1393
1409
  }
1394
- async function resolveTaskFieldEntries(args) {
1395
- const { client, instance, stage, task, now } = args;
1410
+ async function resolveActivityFieldEntries(args) {
1411
+ const { client, instance, stage, activity, now, recordDiscard } = args;
1396
1412
  return resolveDeclaredFields({
1397
- entryDefs: task.fields,
1413
+ entryDefs: activity.fields,
1398
1414
  initialFields: [],
1399
1415
  ctx: {
1400
1416
  client,
@@ -1403,85 +1419,70 @@ async function resolveTaskFieldEntries(args) {
1403
1419
  tag: instance.tag,
1404
1420
  stageName: stage.name,
1405
1421
  workflowResource: instance.workflowResource,
1406
- taskName: task.name,
1422
+ activityName: activity.name,
1407
1423
  workflowFields: instance.fields ?? [],
1408
- ...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
1424
+ ...instance.perspective !== void 0 ? { perspective: instance.perspective } : {},
1425
+ ...recordDiscard !== void 0 ? { recordDiscard } : {}
1409
1426
  },
1410
1427
  randomKey
1411
1428
  });
1412
1429
  }
1413
- async function deployOrRollback(args) {
1414
- try {
1415
- await args.deploy();
1416
- } catch (guardError) {
1417
- if (!args.reversible)
1418
- throw new WorkflowStateDivergedError({
1419
- instanceId: args.instanceId,
1420
- guardError,
1421
- reason: "the move created child instances a parent-only rollback cannot undo"
1422
- });
1423
- try {
1424
- let patch = args.client.patch(args.instanceId).set(args.restore);
1425
- args.unset && args.unset.length > 0 && (patch = patch.unset(args.unset)), args.committedRev !== void 0 && (patch = patch.ifRevisionId(args.committedRev)), await patch.commit(SYNC_COMMIT);
1426
- } catch (rollbackError) {
1427
- throw new WorkflowStateDivergedError({
1428
- instanceId: args.instanceId,
1429
- guardError,
1430
- rollbackError,
1431
- reason: "rollback of the committed move failed"
1432
- });
1433
- }
1434
- throw guardError instanceof PartialGuardDeployError ? new WorkflowStateDivergedError({
1435
- instanceId: args.instanceId,
1436
- guardError,
1437
- reason: "a multi-guard deploy partially applied; the rolled-back move left orphaned guard locks"
1438
- }) : guardError;
1439
- }
1430
+ async function resolveBindings(args) {
1431
+ const resolved = {};
1432
+ for (const [key, groq] of Object.entries(args.bindings ?? {}))
1433
+ resolved[key] = await runGroq(groq, args.params, args.snapshot);
1434
+ return { ...resolved, ...args.staticInput };
1440
1435
  }
1441
- function applyTaskStatusChange(args) {
1442
- const { entry, history, stage, to, at, actor } = args, from = entry.status;
1443
- entry.status = to, schema.isTerminalTaskStatus(to) && (entry.completedAt = at, actor !== void 0 && (entry.completedBy = actor.id)), history.push({
1444
- _key: randomKey(),
1445
- _type: "taskStatusChanged",
1446
- at,
1447
- stage,
1448
- task: entry.name,
1449
- from,
1450
- to,
1451
- ...actor !== void 0 ? { actor } : {}
1452
- });
1436
+ async function reload(client, instanceId, tag) {
1437
+ const doc = await client.getDocument(instanceId);
1438
+ if (!doc)
1439
+ throw new Error(`Workflow instance ${instanceId} not found`);
1440
+ if (doc.tag !== tag)
1441
+ throw new Error(`Workflow instance ${instanceId} not visible to this engine (tag mismatch)`);
1442
+ return doc;
1453
1443
  }
1454
- function stageTransitionHistory(args) {
1455
- const { fromStage, toStage, at, transition, actor, via, reason } = args, shared = {
1456
- at,
1457
- ...transition !== void 0 ? { transition } : {},
1458
- ...actor !== void 0 ? { actor } : {},
1459
- ...via !== void 0 ? { via } : {},
1460
- ...reason !== void 0 ? { reason } : {}
1444
+ function effectsContextEntry(name, value) {
1445
+ const _key = randomKey();
1446
+ return typeof value == "string" ? { _key, _type: "effectsContext.string", name, value } : typeof value == "number" ? { _key, _type: "effectsContext.number", name, value } : typeof value == "boolean" ? { _key, _type: "effectsContext.boolean", name, value } : { _key, _type: "effectsContext.ref", name, value };
1447
+ }
1448
+ function effectsContextJsonEntry(name, value) {
1449
+ return { _key: randomKey(), _type: "effectsContext.json", name, value: JSON.stringify(value) };
1450
+ }
1451
+ function buildInstanceBase(args) {
1452
+ const { id, now, actor, perspective } = args;
1453
+ return {
1454
+ _id: id,
1455
+ _type: WORKFLOW_INSTANCE_TYPE,
1456
+ _rev: "",
1457
+ _createdAt: now,
1458
+ _updatedAt: now,
1459
+ tag: args.tag,
1460
+ workflowResource: args.workflowResource,
1461
+ definition: args.definitionName,
1462
+ pinnedVersion: args.pinnedVersion,
1463
+ ...args.pinnedContentHash !== void 0 ? { pinnedContentHash: args.pinnedContentHash } : {},
1464
+ definitionSnapshot: JSON.stringify(args.definition),
1465
+ fields: args.fields,
1466
+ effectsContext: args.effectsContext,
1467
+ ancestors: args.ancestors,
1468
+ ...perspective !== void 0 ? { perspective } : {},
1469
+ currentStage: args.initialStage,
1470
+ stages: [],
1471
+ pendingEffects: [],
1472
+ effectHistory: [],
1473
+ history: [
1474
+ {
1475
+ _key: randomKey(),
1476
+ _type: "stageEntered",
1477
+ at: now,
1478
+ stage: args.initialStage,
1479
+ ...actor !== void 0 ? { actor } : {}
1480
+ },
1481
+ ...args.extraHistory ?? []
1482
+ ],
1483
+ startedAt: now,
1484
+ lastChangedAt: now
1461
1485
  };
1462
- return [
1463
- {
1464
- _key: randomKey(),
1465
- _type: "stageExited",
1466
- stage: fromStage,
1467
- toStage,
1468
- ...shared
1469
- },
1470
- {
1471
- _key: randomKey(),
1472
- _type: "transitionFired",
1473
- fromStage,
1474
- toStage,
1475
- ...shared
1476
- },
1477
- {
1478
- _key: randomKey(),
1479
- _type: "stageEntered",
1480
- stage: toStage,
1481
- fromStage,
1482
- ...shared
1483
- }
1484
- ];
1485
1486
  }
1486
1487
  function startMutation(instance) {
1487
1488
  return {
@@ -1492,7 +1493,7 @@ function startMutation(instance) {
1492
1493
  stages: instance.stages.map((s) => ({
1493
1494
  ...s,
1494
1495
  fields: (s.fields ?? []).map((entry) => ({ ...entry })),
1495
- tasks: s.tasks.map((t) => ({
1496
+ activities: s.activities.map((t) => ({
1496
1497
  ...t,
1497
1498
  ...t.fields !== void 0 ? { fields: t.fields.map((entry) => ({ ...entry })) } : {}
1498
1499
  }))
@@ -1568,344 +1569,97 @@ function currentStageEntry(mutation) {
1568
1569
  );
1569
1570
  return entry;
1570
1571
  }
1571
- function currentTasks(mutation) {
1572
- return currentStageEntry(mutation).tasks;
1572
+ function currentActivities(mutation) {
1573
+ return currentStageEntry(mutation).activities;
1573
1574
  }
1574
- function findTaskInCurrentStage(ctx, taskName) {
1575
- const stage = findStage(ctx.definition, ctx.instance.currentStage), task = (stage.tasks ?? []).find((t) => t.name === taskName);
1576
- if (task === void 0)
1575
+ function findActivityInCurrentStage(ctx, activityName) {
1576
+ const stage = findStage(ctx.definition, ctx.instance.currentStage), activity = (stage.activities ?? []).find((t) => t.name === activityName);
1577
+ if (activity === void 0)
1577
1578
  throw new Error(
1578
- `Task "${taskName}" not found in current stage "${stage.name}" of ${ctx.definition.name}`
1579
+ `Activity "${activityName}" not found in current stage "${stage.name}" of ${ctx.definition.name}`
1579
1580
  );
1580
- return { stage, task };
1581
+ return { stage, activity };
1581
1582
  }
1582
- function requireMutationTaskEntry(mutation, task) {
1583
- const mutEntry = currentTasks(mutation).find((t) => t.name === task);
1583
+ function requireMutationActivityEntry(mutation, activity) {
1584
+ const mutEntry = currentActivities(mutation).find((t) => t.name === activity);
1584
1585
  if (mutEntry === void 0)
1585
- throw new Error(`Task "${task}" disappeared from mutation copy \u2014 invariant broken`);
1586
+ throw new Error(`Activity "${activity}" disappeared from mutation copy \u2014 invariant broken`);
1586
1587
  return mutEntry;
1587
1588
  }
1588
1589
  function findCurrentStageEntry(instance) {
1589
1590
  return findOpenStageEntry(instance);
1590
1591
  }
1591
- function findCurrentTasks(instance) {
1592
- return findCurrentStageEntry(instance)?.tasks ?? [];
1593
- }
1594
- const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
1595
- function validateTag(tag) {
1596
- if (!TAG_RE.test(tag))
1597
- throw new Error(
1598
- `tag: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
1599
- );
1600
- }
1601
- function tagScopeFilter() {
1602
- return "tag == $tag";
1603
- }
1604
- function definitionLookupGroq(explicit) {
1605
- const scoped = `_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}`;
1606
- return explicit ? `*[${scoped} && version == $version][0]` : `*[${scoped}] | order(version desc)[0]`;
1607
- }
1608
- async function discoverItems(args) {
1609
- const { client, groq, params, perspective } = args, fetchOptions = perspective !== void 0 ? { perspective } : void 0;
1610
- return client.fetch(groq, paramsForLake(params), fetchOptions);
1592
+ function findCurrentActivities(instance) {
1593
+ return findCurrentStageEntry(instance)?.activities ?? [];
1611
1594
  }
1612
- function clientForDiscovery(defaultClient, clientForGdr, subjectGdrUri) {
1613
- if (!subjectGdrUri || !subjectGdrUri.includes(":")) return defaultClient;
1595
+ async function deployOrRollback(args) {
1614
1596
  try {
1615
- return clientForGdr(parseGdr(subjectGdrUri));
1616
- } catch {
1617
- return defaultClient;
1597
+ await args.deploy();
1598
+ } catch (guardError) {
1599
+ if (!args.reversible)
1600
+ throw new WorkflowStateDivergedError({
1601
+ instanceId: args.instanceId,
1602
+ guardError,
1603
+ reason: "the move created child instances a parent-only rollback cannot undo"
1604
+ });
1605
+ try {
1606
+ let patch = args.client.patch(args.instanceId).set(args.restore);
1607
+ args.unset && args.unset.length > 0 && (patch = patch.unset(args.unset)), args.committedRev !== void 0 && (patch = patch.ifRevisionId(args.committedRev)), await patch.commit(SYNC_COMMIT);
1608
+ } catch (rollbackError) {
1609
+ throw new WorkflowStateDivergedError({
1610
+ instanceId: args.instanceId,
1611
+ guardError,
1612
+ rollbackError,
1613
+ reason: "rollback of the committed move failed"
1614
+ });
1615
+ }
1616
+ throw guardError instanceof PartialGuardDeployError ? new WorkflowStateDivergedError({
1617
+ instanceId: args.instanceId,
1618
+ guardError,
1619
+ reason: "a multi-guard deploy partially applied; the rolled-back move left orphaned guard locks"
1620
+ }) : guardError;
1618
1621
  }
1619
1622
  }
1620
- async function reload(client, instanceId, tag) {
1621
- const doc = await client.getDocument(instanceId);
1622
- if (!doc)
1623
- throw new Error(`Workflow instance ${instanceId} not found`);
1624
- if (doc.tag !== tag)
1625
- throw new Error(`Workflow instance ${instanceId} not visible to this engine (tag mismatch)`);
1626
- return doc;
1623
+ async function persistThenDeploy(ctx, mutation, deploy) {
1624
+ const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
1625
+ await deployOrRollback({
1626
+ client: ctx.client,
1627
+ instanceId: ctx.instance._id,
1628
+ committedRev: committed._rev,
1629
+ restore: instanceStateFields(ctx.instance),
1630
+ unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
1631
+ reversible: !spawned,
1632
+ deploy
1633
+ });
1627
1634
  }
1628
- function effectsContextEntry(name, value) {
1629
- const _key = randomKey();
1630
- return typeof value == "string" ? { _key, _type: "effectsContext.string", name, value } : typeof value == "number" ? { _key, _type: "effectsContext.number", name, value } : typeof value == "boolean" ? { _key, _type: "effectsContext.boolean", name, value } : { _key, _type: "effectsContext.ref", name, value };
1635
+ async function refreshStageGuards(ctx, mutation, stageName) {
1636
+ await deployStageGuards({
1637
+ client: ctx.client,
1638
+ clientForGdr: ctx.clientForGdr,
1639
+ instance: materializeInstance(ctx.instance, mutation),
1640
+ definition: ctx.definition,
1641
+ stageName,
1642
+ now: ctx.now
1643
+ });
1631
1644
  }
1632
- function effectsContextJsonEntry(name, value) {
1633
- return { _key: randomKey(), _type: "effectsContext.json", name, value: JSON.stringify(value) };
1645
+ async function completeEffect(args) {
1646
+ const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
1647
+ ...options?.clock ? { clock: options.clock } : {},
1648
+ ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
1649
+ });
1650
+ return commitCompleteEffect(
1651
+ ctx,
1652
+ effectKey,
1653
+ status,
1654
+ outputs,
1655
+ detail,
1656
+ error,
1657
+ durationMs,
1658
+ options?.actor
1659
+ );
1634
1660
  }
1635
- function buildInstanceBase(args) {
1636
- const { id, now, actor, perspective } = args;
1637
- return {
1638
- _id: id,
1639
- _type: WORKFLOW_INSTANCE_TYPE,
1640
- _rev: "",
1641
- _createdAt: now,
1642
- _updatedAt: now,
1643
- tag: args.tag,
1644
- workflowResource: args.workflowResource,
1645
- definition: args.definitionName,
1646
- pinnedVersion: args.pinnedVersion,
1647
- definitionSnapshot: JSON.stringify(args.definition),
1648
- fields: args.fields,
1649
- effectsContext: args.effectsContext,
1650
- ancestors: args.ancestors,
1651
- ...perspective !== void 0 ? { perspective } : {},
1652
- currentStage: args.initialStage,
1653
- stages: [],
1654
- pendingEffects: [],
1655
- effectHistory: [],
1656
- history: [
1657
- {
1658
- _key: randomKey(),
1659
- _type: "stageEntered",
1660
- at: now,
1661
- stage: args.initialStage,
1662
- ...actor !== void 0 ? { actor } : {}
1663
- }
1664
- ],
1665
- startedAt: now,
1666
- lastChangedAt: now
1667
- };
1668
- }
1669
- const MAX_SPAWN_DEPTH = 6, SUBWORKFLOWS_ALL_DONE = "count($subworkflows[status != 'done']) == 0", FAIL_WHEN_SYSTEM_ID = "engine.failWhen", COMPLETE_WHEN_SYSTEM_ID = "engine.completeWhen";
1670
- function gateActor(outcome) {
1671
- return {
1672
- kind: "system",
1673
- id: outcome === "failed" ? FAIL_WHEN_SYSTEM_ID : COMPLETE_WHEN_SYSTEM_ID
1674
- };
1675
- }
1676
- function effectiveCompleteWhen(task) {
1677
- return task.completeWhen ?? (task.subworkflows !== void 0 ? SUBWORKFLOWS_ALL_DONE : void 0);
1678
- }
1679
- async function evaluateResolutionGate(ctx, task, vars) {
1680
- const scope = { taskName: task.name, vars };
1681
- if (task.failWhen !== void 0 && await ctxEvaluateCondition(ctx, task.failWhen, scope))
1682
- return "failed";
1683
- const completeWhen = effectiveCompleteWhen(task);
1684
- if (completeWhen !== void 0 && await ctxEvaluateCondition(ctx, completeWhen, scope))
1685
- return "done";
1686
- }
1687
- async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
1688
- if (ctx.instance.ancestors.length + 1 > MAX_SPAWN_DEPTH) {
1689
- const chain = [...ctx.instance.ancestors.map((a) => a.id), selfGdr(ctx.instance)].join(" \u2192 ");
1690
- throw new Error(
1691
- `Subworkflow depth limit (${MAX_SPAWN_DEPTH}) exceeded on task "${task.name}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
1692
- );
1693
- }
1694
- const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tag);
1695
- if (definition === null) {
1696
- const versionLabel = sub.definition.version === void 0 || sub.definition.version === "latest" ? "latest" : `v${sub.definition.version}`;
1697
- throw new Error(
1698
- `Subworkflow definition "${sub.definition.name}" ${versionLabel} not deployed (parent ${ctx.instance._id}, task ${task.name})`
1699
- );
1700
- }
1701
- const parentScope = await ctxConditionParams(ctx, {
1702
- taskName: task.name,
1703
- ...actor ? { actor } : {}
1704
- }), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
1705
- for (const row of rows) {
1706
- const initialFields = await projectRowFields(
1707
- ctx,
1708
- sub,
1709
- definition,
1710
- parentScope,
1711
- row,
1712
- discoveryResource
1713
- ), { ref, body } = await prepareChildInstance({
1714
- client: ctx.client,
1715
- parent: ctx.instance,
1716
- definition,
1717
- initialFields,
1718
- effectsContext,
1719
- actor,
1720
- now
1721
- });
1722
- refs.push(ref), mutation.pendingCreates.push(body);
1723
- }
1724
- return refs;
1725
- }
1726
- async function resolveSpawnRows(ctx, sub) {
1727
- const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
1728
- let value, discoveryResource;
1729
- if (groq.includes("*")) {
1730
- const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
1731
- client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
1732
- client,
1733
- groq,
1734
- params,
1735
- // Draft-aware by default (drafts overlay published) when there's no
1736
- // release, so a `forEach` discovers in-flight items the same way the
1737
- // engine reads any other content.
1738
- perspective: ctx.instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
1739
- });
1740
- } else {
1741
- const tree = groqJs.parse(groq, { params });
1742
- value = await (await groqJs.evaluate(tree, { dataset: [ctx.instance], params, root: ctx.instance })).get();
1743
- }
1744
- return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
1745
- }
1746
- async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
1747
- const out = [];
1748
- for (const [name, groq] of Object.entries(sub.with ?? {})) {
1749
- const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
1750
- if (declared === void 0)
1751
- throw new Error(
1752
- `subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
1753
- );
1754
- const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
1755
- parentResource: ctx.instance.workflowResource,
1756
- discoveryResource,
1757
- entryName: name,
1758
- childName: childDefinition.name
1759
- });
1760
- value != null && out.push({
1761
- type: declared.type,
1762
- name,
1763
- value
1764
- });
1765
- }
1766
- return out;
1767
- }
1768
- async function resolveContextHandoff(ctx, sub, parentScope) {
1769
- const out = {};
1770
- for (const [name, groq] of Object.entries(sub.context ?? {})) {
1771
- const v2 = await runGroq(groq, parentScope, ctx.snapshot);
1772
- v2 != null && (typeof v2 == "string" || typeof v2 == "number" || typeof v2 == "boolean" || typeof v2 == "object" && "id" in v2 && "type" in v2) && (out[name] = v2);
1773
- }
1774
- return out;
1775
- }
1776
- function canonicaliseSpawnValue(kind, value, cx) {
1777
- return kind === "doc.ref" ? coerceSpawnGdr(value, cx) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, cx)).filter((v2) => v2 !== null) : value;
1778
- }
1779
- function assertBareIdRootsCorrectly(id, cx) {
1780
- if (!(cx.discoveryResource === void 0 || sameResource(cx.discoveryResource, cx.parentResource)))
1781
- throw new Error(
1782
- `subworkflows.with["${cx.entryName}"]: subworkflow "${cx.childName}" projected the bare id "${id}", which roots at the parent resource "${cx.parentResource.id}", but spawn discovery ran against "${cx.discoveryResource.id}". The child would reference a document that doesn't exist in the parent resource. Project an explicit GDR URI in the with-GROQ (e.g. "dataset:<projectId>:<dataset>:" + _id) so the child's subject points at the resource the discovered rows came from.`
1783
- );
1784
- }
1785
- function coerceSpawnGdr(raw, cx) {
1786
- const toUri = (id) => isGdrUri(id) ? id : (assertBareIdRootsCorrectly(id, cx), gdrFromResource(cx.parentResource, id));
1787
- if (raw == null) return null;
1788
- if (typeof raw == "object" && "id" in raw && "type" in raw) {
1789
- const r = raw;
1790
- if (typeof r.id == "string" && typeof r.type == "string")
1791
- return { id: toUri(r.id), type: r.type };
1792
- }
1793
- if (typeof raw == "object" && "_ref" in raw) {
1794
- const r = raw;
1795
- if (typeof r._ref == "string") return { id: toUri(r._ref), type: "document" };
1796
- }
1797
- return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
1798
- }
1799
- async function resolveDefinitionRef(client, ref, tag) {
1800
- const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
1801
- return wantsExplicit && (params.version = ref.version), await client.fetch(
1802
- definitionLookupGroq(wantsExplicit),
1803
- params
1804
- ) ?? null;
1805
- }
1806
- async function prepareChildInstance(args) {
1807
- const { client, parent, definition, initialFields, effectsContext, actor, now } = args, childTag = parent.tag, workflowResource = parent.workflowResource, childDocId = `${childTag}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type: WORKFLOW_INSTANCE_TYPE }, ancestors = [
1808
- ...parent.ancestors,
1809
- {
1810
- id: selfGdr(parent),
1811
- type: WORKFLOW_INSTANCE_TYPE
1812
- }
1813
- ], inheritedPerspective = parent.perspective, childFields = await resolveDeclaredFields({
1814
- entryDefs: definition.fields,
1815
- initialFields,
1816
- ctx: {
1817
- client,
1818
- now,
1819
- selfId: childDocId,
1820
- tag: childTag,
1821
- workflowResource,
1822
- definitionName: definition.name,
1823
- ...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
1824
- },
1825
- randomKey
1826
- }), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
1827
- ([key, value]) => {
1828
- if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
1829
- if (!isGdrUri(value.id))
1830
- throw new Error(
1831
- `subworkflows.context["${key}"]: GDR id must be a "<scheme>:..." URI, got ${JSON.stringify(value.id)}.`
1832
- );
1833
- return effectsContextEntry(key, { id: value.id, type: value.type });
1834
- }
1835
- return effectsContextEntry(key, value);
1836
- }
1837
- ), base = buildInstanceBase({
1838
- id: childDocId,
1839
- now,
1840
- tag: childTag,
1841
- workflowResource,
1842
- definitionName: definition.name,
1843
- pinnedVersion: definition.version,
1844
- definition,
1845
- fields: childFields,
1846
- effectsContext: effectsContextEntries,
1847
- ancestors,
1848
- perspective: childPerspective,
1849
- initialStage: definition.initialStage,
1850
- actor
1851
- });
1852
- return { ref: childRef, body: base };
1853
- }
1854
- async function subworkflowRows(ctx, refs) {
1855
- if (refs.length === 0) return [];
1856
- const ids = refs.map((r) => gdrToBareId(r.id));
1857
- return (await ctx.client.fetch("*[_id in $ids]{_id, currentStage, completedAt}", { ids })).map((c) => ({
1858
- _id: c._id,
1859
- stage: c.currentStage,
1860
- status: c.completedAt !== void 0 && c.completedAt !== null ? "done" : "active"
1861
- }));
1862
- }
1863
- async function resolveBindings(args) {
1864
- const resolved = {};
1865
- for (const [key, groq] of Object.entries(args.bindings ?? {}))
1866
- resolved[key] = await runGroq(groq, args.params, args.snapshot);
1867
- return { ...resolved, ...args.staticInput };
1868
- }
1869
- async function persistThenDeploy(ctx, mutation, deploy) {
1870
- const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
1871
- await deployOrRollback({
1872
- client: ctx.client,
1873
- instanceId: ctx.instance._id,
1874
- committedRev: committed._rev,
1875
- restore: instanceStateFields(ctx.instance),
1876
- unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
1877
- reversible: !spawned,
1878
- deploy
1879
- });
1880
- }
1881
- async function refreshStageGuards(ctx, mutation, stageName) {
1882
- await deployStageGuards({
1883
- client: ctx.client,
1884
- clientForGdr: ctx.clientForGdr,
1885
- instance: materializeInstance(ctx.instance, mutation),
1886
- definition: ctx.definition,
1887
- stageName,
1888
- now: ctx.now
1889
- });
1890
- }
1891
- async function completeEffect(args) {
1892
- const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
1893
- ...options?.clock ? { clock: options.clock } : {},
1894
- ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
1895
- });
1896
- return commitCompleteEffect(
1897
- ctx,
1898
- effectKey,
1899
- status,
1900
- outputs,
1901
- detail,
1902
- error,
1903
- durationMs,
1904
- options?.actor
1905
- );
1906
- }
1907
- function buildEffectHistoryEntry(pending, outcome) {
1908
- const { status, ranAt, actor, detail, error, durationMs, outputs } = outcome, resolvedActor = actor ?? pending.actor;
1661
+ function buildEffectHistoryEntry(pending, outcome) {
1662
+ const { status, ranAt, actor, detail, error, durationMs, outputs } = outcome, resolvedActor = actor ?? pending.actor;
1909
1663
  return {
1910
1664
  _key: pending._key,
1911
1665
  name: pending.name,
@@ -1978,7 +1732,7 @@ function buildQueuedEffect(effect, origin, params, actor, now) {
1978
1732
  async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
1979
1733
  if (!effects || effects.length === 0) return;
1980
1734
  const now = ctx.now, liveCtx = { ...ctx, instance: materializeInstance(ctx.instance, mutation) }, params = await ctxConditionParams(liveCtx, {
1981
- ...opts?.taskName !== void 0 ? { taskName: opts.taskName } : {},
1735
+ ...opts?.activityName !== void 0 ? { activityName: opts.activityName } : {},
1982
1736
  ...actor !== void 0 ? { actor } : {},
1983
1737
  // Caller-supplied action params, readable in bindings as `$params.<name>`.
1984
1738
  vars: { params: opts?.callerParams ?? {} }
@@ -1993,18 +1747,74 @@ async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
1993
1747
  mutation.pendingEffects.push(pending), mutation.history.push(history);
1994
1748
  }
1995
1749
  }
1996
- function resolveStaticSource(src, ctx) {
1750
+ function fieldQueryDiscardedEntry(args) {
1751
+ return {
1752
+ _key: randomKey(),
1753
+ _type: "fieldQueryDiscarded",
1754
+ at: args.at,
1755
+ scope: args.scope,
1756
+ field: args.field,
1757
+ detail: args.detail
1758
+ };
1759
+ }
1760
+ function recordFieldDiscards(target, scope, at) {
1761
+ return (discard) => target.push(fieldQueryDiscardedEntry({ scope, field: discard.field, detail: discard.detail, at }));
1762
+ }
1763
+ function applyActivityStatusChange(args) {
1764
+ const { entry, history, stage, to, at, actor } = args, from = entry.status;
1765
+ entry.status = to, schema.isTerminalActivityStatus(to) && (entry.completedAt = at, actor !== void 0 && (entry.completedBy = actor.id)), history.push({
1766
+ _key: randomKey(),
1767
+ _type: "activityStatusChanged",
1768
+ at,
1769
+ stage,
1770
+ activity: entry.name,
1771
+ from,
1772
+ to,
1773
+ ...actor !== void 0 ? { actor } : {}
1774
+ });
1775
+ }
1776
+ function stageTransitionHistory(args) {
1777
+ const { fromStage, toStage, at, transition, actor, via, reason } = args, shared = {
1778
+ at,
1779
+ ...transition !== void 0 ? { transition } : {},
1780
+ ...actor !== void 0 ? { actor } : {},
1781
+ ...via !== void 0 ? { via } : {},
1782
+ ...reason !== void 0 ? { reason } : {}
1783
+ };
1784
+ return [
1785
+ {
1786
+ _key: randomKey(),
1787
+ _type: "stageExited",
1788
+ stage: fromStage,
1789
+ toStage,
1790
+ ...shared
1791
+ },
1792
+ {
1793
+ _key: randomKey(),
1794
+ _type: "transitionFired",
1795
+ fromStage,
1796
+ toStage,
1797
+ ...shared
1798
+ },
1799
+ {
1800
+ _key: randomKey(),
1801
+ _type: "stageEntered",
1802
+ stage: toStage,
1803
+ fromStage,
1804
+ ...shared
1805
+ }
1806
+ ];
1807
+ }
1808
+ function resolveStaticValueExpr(src, ctx) {
1997
1809
  switch (src.type) {
1998
1810
  case "literal":
1999
- return { handled: !0, value: src.value };
1811
+ return src.value;
2000
1812
  case "param":
2001
- return { handled: !0, value: ctx.params?.[src.param] };
1813
+ return ctx.params?.[src.param];
2002
1814
  case "actor":
2003
- return { handled: !0, value: ctx.actor };
1815
+ return ctx.actor;
2004
1816
  case "now":
2005
- return { handled: !0, value: ctx.now };
2006
- default:
2007
- return { handled: !1 };
1817
+ return ctx.now;
2008
1818
  }
2009
1819
  }
2010
1820
  const FIELD_OP_TYPES = [
@@ -2017,7 +1827,7 @@ const FIELD_OP_TYPES = [
2017
1827
  function isFieldOp(summary) {
2018
1828
  return FIELD_OP_TYPES.includes(summary.opType);
2019
1829
  }
2020
- function validateActionParams(action, taskName, callerParams) {
1830
+ function validateActionParams(action, activityName, callerParams) {
2021
1831
  const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
2022
1832
  for (const decl of declared) {
2023
1833
  const value = params[decl.name], present = decl.name in params && value !== void 0 && value !== null;
@@ -2031,7 +1841,7 @@ function validateActionParams(action, taskName, callerParams) {
2031
1841
  }));
2032
1842
  }
2033
1843
  if (issues.length > 0)
2034
- throw new ActionParamsInvalidError({ action: action.name, task: taskName, issues });
1844
+ throw new ActionParamsInvalidError({ action: action.name, activity: activityName, issues });
2035
1845
  return params;
2036
1846
  }
2037
1847
  function hasStringField(value, field) {
@@ -2064,7 +1874,15 @@ function runOps(args) {
2064
1874
  if (ops === void 0 || ops.length === 0) return [];
2065
1875
  const summaries = [];
2066
1876
  for (const op of ops) {
2067
- const summary = applyOp(op, { mutation, stage, taskName: origin.task, params, actor, self, now });
1877
+ const summary = applyOp(op, {
1878
+ mutation,
1879
+ stage,
1880
+ activityName: origin.activity,
1881
+ params,
1882
+ actor,
1883
+ self,
1884
+ now
1885
+ });
2068
1886
  summaries.push(summary), mutation.history.push(opAppliedEntry({ origin, summary, stage, actor, now }));
2069
1887
  }
2070
1888
  return summaries;
@@ -2076,7 +1894,7 @@ function opAppliedEntry(args) {
2076
1894
  _type: "opApplied",
2077
1895
  at: now,
2078
1896
  stage,
2079
- ...origin.task !== void 0 ? { task: origin.task } : {},
1897
+ ...origin.activity !== void 0 ? { activity: origin.activity } : {},
2080
1898
  ...origin.action !== void 0 ? { action: origin.action } : {},
2081
1899
  ...origin.transition !== void 0 ? { transition: origin.transition } : {},
2082
1900
  ...origin.edit === !0 ? { edit: !0 } : {},
@@ -2103,13 +1921,15 @@ function applyOp(op, ctx) {
2103
1921
  }
2104
1922
  }
2105
1923
  function applyFieldSet(op, ctx) {
2106
- const value = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
2107
- return validateFieldValue({ entryType: entry._type, entryName: entry.name, value }), setEntryValue(entry, value), { opType: op.type, target: op.target, resolved: { value } };
1924
+ const value = resolveOpValue(op.value, ctx), entry = locateEntry(ctx, op.target);
1925
+ return validateFieldValue({ entryType: entry._type, entryName: entry.name, value, ...entryShape(entry) }), setEntryValue(entry, value), { opType: op.type, target: op.target, resolved: { value } };
1926
+ }
1927
+ function entryShape(entry) {
1928
+ return entry._type === "object" ? { fields: entry.fields } : entry._type === "array" ? { of: entry.of } : {};
2108
1929
  }
2109
1930
  const EMPTY_BY_KIND = {
2110
1931
  "doc.refs": [],
2111
- checklist: [],
2112
- notes: [],
1932
+ array: [],
2113
1933
  assignees: []
2114
1934
  };
2115
1935
  function applyFieldUnset(op, ctx) {
@@ -2117,8 +1937,13 @@ function applyFieldUnset(op, ctx) {
2117
1937
  return setEntryValue(entry, EMPTY_BY_KIND[entry._type] ?? null), { opType: op.type, target: op.target };
2118
1938
  }
2119
1939
  function applyFieldAppend(op, ctx) {
2120
- const item = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
2121
- validateFieldAppendItem({ entryType: entry._type, entryName: entry.name, item });
1940
+ const item = resolveOpValue(op.value, ctx), entry = locateEntry(ctx, op.target);
1941
+ validateFieldAppendItem({
1942
+ entryType: entry._type,
1943
+ entryName: entry.name,
1944
+ item,
1945
+ of: entry._type === "array" ? entry.of : void 0
1946
+ });
2122
1947
  const current = Array.isArray(entry.value) ? entry.value : [];
2123
1948
  return setEntryValue(entry, [...current, withRowKey(item)]), { opType: op.type, target: op.target, resolved: { item } };
2124
1949
  }
@@ -2126,7 +1951,7 @@ function withRowKey(item) {
2126
1951
  return typeof item == "object" && item !== null && !Array.isArray(item) && !("_key" in item) ? { _key: randomKey(), ...item } : item;
2127
1952
  }
2128
1953
  function applyFieldUpdateWhere(op, ctx) {
2129
- const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op), merge = resolveOpSource(op.value, ctx);
1954
+ const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op), merge = resolveOpValue(op.value, ctx);
2130
1955
  if (merge === null || typeof merge != "object" || Array.isArray(merge))
2131
1956
  throw new Error(
2132
1957
  `field.updateWhere value must resolve to an object of fields to merge (target "${op.target.field}")`
@@ -2152,145 +1977,385 @@ function requireArrayValue(entry, op) {
2152
1977
  );
2153
1978
  return entry.value;
2154
1979
  }
2155
- function applyStatusSet(op, ctx) {
2156
- const entry = findOpenStageEntry(ctx.mutation)?.tasks.find((t) => t.name === op.task);
2157
- if (entry === void 0)
2158
- throw new Error(`status.set targets task "${op.task}" which has no entry in the open stage`);
2159
- return applyTaskStatusChange({
2160
- entry,
2161
- history: ctx.mutation.history,
2162
- stage: ctx.stage,
2163
- to: op.status,
2164
- at: ctx.now,
2165
- ...ctx.actor !== void 0 ? { actor: ctx.actor } : {}
2166
- }), { opType: op.type, resolved: { task: op.task, status: op.status } };
1980
+ function applyStatusSet(op, ctx) {
1981
+ const entry = findOpenStageEntry(ctx.mutation)?.activities.find((t) => t.name === op.activity);
1982
+ if (entry === void 0)
1983
+ throw new Error(
1984
+ `status.set targets activity "${op.activity}" which has no entry in the open stage`
1985
+ );
1986
+ return applyActivityStatusChange({
1987
+ entry,
1988
+ history: ctx.mutation.history,
1989
+ stage: ctx.stage,
1990
+ to: op.status,
1991
+ at: ctx.now,
1992
+ ...ctx.actor !== void 0 ? { actor: ctx.actor } : {}
1993
+ }), { opType: op.type, resolved: { activity: op.activity, status: op.status } };
1994
+ }
1995
+ function setEntryValue(entry, value) {
1996
+ entry.value = value;
1997
+ }
1998
+ function locateEntry(ctx, target) {
1999
+ const entry = fieldHost(ctx, target.scope)?.find((s) => s.name === target.field);
2000
+ if (entry === void 0)
2001
+ throw new Error(
2002
+ `Op target ${target.scope}:"${target.field}" has no resolved field entry on this instance \u2014 the deployed definition and the instance state have diverged`
2003
+ );
2004
+ return entry;
2005
+ }
2006
+ function fieldHost(ctx, scope) {
2007
+ if (scope === "workflow") return ctx.mutation.fields;
2008
+ const stageEntry = findOpenStageEntry(ctx.mutation);
2009
+ if (scope === "stage") return stageEntry?.fields;
2010
+ const activity = stageEntry?.activities.find((t) => t.name === ctx.activityName);
2011
+ return activity !== void 0 && activity.fields === void 0 && (activity.fields = []), activity?.fields;
2012
+ }
2013
+ function resolveOpValue(src, ctx) {
2014
+ switch (src.type) {
2015
+ case "literal":
2016
+ case "param":
2017
+ case "actor":
2018
+ case "now":
2019
+ return resolveStaticValueExpr(src, ctx);
2020
+ case "self":
2021
+ return ctx.self;
2022
+ case "stage":
2023
+ return ctx.stage;
2024
+ case "fieldRead": {
2025
+ const value = readEntryFromMutation(ctx, src);
2026
+ return src.path !== void 0 ? getPath(value, src.path) : value;
2027
+ }
2028
+ case "object": {
2029
+ const out = {};
2030
+ for (const [field, fieldSrc] of Object.entries(src.fields))
2031
+ out[field] = resolveOpValue(fieldSrc, ctx);
2032
+ return out;
2033
+ }
2034
+ }
2035
+ }
2036
+ function entryValue(entries, name) {
2037
+ return entries?.find((s) => s.name === name)?.value;
2038
+ }
2039
+ function readEntryFromMutation(ctx, src) {
2040
+ const scopes = src.scope !== void 0 ? [src.scope] : ["stage", "workflow"], stageEntry = findOpenStageEntry(ctx.mutation);
2041
+ for (const scope of scopes) {
2042
+ const host = scope === "workflow" ? ctx.mutation.fields : stageEntry?.fields, value = entryValue(host, src.field);
2043
+ if (value !== void 0) return value;
2044
+ }
2045
+ }
2046
+ function evalOpPredicate(p, row, ctx) {
2047
+ switch (p.type) {
2048
+ case "all":
2049
+ return p.of.every((sub) => evalOpPredicate(sub, row, ctx));
2050
+ case "any":
2051
+ return p.of.some((sub) => evalOpPredicate(sub, row, ctx));
2052
+ case "field":
2053
+ return row[p.field] === resolveOpValue(p.equals, ctx);
2054
+ }
2055
+ }
2056
+ const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
2057
+ function validateTag(tag) {
2058
+ if (!TAG_RE.test(tag))
2059
+ throw new Error(
2060
+ `tag: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
2061
+ );
2062
+ }
2063
+ function tagScopeFilter() {
2064
+ return "tag == $tag";
2065
+ }
2066
+ function definitionLookupGroq(explicit) {
2067
+ const scoped = `_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}`;
2068
+ return explicit ? `*[${scoped} && version == $version][0]` : `*[${scoped}] | order(version desc)[0]`;
2069
+ }
2070
+ async function discoverItems(args) {
2071
+ const { client, groq, params, perspective } = args, fetchOptions = perspective !== void 0 ? { perspective } : void 0;
2072
+ return client.fetch(groq, paramsForLake(params), fetchOptions);
2073
+ }
2074
+ function clientForDiscovery(defaultClient, clientForGdr, subjectGdrUri) {
2075
+ if (!subjectGdrUri || !subjectGdrUri.includes(":")) return defaultClient;
2076
+ try {
2077
+ return clientForGdr(parseGdr(subjectGdrUri));
2078
+ } catch {
2079
+ return defaultClient;
2080
+ }
2081
+ }
2082
+ const ENGINE_ACTOR_ID_PREFIX = "engine.";
2083
+ function engineSystemActor(name) {
2084
+ return { kind: "system", id: `${ENGINE_ACTOR_ID_PREFIX}${name}` };
2085
+ }
2086
+ function isEngineActor(actor) {
2087
+ return actor.kind === "system" && actor.id.startsWith(ENGINE_ACTOR_ID_PREFIX);
2088
+ }
2089
+ const MAX_SPAWN_DEPTH = 6, SUBWORKFLOWS_ALL_DONE = "count($subworkflows[status != 'done']) == 0";
2090
+ function gateActor(outcome) {
2091
+ return engineSystemActor(outcome === "failed" ? "failWhen" : "completeWhen");
2092
+ }
2093
+ function effectiveCompleteWhen(activity) {
2094
+ return activity.completeWhen ?? (activity.subworkflows !== void 0 ? SUBWORKFLOWS_ALL_DONE : void 0);
2095
+ }
2096
+ async function evaluateResolutionGate(ctx, activity, vars) {
2097
+ const scope = { activityName: activity.name, vars };
2098
+ if (activity.failWhen !== void 0 && await ctxEvaluateCondition(ctx, activity.failWhen, scope))
2099
+ return "failed";
2100
+ const completeWhen = effectiveCompleteWhen(activity);
2101
+ if (completeWhen !== void 0 && await ctxEvaluateCondition(ctx, completeWhen, scope))
2102
+ return "done";
2103
+ }
2104
+ async function spawnSubworkflows(ctx, mutation, activity, sub, actor) {
2105
+ if (ctx.instance.ancestors.length + 1 > MAX_SPAWN_DEPTH) {
2106
+ const chain = [...ctx.instance.ancestors.map((a) => a.id), selfGdr(ctx.instance)].join(" \u2192 ");
2107
+ throw new Error(
2108
+ `Subworkflow depth limit (${MAX_SPAWN_DEPTH}) exceeded on activity "${activity.name}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
2109
+ );
2110
+ }
2111
+ const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tag);
2112
+ if (definition === null) {
2113
+ const versionLabel = sub.definition.version === void 0 || sub.definition.version === "latest" ? "latest" : `v${sub.definition.version}`;
2114
+ throw new Error(
2115
+ `Subworkflow definition "${sub.definition.name}" ${versionLabel} not deployed (parent ${ctx.instance._id}, activity ${activity.name})`
2116
+ );
2117
+ }
2118
+ const parentScope = await ctxConditionParams(ctx, {
2119
+ activityName: activity.name,
2120
+ ...actor ? { actor } : {}
2121
+ }), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
2122
+ for (const row of rows) {
2123
+ const initialFields = await projectRowFields(
2124
+ ctx,
2125
+ sub,
2126
+ definition,
2127
+ parentScope,
2128
+ row,
2129
+ discoveryResource
2130
+ ), { ref, body } = await prepareChildInstance({
2131
+ client: ctx.client,
2132
+ parent: ctx.instance,
2133
+ definition,
2134
+ initialFields,
2135
+ effectsContext,
2136
+ actor,
2137
+ now
2138
+ });
2139
+ refs.push(ref), mutation.pendingCreates.push(body);
2140
+ }
2141
+ return refs;
2142
+ }
2143
+ async function resolveSpawnRows(ctx, sub) {
2144
+ const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
2145
+ let value, discoveryResource;
2146
+ if (groq.includes("*")) {
2147
+ const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
2148
+ client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
2149
+ client,
2150
+ groq,
2151
+ params,
2152
+ // Draft-aware by default (drafts overlay published) when there's no
2153
+ // release, so a `forEach` discovers in-flight items the same way the
2154
+ // engine reads any other content.
2155
+ perspective: ctx.instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
2156
+ });
2157
+ } else {
2158
+ const tree = groqJs.parse(groq, { params });
2159
+ value = await (await groqJs.evaluate(tree, { dataset: [ctx.instance], params, root: ctx.instance })).get();
2160
+ }
2161
+ return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
2162
+ }
2163
+ async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
2164
+ const out = [];
2165
+ for (const [name, groq] of Object.entries(sub.with ?? {})) {
2166
+ const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
2167
+ if (declared === void 0)
2168
+ throw new Error(
2169
+ `subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
2170
+ );
2171
+ const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
2172
+ parentResource: ctx.instance.workflowResource,
2173
+ discoveryResource,
2174
+ entryName: name,
2175
+ childName: childDefinition.name
2176
+ });
2177
+ value != null && out.push({
2178
+ type: declared.type,
2179
+ name,
2180
+ value
2181
+ });
2182
+ }
2183
+ return out;
2184
+ }
2185
+ async function resolveContextHandoff(ctx, sub, parentScope) {
2186
+ const out = {};
2187
+ for (const [name, groq] of Object.entries(sub.context ?? {})) {
2188
+ const v2 = await runGroq(groq, parentScope, ctx.snapshot);
2189
+ v2 != null && (typeof v2 == "string" || typeof v2 == "number" || typeof v2 == "boolean" || typeof v2 == "object" && "id" in v2 && "type" in v2) && (out[name] = v2);
2190
+ }
2191
+ return out;
2167
2192
  }
2168
- function setEntryValue(entry, value) {
2169
- entry.value = value;
2193
+ function canonicaliseSpawnValue(kind, value, cx) {
2194
+ return kind === "doc.ref" ? coerceSpawnGdr(value, cx) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, cx)).filter((v2) => v2 !== null) : value;
2170
2195
  }
2171
- function locateEntry(ctx, target) {
2172
- const entry = fieldHost(ctx, target.scope)?.find((s) => s.name === target.field);
2173
- if (entry === void 0)
2196
+ function assertBareIdRootsCorrectly(id, cx) {
2197
+ if (!(cx.discoveryResource === void 0 || sameResource(cx.discoveryResource, cx.parentResource)))
2174
2198
  throw new Error(
2175
- `Op target ${target.scope}:"${target.field}" has no resolved field entry on this instance \u2014 the deployed definition and the instance state have diverged`
2199
+ `subworkflows.with["${cx.entryName}"]: subworkflow "${cx.childName}" projected the bare id "${id}", which roots at the parent resource "${cx.parentResource.id}", but spawn discovery ran against "${cx.discoveryResource.id}". The child would reference a document that doesn't exist in the parent resource. Project an explicit GDR URI in the with-GROQ (e.g. "dataset:<projectId>:<dataset>:" + _id) so the child's subject points at the resource the discovered rows came from.`
2176
2200
  );
2177
- return entry;
2178
2201
  }
2179
- function fieldHost(ctx, scope) {
2180
- if (scope === "workflow") return ctx.mutation.fields;
2181
- const stageEntry = findOpenStageEntry(ctx.mutation);
2182
- if (scope === "stage") return stageEntry?.fields;
2183
- const task = stageEntry?.tasks.find((t) => t.name === ctx.taskName);
2184
- return task !== void 0 && task.fields === void 0 && (task.fields = []), task?.fields;
2185
- }
2186
- function resolveOpSource(src, ctx) {
2187
- const staticValue = resolveStaticSource(src, ctx);
2188
- if (staticValue.handled) return staticValue.value;
2189
- switch (src.type) {
2190
- case "self":
2191
- return ctx.self;
2192
- case "stage":
2193
- return ctx.stage;
2194
- case "fieldRead": {
2195
- const value = readEntryFromMutation(ctx, src);
2196
- return src.path !== void 0 ? getPath(value, src.path) : value;
2197
- }
2198
- case "object": {
2199
- const out = {};
2200
- for (const [field, fieldSrc] of Object.entries(src.fields))
2201
- out[field] = resolveOpSource(fieldSrc, ctx);
2202
- return out;
2203
- }
2204
- default:
2205
- throw new Error(`Source type "${src.type}" is a field-entry origin, not a value`);
2202
+ function coerceSpawnGdr(raw, cx) {
2203
+ const toUri = (id) => isGdrUri(id) ? id : (assertBareIdRootsCorrectly(id, cx), gdrFromResource(cx.parentResource, id));
2204
+ if (raw == null) return null;
2205
+ if (typeof raw == "object" && "id" in raw && "type" in raw) {
2206
+ const r = raw;
2207
+ if (typeof r.id == "string" && typeof r.type == "string")
2208
+ return { id: toUri(r.id), type: r.type };
2209
+ }
2210
+ if (typeof raw == "object" && "_ref" in raw) {
2211
+ const r = raw;
2212
+ if (typeof r._ref == "string") return { id: toUri(r._ref), type: "document" };
2206
2213
  }
2214
+ return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
2207
2215
  }
2208
- function entryValue(entries, name) {
2209
- return entries?.find((s) => s.name === name)?.value;
2216
+ async function resolveDefinitionRef(client, ref, tag) {
2217
+ const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
2218
+ return wantsExplicit && (params.version = ref.version), await client.fetch(
2219
+ definitionLookupGroq(wantsExplicit),
2220
+ params
2221
+ ) ?? null;
2210
2222
  }
2211
- function readEntryFromMutation(ctx, src) {
2212
- const scopes = src.scope !== void 0 ? [src.scope] : ["stage", "workflow"], stageEntry = findOpenStageEntry(ctx.mutation);
2213
- for (const scope of scopes) {
2214
- const host = scope === "workflow" ? ctx.mutation.fields : stageEntry?.fields, value = entryValue(host, src.field);
2215
- if (value !== void 0) return value;
2216
- }
2223
+ async function prepareChildInstance(args) {
2224
+ const { client, parent, definition, initialFields, effectsContext, actor, now } = args, childTag = parent.tag, workflowResource = parent.workflowResource, childDocId = `${childTag}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type: WORKFLOW_INSTANCE_TYPE }, ancestors = [
2225
+ ...parent.ancestors,
2226
+ {
2227
+ id: selfGdr(parent),
2228
+ type: WORKFLOW_INSTANCE_TYPE
2229
+ }
2230
+ ], inheritedPerspective = parent.perspective, fieldDiscards = [], childFields = await resolveDeclaredFields({
2231
+ entryDefs: definition.fields,
2232
+ initialFields,
2233
+ ctx: {
2234
+ client,
2235
+ now,
2236
+ selfId: childDocId,
2237
+ tag: childTag,
2238
+ workflowResource,
2239
+ definitionName: definition.name,
2240
+ ...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {},
2241
+ recordDiscard: recordFieldDiscards(fieldDiscards, "workflow", now)
2242
+ },
2243
+ randomKey
2244
+ }), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
2245
+ ([key, value]) => {
2246
+ if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
2247
+ if (!isGdrUri(value.id))
2248
+ throw new Error(
2249
+ `subworkflows.context["${key}"]: GDR id must be a "<scheme>:..." URI, got ${JSON.stringify(value.id)}.`
2250
+ );
2251
+ return effectsContextEntry(key, { id: value.id, type: value.type });
2252
+ }
2253
+ return effectsContextEntry(key, value);
2254
+ }
2255
+ ), base = buildInstanceBase({
2256
+ id: childDocId,
2257
+ now,
2258
+ tag: childTag,
2259
+ workflowResource,
2260
+ definitionName: definition.name,
2261
+ pinnedVersion: definition.version,
2262
+ pinnedContentHash: definition.contentHash,
2263
+ definition,
2264
+ fields: childFields,
2265
+ effectsContext: effectsContextEntries,
2266
+ ancestors,
2267
+ perspective: childPerspective,
2268
+ initialStage: definition.initialStage,
2269
+ actor,
2270
+ ...fieldDiscards.length > 0 ? { extraHistory: fieldDiscards } : {}
2271
+ });
2272
+ return { ref: childRef, body: base };
2217
2273
  }
2218
- function evalOpPredicate(p, row, ctx) {
2219
- switch (p.type) {
2220
- case "all":
2221
- return p.of.every((sub) => evalOpPredicate(sub, row, ctx));
2222
- case "any":
2223
- return p.of.some((sub) => evalOpPredicate(sub, row, ctx));
2224
- case "field":
2225
- return row[p.field] === resolveOpSource(p.equals, ctx);
2226
- }
2274
+ async function subworkflowRows(ctx, refs) {
2275
+ if (refs.length === 0) return [];
2276
+ const ids = refs.map((r) => gdrToBareId(r.id));
2277
+ return (await ctx.client.fetch("*[_id in $ids]{_id, currentStage, completedAt}", { ids })).map((c) => ({
2278
+ _id: c._id,
2279
+ stage: c.currentStage,
2280
+ status: c.completedAt !== void 0 && c.completedAt !== null ? "done" : "active"
2281
+ }));
2227
2282
  }
2228
- async function invokeTask(args) {
2229
- const { client, instanceId, task: taskName, options } = args, ctx = await loadContext(client, instanceId, {
2283
+ async function invokeActivity(args) {
2284
+ const { client, instanceId, activity: activityName, options } = args, ctx = await loadContext(client, instanceId, {
2230
2285
  ...options?.clock ? { clock: options.clock } : {},
2231
2286
  ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
2232
2287
  });
2233
- return commitInvoke(ctx, taskName, options?.actor);
2288
+ return commitInvoke(ctx, activityName, options?.actor);
2234
2289
  }
2235
- async function commitInvoke(ctx, taskName, actor) {
2236
- const { task } = findTaskInCurrentStage(ctx, taskName), entry = findCurrentTasks(ctx.instance).find((t) => t.name === taskName);
2290
+ async function commitInvoke(ctx, activityName, actor) {
2291
+ const { activity } = findActivityInCurrentStage(ctx, activityName), entry = findCurrentActivities(ctx.instance).find((t) => t.name === activityName);
2237
2292
  if (entry === void 0)
2238
- throw new Error(`Task "${taskName}" has no status entry on instance ${ctx.instance._id}`);
2293
+ throw new Error(
2294
+ `Activity "${activityName}" has no status entry on instance ${ctx.instance._id}`
2295
+ );
2239
2296
  if (entry.status !== "pending")
2240
- return { invoked: !1, task: taskName };
2241
- const mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation, taskName);
2242
- return await activateTask(ctx, mutation, task, mutEntry, actor), await persist(ctx, mutation), { invoked: !0, task: taskName };
2297
+ return { invoked: !1, activity: activityName };
2298
+ const mutation = startMutation(ctx.instance), mutEntry = requireMutationActivityEntry(mutation, activityName);
2299
+ return await activateActivity(ctx, mutation, activity, mutEntry, actor), await persist(ctx, mutation), { invoked: !0, activity: activityName };
2243
2300
  }
2244
- async function activateTask(ctx, mutation, task, entry, actor) {
2301
+ async function activateActivity(ctx, mutation, activity, entry, actor) {
2245
2302
  const now = ctx.now;
2246
- if (entry.status = "active", entry.startedAt = now, task.fields !== void 0 && task.fields.length > 0) {
2303
+ if (entry.status = "active", entry.startedAt = now, activity.fields !== void 0 && activity.fields.length > 0) {
2247
2304
  const stage = findStage(ctx.definition, mutation.currentStage);
2248
- entry.fields = await resolveTaskFieldEntries({
2305
+ entry.fields = await resolveActivityFieldEntries({
2249
2306
  client: ctx.client,
2250
2307
  instance: ctx.instance,
2251
2308
  stage,
2252
- task,
2253
- now
2309
+ activity,
2310
+ now,
2311
+ recordDiscard: recordFieldDiscards(mutation.history, "activity", now)
2254
2312
  });
2255
2313
  }
2256
2314
  runOps({
2257
- ops: task.ops,
2315
+ ops: activity.ops,
2258
2316
  mutation,
2259
2317
  stage: mutation.currentStage,
2260
- origin: { task: task.name },
2318
+ origin: { activity: activity.name },
2261
2319
  params: {},
2262
2320
  actor,
2263
2321
  self: selfGdr(ctx.instance),
2264
2322
  now
2265
2323
  }), mutation.history.push({
2266
2324
  _key: randomKey(),
2267
- _type: "taskActivated",
2325
+ _type: "activityActivated",
2268
2326
  at: now,
2269
2327
  stage: mutation.currentStage,
2270
- task: task.name,
2328
+ activity: activity.name,
2271
2329
  ...actor !== void 0 ? { actor } : {}
2272
- }), await queueEffects(ctx, mutation, task.effects, { kind: "task", name: task.name }, actor, {
2273
- taskName: task.name
2274
- });
2330
+ }), await queueEffects(
2331
+ ctx,
2332
+ mutation,
2333
+ activity.effects,
2334
+ { kind: "activity", name: activity.name },
2335
+ actor,
2336
+ {
2337
+ activityName: activity.name
2338
+ }
2339
+ );
2275
2340
  let pendingChildren;
2276
- if (task.subworkflows !== void 0) {
2277
- const createsBefore = mutation.pendingCreates.length, refs = await spawnSubworkflows(ctx, mutation, task, task.subworkflows, actor);
2341
+ if (activity.subworkflows !== void 0) {
2342
+ const createsBefore = mutation.pendingCreates.length, refs = await spawnSubworkflows(ctx, mutation, activity, activity.subworkflows, actor);
2278
2343
  entry.spawnedInstances = refs, pendingChildren = mutation.pendingCreates.slice(createsBefore).map((c) => ({ _id: c._id, stage: c.currentStage, status: "active" }));
2279
2344
  for (const ref of refs)
2280
2345
  mutation.history.push({
2281
2346
  _key: randomKey(),
2282
2347
  _type: "spawned",
2283
2348
  at: now,
2284
- task: task.name,
2349
+ activity: activity.name,
2285
2350
  instanceRef: ref
2286
2351
  });
2287
2352
  }
2288
- await autoResolveOnActivate(ctx, mutation, task, entry, now, pendingChildren) || resolveMachineStep(mutation, task, entry, now);
2353
+ await autoResolveOnActivate(ctx, mutation, activity, entry, now, pendingChildren) || resolveMachineStep(mutation, activity, entry, now);
2289
2354
  }
2290
- async function autoResolveOnActivate(ctx, mutation, task, entry, now, pendingChildren) {
2291
- if (task.failWhen === void 0 && effectiveCompleteWhen(task) === void 0) return !1;
2292
- const vars = pendingChildren !== void 0 ? { subworkflows: pendingChildren } : await taskConditionVars(ctx, entry), outcome = await evaluateResolutionGate(ctx, task, vars);
2293
- return outcome === void 0 ? !1 : (applyTaskStatusChange({
2355
+ async function autoResolveOnActivate(ctx, mutation, activity, entry, now, pendingChildren) {
2356
+ if (activity.failWhen === void 0 && effectiveCompleteWhen(activity) === void 0) return !1;
2357
+ const vars = pendingChildren !== void 0 ? { subworkflows: pendingChildren } : await activityConditionVars(ctx, entry), outcome = await evaluateResolutionGate(ctx, activity, vars);
2358
+ return outcome === void 0 ? !1 : (applyActivityStatusChange({
2294
2359
  entry,
2295
2360
  history: mutation.history,
2296
2361
  stage: mutation.currentStage,
@@ -2299,29 +2364,31 @@ async function autoResolveOnActivate(ctx, mutation, task, entry, now, pendingChi
2299
2364
  actor: gateActor(outcome)
2300
2365
  }), !0);
2301
2366
  }
2302
- async function taskConditionVars(ctx, entry) {
2367
+ async function activityConditionVars(ctx, entry) {
2303
2368
  return entry.spawnedInstances === void 0 || entry.spawnedInstances.length === 0 ? { subworkflows: [] } : { subworkflows: await subworkflowRows(ctx, entry.spawnedInstances) };
2304
2369
  }
2305
- function resolveMachineStep(mutation, task, entry, now) {
2306
- const waitsForActor = task.actions !== void 0 && task.actions.length > 0, waitsForCondition = task.completeWhen !== void 0 || task.subworkflows !== void 0;
2307
- waitsForActor || waitsForCondition || applyTaskStatusChange({
2370
+ function resolveMachineStep(mutation, activity, entry, now) {
2371
+ const waitsForActor = activity.actions !== void 0 && activity.actions.length > 0, waitsForCondition = activity.completeWhen !== void 0 || activity.subworkflows !== void 0;
2372
+ waitsForActor || waitsForCondition || applyActivityStatusChange({
2308
2373
  entry,
2309
2374
  history: mutation.history,
2310
2375
  stage: mutation.currentStage,
2311
2376
  to: "done",
2312
2377
  at: now,
2313
- actor: { kind: "system", id: "engine.machineStep" }
2378
+ actor: engineSystemActor("machineStep")
2314
2379
  });
2315
2380
  }
2316
- async function buildStageTasks(ctx, stage) {
2381
+ async function buildStageActivities(ctx, stage) {
2317
2382
  const entries = [];
2318
- for (const task of stage.tasks ?? []) {
2319
- const outcome = await ctxEvaluateConditionOutcome(ctx, task.filter, { taskName: task.name }), inScope = outcome !== "unsatisfied";
2383
+ for (const activity of stage.activities ?? []) {
2384
+ const outcome = await ctxEvaluateConditionOutcome(ctx, activity.filter, {
2385
+ activityName: activity.name
2386
+ }), inScope = outcome !== "unsatisfied";
2320
2387
  entries.push({
2321
2388
  _key: randomKey(),
2322
- name: task.name,
2389
+ name: activity.name,
2323
2390
  status: inScope ? "pending" : "skipped",
2324
- ...task.filter !== void 0 ? { filterEvaluation: buildFilterEvaluation(ctx.now, task.filter, outcome) } : {}
2391
+ ...activity.filter !== void 0 ? { filterEvaluation: buildFilterEvaluation(ctx.now, activity.filter, outcome) } : {}
2325
2392
  });
2326
2393
  }
2327
2394
  return entries;
@@ -2331,7 +2398,7 @@ function buildFilterEvaluation(at, filter, outcome) {
2331
2398
  at,
2332
2399
  truthy: !1,
2333
2400
  unevaluable: !0,
2334
- detail: `Filter "${filter}" could not be evaluated (GROQ null \u2014 a referenced operand was missing or incomparable). Task kept in scope so the gate is not silently skipped.`
2401
+ detail: `Filter "${filter}" could not be evaluated (GROQ null \u2014 a referenced operand was missing or incomparable). Activity kept in scope so the gate is not silently skipped.`
2335
2402
  } : { at, truthy: outcome === "satisfied" };
2336
2403
  }
2337
2404
  function isTerminalStage(stage) {
@@ -2354,15 +2421,16 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
2354
2421
  client: ctx.client,
2355
2422
  instance: ctx.instance,
2356
2423
  stage: nextStage,
2357
- now: at
2424
+ now: at,
2425
+ recordDiscard: recordFieldDiscards(mutation.history, "stage", at)
2358
2426
  }),
2359
- tasks: await buildStageTasks(ctx, nextStage)
2427
+ activities: await buildStageActivities(ctx, nextStage)
2360
2428
  };
2361
2429
  mutation.stages.push(nextStageEntry);
2362
- for (const task of nextStage.tasks ?? []) {
2363
- if (task.activation !== "auto") continue;
2364
- const entry = nextStageEntry.tasks.find((t) => t.name === task.name);
2365
- entry && entry.status === "pending" && await activateTask(ctx, mutation, task, entry, actor);
2430
+ for (const activity of nextStage.activities ?? []) {
2431
+ if (activity.activation !== "auto") continue;
2432
+ const entry = nextStageEntry.activities.find((t) => t.name === activity.name);
2433
+ entry && entry.status === "pending" && await activateActivity(ctx, mutation, activity, entry, actor);
2366
2434
  }
2367
2435
  isTerminalStage(nextStage) && (mutation.completedAt = at);
2368
2436
  }
@@ -2486,15 +2554,22 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
2486
2554
  instance,
2487
2555
  definition,
2488
2556
  ...clock ? { clock } : {}
2489
- }), now = ctx.now, initialStageEntry = {
2557
+ }), now = ctx.now, discards = [], initialStageEntry = {
2490
2558
  _key: randomKey(),
2491
2559
  name: stage.name,
2492
2560
  enteredAt: now,
2493
- fields: await resolveStageFieldEntries({ client, instance, stage, now }),
2494
- tasks: await buildStageTasks(ctx, stage)
2561
+ fields: await resolveStageFieldEntries({
2562
+ client,
2563
+ instance,
2564
+ stage,
2565
+ now,
2566
+ recordDiscard: recordFieldDiscards(discards, "stage", now)
2567
+ }),
2568
+ activities: await buildStageActivities(ctx, stage)
2495
2569
  }, committed = await client.patch(instance._id).set({
2496
2570
  stages: [initialStageEntry],
2497
- lastChangedAt: now
2571
+ lastChangedAt: now,
2572
+ ...discards.length > 0 ? { history: [...instance.history, ...discards] } : {}
2498
2573
  }).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
2499
2574
  await deployOrRollback({
2500
2575
  client,
@@ -2513,12 +2588,20 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
2513
2588
  stageName: stage.name,
2514
2589
  now
2515
2590
  })
2516
- }), await autoActivatePrimedTasks(client, instanceId, definition, stage, actor, clientForGdr, clock);
2591
+ }), await autoActivatePrimedActivities(
2592
+ client,
2593
+ instanceId,
2594
+ definition,
2595
+ stage,
2596
+ actor,
2597
+ clientForGdr,
2598
+ clock
2599
+ );
2517
2600
  }
2518
- async function autoActivatePrimedTasks(client, instanceId, definition, stage, actor, clientForGdr, clock) {
2601
+ async function autoActivatePrimedActivities(client, instanceId, definition, stage, actor, clientForGdr, clock) {
2519
2602
  const resolvedClientForGdr = clientForGdr ?? (() => client);
2520
- for (const task of stage.tasks ?? []) {
2521
- if (task.activation !== "auto") continue;
2603
+ for (const activity of stage.activities ?? []) {
2604
+ if (activity.activation !== "auto") continue;
2522
2605
  const updated = await client.getDocument(instanceId);
2523
2606
  if (!updated) continue;
2524
2607
  const updatedCtx = await buildEngineContext({
@@ -2527,18 +2610,22 @@ async function autoActivatePrimedTasks(client, instanceId, definition, stage, ac
2527
2610
  instance: updated,
2528
2611
  definition,
2529
2612
  ...clock ? { clock } : {}
2530
- }), mutation = startMutation(updated), mutEntry = currentTasks(mutation).find((t) => t.name === task.name);
2531
- mutEntry && mutEntry.status === "pending" && (await activateTask(updatedCtx, mutation, task, mutEntry, actor), await persist(updatedCtx, mutation));
2613
+ }), mutation = startMutation(updated), mutEntry = currentActivities(mutation).find((t) => t.name === activity.name);
2614
+ mutEntry && mutEntry.status === "pending" && (await activateActivity(updatedCtx, mutation, activity, mutEntry, actor), await persist(updatedCtx, mutation));
2532
2615
  }
2533
2616
  }
2534
2617
  const CASCADE_LIMIT = 100;
2535
- async function gatherAutoResolveCandidates(ctx, tasks) {
2536
- const currentTasksList = findCurrentTasks(ctx.instance), candidates = [];
2537
- for (const task of tasks) {
2538
- const entry = currentTasksList.find((e) => e.name === task.name);
2618
+ async function gatherAutoResolveCandidates(ctx, activities) {
2619
+ const currentActivitiesList = findCurrentActivities(ctx.instance), candidates = [];
2620
+ for (const activity of activities) {
2621
+ const entry = currentActivitiesList.find((e) => e.name === activity.name);
2539
2622
  if (entry?.status !== "active") continue;
2540
- const outcome = await evaluateResolutionGate(ctx, task, await taskConditionVars(ctx, entry));
2541
- outcome !== void 0 && candidates.push({ task, outcome });
2623
+ const outcome = await evaluateResolutionGate(
2624
+ ctx,
2625
+ activity,
2626
+ await activityConditionVars(ctx, entry)
2627
+ );
2628
+ outcome !== void 0 && candidates.push({ activity, outcome });
2542
2629
  }
2543
2630
  return candidates;
2544
2631
  }
@@ -2547,17 +2634,17 @@ async function resolveCompleteWhen(client, instanceId, clientForGdr, clock, over
2547
2634
  ...clientForGdr ? { clientForGdr } : {},
2548
2635
  ...clock ? { clock } : {},
2549
2636
  ...overlay ? { overlay } : {}
2550
- }), stage = findStage(ctx.definition, ctx.instance.currentStage), gatedTasks = (stage.tasks ?? []).filter(
2637
+ }), stage = findStage(ctx.definition, ctx.instance.currentStage), gatedActivities = (stage.activities ?? []).filter(
2551
2638
  (t) => effectiveCompleteWhen(t) !== void 0 || t.failWhen !== void 0
2552
2639
  );
2553
- if (gatedTasks.length === 0) return 0;
2554
- const candidates = await gatherAutoResolveCandidates(ctx, gatedTasks);
2640
+ if (gatedActivities.length === 0) return 0;
2641
+ const candidates = await gatherAutoResolveCandidates(ctx, gatedActivities);
2555
2642
  if (candidates.length === 0) return 0;
2556
2643
  const mutation = startMutation(ctx.instance);
2557
2644
  let count = 0;
2558
- for (const { task, outcome } of candidates) {
2559
- const mutEntry = currentTasks(mutation).find((t) => t.name === task.name);
2560
- mutEntry === void 0 || mutEntry.status !== "active" || (applyTaskStatusChange({
2645
+ for (const { activity, outcome } of candidates) {
2646
+ const mutEntry = currentActivities(mutation).find((t) => t.name === activity.name);
2647
+ mutEntry === void 0 || mutEntry.status !== "active" || (applyActivityStatusChange({
2561
2648
  entry: mutEntry,
2562
2649
  history: mutation.history,
2563
2650
  stage: stage.name,
@@ -2590,9 +2677,9 @@ async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
2590
2677
  if (!parent || parent._type !== WORKFLOW_INSTANCE_TYPE || typeof parent.definitionSnapshot != "string") return;
2591
2678
  const definition = parseDefinitionSnapshot(parent), stage = definition.stages.find((s) => s.name === parent.currentStage);
2592
2679
  if (stage === void 0) return;
2593
- const parentCurrentTasks = findCurrentTasks(parent), task = (stage.tasks ?? []).find((t) => t.subworkflows === void 0 ? !1 : parentCurrentTasks.find((e) => e.name === t.name)?.spawnedInstances?.some((r) => gdrToBareId(r.id) === child._id) ?? !1);
2594
- if (task?.subworkflows === void 0) return;
2595
- const entry = parentCurrentTasks.find((e) => e.name === task.name);
2680
+ const parentCurrentActivities = findCurrentActivities(parent), activity = (stage.activities ?? []).find((t) => t.subworkflows === void 0 ? !1 : parentCurrentActivities.find((e) => e.name === t.name)?.spawnedInstances?.some((r) => gdrToBareId(r.id) === child._id) ?? !1);
2681
+ if (activity?.subworkflows === void 0) return;
2682
+ const entry = parentCurrentActivities.find((e) => e.name === activity.name);
2596
2683
  if (entry === void 0 || entry.status !== "active") return;
2597
2684
  const ctx = await buildEngineContext({
2598
2685
  client,
@@ -2600,23 +2687,27 @@ async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
2600
2687
  instance: parent,
2601
2688
  definition,
2602
2689
  ...clock ? { clock } : {}
2603
- }), outcome = await evaluateResolutionGate(ctx, task, await taskConditionVars(ctx, entry));
2690
+ }), outcome = await evaluateResolutionGate(
2691
+ ctx,
2692
+ activity,
2693
+ await activityConditionVars(ctx, entry)
2694
+ );
2604
2695
  if (outcome !== void 0)
2605
- return { parent, definition, stage, task, outcome };
2696
+ return { parent, definition, stage, activity, outcome };
2606
2697
  }
2607
2698
  async function propagateToAncestors(client, instanceId, actor, clientForGdr, clock) {
2608
2699
  const instance = await client.getDocument(instanceId);
2609
2700
  if (!instance) return;
2610
2701
  const resolvedClientForGdr = clientForGdr ?? (() => client), found = await findResolvedParentSpawn(client, instance, resolvedClientForGdr, clock);
2611
2702
  if (found === void 0) return;
2612
- const { parent, definition, stage, task, outcome } = found, ctx = await buildEngineContext({
2703
+ const { parent, definition, stage, activity, outcome } = found, ctx = await buildEngineContext({
2613
2704
  client,
2614
2705
  clientForGdr: resolvedClientForGdr,
2615
2706
  instance: parent,
2616
2707
  definition,
2617
2708
  ...clock ? { clock } : {}
2618
- }), mutation = startMutation(parent), mutEntry = currentTasks(mutation).find((e) => e.name === task.name);
2619
- mutEntry !== void 0 && (applyTaskStatusChange({
2709
+ }), mutation = startMutation(parent), mutEntry = currentActivities(mutation).find((e) => e.name === activity.name);
2710
+ mutEntry !== void 0 && (applyActivityStatusChange({
2620
2711
  entry: mutEntry,
2621
2712
  history: mutation.history,
2622
2713
  stage: stage.name,
@@ -2719,6 +2810,18 @@ async function fetchGrantsCached(requestFn, resourcePath) {
2719
2810
  return;
2720
2811
  }
2721
2812
  }
2813
+ function hasItems(arr) {
2814
+ return arr !== void 0 && arr.length > 0;
2815
+ }
2816
+ function deriveActivityKind(activity) {
2817
+ return hasItems(activity.actions) ? "user" : hasItems(activity.effects) ? "service" : activity.completeWhen !== void 0 || activity.subworkflows !== void 0 ? "receive" : "script";
2818
+ }
2819
+ function activityKind(activity) {
2820
+ return activity.kind ?? deriveActivityKind(activity);
2821
+ }
2822
+ function driverKind(actor) {
2823
+ return actor.kind === "user" ? "person" : actor.kind === "ai" ? "agent" : isEngineActor(actor) ? "engine" : "service";
2824
+ }
2722
2825
  function effectiveEditable(baseline, override) {
2723
2826
  if (baseline === void 0) return;
2724
2827
  if (override === void 0) return baseline;
@@ -2730,14 +2833,15 @@ function slotSites(definition, stage) {
2730
2833
  const sites = [];
2731
2834
  for (const entry of definition.fields ?? []) sites.push({ scope: "workflow", entry });
2732
2835
  for (const entry of stage.fields ?? []) sites.push({ scope: "stage", entry });
2733
- for (const task of stage.tasks ?? [])
2734
- for (const entry of task.fields ?? []) sites.push({ scope: "task", task: task.name, entry });
2836
+ for (const activity of stage.activities ?? [])
2837
+ for (const entry of activity.fields ?? [])
2838
+ sites.push({ scope: "activity", activity: activity.name, entry });
2735
2839
  return sites;
2736
2840
  }
2737
2841
  function toResolvedSlot(site, stage) {
2738
2842
  return {
2739
2843
  scope: site.scope,
2740
- ...site.task !== void 0 ? { task: site.task } : {},
2844
+ ...site.activity !== void 0 ? { activity: site.activity } : {},
2741
2845
  name: site.entry.name,
2742
2846
  type: site.entry.type,
2743
2847
  ...site.entry.title !== void 0 ? { title: site.entry.title } : {},
@@ -2749,16 +2853,16 @@ function editableSlotsInStage(definition, stage) {
2749
2853
  return slotSites(definition, stage).filter((site) => site.entry.editable !== void 0).map((site) => toResolvedSlot(site, stage));
2750
2854
  }
2751
2855
  function editTargetMatches(slot, target) {
2752
- return slot.name !== target.field ? !1 : target.task !== void 0 ? slot.scope === "task" && slot.task === target.task : target.scope !== void 0 ? slot.scope === target.scope : !0;
2856
+ return slot.name !== target.field ? !1 : target.activity !== void 0 ? slot.scope === "activity" && slot.activity === target.activity : target.scope !== void 0 ? slot.scope === target.scope : !0;
2753
2857
  }
2754
- const SCOPE_PRECEDENCE = { task: 0, stage: 1, workflow: 2 };
2858
+ const SCOPE_PRECEDENCE = { activity: 0, stage: 1, workflow: 2 };
2755
2859
  function resolveEditTarget(args) {
2756
2860
  const { definition, stage, target } = args, found = slotSites(definition, stage).filter(
2757
2861
  (site) => editTargetMatches(
2758
2862
  {
2759
2863
  scope: site.scope,
2760
2864
  name: site.entry.name,
2761
- ...site.task !== void 0 ? { task: site.task } : {}
2865
+ ...site.activity !== void 0 ? { activity: site.activity } : {}
2762
2866
  },
2763
2867
  target
2764
2868
  )
@@ -2768,9 +2872,11 @@ function resolveEditTarget(args) {
2768
2872
  function slotWindowOpen(instance, slot) {
2769
2873
  if (instance.completedAt !== void 0) return { open: !1, detail: "instance completed" };
2770
2874
  if (instance.abortedAt !== void 0) return { open: !1, detail: "instance aborted" };
2771
- if (slot.scope !== "task") return { open: !0 };
2772
- const status = findOpenStageEntry(instance)?.tasks.find((t) => t.name === slot.task)?.status;
2773
- return status === "active" ? { open: !0 } : { open: !1, detail: `task "${slot.task}" is ${status ?? "not active"}` };
2875
+ if (slot.scope !== "activity") return { open: !0 };
2876
+ const status = findOpenStageEntry(instance)?.activities.find(
2877
+ (t) => t.name === slot.activity
2878
+ )?.status;
2879
+ return status === "active" ? { open: !0 } : { open: !1, detail: `activity "${slot.activity}" is ${status ?? "not active"}` };
2774
2880
  }
2775
2881
  function readSlotValue(instance, slot) {
2776
2882
  return slotFieldHost(instance, slot)?.find((e) => e.name === slot.name)?.value;
@@ -2778,7 +2884,7 @@ function readSlotValue(instance, slot) {
2778
2884
  function slotFieldHost(instance, slot) {
2779
2885
  if (slot.scope === "workflow") return instance.fields;
2780
2886
  const stageEntry = findOpenStageEntry(instance);
2781
- return slot.scope === "stage" ? stageEntry?.fields : stageEntry?.tasks.find((t) => t.name === slot.task)?.fields;
2887
+ return slot.scope === "stage" ? stageEntry?.fields : stageEntry?.activities.find((t) => t.name === slot.activity)?.fields;
2782
2888
  }
2783
2889
  function slotProvenance(instance, ref) {
2784
2890
  let latest;
@@ -2821,12 +2927,12 @@ async function evaluateFromSnapshot(args) {
2821
2927
  throw new Error(
2822
2928
  `Instance "${instance._id}" currentStage "${instance.currentStage}" not in definition`
2823
2929
  );
2824
- const scope = await renderScope({ instance, definition, actor, grants, snapshot, now }), currentTaskEntries = findOpenStageEntry(instance)?.tasks ?? [], guardDenial = await instanceGuardReason(instance, actor, args.guards), taskEvaluations = [];
2825
- for (const task of stage.tasks ?? [])
2826
- taskEvaluations.push(
2827
- await evaluateTask({
2828
- task,
2829
- statusEntry: currentTaskEntries.find((t) => t.name === task.name),
2930
+ const scope = await renderScope({ instance, definition, actor, grants, snapshot, now }), currentActivityEntries = findOpenStageEntry(instance)?.activities ?? [], guardDenial = await instanceGuardReason(instance, actor, args.guards), activityEvaluations = [];
2931
+ for (const activity of stage.activities ?? [])
2932
+ activityEvaluations.push(
2933
+ await evaluateActivity({
2934
+ activity,
2935
+ statusEntry: currentActivityEntries.find((t) => t.name === activity.name),
2830
2936
  instance,
2831
2937
  actor,
2832
2938
  snapshot,
@@ -2851,9 +2957,9 @@ async function evaluateFromSnapshot(args) {
2851
2957
  }
2852
2958
  const currentStage = {
2853
2959
  stage,
2854
- tasks: taskEvaluations,
2960
+ activities: activityEvaluations,
2855
2961
  transitions: transitionEvaluations
2856
- }, pendingOnYou = taskEvaluations.filter((t) => t.pendingOnActor), canInteract = taskEvaluations.some((t) => t.actions.some((a) => a.allowed)), editGuardDenial = guardDenial?.kind === "mutation-guard-denied" ? guardDenial : void 0, editableSlots = await evaluateEditableSlots({
2962
+ }, pendingOnYou = activityEvaluations.filter((t) => t.pendingOnActor), canInteract = activityEvaluations.some((t) => t.actions.some((a) => a.allowed)), editGuardDenial = guardDenial?.kind === "mutation-guard-denied" ? guardDenial : void 0, editableSlots = await evaluateEditableSlots({
2857
2963
  instance,
2858
2964
  definition,
2859
2965
  stage,
@@ -2893,7 +2999,7 @@ async function evaluateEditableSlots(args) {
2893
2999
  }), value = readSlotValue(instance, slot);
2894
3000
  slots.push({
2895
3001
  scope: slot.scope,
2896
- ...slot.task !== void 0 ? { task: slot.task } : {},
3002
+ ...slot.activity !== void 0 ? { activity: slot.activity } : {},
2897
3003
  name: slot.name,
2898
3004
  type: slot.type,
2899
3005
  ...slot.title !== void 0 ? { title: slot.title } : {},
@@ -2909,7 +3015,14 @@ async function editPredicateSatisfied(args) {
2909
3015
  const { slot, window, guardDenial, instance, snapshot, scope, actor, roleAliases } = args;
2910
3016
  if (!window.open || guardDenial !== void 0) return !1;
2911
3017
  if (slot.effective === !0) return !0;
2912
- const params = slot.scope === "task" && slot.task !== void 0 ? taskScopeFor({ scope, instance, snapshot, actor, taskName: slot.task, roleAliases }) : scope;
3018
+ const params = slot.scope === "activity" && slot.activity !== void 0 ? activityScopeFor({
3019
+ scope,
3020
+ instance,
3021
+ snapshot,
3022
+ actor,
3023
+ activityName: slot.activity,
3024
+ roleAliases
3025
+ }) : scope;
2913
3026
  return evaluateCondition({ condition: slot.effective, snapshot, params });
2914
3027
  }
2915
3028
  async function renderScope(args) {
@@ -2937,20 +3050,20 @@ async function advisoryCan(instance, actor, grants) {
2937
3050
  });
2938
3051
  return can;
2939
3052
  }
2940
- function taskScopeFor(args) {
2941
- const { scope, instance, snapshot, actor, taskName, roleAliases } = args;
3053
+ function activityScopeFor(args) {
3054
+ const { scope, instance, snapshot, actor, activityName, roleAliases } = args;
2942
3055
  return {
2943
3056
  ...scope,
2944
3057
  fields: {
2945
3058
  ...scope.fields,
2946
- ...scopedFieldOverlay(instance, snapshot, taskName)
3059
+ ...scopedFieldOverlay(instance, snapshot, activityName)
2947
3060
  },
2948
- assigned: assignedFor(instance, taskName, actor, roleAliases)
3061
+ assigned: assignedFor(instance, activityName, actor, roleAliases)
2949
3062
  };
2950
3063
  }
2951
- async function evaluateTask(args) {
3064
+ async function evaluateActivity(args) {
2952
3065
  const {
2953
- task,
3066
+ activity,
2954
3067
  statusEntry,
2955
3068
  instance,
2956
3069
  actor,
@@ -2959,36 +3072,37 @@ async function evaluateTask(args) {
2959
3072
  roleAliases,
2960
3073
  stageHasExits,
2961
3074
  guardDenial
2962
- } = args, status = statusEntry?.status ?? "pending", taskScope = taskScopeFor({
3075
+ } = args, status = statusEntry?.status ?? "pending", activityScope = activityScopeFor({
2963
3076
  scope,
2964
3077
  instance,
2965
3078
  snapshot,
2966
3079
  actor,
2967
- taskName: task.name,
3080
+ activityName: activity.name,
2968
3081
  roleAliases
2969
- }), assigned = taskScope.assigned === !0, unmetRequirements = await evaluateRequirements({
2970
- requirements: task.requirements,
3082
+ }), assigned = activityScope.assigned === !0, unmetRequirements = await evaluateRequirements({
3083
+ requirements: activity.requirements,
2971
3084
  snapshot,
2972
- params: taskScope
3085
+ params: activityScope
2973
3086
  }), requirementsReason = unmetRequirements.length > 0 ? { kind: "requirements-unmet", unmetRequirements } : void 0, actions = [];
2974
- for (const action of task.actions ?? [])
3087
+ for (const action of activity.actions ?? [])
2975
3088
  actions.push(
2976
3089
  await evaluateAction({
2977
3090
  action,
2978
3091
  status,
2979
3092
  instance,
2980
3093
  snapshot,
2981
- taskScope,
3094
+ activityScope,
2982
3095
  stageHasExits,
2983
3096
  guardDenial,
2984
3097
  requirementsReason
2985
3098
  })
2986
3099
  );
2987
3100
  return {
2988
- task,
3101
+ activity,
2989
3102
  status,
3103
+ kind: activityKind(activity),
2990
3104
  // "pending on actor" treats both `pending` and `active` as waiting on
2991
- // the assignee — pending tasks just haven't been activated yet, but
3105
+ // the assignee — pending activities just haven't been activated yet, but
2992
3106
  // they're still inbox items. The inbox reads the assignees-kind field
2993
3107
  // entry BY KIND (who-data is fields).
2994
3108
  pendingOnActor: (status === "active" || status === "pending") && assigned,
@@ -3002,7 +3116,7 @@ async function evaluateAction(args) {
3002
3116
  status,
3003
3117
  instance,
3004
3118
  snapshot,
3005
- taskScope,
3119
+ activityScope,
3006
3120
  stageHasExits,
3007
3121
  guardDenial,
3008
3122
  requirementsReason
@@ -3010,7 +3124,7 @@ async function evaluateAction(args) {
3010
3124
  return lifecycle !== void 0 ? disabled(action, lifecycle) : guardDenial !== void 0 ? disabled(action, guardDenial) : requirementsReason !== void 0 ? disabled(action, requirementsReason) : action.filter !== void 0 && !await evaluateCondition({
3011
3125
  condition: action.filter,
3012
3126
  snapshot,
3013
- params: taskScope
3127
+ params: activityScope
3014
3128
  }) ? disabled(action, { kind: "filter-failed", filter: action.filter }) : { action, allowed: !0 };
3015
3129
  }
3016
3130
  async function instanceGuardReason(instance, actor, guards) {
@@ -3028,8 +3142,8 @@ function lifecycleReason(instance, status, stageHasExits) {
3028
3142
  return { kind: "instance-completed", completedAt: instance.completedAt };
3029
3143
  if (!stageHasExits)
3030
3144
  return { kind: "stage-terminal", stage: instance.currentStage };
3031
- if (schema.isTerminalTaskStatus(status))
3032
- return { kind: "task-not-active", status };
3145
+ if (schema.isTerminalActivityStatus(status))
3146
+ return { kind: "activity-not-active", status };
3033
3147
  }
3034
3148
  function disabled(action, reason) {
3035
3149
  return { action, allowed: !1, disabledReason: reason };
@@ -3037,37 +3151,30 @@ function disabled(action, reason) {
3037
3151
  function definitionDocId(_workflowResource, tag, definition, version) {
3038
3152
  return `${tag}.${definition}.v${version}`;
3039
3153
  }
3040
- async function sortByDependencies(client, definitions, tag, workflowResource) {
3154
+ async function sortByDependencies(client, definitions, tag) {
3041
3155
  const byName = /* @__PURE__ */ new Map();
3042
3156
  for (const def of definitions) {
3043
- const list = byName.get(def.name) ?? [];
3044
- list.push(def), byName.set(def.name, list);
3157
+ if (byName.has(def.name))
3158
+ throw new Error(
3159
+ `workflow.deployDefinitions: duplicate definition name "${def.name}" in batch \u2014 names identify a batch member (versions are deploy-assigned, not authored)`
3160
+ );
3161
+ byName.set(def.name, def);
3045
3162
  }
3046
3163
  const findInBatch = (ref) => findRefInBatch(byName, ref);
3047
- return await assertCrossBatchRefsDeployed(client, definitions, {
3048
- tag,
3049
- workflowResource,
3050
- findInBatch
3051
- }), topoSortDefinitions(definitions, { workflowResource, tag, findInBatch });
3164
+ return await assertCrossBatchRefsDeployed(client, definitions, { tag, findInBatch }), topoSortDefinitions(definitions, { findInBatch });
3052
3165
  }
3053
3166
  function findRefInBatch(byName, ref) {
3054
- const candidates = byName.get(ref.name);
3055
- if (!(candidates === void 0 || candidates.length === 0))
3056
- return typeof ref.version == "number" ? candidates.find((c) => c.version === ref.version) : candidates.reduce(
3057
- (best, c) => best === void 0 || c.version > best.version ? c : best,
3058
- void 0
3059
- );
3167
+ if (typeof ref.version != "number")
3168
+ return byName.get(ref.name);
3060
3169
  }
3061
3170
  async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
3062
3171
  const missing = [];
3063
- for (const def of definitions) {
3064
- const fromId = definitionDocId(ctx.workflowResource, ctx.tag, def.name, def.version);
3172
+ for (const def of definitions)
3065
3173
  for (const ref of refsOf(def)) {
3066
3174
  if (ctx.findInBatch(ref) !== void 0) continue;
3067
3175
  const label = await resolveDeployedRefLabel(client, ref, ctx.tag);
3068
- label !== void 0 && missing.push({ from: fromId, ref: label });
3176
+ label !== void 0 && missing.push({ from: def.name, ref: label });
3069
3177
  }
3070
- }
3071
3178
  if (missing.length === 0) return;
3072
3179
  const lines = missing.map((m) => ` - ${m.from} \u2192 ${m.ref} (neither in batch nor deployed)`);
3073
3180
  throw new Error(
@@ -3086,16 +3193,15 @@ async function resolveDeployedRefLabel(client, ref, tag) {
3086
3193
  }
3087
3194
  function topoSortDefinitions(definitions, ctx) {
3088
3195
  const visited = /* @__PURE__ */ new Set(), visiting = /* @__PURE__ */ new Set(), ordered = [], visit = (def) => {
3089
- const id = definitionDocId(ctx.workflowResource, ctx.tag, def.name, def.version);
3090
- if (!visited.has(id)) {
3091
- if (visiting.has(id))
3092
- throw new Error(`workflow.deployDefinitions: dependency cycle involving "${id}"`);
3093
- visiting.add(id);
3196
+ if (!visited.has(def.name)) {
3197
+ if (visiting.has(def.name))
3198
+ throw new Error(`workflow.deployDefinitions: dependency cycle involving "${def.name}"`);
3199
+ visiting.add(def.name);
3094
3200
  for (const ref of refsOf(def)) {
3095
3201
  const child = ctx.findInBatch(ref);
3096
3202
  child !== void 0 && visit(child);
3097
3203
  }
3098
- visiting.delete(id), visited.add(id), ordered.push(def);
3204
+ visiting.delete(def.name), visited.add(def.name), ordered.push(def);
3099
3205
  }
3100
3206
  };
3101
3207
  for (const def of definitions) visit(def);
@@ -3104,8 +3210,8 @@ function topoSortDefinitions(definitions, ctx) {
3104
3210
  function refsOf(def) {
3105
3211
  const out = [];
3106
3212
  for (const stage of def.stages)
3107
- for (const task of stage.tasks ?? []) {
3108
- const ref = task.subworkflows?.definition;
3213
+ for (const activity of stage.activities ?? []) {
3214
+ const ref = activity.subworkflows?.definition;
3109
3215
  ref !== void 0 && out.push({
3110
3216
  name: ref.name,
3111
3217
  ...ref.version !== void 0 ? { version: ref.version } : {}
@@ -3113,8 +3219,23 @@ function refsOf(def) {
3113
3219
  }
3114
3220
  return out;
3115
3221
  }
3116
- function isDefinitionUnchanged(existing, expected) {
3117
- return stableStringify(stripSystemFields(existing)) === stableStringify(stripSystemFields(expected));
3222
+ function hashDefinitionContent(def) {
3223
+ const canonical = stableStringify(def);
3224
+ let hash = 0xcbf29ce484222325n;
3225
+ for (let i = 0; i < canonical.length; i++)
3226
+ hash ^= BigInt(canonical.charCodeAt(i)), hash = hash * 0x100000001b3n & 0xffffffffffffffffn;
3227
+ return hash.toString(16).padStart(16, "0");
3228
+ }
3229
+ function planDefinitionDeploy(def, latest, target) {
3230
+ const contentHash = hashDefinitionContent(def), unchanged = latest !== void 0 && latest.contentHash === contentHash, version = unchanged ? latest.version : (latest?.version ?? 0) + 1, docId = definitionDocId(target.workflowResource, target.tag, def.name, version), document = {
3231
+ ...def,
3232
+ _id: docId,
3233
+ _type: schema.WORKFLOW_DEFINITION_TYPE,
3234
+ tag: target.tag,
3235
+ version,
3236
+ contentHash
3237
+ };
3238
+ return { status: unchanged ? "unchanged" : "create", version, contentHash, docId, document };
3118
3239
  }
3119
3240
  function stripSystemFields(doc) {
3120
3241
  const out = {};
@@ -3129,22 +3250,26 @@ function stableStringify(value) {
3129
3250
  }
3130
3251
  async function loadDefinition(client, definition, version, tag) {
3131
3252
  if (version !== void 0) {
3132
- const doc = await client.fetch(
3133
- definitionLookupGroq(!0),
3134
- { definition, version, tag }
3135
- );
3253
+ const doc = await client.fetch(definitionLookupGroq(!0), {
3254
+ definition,
3255
+ version,
3256
+ tag
3257
+ });
3136
3258
  if (!doc)
3137
3259
  throw new Error(`Workflow definition ${definition} v${version} not deployed`);
3138
3260
  return doc;
3139
3261
  }
3140
- const latest = await client.fetch(definitionLookupGroq(!1), {
3141
- definition,
3142
- tag
3143
- });
3262
+ const latest = await loadLatestDeployed(client, definition, tag);
3144
3263
  if (!latest)
3145
3264
  throw new Error(`No deployed definition for workflow ${definition}`);
3146
3265
  return latest;
3147
3266
  }
3267
+ async function loadLatestDeployed(client, definition, tag) {
3268
+ return await client.fetch(definitionLookupGroq(!1), {
3269
+ definition,
3270
+ tag
3271
+ }) ?? void 0;
3272
+ }
3148
3273
  async function loadDefinitionVersions(client, definition, tag) {
3149
3274
  return client.fetch(
3150
3275
  `*[_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}] | order(version desc)`,
@@ -3191,63 +3316,63 @@ async function commitAbort(ctx, reason, actor) {
3191
3316
  }), { fired: !0, stage: stage.name };
3192
3317
  }
3193
3318
  async function fireAction(args) {
3194
- const { client, instanceId, task, action, params, options } = args;
3319
+ const { client, instanceId, activity, action, params, options } = args;
3195
3320
  for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
3196
3321
  const ctx = await loadCallContext(client, instanceId, options);
3197
3322
  try {
3198
- return await commitAction(ctx, task, action, params, options);
3323
+ return await commitAction(ctx, activity, action, params, options);
3199
3324
  } catch (error) {
3200
3325
  if (!isRevisionConflict(error)) throw error;
3201
3326
  }
3202
3327
  }
3203
3328
  throw new ConcurrentFireActionError({
3204
3329
  instanceId,
3205
- task,
3330
+ activity,
3206
3331
  action,
3207
3332
  attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
3208
3333
  });
3209
3334
  }
3210
- async function resolveActionCommit(ctx, taskName, actionName, callerParams, options) {
3211
- const actor = options?.actor, { stage, task } = findTaskInCurrentStage(ctx, taskName), action = (task.actions ?? []).find((a) => a.name === actionName);
3335
+ async function resolveActionCommit(ctx, activityName, actionName, callerParams, options) {
3336
+ const actor = options?.actor, { stage, activity } = findActivityInCurrentStage(ctx, activityName), action = (activity.actions ?? []).find((a) => a.name === actionName);
3212
3337
  if (action === void 0)
3213
- throw new Error(`Action "${actionName}" not declared on task "${taskName}"`);
3338
+ throw new Error(`Action "${actionName}" not declared on activity "${activityName}"`);
3214
3339
  const can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan(ctx.instance, actor, options.grants) : void 0;
3215
3340
  if (!await ctxEvaluateCondition(ctx, action.filter, {
3216
- taskName,
3341
+ activityName,
3217
3342
  ...actor !== void 0 ? { actor } : {},
3218
3343
  ...can !== void 0 ? { vars: { can } } : {}
3219
3344
  }))
3220
- throw new Error(`Action "${actionName}" filter rejected on task "${taskName}"`);
3221
- const entry = findCurrentTasks(ctx.instance).find((t) => t.name === taskName);
3345
+ throw new Error(`Action "${actionName}" filter rejected on activity "${activityName}"`);
3346
+ const entry = findCurrentActivities(ctx.instance).find((t) => t.name === activityName);
3222
3347
  if (entry === void 0 || entry.status !== "active")
3223
3348
  throw new Error(
3224
- `Task "${taskName}" must be active to fire action "${actionName}"; status is ${entry?.status ?? "missing"}`
3349
+ `Activity "${activityName}" must be active to fire action "${actionName}"; status is ${entry?.status ?? "missing"}`
3225
3350
  );
3226
- const params = validateActionParams(action, taskName, callerParams);
3227
- return { stage, task, action, params };
3351
+ const params = validateActionParams(action, activityName, callerParams);
3352
+ return { stage, activity, action, params };
3228
3353
  }
3229
- async function commitAction(ctx, taskName, actionName, callerParams, options) {
3354
+ async function commitAction(ctx, activityName, actionName, callerParams, options) {
3230
3355
  const actor = options?.actor, { stage, action, params } = await resolveActionCommit(
3231
3356
  ctx,
3232
- taskName,
3357
+ activityName,
3233
3358
  actionName,
3234
3359
  callerParams,
3235
3360
  options
3236
- ), mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation, taskName), now = ctx.now;
3361
+ ), mutation = startMutation(ctx.instance), mutEntry = requireMutationActivityEntry(mutation, activityName), now = ctx.now;
3237
3362
  mutation.history.push({
3238
3363
  _key: randomKey(),
3239
3364
  _type: "actionFired",
3240
3365
  at: now,
3241
3366
  stage: stage.name,
3242
- task: taskName,
3367
+ activity: activityName,
3243
3368
  action: actionName,
3244
- ...actor !== void 0 ? { actor } : {}
3369
+ ...actor !== void 0 ? { actor, driverKind: driverKind(actor) } : {}
3245
3370
  });
3246
3371
  const statusBefore = mutEntry.status, ranOps = await runOps({
3247
3372
  ops: action.ops,
3248
3373
  mutation,
3249
3374
  stage: stage.name,
3250
- origin: { task: taskName, action: actionName },
3375
+ origin: { activity: activityName, action: actionName },
3251
3376
  params,
3252
3377
  actor,
3253
3378
  self: selfGdr(ctx.instance),
@@ -3255,14 +3380,14 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
3255
3380
  }), newStatus = mutEntry.status !== statusBefore ? mutEntry.status : void 0;
3256
3381
  return await queueEffects(ctx, mutation, action.effects, { kind: "action", name: actionName }, actor, {
3257
3382
  callerParams: params,
3258
- taskName
3383
+ activityName
3259
3384
  }), await persistThenDeploy(
3260
3385
  ctx,
3261
3386
  mutation,
3262
3387
  () => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
3263
3388
  ), {
3264
3389
  fired: !0,
3265
- task: taskName,
3390
+ activity: activityName,
3266
3391
  action: actionName,
3267
3392
  ...newStatus !== void 0 ? { newStatus } : {},
3268
3393
  ...ranOps.length > 0 ? { ranOps } : {}
@@ -3270,23 +3395,23 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
3270
3395
  }
3271
3396
  class ActionDisabledError extends Error {
3272
3397
  reason;
3273
- task;
3398
+ activity;
3274
3399
  action;
3275
3400
  constructor(args) {
3276
- super(formatDisabledReason(args.task, args.action, args.reason)), this.name = "ActionDisabledError", this.reason = args.reason, this.task = args.task, this.action = args.action;
3401
+ super(formatDisabledReason(args.activity, args.action, args.reason)), this.name = "ActionDisabledError", this.reason = args.reason, this.activity = args.activity, this.action = args.action;
3277
3402
  }
3278
3403
  }
3279
3404
  const disabledReasonDetail = {
3280
3405
  "filter-failed": (r) => `action filter returned false${r.detail ? ` (${r.detail})` : ""}`,
3281
- "task-not-active": (r) => `task status is "${r.status}"`,
3406
+ "activity-not-active": (r) => `activity status is "${r.status}"`,
3282
3407
  "stage-terminal": (r) => `stage "${r.stage}" is terminal`,
3283
3408
  "instance-completed": (r) => `instance completed at ${r.completedAt}`,
3284
3409
  "mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`,
3285
3410
  "requirements-unmet": (r) => `unmet requirement(s): ${r.unmetRequirements.join(", ")}`
3286
3411
  };
3287
- function formatDisabledReason(task, action, reason) {
3412
+ function formatDisabledReason(activity, action, reason) {
3288
3413
  const detail = disabledReasonDetail[reason.kind](reason);
3289
- return `Action "${task}:${action}" is not allowed: ${detail}`;
3414
+ return `Action "${activity}:${action}" is not allowed: ${detail}`;
3290
3415
  }
3291
3416
  class EditFieldDeniedError extends Error {
3292
3417
  reason;
@@ -3305,7 +3430,7 @@ const editDisabledReasonDetail = {
3305
3430
  function formatEditDisabledReason(target, reason) {
3306
3431
  const detail = editDisabledReasonDetail[reason.kind](
3307
3432
  reason
3308
- ), where = target.task !== void 0 ? `${target.task}.${target.field}` : target.field;
3433
+ ), where = target.activity !== void 0 ? `${target.activity}.${target.field}` : target.field;
3309
3434
  return `Field "${target.scope}:${where}" is not editable: ${detail}`;
3310
3435
  }
3311
3436
  async function editField(args) {
@@ -3337,7 +3462,7 @@ async function commitEdit(ctx, target, mode, value, options) {
3337
3462
  stage: stage.name,
3338
3463
  origin: {
3339
3464
  edit: !0,
3340
- ...slot.scope === "task" && slot.task !== void 0 ? { task: slot.task } : {}
3465
+ ...slot.scope === "activity" && slot.activity !== void 0 ? { activity: slot.activity } : {}
3341
3466
  },
3342
3467
  params: {},
3343
3468
  actor,
@@ -3356,7 +3481,7 @@ async function commitEdit(ctx, target, mode, value, options) {
3356
3481
  }
3357
3482
  async function assertSlotEditable(ctx, slot, options) {
3358
3483
  const actor = options?.actor, window = slotWindowOpen(ctx.instance, slot), can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan(ctx.instance, actor, options.grants) : void 0, predicateSatisfied = window.open && slot.effective !== !0 && slot.effective !== void 0 ? await ctxEvaluateCondition(ctx, slot.effective, {
3359
- ...slot.task !== void 0 ? { taskName: slot.task } : {},
3484
+ ...slot.activity !== void 0 ? { activityName: slot.activity } : {},
3360
3485
  ...actor !== void 0 ? { actor } : {},
3361
3486
  ...can !== void 0 ? { vars: { can } } : {}
3362
3487
  }) : slot.effective === !0, reason = editDisabledReason({
@@ -3382,18 +3507,18 @@ function slotTarget(slot) {
3382
3507
  return {
3383
3508
  scope: slot.scope,
3384
3509
  field: slot.name,
3385
- ...slot.task !== void 0 ? { task: slot.task } : {}
3510
+ ...slot.activity !== void 0 ? { activity: slot.activity } : {}
3386
3511
  };
3387
3512
  }
3388
3513
  function labelTarget(target) {
3389
3514
  return {
3390
- scope: target.scope ?? (target.task !== void 0 ? "task" : "workflow"),
3515
+ scope: target.scope ?? (target.activity !== void 0 ? "activity" : "workflow"),
3391
3516
  field: target.field,
3392
- ...target.task !== void 0 ? { task: target.task } : {}
3517
+ ...target.activity !== void 0 ? { activity: target.activity } : {}
3393
3518
  };
3394
3519
  }
3395
3520
  function describeTarget(target) {
3396
- const where = target.task !== void 0 ? `${target.task}.${target.field}` : target.field;
3521
+ const where = target.activity !== void 0 ? `${target.activity}.${target.field}` : target.field;
3397
3522
  return target.scope !== void 0 ? `${target.scope}:${where}` : where;
3398
3523
  }
3399
3524
  function bareIdFromSpawnRef(uri) {
@@ -3458,10 +3583,10 @@ async function dispatchGatedWrite(args) {
3458
3583
  ...ranOps !== void 0 ? { ranOps } : {}
3459
3584
  };
3460
3585
  }
3461
- function assertActionAllowed(evaluation, task, action) {
3462
- const actionEval = evaluation.currentStage.tasks.find((t) => t.task.name === task)?.actions.find((a) => a.action.name === action);
3586
+ function assertActionAllowed(evaluation, activity, action) {
3587
+ const actionEval = evaluation.currentStage.activities.find((t) => t.activity.name === activity)?.actions.find((a) => a.action.name === action);
3463
3588
  if (actionEval?.allowed === !1 && actionEval.disabledReason)
3464
- throw new ActionDisabledError({ task, action, reason: actionEval.disabledReason });
3589
+ throw new ActionDisabledError({ activity, action, reason: actionEval.disabledReason });
3465
3590
  }
3466
3591
  function assertEditAllowed(evaluation, target) {
3467
3592
  const slot = evaluation.editableSlots.filter((s) => editTargetMatches(s, target)).sort((a, b) => SCOPE_PRECEDENCE[a.scope] - SCOPE_PRECEDENCE[b.scope])[0];
@@ -3470,7 +3595,7 @@ function assertEditAllowed(evaluation, target) {
3470
3595
  target: {
3471
3596
  scope: slot.scope,
3472
3597
  field: slot.name,
3473
- ...slot.task !== void 0 ? { task: slot.task } : {}
3598
+ ...slot.activity !== void 0 ? { activity: slot.activity } : {}
3474
3599
  },
3475
3600
  reason: slot.disabledReason
3476
3601
  });
@@ -3495,11 +3620,11 @@ function buildEngineCallOptions(args) {
3495
3620
  };
3496
3621
  }
3497
3622
  async function applyAction(args) {
3498
- const { client, instance, task, action, params, actor, grants, clock, clientForGdr } = args, options = buildEngineCallOptions({ actor, grants, clock, clientForGdr });
3499
- return findOpenStageEntry(instance)?.tasks.find((t) => t.name === task)?.status === "pending" && await invokeTask({ client, instanceId: instance._id, task, options }), (await fireAction({
3623
+ const { client, instance, activity, action, params, actor, grants, clock, clientForGdr } = args, options = buildEngineCallOptions({ actor, grants, clock, clientForGdr });
3624
+ return findOpenStageEntry(instance)?.activities.find((t) => t.name === activity)?.status === "pending" && await invokeActivity({ client, instanceId: instance._id, activity, options }), (await fireAction({
3500
3625
  client,
3501
3626
  instanceId: instance._id,
3502
- task,
3627
+ activity,
3503
3628
  action,
3504
3629
  ...params !== void 0 ? { params } : {},
3505
3630
  options
@@ -3579,14 +3704,16 @@ function previewIds(ids) {
3579
3704
  }
3580
3705
  const workflow = {
3581
3706
  /**
3582
- * Deploy a set of workflow definitions as one call. Each lands as a
3583
- * {@link WORKFLOW_DEFINITION_TYPE} document; subsequent versions are
3584
- * stored alongside earlier ones `startInstance` picks the highest
3585
- * version by default. The SDK figures out the dependency order itself
3586
- * (children before parents that spawn them via
3587
- * `subworkflows.definition`), idempotently writes any definition
3588
- * whose content has changed, and reports a per-definition outcome
3589
- * (`created` / `updated` / `unchanged`).
3707
+ * Deploy a set of workflow definitions as one call. Definitions are
3708
+ * immutable and content-addressed: the author writes no version, and each
3709
+ * deploy compares a definition's content fingerprint to the latest version
3710
+ * already deployed under its name. Identical content is a no-op
3711
+ * (`unchanged`); any change mints the next version (`created`) — deploy
3712
+ * never patches a deployed version, so a definition can't change out from
3713
+ * under the instances pinned to it. `startInstance` picks the highest
3714
+ * version by default. The engine figures out the dependency order itself
3715
+ * (children before parents that spawn them via `subworkflows.definition`)
3716
+ * and reports a per-definition outcome (`created` / `unchanged`).
3590
3717
  *
3591
3718
  * Refs may point inside the batch OR at already-deployed definitions
3592
3719
  * in the lake — both are valid. A ref pointing at neither errors with
@@ -3599,31 +3726,15 @@ const workflow = {
3599
3726
  validateTag(tag);
3600
3727
  for (const def of definitions)
3601
3728
  validateDefinition(def);
3602
- const ordered = await sortByDependencies(client, definitions, tag, workflowResource), results = [], tx = client.transaction();
3729
+ const ordered = await sortByDependencies(client, definitions, tag), results = [], tx = client.transaction();
3603
3730
  let hasWrites = !1;
3604
3731
  for (const def of ordered) {
3605
- const id = definitionDocId(workflowResource, tag, def.name, def.version), expected = {
3606
- ...def,
3607
- _id: id,
3608
- _type: schema.WORKFLOW_DEFINITION_TYPE,
3609
- tag
3610
- }, existing = await client.getDocument(id);
3611
- if (existing && existing.tag === tag && isDefinitionUnchanged(existing, expected)) {
3612
- results.push({ definition: def.name, version: def.version, status: "unchanged" });
3732
+ const latest = await loadLatestDeployed(client, def.name, tag), plan = planDefinitionDeploy(def, latest, { tag, workflowResource });
3733
+ if (plan.status === "unchanged") {
3734
+ results.push({ definition: def.name, version: plan.version, status: "unchanged" });
3613
3735
  continue;
3614
3736
  }
3615
- if (existing) {
3616
- const expectedKeys = new Set(Object.keys(expected)), toUnset = Object.keys(existing).filter(
3617
- (k) => !k.startsWith("_") && !expectedKeys.has(k)
3618
- ), patch = client.patch(id).set(expected).ifRevisionId(existing._rev);
3619
- toUnset.length > 0 && patch.unset(toUnset), tx.patch(patch);
3620
- } else
3621
- tx.create(expected);
3622
- hasWrites = !0, results.push({
3623
- definition: def.name,
3624
- version: def.version,
3625
- status: existing ? "updated" : "created"
3626
- });
3737
+ tx.create(plan.document), hasWrites = !0, results.push({ definition: def.name, version: plan.version, status: "created" });
3627
3738
  }
3628
3739
  return hasWrites && await tx.commit(), { results };
3629
3740
  },
@@ -3639,8 +3750,8 @@ const workflow = {
3639
3750
  * Spawn a new workflow instance from a deployed definition.
3640
3751
  *
3641
3752
  * Pins the snapshot at start-time, seeds `effectsContext`, enters
3642
- * the initial stage (which builds taskStatus, queues onEnter
3643
- * effects, auto-activates tasks). Then cascades auto-transitions
3753
+ * the initial stage (which builds activityStatus, queues onEnter
3754
+ * effects, auto-activates activities). Then cascades auto-transitions
3644
3755
  * until stable. Returns the resulting instance.
3645
3756
  */
3646
3757
  startInstance: async (args) => {
@@ -3660,7 +3771,7 @@ const workflow = {
3660
3771
  throw new Error(
3661
3772
  `Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
3662
3773
  );
3663
- const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedFields = await resolveDeclaredFields({
3774
+ const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], fieldDiscards = [], resolvedFields = await resolveDeclaredFields({
3664
3775
  entryDefs: definition.fields ?? [],
3665
3776
  initialFields: initialFields ?? [],
3666
3777
  ctx: {
@@ -3670,7 +3781,8 @@ const workflow = {
3670
3781
  tag,
3671
3782
  workflowResource,
3672
3783
  definitionName: definition.name,
3673
- ...perspective !== void 0 ? { perspective } : {}
3784
+ ...perspective !== void 0 ? { perspective } : {},
3785
+ recordDiscard: recordFieldDiscards(fieldDiscards, "workflow", now)
3674
3786
  },
3675
3787
  randomKey
3676
3788
  }), effectivePerspective = perspective ?? derivePerspectiveFromFields(resolvedFields), base = buildInstanceBase({
@@ -3680,18 +3792,20 @@ const workflow = {
3680
3792
  workflowResource,
3681
3793
  definitionName: definition.name,
3682
3794
  pinnedVersion: definition.version,
3795
+ pinnedContentHash: definition.contentHash,
3683
3796
  definition,
3684
3797
  fields: resolvedFields,
3685
3798
  effectsContext: effectsContextEntries,
3686
3799
  ancestors: ancestors ?? [],
3687
3800
  perspective: effectivePerspective,
3688
3801
  initialStage: definition.initialStage,
3689
- actor
3802
+ actor,
3803
+ ...fieldDiscards.length > 0 ? { extraHistory: fieldDiscards } : {}
3690
3804
  });
3691
3805
  return await client.create(base, SYNC_COMMIT), await primeInitialStage(client, id, actor, clientForGdr, clock), await cascade(client, id, actor, clientForGdr, clock), reload(client, id, tag);
3692
3806
  },
3693
3807
  /**
3694
- * Fire an action against a task. If the task is pending, it is
3808
+ * Fire an action against an activity. If the activity is pending, it is
3695
3809
  * auto-invoked first (pending → active) so callers don't have to know
3696
3810
  * the lifecycle. Cascades auto-transitions and propagates to ancestors
3697
3811
  * after the action commits.
@@ -3705,13 +3819,13 @@ const workflow = {
3705
3819
  client,
3706
3820
  tag,
3707
3821
  instanceId,
3708
- task,
3822
+ activity,
3709
3823
  action,
3710
3824
  params,
3711
3825
  idempotent,
3712
3826
  resourceClients
3713
3827
  } = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
3714
- return findOpenStageEntry(before)?.tasks.find((t) => t.name === task) === void 0 && idempotent === !0 ? { instance: before, cascaded: 0, fired: !1 } : dispatchGatedWrite({
3828
+ return findOpenStageEntry(before)?.activities.find((t) => t.name === activity) === void 0 && idempotent === !0 ? { instance: before, cascaded: 0, fired: !1 } : dispatchGatedWrite({
3715
3829
  client,
3716
3830
  tag,
3717
3831
  instanceId,
@@ -3721,10 +3835,10 @@ const workflow = {
3721
3835
  clock,
3722
3836
  before,
3723
3837
  ...resourceClients !== void 0 ? { resourceClients } : {},
3724
- apply: (instance, evaluation) => (assertActionAllowed(evaluation, task, action), applyAction({
3838
+ apply: (instance, evaluation) => (assertActionAllowed(evaluation, activity, action), applyAction({
3725
3839
  client,
3726
3840
  instance,
3727
- task,
3841
+ activity,
3728
3842
  action,
3729
3843
  actor,
3730
3844
  ...access.grants !== void 0 ? { grants: access.grants } : {},
@@ -3805,7 +3919,7 @@ const workflow = {
3805
3919
  * Re-evaluate auto-transitions and resolve due waits until stable.
3806
3920
  *
3807
3921
  * Used by the runtime after any event that might affect the workflow
3808
- * but isn't itself a task action: a subject doc was patched, a sibling
3922
+ * but isn't itself an activity action: a subject doc was patched, a sibling
3809
3923
  * workflow completed, the clock crossed a `completeWhen` deadline, etc.
3810
3924
  * The runtime doesn't need to know what changed — it just nudges
3811
3925
  * affected instances and the engine re-evaluates.
@@ -3915,8 +4029,8 @@ const workflow = {
3915
4029
  * declared by a `doc.ref` / `doc.refs` entry in scope), then
3916
4030
  * evaluates the supplied GROQ in groq-js against that dataset. The
3917
4031
  * rendered scope is auto-bound: `$self`, `$fields`, `$parent`,
3918
- * `$ancestors`, `$stage`, `$now`, `$effects`, `$tasks`,
3919
- * `$allTasksDone`, `$anyTaskFailed` — ids in GDR URI form to match
4032
+ * `$ancestors`, `$stage`, `$now`, `$effects`, `$activities`,
4033
+ * `$allActivitiesDone`, `$anyActivityFailed` — ids in GDR URI form to match
3920
4034
  * the snapshot's keying.
3921
4035
  *
3922
4036
  * Use when an external consumer (a UI, a debug pane, a test) wants
@@ -3969,34 +4083,83 @@ const workflow = {
3969
4083
  * List the actions an actor could fire on an instance's current stage,
3970
4084
  * each flagged `allowed` (with a structured `disabledReason` when not).
3971
4085
  * Projects the instance from the actor's perspective and flattens its
3972
- * tasks' actions. Returns the evaluation alongside the actions so a
4086
+ * activities' actions. Returns the evaluation alongside the actions so a
3973
4087
  * consumer can read the instance/stage context. Pure read.
3974
4088
  */
3975
4089
  availableActions: async (args) => {
3976
4090
  const evaluation = await evaluateInstance(args);
3977
- return { evaluation, actions: availableActions(evaluation.currentStage.tasks) };
4091
+ return { evaluation, actions: availableActions(evaluation.currentStage.activities) };
3978
4092
  },
3979
4093
  /**
3980
4094
  * Materialised spawned children of a parent instance.
3981
4095
  *
3982
4096
  * Walks `history` for `spawned` events — the durable
3983
- * record. (The per-task `spawnedInstances` field on the active stage
4097
+ * record. (The per-activity `spawnedInstances` field on the active stage
3984
4098
  * only exists while that stage is current; history survives.) Strips
3985
4099
  * the GDR URI on each `instanceRef` to a bare `_id`, fetches the
3986
4100
  * instances, drops any that aren't visible to this engine's tag, and
3987
4101
  * returns them sorted by `startedAt` ascending.
3988
4102
  *
3989
- * Pass `task` to restrict to a single spawning task on the parent.
4103
+ * Pass `activity` to restrict to a single spawning activity on the parent.
3990
4104
  */
3991
4105
  children: async (args) => {
3992
- const { client, tag, instanceId, task } = args;
4106
+ const { client, tag, instanceId, activity } = args;
3993
4107
  validateTag(tag);
3994
- const parent = await reload(client, instanceId, tag), isSpawned = (h) => h._type === "spawned", ids = parent.history.filter(isSpawned).filter((h) => task === void 0 || h.task === task).flatMap((h) => bareIdFromSpawnRef(h.instanceRef.id));
4108
+ const parent = await reload(client, instanceId, tag), isSpawned = (h) => h._type === "spawned", ids = parent.history.filter(isSpawned).filter((h) => activity === void 0 || h.activity === activity).flatMap((h) => bareIdFromSpawnRef(h.instanceRef.id));
3995
4109
  return ids.length === 0 ? [] : client.fetch(
3996
4110
  `*[_id in $ids && ${tagScopeFilter()}] | order(startedAt asc)`,
3997
4111
  { ids, tag }
3998
4112
  );
3999
4113
  },
4114
+ /**
4115
+ * Every in-flight instance whose reactive watch-set includes `document` —
4116
+ * the reverse of {@link subscriptionDocumentsForInstance}.
4117
+ *
4118
+ * For a non-reactive, content-change-driven runtime (a Sanity Function, an
4119
+ * Inngest/durable worker, any server) that holds no instances in memory: a
4120
+ * document changed; which instances should it `tick`? The watch-set covers
4121
+ * the instance itself, its ancestors, and the docs named by
4122
+ * `doc.ref` / `doc.refs` / `release.ref` field entries on the workflow scope
4123
+ * **and the current stage** — so a hand-rolled GROQ over `fields[]` gets it
4124
+ * subtly wrong (misses stage-scope refs, `release.ref`, ancestors).
4125
+ *
4126
+ * A coarse GROQ prefilter narrows candidates server-side (in-flight,
4127
+ * tag-scoped, mentioning the doc anywhere in state); the authoritative match
4128
+ * is {@link instanceWatchesDocument}, derived from `collectWatchRefs`, so the
4129
+ * reverse stays in lockstep with the forward set and honours the
4130
+ * open-stage-only rule the prefilter deliberately over-approximates.
4131
+ *
4132
+ * Single-resource: instances always live in the engine's own resource, so
4133
+ * this reads one client (unlike {@link guardsForInstance}, whose guard docs
4134
+ * are scattered across the watched resources). "Routed by resource" applies
4135
+ * to the **subject**: a cross-dataset doc is matched by its full,
4136
+ * resource-qualified GDR URI, never its bare id, so an instance watching
4137
+ * `dataset:A:ds:doc` is not matched by a change to `dataset:B:ds:doc`.
4138
+ *
4139
+ * `document` must be a resource-qualified GDR URI; a bare id is rejected
4140
+ * (it can't be resource-routed and would silently mismatch). Sorted by
4141
+ * `startedAt` ascending.
4142
+ */
4143
+ instancesForDocument: async (args) => {
4144
+ const { client, tag, document } = args;
4145
+ if (validateTag(tag), !isGdrUri(document))
4146
+ throw new Error(
4147
+ `workflow.instancesForDocument: "document" must be a resource-qualified GDR URI (e.g. "dataset:project:dataset:doc-id"); got: ${JSON.stringify(document)}`
4148
+ );
4149
+ const fieldRefsDoc = "count(fields[value.id == $document]) > 0 || count(fields[$document in value[].id]) > 0", query = `*[
4150
+ _type == "${WORKFLOW_INSTANCE_TYPE}" && ${tagScopeFilter()} && !defined(completedAt) && (
4151
+ _id == $bareId ||
4152
+ $document in ancestors[].id ||
4153
+ ${fieldRefsDoc} ||
4154
+ count(stages[${fieldRefsDoc}]) > 0
4155
+ )
4156
+ ] | order(startedAt asc)`;
4157
+ return (await client.fetch(query, {
4158
+ tag,
4159
+ document,
4160
+ bareId: extractDocumentId(document)
4161
+ })).filter((instance) => instanceWatchesDocument(instance, document));
4162
+ },
4000
4163
  /**
4001
4164
  * Permission helpers — Sanity ACL grants evaluated against documents
4002
4165
  * via GROQ. Used by `workflow.evaluate` to soft-gate actions when the
@@ -4050,10 +4213,13 @@ function walkEffectNames(def, visit) {
4050
4213
  for (const e of effects ?? []) visit(e.name, location);
4051
4214
  };
4052
4215
  for (const stage of def.stages ?? []) {
4053
- for (const t of stage.tasks ?? []) {
4054
- visitEffects(t.effects, `stage[${stage.name}].task[${t.name}].effects`);
4216
+ for (const t of stage.activities ?? []) {
4217
+ visitEffects(t.effects, `stage[${stage.name}].activity[${t.name}].effects`);
4055
4218
  for (const a of t.actions ?? [])
4056
- visitEffects(a.effects, `stage[${stage.name}].task[${t.name}].action[${a.name}].effects`);
4219
+ visitEffects(
4220
+ a.effects,
4221
+ `stage[${stage.name}].activity[${t.name}].action[${a.name}].effects`
4222
+ );
4057
4223
  }
4058
4224
  for (const tr of stage.transitions ?? [])
4059
4225
  visitEffects(tr.effects, `stage[${stage.name}].transition[${tr.name}].effects`);
@@ -4069,7 +4235,7 @@ async function drainEffectsInternal(args) {
4069
4235
  effectHandlers,
4070
4236
  missingHandler,
4071
4237
  logger
4072
- } = args, drainerActor = args.access?.actor ?? { kind: "system", id: "engine.drainEffects" }, completionAccess = {
4238
+ } = args, routeGdr = buildClientForGdr(client, resourceClients), clientFor = (ref) => routeGdr(parseGdr(typeof ref == "string" ? ref : ref.id)), drainerActor = args.access?.actor ?? engineSystemActor("drainEffects"), completionAccess = {
4073
4239
  actor: drainerActor,
4074
4240
  ...args.access?.grants !== void 0 ? { grants: args.access.grants } : {}
4075
4241
  };
@@ -4088,6 +4254,7 @@ async function drainEffectsInternal(args) {
4088
4254
  if (!await claimPendingEffect(client, before, candidate._key, drainerActor)) continue;
4089
4255
  const { outputs, dispatchError } = await dispatchEffect(handler, candidate, {
4090
4256
  client,
4257
+ clientFor,
4091
4258
  instanceId,
4092
4259
  logger
4093
4260
  });
@@ -4142,6 +4309,7 @@ async function dispatchEffect(handler, candidate, ctx) {
4142
4309
  try {
4143
4310
  const result = await handler(candidate.params, {
4144
4311
  client: ctx.client,
4312
+ clientFor: ctx.clientFor,
4145
4313
  instanceId: ctx.instanceId,
4146
4314
  effectKey: candidate._key,
4147
4315
  log: (message, extra) => ctx.logger(`effect.${candidate.name}`).info(message, extra)
@@ -4230,13 +4398,13 @@ function createInstanceSession(args) {
4230
4398
  return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: cascaded > 0 };
4231
4399
  });
4232
4400
  },
4233
- fireAction({ task, action, params }) {
4401
+ fireAction({ activity, action, params }) {
4234
4402
  return commit(async (actor, held) => {
4235
- assertActionAllowed(await evaluateWith(held, heldGuards), task, action);
4403
+ assertActionAllowed(await evaluateWith(held, heldGuards), activity, action);
4236
4404
  const { grants } = await access(), ranOps = await applyAction({
4237
4405
  client,
4238
4406
  instance,
4239
- task,
4407
+ activity,
4240
4408
  action,
4241
4409
  actor,
4242
4410
  clientForGdr,
@@ -4343,13 +4511,14 @@ function createEngine(args) {
4343
4511
  definition,
4344
4512
  ...resourceClients !== void 0 ? { resourceClients } : {}
4345
4513
  }),
4346
- children: ({ instanceId, task }) => workflow.children({
4514
+ children: ({ instanceId, activity }) => workflow.children({
4347
4515
  client,
4348
4516
  tag,
4349
4517
  workflowResource,
4350
4518
  instanceId,
4351
- ...task !== void 0 ? { task } : {}
4519
+ ...activity !== void 0 ? { activity } : {}
4352
4520
  }),
4521
+ instancesForDocument: ({ document }) => workflow.instancesForDocument({ client, tag, workflowResource, document }),
4353
4522
  query: ({ groq, params }) => workflow.query({
4354
4523
  client,
4355
4524
  tag,
@@ -4395,33 +4564,25 @@ function createEngine(args) {
4395
4564
  })
4396
4565
  };
4397
4566
  }
4398
- function docIdFor(def, target) {
4399
- return definitionDocId(target.workflowResource, target.tag, def.name, def.version);
4400
- }
4401
- function diffStatus(existing, expected) {
4402
- return existing === void 0 ? "create" : isDefinitionUnchanged(existing, expected) ? "unchanged" : "update";
4403
- }
4404
- function diffEntry(def, existingRaw, target) {
4405
- const docId = docIdFor(def, target), expected = {
4406
- ...def,
4407
- _id: docId,
4408
- _type: schema.WORKFLOW_DEFINITION_TYPE,
4409
- tag: target.tag
4410
- }, status = diffStatus(existingRaw, expected), existing = existingRaw ? stripSystemFields(existingRaw) : void 0;
4567
+ function diffEntry(def, latestRaw, target) {
4568
+ const plan = planDefinitionDeploy(def, asLatest(latestRaw), target), existing = latestRaw ? stripSystemFields(latestRaw) : void 0;
4411
4569
  return {
4412
4570
  name: def.name,
4413
- version: def.version,
4414
- status,
4415
- docId,
4416
- expected,
4571
+ version: plan.version,
4572
+ status: plan.status,
4573
+ docId: plan.docId,
4574
+ expected: plan.document,
4417
4575
  ...existing !== void 0 ? { existing } : {}
4418
4576
  };
4419
4577
  }
4578
+ function asLatest(raw) {
4579
+ return raw === void 0 ? void 0 : { version: typeof raw.version == "number" ? raw.version : 0, ...typeof raw.contentHash == "string" ? { contentHash: raw.contentHash } : {} };
4580
+ }
4420
4581
  async function computeDiffEntries(client, defs, target) {
4421
4582
  const entries = [];
4422
4583
  for (const def of defs) {
4423
- const existingRaw = await client.getDocument(docIdFor(def, target)) ?? void 0;
4424
- entries.push(diffEntry(def, existingRaw, target));
4584
+ const latest = await loadLatestDeployed(client, def.name, target.tag);
4585
+ entries.push(diffEntry(def, latest, target));
4425
4586
  }
4426
4587
  return entries;
4427
4588
  }
@@ -4434,17 +4595,17 @@ const HISTORY_DISPLAY = {
4434
4595
  title: "Stage exited",
4435
4596
  description: "The instance left a stage on the way to the next one."
4436
4597
  },
4437
- taskActivated: {
4438
- title: "Task activated",
4439
- description: "A task flipped from `pending` to `active` and its onEnter effects queued."
4598
+ activityActivated: {
4599
+ title: "Activity activated",
4600
+ description: "An activity flipped from `pending` to `active` and its onEnter effects queued."
4440
4601
  },
4441
- taskStatusChanged: {
4442
- title: "Task status changed",
4443
- description: "An action or op flipped a task's status (active \u2192 done / skipped / failed)."
4602
+ activityStatusChanged: {
4603
+ title: "Activity status changed",
4604
+ description: "An action or op flipped an activity's status (active \u2192 done / skipped / failed)."
4444
4605
  },
4445
4606
  actionFired: {
4446
4607
  title: "Action fired",
4447
- description: "An action was invoked against a task \u2014 ops ran, status may have flipped, effects queued."
4608
+ description: "An action was invoked against an activity \u2014 ops ran, status may have flipped, effects queued."
4448
4609
  },
4449
4610
  transitionFired: {
4450
4611
  title: "Transition fired",
@@ -4460,7 +4621,7 @@ const HISTORY_DISPLAY = {
4460
4621
  },
4461
4622
  spawned: {
4462
4623
  title: "Spawned child",
4463
- description: "A task's `subworkflows` declaration spawned a child workflow instance."
4624
+ description: "An activity's `subworkflows` declaration spawned a child workflow instance."
4464
4625
  },
4465
4626
  aborted: {
4466
4627
  title: "Instance aborted",
@@ -4469,6 +4630,10 @@ const HISTORY_DISPLAY = {
4469
4630
  opApplied: {
4470
4631
  title: "Op applied",
4471
4632
  description: "An inline field-mutation op (field.set / field.append / status.set / etc.) ran during an action commit."
4633
+ },
4634
+ fieldQueryDiscarded: {
4635
+ title: "Query result discarded",
4636
+ description: "A query-sourced field's GROQ result didn't fit the field's declared kind, so it was discarded and the field fell back to its default."
4472
4637
  }
4473
4638
  }, FIELD_SLOT_DISPLAY = {
4474
4639
  "doc.ref": {
@@ -4483,50 +4648,58 @@ const HISTORY_DISPLAY = {
4483
4648
  title: "Content Release reference",
4484
4649
  description: "GDR pointer at a Content Release system doc, carrying the release name the engine derives the instance's read perspective from."
4485
4650
  },
4486
- query: {
4487
- title: "Query result",
4488
- description: "Snapshot of a GROQ projection captured at entry-resolve time; resolvedAt is stamped on the value."
4489
- },
4490
- "value.string": {
4651
+ string: {
4491
4652
  title: "String value",
4492
- description: "Free-form string entry."
4653
+ description: "Free-form single-line string entry."
4493
4654
  },
4494
- "value.url": {
4495
- title: "URL value",
4496
- description: "Validated URL entry."
4655
+ text: {
4656
+ title: "Text value",
4657
+ description: "Free-form multiline string entry."
4497
4658
  },
4498
- "value.number": {
4659
+ number: {
4499
4660
  title: "Number value",
4500
4661
  description: "Numeric entry."
4501
4662
  },
4502
- "value.boolean": {
4663
+ boolean: {
4503
4664
  title: "Boolean value",
4504
4665
  description: "True/false entry."
4505
4666
  },
4506
- "value.dateTime": {
4667
+ date: {
4668
+ title: "Date value",
4669
+ description: "Date-only (YYYY-MM-DD) entry, no time component."
4670
+ },
4671
+ datetime: {
4507
4672
  title: "Date / time value",
4508
- description: "ISO-timestamp entry."
4673
+ description: "ISO-8601 timestamp entry."
4509
4674
  },
4510
- "value.actor": {
4511
- title: "Actor value",
4512
- description: "A typed (user|ai|system) actor identity."
4675
+ url: {
4676
+ title: "URL value",
4677
+ description: "Validated URL entry."
4513
4678
  },
4514
- checklist: {
4515
- title: "Checklist",
4516
- description: "Append-only list of checkable items; ops add/update/remove entries."
4679
+ actor: {
4680
+ title: "Actor value",
4681
+ description: "A typed (user|ai|system) actor identity \u2014 a concrete principal."
4517
4682
  },
4518
- notes: {
4519
- title: "Notes log",
4520
- description: "Append-only structured-note log (signoffs, comments, audit rows)."
4683
+ assignee: {
4684
+ title: "Assignee",
4685
+ description: "A single typed (user|role) assignee \u2014 who is expected/responsible."
4521
4686
  },
4522
4687
  assignees: {
4523
4688
  title: "Assignees",
4524
4689
  description: "Ordered list of typed (user|role) assignees."
4690
+ },
4691
+ object: {
4692
+ title: "Object",
4693
+ description: "An object with named sub-fields; the value is keyed by sub-field name."
4694
+ },
4695
+ array: {
4696
+ title: "Array",
4697
+ description: "An array of objects shaped by the declared `of` sub-fields; ops add/update/remove rows."
4525
4698
  }
4526
4699
  }, OP_DISPLAY = {
4527
4700
  "field.set": {
4528
4701
  title: "Set field entry",
4529
- description: "Overwrite a field entry's value with a resolved Source."
4702
+ description: "Overwrite a field entry's value with a resolved value expression."
4530
4703
  },
4531
4704
  "field.unset": {
4532
4705
  title: "Unset field entry",
@@ -4534,7 +4707,7 @@ const HISTORY_DISPLAY = {
4534
4707
  },
4535
4708
  "field.append": {
4536
4709
  title: "Append to field entry",
4537
- description: "Push a resolved item onto an array-kind entry (notes, checklist, assignees, refs)."
4710
+ description: "Push a resolved item onto an array-kind entry (array, assignees, doc.refs)."
4538
4711
  },
4539
4712
  "field.updateWhere": {
4540
4713
  title: "Update matching rows",
@@ -4545,8 +4718,8 @@ const HISTORY_DISPLAY = {
4545
4718
  description: "Drop rows of an array-kind entry that match the predicate."
4546
4719
  },
4547
4720
  "status.set": {
4548
- title: "Set task status",
4549
- description: "Flip a task's status explicitly (the action-level `status:` sugar desugars to this)."
4721
+ title: "Set activity status",
4722
+ description: "Flip an activity's status explicitly (the action-level `status:` sugar desugars to this)."
4550
4723
  }
4551
4724
  }, EFFECTS_CONTEXT_DISPLAY = {
4552
4725
  "effectsContext.string": {
@@ -4586,6 +4759,35 @@ const HISTORY_DISPLAY = {
4586
4759
  title: "Workflow instance",
4587
4760
  description: "A running (or finished) workflow against its declared fields."
4588
4761
  }
4762
+ }, ACTIVITY_KIND_DISPLAY = {
4763
+ user: {
4764
+ title: "User activity",
4765
+ description: "A person acts via the app \u2014 renders action buttons ranked by outcome."
4766
+ },
4767
+ service: {
4768
+ title: "Service activity",
4769
+ description: "An automated system or effect does the work \u2014 renders effect drain status."
4770
+ },
4771
+ script: {
4772
+ title: "Script activity",
4773
+ description: "The engine runs a step inline and resolves on activation \u2014 an audit row."
4774
+ },
4775
+ manual: {
4776
+ title: "Manual activity",
4777
+ description: "Work done off-system, acknowledged by a person or verified by a predicate \u2014 renders an instruction card with a deep-link."
4778
+ },
4779
+ receive: {
4780
+ title: "Receive activity",
4781
+ description: 'No actor; the engine waits on a condition \u2014 renders "waiting until \u2026", no buttons.'
4782
+ }
4783
+ }, DRIVER_KIND_DISPLAY = {
4784
+ person: { title: "Person", description: "A human fired this action." },
4785
+ agent: { title: "Agent", description: "An AI agent fired this action." },
4786
+ service: { title: "Service", description: "An external system or Function fired this action." },
4787
+ engine: {
4788
+ title: "Engine",
4789
+ description: "The workflow engine itself drove this \u2014 a gate, machine step, or housekeeping."
4790
+ }
4589
4791
  }, DISPLAY = {
4590
4792
  ...HISTORY_DISPLAY,
4591
4793
  ...FIELD_SLOT_DISPLAY,
@@ -4600,8 +4802,11 @@ function displayDescription(typeKey) {
4600
4802
  if (typeKey)
4601
4803
  return DISPLAY[typeKey]?.description;
4602
4804
  }
4805
+ exports.ACTIVITY_KINDS = schema.ACTIVITY_KINDS;
4806
+ exports.DRIVER_KINDS = schema.DRIVER_KINDS;
4603
4807
  exports.WORKFLOW_DEFINITION_TYPE = schema.WORKFLOW_DEFINITION_TYPE;
4604
4808
  exports.isStartableDefinition = schema.isStartableDefinition;
4809
+ exports.ACTIVITY_KIND_DISPLAY = ACTIVITY_KIND_DISPLAY;
4605
4810
  exports.AUTHORING_DISPLAY = AUTHORING_DISPLAY;
4606
4811
  exports.ActionDisabledError = ActionDisabledError;
4607
4812
  exports.ActionParamsInvalidError = ActionParamsInvalidError;
@@ -4610,6 +4815,7 @@ exports.ConcurrentEditFieldError = ConcurrentEditFieldError;
4610
4815
  exports.ConcurrentFireActionError = ConcurrentFireActionError;
4611
4816
  exports.DEFAULT_CONTENT_PERSPECTIVE = DEFAULT_CONTENT_PERSPECTIVE;
4612
4817
  exports.DISPLAY = DISPLAY;
4818
+ exports.DRIVER_KIND_DISPLAY = DRIVER_KIND_DISPLAY;
4613
4819
  exports.EFFECTS_CONTEXT_DISPLAY = EFFECTS_CONTEXT_DISPLAY;
4614
4820
  exports.EditFieldDeniedError = EditFieldDeniedError;
4615
4821
  exports.FIELD_SLOT_DISPLAY = FIELD_SLOT_DISPLAY;
@@ -4625,6 +4831,7 @@ exports.WORKFLOW_INSTANCE_TYPE = WORKFLOW_INSTANCE_TYPE;
4625
4831
  exports.WorkflowStateDivergedError = WorkflowStateDivergedError;
4626
4832
  exports.abortReason = abortReason;
4627
4833
  exports.actionVerdict = actionVerdict;
4834
+ exports.activityKind = activityKind;
4628
4835
  exports.availableActions = availableActions;
4629
4836
  exports.buildSnapshot = buildSnapshot;
4630
4837
  exports.compileGuard = compileGuard;
@@ -4635,11 +4842,13 @@ exports.datasetResourceParts = datasetResourceParts;
4635
4842
  exports.defaultLoggerFactory = defaultLoggerFactory;
4636
4843
  exports.denyingGuards = denyingGuards;
4637
4844
  exports.deployStageGuards = deployStageGuards;
4845
+ exports.deriveActivityKind = deriveActivityKind;
4638
4846
  exports.diagnoseInputFromEvaluation = diagnoseInputFromEvaluation;
4639
4847
  exports.diagnoseInstance = diagnoseInstance;
4640
4848
  exports.diffEntry = diffEntry;
4641
4849
  exports.displayDescription = displayDescription;
4642
4850
  exports.displayTitle = displayTitle;
4851
+ exports.driverKind = driverKind;
4643
4852
  exports.effectsContextMap = effectsContextMap;
4644
4853
  exports.evaluateFromSnapshot = evaluateFromSnapshot;
4645
4854
  exports.evaluateMutationGuard = evaluateMutationGuard;
@@ -4651,8 +4860,9 @@ exports.guardMatches = guardMatches;
4651
4860
  exports.guardsForDefinition = guardsForDefinition;
4652
4861
  exports.guardsForInstance = guardsForInstance;
4653
4862
  exports.guardsForResource = guardsForResource;
4863
+ exports.hashDefinitionContent = hashDefinitionContent;
4654
4864
  exports.instanceGuardQuery = instanceGuardQuery;
4655
- exports.isDefinitionUnchanged = isDefinitionUnchanged;
4865
+ exports.instanceWatchesDocument = instanceWatchesDocument;
4656
4866
  exports.isGdr = isGdr;
4657
4867
  exports.isTerminalStage = isTerminalStage;
4658
4868
  exports.lakeGuardId = lakeGuardId;