@sanity/workflow-engine 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -24,20 +24,8 @@ function _interopNamespaceCompat(e) {
24
24
 
25
25
  var v__namespace = /* @__PURE__ */ _interopNamespaceCompat(v);
26
26
 
27
- function hasItems(arr) {
28
- return arr !== void 0 && arr.length > 0;
29
- }
30
-
31
- function deriveActivityKind(activity) {
32
- return hasItems(activity.actions) ? "user" : hasItems(activity.effects) ? "service" : activity.completeWhen !== void 0 || activity.subworkflows !== void 0 ? "receive" : "script";
33
- }
34
-
35
- function activityKind(activity) {
36
- return activity.kind ?? deriveActivityKind(activity);
37
- }
38
-
39
- function driverKind(actor) {
40
- return actor.kind === "person" || actor.kind === "agent" ? actor.kind : "service";
27
+ function isFilterScopedOut(entry) {
28
+ return entry.status === "skipped" && entry.startedAt === void 0;
41
29
  }
42
30
 
43
31
  function liveSubworkflows(host) {
@@ -302,7 +290,7 @@ const HIGH_FREQUENCY_SAMPLE_MS = 6e4, WorkflowDefinitionDeployed = defineWorkflo
302
290
  });
303
291
 
304
292
  function definitionDeployedData(definition, outcome) {
305
- const stages = definition.stages, activities = stages.flatMap(stage => stage.activities ?? []), actions = activities.flatMap(activity => activity.actions ?? []), transitions = stages.flatMap(stage => stage.transitions ?? []), fieldEntries2 = [ ...definition.fields ?? [], ...stages.flatMap(stage => stage.fields ?? []), ...activities.flatMap(activity => activity.fields ?? []) ], effectCarriers = [ ...activities, ...actions, ...transitions ];
293
+ const stages = definition.stages, activities = stages.flatMap(stage => stage.activities ?? []), actions = activities.flatMap(activity => activity.actions ?? []), transitions = stages.flatMap(stage => stage.transitions ?? []), fieldEntries2 = [ ...definition.fields ?? [], ...stages.flatMap(stage => stage.fields ?? []), ...activities.flatMap(activity => activity.fields ?? []) ];
306
294
  return {
307
295
  contentHash: outcome.contentHash,
308
296
  status: outcome.status,
@@ -312,11 +300,11 @@ function definitionDeployedData(definition, outcome) {
312
300
  actionCount: actions.length,
313
301
  transitionCount: transitions.length,
314
302
  fieldCount: fieldEntries2.length,
315
- activityKinds: [ ...new Set(activities.map(activityKind)) ].sort(),
303
+ activityKinds: [ ...new Set(activities.map(invariants.deriveActivityKind)) ].sort(),
316
304
  fieldKinds: [ ...new Set(fieldEntries2.map(entry => entry.type)) ].sort(),
317
305
  guardCount: stages.reduce((n, stage) => n + (stage.guards ?? []).length, 0),
318
- effectCount: effectCarriers.reduce((n, node) => n + (node.effects ?? []).length, 0),
319
- subworkflowCount: activities.filter(activity => activity.subworkflows !== void 0).length,
306
+ effectCount: actions.reduce((n, action) => n + (action.effects ?? []).length, 0),
307
+ subworkflowCount: actions.filter(action => action.spawn !== void 0).length,
320
308
  lifecycle: definition.lifecycle ?? "standalone"
321
309
  };
322
310
  }
@@ -363,7 +351,7 @@ function openStage$1(instance) {
363
351
 
364
352
  function firedActivityKind(instance, activity) {
365
353
  const node = openStage$1(instance).stage?.activities?.find(a => a.name === activity);
366
- return node === void 0 ? void 0 : activityKind(node);
354
+ return node === void 0 ? void 0 : invariants.deriveActivityKind(node);
367
355
  }
368
356
 
369
357
  function editedFieldKind(instance, target) {
@@ -401,20 +389,11 @@ function fieldEditedData(args) {
401
389
  }
402
390
 
403
391
  function effectSites(def) {
404
- const sites = [], collect = (effects, location) => {
405
- for (const effect of effects ?? []) sites.push({
406
- effect: effect,
407
- location: location
408
- });
409
- };
410
- for (const stage of def.stages ?? []) {
411
- for (const activity of stage.activities ?? []) {
412
- const at = `stage[${stage.name}].activity[${activity.name}]`;
413
- collect(activity.effects, `${at}.effects`);
414
- for (const action of activity.actions ?? []) collect(action.effects, `${at}.action[${action.name}].effects`);
415
- }
416
- for (const transition of stage.transitions ?? []) collect(transition.effects, `stage[${stage.name}].transition[${transition.name}].effects`);
417
- }
392
+ const sites = [];
393
+ for (const stage of def.stages ?? []) for (const activity of stage.activities ?? []) for (const action of activity.actions ?? []) for (const effect of action.effects ?? []) sites.push({
394
+ effect: effect,
395
+ location: `stage[${stage.name}].activity[${activity.name}].action[${action.name}].effects`
396
+ });
418
397
  return sites;
419
398
  }
420
399
 
@@ -640,25 +619,15 @@ function describeSiteHeading(address, ctx) {
640
619
  });
641
620
  }
642
621
 
643
- case "complete-when":
644
- {
645
- const activity = activityTitle(address.activity, ctx);
646
- return groqConditionDescribe.phrase(id, {
647
- params: {
648
- activity: activity
649
- },
650
- text: `${groqConditionDescribe.quoted(activity)} completes automatically when:`
651
- });
652
- }
653
-
654
- case "fail-when":
622
+ case "action-when":
655
623
  {
656
- const activity = activityTitle(address.activity, ctx);
624
+ const title = actionTitle(address, ctx), activity = activityTitle(address.activity, ctx);
657
625
  return groqConditionDescribe.phrase(id, {
658
626
  params: {
627
+ title: title,
659
628
  activity: activity
660
629
  },
661
- text: `${groqConditionDescribe.quoted(activity)} fails automatically when:`
630
+ text: `action ${groqConditionDescribe.quoted(title)} on ${groqConditionDescribe.quoted(activity)} fires automatically when:`
662
631
  });
663
632
  }
664
633
 
@@ -728,20 +697,26 @@ function describeSite(site, ctx) {
728
697
  });
729
698
  }
730
699
 
700
+ case "action-when":
701
+ {
702
+ const title = actionTitle(site, ctx), activity = activityTitle(site.activity, ctx);
703
+ return groqConditionDescribe.phrase(id, {
704
+ params: {
705
+ title: title,
706
+ activity: activity
707
+ },
708
+ text: `the ${groqConditionDescribe.quoted(title)} trigger on ${groqConditionDescribe.quoted(activity)}`
709
+ });
710
+ }
711
+
731
712
  case "activity-filter":
732
- case "complete-when":
733
- case "fail-when":
734
713
  {
735
- const activity = activityTitle(site.activity, ctx), verb = {
736
- "activity-filter": "visibility of",
737
- "complete-when": "auto-completion of",
738
- "fail-when": "auto-failure of"
739
- }[site.kind];
714
+ const activity = activityTitle(site.activity, ctx);
740
715
  return groqConditionDescribe.phrase(id, {
741
716
  params: {
742
717
  activity: activity
743
718
  },
744
- text: `${verb} ${groqConditionDescribe.quoted(activity)}`
719
+ text: `existence of ${groqConditionDescribe.quoted(activity)}`
745
720
  });
746
721
  }
747
722
 
