@sanity/workflow-engine 0.11.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 { DRIVER_KINDS, TASK_KINDS, 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,348 +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 ENGINE_ACTOR_ID_PREFIX = "engine.";
1655
- function engineSystemActor(name) {
1656
- return { kind: "system", id: `${ENGINE_ACTOR_ID_PREFIX}${name}` };
1657
- }
1658
- function isEngineActor(actor) {
1659
- return actor.kind === "system" && actor.id.startsWith(ENGINE_ACTOR_ID_PREFIX);
1660
- }
1661
- const MAX_SPAWN_DEPTH = 6, SUBWORKFLOWS_ALL_DONE = "count($subworkflows[status != 'done']) == 0";
1662
- function gateActor(outcome) {
1663
- return engineSystemActor(outcome === "failed" ? "failWhen" : "completeWhen");
1664
- }
1665
- function effectiveCompleteWhen(task) {
1666
- return task.completeWhen ?? (task.subworkflows !== void 0 ? SUBWORKFLOWS_ALL_DONE : void 0);
1667
- }
1668
- async function evaluateResolutionGate(ctx, task, vars) {
1669
- const scope = { taskName: task.name, vars };
1670
- if (task.failWhen !== void 0 && await ctxEvaluateCondition(ctx, task.failWhen, scope))
1671
- return "failed";
1672
- const completeWhen = effectiveCompleteWhen(task);
1673
- if (completeWhen !== void 0 && await ctxEvaluateCondition(ctx, completeWhen, scope))
1674
- return "done";
1675
- }
1676
- async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
1677
- if (ctx.instance.ancestors.length + 1 > MAX_SPAWN_DEPTH) {
1678
- const chain = [...ctx.instance.ancestors.map((a) => a.id), selfGdr(ctx.instance)].join(" \u2192 ");
1679
- throw new Error(
1680
- `Subworkflow depth limit (${MAX_SPAWN_DEPTH}) exceeded on task "${task.name}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
1681
- );
1682
- }
1683
- const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tag);
1684
- if (definition === null) {
1685
- const versionLabel = sub.definition.version === void 0 || sub.definition.version === "latest" ? "latest" : `v${sub.definition.version}`;
1686
- throw new Error(
1687
- `Subworkflow definition "${sub.definition.name}" ${versionLabel} not deployed (parent ${ctx.instance._id}, task ${task.name})`
1688
- );
1689
- }
1690
- const parentScope = await ctxConditionParams(ctx, {
1691
- taskName: task.name,
1692
- ...actor ? { actor } : {}
1693
- }), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
1694
- for (const row of rows) {
1695
- const initialFields = await projectRowFields(
1696
- ctx,
1697
- sub,
1698
- definition,
1699
- parentScope,
1700
- row,
1701
- discoveryResource
1702
- ), { ref, body } = await prepareChildInstance({
1703
- client: ctx.client,
1704
- parent: ctx.instance,
1705
- definition,
1706
- initialFields,
1707
- effectsContext,
1708
- actor,
1709
- now
1710
- });
1711
- refs.push(ref), mutation.pendingCreates.push(body);
1712
- }
1713
- return refs;
1714
- }
1715
- async function resolveSpawnRows(ctx, sub) {
1716
- const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
1717
- let value, discoveryResource;
1718
- if (groq.includes("*")) {
1719
- const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
1720
- client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
1721
- client,
1722
- groq,
1723
- params,
1724
- // Draft-aware by default (drafts overlay published) when there's no
1725
- // release, so a `forEach` discovers in-flight items the same way the
1726
- // engine reads any other content.
1727
- perspective: ctx.instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
1728
- });
1729
- } else {
1730
- const tree = parse(groq, { params });
1731
- value = await (await evaluate(tree, { dataset: [ctx.instance], params, root: ctx.instance })).get();
1732
- }
1733
- return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
1734
- }
1735
- async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
1736
- const out = [];
1737
- for (const [name, groq] of Object.entries(sub.with ?? {})) {
1738
- const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
1739
- if (declared === void 0)
1740
- throw new Error(
1741
- `subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
1742
- );
1743
- const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
1744
- parentResource: ctx.instance.workflowResource,
1745
- discoveryResource,
1746
- entryName: name,
1747
- childName: childDefinition.name
1748
- });
1749
- value != null && out.push({
1750
- type: declared.type,
1751
- name,
1752
- value
1753
- });
1754
- }
1755
- return out;
1756
- }
1757
- async function resolveContextHandoff(ctx, sub, parentScope) {
1758
- const out = {};
1759
- for (const [name, groq] of Object.entries(sub.context ?? {})) {
1760
- const v2 = await runGroq(groq, parentScope, ctx.snapshot);
1761
- v2 != null && (typeof v2 == "string" || typeof v2 == "number" || typeof v2 == "boolean" || typeof v2 == "object" && "id" in v2 && "type" in v2) && (out[name] = v2);
1762
- }
1763
- return out;
1764
- }
1765
- function canonicaliseSpawnValue(kind, value, cx) {
1766
- return kind === "doc.ref" ? coerceSpawnGdr(value, cx) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, cx)).filter((v2) => v2 !== null) : value;
1767
- }
1768
- function assertBareIdRootsCorrectly(id, cx) {
1769
- if (!(cx.discoveryResource === void 0 || sameResource(cx.discoveryResource, cx.parentResource)))
1770
- throw new Error(
1771
- `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.`
1772
- );
1773
- }
1774
- function coerceSpawnGdr(raw, cx) {
1775
- const toUri = (id) => isGdrUri(id) ? id : (assertBareIdRootsCorrectly(id, cx), gdrFromResource(cx.parentResource, id));
1776
- if (raw == null) return null;
1777
- if (typeof raw == "object" && "id" in raw && "type" in raw) {
1778
- const r = raw;
1779
- if (typeof r.id == "string" && typeof r.type == "string")
1780
- return { id: toUri(r.id), type: r.type };
1781
- }
1782
- if (typeof raw == "object" && "_ref" in raw) {
1783
- const r = raw;
1784
- if (typeof r._ref == "string") return { id: toUri(r._ref), type: "document" };
1785
- }
1786
- return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
1787
- }
1788
- async function resolveDefinitionRef(client, ref, tag) {
1789
- const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
1790
- return wantsExplicit && (params.version = ref.version), await client.fetch(
1791
- definitionLookupGroq(wantsExplicit),
1792
- params
1793
- ) ?? null;
1794
- }
1795
- async function prepareChildInstance(args) {
1796
- 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 = [
1797
- ...parent.ancestors,
1798
- {
1799
- id: selfGdr(parent),
1800
- type: WORKFLOW_INSTANCE_TYPE
1801
- }
1802
- ], inheritedPerspective = parent.perspective, childFields = await resolveDeclaredFields({
1803
- entryDefs: definition.fields,
1804
- initialFields,
1805
- ctx: {
1806
- client,
1807
- now,
1808
- selfId: childDocId,
1809
- tag: childTag,
1810
- workflowResource,
1811
- definitionName: definition.name,
1812
- ...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
1813
- },
1814
- randomKey
1815
- }), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
1816
- ([key, value]) => {
1817
- if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
1818
- if (!isGdrUri(value.id))
1819
- throw new Error(
1820
- `subworkflows.context["${key}"]: GDR id must be a "<scheme>:..." URI, got ${JSON.stringify(value.id)}.`
1821
- );
1822
- return effectsContextEntry(key, { id: value.id, type: value.type });
1823
- }
1824
- return effectsContextEntry(key, value);
1825
- }
1826
- ), base = buildInstanceBase({
1827
- id: childDocId,
1828
- now,
1829
- tag: childTag,
1830
- workflowResource,
1831
- definitionName: definition.name,
1832
- pinnedVersion: definition.version,
1833
- definition,
1834
- fields: childFields,
1835
- effectsContext: effectsContextEntries,
1836
- ancestors,
1837
- perspective: childPerspective,
1838
- initialStage: definition.initialStage,
1839
- actor
1840
- });
1841
- return { ref: childRef, body: base };
1842
- }
1843
- async function subworkflowRows(ctx, refs) {
1844
- if (refs.length === 0) return [];
1845
- const ids = refs.map((r) => gdrToBareId(r.id));
1846
- return (await ctx.client.fetch("*[_id in $ids]{_id, currentStage, completedAt}", { ids })).map((c) => ({
1847
- _id: c._id,
1848
- stage: c.currentStage,
1849
- status: c.completedAt !== void 0 && c.completedAt !== null ? "done" : "active"
1850
- }));
1851
- }
1852
- async function resolveBindings(args) {
1853
- const resolved = {};
1854
- for (const [key, groq] of Object.entries(args.bindings ?? {}))
1855
- resolved[key] = await runGroq(groq, args.params, args.snapshot);
1856
- return { ...resolved, ...args.staticInput };
1857
- }
1858
- async function persistThenDeploy(ctx, mutation, deploy) {
1859
- const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
1860
- await deployOrRollback({
1861
- client: ctx.client,
1862
- instanceId: ctx.instance._id,
1863
- committedRev: committed._rev,
1864
- restore: instanceStateFields(ctx.instance),
1865
- unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
1866
- reversible: !spawned,
1867
- deploy
1868
- });
1869
- }
1870
- async function refreshStageGuards(ctx, mutation, stageName) {
1871
- await deployStageGuards({
1872
- client: ctx.client,
1873
- clientForGdr: ctx.clientForGdr,
1874
- instance: materializeInstance(ctx.instance, mutation),
1875
- definition: ctx.definition,
1876
- stageName,
1877
- now: ctx.now
1878
- });
1879
- }
1880
- async function completeEffect(args) {
1881
- const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
1882
- ...options?.clock ? { clock: options.clock } : {},
1883
- ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
1884
- });
1885
- return commitCompleteEffect(
1886
- ctx,
1887
- effectKey,
1888
- status,
1889
- outputs,
1890
- detail,
1891
- error,
1892
- durationMs,
1893
- options?.actor
1894
- );
1895
- }
1896
- function buildEffectHistoryEntry(pending, outcome) {
1897
- 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;
1898
1648
  return {
1899
1649
  _key: pending._key,
1900
1650
  name: pending.name,
@@ -1967,7 +1717,7 @@ function buildQueuedEffect(effect, origin, params, actor, now) {
1967
1717
  async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
1968
1718
  if (!effects || effects.length === 0) return;
1969
1719
  const now = ctx.now, liveCtx = { ...ctx, instance: materializeInstance(ctx.instance, mutation) }, params = await ctxConditionParams(liveCtx, {
1970
- ...opts?.taskName !== void 0 ? { taskName: opts.taskName } : {},
1720
+ ...opts?.activityName !== void 0 ? { activityName: opts.activityName } : {},
1971
1721
  ...actor !== void 0 ? { actor } : {},
1972
1722
  // Caller-supplied action params, readable in bindings as `$params.<name>`.
1973
1723
  vars: { params: opts?.callerParams ?? {} }
@@ -1982,18 +1732,74 @@ async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
1982
1732
  mutation.pendingEffects.push(pending), mutation.history.push(history);
1983
1733
  }
1984
1734
  }
1985
- 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) {
1986
1794
  switch (src.type) {
1987
1795
  case "literal":
1988
- return { handled: !0, value: src.value };
1796
+ return src.value;
1989
1797
  case "param":
1990
- return { handled: !0, value: ctx.params?.[src.param] };
1798
+ return ctx.params?.[src.param];
1991
1799
  case "actor":
1992
- return { handled: !0, value: ctx.actor };
1800
+ return ctx.actor;
1993
1801
  case "now":
1994
- return { handled: !0, value: ctx.now };
1995
- default:
1996
- return { handled: !1 };
1802
+ return ctx.now;
1997
1803
  }
1998
1804
  }
1999
1805
  const FIELD_OP_TYPES = [
@@ -2006,7 +1812,7 @@ const FIELD_OP_TYPES = [
2006
1812
  function isFieldOp(summary) {
2007
1813
  return FIELD_OP_TYPES.includes(summary.opType);
2008
1814
  }
2009
- function validateActionParams(action, taskName, callerParams) {
1815
+ function validateActionParams(action, activityName, callerParams) {
2010
1816
  const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
2011
1817
  for (const decl of declared) {
2012
1818
  const value = params[decl.name], present = decl.name in params && value !== void 0 && value !== null;
@@ -2020,7 +1826,7 @@ function validateActionParams(action, taskName, callerParams) {
2020
1826
  }));
2021
1827
  }
2022
1828
  if (issues.length > 0)
2023
- throw new ActionParamsInvalidError({ action: action.name, task: taskName, issues });
1829
+ throw new ActionParamsInvalidError({ action: action.name, activity: activityName, issues });
2024
1830
  return params;
2025
1831
  }
2026
1832
  function hasStringField(value, field) {
@@ -2053,7 +1859,15 @@ function runOps(args) {
2053
1859
  if (ops === void 0 || ops.length === 0) return [];
2054
1860
  const summaries = [];
2055
1861
  for (const op of ops) {
2056
- 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
+ });
2057
1871
  summaries.push(summary), mutation.history.push(opAppliedEntry({ origin, summary, stage, actor, now }));
2058
1872
  }
2059
1873
  return summaries;
@@ -2065,7 +1879,7 @@ function opAppliedEntry(args) {
2065
1879
  _type: "opApplied",
2066
1880
  at: now,
2067
1881
  stage,
2068
- ...origin.task !== void 0 ? { task: origin.task } : {},
1882
+ ...origin.activity !== void 0 ? { activity: origin.activity } : {},
2069
1883
  ...origin.action !== void 0 ? { action: origin.action } : {},
2070
1884
  ...origin.transition !== void 0 ? { transition: origin.transition } : {},
2071
1885
  ...origin.edit === !0 ? { edit: !0 } : {},
@@ -2092,13 +1906,15 @@ function applyOp(op, ctx) {
2092
1906
  }
2093
1907
  }
2094
1908
  function applyFieldSet(op, ctx) {
2095
- const value = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
2096
- 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 } : {};
2097
1914
  }
2098
1915
  const EMPTY_BY_KIND = {
2099
1916
  "doc.refs": [],
2100
- checklist: [],
2101
- notes: [],
1917
+ array: [],
2102
1918
  assignees: []
2103
1919
  };
2104
1920
  function applyFieldUnset(op, ctx) {
@@ -2106,8 +1922,13 @@ function applyFieldUnset(op, ctx) {
2106
1922
  return setEntryValue(entry, EMPTY_BY_KIND[entry._type] ?? null), { opType: op.type, target: op.target };
2107
1923
  }
2108
1924
  function applyFieldAppend(op, ctx) {
2109
- const item = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
2110
- 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
+ });
2111
1932
  const current = Array.isArray(entry.value) ? entry.value : [];
2112
1933
  return setEntryValue(entry, [...current, withRowKey(item)]), { opType: op.type, target: op.target, resolved: { item } };
2113
1934
  }
@@ -2115,7 +1936,7 @@ function withRowKey(item) {
2115
1936
  return typeof item == "object" && item !== null && !Array.isArray(item) && !("_key" in item) ? { _key: randomKey(), ...item } : item;
2116
1937
  }
2117
1938
  function applyFieldUpdateWhere(op, ctx) {
2118
- 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);
2119
1940
  if (merge === null || typeof merge != "object" || Array.isArray(merge))
2120
1941
  throw new Error(
2121
1942
  `field.updateWhere value must resolve to an object of fields to merge (target "${op.target.field}")`
@@ -2141,145 +1962,385 @@ function requireArrayValue(entry, op) {
2141
1962
  );
2142
1963
  return entry.value;
2143
1964
  }
2144
- function applyStatusSet(op, ctx) {
2145
- const entry = findOpenStageEntry(ctx.mutation)?.tasks.find((t) => t.name === op.task);
2146
- if (entry === void 0)
2147
- throw new Error(`status.set targets task "${op.task}" which has no entry in the open stage`);
2148
- return applyTaskStatusChange({
2149
- entry,
2150
- history: ctx.mutation.history,
2151
- stage: ctx.stage,
2152
- to: op.status,
2153
- at: ctx.now,
2154
- ...ctx.actor !== void 0 ? { actor: ctx.actor } : {}
2155
- }), { 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;
2156
2177
  }
2157
- function setEntryValue(entry, value) {
2158
- 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;
2159
2180
  }
2160
- function locateEntry(ctx, target) {
2161
- const entry = fieldHost(ctx, target.scope)?.find((s) => s.name === target.field);
2162
- if (entry === void 0)
2181
+ function assertBareIdRootsCorrectly(id, cx) {
2182
+ if (!(cx.discoveryResource === void 0 || sameResource(cx.discoveryResource, cx.parentResource)))
2163
2183
  throw new Error(
2164
- `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.`
2165
2185
  );
2166
- return entry;
2167
- }
2168
- function fieldHost(ctx, scope) {
2169
- if (scope === "workflow") return ctx.mutation.fields;
2170
- const stageEntry = findOpenStageEntry(ctx.mutation);
2171
- if (scope === "stage") return stageEntry?.fields;
2172
- const task = stageEntry?.tasks.find((t) => t.name === ctx.taskName);
2173
- return task !== void 0 && task.fields === void 0 && (task.fields = []), task?.fields;
2174
2186
  }
2175
- function resolveOpSource(src, ctx) {
2176
- const staticValue = resolveStaticSource(src, ctx);
2177
- if (staticValue.handled) return staticValue.value;
2178
- switch (src.type) {
2179
- case "self":
2180
- return ctx.self;
2181
- case "stage":
2182
- return ctx.stage;
2183
- case "fieldRead": {
2184
- const value = readEntryFromMutation(ctx, src);
2185
- return src.path !== void 0 ? getPath(value, src.path) : value;
2186
- }
2187
- case "object": {
2188
- const out = {};
2189
- for (const [field, fieldSrc] of Object.entries(src.fields))
2190
- out[field] = resolveOpSource(fieldSrc, ctx);
2191
- return out;
2192
- }
2193
- default:
2194
- 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" };
2195
2198
  }
2199
+ return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
2196
2200
  }
2197
- function entryValue(entries, name) {
2198
- 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;
2199
2207
  }
2200
- function readEntryFromMutation(ctx, src) {
2201
- const scopes = src.scope !== void 0 ? [src.scope] : ["stage", "workflow"], stageEntry = findOpenStageEntry(ctx.mutation);
2202
- for (const scope of scopes) {
2203
- const host = scope === "workflow" ? ctx.mutation.fields : stageEntry?.fields, value = entryValue(host, src.field);
2204
- if (value !== void 0) return value;
2205
- }
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 };
2206
2258
  }
2207
- function evalOpPredicate(p, row, ctx) {
2208
- switch (p.type) {
2209
- case "all":
2210
- return p.of.every((sub) => evalOpPredicate(sub, row, ctx));
2211
- case "any":
2212
- return p.of.some((sub) => evalOpPredicate(sub, row, ctx));
2213
- case "field":
2214
- return row[p.field] === resolveOpSource(p.equals, ctx);
2215
- }
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
+ }));
2216
2267
  }
