@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.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { parse, evaluate } from "groq-js";
2
- import { actorFulfillsRole, isTerminalTaskStatus, WORKFLOW_DEFINITION_TYPE, andConditions, DOCUMENT_VALUE_PERMISSIONS } from "./_chunks-es/schema.js";
3
- import { isStartableDefinition } from "./_chunks-es/schema.js";
2
+ import { actorFulfillsRole, isTerminalActivityStatus, WORKFLOW_DEFINITION_TYPE, andConditions, DOCUMENT_VALUE_PERMISSIONS } from "./_chunks-es/schema.js";
3
+ import { ACTIVITY_KINDS, DRIVER_KINDS, isStartableDefinition } from "./_chunks-es/schema.js";
4
4
  import * as v from "valibot";
5
5
  function findOpenStageEntry(host) {
6
6
  return host.stages.find((s) => s.name === host.currentStage && s.exitedAt === void 0);
@@ -108,7 +108,7 @@ function isGdr(value) {
108
108
  return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
109
109
  }
110
110
  function buildParams(args) {
111
- const { instance, now, snapshot, extra } = args, currentTasks2 = findOpenStageEntry(instance)?.tasks ?? [];
111
+ const { instance, now, snapshot, extra } = args, currentActivities2 = findOpenStageEntry(instance)?.activities ?? [];
112
112
  return {
113
113
  self: selfGdr(instance),
114
114
  fields: renderedFields(instance.fields ?? [], snapshot),
@@ -119,10 +119,12 @@ function buildParams(args) {
119
119
  /** `$effects` — parent context handoff by entry name, completed-effect
120
120
  * outputs namespaced under the effect's name (`$effects['x.y'].out`). */
121
121
  effects: effectsContextMap(instance),
122
- /** `$tasks` — the current stage's task rows, statuses included. */
123
- tasks: currentTasks2,
124
- allTasksDone: currentTasks2.every((t) => t.status === "done" || t.status === "skipped"),
125
- anyTaskFailed: currentTasks2.some((t) => t.status === "failed"),
122
+ /** `$activities` — the current stage's activity rows, statuses included. */
123
+ activities: currentActivities2,
124
+ allActivitiesDone: currentActivities2.every(
125
+ (activity) => activity.status === "done" || activity.status === "skipped"
126
+ ),
127
+ anyActivityFailed: currentActivities2.some((activity) => activity.status === "failed"),
126
128
  ...extra
127
129
  };
128
130
  }
@@ -134,15 +136,15 @@ function renderedFields(entries, snapshot) {
134
136
  function renderedValue(entry, snapshot) {
135
137
  return entry._type !== "doc.ref" || entry.value === null || snapshot === void 0 ? entry.value : snapshot.docs.find((d) => d._id === entry.value.id) ?? entry.value;
136
138
  }
137
- function scopedFieldOverlay(instance, snapshot, taskName) {
139
+ function scopedFieldOverlay(instance, snapshot, activityName) {
138
140
  const stageEntry = findOpenStageEntry(instance);
139
141
  if (stageEntry === void 0) return {};
140
- const stageFields = renderedFields(stageEntry.fields ?? [], snapshot), task = taskName ? stageEntry.tasks.find((t) => t.name === taskName) : void 0;
141
- return { ...stageFields, ...renderedFields(task?.fields ?? [], snapshot) };
142
+ const stageFields = renderedFields(stageEntry.fields ?? [], snapshot), activity = activityName ? stageEntry.activities.find((t) => t.name === activityName) : void 0;
143
+ return { ...stageFields, ...renderedFields(activity?.fields ?? [], snapshot) };
142
144
  }
143
- function assignedFor(instance, taskName, actor, roleAliases) {
145
+ function assignedFor(instance, activityName, actor, roleAliases) {
144
146
  if (actor === void 0) return !1;
145
- const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.fields?.find((s) => s._type === "assignees");
147
+ const entry = findOpenStageEntry(instance)?.activities.find((t) => t.name === activityName)?.fields?.find((s) => s._type === "assignees");
146
148
  return entry === void 0 ? !1 : entry.value.some(
147
149
  (a) => a.type === "user" ? a.id === actor.id : actorFulfillsRole(actor.roles, a.role, roleAliases)
148
150
  );
@@ -274,7 +276,7 @@ function validateDefinition(definition) {
274
276
  validateStage(v2, stage);
275
277
  if (v2.errors.length > 0)
276
278
  throw new Error(
277
- `defineWorkflow("${definition.name}", v${definition.version}): ${v2.errors.length} validation error${v2.errors.length === 1 ? "" : "s"}:
279
+ `defineWorkflow("${definition.name}"): ${v2.errors.length} validation error${v2.errors.length === 1 ? "" : "s"}:
278
280
  ` + v2.errors.join(`
279
281
  `)
280
282
  );
@@ -295,7 +297,7 @@ function createDefinitionValidator() {
295
297
  return { errors, tryParse, checkCondition: (groq, where) => {
296
298
  tryParse(groq, where), rejectTypeScan(groq, where);
297
299
  }, checkEntry: (entry, label) => {
298
- entry.source.type === "query" && tryParse(entry.source.query, `${label}.query`);
300
+ entry.initialValue?.type === "query" && tryParse(entry.initialValue.query, `${label}.query`);
299
301
  }, checkGuardRead: (expr, where) => {
300
302
  isGuardReadExpr(expr) || errors.push(
301
303
  ` \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)`
@@ -312,8 +314,8 @@ function validateStage(v2, stage) {
312
314
  for (const [key, groq] of effectBindings(t.effects))
313
315
  v2.tryParse(groq, `transition "${t.name}" effect binding "${key}"`);
314
316
  }
315
- for (const task of stage.tasks ?? [])
316
- validateTask(v2, stage.name, task);
317
+ for (const activity of stage.activities ?? [])
318
+ validateActivity(v2, stage.name, activity);
317
319
  }
318
320
  function validateGuardReads(v2, stageName, guard) {
319
321
  const where = `stage "${stageName}" guard "${guard.name}"`;
@@ -322,22 +324,22 @@ function validateGuardReads(v2, stageName, guard) {
322
324
  for (const [key, expr] of Object.entries(guard.metadata ?? {}))
323
325
  v2.checkGuardRead(expr, `${where} metadata "${key}"`);
324
326
  }
325
- function validateTask(v2, stageName, task) {
326
- const where = `stage "${stageName}" task "${task.name}"`;
327
- for (const entry of task.fields ?? [])
327
+ function validateActivity(v2, stageName, activity) {
328
+ const where = `stage "${stageName}" activity "${activity.name}"`;
329
+ for (const entry of activity.fields ?? [])
328
330
  v2.checkEntry(entry, `${where}.fields "${entry.name}"`);
329
- 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`);
330
- for (const [name, groq] of Object.entries(task.requirements ?? {}))
331
+ 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`);
332
+ for (const [name, groq] of Object.entries(activity.requirements ?? {}))
331
333
  v2.checkCondition(groq, `${where}.requirements "${name}"`);
332
- for (const [key, groq] of effectBindings(task.effects))
334
+ for (const [key, groq] of effectBindings(activity.effects))
333
335
  v2.tryParse(groq, `${where} effect binding "${key}"`);
334
- validateTaskActions(v2, task), task.subworkflows !== void 0 && validateSubworkflows(v2, where, task.subworkflows);
336
+ validateActivityActions(v2, activity), activity.subworkflows !== void 0 && validateSubworkflows(v2, where, activity.subworkflows);
335
337
  }
336
- function validateTaskActions(v2, task) {
337
- for (const a of task.actions ?? []) {
338
- a.filter !== void 0 && v2.checkCondition(a.filter, `task "${task.name}" action "${a.name}".filter`);
338
+ function validateActivityActions(v2, activity) {
339
+ for (const a of activity.actions ?? []) {
340
+ a.filter !== void 0 && v2.checkCondition(a.filter, `activity "${activity.name}" action "${a.name}".filter`);
339
341
  for (const [key, groq] of effectBindings(a.effects))
340
- v2.tryParse(groq, `task "${task.name}" action "${a.name}" effect binding "${key}"`);
342
+ v2.tryParse(groq, `activity "${activity.name}" action "${a.name}" effect binding "${key}"`);
341
343
  }
342
344
  }
343
345
  function validateSubworkflows(v2, where, sub) {
@@ -352,10 +354,10 @@ function effectBindings(effects) {
352
354
  (e) => Object.entries(e.bindings ?? {}).map(([k, groq]) => [`${e.name}.${k}`, groq])
353
355
  );
354
356
  }
355
- function actionVerdict(task, action) {
357
+ function actionVerdict(activity, action) {
356
358
  return {
357
- task: task.task.name,
358
- taskStatus: task.status,
359
+ activity: activity.activity.name,
360
+ activityStatus: activity.status,
359
361
  action: action.action.name,
360
362
  title: action.action.title,
361
363
  allowed: action.allowed,
@@ -363,8 +365,8 @@ function actionVerdict(task, action) {
363
365
  params: action.action.params ?? []
364
366
  };
365
367
  }
366
- function availableActions(tasks) {
367
- return tasks.flatMap((t) => t.actions.map((a) => actionVerdict(t, a)));
368
+ function availableActions(activities) {
369
+ return activities.flatMap((t) => t.actions.map((a) => actionVerdict(t, a)));
368
370
  }
369
371
  const wallClock = () => (/* @__PURE__ */ new Date()).toISOString(), GUARD_DOC_TYPE = "temp.system.guard";
370
372
  class MutationGuardDeniedError extends Error {
@@ -486,60 +488,67 @@ function diagnoseInputFromEvaluation(evaluation) {
486
488
  const stage = openStage(evaluation.instance);
487
489
  return {
488
490
  instance: evaluation.instance,
489
- tasks: evaluation.currentStage.tasks,
491
+ activities: evaluation.currentStage.activities,
490
492
  transitions: evaluation.currentStage.transitions,
491
493
  assignees: Object.fromEntries(
492
- evaluation.currentStage.tasks.map((t) => [t.task.name, assigneesOf(stage, t.task.name)])
494
+ evaluation.currentStage.activities.map((t) => [
495
+ t.activity.name,
496
+ assigneesOf(stage, t.activity.name)
497
+ ])
493
498
  )
494
499
  };
495
500
  }
496
501
  function openStage(instance) {
497
502
  return findOpenStageEntry(instance);
498
503
  }
499
- function assigneesOf(stage, taskName) {
500
- return (stage?.tasks.find((t) => t.name === taskName)?.fields ?? []).filter((s) => s._type === "assignees").flatMap((s) => s.value);
504
+ function assigneesOf(stage, activityName) {
505
+ return (stage?.activities.find((t) => t.name === activityName)?.fields ?? []).filter((s) => s._type === "assignees").flatMap((s) => s.value);
501
506
  }
502
507
  function isResolved(status) {
503
508
  return status === "done" || status === "skipped";
504
509
  }
505
510
  function failedEffectCause(input) {
506
- 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));
511
+ const stalled = new Set(
512
+ input.activities.filter((t) => !isResolved(t.status)).map((t) => t.activity.name)
513
+ ), effect = [...input.instance.effectHistory].reverse().find(
514
+ (e) => e.status === "failed" && e.origin.kind === "activity" && stalled.has(e.origin.name)
515
+ );
507
516
  return effect ? { kind: "failed-effect", effect } : void 0;
508
517
  }
509
518
  function hungEffectCause(input) {
510
519
  const effect = input.instance.pendingEffects.find((e) => e.claim !== void 0);
511
520
  return effect ? { kind: "hung-effect", effect } : void 0;
512
521
  }
513
- function failedTaskCause(input) {
514
- const failed = input.tasks.find((t) => t.status === "failed");
515
- return failed ? { kind: "failed-task", task: failed.task.name } : void 0;
522
+ function failedActivityCause(input) {
523
+ const failed = input.activities.find((t) => t.status === "failed");
524
+ return failed ? { kind: "failed-activity", activity: failed.activity.name } : void 0;
516
525
  }
517
526
  function waitingState(input) {
518
- const active = input.tasks.find(
519
- (t) => t.status === "active" && (t.task.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length === 0
527
+ const active = input.activities.find(
528
+ (t) => t.status === "active" && (t.activity.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length === 0
520
529
  );
521
530
  if (active !== void 0)
522
531
  return {
523
532
  state: "waiting",
524
- task: active.task.name,
525
- assignees: input.assignees[active.task.name] ?? [],
526
- actions: (active.task.actions ?? []).map((a) => a.name)
533
+ activity: active.activity.name,
534
+ assignees: input.assignees[active.activity.name] ?? [],
535
+ actions: (active.activity.actions ?? []).map((a) => a.name)
527
536
  };
528
537
  }
529
538
  function blockedState(input) {
530
- const blocked = input.tasks.find(
531
- (t) => t.status === "active" && (t.task.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length > 0
539
+ const blocked = input.activities.find(
540
+ (t) => t.status === "active" && (t.activity.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length > 0
532
541
  );
533
542
  if (blocked !== void 0)
534
543
  return {
535
544
  state: "blocked",
536
- task: blocked.task.name,
545
+ activity: blocked.activity.name,
537
546
  requirements: blocked.unmetRequirements ?? [],
538
- assignees: input.assignees[blocked.task.name] ?? []
547
+ assignees: input.assignees[blocked.activity.name] ?? []
539
548
  };
540
549
  }
541
550
  function noTransitionFiresCause(input) {
542
- if (input.transitions.length === 0 || input.transitions.some((t) => t.filterSatisfied) || !input.tasks.every((t) => isResolved(t.status)))
551
+ if (input.transitions.length === 0 || input.transitions.some((t) => t.filterSatisfied) || !input.activities.every((t) => isResolved(t.status)))
543
552
  return;
544
553
  const undecidable = input.transitions.filter((t) => t.unevaluable);
545
554
  return undecidable.length > 0 ? { kind: "transition-unevaluable", transitions: undecidable.map((t) => t.transition.name) } : { kind: "no-transition-fires" };
@@ -552,7 +561,7 @@ function diagnoseInstance(input) {
552
561
  }
553
562
  if (instance.completedAt !== void 0)
554
563
  return { state: "completed", at: instance.completedAt };
555
- const cause = failedEffectCause(input) ?? failedTaskCause(input) ?? hungEffectCause(input) ?? noTransitionFiresCause(input);
564
+ const cause = failedEffectCause(input) ?? failedActivityCause(input) ?? hungEffectCause(input) ?? noTransitionFiresCause(input);
556
565
  return cause !== void 0 ? { state: "stuck", cause } : waitingState(input) ?? blockedState(input) ?? { state: "progressing" };
557
566
  }
558
567
  const RUNNABLE_VERBS = /* @__PURE__ */ new Set(["set-stage", "abort"]);
@@ -574,12 +583,12 @@ function remediationsForCause(cause) {
574
583
  },
575
584
  { verb: "abort", rationale: "Abort the instance if the effect never drains." }
576
585
  ];
577
- case "failed-task":
586
+ case "failed-activity":
578
587
  return [
579
- { verb: "reset-task", rationale: "Reset or skip the failed task." },
588
+ { verb: "reset-activity", rationale: "Reset or skip the failed activity." },
580
589
  {
581
590
  verb: "set-stage",
582
- rationale: "Move the instance past the failed task to the intended next stage."
591
+ rationale: "Move the instance past the failed activity to the intended next stage."
583
592
  }
584
593
  ];
585
594
  case "no-transition-fires":
@@ -601,13 +610,15 @@ function remediationsFor(diagnosis) {
601
610
  }
602
611
  class ActionParamsInvalidError extends Error {
603
612
  action;
604
- task;
613
+ activity;
605
614
  issues;
606
615
  constructor(args) {
607
616
  const lines = args.issues.map((i) => ` - ${i.param}: ${i.reason}`).join(`
608
617
  `);
609
- super(`Action "${args.action}" on task "${args.task}" rejected: invalid params
610
- ${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.task = args.task, this.issues = args.issues;
618
+ super(
619
+ `Action "${args.action}" on activity "${args.activity}" rejected: invalid params
620
+ ${lines}`
621
+ ), this.name = "ActionParamsInvalidError", this.action = args.action, this.activity = args.activity, this.issues = args.issues;
611
622
  }
612
623
  }
613
624
  class RequiredFieldNotProvidedError extends Error {
@@ -617,7 +628,7 @@ class RequiredFieldNotProvidedError extends Error {
617
628
  const lines = args.missing.map((m) => ` - ${m.name} (${m.type})`).join(`
618
629
  `), where = args.definition !== void 0 ? ` for workflow "${args.definition}"` : "";
619
630
  super(
620
- `Required init fields${where} was not provided:
631
+ `Required input fields${where} were not provided:
621
632
  ${lines}
622
633
  Provide each via \`initialFields\` when starting standalone, or via the parent's \`subworkflows.with\` when spawned.`
623
634
  ), this.name = "RequiredFieldNotProvidedError", args.definition !== void 0 && (this.definition = args.definition), this.missing = args.missing;
@@ -649,13 +660,13 @@ class PartialGuardDeployError extends Error {
649
660
  const CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS = 3;
650
661
  class ConcurrentFireActionError extends Error {
651
662
  instanceId;
652
- task;
663
+ activity;
653
664
  action;
654
665
  attempts;
655
666
  constructor(args) {
656
667
  super(
657
- `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.`
658
- ), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.task = args.task, this.action = args.action, this.attempts = args.attempts;
668
+ `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.`
669
+ ), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.activity = args.activity, this.action = args.action, this.attempts = args.attempts;
659
670
  }
660
671
  }
661
672
  function isRevisionConflict(error) {
@@ -668,7 +679,7 @@ class ConcurrentEditFieldError extends Error {
668
679
  target;
669
680
  attempts;
670
681
  constructor(args) {
671
- const where = args.target.task !== void 0 ? `${args.target.task}.${args.target.field}` : args.target.field;
682
+ const where = args.target.activity !== void 0 ? `${args.target.activity}.${args.target.field}` : args.target.field;
672
683
  super(
673
684
  `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.`
674
685
  ), this.name = "ConcurrentEditFieldError", this.instanceId = args.instanceId, this.target = args.target, this.attempts = args.attempts;
@@ -746,6 +757,9 @@ function subscriptionDocumentsForInstance(instance) {
746
757
  ...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
747
758
  };
748
759
  }
760
+ function instanceWatchesDocument(instance, document) {
761
+ return collectWatchRefs(instance).some((ref) => ref.id === document);
762
+ }
749
763
  function entryDocRefs(entries) {
750
764
  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) : [] : []);
751
765
  }
@@ -860,14 +874,14 @@ function guardIdRefs(definition) {
860
874
  function staticIdRefGdr(idRef, stage, definition) {
861
875
  const read = /^\$fields\.([\w-]+)/.exec(idRef);
862
876
  if (read === null) return;
863
- const source = entriesInScope(stage, definition).find((e) => e.name === read[1])?.source;
864
- return source?.type === "literal" ? gdrFromValue(source.value) : void 0;
877
+ const seed = entriesInScope(stage, definition).find((e) => e.name === read[1])?.initialValue;
878
+ return seed?.type === "literal" ? gdrFromValue(seed.value) : void 0;
865
879
  }
866
880
  function entriesInScope(stage, definition) {
867
881
  return [
868
882
  ...definition.fields ?? [],
869
883
  ...stage.fields ?? [],
870
- ...(stage.tasks ?? []).flatMap((task) => task.fields ?? [])
884
+ ...(stage.activities ?? []).flatMap((activity) => activity.fields ?? [])
871
885
  ];
872
886
  }
873
887
  function gdrFromValue(value) {
@@ -1049,76 +1063,73 @@ const GdrShape = v.looseObject({
1049
1063
  }), AssigneeShape = v.union([
1050
1064
  v.looseObject({ type: v.literal("user"), id: v.pipe(v.string(), v.minLength(1)) }),
1051
1065
  v.looseObject({ type: v.literal("role"), role: v.pipe(v.string(), v.minLength(1)) })
1052
- ]), ChecklistItemShape = v.looseObject({
1053
- label: v.pipe(v.string(), v.minLength(1)),
1054
- done: v.boolean(),
1055
- doneBy: v.optional(v.string()),
1056
- doneAt: v.optional(v.string()),
1057
- _key: v.optional(v.pipe(v.string(), v.minLength(1)))
1058
- }), NoteItemShape = v.pipe(
1059
- v.looseObject({
1060
- _key: v.optional(v.pipe(v.string(), v.minLength(1)))
1061
- }),
1062
- v.check(
1063
- (val) => typeof val == "object" && val !== null && !Array.isArray(val),
1064
- "must be an object"
1065
- )
1066
- ), NullableString = v.union([v.null(), v.string()]), NullableNumber = v.union([v.null(), v.number()]), NullableBoolean = v.union([v.null(), v.boolean()]), NullableDateTime = v.union([
1066
+ ]), NullableString = v.union([v.null(), v.string()]), NullableNumber = v.union([v.null(), v.number()]), NullableBoolean = v.union([v.null(), v.boolean()]), NullableDateTime = v.union([
1067
1067
  v.null(),
1068
1068
  v.pipe(
1069
1069
  v.string(),
1070
1070
  v.check((s) => !Number.isNaN(Date.parse(s)), "must be an ISO-8601 datetime string")
1071
1071
  )
1072
+ ]), NullableDate = v.union([
1073
+ v.null(),
1074
+ v.pipe(v.string(), v.regex(/^\d{4}-\d{2}-\d{2}$/, "must be a `YYYY-MM-DD` date"))
1072
1075
  ]), NullableUrl = NullableString, valueSchemas = {
1073
1076
  "doc.ref": v.union([v.null(), GdrShape]),
1074
1077
  "doc.refs": v.array(GdrShape),
1075
1078
  "release.ref": v.union([v.null(), ReleaseRefShape]),
1076
1079
  query: v.any(),
1077
- "value.string": NullableString,
1078
- "value.url": NullableUrl,
1079
- "value.number": NullableNumber,
1080
- "value.boolean": NullableBoolean,
1081
- "value.dateTime": NullableDateTime,
1082
- "value.actor": v.union([v.null(), ActorShape]),
1083
- checklist: v.array(ChecklistItemShape),
1084
- notes: v.array(NoteItemShape),
1080
+ string: NullableString,
1081
+ text: NullableString,
1082
+ number: NullableNumber,
1083
+ boolean: NullableBoolean,
1084
+ date: NullableDate,
1085
+ datetime: NullableDateTime,
1086
+ url: NullableUrl,
1087
+ actor: v.union([v.null(), ActorShape]),
1088
+ assignee: v.union([v.null(), AssigneeShape]),
1085
1089
  assignees: v.array(AssigneeShape)
1086
- }, itemSchemas = {
1087
- "doc.refs": GdrShape,
1088
- checklist: ChecklistItemShape,
1089
- notes: NoteItemShape,
1090
- assignees: AssigneeShape
1091
1090
  };
1092
- function isAppendable(entryType) {
1093
- return entryType in itemSchemas;
1091
+ function shapeValueSchema(shape) {
1092
+ return shape.type === "object" ? objectSchema(shape.fields ?? []) : shape.type === "array" ? v.array(objectSchema(shape.of ?? [])) : valueSchemas[shape.type] ?? v.any();
1094
1093
  }
1095
- function validateFieldValue(args) {
1096
- const schema = valueSchemas[args.entryType];
1097
- if (schema === void 0)
1098
- throw new FieldValueShapeError({
1099
- entryType: args.entryType,
1100
- entryName: args.entryName,
1101
- issues: [`unknown field entry type ${args.entryType}`],
1102
- mode: "value"
1103
- });
1094
+ function objectSchema(fields) {
1095
+ const entries = /* @__PURE__ */ Object.create(null);
1096
+ for (const f of fields) entries[f.name] = v.optional(shapeValueSchema(f));
1097
+ return v.looseObject(entries);
1098
+ }
1099
+ function wholeValueSchema(entryType, shape) {
1100
+ return entryType === "object" ? v.union([v.null(), objectSchema(shape.fields ?? [])]) : entryType === "array" ? v.array(objectSchema(shape.of ?? [])) : valueSchemas[entryType];
1101
+ }
1102
+ function appendItemSchema(entryType, shape) {
1103
+ if (entryType === "array") return objectSchema(shape.of ?? []);
1104
+ if (entryType === "doc.refs") return GdrShape;
1105
+ if (entryType === "assignees") return AssigneeShape;
1106
+ }
1107
+ function checkFieldValue(args) {
1108
+ const schema = wholeValueSchema(args.entryType, args);
1109
+ if (schema === void 0) return [`unknown field entry type ${args.entryType}`];
1104
1110
  const result = v.safeParse(schema, args.value);
1105
- if (!result.success)
1111
+ return result.success ? void 0 : formatIssues(result.issues);
1112
+ }
1113
+ function validateFieldValue(args) {
1114
+ const issues = checkFieldValue(args);
1115
+ if (issues !== void 0)
1106
1116
  throw new FieldValueShapeError({
1107
1117
  entryType: args.entryType,
1108
1118
  entryName: args.entryName,
1109
- issues: formatIssues(result.issues),
1119
+ issues,
1110
1120
  mode: "value"
1111
1121
  });
1112
1122
  }
1113
1123
  function validateFieldAppendItem(args) {
1114
- if (!isAppendable(args.entryType))
1124
+ const schema = appendItemSchema(args.entryType, args);
1125
+ if (schema === void 0)
1115
1126
  throw new FieldValueShapeError({
1116
1127
  entryType: args.entryType,
1117
1128
  entryName: args.entryName,
1118
1129
  issues: [`field entry type ${args.entryType} does not support append`],
1119
1130
  mode: "item"
1120
1131
  });
1121
- const schema = itemSchemas[args.entryType], result = v.safeParse(schema, args.item);
1132
+ const result = v.safeParse(schema, args.item);
1122
1133
  if (!result.success)
1123
1134
  throw new FieldValueShapeError({
1124
1135
  entryType: args.entryType,
@@ -1146,7 +1157,7 @@ function derivePerspectiveFromFields(entries) {
1146
1157
  async function resolveDeclaredFields(args) {
1147
1158
  const { entryDefs, initialFields, ctx, randomKey: randomKey2 } = args;
1148
1159
  if (entryDefs === void 0 || entryDefs.length === 0) return [];
1149
- assertRequiredInitProvided(entryDefs, initialFields, ctx.definitionName);
1160
+ assertRequiredInputProvided(entryDefs, initialFields, ctx.definitionName);
1150
1161
  const out = [];
1151
1162
  for (const entry of entryDefs) {
1152
1163
  const scopedCtx = { ...ctx, resolvedFields: out };
@@ -1154,8 +1165,8 @@ async function resolveDeclaredFields(args) {
1154
1165
  }
1155
1166
  return out;
1156
1167
  }
1157
- function assertRequiredInitProvided(entryDefs, initialFields, definitionName) {
1158
- const missing = entryDefs.filter((entry) => entry.required === !0 && entry.source.type === "init").filter((entry) => {
1168
+ function assertRequiredInputProvided(entryDefs, initialFields, definitionName) {
1169
+ const missing = entryDefs.filter((entry) => entry.required === !0 && entry.initialValue?.type === "input").filter((entry) => {
1159
1170
  const match = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
1160
1171
  return match === void 0 || match.value === void 0 || match.value === null;
1161
1172
  }).map((entry) => ({ name: entry.name, type: entry.type }));
@@ -1165,18 +1176,13 @@ function assertRequiredInitProvided(entryDefs, initialFields, definitionName) {
1165
1176
  missing
1166
1177
  });
1167
1178
  }
1168
- const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set([
1169
- "doc.refs",
1170
- "checklist",
1171
- "notes",
1172
- "assignees"
1173
- ]);
1179
+ const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set(["doc.refs", "array", "assignees"]);
1174
1180
  function defaultEntryValue(entryType) {
1175
1181
  return ARRAY_SLOT_TYPES.has(entryType) ? [] : null;
1176
1182
  }
1177
- function resolveInitValue(entry, initialFields, defaultValue) {
1183
+ function resolveInputValue(entry, initialFields, defaultValue) {
1178
1184
  const initMatch = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
1179
- return initMatch === void 0 ? defaultValue : (assertInitValueShape(entry, initMatch.value), initMatch.value);
1185
+ return initMatch === void 0 ? defaultValue : (assertInputValueShape(entry, initMatch.value), initMatch.value);
1180
1186
  }
1181
1187
  function resolveFieldReadValue(source, ctx, defaultValue) {
1182
1188
  const target = (source.scope === "workflow" ? ctx.workflowFields ?? [] : ctx.resolvedFields ?? []).find((s) => s.name === source.field), targetValue = target === void 0 ? void 0 : target.value;
@@ -1187,7 +1193,7 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
1187
1193
  self: ctx.selfId ?? null,
1188
1194
  fields: fieldMapFromResolved(ctx.resolvedFields ?? []),
1189
1195
  stage: ctx.stageName ?? null,
1190
- task: ctx.taskName ?? null,
1196
+ activity: ctx.activityName ?? null,
1191
1197
  now: ctx.now,
1192
1198
  tag: ctx.tag
1193
1199
  });
@@ -1202,38 +1208,47 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
1202
1208
  }
1203
1209
  }
1204
1210
  async function resolveEntryValue(entry, initialFields, ctx, defaultValue) {
1205
- const source = entry.source;
1206
- 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;
1211
+ const source = entry.initialValue;
1212
+ return source === void 0 ? defaultValue : source.type === "input" ? resolveInputValue(entry, initialFields, defaultValue) : source.type === "literal" ? source.value ?? defaultValue : source.type === "fieldRead" ? resolveFieldReadValue(source, ctx, defaultValue) : source.type === "query" && ctx.client !== void 0 ? resolveQueryValue(entry, source, ctx, ctx.client, defaultValue) : defaultValue;
1207
1213
  }
1208
1214
  function buildResolvedEntry(entry, value, _key, now) {
1209
- const titleProp = entry.title !== void 0 ? { title: entry.title } : {}, descriptionProp = entry.description !== void 0 ? { description: entry.description } : {};
1210
- return entry.type === "query" ? {
1211
- _key,
1212
- _type: "query",
1213
- name: entry.name,
1214
- ...titleProp,
1215
- ...descriptionProp,
1216
- value,
1217
- resolvedAt: now
1218
- } : {
1215
+ return {
1219
1216
  _key,
1220
1217
  _type: entry.type,
1221
1218
  name: entry.name,
1222
- ...titleProp,
1223
- ...descriptionProp,
1224
- value
1219
+ ...entry.title !== void 0 ? { title: entry.title } : {},
1220
+ ...entry.description !== void 0 ? { description: entry.description } : {},
1221
+ value,
1222
+ ...entry.initialValue?.type === "query" ? { resolvedAt: now } : {},
1223
+ ...entry.type === "object" ? { fields: entry.fields ?? [] } : {},
1224
+ ...entry.type === "array" ? { of: entry.of ?? [] } : {}
1225
1225
  };
1226
1226
  }
1227
1227
  async function resolveOneEntry(entry, initialFields, ctx, randomKey2) {
1228
1228
  const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue(entry, initialFields, ctx, defaultValue);
1229
- return validateFieldValue({ entryType: entry.type, entryName: entry.name, value }), buildResolvedEntry(entry, value, randomKey2(), ctx.now);
1229
+ if (entry.initialValue?.type === "query") {
1230
+ const issues = checkFieldValue({
1231
+ entryType: entry.type,
1232
+ value,
1233
+ fields: entry.fields,
1234
+ of: entry.of
1235
+ });
1236
+ return issues !== void 0 ? (ctx.recordDiscard?.({ field: entry.name, detail: issues.join("; ") }), buildResolvedEntry(entry, defaultValue, randomKey2(), ctx.now)) : buildResolvedEntry(entry, value, randomKey2(), ctx.now);
1237
+ }
1238
+ return validateFieldValue({
1239
+ entryType: entry.type,
1240
+ entryName: entry.name,
1241
+ value,
1242
+ fields: entry.fields,
1243
+ of: entry.of
1244
+ }), buildResolvedEntry(entry, value, randomKey2(), ctx.now);
1230
1245
  }
1231
1246
  function fieldMapFromResolved(entries) {
1232
1247
  const out = {};
1233
1248
  for (const s of entries) out[s.name] = s.value;
1234
1249
  return out;
1235
1250
  }
1236
- function assertInitValueShape(entry, value) {
1251
+ function assertInputValueShape(entry, value) {
1237
1252
  if (entry.type === "doc.ref") {
1238
1253
  assertGdrShape(value, `field entry "${entry.name}" (doc.ref)`);
1239
1254
  return;
@@ -1243,14 +1258,14 @@ function assertInitValueShape(entry, value) {
1243
1258
  const v2 = value;
1244
1259
  if (typeof v2.releaseName != "string" || v2.releaseName.length === 0)
1245
1260
  throw new Error(
1246
- `Invalid init value for field entry "${entry.name}" (release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(v2.releaseName)}.`
1261
+ `Invalid input value for field entry "${entry.name}" (release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(v2.releaseName)}.`
1247
1262
  );
1248
1263
  return;
1249
1264
  }
1250
1265
  if (entry.type === "doc.refs") {
1251
1266
  if (!Array.isArray(value))
1252
1267
  throw new Error(
1253
- `Invalid init value for field entry "${entry.name}" (doc.refs): expected an array of GDRs, got ${typeof value}.`
1268
+ `Invalid input value for field entry "${entry.name}" (doc.refs): expected an array of GDRs, got ${typeof value}.`
1254
1269
  );
1255
1270
  for (const [i, item] of value.entries())
1256
1271
  assertGdrShape(item, `field entry "${entry.name}" (doc.refs) item [${i}]`);
@@ -1314,12 +1329,12 @@ async function loadContext(client, instanceId, options) {
1314
1329
  async function ctxConditionParams(ctx, opts) {
1315
1330
  const base = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), fields = {
1316
1331
  ...base.fields,
1317
- ...scopedFieldOverlay(ctx.instance, ctx.snapshot, opts?.taskName)
1332
+ ...scopedFieldOverlay(ctx.instance, ctx.snapshot, opts?.activityName)
1318
1333
  }, params = {
1319
1334
  ...base,
1320
1335
  fields,
1321
1336
  actor: opts?.actor,
1322
- assigned: opts?.taskName !== void 0 ? assignedFor(ctx.instance, opts.taskName, opts?.actor, ctx.definition.roleAliases) : !1,
1337
+ assigned: opts?.activityName !== void 0 ? assignedFor(ctx.instance, opts.activityName, opts?.actor, ctx.definition.roleAliases) : !1,
1323
1338
  ...opts?.vars
1324
1339
  };
1325
1340
  return { ...await evaluatePredicates({
@@ -1357,7 +1372,7 @@ function findStage(definition, stageName) {
1357
1372
  return stage;
1358
1373
  }
1359
1374
  async function resolveStageFieldEntries(args) {
1360
- const { client, instance, stage, now, initialFields } = args;
1375
+ const { client, instance, stage, now, initialFields, recordDiscard } = args;
1361
1376
  return resolveDeclaredFields({
1362
1377
  entryDefs: stage.fields,
1363
1378
  initialFields: initialFields ?? [],
@@ -1368,18 +1383,19 @@ async function resolveStageFieldEntries(args) {
1368
1383
  tag: instance.tag,
1369
1384
  stageName: stage.name,
1370
1385
  workflowResource: instance.workflowResource,
1371
- // Allow stage-scope entries' `source: { type: "fieldRead", scope:
1386
+ // Allow stage-scope entries' `initialValue: { type: "fieldRead", scope:
1372
1387
  // "workflow", ... }` to see the already-resolved workflow-scope fields.
1373
1388
  workflowFields: instance.fields ?? [],
1374
- ...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
1389
+ ...instance.perspective !== void 0 ? { perspective: instance.perspective } : {},
1390
+ ...recordDiscard !== void 0 ? { recordDiscard } : {}
1375
1391
  },
1376
1392
  randomKey
1377
1393
  });
1378
1394
  }
1379
- async function resolveTaskFieldEntries(args) {
1380
- const { client, instance, stage, task, now } = args;
1395
+ async function resolveActivityFieldEntries(args) {
1396
+ const { client, instance, stage, activity, now, recordDiscard } = args;
1381
1397
  return resolveDeclaredFields({
1382
- entryDefs: task.fields,
1398
+ entryDefs: activity.fields,
1383
1399
  initialFields: [],
1384
1400
  ctx: {
1385
1401
  client,
@@ -1388,85 +1404,70 @@ async function resolveTaskFieldEntries(args) {
1388
1404
  tag: instance.tag,
1389
1405
  stageName: stage.name,
1390
1406
  workflowResource: instance.workflowResource,
1391
- taskName: task.name,
1407
+ activityName: activity.name,
1392
1408
  workflowFields: instance.fields ?? [],
1393
- ...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
1409
+ ...instance.perspective !== void 0 ? { perspective: instance.perspective } : {},
1410
+ ...recordDiscard !== void 0 ? { recordDiscard } : {}
1394
1411
  },
1395
1412
  randomKey
1396
1413
  });
1397
1414
  }
1398
- async function deployOrRollback(args) {
1399
- try {
1400
- await args.deploy();
1401
- } catch (guardError) {
1402
- if (!args.reversible)
1403
- throw new WorkflowStateDivergedError({
1404
- instanceId: args.instanceId,
1405
- guardError,
1406
- reason: "the move created child instances a parent-only rollback cannot undo"
1407
- });
1408
- try {
1409
- let patch = args.client.patch(args.instanceId).set(args.restore);
1410
- 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);
1411
- } catch (rollbackError) {
1412
- throw new WorkflowStateDivergedError({
1413
- instanceId: args.instanceId,
1414
- guardError,
1415
- rollbackError,
1416
- reason: "rollback of the committed move failed"
1417
- });
1418
- }
1419
- throw guardError instanceof PartialGuardDeployError ? new WorkflowStateDivergedError({
1420
- instanceId: args.instanceId,
1421
- guardError,
1422
- reason: "a multi-guard deploy partially applied; the rolled-back move left orphaned guard locks"
1423
- }) : guardError;
1424
- }
1415
+ async function resolveBindings(args) {
1416
+ const resolved = {};
1417
+ for (const [key, groq] of Object.entries(args.bindings ?? {}))
1418
+ resolved[key] = await runGroq(groq, args.params, args.snapshot);
1419
+ return { ...resolved, ...args.staticInput };
1425
1420
  }
1426
- function applyTaskStatusChange(args) {
1427
- const { entry, history, stage, to, at, actor } = args, from = entry.status;
1428
- entry.status = to, isTerminalTaskStatus(to) && (entry.completedAt = at, actor !== void 0 && (entry.completedBy = actor.id)), history.push({
1429
- _key: randomKey(),
1430
- _type: "taskStatusChanged",
1431
- at,
1432
- stage,
1433
- task: entry.name,
1434
- from,
1435
- to,
1436
- ...actor !== void 0 ? { actor } : {}
1437
- });
1421
+ async function reload(client, instanceId, tag) {
1422
+ const doc = await client.getDocument(instanceId);
1423
+ if (!doc)
1424
+ throw new Error(`Workflow instance ${instanceId} not found`);
1425
+ if (doc.tag !== tag)
1426
+ throw new Error(`Workflow instance ${instanceId} not visible to this engine (tag mismatch)`);
1427
+ return doc;
1438
1428
  }
1439
- function stageTransitionHistory(args) {
1440
- const { fromStage, toStage, at, transition, actor, via, reason } = args, shared = {
1441
- at,
1442
- ...transition !== void 0 ? { transition } : {},
1443
- ...actor !== void 0 ? { actor } : {},
1444
- ...via !== void 0 ? { via } : {},
1445
- ...reason !== void 0 ? { reason } : {}
1429
+ function effectsContextEntry(name, value) {
1430
+ const _key = randomKey();
1431
+ 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 };
1432
+ }
1433
+ function effectsContextJsonEntry(name, value) {
1434
+ return { _key: randomKey(), _type: "effectsContext.json", name, value: JSON.stringify(value) };
1435
+ }
1436
+ function buildInstanceBase(args) {
1437
+ const { id, now, actor, perspective } = args;
1438
+ return {
1439
+ _id: id,
1440
+ _type: WORKFLOW_INSTANCE_TYPE,
1441
+ _rev: "",
1442
+ _createdAt: now,
1443
+ _updatedAt: now,
1444
+ tag: args.tag,
1445
+ workflowResource: args.workflowResource,
1446
+ definition: args.definitionName,
1447
+ pinnedVersion: args.pinnedVersion,
1448
+ ...args.pinnedContentHash !== void 0 ? { pinnedContentHash: args.pinnedContentHash } : {},
1449
+ definitionSnapshot: JSON.stringify(args.definition),
1450
+ fields: args.fields,
1451
+ effectsContext: args.effectsContext,
1452
+ ancestors: args.ancestors,
1453
+ ...perspective !== void 0 ? { perspective } : {},
1454
+ currentStage: args.initialStage,
1455
+ stages: [],
1456
+ pendingEffects: [],
1457
+ effectHistory: [],
1458
+ history: [
1459
+ {
1460
+ _key: randomKey(),
1461
+ _type: "stageEntered",
1462
+ at: now,
1463
+ stage: args.initialStage,
1464
+ ...actor !== void 0 ? { actor } : {}
1465
+ },
1466
+ ...args.extraHistory ?? []
1467
+ ],
1468
+ startedAt: now,
1469
+ lastChangedAt: now
1446
1470
  };
1447
- return [
1448
- {
1449
- _key: randomKey(),
1450
- _type: "stageExited",
1451
- stage: fromStage,
1452
- toStage,
1453
- ...shared
1454
- },
1455
- {
1456
- _key: randomKey(),
1457
- _type: "transitionFired",
1458
- fromStage,
1459
- toStage,
1460
- ...shared
1461
- },
1462
- {
1463
- _key: randomKey(),
1464
- _type: "stageEntered",
1465
- stage: toStage,
1466
- fromStage,
1467
- ...shared
1468
- }
1469
- ];
1470
1471
  }
1471
1472
  function startMutation(instance) {
1472
1473
  return {
@@ -1477,7 +1478,7 @@ function startMutation(instance) {
1477
1478
  stages: instance.stages.map((s) => ({
1478
1479
  ...s,
1479
1480
  fields: (s.fields ?? []).map((entry) => ({ ...entry })),
1480
- tasks: s.tasks.map((t) => ({
1481
+ activities: s.activities.map((t) => ({
1481
1482
  ...t,
1482
1483
  ...t.fields !== void 0 ? { fields: t.fields.map((entry) => ({ ...entry })) } : {}
1483
1484
  }))
@@ -1553,344 +1554,97 @@ function currentStageEntry(mutation) {
1553
1554
  );
1554
1555
  return entry;
1555
1556
  }
1556
- function currentTasks(mutation) {
1557
- return currentStageEntry(mutation).tasks;
1557
+ function currentActivities(mutation) {
1558
+ return currentStageEntry(mutation).activities;
1558
1559
  }
1559
- function findTaskInCurrentStage(ctx, taskName) {
1560
- const stage = findStage(ctx.definition, ctx.instance.currentStage), task = (stage.tasks ?? []).find((t) => t.name === taskName);
1561
- if (task === void 0)
1560
+ function findActivityInCurrentStage(ctx, activityName) {
1561
+ const stage = findStage(ctx.definition, ctx.instance.currentStage), activity = (stage.activities ?? []).find((t) => t.name === activityName);
1562
+ if (activity === void 0)
1562
1563
  throw new Error(
1563
- `Task "${taskName}" not found in current stage "${stage.name}" of ${ctx.definition.name}`
1564
+ `Activity "${activityName}" not found in current stage "${stage.name}" of ${ctx.definition.name}`
1564
1565
  );
1565
- return { stage, task };
1566
+ return { stage, activity };
1566
1567
  }
1567
- function requireMutationTaskEntry(mutation, task) {
1568
- const mutEntry = currentTasks(mutation).find((t) => t.name === task);
1568
+ function requireMutationActivityEntry(mutation, activity) {
1569
+ const mutEntry = currentActivities(mutation).find((t) => t.name === activity);
1569
1570
  if (mutEntry === void 0)
1570
- throw new Error(`Task "${task}" disappeared from mutation copy \u2014 invariant broken`);
1571
+ throw new Error(`Activity "${activity}" disappeared from mutation copy \u2014 invariant broken`);
1571
1572
  return mutEntry;
1572
1573
  }
1573
1574
  function findCurrentStageEntry(instance) {
1574
1575
  return findOpenStageEntry(instance);
1575
1576
  }
1576
- function findCurrentTasks(instance) {
1577
- return findCurrentStageEntry(instance)?.tasks ?? [];
1578
- }
1579
- const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
1580
- function validateTag(tag) {
1581
- if (!TAG_RE.test(tag))
1582
- throw new Error(
1583
- `tag: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
1584
- );
1585
- }
1586
- function tagScopeFilter() {
1587
- return "tag == $tag";
1588
- }
1589
- function definitionLookupGroq(explicit) {
1590
- const scoped = `_type == "${WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}`;
1591
- return explicit ? `*[${scoped} && version == $version][0]` : `*[${scoped}] | order(version desc)[0]`;
1592
- }
1593
- async function discoverItems(args) {
1594
- const { client, groq, params, perspective } = args, fetchOptions = perspective !== void 0 ? { perspective } : void 0;
1595
- return client.fetch(groq, paramsForLake(params), fetchOptions);
1577
+ function findCurrentActivities(instance) {
1578
+ return findCurrentStageEntry(instance)?.activities ?? [];
1596
1579
  }
1597
- function clientForDiscovery(defaultClient, clientForGdr, subjectGdrUri) {
1598
- if (!subjectGdrUri || !subjectGdrUri.includes(":")) return defaultClient;
1580
+ async function deployOrRollback(args) {
1599
1581
  try {
1600
- return clientForGdr(parseGdr(subjectGdrUri));
1601
- } catch {
1602
- return defaultClient;
1582
+ await args.deploy();
1583
+ } catch (guardError) {
1584
+ if (!args.reversible)
1585
+ throw new WorkflowStateDivergedError({
1586
+ instanceId: args.instanceId,
1587
+ guardError,
1588
+ reason: "the move created child instances a parent-only rollback cannot undo"
1589
+ });
1590
+ try {
1591
+ let patch = args.client.patch(args.instanceId).set(args.restore);
1592
+ args.unset && args.unset.length > 0 && (patch = patch.unset(args.unset)), args.committedRev !== void 0 && (patch = patch.ifRevisionId(args.committedRev)), await patch.commit(SYNC_COMMIT);
1593
+ } catch (rollbackError) {
1594
+ throw new WorkflowStateDivergedError({
1595
+ instanceId: args.instanceId,
1596
+ guardError,
1597
+ rollbackError,
1598
+ reason: "rollback of the committed move failed"
1599
+ });
1600
+ }
1601
+ throw guardError instanceof PartialGuardDeployError ? new WorkflowStateDivergedError({
1602
+ instanceId: args.instanceId,
1603
+ guardError,
1604
+ reason: "a multi-guard deploy partially applied; the rolled-back move left orphaned guard locks"
1605
+ }) : guardError;
1603
1606
  }
1604
1607
  }
1605
- async function reload(client, instanceId, tag) {
1606
- const doc = await client.getDocument(instanceId);
1607
- if (!doc)
1608
- throw new Error(`Workflow instance ${instanceId} not found`);
1609
- if (doc.tag !== tag)
1610
- throw new Error(`Workflow instance ${instanceId} not visible to this engine (tag mismatch)`);
1611
- return doc;
1608
+ async function persistThenDeploy(ctx, mutation, deploy) {
1609
+ const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
1610
+ await deployOrRollback({
1611
+ client: ctx.client,
1612
+ instanceId: ctx.instance._id,
1613
+ committedRev: committed._rev,
1614
+ restore: instanceStateFields(ctx.instance),
1615
+ unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
1616
+ reversible: !spawned,
1617
+ deploy
1618
+ });
1612
1619
  }
1613
- function effectsContextEntry(name, value) {
1614
- const _key = randomKey();
1615
- 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 };
1620
+ async function refreshStageGuards(ctx, mutation, stageName) {
1621
+ await deployStageGuards({
1622
+ client: ctx.client,
1623
+ clientForGdr: ctx.clientForGdr,
1624
+ instance: materializeInstance(ctx.instance, mutation),
1625
+ definition: ctx.definition,
1626
+ stageName,
1627
+ now: ctx.now
1628
+ });
1616
1629
  }
1617
- function effectsContextJsonEntry(name, value) {
1618
- return { _key: randomKey(), _type: "effectsContext.json", name, value: JSON.stringify(value) };
1630
+ async function completeEffect(args) {
1631
+ const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
1632
+ ...options?.clock ? { clock: options.clock } : {},
1633
+ ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
1634
+ });
1635
+ return commitCompleteEffect(
1636
+ ctx,
1637
+ effectKey,
1638
+ status,
1639
+ outputs,
1640
+ detail,
1641
+ error,
1642
+ durationMs,
1643
+ options?.actor
1644
+ );
1619
1645
  }
1620
- function buildInstanceBase(args) {
1621
- const { id, now, actor, perspective } = args;
1622
- return {
1623
- _id: id,
1624
- _type: WORKFLOW_INSTANCE_TYPE,
1625
- _rev: "",
1626
- _createdAt: now,
1627
- _updatedAt: now,
1628
- tag: args.tag,
1629
- workflowResource: args.workflowResource,
1630
- definition: args.definitionName,
1631
- pinnedVersion: args.pinnedVersion,
1632
- definitionSnapshot: JSON.stringify(args.definition),
1633
- fields: args.fields,
1634
- effectsContext: args.effectsContext,
1635
- ancestors: args.ancestors,
1636
- ...perspective !== void 0 ? { perspective } : {},
1637
- currentStage: args.initialStage,
1638
- stages: [],
1639
- pendingEffects: [],
1640
- effectHistory: [],
1641
- history: [
1642
- {
1643
- _key: randomKey(),
1644
- _type: "stageEntered",
1645
- at: now,
1646
- stage: args.initialStage,
1647
- ...actor !== void 0 ? { actor } : {}
1648
- }
1649
- ],
1650
- startedAt: now,
1651
- lastChangedAt: now
1652
- };
1653
- }
1654
- 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";
1655
- function gateActor(outcome) {
1656
- return {
1657
- kind: "system",
1658
- id: outcome === "failed" ? FAIL_WHEN_SYSTEM_ID : COMPLETE_WHEN_SYSTEM_ID
1659
- };
1660
- }
1661
- function effectiveCompleteWhen(task) {
1662
- return task.completeWhen ?? (task.subworkflows !== void 0 ? SUBWORKFLOWS_ALL_DONE : void 0);
1663
- }
1664
- async function evaluateResolutionGate(ctx, task, vars) {
1665
- const scope = { taskName: task.name, vars };
1666
- if (task.failWhen !== void 0 && await ctxEvaluateCondition(ctx, task.failWhen, scope))
1667
- return "failed";
1668
- const completeWhen = effectiveCompleteWhen(task);
1669
- if (completeWhen !== void 0 && await ctxEvaluateCondition(ctx, completeWhen, scope))
1670
- return "done";
1671
- }
1672
- async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
1673
- if (ctx.instance.ancestors.length + 1 > MAX_SPAWN_DEPTH) {
1674
- const chain = [...ctx.instance.ancestors.map((a) => a.id), selfGdr(ctx.instance)].join(" \u2192 ");
1675
- throw new Error(
1676
- `Subworkflow depth limit (${MAX_SPAWN_DEPTH}) exceeded on task "${task.name}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
1677
- );
1678
- }
1679
- const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tag);
1680
- if (definition === null) {
1681
- const versionLabel = sub.definition.version === void 0 || sub.definition.version === "latest" ? "latest" : `v${sub.definition.version}`;
1682
- throw new Error(
1683
- `Subworkflow definition "${sub.definition.name}" ${versionLabel} not deployed (parent ${ctx.instance._id}, task ${task.name})`
1684
- );
1685
- }
1686
- const parentScope = await ctxConditionParams(ctx, {
1687
- taskName: task.name,
1688
- ...actor ? { actor } : {}
1689
- }), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
1690
- for (const row of rows) {
1691
- const initialFields = await projectRowFields(
1692
- ctx,
1693
- sub,
1694
- definition,
1695
- parentScope,
1696
- row,
1697
- discoveryResource
1698
- ), { ref, body } = await prepareChildInstance({
1699
- client: ctx.client,
1700
- parent: ctx.instance,
1701
- definition,
1702
- initialFields,
1703
- effectsContext,
1704
- actor,
1705
- now
1706
- });
1707
- refs.push(ref), mutation.pendingCreates.push(body);
1708
- }
1709
- return refs;
1710
- }
1711
- async function resolveSpawnRows(ctx, sub) {
1712
- const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
1713
- let value, discoveryResource;
1714
- if (groq.includes("*")) {
1715
- const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
1716
- client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
1717
- client,
1718
- groq,
1719
- params,
1720
- // Draft-aware by default (drafts overlay published) when there's no
1721
- // release, so a `forEach` discovers in-flight items the same way the
1722
- // engine reads any other content.
1723
- perspective: ctx.instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
1724
- });
1725
- } else {
1726
- const tree = parse(groq, { params });
1727
- value = await (await evaluate(tree, { dataset: [ctx.instance], params, root: ctx.instance })).get();
1728
- }
1729
- return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
1730
- }
1731
- async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
1732
- const out = [];
1733
- for (const [name, groq] of Object.entries(sub.with ?? {})) {
1734
- const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
1735
- if (declared === void 0)
1736
- throw new Error(
1737
- `subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
1738
- );
1739
- const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
1740
- parentResource: ctx.instance.workflowResource,
1741
- discoveryResource,
1742
- entryName: name,
1743
- childName: childDefinition.name
1744
- });
1745
- value != null && out.push({
1746
- type: declared.type,
1747
- name,
1748
- value
1749
- });
1750
- }
1751
- return out;
1752
- }
1753
- async function resolveContextHandoff(ctx, sub, parentScope) {
1754
- const out = {};
1755
- for (const [name, groq] of Object.entries(sub.context ?? {})) {
1756
- const v2 = await runGroq(groq, parentScope, ctx.snapshot);
1757
- v2 != null && (typeof v2 == "string" || typeof v2 == "number" || typeof v2 == "boolean" || typeof v2 == "object" && "id" in v2 && "type" in v2) && (out[name] = v2);
1758
- }
1759
- return out;
1760
- }
1761
- function canonicaliseSpawnValue(kind, value, cx) {
1762
- return kind === "doc.ref" ? coerceSpawnGdr(value, cx) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, cx)).filter((v2) => v2 !== null) : value;
1763
- }
1764
- function assertBareIdRootsCorrectly(id, cx) {
1765
- if (!(cx.discoveryResource === void 0 || sameResource(cx.discoveryResource, cx.parentResource)))
1766
- throw new Error(
1767
- `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.`
1768
- );
1769
- }
1770
- function coerceSpawnGdr(raw, cx) {
1771
- const toUri = (id) => isGdrUri(id) ? id : (assertBareIdRootsCorrectly(id, cx), gdrFromResource(cx.parentResource, id));
1772
- if (raw == null) return null;
1773
- if (typeof raw == "object" && "id" in raw && "type" in raw) {
1774
- const r = raw;
1775
- if (typeof r.id == "string" && typeof r.type == "string")
1776
- return { id: toUri(r.id), type: r.type };
1777
- }
1778
- if (typeof raw == "object" && "_ref" in raw) {
1779
- const r = raw;
1780
- if (typeof r._ref == "string") return { id: toUri(r._ref), type: "document" };
1781
- }
1782
- return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
1783
- }
1784
- async function resolveDefinitionRef(client, ref, tag) {
1785
- const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
1786
- return wantsExplicit && (params.version = ref.version), await client.fetch(
1787
- definitionLookupGroq(wantsExplicit),
1788
- params
1789
- ) ?? null;
1790
- }
1791
- async function prepareChildInstance(args) {
1792
- 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 = [
1793
- ...parent.ancestors,
1794
- {
1795
- id: selfGdr(parent),
1796
- type: WORKFLOW_INSTANCE_TYPE
1797
- }
1798
- ], inheritedPerspective = parent.perspective, childFields = await resolveDeclaredFields({
1799
- entryDefs: definition.fields,
1800
- initialFields,
1801
- ctx: {
1802
- client,
1803
- now,
1804
- selfId: childDocId,
1805
- tag: childTag,
1806
- workflowResource,
1807
- definitionName: definition.name,
1808
- ...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
1809
- },
1810
- randomKey
1811
- }), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
1812
- ([key, value]) => {
1813
- if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
1814
- if (!isGdrUri(value.id))
1815
- throw new Error(
1816
- `subworkflows.context["${key}"]: GDR id must be a "<scheme>:..." URI, got ${JSON.stringify(value.id)}.`
1817
- );
1818
- return effectsContextEntry(key, { id: value.id, type: value.type });
1819
- }
1820
- return effectsContextEntry(key, value);
1821
- }
1822
- ), base = buildInstanceBase({
1823
- id: childDocId,
1824
- now,
1825
- tag: childTag,
1826
- workflowResource,
1827
- definitionName: definition.name,
1828
- pinnedVersion: definition.version,
1829
- definition,
1830
- fields: childFields,
1831
- effectsContext: effectsContextEntries,
1832
- ancestors,
1833
- perspective: childPerspective,
1834
- initialStage: definition.initialStage,
1835
- actor
1836
- });
1837
- return { ref: childRef, body: base };
1838
- }
1839
- async function subworkflowRows(ctx, refs) {
1840
- if (refs.length === 0) return [];
1841
- const ids = refs.map((r) => gdrToBareId(r.id));
1842
- return (await ctx.client.fetch("*[_id in $ids]{_id, currentStage, completedAt}", { ids })).map((c) => ({
1843
- _id: c._id,
1844
- stage: c.currentStage,
1845
- status: c.completedAt !== void 0 && c.completedAt !== null ? "done" : "active"
1846
- }));
1847
- }
1848
- async function resolveBindings(args) {
1849
- const resolved = {};
1850
- for (const [key, groq] of Object.entries(args.bindings ?? {}))
1851
- resolved[key] = await runGroq(groq, args.params, args.snapshot);
1852
- return { ...resolved, ...args.staticInput };
1853
- }
1854
- async function persistThenDeploy(ctx, mutation, deploy) {
1855
- const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
1856
- await deployOrRollback({
1857
- client: ctx.client,
1858
- instanceId: ctx.instance._id,
1859
- committedRev: committed._rev,
1860
- restore: instanceStateFields(ctx.instance),
1861
- unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
1862
- reversible: !spawned,
1863
- deploy
1864
- });
1865
- }
1866
- async function refreshStageGuards(ctx, mutation, stageName) {
1867
- await deployStageGuards({
1868
- client: ctx.client,
1869
- clientForGdr: ctx.clientForGdr,
1870
- instance: materializeInstance(ctx.instance, mutation),
1871
- definition: ctx.definition,
1872
- stageName,
1873
- now: ctx.now
1874
- });
1875
- }
1876
- async function completeEffect(args) {
1877
- const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
1878
- ...options?.clock ? { clock: options.clock } : {},
1879
- ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
1880
- });
1881
- return commitCompleteEffect(
1882
- ctx,
1883
- effectKey,
1884
- status,
1885
- outputs,
1886
- detail,
1887
- error,
1888
- durationMs,
1889
- options?.actor
1890
- );
1891
- }
1892
- function buildEffectHistoryEntry(pending, outcome) {
1893
- const { status, ranAt, actor, detail, error, durationMs, outputs } = outcome, resolvedActor = actor ?? pending.actor;
1646
+ function buildEffectHistoryEntry(pending, outcome) {
1647
+ const { status, ranAt, actor, detail, error, durationMs, outputs } = outcome, resolvedActor = actor ?? pending.actor;
1894
1648
  return {
1895
1649
  _key: pending._key,
1896
1650
  name: pending.name,
@@ -1963,7 +1717,7 @@ function buildQueuedEffect(effect, origin, params, actor, now) {
1963
1717
  async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
1964
1718
  if (!effects || effects.length === 0) return;
1965
1719
  const now = ctx.now, liveCtx = { ...ctx, instance: materializeInstance(ctx.instance, mutation) }, params = await ctxConditionParams(liveCtx, {
1966
- ...opts?.taskName !== void 0 ? { taskName: opts.taskName } : {},
1720
+ ...opts?.activityName !== void 0 ? { activityName: opts.activityName } : {},
1967
1721
  ...actor !== void 0 ? { actor } : {},
1968
1722
  // Caller-supplied action params, readable in bindings as `$params.<name>`.
1969
1723
  vars: { params: opts?.callerParams ?? {} }
@@ -1978,18 +1732,74 @@ async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
1978
1732
  mutation.pendingEffects.push(pending), mutation.history.push(history);
1979
1733
  }
1980
1734
  }
1981
- function resolveStaticSource(src, ctx) {
1735
+ function fieldQueryDiscardedEntry(args) {
1736
+ return {
1737
+ _key: randomKey(),
1738
+ _type: "fieldQueryDiscarded",
1739
+ at: args.at,
1740
+ scope: args.scope,
1741
+ field: args.field,
1742
+ detail: args.detail
1743
+ };
1744
+ }
1745
+ function recordFieldDiscards(target, scope, at) {
1746
+ return (discard) => target.push(fieldQueryDiscardedEntry({ scope, field: discard.field, detail: discard.detail, at }));
1747
+ }
1748
+ function applyActivityStatusChange(args) {
1749
+ const { entry, history, stage, to, at, actor } = args, from = entry.status;
1750
+ entry.status = to, isTerminalActivityStatus(to) && (entry.completedAt = at, actor !== void 0 && (entry.completedBy = actor.id)), history.push({
1751
+ _key: randomKey(),
1752
+ _type: "activityStatusChanged",
1753
+ at,
1754
+ stage,
1755
+ activity: entry.name,
1756
+ from,
1757
+ to,
1758
+ ...actor !== void 0 ? { actor } : {}
1759
+ });
1760
+ }
1761
+ function stageTransitionHistory(args) {
1762
+ const { fromStage, toStage, at, transition, actor, via, reason } = args, shared = {
1763
+ at,
1764
+ ...transition !== void 0 ? { transition } : {},
1765
+ ...actor !== void 0 ? { actor } : {},
1766
+ ...via !== void 0 ? { via } : {},
1767
+ ...reason !== void 0 ? { reason } : {}
1768
+ };
1769
+ return [
1770
+ {
1771
+ _key: randomKey(),
1772
+ _type: "stageExited",
1773
+ stage: fromStage,
1774
+ toStage,
1775
+ ...shared
1776
+ },
1777
+ {
1778
+ _key: randomKey(),
1779
+ _type: "transitionFired",
1780
+ fromStage,
1781
+ toStage,
1782
+ ...shared
1783
+ },
1784
+ {
1785
+ _key: randomKey(),
1786
+ _type: "stageEntered",
1787
+ stage: toStage,
1788
+ fromStage,
1789
+ ...shared
1790
+ }
1791
+ ];
1792
+ }
1793
+ function resolveStaticValueExpr(src, ctx) {
1982
1794
  switch (src.type) {
1983
1795
  case "literal":
1984
- return { handled: !0, value: src.value };
1796
+ return src.value;
1985
1797
  case "param":
1986
- return { handled: !0, value: ctx.params?.[src.param] };
1798
+ return ctx.params?.[src.param];
1987
1799
  case "actor":
1988
- return { handled: !0, value: ctx.actor };
1800
+ return ctx.actor;
1989
1801
  case "now":
1990
- return { handled: !0, value: ctx.now };
1991
- default:
1992
- return { handled: !1 };
1802
+ return ctx.now;
1993
1803
  }
1994
1804
  }
1995
1805
  const FIELD_OP_TYPES = [
@@ -2002,7 +1812,7 @@ const FIELD_OP_TYPES = [
2002
1812
  function isFieldOp(summary) {
2003
1813
  return FIELD_OP_TYPES.includes(summary.opType);
2004
1814
  }
2005
- function validateActionParams(action, taskName, callerParams) {
1815
+ function validateActionParams(action, activityName, callerParams) {
2006
1816
  const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
2007
1817
  for (const decl of declared) {
2008
1818
  const value = params[decl.name], present = decl.name in params && value !== void 0 && value !== null;
@@ -2016,7 +1826,7 @@ function validateActionParams(action, taskName, callerParams) {
2016
1826
  }));
2017
1827
  }
2018
1828
  if (issues.length > 0)
2019
- throw new ActionParamsInvalidError({ action: action.name, task: taskName, issues });
1829
+ throw new ActionParamsInvalidError({ action: action.name, activity: activityName, issues });
2020
1830
  return params;
2021
1831
  }
2022
1832
  function hasStringField(value, field) {
@@ -2049,7 +1859,15 @@ function runOps(args) {
2049
1859
  if (ops === void 0 || ops.length === 0) return [];
2050
1860
  const summaries = [];
2051
1861
  for (const op of ops) {
2052
- const summary = applyOp(op, { mutation, stage, taskName: origin.task, params, actor, self, now });
1862
+ const summary = applyOp(op, {
1863
+ mutation,
1864
+ stage,
1865
+ activityName: origin.activity,
1866
+ params,
1867
+ actor,
1868
+ self,
1869
+ now
1870
+ });
2053
1871
  summaries.push(summary), mutation.history.push(opAppliedEntry({ origin, summary, stage, actor, now }));
2054
1872
  }
2055
1873
  return summaries;
@@ -2061,7 +1879,7 @@ function opAppliedEntry(args) {
2061
1879
  _type: "opApplied",
2062
1880
  at: now,
2063
1881
  stage,
2064
- ...origin.task !== void 0 ? { task: origin.task } : {},
1882
+ ...origin.activity !== void 0 ? { activity: origin.activity } : {},
2065
1883
  ...origin.action !== void 0 ? { action: origin.action } : {},
2066
1884
  ...origin.transition !== void 0 ? { transition: origin.transition } : {},
2067
1885
  ...origin.edit === !0 ? { edit: !0 } : {},
@@ -2088,13 +1906,15 @@ function applyOp(op, ctx) {
2088
1906
  }
2089
1907
  }
2090
1908
  function applyFieldSet(op, ctx) {
2091
- const value = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
2092
- return validateFieldValue({ entryType: entry._type, entryName: entry.name, value }), setEntryValue(entry, value), { opType: op.type, target: op.target, resolved: { value } };
1909
+ const value = resolveOpValue(op.value, ctx), entry = locateEntry(ctx, op.target);
1910
+ return validateFieldValue({ entryType: entry._type, entryName: entry.name, value, ...entryShape(entry) }), setEntryValue(entry, value), { opType: op.type, target: op.target, resolved: { value } };
1911
+ }
1912
+ function entryShape(entry) {
1913
+ return entry._type === "object" ? { fields: entry.fields } : entry._type === "array" ? { of: entry.of } : {};
2093
1914
  }
2094
1915
  const EMPTY_BY_KIND = {
2095
1916
  "doc.refs": [],
2096
- checklist: [],
2097
- notes: [],
1917
+ array: [],
2098
1918
  assignees: []
2099
1919
  };
2100
1920
  function applyFieldUnset(op, ctx) {
@@ -2102,8 +1922,13 @@ function applyFieldUnset(op, ctx) {
2102
1922
  return setEntryValue(entry, EMPTY_BY_KIND[entry._type] ?? null), { opType: op.type, target: op.target };
2103
1923
  }
2104
1924
  function applyFieldAppend(op, ctx) {
2105
- const item = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
2106
- validateFieldAppendItem({ entryType: entry._type, entryName: entry.name, item });
1925
+ const item = resolveOpValue(op.value, ctx), entry = locateEntry(ctx, op.target);
1926
+ validateFieldAppendItem({
1927
+ entryType: entry._type,
1928
+ entryName: entry.name,
1929
+ item,
1930
+ of: entry._type === "array" ? entry.of : void 0
1931
+ });
2107
1932
  const current = Array.isArray(entry.value) ? entry.value : [];
2108
1933
  return setEntryValue(entry, [...current, withRowKey(item)]), { opType: op.type, target: op.target, resolved: { item } };
2109
1934
  }
@@ -2111,7 +1936,7 @@ function withRowKey(item) {
2111
1936
  return typeof item == "object" && item !== null && !Array.isArray(item) && !("_key" in item) ? { _key: randomKey(), ...item } : item;
2112
1937
  }
2113
1938
  function applyFieldUpdateWhere(op, ctx) {
2114
- const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op), merge = resolveOpSource(op.value, ctx);
1939
+ const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op), merge = resolveOpValue(op.value, ctx);
2115
1940
  if (merge === null || typeof merge != "object" || Array.isArray(merge))
2116
1941
  throw new Error(
2117
1942
  `field.updateWhere value must resolve to an object of fields to merge (target "${op.target.field}")`
@@ -2137,145 +1962,385 @@ function requireArrayValue(entry, op) {
2137
1962
  );
2138
1963
  return entry.value;
2139
1964
  }
2140
- function applyStatusSet(op, ctx) {
2141
- const entry = findOpenStageEntry(ctx.mutation)?.tasks.find((t) => t.name === op.task);
2142
- if (entry === void 0)
2143
- throw new Error(`status.set targets task "${op.task}" which has no entry in the open stage`);
2144
- return applyTaskStatusChange({
2145
- entry,
2146
- history: ctx.mutation.history,
2147
- stage: ctx.stage,
2148
- to: op.status,
2149
- at: ctx.now,
2150
- ...ctx.actor !== void 0 ? { actor: ctx.actor } : {}
2151
- }), { opType: op.type, resolved: { task: op.task, status: op.status } };
1965
+ function applyStatusSet(op, ctx) {
1966
+ const entry = findOpenStageEntry(ctx.mutation)?.activities.find((t) => t.name === op.activity);
1967
+ if (entry === void 0)
1968
+ throw new Error(
1969
+ `status.set targets activity "${op.activity}" which has no entry in the open stage`
1970
+ );
1971
+ return applyActivityStatusChange({
1972
+ entry,
1973
+ history: ctx.mutation.history,
1974
+ stage: ctx.stage,
1975
+ to: op.status,
1976
+ at: ctx.now,
1977
+ ...ctx.actor !== void 0 ? { actor: ctx.actor } : {}
1978
+ }), { opType: op.type, resolved: { activity: op.activity, status: op.status } };
1979
+ }
1980
+ function setEntryValue(entry, value) {
1981
+ entry.value = value;
1982
+ }
1983
+ function locateEntry(ctx, target) {
1984
+ const entry = fieldHost(ctx, target.scope)?.find((s) => s.name === target.field);
1985
+ if (entry === void 0)
1986
+ throw new Error(
1987
+ `Op target ${target.scope}:"${target.field}" has no resolved field entry on this instance \u2014 the deployed definition and the instance state have diverged`
1988
+ );
1989
+ return entry;
1990
+ }
1991
+ function fieldHost(ctx, scope) {
1992
+ if (scope === "workflow") return ctx.mutation.fields;
1993
+ const stageEntry = findOpenStageEntry(ctx.mutation);
1994
+ if (scope === "stage") return stageEntry?.fields;
1995
+ const activity = stageEntry?.activities.find((t) => t.name === ctx.activityName);
1996
+ return activity !== void 0 && activity.fields === void 0 && (activity.fields = []), activity?.fields;
1997
+ }
1998
+ function resolveOpValue(src, ctx) {
1999
+ switch (src.type) {
2000
+ case "literal":
2001
+ case "param":
2002
+ case "actor":
2003
+ case "now":
2004
+ return resolveStaticValueExpr(src, ctx);
2005
+ case "self":
2006
+ return ctx.self;
2007
+ case "stage":
2008
+ return ctx.stage;
2009
+ case "fieldRead": {
2010
+ const value = readEntryFromMutation(ctx, src);
2011
+ return src.path !== void 0 ? getPath(value, src.path) : value;
2012
+ }
2013
+ case "object": {
2014
+ const out = {};
2015
+ for (const [field, fieldSrc] of Object.entries(src.fields))
2016
+ out[field] = resolveOpValue(fieldSrc, ctx);
2017
+ return out;
2018
+ }
2019
+ }
2020
+ }
2021
+ function entryValue(entries, name) {
2022
+ return entries?.find((s) => s.name === name)?.value;
2023
+ }
2024
+ function readEntryFromMutation(ctx, src) {
2025
+ const scopes = src.scope !== void 0 ? [src.scope] : ["stage", "workflow"], stageEntry = findOpenStageEntry(ctx.mutation);
2026
+ for (const scope of scopes) {
2027
+ const host = scope === "workflow" ? ctx.mutation.fields : stageEntry?.fields, value = entryValue(host, src.field);
2028
+ if (value !== void 0) return value;
2029
+ }
2030
+ }
2031
+ function evalOpPredicate(p, row, ctx) {
2032
+ switch (p.type) {
2033
+ case "all":
2034
+ return p.of.every((sub) => evalOpPredicate(sub, row, ctx));
2035
+ case "any":
2036
+ return p.of.some((sub) => evalOpPredicate(sub, row, ctx));
2037
+ case "field":
2038
+ return row[p.field] === resolveOpValue(p.equals, ctx);
2039
+ }
2040
+ }
2041
+ const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
2042
+ function validateTag(tag) {
2043
+ if (!TAG_RE.test(tag))
2044
+ throw new Error(
2045
+ `tag: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
2046
+ );
2047
+ }
2048
+ function tagScopeFilter() {
2049
+ return "tag == $tag";
2050
+ }
2051
+ function definitionLookupGroq(explicit) {
2052
+ const scoped = `_type == "${WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}`;
2053
+ return explicit ? `*[${scoped} && version == $version][0]` : `*[${scoped}] | order(version desc)[0]`;
2054
+ }
2055
+ async function discoverItems(args) {
2056
+ const { client, groq, params, perspective } = args, fetchOptions = perspective !== void 0 ? { perspective } : void 0;
2057
+ return client.fetch(groq, paramsForLake(params), fetchOptions);
2058
+ }
2059
+ function clientForDiscovery(defaultClient, clientForGdr, subjectGdrUri) {
2060
+ if (!subjectGdrUri || !subjectGdrUri.includes(":")) return defaultClient;
2061
+ try {
2062
+ return clientForGdr(parseGdr(subjectGdrUri));
2063
+ } catch {
2064
+ return defaultClient;
2065
+ }
2066
+ }
2067
+ const ENGINE_ACTOR_ID_PREFIX = "engine.";
2068
+ function engineSystemActor(name) {
2069
+ return { kind: "system", id: `${ENGINE_ACTOR_ID_PREFIX}${name}` };
2070
+ }
2071
+ function isEngineActor(actor) {
2072
+ return actor.kind === "system" && actor.id.startsWith(ENGINE_ACTOR_ID_PREFIX);
2073
+ }
2074
+ const MAX_SPAWN_DEPTH = 6, SUBWORKFLOWS_ALL_DONE = "count($subworkflows[status != 'done']) == 0";
2075
+ function gateActor(outcome) {
2076
+ return engineSystemActor(outcome === "failed" ? "failWhen" : "completeWhen");
2077
+ }
2078
+ function effectiveCompleteWhen(activity) {
2079
+ return activity.completeWhen ?? (activity.subworkflows !== void 0 ? SUBWORKFLOWS_ALL_DONE : void 0);
2080
+ }
2081
+ async function evaluateResolutionGate(ctx, activity, vars) {
2082
+ const scope = { activityName: activity.name, vars };
2083
+ if (activity.failWhen !== void 0 && await ctxEvaluateCondition(ctx, activity.failWhen, scope))
2084
+ return "failed";
2085
+ const completeWhen = effectiveCompleteWhen(activity);
2086
+ if (completeWhen !== void 0 && await ctxEvaluateCondition(ctx, completeWhen, scope))
2087
+ return "done";
2088
+ }
2089
+ async function spawnSubworkflows(ctx, mutation, activity, sub, actor) {
2090
+ if (ctx.instance.ancestors.length + 1 > MAX_SPAWN_DEPTH) {
2091
+ const chain = [...ctx.instance.ancestors.map((a) => a.id), selfGdr(ctx.instance)].join(" \u2192 ");
2092
+ throw new Error(
2093
+ `Subworkflow depth limit (${MAX_SPAWN_DEPTH}) exceeded on activity "${activity.name}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
2094
+ );
2095
+ }
2096
+ const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tag);
2097
+ if (definition === null) {
2098
+ const versionLabel = sub.definition.version === void 0 || sub.definition.version === "latest" ? "latest" : `v${sub.definition.version}`;
2099
+ throw new Error(
2100
+ `Subworkflow definition "${sub.definition.name}" ${versionLabel} not deployed (parent ${ctx.instance._id}, activity ${activity.name})`
2101
+ );
2102
+ }
2103
+ const parentScope = await ctxConditionParams(ctx, {
2104
+ activityName: activity.name,
2105
+ ...actor ? { actor } : {}
2106
+ }), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
2107
+ for (const row of rows) {
2108
+ const initialFields = await projectRowFields(
2109
+ ctx,
2110
+ sub,
2111
+ definition,
2112
+ parentScope,
2113
+ row,
2114
+ discoveryResource
2115
+ ), { ref, body } = await prepareChildInstance({
2116
+ client: ctx.client,
2117
+ parent: ctx.instance,
2118
+ definition,
2119
+ initialFields,
2120
+ effectsContext,
2121
+ actor,
2122
+ now
2123
+ });
2124
+ refs.push(ref), mutation.pendingCreates.push(body);
2125
+ }
2126
+ return refs;
2127
+ }
2128
+ async function resolveSpawnRows(ctx, sub) {
2129
+ const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
2130
+ let value, discoveryResource;
2131
+ if (groq.includes("*")) {
2132
+ const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
2133
+ client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
2134
+ client,
2135
+ groq,
2136
+ params,
2137
+ // Draft-aware by default (drafts overlay published) when there's no
2138
+ // release, so a `forEach` discovers in-flight items the same way the
2139
+ // engine reads any other content.
2140
+ perspective: ctx.instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
2141
+ });
2142
+ } else {
2143
+ const tree = parse(groq, { params });
2144
+ value = await (await evaluate(tree, { dataset: [ctx.instance], params, root: ctx.instance })).get();
2145
+ }
2146
+ return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
2147
+ }
2148
+ async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
2149
+ const out = [];
2150
+ for (const [name, groq] of Object.entries(sub.with ?? {})) {
2151
+ const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
2152
+ if (declared === void 0)
2153
+ throw new Error(
2154
+ `subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
2155
+ );
2156
+ const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
2157
+ parentResource: ctx.instance.workflowResource,
2158
+ discoveryResource,
2159
+ entryName: name,
2160
+ childName: childDefinition.name
2161
+ });
2162
+ value != null && out.push({
2163
+ type: declared.type,
2164
+ name,
2165
+ value
2166
+ });
2167
+ }
2168
+ return out;
2169
+ }
2170
+ async function resolveContextHandoff(ctx, sub, parentScope) {
2171
+ const out = {};
2172
+ for (const [name, groq] of Object.entries(sub.context ?? {})) {
2173
+ const v2 = await runGroq(groq, parentScope, ctx.snapshot);
2174
+ v2 != null && (typeof v2 == "string" || typeof v2 == "number" || typeof v2 == "boolean" || typeof v2 == "object" && "id" in v2 && "type" in v2) && (out[name] = v2);
2175
+ }
2176
+ return out;
2152
2177
  }
2153
- function setEntryValue(entry, value) {
2154
- entry.value = value;
2178
+ function canonicaliseSpawnValue(kind, value, cx) {
2179
+ return kind === "doc.ref" ? coerceSpawnGdr(value, cx) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, cx)).filter((v2) => v2 !== null) : value;
2155
2180
  }
2156
- function locateEntry(ctx, target) {
2157
- const entry = fieldHost(ctx, target.scope)?.find((s) => s.name === target.field);
2158
- if (entry === void 0)
2181
+ function assertBareIdRootsCorrectly(id, cx) {
2182
+ if (!(cx.discoveryResource === void 0 || sameResource(cx.discoveryResource, cx.parentResource)))
2159
2183
  throw new Error(
2160
- `Op target ${target.scope}:"${target.field}" has no resolved field entry on this instance \u2014 the deployed definition and the instance state have diverged`
2184
+ `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.`
2161
2185
  );
2162
- return entry;
2163
2186
  }
2164
- function fieldHost(ctx, scope) {
2165
- if (scope === "workflow") return ctx.mutation.fields;
2166
- const stageEntry = findOpenStageEntry(ctx.mutation);
2167
- if (scope === "stage") return stageEntry?.fields;
2168
- const task = stageEntry?.tasks.find((t) => t.name === ctx.taskName);
2169
- return task !== void 0 && task.fields === void 0 && (task.fields = []), task?.fields;
2170
- }
2171
- function resolveOpSource(src, ctx) {
2172
- const staticValue = resolveStaticSource(src, ctx);
2173
- if (staticValue.handled) return staticValue.value;
2174
- switch (src.type) {
2175
- case "self":
2176
- return ctx.self;
2177
- case "stage":
2178
- return ctx.stage;
2179
- case "fieldRead": {
2180
- const value = readEntryFromMutation(ctx, src);
2181
- return src.path !== void 0 ? getPath(value, src.path) : value;
2182
- }
2183
- case "object": {
2184
- const out = {};
2185
- for (const [field, fieldSrc] of Object.entries(src.fields))
2186
- out[field] = resolveOpSource(fieldSrc, ctx);
2187
- return out;
2188
- }
2189
- default:
2190
- throw new Error(`Source type "${src.type}" is a field-entry origin, not a value`);
2187
+ function coerceSpawnGdr(raw, cx) {
2188
+ const toUri = (id) => isGdrUri(id) ? id : (assertBareIdRootsCorrectly(id, cx), gdrFromResource(cx.parentResource, id));
2189
+ if (raw == null) return null;
2190
+ if (typeof raw == "object" && "id" in raw && "type" in raw) {
2191
+ const r = raw;
2192
+ if (typeof r.id == "string" && typeof r.type == "string")
2193
+ return { id: toUri(r.id), type: r.type };
2194
+ }
2195
+ if (typeof raw == "object" && "_ref" in raw) {
2196
+ const r = raw;
2197
+ if (typeof r._ref == "string") return { id: toUri(r._ref), type: "document" };
2191
2198
  }
2199
+ return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
2192
2200
  }
2193
- function entryValue(entries, name) {
2194
- return entries?.find((s) => s.name === name)?.value;
2201
+ async function resolveDefinitionRef(client, ref, tag) {
2202
+ const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
2203
+ return wantsExplicit && (params.version = ref.version), await client.fetch(
2204
+ definitionLookupGroq(wantsExplicit),
2205
+ params
2206
+ ) ?? null;
2195
2207
  }
2196
- function readEntryFromMutation(ctx, src) {
2197
- const scopes = src.scope !== void 0 ? [src.scope] : ["stage", "workflow"], stageEntry = findOpenStageEntry(ctx.mutation);
2198
- for (const scope of scopes) {
2199
- const host = scope === "workflow" ? ctx.mutation.fields : stageEntry?.fields, value = entryValue(host, src.field);
2200
- if (value !== void 0) return value;
2201
- }
2208
+ async function prepareChildInstance(args) {
2209
+ const { client, parent, definition, initialFields, effectsContext, actor, now } = args, childTag = parent.tag, workflowResource = parent.workflowResource, childDocId = `${childTag}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type: WORKFLOW_INSTANCE_TYPE }, ancestors = [
2210
+ ...parent.ancestors,
2211
+ {
2212
+ id: selfGdr(parent),
2213
+ type: WORKFLOW_INSTANCE_TYPE
2214
+ }
2215
+ ], inheritedPerspective = parent.perspective, fieldDiscards = [], childFields = await resolveDeclaredFields({
2216
+ entryDefs: definition.fields,
2217
+ initialFields,
2218
+ ctx: {
2219
+ client,
2220
+ now,
2221
+ selfId: childDocId,
2222
+ tag: childTag,
2223
+ workflowResource,
2224
+ definitionName: definition.name,
2225
+ ...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {},
2226
+ recordDiscard: recordFieldDiscards(fieldDiscards, "workflow", now)
2227
+ },
2228
+ randomKey
2229
+ }), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
2230
+ ([key, value]) => {
2231
+ if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
2232
+ if (!isGdrUri(value.id))
2233
+ throw new Error(
2234
+ `subworkflows.context["${key}"]: GDR id must be a "<scheme>:..." URI, got ${JSON.stringify(value.id)}.`
2235
+ );
2236
+ return effectsContextEntry(key, { id: value.id, type: value.type });
2237
+ }
2238
+ return effectsContextEntry(key, value);
2239
+ }
2240
+ ), base = buildInstanceBase({
2241
+ id: childDocId,
2242
+ now,
2243
+ tag: childTag,
2244
+ workflowResource,
2245
+ definitionName: definition.name,
2246
+ pinnedVersion: definition.version,
2247
+ pinnedContentHash: definition.contentHash,
2248
+ definition,
2249
+ fields: childFields,
2250
+ effectsContext: effectsContextEntries,
2251
+ ancestors,
2252
+ perspective: childPerspective,
2253
+ initialStage: definition.initialStage,
2254
+ actor,
2255
+ ...fieldDiscards.length > 0 ? { extraHistory: fieldDiscards } : {}
2256
+ });
2257
+ return { ref: childRef, body: base };
2202
2258
  }
2203
- function evalOpPredicate(p, row, ctx) {
2204
- switch (p.type) {
2205
- case "all":
2206
- return p.of.every((sub) => evalOpPredicate(sub, row, ctx));
2207
- case "any":
2208
- return p.of.some((sub) => evalOpPredicate(sub, row, ctx));
2209
- case "field":
2210
- return row[p.field] === resolveOpSource(p.equals, ctx);
2211
- }
2259
+ async function subworkflowRows(ctx, refs) {
2260
+ if (refs.length === 0) return [];
2261
+ const ids = refs.map((r) => gdrToBareId(r.id));
2262
+ return (await ctx.client.fetch("*[_id in $ids]{_id, currentStage, completedAt}", { ids })).map((c) => ({
2263
+ _id: c._id,
2264
+ stage: c.currentStage,
2265
+ status: c.completedAt !== void 0 && c.completedAt !== null ? "done" : "active"
2266
+ }));
2212
2267
  }
2213
- async function invokeTask(args) {
2214
- const { client, instanceId, task: taskName, options } = args, ctx = await loadContext(client, instanceId, {
2268
+ async function invokeActivity(args) {
2269
+ const { client, instanceId, activity: activityName, options } = args, ctx = await loadContext(client, instanceId, {
2215
2270
  ...options?.clock ? { clock: options.clock } : {},
2216
2271
  ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
2217
2272
  });
2218
- return commitInvoke(ctx, taskName, options?.actor);
2273
+ return commitInvoke(ctx, activityName, options?.actor);
2219
2274
  }
2220
- async function commitInvoke(ctx, taskName, actor) {
2221
- const { task } = findTaskInCurrentStage(ctx, taskName), entry = findCurrentTasks(ctx.instance).find((t) => t.name === taskName);
2275
+ async function commitInvoke(ctx, activityName, actor) {
2276
+ const { activity } = findActivityInCurrentStage(ctx, activityName), entry = findCurrentActivities(ctx.instance).find((t) => t.name === activityName);
2222
2277
  if (entry === void 0)
2223
- throw new Error(`Task "${taskName}" has no status entry on instance ${ctx.instance._id}`);
2278
+ throw new Error(
2279
+ `Activity "${activityName}" has no status entry on instance ${ctx.instance._id}`
2280
+ );
2224
2281
  if (entry.status !== "pending")
2225
- return { invoked: !1, task: taskName };
2226
- const mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation, taskName);
2227
- return await activateTask(ctx, mutation, task, mutEntry, actor), await persist(ctx, mutation), { invoked: !0, task: taskName };
2282
+ return { invoked: !1, activity: activityName };
2283
+ const mutation = startMutation(ctx.instance), mutEntry = requireMutationActivityEntry(mutation, activityName);
2284
+ return await activateActivity(ctx, mutation, activity, mutEntry, actor), await persist(ctx, mutation), { invoked: !0, activity: activityName };
2228
2285
  }
2229
- async function activateTask(ctx, mutation, task, entry, actor) {
2286
+ async function activateActivity(ctx, mutation, activity, entry, actor) {
2230
2287
  const now = ctx.now;
2231
- if (entry.status = "active", entry.startedAt = now, task.fields !== void 0 && task.fields.length > 0) {
2288
+ if (entry.status = "active", entry.startedAt = now, activity.fields !== void 0 && activity.fields.length > 0) {
2232
2289
  const stage = findStage(ctx.definition, mutation.currentStage);
2233
- entry.fields = await resolveTaskFieldEntries({
2290
+ entry.fields = await resolveActivityFieldEntries({
2234
2291
  client: ctx.client,
2235
2292
  instance: ctx.instance,
2236
2293
  stage,
2237
- task,
2238
- now
2294
+ activity,
2295
+ now,
2296
+ recordDiscard: recordFieldDiscards(mutation.history, "activity", now)
2239
2297
  });
2240
2298
  }
2241
2299
  runOps({
2242
- ops: task.ops,
2300
+ ops: activity.ops,
2243
2301
  mutation,
2244
2302
  stage: mutation.currentStage,
2245
- origin: { task: task.name },
2303
+ origin: { activity: activity.name },
2246
2304
  params: {},
2247
2305
  actor,
2248
2306
  self: selfGdr(ctx.instance),
2249
2307
  now
2250
2308
  }), mutation.history.push({
2251
2309
  _key: randomKey(),
2252
- _type: "taskActivated",
2310
+ _type: "activityActivated",
2253
2311
  at: now,
2254
2312
  stage: mutation.currentStage,
2255
- task: task.name,
2313
+ activity: activity.name,
2256
2314
  ...actor !== void 0 ? { actor } : {}
2257
- }), await queueEffects(ctx, mutation, task.effects, { kind: "task", name: task.name }, actor, {
2258
- taskName: task.name
2259
- });
2315
+ }), await queueEffects(
2316
+ ctx,
2317
+ mutation,
2318
+ activity.effects,
2319
+ { kind: "activity", name: activity.name },
2320
+ actor,
2321
+ {
2322
+ activityName: activity.name
2323
+ }
2324
+ );
2260
2325
  let pendingChildren;
2261
- if (task.subworkflows !== void 0) {
2262
- const createsBefore = mutation.pendingCreates.length, refs = await spawnSubworkflows(ctx, mutation, task, task.subworkflows, actor);
2326
+ if (activity.subworkflows !== void 0) {
2327
+ const createsBefore = mutation.pendingCreates.length, refs = await spawnSubworkflows(ctx, mutation, activity, activity.subworkflows, actor);
2263
2328
  entry.spawnedInstances = refs, pendingChildren = mutation.pendingCreates.slice(createsBefore).map((c) => ({ _id: c._id, stage: c.currentStage, status: "active" }));
2264
2329
  for (const ref of refs)
2265
2330
  mutation.history.push({
2266
2331
  _key: randomKey(),
2267
2332
  _type: "spawned",
2268
2333
  at: now,
2269
- task: task.name,
2334
+ activity: activity.name,
2270
2335
  instanceRef: ref
2271
2336
  });
2272
2337
  }
2273
- await autoResolveOnActivate(ctx, mutation, task, entry, now, pendingChildren) || resolveMachineStep(mutation, task, entry, now);
2338
+ await autoResolveOnActivate(ctx, mutation, activity, entry, now, pendingChildren) || resolveMachineStep(mutation, activity, entry, now);
2274
2339
  }
2275
- async function autoResolveOnActivate(ctx, mutation, task, entry, now, pendingChildren) {
2276
- if (task.failWhen === void 0 && effectiveCompleteWhen(task) === void 0) return !1;
2277
- const vars = pendingChildren !== void 0 ? { subworkflows: pendingChildren } : await taskConditionVars(ctx, entry), outcome = await evaluateResolutionGate(ctx, task, vars);
2278
- return outcome === void 0 ? !1 : (applyTaskStatusChange({
2340
+ async function autoResolveOnActivate(ctx, mutation, activity, entry, now, pendingChildren) {
2341
+ if (activity.failWhen === void 0 && effectiveCompleteWhen(activity) === void 0) return !1;
2342
+ const vars = pendingChildren !== void 0 ? { subworkflows: pendingChildren } : await activityConditionVars(ctx, entry), outcome = await evaluateResolutionGate(ctx, activity, vars);
2343
+ return outcome === void 0 ? !1 : (applyActivityStatusChange({
2279
2344
  entry,
2280
2345
  history: mutation.history,
2281
2346
  stage: mutation.currentStage,
@@ -2284,29 +2349,31 @@ async function autoResolveOnActivate(ctx, mutation, task, entry, now, pendingChi
2284
2349
  actor: gateActor(outcome)
2285
2350
  }), !0);
2286
2351
  }
2287
- async function taskConditionVars(ctx, entry) {
2352
+ async function activityConditionVars(ctx, entry) {
2288
2353
  return entry.spawnedInstances === void 0 || entry.spawnedInstances.length === 0 ? { subworkflows: [] } : { subworkflows: await subworkflowRows(ctx, entry.spawnedInstances) };
2289
2354
  }
2290
- function resolveMachineStep(mutation, task, entry, now) {
2291
- const waitsForActor = task.actions !== void 0 && task.actions.length > 0, waitsForCondition = task.completeWhen !== void 0 || task.subworkflows !== void 0;
2292
- waitsForActor || waitsForCondition || applyTaskStatusChange({
2355
+ function resolveMachineStep(mutation, activity, entry, now) {
2356
+ const waitsForActor = activity.actions !== void 0 && activity.actions.length > 0, waitsForCondition = activity.completeWhen !== void 0 || activity.subworkflows !== void 0;
2357
+ waitsForActor || waitsForCondition || applyActivityStatusChange({
2293
2358
  entry,
2294
2359
  history: mutation.history,
2295
2360
  stage: mutation.currentStage,
2296
2361
  to: "done",
2297
2362
  at: now,
2298
- actor: { kind: "system", id: "engine.machineStep" }
2363
+ actor: engineSystemActor("machineStep")
2299
2364
  });
2300
2365
  }
2301
- async function buildStageTasks(ctx, stage) {
2366
+ async function buildStageActivities(ctx, stage) {
2302
2367
  const entries = [];
2303
- for (const task of stage.tasks ?? []) {
2304
- const outcome = await ctxEvaluateConditionOutcome(ctx, task.filter, { taskName: task.name }), inScope = outcome !== "unsatisfied";
2368
+ for (const activity of stage.activities ?? []) {
2369
+ const outcome = await ctxEvaluateConditionOutcome(ctx, activity.filter, {
2370
+ activityName: activity.name
2371
+ }), inScope = outcome !== "unsatisfied";
2305
2372
  entries.push({
2306
2373
  _key: randomKey(),
2307
- name: task.name,
2374
+ name: activity.name,
2308
2375
  status: inScope ? "pending" : "skipped",
2309
- ...task.filter !== void 0 ? { filterEvaluation: buildFilterEvaluation(ctx.now, task.filter, outcome) } : {}
2376
+ ...activity.filter !== void 0 ? { filterEvaluation: buildFilterEvaluation(ctx.now, activity.filter, outcome) } : {}
2310
2377
  });
2311
2378
  }
2312
2379
  return entries;
@@ -2316,7 +2383,7 @@ function buildFilterEvaluation(at, filter, outcome) {
2316
2383
  at,
2317
2384
  truthy: !1,
2318
2385
  unevaluable: !0,
2319
- 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.`
2386
+ 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.`
2320
2387
  } : { at, truthy: outcome === "satisfied" };
2321
2388
  }
2322
2389
  function isTerminalStage(stage) {
@@ -2339,15 +2406,16 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
2339
2406
  client: ctx.client,
2340
2407
  instance: ctx.instance,
2341
2408
  stage: nextStage,
2342
- now: at
2409
+ now: at,
2410
+ recordDiscard: recordFieldDiscards(mutation.history, "stage", at)
2343
2411
  }),
2344
- tasks: await buildStageTasks(ctx, nextStage)
2412
+ activities: await buildStageActivities(ctx, nextStage)
2345
2413
  };
2346
2414
  mutation.stages.push(nextStageEntry);
2347
- for (const task of nextStage.tasks ?? []) {
2348
- if (task.activation !== "auto") continue;
2349
- const entry = nextStageEntry.tasks.find((t) => t.name === task.name);
2350
- entry && entry.status === "pending" && await activateTask(ctx, mutation, task, entry, actor);
2415
+ for (const activity of nextStage.activities ?? []) {
2416
+ if (activity.activation !== "auto") continue;
2417
+ const entry = nextStageEntry.activities.find((t) => t.name === activity.name);
2418
+ entry && entry.status === "pending" && await activateActivity(ctx, mutation, activity, entry, actor);
2351
2419
  }
2352
2420
  isTerminalStage(nextStage) && (mutation.completedAt = at);
2353
2421
  }
@@ -2471,15 +2539,22 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
2471
2539
  instance,
2472
2540
  definition,
2473
2541
  ...clock ? { clock } : {}
2474
- }), now = ctx.now, initialStageEntry = {
2542
+ }), now = ctx.now, discards = [], initialStageEntry = {
2475
2543
  _key: randomKey(),
2476
2544
  name: stage.name,
2477
2545
  enteredAt: now,
2478
- fields: await resolveStageFieldEntries({ client, instance, stage, now }),
2479
- tasks: await buildStageTasks(ctx, stage)
2546
+ fields: await resolveStageFieldEntries({
2547
+ client,
2548
+ instance,
2549
+ stage,
2550
+ now,
2551
+ recordDiscard: recordFieldDiscards(discards, "stage", now)
2552
+ }),
2553
+ activities: await buildStageActivities(ctx, stage)
2480
2554
  }, committed = await client.patch(instance._id).set({
2481
2555
  stages: [initialStageEntry],
2482
- lastChangedAt: now
2556
+ lastChangedAt: now,
2557
+ ...discards.length > 0 ? { history: [...instance.history, ...discards] } : {}
2483
2558
  }).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
2484
2559
  await deployOrRollback({
2485
2560
  client,
@@ -2498,12 +2573,20 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
2498
2573
  stageName: stage.name,
2499
2574
  now
2500
2575
  })
2501
- }), await autoActivatePrimedTasks(client, instanceId, definition, stage, actor, clientForGdr, clock);
2576
+ }), await autoActivatePrimedActivities(
2577
+ client,
2578
+ instanceId,
2579
+ definition,
2580
+ stage,
2581
+ actor,
2582
+ clientForGdr,
2583
+ clock
2584
+ );
2502
2585
  }
2503
- async function autoActivatePrimedTasks(client, instanceId, definition, stage, actor, clientForGdr, clock) {
2586
+ async function autoActivatePrimedActivities(client, instanceId, definition, stage, actor, clientForGdr, clock) {
2504
2587
  const resolvedClientForGdr = clientForGdr ?? (() => client);
2505
- for (const task of stage.tasks ?? []) {
2506
- if (task.activation !== "auto") continue;
2588
+ for (const activity of stage.activities ?? []) {
2589
+ if (activity.activation !== "auto") continue;
2507
2590
  const updated = await client.getDocument(instanceId);
2508
2591
  if (!updated) continue;
2509
2592
  const updatedCtx = await buildEngineContext({
@@ -2512,18 +2595,22 @@ async function autoActivatePrimedTasks(client, instanceId, definition, stage, ac
2512
2595
  instance: updated,
2513
2596
  definition,
2514
2597
  ...clock ? { clock } : {}
2515
- }), mutation = startMutation(updated), mutEntry = currentTasks(mutation).find((t) => t.name === task.name);
2516
- mutEntry && mutEntry.status === "pending" && (await activateTask(updatedCtx, mutation, task, mutEntry, actor), await persist(updatedCtx, mutation));
2598
+ }), mutation = startMutation(updated), mutEntry = currentActivities(mutation).find((t) => t.name === activity.name);
2599
+ mutEntry && mutEntry.status === "pending" && (await activateActivity(updatedCtx, mutation, activity, mutEntry, actor), await persist(updatedCtx, mutation));
2517
2600
  }
2518
2601
  }
2519
2602
  const CASCADE_LIMIT = 100;
2520
- async function gatherAutoResolveCandidates(ctx, tasks) {
2521
- const currentTasksList = findCurrentTasks(ctx.instance), candidates = [];
2522
- for (const task of tasks) {
2523
- const entry = currentTasksList.find((e) => e.name === task.name);
2603
+ async function gatherAutoResolveCandidates(ctx, activities) {
2604
+ const currentActivitiesList = findCurrentActivities(ctx.instance), candidates = [];
2605
+ for (const activity of activities) {
2606
+ const entry = currentActivitiesList.find((e) => e.name === activity.name);
2524
2607
  if (entry?.status !== "active") continue;
2525
- const outcome = await evaluateResolutionGate(ctx, task, await taskConditionVars(ctx, entry));
2526
- outcome !== void 0 && candidates.push({ task, outcome });
2608
+ const outcome = await evaluateResolutionGate(
2609
+ ctx,
2610
+ activity,
2611
+ await activityConditionVars(ctx, entry)
2612
+ );
2613
+ outcome !== void 0 && candidates.push({ activity, outcome });
2527
2614
  }
2528
2615
  return candidates;
2529
2616
  }
@@ -2532,17 +2619,17 @@ async function resolveCompleteWhen(client, instanceId, clientForGdr, clock, over
2532
2619
  ...clientForGdr ? { clientForGdr } : {},
2533
2620
  ...clock ? { clock } : {},
2534
2621
  ...overlay ? { overlay } : {}
2535
- }), stage = findStage(ctx.definition, ctx.instance.currentStage), gatedTasks = (stage.tasks ?? []).filter(
2622
+ }), stage = findStage(ctx.definition, ctx.instance.currentStage), gatedActivities = (stage.activities ?? []).filter(
2536
2623
  (t) => effectiveCompleteWhen(t) !== void 0 || t.failWhen !== void 0
2537
2624
  );
2538
- if (gatedTasks.length === 0) return 0;
2539
- const candidates = await gatherAutoResolveCandidates(ctx, gatedTasks);
2625
+ if (gatedActivities.length === 0) return 0;
2626
+ const candidates = await gatherAutoResolveCandidates(ctx, gatedActivities);
2540
2627
  if (candidates.length === 0) return 0;
2541
2628
  const mutation = startMutation(ctx.instance);
2542
2629
  let count = 0;
2543
- for (const { task, outcome } of candidates) {
2544
- const mutEntry = currentTasks(mutation).find((t) => t.name === task.name);
2545
- mutEntry === void 0 || mutEntry.status !== "active" || (applyTaskStatusChange({
2630
+ for (const { activity, outcome } of candidates) {
2631
+ const mutEntry = currentActivities(mutation).find((t) => t.name === activity.name);
2632
+ mutEntry === void 0 || mutEntry.status !== "active" || (applyActivityStatusChange({
2546
2633
  entry: mutEntry,
2547
2634
  history: mutation.history,
2548
2635
  stage: stage.name,
@@ -2575,9 +2662,9 @@ async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
2575
2662
  if (!parent || parent._type !== WORKFLOW_INSTANCE_TYPE || typeof parent.definitionSnapshot != "string") return;
2576
2663
  const definition = parseDefinitionSnapshot(parent), stage = definition.stages.find((s) => s.name === parent.currentStage);
2577
2664
  if (stage === void 0) return;
2578
- 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);
2579
- if (task?.subworkflows === void 0) return;
2580
- const entry = parentCurrentTasks.find((e) => e.name === task.name);
2665
+ const parentCurrentActivities = findCurrentActivities(parent), activity = (stage.activities ?? []).find((t) => t.subworkflows === void 0 ? !1 : parentCurrentActivities.find((e) => e.name === t.name)?.spawnedInstances?.some((r) => gdrToBareId(r.id) === child._id) ?? !1);
2666
+ if (activity?.subworkflows === void 0) return;
2667
+ const entry = parentCurrentActivities.find((e) => e.name === activity.name);
2581
2668
  if (entry === void 0 || entry.status !== "active") return;
2582
2669
  const ctx = await buildEngineContext({
2583
2670
  client,
@@ -2585,23 +2672,27 @@ async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
2585
2672
  instance: parent,
2586
2673
  definition,
2587
2674
  ...clock ? { clock } : {}
2588
- }), outcome = await evaluateResolutionGate(ctx, task, await taskConditionVars(ctx, entry));
2675
+ }), outcome = await evaluateResolutionGate(
2676
+ ctx,
2677
+ activity,
2678
+ await activityConditionVars(ctx, entry)
2679
+ );
2589
2680
  if (outcome !== void 0)
2590
- return { parent, definition, stage, task, outcome };
2681
+ return { parent, definition, stage, activity, outcome };
2591
2682
  }
2592
2683
  async function propagateToAncestors(client, instanceId, actor, clientForGdr, clock) {
2593
2684
  const instance = await client.getDocument(instanceId);
2594
2685
  if (!instance) return;
2595
2686
  const resolvedClientForGdr = clientForGdr ?? (() => client), found = await findResolvedParentSpawn(client, instance, resolvedClientForGdr, clock);
2596
2687
  if (found === void 0) return;
2597
- const { parent, definition, stage, task, outcome } = found, ctx = await buildEngineContext({
2688
+ const { parent, definition, stage, activity, outcome } = found, ctx = await buildEngineContext({
2598
2689
  client,
2599
2690
  clientForGdr: resolvedClientForGdr,
2600
2691
  instance: parent,
2601
2692
  definition,
2602
2693
  ...clock ? { clock } : {}
2603
- }), mutation = startMutation(parent), mutEntry = currentTasks(mutation).find((e) => e.name === task.name);
2604
- mutEntry !== void 0 && (applyTaskStatusChange({
2694
+ }), mutation = startMutation(parent), mutEntry = currentActivities(mutation).find((e) => e.name === activity.name);
2695
+ mutEntry !== void 0 && (applyActivityStatusChange({
2605
2696
  entry: mutEntry,
2606
2697
  history: mutation.history,
2607
2698
  stage: stage.name,
@@ -2704,6 +2795,18 @@ async function fetchGrantsCached(requestFn, resourcePath) {
2704
2795
  return;
2705
2796
  }
2706
2797
  }
2798
+ function hasItems(arr) {
2799
+ return arr !== void 0 && arr.length > 0;
2800
+ }
2801
+ function deriveActivityKind(activity) {
2802
+ return hasItems(activity.actions) ? "user" : hasItems(activity.effects) ? "service" : activity.completeWhen !== void 0 || activity.subworkflows !== void 0 ? "receive" : "script";
2803
+ }
2804
+ function activityKind(activity) {
2805
+ return activity.kind ?? deriveActivityKind(activity);
2806
+ }
2807
+ function driverKind(actor) {
2808
+ return actor.kind === "user" ? "person" : actor.kind === "ai" ? "agent" : isEngineActor(actor) ? "engine" : "service";
2809
+ }
2707
2810
  function effectiveEditable(baseline, override) {
2708
2811
  if (baseline === void 0) return;
2709
2812
  if (override === void 0) return baseline;
@@ -2715,14 +2818,15 @@ function slotSites(definition, stage) {
2715
2818
  const sites = [];
2716
2819
  for (const entry of definition.fields ?? []) sites.push({ scope: "workflow", entry });
2717
2820
  for (const entry of stage.fields ?? []) sites.push({ scope: "stage", entry });
2718
- for (const task of stage.tasks ?? [])
2719
- for (const entry of task.fields ?? []) sites.push({ scope: "task", task: task.name, entry });
2821
+ for (const activity of stage.activities ?? [])
2822
+ for (const entry of activity.fields ?? [])
2823
+ sites.push({ scope: "activity", activity: activity.name, entry });
2720
2824
  return sites;
2721
2825
  }
2722
2826
  function toResolvedSlot(site, stage) {
2723
2827
  return {
2724
2828
  scope: site.scope,
2725
- ...site.task !== void 0 ? { task: site.task } : {},
2829
+ ...site.activity !== void 0 ? { activity: site.activity } : {},
2726
2830
  name: site.entry.name,
2727
2831
  type: site.entry.type,
2728
2832
  ...site.entry.title !== void 0 ? { title: site.entry.title } : {},
@@ -2734,16 +2838,16 @@ function editableSlotsInStage(definition, stage) {
2734
2838
  return slotSites(definition, stage).filter((site) => site.entry.editable !== void 0).map((site) => toResolvedSlot(site, stage));
2735
2839
  }
2736
2840
  function editTargetMatches(slot, target) {
2737
- 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;
2841
+ 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;
2738
2842
  }
2739
- const SCOPE_PRECEDENCE = { task: 0, stage: 1, workflow: 2 };
2843
+ const SCOPE_PRECEDENCE = { activity: 0, stage: 1, workflow: 2 };
2740
2844
  function resolveEditTarget(args) {
2741
2845
  const { definition, stage, target } = args, found = slotSites(definition, stage).filter(
2742
2846
  (site) => editTargetMatches(
2743
2847
  {
2744
2848
  scope: site.scope,
2745
2849
  name: site.entry.name,
2746
- ...site.task !== void 0 ? { task: site.task } : {}
2850
+ ...site.activity !== void 0 ? { activity: site.activity } : {}
2747
2851
  },
2748
2852
  target
2749
2853
  )
@@ -2753,9 +2857,11 @@ function resolveEditTarget(args) {
2753
2857
  function slotWindowOpen(instance, slot) {
2754
2858
  if (instance.completedAt !== void 0) return { open: !1, detail: "instance completed" };
2755
2859
  if (instance.abortedAt !== void 0) return { open: !1, detail: "instance aborted" };
2756
- if (slot.scope !== "task") return { open: !0 };
2757
- const status = findOpenStageEntry(instance)?.tasks.find((t) => t.name === slot.task)?.status;
2758
- return status === "active" ? { open: !0 } : { open: !1, detail: `task "${slot.task}" is ${status ?? "not active"}` };
2860
+ if (slot.scope !== "activity") return { open: !0 };
2861
+ const status = findOpenStageEntry(instance)?.activities.find(
2862
+ (t) => t.name === slot.activity
2863
+ )?.status;
2864
+ return status === "active" ? { open: !0 } : { open: !1, detail: `activity "${slot.activity}" is ${status ?? "not active"}` };
2759
2865
  }
2760
2866
  function readSlotValue(instance, slot) {
2761
2867
  return slotFieldHost(instance, slot)?.find((e) => e.name === slot.name)?.value;
@@ -2763,7 +2869,7 @@ function readSlotValue(instance, slot) {
2763
2869
  function slotFieldHost(instance, slot) {
2764
2870
  if (slot.scope === "workflow") return instance.fields;
2765
2871
  const stageEntry = findOpenStageEntry(instance);
2766
- return slot.scope === "stage" ? stageEntry?.fields : stageEntry?.tasks.find((t) => t.name === slot.task)?.fields;
2872
+ return slot.scope === "stage" ? stageEntry?.fields : stageEntry?.activities.find((t) => t.name === slot.activity)?.fields;
2767
2873
  }
2768
2874
  function slotProvenance(instance, ref) {
2769
2875
  let latest;
@@ -2806,12 +2912,12 @@ async function evaluateFromSnapshot(args) {
2806
2912
  throw new Error(
2807
2913
  `Instance "${instance._id}" currentStage "${instance.currentStage}" not in definition`
2808
2914
  );
2809
- const scope = await renderScope({ instance, definition, actor, grants, snapshot, now }), currentTaskEntries = findOpenStageEntry(instance)?.tasks ?? [], guardDenial = await instanceGuardReason(instance, actor, args.guards), taskEvaluations = [];
2810
- for (const task of stage.tasks ?? [])
2811
- taskEvaluations.push(
2812
- await evaluateTask({
2813
- task,
2814
- statusEntry: currentTaskEntries.find((t) => t.name === task.name),
2915
+ const scope = await renderScope({ instance, definition, actor, grants, snapshot, now }), currentActivityEntries = findOpenStageEntry(instance)?.activities ?? [], guardDenial = await instanceGuardReason(instance, actor, args.guards), activityEvaluations = [];
2916
+ for (const activity of stage.activities ?? [])
2917
+ activityEvaluations.push(
2918
+ await evaluateActivity({
2919
+ activity,
2920
+ statusEntry: currentActivityEntries.find((t) => t.name === activity.name),
2815
2921
  instance,
2816
2922
  actor,
2817
2923
  snapshot,
@@ -2836,9 +2942,9 @@ async function evaluateFromSnapshot(args) {
2836
2942
  }
2837
2943
  const currentStage = {
2838
2944
  stage,
2839
- tasks: taskEvaluations,
2945
+ activities: activityEvaluations,
2840
2946
  transitions: transitionEvaluations
2841
- }, 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({
2947
+ }, 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({
2842
2948
  instance,
2843
2949
  definition,
2844
2950
  stage,
@@ -2878,7 +2984,7 @@ async function evaluateEditableSlots(args) {
2878
2984
  }), value = readSlotValue(instance, slot);
2879
2985
  slots.push({
2880
2986
  scope: slot.scope,
2881
- ...slot.task !== void 0 ? { task: slot.task } : {},
2987
+ ...slot.activity !== void 0 ? { activity: slot.activity } : {},
2882
2988
  name: slot.name,
2883
2989
  type: slot.type,
2884
2990
  ...slot.title !== void 0 ? { title: slot.title } : {},
@@ -2894,7 +3000,14 @@ async function editPredicateSatisfied(args) {
2894
3000
  const { slot, window, guardDenial, instance, snapshot, scope, actor, roleAliases } = args;
2895
3001
  if (!window.open || guardDenial !== void 0) return !1;
2896
3002
  if (slot.effective === !0) return !0;
2897
- const params = slot.scope === "task" && slot.task !== void 0 ? taskScopeFor({ scope, instance, snapshot, actor, taskName: slot.task, roleAliases }) : scope;
3003
+ const params = slot.scope === "activity" && slot.activity !== void 0 ? activityScopeFor({
3004
+ scope,
3005
+ instance,
3006
+ snapshot,
3007
+ actor,
3008
+ activityName: slot.activity,
3009
+ roleAliases
3010
+ }) : scope;
2898
3011
  return evaluateCondition({ condition: slot.effective, snapshot, params });
2899
3012
  }
2900
3013
  async function renderScope(args) {
@@ -2922,20 +3035,20 @@ async function advisoryCan(instance, actor, grants) {
2922
3035
  });
2923
3036
  return can;
2924
3037
  }
2925
- function taskScopeFor(args) {
2926
- const { scope, instance, snapshot, actor, taskName, roleAliases } = args;
3038
+ function activityScopeFor(args) {
3039
+ const { scope, instance, snapshot, actor, activityName, roleAliases } = args;
2927
3040
  return {
2928
3041
  ...scope,
2929
3042
  fields: {
2930
3043
  ...scope.fields,
2931
- ...scopedFieldOverlay(instance, snapshot, taskName)
3044
+ ...scopedFieldOverlay(instance, snapshot, activityName)
2932
3045
  },
2933
- assigned: assignedFor(instance, taskName, actor, roleAliases)
3046
+ assigned: assignedFor(instance, activityName, actor, roleAliases)
2934
3047
  };
2935
3048
  }
2936
- async function evaluateTask(args) {
3049
+ async function evaluateActivity(args) {
2937
3050
  const {
2938
- task,
3051
+ activity,
2939
3052
  statusEntry,
2940
3053
  instance,
2941
3054
  actor,
@@ -2944,36 +3057,37 @@ async function evaluateTask(args) {
2944
3057
  roleAliases,
2945
3058
  stageHasExits,
2946
3059
  guardDenial
2947
- } = args, status = statusEntry?.status ?? "pending", taskScope = taskScopeFor({
3060
+ } = args, status = statusEntry?.status ?? "pending", activityScope = activityScopeFor({
2948
3061
  scope,
2949
3062
  instance,
2950
3063
  snapshot,
2951
3064
  actor,
2952
- taskName: task.name,
3065
+ activityName: activity.name,
2953
3066
  roleAliases
2954
- }), assigned = taskScope.assigned === !0, unmetRequirements = await evaluateRequirements({
2955
- requirements: task.requirements,
3067
+ }), assigned = activityScope.assigned === !0, unmetRequirements = await evaluateRequirements({
3068
+ requirements: activity.requirements,
2956
3069
  snapshot,
2957
- params: taskScope
3070
+ params: activityScope
2958
3071
  }), requirementsReason = unmetRequirements.length > 0 ? { kind: "requirements-unmet", unmetRequirements } : void 0, actions = [];
2959
- for (const action of task.actions ?? [])
3072
+ for (const action of activity.actions ?? [])
2960
3073
  actions.push(
2961
3074
  await evaluateAction({
2962
3075
  action,
2963
3076
  status,
2964
3077
  instance,
2965
3078
  snapshot,
2966
- taskScope,
3079
+ activityScope,
2967
3080
  stageHasExits,
2968
3081
  guardDenial,
2969
3082
  requirementsReason
2970
3083
  })
2971
3084
  );
2972
3085
  return {
2973
- task,
3086
+ activity,
2974
3087
  status,
3088
+ kind: activityKind(activity),
2975
3089
  // "pending on actor" treats both `pending` and `active` as waiting on
2976
- // the assignee — pending tasks just haven't been activated yet, but
3090
+ // the assignee — pending activities just haven't been activated yet, but
2977
3091
  // they're still inbox items. The inbox reads the assignees-kind field
2978
3092
  // entry BY KIND (who-data is fields).
2979
3093
  pendingOnActor: (status === "active" || status === "pending") && assigned,
@@ -2987,7 +3101,7 @@ async function evaluateAction(args) {
2987
3101
  status,
2988
3102
  instance,
2989
3103
  snapshot,
2990
- taskScope,
3104
+ activityScope,
2991
3105
  stageHasExits,
2992
3106
  guardDenial,
2993
3107
  requirementsReason
@@ -2995,7 +3109,7 @@ async function evaluateAction(args) {
2995
3109
  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({
2996
3110
  condition: action.filter,
2997
3111
  snapshot,
2998
- params: taskScope
3112
+ params: activityScope
2999
3113
  }) ? disabled(action, { kind: "filter-failed", filter: action.filter }) : { action, allowed: !0 };
3000
3114
  }
3001
3115
  async function instanceGuardReason(instance, actor, guards) {
@@ -3013,8 +3127,8 @@ function lifecycleReason(instance, status, stageHasExits) {
3013
3127
  return { kind: "instance-completed", completedAt: instance.completedAt };
3014
3128
  if (!stageHasExits)
3015
3129
  return { kind: "stage-terminal", stage: instance.currentStage };
3016
- if (isTerminalTaskStatus(status))
3017
- return { kind: "task-not-active", status };
3130
+ if (isTerminalActivityStatus(status))
3131
+ return { kind: "activity-not-active", status };
3018
3132
  }
3019
3133
  function disabled(action, reason) {
3020
3134
  return { action, allowed: !1, disabledReason: reason };
@@ -3022,37 +3136,30 @@ function disabled(action, reason) {
3022
3136
  function definitionDocId(_workflowResource, tag, definition, version) {
3023
3137
  return `${tag}.${definition}.v${version}`;
3024
3138
  }
3025
- async function sortByDependencies(client, definitions, tag, workflowResource) {
3139
+ async function sortByDependencies(client, definitions, tag) {
3026
3140
  const byName = /* @__PURE__ */ new Map();
3027
3141
  for (const def of definitions) {
3028
- const list = byName.get(def.name) ?? [];
3029
- list.push(def), byName.set(def.name, list);
3142
+ if (byName.has(def.name))
3143
+ throw new Error(
3144
+ `workflow.deployDefinitions: duplicate definition name "${def.name}" in batch \u2014 names identify a batch member (versions are deploy-assigned, not authored)`
3145
+ );
3146
+ byName.set(def.name, def);
3030
3147
  }
3031
3148
  const findInBatch = (ref) => findRefInBatch(byName, ref);
3032
- return await assertCrossBatchRefsDeployed(client, definitions, {
3033
- tag,
3034
- workflowResource,
3035
- findInBatch
3036
- }), topoSortDefinitions(definitions, { workflowResource, tag, findInBatch });
3149
+ return await assertCrossBatchRefsDeployed(client, definitions, { tag, findInBatch }), topoSortDefinitions(definitions, { findInBatch });
3037
3150
  }
3038
3151
  function findRefInBatch(byName, ref) {
3039
- const candidates = byName.get(ref.name);
3040
- if (!(candidates === void 0 || candidates.length === 0))
3041
- return typeof ref.version == "number" ? candidates.find((c) => c.version === ref.version) : candidates.reduce(
3042
- (best, c) => best === void 0 || c.version > best.version ? c : best,
3043
- void 0
3044
- );
3152
+ if (typeof ref.version != "number")
3153
+ return byName.get(ref.name);
3045
3154
  }
3046
3155
  async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
3047
3156
  const missing = [];
3048
- for (const def of definitions) {
3049
- const fromId = definitionDocId(ctx.workflowResource, ctx.tag, def.name, def.version);
3157
+ for (const def of definitions)
3050
3158
  for (const ref of refsOf(def)) {
3051
3159
  if (ctx.findInBatch(ref) !== void 0) continue;
3052
3160
  const label = await resolveDeployedRefLabel(client, ref, ctx.tag);
3053
- label !== void 0 && missing.push({ from: fromId, ref: label });
3161
+ label !== void 0 && missing.push({ from: def.name, ref: label });
3054
3162
  }
3055
- }
3056
3163
  if (missing.length === 0) return;
3057
3164
  const lines = missing.map((m) => ` - ${m.from} \u2192 ${m.ref} (neither in batch nor deployed)`);
3058
3165
  throw new Error(
@@ -3071,16 +3178,15 @@ async function resolveDeployedRefLabel(client, ref, tag) {
3071
3178
  }
3072
3179
  function topoSortDefinitions(definitions, ctx) {
3073
3180
  const visited = /* @__PURE__ */ new Set(), visiting = /* @__PURE__ */ new Set(), ordered = [], visit = (def) => {
3074
- const id = definitionDocId(ctx.workflowResource, ctx.tag, def.name, def.version);
3075
- if (!visited.has(id)) {
3076
- if (visiting.has(id))
3077
- throw new Error(`workflow.deployDefinitions: dependency cycle involving "${id}"`);
3078
- visiting.add(id);
3181
+ if (!visited.has(def.name)) {
3182
+ if (visiting.has(def.name))
3183
+ throw new Error(`workflow.deployDefinitions: dependency cycle involving "${def.name}"`);
3184
+ visiting.add(def.name);
3079
3185
  for (const ref of refsOf(def)) {
3080
3186
  const child = ctx.findInBatch(ref);
3081
3187
  child !== void 0 && visit(child);
3082
3188
  }
3083
- visiting.delete(id), visited.add(id), ordered.push(def);
3189
+ visiting.delete(def.name), visited.add(def.name), ordered.push(def);
3084
3190
  }
3085
3191
  };
3086
3192
  for (const def of definitions) visit(def);
@@ -3089,8 +3195,8 @@ function topoSortDefinitions(definitions, ctx) {
3089
3195
  function refsOf(def) {
3090
3196
  const out = [];
3091
3197
  for (const stage of def.stages)
3092
- for (const task of stage.tasks ?? []) {
3093
- const ref = task.subworkflows?.definition;
3198
+ for (const activity of stage.activities ?? []) {
3199
+ const ref = activity.subworkflows?.definition;
3094
3200
  ref !== void 0 && out.push({
3095
3201
  name: ref.name,
3096
3202
  ...ref.version !== void 0 ? { version: ref.version } : {}
@@ -3098,8 +3204,23 @@ function refsOf(def) {
3098
3204
  }
3099
3205
  return out;
3100
3206
  }
3101
- function isDefinitionUnchanged(existing, expected) {
3102
- return stableStringify(stripSystemFields(existing)) === stableStringify(stripSystemFields(expected));
3207
+ function hashDefinitionContent(def) {
3208
+ const canonical = stableStringify(def);
3209
+ let hash = 0xcbf29ce484222325n;
3210
+ for (let i = 0; i < canonical.length; i++)
3211
+ hash ^= BigInt(canonical.charCodeAt(i)), hash = hash * 0x100000001b3n & 0xffffffffffffffffn;
3212
+ return hash.toString(16).padStart(16, "0");
3213
+ }
3214
+ function planDefinitionDeploy(def, latest, target) {
3215
+ const contentHash = hashDefinitionContent(def), unchanged = latest !== void 0 && latest.contentHash === contentHash, version = unchanged ? latest.version : (latest?.version ?? 0) + 1, docId = definitionDocId(target.workflowResource, target.tag, def.name, version), document = {
3216
+ ...def,
3217
+ _id: docId,
3218
+ _type: WORKFLOW_DEFINITION_TYPE,
3219
+ tag: target.tag,
3220
+ version,
3221
+ contentHash
3222
+ };
3223
+ return { status: unchanged ? "unchanged" : "create", version, contentHash, docId, document };
3103
3224
  }
3104
3225
  function stripSystemFields(doc) {
3105
3226
  const out = {};
@@ -3114,22 +3235,26 @@ function stableStringify(value) {
3114
3235
  }
3115
3236
  async function loadDefinition(client, definition, version, tag) {
3116
3237
  if (version !== void 0) {
3117
- const doc = await client.fetch(
3118
- definitionLookupGroq(!0),
3119
- { definition, version, tag }
3120
- );
3238
+ const doc = await client.fetch(definitionLookupGroq(!0), {
3239
+ definition,
3240
+ version,
3241
+ tag
3242
+ });
3121
3243
  if (!doc)
3122
3244
  throw new Error(`Workflow definition ${definition} v${version} not deployed`);
3123
3245
  return doc;
3124
3246
  }
3125
- const latest = await client.fetch(definitionLookupGroq(!1), {
3126
- definition,
3127
- tag
3128
- });
3247
+ const latest = await loadLatestDeployed(client, definition, tag);
3129
3248
  if (!latest)
3130
3249
  throw new Error(`No deployed definition for workflow ${definition}`);
3131
3250
  return latest;
3132
3251
  }
3252
+ async function loadLatestDeployed(client, definition, tag) {
3253
+ return await client.fetch(definitionLookupGroq(!1), {
3254
+ definition,
3255
+ tag
3256
+ }) ?? void 0;
3257
+ }
3133
3258
  async function loadDefinitionVersions(client, definition, tag) {
3134
3259
  return client.fetch(
3135
3260
  `*[_type == "${WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}] | order(version desc)`,
@@ -3176,63 +3301,63 @@ async function commitAbort(ctx, reason, actor) {
3176
3301
  }), { fired: !0, stage: stage.name };
3177
3302
  }
3178
3303
  async function fireAction(args) {
3179
- const { client, instanceId, task, action, params, options } = args;
3304
+ const { client, instanceId, activity, action, params, options } = args;
3180
3305
  for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
3181
3306
  const ctx = await loadCallContext(client, instanceId, options);
3182
3307
  try {
3183
- return await commitAction(ctx, task, action, params, options);
3308
+ return await commitAction(ctx, activity, action, params, options);
3184
3309
  } catch (error) {
3185
3310
  if (!isRevisionConflict(error)) throw error;
3186
3311
  }
3187
3312
  }
3188
3313
  throw new ConcurrentFireActionError({
3189
3314
  instanceId,
3190
- task,
3315
+ activity,
3191
3316
  action,
3192
3317
  attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
3193
3318
  });
3194
3319
  }
3195
- async function resolveActionCommit(ctx, taskName, actionName, callerParams, options) {
3196
- const actor = options?.actor, { stage, task } = findTaskInCurrentStage(ctx, taskName), action = (task.actions ?? []).find((a) => a.name === actionName);
3320
+ async function resolveActionCommit(ctx, activityName, actionName, callerParams, options) {
3321
+ const actor = options?.actor, { stage, activity } = findActivityInCurrentStage(ctx, activityName), action = (activity.actions ?? []).find((a) => a.name === actionName);
3197
3322
  if (action === void 0)
3198
- throw new Error(`Action "${actionName}" not declared on task "${taskName}"`);
3323
+ throw new Error(`Action "${actionName}" not declared on activity "${activityName}"`);
3199
3324
  const can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan(ctx.instance, actor, options.grants) : void 0;
3200
3325
  if (!await ctxEvaluateCondition(ctx, action.filter, {
3201
- taskName,
3326
+ activityName,
3202
3327
  ...actor !== void 0 ? { actor } : {},
3203
3328
  ...can !== void 0 ? { vars: { can } } : {}
3204
3329
  }))
3205
- throw new Error(`Action "${actionName}" filter rejected on task "${taskName}"`);
3206
- const entry = findCurrentTasks(ctx.instance).find((t) => t.name === taskName);
3330
+ throw new Error(`Action "${actionName}" filter rejected on activity "${activityName}"`);
3331
+ const entry = findCurrentActivities(ctx.instance).find((t) => t.name === activityName);
3207
3332
  if (entry === void 0 || entry.status !== "active")
3208
3333
  throw new Error(
3209
- `Task "${taskName}" must be active to fire action "${actionName}"; status is ${entry?.status ?? "missing"}`
3334
+ `Activity "${activityName}" must be active to fire action "${actionName}"; status is ${entry?.status ?? "missing"}`
3210
3335
  );
3211
- const params = validateActionParams(action, taskName, callerParams);
3212
- return { stage, task, action, params };
3336
+ const params = validateActionParams(action, activityName, callerParams);
3337
+ return { stage, activity, action, params };
3213
3338
  }
3214
- async function commitAction(ctx, taskName, actionName, callerParams, options) {
3339
+ async function commitAction(ctx, activityName, actionName, callerParams, options) {
3215
3340
  const actor = options?.actor, { stage, action, params } = await resolveActionCommit(
3216
3341
  ctx,
3217
- taskName,
3342
+ activityName,
3218
3343
  actionName,
3219
3344
  callerParams,
3220
3345
  options
3221
- ), mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation, taskName), now = ctx.now;
3346
+ ), mutation = startMutation(ctx.instance), mutEntry = requireMutationActivityEntry(mutation, activityName), now = ctx.now;
3222
3347
  mutation.history.push({
3223
3348
  _key: randomKey(),
3224
3349
  _type: "actionFired",
3225
3350
  at: now,
3226
3351
  stage: stage.name,
3227
- task: taskName,
3352
+ activity: activityName,
3228
3353
  action: actionName,
3229
- ...actor !== void 0 ? { actor } : {}
3354
+ ...actor !== void 0 ? { actor, driverKind: driverKind(actor) } : {}
3230
3355
  });
3231
3356
  const statusBefore = mutEntry.status, ranOps = await runOps({
3232
3357
  ops: action.ops,
3233
3358
  mutation,
3234
3359
  stage: stage.name,
3235
- origin: { task: taskName, action: actionName },
3360
+ origin: { activity: activityName, action: actionName },
3236
3361
  params,
3237
3362
  actor,
3238
3363
  self: selfGdr(ctx.instance),
@@ -3240,14 +3365,14 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
3240
3365
  }), newStatus = mutEntry.status !== statusBefore ? mutEntry.status : void 0;
3241
3366
  return await queueEffects(ctx, mutation, action.effects, { kind: "action", name: actionName }, actor, {
3242
3367
  callerParams: params,
3243
- taskName
3368
+ activityName
3244
3369
  }), await persistThenDeploy(
3245
3370
  ctx,
3246
3371
  mutation,
3247
3372
  () => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
3248
3373
  ), {
3249
3374
  fired: !0,
3250
- task: taskName,
3375
+ activity: activityName,
3251
3376
  action: actionName,
3252
3377
  ...newStatus !== void 0 ? { newStatus } : {},
3253
3378
  ...ranOps.length > 0 ? { ranOps } : {}
@@ -3255,23 +3380,23 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
3255
3380
  }
3256
3381
  class ActionDisabledError extends Error {
3257
3382
  reason;
3258
- task;
3383
+ activity;
3259
3384
  action;
3260
3385
  constructor(args) {
3261
- super(formatDisabledReason(args.task, args.action, args.reason)), this.name = "ActionDisabledError", this.reason = args.reason, this.task = args.task, this.action = args.action;
3386
+ super(formatDisabledReason(args.activity, args.action, args.reason)), this.name = "ActionDisabledError", this.reason = args.reason, this.activity = args.activity, this.action = args.action;
3262
3387
  }
3263
3388
  }
3264
3389
  const disabledReasonDetail = {
3265
3390
  "filter-failed": (r) => `action filter returned false${r.detail ? ` (${r.detail})` : ""}`,
3266
- "task-not-active": (r) => `task status is "${r.status}"`,
3391
+ "activity-not-active": (r) => `activity status is "${r.status}"`,
3267
3392
  "stage-terminal": (r) => `stage "${r.stage}" is terminal`,
3268
3393
  "instance-completed": (r) => `instance completed at ${r.completedAt}`,
3269
3394
  "mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`,
3270
3395
  "requirements-unmet": (r) => `unmet requirement(s): ${r.unmetRequirements.join(", ")}`
3271
3396
  };
3272
- function formatDisabledReason(task, action, reason) {
3397
+ function formatDisabledReason(activity, action, reason) {
3273
3398
  const detail = disabledReasonDetail[reason.kind](reason);
3274
- return `Action "${task}:${action}" is not allowed: ${detail}`;
3399
+ return `Action "${activity}:${action}" is not allowed: ${detail}`;
3275
3400
  }
3276
3401
  class EditFieldDeniedError extends Error {
3277
3402
  reason;
@@ -3290,7 +3415,7 @@ const editDisabledReasonDetail = {
3290
3415
  function formatEditDisabledReason(target, reason) {
3291
3416
  const detail = editDisabledReasonDetail[reason.kind](
3292
3417
  reason
3293
- ), where = target.task !== void 0 ? `${target.task}.${target.field}` : target.field;
3418
+ ), where = target.activity !== void 0 ? `${target.activity}.${target.field}` : target.field;
3294
3419
  return `Field "${target.scope}:${where}" is not editable: ${detail}`;
3295
3420
  }
3296
3421
  async function editField(args) {
@@ -3322,7 +3447,7 @@ async function commitEdit(ctx, target, mode, value, options) {
3322
3447
  stage: stage.name,
3323
3448
  origin: {
3324
3449
  edit: !0,
3325
- ...slot.scope === "task" && slot.task !== void 0 ? { task: slot.task } : {}
3450
+ ...slot.scope === "activity" && slot.activity !== void 0 ? { activity: slot.activity } : {}
3326
3451
  },
3327
3452
  params: {},
3328
3453
  actor,
@@ -3341,7 +3466,7 @@ async function commitEdit(ctx, target, mode, value, options) {
3341
3466
  }
3342
3467
  async function assertSlotEditable(ctx, slot, options) {
3343
3468
  const actor = options?.actor, window = slotWindowOpen(ctx.instance, slot), can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan(ctx.instance, actor, options.grants) : void 0, predicateSatisfied = window.open && slot.effective !== !0 && slot.effective !== void 0 ? await ctxEvaluateCondition(ctx, slot.effective, {
3344
- ...slot.task !== void 0 ? { taskName: slot.task } : {},
3469
+ ...slot.activity !== void 0 ? { activityName: slot.activity } : {},
3345
3470
  ...actor !== void 0 ? { actor } : {},
3346
3471
  ...can !== void 0 ? { vars: { can } } : {}
3347
3472
  }) : slot.effective === !0, reason = editDisabledReason({
@@ -3367,18 +3492,18 @@ function slotTarget(slot) {
3367
3492
  return {
3368
3493
  scope: slot.scope,
3369
3494
  field: slot.name,
3370
- ...slot.task !== void 0 ? { task: slot.task } : {}
3495
+ ...slot.activity !== void 0 ? { activity: slot.activity } : {}
3371
3496
  };
3372
3497
  }
3373
3498
  function labelTarget(target) {
3374
3499
  return {
3375
- scope: target.scope ?? (target.task !== void 0 ? "task" : "workflow"),
3500
+ scope: target.scope ?? (target.activity !== void 0 ? "activity" : "workflow"),
3376
3501
  field: target.field,
3377
- ...target.task !== void 0 ? { task: target.task } : {}
3502
+ ...target.activity !== void 0 ? { activity: target.activity } : {}
3378
3503
  };
3379
3504
  }
3380
3505
  function describeTarget(target) {
3381
- const where = target.task !== void 0 ? `${target.task}.${target.field}` : target.field;
3506
+ const where = target.activity !== void 0 ? `${target.activity}.${target.field}` : target.field;
3382
3507
  return target.scope !== void 0 ? `${target.scope}:${where}` : where;
3383
3508
  }
3384
3509
  function bareIdFromSpawnRef(uri) {
@@ -3443,10 +3568,10 @@ async function dispatchGatedWrite(args) {
3443
3568
  ...ranOps !== void 0 ? { ranOps } : {}
3444
3569
  };
3445
3570
  }
3446
- function assertActionAllowed(evaluation, task, action) {
3447
- const actionEval = evaluation.currentStage.tasks.find((t) => t.task.name === task)?.actions.find((a) => a.action.name === action);
3571
+ function assertActionAllowed(evaluation, activity, action) {
3572
+ const actionEval = evaluation.currentStage.activities.find((t) => t.activity.name === activity)?.actions.find((a) => a.action.name === action);
3448
3573
  if (actionEval?.allowed === !1 && actionEval.disabledReason)
3449
- throw new ActionDisabledError({ task, action, reason: actionEval.disabledReason });
3574
+ throw new ActionDisabledError({ activity, action, reason: actionEval.disabledReason });
3450
3575
  }
3451
3576
  function assertEditAllowed(evaluation, target) {
3452
3577
  const slot = evaluation.editableSlots.filter((s) => editTargetMatches(s, target)).sort((a, b) => SCOPE_PRECEDENCE[a.scope] - SCOPE_PRECEDENCE[b.scope])[0];
@@ -3455,7 +3580,7 @@ function assertEditAllowed(evaluation, target) {
3455
3580
  target: {
3456
3581
  scope: slot.scope,
3457
3582
  field: slot.name,
3458
- ...slot.task !== void 0 ? { task: slot.task } : {}
3583
+ ...slot.activity !== void 0 ? { activity: slot.activity } : {}
3459
3584
  },
3460
3585
  reason: slot.disabledReason
3461
3586
  });
@@ -3480,11 +3605,11 @@ function buildEngineCallOptions(args) {
3480
3605
  };
3481
3606
  }
3482
3607
  async function applyAction(args) {
3483
- const { client, instance, task, action, params, actor, grants, clock, clientForGdr } = args, options = buildEngineCallOptions({ actor, grants, clock, clientForGdr });
3484
- return findOpenStageEntry(instance)?.tasks.find((t) => t.name === task)?.status === "pending" && await invokeTask({ client, instanceId: instance._id, task, options }), (await fireAction({
3608
+ const { client, instance, activity, action, params, actor, grants, clock, clientForGdr } = args, options = buildEngineCallOptions({ actor, grants, clock, clientForGdr });
3609
+ return findOpenStageEntry(instance)?.activities.find((t) => t.name === activity)?.status === "pending" && await invokeActivity({ client, instanceId: instance._id, activity, options }), (await fireAction({
3485
3610
  client,
3486
3611
  instanceId: instance._id,
3487
- task,
3612
+ activity,
3488
3613
  action,
3489
3614
  ...params !== void 0 ? { params } : {},
3490
3615
  options
@@ -3564,14 +3689,16 @@ function previewIds(ids) {
3564
3689
  }
3565
3690
  const workflow = {
3566
3691
  /**
3567
- * Deploy a set of workflow definitions as one call. Each lands as a
3568
- * {@link WORKFLOW_DEFINITION_TYPE} document; subsequent versions are
3569
- * stored alongside earlier ones `startInstance` picks the highest
3570
- * version by default. The SDK figures out the dependency order itself
3571
- * (children before parents that spawn them via
3572
- * `subworkflows.definition`), idempotently writes any definition
3573
- * whose content has changed, and reports a per-definition outcome
3574
- * (`created` / `updated` / `unchanged`).
3692
+ * Deploy a set of workflow definitions as one call. Definitions are
3693
+ * immutable and content-addressed: the author writes no version, and each
3694
+ * deploy compares a definition's content fingerprint to the latest version
3695
+ * already deployed under its name. Identical content is a no-op
3696
+ * (`unchanged`); any change mints the next version (`created`) — deploy
3697
+ * never patches a deployed version, so a definition can't change out from
3698
+ * under the instances pinned to it. `startInstance` picks the highest
3699
+ * version by default. The engine figures out the dependency order itself
3700
+ * (children before parents that spawn them via `subworkflows.definition`)
3701
+ * and reports a per-definition outcome (`created` / `unchanged`).
3575
3702
  *
3576
3703
  * Refs may point inside the batch OR at already-deployed definitions
3577
3704
  * in the lake — both are valid. A ref pointing at neither errors with
@@ -3584,31 +3711,15 @@ const workflow = {
3584
3711
  validateTag(tag);
3585
3712
  for (const def of definitions)
3586
3713
  validateDefinition(def);
3587
- const ordered = await sortByDependencies(client, definitions, tag, workflowResource), results = [], tx = client.transaction();
3714
+ const ordered = await sortByDependencies(client, definitions, tag), results = [], tx = client.transaction();
3588
3715
  let hasWrites = !1;
3589
3716
  for (const def of ordered) {
3590
- const id = definitionDocId(workflowResource, tag, def.name, def.version), expected = {
3591
- ...def,
3592
- _id: id,
3593
- _type: WORKFLOW_DEFINITION_TYPE,
3594
- tag
3595
- }, existing = await client.getDocument(id);
3596
- if (existing && existing.tag === tag && isDefinitionUnchanged(existing, expected)) {
3597
- results.push({ definition: def.name, version: def.version, status: "unchanged" });
3717
+ const latest = await loadLatestDeployed(client, def.name, tag), plan = planDefinitionDeploy(def, latest, { tag, workflowResource });
3718
+ if (plan.status === "unchanged") {
3719
+ results.push({ definition: def.name, version: plan.version, status: "unchanged" });
3598
3720
  continue;
3599
3721
  }
3600
- if (existing) {
3601
- const expectedKeys = new Set(Object.keys(expected)), toUnset = Object.keys(existing).filter(
3602
- (k) => !k.startsWith("_") && !expectedKeys.has(k)
3603
- ), patch = client.patch(id).set(expected).ifRevisionId(existing._rev);
3604
- toUnset.length > 0 && patch.unset(toUnset), tx.patch(patch);
3605
- } else
3606
- tx.create(expected);
3607
- hasWrites = !0, results.push({
3608
- definition: def.name,
3609
- version: def.version,
3610
- status: existing ? "updated" : "created"
3611
- });
3722
+ tx.create(plan.document), hasWrites = !0, results.push({ definition: def.name, version: plan.version, status: "created" });
3612
3723
  }
3613
3724
  return hasWrites && await tx.commit(), { results };
3614
3725
  },
@@ -3624,8 +3735,8 @@ const workflow = {
3624
3735
  * Spawn a new workflow instance from a deployed definition.
3625
3736
  *
3626
3737
  * Pins the snapshot at start-time, seeds `effectsContext`, enters
3627
- * the initial stage (which builds taskStatus, queues onEnter
3628
- * effects, auto-activates tasks). Then cascades auto-transitions
3738
+ * the initial stage (which builds activityStatus, queues onEnter
3739
+ * effects, auto-activates activities). Then cascades auto-transitions
3629
3740
  * until stable. Returns the resulting instance.
3630
3741
  */
3631
3742
  startInstance: async (args) => {
@@ -3645,7 +3756,7 @@ const workflow = {
3645
3756
  throw new Error(
3646
3757
  `Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
3647
3758
  );
3648
- const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedFields = await resolveDeclaredFields({
3759
+ const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], fieldDiscards = [], resolvedFields = await resolveDeclaredFields({
3649
3760
  entryDefs: definition.fields ?? [],
3650
3761
  initialFields: initialFields ?? [],
3651
3762
  ctx: {
@@ -3655,7 +3766,8 @@ const workflow = {
3655
3766
  tag,
3656
3767
  workflowResource,
3657
3768
  definitionName: definition.name,
3658
- ...perspective !== void 0 ? { perspective } : {}
3769
+ ...perspective !== void 0 ? { perspective } : {},
3770
+ recordDiscard: recordFieldDiscards(fieldDiscards, "workflow", now)
3659
3771
  },
3660
3772
  randomKey
3661
3773
  }), effectivePerspective = perspective ?? derivePerspectiveFromFields(resolvedFields), base = buildInstanceBase({
@@ -3665,18 +3777,20 @@ const workflow = {
3665
3777
  workflowResource,
3666
3778
  definitionName: definition.name,
3667
3779
  pinnedVersion: definition.version,
3780
+ pinnedContentHash: definition.contentHash,
3668
3781
  definition,
3669
3782
  fields: resolvedFields,
3670
3783
  effectsContext: effectsContextEntries,
3671
3784
  ancestors: ancestors ?? [],
3672
3785
  perspective: effectivePerspective,
3673
3786
  initialStage: definition.initialStage,
3674
- actor
3787
+ actor,
3788
+ ...fieldDiscards.length > 0 ? { extraHistory: fieldDiscards } : {}
3675
3789
  });
3676
3790
  return await client.create(base, SYNC_COMMIT), await primeInitialStage(client, id, actor, clientForGdr, clock), await cascade(client, id, actor, clientForGdr, clock), reload(client, id, tag);
3677
3791
  },
3678
3792
  /**
3679
- * Fire an action against a task. If the task is pending, it is
3793
+ * Fire an action against an activity. If the activity is pending, it is
3680
3794
  * auto-invoked first (pending → active) so callers don't have to know
3681
3795
  * the lifecycle. Cascades auto-transitions and propagates to ancestors
3682
3796
  * after the action commits.
@@ -3690,13 +3804,13 @@ const workflow = {
3690
3804
  client,
3691
3805
  tag,
3692
3806
  instanceId,
3693
- task,
3807
+ activity,
3694
3808
  action,
3695
3809
  params,
3696
3810
  idempotent,
3697
3811
  resourceClients
3698
3812
  } = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
3699
- return findOpenStageEntry(before)?.tasks.find((t) => t.name === task) === void 0 && idempotent === !0 ? { instance: before, cascaded: 0, fired: !1 } : dispatchGatedWrite({
3813
+ return findOpenStageEntry(before)?.activities.find((t) => t.name === activity) === void 0 && idempotent === !0 ? { instance: before, cascaded: 0, fired: !1 } : dispatchGatedWrite({
3700
3814
  client,
3701
3815
  tag,
3702
3816
  instanceId,
@@ -3706,10 +3820,10 @@ const workflow = {
3706
3820
  clock,
3707
3821
  before,
3708
3822
  ...resourceClients !== void 0 ? { resourceClients } : {},
3709
- apply: (instance, evaluation) => (assertActionAllowed(evaluation, task, action), applyAction({
3823
+ apply: (instance, evaluation) => (assertActionAllowed(evaluation, activity, action), applyAction({
3710
3824
  client,
3711
3825
  instance,
3712
- task,
3826
+ activity,
3713
3827
  action,
3714
3828
  actor,
3715
3829
  ...access.grants !== void 0 ? { grants: access.grants } : {},
@@ -3790,7 +3904,7 @@ const workflow = {
3790
3904
  * Re-evaluate auto-transitions and resolve due waits until stable.
3791
3905
  *
3792
3906
  * Used by the runtime after any event that might affect the workflow
3793
- * but isn't itself a task action: a subject doc was patched, a sibling
3907
+ * but isn't itself an activity action: a subject doc was patched, a sibling
3794
3908
  * workflow completed, the clock crossed a `completeWhen` deadline, etc.
3795
3909
  * The runtime doesn't need to know what changed — it just nudges
3796
3910
  * affected instances and the engine re-evaluates.
@@ -3900,8 +4014,8 @@ const workflow = {
3900
4014
  * declared by a `doc.ref` / `doc.refs` entry in scope), then
3901
4015
  * evaluates the supplied GROQ in groq-js against that dataset. The
3902
4016
  * rendered scope is auto-bound: `$self`, `$fields`, `$parent`,
3903
- * `$ancestors`, `$stage`, `$now`, `$effects`, `$tasks`,
3904
- * `$allTasksDone`, `$anyTaskFailed` — ids in GDR URI form to match
4017
+ * `$ancestors`, `$stage`, `$now`, `$effects`, `$activities`,
4018
+ * `$allActivitiesDone`, `$anyActivityFailed` — ids in GDR URI form to match
3905
4019
  * the snapshot's keying.
3906
4020
  *
3907
4021
  * Use when an external consumer (a UI, a debug pane, a test) wants
@@ -3954,34 +4068,83 @@ const workflow = {
3954
4068
  * List the actions an actor could fire on an instance's current stage,
3955
4069
  * each flagged `allowed` (with a structured `disabledReason` when not).
3956
4070
  * Projects the instance from the actor's perspective and flattens its
3957
- * tasks' actions. Returns the evaluation alongside the actions so a
4071
+ * activities' actions. Returns the evaluation alongside the actions so a
3958
4072
  * consumer can read the instance/stage context. Pure read.
3959
4073
  */
3960
4074
  availableActions: async (args) => {
3961
4075
  const evaluation = await evaluateInstance(args);
3962
- return { evaluation, actions: availableActions(evaluation.currentStage.tasks) };
4076
+ return { evaluation, actions: availableActions(evaluation.currentStage.activities) };
3963
4077
  },
3964
4078
  /**
3965
4079
  * Materialised spawned children of a parent instance.
3966
4080
  *
3967
4081
  * Walks `history` for `spawned` events — the durable
3968
- * record. (The per-task `spawnedInstances` field on the active stage
4082
+ * record. (The per-activity `spawnedInstances` field on the active stage
3969
4083
  * only exists while that stage is current; history survives.) Strips
3970
4084
  * the GDR URI on each `instanceRef` to a bare `_id`, fetches the
3971
4085
  * instances, drops any that aren't visible to this engine's tag, and
3972
4086
  * returns them sorted by `startedAt` ascending.
3973
4087
  *
3974
- * Pass `task` to restrict to a single spawning task on the parent.
4088
+ * Pass `activity` to restrict to a single spawning activity on the parent.
3975
4089
  */
3976
4090
  children: async (args) => {
3977
- const { client, tag, instanceId, task } = args;
4091
+ const { client, tag, instanceId, activity } = args;
3978
4092
  validateTag(tag);
3979
- 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));
4093
+ const parent = await reload(client, instanceId, tag), isSpawned = (h) => h._type === "spawned", ids = parent.history.filter(isSpawned).filter((h) => activity === void 0 || h.activity === activity).flatMap((h) => bareIdFromSpawnRef(h.instanceRef.id));
3980
4094
  return ids.length === 0 ? [] : client.fetch(
3981
4095
  `*[_id in $ids && ${tagScopeFilter()}] | order(startedAt asc)`,
3982
4096
  { ids, tag }
3983
4097
  );
3984
4098
  },
4099
+ /**
4100
+ * Every in-flight instance whose reactive watch-set includes `document` —
4101
+ * the reverse of {@link subscriptionDocumentsForInstance}.
4102
+ *
4103
+ * For a non-reactive, content-change-driven runtime (a Sanity Function, an
4104
+ * Inngest/durable worker, any server) that holds no instances in memory: a
4105
+ * document changed; which instances should it `tick`? The watch-set covers
4106
+ * the instance itself, its ancestors, and the docs named by
4107
+ * `doc.ref` / `doc.refs` / `release.ref` field entries on the workflow scope
4108
+ * **and the current stage** — so a hand-rolled GROQ over `fields[]` gets it
4109
+ * subtly wrong (misses stage-scope refs, `release.ref`, ancestors).
4110
+ *
4111
+ * A coarse GROQ prefilter narrows candidates server-side (in-flight,
4112
+ * tag-scoped, mentioning the doc anywhere in state); the authoritative match
4113
+ * is {@link instanceWatchesDocument}, derived from `collectWatchRefs`, so the
4114
+ * reverse stays in lockstep with the forward set and honours the
4115
+ * open-stage-only rule the prefilter deliberately over-approximates.
4116
+ *
4117
+ * Single-resource: instances always live in the engine's own resource, so
4118
+ * this reads one client (unlike {@link guardsForInstance}, whose guard docs
4119
+ * are scattered across the watched resources). "Routed by resource" applies
4120
+ * to the **subject**: a cross-dataset doc is matched by its full,
4121
+ * resource-qualified GDR URI, never its bare id, so an instance watching
4122
+ * `dataset:A:ds:doc` is not matched by a change to `dataset:B:ds:doc`.
4123
+ *
4124
+ * `document` must be a resource-qualified GDR URI; a bare id is rejected
4125
+ * (it can't be resource-routed and would silently mismatch). Sorted by
4126
+ * `startedAt` ascending.
4127
+ */
4128
+ instancesForDocument: async (args) => {
4129
+ const { client, tag, document } = args;
4130
+ if (validateTag(tag), !isGdrUri(document))
4131
+ throw new Error(
4132
+ `workflow.instancesForDocument: "document" must be a resource-qualified GDR URI (e.g. "dataset:project:dataset:doc-id"); got: ${JSON.stringify(document)}`
4133
+ );
4134
+ const fieldRefsDoc = "count(fields[value.id == $document]) > 0 || count(fields[$document in value[].id]) > 0", query = `*[
4135
+ _type == "${WORKFLOW_INSTANCE_TYPE}" && ${tagScopeFilter()} && !defined(completedAt) && (
4136
+ _id == $bareId ||
4137
+ $document in ancestors[].id ||
4138
+ ${fieldRefsDoc} ||
4139
+ count(stages[${fieldRefsDoc}]) > 0
4140
+ )
4141
+ ] | order(startedAt asc)`;
4142
+ return (await client.fetch(query, {
4143
+ tag,
4144
+ document,
4145
+ bareId: extractDocumentId(document)
4146
+ })).filter((instance) => instanceWatchesDocument(instance, document));
4147
+ },
3985
4148
  /**
3986
4149
  * Permission helpers — Sanity ACL grants evaluated against documents
3987
4150
  * via GROQ. Used by `workflow.evaluate` to soft-gate actions when the
@@ -4035,10 +4198,13 @@ function walkEffectNames(def, visit) {
4035
4198
  for (const e of effects ?? []) visit(e.name, location);
4036
4199
  };
4037
4200
  for (const stage of def.stages ?? []) {
4038
- for (const t of stage.tasks ?? []) {
4039
- visitEffects(t.effects, `stage[${stage.name}].task[${t.name}].effects`);
4201
+ for (const t of stage.activities ?? []) {
4202
+ visitEffects(t.effects, `stage[${stage.name}].activity[${t.name}].effects`);
4040
4203
  for (const a of t.actions ?? [])
4041
- visitEffects(a.effects, `stage[${stage.name}].task[${t.name}].action[${a.name}].effects`);
4204
+ visitEffects(
4205
+ a.effects,
4206
+ `stage[${stage.name}].activity[${t.name}].action[${a.name}].effects`
4207
+ );
4042
4208
  }
4043
4209
  for (const tr of stage.transitions ?? [])
4044
4210
  visitEffects(tr.effects, `stage[${stage.name}].transition[${tr.name}].effects`);
@@ -4054,7 +4220,7 @@ async function drainEffectsInternal(args) {
4054
4220
  effectHandlers,
4055
4221
  missingHandler,
4056
4222
  logger
4057
- } = args, drainerActor = args.access?.actor ?? { kind: "system", id: "engine.drainEffects" }, completionAccess = {
4223
+ } = args, routeGdr = buildClientForGdr(client, resourceClients), clientFor = (ref) => routeGdr(parseGdr(typeof ref == "string" ? ref : ref.id)), drainerActor = args.access?.actor ?? engineSystemActor("drainEffects"), completionAccess = {
4058
4224
  actor: drainerActor,
4059
4225
  ...args.access?.grants !== void 0 ? { grants: args.access.grants } : {}
4060
4226
  };
@@ -4073,6 +4239,7 @@ async function drainEffectsInternal(args) {
4073
4239
  if (!await claimPendingEffect(client, before, candidate._key, drainerActor)) continue;
4074
4240
  const { outputs, dispatchError } = await dispatchEffect(handler, candidate, {
4075
4241
  client,
4242
+ clientFor,
4076
4243
  instanceId,
4077
4244
  logger
4078
4245
  });
@@ -4127,6 +4294,7 @@ async function dispatchEffect(handler, candidate, ctx) {
4127
4294
  try {
4128
4295
  const result = await handler(candidate.params, {
4129
4296
  client: ctx.client,
4297
+ clientFor: ctx.clientFor,
4130
4298
  instanceId: ctx.instanceId,
4131
4299
  effectKey: candidate._key,
4132
4300
  log: (message, extra) => ctx.logger(`effect.${candidate.name}`).info(message, extra)
@@ -4215,13 +4383,13 @@ function createInstanceSession(args) {
4215
4383
  return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: cascaded > 0 };
4216
4384
  });
4217
4385
  },
4218
- fireAction({ task, action, params }) {
4386
+ fireAction({ activity, action, params }) {
4219
4387
  return commit(async (actor, held) => {
4220
- assertActionAllowed(await evaluateWith(held, heldGuards), task, action);
4388
+ assertActionAllowed(await evaluateWith(held, heldGuards), activity, action);
4221
4389
  const { grants } = await access(), ranOps = await applyAction({
4222
4390
  client,
4223
4391
  instance,
4224
- task,
4392
+ activity,
4225
4393
  action,
4226
4394
  actor,
4227
4395
  clientForGdr,
@@ -4328,13 +4496,14 @@ function createEngine(args) {
4328
4496
  definition,
4329
4497
  ...resourceClients !== void 0 ? { resourceClients } : {}
4330
4498
  }),
4331
- children: ({ instanceId, task }) => workflow.children({
4499
+ children: ({ instanceId, activity }) => workflow.children({
4332
4500
  client,
4333
4501
  tag,
4334
4502
  workflowResource,
4335
4503
  instanceId,
4336
- ...task !== void 0 ? { task } : {}
4504
+ ...activity !== void 0 ? { activity } : {}
4337
4505
  }),
4506
+ instancesForDocument: ({ document }) => workflow.instancesForDocument({ client, tag, workflowResource, document }),
4338
4507
  query: ({ groq, params }) => workflow.query({
4339
4508
  client,
4340
4509
  tag,
@@ -4380,33 +4549,25 @@ function createEngine(args) {
4380
4549
  })
4381
4550
  };
4382
4551
  }
4383
- function docIdFor(def, target) {
4384
- return definitionDocId(target.workflowResource, target.tag, def.name, def.version);
4385
- }
4386
- function diffStatus(existing, expected) {
4387
- return existing === void 0 ? "create" : isDefinitionUnchanged(existing, expected) ? "unchanged" : "update";
4388
- }
4389
- function diffEntry(def, existingRaw, target) {
4390
- const docId = docIdFor(def, target), expected = {
4391
- ...def,
4392
- _id: docId,
4393
- _type: WORKFLOW_DEFINITION_TYPE,
4394
- tag: target.tag
4395
- }, status = diffStatus(existingRaw, expected), existing = existingRaw ? stripSystemFields(existingRaw) : void 0;
4552
+ function diffEntry(def, latestRaw, target) {
4553
+ const plan = planDefinitionDeploy(def, asLatest(latestRaw), target), existing = latestRaw ? stripSystemFields(latestRaw) : void 0;
4396
4554
  return {
4397
4555
  name: def.name,
4398
- version: def.version,
4399
- status,
4400
- docId,
4401
- expected,
4556
+ version: plan.version,
4557
+ status: plan.status,
4558
+ docId: plan.docId,
4559
+ expected: plan.document,
4402
4560
  ...existing !== void 0 ? { existing } : {}
4403
4561
  };
4404
4562
  }
4563
+ function asLatest(raw) {
4564
+ return raw === void 0 ? void 0 : { version: typeof raw.version == "number" ? raw.version : 0, ...typeof raw.contentHash == "string" ? { contentHash: raw.contentHash } : {} };
4565
+ }
4405
4566
  async function computeDiffEntries(client, defs, target) {
4406
4567
  const entries = [];
4407
4568
  for (const def of defs) {
4408
- const existingRaw = await client.getDocument(docIdFor(def, target)) ?? void 0;
4409
- entries.push(diffEntry(def, existingRaw, target));
4569
+ const latest = await loadLatestDeployed(client, def.name, target.tag);
4570
+ entries.push(diffEntry(def, latest, target));
4410
4571
  }
4411
4572
  return entries;
4412
4573
  }
@@ -4419,17 +4580,17 @@ const HISTORY_DISPLAY = {
4419
4580
  title: "Stage exited",
4420
4581
  description: "The instance left a stage on the way to the next one."
4421
4582
  },
4422
- taskActivated: {
4423
- title: "Task activated",
4424
- description: "A task flipped from `pending` to `active` and its onEnter effects queued."
4583
+ activityActivated: {
4584
+ title: "Activity activated",
4585
+ description: "An activity flipped from `pending` to `active` and its onEnter effects queued."
4425
4586
  },
4426
- taskStatusChanged: {
4427
- title: "Task status changed",
4428
- description: "An action or op flipped a task's status (active \u2192 done / skipped / failed)."
4587
+ activityStatusChanged: {
4588
+ title: "Activity status changed",
4589
+ description: "An action or op flipped an activity's status (active \u2192 done / skipped / failed)."
4429
4590
  },
4430
4591
  actionFired: {
4431
4592
  title: "Action fired",
4432
- description: "An action was invoked against a task \u2014 ops ran, status may have flipped, effects queued."
4593
+ description: "An action was invoked against an activity \u2014 ops ran, status may have flipped, effects queued."
4433
4594
  },
4434
4595
  transitionFired: {
4435
4596
  title: "Transition fired",
@@ -4445,7 +4606,7 @@ const HISTORY_DISPLAY = {
4445
4606
  },
4446
4607
  spawned: {
4447
4608
  title: "Spawned child",
4448
- description: "A task's `subworkflows` declaration spawned a child workflow instance."
4609
+ description: "An activity's `subworkflows` declaration spawned a child workflow instance."
4449
4610
  },
4450
4611
  aborted: {
4451
4612
  title: "Instance aborted",
@@ -4454,6 +4615,10 @@ const HISTORY_DISPLAY = {
4454
4615
  opApplied: {
4455
4616
  title: "Op applied",
4456
4617
  description: "An inline field-mutation op (field.set / field.append / status.set / etc.) ran during an action commit."
4618
+ },
4619
+ fieldQueryDiscarded: {
4620
+ title: "Query result discarded",
4621
+ 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."
4457
4622
  }
4458
4623
  }, FIELD_SLOT_DISPLAY = {
4459
4624
  "doc.ref": {
@@ -4468,50 +4633,58 @@ const HISTORY_DISPLAY = {
4468
4633
  title: "Content Release reference",
4469
4634
  description: "GDR pointer at a Content Release system doc, carrying the release name the engine derives the instance's read perspective from."
4470
4635
  },
4471
- query: {
4472
- title: "Query result",
4473
- description: "Snapshot of a GROQ projection captured at entry-resolve time; resolvedAt is stamped on the value."
4474
- },
4475
- "value.string": {
4636
+ string: {
4476
4637
  title: "String value",
4477
- description: "Free-form string entry."
4638
+ description: "Free-form single-line string entry."
4478
4639
  },
4479
- "value.url": {
4480
- title: "URL value",
4481
- description: "Validated URL entry."
4640
+ text: {
4641
+ title: "Text value",
4642
+ description: "Free-form multiline string entry."
4482
4643
  },
4483
- "value.number": {
4644
+ number: {
4484
4645
  title: "Number value",
4485
4646
  description: "Numeric entry."
4486
4647
  },
4487
- "value.boolean": {
4648
+ boolean: {
4488
4649
  title: "Boolean value",
4489
4650
  description: "True/false entry."
4490
4651
  },
4491
- "value.dateTime": {
4652
+ date: {
4653
+ title: "Date value",
4654
+ description: "Date-only (YYYY-MM-DD) entry, no time component."
4655
+ },
4656
+ datetime: {
4492
4657
  title: "Date / time value",
4493
- description: "ISO-timestamp entry."
4658
+ description: "ISO-8601 timestamp entry."
4494
4659
  },
4495
- "value.actor": {
4496
- title: "Actor value",
4497
- description: "A typed (user|ai|system) actor identity."
4660
+ url: {
4661
+ title: "URL value",
4662
+ description: "Validated URL entry."
4498
4663
  },
4499
- checklist: {
4500
- title: "Checklist",
4501
- description: "Append-only list of checkable items; ops add/update/remove entries."
4664
+ actor: {
4665
+ title: "Actor value",
4666
+ description: "A typed (user|ai|system) actor identity \u2014 a concrete principal."
4502
4667
  },
4503
- notes: {
4504
- title: "Notes log",
4505
- description: "Append-only structured-note log (signoffs, comments, audit rows)."
4668
+ assignee: {
4669
+ title: "Assignee",
4670
+ description: "A single typed (user|role) assignee \u2014 who is expected/responsible."
4506
4671
  },
4507
4672
  assignees: {
4508
4673
  title: "Assignees",
4509
4674
  description: "Ordered list of typed (user|role) assignees."
4675
+ },
4676
+ object: {
4677
+ title: "Object",
4678
+ description: "An object with named sub-fields; the value is keyed by sub-field name."
4679
+ },
4680
+ array: {
4681
+ title: "Array",
4682
+ description: "An array of objects shaped by the declared `of` sub-fields; ops add/update/remove rows."
4510
4683
  }
4511
4684
  }, OP_DISPLAY = {
4512
4685
  "field.set": {
4513
4686
  title: "Set field entry",
4514
- description: "Overwrite a field entry's value with a resolved Source."
4687
+ description: "Overwrite a field entry's value with a resolved value expression."
4515
4688
  },
4516
4689
  "field.unset": {
4517
4690
  title: "Unset field entry",
@@ -4519,7 +4692,7 @@ const HISTORY_DISPLAY = {
4519
4692
  },
4520
4693
  "field.append": {
4521
4694
  title: "Append to field entry",
4522
- description: "Push a resolved item onto an array-kind entry (notes, checklist, assignees, refs)."
4695
+ description: "Push a resolved item onto an array-kind entry (array, assignees, doc.refs)."
4523
4696
  },
4524
4697
  "field.updateWhere": {
4525
4698
  title: "Update matching rows",
@@ -4530,8 +4703,8 @@ const HISTORY_DISPLAY = {
4530
4703
  description: "Drop rows of an array-kind entry that match the predicate."
4531
4704
  },
4532
4705
  "status.set": {
4533
- title: "Set task status",
4534
- description: "Flip a task's status explicitly (the action-level `status:` sugar desugars to this)."
4706
+ title: "Set activity status",
4707
+ description: "Flip an activity's status explicitly (the action-level `status:` sugar desugars to this)."
4535
4708
  }
4536
4709
  }, EFFECTS_CONTEXT_DISPLAY = {
4537
4710
  "effectsContext.string": {
@@ -4571,6 +4744,35 @@ const HISTORY_DISPLAY = {
4571
4744
  title: "Workflow instance",
4572
4745
  description: "A running (or finished) workflow against its declared fields."
4573
4746
  }
4747
+ }, ACTIVITY_KIND_DISPLAY = {
4748
+ user: {
4749
+ title: "User activity",
4750
+ description: "A person acts via the app \u2014 renders action buttons ranked by outcome."
4751
+ },
4752
+ service: {
4753
+ title: "Service activity",
4754
+ description: "An automated system or effect does the work \u2014 renders effect drain status."
4755
+ },
4756
+ script: {
4757
+ title: "Script activity",
4758
+ description: "The engine runs a step inline and resolves on activation \u2014 an audit row."
4759
+ },
4760
+ manual: {
4761
+ title: "Manual activity",
4762
+ description: "Work done off-system, acknowledged by a person or verified by a predicate \u2014 renders an instruction card with a deep-link."
4763
+ },
4764
+ receive: {
4765
+ title: "Receive activity",
4766
+ description: 'No actor; the engine waits on a condition \u2014 renders "waiting until \u2026", no buttons.'
4767
+ }
4768
+ }, DRIVER_KIND_DISPLAY = {
4769
+ person: { title: "Person", description: "A human fired this action." },
4770
+ agent: { title: "Agent", description: "An AI agent fired this action." },
4771
+ service: { title: "Service", description: "An external system or Function fired this action." },
4772
+ engine: {
4773
+ title: "Engine",
4774
+ description: "The workflow engine itself drove this \u2014 a gate, machine step, or housekeeping."
4775
+ }
4574
4776
  }, DISPLAY = {
4575
4777
  ...HISTORY_DISPLAY,
4576
4778
  ...FIELD_SLOT_DISPLAY,
@@ -4586,6 +4788,8 @@ function displayDescription(typeKey) {
4586
4788
  return DISPLAY[typeKey]?.description;
4587
4789
  }
4588
4790
  export {
4791
+ ACTIVITY_KINDS,
4792
+ ACTIVITY_KIND_DISPLAY,
4589
4793
  AUTHORING_DISPLAY,
4590
4794
  ActionDisabledError,
4591
4795
  ActionParamsInvalidError,
@@ -4594,6 +4798,8 @@ export {
4594
4798
  ConcurrentFireActionError,
4595
4799
  DEFAULT_CONTENT_PERSPECTIVE,
4596
4800
  DISPLAY,
4801
+ DRIVER_KINDS,
4802
+ DRIVER_KIND_DISPLAY,
4597
4803
  EFFECTS_CONTEXT_DISPLAY,
4598
4804
  EditFieldDeniedError,
4599
4805
  FIELD_SLOT_DISPLAY,
@@ -4610,6 +4816,7 @@ export {
4610
4816
  WorkflowStateDivergedError,
4611
4817
  abortReason,
4612
4818
  actionVerdict,
4819
+ activityKind,
4613
4820
  availableActions,
4614
4821
  buildSnapshot,
4615
4822
  compileGuard,
@@ -4620,11 +4827,13 @@ export {
4620
4827
  defaultLoggerFactory,
4621
4828
  denyingGuards,
4622
4829
  deployStageGuards,
4830
+ deriveActivityKind,
4623
4831
  diagnoseInputFromEvaluation,
4624
4832
  diagnoseInstance,
4625
4833
  diffEntry,
4626
4834
  displayDescription,
4627
4835
  displayTitle,
4836
+ driverKind,
4628
4837
  effectsContextMap,
4629
4838
  evaluateFromSnapshot,
4630
4839
  evaluateMutationGuard,
@@ -4636,8 +4845,9 @@ export {
4636
4845
  guardsForDefinition,
4637
4846
  guardsForInstance,
4638
4847
  guardsForResource,
4848
+ hashDefinitionContent,
4639
4849
  instanceGuardQuery,
4640
- isDefinitionUnchanged,
4850
+ instanceWatchesDocument,
4641
4851
  isGdr,
4642
4852
  isStartableDefinition,
4643
4853
  isTerminalStage,