@@ -790,7 +765,7 @@ function unsatisfiedTransitionSummaries(evaluation) {
790
765
  const ctx = {
791
766
  definition: evaluation.definition
792
767
  };
793
- return evaluation.currentStage.transitions.filter(t => !t.filterSatisfied).flatMap(t => {
768
+ return evaluation.currentStage.transitions.filter(t => !t.whenSatisfied).flatMap(t => {
794
769
  const summary = describeCondition(t.insight, ctx).summary;
795
770
  return summary !== void 0 ? [ {
796
771
  transition: t.transition.name,
@@ -804,7 +779,12 @@ function describeNode(node, ctx) {
804
779
  }
805
780
 
806
781
  function dispatchDescribe(node, ctx) {
807
- return "involvedIn" in node ? describeFieldInsight(node, ctx) : "actions" in node ? describeActivity(node, ctx) : "transition" in node ? describeCondition(node.insight, ctx) : "analysis" in node ? describeCondition(node, ctx) : node.insight !== void 0 ? describeCondition(node.insight, ctx) : void 0;
782
+ if ("involvedIn" in node) return describeFieldInsight(node, ctx);
783
+ if ("actions" in node) return describeActivity(node, ctx);
784
+ if ("transition" in node) return describeCondition(node.insight, ctx);
785
+ if ("analysis" in node) return describeCondition(node, ctx);
786
+ const gate = "whenInsight" in node ? node.whenInsight ?? node.insight : node.insight;
787
+ return gate !== void 0 ? describeCondition(gate, ctx) : void 0;
808
788
  }
809
789
 
810
790
  function describeActivity(node, ctx) {
@@ -815,12 +795,6 @@ function describeActivity(node, ctx) {
815
795
  } : {},
816
796
  ...node.filterInsight !== void 0 ? {
817
797
  filter: describeCondition(node.filterInsight, ctx)
818
- } : {},
819
- ...node.completeWhenInsight !== void 0 ? {
820
- completeWhen: describeCondition(node.completeWhenInsight, ctx)
821
- } : {},
822
- ...node.failWhenInsight !== void 0 ? {
823
- failWhen: describeCondition(node.failWhenInsight, ctx)
824
798
  } : {}
825
799
  };
826
800
  }
@@ -867,7 +841,7 @@ function stageSites(stage, definition) {
867
841
  kind: "transition",
868
842
  transition: transition.name
869
843
  },
870
- condition: transition.filter
844
+ condition: transition.when
871
845
  });
872
846
  for (const activity of stage.activities ?? []) sites.push(...activitySites(activity, stage.name));
873
847
  for (const site of editableFieldsInStage(definition, stage)) typeof site.effective == "string" && sites.push({
@@ -894,15 +868,14 @@ function stageSites(stage, definition) {
894
868
  }
895
869
 
896
870
  function activitySites(activity, stage) {
897
- const named = (kind, condition) => condition !== void 0 ? [ {
871
+ return [ ...activity.filter !== void 0 ? [ {
898
872
  stage: stage,
899
873
  address: {
900
- kind: kind,
874
+ kind: "activity-filter",
901
875
  activity: activity.name
902
876
  },
903
- condition: condition
904
- } ] : [];
905
- return [ ...named("activity-filter", activity.filter), ...Object.entries(activity.requirements ?? {}).map(([requirement, condition]) => ({
877
+ condition: activity.filter
878
+ } ] : [], ...Object.entries(activity.requirements ?? {}).map(([requirement, condition]) => ({
906
879
  stage: stage,
907
880
  address: {
908
881
  kind: "requirement",
@@ -910,7 +883,7 @@ function activitySites(activity, stage) {
910
883
  requirement: requirement
911
884
  },
912
885
  condition: condition
913
- })), ...named("complete-when", activity.completeWhen), ...named("fail-when", activity.failWhen), ...(activity.actions ?? []).flatMap(action => action.filter !== void 0 ? [ {
886
+ })), ...(activity.actions ?? []).flatMap(action => [ ...action.filter !== void 0 ? [ {
914
887
  stage: stage,
915
888
  address: {
916
889
  kind: "action",
@@ -918,7 +891,15 @@ function activitySites(activity, stage) {
918
891
  action: action.name
919
892
  },
920
893
  condition: action.filter
921
- } ] : []) ];
894
+ } ] : [], ...action.when !== void 0 ? [ {
895
+ stage: stage,
896
+ address: {
897
+ kind: "action-when",
898
+ activity: activity.name,
899
+ action: action.name
900
+ },
901
+ condition: action.when
902
+ } ] : [] ]) ];
922
903
  }
923
904
 
924
905
  async function describeDefinition(definition) {
@@ -940,58 +921,111 @@ async function describeDefinition(definition) {
940
921
  }
941
922
 
942
923
  function acceptsDocumentType(definition, documentType) {
943
- return (definition.fields ?? []).some(entry => entry.required === !0 && (entry.type === "doc.ref" || entry.type === "doc.refs") && (entry.types === void 0 || entry.types.includes(documentType)));
924
+ return (definition.fields ?? []).some(entry => invariants.isSubjectEntry(entry) && (entry.types === void 0 || entry.types.includes(documentType)));
944
925
  }
945
926
 
946
927
  async function isDefinitionApplicable(args) {
947
- const {definition: definition, document: document} = args;
948
- return assertCandidateDocument(document), !invariants.isStartableDefinition(definition) || !acceptsDocumentType(definition, document._type) ? !1 : evaluateApplicableWhen(definition, document);
928
+ const {definition: definition, document: document, scope: scope} = args;
929
+ return assertCandidateDocument(document), !invariants.isStartableDefinition(definition) || !acceptsDocumentType(definition, document._type) ? !1 : evaluateDeclaredStartFilter({
930
+ definition: definition,
931
+ document: document,
932
+ scope: scope
933
+ });
949
934
  }
950
935
 
951
936
  async function applicableDefinitions(args) {
952
- const {definitions: definitions, document: document} = args;
937
+ const {definitions: definitions, document: document, scope: scope} = args;
953
938
  assertCandidateDocument(document);
954
939
  const verdicts = await Promise.all(definitions.map(definition => isDefinitionApplicable({
955
940
  definition: definition,
956
- document: document
941
+ document: document,
942
+ ...scope !== void 0 ? {
943
+ scope: scope
944
+ } : {}
957
945
  })));
958
946
  return definitions.filter((_, i) => verdicts[i]);
959
947
  }
960
948
 
961
- async function evaluateApplicableWhen(definition, document) {
962
- const groq = definition.applicableWhen;
963
- if (groq === void 0) return !0;
949
+ async function evaluateStartFilter(args) {
950
+ const {filter: filter, definition: definition, document: document, scope: scope} = args;
951
+ let parsed;
952
+ try {
953
+ parsed = {
954
+ tree: groqJs.parse(filter),
955
+ needsDataset: groqConditionDescribe.analyzeCondition(filter).readsDataset
956
+ };
957
+ } catch (err) {
958
+ rethrowNamingDefinition(err, definition);
959
+ }
960
+ let dataset = [];
961
+ if (parsed.needsDataset) {
962
+ if (scope?.fetchDataset === void 0) return !1;
963
+ dataset = await scope.fetchDataset();
964
+ }
964
965
  try {
965
- const tree = groqJs.parse(groq), result = await (await groqJs.evaluate(tree, {
966
- root: document,
967
- dataset: [ document ]
966
+ const result = await (await groqJs.evaluate(parsed.tree, {
967
+ ...document !== void 0 ? {
968
+ root: document
969
+ } : {},
970
+ dataset: dataset,
971
+ params: startFilterParams({
972
+ definition: definition,
973
+ scope: scope
974
+ })
968
975
  })).get();
969
976
  return invariants.isUnevaluable(result) ? !1 : !!result;
970
977
  } catch (err) {
971
- invariants.rethrowWithContext(err, `applicableWhen on definition "${definition.name ?? "<unnamed>"}" failed to evaluate (deploy validation rejects this — was the definition written around it?)`);
978
+ rethrowNamingDefinition(err, definition);
972
979
  }
973
980
  }
974
981
 
982
+ function rethrowNamingDefinition(err, definition) {
983
+ invariants.rethrowWithContext(err, `start.filter on definition "${definition.name ?? "<unnamed>"}" failed to evaluate (deploy validation rejects this — was the definition written around it?)`);
984
+ }
985
+
986
+ function startFilterParams({definition: definition, scope: scope}) {
987
+ return {
988
+ ...definition.name !== void 0 ? {
989
+ definition: definition.name
990
+ } : {},
991
+ ...scope?.tag !== void 0 ? {
992
+ tag: scope.tag
993
+ } : {},
994
+ ...scope?.now !== void 0 ? {
995
+ now: scope.now
996
+ } : {},
997
+ ...scope?.fields !== void 0 ? {
998
+ fields: scope.fields
999
+ } : {}
1000
+ };
1001
+ }
1002
+
1003
+ async function evaluateDeclaredStartFilter(args) {
1004
+ const {definition: definition, document: document, scope: scope} = args, filter = definition.start?.filter;
1005
+ return filter === void 0 ? !0 : evaluateStartFilter({
1006
+ filter: filter,
1007
+ definition: definition,
1008
+ document: document,
1009
+ ...scope !== void 0 ? {
1010
+ scope: scope
1011
+ } : {}
1012
+ });
1013
+ }
1014
+
975
1015
  function assertCandidateDocument(document) {
976
1016
  const docType = document._type;
977
1017
  if (typeof docType != "string" || docType.length === 0) throw new invariants.ContractViolationError("Applicability: the candidate document must carry a non-empty `_type` — pass the loaded document, not a reference or bare id.");
978
1018
  }
979
1019
 
980
- function applicabilitySyntaxIssues(groq) {
981
- let tree;
1020
+ const START_FILTER_VAR_NAMES = new Set(invariants.START_FILTER_VARS.map(v2 => v2.name));
1021
+
1022
+ function startFilterSyntaxIssues(groq) {
982
1023
  try {
983
- tree = groqJs.parse(groq);
1024
+ groqJs.parse(groq);
984
1025
  } catch (err) {
985
1026
  return [ err instanceof Error ? err.message : String(err) ];
986
1027
  }
987
- const issues = [];
988
- hasEverythingNode(tree) && issues.push("scans the dataset with `*` — `applicableWhen` evaluates against the candidate document alone (there is no lake in this context), so a scan reads a phantom dataset and silently misleads. Read the document's own attributes instead (e.g. `_type == 'article' && articleType == 'finance'`)");
989
- for (const name of invariants.conditionParameterNames(groq)) issues.push(`reads $${name} — \`applicableWhen\` evaluates against the candidate document alone; no \`$\` vars are bound (an unbound variable is GROQ null, so the predicate would silently never match)`);
990
- return issues;
991
- }
992
-
993
- function hasEverythingNode(node) {
994
- return Array.isArray(node) ? node.some(hasEverythingNode) : typeof node != "object" || node === null ? !1 : node.type === "Everything" ? !0 : Object.values(node).some(hasEverythingNode);
1028
+ return [ ...invariants.conditionParameterNames(groq) ].filter(name => !START_FILTER_VAR_NAMES.has(name)).map(name => `reads $${name}, which the start-filter context does not bind (an unbound variable is GROQ null, so the predicate would silently never match). Bound here: ` + invariants.START_FILTER_VARS.map(v2 => `$${v2.name}`).join(", "));
995
1029
  }
996
1030
 
997
1031
  function getPath(value, path) {
@@ -1069,10 +1103,25 @@ function rewriteRefsRecursive(value, resource) {
1069
1103
  return mapJsonStrings(value, (str, key) => key === "_ref" && !str.includes(":") ? invariants.gdrFromResource(resource, str) : str);
1070
1104
  }
1071
1105
 
1106
+ function overlayInstanceInSnapshot(snapshot, instance) {
1107
+ const uri = invariants.selfGdr(instance), restamped = {
1108
+ ...instance,
1109
+ _id: uri
1110
+ };
1111
+ return snapshot.knownIds.has(uri) ? {
1112
+ docs: snapshot.docs.map(doc => doc._id === uri ? restamped : doc),
1113
+ knownIds: snapshot.knownIds
1114
+ } : {
1115
+ docs: [ ...snapshot.docs, restamped ],
1116
+ knownIds: /* @__PURE__ */ new Set([ ...snapshot.knownIds, uri ])
1117
+ };
1118
+ }
1119
+
1072
1120
  function resolveFieldRead(args) {
1073
- const {kind: kind, value: value, path: path, snapshot: snapshot} = args;
1121
+ const {kind: kind, value: value, path: path, snapshot: snapshot, targetKind: targetKind} = args;
1074
1122
  if (kind === "doc.ref") {
1075
1123
  if (!invariants.isGdr(value)) return null;
1124
+ if (path === void 0 && targetKind === "doc.ref") return value;
1076
1125
  const base = derefBase(value, snapshot);
1077
1126
  return path === void 0 ? base : walkDocPath(base, path);
1078
1127
  }
@@ -1095,7 +1144,8 @@ function buildParams(args) {
1095
1144
  ancestors: instance.ancestors.map(a => a.id),
1096
1145
  stage: instance.currentStage,
1097
1146
  now: now,
1098
- effects: effectsContextMap(instance),
1147
+ context: contextMap(instance),
1148
+ effects: effectOutputsMap(instance),
1099
1149
  effectStatus: effectStatusMap(instance),
1100
1150
  activities: currentActivities2,
1101
1151
  subworkflows: subworkflowVarRows(instance, snapshot),
@@ -1110,6 +1160,7 @@ function subworkflowVarRows(instance, snapshot) {
1110
1160
  _id: invariants.toBareId(row.ref.id),
1111
1161
  rowKey: row.rowKey,
1112
1162
  activity: row.activity,
1163
+ action: row.action,
1113
1164
  definition: row.definition,
1114
1165
  current: row.stageEntry === openKey,
1115
1166
  ...childState(row, snapshot),
@@ -1182,9 +1233,15 @@ function effectStatusMap(instance) {
1182
1233
  return out;
1183
1234
  }
1184
1235
 
1185
- function effectsContextMap(instance) {
1236
+ function contextMap(instance) {
1237
+ const out = {};
1238
+ for (const entry of instance.context) out[entry.name] = entry._type === "context.json" ? parseJsonContextEntry(entry) : entry.value;
1239
+ return out;
1240
+ }
1241
+
1242
+ function effectOutputsMap(instance) {
1186
1243
  const out = {};
1187
- for (const entry of instance.effectsContext) out[entry.name] = entry._type === "effectsContext.json" ? parseJsonContextEntry(entry) : entry.value;
1244
+ for (const run of instance.effectHistory) run.outputs !== void 0 && (out[run.name] = run.outputs);
1188
1245
  return out;
1189
1246
  }
1190
1247
 
@@ -1192,7 +1249,7 @@ function parseJsonContextEntry(entry) {
1192
1249
  try {
1193
1250
  return JSON.parse(entry.value);
1194
1251
  } catch (err) {
1195
- invariants.rethrowWithContext(err, `effectsContext entry "${entry.name}" holds unparseable JSON`);
1252
+ invariants.rethrowWithContext(err, `context entry "${entry.name}" holds unparseable JSON`);
1196
1253
  }
1197
1254
  }
1198
1255
 
@@ -1257,8 +1314,9 @@ function recordFieldDiscards({target: target, scope: scope, at: at}) {
1257
1314
 
1258
1315
  function applyActivityStatusChange(args) {
1259
1316
  const {entry: entry, history: history, stage: stage, to: to, at: at, actor: actor} = args, from = entry.status;
1260
- entry.status = to, invariants.isTerminalActivityStatus(to) && (entry.completedAt = at,
1261
- actor !== void 0 && (entry.completedBy = actor.id)), history.push({
1317
+ entry.status = to, invariants.isTerminalActivityStatus(to) ? (entry.completedAt = at,
1318
+ actor !== void 0 && (entry.completedBy = actor.id)) : (delete entry.completedAt,
1319
+ delete entry.completedBy), history.push({
1262
1320
  _key: randomKey(),
1263
1321
  _type: "activityStatusChanged",
1264
1322
  at: at,
@@ -1346,7 +1404,7 @@ class RequiredFieldNotProvidedError extends invariants.WorkflowError {
1346
1404
  missing;
1347
1405
  constructor(args) {
1348
1406
  const lines = args.missing.map(m => ` - ${m.name} (${m.type})`).join(`\n`), where = args.definition !== void 0 ? ` for workflow "${args.definition}"` : "";
1349
- super("required-field-not-provided", `Required input fields${where} were not provided:\n${lines}\nProvide each via \`initialFields\` when starting standalone, or via the parent's \`subworkflows.with\` when spawned.`),
1407
+ super("required-field-not-provided", `Required input fields${where} were not provided:\n${lines}\nProvide each via \`initialFields\` when starting standalone, or via the parent's \`spawn.with\` when spawned.`),
1350
1408
  this.name = "RequiredFieldNotProvidedError", args.definition !== void 0 && (this.definition = args.definition),
1351
1409
  this.missing = args.missing;
1352
1410
  }
@@ -1374,7 +1432,11 @@ class PartialGuardDeployError extends invariants.WorkflowError {
1374
1432
  }
1375
1433
  }
1376
1434
 
1377
- const CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS = 3;
1435
+ const CONCURRENT_COMMIT_MAX_ATTEMPTS = 3;
1436
+
1437
+ function lostRaceMessage(args) {
1438
+ return `${args.what} (instance ${args.instanceId}) lost the optimistic-locking race ${args.attempts} attempts running — a concurrent writer kept committing first. Re-read and retry, or investigate a write storm on this instance.`;
1439
+ }
1378
1440
 
1379
1441
  class ConcurrentFireActionError extends invariants.WorkflowError {
1380
1442
  instanceId;
@@ -1382,9 +1444,12 @@ class ConcurrentFireActionError extends invariants.WorkflowError {
1382
1444
  action;
1383
1445
  attempts;
1384
1446
  constructor(args) {
1385
- super("concurrent-fire-action", `Action "${args.action}" on activity "${args.activity}" (instance ${args.instanceId}) lost the optimistic-locking race ${args.attempts} attempts running — a concurrent writer kept committing first. Re-read and retry, or investigate a write storm on this instance.`),
1386
- this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.activity = args.activity,
1387
- this.action = args.action, this.attempts = args.attempts;
1447
+ super("concurrent-fire-action", lostRaceMessage({
1448
+ what: `Action "${args.action}" on activity "${args.activity}"`,
1449
+ instanceId: args.instanceId,
1450
+ attempts: args.attempts
1451
+ })), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId,
1452
+ this.activity = args.activity, this.action = args.action, this.attempts = args.attempts;
1388
1453
  }
1389
1454
  }
1390
1455
 
@@ -1400,9 +1465,26 @@ class ConcurrentEditFieldError extends invariants.WorkflowError {
1400
1465
  attempts;
1401
1466
  constructor(args) {
1402
1467
  const where = args.target.activity !== void 0 ? `${args.target.activity}.${args.target.field}` : args.target.field;
1403
- super("concurrent-edit-field", `Edit of "${args.target.scope}:${where}" (instance ${args.instanceId}) lost the optimistic-locking race ${args.attempts} attempts running — a concurrent writer kept committing first. Re-read and retry, or investigate a write storm on this instance.`),
1404
- this.name = "ConcurrentEditFieldError", this.instanceId = args.instanceId, this.target = args.target,
1405
- this.attempts = args.attempts;
1468
+ super("concurrent-edit-field", lostRaceMessage({
1469
+ what: `Edit of "${args.target.scope}:${where}"`,
1470
+ instanceId: args.instanceId,
1471
+ attempts: args.attempts
1472
+ })), this.name = "ConcurrentEditFieldError", this.instanceId = args.instanceId,
1473
+ this.target = args.target, this.attempts = args.attempts;
1474
+ }
1475
+ }
1476
+
1477
+ class ConcurrentCompleteEffectError extends invariants.WorkflowError {
1478
+ instanceId;
1479
+ effectKey;
1480
+ attempts;
1481
+ constructor(args) {
1482
+ super("concurrent-complete-effect", lostRaceMessage({
1483
+ what: `Completion of effect entry "${args.effectKey}"`,
1484
+ instanceId: args.instanceId,
1485
+ attempts: args.attempts
1486
+ })), this.name = "ConcurrentCompleteEffectError", this.instanceId = args.instanceId,
1487
+ this.effectKey = args.effectKey, this.attempts = args.attempts;
1406
1488
  }
1407
1489
  }
1408
1490
 
@@ -1410,7 +1492,7 @@ class CascadeLimitError extends invariants.WorkflowError {
1410
1492
  instanceId;
1411
1493
  limit;
1412
1494
  constructor(args) {
1413
- super("cascade-limit", `Cascade did not stabilise after ${args.limit} auto-transitions on ${args.instanceId} — likely a runaway loop (transition guards that stay mutually satisfied). Check that each transition's filter eventually goes false.`),
1495
+ super("cascade-limit", `Cascade did not stabilise after ${args.limit} hops on ${args.instanceId} — likely a runaway loop (transition \`when\`s that stay mutually satisfied). Check that each transition's \`when\` eventually goes false.`),
1414
1496
  this.name = "CascadeLimitError", this.instanceId = args.instanceId, this.limit = args.limit;
1415
1497
  }
1416
1498
  }
@@ -1520,9 +1602,6 @@ function opAppliedEntry(args) {
1520
1602
  ...origin.action !== void 0 ? {
1521
1603
  action: origin.action
1522
1604
  } : {},
1523
- ...origin.transition !== void 0 ? {
1524
- transition: origin.transition
1525
- } : {},
1526
1605
  ...origin.edit === !0 ? {
1527
1606
  edit: !0
1528
1607
  } : {},
@@ -1584,7 +1663,11 @@ async function applyOp(op, ctx) {
1584
1663
  }
1585
1664
 
1586
1665
  function applyFieldSet(op, ctx) {
1587
- const value = resolveOpValue(op.value, ctx), entry = locateEntry(ctx, op.target);
1666
+ const entry = locateEntry(ctx, op.target), value = resolveOpValue({
1667
+ src: op.value,
1668
+ ctx: ctx,
1669
+ target: entrySlot(entry)
1670
+ });
1588
1671
  return invariants.validateFieldValue({
1589
1672
  entryType: entry._type,
1590
1673
  entryName: entry.name,
@@ -1609,6 +1692,24 @@ function entryShape(entry) {
1609
1692
  } : {} : {};
1610
1693
  }
1611
1694
 
1695
+ function entrySlot(entry) {
1696
+ return entry._type === "object" ? {
1697
+ kind: entry._type,
1698
+ fields: entry.fields
1699
+ } : {
1700
+ kind: entry._type
1701
+ };
1702
+ }
1703
+
1704
+ function appendItemSlot(entry) {
1705
+ if (entry._type === "doc.refs") return {
1706
+ kind: "doc.ref"
1707
+ };
1708
+ if (entry._type === "array") return {
1709
+ fields: entry.of
1710
+ };
1711
+ }
1712
+
1612
1713
  const EMPTY_BY_KIND = {
1613
1714
  "doc.refs": [],
1614
1715
  array: [],
@@ -1624,7 +1725,13 @@ function applyFieldUnset(op, ctx) {
1624
1725
  }
1625
1726
 
1626
1727
  function applyFieldAppend(op, ctx) {
1627
- const item = resolveOpValue(op.value, ctx), entry = locateEntry(ctx, op.target);
1728
+ const entry = locateEntry(ctx, op.target), slot = appendItemSlot(entry), item = resolveOpValue({
1729
+ src: op.value,
1730
+ ctx: ctx,
1731
+ ...slot !== void 0 ? {
1732
+ target: slot
1733
+ } : {}
1734
+ });
1628
1735
  invariants.validateFieldAppendItem({
1629
1736
  entryType: entry._type,
1630
1737
  entryName: entry.name,
@@ -1650,17 +1757,31 @@ function withRowKey(item) {
1650
1757
  }
1651
1758
 
1652
1759
  async function applyFieldUpdateWhere(op, ctx) {
1653
- const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op), merge = resolveOpValue(op.value, ctx);
1760
+ const entry = locateEntry(ctx, op.target);
1761
+ requireMergeableRows(entry, op);
1762
+ const rows = requireArrayValue(entry, op), merge = resolveOpValue({
1763
+ src: op.value,
1764
+ ctx: ctx,
1765
+ target: {
1766
+ fields: entry.of
1767
+ }
1768
+ });
1654
1769
  if (merge === null || typeof merge != "object" || Array.isArray(merge)) throw new Error(`field.updateWhere value must resolve to an object of fields to merge (target "${op.target.field}")`);
1770
+ requireNoReservedMergeKeys(merge, op);
1655
1771
  const matches = await rowMatches({
1656
1772
  where: op.where,
1657
1773
  rows: rows,
1658
1774
  ctx: ctx
1659
- });
1660
- return setEntryValue(entry, rows.map((row, index) => matches[index] ? {
1775
+ }), next = rows.map((row, index) => matches[index] ? {
1661
1776
  ...row,
1662
1777
  ...merge
1663
- } : row)), {
1778
+ } : row);
1779
+ return invariants.validateFieldValue({
1780
+ entryType: entry._type,
1781
+ entryName: entry.name,
1782
+ value: next,
1783
+ of: entry.of
1784
+ }), setEntryValue(entry, next), {
1664
1785
  opType: op.type,
1665
1786
  target: op.target,
1666
1787
  resolved: {
@@ -1681,8 +1802,17 @@ async function applyFieldRemoveWhere(op, ctx) {
1681
1802
  };
1682
1803
  }
1683
1804
 
1805
+ function requireMergeableRows(entry, op) {
1806
+ if (entry._type !== "array") throw new Error(`${op.type} target ${op.target.scope}:"${op.target.field}" is a ${entry._type} entry — updateWhere merges declared row sub-fields, so it targets \`array\` entries only`);
1807
+ }
1808
+
1809
+ function requireNoReservedMergeKeys(merge, op) {
1810
+ const reserved = [ "_key", "_type" ].filter(key => key in merge);
1811
+ if (reserved.length !== 0) throw new Error(`field.updateWhere merge writes the reserved row key${reserved.length === 1 ? "" : "s"} ${reserved.map(k => `"${k}"`).join(", ")} (target "${op.target.field}") — row identity and bookkeeping are engine-stamped, never merged`);
1812
+ }
1813
+
1684
1814
  function requireArrayValue(entry, op) {
1685
- if (!Array.isArray(entry.value)) throw new Error(`${op.type} target ${op.target.scope}:"${op.target.field}" holds a non-array ${entry._type} value — where-ops operate on array entries`);
1815
+ if (!Array.isArray(entry.value)) throw new Error(`${op.type} target ${op.target.scope}:"${op.target.field}" (${entry._type}) holds a non-array value — where-ops operate on array entries`);
1686
1816
  return entry.value;
1687
1817
  }
1688
1818
 
@@ -1726,7 +1856,18 @@ function fieldHost(ctx, scope) {
1726
1856
  activity?.fields;
1727
1857
  }
1728
1858
 
1729
- function resolveOpValue(src, ctx) {
1859
+ function subSlot(target, field) {
1860
+ const shape = target?.fields?.find(f => f.name === field);
1861
+ if (shape !== void 0) return {
1862
+ kind: shape.type,
1863
+ ...shape.fields !== void 0 ? {
1864
+ fields: shape.fields
1865
+ } : {}
1866
+ };
1867
+ }
1868
+
1869
+ function resolveOpValue(args) {
1870
+ const {src: src, ctx: ctx, target: target} = args;
1730
1871
  switch (src.type) {
1731
1872
  case "literal":
1732
1873
  case "param":
@@ -1747,14 +1888,26 @@ function resolveOpValue(src, ctx) {
1747
1888
  kind: entry._type,
1748
1889
  value: entry.value,
1749
1890
  path: src.path,
1750
- snapshot: ctx.snapshot
1891
+ snapshot: ctx.snapshot,
1892
+ ...target?.kind !== void 0 ? {
1893
+ targetKind: target.kind
1894
+ } : {}
1751
1895
  });
1752
1896
  }
1753
1897
 
1754
1898
  case "object":
1755
1899
  {
1756
1900
  const out = {};
1757
- for (const [field, fieldSrc] of Object.entries(src.fields)) out[field] = resolveOpValue(fieldSrc, ctx);
1901
+ for (const [field, fieldSrc] of Object.entries(src.fields)) {
1902
+ const slot = subSlot(target, field);
1903
+ out[field] = resolveOpValue({
1904
+ src: fieldSrc,
1905
+ ctx: ctx,
1906
+ ...slot !== void 0 ? {
1907
+ target: slot
1908
+ } : {}
1909
+ });
1910
+ }
1758
1911
  return out;
1759
1912
  }
1760
1913
  }
@@ -1782,7 +1935,7 @@ async function rowMatches({where: where, rows: rows, ctx: ctx}) {
1782
1935
  })));
1783
1936
  }
1784
1937
 
1785
- const WHERE_SCOPE_PARAM_NAMES = [ "row", "params", "actor", "now", "self", "stage", "fields", "effects", "effectStatus", "activities", "allActivitiesDone", "anyActivityFailed" ];
1938
+ const WHERE_SCOPE_PARAM_NAMES = [ "row", "params", "actor", "now", "self", "stage", "fields", "context", "effects", "effectStatus", "activities", "allActivitiesDone", "anyActivityFailed" ];
1786
1939
 
1787
1940
  function whereParams(ctx) {
1788
1941
  const activities = findOpenStageEntry(ctx.mutation)?.activities ?? [];
@@ -1800,7 +1953,8 @@ function whereParams(ctx) {
1800
1953
  activityName: ctx.activityName
1801
1954
  })
1802
1955
  },
1803
- effects: effectsContextMap(ctx.mutation),
1956
+ context: contextMap(ctx.mutation),
1957
+ effects: effectOutputsMap(ctx.mutation),
1804
1958
  effectStatus: effectStatusMap(ctx.mutation),
1805
1959
  activities: activities,
1806
1960
  ...activityGateParams(activities)
@@ -1816,7 +1970,7 @@ function validateDefinition(definition) {
1816
1970
  address: address
1817
1971
  }));
1818
1972
  }
1819
- definition.applicableWhen !== void 0 && v2.report("applicableWhen", applicabilitySyntaxIssues(definition.applicableWhen));
1973
+ definition.start?.filter !== void 0 && v2.report("start.filter", startFilterSyntaxIssues(definition.start.filter));
1820
1974
  for (const entry of definition.fields ?? []) v2.checkEntry(entry, `workflow.fields "${entry.name}"`);
1821
1975
  for (const stage of definition.stages) validateStage(v2, stage);
1822
1976
  for (const issue of invariants.checkWorkflowInvariants(definition)) v2.errors.push(` · ${invariants.formatIssuePath(issue.path)}: ${issue.message}`);
@@ -1856,23 +2010,20 @@ function conditionSiteLabel({stage: stage, address: address}) {
1856
2010
  return `predicate "${address.predicate}"`;
1857
2011
 
1858
2012
  case "transition":
1859
- return `${at} transition "${address.transition}" filter`;
2013
+ return `${at} transition "${address.transition}" when`;
1860
2014
 
1861
2015
  case "activity-filter":
1862
2016
  return `${at} activity "${address.activity}".filter`;
1863
2017
 
1864
- case "complete-when":
1865
- return `${at} activity "${address.activity}".completeWhen`;
1866
-
1867
- case "fail-when":
1868
- return `${at} activity "${address.activity}".failWhen`;
1869
-
1870
2018
  case "requirement":
1871
2019
  return `${at} activity "${address.activity}".requirements "${address.requirement}"`;
1872
2020
 
1873
2021
  case "action":
1874
2022
  return `${at} activity "${address.activity}" action "${address.action}".filter`;
1875
2023
 
2024
+ case "action-when":
2025
+ return `${at} activity "${address.activity}" action "${address.action}".when`;
2026
+
1876
2027
  case "editable-override":
1877
2028
  return `${at}.editable "${address.name}"`;
1878
2029
  }
@@ -1885,15 +2036,6 @@ function validateStage(v2, stage) {
1885
2036
  stageName: stage.name,
1886
2037
  guard: guard
1887
2038
  });
1888
- for (const t of stage.transitions ?? []) {
1889
- const where = `stage "${stage.name}" transition "${t.name}"`;
1890
- checkOpWheres({
1891
- v: v2,
1892
- ops: t.ops,
1893
- label: where
1894
- });
1895
- for (const [key, groq] of effectBindings(t.effects)) v2.tryParse(groq, `${where} effect binding "${key}"`);
1896
- }
1897
2039
  for (const activity of stage.activities ?? []) validateActivity({
1898
2040
  v: v2,
1899
2041
  stageName: stage.name,
@@ -1910,20 +2052,10 @@ function validateGuardReads({v: v2, stageName: stageName, guard: guard}) {
1910
2052
  function validateActivity({v: v2, stageName: stageName, activity: activity}) {
1911
2053
  const where = `stage "${stageName}" activity "${activity.name}"`;
1912
2054
  for (const entry of activity.fields ?? []) v2.checkEntry(entry, `${where}.fields "${entry.name}"`);
1913
- checkOpWheres({
1914
- v: v2,
1915
- ops: activity.ops,
1916
- label: where
1917
- });
1918
- for (const [key, groq] of effectBindings(activity.effects)) v2.tryParse(groq, `${where} effect binding "${key}"`);
1919
2055
  validateActivityActions({
1920
2056
  v: v2,
1921
2057
  where: where,
1922
2058
  activity: activity
1923
- }), activity.subworkflows !== void 0 && validateSubworkflows({
1924
- v: v2,
1925
- where: where,
1926
- sub: activity.subworkflows
1927
2059
  });
1928
2060
  }
1929
2061
 
@@ -1936,6 +2068,11 @@ function validateActivityActions({v: v2, where: where, activity: activity}) {
1936
2068
  label: label
1937
2069
  });
1938
2070
  for (const [key, groq] of effectBindings(a.effects)) v2.tryParse(groq, `${label} effect binding "${key}"`);
2071
+ a.spawn !== void 0 && validateSpawn({
2072
+ v: v2,
2073
+ where: label,
2074
+ sub: a.spawn
2075
+ });
1939
2076
  }
1940
2077
  }
1941
2078
 
@@ -1943,10 +2080,10 @@ function checkOpWheres({v: v2, ops: ops, label: label}) {
1943
2080
  for (const op of ops ?? []) (op.type === "field.updateWhere" || op.type === "field.removeWhere") && v2.checkWhere(op.where, `${label} ${op.type} where`);
1944
2081
  }
1945
2082
 
1946
- function validateSubworkflows({v: v2, where: where, sub: sub}) {
1947
- v2.tryParse(sub.forEach, `${where}.subworkflows.forEach`);
1948
- for (const [key, groq] of Object.entries(sub.with ?? {})) v2.tryParse(groq, `${where}.subworkflows.with "${key}"`);
1949
- for (const [key, groq] of Object.entries(sub.context ?? {})) v2.tryParse(groq, `${where}.subworkflows.context "${key}"`);
2083
+ function validateSpawn({v: v2, where: where, sub: sub}) {
2084
+ v2.tryParse(sub.forEach, `${where}.spawn.forEach`);
2085
+ for (const [key, groq] of Object.entries(sub.with ?? {})) v2.tryParse(groq, `${where}.spawn.with "${key}"`);
2086
+ for (const [key, groq] of Object.entries(sub.context ?? {})) v2.tryParse(groq, `${where}.spawn.context "${key}"`);
1950
2087
  }
1951
2088
 
1952
2089
  function effectBindings(effects) {
@@ -1958,7 +2095,11 @@ function lintEffectOutputs(definition) {
1958
2095
  for (const {effect: effect} of effectSites(definition)) declared.set(effect.name, effect.outputs?.map(shape => shape.name) ?? []);
1959
2096
  const warnings = /* @__PURE__ */ new Set, check = ({effect: effect, key: key}) => {
1960
2097
  const outputs = declared.get(effect);
1961
- outputs === void 0 || outputs.includes(key) || warnings.add(`effect "${effect}" is read as \`$effects['${effect}'].${key}\` but declares outputs [${outputs.join(", ")}] — no declared producer for "${key}"`);
2098
+ if (outputs === void 0) {
2099
+ warnings.add(`\`$effects['${effect}'].${key}\` reads an effect this definition never declares — $effects carries completed effects' outputs only (a start seed or spawn handoff is read via $context)`);
2100
+ return;
2101
+ }
2102
+ outputs.includes(key) || warnings.add(`effect "${effect}" is read as \`$effects['${effect}'].${key}\` but declares outputs [${outputs.join(", ")}] — no declared producer for "${key}"`);
1962
2103
  };
1963
2104
  for (const groq of effectReadGroq(definition)) invariants.conditionEffectReads(groq).forEach(check);
1964
2105
  return guardEffectReads(definition).forEach(check), [ ...warnings ];
@@ -1997,7 +2138,7 @@ function actionVerdict(activity, action) {
1997
2138
  }
1998
2139
 
1999
2140
  function availableActions(activities) {
2000
- return activities.flatMap(t => t.actions.map(a => actionVerdict(t, a)));
2141
+ return activities.filter(t => !t.scopedOut).flatMap(t => t.actions.map(a => actionVerdict(t, a)));
2001
2142
  }
2002
2143
 
2003
2144
  function buildClientForGdr(args) {
@@ -2222,7 +2363,13 @@ const DATA_MODEL_VERSION = 1, DATA_MODEL_MIN_READER = 0, MODEL_STAMP = {
2222
2363
  };
2223
2364
 
2224
2365
  function fieldTreeShape(value) {
2225
- return Array.isArray(value) ? value.map(fieldTreeShape) : value === null ? "null" : typeof value == "object" ? Object.fromEntries(Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => [ key, fieldTreeShape(entry) ])) : typeof value;
2366
+ if (Array.isArray(value)) return value.map(fieldTreeShape);
2367
+ if (value === null) return "null";
2368
+ if (typeof value == "object") {
2369
+ const record = value;
2370
+ return Object.fromEntries(Object.keys(record).toSorted().map(key => [ key, fieldTreeShape(record[key]) ]));
2371
+ }
2372
+ return typeof value;
2226
2373
  }
2227
2374
 
2228
2375
  function modelVersionOf(doc) {
@@ -2309,7 +2456,7 @@ function isResolved(status) {
2309
2456
  }
2310
2457
 
2311
2458
  function failedEffectCause(input) {
2312
- const stalled = new Set(input.activities.filter(t => !isResolved(t.status)).map(t => t.activity.name)), effect = [ ...input.instance.effectHistory ].reverse().find(e => e.status === "failed" && e.origin.kind === "activity" && stalled.has(e.origin.name));
2459
+ const stalledEffectNames = new Set(input.activities.filter(t => !isResolved(t.status)).flatMap(t => (t.activity.actions ?? []).flatMap(action => action.effects ?? [])).map(effect2 => effect2.name)), effect = [ ...input.instance.effectHistory ].reverse().find(e => e.status === "failed" && stalledEffectNames.has(e.name));
2313
2460
  return effect ? {
2314
2461
  kind: "failed-effect",
2315
2462
  effect: effect
@@ -2338,7 +2485,7 @@ function waitingState(input) {
2338
2485
  state: "waiting",
2339
2486
  activity: active.activity.name,
2340
2487
  assignees: input.assignees[active.activity.name] ?? [],
2341
- actions: (active.activity.actions ?? []).map(a => a.name)
2488
+ actions: (active.activity.actions ?? []).filter(a => !invariants.isCascadeFired(a)).map(a => a.name)
2342
2489
  };
2343
2490
  }
2344
2491
 
@@ -2353,7 +2500,7 @@ function blockedState(input) {
2353
2500
  }
2354
2501
 
2355
2502
  function noTransitionFiresCause(input) {
2356
- if (input.transitions.length === 0 || input.transitions.some(t => t.filterSatisfied) || !input.activities.every(t => isResolved(t.status))) return;
2503
+ if (input.transitions.length === 0 || input.transitions.some(t => t.whenSatisfied) || !input.activities.every(t => isResolved(t.status))) return;
2357
2504
  const undecidable = input.transitions.filter(t => t.unevaluable);
2358
2505
  return undecidable.length > 0 ? {
2359
2506
  kind: "transition-unevaluable",
@@ -2390,7 +2537,7 @@ function diagnoseInstance(input) {
2390
2537
  };
2391
2538
  }
2392
2539
 
2393
- const RUNNABLE_VERBS = /* @__PURE__ */ new Set([ "set-stage", "abort" ]);
2540
+ const RUNNABLE_VERBS = /* @__PURE__ */ new Set([ "set-stage", "abort", "drain-effects" ]);
2394
2541
 
2395
2542
  function remediationsForCause(cause) {
2396
2543
  switch (cause.kind) {
@@ -2406,7 +2553,7 @@ function remediationsForCause(cause) {
2406
2553
  case "hung-effect":
2407
2554
  return [ {
2408
2555
  verb: "drain-effects",
2409
- rationale: "Re-run the effect drainer to re-pick the claimed effect."
2556
+ rationale: "Re-run the effect drainer — the runtime that registered the effect handlers — to re-pick the claimed effect."
2410
2557
  }, {
2411
2558
  verb: "abort",
2412
2559
  rationale: "Abort the instance if the effect never drains."
@@ -2499,7 +2646,7 @@ function resolveGuardRead(args) {
2499
2646
  }
2500
2647
  const effectsRead = invariants.EFFECTS_READ.exec(expr);
2501
2648
  if (effectsRead !== null) {
2502
- const outputs = effectsContextMap(ctx.instance)[effectsRead[1]];
2649
+ const outputs = effectOutputsMap(ctx.instance)[effectsRead[1]];
2503
2650
  return effectsRead[2] !== void 0 ? getPath(outputs, effectsRead[2]) : outputs;
2504
2651
  }
2505
2652
  throw new Error(`Guard read "${expr}" is not a supported deploy-time value — use "$self", "$now", "$fields.<name>[.path]", or "$effects['<name>'][.path]" (guards store resolved values; the lake cannot see $fields or $effects)`);
@@ -2751,6 +2898,40 @@ function contentReleaseName(args) {
2751
2898
  if (!readsRaw(ref) && Array.isArray(perspective)) return perspective.find(entry => entry !== "drafts" && entry !== "published" && entry !== "raw");
2752
2899
  }
2753
2900
 
2901
+ function contentDraftFallback(args) {
2902
+ if (readsRaw(args.ref)) return !0;
2903
+ const effective = args.perspective ?? DEFAULT_CONTENT_PERSPECTIVE;
2904
+ return Array.isArray(effective) ? effective.includes("drafts") : effective === "drafts";
2905
+ }
2906
+
2907
+ function contentDocQuery(documentId) {
2908
+ return {
2909
+ query: "*[_id == $id][0]",
2910
+ params: {
2911
+ id: documentId
2912
+ }
2913
+ };
2914
+ }
2915
+
2916
+ function watchRefRepresentations(args) {
2917
+ const {ref: ref} = args;
2918
+ if (readsRaw(ref)) return [ ref.documentId ];
2919
+ const release = contentReleaseName(args);
2920
+ return [ ref.documentId, ...contentDraftFallback(args) ? [ `drafts.${ref.documentId}` ] : [], ...release !== void 0 ? [ `versions.${release}.${ref.documentId}` ] : [] ];
2921
+ }
2922
+
2923
+ function projectToWatchRef(args) {
2924
+ const {doc: doc, ref: ref} = args;
2925
+ if (doc._id === ref.documentId) return doc;
2926
+ const accepted = watchRefRepresentations(args);
2927
+ if (!accepted.includes(doc._id)) throw new Error(`projectToWatchRef: the store resolved "${doc._id}" for watch ref "${ref.globalDocumentId}" — not a representation of "${ref.documentId}" under the instance's perspective (accepted: ${accepted.map(id => `"${id}"`).join(", ")}). The consumer is feeding the wrong document.`);
2928
+ return {
2929
+ ...doc,
2930
+ _id: ref.documentId,
2931
+ _originalId: doc._id
2932
+ };
2933
+ }
2934
+
2754
2935
  function subscriptionDocument(ref) {
2755
2936
  return {
2756
2937
  ...invariants.parseGdr(ref.id),
@@ -2847,9 +3028,8 @@ async function readDoc({client: client, id: id, perspective: perspective}) {
2847
3028
  const doc2 = await client.getDocument(id) ?? null;
2848
3029
  return doc2 ? assertReadableModel(doc2) : null;
2849
3030
  }
2850
- return await client.fetch("*[_id == $id][0]", {
2851
- id: id
2852
- }, {
3031
+ const {query: query, params: params} = contentDocQuery(id);
3032
+ return await client.fetch(query, params, {
2853
3033
  perspective: perspective
2854
3034
  }) ?? null;
2855
3035
  }
@@ -3002,35 +3182,35 @@ async function reload({client: client, instanceId: instanceId, tag: tag}) {
3002
3182
  return doc;
3003
3183
  }
3004
3184
 
3005
- function effectsContextEntry(name, value) {
3185
+ function contextEntry(name, value) {
3006
3186
  const _key = randomKey();
3007
3187
  return typeof value == "string" ? {
3008
3188
  _key: _key,
3009
- _type: "effectsContext.string",
3189
+ _type: "context.string",
3010
3190
  name: name,
3011
3191
  value: value
3012
3192
  } : typeof value == "number" ? {
3013
3193
  _key: _key,
3014
- _type: "effectsContext.number",
3194
+ _type: "context.number",
3015
3195
  name: name,
3016
3196
  value: value
3017
3197
  } : typeof value == "boolean" ? {
3018
3198
  _key: _key,
3019
- _type: "effectsContext.boolean",
3199
+ _type: "context.boolean",
3020
3200
  name: name,
3021
3201
  value: value
3022
3202
  } : {
3023
3203
  _key: _key,
3024
- _type: "effectsContext.ref",
3204
+ _type: "context.ref",
3025
3205
  name: name,
3026
3206
  value: value
3027
3207
  };
3028
3208
  }
3029
3209
 
3030
- function effectsContextJsonEntry(name, value) {
3210
+ function contextJsonEntry(name, value) {
3031
3211
  return {
3032
3212
  _key: randomKey(),
3033
- _type: "effectsContext.json",
3213
+ _type: "context.json",
3034
3214
  name: name,
3035
3215
  value: serializeContextValue(name, value)
3036
3216
  };
@@ -3041,9 +3221,9 @@ function serializeContextValue(name, value) {
3041
3221
  try {
3042
3222
  json = JSON.stringify(value);
3043
3223
  } catch (err) {
3044
- invariants.rethrowWithContext(err, `effectsContext entry "${name}" holds unserializable JSON`);
3224
+ invariants.rethrowWithContext(err, `context entry "${name}" holds unserializable JSON`);
3045
3225
  }
3046
- if (json === void 0) throw new Error(`effectsContext entry "${name}" holds a non-JSON value (undefined, a function, or a symbol)`);
3226
+ if (json === void 0) throw new Error(`context entry "${name}" holds a non-JSON value (undefined, a function, or a symbol)`);
3047
3227
  return json;
3048
3228
  }
3049
3229
 
@@ -3073,7 +3253,7 @@ function buildInstanceBase(args) {
3073
3253
  } : {},
3074
3254
  definitionSnapshot: JSON.stringify(args.definition),
3075
3255
  fields: args.fields,
3076
- effectsContext: args.effectsContext,
3256
+ context: args.context,
3077
3257
  ancestors: args.ancestors,
3078
3258
  ...perspective !== void 0 ? {
3079
3259
  perspective: perspective
@@ -3252,14 +3432,19 @@ async function deleteOrphanedDefinitionGuards(args) {
3252
3432
  }
3253
3433
 
3254
3434
  function derivePerspectiveFromFields(entries) {
3255
- if (entries !== void 0) {
3256
- for (const entry of entries) if (entry._type === "release.ref" && entry.value !== null) {
3257
- const releaseName = entry.value.releaseName;
3258
- if (typeof releaseName == "string" && releaseName.length > 0) return [ releaseName ];
3259
- }
3435
+ for (const entry of entries ?? []) {
3436
+ if (entry._type !== "release.ref") continue;
3437
+ const perspective = perspectiveFromReleaseValue(entry.value);
3438
+ if (perspective !== void 0) return perspective;
3260
3439
  }
3261
3440
  }
3262
3441
 
3442
+ function perspectiveFromReleaseValue(value) {
3443
+ if (typeof value != "object" || value === null) return;
3444
+ const releaseName = value.releaseName;
3445
+ return typeof releaseName == "string" && releaseName.length > 0 ? [ releaseName ] : void 0;
3446
+ }
3447
+
3263
3448
  async function resolveDeclaredFields(args) {
3264
3449
  const {entryDefs: entryDefs, initialFields: initialFields, ctx: ctx, randomKey: randomKey2} = args;
3265
3450
  if (entryDefs === void 0 || entryDefs.length === 0) return [];
@@ -3285,8 +3470,8 @@ async function resolveDeclaredFields(args) {
3285
3470
  }
3286
3471
 
3287
3472
  function missingRequiredInputs(args) {
3288
- return args.entryDefs.filter(entry => entry.required === !0 && entry.initialValue?.type === "input").filter(entry => {
3289
- const match = args.initialFields.find(s => s.name === entry.name && s.type === entry.type);
3473
+ return args.entryDefs.filter(entry => entry.required === !0 && invariants.isInputSourced(entry)).filter(entry => {
3474
+ const match = suppliedFieldFor(entry, args.initialFields);
3290
3475
  return match === void 0 || match.value === void 0 || match.value === null;
3291
3476
  }).map(entry => ({
3292
3477
  name: entry.name,
@@ -3294,6 +3479,10 @@ function missingRequiredInputs(args) {
3294
3479
  }));
3295
3480
  }
3296
3481
 
3482
+ function suppliedFieldFor(entry, initialFields) {
3483
+ return initialFields.find(field => field.name === entry.name && field.type === entry.type);
3484
+ }
3485
+
3297
3486
  function assertRequiredInputProvided({entryDefs: entryDefs, initialFields: initialFields, definitionName: definitionName}) {
3298
3487
  const missing = missingRequiredInputs({
3299
3488
  entryDefs: entryDefs,
@@ -3314,18 +3503,19 @@ function defaultEntryValue(entryType) {
3314
3503
  }
3315
3504
 
3316
3505
  function resolveInputValue({entry: entry, initialFields: initialFields, defaultValue: defaultValue}) {
3317
- const initMatch = initialFields.find(s => s.name === entry.name && s.type === entry.type);
3506
+ const initMatch = suppliedFieldFor(entry, initialFields);
3318
3507
  return initMatch === void 0 ? defaultValue : (assertInputValueShape(entry, initMatch.value),
3319
3508
  initMatch.value);
3320
3509
  }
3321
3510
 
3322
- function resolveFieldReadValue({source: source, ctx: ctx, defaultValue: defaultValue}) {
3323
- const target = (source.scope === "workflow" ? ctx.workflowFields ?? [] : ctx.resolvedFields ?? []).find(s => s.name === source.field);
3324
- return target === void 0 ? defaultValue : resolveFieldRead({
3325
- kind: target._type,
3326
- value: target.value,
3511
+ function resolveFieldReadValue({entry: entry, source: source, ctx: ctx, defaultValue: defaultValue}) {
3512
+ const sourceEntry = (source.scope === "workflow" ? ctx.workflowFields ?? [] : ctx.resolvedFields ?? []).find(s => s.name === source.field);
3513
+ return sourceEntry === void 0 ? defaultValue : resolveFieldRead({
3514
+ kind: sourceEntry._type,
3515
+ value: sourceEntry.value,
3327
3516
  path: source.path,
3328
- snapshot: void 0
3517
+ snapshot: void 0,
3518
+ targetKind: entry.type
3329
3519
  }) ?? defaultValue;
3330
3520
  }
3331
3521
 
@@ -3364,6 +3554,7 @@ async function resolveEntryValue({entry: entry, initialFields: initialFields, ct
3364
3554
  ctx: ctx,
3365
3555
  defaultValue: defaultValue
3366
3556
  }) : source.type === "fieldRead" ? resolveFieldReadValue({
3557
+ entry: entry,
3367
3558
  source: source,
3368
3559
  ctx: ctx,
3369
3560
  defaultValue: defaultValue
@@ -3581,7 +3772,7 @@ function loadCallContext({client: client, instanceId: instanceId, options: optio
3581
3772
 
3582
3773
  async function retryOnRevisionConflict(args) {
3583
3774
  const {client: client, instanceId: instanceId, options: options, commit: commit, onExhausted: onExhausted} = args;
3584
- for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
3775
+ for (let attempt = 1; attempt <= CONCURRENT_COMMIT_MAX_ATTEMPTS; attempt++) {
3585
3776
  const ctx = await loadCallContext({
3586
3777
  client: client,
3587
3778
  instanceId: instanceId,
@@ -3849,7 +4040,7 @@ function startMutation(instance) {
3849
4040
  })),
3850
4041
  pendingEffects: [ ...instance.pendingEffects ],
3851
4042
  effectHistory: [ ...instance.effectHistory ],
3852
- effectsContext: [ ...instance.effectsContext ],
4043
+ context: [ ...instance.context ],
3853
4044
  history: [ ...instance.history ],
3854
4045
  processedRequests: [ ...instance.processedRequests ?? [] ],
3855
4046
  lastChangedAt: instance.lastChangedAt,
@@ -3873,7 +4064,7 @@ function instanceStateFields(src) {
3873
4064
  subworkflows: src.subworkflows ?? [],
3874
4065
  pendingEffects: src.pendingEffects,
3875
4066
  effectHistory: src.effectHistory,
3876
- effectsContext: src.effectsContext,
4067
+ context: src.context,
3877
4068
  history: src.history,
3878
4069
  processedRequests: src.processedRequests ?? []
3879
4070
  };
@@ -3881,6 +4072,15 @@ function instanceStateFields(src) {
3881
4072
  fields;
3882
4073
  }
3883
4074
 
4075
+ function liveViewContext(ctx, mutation) {
4076
+ const materialized = materializeInstance(ctx.instance, mutation);
4077
+ return {
4078
+ ...ctx,
4079
+ instance: materialized,
4080
+ snapshot: overlayInstanceInSnapshot(ctx.snapshot, materialized)
4081
+ };
4082
+ }
4083
+
3884
4084
  function materializeInstance(base, mutation) {
3885
4085
  return {
3886
4086
  ...base,
@@ -3888,7 +4088,9 @@ function materializeInstance(base, mutation) {
3888
4088
  fields: mutation.fields,
3889
4089
  stages: mutation.stages,
3890
4090
  subworkflows: mutation.subworkflows,
3891
- effectsContext: mutation.effectsContext
4091
+ context: mutation.context,
4092
+ pendingEffects: mutation.pendingEffects,
4093
+ effectHistory: mutation.effectHistory
3892
4094
  };
3893
4095
  }
3894
4096
 
@@ -4053,22 +4255,27 @@ async function refreshStageGuards({ctx: ctx, mutation: mutation, stageName: stag
4053
4255
  async function completeEffect(args) {
4054
4256
  const {client: client, instanceId: instanceId, effectKey: effectKey, status: status, outputs: outputs, ops: ops, detail: detail, error: error, durationMs: durationMs, requestRecord: requestRecord, options: options} = args;
4055
4257
  if (status !== "done" && status !== "failed") throw new invariants.ContractViolationError(`completeEffect: status must be "done" or "failed", got ${JSON.stringify(status)} — "cancelled" is engine-stamped by abort, never reportable`);
4056
- const ctx = await loadCallContext({
4258
+ return retryOnRevisionConflict({
4057
4259
  client: client,
4058
4260
  instanceId: instanceId,
4059
- options: options
4060
- });
4061
- return commitCompleteEffect({
4062
- ctx: ctx,
4063
- effectKey: effectKey,
4064
- status: status,
4065
- outputs: outputs,
4066
- ops: ops,
4067
- detail: detail,
4068
- error: error,
4069
- durationMs: durationMs,
4070
- requestRecord: requestRecord,
4071
- actor: options?.actor
4261
+ options: options,
4262
+ commit: ctx => commitCompleteEffect({
4263
+ ctx: ctx,
4264
+ effectKey: effectKey,
4265
+ status: status,
4266
+ outputs: outputs,
4267
+ ops: ops,
4268
+ detail: detail,
4269
+ error: error,
4270
+ durationMs: durationMs,
4271
+ requestRecord: requestRecord,
4272
+ actor: options.actor
4273
+ }),
4274
+ onExhausted: () => new ConcurrentCompleteEffectError({
4275
+ instanceId: instanceId,
4276
+ effectKey: effectKey,
4277
+ attempts: CONCURRENT_COMMIT_MAX_ATTEMPTS
4278
+ })
4072
4279
  });
4073
4280
  }
4074
4281
 
@@ -4158,11 +4365,6 @@ function validateEffectOutputs(args) {
4158
4365
  });
4159
4366
  }
4160
4367
 
4161
- function upsertEffectsContext({mutation: mutation, effectName: effectName, outputs: outputs}) {
4162
- const existingIndex = mutation.effectsContext.findIndex(e => e.name === effectName), entry = effectsContextJsonEntry(effectName, outputs);
4163
- existingIndex >= 0 ? mutation.effectsContext[existingIndex] = entry : mutation.effectsContext.push(entry);
4164
- }
4165
-
4166
4368
  function requirePendingEffect(instance, effectKey) {
4167
4369
  const pending = instance.pendingEffects.find(e => e._key === effectKey);
4168
4370
  if (pending !== void 0) return pending;
@@ -4230,12 +4432,8 @@ async function commitCompleteEffect({ctx: ctx, effectKey: effectKey, status: sta
4230
4432
  outputs: outputs
4231
4433
  });
4232
4434
  mutation.effectHistory.push(settlement.run);
4233
- const wroteEffectsContext = status === "done" && outputs !== void 0;
4234
- wroteEffectsContext && upsertEffectsContext({
4235
- mutation: mutation,
4236
- effectName: pending.name,
4237
- outputs: outputs
4238
- }), mutation.history.push(settlement.event);
4435
+ const wroteEffectOutputs = status === "done" && outputs !== void 0;
4436
+ mutation.history.push(settlement.event);
4239
4437
  const ranOps = await runOps({
4240
4438
  ops: validatedOps,
4241
4439
  mutation: mutation,
@@ -4248,7 +4446,7 @@ async function commitCompleteEffect({ctx: ctx, effectKey: effectKey, status: sta
4248
4446
  self: invariants.selfGdr(ctx.instance),
4249
4447
  now: ranAt,
4250
4448
  snapshot: ctx.snapshot
4251
- }), needsGuardRefresh = wroteEffectsContext || ranOps.some(isFieldOp);
4449
+ }), needsGuardRefresh = wroteEffectOutputs || ranOps.some(isFieldOp);
4252
4450
  return await persistThenMaybeRefresh({
4253
4451
  ctx: ctx,
4254
4452
  mutation: mutation,
@@ -4418,6 +4616,60 @@ async function commitAbort({ctx: ctx, reason: reason, requestRecord: requestReco
4418
4616
  };
4419
4617
  }
4420
4618
 
4619
+ async function buildStageActivities({ctx: ctx, stage: stage, recordDiscard: recordDiscard}) {
4620
+ const entries = [];
4621
+ for (const activity of stage.activities ?? []) entries.push(await buildActivityEntry({
4622
+ ctx: ctx,
4623
+ stage: stage,
4624
+ activity: activity,
4625
+ recordDiscard: recordDiscard
4626
+ }));
4627
+ return entries;
4628
+ }
4629
+
4630
+ async function buildActivityEntry({ctx: ctx, stage: stage, activity: activity, recordDiscard: recordDiscard}) {
4631
+ const outcome = await ctxEvaluateConditionOutcome({
4632
+ ctx: ctx,
4633
+ condition: activity.filter,
4634
+ opts: {
4635
+ activityName: activity.name
4636
+ }
4637
+ }), inScope = outcome !== "unsatisfied", entry = {
4638
+ _key: randomKey(),
4639
+ name: activity.name,
4640
+ status: inScope ? "active" : "skipped",
4641
+ ...activity.filter !== void 0 ? {
4642
+ filterEvaluation: buildFilterEvaluation({
4643
+ at: ctx.now,
4644
+ filter: activity.filter,
4645
+ outcome: outcome
4646
+ })
4647
+ } : {}
4648
+ };
4649
+ return inScope && (entry.startedAt = ctx.now, activity.fields !== void 0 && activity.fields.length > 0 && (entry.fields = await resolveActivityFieldEntries({
4650
+ client: ctx.client,
4651
+ instance: ctx.instance,
4652
+ stage: stage,
4653
+ activity: activity,
4654
+ now: ctx.now,
4655
+ ...recordDiscard !== void 0 ? {
4656
+ recordDiscard: recordDiscard
4657
+ } : {}
4658
+ }))), entry;
4659
+ }
4660
+
4661
+ function buildFilterEvaluation({at: at, filter: filter, outcome: outcome}) {
4662
+ return outcome === "unevaluable" ? {
4663
+ at: at,
4664
+ truthy: !1,
4665
+ unevaluable: !0,
4666
+ detail: `Filter "${filter}" could not be evaluated (GROQ null — a referenced operand was missing or incomparable). Activity kept in scope so the gate is not silently skipped.`
4667
+ } : {
4668
+ at: at,
4669
+ truthy: outcome === "satisfied"
4670
+ };
4671
+ }
4672
+
4421
4673
  async function discoverItems(args) {
4422
4674
  const {client: client, groq: groq, params: params, perspective: perspective} = args;
4423
4675
  return client.fetch(groq, paramsForLake(params), {
@@ -4436,65 +4688,28 @@ function clientForDiscovery({defaultClient: defaultClient, clientForGdr: clientF
4436
4688
 
4437
4689
  const MAX_SPAWN_DEPTH = 6;
4438
4690
 
4439
- function subworkflowsSettledGate(activityName) {
4440
- return `count($subworkflows[activity == ${JSON.stringify(activityName)} && current && status == 'active']) == 0`;
4441
- }
4442
-
4443
4691
  function subworkflowsOnExit(sub) {
4444
4692
  return sub.onExit ?? "detach";
4445
4693
  }
4446
4694
 
4447
- function applyGateOutcome(args) {
4448
- applyActivityStatusChange({
4449
- entry: args.entry,
4450
- history: args.history,
4451
- stage: args.stage,
4452
- to: args.outcome,
4453
- at: args.at,
4454
- ...args.actor !== void 0 ? {
4455
- actor: args.actor
4456
- } : {}
4457
- });
4458
- }
4459
-
4460
- function effectiveCompleteWhen(activity) {
4461
- return activity.completeWhen ?? (activity.subworkflows !== void 0 ? subworkflowsSettledGate(activity.name) : void 0);
4462
- }
4463
-
4464
- async function evaluateResolutionGate({ctx: ctx, activity: activity}) {
4465
- const scope = {
4466
- activityName: activity.name
4467
- };
4468
- if (activity.failWhen !== void 0 && await ctxEvaluateCondition({
4469
- ctx: ctx,
4470
- condition: activity.failWhen,
4471
- opts: scope
4472
- })) return "failed";
4473
- const completeWhen = effectiveCompleteWhen(activity);
4474
- if (completeWhen !== void 0 && await ctxEvaluateCondition({
4475
- ctx: ctx,
4476
- condition: completeWhen,
4477
- opts: scope
4478
- })) return "done";
4479
- }
4480
-
4481
- async function spawnSubworkflows({ctx: ctx, mutation: mutation, activity: activity, sub: sub, actor: actor}) {
4482
- assertSpawnDepth(ctx, activity);
4483
- const openEntry = findOpenStageEntry(mutation);
4484
- if (openEntry === void 0) throw new Error(`Mutation invariant broken: no open StageEntry while spawning on activity "${activity.name}" of ${ctx.instance._id}`);
4485
- const definition = await requireChildDefinition({
4486
- ctx: ctx,
4487
- activity: activity,
4488
- sub: sub
4489
- }), parentScope = await ctxConditionParams(ctx, {
4490
- activityName: activity.name,
4491
- ...actor ? {
4492
- actor: actor
4695
+ async function spawnSubworkflows({ctx: ctx, mutation: mutation, activity: activity, action: action, sub: sub, actor: actor}) {
4696
+ assertSpawnDepth(ctx, activity);
4697
+ const openEntry = findOpenStageEntry(mutation);
4698
+ if (openEntry === void 0) throw new Error(`Mutation invariant broken: no open StageEntry while spawning on activity "${activity.name}" of ${ctx.instance._id}`);
4699
+ const definition = await requireChildDefinition({
4700
+ ctx: ctx,
4701
+ activity: activity,
4702
+ sub: sub
4703
+ }), parentScope = await ctxConditionParams(ctx, {
4704
+ activityName: activity.name,
4705
+ ...actor ? {
4706
+ actor: actor
4493
4707
  } : {}
4494
4708
  }), {rows: rows, discoveryResource: discoveryResource} = await resolveSpawnRows(ctx, sub), keyedRows = keyRows(rows, {
4495
4709
  activity: activity.name,
4710
+ action: action.name,
4496
4711
  instanceId: ctx.instance._id
4497
- }), live = liveSubworkflows(mutation).filter(r => r.activity === activity.name && r.definition === sub.definition.name && r.abortPending === void 0);
4712
+ }), live = liveSubworkflows(mutation).filter(r => r.activity === activity.name && r.action === action.name && r.definition === sub.definition.name && r.abortPending === void 0);
4498
4713
  adoptRediscoveredRows({
4499
4714
  mutation: mutation,
4500
4715
  live: live,
@@ -4503,7 +4718,7 @@ async function spawnSubworkflows({ctx: ctx, mutation: mutation, activity: activi
4503
4718
  activity: activity,
4504
4719
  now: ctx.now
4505
4720
  });
4506
- const effectsContext = await resolveContextHandoff({
4721
+ const context = await resolveContextHandoff({
4507
4722
  ctx: ctx,
4508
4723
  sub: sub,
4509
4724
  parentScope: parentScope
@@ -4512,10 +4727,11 @@ async function spawnSubworkflows({ctx: ctx, mutation: mutation, activity: activi
4512
4727
  ctx: ctx,
4513
4728
  mutation: mutation,
4514
4729
  activity: activity,
4730
+ action: action,
4515
4731
  sub: sub,
4516
4732
  definition: definition,
4517
4733
  parentScope: parentScope,
4518
- effectsContext: effectsContext,
4734
+ context: context,
4519
4735
  discoveryResource: discoveryResource,
4520
4736
  openEntry: openEntry,
4521
4737
  keyed: keyed,
@@ -4553,7 +4769,7 @@ function adoptRediscoveredRows({mutation: mutation, live: live, keyedRows: keyed
4553
4769
  }));
4554
4770
  }
4555
4771
 
4556
- async function spawnRow({ctx: ctx, mutation: mutation, activity: activity, sub: sub, definition: definition, parentScope: parentScope, effectsContext: effectsContext, discoveryResource: discoveryResource, openEntry: openEntry, keyed: keyed, actor: actor}) {
4772
+ async function spawnRow({ctx: ctx, mutation: mutation, activity: activity, action: action, sub: sub, definition: definition, parentScope: parentScope, context: context, discoveryResource: discoveryResource, openEntry: openEntry, keyed: keyed, actor: actor}) {
4557
4773
  const now = ctx.now, initialFields = await projectRowFields({
4558
4774
  ctx: ctx,
4559
4775
  sub: sub,
@@ -4566,7 +4782,7 @@ async function spawnRow({ctx: ctx, mutation: mutation, activity: activity, sub:
4566
4782
  parent: ctx.instance,
4567
4783
  definition: definition,
4568
4784
  initialFields: initialFields,
4569
- effectsContext: effectsContext,
4785
+ context: context,
4570
4786
  actor: actor,
4571
4787
  now: now
4572
4788
  });
@@ -4581,6 +4797,7 @@ async function spawnRow({ctx: ctx, mutation: mutation, activity: activity, sub:
4581
4797
  }), mutation.subworkflows.push({
4582
4798
  _key: randomKey(),
4583
4799
  activity: activity.name,
4800
+ action: action.name,
4584
4801
  definition: sub.definition.name,
4585
4802
  stageEntry: openEntry._key,
4586
4803
  cohortStage: openEntry.name,
@@ -4606,7 +4823,7 @@ function keyRows(rows, cx) {
4606
4823
  rowKey: spawnRowKey(row, cx)
4607
4824
  })), seen = /* @__PURE__ */ new Set;
4608
4825
  for (const {rowKey: rowKey} of keyed) {
4609
- if (seen.has(rowKey)) throw new Error(`subworkflows.forEach on activity "${cx.activity}" of ${cx.instanceId} produced two rows with the same identity "${rowKey}" — adopt-matching cannot tell them apart. Make each row's identity unique: distinct \`_key\`/\`_id\`/GDR \`id\` values for object rows, distinct values for scalar rows.`);
4826
+ if (seen.has(rowKey)) throw new Error(`spawn.forEach on action "${cx.action}" (activity "${cx.activity}") of ${cx.instanceId} produced two rows with the same identity "${rowKey}" — adopt-matching cannot tell them apart. Make each row's identity unique: distinct \`_key\`/\`_id\`/GDR \`id\` values for object rows, distinct values for scalar rows.`);
4610
4827
  seen.add(rowKey);
4611
4828
  }
4612
4829
  return keyed;
@@ -4620,15 +4837,19 @@ function spawnRowKey(row, cx) {
4620
4837
  if (typeof _id == "string" && _id.length > 0) return _id;
4621
4838
  if (invariants.isGdr(row) && invariants.isGdrUri(row.id)) return row.id;
4622
4839
  }
4623
- throw new Error(`subworkflows.forEach on activity "${cx.activity}" of ${cx.instanceId} produced a row without an identity, so re-entering the stage could not tell "same row" from "new row". Project \`_id\` (document rows) or \`_key\` (array items), keep GDR rows whole (a \`{id, type}\` row with a GDR-URI \`id\` — the shape a \`doc.refs\` field stores — keys on its \`id\`), or mint a \`_key\` for synthetic rows (e.g. \`{"_key": lang + "-" + market, ...}\`); scalar rows key on their value. Row: ${JSON.stringify(row)}`);
4840
+ throw new Error(`spawn.forEach on action "${cx.action}" (activity "${cx.activity}") of ${cx.instanceId} produced a row without an identity, so re-entering the stage could not tell "same row" from "new row". Project \`_id\` (document rows) or \`_key\` (array items), keep GDR rows whole (a \`{id, type}\` row with a GDR-URI \`id\` — the shape a \`doc.refs\` field stores — keys on its \`id\`), or mint a \`_key\` for synthetic rows (e.g. \`{"_key": lang + "-" + market, ...}\`); scalar rows key on their value. Row: ${JSON.stringify(row)}`);
4624
4841
  }
4625
4842
 
4626
4843
  function condemnOnExitCohorts({mutation: mutation, stage: stage, exitedEntry: exitedEntry, at: at}) {
4627
- for (const activity of stage.activities ?? []) {
4628
- const sub = activity.subworkflows;
4629
- if (!(sub === void 0 || subworkflowsOnExit(sub) !== "abort")) for (const row of liveSubworkflows(mutation)) row.activity !== activity.name || row.stageEntry !== exitedEntry._key || condemnSubworkflow(row, {
4844
+ const abortCohorts = (stage.activities ?? []).flatMap(activity => (activity.actions ?? []).filter(action => action.spawn !== void 0 && subworkflowsOnExit(action.spawn) === "abort").map(action => ({
4845
+ activity: activity.name,
4846
+ action: action.name
4847
+ })));
4848
+ for (const cohort of abortCohorts) {
4849
+ const owed = liveSubworkflows(mutation).filter(row => row.activity === cohort.activity && row.action === cohort.action && row.stageEntry === exitedEntry._key);
4850
+ for (const row of owed) condemnSubworkflow(row, {
4630
4851
  at: at,
4631
- reason: `stage "${stage.name}" exited with the child still live (onExit: "abort" on activity "${activity.name}")`
4852
+ reason: `stage "${stage.name}" exited with the child still live (onExit: "abort" on action "${cohort.action}" of activity "${cohort.activity}")`
4632
4853
  });
4633
4854
  }
4634
4855
  }
@@ -4679,7 +4900,7 @@ async function projectRowFields({ctx: ctx, sub: sub, childDefinition: childDefin
4679
4900
  const out = [];
4680
4901
  for (const [name, groq] of Object.entries(sub.with ?? {})) {
4681
4902
  const declared = (childDefinition.fields ?? []).find(e => e.name === name);
4682
- if (declared === void 0) throw new Error(`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`);
4903
+ if (declared === void 0) throw new Error(`spawn.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`);
4683
4904
  const raw = await invariants.runGroq({
4684
4905
  groq: groq,
4685
4906
  params: {
@@ -4709,7 +4930,7 @@ async function projectRowFields({ctx: ctx, sub: sub, childDefinition: childDefin
4709
4930
  types: declared.types,
4710
4931
  value: value
4711
4932
  }).includes("document") ? ' A projected bare id or `_ref` canonicalises with the generic type "document" — project the target\'s schema type alongside its id (e.g. {"id": _id, "type": _type}).' : "";
4712
- throw new Error(`subworkflows.with["${name}"] on subworkflow "${childDefinition.name}": ${typeIssues.join("; ")}.${hint}`);
4933
+ throw new Error(`spawn.with["${name}"] on subworkflow "${childDefinition.name}": ${typeIssues.join("; ")}.${hint}`);
4713
4934
  }
4714
4935
  out.push({
4715
4936
  type: declared.type,
@@ -4740,7 +4961,7 @@ async function resolveContextHandoff({ctx: ctx, sub: sub, parentScope: parentSco
4740
4961
  };
4741
4962
  continue;
4742
4963
  }
4743
- throw new Error(`subworkflows.context["${name}"] evaluated to ${JSON.stringify(v2)}, which the handoff cannot carry. A context entry seeds the child's $effects bag with a string, number, boolean, or a document reference — a full GDR \`{id, type}\` whose \`id\` is a "<scheme>:..." URI (parent-scope ids are already canonical).`);
4964
+ throw new Error(`spawn.context["${name}"] evaluated to ${JSON.stringify(v2)}, which the handoff cannot carry. A context entry seeds the child's $context bag with a string, number, boolean, or a document reference — a full GDR \`{id, type}\` whose \`id\` is a "<scheme>:..." URI (parent-scope ids are already canonical).`);
4744
4965
  }
4745
4966
  }
4746
4967
  return out;
@@ -4751,7 +4972,7 @@ function canonicaliseSpawnValue({kind: kind, value: value, cx: cx}) {
4751
4972
  }
4752
4973
 
4753
4974
  function assertBareIdRootsCorrectly(id, cx) {
4754
- if (!(cx.discoveryResource === void 0 || invariants.sameResource(cx.discoveryResource, cx.parentResource))) throw new Error(`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.`);
4975
+ if (!(cx.discoveryResource === void 0 || invariants.sameResource(cx.discoveryResource, cx.parentResource))) throw new Error(`spawn.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.`);
4755
4976
  }
4756
4977
 
4757
4978
  function coerceSpawnGdr(raw, cx) {
@@ -4786,7 +5007,7 @@ async function resolveDefinitionRef({client: client, ref: ref, tag: tag}) {
4786
5007
  }
4787
5008
 
4788
5009
  async function prepareChildInstance(args) {
4789
- const {client: client, parent: parent, definition: definition, initialFields: initialFields, effectsContext: effectsContext, actor: actor, now: now} = args, childTag = parent.tag, workflowResource = parent.workflowResource, childDocId = instanceDocId(childTag), childRef = {
5010
+ const {client: client, parent: parent, definition: definition, initialFields: initialFields, context: context, actor: actor, now: now} = args, childTag = parent.tag, workflowResource = parent.workflowResource, childDocId = instanceDocId(childTag), childRef = {
4790
5011
  id: invariants.gdrFromResource(workflowResource, childDocId),
4791
5012
  type: WORKFLOW_INSTANCE_TYPE
4792
5013
  }, ancestors = [ ...parent.ancestors, {
@@ -4812,7 +5033,7 @@ async function prepareChildInstance(args) {
4812
5033
  })
4813
5034
  },
4814
5035
  randomKey: randomKey
4815
- }), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(([key, value]) => effectsContextEntry(key, value)), base = buildInstanceBase({
5036
+ }), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, contextEntries = Object.entries(context).map(([key, value]) => contextEntry(key, value)), base = buildInstanceBase({
4816
5037
  id: childDocId,
4817
5038
  now: now,
4818
5039
  tag: childTag,
@@ -4822,7 +5043,7 @@ async function prepareChildInstance(args) {
4822
5043
  pinnedContentHash: definition.contentHash,
4823
5044
  definition: definition,
4824
5045
  fields: childFields,
4825
- effectsContext: effectsContextEntries,
5046
+ context: contextEntries,
4826
5047
  ancestors: ancestors,
4827
5048
  perspective: childPerspective,
4828
5049
  initialStage: definition.initialStage,
@@ -4837,182 +5058,6 @@ async function prepareChildInstance(args) {
4837
5058
  };
4838
5059
  }
4839
5060
 
4840
- async function invokeActivity(args) {
4841
- const {client: client, instanceId: instanceId, activity: activityName, options: options} = args, ctx = await loadCallContext({
4842
- client: client,
4843
- instanceId: instanceId,
4844
- options: options
4845
- });
4846
- return commitInvoke({
4847
- ctx: ctx,
4848
- activityName: activityName,
4849
- actor: options?.actor
4850
- });
4851
- }
4852
-
4853
- async function commitInvoke({ctx: ctx, activityName: activityName, actor: actor}) {
4854
- const {activity: activity} = findActivityInCurrentStage(ctx, activityName), entry = findCurrentActivities(ctx.instance).find(t => t.name === activityName);
4855
- if (entry === void 0) throw new Error(`Activity "${activityName}" has no status entry on instance ${ctx.instance._id}`);
4856
- if (entry.status !== "pending") return {
4857
- invoked: !1,
4858
- activity: activityName
4859
- };
4860
- const mutation = startMutation(ctx.instance), mutEntry = requireMutationActivityEntry(mutation, activityName);
4861
- return await activateActivity({
4862
- ctx: ctx,
4863
- mutation: mutation,
4864
- activity: activity,
4865
- entry: mutEntry,
4866
- actor: actor
4867
- }), await persist(ctx, mutation), {
4868
- invoked: !0,
4869
- activity: activityName
4870
- };
4871
- }
4872
-
4873
- async function activateActivity({ctx: ctx, mutation: mutation, activity: activity, entry: entry, actor: actor}) {
4874
- const now = ctx.now;
4875
- if (entry.status = "active", entry.startedAt = now, activity.fields !== void 0 && activity.fields.length > 0) {
4876
- const stage = findStage(ctx.definition, mutation.currentStage);
4877
- entry.fields = await resolveActivityFieldEntries({
4878
- client: ctx.client,
4879
- instance: ctx.instance,
4880
- stage: stage,
4881
- activity: activity,
4882
- now: now,
4883
- recordDiscard: recordFieldDiscards({
4884
- target: mutation.history,
4885
- scope: "activity",
4886
- at: now
4887
- })
4888
- });
4889
- }
4890
- await runOps({
4891
- ops: activity.ops,
4892
- mutation: mutation,
4893
- stage: mutation.currentStage,
4894
- origin: {
4895
- activity: activity.name
4896
- },
4897
- params: {},
4898
- actor: actor,
4899
- self: invariants.selfGdr(ctx.instance),
4900
- now: now,
4901
- snapshot: ctx.snapshot
4902
- }), mutation.history.push({
4903
- _key: randomKey(),
4904
- _type: "activityActivated",
4905
- at: now,
4906
- stage: mutation.currentStage,
4907
- activity: activity.name,
4908
- ...actor !== void 0 ? {
4909
- actor: actor
4910
- } : {}
4911
- }), await queueEffects({
4912
- ctx: ctx,
4913
- mutation: mutation,
4914
- effects: activity.effects,
4915
- origin: {
4916
- kind: "activity",
4917
- name: activity.name
4918
- },
4919
- actor: actor,
4920
- opts: {
4921
- activityName: activity.name
4922
- }
4923
- }), activity.subworkflows !== void 0 && await spawnSubworkflows({
4924
- ctx: ctx,
4925
- mutation: mutation,
4926
- activity: activity,
4927
- sub: activity.subworkflows,
4928
- actor: actor
4929
- }), !await autoResolveOnActivate({
4930
- ctx: ctx,
4931
- mutation: mutation,
4932
- activity: activity,
4933
- entry: entry,
4934
- now: now,
4935
- actor: actor
4936
- }) && resolveMachineStep({
4937
- mutation: mutation,
4938
- activity: activity,
4939
- entry: entry,
4940
- now: now,
4941
- actor: actor
4942
- });
4943
- }
4944
-
4945
- async function autoResolveOnActivate({ctx: ctx, mutation: mutation, activity: activity, entry: entry, now: now, actor: actor}) {
4946
- if (activity.failWhen === void 0 && effectiveCompleteWhen(activity) === void 0) return !1;
4947
- const gateCtx = {
4948
- ...ctx,
4949
- instance: materializeInstance(ctx.instance, mutation)
4950
- }, outcome = await evaluateResolutionGate({
4951
- ctx: gateCtx,
4952
- activity: activity
4953
- });
4954
- return outcome === void 0 ? !1 : (applyGateOutcome({
4955
- entry: entry,
4956
- history: mutation.history,
4957
- stage: mutation.currentStage,
4958
- at: now,
4959
- outcome: outcome,
4960
- actor: actor
4961
- }), !0);
4962
- }
4963
-
4964
- function resolveMachineStep({mutation: mutation, activity: activity, entry: entry, now: now, actor: actor}) {
4965
- const waitsForActor = activity.actions !== void 0 && activity.actions.length > 0, waitsForCondition = activity.completeWhen !== void 0 || activity.subworkflows !== void 0;
4966
- waitsForActor || waitsForCondition || applyActivityStatusChange({
4967
- entry: entry,
4968
- history: mutation.history,
4969
- stage: mutation.currentStage,
4970
- to: "done",
4971
- at: now,
4972
- ...actor !== void 0 ? {
4973
- actor: actor
4974
- } : {}
4975
- });
4976
- }
4977
-
4978
- async function buildStageActivities(ctx, stage) {
4979
- const entries = [];
4980
- for (const activity of stage.activities ?? []) {
4981
- const outcome = await ctxEvaluateConditionOutcome({
4982
- ctx: ctx,
4983
- condition: activity.filter,
4984
- opts: {
4985
- activityName: activity.name
4986
- }
4987
- }), inScope = outcome !== "unsatisfied";
4988
- entries.push({
4989
- _key: randomKey(),
4990
- name: activity.name,
4991
- status: inScope ? "pending" : "skipped",
4992
- ...activity.filter !== void 0 ? {
4993
- filterEvaluation: buildFilterEvaluation({
4994
- at: ctx.now,
4995
- filter: activity.filter,
4996
- outcome: outcome
4997
- })
4998
- } : {}
4999
- });
5000
- }
5001
- return entries;
5002
- }
5003
-
5004
- function buildFilterEvaluation({at: at, filter: filter, outcome: outcome}) {
5005
- return outcome === "unevaluable" ? {
5006
- at: at,
5007
- truthy: !1,
5008
- unevaluable: !0,
5009
- detail: `Filter "${filter}" could not be evaluated (GROQ null — a referenced operand was missing or incomparable). Activity kept in scope so the gate is not silently skipped.`
5010
- } : {
5011
- at: at,
5012
- truthy: outcome === "satisfied"
5013
- };
5014
- }
5015
-
5016
5061
  function isTerminalStage(stage) {
5017
5062
  return (stage.transitions ?? []).length === 0;
5018
5063
  }
@@ -5032,13 +5077,13 @@ async function setStage(args) {
5032
5077
  });
5033
5078
  }
5034
5079
 
5035
- async function enterStage({ctx: ctx, mutation: mutation, nextStage: nextStage, actor: actor, at: at, entryKey: entryKey}) {
5080
+ async function enterStage({ctx: ctx, mutation: mutation, nextStage: nextStage, at: at}) {
5036
5081
  mutation.currentStage = nextStage.name;
5037
5082
  const entryCtx = {
5038
5083
  ...ctx,
5039
5084
  instance: materializeInstance(ctx.instance, mutation)
5040
5085
  }, nextStageEntry = {
5041
- _key: entryKey ?? randomKey(),
5086
+ _key: randomKey(),
5042
5087
  name: nextStage.name,
5043
5088
  enteredAt: at,
5044
5089
  fields: await resolveStageFieldEntries({
@@ -5054,19 +5099,15 @@ async function enterStage({ctx: ctx, mutation: mutation, nextStage: nextStage, a
5054
5099
  }),
5055
5100
  activities: []
5056
5101
  };
5057
- mutation.stages.push(nextStageEntry), nextStageEntry.activities = await buildStageActivities(entryCtx, nextStage);
5058
- for (const activity of nextStage.activities ?? []) {
5059
- if (activity.activation !== "auto") continue;
5060
- const entry = nextStageEntry.activities.find(t => t.name === activity.name);
5061
- entry && entry.status === "pending" && await activateActivity({
5062
- ctx: entryCtx,
5063
- mutation: mutation,
5064
- activity: activity,
5065
- entry: entry,
5066
- actor: actor
5067
- });
5068
- }
5069
- isTerminalStage(nextStage) && (mutation.completedAt = at);
5102
+ mutation.stages.push(nextStageEntry), nextStageEntry.activities = await buildStageActivities({
5103
+ ctx: entryCtx,
5104
+ stage: nextStage,
5105
+ recordDiscard: recordFieldDiscards({
5106
+ target: mutation.history,
5107
+ scope: "activity",
5108
+ at: at
5109
+ })
5110
+ }), isTerminalStage(nextStage) && (mutation.completedAt = at);
5070
5111
  }
5071
5112
 
5072
5113
  function isTerminal(ctx) {
@@ -5091,13 +5132,34 @@ async function commitSetStage({ctx: ctx, targetStage: targetStage, reason: reaso
5091
5132
  record: requestRecord,
5092
5133
  now: ctx.now
5093
5134
  });
5094
- const at = ctx.now, transitionName = `setStage:${currentStage.name}->${nextStage.name}`;
5095
- return mutation.history.push(...stageTransitionHistory({
5135
+ const transitionName = `setStage:${currentStage.name}->${nextStage.name}`;
5136
+ return await commitStageMove({
5137
+ ctx: ctx,
5138
+ mutation: mutation,
5139
+ fromStage: currentStage,
5140
+ toStage: nextStage,
5141
+ transition: transitionName,
5142
+ via: "setStage",
5143
+ actor: actor,
5144
+ reason: reason
5145
+ }), {
5146
+ fired: !0,
5096
5147
  fromStage: currentStage.name,
5097
5148
  toStage: nextStage.name,
5149
+ transition: transitionName
5150
+ };
5151
+ }
5152
+
5153
+ async function commitStageMove({ctx: ctx, mutation: mutation, fromStage: fromStage, toStage: toStage, transition: transition, via: via, actor: actor, reason: reason}) {
5154
+ const at = ctx.now;
5155
+ mutation.history.push(...stageTransitionHistory({
5156
+ fromStage: fromStage.name,
5157
+ toStage: toStage.name,
5098
5158
  at: at,
5099
- transition: transitionName,
5100
- via: "setStage",
5159
+ transition: transition,
5160
+ ...via === "setStage" ? {
5161
+ via: via
5162
+ } : {},
5101
5163
  ...actor !== void 0 ? {
5102
5164
  actor: actor
5103
5165
  } : {},
@@ -5106,30 +5168,24 @@ async function commitSetStage({ctx: ctx, targetStage: targetStage, reason: reaso
5106
5168
  } : {}
5107
5169
  })), exitOpenStage({
5108
5170
  mutation: mutation,
5109
- stage: currentStage,
5171
+ stage: fromStage,
5110
5172
  at: at
5111
5173
  }), await enterStage({
5112
5174
  ctx: ctx,
5113
5175
  mutation: mutation,
5114
- nextStage: nextStage,
5115
- actor: actor,
5176
+ nextStage: toStage,
5116
5177
  at: at
5117
5178
  }), await persistStageMove({
5118
5179
  ctx: ctx,
5119
5180
  mutation: mutation,
5120
- exitedStage: currentStage.name,
5121
- enteredStage: nextStage.name
5181
+ exitedStage: fromStage.name,
5182
+ enteredStage: toStage.name
5122
5183
  }), emitStageTransitioned({
5123
5184
  ctx: ctx,
5124
- nextStage: nextStage,
5185
+ nextStage: toStage,
5125
5186
  at: at,
5126
- via: "setStage"
5127
- }), {
5128
- fired: !0,
5129
- fromStage: currentStage.name,
5130
- toStage: nextStage.name,
5131
- transition: transitionName
5132
- };
5187
+ via: via
5188
+ });
5133
5189
  }
5134
5190
 
5135
5191
  function emitStageTransitioned({ctx: ctx, nextStage: nextStage, at: at, via: via}) {
@@ -5177,893 +5233,321 @@ async function persistStageMove({ctx: ctx, mutation: mutation, exitedStage: exit
5177
5233
  });
5178
5234
  }
5179
5235
 
5180
- async function commitTransition(ctx, actor) {
5181
- if (isTerminal(ctx)) return {
5182
- fired: !1
5183
- };
5184
- const currentStage = findStage(ctx.definition, ctx.instance.currentStage), transition = await pickTransition(ctx, currentStage);
5185
- if (transition === void 0) return {
5186
- fired: !1
5187
- };
5188
- const mutation = startMutation(ctx.instance), at = ctx.now;
5189
- await runOps({
5190
- ops: transition.ops,
5191
- mutation: mutation,
5192
- stage: currentStage.name,
5193
- origin: {
5194
- transition: transition.name
5195
- },
5196
- params: {},
5197
- actor: actor,
5198
- self: invariants.selfGdr(ctx.instance),
5199
- now: at,
5200
- snapshot: ctx.snapshot
5201
- });
5202
- const nextEntryKey = randomKey();
5203
- await queueEffects({
5204
- ctx: ctx,
5205
- mutation: mutation,
5206
- effects: transition.effects,
5207
- origin: {
5208
- kind: "transition",
5209
- name: transition.name
5210
- },
5211
- actor: actor,
5212
- opts: {
5213
- stageEntryKey: nextEntryKey
5214
- }
5215
- }), mutation.history.push(...stageTransitionHistory({
5216
- fromStage: currentStage.name,
5217
- toStage: transition.to,
5218
- at: at,
5219
- transition: transition.name,
5220
- ...actor !== void 0 ? {
5221
- actor: actor
5222
- } : {}
5223
- })), exitOpenStage({
5224
- mutation: mutation,
5225
- stage: currentStage,
5226
- at: at
5227
- });
5228
- const nextStage = findStage(ctx.definition, transition.to);
5229
- return await enterStage({
5230
- ctx: ctx,
5231
- mutation: mutation,
5232
- nextStage: nextStage,
5233
- actor: actor,
5234
- at: at,
5235
- entryKey: nextEntryKey
5236
- }), await persistStageMove({
5237
- ctx: ctx,
5238
- mutation: mutation,
5239
- exitedStage: currentStage.name,
5240
- enteredStage: nextStage.name
5241
- }), emitStageTransitioned({
5242
- ctx: ctx,
5243
- nextStage: nextStage,
5244
- at: at,
5245
- via: "transition"
5246
- }), {
5247
- fired: !0,
5248
- fromStage: currentStage.name,
5249
- toStage: transition.to,
5250
- transition: transition.name
5251
- };
5252
- }
5253
-
5254
5236
  async function pickTransition(ctx, stage) {
5255
5237
  for (const candidate of stage.transitions ?? []) {
5256
5238
  const outcome = await ctxEvaluateConditionOutcome({
5257
5239
  ctx: ctx,
5258
- condition: candidate.filter
5240
+ condition: candidate.when
5259
5241
  });
5260
5242
  if (outcome === "satisfied") return candidate;
5261
5243
  if (outcome === "unevaluable") return;
5262
5244
  }
5263
5245
  }
5264
5246
 
5265
- async function evaluateAutoTransitions(args) {
5266
- const {client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, clock: clock, executionContext: executionContext, telemetry: telemetry, overlay: overlay} = args, ctx = await loadContext({
5267
- client: client,
5268
- instanceId: instanceId,
5269
- options: {
5270
- clientForGdr: clientForGdr,
5271
- ...actor ? {
5272
- actor: actor
5273
- } : {},
5274
- ...clock ? {
5275
- clock: clock
5276
- } : {},
5277
- ...executionContext ? {
5278
- executionContext: executionContext
5279
- } : {},
5280
- ...telemetry ? {
5281
- telemetry: telemetry
5282
- } : {},
5283
- ...overlay ? {
5284
- overlay: overlay
5285
- } : {}
5286
- }
5287
- });
5288
- return commitTransition(ctx, actor);
5289
- }
5247
+ const parsedFilters = /* @__PURE__ */ new Map;
5290
5248
 
5291
- async function primeInitialStage({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, clock: clock, executionContext: executionContext, telemetry: telemetry}) {
5292
- const instance = await getInstanceDocument(client, instanceId);
5293
- if (!instance || instance.stages.length > 0) return;
5294
- const definition = parseDefinitionSnapshot(instance), stage = definition.stages.find(s => s.name === instance.currentStage);
5295
- if (stage === void 0) return;
5296
- const ctx = await buildEngineContext({
5297
- client: client,
5298
- clientForGdr: clientForGdr,
5299
- instance: instance,
5300
- definition: definition,
5301
- ...clock ? {
5302
- clock: clock
5303
- } : {},
5304
- ...actor ? {
5305
- actor: actor
5306
- } : {},
5307
- ...executionContext ? {
5308
- executionContext: executionContext
5309
- } : {},
5310
- ...telemetry ? {
5311
- telemetry: telemetry
5249
+ async function matchesFilter(args) {
5250
+ const {document: document, filter: filter, userId: userId} = args;
5251
+ parsedFilters.has(filter) || parsedFilters.set(filter, groqJs.parse(`*[${filter}]`));
5252
+ const ast = parsedFilters.get(filter), data = await (await groqJs.evaluate(ast, {
5253
+ dataset: [ document ],
5254
+ ...userId !== void 0 ? {
5255
+ identity: userId
5312
5256
  } : {}
5313
- }), now = ctx.now, discards = [], initialStageEntry = {
5314
- _key: randomKey(),
5315
- name: stage.name,
5316
- enteredAt: now,
5317
- fields: await resolveStageFieldEntries({
5318
- client: client,
5319
- instance: instance,
5320
- stage: stage,
5321
- now: now,
5322
- recordDiscard: recordFieldDiscards({
5323
- target: discards,
5324
- scope: "stage",
5325
- at: now
5326
- })
5327
- }),
5328
- activities: []
5329
- }, primedCtx = {
5330
- ...ctx,
5331
- instance: {
5332
- ...instance,
5333
- stages: [ initialStageEntry ]
5334
- }
5335
- };
5336
- initialStageEntry.activities = await buildStageActivities(primedCtx, stage);
5337
- const committed = await client.patch(instance._id).set({
5338
- stages: [ initialStageEntry ],
5339
- lastChangedAt: now,
5340
- ...discards.length > 0 ? {
5341
- history: [ ...instance.history, ...stampHistoryEntries(discards, ctx.executionContext) ]
5257
+ })).get();
5258
+ return Array.isArray(data) && data.length === 1;
5259
+ }
5260
+
5261
+ async function grantsPermissionOn(args) {
5262
+ const {document: document, grants: grants, permission: permission, userId: userId} = args;
5263
+ if (!document || grants.length === 0) return !1;
5264
+ for (const grant of grants) if (grant.permissions.includes(permission) && await matchesFilter({
5265
+ document: document,
5266
+ filter: grant.filter,
5267
+ ...userId !== void 0 ? {
5268
+ userId: userId
5269
+ } : {}
5270
+ })) return !0;
5271
+ return !1;
5272
+ }
5273
+
5274
+ function aclPathForResource(res) {
5275
+ if (res.type === "dataset") {
5276
+ const {projectId: projectId, dataset: dataset} = invariants.datasetResourceParts(res.id);
5277
+ return `/projects/${projectId}/datasets/${dataset}/acl`;
5278
+ }
5279
+ if (res.type === "canvas") return `/canvases/${res.id}/acl`;
5280
+ }
5281
+
5282
+ async function fetchGrants(args) {
5283
+ const {client: client, resourcePath: resourcePath, signal: signal} = args;
5284
+ return client.request({
5285
+ url: resourcePath,
5286
+ tag: REQUEST_TAG.accessGrants,
5287
+ ...signal ? {
5288
+ signal: signal
5342
5289
  } : {}
5343
- }).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
5344
- await deployOrRollback({
5345
- client: client,
5346
- instanceId: instance._id,
5347
- committedRev: committed._rev,
5348
- restore: {
5349
- stages: instance.stages,
5350
- history: instance.history
5351
- },
5352
- reversible: !0,
5353
- deploy: () => deployStageGuards({
5354
- client: client,
5355
- clientForGdr: clientForGdr,
5356
- instance: instance,
5357
- definition: definition,
5358
- stageName: stage.name,
5359
- now: now,
5360
- snapshot: ctx.snapshot
5361
- })
5362
- }), await autoActivatePrimedActivities({
5363
- client: client,
5364
- instanceId: instanceId,
5365
- definition: definition,
5366
- stage: stage,
5367
- actor: actor,
5368
- clientForGdr: clientForGdr,
5369
- clock: clock,
5370
- executionContext: executionContext,
5371
- telemetry: telemetry
5372
5290
  });
5373
5291
  }
5374
5292
 
5375
- async function autoActivatePrimedActivities({client: client, instanceId: instanceId, definition: definition, stage: stage, actor: actor, clientForGdr: clientForGdr, clock: clock, executionContext: executionContext, telemetry: telemetry}) {
5376
- for (const activity of stage.activities ?? []) activity.activation === "auto" && await activatePrimedActivity({
5293
+ const actorCache = /* @__PURE__ */ new WeakMap, grantsCache = /* @__PURE__ */ new WeakMap;
5294
+
5295
+ async function resolveAccess(taggedClient, args = {}) {
5296
+ const client = unwrapRequestTag(taggedClient), requestFn = lazyRequest(client);
5297
+ if (requestFn === void 0) throw new invariants.ContractViolationError("workflow: no actor available. The engine resolves the actor from the client's token via `client.request({ uri: '/users/me' })`. Supply a real `@sanity/client` configured with a token (the test bench serves these endpoints per registered token).");
5298
+ const grantsPromise = args.grantsFromPath !== void 0 ? cachedGrants({
5377
5299
  client: client,
5378
- instanceId: instanceId,
5379
- definition: definition,
5380
- activity: activity,
5300
+ requestFn: requestFn,
5301
+ resourcePath: args.grantsFromPath
5302
+ }) : Promise.resolve(void 0), [actor, grants] = await Promise.all([ cachedActor(client, requestFn), grantsPromise ]);
5303
+ if (actor === void 0) throw new invariants.ContractViolationError("workflow: failed to resolve actor from `/users/me`. The client is configured but the endpoint returned no usable identity — check the token.");
5304
+ return {
5381
5305
  actor: actor,
5382
- clientForGdr: clientForGdr,
5383
- clock: clock,
5384
- executionContext: executionContext,
5385
- telemetry: telemetry
5306
+ ...grants !== void 0 ? {
5307
+ grants: grants
5308
+ } : {}
5309
+ };
5310
+ }
5311
+
5312
+ function cachedActor(client, requestFn) {
5313
+ const cached = actorCache.get(client);
5314
+ if (cached !== void 0) return cached;
5315
+ const pending = fetchActor(requestFn).catch(err => {
5316
+ throw actorCache.get(client) === pending && actorCache.delete(client), err;
5386
5317
  });
5318
+ return actorCache.set(client, pending), pending;
5387
5319
  }
5388
5320
 
5389
- async function activatePrimedActivity({client: client, instanceId: instanceId, definition: definition, activity: activity, actor: actor, clientForGdr: clientForGdr, clock: clock, executionContext: executionContext, telemetry: telemetry}) {
5390
- const updated = await getInstanceDocument(client, instanceId);
5391
- if (!updated) return;
5392
- const ctx = await buildEngineContext({
5321
+ function grantsForClientPath(taggedClient, resourcePath) {
5322
+ const client = unwrapRequestTag(taggedClient), requestFn = lazyRequest(client);
5323
+ return requestFn === void 0 ? Promise.resolve(void 0) : cachedGrants({
5393
5324
  client: client,
5394
- clientForGdr: clientForGdr,
5395
- instance: updated,
5396
- definition: definition,
5397
- ...clock ? {
5398
- clock: clock
5399
- } : {},
5400
- ...actor ? {
5401
- actor: actor
5402
- } : {},
5403
- ...executionContext ? {
5404
- executionContext: executionContext
5405
- } : {},
5406
- ...telemetry ? {
5407
- telemetry: telemetry
5408
- } : {}
5409
- }), mutation = startMutation(updated), mutEntry = currentActivities(mutation).find(t => t.name === activity.name);
5410
- mutEntry && mutEntry.status === "pending" && (await activateActivity({
5411
- ctx: ctx,
5412
- mutation: mutation,
5413
- activity: activity,
5414
- entry: mutEntry,
5415
- actor: actor
5416
- }), await persist(ctx, mutation));
5325
+ requestFn: requestFn,
5326
+ resourcePath: resourcePath
5327
+ });
5417
5328
  }
5418
5329
 
5419
- const CASCADE_LIMIT = 100;
5330
+ function lazyRequest(client) {
5331
+ return client.request === void 0 ? void 0 : opts => client.request(opts);
5332
+ }
5420
5333
 
5421
- async function gatherAutoResolveCandidates(ctx, activities) {
5422
- const currentActivitiesList = findCurrentActivities(ctx.instance), candidates = [];
5423
- for (const activity of activities) {
5424
- if (currentActivitiesList.find(e => e.name === activity.name)?.status !== "active") continue;
5425
- const outcome = await evaluateResolutionGate({
5426
- ctx: ctx,
5427
- activity: activity
5334
+ function cachedGrants({client: client, requestFn: requestFn, resourcePath: resourcePath}) {
5335
+ let byPath = grantsCache.get(client);
5336
+ byPath === void 0 && (byPath = /* @__PURE__ */ new Map, grantsCache.set(client, byPath));
5337
+ let cached = byPath.get(resourcePath);
5338
+ return cached === void 0 && (cached = fetchGrantsCached(requestFn, resourcePath),
5339
+ byPath.set(resourcePath, cached)), cached;
5340
+ }
5341
+
5342
+ async function fetchActor(requestFn) {
5343
+ let user;
5344
+ try {
5345
+ user = await requestFn({
5346
+ uri: "/users/me",
5347
+ tag: REQUEST_TAG.accessResolveActor
5428
5348
  });
5429
- outcome !== void 0 && candidates.push({
5430
- activity: activity,
5431
- outcome: outcome
5349
+ } catch (err) {
5350
+ throw new Error('workflow: /users/me request failed. The engine resolves the actor from the client\'s token via `client.request({ uri: "/users/me" })`. Check the token/connectivity.', {
5351
+ cause: err
5432
5352
  });
5433
5353
  }
5434
- return candidates;
5354
+ if (!user || typeof user.id != "string" || user.id.length === 0) return;
5355
+ const roleNames = user.roles?.map(r => r.name).filter(n => !!n) ?? [];
5356
+ return {
5357
+ kind: "person",
5358
+ id: user.id,
5359
+ ...roleNames.length > 0 ? {
5360
+ roles: roleNames
5361
+ } : {}
5362
+ };
5435
5363
  }
5436
5364
 
5437
- async function resolveCompleteWhen({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, clock: clock, executionContext: executionContext, telemetry: telemetry, overlay: overlay}) {
5438
- const ctx = await loadContext({
5439
- client: client,
5440
- instanceId: instanceId,
5441
- options: {
5442
- clientForGdr: clientForGdr,
5443
- ...actor ? {
5444
- actor: actor
5445
- } : {},
5446
- ...clock ? {
5447
- clock: clock
5448
- } : {},
5449
- ...executionContext ? {
5450
- executionContext: executionContext
5451
- } : {},
5452
- ...telemetry ? {
5453
- telemetry: telemetry
5454
- } : {},
5455
- ...overlay ? {
5456
- overlay: overlay
5457
- } : {}
5458
- }
5459
- }), stage = findStage(ctx.definition, ctx.instance.currentStage), gatedActivities = (stage.activities ?? []).filter(t => effectiveCompleteWhen(t) !== void 0 || t.failWhen !== void 0);
5460
- if (gatedActivities.length === 0) return 0;
5461
- const candidates = await gatherAutoResolveCandidates(ctx, gatedActivities);
5462
- if (candidates.length === 0) return 0;
5463
- const mutation = startMutation(ctx.instance);
5464
- let count = 0;
5465
- for (const {activity: activity, outcome: outcome} of candidates) {
5466
- const mutEntry = currentActivities(mutation).find(t => t.name === activity.name);
5467
- mutEntry === void 0 || mutEntry.status !== "active" || (applyGateOutcome({
5468
- entry: mutEntry,
5469
- history: mutation.history,
5470
- stage: stage.name,
5471
- at: ctx.now,
5472
- outcome: outcome,
5473
- actor: actor
5474
- }), count++);
5365
+ async function fetchGrantsCached(requestFn, resourcePath) {
5366
+ try {
5367
+ return await fetchGrants({
5368
+ client: {
5369
+ request: requestFn
5370
+ },
5371
+ resourcePath: resourcePath
5372
+ });
5373
+ } catch (err) {
5374
+ console.warn(`workflow: failed to fetch grants from "${resourcePath}"; advisory permission reads that depend on them are skipped — a rendered $can stays undefined (conditions referencing it fail closed) and the subject-write forecast omits this resource. The lake still enforces writes. Original error: ${invariants.errorMessage(err)}`);
5375
+ return;
5475
5376
  }
5476
- return count > 0 && await persist(ctx, mutation), count;
5477
5377
  }
5478
5378
 
5479
- async function cascadeAutoTransitions({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, clock: clock, executionContext: executionContext, telemetry: telemetry, overlay: overlay}) {
5480
- let count = 0;
5481
- for (;;) {
5482
- if (await drainCondemnedChildren({
5483
- client: client,
5484
- instanceId: instanceId,
5485
- actor: actor,
5486
- clientForGdr: clientForGdr,
5487
- clock: clock,
5488
- executionContext: executionContext,
5489
- telemetry: telemetry
5490
- }), await resolveCompleteWhen({
5491
- client: client,
5492
- instanceId: instanceId,
5493
- actor: actor,
5494
- clientForGdr: clientForGdr,
5495
- clock: clock,
5496
- executionContext: executionContext,
5497
- telemetry: telemetry,
5498
- overlay: overlay
5499
- }), !(await evaluateAutoTransitions({
5500
- client: client,
5501
- instanceId: instanceId,
5502
- actor: actor,
5503
- clientForGdr: clientForGdr,
5504
- ...clock ? {
5505
- clock: clock
5506
- } : {},
5507
- ...executionContext ? {
5508
- executionContext: executionContext
5509
- } : {},
5510
- ...telemetry ? {
5511
- telemetry: telemetry
5512
- } : {},
5513
- ...overlay ? {
5514
- overlay: overlay
5515
- } : {}
5516
- })).fired) return count;
5517
- if (count++, count >= CASCADE_LIMIT) throw new CascadeLimitError({
5518
- instanceId: instanceId,
5519
- limit: CASCADE_LIMIT
5379
+ async function buildFieldInsights({sites: sites, snapshot: snapshot}) {
5380
+ const insights = [];
5381
+ for (const field of fieldsReadAcross(sites)) {
5382
+ const involved = sites.filter(entry => readsField(entry.insight, field));
5383
+ insights.push({
5384
+ field: field,
5385
+ reads: groqConditionDescribe.dedupeReads(involved.flatMap(entry => fieldReads(entry.insight, field))),
5386
+ involvedIn: involved.map(entry => entry.site),
5387
+ proposals: await verifyProposals({
5388
+ field: field,
5389
+ involved: involved,
5390
+ snapshot: snapshot
5391
+ })
5520
5392
  });
5521
5393
  }
5394
+ return insights;
5522
5395
  }
5523
5396
 
5524
- async function drainCondemnedChildren({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, clock: clock, executionContext: executionContext, telemetry: telemetry, draining: draining = /* @__PURE__ */ new Set}) {
5525
- if (draining.has(instanceId)) return;
5526
- draining.add(instanceId);
5527
- const instance = await getInstanceDocument(client, instanceId);
5528
- if (!instance) return;
5529
- const condemned = condemnedSubworkflows(instance);
5530
- if (condemned.length !== 0) {
5531
- for (const row of condemned) await settleCondemnedRow({
5532
- client: client,
5533
- ownerId: instanceId,
5534
- row: row,
5535
- actor: actor,
5536
- clientForGdr: clientForGdr,
5537
- clock: clock,
5538
- executionContext: executionContext,
5539
- telemetry: telemetry,
5540
- draining: draining
5541
- });
5542
- await stampDrainedRows({
5543
- client: client,
5544
- instance: instance,
5545
- condemned: condemned,
5546
- clock: clock,
5547
- executionContext: executionContext
5548
- });
5549
- }
5397
+ function fieldsReadAcross(sites) {
5398
+ const names = sites.flatMap(entry => entry.insight.analysis.reads).filter(read => read.variable === "fields").map(read => read.path[0]).filter(head => typeof head == "string");
5399
+ return [ ...new Set(names) ];
5550
5400
  }
5551
5401
 
5552
- async function settleCondemnedRow({client: client, ownerId: ownerId, row: row, actor: actor, clientForGdr: clientForGdr, clock: clock, executionContext: executionContext, telemetry: telemetry, draining: draining}) {
5553
- const childId = invariants.toBareId(row.ref.id);
5554
- try {
5555
- await abortInstance({
5556
- client: client,
5557
- instanceId: childId,
5558
- reason: row.abortPending?.reason ?? "condemned by parent",
5559
- options: {
5560
- ...actor ? {
5561
- actor: actor
5562
- } : {},
5563
- clientForGdr: clientForGdr,
5564
- ...clock ? {
5565
- clock: clock
5566
- } : {},
5567
- ...executionContext ? {
5568
- executionContext: executionContext
5569
- } : {},
5570
- ...telemetry ? {
5571
- telemetry: telemetry
5572
- } : {}
5573
- }
5574
- }), await drainCondemnedChildren({
5575
- client: client,
5576
- instanceId: childId,
5577
- actor: actor,
5578
- clientForGdr: clientForGdr,
5579
- clock: clock,
5580
- executionContext: executionContext,
5581
- telemetry: telemetry,
5582
- draining: draining
5583
- });
5584
- } catch (cause) {
5585
- if (cause instanceof invariants.InstanceNotFoundError) return;
5586
- throw cause instanceof WorkflowStateDivergedError ? cause : new WorkflowStateDivergedError({
5587
- instanceId: ownerId,
5588
- guardError: cause,
5589
- reason: `owed child abort of "${childId}" failed while draining condemned rows`
5402
+ function fieldReads(insight, field) {
5403
+ return insight.analysis.reads.filter(read => read.variable === "fields" && read.path[0] === field);
5404
+ }
5405
+
5406
+ function readsField(insight, field) {
5407
+ return fieldReads(insight, field).length > 0;
5408
+ }
5409
+
5410
+ async function verifyProposals({field: field, involved: involved, snapshot: snapshot}) {
5411
+ const proposals = [];
5412
+ for (const assign of candidateAssignments(involved, field)) {
5413
+ const consequences = [];
5414
+ for (const entry of involved) {
5415
+ const result = await groqConditionDescribe.whatIfCondition({
5416
+ condition: entry.condition,
5417
+ dataset: snapshot.docs,
5418
+ params: entry.params,
5419
+ assign: assign
5420
+ });
5421
+ result.changed && consequences.push({
5422
+ site: entry.site,
5423
+ before: result.before,
5424
+ after: result.after
5425
+ });
5426
+ }
5427
+ consequences.length > 0 && proposals.push({
5428
+ assign: assign,
5429
+ consequences: consequences
5590
5430
  });
5591
5431
  }
5432
+ return proposals;
5592
5433
  }
5593
5434
 
5594
- async function stampDrainedRows({client: client, instance: instance, condemned: condemned, clock: clock, executionContext: executionContext}) {
5595
- const now = (clock ?? wallClock)(), stamp = resolveExecutionContext(executionContext), ids = condemned.map(row => invariants.toBareId(row.ref.id)), children = await client.fetch("*[_id in $ids]{_id, currentStage, completedAt, abortedAt, modelVersion, minReaderModel}", {
5596
- ids: ids
5597
- }), byId = new Map(children.map(c => [ assertReadableModel(c)._id, c ])), condemnedKeys = new Set(condemned.map(row => row._key)), history = [ ...instance.history ], subworkflows = (instance.subworkflows ?? []).map(row => {
5598
- if (row.resolved !== void 0 || !condemnedKeys.has(row._key)) return row;
5599
- const child = byId.get(invariants.toBareId(row.ref.id)), resolved = child !== void 0 ? terminalResolution(child) : {
5600
- at: now,
5601
- aborted: !0
5602
- };
5603
- return resolved === void 0 ? row : (history.push(...stampHistoryEntries([ subworkflowResolvedEntry({
5604
- row: row,
5605
- at: now,
5606
- status: resolved.aborted === !0 ? "aborted" : "done"
5607
- }) ], stamp)), {
5608
- ...row,
5609
- resolved: resolved
5610
- });
5611
- });
5612
- await client.patch(instance._id).set({
5613
- subworkflows: subworkflows,
5614
- history: history,
5615
- lastChangedAt: now
5616
- }).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
5435
+ function candidateAssignments(involved, field) {
5436
+ const candidates = involved.flatMap(entry => entry.insight.blockedBy).flatMap(atom => atom.requirement !== void 0 ? [ atom.requirement ] : []).filter(requirement => requirement.target.variable === "fields").filter(requirement => requirement.target.path[0] === field).flatMap(assignmentFor).filter(fabricatable);
5437
+ return groqConditionDescribe.dedupeBy(candidates, assignment => JSON.stringify([ assignment.target.path, assignment.value ]));
5617
5438
  }
5618
5439
 
5619
- function terminalResolution(child) {
5620
- const status = resolvedChildStatus(child);
5621
- if (!(status === void 0 || child.completedAt === void 0 || child.completedAt === null)) return {
5622
- at: child.completedAt,
5623
- stage: child.currentStage,
5624
- ...status === "aborted" ? {
5625
- aborted: !0
5626
- } : {}
5627
- };
5440
+ function assignmentFor(requirement) {
5441
+ switch (requirement.kind) {
5442
+ case "equals":
5443
+ return [ {
5444
+ target: requirement.target,
5445
+ value: requirement.value
5446
+ } ];
5447
+
5448
+ case "truthy":
5449
+ return [ {
5450
+ target: requirement.target,
5451
+ value: !0
5452
+ } ];
5453
+
5454
+ case "falsy":
5455
+ return [ {
5456
+ target: requirement.target,
5457
+ value: !1
5458
+ } ];
5459
+
5460
+ case "differs":
5461
+ case "defined":
5462
+ case "undefined":
5463
+ case "compares":
5464
+ return [];
5465
+ }
5628
5466
  }
5629
5467
 
5630
- function subworkflowResolvedEntry({row: row, at: at, status: status}) {
5631
- return {
5632
- _key: randomKey(),
5633
- _type: "subworkflowResolved",
5634
- at: at,
5635
- activity: row.activity,
5636
- instanceRef: row.ref,
5637
- status: status
5638
- };
5468
+ function fabricatable(assignment) {
5469
+ return assignment.target.path.every(segment => typeof segment != "number" || segment <= groqConditionDescribe.MAX_COUNTERFACTUAL_INDEX);
5639
5470
  }
5640
5471
 
5641
- async function propagateToAncestors({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, clock: clock, executionContext: executionContext, telemetry: telemetry}) {
5642
- const loaded = await loadPropagationPair(client, instanceId);
5643
- if (loaded === void 0) return;
5644
- const {child: child, parent: parent} = loaded, definition = parseDefinitionSnapshot(parent), ctx = await buildEngineContext({
5472
+ async function subjectResourceGrants(args) {
5473
+ const {clientForGdr: clientForGdr, instance: instance} = args, clients = /* @__PURE__ */ new Map;
5474
+ for (const {parsed: parsed, resource: resource} of foreignSubjectRefs(instance)) {
5475
+ const key = invariants.resourceGdr(resource);
5476
+ clients.has(key) || clients.set(key, {
5477
+ resource: resource,
5478
+ client: clientForGdr(parsed)
5479
+ });
5480
+ }
5481
+ const resolved = await Promise.all([ ...clients.entries() ].map(async ([key, entry]) => {
5482
+ const path = aclPathForResource(entry.resource);
5483
+ if (path === void 0) return;
5484
+ const grants = await grantsForClientPath(entry.client, path);
5485
+ return grants === void 0 ? void 0 : [ key, grants ];
5486
+ }));
5487
+ return new Map(resolved.filter(entry => entry !== void 0));
5488
+ }
5489
+
5490
+ const UNBOUND_CALLER_VARS = Object.fromEntries(invariants.CALLER_BOUND_VARS.map(name => [ name, void 0 ]));
5491
+
5492
+ async function evaluateInstance(args) {
5493
+ const {client: client, tag: tag, workflowResource: workflowResource, instanceId: instanceId, resourceClients: resourceClients} = args, now = (args.clock ?? wallClock)();
5494
+ invariants.validateTag(tag);
5495
+ const {actor: actor, grants: grants} = await resolveAccess(client, {
5496
+ ...args.grantsFromPath !== void 0 ? {
5497
+ grantsFromPath: args.grantsFromPath
5498
+ } : {}
5499
+ }), instance = await reload({
5500
+ client: client,
5501
+ instanceId: instanceId,
5502
+ tag: tag
5503
+ }), definition = parseDefinitionSnapshot(instance), clientForGdr = buildClientForGdr({
5504
+ client: client,
5505
+ workflowResource: workflowResource,
5506
+ resourceClients: resourceClients
5507
+ }), snapshot = await hydrateSnapshot({
5645
5508
  client: client,
5646
5509
  clientForGdr: clientForGdr,
5647
- instance: parent,
5510
+ instance: instance
5511
+ }), guards = await verdictGuardsForInstance(client, instance._id), resourceGrants = await subjectResourceGrants({
5512
+ clientForGdr: clientForGdr,
5513
+ instance: instance
5514
+ });
5515
+ return evaluateFromSnapshot({
5516
+ instance: instance,
5648
5517
  definition: definition,
5649
- ...clock ? {
5650
- clock: clock
5651
- } : {},
5652
- ...actor ? {
5653
- actor: actor
5654
- } : {},
5655
- ...executionContext ? {
5656
- executionContext: executionContext
5657
- } : {},
5658
- ...telemetry ? {
5659
- telemetry: telemetry
5660
- } : {}
5661
- }), mutation = startMutation(parent), row = mutation.subworkflows.find(r => invariants.toBareId(r.ref.id) === child._id);
5662
- if (row === void 0) {
5663
- await recordOrphanedPropagation({
5664
- ctx: ctx,
5665
- mutation: mutation,
5666
- child: child
5667
- });
5668
- return;
5669
- }
5670
- let changed = stampResolvedChild({
5671
- mutation: mutation,
5672
- row: row,
5673
- child: child,
5674
- now: ctx.now
5675
- });
5676
- const parentTerminal = parent.completedAt !== void 0;
5677
- !parentTerminal && await flipOpenCohortGate({
5678
- ctx: ctx,
5679
- mutation: mutation,
5680
- definition: definition,
5681
- row: row
5682
- }) && (changed = !0), changed && await persist(ctx, mutation), !parentTerminal && (await cascadeAutoTransitions({
5683
- client: client,
5684
- instanceId: parent._id,
5685
- actor: actor,
5686
- clientForGdr: clientForGdr,
5687
- clock: clock,
5688
- executionContext: executionContext,
5689
- telemetry: telemetry
5690
- }), await propagateToAncestors({
5691
- client: client,
5692
- instanceId: parent._id,
5693
- actor: actor,
5694
- clientForGdr: clientForGdr,
5695
- clock: clock,
5696
- executionContext: executionContext,
5697
- telemetry: telemetry
5698
- }));
5699
- }
5700
-
5701
- async function loadPropagationPair(client, instanceId) {
5702
- const child = await getInstanceDocument(client, instanceId);
5703
- if (!child) return;
5704
- const parentGdr = parentRef(child);
5705
- if (parentGdr === void 0) return;
5706
- const parent = await getInstanceDocument(client, invariants.toBareId(parentGdr.id));
5707
- if (!(!parent || parent._type !== WORKFLOW_INSTANCE_TYPE) && typeof parent.definitionSnapshot == "string") return {
5708
- child: child,
5709
- parent: parent
5710
- };
5711
- }
5712
-
5713
- function stampResolvedChild({mutation: mutation, row: row, child: child, now: now}) {
5714
- if (row.resolved !== void 0) return !1;
5715
- const resolved = terminalResolution(child);
5716
- return resolved === void 0 ? !1 : (row.resolved = resolved, mutation.history.push(subworkflowResolvedEntry({
5717
- row: row,
5718
- at: now,
5719
- status: resolved.aborted === !0 ? "aborted" : "done"
5720
- })), !0);
5721
- }
5722
-
5723
- async function flipOpenCohortGate({ctx: ctx, mutation: mutation, definition: definition, row: row}) {
5724
- const openEntry = findOpenStageEntry(mutation);
5725
- if (openEntry === void 0 || row.stageEntry !== openEntry._key) return !1;
5726
- const entry = openEntry.activities.find(e => e.name === row.activity);
5727
- if (entry === void 0 || entry.status !== "active") return !1;
5728
- const activityDef = (definition.stages.find(s => s.name === openEntry.name)?.activities ?? []).find(a => a.name === row.activity);
5729
- if (activityDef?.subworkflows === void 0) return !1;
5730
- const gateCtx = {
5731
- ...ctx,
5732
- instance: materializeInstance(ctx.instance, mutation)
5733
- }, outcome = await evaluateResolutionGate({
5734
- ctx: gateCtx,
5735
- activity: activityDef
5736
- });
5737
- return outcome === void 0 ? !1 : (applyGateOutcome({
5738
- entry: entry,
5739
- history: mutation.history,
5740
- stage: openEntry.name,
5741
- at: ctx.now,
5742
- outcome: outcome,
5743
- ...ctx.actor !== void 0 ? {
5744
- actor: ctx.actor
5745
- } : {}
5746
- }), !0);
5747
- }
5748
-
5749
- async function recordOrphanedPropagation({ctx: ctx, mutation: mutation, child: child}) {
5750
- const childUri = invariants.selfGdr(child);
5751
- mutation.history.some(h => h._type === "subworkflowOrphaned" && h.instanceRef.id === childUri) || (mutation.history.push({
5752
- _key: randomKey(),
5753
- _type: "subworkflowOrphaned",
5754
- at: ctx.now,
5755
- instanceRef: {
5756
- id: childUri,
5757
- type: WORKFLOW_INSTANCE_TYPE
5758
- },
5759
- detail: `Instance "${child._id}" names this instance as its parent but no subworkflow-registry row matches it, so its state cannot drive any gate here.`
5760
- }), await persist(ctx, mutation));
5761
- }
5762
-
5763
- const parsedFilters = /* @__PURE__ */ new Map;
5764
-
5765
- async function matchesFilter(args) {
5766
- const {document: document, filter: filter, userId: userId} = args;
5767
- parsedFilters.has(filter) || parsedFilters.set(filter, groqJs.parse(`*[${filter}]`));
5768
- const ast = parsedFilters.get(filter), data = await (await groqJs.evaluate(ast, {
5769
- dataset: [ document ],
5770
- ...userId !== void 0 ? {
5771
- identity: userId
5772
- } : {}
5773
- })).get();
5774
- return Array.isArray(data) && data.length === 1;
5775
- }
5776
-
5777
- async function grantsPermissionOn(args) {
5778
- const {document: document, grants: grants, permission: permission, userId: userId} = args;
5779
- if (!document || grants.length === 0) return !1;
5780
- for (const grant of grants) if (grant.permissions.includes(permission) && await matchesFilter({
5781
- document: document,
5782
- filter: grant.filter,
5783
- ...userId !== void 0 ? {
5784
- userId: userId
5785
- } : {}
5786
- })) return !0;
5787
- return !1;
5788
- }
5789
-
5790
- function aclPathForResource(res) {
5791
- if (res.type === "dataset") {
5792
- const {projectId: projectId, dataset: dataset} = invariants.datasetResourceParts(res.id);
5793
- return `/projects/${projectId}/datasets/${dataset}/acl`;
5794
- }
5795
- if (res.type === "canvas") return `/canvases/${res.id}/acl`;
5796
- }
5797
-
5798
- async function fetchGrants(args) {
5799
- const {client: client, resourcePath: resourcePath, signal: signal} = args;
5800
- return client.request({
5801
- url: resourcePath,
5802
- tag: REQUEST_TAG.accessGrants,
5803
- ...signal ? {
5804
- signal: signal
5805
- } : {}
5806
- });
5807
- }
5808
-
5809
- const actorCache = /* @__PURE__ */ new WeakMap, grantsCache = /* @__PURE__ */ new WeakMap;
5810
-
5811
- async function resolveAccess(taggedClient, args = {}) {
5812
- const client = unwrapRequestTag(taggedClient), requestFn = lazyRequest(client);
5813
- if (requestFn === void 0) throw new invariants.ContractViolationError("workflow: no actor available. The engine resolves the actor from the client's token via `client.request({ uri: '/users/me' })`. Supply a real `@sanity/client` configured with a token (the test bench serves these endpoints per registered token).");
5814
- const grantsPromise = args.grantsFromPath !== void 0 ? cachedGrants({
5815
- client: client,
5816
- requestFn: requestFn,
5817
- resourcePath: args.grantsFromPath
5818
- }) : Promise.resolve(void 0), [actor, grants] = await Promise.all([ cachedActor(client, requestFn), grantsPromise ]);
5819
- if (actor === void 0) throw new invariants.ContractViolationError("workflow: failed to resolve actor from `/users/me`. The client is configured but the endpoint returned no usable identity — check the token.");
5820
- return {
5821
5518
  actor: actor,
5519
+ snapshot: snapshot,
5520
+ guards: guards,
5521
+ now: now,
5522
+ resourceGrants: resourceGrants,
5822
5523
  ...grants !== void 0 ? {
5823
5524
  grants: grants
5824
5525
  } : {}
5825
- };
5826
- }
5827
-
5828
- function cachedActor(client, requestFn) {
5829
- const cached = actorCache.get(client);
5830
- if (cached !== void 0) return cached;
5831
- const pending = fetchActor(requestFn).catch(err => {
5832
- throw actorCache.get(client) === pending && actorCache.delete(client), err;
5833
- });
5834
- return actorCache.set(client, pending), pending;
5835
- }
5836
-
5837
- function grantsForClientPath(taggedClient, resourcePath) {
5838
- const client = unwrapRequestTag(taggedClient), requestFn = lazyRequest(client);
5839
- return requestFn === void 0 ? Promise.resolve(void 0) : cachedGrants({
5840
- client: client,
5841
- requestFn: requestFn,
5842
- resourcePath: resourcePath
5843
5526
  });
5844
5527
  }
5845
5528
 
5846
- function lazyRequest(client) {
5847
- return client.request === void 0 ? void 0 : opts => client.request(opts);
5848
- }
5849
-
5850
- function cachedGrants({client: client, requestFn: requestFn, resourcePath: resourcePath}) {
5851
- let byPath = grantsCache.get(client);
5852
- byPath === void 0 && (byPath = /* @__PURE__ */ new Map, grantsCache.set(client, byPath));
5853
- let cached = byPath.get(resourcePath);
5854
- return cached === void 0 && (cached = fetchGrantsCached(requestFn, resourcePath),
5855
- byPath.set(resourcePath, cached)), cached;
5856
- }
5857
-
5858
- async function fetchActor(requestFn) {
5859
- let user;
5529
+ function currentStageOf(instance, definition) {
5860
5530
  try {
5861
- user = await requestFn({
5862
- uri: "/users/me",
5863
- tag: REQUEST_TAG.accessResolveActor
5864
- });
5531
+ return findStage(definition, instance.currentStage);
5865
5532
  } catch (err) {
5866
- throw new Error('workflow: /users/me request failed. The engine resolves the actor from the client\'s token via `client.request({ uri: "/users/me" })`. Check the token/connectivity.', {
5533
+ throw new Error(`Instance "${instance._id}" currentStage "${instance.currentStage}" not in definition`, {
5867
5534
  cause: err
5868
5535
  });
5869
5536
  }
5870
- if (!user || typeof user.id != "string" || user.id.length === 0) return;
5871
- const roleNames = user.roles?.map(r => r.name).filter(n => !!n) ?? [];
5872
- return {
5873
- kind: "person",
5874
- id: user.id,
5875
- ...roleNames.length > 0 ? {
5876
- roles: roleNames
5877
- } : {}
5878
- };
5879
- }
5880
-
5881
- async function fetchGrantsCached(requestFn, resourcePath) {
5882
- try {
5883
- return await fetchGrants({
5884
- client: {
5885
- request: requestFn
5886
- },
5887
- resourcePath: resourcePath
5888
- });
5889
- } catch (err) {
5890
- console.warn(`workflow: failed to fetch grants from "${resourcePath}"; advisory permission reads that depend on them are skipped — a rendered $can stays undefined (conditions referencing it fail closed) and the subject-write forecast omits this resource. The lake still enforces writes. Original error: ${invariants.errorMessage(err)}`);
5891
- return;
5892
- }
5893
- }
5894
-
5895
- async function buildFieldInsights({sites: sites, snapshot: snapshot}) {
5896
- const insights = [];
5897
- for (const field of fieldsReadAcross(sites)) {
5898
- const involved = sites.filter(entry => readsField(entry.insight, field));
5899
- insights.push({
5900
- field: field,
5901
- reads: groqConditionDescribe.dedupeReads(involved.flatMap(entry => fieldReads(entry.insight, field))),
5902
- involvedIn: involved.map(entry => entry.site),
5903
- proposals: await verifyProposals({
5904
- field: field,
5905
- involved: involved,
5906
- snapshot: snapshot
5907
- })
5908
- });
5909
- }
5910
- return insights;
5911
- }
5912
-
5913
- function fieldsReadAcross(sites) {
5914
- const names = sites.flatMap(entry => entry.insight.analysis.reads).filter(read => read.variable === "fields").map(read => read.path[0]).filter(head => typeof head == "string");
5915
- return [ ...new Set(names) ];
5916
- }
5917
-
5918
- function fieldReads(insight, field) {
5919
- return insight.analysis.reads.filter(read => read.variable === "fields" && read.path[0] === field);
5920
5537
  }
5921
5538
 
5922
- function readsField(insight, field) {
5923
- return fieldReads(insight, field).length > 0;
5924
- }
5925
-
5926
- async function verifyProposals({field: field, involved: involved, snapshot: snapshot}) {
5927
- const proposals = [];
5928
- for (const assign of candidateAssignments(involved, field)) {
5929
- const consequences = [];
5930
- for (const entry of involved) {
5931
- const result = await groqConditionDescribe.whatIfCondition({
5932
- condition: entry.condition,
5933
- dataset: snapshot.docs,
5934
- params: entry.params,
5935
- assign: assign
5936
- });
5937
- result.changed && consequences.push({
5938
- site: entry.site,
5939
- before: result.before,
5940
- after: result.after
5941
- });
5942
- }
5943
- consequences.length > 0 && proposals.push({
5944
- assign: assign,
5945
- consequences: consequences
5946
- });
5947
- }
5948
- return proposals;
5949
- }
5950
-
5951
- function candidateAssignments(involved, field) {
5952
- const candidates = involved.flatMap(entry => entry.insight.blockedBy).flatMap(atom => atom.requirement !== void 0 ? [ atom.requirement ] : []).filter(requirement => requirement.target.variable === "fields").filter(requirement => requirement.target.path[0] === field).flatMap(assignmentFor).filter(fabricatable);
5953
- return groqConditionDescribe.dedupeBy(candidates, assignment => JSON.stringify([ assignment.target.path, assignment.value ]));
5954
- }
5955
-
5956
- function assignmentFor(requirement) {
5957
- switch (requirement.kind) {
5958
- case "equals":
5959
- return [ {
5960
- target: requirement.target,
5961
- value: requirement.value
5962
- } ];
5963
-
5964
- case "truthy":
5965
- return [ {
5966
- target: requirement.target,
5967
- value: !0
5968
- } ];
5969
-
5970
- case "falsy":
5971
- return [ {
5972
- target: requirement.target,
5973
- value: !1
5974
- } ];
5975
-
5976
- case "differs":
5977
- case "defined":
5978
- case "undefined":
5979
- case "compares":
5980
- return [];
5981
- }
5982
- }
5983
-
5984
- function fabricatable(assignment) {
5985
- return assignment.target.path.every(segment => typeof segment != "number" || segment <= groqConditionDescribe.MAX_COUNTERFACTUAL_INDEX);
5986
- }
5987
-
5988
- async function subjectResourceGrants(args) {
5989
- const {clientForGdr: clientForGdr, instance: instance} = args, clients = /* @__PURE__ */ new Map;
5990
- for (const {parsed: parsed, resource: resource} of foreignSubjectRefs(instance)) {
5991
- const key = invariants.resourceGdr(resource);
5992
- clients.has(key) || clients.set(key, {
5993
- resource: resource,
5994
- client: clientForGdr(parsed)
5995
- });
5996
- }
5997
- const resolved = await Promise.all([ ...clients.entries() ].map(async ([key, entry]) => {
5998
- const path = aclPathForResource(entry.resource);
5999
- if (path === void 0) return;
6000
- const grants = await grantsForClientPath(entry.client, path);
6001
- return grants === void 0 ? void 0 : [ key, grants ];
6002
- }));
6003
- return new Map(resolved.filter(entry => entry !== void 0));
6004
- }
6005
-
6006
- const UNBOUND_CALLER_VARS = Object.fromEntries(invariants.CALLER_BOUND_VARS.map(name => [ name, void 0 ]));
6007
-
6008
- async function evaluateInstance(args) {
6009
- const {client: client, tag: tag, workflowResource: workflowResource, instanceId: instanceId, resourceClients: resourceClients} = args, now = (args.clock ?? wallClock)();
6010
- invariants.validateTag(tag);
6011
- const {actor: actor, grants: grants} = await resolveAccess(client, {
6012
- ...args.grantsFromPath !== void 0 ? {
6013
- grantsFromPath: args.grantsFromPath
6014
- } : {}
6015
- }), instance = await reload({
6016
- client: client,
6017
- instanceId: instanceId,
6018
- tag: tag
6019
- }), definition = parseDefinitionSnapshot(instance), clientForGdr = buildClientForGdr({
6020
- client: client,
6021
- workflowResource: workflowResource,
6022
- resourceClients: resourceClients
6023
- }), snapshot = await hydrateSnapshot({
6024
- client: client,
6025
- clientForGdr: clientForGdr,
6026
- instance: instance
6027
- }), guards = await verdictGuardsForInstance(client, instance._id), resourceGrants = await subjectResourceGrants({
6028
- clientForGdr: clientForGdr,
6029
- instance: instance
6030
- });
6031
- return evaluateFromSnapshot({
6032
- instance: instance,
6033
- definition: definition,
6034
- actor: actor,
6035
- snapshot: snapshot,
6036
- guards: guards,
6037
- now: now,
6038
- resourceGrants: resourceGrants,
6039
- ...grants !== void 0 ? {
6040
- grants: grants
6041
- } : {}
6042
- });
6043
- }
6044
-
6045
- function currentStageOf(instance, definition) {
6046
- try {
6047
- return findStage(definition, instance.currentStage);
6048
- } catch (err) {
6049
- throw new Error(`Instance "${instance._id}" currentStage "${instance.currentStage}" not in definition`, {
6050
- cause: err
6051
- });
6052
- }
6053
- }
6054
-
6055
- async function explainSite(args) {
6056
- const {site: site, condition: condition, params: params, snapshot: snapshot, sites: sites} = args, insight = await groqConditionDescribe.explainCondition({
6057
- condition: condition,
6058
- dataset: snapshot.docs,
6059
- params: params
6060
- });
6061
- return sites.push({
6062
- site: site,
6063
- condition: condition,
6064
- params: params,
6065
- insight: insight
6066
- }), insight;
5539
+ async function explainSite(args) {
5540
+ const {site: site, condition: condition, params: params, snapshot: snapshot, sites: sites} = args, insight = await groqConditionDescribe.explainCondition({
5541
+ condition: condition,
5542
+ dataset: snapshot.docs,
5543
+ params: params
5544
+ });
5545
+ return sites.push({
5546
+ site: site,
5547
+ condition: condition,
5548
+ params: params,
5549
+ insight: insight
5550
+ }), insight;
6067
5551
  }
6068
5552
 
6069
5553
  async function evaluateFromSnapshot(args) {
@@ -6111,14 +5595,14 @@ async function evaluateFromSnapshot(args) {
6111
5595
  kind: "transition",
6112
5596
  transition: transition.name
6113
5597
  },
6114
- condition: transition.filter,
5598
+ condition: transition.when,
6115
5599
  params: cascadeParams,
6116
5600
  snapshot: snapshot,
6117
5601
  sites: sites
6118
5602
  });
6119
5603
  transitionEvaluations.push({
6120
5604
  transition: transition,
6121
- filterSatisfied: insight.outcome === "satisfied",
5605
+ whenSatisfied: insight.outcome === "satisfied",
6122
5606
  unevaluable: insight.outcome === "unevaluable",
6123
5607
  insight: insight
6124
5608
  });
@@ -6322,7 +5806,7 @@ function activityScopeFor(args) {
6322
5806
  }
6323
5807
 
6324
5808
  async function evaluateActivity(args) {
6325
- const {activity: activity, statusEntry: statusEntry, instance: instance, actor: actor, snapshot: snapshot, scope: scope, roleAliases: roleAliases, stageHasExits: stageHasExits, guardDenial: guardDenial, subjectPermissionReason: subjectPermissionReason, sites: sites} = args, status = statusEntry?.status ?? "pending", activityScope = activityScopeFor({
5809
+ const {activity: activity, statusEntry: statusEntry, instance: instance, actor: actor, snapshot: snapshot, scope: scope, roleAliases: roleAliases, stageHasExits: stageHasExits, guardDenial: guardDenial, subjectPermissionReason: subjectPermissionReason, sites: sites} = args, status = statusEntry?.status ?? "skipped", activityScope = activityScopeFor({
6326
5810
  scope: scope,
6327
5811
  instance: instance,
6328
5812
  snapshot: snapshot,
@@ -6354,8 +5838,13 @@ async function evaluateActivity(args) {
6354
5838
  return {
6355
5839
  activity: activity,
6356
5840
  status: status,
6357
- kind: activityKind(activity),
6358
- pendingOnActor: (status === "active" || status === "pending") && assigned,
5841
+ kind: invariants.deriveActivityKind(activity),
5842
+ classification: invariants.deriveExecutorClassification(activity),
5843
+ pendingOnActor: status === "active" && assigned,
5844
+ scopedOut: isFilterScopedOut({
5845
+ status: status,
5846
+ startedAt: statusEntry?.startedAt
5847
+ }),
6359
5848
  ...unmetRequirements.length > 0 ? {
6360
5849
  unmetRequirements: unmetRequirements
6361
5850
  } : {},
@@ -6380,13 +5869,7 @@ async function explainActivityConditions({activity: activity, activityScope: act
6380
5869
  const requirementInsights = Object.fromEntries(requirementEntries), unmetRequirements = requirementEntries.filter(([, insight]) => insight.outcome !== "satisfied").map(([name]) => name), filterInsight = activity.filter !== void 0 ? await explainAt({
6381
5870
  kind: "activity-filter",
6382
5871
  activity: activity.name
6383
- }, activity.filter) : void 0, completeWhenInsight = activity.completeWhen !== void 0 ? await explainAt({
6384
- kind: "complete-when",
6385
- activity: activity.name
6386
- }, activity.completeWhen) : void 0, failWhenInsight = activity.failWhen !== void 0 ? await explainAt({
6387
- kind: "fail-when",
6388
- activity: activity.name
6389
- }, activity.failWhen) : void 0;
5872
+ }, activity.filter) : void 0;
6390
5873
  return {
6391
5874
  unmetRequirements: unmetRequirements,
6392
5875
  ...activity.requirements !== void 0 ? {
@@ -6394,116 +5877,915 @@ async function explainActivityConditions({activity: activity, activityScope: act
6394
5877
  } : {},
6395
5878
  ...filterInsight !== void 0 ? {
6396
5879
  filterInsight: filterInsight
6397
- } : {},
6398
- ...completeWhenInsight !== void 0 ? {
6399
- completeWhenInsight: completeWhenInsight
6400
- } : {},
6401
- ...failWhenInsight !== void 0 ? {
6402
- failWhenInsight: failWhenInsight
6403
5880
  } : {}
6404
5881
  };
6405
5882
  }
6406
5883
 
6407
5884
  async function evaluateAction(args) {
6408
- const {action: action, activityName: activityName, status: status, instance: instance, snapshot: snapshot, activityScope: activityScope, stageHasExits: stageHasExits, guardDenial: guardDenial, subjectPermissionReason: subjectPermissionReason, requirementsReason: requirementsReason, sites: sites} = args, insight = action.filter !== void 0 ? await explainSite({
5885
+ const {action: action, activityName: activityName, snapshot: snapshot, activityScope: activityScope, sites: sites} = args, conditionScope = invariants.isCascadeFired(action) ? {
5886
+ ...activityScope,
5887
+ ...UNBOUND_CALLER_VARS,
5888
+ assigned: !1
5889
+ } : activityScope, insights = await explainActionGates({
5890
+ action: action,
5891
+ activityName: activityName,
5892
+ conditionScope: conditionScope,
5893
+ snapshot: snapshot,
5894
+ sites: sites
5895
+ });
5896
+ return action.when !== void 0 ? triggeredActionVerdict({
5897
+ action: action,
5898
+ when: action.when,
5899
+ insights: insights
5900
+ }) : fireableActionVerdict({
5901
+ args: args,
5902
+ insights: insights
5903
+ });
5904
+ }
5905
+
5906
+ async function explainActionGates({action: action, activityName: activityName, conditionScope: conditionScope, snapshot: snapshot, sites: sites}) {
5907
+ const insight = action.filter !== void 0 ? await explainSite({
6409
5908
  site: {
6410
5909
  kind: "action",
6411
5910
  activity: activityName,
6412
5911
  action: action.name
6413
5912
  },
6414
5913
  condition: action.filter,
6415
- params: activityScope,
5914
+ params: conditionScope,
5915
+ snapshot: snapshot,
5916
+ sites: sites
5917
+ }) : void 0, whenInsight = action.when !== void 0 ? await explainSite({
5918
+ site: {
5919
+ kind: "action-when",
5920
+ activity: activityName,
5921
+ action: action.name
5922
+ },
5923
+ condition: action.when,
5924
+ params: conditionScope,
6416
5925
  snapshot: snapshot,
6417
5926
  sites: sites
6418
- }) : void 0, lifecycle = lifecycleReason({
5927
+ }) : void 0;
5928
+ return {
5929
+ ...insight !== void 0 ? {
5930
+ insight: insight
5931
+ } : {},
5932
+ ...whenInsight !== void 0 ? {
5933
+ whenInsight: whenInsight
5934
+ } : {}
5935
+ };
5936
+ }
5937
+
5938
+ function triggeredActionVerdict({action: action, when: when, insights: insights}) {
5939
+ return insights.insight !== void 0 && insights.insight.outcome === "unsatisfied" ? disabled({
5940
+ action: action,
5941
+ reason: {
5942
+ kind: "filter-failed",
5943
+ filter: action.filter ?? ""
5944
+ },
5945
+ ...insights
5946
+ }) : {
5947
+ action: action,
5948
+ allowed: !1,
5949
+ triggered: !0,
5950
+ disabledReason: {
5951
+ kind: "cascade-fired",
5952
+ when: when
5953
+ },
5954
+ ...insights
5955
+ };
5956
+ }
5957
+
5958
+ function fireableActionVerdict({args: args, insights: insights}) {
5959
+ const {action: action, status: status, instance: instance, stageHasExits: stageHasExits, guardDenial: guardDenial} = args, lifecycle = lifecycleReason({
6419
5960
  instance: instance,
6420
5961
  status: status,
6421
5962
  stageHasExits: stageHasExits
6422
5963
  });
6423
- return lifecycle !== void 0 ? disabled({
5964
+ if (lifecycle !== void 0) return disabled({
6424
5965
  action: action,
6425
5966
  reason: lifecycle,
6426
- insight: insight
6427
- }) : guardDenial !== void 0 ? disabled({
5967
+ ...insights
5968
+ });
5969
+ if (guardDenial !== void 0) return disabled({
6428
5970
  action: action,
6429
5971
  reason: guardDenial,
6430
- insight: insight
6431
- }) : subjectPermissionReason !== void 0 && (action.effects?.length ?? 0) > 0 ? disabled({
5972
+ ...insights
5973
+ });
5974
+ if (args.subjectPermissionReason !== void 0 && (action.effects?.length ?? 0) > 0) return disabled({
6432
5975
  action: action,
6433
- reason: subjectPermissionReason,
6434
- insight: insight
6435
- }) : requirementsReason !== void 0 ? disabled({
5976
+ reason: args.subjectPermissionReason,
5977
+ ...insights
5978
+ });
5979
+ if (args.requirementsReason !== void 0) return disabled({
6436
5980
  action: action,
6437
- reason: requirementsReason,
6438
- insight: insight
6439
- }) : action.filter !== void 0 && insight !== void 0 && insight.outcome !== "satisfied" ? disabled({
5981
+ reason: args.requirementsReason,
5982
+ ...insights
5983
+ });
5984
+ const {insight: insight} = insights;
5985
+ return action.filter !== void 0 && insight !== void 0 && insight.outcome !== "satisfied" ? disabled({
6440
5986
  action: action,
6441
5987
  reason: {
6442
5988
  kind: "filter-failed",
6443
5989
  filter: action.filter
6444
5990
  },
6445
- insight: insight
5991
+ ...insights
6446
5992
  }) : {
6447
5993
  action: action,
6448
5994
  allowed: !0,
6449
- ...insight !== void 0 ? {
6450
- insight: insight
5995
+ ...insights
5996
+ };
5997
+ }
5998
+
5999
+ async function instanceGuardReason({instance: instance, actor: actor, guards: guards}) {
6000
+ if (guards === void 0 || guards.length === 0) return;
6001
+ const denied = await instanceWriteDenials({
6002
+ instance: instance,
6003
+ guards: guards,
6004
+ identity: actor.id
6005
+ });
6006
+ if (denied.length !== 0) return {
6007
+ kind: "mutation-guard-denied",
6008
+ denied: deniedGuardRefs(denied)
6009
+ };
6010
+ }
6011
+
6012
+ function lifecycleReason({instance: instance, status: status, stageHasExits: stageHasExits}) {
6013
+ const terminal = terminalState(instance);
6014
+ if (terminal === "aborted" && instance.abortedAt !== void 0) return {
6015
+ kind: "instance-aborted",
6016
+ abortedAt: instance.abortedAt
6017
+ };
6018
+ if (terminal === "completed" && instance.completedAt !== void 0) return {
6019
+ kind: "instance-completed",
6020
+ completedAt: instance.completedAt
6021
+ };
6022
+ if (!stageHasExits) return {
6023
+ kind: "stage-terminal",
6024
+ stage: instance.currentStage
6025
+ };
6026
+ if (invariants.isTerminalActivityStatus(status)) return {
6027
+ kind: "activity-not-active",
6028
+ status: status
6029
+ };
6030
+ }
6031
+
6032
+ function disabled(args) {
6033
+ return {
6034
+ action: args.action,
6035
+ allowed: !1,
6036
+ disabledReason: args.reason,
6037
+ ...args.insight !== void 0 ? {
6038
+ insight: args.insight
6039
+ } : {},
6040
+ ...args.whenInsight !== void 0 ? {
6041
+ whenInsight: args.whenInsight
6042
+ } : {}
6043
+ };
6044
+ }
6045
+
6046
+ function subjectDenialLabels(denied) {
6047
+ return denied.map(d => `${d.permission} on ${d.subject} (${d.resource})`);
6048
+ }
6049
+
6050
+ class ActionDisabledError extends invariants.WorkflowError {
6051
+ reason;
6052
+ activity;
6053
+ action;
6054
+ constructor(args) {
6055
+ super("action-disabled", formatDisabledReason({
6056
+ activity: args.activity,
6057
+ action: args.action,
6058
+ reason: args.reason
6059
+ })), this.name = "ActionDisabledError", this.reason = args.reason, this.activity = args.activity,
6060
+ this.action = args.action;
6061
+ }
6062
+ }
6063
+
6064
+ function actionRendering(action) {
6065
+ const kind = action.disabledReason?.kind;
6066
+ return kind === "filter-failed" ? "absent" : action.triggered === !0 || kind === "cascade-fired" ? "automation" : "button";
6067
+ }
6068
+
6069
+ const disabledReasonDetail = {
6070
+ "filter-failed": r => `action filter returned false${r.detail ? ` (${r.detail})` : ""}`,
6071
+ "cascade-fired": r => `the action is cascade-fired (when: ${JSON.stringify(r.when)}) — the engine fires it on truth; it cannot be invoked via fireAction`,
6072
+ "activity-not-active": r => `activity status is "${r.status}"`,
6073
+ "stage-terminal": r => `stage "${r.stage}" is terminal`,
6074
+ "instance-completed": r => `instance completed at ${r.completedAt}`,
6075
+ "instance-aborted": r => `instance aborted at ${r.abortedAt}`,
6076
+ "requirements-unmet": r => `unmet requirement(s): ${r.unmetRequirements.join(", ")}`,
6077
+ "subject-permission-denied": r => `missing subject permission(s): ${subjectDenialLabels(r.denied).join(", ")}`
6078
+ };
6079
+
6080
+ function actionDisabledDetail(reason) {
6081
+ return disabledReasonDetail[reason.kind](reason);
6082
+ }
6083
+
6084
+ function formatDisabledReason({activity: activity, action: action, reason: reason}) {
6085
+ return `Action "${activity}:${action}" is not allowed: ${actionDisabledDetail(reason)}`;
6086
+ }
6087
+
6088
+ class EditFieldDeniedError extends invariants.WorkflowError {
6089
+ reason;
6090
+ target;
6091
+ constructor(args) {
6092
+ super("edit-field-denied", formatEditDisabledReason(args.target, args.reason)),
6093
+ this.name = "EditFieldDeniedError", this.reason = args.reason, this.target = args.target;
6094
+ }
6095
+ }
6096
+
6097
+ const editDisabledReasonDetail = {
6098
+ "not-editable": () => "field is not declared editable",
6099
+ "instance-completed": r => `instance completed at ${r.completedAt}`,
6100
+ "instance-aborted": r => `instance aborted at ${r.abortedAt}`,
6101
+ "edit-window-closed": r => `edit window closed (${r.detail})`,
6102
+ "editor-not-permitted": r => `editor not permitted (${r.predicate})`
6103
+ };
6104
+
6105
+ function formatEditDisabledReason(target, reason) {
6106
+ const detail = editDisabledReasonDetail[reason.kind](reason), where = target.activity !== void 0 ? `${target.activity}.${target.field}` : target.field;
6107
+ return `Field "${target.scope}:${where}" is not editable: ${detail}`;
6108
+ }
6109
+
6110
+ async function fireAction(args) {
6111
+ const {client: client, instanceId: instanceId, activity: activity, action: action, params: params, requestRecord: requestRecord, options: options} = args;
6112
+ return retryOnRevisionConflict({
6113
+ client: client,
6114
+ instanceId: instanceId,
6115
+ options: options,
6116
+ commit: ctx => commitAction({
6117
+ ctx: ctx,
6118
+ activityName: activity,
6119
+ actionName: action,
6120
+ callerParams: params,
6121
+ requestRecord: requestRecord,
6122
+ options: options
6123
+ }),
6124
+ onExhausted: () => new ConcurrentFireActionError({
6125
+ instanceId: instanceId,
6126
+ activity: activity,
6127
+ action: action,
6128
+ attempts: CONCURRENT_COMMIT_MAX_ATTEMPTS
6129
+ })
6130
+ });
6131
+ }
6132
+
6133
+ async function resolveActionCommit({ctx: ctx, activityName: activityName, actionName: actionName, callerParams: callerParams, options: options}) {
6134
+ const actor = options?.actor, {stage: stage, activity: activity} = findActivityInCurrentStage(ctx, activityName), action = (activity.actions ?? []).find(a => a.name === actionName);
6135
+ if (action === void 0) throw new invariants.ContractViolationError(`Action "${actionName}" not declared on activity "${activityName}"`);
6136
+ if (action.when !== void 0) throw new ActionDisabledError({
6137
+ activity: activityName,
6138
+ action: actionName,
6139
+ reason: {
6140
+ kind: "cascade-fired",
6141
+ when: action.when
6142
+ }
6143
+ });
6144
+ if (action.filter !== void 0) {
6145
+ const can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan({
6146
+ instance: ctx.instance,
6147
+ actor: actor,
6148
+ grants: options.grants
6149
+ }) : void 0;
6150
+ if (!await ctxEvaluateCondition({
6151
+ ctx: ctx,
6152
+ condition: action.filter,
6153
+ opts: {
6154
+ activityName: activityName,
6155
+ ...actor !== void 0 ? {
6156
+ actor: actor
6157
+ } : {},
6158
+ ...can !== void 0 ? {
6159
+ vars: {
6160
+ can: can
6161
+ }
6162
+ } : {}
6163
+ }
6164
+ })) throw new ActionDisabledError({
6165
+ activity: activityName,
6166
+ action: actionName,
6167
+ reason: {
6168
+ kind: "filter-failed",
6169
+ filter: action.filter,
6170
+ detail: "commit re-check"
6171
+ }
6172
+ });
6173
+ }
6174
+ const entry = findCurrentActivities(ctx.instance).find(t => t.name === activityName);
6175
+ if (entry === void 0 || entry.status !== "active") throw notActiveAtCommit({
6176
+ activityName: activityName,
6177
+ actionName: actionName,
6178
+ status: entry?.status
6179
+ });
6180
+ const params = validateActionParams({
6181
+ action: action,
6182
+ activityName: activityName,
6183
+ callerParams: callerParams
6184
+ });
6185
+ return {
6186
+ stage: stage,
6187
+ activity: activity,
6188
+ action: action,
6189
+ params: params
6190
+ };
6191
+ }
6192
+
6193
+ function notActiveAtCommit(args) {
6194
+ const {activityName: activityName, actionName: actionName, status: status} = args;
6195
+ return status !== void 0 && invariants.isTerminalActivityStatus(status) ? new ActionDisabledError({
6196
+ activity: activityName,
6197
+ action: actionName,
6198
+ reason: {
6199
+ kind: "activity-not-active",
6200
+ status: status
6201
+ }
6202
+ }) : new Error(`Activity "${activityName}" must be active to fire action "${actionName}"; status is ${status ?? "missing"}`);
6203
+ }
6204
+
6205
+ async function applyActionFire({ctx: ctx, mutation: mutation, activity: activity, action: action, params: params, actor: actor, triggered: triggered}) {
6206
+ mutation.history.push({
6207
+ _key: randomKey(),
6208
+ _type: "actionFired",
6209
+ at: ctx.now,
6210
+ stage: mutation.currentStage,
6211
+ activity: activity.name,
6212
+ action: action.name,
6213
+ ...actor !== void 0 ? {
6214
+ actor: actor,
6215
+ driverKind: invariants.driverKind(actor)
6216
+ } : {},
6217
+ ...triggered ? {
6218
+ triggered: !0
6219
+ } : {}
6220
+ });
6221
+ const ranOps = await runOps({
6222
+ ops: action.ops,
6223
+ mutation: mutation,
6224
+ stage: mutation.currentStage,
6225
+ origin: {
6226
+ activity: activity.name,
6227
+ action: action.name
6228
+ },
6229
+ params: params,
6230
+ actor: actor,
6231
+ self: invariants.selfGdr(ctx.instance),
6232
+ now: ctx.now,
6233
+ snapshot: ctx.snapshot
6234
+ });
6235
+ return await queueEffects({
6236
+ ctx: ctx,
6237
+ mutation: mutation,
6238
+ effects: action.effects,
6239
+ origin: {
6240
+ kind: "action",
6241
+ name: action.name
6242
+ },
6243
+ actor: actor,
6244
+ opts: {
6245
+ callerParams: params,
6246
+ activityName: activity.name
6247
+ }
6248
+ }), action.spawn !== void 0 && await spawnSubworkflows({
6249
+ ctx: ctx,
6250
+ mutation: mutation,
6251
+ activity: activity,
6252
+ action: action,
6253
+ sub: action.spawn,
6254
+ actor: actor
6255
+ }), {
6256
+ ranOps: ranOps
6257
+ };
6258
+ }
6259
+
6260
+ async function commitAction({ctx: ctx, activityName: activityName, actionName: actionName, callerParams: callerParams, requestRecord: requestRecord, options: options}) {
6261
+ assertRequestUnprocessed({
6262
+ instance: ctx.instance,
6263
+ record: requestRecord,
6264
+ now: ctx.now
6265
+ });
6266
+ const actor = options?.actor, {stage: stage, activity: activity, action: action, params: params} = await resolveActionCommit({
6267
+ ctx: ctx,
6268
+ activityName: activityName,
6269
+ actionName: actionName,
6270
+ callerParams: callerParams,
6271
+ options: options
6272
+ }), mutation = startMutation(ctx.instance);
6273
+ recordProcessedRequest({
6274
+ mutation: mutation,
6275
+ record: requestRecord,
6276
+ now: ctx.now
6277
+ });
6278
+ const mutEntry = requireMutationActivityEntry(mutation, activityName), statusBefore = mutEntry.status, {ranOps: ranOps} = await applyActionFire({
6279
+ ctx: ctx,
6280
+ mutation: mutation,
6281
+ activity: activity,
6282
+ action: action,
6283
+ params: params,
6284
+ actor: actor,
6285
+ triggered: !1
6286
+ }), newStatus = mutEntry.status !== statusBefore ? mutEntry.status : void 0;
6287
+ return await persistThenMaybeRefresh({
6288
+ ctx: ctx,
6289
+ mutation: mutation,
6290
+ stageName: stage.name,
6291
+ didChangeState: ranOps.some(isFieldOp)
6292
+ }), {
6293
+ fired: !0,
6294
+ activity: activityName,
6295
+ action: actionName,
6296
+ ...newStatus !== void 0 ? {
6297
+ newStatus: newStatus
6298
+ } : {},
6299
+ ...ranOps.length > 0 ? {
6300
+ ranOps: ranOps
6301
+ } : {}
6302
+ };
6303
+ }
6304
+
6305
+ async function runTriggeredActions({ctx: ctx, mutation: mutation, stage: stage}) {
6306
+ let fires = 0, ranFieldOps = !1;
6307
+ const liveCtx = liveViewContext(ctx, mutation);
6308
+ let firedThisPass = !0;
6309
+ for (;firedThisPass; ) {
6310
+ firedThisPass = !1;
6311
+ for (const activity of stage.activities ?? []) {
6312
+ const fired = await runActivityTriggers({
6313
+ ctx: liveCtx,
6314
+ mutation: mutation,
6315
+ activity: activity
6316
+ });
6317
+ fires += fired.fires, ranFieldOps = ranFieldOps || fired.ranFieldOps, fired.fires > 0 && (firedThisPass = !0);
6318
+ }
6319
+ }
6320
+ return {
6321
+ fires: fires,
6322
+ ranFieldOps: ranFieldOps
6323
+ };
6324
+ }
6325
+
6326
+ async function runActivityTriggers({ctx: ctx, mutation: mutation, activity: activity}) {
6327
+ let fires = 0, ranFieldOps = !1;
6328
+ for (const action of activity.actions ?? []) {
6329
+ if (!invariants.isCascadeFired(action)) continue;
6330
+ const entry = currentActivities(mutation).find(e => e.name === activity.name);
6331
+ if (entry === void 0 || entry.status !== "active") break;
6332
+ if ((entry.firedActions ?? []).includes(action.name) || !await triggerIsLive({
6333
+ ctx: ctx,
6334
+ activity: activity,
6335
+ action: action
6336
+ })) continue;
6337
+ const result = await fireTriggeredAction({
6338
+ ctx: ctx,
6339
+ mutation: mutation,
6340
+ activity: activity,
6341
+ action: action,
6342
+ entry: entry
6343
+ });
6344
+ if (fires++, ranFieldOps = ranFieldOps || result.ranFieldOps, invariants.isTerminalActivityStatus(entry.status)) break;
6345
+ }
6346
+ return {
6347
+ fires: fires,
6348
+ ranFieldOps: ranFieldOps
6349
+ };
6350
+ }
6351
+
6352
+ async function triggerIsLive({ctx: ctx, activity: activity, action: action}) {
6353
+ if (!tokenMayExecute({
6354
+ actor: ctx.actor,
6355
+ action: action,
6356
+ ctx: ctx
6357
+ })) return !1;
6358
+ const scope = {
6359
+ activityName: activity.name
6360
+ };
6361
+ return action.filter !== void 0 && await ctxEvaluateConditionOutcome({
6362
+ ctx: ctx,
6363
+ condition: action.filter,
6364
+ opts: scope
6365
+ }) !== "satisfied" ? !1 : await ctxEvaluateConditionOutcome({
6366
+ ctx: ctx,
6367
+ condition: action.when,
6368
+ opts: scope
6369
+ }) === "satisfied";
6370
+ }
6371
+
6372
+ function tokenMayExecute({actor: actor, action: action, ctx: ctx}) {
6373
+ const roles = action.roles;
6374
+ return roles === void 0 || roles.length === 0 ? !0 : actor === void 0 ? !1 : roles.some(required => invariants.actorFulfillsRole({
6375
+ actorRoles: actor.roles,
6376
+ required: required,
6377
+ aliases: ctx.definition.roleAliases
6378
+ }));
6379
+ }
6380
+
6381
+ async function fireTriggeredAction({ctx: ctx, mutation: mutation, activity: activity, action: action, entry: entry}) {
6382
+ entry.firedActions = [ ...entry.firedActions ?? [], action.name ];
6383
+ const {ranOps: ranOps} = await applyActionFire({
6384
+ ctx: ctx,
6385
+ mutation: mutation,
6386
+ activity: activity,
6387
+ action: action,
6388
+ params: {},
6389
+ actor: ctx.actor,
6390
+ triggered: !0
6391
+ });
6392
+ return {
6393
+ ranFieldOps: ranOps.some(isFieldOp)
6394
+ };
6395
+ }
6396
+
6397
+ async function primeInitialStage({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, clock: clock, executionContext: executionContext, telemetry: telemetry}) {
6398
+ const instance = await getInstanceDocument(client, instanceId);
6399
+ if (!instance || instance.stages.length > 0) return;
6400
+ const definition = parseDefinitionSnapshot(instance), stage = definition.stages.find(s => s.name === instance.currentStage);
6401
+ if (stage === void 0) return;
6402
+ const ctx = await buildEngineContext({
6403
+ client: client,
6404
+ clientForGdr: clientForGdr,
6405
+ instance: instance,
6406
+ definition: definition,
6407
+ ...clock ? {
6408
+ clock: clock
6409
+ } : {},
6410
+ ...actor ? {
6411
+ actor: actor
6412
+ } : {},
6413
+ ...executionContext ? {
6414
+ executionContext: executionContext
6415
+ } : {},
6416
+ ...telemetry ? {
6417
+ telemetry: telemetry
6418
+ } : {}
6419
+ }), now = ctx.now, discards = [], initialStageEntry = {
6420
+ _key: randomKey(),
6421
+ name: stage.name,
6422
+ enteredAt: now,
6423
+ fields: await resolveStageFieldEntries({
6424
+ client: client,
6425
+ instance: instance,
6426
+ stage: stage,
6427
+ now: now,
6428
+ recordDiscard: recordFieldDiscards({
6429
+ target: discards,
6430
+ scope: "stage",
6431
+ at: now
6432
+ })
6433
+ }),
6434
+ activities: []
6435
+ }, primedCtx = {
6436
+ ...ctx,
6437
+ instance: {
6438
+ ...instance,
6439
+ stages: [ initialStageEntry ]
6440
+ }
6441
+ };
6442
+ initialStageEntry.activities = await buildStageActivities({
6443
+ ctx: primedCtx,
6444
+ stage: stage,
6445
+ recordDiscard: recordFieldDiscards({
6446
+ target: discards,
6447
+ scope: "activity",
6448
+ at: now
6449
+ })
6450
+ });
6451
+ const terminal = isTerminalStage(stage), committed = await client.patch(instance._id).set({
6452
+ stages: [ initialStageEntry ],
6453
+ lastChangedAt: now,
6454
+ ...terminal ? {
6455
+ completedAt: now
6456
+ } : {},
6457
+ ...discards.length > 0 ? {
6458
+ history: [ ...instance.history, ...stampHistoryEntries(discards, ctx.executionContext) ]
6459
+ } : {}
6460
+ }).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
6461
+ await deployOrRollback({
6462
+ client: client,
6463
+ instanceId: instance._id,
6464
+ committedRev: committed._rev,
6465
+ restore: {
6466
+ stages: instance.stages,
6467
+ history: instance.history
6468
+ },
6469
+ ...terminal ? {
6470
+ unset: [ "completedAt" ]
6471
+ } : {},
6472
+ reversible: !0,
6473
+ deploy: () => deployStageGuards({
6474
+ client: client,
6475
+ clientForGdr: clientForGdr,
6476
+ instance: instance,
6477
+ definition: definition,
6478
+ stageName: stage.name,
6479
+ now: now,
6480
+ snapshot: ctx.snapshot
6481
+ })
6482
+ });
6483
+ }
6484
+
6485
+ const CASCADE_LIMIT = 100;
6486
+
6487
+ async function runCascadeHop({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, clock: clock, executionContext: executionContext, telemetry: telemetry, overlay: overlay}) {
6488
+ const ctx = await loadContext({
6489
+ client: client,
6490
+ instanceId: instanceId,
6491
+ options: {
6492
+ clientForGdr: clientForGdr,
6493
+ ...actor ? {
6494
+ actor: actor
6495
+ } : {},
6496
+ ...clock ? {
6497
+ clock: clock
6498
+ } : {},
6499
+ ...executionContext ? {
6500
+ executionContext: executionContext
6501
+ } : {},
6502
+ ...telemetry ? {
6503
+ telemetry: telemetry
6504
+ } : {},
6505
+ ...overlay ? {
6506
+ overlay: overlay
6507
+ } : {}
6508
+ }
6509
+ });
6510
+ if (isTerminal(ctx)) return {
6511
+ moved: !1
6512
+ };
6513
+ const stage = findStage(ctx.definition, ctx.instance.currentStage), mutation = startMutation(ctx.instance), fired = await runTriggeredActions({
6514
+ ctx: ctx,
6515
+ mutation: mutation,
6516
+ stage: stage
6517
+ }), hopCtx = liveViewContext(ctx, mutation), transition = await pickTransition(hopCtx, stage);
6518
+ return transition === void 0 ? (fired.fires > 0 && await persistThenMaybeRefresh({
6519
+ ctx: ctx,
6520
+ mutation: mutation,
6521
+ stageName: stage.name,
6522
+ didChangeState: fired.ranFieldOps
6523
+ }), {
6524
+ moved: !1
6525
+ }) : (await commitStageMove({
6526
+ ctx: ctx,
6527
+ mutation: mutation,
6528
+ fromStage: stage,
6529
+ toStage: findStage(ctx.definition, transition.to),
6530
+ transition: transition.name,
6531
+ via: "transition",
6532
+ actor: actor
6533
+ }), {
6534
+ moved: !0
6535
+ });
6536
+ }
6537
+
6538
+ async function cascadeAutoTransitions({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, clock: clock, executionContext: executionContext, telemetry: telemetry, overlay: overlay}) {
6539
+ let count = 0;
6540
+ for (;;) {
6541
+ if (await drainCondemnedChildren({
6542
+ client: client,
6543
+ instanceId: instanceId,
6544
+ actor: actor,
6545
+ clientForGdr: clientForGdr,
6546
+ clock: clock,
6547
+ executionContext: executionContext,
6548
+ telemetry: telemetry
6549
+ }), !(await runCascadeHop({
6550
+ client: client,
6551
+ instanceId: instanceId,
6552
+ actor: actor,
6553
+ clientForGdr: clientForGdr,
6554
+ clock: clock,
6555
+ executionContext: executionContext,
6556
+ telemetry: telemetry,
6557
+ overlay: overlay
6558
+ })).moved) return count;
6559
+ if (count++, count >= CASCADE_LIMIT) throw new CascadeLimitError({
6560
+ instanceId: instanceId,
6561
+ limit: CASCADE_LIMIT
6562
+ });
6563
+ }
6564
+ }
6565
+
6566
+ async function drainCondemnedChildren({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, clock: clock, executionContext: executionContext, telemetry: telemetry, draining: draining = /* @__PURE__ */ new Set}) {
6567
+ if (draining.has(instanceId)) return;
6568
+ draining.add(instanceId);
6569
+ const instance = await getInstanceDocument(client, instanceId);
6570
+ if (!instance) return;
6571
+ const condemned = condemnedSubworkflows(instance);
6572
+ if (condemned.length !== 0) {
6573
+ for (const row of condemned) await settleCondemnedRow({
6574
+ client: client,
6575
+ ownerId: instanceId,
6576
+ row: row,
6577
+ actor: actor,
6578
+ clientForGdr: clientForGdr,
6579
+ clock: clock,
6580
+ executionContext: executionContext,
6581
+ telemetry: telemetry,
6582
+ draining: draining
6583
+ });
6584
+ await stampDrainedRows({
6585
+ client: client,
6586
+ instance: instance,
6587
+ condemned: condemned,
6588
+ clock: clock,
6589
+ executionContext: executionContext
6590
+ });
6591
+ }
6592
+ }
6593
+
6594
+ async function settleCondemnedRow({client: client, ownerId: ownerId, row: row, actor: actor, clientForGdr: clientForGdr, clock: clock, executionContext: executionContext, telemetry: telemetry, draining: draining}) {
6595
+ const childId = invariants.toBareId(row.ref.id);
6596
+ try {
6597
+ await abortInstance({
6598
+ client: client,
6599
+ instanceId: childId,
6600
+ reason: row.abortPending?.reason ?? "condemned by parent",
6601
+ options: {
6602
+ ...actor ? {
6603
+ actor: actor
6604
+ } : {},
6605
+ clientForGdr: clientForGdr,
6606
+ ...clock ? {
6607
+ clock: clock
6608
+ } : {},
6609
+ ...executionContext ? {
6610
+ executionContext: executionContext
6611
+ } : {},
6612
+ ...telemetry ? {
6613
+ telemetry: telemetry
6614
+ } : {}
6615
+ }
6616
+ }), await drainCondemnedChildren({
6617
+ client: client,
6618
+ instanceId: childId,
6619
+ actor: actor,
6620
+ clientForGdr: clientForGdr,
6621
+ clock: clock,
6622
+ executionContext: executionContext,
6623
+ telemetry: telemetry,
6624
+ draining: draining
6625
+ });
6626
+ } catch (cause) {
6627
+ if (cause instanceof invariants.InstanceNotFoundError) return;
6628
+ throw cause instanceof WorkflowStateDivergedError ? cause : new WorkflowStateDivergedError({
6629
+ instanceId: ownerId,
6630
+ guardError: cause,
6631
+ reason: `owed child abort of "${childId}" failed while draining condemned rows`
6632
+ });
6633
+ }
6634
+ }
6635
+
6636
+ async function stampDrainedRows({client: client, instance: instance, condemned: condemned, clock: clock, executionContext: executionContext}) {
6637
+ const now = (clock ?? wallClock)(), stamp = resolveExecutionContext(executionContext), ids = condemned.map(row => invariants.toBareId(row.ref.id)), children = await client.fetch("*[_id in $ids]{_id, currentStage, completedAt, abortedAt, modelVersion, minReaderModel}", {
6638
+ ids: ids
6639
+ }), byId = new Map(children.map(c => [ assertReadableModel(c)._id, c ])), condemnedKeys = new Set(condemned.map(row => row._key)), history = [ ...instance.history ], subworkflows = (instance.subworkflows ?? []).map(row => {
6640
+ if (row.resolved !== void 0 || !condemnedKeys.has(row._key)) return row;
6641
+ const child = byId.get(invariants.toBareId(row.ref.id)), resolved = child !== void 0 ? terminalResolution(child) : {
6642
+ at: now,
6643
+ aborted: !0
6644
+ };
6645
+ return resolved === void 0 ? row : (history.push(...stampHistoryEntries([ subworkflowResolvedEntry({
6646
+ row: row,
6647
+ at: now,
6648
+ status: resolved.aborted === !0 ? "aborted" : "done"
6649
+ }) ], stamp)), {
6650
+ ...row,
6651
+ resolved: resolved
6652
+ });
6653
+ });
6654
+ await client.patch(instance._id).set({
6655
+ subworkflows: subworkflows,
6656
+ history: history,
6657
+ lastChangedAt: now
6658
+ }).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
6659
+ }
6660
+
6661
+ function terminalResolution(child) {
6662
+ const status = resolvedChildStatus(child);
6663
+ if (!(status === void 0 || child.completedAt === void 0 || child.completedAt === null)) return {
6664
+ at: child.completedAt,
6665
+ stage: child.currentStage,
6666
+ ...status === "aborted" ? {
6667
+ aborted: !0
6451
6668
  } : {}
6452
6669
  };
6453
6670
  }
6454
6671
 
6455
- async function instanceGuardReason({instance: instance, actor: actor, guards: guards}) {
6456
- if (guards === void 0 || guards.length === 0) return;
6457
- const denied = await instanceWriteDenials({
6458
- instance: instance,
6459
- guards: guards,
6460
- identity: actor.id
6461
- });
6462
- if (denied.length !== 0) return {
6463
- kind: "mutation-guard-denied",
6464
- denied: deniedGuardRefs(denied)
6465
- };
6466
- }
6467
-
6468
- function lifecycleReason({instance: instance, status: status, stageHasExits: stageHasExits}) {
6469
- const terminal = terminalState(instance);
6470
- if (terminal === "aborted" && instance.abortedAt !== void 0) return {
6471
- kind: "instance-aborted",
6472
- abortedAt: instance.abortedAt
6473
- };
6474
- if (terminal === "completed" && instance.completedAt !== void 0) return {
6475
- kind: "instance-completed",
6476
- completedAt: instance.completedAt
6477
- };
6478
- if (!stageHasExits) return {
6479
- kind: "stage-terminal",
6480
- stage: instance.currentStage
6481
- };
6482
- if (invariants.isTerminalActivityStatus(status)) return {
6483
- kind: "activity-not-active",
6672
+ function subworkflowResolvedEntry({row: row, at: at, status: status}) {
6673
+ return {
6674
+ _key: randomKey(),
6675
+ _type: "subworkflowResolved",
6676
+ at: at,
6677
+ activity: row.activity,
6678
+ instanceRef: row.ref,
6484
6679
  status: status
6485
6680
  };
6486
6681
  }
6487
6682
 
6488
- function disabled(args) {
6489
- return {
6490
- action: args.action,
6491
- allowed: !1,
6492
- disabledReason: args.reason,
6493
- ...args.insight !== void 0 ? {
6494
- insight: args.insight
6683
+ async function propagateToAncestors({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, clock: clock, executionContext: executionContext, telemetry: telemetry}) {
6684
+ const loaded = await loadPropagationPair(client, instanceId);
6685
+ if (loaded === void 0) return;
6686
+ const {child: child, parent: parent} = loaded, definition = parseDefinitionSnapshot(parent), ctx = await buildEngineContext({
6687
+ client: client,
6688
+ clientForGdr: clientForGdr,
6689
+ instance: parent,
6690
+ definition: definition,
6691
+ ...clock ? {
6692
+ clock: clock
6693
+ } : {},
6694
+ ...actor ? {
6695
+ actor: actor
6696
+ } : {},
6697
+ ...executionContext ? {
6698
+ executionContext: executionContext
6699
+ } : {},
6700
+ ...telemetry ? {
6701
+ telemetry: telemetry
6495
6702
  } : {}
6703
+ }), mutation = startMutation(parent), row = mutation.subworkflows.find(r => invariants.toBareId(r.ref.id) === child._id);
6704
+ if (row === void 0) {
6705
+ await recordOrphanedPropagation({
6706
+ ctx: ctx,
6707
+ mutation: mutation,
6708
+ child: child
6709
+ });
6710
+ return;
6711
+ }
6712
+ const changed = stampResolvedChild({
6713
+ mutation: mutation,
6714
+ row: row,
6715
+ child: child,
6716
+ now: ctx.now
6717
+ }), parentTerminal = parent.completedAt !== void 0;
6718
+ changed && await persist(ctx, mutation), !parentTerminal && (await cascadeAutoTransitions({
6719
+ client: client,
6720
+ instanceId: parent._id,
6721
+ actor: actor,
6722
+ clientForGdr: clientForGdr,
6723
+ clock: clock,
6724
+ executionContext: executionContext,
6725
+ telemetry: telemetry
6726
+ }), await propagateToAncestors({
6727
+ client: client,
6728
+ instanceId: parent._id,
6729
+ actor: actor,
6730
+ clientForGdr: clientForGdr,
6731
+ clock: clock,
6732
+ executionContext: executionContext,
6733
+ telemetry: telemetry
6734
+ }));
6735
+ }
6736
+
6737
+ async function loadPropagationPair(client, instanceId) {
6738
+ const child = await getInstanceDocument(client, instanceId);
6739
+ if (!child) return;
6740
+ const parentGdr = parentRef(child);
6741
+ if (parentGdr === void 0) return;
6742
+ const parent = await getInstanceDocument(client, invariants.toBareId(parentGdr.id));
6743
+ if (!(!parent || parent._type !== WORKFLOW_INSTANCE_TYPE) && typeof parent.definitionSnapshot == "string") return {
6744
+ child: child,
6745
+ parent: parent
6496
6746
  };
6497
6747
  }
6498
6748
 
6749
+ function stampResolvedChild({mutation: mutation, row: row, child: child, now: now}) {
6750
+ if (row.resolved !== void 0) return !1;
6751
+ const resolved = terminalResolution(child);
6752
+ return resolved === void 0 ? !1 : (row.resolved = resolved, mutation.history.push(subworkflowResolvedEntry({
6753
+ row: row,
6754
+ at: now,
6755
+ status: resolved.aborted === !0 ? "aborted" : "done"
6756
+ })), !0);
6757
+ }
6758
+
6759
+ async function recordOrphanedPropagation({ctx: ctx, mutation: mutation, child: child}) {
6760
+ const childUri = invariants.selfGdr(child);
6761
+ mutation.history.some(h => h._type === "subworkflowOrphaned" && h.instanceRef.id === childUri) || (mutation.history.push({
6762
+ _key: randomKey(),
6763
+ _type: "subworkflowOrphaned",
6764
+ at: ctx.now,
6765
+ instanceRef: {
6766
+ id: childUri,
6767
+ type: WORKFLOW_INSTANCE_TYPE
6768
+ },
6769
+ detail: `Instance "${child._id}" names this instance as its parent but no subworkflow-registry row matches it, so its state cannot drive any gate here.`
6770
+ }), await persist(ctx, mutation));
6771
+ }
6772
+
6773
+ function inFlightFilter() {
6774
+ return "!defined(completedAt)";
6775
+ }
6776
+
6499
6777
  const FIELD_REFS_DOC = "count(fields[value.id in $documents]) > 0 || count(fields[count(value[id in $documents]) > 0]) > 0", ACTIVITY_REFS_DOC = `count(stages[count(activities[${FIELD_REFS_DOC}]) > 0]) > 0`;
6500
6778
 
6779
+ function documentPrefilter(documents, params) {
6780
+ for (const document of documents) if (!invariants.isGdrUri(document)) throw new invariants.ContractViolationError(`every document must be a resource-qualified GDR URI (e.g. "dataset:project:dataset:doc-id"); got: ${JSON.stringify(document)}`);
6781
+ return params.documents = [ ...documents ], params.bareIds = documents.map(invariants.extractDocumentId),
6782
+ `_id in $bareIds || count(ancestors[id in $documents]) > 0 || ${FIELD_REFS_DOC} || count(stages[${FIELD_REFS_DOC}]) > 0 || ${ACTIVITY_REFS_DOC}`;
6783
+ }
6784
+
6501
6785
  function documentArm(filter, params) {
6502
6786
  if (filter.documents === void 0 && filter.document === void 0) return;
6503
6787
  const documents = [ ...filter.documents ?? [], ...filter.document !== void 0 ? [ filter.document ] : [] ];
6504
- for (const document of documents) if (!invariants.isGdrUri(document)) throw new invariants.ContractViolationError(`instancesQuery: every document must be a resource-qualified GDR URI (e.g. "dataset:project:dataset:doc-id"); got: ${JSON.stringify(document)}`);
6505
- return params.documents = documents, params.bareIds = documents.map(invariants.extractDocumentId),
6506
- `_id in $bareIds || count(ancestors[id in $documents]) > 0 || ${FIELD_REFS_DOC} || count(stages[${FIELD_REFS_DOC}]) > 0 || ${ACTIVITY_REFS_DOC}`;
6788
+ return documentPrefilter(documents, params);
6507
6789
  }
6508
6790
 
6509
6791
  function idsArm(filter, params) {
@@ -6519,7 +6801,7 @@ function instancesQuery(args) {
6519
6801
  const conditions = [ `_type == "${WORKFLOW_INSTANCE_TYPE}"`, invariants.tagScopeFilter() ], params = {
6520
6802
  tag: tag
6521
6803
  };
6522
- filter.includeCompleted !== !0 && conditions.push("!defined(completedAt)"), filter.definition !== void 0 && (conditions.push("definition == $definition"),
6804
+ filter.includeCompleted !== !0 && conditions.push(inFlightFilter()), filter.definition !== void 0 && (conditions.push("definition == $definition"),
6523
6805
  params.definition = filter.definition), filter.stage !== void 0 && (conditions.push("currentStage == $stage"),
6524
6806
  params.stage = filter.stage);
6525
6807
  const arms = [ documentArm(filter, params), idsArm(filter, params) ].filter(arm => arm !== void 0);
@@ -6596,17 +6878,12 @@ function topoSortDefinitions(definitions, ctx) {
6596
6878
  }
6597
6879
 
6598
6880
  function refsOf(def) {
6599
- const out = [];
6600
- for (const stage of def.stages) for (const activity of stage.activities ?? []) {
6601
- const ref = activity.subworkflows?.definition;
6602
- ref !== void 0 && out.push({
6603
- name: ref.name,
6604
- ...ref.version !== void 0 ? {
6605
- version: ref.version
6606
- } : {}
6607
- });
6608
- }
6609
- return out;
6881
+ return def.stages.flatMap(stage => stage.activities ?? []).flatMap(activity => activity.actions ?? []).flatMap(action => action.spawn === void 0 ? [] : [ action.spawn.definition ]).map(ref => ({
6882
+ name: ref.name,
6883
+ ...ref.version !== void 0 ? {
6884
+ version: ref.version
6885
+ } : {}
6886
+ }));
6610
6887
  }
6611
6888
 
6612
6889
  function hashDefinitionContent(def) {
@@ -6681,284 +6958,66 @@ function parseDefinitionInput(def, caller) {
6681
6958
 
6682
6959
  function stripSystemFields(doc) {
6683
6960
  const out = {};
6684
- for (const [k, v2] of Object.entries(doc)) k === "_rev" || k === "_createdAt" || k === "_updatedAt" || (out[k] = v2);
6685
- return out;
6686
- }
6687
-
6688
- function stableStringify(value) {
6689
- return value === null || typeof value != "object" ? JSON.stringify(value) : Array.isArray(value) ? `[${value.map(stableStringify).join(",")}]` : `{${Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b)).map(([k, v2]) => `${JSON.stringify(k)}:${stableStringify(v2)}`).join(",")}}`;
6690
- }
6691
-
6692
- async function loadDefinition({client: client, definition: definition, version: version, tag: tag}) {
6693
- if (version !== void 0) {
6694
- const doc = await client.fetch(definitionLookupGroq(!0), {
6695
- definition: definition,
6696
- version: version,
6697
- tag: tag
6698
- });
6699
- if (!doc) throw new invariants.DefinitionNotFoundError({
6700
- definition: definition,
6701
- version: version
6702
- });
6703
- return assertReadableModel(doc);
6704
- }
6705
- const latest = await loadLatestDeployed({
6706
- client: client,
6707
- definition: definition,
6708
- tag: tag
6709
- });
6710
- if (!latest) throw new invariants.DefinitionNotFoundError({
6711
- definition: definition
6712
- });
6713
- return latest;
6714
- }
6715
-
6716
- async function loadLatestDeployed({client: client, definition: definition, tag: tag}) {
6717
- const doc = await client.fetch(definitionLookupGroq(!1), {
6718
- definition: definition,
6719
- tag: tag
6720
- });
6721
- return doc ? assertReadableModel(doc) : void 0;
6722
- }
6723
-
6724
- async function loadDefinitionVersions({client: client, definition: definition, tag: tag}) {
6725
- return (await client.fetch(`*[_type == "${invariants.WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${invariants.tagScopeFilter()}] | order(version desc)`, {
6726
- definition: definition,
6727
- tag: tag
6728
- })).map(assertReadableModel);
6729
- }
6730
-
6731
- async function loadDefinitionVersionsOrThrow({client: client, definition: definition, tag: tag}) {
6732
- const versions = await loadDefinitionVersions({
6733
- client: client,
6734
- definition: definition,
6735
- tag: tag
6736
- });
6737
- if (versions.length === 0) throw new invariants.DefinitionNotFoundError({
6738
- definition: definition
6739
- });
6740
- return versions;
6741
- }
6742
-
6743
- function subjectDenialLabels(denied) {
6744
- return denied.map(d => `${d.permission} on ${d.subject} (${d.resource})`);
6745
- }
6746
-
6747
- class ActionDisabledError extends invariants.WorkflowError {
6748
- reason;
6749
- activity;
6750
- action;
6751
- constructor(args) {
6752
- super("action-disabled", formatDisabledReason({
6753
- activity: args.activity,
6754
- action: args.action,
6755
- reason: args.reason
6756
- })), this.name = "ActionDisabledError", this.reason = args.reason, this.activity = args.activity,
6757
- this.action = args.action;
6758
- }
6759
- }
6760
-
6761
- const disabledReasonDetail = {
6762
- "filter-failed": r => `action filter returned false${r.detail ? ` (${r.detail})` : ""}`,
6763
- "activity-not-active": r => `activity status is "${r.status}"`,
6764
- "stage-terminal": r => `stage "${r.stage}" is terminal`,
6765
- "instance-completed": r => `instance completed at ${r.completedAt}`,
6766
- "instance-aborted": r => `instance aborted at ${r.abortedAt}`,
6767
- "requirements-unmet": r => `unmet requirement(s): ${r.unmetRequirements.join(", ")}`,
6768
- "subject-permission-denied": r => `missing subject permission(s): ${subjectDenialLabels(r.denied).join(", ")}`
6769
- };
6770
-
6771
- function actionDisabledDetail(reason) {
6772
- return disabledReasonDetail[reason.kind](reason);
6773
- }
6774
-
6775
- function formatDisabledReason({activity: activity, action: action, reason: reason}) {
6776
- return `Action "${activity}:${action}" is not allowed: ${actionDisabledDetail(reason)}`;
6777
- }
6778
-
6779
- class EditFieldDeniedError extends invariants.WorkflowError {
6780
- reason;
6781
- target;
6782
- constructor(args) {
6783
- super("edit-field-denied", formatEditDisabledReason(args.target, args.reason)),
6784
- this.name = "EditFieldDeniedError", this.reason = args.reason, this.target = args.target;
6785
- }
6786
- }
6787
-
6788
- const editDisabledReasonDetail = {
6789
- "not-editable": () => "field is not declared editable",
6790
- "instance-completed": r => `instance completed at ${r.completedAt}`,
6791
- "instance-aborted": r => `instance aborted at ${r.abortedAt}`,
6792
- "edit-window-closed": r => `edit window closed (${r.detail})`,
6793
- "editor-not-permitted": r => `editor not permitted (${r.predicate})`
6794
- };
6795
-
6796
- function formatEditDisabledReason(target, reason) {
6797
- const detail = editDisabledReasonDetail[reason.kind](reason), where = target.activity !== void 0 ? `${target.activity}.${target.field}` : target.field;
6798
- return `Field "${target.scope}:${where}" is not editable: ${detail}`;
6961
+ for (const [k, v2] of Object.entries(doc)) k === "_rev" || k === "_createdAt" || k === "_updatedAt" || (out[k] = v2);
6962
+ return out;
6799
6963
  }
6800
6964
 
6801
- async function fireAction(args) {
6802
- const {client: client, instanceId: instanceId, activity: activity, action: action, params: params, requestRecord: requestRecord, options: options} = args;
6803
- return retryOnRevisionConflict({
6804
- client: client,
6805
- instanceId: instanceId,
6806
- options: options,
6807
- commit: ctx => commitAction({
6808
- ctx: ctx,
6809
- activityName: activity,
6810
- actionName: action,
6811
- callerParams: params,
6812
- requestRecord: requestRecord,
6813
- options: options
6814
- }),
6815
- onExhausted: () => new ConcurrentFireActionError({
6816
- instanceId: instanceId,
6817
- activity: activity,
6818
- action: action,
6819
- attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
6820
- })
6821
- });
6965
+ function stableStringify(value) {
6966
+ if (value === null || typeof value != "object") return JSON.stringify(value);
6967
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
6968
+ const record = value;
6969
+ return `{${Object.keys(record).toSorted().map(k => `${JSON.stringify(k)}:${stableStringify(record[k])}`).join(",")}}`;
6822
6970
  }
6823
6971
 
6824
- async function resolveActionCommit({ctx: ctx, activityName: activityName, actionName: actionName, callerParams: callerParams, options: options}) {
6825
- const actor = options?.actor, {stage: stage, activity: activity} = findActivityInCurrentStage(ctx, activityName), action = (activity.actions ?? []).find(a => a.name === actionName);
6826
- if (action === void 0) throw new invariants.ContractViolationError(`Action "${actionName}" not declared on activity "${activityName}"`);
6827
- if (action.filter !== void 0) {
6828
- const can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan({
6829
- instance: ctx.instance,
6830
- actor: actor,
6831
- grants: options.grants
6832
- }) : void 0;
6833
- if (!await ctxEvaluateCondition({
6834
- ctx: ctx,
6835
- condition: action.filter,
6836
- opts: {
6837
- activityName: activityName,
6838
- ...actor !== void 0 ? {
6839
- actor: actor
6840
- } : {},
6841
- ...can !== void 0 ? {
6842
- vars: {
6843
- can: can
6844
- }
6845
- } : {}
6846
- }
6847
- })) throw new ActionDisabledError({
6848
- activity: activityName,
6849
- action: actionName,
6850
- reason: {
6851
- kind: "filter-failed",
6852
- filter: action.filter,
6853
- detail: "commit re-check"
6854
- }
6972
+ async function loadDefinition({client: client, definition: definition, version: version, tag: tag}) {
6973
+ if (version !== void 0) {
6974
+ const doc = await client.fetch(definitionLookupGroq(!0), {
6975
+ definition: definition,
6976
+ version: version,
6977
+ tag: tag
6978
+ });
6979
+ if (!doc) throw new invariants.DefinitionNotFoundError({
6980
+ definition: definition,
6981
+ version: version
6855
6982
  });
6983
+ return assertReadableModel(doc);
6856
6984
  }
6857
- const entry = findCurrentActivities(ctx.instance).find(t => t.name === activityName);
6858
- if (entry === void 0 || entry.status !== "active") throw notActiveAtCommit({
6859
- activityName: activityName,
6860
- actionName: actionName,
6861
- status: entry?.status
6985
+ const latest = await loadLatestDeployed({
6986
+ client: client,
6987
+ definition: definition,
6988
+ tag: tag
6862
6989
  });
6863
- const params = validateActionParams({
6864
- action: action,
6865
- activityName: activityName,
6866
- callerParams: callerParams
6990
+ if (!latest) throw new invariants.DefinitionNotFoundError({
6991
+ definition: definition
6867
6992
  });
6868
- return {
6869
- stage: stage,
6870
- activity: activity,
6871
- action: action,
6872
- params: params
6873
- };
6993
+ return latest;
6874
6994
  }
6875
6995
 
6876
- function notActiveAtCommit(args) {
6877
- const {activityName: activityName, actionName: actionName, status: status} = args;
6878
- return status !== void 0 && invariants.isTerminalActivityStatus(status) ? new ActionDisabledError({
6879
- activity: activityName,
6880
- action: actionName,
6881
- reason: {
6882
- kind: "activity-not-active",
6883
- status: status
6884
- }
6885
- }) : new Error(`Activity "${activityName}" must be active to fire action "${actionName}"; status is ${status ?? "missing"}`);
6996
+ async function loadLatestDeployed({client: client, definition: definition, tag: tag}) {
6997
+ const doc = await client.fetch(definitionLookupGroq(!1), {
6998
+ definition: definition,
6999
+ tag: tag
7000
+ });
7001
+ return doc ? assertReadableModel(doc) : void 0;
6886
7002
  }
6887
7003
 
6888
- async function commitAction({ctx: ctx, activityName: activityName, actionName: actionName, callerParams: callerParams, requestRecord: requestRecord, options: options}) {
6889
- assertRequestUnprocessed({
6890
- instance: ctx.instance,
6891
- record: requestRecord,
6892
- now: ctx.now
6893
- });
6894
- const actor = options?.actor, {stage: stage, action: action, params: params} = await resolveActionCommit({
6895
- ctx: ctx,
6896
- activityName: activityName,
6897
- actionName: actionName,
6898
- callerParams: callerParams,
6899
- options: options
6900
- }), mutation = startMutation(ctx.instance);
6901
- recordProcessedRequest({
6902
- mutation: mutation,
6903
- record: requestRecord,
6904
- now: ctx.now
7004
+ async function loadDefinitionVersions({client: client, definition: definition, tag: tag}) {
7005
+ return (await client.fetch(`*[_type == "${invariants.WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${invariants.tagScopeFilter()}] | order(version desc)`, {
7006
+ definition: definition,
7007
+ tag: tag
7008
+ })).map(assertReadableModel);
7009
+ }
7010
+
7011
+ async function loadDefinitionVersionsOrThrow({client: client, definition: definition, tag: tag}) {
7012
+ const versions = await loadDefinitionVersions({
7013
+ client: client,
7014
+ definition: definition,
7015
+ tag: tag
6905
7016
  });
6906
- const mutEntry = requireMutationActivityEntry(mutation, activityName), now = ctx.now;
6907
- mutation.history.push({
6908
- _key: randomKey(),
6909
- _type: "actionFired",
6910
- at: now,
6911
- stage: stage.name,
6912
- activity: activityName,
6913
- action: actionName,
6914
- ...actor !== void 0 ? {
6915
- actor: actor,
6916
- driverKind: driverKind(actor)
6917
- } : {}
7017
+ if (versions.length === 0) throw new invariants.DefinitionNotFoundError({
7018
+ definition: definition
6918
7019
  });
6919
- const statusBefore = mutEntry.status, ranOps = await runOps({
6920
- ops: action.ops,
6921
- mutation: mutation,
6922
- stage: stage.name,
6923
- origin: {
6924
- activity: activityName,
6925
- action: actionName
6926
- },
6927
- params: params,
6928
- actor: actor,
6929
- self: invariants.selfGdr(ctx.instance),
6930
- now: now,
6931
- snapshot: ctx.snapshot
6932
- }), newStatus = mutEntry.status !== statusBefore ? mutEntry.status : void 0;
6933
- return await queueEffects({
6934
- ctx: ctx,
6935
- mutation: mutation,
6936
- effects: action.effects,
6937
- origin: {
6938
- kind: "action",
6939
- name: actionName
6940
- },
6941
- actor: actor,
6942
- opts: {
6943
- callerParams: params,
6944
- activityName: activityName
6945
- }
6946
- }), await persistThenMaybeRefresh({
6947
- ctx: ctx,
6948
- mutation: mutation,
6949
- stageName: stage.name,
6950
- didChangeState: ranOps.some(isFieldOp)
6951
- }), {
6952
- fired: !0,
6953
- activity: activityName,
6954
- action: actionName,
6955
- ...newStatus !== void 0 ? {
6956
- newStatus: newStatus
6957
- } : {},
6958
- ...ranOps.length > 0 ? {
6959
- ranOps: ranOps
6960
- } : {}
6961
- };
7020
+ return versions;
6962
7021
  }
6963
7022
 
6964
7023
  async function editField(args) {
@@ -6978,7 +7037,7 @@ async function editField(args) {
6978
7037
  onExhausted: () => new ConcurrentEditFieldError({
6979
7038
  instanceId: instanceId,
6980
7039
  target: labelTarget(target),
6981
- attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
7040
+ attempts: CONCURRENT_COMMIT_MAX_ATTEMPTS
6982
7041
  })
6983
7042
  });
6984
7043
  }
@@ -7274,7 +7333,7 @@ async function abortAndPropagate(args) {
7274
7333
  ...telemetry !== void 0 ? {
7275
7334
  telemetry: telemetry
7276
7335
  } : {}
7277
- }), result.fired && await propagateToAncestors({
7336
+ }), await propagateToAncestors({
7278
7337
  client: client,
7279
7338
  instanceId: instanceId,
7280
7339
  actor: actor,
@@ -7289,12 +7348,12 @@ async function abortAndPropagate(args) {
7289
7348
  }), result.fired;
7290
7349
  }
7291
7350
 
7292
- function toEffectsContextEntries(ctx) {
7293
- return Object.entries(ctx).map(([id, value]) => toEffectsContextSeedEntry(id, value));
7351
+ function toContextEntries(ctx) {
7352
+ return Object.entries(ctx).map(([id, value]) => toContextSeedEntry(id, value));
7294
7353
  }
7295
7354
 
7296
- function toEffectsContextSeedEntry(name, value) {
7297
- return typeof value == "string" || typeof value == "number" || typeof value == "boolean" || invariants.isGdr(value) ? effectsContextEntry(name, value) : effectsContextJsonEntry(name, value);
7355
+ function toContextSeedEntry(name, value) {
7356
+ return typeof value == "string" || typeof value == "number" || typeof value == "boolean" || invariants.isGdr(value) ? contextEntry(name, value) : contextJsonEntry(name, value);
7298
7357
  }
7299
7358
 
7300
7359
  async function dispatchGatedWrite(args) {
@@ -7414,12 +7473,7 @@ function buildEngineCallOptions(args) {
7414
7473
 
7415
7474
  async function applyAction(args) {
7416
7475
  const {client: client, instance: instance, activity: activity, action: action, params: params, requestRecord: requestRecord, ...rest} = args, options = buildEngineCallOptions(rest);
7417
- return findCurrentActivityEntry(instance, activity)?.status === "pending" && await invokeActivity({
7418
- client: client,
7419
- instanceId: instance._id,
7420
- activity: activity,
7421
- options: options
7422
- }), (await fireAction({
7476
+ return (await fireAction({
7423
7477
  client: client,
7424
7478
  instanceId: instance._id,
7425
7479
  activity: activity,
@@ -7513,7 +7567,7 @@ async function assertNoSpawnReferrers({client: client, tag: tag, definition: def
7513
7567
 
7514
7568
  async function nonTerminalInstanceIds({client: client, tag: tag, definition: definition, version: version}) {
7515
7569
  const versionFilter = version === void 0 ? "" : " && pinnedVersion == $version";
7516
- return (await client.fetch(`*[_type == "${WORKFLOW_INSTANCE_TYPE}" && definition == $definition && ${invariants.tagScopeFilter()} && !defined(completedAt)${versionFilter}] | order(_id asc){_id}`, {
7570
+ return (await client.fetch(`*[_type == "${WORKFLOW_INSTANCE_TYPE}" && definition == $definition && ${invariants.tagScopeFilter()} && ${inFlightFilter()}${versionFilter}] | order(_id asc){_id}`, {
7517
7571
  definition: definition,
7518
7572
  tag: tag,
7519
7573
  ...version !== void 0 ? {
@@ -7541,6 +7595,16 @@ async function abortInstances(args) {
7541
7595
  return aborted;
7542
7596
  }
7543
7597
 
7598
+ async function fetchStartFilterSlice(args) {
7599
+ const {client: client, tag: tag} = args, {query: query, params: params} = instancesQuery({
7600
+ tag: tag,
7601
+ filter: {
7602
+ includeCompleted: !0
7603
+ }
7604
+ });
7605
+ return (await client.fetch(query, params)).map(assertReadableModel);
7606
+ }
7607
+
7544
7608
  const workflow = {
7545
7609
  deployDefinitions: async rawArgs => {
7546
7610
  const args = taggedScope(rawArgs, REQUEST_TAG.deploy), {client: client, tag: tag, resourceAliases: resourceAliases} = args;
@@ -7615,16 +7679,22 @@ const workflow = {
7615
7679
  }), result;
7616
7680
  },
7617
7681
  startInstance: async rawArgs => {
7618
- const args = taggedScope(rawArgs, REQUEST_TAG.start), {client: client, tag: tag, workflowResource: workflowResource, definition: definitionName, version: version, initialFields: initialFields, ancestors: ancestors, effectsContext: effectsContext, instanceId: instanceId, perspective: perspective, executionContext: executionContext} = args, {actor: actor, clientForGdr: clientForGdr} = await resolveOperationContext(args), clock = args.clock ?? wallClock, definition = await loadDefinition({
7682
+ const args = taggedScope(rawArgs, REQUEST_TAG.start), {client: client, tag: tag, workflowResource: workflowResource, definition: definitionName, version: version, initialFields: initialFields, ancestors: ancestors, context: context, instanceId: instanceId, perspective: perspective, executionContext: executionContext} = args, {actor: actor, clientForGdr: clientForGdr} = await resolveOperationContext(args), clock = args.clock ?? wallClock, seedFields = initialFields ?? [], definition = await loadDefinition({
7619
7683
  client: client,
7620
7684
  definition: definitionName,
7621
7685
  version: version,
7622
7686
  tag: tag
7623
7687
  }), id = instanceId ?? instanceDocId(tag);
7624
7688
  if (definition.stages.find(s => s.name === definition.initialStage) === void 0) throw new Error(`Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`);
7625
- const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], fieldDiscards = [], resolvedFields = await resolveDeclaredFields({
7689
+ const now = clock();
7690
+ assertRequiredInputProvided({
7626
7691
  entryDefs: definition.fields ?? [],
7627
- initialFields: initialFields ?? [],
7692
+ initialFields: seedFields,
7693
+ definitionName: definition.name
7694
+ });
7695
+ const contextEntries = context ? toContextEntries(context) : [], fieldDiscards = [], resolvedFields = await resolveDeclaredFields({
7696
+ entryDefs: definition.fields ?? [],
7697
+ initialFields: seedFields,
7628
7698
  ctx: {
7629
7699
  client: client,
7630
7700
  now: now,
@@ -7652,7 +7722,7 @@ const workflow = {
7652
7722
  pinnedContentHash: definition.contentHash,
7653
7723
  definition: definition,
7654
7724
  fields: resolvedFields,
7655
- effectsContext: effectsContextEntries,
7725
+ context: contextEntries,
7656
7726
  ancestors: ancestors ?? [],
7657
7727
  perspective: effectivePerspective,
7658
7728
  initialStage: definition.initialStage,
@@ -7662,20 +7732,8 @@ const workflow = {
7662
7732
  extraHistory: fieldDiscards
7663
7733
  } : {}
7664
7734
  });
7665
- await client.create(base, SYNC_COMMIT), await primeInitialStage({
7666
- client: client,
7667
- instanceId: id,
7668
- actor: actor,
7669
- clientForGdr: clientForGdr,
7670
- clock: clock,
7671
- ...executionContext !== void 0 ? {
7672
- executionContext: executionContext
7673
- } : {},
7674
- ...args.telemetry !== void 0 ? {
7675
- telemetry: args.telemetry
7676
- } : {}
7677
- });
7678
- const cascaded = await cascade({
7735
+ await client.create(base, SYNC_COMMIT);
7736
+ const runArgs = {
7679
7737
  client: client,
7680
7738
  instanceId: id,
7681
7739
  actor: actor,
@@ -7687,11 +7745,13 @@ const workflow = {
7687
7745
  ...args.telemetry !== void 0 ? {
7688
7746
  telemetry: args.telemetry
7689
7747
  } : {}
7690
- });
7748
+ };
7749
+ await primeInitialStage(runArgs);
7750
+ const cascaded = await cascade(runArgs);
7691
7751
  return resolveTelemetry(args.telemetry).log(WorkflowInstanceStarted, instanceStartedData({
7692
7752
  definition: definition,
7693
7753
  instanceId: id,
7694
- initialFieldCount: initialFields?.length ?? 0,
7754
+ initialFieldCount: seedFields.length,
7695
7755
  viaSpawn: !1
7696
7756
  })), {
7697
7757
  instance: await reload({
@@ -8264,12 +8324,21 @@ const workflow = {
8264
8324
  definitionsForDocument: async rawArgs => {
8265
8325
  const args = taggedScope(rawArgs, REQUEST_TAG.definitionsForDocument), {client: client, tag: tag, document: document} = args;
8266
8326
  invariants.validateTag(tag);
8267
- const deployed = (await client.fetch(definitionsListGroq("desc"), {
8327
+ const clock = args.clock ?? wallClock, deployed = (await client.fetch(definitionsListGroq("desc"), {
8268
8328
  tag: tag
8269
8329
  })).map(assertReadableModel), latest = latestDeployedDefinitions(deployed);
8330
+ let slice;
8270
8331
  return applicableDefinitions({
8271
8332
  definitions: latest,
8272
- document: document
8333
+ document: document,
8334
+ scope: {
8335
+ tag: tag,
8336
+ now: clock(),
8337
+ fetchDataset: () => slice ??= fetchStartFilterSlice({
8338
+ client: client,
8339
+ tag: tag
8340
+ })
8341
+ }
8273
8342
  });
8274
8343
  },
8275
8344
  permissions: {
@@ -9381,6 +9450,24 @@ async function computeDiffEntries({client: client, defs: defs, target: target})
9381
9450
  return entries;
9382
9451
  }
9383
9452
 
9453
+ function buildInitialFields({declared: declared, values: values}) {
9454
+ const settable = declared.filter(f => f.initialValue?.type === "input"), settableNames = settable.map(f => f.name).join(", ");
9455
+ return Object.entries(values).map(([name, value]) => {
9456
+ const entry = declared.find(f => f.name === name);
9457
+ if (entry === void 0) throw new Error(settable.length > 0 ? `workflow declares no field "${name}" — input fields: ${settableNames}` : `workflow declares no field "${name}" — it declares no input fields at all`);
9458
+ if (entry.initialValue?.type !== "input") throw new Error(`field "${name}" is not input-sourced — the engine would silently ignore the value. ` + (settable.length > 0 ? `Input fields: ${settableNames}` : "This workflow has no input fields."));
9459
+ return {
9460
+ type: entry.type,
9461
+ name: name,
9462
+ value: value
9463
+ };
9464
+ });
9465
+ }
9466
+
9467
+ function startRefusal(definition) {
9468
+ if (!invariants.isStartableDefinition(definition)) return "definition is spawn-only (lifecycle: 'child') — instances of it are spawned by a parent workflow's activity, not started standalone";
9469
+ }
9470
+
9384
9471
  const HISTORY_DISPLAY = {
9385
9472
  stageEntered: {
9386
9473
  title: "Stage entered",
@@ -9390,17 +9477,13 @@ const HISTORY_DISPLAY = {
9390
9477
  title: "Stage exited",
9391
9478
  description: "The instance left a stage on the way to the next one."
9392
9479
  },
9393
- activityActivated: {
9394
- title: "Activity activated",
9395
- description: "An activity flipped from `pending` to `active` and its onEnter effects queued."
9396
- },
9397
9480
  activityStatusChanged: {
9398
9481
  title: "Activity status changed",
9399
9482
  description: "An action or op flipped an activity's status (active → done / skipped / failed)."
9400
9483
  },
9401
9484
  actionFired: {
9402
9485
  title: "Action fired",
9403
- description: "An action was invoked against an activity — ops ran, status may have flipped, effects queued."
9486
+ description: "An action fired against an activity — via fireAction, or cascade-fired when its `when` turned true (`triggered`) — ops ran, status may have flipped, effects queued, spawn reconciled."
9404
9487
  },
9405
9488
  transitionFired: {
9406
9489
  title: "Transition fired",
@@ -9412,7 +9495,7 @@ const HISTORY_DISPLAY = {
9412
9495
  },
9413
9496
  effectCompleted: {
9414
9497
  title: "Effect settled",
9415
- description: "A queued effect settled: a drainer ran its handler to completion (done or failed, outputs merged into effectsContext), or an abort cancelled it before dispatch."
9498
+ description: "A queued effect settled: a drainer ran its handler to completion (done or failed, outputs recorded on the run row and readable as $effects), or an abort cancelled it before dispatch."
9416
9499
  },
9417
9500
  effectClaimReleased: {
9418
9501
  title: "Stale claim released",
@@ -9420,7 +9503,7 @@ const HISTORY_DISPLAY = {
9420
9503
  },
9421
9504
  spawned: {
9422
9505
  title: "Spawned child",
9423
- description: "An activity's `subworkflows` declaration spawned a child workflow instance."
9506
+ description: "An action's `spawn` declaration spawned a child workflow instance."
9424
9507
  },
9425
9508
  subworkflowAdopted: {
9426
9509
  title: "Child adopted",
@@ -9446,6 +9529,23 @@ const HISTORY_DISPLAY = {
9446
9529
  title: "Query result discarded",
9447
9530
  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."
9448
9531
  }
9532
+ }, EXECUTOR_CLASSIFICATION_DISPLAY = {
9533
+ autonomous: {
9534
+ title: "Autonomous",
9535
+ description: "Every action is cascade-fired — the engine advances it; no caller is ever needed."
9536
+ },
9537
+ interactive: {
9538
+ title: "Interactive",
9539
+ description: "Only fireAction-fired actions — a caller (editor, robot, agent) must act."
9540
+ },
9541
+ "off-system": {
9542
+ title: "Off-system",
9543
+ description: "The work happens outside the system (the `target` deep-link); someone records it here."
9544
+ },
9545
+ hybrid: {
9546
+ title: "Hybrid",
9547
+ description: "A mix of caller-fired actions and cascade-fired triggers."
9548
+ }
9449
9549
  }, FIELD_KIND_DISPLAY = {
9450
9550
  "doc.ref": {
9451
9551
  title: "Document reference",
@@ -9522,7 +9622,7 @@ const HISTORY_DISPLAY = {
9522
9622
  },
9523
9623
  "field.updateWhere": {
9524
9624
  title: "Update matching rows",
9525
- description: "Merge fields into rows of an array-kind entry that match the `where` condition."
9625
+ description: "Merge declared sub-fields into rows of an `array` entry that match the `where` condition (`array` only — `doc.refs`/`assignees` rows are engine-shaped)."
9526
9626
  },
9527
9627
  "field.removeWhere": {
9528
9628
  title: "Remove matching rows",
@@ -9532,26 +9632,26 @@ const HISTORY_DISPLAY = {
9532
9632
  title: "Set activity status",
9533
9633
  description: "Flip an activity's status explicitly (the action-level `status:` sugar desugars to this)."
9534
9634
  }
9535
- }, EFFECTS_CONTEXT_DISPLAY = {
9536
- "effectsContext.string": {
9635
+ }, CONTEXT_ENTRY_DISPLAY = {
9636
+ "context.string": {
9537
9637
  title: "String",
9538
- description: "Stable string parameter for effect handlers."
9638
+ description: "Start-seeded string value, read as `$context.<name>`."
9539
9639
  },
9540
- "effectsContext.number": {
9640
+ "context.number": {
9541
9641
  title: "Number",
9542
- description: "Stable numeric parameter for effect handlers."
9642
+ description: "Start-seeded numeric value, read as `$context.<name>`."
9543
9643
  },
9544
- "effectsContext.boolean": {
9644
+ "context.boolean": {
9545
9645
  title: "Boolean",
9546
- description: "Stable boolean parameter for effect handlers."
9646
+ description: "Start-seeded boolean value, read as `$context.<name>`."
9547
9647
  },
9548
- "effectsContext.ref": {
9648
+ "context.ref": {
9549
9649
  title: "GDR reference",
9550
- description: "Stable GDR pointer parameter for effect handlers."
9650
+ description: "Start-seeded GDR pointer, read as `$context.<name>`."
9551
9651
  },
9552
- "effectsContext.json": {
9652
+ "context.json": {
9553
9653
  title: "JSON",
9554
- description: "Stable JSON payload for effect handlers."
9654
+ description: "Start-seeded JSON payload, read as `$context.<name>`."
9555
9655
  }
9556
9656
  }, AUTHORING_DISPLAY = {
9557
9657
  pendingEffect: {
@@ -9581,7 +9681,7 @@ const HISTORY_DISPLAY = {
9581
9681
  },
9582
9682
  script: {
9583
9683
  title: "Script activity",
9584
- description: "The engine runs a step inline and resolves on activation — an audit row."
9684
+ description: "The engine runs a step inline and resolves it in the same cascade hop — an audit row."
9585
9685
  },
9586
9686
  manual: {
9587
9687
  title: "Manual activity",
@@ -9621,7 +9721,7 @@ const HISTORY_DISPLAY = {
9621
9721
  ...HISTORY_DISPLAY,
9622
9722
  ...FIELD_KIND_DISPLAY,
9623
9723
  ...OP_DISPLAY,
9624
- ...EFFECTS_CONTEXT_DISPLAY,
9724
+ ...CONTEXT_ENTRY_DISPLAY,
9625
9725
  ...AUTHORING_DISPLAY
9626
9726
  };
9627
9727
 
@@ -9641,12 +9741,16 @@ exports.CONDITION_VARS = invariants.CONDITION_VARS;
9641
9741
 
9642
9742
  exports.ContractViolationError = invariants.ContractViolationError;
9643
9743
 
9744
+ exports.DEFAULT_TRANSITION_WHEN = invariants.DEFAULT_TRANSITION_WHEN;
9745
+
9644
9746
  exports.DRIVER_KINDS = invariants.DRIVER_KINDS;
9645
9747
 
9646
9748
  exports.DefinitionInUseError = invariants.DefinitionInUseError;
9647
9749
 
9648
9750
  exports.DefinitionNotFoundError = invariants.DefinitionNotFoundError;
9649
9751
 
9752
+ exports.EXECUTOR_CLASSIFICATIONS = invariants.EXECUTOR_CLASSIFICATIONS;
9753
+
9650
9754
  exports.EffectNotFoundError = invariants.EffectNotFoundError;
9651
9755
 
9652
9756
  exports.FILTER_SCOPE_VARS = invariants.FILTER_SCOPE_VARS;
@@ -9661,14 +9765,24 @@ exports.InstanceNotFoundError = invariants.InstanceNotFoundError;
9661
9765
 
9662
9766
  exports.RESERVED_CONDITION_VARS = invariants.RESERVED_CONDITION_VARS;
9663
9767
 
9768
+ exports.START_FILTER_VARS = invariants.START_FILTER_VARS;
9769
+
9664
9770
  exports.WORKFLOW_DEFINITION_TYPE = invariants.WORKFLOW_DEFINITION_TYPE;
9665
9771
 
9666
9772
  exports.WorkflowError = invariants.WorkflowError;
9667
9773
 
9668
9774
  exports.clientConfigFromResource = invariants.clientConfigFromResource;
9669
9775
 
9776
+ exports.conditionFieldReadNames = invariants.conditionFieldReadNames;
9777
+
9670
9778
  exports.datasetResourceParts = invariants.datasetResourceParts;
9671
9779
 
9780
+ exports.deriveActivityKind = invariants.deriveActivityKind;
9781
+
9782
+ exports.deriveExecutorClassification = invariants.deriveExecutorClassification;
9783
+
9784
+ exports.driverKind = invariants.driverKind;
9785
+
9672
9786
  exports.errorMessage = invariants.errorMessage;
9673
9787
 
9674
9788
  exports.extractDocumentId = invariants.extractDocumentId;
@@ -9681,12 +9795,16 @@ exports.gdrUri = invariants.gdrUri;
9681
9795
 
9682
9796
  exports.groupMembershipNames = invariants.groupMembershipNames;
9683
9797
 
9798
+ exports.isCascadeFired = invariants.isCascadeFired;
9799
+
9684
9800
  exports.isGdr = invariants.isGdr;
9685
9801
 
9686
9802
  exports.isNotesEntry = invariants.isNotesEntry;
9687
9803
 
9688
9804
  exports.isStartableDefinition = invariants.isStartableDefinition;
9689
9805
 
9806
+ exports.isSubjectEntry = invariants.isSubjectEntry;
9807
+
9690
9808
  exports.isTerminalActivityStatus = invariants.isTerminalActivityStatus;
9691
9809
 
9692
9810
  exports.isTodoListEntry = invariants.isTodoListEntry;
@@ -9697,6 +9815,8 @@ exports.parseGdr = invariants.parseGdr;
9697
9815
 
9698
9816
  exports.parseResourceGdr = invariants.parseResourceGdr;
9699
9817
 
9818
+ exports.readsRootDocument = invariants.readsRootDocument;
9819
+
9700
9820
  exports.refCanvas = invariants.refCanvas;
9701
9821
 
9702
9822
  exports.refDashboard = invariants.refDashboard;
@@ -9717,6 +9837,8 @@ exports.resourceFromParsed = invariants.resourceFromParsed;
9717
9837
 
9718
9838
  exports.resourceGdr = invariants.resourceGdr;
9719
9839
 
9840
+ exports.startKindOf = invariants.startKindOf;
9841
+
9720
9842
  exports.tagScopeFilter = invariants.tagScopeFilter;
9721
9843
 
9722
9844
  exports.toBareId = invariants.toBareId;
@@ -9810,8 +9932,12 @@ exports.ActionDisabledError = ActionDisabledError;
9810
9932
 
9811
9933
  exports.ActionParamsInvalidError = ActionParamsInvalidError;
9812
9934
 
9935
+ exports.CONTEXT_ENTRY_DISPLAY = CONTEXT_ENTRY_DISPLAY;
9936
+
9813
9937
  exports.CascadeLimitError = CascadeLimitError;
9814
9938
 
9939
+ exports.ConcurrentCompleteEffectError = ConcurrentCompleteEffectError;
9940
+
9815
9941
  exports.ConcurrentEditFieldError = ConcurrentEditFieldError;
9816
9942
 
9817
9943
  exports.ConcurrentFireActionError = ConcurrentFireActionError;
@@ -9830,12 +9956,12 @@ exports.DISPLAY = DISPLAY;
9830
9956
 
9831
9957
  exports.DRIVER_KIND_DISPLAY = DRIVER_KIND_DISPLAY;
9832
9958
 
9833
- exports.EFFECTS_CONTEXT_DISPLAY = EFFECTS_CONTEXT_DISPLAY;
9834
-
9835
9959
  exports.ENGINE_API_VERSION = ENGINE_API_VERSION;
9836
9960
 
9837
9961
  exports.EXECUTION_KINDS = EXECUTION_KINDS;
9838
9962
 
9963
+ exports.EXECUTOR_CLASSIFICATION_DISPLAY = EXECUTOR_CLASSIFICATION_DISPLAY;
9964
+
9839
9965
  exports.EditFieldDeniedError = EditFieldDeniedError;
9840
9966
 
9841
9967
  exports.EffectOpsInvalidError = EffectOpsInvalidError;
@@ -9900,9 +10026,9 @@ exports.aclPathForResource = aclPathForResource;
9900
10026
 
9901
10027
  exports.actionDisabledDetail = actionDisabledDetail;
9902
10028
 
9903
- exports.actionVerdict = actionVerdict;
10029
+ exports.actionRendering = actionRendering;
9904
10030
 
9905
- exports.activityKind = activityKind;
10031
+ exports.actionVerdict = actionVerdict;
9906
10032
 
9907
10033
  exports.applicableDefinitions = applicableDefinitions;
9908
10034
 
@@ -9910,6 +10036,8 @@ exports.assertReadableModel = assertReadableModel;
9910
10036
 
9911
10037
  exports.availableActions = availableActions;
9912
10038
 
10039
+ exports.buildInitialFields = buildInitialFields;
10040
+
9913
10041
  exports.buildSnapshot = buildSnapshot;
9914
10042
 
9915
10043
  exports.checklistLines = checklistLines;
@@ -9920,8 +10048,14 @@ exports.computeDiffEntries = computeDiffEntries;
9920
10048
 
9921
10049
  exports.conditionSitesOf = conditionSitesOf;
9922
10050
 
10051
+ exports.contentDocQuery = contentDocQuery;
10052
+
10053
+ exports.contentDraftFallback = contentDraftFallback;
10054
+
9923
10055
  exports.contentReleaseName = contentReleaseName;
9924
10056
 
10057
+ exports.contextMap = contextMap;
10058
+
9925
10059
  exports.createEngine = createEngine;
9926
10060
 
9927
10061
  exports.createTelemetryIntake = createTelemetryIntake;
@@ -9942,8 +10076,6 @@ exports.denyingGuards = denyingGuards;
9942
10076
 
9943
10077
  exports.deployStageGuards = deployStageGuards;
9944
10078
 
9945
- exports.deriveActivityKind = deriveActivityKind;
9946
-
9947
10079
  exports.describeAtom = describeAtom;
9948
10080
 
9949
10081
  exports.describeCondition = describeCondition;
@@ -9970,14 +10102,16 @@ exports.displayTitle = displayTitle;
9970
10102
 
9971
10103
  exports.documentActionDenials = documentActionDenials;
9972
10104
 
9973
- exports.driverKind = driverKind;
10105
+ exports.documentPrefilter = documentPrefilter;
9974
10106
 
9975
- exports.effectsContextMap = effectsContextMap;
10107
+ exports.effectOutputsMap = effectOutputsMap;
9976
10108
 
9977
10109
  exports.evaluateFromSnapshot = evaluateFromSnapshot;
9978
10110
 
9979
10111
  exports.evaluateMutationGuard = evaluateMutationGuard;
9980
10112
 
10113
+ exports.evaluateStartFilter = evaluateStartFilter;
10114
+
9981
10115
  exports.fieldTreeShape = fieldTreeShape;
9982
10116
 
9983
10117
  exports.findCurrentActivityEntry = findCurrentActivityEntry;
@@ -9996,6 +10130,8 @@ exports.guardsForResource = guardsForResource;
9996
10130
 
9997
10131
  exports.hashDefinitionContent = hashDefinitionContent;
9998
10132
 
10133
+ exports.inFlightFilter = inFlightFilter;
10134
+
9999
10135
  exports.instanceGuardQuery = instanceGuardQuery;
10000
10136
 
10001
10137
  exports.instanceWatchesDocument = instanceWatchesDocument;
@@ -10006,6 +10142,8 @@ exports.isClaimExpired = isClaimExpired;
10006
10142
 
10007
10143
  exports.isDefinitionApplicable = isDefinitionApplicable;
10008
10144
 
10145
+ exports.isFilterScopedOut = isFilterScopedOut;
10146
+
10009
10147
  exports.isGuardLifted = isGuardLifted;
10010
10148
 
10011
10149
  exports.isTelemetryEnvDenied = isTelemetryEnvDenied;
@@ -10028,10 +10166,14 @@ exports.noopTelemetry = noopTelemetry;
10028
10166
 
10029
10167
  exports.parentRef = parentRef;
10030
10168
 
10169
+ exports.parseDefinitionInput = parseDefinitionInput;
10170
+
10031
10171
  exports.parseDefinitionSnapshot = parseDefinitionSnapshot;
10032
10172
 
10033
10173
  exports.processShellUserProperties = processShellUserProperties;
10034
10174
 
10175
+ exports.projectToWatchRef = projectToWatchRef;
10176
+
10035
10177
  exports.readsRaw = readsRaw;
10036
10178
 
10037
10179
  exports.remediationsFor = remediationsFor;
@@ -10044,6 +10186,8 @@ exports.retractStageGuards = retractStageGuards;
10044
10186
 
10045
10187
  exports.silentLogger = silentLogger;
10046
10188
 
10189
+ exports.startRefusal = startRefusal;
10190
+
10047
10191
  exports.stripSystemFields = stripSystemFields;
10048
10192
 
10049
10193
  exports.subjectDenialLabels = subjectDenialLabels;