2217
- async function invokeTask(args) {
2218
- 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, {
2219
2270
  ...options?.clock ? { clock: options.clock } : {},
2220
2271
  ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
2221
2272
  });
2222
- return commitInvoke(ctx, taskName, options?.actor);
2273
+ return commitInvoke(ctx, activityName, options?.actor);
2223
2274
  }
2224
- async function commitInvoke(ctx, taskName, actor) {
2225
- 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);
2226
2277
  if (entry === void 0)
2227
- 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
+ );
2228
2281
  if (entry.status !== "pending")
2229
- return { invoked: !1, task: taskName };
2230
- const mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation, taskName);
2231
- 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 };
2232
2285
  }
2233
- async function activateTask(ctx, mutation, task, entry, actor) {
2286
+ async function activateActivity(ctx, mutation, activity, entry, actor) {
2234
2287
  const now = ctx.now;
2235
- 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) {
2236
2289
  const stage = findStage(ctx.definition, mutation.currentStage);
2237
- entry.fields = await resolveTaskFieldEntries({
2290
+ entry.fields = await resolveActivityFieldEntries({
2238
2291
  client: ctx.client,
2239
2292
  instance: ctx.instance,
2240
2293
  stage,
2241
- task,
2242
- now
2294
+ activity,
2295
+ now,
2296
+ recordDiscard: recordFieldDiscards(mutation.history, "activity", now)
2243
2297
  });
2244
2298
  }
2245
2299
  runOps({
2246
- ops: task.ops,
2300
+ ops: activity.ops,
2247
2301
  mutation,
2248
2302
  stage: mutation.currentStage,
2249
- origin: { task: task.name },
2303
+ origin: { activity: activity.name },
2250
2304
  params: {},
2251
2305
  actor,
2252
2306
  self: selfGdr(ctx.instance),
2253
2307
  now
2254
2308
  }), mutation.history.push({
2255
2309
  _key: randomKey(),
2256
- _type: "taskActivated",
2310
+ _type: "activityActivated",
2257
2311
  at: now,
2258
2312
  stage: mutation.currentStage,
2259
- task: task.name,
2313
+ activity: activity.name,
2260
2314
  ...actor !== void 0 ? { actor } : {}
2261
- }), await queueEffects(ctx, mutation, task.effects, { kind: "task", name: task.name }, actor, {
2262
- taskName: task.name
2263
- });
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
+ );
2264
2325
  let pendingChildren;
2265
- if (task.subworkflows !== void 0) {
2266
- 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);
2267
2328
  entry.spawnedInstances = refs, pendingChildren = mutation.pendingCreates.slice(createsBefore).map((c) => ({ _id: c._id, stage: c.currentStage, status: "active" }));
2268
2329
  for (const ref of refs)
2269
2330
  mutation.history.push({
2270
2331
  _key: randomKey(),
2271
2332
  _type: "spawned",
2272
2333
  at: now,
2273
- task: task.name,
2334
+ activity: activity.name,
2274
2335
  instanceRef: ref
2275
2336
  });
2276
2337
  }
2277
- 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);
2278
2339
  }
2279
- async function autoResolveOnActivate(ctx, mutation, task, entry, now, pendingChildren) {
2280
- if (task.failWhen === void 0 && effectiveCompleteWhen(task) === void 0) return !1;
2281
- const vars = pendingChildren !== void 0 ? { subworkflows: pendingChildren } : await taskConditionVars(ctx, entry), outcome = await evaluateResolutionGate(ctx, task, vars);
2282
- 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({
2283
2344
  entry,
2284
2345
  history: mutation.history,
2285
2346
  stage: mutation.currentStage,
@@ -2288,12 +2349,12 @@ async function autoResolveOnActivate(ctx, mutation, task, entry, now, pendingChi
2288
2349
  actor: gateActor(outcome)
2289
2350
  }), !0);
2290
2351
  }
2291
- async function taskConditionVars(ctx, entry) {
2352
+ async function activityConditionVars(ctx, entry) {
2292
2353
  return entry.spawnedInstances === void 0 || entry.spawnedInstances.length === 0 ? { subworkflows: [] } : { subworkflows: await subworkflowRows(ctx, entry.spawnedInstances) };
2293
2354
  }
2294
- function resolveMachineStep(mutation, task, entry, now) {
2295
- const waitsForActor = task.actions !== void 0 && task.actions.length > 0, waitsForCondition = task.completeWhen !== void 0 || task.subworkflows !== void 0;
2296
- 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({
2297
2358
  entry,
2298
2359
  history: mutation.history,
2299
2360
  stage: mutation.currentStage,
@@ -2302,15 +2363,17 @@ function resolveMachineStep(mutation, task, entry, now) {
2302
2363
  actor: engineSystemActor("machineStep")
2303
2364
  });
2304
2365
  }
2305
- async function buildStageTasks(ctx, stage) {
2366
+ async function buildStageActivities(ctx, stage) {
2306
2367
  const entries = [];
2307
- for (const task of stage.tasks ?? []) {
2308
- 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";
2309
2372
  entries.push({
2310
2373
  _key: randomKey(),
2311
- name: task.name,
2374
+ name: activity.name,
2312
2375
  status: inScope ? "pending" : "skipped",
2313
- ...task.filter !== void 0 ? { filterEvaluation: buildFilterEvaluation(ctx.now, task.filter, outcome) } : {}
2376
+ ...activity.filter !== void 0 ? { filterEvaluation: buildFilterEvaluation(ctx.now, activity.filter, outcome) } : {}
2314
2377
  });
2315
2378
  }
2316
2379
  return entries;
@@ -2320,7 +2383,7 @@ function buildFilterEvaluation(at, filter, outcome) {
2320
2383
  at,
2321
2384
  truthy: !1,
2322
2385
  unevaluable: !0,
2323
- 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.`
2324
2387
  } : { at, truthy: outcome === "satisfied" };
2325
2388
  }
2326
2389
  function isTerminalStage(stage) {
@@ -2343,15 +2406,16 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
2343
2406
  client: ctx.client,
2344
2407
  instance: ctx.instance,
2345
2408
  stage: nextStage,
2346
- now: at
2409
+ now: at,
2410
+ recordDiscard: recordFieldDiscards(mutation.history, "stage", at)
2347
2411
  }),
2348
- tasks: await buildStageTasks(ctx, nextStage)
2412
+ activities: await buildStageActivities(ctx, nextStage)
2349
2413
  };
2350
2414
  mutation.stages.push(nextStageEntry);
2351
- for (const task of nextStage.tasks ?? []) {
2352
- if (task.activation !== "auto") continue;
2353
- const entry = nextStageEntry.tasks.find((t) => t.name === task.name);
2354
- 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);
2355
2419
  }
2356
2420
  isTerminalStage(nextStage) && (mutation.completedAt = at);
2357
2421
  }
@@ -2475,15 +2539,22 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
2475
2539
  instance,
2476
2540
  definition,
2477
2541
  ...clock ? { clock } : {}
2478
- }), now = ctx.now, initialStageEntry = {
2542
+ }), now = ctx.now, discards = [], initialStageEntry = {
2479
2543
  _key: randomKey(),
2480
2544
  name: stage.name,
2481
2545
  enteredAt: now,
2482
- fields: await resolveStageFieldEntries({ client, instance, stage, now }),
2483
- 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)
2484
2554
  }, committed = await client.patch(instance._id).set({
2485
2555
  stages: [initialStageEntry],
2486
- lastChangedAt: now
2556
+ lastChangedAt: now,
2557
+ ...discards.length > 0 ? { history: [...instance.history, ...discards] } : {}
2487
2558
  }).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
2488
2559
  await deployOrRollback({
2489
2560
  client,
@@ -2502,12 +2573,20 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
2502
2573
  stageName: stage.name,
2503
2574
  now
2504
2575
  })
2505
- }), 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
+ );
2506
2585
  }
2507
- async function autoActivatePrimedTasks(client, instanceId, definition, stage, actor, clientForGdr, clock) {
2586
+ async function autoActivatePrimedActivities(client, instanceId, definition, stage, actor, clientForGdr, clock) {
2508
2587
  const resolvedClientForGdr = clientForGdr ?? (() => client);
2509
- for (const task of stage.tasks ?? []) {
2510
- if (task.activation !== "auto") continue;
2588
+ for (const activity of stage.activities ?? []) {
2589
+ if (activity.activation !== "auto") continue;
2511
2590
  const updated = await client.getDocument(instanceId);
2512
2591
  if (!updated) continue;
2513
2592
  const updatedCtx = await buildEngineContext({
@@ -2516,18 +2595,22 @@ async function autoActivatePrimedTasks(client, instanceId, definition, stage, ac
2516
2595
  instance: updated,
2517
2596
  definition,
2518
2597
  ...clock ? { clock } : {}
2519
- }), mutation = startMutation(updated), mutEntry = currentTasks(mutation).find((t) => t.name === task.name);
2520
- 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));
2521
2600
  }
2522
2601
  }
2523
2602
  const CASCADE_LIMIT = 100;
2524
- async function gatherAutoResolveCandidates(ctx, tasks) {
2525
- const currentTasksList = findCurrentTasks(ctx.instance), candidates = [];
2526
- for (const task of tasks) {
2527
- 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);
2528
2607
  if (entry?.status !== "active") continue;
2529
- const outcome = await evaluateResolutionGate(ctx, task, await taskConditionVars(ctx, entry));
2530
- 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 });
2531
2614
  }
2532
2615
  return candidates;
2533
2616
  }
@@ -2536,17 +2619,17 @@ async function resolveCompleteWhen(client, instanceId, clientForGdr, clock, over
2536
2619
  ...clientForGdr ? { clientForGdr } : {},
2537
2620
  ...clock ? { clock } : {},
2538
2621
  ...overlay ? { overlay } : {}
2539
- }), stage = findStage(ctx.definition, ctx.instance.currentStage), gatedTasks = (stage.tasks ?? []).filter(
2622
+ }), stage = findStage(ctx.definition, ctx.instance.currentStage), gatedActivities = (stage.activities ?? []).filter(
2540
2623
  (t) => effectiveCompleteWhen(t) !== void 0 || t.failWhen !== void 0
2541
2624
  );
2542
- if (gatedTasks.length === 0) return 0;
2543
- const candidates = await gatherAutoResolveCandidates(ctx, gatedTasks);
2625
+ if (gatedActivities.length === 0) return 0;
2626
+ const candidates = await gatherAutoResolveCandidates(ctx, gatedActivities);
2544
2627
  if (candidates.length === 0) return 0;
2545
2628
  const mutation = startMutation(ctx.instance);
2546
2629
  let count = 0;
2547
- for (const { task, outcome } of candidates) {
2548
- const mutEntry = currentTasks(mutation).find((t) => t.name === task.name);
2549
- 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({
2550
2633
  entry: mutEntry,
2551
2634
  history: mutation.history,
2552
2635
  stage: stage.name,
@@ -2579,9 +2662,9 @@ async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
2579
2662
  if (!parent || parent._type !== WORKFLOW_INSTANCE_TYPE || typeof parent.definitionSnapshot != "string") return;
2580
2663
  const definition = parseDefinitionSnapshot(parent), stage = definition.stages.find((s) => s.name === parent.currentStage);
2581
2664
  if (stage === void 0) return;
2582
- 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);
2583
- if (task?.subworkflows === void 0) return;
2584
- 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);
2585
2668
  if (entry === void 0 || entry.status !== "active") return;
2586
2669
  const ctx = await buildEngineContext({
2587
2670
  client,
@@ -2589,23 +2672,27 @@ async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
2589
2672
  instance: parent,
2590
2673
  definition,
2591
2674
  ...clock ? { clock } : {}
2592
- }), outcome = await evaluateResolutionGate(ctx, task, await taskConditionVars(ctx, entry));
2675
+ }), outcome = await evaluateResolutionGate(
2676
+ ctx,
2677
+ activity,
2678
+ await activityConditionVars(ctx, entry)
2679
+ );
2593
2680
  if (outcome !== void 0)
2594
- return { parent, definition, stage, task, outcome };
2681
+ return { parent, definition, stage, activity, outcome };
2595
2682
  }
2596
2683
  async function propagateToAncestors(client, instanceId, actor, clientForGdr, clock) {
2597
2684
  const instance = await client.getDocument(instanceId);
2598
2685
  if (!instance) return;
2599
2686
  const resolvedClientForGdr = clientForGdr ?? (() => client), found = await findResolvedParentSpawn(client, instance, resolvedClientForGdr, clock);
2600
2687
  if (found === void 0) return;
2601
- const { parent, definition, stage, task, outcome } = found, ctx = await buildEngineContext({
2688
+ const { parent, definition, stage, activity, outcome } = found, ctx = await buildEngineContext({
2602
2689
  client,
2603
2690
  clientForGdr: resolvedClientForGdr,
2604
2691
  instance: parent,
2605
2692
  definition,
2606
2693
  ...clock ? { clock } : {}
2607
- }), mutation = startMutation(parent), mutEntry = currentTasks(mutation).find((e) => e.name === task.name);
2608
- mutEntry !== void 0 && (applyTaskStatusChange({
2694
+ }), mutation = startMutation(parent), mutEntry = currentActivities(mutation).find((e) => e.name === activity.name);
2695
+ mutEntry !== void 0 && (applyActivityStatusChange({
2609
2696
  entry: mutEntry,
2610
2697
  history: mutation.history,
2611
2698
  stage: stage.name,
@@ -2708,6 +2795,18 @@ async function fetchGrantsCached(requestFn, resourcePath) {
2708
2795
  return;
2709
2796
  }
2710
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
+ }
2711
2810
  function effectiveEditable(baseline, override) {
2712
2811
  if (baseline === void 0) return;
2713
2812
  if (override === void 0) return baseline;
@@ -2719,14 +2818,15 @@ function slotSites(definition, stage) {
2719
2818
  const sites = [];
2720
2819
  for (const entry of definition.fields ?? []) sites.push({ scope: "workflow", entry });
2721
2820
  for (const entry of stage.fields ?? []) sites.push({ scope: "stage", entry });
2722
- for (const task of stage.tasks ?? [])
2723
- 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 });
2724
2824
  return sites;
2725
2825
  }
2726
2826
  function toResolvedSlot(site, stage) {
2727
2827
  return {
2728
2828
  scope: site.scope,
2729
- ...site.task !== void 0 ? { task: site.task } : {},
2829
+ ...site.activity !== void 0 ? { activity: site.activity } : {},
2730
2830
  name: site.entry.name,
2731
2831
  type: site.entry.type,
2732
2832
  ...site.entry.title !== void 0 ? { title: site.entry.title } : {},
@@ -2738,16 +2838,16 @@ function editableSlotsInStage(definition, stage) {
2738
2838
  return slotSites(definition, stage).filter((site) => site.entry.editable !== void 0).map((site) => toResolvedSlot(site, stage));
2739
2839
  }
2740
2840
  function editTargetMatches(slot, target) {
2741
- 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;
2742
2842
  }
2743
- const SCOPE_PRECEDENCE = { task: 0, stage: 1, workflow: 2 };
2843
+ const SCOPE_PRECEDENCE = { activity: 0, stage: 1, workflow: 2 };
2744
2844
  function resolveEditTarget(args) {
2745
2845
  const { definition, stage, target } = args, found = slotSites(definition, stage).filter(
2746
2846
  (site) => editTargetMatches(
2747
2847
  {
2748
2848
  scope: site.scope,
2749
2849
  name: site.entry.name,
2750
- ...site.task !== void 0 ? { task: site.task } : {}
2850
+ ...site.activity !== void 0 ? { activity: site.activity } : {}
2751
2851
  },
2752
2852
  target
2753
2853
  )
@@ -2757,9 +2857,11 @@ function resolveEditTarget(args) {
2757
2857
  function slotWindowOpen(instance, slot) {
2758
2858
  if (instance.completedAt !== void 0) return { open: !1, detail: "instance completed" };
2759
2859
  if (instance.abortedAt !== void 0) return { open: !1, detail: "instance aborted" };
2760
- if (slot.scope !== "task") return { open: !0 };
2761
- const status = findOpenStageEntry(instance)?.tasks.find((t) => t.name === slot.task)?.status;
2762
- 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"}` };
2763
2865
  }
2764
2866
  function readSlotValue(instance, slot) {
2765
2867
  return slotFieldHost(instance, slot)?.find((e) => e.name === slot.name)?.value;
@@ -2767,7 +2869,7 @@ function readSlotValue(instance, slot) {
2767
2869
  function slotFieldHost(instance, slot) {
2768
2870
  if (slot.scope === "workflow") return instance.fields;
2769
2871
  const stageEntry = findOpenStageEntry(instance);
2770
- 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;
2771
2873
  }
2772
2874
  function slotProvenance(instance, ref) {
2773
2875
  let latest;
@@ -2787,18 +2889,6 @@ function editDisabledReason(args) {
2787
2889
  if (!predicateSatisfied)
2788
2890
  return { kind: "editor-not-permitted", predicate: effective === !0 ? "true" : effective };
2789
2891
  }
2790
- function hasItems(arr) {
2791
- return arr !== void 0 && arr.length > 0;
2792
- }
2793
- function deriveTaskKind(task) {
2794
- return hasItems(task.actions) ? "user" : hasItems(task.effects) ? "service" : task.completeWhen !== void 0 || task.subworkflows !== void 0 ? "receive" : "script";
2795
- }
2796
- function taskKind(task) {
2797
- return task.kind ?? deriveTaskKind(task);
2798
- }
2799
- function driverKind(actor) {
2800
- return actor.kind === "user" ? "person" : actor.kind === "ai" ? "agent" : isEngineActor(actor) ? "engine" : "service";
2801
- }
2802
2892
  async function evaluateInstance(args) {
2803
2893
  const { client, tag, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
2804
2894
  validateTag(tag);
@@ -2822,12 +2912,12 @@ async function evaluateFromSnapshot(args) {
2822
2912
  throw new Error(
2823
2913
  `Instance "${instance._id}" currentStage "${instance.currentStage}" not in definition`
2824
2914
  );
2825
- const scope = await renderScope({ instance, definition, actor, grants, snapshot, now }), currentTaskEntries = findOpenStageEntry(instance)?.tasks ?? [], guardDenial = await instanceGuardReason(instance, actor, args.guards), taskEvaluations = [];
2826
- for (const task of stage.tasks ?? [])
2827
- taskEvaluations.push(
2828
- await evaluateTask({
2829
- task,
2830
- 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),
2831
2921
  instance,
2832
2922
  actor,
2833
2923
  snapshot,
@@ -2852,9 +2942,9 @@ async function evaluateFromSnapshot(args) {
2852
2942
  }
2853
2943
  const currentStage = {
2854
2944
  stage,
2855
- tasks: taskEvaluations,
2945
+ activities: activityEvaluations,
2856
2946
  transitions: transitionEvaluations
2857
- }, 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({
2858
2948
  instance,
2859
2949
  definition,
2860
2950
  stage,
@@ -2894,7 +2984,7 @@ async function evaluateEditableSlots(args) {
2894
2984
  }), value = readSlotValue(instance, slot);
2895
2985
  slots.push({
2896
2986
  scope: slot.scope,
2897
- ...slot.task !== void 0 ? { task: slot.task } : {},
2987
+ ...slot.activity !== void 0 ? { activity: slot.activity } : {},
2898
2988
  name: slot.name,
2899
2989
  type: slot.type,
2900
2990
  ...slot.title !== void 0 ? { title: slot.title } : {},
@@ -2910,7 +3000,14 @@ async function editPredicateSatisfied(args) {
2910
3000
  const { slot, window, guardDenial, instance, snapshot, scope, actor, roleAliases } = args;
2911
3001
  if (!window.open || guardDenial !== void 0) return !1;
2912
3002
  if (slot.effective === !0) return !0;
2913
- 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;
2914
3011
  return evaluateCondition({ condition: slot.effective, snapshot, params });
2915
3012
  }
2916
3013
  async function renderScope(args) {
@@ -2938,20 +3035,20 @@ async function advisoryCan(instance, actor, grants) {
2938
3035
  });
2939
3036
  return can;
2940
3037
  }
2941
- function taskScopeFor(args) {
2942
- const { scope, instance, snapshot, actor, taskName, roleAliases } = args;
3038
+ function activityScopeFor(args) {
3039
+ const { scope, instance, snapshot, actor, activityName, roleAliases } = args;
2943
3040
  return {
2944
3041
  ...scope,
2945
3042
  fields: {
2946
3043
  ...scope.fields,
2947
- ...scopedFieldOverlay(instance, snapshot, taskName)
3044
+ ...scopedFieldOverlay(instance, snapshot, activityName)
2948
3045
  },
2949
- assigned: assignedFor(instance, taskName, actor, roleAliases)
3046
+ assigned: assignedFor(instance, activityName, actor, roleAliases)
2950
3047
  };
2951
3048
  }
2952
- async function evaluateTask(args) {
3049
+ async function evaluateActivity(args) {
2953
3050
  const {
2954
- task,
3051
+ activity,
2955
3052
  statusEntry,
2956
3053
  instance,
2957
3054
  actor,
@@ -2960,37 +3057,37 @@ async function evaluateTask(args) {
2960
3057
  roleAliases,
2961
3058
  stageHasExits,
2962
3059
  guardDenial
2963
- } = args, status = statusEntry?.status ?? "pending", taskScope = taskScopeFor({
3060
+ } = args, status = statusEntry?.status ?? "pending", activityScope = activityScopeFor({
2964
3061
  scope,
2965
3062
  instance,
2966
3063
  snapshot,
2967
3064
  actor,
2968
- taskName: task.name,
3065
+ activityName: activity.name,
2969
3066
  roleAliases
2970
- }), assigned = taskScope.assigned === !0, unmetRequirements = await evaluateRequirements({
2971
- requirements: task.requirements,
3067
+ }), assigned = activityScope.assigned === !0, unmetRequirements = await evaluateRequirements({
3068
+ requirements: activity.requirements,
2972
3069
  snapshot,
2973
- params: taskScope
3070
+ params: activityScope
2974
3071
  }), requirementsReason = unmetRequirements.length > 0 ? { kind: "requirements-unmet", unmetRequirements } : void 0, actions = [];
2975
- for (const action of task.actions ?? [])
3072
+ for (const action of activity.actions ?? [])
2976
3073
  actions.push(
2977
3074
  await evaluateAction({
2978
3075
  action,
2979
3076
  status,
2980
3077
  instance,
2981
3078
  snapshot,
2982
- taskScope,
3079
+ activityScope,
2983
3080
  stageHasExits,
2984
3081
  guardDenial,
2985
3082
  requirementsReason
2986
3083
  })
2987
3084
  );
2988
3085
  return {
2989
- task,
3086
+ activity,
2990
3087
  status,
2991
- kind: taskKind(task),
3088
+ kind: activityKind(activity),
2992
3089
  // "pending on actor" treats both `pending` and `active` as waiting on
2993
- // the assignee — pending tasks just haven't been activated yet, but
3090
+ // the assignee — pending activities just haven't been activated yet, but
2994
3091
  // they're still inbox items. The inbox reads the assignees-kind field
2995
3092
  // entry BY KIND (who-data is fields).
2996
3093
  pendingOnActor: (status === "active" || status === "pending") && assigned,
@@ -3004,7 +3101,7 @@ async function evaluateAction(args) {
3004
3101
  status,
3005
3102
  instance,
3006
3103
  snapshot,
3007
- taskScope,
3104
+ activityScope,
3008
3105
  stageHasExits,
3009
3106
  guardDenial,
3010
3107
  requirementsReason
@@ -3012,7 +3109,7 @@ async function evaluateAction(args) {
3012
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({
3013
3110
  condition: action.filter,
3014
3111
  snapshot,
3015
- params: taskScope
3112
+ params: activityScope
3016
3113
  }) ? disabled(action, { kind: "filter-failed", filter: action.filter }) : { action, allowed: !0 };
3017
3114
  }
3018
3115
  async function instanceGuardReason(instance, actor, guards) {
@@ -3030,8 +3127,8 @@ function lifecycleReason(instance, status, stageHasExits) {
3030
3127
  return { kind: "instance-completed", completedAt: instance.completedAt };
3031
3128
  if (!stageHasExits)
3032
3129
  return { kind: "stage-terminal", stage: instance.currentStage };
3033
- if (isTerminalTaskStatus(status))
3034
- return { kind: "task-not-active", status };
3130
+ if (isTerminalActivityStatus(status))
3131
+ return { kind: "activity-not-active", status };
3035
3132
  }
3036
3133
  function disabled(action, reason) {
3037
3134
  return { action, allowed: !1, disabledReason: reason };
@@ -3039,37 +3136,30 @@ function disabled(action, reason) {
3039
3136
  function definitionDocId(_workflowResource, tag, definition, version) {
3040
3137
  return `${tag}.${definition}.v${version}`;
3041
3138
  }
3042
- async function sortByDependencies(client, definitions, tag, workflowResource) {
3139
+ async function sortByDependencies(client, definitions, tag) {
3043
3140
  const byName = /* @__PURE__ */ new Map();
3044
3141
  for (const def of definitions) {
3045
- const list = byName.get(def.name) ?? [];
3046
- 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);
3047
3147
  }
3048
3148
  const findInBatch = (ref) => findRefInBatch(byName, ref);
3049
- return await assertCrossBatchRefsDeployed(client, definitions, {
3050
- tag,
3051
- workflowResource,
3052
- findInBatch
3053
- }), topoSortDefinitions(definitions, { workflowResource, tag, findInBatch });
3149
+ return await assertCrossBatchRefsDeployed(client, definitions, { tag, findInBatch }), topoSortDefinitions(definitions, { findInBatch });
3054
3150
  }
3055
3151
  function findRefInBatch(byName, ref) {
3056
- const candidates = byName.get(ref.name);
3057
- if (!(candidates === void 0 || candidates.length === 0))
3058
- return typeof ref.version == "number" ? candidates.find((c) => c.version === ref.version) : candidates.reduce(
3059
- (best, c) => best === void 0 || c.version > best.version ? c : best,
3060
- void 0
3061
- );
3152
+ if (typeof ref.version != "number")
3153
+ return byName.get(ref.name);
3062
3154
  }
3063
3155
  async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
3064
3156
  const missing = [];
3065
- for (const def of definitions) {
3066
- const fromId = definitionDocId(ctx.workflowResource, ctx.tag, def.name, def.version);
3157
+ for (const def of definitions)
3067
3158
  for (const ref of refsOf(def)) {
3068
3159
  if (ctx.findInBatch(ref) !== void 0) continue;
3069
3160
  const label = await resolveDeployedRefLabel(client, ref, ctx.tag);
3070
- label !== void 0 && missing.push({ from: fromId, ref: label });
3161
+ label !== void 0 && missing.push({ from: def.name, ref: label });
3071
3162
  }
3072
- }
3073
3163
  if (missing.length === 0) return;
3074
3164
  const lines = missing.map((m) => ` - ${m.from} \u2192 ${m.ref} (neither in batch nor deployed)`);
3075
3165
  throw new Error(
@@ -3088,16 +3178,15 @@ async function resolveDeployedRefLabel(client, ref, tag) {
3088
3178
  }
3089
3179
  function topoSortDefinitions(definitions, ctx) {
3090
3180
  const visited = /* @__PURE__ */ new Set(), visiting = /* @__PURE__ */ new Set(), ordered = [], visit = (def) => {
3091
- const id = definitionDocId(ctx.workflowResource, ctx.tag, def.name, def.version);
3092
- if (!visited.has(id)) {
3093
- if (visiting.has(id))
3094
- throw new Error(`workflow.deployDefinitions: dependency cycle involving "${id}"`);
3095
- 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);
3096
3185
  for (const ref of refsOf(def)) {
3097
3186
  const child = ctx.findInBatch(ref);
3098
3187
  child !== void 0 && visit(child);
3099
3188
  }
3100
- visiting.delete(id), visited.add(id), ordered.push(def);
3189
+ visiting.delete(def.name), visited.add(def.name), ordered.push(def);
3101
3190
  }
3102
3191
  };
3103
3192
  for (const def of definitions) visit(def);
@@ -3106,8 +3195,8 @@ function topoSortDefinitions(definitions, ctx) {
3106
3195
  function refsOf(def) {
3107
3196
  const out = [];
3108
3197
  for (const stage of def.stages)
3109
- for (const task of stage.tasks ?? []) {
3110
- const ref = task.subworkflows?.definition;
3198
+ for (const activity of stage.activities ?? []) {
3199
+ const ref = activity.subworkflows?.definition;
3111
3200
  ref !== void 0 && out.push({
3112
3201
  name: ref.name,
3113
3202
  ...ref.version !== void 0 ? { version: ref.version } : {}
@@ -3115,8 +3204,23 @@ function refsOf(def) {
3115
3204
  }
3116
3205
  return out;
3117
3206
  }
3118
- function isDefinitionUnchanged(existing, expected) {
3119
- 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 };
3120
3224
  }
3121
3225
  function stripSystemFields(doc) {
3122
3226
  const out = {};
@@ -3131,22 +3235,26 @@ function stableStringify(value) {
3131
3235
  }
3132
3236
  async function loadDefinition(client, definition, version, tag) {
3133
3237
  if (version !== void 0) {
3134
- const doc = await client.fetch(
3135
- definitionLookupGroq(!0),
3136
- { definition, version, tag }
3137
- );
3238
+ const doc = await client.fetch(definitionLookupGroq(!0), {
3239
+ definition,
3240
+ version,
3241
+ tag
3242
+ });
3138
3243
  if (!doc)
3139
3244
  throw new Error(`Workflow definition ${definition} v${version} not deployed`);
3140
3245
  return doc;
3141
3246
  }
3142
- const latest = await client.fetch(definitionLookupGroq(!1), {
3143
- definition,
3144
- tag
3145
- });
3247
+ const latest = await loadLatestDeployed(client, definition, tag);
3146
3248
  if (!latest)
3147
3249
  throw new Error(`No deployed definition for workflow ${definition}`);
3148
3250
  return latest;
3149
3251
  }
3252
+ async function loadLatestDeployed(client, definition, tag) {
3253
+ return await client.fetch(definitionLookupGroq(!1), {
3254
+ definition,
3255
+ tag
3256
+ }) ?? void 0;
3257
+ }
3150
3258
  async function loadDefinitionVersions(client, definition, tag) {
3151
3259
  return client.fetch(
3152
3260
  `*[_type == "${WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}] | order(version desc)`,
@@ -3193,55 +3301,55 @@ async function commitAbort(ctx, reason, actor) {
3193
3301
  }), { fired: !0, stage: stage.name };
3194
3302
  }
3195
3303
  async function fireAction(args) {
3196
- const { client, instanceId, task, action, params, options } = args;
3304
+ const { client, instanceId, activity, action, params, options } = args;
3197
3305
  for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
3198
3306
  const ctx = await loadCallContext(client, instanceId, options);
3199
3307
  try {
3200
- return await commitAction(ctx, task, action, params, options);
3308
+ return await commitAction(ctx, activity, action, params, options);
3201
3309
  } catch (error) {
3202
3310
  if (!isRevisionConflict(error)) throw error;
3203
3311
  }
3204
3312
  }
3205
3313
  throw new ConcurrentFireActionError({
3206
3314
  instanceId,
3207
- task,
3315
+ activity,
3208
3316
  action,
3209
3317
  attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
3210
3318
  });
3211
3319
  }
3212
- async function resolveActionCommit(ctx, taskName, actionName, callerParams, options) {
3213
- 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);
3214
3322
  if (action === void 0)
3215
- throw new Error(`Action "${actionName}" not declared on task "${taskName}"`);
3323
+ throw new Error(`Action "${actionName}" not declared on activity "${activityName}"`);
3216
3324
  const can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan(ctx.instance, actor, options.grants) : void 0;
3217
3325
  if (!await ctxEvaluateCondition(ctx, action.filter, {
3218
- taskName,
3326
+ activityName,
3219
3327
  ...actor !== void 0 ? { actor } : {},
3220
3328
  ...can !== void 0 ? { vars: { can } } : {}
3221
3329
  }))
3222
- throw new Error(`Action "${actionName}" filter rejected on task "${taskName}"`);
3223
- 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);
3224
3332
  if (entry === void 0 || entry.status !== "active")
3225
3333
  throw new Error(
3226
- `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"}`
3227
3335
  );
3228
- const params = validateActionParams(action, taskName, callerParams);
3229
- return { stage, task, action, params };
3336
+ const params = validateActionParams(action, activityName, callerParams);
3337
+ return { stage, activity, action, params };
3230
3338
  }
3231
- async function commitAction(ctx, taskName, actionName, callerParams, options) {
3339
+ async function commitAction(ctx, activityName, actionName, callerParams, options) {
3232
3340
  const actor = options?.actor, { stage, action, params } = await resolveActionCommit(
3233
3341
  ctx,
3234
- taskName,
3342
+ activityName,
3235
3343
  actionName,
3236
3344
  callerParams,
3237
3345
  options
3238
- ), mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation, taskName), now = ctx.now;
3346
+ ), mutation = startMutation(ctx.instance), mutEntry = requireMutationActivityEntry(mutation, activityName), now = ctx.now;
3239
3347
  mutation.history.push({
3240
3348
  _key: randomKey(),
3241
3349
  _type: "actionFired",
3242
3350
  at: now,
3243
3351
  stage: stage.name,
3244
- task: taskName,
3352
+ activity: activityName,
3245
3353
  action: actionName,
3246
3354
  ...actor !== void 0 ? { actor, driverKind: driverKind(actor) } : {}
3247
3355
  });
@@ -3249,7 +3357,7 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
3249
3357
  ops: action.ops,
3250
3358
  mutation,
3251
3359
  stage: stage.name,
3252
- origin: { task: taskName, action: actionName },
3360
+ origin: { activity: activityName, action: actionName },
3253
3361
  params,
3254
3362
  actor,
3255
3363
  self: selfGdr(ctx.instance),
@@ -3257,14 +3365,14 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
3257
3365
  }), newStatus = mutEntry.status !== statusBefore ? mutEntry.status : void 0;
3258
3366
  return await queueEffects(ctx, mutation, action.effects, { kind: "action", name: actionName }, actor, {
3259
3367
  callerParams: params,
3260
- taskName
3368
+ activityName
3261
3369
  }), await persistThenDeploy(
3262
3370
  ctx,
3263
3371
  mutation,
3264
3372
  () => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
3265
3373
  ), {
3266
3374
  fired: !0,
3267
- task: taskName,
3375
+ activity: activityName,
3268
3376
  action: actionName,
3269
3377
  ...newStatus !== void 0 ? { newStatus } : {},
3270
3378
  ...ranOps.length > 0 ? { ranOps } : {}
@@ -3272,23 +3380,23 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
3272
3380
  }
3273
3381
  class ActionDisabledError extends Error {
3274
3382
  reason;
3275
- task;
3383
+ activity;
3276
3384
  action;
3277
3385
  constructor(args) {
3278
- 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;
3279
3387
  }
3280
3388
  }
3281
3389
  const disabledReasonDetail = {
3282
3390
  "filter-failed": (r) => `action filter returned false${r.detail ? ` (${r.detail})` : ""}`,
3283
- "task-not-active": (r) => `task status is "${r.status}"`,
3391
+ "activity-not-active": (r) => `activity status is "${r.status}"`,
3284
3392
  "stage-terminal": (r) => `stage "${r.stage}" is terminal`,
3285
3393
  "instance-completed": (r) => `instance completed at ${r.completedAt}`,
3286
3394
  "mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`,
3287
3395
  "requirements-unmet": (r) => `unmet requirement(s): ${r.unmetRequirements.join(", ")}`
3288
3396
  };
3289
- function formatDisabledReason(task, action, reason) {
3397
+ function formatDisabledReason(activity, action, reason) {
3290
3398
  const detail = disabledReasonDetail[reason.kind](reason);
3291
- return `Action "${task}:${action}" is not allowed: ${detail}`;
3399
+ return `Action "${activity}:${action}" is not allowed: ${detail}`;
3292
3400
  }
3293
3401
  class EditFieldDeniedError extends Error {
3294
3402
  reason;
@@ -3307,7 +3415,7 @@ const editDisabledReasonDetail = {
3307
3415
  function formatEditDisabledReason(target, reason) {
3308
3416
  const detail = editDisabledReasonDetail[reason.kind](
3309
3417
  reason
3310
- ), where = target.task !== void 0 ? `${target.task}.${target.field}` : target.field;
3418
+ ), where = target.activity !== void 0 ? `${target.activity}.${target.field}` : target.field;
3311
3419
  return `Field "${target.scope}:${where}" is not editable: ${detail}`;
3312
3420
  }
3313
3421
  async function editField(args) {
@@ -3339,7 +3447,7 @@ async function commitEdit(ctx, target, mode, value, options) {
3339
3447
  stage: stage.name,
3340
3448
  origin: {
3341
3449
  edit: !0,
3342
- ...slot.scope === "task" && slot.task !== void 0 ? { task: slot.task } : {}
3450
+ ...slot.scope === "activity" && slot.activity !== void 0 ? { activity: slot.activity } : {}
3343
3451
  },
3344
3452
  params: {},
3345
3453
  actor,
@@ -3358,7 +3466,7 @@ async function commitEdit(ctx, target, mode, value, options) {
3358
3466
  }
3359
3467
  async function assertSlotEditable(ctx, slot, options) {
3360
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, {
3361
- ...slot.task !== void 0 ? { taskName: slot.task } : {},
3469
+ ...slot.activity !== void 0 ? { activityName: slot.activity } : {},
3362
3470
  ...actor !== void 0 ? { actor } : {},
3363
3471
  ...can !== void 0 ? { vars: { can } } : {}
3364
3472
  }) : slot.effective === !0, reason = editDisabledReason({
@@ -3384,18 +3492,18 @@ function slotTarget(slot) {
3384
3492
  return {
3385
3493
  scope: slot.scope,
3386
3494
  field: slot.name,
3387
- ...slot.task !== void 0 ? { task: slot.task } : {}
3495
+ ...slot.activity !== void 0 ? { activity: slot.activity } : {}
3388
3496
  };
3389
3497
  }
3390
3498
  function labelTarget(target) {
3391
3499
  return {
3392
- scope: target.scope ?? (target.task !== void 0 ? "task" : "workflow"),
3500
+ scope: target.scope ?? (target.activity !== void 0 ? "activity" : "workflow"),
3393
3501
  field: target.field,
3394
- ...target.task !== void 0 ? { task: target.task } : {}
3502
+ ...target.activity !== void 0 ? { activity: target.activity } : {}
3395
3503
  };
3396
3504
  }
3397
3505
  function describeTarget(target) {
3398
- 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;
3399
3507
  return target.scope !== void 0 ? `${target.scope}:${where}` : where;
3400
3508
  }
3401
3509
  function bareIdFromSpawnRef(uri) {
@@ -3460,10 +3568,10 @@ async function dispatchGatedWrite(args) {
3460
3568
  ...ranOps !== void 0 ? { ranOps } : {}
3461
3569
  };
3462
3570
  }
3463
- function assertActionAllowed(evaluation, task, action) {
3464
- 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);
3465
3573
  if (actionEval?.allowed === !1 && actionEval.disabledReason)
3466
- throw new ActionDisabledError({ task, action, reason: actionEval.disabledReason });
3574
+ throw new ActionDisabledError({ activity, action, reason: actionEval.disabledReason });
3467
3575
  }
3468
3576
  function assertEditAllowed(evaluation, target) {
3469
3577
  const slot = evaluation.editableSlots.filter((s) => editTargetMatches(s, target)).sort((a, b) => SCOPE_PRECEDENCE[a.scope] - SCOPE_PRECEDENCE[b.scope])[0];
@@ -3472,7 +3580,7 @@ function assertEditAllowed(evaluation, target) {
3472
3580
  target: {
3473
3581
  scope: slot.scope,
3474
3582
  field: slot.name,
3475
- ...slot.task !== void 0 ? { task: slot.task } : {}
3583
+ ...slot.activity !== void 0 ? { activity: slot.activity } : {}
3476
3584
  },
3477
3585
  reason: slot.disabledReason
3478
3586
  });
@@ -3497,11 +3605,11 @@ function buildEngineCallOptions(args) {
3497
3605
  };
3498
3606
  }
3499
3607
  async function applyAction(args) {
3500
- const { client, instance, task, action, params, actor, grants, clock, clientForGdr } = args, options = buildEngineCallOptions({ actor, grants, clock, clientForGdr });
3501
- 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({
3502
3610
  client,
3503
3611
  instanceId: instance._id,
3504
- task,
3612
+ activity,
3505
3613
  action,
3506
3614
  ...params !== void 0 ? { params } : {},
3507
3615
  options
@@ -3581,14 +3689,16 @@ function previewIds(ids) {
3581
3689
  }
3582
3690
  const workflow = {
3583
3691
  /**
3584
- * Deploy a set of workflow definitions as one call. Each lands as a
3585
- * {@link WORKFLOW_DEFINITION_TYPE} document; subsequent versions are
3586
- * stored alongside earlier ones `startInstance` picks the highest
3587
- * version by default. The SDK figures out the dependency order itself
3588
- * (children before parents that spawn them via
3589
- * `subworkflows.definition`), idempotently writes any definition
3590
- * whose content has changed, and reports a per-definition outcome
3591
- * (`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`).
3592
3702
  *
3593
3703
  * Refs may point inside the batch OR at already-deployed definitions
3594
3704
  * in the lake — both are valid. A ref pointing at neither errors with
@@ -3601,31 +3711,15 @@ const workflow = {
3601
3711
  validateTag(tag);
3602
3712
  for (const def of definitions)
3603
3713
  validateDefinition(def);
3604
- const ordered = await sortByDependencies(client, definitions, tag, workflowResource), results = [], tx = client.transaction();
3714
+ const ordered = await sortByDependencies(client, definitions, tag), results = [], tx = client.transaction();
3605
3715
  let hasWrites = !1;
3606
3716
  for (const def of ordered) {
3607
- const id = definitionDocId(workflowResource, tag, def.name, def.version), expected = {
3608
- ...def,
3609
- _id: id,
3610
- _type: WORKFLOW_DEFINITION_TYPE,
3611
- tag
3612
- }, existing = await client.getDocument(id);
3613
- if (existing && existing.tag === tag && isDefinitionUnchanged(existing, expected)) {
3614
- 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" });
3615
3720
  continue;
3616
3721
  }
3617
- if (existing) {
3618
- const expectedKeys = new Set(Object.keys(expected)), toUnset = Object.keys(existing).filter(
3619
- (k) => !k.startsWith("_") && !expectedKeys.has(k)
3620
- ), patch = client.patch(id).set(expected).ifRevisionId(existing._rev);
3621
- toUnset.length > 0 && patch.unset(toUnset), tx.patch(patch);
3622
- } else
3623
- tx.create(expected);
3624
- hasWrites = !0, results.push({
3625
- definition: def.name,
3626
- version: def.version,
3627
- status: existing ? "updated" : "created"
3628
- });
3722
+ tx.create(plan.document), hasWrites = !0, results.push({ definition: def.name, version: plan.version, status: "created" });
3629
3723
  }
3630
3724
  return hasWrites && await tx.commit(), { results };
3631
3725
  },
@@ -3641,8 +3735,8 @@ const workflow = {
3641
3735
  * Spawn a new workflow instance from a deployed definition.
3642
3736
  *
3643
3737
  * Pins the snapshot at start-time, seeds `effectsContext`, enters
3644
- * the initial stage (which builds taskStatus, queues onEnter
3645
- * 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
3646
3740
  * until stable. Returns the resulting instance.
3647
3741
  */
3648
3742
  startInstance: async (args) => {
@@ -3662,7 +3756,7 @@ const workflow = {
3662
3756
  throw new Error(
3663
3757
  `Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
3664
3758
  );
3665
- const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedFields = await resolveDeclaredFields({
3759
+ const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], fieldDiscards = [], resolvedFields = await resolveDeclaredFields({
3666
3760
  entryDefs: definition.fields ?? [],
3667
3761
  initialFields: initialFields ?? [],
3668
3762
  ctx: {
@@ -3672,7 +3766,8 @@ const workflow = {
3672
3766
  tag,
3673
3767
  workflowResource,
3674
3768
  definitionName: definition.name,
3675
- ...perspective !== void 0 ? { perspective } : {}
3769
+ ...perspective !== void 0 ? { perspective } : {},
3770
+ recordDiscard: recordFieldDiscards(fieldDiscards, "workflow", now)
3676
3771
  },
3677
3772
  randomKey
3678
3773
  }), effectivePerspective = perspective ?? derivePerspectiveFromFields(resolvedFields), base = buildInstanceBase({
@@ -3682,18 +3777,20 @@ const workflow = {
3682
3777
  workflowResource,
3683
3778
  definitionName: definition.name,
3684
3779
  pinnedVersion: definition.version,
3780
+ pinnedContentHash: definition.contentHash,
3685
3781
  definition,
3686
3782
  fields: resolvedFields,
3687
3783
  effectsContext: effectsContextEntries,
3688
3784
  ancestors: ancestors ?? [],
3689
3785
  perspective: effectivePerspective,
3690
3786
  initialStage: definition.initialStage,
3691
- actor
3787
+ actor,
3788
+ ...fieldDiscards.length > 0 ? { extraHistory: fieldDiscards } : {}
3692
3789
  });
3693
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);
3694
3791
  },
3695
3792
  /**
3696
- * 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
3697
3794
  * auto-invoked first (pending → active) so callers don't have to know
3698
3795
  * the lifecycle. Cascades auto-transitions and propagates to ancestors
3699
3796
  * after the action commits.
@@ -3707,13 +3804,13 @@ const workflow = {
3707
3804
  client,
3708
3805
  tag,
3709
3806
  instanceId,
3710
- task,
3807
+ activity,
3711
3808
  action,
3712
3809
  params,
3713
3810
  idempotent,
3714
3811
  resourceClients
3715
3812
  } = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
3716
- 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({
3717
3814
  client,
3718
3815
  tag,
3719
3816
  instanceId,
@@ -3723,10 +3820,10 @@ const workflow = {
3723
3820
  clock,
3724
3821
  before,
3725
3822
  ...resourceClients !== void 0 ? { resourceClients } : {},
3726
- apply: (instance, evaluation) => (assertActionAllowed(evaluation, task, action), applyAction({
3823
+ apply: (instance, evaluation) => (assertActionAllowed(evaluation, activity, action), applyAction({
3727
3824
  client,
3728
3825
  instance,
3729
- task,
3826
+ activity,
3730
3827
  action,
3731
3828
  actor,
3732
3829
  ...access.grants !== void 0 ? { grants: access.grants } : {},
@@ -3807,7 +3904,7 @@ const workflow = {
3807
3904
  * Re-evaluate auto-transitions and resolve due waits until stable.
3808
3905
  *
3809
3906
  * Used by the runtime after any event that might affect the workflow
3810
- * 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
3811
3908
  * workflow completed, the clock crossed a `completeWhen` deadline, etc.
3812
3909
  * The runtime doesn't need to know what changed — it just nudges
3813
3910
  * affected instances and the engine re-evaluates.
@@ -3917,8 +4014,8 @@ const workflow = {
3917
4014
  * declared by a `doc.ref` / `doc.refs` entry in scope), then
3918
4015
  * evaluates the supplied GROQ in groq-js against that dataset. The
3919
4016
  * rendered scope is auto-bound: `$self`, `$fields`, `$parent`,
3920
- * `$ancestors`, `$stage`, `$now`, `$effects`, `$tasks`,
3921
- * `$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
3922
4019
  * the snapshot's keying.
3923
4020
  *
3924
4021
  * Use when an external consumer (a UI, a debug pane, a test) wants
@@ -3971,34 +4068,83 @@ const workflow = {
3971
4068
  * List the actions an actor could fire on an instance's current stage,
3972
4069
  * each flagged `allowed` (with a structured `disabledReason` when not).
3973
4070
  * Projects the instance from the actor's perspective and flattens its
3974
- * tasks' actions. Returns the evaluation alongside the actions so a
4071
+ * activities' actions. Returns the evaluation alongside the actions so a
3975
4072
  * consumer can read the instance/stage context. Pure read.
3976
4073
  */
3977
4074
  availableActions: async (args) => {
3978
4075
  const evaluation = await evaluateInstance(args);
3979
- return { evaluation, actions: availableActions(evaluation.currentStage.tasks) };
4076
+ return { evaluation, actions: availableActions(evaluation.currentStage.activities) };
3980
4077
  },
3981
4078
  /**
3982
4079
  * Materialised spawned children of a parent instance.
3983
4080
  *
3984
4081
  * Walks `history` for `spawned` events — the durable
3985
- * record. (The per-task `spawnedInstances` field on the active stage
4082
+ * record. (The per-activity `spawnedInstances` field on the active stage
3986
4083
  * only exists while that stage is current; history survives.) Strips
3987
4084
  * the GDR URI on each `instanceRef` to a bare `_id`, fetches the
3988
4085
  * instances, drops any that aren't visible to this engine's tag, and
3989
4086
  * returns them sorted by `startedAt` ascending.
3990
4087
  *
3991
- * 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.
3992
4089
  */
3993
4090
  children: async (args) => {
3994
- const { client, tag, instanceId, task } = args;
4091
+ const { client, tag, instanceId, activity } = args;
3995
4092
  validateTag(tag);
3996
- 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));
3997
4094
  return ids.length === 0 ? [] : client.fetch(
3998
4095
  `*[_id in $ids && ${tagScopeFilter()}] | order(startedAt asc)`,
3999
4096
  { ids, tag }
4000
4097
  );
4001
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
+ },
4002
4148
  /**
4003
4149
  * Permission helpers — Sanity ACL grants evaluated against documents
4004
4150
  * via GROQ. Used by `workflow.evaluate` to soft-gate actions when the
@@ -4052,10 +4198,13 @@ function walkEffectNames(def, visit) {
4052
4198
  for (const e of effects ?? []) visit(e.name, location);
4053
4199
  };
4054
4200
  for (const stage of def.stages ?? []) {
4055
- for (const t of stage.tasks ?? []) {
4056
- 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`);
4057
4203
  for (const a of t.actions ?? [])
4058
- 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
+ );
4059
4208
  }
4060
4209
  for (const tr of stage.transitions ?? [])
4061
4210
  visitEffects(tr.effects, `stage[${stage.name}].transition[${tr.name}].effects`);
@@ -4071,7 +4220,7 @@ async function drainEffectsInternal(args) {
4071
4220
  effectHandlers,
4072
4221
  missingHandler,
4073
4222
  logger
4074
- } = args, drainerActor = args.access?.actor ?? engineSystemActor("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 = {
4075
4224
  actor: drainerActor,
4076
4225
  ...args.access?.grants !== void 0 ? { grants: args.access.grants } : {}
4077
4226
  };
@@ -4090,6 +4239,7 @@ async function drainEffectsInternal(args) {
4090
4239
  if (!await claimPendingEffect(client, before, candidate._key, drainerActor)) continue;
4091
4240
  const { outputs, dispatchError } = await dispatchEffect(handler, candidate, {
4092
4241
  client,
4242
+ clientFor,
4093
4243
  instanceId,
4094
4244
  logger
4095
4245
  });
@@ -4144,6 +4294,7 @@ async function dispatchEffect(handler, candidate, ctx) {
4144
4294
  try {
4145
4295
  const result = await handler(candidate.params, {
4146
4296
  client: ctx.client,
4297
+ clientFor: ctx.clientFor,
4147
4298
  instanceId: ctx.instanceId,
4148
4299
  effectKey: candidate._key,
4149
4300
  log: (message, extra) => ctx.logger(`effect.${candidate.name}`).info(message, extra)
@@ -4232,13 +4383,13 @@ function createInstanceSession(args) {
4232
4383
  return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: cascaded > 0 };
4233
4384
  });
4234
4385
  },
4235
- fireAction({ task, action, params }) {
4386
+ fireAction({ activity, action, params }) {
4236
4387
  return commit(async (actor, held) => {
4237
- assertActionAllowed(await evaluateWith(held, heldGuards), task, action);
4388
+ assertActionAllowed(await evaluateWith(held, heldGuards), activity, action);
4238
4389
  const { grants } = await access(), ranOps = await applyAction({
4239
4390
  client,
4240
4391
  instance,
4241
- task,
4392
+ activity,
4242
4393
  action,
4243
4394
  actor,
4244
4395
  clientForGdr,
@@ -4345,13 +4496,14 @@ function createEngine(args) {
4345
4496
  definition,
4346
4497
  ...resourceClients !== void 0 ? { resourceClients } : {}
4347
4498
  }),
4348
- children: ({ instanceId, task }) => workflow.children({
4499
+ children: ({ instanceId, activity }) => workflow.children({
4349
4500
  client,
4350
4501
  tag,
4351
4502
  workflowResource,
4352
4503
  instanceId,
4353
- ...task !== void 0 ? { task } : {}
4504
+ ...activity !== void 0 ? { activity } : {}
4354
4505
  }),
4506
+ instancesForDocument: ({ document }) => workflow.instancesForDocument({ client, tag, workflowResource, document }),
4355
4507
  query: ({ groq, params }) => workflow.query({
4356
4508
  client,
4357
4509
  tag,
@@ -4397,33 +4549,25 @@ function createEngine(args) {
4397
4549
  })
4398
4550
  };
4399
4551
  }
4400
- function docIdFor(def, target) {
4401
- return definitionDocId(target.workflowResource, target.tag, def.name, def.version);
4402
- }
4403
- function diffStatus(existing, expected) {
4404
- return existing === void 0 ? "create" : isDefinitionUnchanged(existing, expected) ? "unchanged" : "update";
4405
- }
4406
- function diffEntry(def, existingRaw, target) {
4407
- const docId = docIdFor(def, target), expected = {
4408
- ...def,
4409
- _id: docId,
4410
- _type: WORKFLOW_DEFINITION_TYPE,
4411
- tag: target.tag
4412
- }, 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;
4413
4554
  return {
4414
4555
  name: def.name,
4415
- version: def.version,
4416
- status,
4417
- docId,
4418
- expected,
4556
+ version: plan.version,
4557
+ status: plan.status,
4558
+ docId: plan.docId,
4559
+ expected: plan.document,
4419
4560
  ...existing !== void 0 ? { existing } : {}
4420
4561
  };
4421
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
+ }
4422
4566
  async function computeDiffEntries(client, defs, target) {
4423
4567
  const entries = [];
4424
4568
  for (const def of defs) {
4425
- const existingRaw = await client.getDocument(docIdFor(def, target)) ?? void 0;
4426
- entries.push(diffEntry(def, existingRaw, target));
4569
+ const latest = await loadLatestDeployed(client, def.name, target.tag);
4570
+ entries.push(diffEntry(def, latest, target));
4427
4571
  }
4428
4572
  return entries;
4429
4573
  }
@@ -4436,17 +4580,17 @@ const HISTORY_DISPLAY = {
4436
4580
  title: "Stage exited",
4437
4581
  description: "The instance left a stage on the way to the next one."
4438
4582
  },
4439
- taskActivated: {
4440
- title: "Task activated",
4441
- 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."
4442
4586
  },
4443
- taskStatusChanged: {
4444
- title: "Task status changed",
4445
- 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)."
4446
4590
  },
4447
4591
  actionFired: {
4448
4592
  title: "Action fired",
4449
- 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."
4450
4594
  },
4451
4595
  transitionFired: {
4452
4596
  title: "Transition fired",
@@ -4462,7 +4606,7 @@ const HISTORY_DISPLAY = {
4462
4606
  },
4463
4607
  spawned: {
4464
4608
  title: "Spawned child",
4465
- description: "A task's `subworkflows` declaration spawned a child workflow instance."
4609
+ description: "An activity's `subworkflows` declaration spawned a child workflow instance."
4466
4610
  },
4467
4611
  aborted: {
4468
4612
  title: "Instance aborted",
@@ -4471,6 +4615,10 @@ const HISTORY_DISPLAY = {
4471
4615
  opApplied: {
4472
4616
  title: "Op applied",
4473
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."
4474
4622
  }
4475
4623
  }, FIELD_SLOT_DISPLAY = {
4476
4624
  "doc.ref": {
@@ -4485,50 +4633,58 @@ const HISTORY_DISPLAY = {
4485
4633
  title: "Content Release reference",
4486
4634
  description: "GDR pointer at a Content Release system doc, carrying the release name the engine derives the instance's read perspective from."
4487
4635
  },
4488
- query: {
4489
- title: "Query result",
4490
- description: "Snapshot of a GROQ projection captured at entry-resolve time; resolvedAt is stamped on the value."
4491
- },
4492
- "value.string": {
4636
+ string: {
4493
4637
  title: "String value",
4494
- description: "Free-form string entry."
4638
+ description: "Free-form single-line string entry."
4495
4639
  },
4496
- "value.url": {
4497
- title: "URL value",
4498
- description: "Validated URL entry."
4640
+ text: {
4641
+ title: "Text value",
4642
+ description: "Free-form multiline string entry."
4499
4643
  },
4500
- "value.number": {
4644
+ number: {
4501
4645
  title: "Number value",
4502
4646
  description: "Numeric entry."
4503
4647
  },
4504
- "value.boolean": {
4648
+ boolean: {
4505
4649
  title: "Boolean value",
4506
4650
  description: "True/false entry."
4507
4651
  },
4508
- "value.dateTime": {
4652
+ date: {
4653
+ title: "Date value",
4654
+ description: "Date-only (YYYY-MM-DD) entry, no time component."
4655
+ },
4656
+ datetime: {
4509
4657
  title: "Date / time value",
4510
- description: "ISO-timestamp entry."
4658
+ description: "ISO-8601 timestamp entry."
4511
4659
  },
4512
- "value.actor": {
4513
- title: "Actor value",
4514
- description: "A typed (user|ai|system) actor identity."
4660
+ url: {
4661
+ title: "URL value",
4662
+ description: "Validated URL entry."
4515
4663
  },
4516
- checklist: {
4517
- title: "Checklist",
4518
- 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."
4519
4667
  },
4520
- notes: {
4521
- title: "Notes log",
4522
- 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."
4523
4671
  },
4524
4672
  assignees: {
4525
4673
  title: "Assignees",
4526
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."
4527
4683
  }
4528
4684
  }, OP_DISPLAY = {
4529
4685
  "field.set": {
4530
4686
  title: "Set field entry",
4531
- description: "Overwrite a field entry's value with a resolved Source."
4687
+ description: "Overwrite a field entry's value with a resolved value expression."
4532
4688
  },
4533
4689
  "field.unset": {
4534
4690
  title: "Unset field entry",
@@ -4536,7 +4692,7 @@ const HISTORY_DISPLAY = {
4536
4692
  },
4537
4693
  "field.append": {
4538
4694
  title: "Append to field entry",
4539
- 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)."
4540
4696
  },
4541
4697
  "field.updateWhere": {
4542
4698
  title: "Update matching rows",
@@ -4547,8 +4703,8 @@ const HISTORY_DISPLAY = {
4547
4703
  description: "Drop rows of an array-kind entry that match the predicate."
4548
4704
  },
4549
4705
  "status.set": {
4550
- title: "Set task status",
4551
- 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)."
4552
4708
  }
4553
4709
  }, EFFECTS_CONTEXT_DISPLAY = {
4554
4710
  "effectsContext.string": {
@@ -4588,25 +4744,25 @@ const HISTORY_DISPLAY = {
4588
4744
  title: "Workflow instance",
4589
4745
  description: "A running (or finished) workflow against its declared fields."
4590
4746
  }
4591
- }, TASK_KIND_DISPLAY = {
4747
+ }, ACTIVITY_KIND_DISPLAY = {
4592
4748
  user: {
4593
- title: "User task",
4749
+ title: "User activity",
4594
4750
  description: "A person acts via the app \u2014 renders action buttons ranked by outcome."
4595
4751
  },
4596
4752
  service: {
4597
- title: "Service task",
4753
+ title: "Service activity",
4598
4754
  description: "An automated system or effect does the work \u2014 renders effect drain status."
4599
4755
  },
4600
4756
  script: {
4601
- title: "Script task",
4757
+ title: "Script activity",
4602
4758
  description: "The engine runs a step inline and resolves on activation \u2014 an audit row."
4603
4759
  },
4604
4760
  manual: {
4605
- title: "Manual task",
4761
+ title: "Manual activity",
4606
4762
  description: "Work done off-system, acknowledged by a person or verified by a predicate \u2014 renders an instruction card with a deep-link."
4607
4763
  },
4608
4764
  receive: {
4609
- title: "Receive task",
4765
+ title: "Receive activity",
4610
4766
  description: 'No actor; the engine waits on a condition \u2014 renders "waiting until \u2026", no buttons.'
4611
4767
  }
4612
4768
  }, DRIVER_KIND_DISPLAY = {
@@ -4632,6 +4788,8 @@ function displayDescription(typeKey) {
4632
4788
  return DISPLAY[typeKey]?.description;
4633
4789
  }
4634
4790
  export {
4791
+ ACTIVITY_KINDS,
4792
+ ACTIVITY_KIND_DISPLAY,
4635
4793
  AUTHORING_DISPLAY,
4636
4794
  ActionDisabledError,
4637
4795
  ActionParamsInvalidError,
@@ -4653,13 +4811,12 @@ export {
4653
4811
  OP_DISPLAY,
4654
4812
  PartialGuardDeployError,
4655
4813
  RequiredFieldNotProvidedError,
4656
- TASK_KINDS,
4657
- TASK_KIND_DISPLAY,
4658
4814
  WORKFLOW_DEFINITION_TYPE,
4659
4815
  WORKFLOW_INSTANCE_TYPE,
4660
4816
  WorkflowStateDivergedError,
4661
4817
  abortReason,
4662
4818
  actionVerdict,
4819
+ activityKind,
4663
4820
  availableActions,
4664
4821
  buildSnapshot,
4665
4822
  compileGuard,
@@ -4670,7 +4827,7 @@ export {
4670
4827
  defaultLoggerFactory,
4671
4828
  denyingGuards,
4672
4829
  deployStageGuards,
4673
- deriveTaskKind,
4830
+ deriveActivityKind,
4674
4831
  diagnoseInputFromEvaluation,
4675
4832
  diagnoseInstance,
4676
4833
  diffEntry,
@@ -4688,8 +4845,9 @@ export {
4688
4845
  guardsForDefinition,
4689
4846
  guardsForInstance,
4690
4847
  guardsForResource,
4848
+ hashDefinitionContent,
4691
4849
  instanceGuardQuery,
4692
- isDefinitionUnchanged,
4850
+ instanceWatchesDocument,
4693
4851
  isGdr,
4694
4852
  isStartableDefinition,
4695
4853
  isTerminalStage,
@@ -4709,7 +4867,6 @@ export {
4709
4867
  subscriptionDocument,
4710
4868
  subscriptionDocumentsForInstance,
4711
4869
  tagScopeFilter,
4712
- taskKind,
4713
4870
  validateDefinition,
4714
4871
  validateTag,
4715
4872
  verdictGuardsForInstance,