@sanity/workflow-engine 0.31.0 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,45 +1,16 @@
1
- import { rethrowWithContext, andConditions, deriveActivityKind, CALLER_BOUND_VARS, START_REQUIREMENT_VARS, CONDITION_VARS, isSubjectEntry, isUnevaluable, ContractViolationError, isStartableDefinition, conditionFieldReadNames, isGdr, errorMessage, conditionParameterNames, START_FILTER_VARS, gdrFromResource, selfGdr, isSingleDocRefKind, actorFulfillsRole, toBareId, WorkflowError, sameResource, resourceFromParsed, tryParseGdr, isTerminalActivityStatus, FieldValueShapeError, validateFieldValue, validateFieldAppendItem, isAlwaysArrayFieldKind, evaluateCondition, isRecord, isSingleDocRefEntry, isAssignmentFieldEntry, choiceValueIssues, scalarValidationIssues, StoredFieldOpSchema, formatIssues, conditionSyntaxIssues, checkWorkflowInvariants, formatIssuePath, isGuardReadExpr, conditionEffectReads, EFFECTS_READ, datasetResourceParts, assignmentKindAcceptsRoles, evaluatePredicates, WORKFLOW_DEFINITION_TYPE, tagScopeFilter, isCascadeFired, classifyPrincipalId, directoryBridgeId, parseGdr, isGdrUri, gdrRef, isInputSourced, parseFieldValue, VersionSpecificDatasetGdrError, toPhysicalGdr, isBareSeedId, NonEmptyString, tolerantEntries, FIELD_VALUE_KINDS, tolerantObject, ActorShape, IsoTimestamp, ACTIVITY_STATUSES, GdrShape, DRIVER_KINDS, FIELD_SCOPES, fieldValueSchemas, parsePersistedDoc, ACTOR_KINDS, InstanceNotFoundError, evaluateConditionOutcome, runGroq, FIELD_READ, MUTATION_GUARD_ACTIONS, resourceGdr, resourceFromGdrUri, refTypeIssues, rejectedRefTypes, DOCUMENT_VALUE_PERMISSIONS, lakePrincipalId, firstCarriedGlobalId, driverKind, EffectNotFoundError, deriveExecutorClassification, validateTag, extractDocumentId, parseStoredDefinition, validateResourceAliasName, labelFor, DefinitionNotFoundError, definitionDocId, SpawnContractsInvalidError, gdrResourcePrefix, RESOURCE_ALIAS_NAME_SOURCE, DefinitionInUseError, isParseableInstant, groupMembershipNames } from "./_chunks-es/invariants.js";
1
+ import { rethrowWithContext, findOpenStageEntry, findCurrentActivityEntry, andConditions, deriveActivityKind, CALLER_BOUND_VARS, START_REQUIREMENT_VARS, CONDITION_VARS, isSubjectEntry, isUnevaluable, ContractViolationError, isStartableDefinition, conditionFieldReadNames, isGdr, errorMessage, conditionParameterNames, START_FILTER_VARS, WorkflowError, gdrFromResource, selfGdr, isGdrUri, MUTATION_GUARD_ID_SPACES, mutationGuardActionIdSpace, LAKE_MUTATION_GUARD_ACTIONS, isBareDocumentId, toBareId, normalizeAssignmentMembers, isSingleDocRefKind, actorMatchesAssignment, assignmentMembers, resolvedChildStatus, sameResource, resourceFromParsed, tryParseGdr, isTerminalActivityStatus, FieldValueShapeError, validateFieldValue, validateFieldAppendItem, isAssignmentFieldEntry, isAlwaysArrayFieldKind, evaluateCondition, isRecord, isSingleDocRefEntry, choiceValueIssues, scalarValidationIssues, StoredFieldOpSchema, formatIssues, conditionSyntaxIssues, checkWorkflowInvariants, formatIssuePath, isGuardReadExpr, GUARD_ACTIONS_REQUIRED_MESSAGE, conditionEffectReads, EFFECTS_READ, datasetResourceParts, refKindAcceptsTypes, mutationGuardRequiresSplitEmission, MUTATION_GUARD_ACTIONS, assignmentKindAcceptsRoles, evaluatePredicates, WORKFLOW_DEFINITION_TYPE, tagScopeFilter, liveSubworkflows, isCascadeFired, classifyPrincipalId, directoryBridgeId, parseGdr, gdrRef, isInputSourced, parseFieldValue, VersionSpecificDatasetGdrError, toPhysicalGdr, isBareSeedId, isParseableInstant, NonEmptyString, tolerantEntries, FIELD_VALUE_KINDS, tolerantObject, ActorShape, IsoTimestamp, ACTIVITY_STATUSES, GdrShape, DRIVER_KINDS, FIELD_SCOPES, fieldValueSchemas, parsePersistedDoc, ACTOR_KINDS, InstanceNotFoundError, evaluateConditionOutcome, runGroq as runGroq$1, FIELD_READ, resourceGdr, condemnSubworkflow, resourceFromGdrUri, refTypeIssues, rejectedRefTypes, DOCUMENT_VALUE_PERMISSIONS, lakePrincipalId, firstCarriedGlobalId, driverKind, actorFulfillsRole, condemnedSubworkflows, EffectNotFoundError, isFilterScopedOut, deriveExecutorClassification, validateTag, activeAssignmentMembers, extractDocumentId, UNIVERSAL_ROLE_ALIAS_KEY, parseStoredDefinition, validateResourceAliasName, labelFor, DefinitionNotFoundError, definitionDocId, SpawnContractsInvalidError, gdrResourcePrefix, RESOURCE_ALIAS_NAME_SOURCE, DefinitionInUseError, effectBackoffMs, groupMembershipNames } from "./_chunks-es/invariants.js";
2
2
 
3
- import { ACTION_SEMANTICS, ACTIVITY_KINDS, ANONYMOUS_IDENTITY, DECISION_SEMANTICS, DEFAULT_TRANSITION_WHEN, EXECUTOR_CLASSIFICATIONS, FILTER_SCOPE_VARS, GROUP_KINDS, GUARD_PREDICATE_VARS, PersistedDocShapeError, RESERVED_CONDITION_VARS, SIGNAL_SEMANTICS, SYSTEM_IDENTITY, clientConfigFromResource, gdrUri, isNotesEntry, isTodoListEntry, isTodoListItem, parseResourceGdr, readsRootDocument, refCanvas, refDashboard, refDataset, refKindAcceptsTypes, refMediaLibrary, releaseDocId, releaseRef, resourceAliasesToMap, schemaTreeShape, startKindOf } from "./_chunks-es/invariants.js";
3
+ import { ACTION_SEMANTICS, ACTIVITY_KINDS, ANONYMOUS_IDENTITY, DECISION_SEMANTICS, DEFAULT_TRANSITION_WHEN, EXECUTOR_CLASSIFICATIONS, FILTER_SCOPE_VARS, GROUP_KINDS, GUARD_PREDICATE_VARS, PersistedDocShapeError, RESERVED_CONDITION_VARS, SIGNAL_SEMANTICS, SYSTEM_IDENTITY, assignmentMatch, assignmentState, assignmentStateCounts, clientConfigFromResource, gdrUri, identityMatchesAssignment, instanceAssignmentStateCounts, isNotesEntry, isTodoListEntry, isTodoListItem, openActivityAssignments, parseResourceGdr, readsRootDocument, refCanvas, refDashboard, refDataset, refMediaLibrary, releaseDocId, releaseRef, resourceAliasesToMap, schemaTreeShape, startKindOf } from "./_chunks-es/invariants.js";
4
4
 
5
- import { atomNode, dedupeBy, analyzeCondition, atomReadsDataset, checklistLines as checklistLines$1, phrase, quoted, listAnd, describeAtom as describeAtom$1, describeCondition as describeCondition$1, guillemets, formatValue, humanize, listOr, scopeReadOf, describeRead, explainCondition, dedupeReads, whatIfCondition, MAX_COUNTERFACTUAL_INDEX } from "@sanity/groq-condition-describe";
5
+ import { atomNode, dedupeBy, analyzeCondition, atomReadsDataset, checklistLines as checklistLines$1, phrase, quoted, listAnd, describeAtom as describeAtom$1, describeCondition as describeCondition$1, guillemets, formatValue, humanize, listOr, scopeReadOf, describeRead, explainCondition, _exhaustiveOptions, whatIfCondition, runGroq, formatRead, dedupeReads, MAX_COUNTERFACTUAL_INDEX } from "@sanity/groq-condition-describe";
6
6
 
7
- import { MAX_COUNTERFACTUAL_INDEX as MAX_COUNTERFACTUAL_INDEX2, NEUTRAL_MARK, OUTCOME_MARKS, analyzeCondition as analyzeCondition2, atomReadsDataset as atomReadsDataset2, explainCondition as explainCondition2, formatRead, guillemets as guillemets2, humanize as humanize2, isComparisonOp, quoted as quoted2, sentenceCase, whatIfCondition as whatIfCondition2, withAssignment } from "@sanity/groq-condition-describe";
7
+ import { MAX_COUNTERFACTUAL_INDEX as MAX_COUNTERFACTUAL_INDEX2, NEUTRAL_MARK, OUTCOME_MARKS, analyzeCondition as analyzeCondition2, atomReadsDataset as atomReadsDataset2, explainCondition as explainCondition2, formatRead as formatRead2, guillemets as guillemets2, humanize as humanize2, isComparisonOp, quoted as quoted2, sentenceCase, whatIfCondition as whatIfCondition2, withAssignment } from "@sanity/groq-condition-describe";
8
8
 
9
9
  import { evaluate, parse } from "groq-js";
10
10
 
11
- import * as v from "valibot";
12
-
13
- function isFilterScopedOut(entry) {
14
- return entry.status === "skipped" && entry.startedAt === void 0;
15
- }
16
-
17
- function liveSubworkflows(host) {
18
- return (host.subworkflows ?? []).filter(row => row.resolved === void 0);
19
- }
20
-
21
- function resolvedChildStatus(child) {
22
- if (!(child.completedAt === void 0 || child.completedAt === null)) return child.abortedAt !== void 0 && child.abortedAt !== null ? "aborted" : "done";
23
- }
24
-
25
- function condemnedSubworkflows(host) {
26
- return liveSubworkflows(host).filter(row => row.abortPending !== void 0);
27
- }
28
-
29
- function condemnSubworkflow(row, owed) {
30
- row.abortPending !== void 0 || row.resolved !== void 0 || (row.abortPending = {
31
- at: owed.at,
32
- reason: owed.reason
33
- });
34
- }
11
+ import { getPublishedId, getDraftId, DocumentId } from "@sanity/id-utils";
35
12
 
36
- function findOpenStageEntry(host) {
37
- return host.stages.find(s => s.name === host.currentStage && s.exitedAt === void 0);
38
- }
39
-
40
- function findCurrentActivityEntry(host, activityName) {
41
- return findOpenStageEntry(host)?.activities.find(a => a.name === activityName);
42
- }
13
+ import * as v from "valibot";
43
14
 
44
15
  const WORKFLOW_INSTANCE_TYPE = "sanity.workflow.instance";
45
16
 
@@ -98,6 +69,10 @@ function effectiveEditable(baseline, override) {
98
69
 
99
70
  const DEFAULT_EDIT_MODE = "set";
100
71
 
72
+ function _fieldTargetLabel(target) {
73
+ return [ target.scope, target.activity, target.field ].filter(Boolean).join(".");
74
+ }
75
+
101
76
  function fieldSites(definition, stage) {
102
77
  const sites = [];
103
78
  for (const entry of definition.fields ?? []) sites.push({
@@ -458,6 +433,11 @@ function rolesGateOf(atom) {
458
433
  if (!(count.type !== "FuncCall" || count.name !== "count" || count.args.length !== 1)) return rolesFilterList(count.args[0]);
459
434
  }
460
435
 
436
+ function isAssignedGate(atom) {
437
+ const node = atomNode(atom);
438
+ return !atom.negated && node.type === "Parameter" && node.name === "assigned";
439
+ }
440
+
461
441
  function rolesFilterList(node) {
462
442
  if (node.type !== "Filter") return;
463
443
  const base = node.base;
@@ -1069,6 +1049,7 @@ function describeSiteHeading(address, ctx) {
1069
1049
  }
1070
1050
 
1071
1051
  case "action":
1052
+ case "action-when":
1072
1053
  {
1073
1054
  const title = actionTitle(address, ctx), activity = activityTitle(address.activity, ctx);
1074
1055
  return phrase(id, {
@@ -1076,7 +1057,7 @@ function describeSiteHeading(address, ctx) {
1076
1057
  title: title,
1077
1058
  activity: activity
1078
1059
  },
1079
- text: `action ${quoted(title)} on ${quoted(activity)} is available when:`
1060
+ text: `action ${quoted(title)} on ${quoted(activity)} ${address.kind === "action" ? "is available" : "fires automatically"} when:`
1080
1061
  });
1081
1062
  }
1082
1063
 
@@ -1103,18 +1084,6 @@ function describeSiteHeading(address, ctx) {
1103
1084
  });
1104
1085
  }
1105
1086
 
1106
- case "action-when":
1107
- {
1108
- const title = actionTitle(address, ctx), activity = activityTitle(address.activity, ctx);
1109
- return phrase(id, {
1110
- params: {
1111
- title: title,
1112
- activity: activity
1113
- },
1114
- text: `action ${quoted(title)} on ${quoted(activity)} fires automatically when:`
1115
- });
1116
- }
1117
-
1118
1087
  case "editable-field":
1119
1088
  {
1120
1089
  const field = fieldTitleOf({
@@ -1158,6 +1127,7 @@ function describeSite(site, ctx) {
1158
1127
  }
1159
1128
 
1160
1129
  case "action":
1130
+ case "action-when":
1161
1131
  {
1162
1132
  const title = actionTitle(site, ctx), activity = activityTitle(site.activity, ctx);
1163
1133
  return phrase(id, {
@@ -1165,7 +1135,7 @@ function describeSite(site, ctx) {
1165
1135
  title: title,
1166
1136
  activity: activity
1167
1137
  },
1168
- text: `the ${quoted(title)} action on ${quoted(activity)}`
1138
+ text: `the ${quoted(title)} ${site.kind === "action" ? "action" : "trigger"} on ${quoted(activity)}`
1169
1139
  });
1170
1140
  }
1171
1141
 
@@ -1181,18 +1151,6 @@ function describeSite(site, ctx) {
1181
1151
  });
1182
1152
  }
1183
1153
 
1184
- case "action-when":
1185
- {
1186
- const title = actionTitle(site, ctx), activity = activityTitle(site.activity, ctx);
1187
- return phrase(id, {
1188
- params: {
1189
- title: title,
1190
- activity: activity
1191
- },
1192
- text: `the ${quoted(title)} trigger on ${quoted(activity)}`
1193
- });
1194
- }
1195
-
1196
1154
  case "activity-filter":
1197
1155
  {
1198
1156
  const activity = activityTitle(site.activity, ctx);
@@ -1250,10 +1208,10 @@ function unsatisfiedTransitionSummaries(evaluation) {
1250
1208
  definition: evaluation.definition
1251
1209
  };
1252
1210
  return evaluation.currentStage.transitions.filter(t => !t.whenSatisfied).flatMap(t => {
1253
- const summary = describeCondition(t.insight, ctx).summary;
1254
- return summary !== void 0 ? [ {
1211
+ const summary2 = describeCondition(t.insight, ctx).summary;
1212
+ return summary2 !== void 0 ? [ {
1255
1213
  transition: t.transition.name,
1256
- summary: summary
1214
+ summary: summary2
1257
1215
  } ] : [];
1258
1216
  });
1259
1217
  }
@@ -1746,27 +1704,38 @@ function startContextSyntaxIssues({groq: groq, vars: vars, context: context, ski
1746
1704
  return [ ...conditionParameterNames(groq) ].filter(name => !bound.has(name) && name !== skip).map(name => `reads $${name}, which the ${context} context does not bind (an unbound variable is GROQ null, so the predicate silently misevaluates — failing closed or passing vacuously by shape). Bound here: ` + vars.map(v2 => `$${v2.name}`).join(", "));
1747
1705
  }
1748
1706
 
1749
- function getPath(value, path) {
1750
- let current = value;
1751
- for (const part of path.split(".")) {
1752
- if (current == null || typeof current != "object") return;
1753
- current = current[part];
1707
+ const GUARD_DOC_TYPE = "temp.system.guard";
1708
+
1709
+ class MutationGuardDeniedError extends WorkflowError {
1710
+ denied;
1711
+ documentId;
1712
+ action;
1713
+ constructor(args) {
1714
+ const ids = args.denied.map(d => d.guardId).join(", ");
1715
+ super("mutation-guard-denied", `Mutation on "${args.documentId}" (${args.action}) denied by guard(s) [${ids}]`),
1716
+ this.name = "MutationGuardDeniedError", this.denied = args.denied, this.documentId = args.documentId,
1717
+ this.action = args.action;
1718
+ }
1719
+ static fromGuards(args) {
1720
+ return new MutationGuardDeniedError({
1721
+ documentId: args.documentId,
1722
+ action: args.action,
1723
+ denied: deniedGuardRefs(args.guards)
1724
+ });
1754
1725
  }
1755
- return current;
1756
1726
  }
1757
1727
 
1758
- function isReferenceObject(value) {
1759
- return typeof value._ref == "string";
1728
+ function deniedGuardRefs(guards) {
1729
+ return guards.map(g => ({
1730
+ guardId: g._id,
1731
+ ...g.name !== void 0 ? {
1732
+ name: g.name
1733
+ } : {}
1734
+ }));
1760
1735
  }
1761
1736
 
1762
- function walkDocPath(value, path) {
1763
- let current = value;
1764
- for (const part of path.split(".")) {
1765
- if (current == null || typeof current != "object") return;
1766
- if (isReferenceObject(current) && !Object.hasOwn(current, part)) throw new Error(`cannot read "${part}" through the nested reference "${current._ref}" — the workflow only loads references it declares. Declare it as a doc.ref field entry to read through it.`);
1767
- current = current[part];
1768
- }
1769
- return current;
1737
+ function deniedGuardLabels(denied) {
1738
+ return denied.map(d => d.name ?? d.guardId);
1770
1739
  }
1771
1740
 
1772
1741
  function mapJsonStrings(value, transform) {
@@ -1835,179 +1804,638 @@ function overlayInstanceInSnapshot(snapshot, instance) {
1835
1804
  };
1836
1805
  }
1837
1806
 
1838
- function resolveFieldRead(args) {
1839
- const {kind: kind, value: value, path: path, snapshot: snapshot, targetKind: targetKind} = args;
1840
- if (isSingleDocRefKind(kind)) {
1841
- if (!isGdr(value)) return null;
1842
- if (path === void 0 && targetKind !== void 0 && isSingleDocRefKind(targetKind)) return value;
1843
- const base = derefBase(value, snapshot);
1844
- return path === void 0 ? base : walkDocPath(base, path);
1845
- }
1846
- return path !== void 0 ? getPath(value, path) : value;
1807
+ function lakeGuardId(args) {
1808
+ return `${GUARD_DOC_TYPE}.${args.instanceDocId}.${args.guardName}`;
1847
1809
  }
1848
1810
 
1849
- function derefBase(ref, snapshot) {
1850
- return (snapshot !== void 0 ? findSnapshotDoc(snapshot, ref.id) : void 0) ?? {
1851
- _id: ref.id,
1852
- _type: ref.type
1853
- };
1811
+ function lakeActionsFor(action) {
1812
+ return action === "publish" ? [ "create", "update" ] : action === "unpublish" ? [ "delete" ] : [ action ];
1854
1813
  }
1855
1814
 
1856
- function buildParams(args) {
1857
- const {instance: instance, now: now, snapshot: snapshot, extra: extra} = args, currentActivities2 = findOpenStageEntry(instance)?.activities ?? [];
1858
- return {
1859
- self: selfGdr(instance),
1860
- fields: renderedFields(instance.fields ?? [], snapshot),
1861
- parent: parentRef(instance)?.id ?? null,
1862
- ancestors: instance.ancestors.map(a => a.id),
1863
- stage: instance.currentStage,
1864
- now: now,
1865
- context: contextMap(instance),
1866
- effects: effectOutputsMap(instance),
1867
- effectStatus: effectStatusMap(instance),
1868
- activities: currentActivities2,
1869
- subworkflows: subworkflowVarRows(instance, snapshot),
1870
- ...activityGateParams(currentActivities2),
1871
- ...extra
1872
- };
1815
+ function lakeGuardActions(actions) {
1816
+ const requested = new Set(actions.flatMap(lakeActionsFor));
1817
+ return LAKE_MUTATION_GUARD_ACTIONS.filter(action => requested.has(action));
1873
1818
  }
1874
1819
 
1875
- function subworkflowVarRows(instance, snapshot) {
1876
- const openKey = findOpenStageEntry(instance)?._key;
1877
- return (instance.subworkflows ?? []).map(row => ({
1878
- _id: toBareId(row.ref.id),
1879
- rowKey: row.rowKey,
1880
- activity: row.activity,
1881
- action: row.action,
1882
- definition: row.definition,
1883
- current: row.stageEntry === openKey,
1884
- ...childState(row, snapshot),
1885
- spawnedAt: row.spawnedAt
1886
- }));
1820
+ function publishedId(id) {
1821
+ return getPublishedId(id);
1887
1822
  }
1888
1823
 
1889
- function childState(row, snapshot) {
1890
- if (row.resolved !== void 0) return {
1891
- stage: row.resolved.stage ?? null,
1892
- status: row.resolved.aborted === !0 ? "aborted" : "done"
1824
+ function draftId(id) {
1825
+ return getDraftId(id);
1826
+ }
1827
+
1828
+ function versionPatternTranslation(pattern) {
1829
+ const documentSeparator = pattern.indexOf(".", 9);
1830
+ if (documentSeparator === -1) return {
1831
+ issue: `id pattern "${pattern}" does not identify a versioned document`
1893
1832
  };
1894
- const doc = snapshot === void 0 ? void 0 : findSnapshotDoc(snapshot, row.ref.id);
1895
- return doc === void 0 ? {
1896
- stage: null,
1897
- status: "active"
1833
+ const documentPattern = pattern.slice(documentSeparator + 1);
1834
+ return /^\*+$/.test(documentPattern) ? {
1835
+ issue: `id pattern "${pattern}" cannot be translated from a release version to a draft or published document without broadening it to every document`
1898
1836
  } : {
1899
- stage: doc.currentStage ?? null,
1900
- status: resolvedChildStatus(doc) ?? "active"
1837
+ documentPattern: documentPattern
1901
1838
  };
1902
1839
  }
1903
1840
 
1904
- function activityGateParams(activities) {
1905
- return {
1906
- allActivitiesDone: activities.every(activity => activity.status === "done" || activity.status === "skipped"),
1907
- anyActivityFailed: activities.some(activity => activity.status === "failed")
1908
- };
1841
+ function publishedPattern(pattern) {
1842
+ if (pattern.startsWith("drafts.")) return pattern.slice(7);
1843
+ if (!pattern.startsWith("versions.")) return pattern;
1844
+ const translated = versionPatternTranslation(pattern);
1845
+ if (translated.issue !== void 0) throw new Error(translated.issue);
1846
+ return translated.documentPattern;
1909
1847
  }
1910
1848
 
1911
- function renderedFields(entries, snapshot) {
1912
- const out = {};
1913
- for (const entry of entries) out[entry.name] = renderedValue(entry, snapshot);
1914
- return out;
1849
+ function draftPattern(pattern) {
1850
+ return `drafts.${publishedPattern(pattern)}`;
1915
1851
  }
1916
1852
 
1917
- function renderedValue(entry, snapshot) {
1918
- return resolveFieldRead({
1919
- kind: entry._type,
1920
- value: entry.value,
1921
- path: void 0,
1922
- snapshot: snapshot
1923
- });
1853
+ function idsInSpace(args) {
1854
+ const {ids: ids, space: space, sourceInstanceId: sourceInstanceId} = args;
1855
+ return space === "authored" ? ids : space === "published" ? [ ...new Set(ids.map(publishedId)) ] : [ ...new Set(ids.map(id => publishedId(id) === sourceInstanceId ? publishedId(id) : draftId(id))) ];
1924
1856
  }
1925
1857
 
1926
- function scopedFieldOverlay({instance: instance, snapshot: snapshot, activityName: activityName}) {
1927
- const stageEntry = findOpenStageEntry(instance);
1928
- if (stageEntry === void 0) return {};
1929
- const stageFields = renderedFields(stageEntry.fields ?? [], snapshot), activity = activityName ? stageEntry.activities.find(t => t.name === activityName) : void 0;
1858
+ function patternsInSpace(patterns, space) {
1859
+ if (space === "authored") return patterns;
1860
+ const convert = space === "edit" ? draftPattern : publishedPattern;
1861
+ return [ ...new Set(patterns.map(convert)) ];
1862
+ }
1863
+
1864
+ function matchInSpace(args) {
1865
+ const {match: match, actions: actions, space: space, sourceInstanceId: sourceInstanceId} = args;
1930
1866
  return {
1931
- ...stageFields,
1932
- ...renderedFields(activity?.fields ?? [], snapshot)
1867
+ ...match,
1868
+ ...match.idRefs !== void 0 ? {
1869
+ idRefs: idsInSpace({
1870
+ ids: match.idRefs,
1871
+ space: space,
1872
+ sourceInstanceId: sourceInstanceId
1873
+ })
1874
+ } : {},
1875
+ ...match.idPatterns !== void 0 ? {
1876
+ idPatterns: patternsInSpace(match.idPatterns, space)
1877
+ } : {},
1878
+ actions: lakeGuardActions(actions)
1933
1879
  };
1934
1880
  }
1935
1881
 
1936
- function assignedFor({instance: instance, activityName: activityName, actor: actor, roleAliases: roleAliases}) {
1937
- if (actor === void 0) return !1;
1938
- const entry = findCurrentActivityEntry(instance, activityName)?.fields?.find(s => s._type === "assignees");
1939
- return entry === void 0 ? !1 : entry.value.some(a => a.type === "user" ? a.id === actor.id : actorFulfillsRole({
1940
- actorRoles: actor.roles,
1941
- required: a.role,
1942
- aliases: roleAliases
1882
+ function guardPartitions(id, actions) {
1883
+ return MUTATION_GUARD_ID_SPACES.flatMap(space => {
1884
+ const matchingActions = actions.filter(action => mutationGuardActionIdSpace(action) === space);
1885
+ return matchingActions.length > 0 ? [ {
1886
+ space: space,
1887
+ actions: matchingActions
1888
+ } ] : [];
1889
+ }).map((partition, index) => ({
1890
+ ...partition,
1891
+ id: index === 0 ? id : `${id}.${partition.space}`
1943
1892
  }));
1944
1893
  }
1945
1894
 
1946
- function effectStatusMap(instance) {
1947
- const entry = findOpenStageEntry(instance);
1948
- if (entry === void 0) return {};
1949
- const out = {};
1950
- for (const run of instance.effectHistory) run.stageEntryKey === entry._key && (out[run.name] = run.status);
1951
- return out;
1895
+ function compiledGuardIds(args) {
1896
+ return guardPartitions(args.id, args.actions).map(partition => partition.id);
1952
1897
  }
1953
1898
 
1954
- function contextMap(instance) {
1955
- const out = {};
1956
- for (const entry of instance.context) out[entry.name] = entry._type === "context.json" ? parseJsonContextEntry(entry) : entry.value;
1957
- return out;
1899
+ function lakeGuardDocument(args) {
1900
+ return {
1901
+ _id: args.id,
1902
+ _type: GUARD_DOC_TYPE,
1903
+ resourceType: args.resourceType,
1904
+ resourceId: args.resourceId,
1905
+ owner: args.owner,
1906
+ sourceInstanceId: args.sourceInstanceId,
1907
+ sourceDefinition: args.sourceDefinition,
1908
+ sourceStage: args.sourceStage,
1909
+ ...args.name !== void 0 ? {
1910
+ name: args.name
1911
+ } : {},
1912
+ ...args.description !== void 0 ? {
1913
+ description: args.description
1914
+ } : {},
1915
+ match: args.match,
1916
+ predicate: args.predicate,
1917
+ metadata: args.metadata
1918
+ };
1958
1919
  }
1959
1920
 
1960
- function effectOutputsMap(instance) {
1961
- const out = {};
1962
- for (const run of instance.effectHistory) run.outputs !== void 0 && (out[run.name] = run.outputs);
1963
- return out;
1921
+ function compileGuards(args) {
1922
+ const [first, ...remaining] = guardPartitions(args.id, args.match.actions);
1923
+ if (first === void 0) throw new Error("cannot compile a guard without an action");
1924
+ const compilePartition = ({space: space, actions: actions, id: id}) => lakeGuardDocument({
1925
+ ...args,
1926
+ id: id,
1927
+ match: matchInSpace({
1928
+ match: args.match,
1929
+ actions: actions,
1930
+ space: space,
1931
+ sourceInstanceId: args.sourceInstanceId
1932
+ })
1933
+ });
1934
+ return [ compilePartition(first), ...remaining.map(compilePartition) ];
1964
1935
  }
1965
1936
 
1966
- function parseJsonContextEntry(entry) {
1967
- try {
1968
- return JSON.parse(entry.value);
1969
- } catch (err) {
1970
- rethrowWithContext(err, `context entry "${entry.name}" holds unparseable JSON`);
1971
- }
1937
+ function globMatch(pattern, value) {
1938
+ if (!pattern.includes("*")) return pattern === value;
1939
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
1940
+ return new RegExp(`^${escaped}$`).test(value);
1972
1941
  }
1973
1942
 
1974
- function paramsForLake(params) {
1975
- return {
1976
- ...params,
1977
- self: typeof params.self == "string" ? toBareId(params.self) : params.self,
1978
- parent: typeof params.parent == "string" ? toBareId(params.parent) : params.parent,
1979
- ancestors: Array.isArray(params.ancestors) ? params.ancestors.map(a => typeof a == "string" ? toBareId(a) : a) : params.ancestors,
1980
- fields: stripFieldsForLake(params.fields)
1981
- };
1943
+ function guardIdPatternIssue(pattern, actions) {
1944
+ const representativeId = pattern.replaceAll("*", "x");
1945
+ if (!isBareDocumentId(representativeId)) return `id pattern "${pattern}" is not a valid resource-local document-id glob; use document-id characters (letters, digits, \`_\`, \`-\`, \`.\`) and \`*\` wildcards`;
1946
+ if (actions?.some(action => mutationGuardActionIdSpace(action) !== "authored") && pattern.startsWith("versions.")) return versionPatternTranslation(pattern).issue;
1982
1947
  }
1983
1948
 
1984
- function stripFieldsForLake(value) {
1985
- return mapJsonStrings(value, s => toBareId(s));
1949
+ function parseGuardPredicate(predicate) {
1950
+ return parse(predicate, {
1951
+ mode: "delta"
1952
+ });
1986
1953
  }
1987
1954
 
1988
- function resolveStaticValueExpr(src, ctx) {
1989
- switch (src.type) {
1990
- case "literal":
1991
- return src.value;
1992
-
1993
- case "param":
1994
- return ctx.params?.[src.param];
1995
-
1996
- case "actor":
1997
- return ctx.actor;
1998
-
1999
- case "now":
2000
- return ctx.now;
1955
+ function guardPredicateSyntaxIssue(predicate) {
1956
+ try {
1957
+ parseGuardPredicate(predicate);
1958
+ return;
1959
+ } catch (error) {
1960
+ return errorMessage(error);
2001
1961
  }
2002
1962
  }
2003
1963
 
2004
- function randomKey(length = 12) {
2005
- const bytes = new Uint8Array(length);
2006
- return globalThis.crypto.getRandomValues(bytes), [ ...bytes ].map(b => b.toString(16).padStart(2, "0")).join("").slice(0, length);
1964
+ function lakeDocumentIdForAction(id, action) {
1965
+ return mutationGuardActionIdSpace(action) === "published" ? publishedId(id) : id;
2007
1966
  }
2008
1967
 
2009
- function instanceDocId(tag) {
2010
- return `${tag}.wf-instance.${randomKey()}`;
1968
+ function legacyLifecycleActionFor(action, documentId) {
1969
+ if (publishedId(documentId) === documentId) {
1970
+ if (action === "create" || action === "update") return "publish";
1971
+ if (action === "delete") return "unpublish";
1972
+ }
1973
+ }
1974
+
1975
+ function guardMatches({guard: guard, doc: doc, action: action}) {
1976
+ const m = guard.match, documentId = lakeDocumentIdForAction(doc.id, action), legacyLifecycleAction = legacyLifecycleActionFor(action, documentId);
1977
+ if (![ action, ...lakeActionsFor(action), legacyLifecycleAction ].some(candidate => candidate !== void 0 && m.actions.includes(candidate)) || m.types && m.types.length > 0 && !(doc.type !== void 0 && m.types.includes(doc.type))) return !1;
1978
+ if (m.idRefs && m.idRefs.length > 0 || m.idPatterns && m.idPatterns.length > 0) {
1979
+ const byRef = m.idRefs?.includes(documentId) ?? !1, byPattern = m.idPatterns?.some(p => globMatch(p, documentId)) ?? !1;
1980
+ if (!byRef && !byPattern) return !1;
1981
+ }
1982
+ return !0;
1983
+ }
1984
+
1985
+ function guardPredicateRoot(args) {
1986
+ return {
1987
+ document: {
1988
+ before: args.context.before,
1989
+ after: args.context.after
1990
+ },
1991
+ guard: args.guard,
1992
+ mutation: {
1993
+ action: args.context.action
1994
+ }
1995
+ };
1996
+ }
1997
+
1998
+ function lakeShapedSnapshotDocument(doc) {
1999
+ return mapJsonStrings(doc, (value, key) => key === "_id" || key === "_ref" ? toBareId(value) : value);
2000
+ }
2001
+
2002
+ function snapshotGuardDereference(args) {
2003
+ return ({_ref: _ref}) => {
2004
+ const lookupId = isGdrUri(_ref) ? _ref : gdrFromResource(args.resource, _ref), found = findSnapshotDoc(args.snapshot, lookupId);
2005
+ return Promise.resolve(found === void 0 ? null : lakeShapedSnapshotDocument(found));
2006
+ };
2007
+ }
2008
+
2009
+ async function evaluateMutationGuard(args) {
2010
+ const {guard: guard, context: context, dereference: dereference} = args;
2011
+ if (guard.predicate === "") return !1;
2012
+ const root = guardPredicateRoot({
2013
+ guard: guard,
2014
+ context: context
2015
+ });
2016
+ let dereferenceFailed = !1;
2017
+ const guardedDereference = dereference === void 0 ? void 0 : async ref => {
2018
+ try {
2019
+ return await dereference(ref);
2020
+ } catch (error) {
2021
+ throw dereferenceFailed = !0, error;
2022
+ }
2023
+ };
2024
+ try {
2025
+ const tree = parseGuardPredicate(guard.predicate);
2026
+ return await (await evaluate(tree, {
2027
+ root: root,
2028
+ before: context.before,
2029
+ after: context.after,
2030
+ ...guardedDereference !== void 0 ? {
2031
+ dataset: [],
2032
+ dereference: guardedDereference
2033
+ } : {},
2034
+ ...context.identity !== void 0 ? {
2035
+ identity: context.identity
2036
+ } : {}
2037
+ })).get() === !0;
2038
+ } catch (error) {
2039
+ if (dereferenceFailed) throw error;
2040
+ return !1;
2041
+ }
2042
+ }
2043
+
2044
+ async function denyingGuards(args) {
2045
+ const {guards: guards, doc: doc, context: context, dereference: dereference} = args, denied = [];
2046
+ for (const guard of guards) guardMatches({
2047
+ guard: guard,
2048
+ doc: doc,
2049
+ action: context.action
2050
+ }) && (await evaluateMutationGuard({
2051
+ guard: guard,
2052
+ context: context,
2053
+ ...dereference !== void 0 ? {
2054
+ dereference: dereference
2055
+ } : {}
2056
+ }) || denied.push(guard));
2057
+ return denied;
2058
+ }
2059
+
2060
+ async function documentActionDenials(args) {
2061
+ const {mutation: mutation, resource: resource, guards: guards, identity: identity, dereference: dereference} = args, document = mutation.after ?? mutation.before;
2062
+ if (document === null) throw new Error("a concrete Lake mutation requires a before or after image");
2063
+ const enforceable = guards.filter(g => g.resourceType === resource.type && g.resourceId === resource.id);
2064
+ return enforceable.length === 0 ? [] : denyingGuards({
2065
+ guards: enforceable,
2066
+ doc: {
2067
+ id: document._id,
2068
+ type: document._type
2069
+ },
2070
+ context: {
2071
+ ...mutation,
2072
+ ...identity !== void 0 ? {
2073
+ identity: identity
2074
+ } : {}
2075
+ },
2076
+ ...dereference !== void 0 ? {
2077
+ dereference: dereference
2078
+ } : {}
2079
+ });
2080
+ }
2081
+
2082
+ async function instanceWriteDenials(args) {
2083
+ const {instance: instance, guards: guards, identity: identity, dereference: dereference} = args;
2084
+ return documentActionDenials({
2085
+ mutation: {
2086
+ before: instance,
2087
+ after: instance,
2088
+ action: "update"
2089
+ },
2090
+ resource: instance.workflowResource,
2091
+ guards: guards,
2092
+ dereference: dereference,
2093
+ ...identity !== void 0 ? {
2094
+ identity: identity
2095
+ } : {}
2096
+ });
2097
+ }
2098
+
2099
+ async function assertInstanceWriteAllowed(args) {
2100
+ const denied = await instanceWriteDenials(args);
2101
+ if (denied.length !== 0) throw MutationGuardDeniedError.fromGuards({
2102
+ documentId: args.instance._id,
2103
+ action: "update",
2104
+ guards: denied
2105
+ });
2106
+ }
2107
+
2108
+ const ASSIGNMENT_LIST_MODEL = 9;
2109
+
2110
+ function isAssignmentListModel(modelVersion) {
2111
+ return modelVersion >= ASSIGNMENT_LIST_MODEL;
2112
+ }
2113
+
2114
+ function definitionAssignmentModel(instance) {
2115
+ if (typeof instance.definitionSnapshot != "string") return 0;
2116
+ const modelVersion = parseDefinitionSnapshotValue({
2117
+ _id: instance._id,
2118
+ definitionSnapshot: instance.definitionSnapshot
2119
+ }).modelVersion;
2120
+ return typeof modelVersion == "number" ? modelVersion : 0;
2121
+ }
2122
+
2123
+ function writesSingularAssignmentLists(instance) {
2124
+ return isAssignmentListModel(definitionAssignmentModel(instance));
2125
+ }
2126
+
2127
+ function legacyAssigneeValue(entry) {
2128
+ return legacyAssignmentValue(entry.value, entry.name);
2129
+ }
2130
+
2131
+ function legacyAssignmentValue(value, path) {
2132
+ if (!Array.isArray(value)) return value;
2133
+ if (value.length === 0) return null;
2134
+ if (value.length === 1) return value[0];
2135
+ throw new ContractViolationError(`legacy assignee field "${path}" cannot persist ${value.length} members; redeploy the definition at the assignment-list reader model before writing a role pool`);
2136
+ }
2137
+
2138
+ function mapShapeValues(args) {
2139
+ const {value: value, shapes: shapes, mapAssignee: mapAssignee, parentPath: parentPath} = args;
2140
+ if (typeof value != "object" || value === null || Array.isArray(value)) return value;
2141
+ const source = value, next = {
2142
+ ...source
2143
+ };
2144
+ for (const shape of shapes) {
2145
+ if (!Object.hasOwn(source, shape.name)) continue;
2146
+ const path = `${parentPath}.${shape.name}`, member = source[shape.name], mapped = mappedShapeValue({
2147
+ shape: shape,
2148
+ member: member,
2149
+ mapAssignee: mapAssignee,
2150
+ path: path
2151
+ });
2152
+ mapped.found && defineOwnValue(next, {
2153
+ name: shape.name,
2154
+ value: mapped.value
2155
+ });
2156
+ }
2157
+ return next;
2158
+ }
2159
+
2160
+ function mappedShapeValue(args) {
2161
+ const {shape: shape, member: member, mapAssignee: mapAssignee, path: path} = args;
2162
+ return shape.type === "assignee" ? {
2163
+ found: !0,
2164
+ value: mapAssignee(member, path)
2165
+ } : shape.type === "object" ? {
2166
+ found: !0,
2167
+ value: mapShapeValues({
2168
+ value: member,
2169
+ shapes: shape.fields ?? [],
2170
+ mapAssignee: mapAssignee,
2171
+ parentPath: path
2172
+ })
2173
+ } : shape.type !== "array" || !Array.isArray(member) ? {
2174
+ found: !1
2175
+ } : {
2176
+ found: !0,
2177
+ value: member.map((row, index) => mapShapeValues({
2178
+ value: row,
2179
+ shapes: shape.of ?? [],
2180
+ mapAssignee: mapAssignee,
2181
+ parentPath: `${path}[${index}]`
2182
+ }))
2183
+ };
2184
+ }
2185
+
2186
+ function defineOwnValue(target, property) {
2187
+ Object.defineProperty(target, property.name, {
2188
+ configurable: !0,
2189
+ enumerable: !0,
2190
+ value: property.value,
2191
+ writable: !0
2192
+ });
2193
+ }
2194
+
2195
+ function mapNestedAssignmentValues(entry, mapAssignee) {
2196
+ return entry._type === "object" ? mapShapeValues({
2197
+ value: entry.value,
2198
+ shapes: entry.fields,
2199
+ mapAssignee: mapAssignee,
2200
+ parentPath: entry.name
2201
+ }) : entry.value.map((row, index) => mapShapeValues({
2202
+ value: row,
2203
+ shapes: entry.of,
2204
+ mapAssignee: mapAssignee,
2205
+ parentPath: `${entry.name}[${index}]`
2206
+ }));
2207
+ }
2208
+
2209
+ function normalizeNestedAssignmentValues(entry) {
2210
+ return entry._type !== "object" && entry._type !== "array" ? entry : {
2211
+ ...entry,
2212
+ value: mapNestedAssignmentValues(entry, normalizeAssignmentMembers)
2213
+ };
2214
+ }
2215
+
2216
+ function assignmentFieldsForWrite(fields, listModel) {
2217
+ return listModel ? fields : fields.map(entry => entry._type === "assignee" ? {
2218
+ ...entry,
2219
+ value: legacyAssigneeValue(entry)
2220
+ } : entry._type === "object" || entry._type === "array" ? {
2221
+ ...entry,
2222
+ value: mapNestedAssignmentValues(entry, legacyAssignmentValue)
2223
+ } : entry);
2224
+ }
2225
+
2226
+ function activityForWrite(activity, listModel) {
2227
+ return {
2228
+ ...activity,
2229
+ ...activity.fields === void 0 ? {} : {
2230
+ fields: assignmentFieldsForWrite(activity.fields, listModel)
2231
+ }
2232
+ };
2233
+ }
2234
+
2235
+ function assignmentStagesForWrite(stages, listModel) {
2236
+ return stages.map(stage => ({
2237
+ ...stage,
2238
+ fields: assignmentFieldsForWrite(stage.fields ?? [], listModel),
2239
+ activities: stage.activities.map(activity => activityForWrite(activity, listModel))
2240
+ }));
2241
+ }
2242
+
2243
+ function getPath(value, path) {
2244
+ let current = value;
2245
+ for (const part of path.split(".")) {
2246
+ if (current == null || typeof current != "object") return;
2247
+ current = current[part];
2248
+ }
2249
+ return current;
2250
+ }
2251
+
2252
+ function isReferenceObject(value) {
2253
+ return typeof value._ref == "string";
2254
+ }
2255
+
2256
+ function walkDocPath(value, path) {
2257
+ let current = value;
2258
+ for (const part of path.split(".")) {
2259
+ if (current == null || typeof current != "object") return;
2260
+ if (isReferenceObject(current) && !Object.hasOwn(current, part)) throw new Error(`cannot read "${part}" through the nested reference "${current._ref}" — the workflow only loads references it declares. Declare it as a doc.ref field entry to read through it.`);
2261
+ current = current[part];
2262
+ }
2263
+ return current;
2264
+ }
2265
+
2266
+ function resolveFieldRead(args) {
2267
+ const {kind: kind, value: value, path: path, snapshot: snapshot, targetKind: targetKind} = args;
2268
+ if (isSingleDocRefKind(kind)) {
2269
+ if (!isGdr(value)) return null;
2270
+ if (path === void 0 && targetKind !== void 0 && isSingleDocRefKind(targetKind)) return value;
2271
+ const base = derefBase(value, snapshot);
2272
+ return path === void 0 ? base : walkDocPath(base, path);
2273
+ }
2274
+ return path !== void 0 ? getPath(value, path) : value;
2275
+ }
2276
+
2277
+ function derefBase(ref, snapshot) {
2278
+ return (snapshot !== void 0 ? findSnapshotDoc(snapshot, ref.id) : void 0) ?? {
2279
+ _id: ref.id,
2280
+ _type: ref.type
2281
+ };
2282
+ }
2283
+
2284
+ function buildParams(args) {
2285
+ const {instance: instance, now: now, snapshot: snapshot, extra: extra} = args, currentActivities2 = findOpenStageEntry(instance)?.activities ?? [];
2286
+ return {
2287
+ self: selfGdr(instance),
2288
+ fields: renderedFields(instance.fields ?? [], snapshot),
2289
+ parent: parentRef(instance)?.id ?? null,
2290
+ ancestors: instance.ancestors.map(a => a.id),
2291
+ stage: instance.currentStage,
2292
+ now: now,
2293
+ context: contextMap(instance),
2294
+ effects: effectOutputsMap(instance),
2295
+ effectStatus: effectStatusMap(instance),
2296
+ activities: currentActivities2,
2297
+ subworkflows: subworkflowVarRows(instance, snapshot),
2298
+ ...activityGateParams(currentActivities2),
2299
+ ...extra
2300
+ };
2301
+ }
2302
+
2303
+ function subworkflowVarRows(instance, snapshot) {
2304
+ const openKey = findOpenStageEntry(instance)?._key;
2305
+ return (instance.subworkflows ?? []).map(row => ({
2306
+ _id: toBareId(row.ref.id),
2307
+ rowKey: row.rowKey,
2308
+ activity: row.activity,
2309
+ action: row.action,
2310
+ definition: row.definition,
2311
+ current: row.stageEntry === openKey,
2312
+ ...childState(row, snapshot),
2313
+ spawnedAt: row.spawnedAt
2314
+ }));
2315
+ }
2316
+
2317
+ function childState(row, snapshot) {
2318
+ if (row.resolved !== void 0) return {
2319
+ stage: row.resolved.stage ?? null,
2320
+ status: row.resolved.aborted === !0 ? "aborted" : "done"
2321
+ };
2322
+ const doc = snapshot === void 0 ? void 0 : findSnapshotDoc(snapshot, row.ref.id);
2323
+ return doc === void 0 ? {
2324
+ stage: null,
2325
+ status: "active"
2326
+ } : {
2327
+ stage: doc.currentStage ?? null,
2328
+ status: resolvedChildStatus(doc) ?? "active"
2329
+ };
2330
+ }
2331
+
2332
+ function activityGateParams(activities) {
2333
+ return {
2334
+ allActivitiesDone: activities.every(activity => activity.status === "done" || activity.status === "skipped"),
2335
+ anyActivityFailed: activities.some(activity => activity.status === "failed")
2336
+ };
2337
+ }
2338
+
2339
+ function renderedFields(entries, snapshot) {
2340
+ const out = {};
2341
+ for (const entry of entries) out[entry.name] = renderedValue(entry, snapshot);
2342
+ return out;
2343
+ }
2344
+
2345
+ function renderedValue(entry, snapshot) {
2346
+ return resolveFieldRead({
2347
+ kind: entry._type,
2348
+ value: entry.value,
2349
+ path: void 0,
2350
+ snapshot: snapshot
2351
+ });
2352
+ }
2353
+
2354
+ function scopedFieldOverlay({instance: instance, snapshot: snapshot, activityName: activityName}) {
2355
+ const stageEntry = findOpenStageEntry(instance);
2356
+ if (stageEntry === void 0) return {};
2357
+ const stageFields = renderedFields(stageEntry.fields ?? [], snapshot), activity = activityName ? stageEntry.activities.find(t => t.name === activityName) : void 0;
2358
+ return {
2359
+ ...stageFields,
2360
+ ...renderedFields(activity?.fields ?? [], snapshot)
2361
+ };
2362
+ }
2363
+
2364
+ function assignedFor({instance: instance, activityName: activityName, actor: actor, roleAliases: roleAliases}) {
2365
+ if (actor === void 0) return !1;
2366
+ const activity = findCurrentActivityEntry(instance, activityName);
2367
+ return actorMatchesAssignment({
2368
+ actor: actor,
2369
+ members: assignmentMembers(activity?.fields ?? []),
2370
+ roleAliases: roleAliases
2371
+ });
2372
+ }
2373
+
2374
+ function effectStatusMap(instance) {
2375
+ const entry = findOpenStageEntry(instance);
2376
+ if (entry === void 0) return {};
2377
+ const out = {};
2378
+ for (const run of instance.effectHistory) run.stageEntryKey === entry._key && (out[run.name] = run.status);
2379
+ return out;
2380
+ }
2381
+
2382
+ function contextMap(instance) {
2383
+ const out = {};
2384
+ for (const entry of instance.context) out[entry.name] = entry._type === "context.json" ? parseJsonContextEntry(entry) : entry.value;
2385
+ return out;
2386
+ }
2387
+
2388
+ function effectOutputsMap(instance) {
2389
+ const out = {};
2390
+ for (const run of instance.effectHistory) run.outputs !== void 0 && (out[run.name] = run.outputs);
2391
+ return out;
2392
+ }
2393
+
2394
+ function parseJsonContextEntry(entry) {
2395
+ try {
2396
+ return JSON.parse(entry.value);
2397
+ } catch (err) {
2398
+ rethrowWithContext(err, `context entry "${entry.name}" holds unparseable JSON`);
2399
+ }
2400
+ }
2401
+
2402
+ function paramsForLake(params) {
2403
+ return {
2404
+ ...params,
2405
+ self: typeof params.self == "string" ? toBareId(params.self) : params.self,
2406
+ parent: typeof params.parent == "string" ? toBareId(params.parent) : params.parent,
2407
+ ancestors: Array.isArray(params.ancestors) ? params.ancestors.map(a => typeof a == "string" ? toBareId(a) : a) : params.ancestors,
2408
+ fields: stripFieldsForLake(params.fields)
2409
+ };
2410
+ }
2411
+
2412
+ function stripFieldsForLake(value) {
2413
+ return mapJsonStrings(value, s => toBareId(s));
2414
+ }
2415
+
2416
+ function resolveStaticValueExpr(src, ctx) {
2417
+ switch (src.type) {
2418
+ case "literal":
2419
+ return src.value;
2420
+
2421
+ case "param":
2422
+ return ctx.params?.[src.param];
2423
+
2424
+ case "actor":
2425
+ return ctx.actor;
2426
+
2427
+ case "now":
2428
+ return ctx.now;
2429
+ }
2430
+ }
2431
+
2432
+ function randomKey(length = 12) {
2433
+ const bytes = new Uint8Array(length);
2434
+ return globalThis.crypto.getRandomValues(bytes), [ ...bytes ].map(b => b.toString(16).padStart(2, "0")).join("").slice(0, length);
2435
+ }
2436
+
2437
+ function instanceDocId(tag) {
2438
+ return `${tag}.wf-instance.${randomKey()}`;
2011
2439
  }
2012
2440
 
2013
2441
  function mapFieldRefValues(args) {
@@ -2254,10 +2682,12 @@ class WorkflowStateDivergedError extends WorkflowError {
2254
2682
  class PartialGuardDeployError extends WorkflowError {
2255
2683
  stageName;
2256
2684
  deployed;
2685
+ rollbackError;
2257
2686
  constructor(args) {
2258
2687
  super("partial-guard-deploy", `Partial guard deploy on stage "${args.stageName}": ${args.deployed} guard(s) deployed before a later one failed.`, {
2259
2688
  cause: args.cause
2260
- }), this.name = "PartialGuardDeployError", this.stageName = args.stageName, this.deployed = args.deployed;
2689
+ }), this.name = "PartialGuardDeployError", this.stageName = args.stageName, this.deployed = args.deployed,
2690
+ args.rollbackError !== void 0 && (this.rollbackError = args.rollbackError);
2261
2691
  }
2262
2692
  }
2263
2693
 
@@ -2298,7 +2728,23 @@ function staleClaimDetail(reason) {
2298
2728
  const CONCURRENT_COMMIT_MAX_ATTEMPTS = 3;
2299
2729
 
2300
2730
  function lostRaceMessage(args) {
2301
- 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.`;
2731
+ return `${args.what} (instance ${args.instanceId}) lost the optimistic-locking race ${args.attempts} attempts running — a concurrent writer kept committing first. ${args.recovery ?? "Re-read and retry, or investigate a write storm on this instance."}`;
2732
+ }
2733
+
2734
+ function isRetryableInstanceCommitConflict(error) {
2735
+ return !isCreateIdCollision(error) && isRevisionConflict(error);
2736
+ }
2737
+
2738
+ class InstanceCommitRevisionConflict extends Error {
2739
+ constructor(cause) {
2740
+ super("Instance commit lost its revision fence", {
2741
+ cause: cause
2742
+ }), this.name = "InstanceCommitRevisionConflict";
2743
+ }
2744
+ }
2745
+
2746
+ function isInstanceCommitRevisionConflict(error) {
2747
+ return error instanceof InstanceCommitRevisionConflict;
2302
2748
  }
2303
2749
 
2304
2750
  class ConcurrentFireActionError extends WorkflowError {
@@ -2316,6 +2762,19 @@ class ConcurrentFireActionError extends WorkflowError {
2316
2762
  }
2317
2763
  }
2318
2764
 
2765
+ class ConcurrentCascadeError extends WorkflowError {
2766
+ instanceId;
2767
+ attempts;
2768
+ constructor(args) {
2769
+ super("concurrent-cascade", lostRaceMessage({
2770
+ what: "Automatic cascade",
2771
+ instanceId: args.instanceId,
2772
+ attempts: args.attempts,
2773
+ recovery: "An earlier verb commit may already have landed. Re-read the instance and invoke tick after contention subsides."
2774
+ })), this.name = "ConcurrentCascadeError", this.instanceId = args.instanceId, this.attempts = args.attempts;
2775
+ }
2776
+ }
2777
+
2319
2778
  function isRevisionConflict(error) {
2320
2779
  if (typeof error != "object" || error === null) return !1;
2321
2780
  const {statusCode: statusCode, message: message} = error;
@@ -2382,8 +2841,8 @@ class CascadeLimitError extends WorkflowError {
2382
2841
 
2383
2842
  const FIELD_OP_TYPES = [ "field.set", "field.setIfMissing", "field.unset", "field.append", "field.inc", "field.dec", "field.updateWhere", "field.removeWhere" ];
2384
2843
 
2385
- function isFieldOp(summary) {
2386
- return FIELD_OP_TYPES.includes(summary.opType);
2844
+ function isFieldOp(summary2) {
2845
+ return FIELD_OP_TYPES.includes(summary2.opType);
2387
2846
  }
2388
2847
 
2389
2848
  function validateActionParams({action: action, activityName: activityName, callerParams: callerParams}) {
@@ -2462,7 +2921,7 @@ async function runOps(args) {
2462
2921
  if (ops === void 0 || ops.length === 0) return [];
2463
2922
  const opsFromDefinition = origin.effect === void 0 && origin.edit !== !0, summaries = [];
2464
2923
  for (const op of ops) {
2465
- const summary = await applyOp(op, {
2924
+ const summary2 = await applyOp(op, {
2466
2925
  mutation: mutation,
2467
2926
  stage: stage,
2468
2927
  activityName: origin.activity,
@@ -2476,9 +2935,9 @@ async function runOps(args) {
2476
2935
  memberRoles: args.memberRoles,
2477
2936
  roleAliases: args.roleAliases
2478
2937
  });
2479
- summaries.push(summary), shouldRecordOpApplied(summary) && mutation.history.push(opAppliedEntry({
2938
+ summaries.push(summary2), shouldRecordOpApplied(summary2) && mutation.history.push(opAppliedEntry({
2480
2939
  origin: origin,
2481
- summary: summary,
2940
+ summary: summary2,
2482
2941
  stage: stage,
2483
2942
  actor: actor,
2484
2943
  now: now
@@ -2487,12 +2946,12 @@ async function runOps(args) {
2487
2946
  return summaries;
2488
2947
  }
2489
2948
 
2490
- function shouldRecordOpApplied(summary) {
2491
- return summary.opType !== "field.setIfMissing" || summary.resolved !== void 0;
2949
+ function shouldRecordOpApplied(summary2) {
2950
+ return summary2.opType !== "field.setIfMissing" || summary2.resolved !== void 0;
2492
2951
  }
2493
2952
 
2494
2953
  function opAppliedEntry(args) {
2495
- const {origin: origin, summary: summary, stage: stage, actor: actor, now: now} = args;
2954
+ const {origin: origin, summary: summary2, stage: stage, actor: actor, now: now} = args;
2496
2955
  return {
2497
2956
  _key: randomKey(),
2498
2957
  _type: "opApplied",
@@ -2510,12 +2969,12 @@ function opAppliedEntry(args) {
2510
2969
  ...origin.effect !== void 0 ? {
2511
2970
  effect: origin.effect
2512
2971
  } : {},
2513
- opType: summary.opType,
2514
- ...summary.target !== void 0 ? {
2515
- target: summary.target
2972
+ opType: summary2.opType,
2973
+ ...summary2.target !== void 0 ? {
2974
+ target: summary2.target
2516
2975
  } : {},
2517
- ...summary.resolved !== void 0 ? {
2518
- resolved: summary.resolved
2976
+ ...summary2.resolved !== void 0 ? {
2977
+ resolved: summary2.resolved
2519
2978
  } : {},
2520
2979
  ...actor !== void 0 ? {
2521
2980
  actor: actor
@@ -2598,11 +3057,15 @@ function applyFieldSet(op, ctx) {
2598
3057
  ...entryShape(entry),
2599
3058
  memberRoles: ctx.memberRoles,
2600
3059
  roleAliases: ctx.roleAliases,
2601
- ...entry._type === "assignees" || entry._type === "object" || entry._type === "array" ? {
3060
+ ...entry._type === "assignee" || entry._type === "assignees" || entry._type === "object" || entry._type === "array" ? {
2602
3061
  previousValue: entry.value
2603
3062
  } : {}
2604
3063
  });
2605
- return assertRuntimeRefsWithinSurface({
3064
+ return assertLegacySingularValue({
3065
+ entry: entry,
3066
+ value: validated,
3067
+ ctx: ctx
3068
+ }), assertRuntimeRefsWithinSurface({
2606
3069
  entryType: entry._type,
2607
3070
  entryName: entry.name,
2608
3071
  value: validated,
@@ -2619,12 +3082,12 @@ function applyFieldSet(op, ctx) {
2619
3082
  }
2620
3083
 
2621
3084
  function applyFieldSetIfMissing(op, ctx) {
2622
- const entry = locateEntry(ctx, op.target);
2623
- return isAlwaysArrayFieldKind(entry._type) && rejectFieldOpTarget({
3085
+ const entry = locateEntry(ctx, op.target), legacySingular = isLegacySingularAssignment(entry, ctx);
3086
+ return isAlwaysArrayFieldKind(entry._type) && !legacySingular && rejectFieldOpTarget({
2624
3087
  entry: entry,
2625
3088
  op: op,
2626
3089
  issue: "is always array-valued — setIfMissing applies to nullable entries only; an empty array entry already holds []"
2627
- }), entry.value !== null && entry.value !== void 0 ? {
3090
+ }), entry.value !== null && entry.value !== void 0 && !(legacySingular && Array.isArray(entry.value) && entry.value.length === 0) ? {
2628
3091
  opType: op.type,
2629
3092
  target: op.target
2630
3093
  } : {
@@ -2744,7 +3207,14 @@ function applyFieldUnset(op, ctx) {
2744
3207
  }
2745
3208
 
2746
3209
  function applyFieldAppend(op, ctx) {
2747
- const entry = locateEntry(ctx, op.target), slot = appendItemSlot(entry), item = resolveOpValue({
3210
+ const entry = locateEntry(ctx, op.target);
3211
+ if (isLegacySingularAssignment(entry, ctx)) throw new FieldValueShapeError({
3212
+ entryType: entry._type,
3213
+ entryName: entry.name,
3214
+ mode: "item",
3215
+ issues: [ "legacy singular assignee entries do not support append; redeploy at model 9" ]
3216
+ });
3217
+ const slot = appendItemSlot(entry), item = resolveOpValue({
2748
3218
  src: op.value,
2749
3219
  ctx: ctx,
2750
3220
  ...slot !== void 0 ? {
@@ -2756,7 +3226,7 @@ function applyFieldAppend(op, ctx) {
2756
3226
  item: item,
2757
3227
  types: entry._type === "doc.refs" ? entry.types : void 0,
2758
3228
  of: entry._type === "array" ? entry.of : void 0,
2759
- roles: entry._type === "assignees" ? entry.roles : void 0,
3229
+ roles: isAssignmentFieldEntry(entry) ? entry.roles : void 0,
2760
3230
  memberRoles: ctx.memberRoles,
2761
3231
  roleAliases: ctx.roleAliases
2762
3232
  });
@@ -2768,8 +3238,16 @@ function applyFieldAppend(op, ctx) {
2768
3238
  ctx: ctx,
2769
3239
  src: op.value
2770
3240
  });
2771
- const current = Array.isArray(entry.value) ? entry.value : [];
2772
- return setEntryValue(entry, [ ...current, withRowKey(validated) ]), {
3241
+ const next = [ ...Array.isArray(entry.value) ? entry.value : [], withRowKey(validated) ], value = isAssignmentFieldEntry(entry) ? validateFieldValue({
3242
+ entryType: entry._type,
3243
+ entryName: entry.name,
3244
+ value: next,
3245
+ ...assignmentEntryShape(entry),
3246
+ previousValue: entry.value,
3247
+ memberRoles: ctx.memberRoles,
3248
+ roleAliases: ctx.roleAliases
3249
+ }) : next;
3250
+ return setEntryValue(entry, value), {
2773
3251
  opType: op.type,
2774
3252
  target: op.target,
2775
3253
  resolved: {
@@ -2846,7 +3324,14 @@ async function applyFieldUpdateWhere(op, ctx) {
2846
3324
  }
2847
3325
 
2848
3326
  async function applyFieldRemoveWhere(op, ctx) {
2849
- const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op), matches = await rowMatches({
3327
+ const entry = locateEntry(ctx, op.target);
3328
+ if (isLegacySingularAssignment(entry, ctx)) throw new FieldValueShapeError({
3329
+ entryType: entry._type,
3330
+ entryName: entry.name,
3331
+ mode: "value",
3332
+ issues: [ "legacy singular assignee entries do not support removeWhere; redeploy at model 9" ]
3333
+ });
3334
+ const rows = requireArrayValue(entry, op), matches = await rowMatches({
2850
3335
  where: op.where,
2851
3336
  rows: rows,
2852
3337
  ctx: ctx
@@ -2857,6 +3342,20 @@ async function applyFieldRemoveWhere(op, ctx) {
2857
3342
  };
2858
3343
  }
2859
3344
 
3345
+ function assertLegacySingularValue(args) {
3346
+ const {entry: entry, value: value, ctx: ctx} = args;
3347
+ if (!(!isLegacySingularAssignment(entry, ctx) || !Array.isArray(value) || value.length <= 1)) throw new FieldValueShapeError({
3348
+ entryType: entry._type,
3349
+ entryName: entry.name,
3350
+ mode: "value",
3351
+ issues: [ "legacy singular assignee entries accept at most one total member; redeploy at model 9" ]
3352
+ });
3353
+ }
3354
+
3355
+ function isLegacySingularAssignment(entry, ctx) {
3356
+ return entry._type === "assignee" && !isAssignmentListModel(ctx.mutation.definitionModelVersion);
3357
+ }
3358
+
2860
3359
  function requireMergeableRows(entry, op) {
2861
3360
  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`);
2862
3361
  }
@@ -3032,6 +3531,14 @@ function whereParams(ctx) {
3032
3531
 
3033
3532
  function validateDefinition(definition) {
3034
3533
  const v2 = createDefinitionValidator();
3534
+ validateConditionSites(v2, definition), validateStart(v2, definition);
3535
+ for (const entry of definition.fields ?? []) v2.checkEntry(entry, `workflow.fields "${entry.name}"`);
3536
+ for (const stage of definition.stages) validateStage(v2, stage);
3537
+ for (const issue of checkWorkflowInvariants(definition)) v2.errors.push(` · ${formatIssuePath(issue.path)}: ${issue.message}`);
3538
+ if (v2.errors.length > 0) throw new Error(`validateDefinition("${definition.name}"): ${v2.errors.length} deploy-time validation error${v2.errors.length === 1 ? "" : "s"}:\n` + v2.errors.join(`\n`));
3539
+ }
3540
+
3541
+ function validateConditionSites(v2, definition) {
3035
3542
  for (const site of conditionSitesOf(definition)) {
3036
3543
  const {address: address} = site;
3037
3544
  address.kind !== "editable-field" && v2.checkCondition(site.condition, conditionSiteLabel({
@@ -3039,12 +3546,11 @@ function validateDefinition(definition) {
3039
3546
  address: address
3040
3547
  }));
3041
3548
  }
3549
+ }
3550
+
3551
+ function validateStart(v2, definition) {
3042
3552
  definition.start?.filter !== void 0 && v2.report("start.filter", startFilterSyntaxIssues(definition.start.filter));
3043
3553
  for (const [index, requirement] of (definition.start?.requirements ?? []).entries()) requirement.type === "groq" && v2.report(`start.requirements[${index}].query`, startRequirementSyntaxIssues(requirement.query));
3044
- for (const entry of definition.fields ?? []) v2.checkEntry(entry, `workflow.fields "${entry.name}"`);
3045
- for (const stage of definition.stages) validateStage(v2, stage);
3046
- for (const issue of checkWorkflowInvariants(definition)) v2.errors.push(` · ${formatIssuePath(issue.path)}: ${issue.message}`);
3047
- if (v2.errors.length > 0) throw new Error(`validateDefinition("${definition.name}"): ${v2.errors.length} deploy-time validation error${v2.errors.length === 1 ? "" : "s"}:\n` + v2.errors.join(`\n`));
3048
3554
  }
3049
3555
 
3050
3556
  function createDefinitionValidator() {
@@ -3075,33 +3581,12 @@ function createDefinitionValidator() {
3075
3581
 
3076
3582
  function conditionSiteLabel({stage: stage, address: address}) {
3077
3583
  const at = `stage "${stage}"`;
3078
- switch (address.kind) {
3079
- case "predicate":
3080
- return `predicate "${address.predicate}"`;
3081
-
3082
- case "transition":
3083
- return `${at} transition "${address.transition}" when`;
3084
-
3085
- case "activity-filter":
3086
- return `${at} activity "${address.activity}".filter`;
3087
-
3088
- case "requirement":
3089
- return `${at} activity "${address.activity}".requirements "${address.requirement}"`;
3090
-
3091
- case "action":
3092
- return `${at} activity "${address.activity}" action "${address.action}".filter`;
3093
-
3094
- case "action-when":
3095
- return `${at} activity "${address.activity}" action "${address.action}".when`;
3096
-
3097
- case "editable-override":
3098
- return `${at}.editable "${address.name}"`;
3099
- }
3584
+ return address.kind === "predicate" ? `predicate "${address.predicate}"` : address.kind === "transition" ? `${at} transition "${address.transition}" when` : address.kind === "activity-filter" ? `${at} activity "${address.activity}".filter` : address.kind === "requirement" ? `${at} activity "${address.activity}".requirements "${address.requirement}"` : address.kind === "action" ? `${at} activity "${address.activity}" action "${address.action}".filter` : address.kind === "action-when" ? `${at} activity "${address.activity}" action "${address.action}".when` : `${at}.editable "${address.name}"`;
3100
3585
  }
3101
3586
 
3102
3587
  function validateStage(v2, stage) {
3103
3588
  for (const entry of stage.fields ?? []) v2.checkEntry(entry, `stage "${stage.name}".fields "${entry.name}"`);
3104
- for (const guard of stage.guards ?? []) validateGuardReads({
3589
+ for (const guard of stage.guards ?? []) validateGuard({
3105
3590
  v: v2,
3106
3591
  stageName: stage.name,
3107
3592
  guard: guard
@@ -3113,12 +3598,28 @@ function validateStage(v2, stage) {
3113
3598
  });
3114
3599
  }
3115
3600
 
3116
- function validateGuardReads({v: v2, stageName: stageName, guard: guard}) {
3601
+ function validateGuard({v: v2, stageName: stageName, guard: guard}) {
3117
3602
  const where = `stage "${stageName}" guard "${guard.name}"`;
3118
- for (const expr of guard.match.idRefs ?? []) v2.checkGuardRead(expr, `${where} match.idRefs`);
3603
+ if (validateGuardMatch({
3604
+ v: v2,
3605
+ where: where,
3606
+ guard: guard
3607
+ }), guard.predicate !== void 0 && guard.predicate !== "") {
3608
+ const issue = guardPredicateSyntaxIssue(guard.predicate);
3609
+ issue !== void 0 && v2.report(`${where} predicate`, [ issue ]);
3610
+ }
3119
3611
  for (const [key, expr] of Object.entries(guard.metadata ?? {})) v2.checkGuardRead(expr, `${where} metadata "${key}"`);
3120
3612
  }
3121
3613
 
3614
+ function validateGuardMatch({v: v2, where: where, guard: guard}) {
3615
+ guard.match.actions.length === 0 && v2.report(`${where} match.actions`, [ GUARD_ACTIONS_REQUIRED_MESSAGE ]);
3616
+ for (const pattern of guard.match.idPatterns ?? []) {
3617
+ const issue = guardIdPatternIssue(pattern, guard.match.actions);
3618
+ issue !== void 0 && v2.report(`${where} match.idPatterns`, [ issue ]);
3619
+ }
3620
+ for (const expr of guard.match.idRefs ?? []) v2.checkGuardRead(expr, `${where} match.idRefs`);
3621
+ }
3622
+
3122
3623
  function validateActivity({v: v2, stageName: stageName, activity: activity}) {
3123
3624
  const where = `stage "${stageName}" activity "${activity.name}"`;
3124
3625
  for (const entry of activity.fields ?? []) v2.checkEntry(entry, `${where}.fields "${entry.name}"`);
@@ -3220,214 +3721,52 @@ function buildClientForGdr(args) {
3220
3721
  return sameResource(target, workflowResource) ? client : siblingFor({
3221
3722
  client: client,
3222
3723
  workflowResource: workflowResource,
3223
- target: target,
3224
- parsed: parsed,
3225
- siblings: siblings
3226
- });
3227
- };
3228
- }
3229
-
3230
- function siblingFor(args) {
3231
- const {client: client, workflowResource: workflowResource, target: target, parsed: parsed, siblings: siblings} = args, key = `${target.type}:${target.id}`, cached = siblings.get(key);
3232
- if (cached !== void 0) return cached;
3233
- if (client.withConfig === void 0) throw new ContractViolationError(`Cannot route "${gdrFromResource(target, parsed.documentId)}": the GDR targets ${target.type} "${target.id}", not the workflow resource (${workflowResource.type} "${workflowResource.id}"), and the workflow client cannot derive a sibling client for it (no withConfig). Pass resourceClients to route foreign resources, or use a client with withConfig (e.g. @sanity/client).`);
3234
- const sibling = client.withConfig(siblingConfig(target));
3235
- return siblings.set(key, sibling), sibling;
3236
- }
3237
-
3238
- function siblingConfig(target) {
3239
- if (target.type === "dataset") {
3240
- const {projectId: projectId, dataset: dataset} = datasetResourceParts(target.id);
3241
- return {
3242
- resource: target,
3243
- projectId: projectId,
3244
- dataset: dataset
3245
- };
3246
- }
3247
- return {
3248
- resource: target
3249
- };
3250
- }
3251
-
3252
- const wallClock = () => /* @__PURE__ */ (new Date).toISOString();
3253
-
3254
- function addMs(instant, ms) {
3255
- return new Date(Date.parse(instant) + ms).toISOString();
3256
- }
3257
-
3258
- function hasPassed(instant, now) {
3259
- return Date.parse(instant) <= Date.parse(now);
3260
- }
3261
-
3262
- const GUARD_DOC_TYPE = "temp.system.guard";
3263
-
3264
- class MutationGuardDeniedError extends WorkflowError {
3265
- denied;
3266
- documentId;
3267
- action;
3268
- constructor(args) {
3269
- const ids = args.denied.map(d => d.guardId).join(", ");
3270
- super("mutation-guard-denied", `Mutation on "${args.documentId}" (${args.action}) denied by guard(s) [${ids}]`),
3271
- this.name = "MutationGuardDeniedError", this.denied = args.denied, this.documentId = args.documentId,
3272
- this.action = args.action;
3273
- }
3274
- static fromGuards(args) {
3275
- return new MutationGuardDeniedError({
3276
- documentId: args.documentId,
3277
- action: args.action,
3278
- denied: deniedGuardRefs(args.guards)
3279
- });
3280
- }
3281
- }
3282
-
3283
- function deniedGuardRefs(guards) {
3284
- return guards.map(g => ({
3285
- guardId: g._id,
3286
- ...g.name !== void 0 ? {
3287
- name: g.name
3288
- } : {}
3289
- }));
3290
- }
3291
-
3292
- function deniedGuardLabels(denied) {
3293
- return denied.map(d => d.name ?? d.guardId);
3294
- }
3295
-
3296
- function lakeGuardId(args) {
3297
- return `${GUARD_DOC_TYPE}.${args.instanceDocId}.${args.guardName}`;
3298
- }
3299
-
3300
- function compileGuard(args) {
3301
- return {
3302
- _id: args.id,
3303
- _type: GUARD_DOC_TYPE,
3304
- resourceType: args.resourceType,
3305
- resourceId: args.resourceId,
3306
- owner: args.owner,
3307
- sourceInstanceId: args.sourceInstanceId,
3308
- sourceDefinition: args.sourceDefinition,
3309
- sourceStage: args.sourceStage,
3310
- ...args.name !== void 0 ? {
3311
- name: args.name
3312
- } : {},
3313
- ...args.description !== void 0 ? {
3314
- description: args.description
3315
- } : {},
3316
- match: args.match,
3317
- predicate: args.predicate,
3318
- metadata: args.metadata
3319
- };
3320
- }
3321
-
3322
- function toGroqJsPredicate(predicate) {
3323
- return predicate.replace(new RegExp("(?<!\\$)\\bguard\\.", "g"), "$guard.").replace(new RegExp("(?<!\\$)\\bmutation\\.", "g"), "$mutation.");
3324
- }
3325
-
3326
- function globMatch(pattern, value) {
3327
- if (!pattern.includes("*")) return pattern === value;
3328
- const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
3329
- return new RegExp(`^${escaped}$`).test(value);
3330
- }
3331
-
3332
- function guardMatches({guard: guard, doc: doc, action: action}) {
3333
- const m = guard.match;
3334
- if (!m.actions.includes(action) || m.types && m.types.length > 0 && !(doc.type !== void 0 && m.types.includes(doc.type))) return !1;
3335
- if (m.idRefs && m.idRefs.length > 0 || m.idPatterns && m.idPatterns.length > 0) {
3336
- const byRef = m.idRefs?.includes(doc.id) ?? !1, byPattern = m.idPatterns?.some(p => globMatch(p, doc.id)) ?? !1;
3337
- if (!byRef && !byPattern) return !1;
3338
- }
3339
- return !0;
3340
- }
3341
-
3342
- function guardPredicateParams(args) {
3343
- return {
3344
- guard: args.guard,
3345
- mutation: {
3346
- action: args.action
3347
- }
3348
- };
3349
- }
3350
-
3351
- async function evaluateMutationGuard(args) {
3352
- const {guard: guard, context: context} = args;
3353
- if (guard.predicate === "") return !1;
3354
- const params = guardPredicateParams({
3355
- guard: guard,
3356
- action: context.action
3357
- });
3358
- try {
3359
- const tree = parse(toGroqJsPredicate(guard.predicate), {
3360
- mode: "delta",
3361
- params: params
3362
- });
3363
- return await (await evaluate(tree, {
3364
- before: context.before,
3365
- after: context.after,
3366
- params: params,
3367
- ...context.identity !== void 0 ? {
3368
- identity: context.identity
3369
- } : {}
3370
- })).get() === !0;
3371
- } catch {
3372
- return !1;
3373
- }
3724
+ target: target,
3725
+ parsed: parsed,
3726
+ siblings: siblings
3727
+ });
3728
+ };
3374
3729
  }
3375
3730
 
3376
- async function denyingGuards(args) {
3377
- const {guards: guards, doc: doc, context: context} = args, denied = [];
3378
- for (const guard of guards) guardMatches({
3379
- guard: guard,
3380
- doc: doc,
3381
- action: context.action
3382
- }) && (await evaluateMutationGuard({
3383
- guard: guard,
3384
- context: context
3385
- }) || denied.push(guard));
3386
- return denied;
3731
+ function siblingFor(args) {
3732
+ const {client: client, workflowResource: workflowResource, target: target, parsed: parsed, siblings: siblings} = args, key = `${target.type}:${target.id}`, cached = siblings.get(key);
3733
+ if (cached !== void 0) return cached;
3734
+ if (client.withConfig === void 0) throw new ContractViolationError(`Cannot route "${gdrFromResource(target, parsed.documentId)}": the GDR targets ${target.type} "${target.id}", not the workflow resource (${workflowResource.type} "${workflowResource.id}"), and the workflow client cannot derive a sibling client for it (no withConfig). Pass resourceClients to route foreign resources, or use a client with withConfig (e.g. @sanity/client).`);
3735
+ const sibling = client.withConfig(siblingConfig(target));
3736
+ return siblings.set(key, sibling), sibling;
3387
3737
  }
3388
3738
 
3389
- async function documentActionDenials(args) {
3390
- const {doc: doc, resource: resource, action: action, guards: guards, identity: identity} = args, enforceable = guards.filter(g => g.resourceType === resource.type && g.resourceId === resource.id);
3391
- return enforceable.length === 0 ? [] : denyingGuards({
3392
- guards: enforceable,
3393
- doc: {
3394
- id: doc._id,
3395
- type: doc._type
3396
- },
3397
- context: {
3398
- action: action,
3399
- before: doc,
3400
- after: doc,
3401
- ...identity !== void 0 ? {
3402
- identity: identity
3403
- } : {}
3404
- }
3405
- });
3739
+ function siblingConfig(target) {
3740
+ if (target.type === "dataset") {
3741
+ const {projectId: projectId, dataset: dataset} = datasetResourceParts(target.id);
3742
+ return {
3743
+ resource: target,
3744
+ projectId: projectId,
3745
+ dataset: dataset
3746
+ };
3747
+ }
3748
+ return {
3749
+ resource: target
3750
+ };
3406
3751
  }
3407
3752
 
3408
- async function instanceWriteDenials(args) {
3409
- const {instance: instance, guards: guards, identity: identity} = args;
3410
- return documentActionDenials({
3411
- doc: instance,
3412
- resource: instance.workflowResource,
3413
- action: "update",
3414
- guards: guards,
3415
- ...identity !== void 0 ? {
3416
- identity: identity
3417
- } : {}
3418
- });
3753
+ const wallClock = () => /* @__PURE__ */ (new Date).toISOString(), tick = ms => new Promise(resolve => {
3754
+ setTimeout(resolve, ms);
3755
+ }), wallSleeper = async ms => {
3756
+ let left = ms;
3757
+ for (;left > 2147483647; ) await tick(2147483647), left -= 2147483647;
3758
+ await tick(left);
3759
+ };
3760
+
3761
+ function addMs(instant, ms) {
3762
+ return new Date(Date.parse(instant) + ms).toISOString();
3419
3763
  }
3420
3764
 
3421
- async function assertInstanceWriteAllowed(args) {
3422
- const denied = await instanceWriteDenials(args);
3423
- if (denied.length !== 0) throw MutationGuardDeniedError.fromGuards({
3424
- documentId: args.instance._id,
3425
- action: "update",
3426
- guards: denied
3427
- });
3765
+ function hasPassed(instant, now) {
3766
+ return Date.parse(instant) <= Date.parse(now);
3428
3767
  }
3429
3768
 
3430
- const DATA_MODEL_VERSION = 8, DATA_MODEL_MIN_READER = 4, DATA_MODEL_MAX_READER = 8, READER_MODEL_ROLLOUT_URL = "https://www.sanity.io/docs/workflows/prerelease";
3769
+ const DATA_MODEL_VERSION = 10, DATA_MODEL_MIN_READER = 4, DATA_MODEL_MAX_READER = 10, READER_MODEL_ROLLOUT_URL = "https://www.sanity.io/docs/workflows/prerelease";
3431
3770
 
3432
3771
  class ReaderModelAcknowledgementError extends WorkflowError {
3433
3772
  code="WORKFLOW_READER_MODEL_ACKNOWLEDGEMENT_MISMATCH";
@@ -3567,6 +3906,38 @@ const DATA_MODEL_CHANGES = Object.freeze([ Object.freeze({
3567
3906
  compatibility: "reader-floor",
3568
3907
  applicability: "detectable",
3569
3908
  summary: "Assignee fields may restrict newly assigned users and collective roles by role."
3909
+ }), Object.freeze({
3910
+ id: "split-guard-id-spaces",
3911
+ introducedInModel: 9,
3912
+ minReaderModel: 9,
3913
+ documentTypes: Object.freeze([ "definition", "instance" ]),
3914
+ compatibility: "reader-floor",
3915
+ applicability: "detectable",
3916
+ summary: "Guards spanning edit and lifecycle id spaces emit independently retractable documents."
3917
+ }), Object.freeze({
3918
+ id: "singular-assignee-lists",
3919
+ introducedInModel: 9,
3920
+ minReaderModel: 9,
3921
+ documentTypes: Object.freeze([ "definition", "instance" ]),
3922
+ compatibility: "reader-floor",
3923
+ applicability: "detectable",
3924
+ summary: "Singular assignee fields use member lists with at most one user and any number of roles."
3925
+ }), Object.freeze({
3926
+ id: "required-content-references",
3927
+ introducedInModel: 10,
3928
+ minReaderModel: 10,
3929
+ documentTypes: Object.freeze([ "definition", "instance" ]),
3930
+ compatibility: "reader-floor",
3931
+ applicability: "detectable",
3932
+ summary: "Required content references must remain readable after initialization before actions or transitions advance."
3933
+ }), Object.freeze({
3934
+ id: "effect-retry-policy",
3935
+ introducedInModel: 10,
3936
+ minReaderModel: 10,
3937
+ documentTypes: Object.freeze([ "definition", "instance" ]),
3938
+ compatibility: "reader-floor",
3939
+ applicability: "detectable",
3940
+ summary: "An effect node declares a bounded retry policy the engine enforces on every runtime."
3570
3941
  }) ]);
3571
3942
 
3572
3943
  function recordOf(value) {
@@ -3690,6 +4061,33 @@ function hasRoleConstrainedAssignments(document) {
3690
4061
  return persistedFieldEntries(document).some(entry => entryKindMatches(entry, assignmentKindAcceptsRoles) && Array.isArray(entry.roles) && entry.roles.length > 0);
3691
4062
  }
3692
4063
 
4064
+ function hasSingularAssigneeLists(document) {
4065
+ const root = recordOf(document);
4066
+ if (root === void 0) return !1;
4067
+ if (typeof root.definitionSnapshot != "string") return persistedFieldEntries(document).some(entry => entryHasKind(entry, "assignee"));
4068
+ const snapshot = parsedDefinitionSnapshot(root);
4069
+ return typeof snapshot?.modelVersion == "number" && isAssignmentListModel(snapshot.modelVersion) && persistedFieldEntries(document).some(entry => entryHasKind(entry, "assignee"));
4070
+ }
4071
+
4072
+ function hasEffectRetryPolicy(document) {
4073
+ return persistedDefinitionTree(document)?.actions.some(action => recordsAt(action, "effects").some(effect => recordOf(effect.retry) !== void 0)) ?? !1;
4074
+ }
4075
+
4076
+ function isMutationGuardAction(action) {
4077
+ return typeof action == "string" && MUTATION_GUARD_ACTIONS.some(candidate => candidate === action);
4078
+ }
4079
+
4080
+ function hasSplitGuardIdSpaces(document) {
4081
+ return persistedDefinitionTree(document)?.stages.some(stage => recordsAt(stage, "guards").some(guard => {
4082
+ const actions = recordOf(guard.match)?.actions;
4083
+ return Array.isArray(actions) && mutationGuardRequiresSplitEmission(actions.filter(isMutationGuardAction));
4084
+ })) ?? !1;
4085
+ }
4086
+
4087
+ function hasRequiredContentReferences(document) {
4088
+ return persistedDefinitionTree(document)?.roots.some(root => recordsAt(root, "fields").some(field => field.required === !0 && typeof field.type == "string" && refKindAcceptsTypes(field.type))) ?? !1;
4089
+ }
4090
+
3693
4091
  const featureDetectors = {
3694
4092
  "governed-model-stamps": () => !0,
3695
4093
  "subject-field-kind": document => hasFieldKind(document, "subject"),
@@ -3702,6 +4100,10 @@ const featureDetectors = {
3702
4100
  "readiness-requirements": hasReadinessRequirements,
3703
4101
  "due-date-field-kinds": document => hasFieldKind(document, "dueDate") || hasFieldKind(document, "dueDatetime"),
3704
4102
  "role-constrained-assignment-fields": hasRoleConstrainedAssignments,
4103
+ "singular-assignee-lists": hasSingularAssigneeLists,
4104
+ "split-guard-id-spaces": hasSplitGuardIdSpaces,
4105
+ "required-content-references": hasRequiredContentReferences,
4106
+ "effect-retry-policy": hasEffectRetryPolicy,
3705
4107
  "node-semantics": hasNodeSemantics,
3706
4108
  "field-patch-ops": document => hasPersistedOpType(document, [ "field.inc", "field.dec", "field.setIfMissing" ]),
3707
4109
  "attributes-condition-var": () => !0
@@ -3841,6 +4243,91 @@ function findActivityNode(args) {
3841
4243
  return findStageNode(args)?.activities?.find(entry => entry.name === args.activityName);
3842
4244
  }
3843
4245
 
4246
+ function requirementDescriptor(requirement) {
4247
+ return {
4248
+ name: requirement.name,
4249
+ ...requirement.title !== void 0 ? {
4250
+ title: requirement.title
4251
+ } : {},
4252
+ ...requirement.description !== void 0 ? {
4253
+ description: requirement.description
4254
+ } : {}
4255
+ };
4256
+ }
4257
+
4258
+ function subjectDenialLabels(denied) {
4259
+ return denied.map(d => `${d.permission} on ${d.subject} (${d.resource})`);
4260
+ }
4261
+
4262
+ class ActionDisabledError extends WorkflowError {
4263
+ reason;
4264
+ activity;
4265
+ action;
4266
+ constructor(args) {
4267
+ super("action-disabled", formatDisabledReason({
4268
+ activity: args.activity,
4269
+ action: args.action,
4270
+ reason: args.reason
4271
+ })), this.name = "ActionDisabledError", this.reason = args.reason, this.activity = args.activity,
4272
+ this.action = args.action;
4273
+ }
4274
+ }
4275
+
4276
+ class StartNotAllowedError extends WorkflowError {
4277
+ definition;
4278
+ unmetRequirements;
4279
+ constructor(args) {
4280
+ super("start-not-allowed", `startInstance refused definition "${args.definition}": ${args.unmetRequirements.map(requirement => requirement.title ?? requirement.name).join(", ")}. Pre-flight the verdict with evaluateStart.`),
4281
+ this.name = "StartNotAllowedError", this.definition = args.definition, this.unmetRequirements = args.unmetRequirements;
4282
+ }
4283
+ }
4284
+
4285
+ function actionRendering(action) {
4286
+ const kind = action.disabledReason?.kind;
4287
+ return kind === "filter-failed" ? "absent" : action.triggered === !0 || kind === "cascade-fired" ? "automation" : "button";
4288
+ }
4289
+
4290
+ const disabledReasonDetail = {
4291
+ "filter-failed": r => `action filter returned false${r.detail ? ` (${r.detail})` : ""}`,
4292
+ "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`,
4293
+ "activity-not-active": r => `activity status is "${r.status}"`,
4294
+ "stage-terminal": r => `stage "${r.stage}" is terminal`,
4295
+ "instance-completed": r => `instance completed at ${r.completedAt}`,
4296
+ "instance-aborted": r => `instance aborted at ${r.abortedAt}`,
4297
+ "requirements-unmet": r => `unmet requirement(s): ${r.unmetRequirements.map(requirement => requirement.name).join(", ")}`,
4298
+ "subject-permission-denied": r => `missing subject permission(s): ${subjectDenialLabels(r.denied).join(", ")}`
4299
+ };
4300
+
4301
+ function actionDisabledDetail(reason) {
4302
+ return disabledReasonDetail[reason.kind](reason);
4303
+ }
4304
+
4305
+ function formatDisabledReason({activity: activity, action: action, reason: reason}) {
4306
+ return `Action "${activity}:${action}" is not allowed: ${actionDisabledDetail(reason)}`;
4307
+ }
4308
+
4309
+ class EditFieldDeniedError extends WorkflowError {
4310
+ reason;
4311
+ target;
4312
+ constructor(args) {
4313
+ super("edit-field-denied", formatEditDisabledReason(args.target, args.reason)),
4314
+ this.name = "EditFieldDeniedError", this.reason = args.reason, this.target = args.target;
4315
+ }
4316
+ }
4317
+
4318
+ const editDisabledReasonDetail = {
4319
+ "not-editable": () => "field is not declared editable",
4320
+ "instance-completed": r => `instance completed at ${r.completedAt}`,
4321
+ "instance-aborted": r => `instance aborted at ${r.abortedAt}`,
4322
+ "edit-window-closed": r => `edit window closed (${r.detail})`,
4323
+ "editor-not-permitted": r => `editor not permitted (${r.predicate})`
4324
+ };
4325
+
4326
+ function formatEditDisabledReason(target, reason) {
4327
+ const detail = editDisabledReasonDetail[reason.kind](reason), where = target.activity !== void 0 ? `${target.activity}.${target.field}` : target.field;
4328
+ return `Field "${target.scope}:${where}" is not editable: ${detail}`;
4329
+ }
4330
+
3844
4331
  function liveChildrenField(instance) {
3845
4332
  const live = liveSubworkflows(instance).length;
3846
4333
  return live > 0 ? {
@@ -3853,9 +4340,13 @@ function abortReason(instance) {
3853
4340
  }
3854
4341
 
3855
4342
  function diagnoseInputFromEvaluation(evaluation) {
4343
+ if (evaluation.missingDocuments?.length && evaluation.blockingMissingDocuments === void 0) throw new Error("Missing blocking document evidence in workflow evaluation");
3856
4344
  const stage = openStage(evaluation.instance);
3857
4345
  return {
3858
4346
  instance: evaluation.instance,
4347
+ ...evaluation.missingDocuments !== void 0 ? {
4348
+ missingDocuments: evaluation.blockingMissingDocuments ?? []
4349
+ } : {},
3859
4350
  activities: evaluation.currentStage.activities,
3860
4351
  transitions: evaluation.currentStage.transitions,
3861
4352
  assignees: Object.fromEntries(evaluation.currentStage.activities.map(t => [ t.activity.name, assigneesOf(stage, t.activity.name) ]))
@@ -3867,7 +4358,8 @@ function openStage(instance) {
3867
4358
  }
3868
4359
 
3869
4360
  function assigneesOf(stage, activityName) {
3870
- return (stage?.activities.find(t => t.name === activityName)?.fields ?? []).filter(s => s._type === "assignees").flatMap(s => s.value);
4361
+ const entry = stage?.activities.find(t => t.name === activityName);
4362
+ return [ ...assignmentMembers(entry?.fields ?? []) ];
3871
4363
  }
3872
4364
 
3873
4365
  function isResolved(status) {
@@ -3900,11 +4392,16 @@ function failedActivityCause(input) {
3900
4392
 
3901
4393
  function waitingState(input) {
3902
4394
  const active = input.activities.find(t => t.status === "active" && (t.activity.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length === 0);
3903
- if (active !== void 0) return {
4395
+ if (active === void 0) return;
4396
+ const actions = active.actions.filter(action => actionRendering(action) === "button" && action.allowed).map(action => action.action.name), hasManualAction = active.actions.some(action => !isCascadeFired(action.action));
4397
+ let waitingFor = "automation";
4398
+ return actions.length > 0 ? waitingFor = "caller" : hasManualAction && (waitingFor = "manual-action"),
4399
+ {
3904
4400
  state: "waiting",
3905
4401
  activity: active.activity.name,
3906
4402
  assignees: input.assignees[active.activity.name] ?? [],
3907
- actions: (active.activity.actions ?? []).filter(a => !isCascadeFired(a)).map(a => a.name)
4403
+ actions: actions,
4404
+ waitingFor: waitingFor
3908
4405
  };
3909
4406
  }
3910
4407
 
@@ -3968,7 +4465,7 @@ function diagnoseInstance(input) {
3968
4465
  at: instance.completedAt,
3969
4466
  ...liveChildrenField(instance)
3970
4467
  };
3971
- const cause = stuckFromDocumentState(input) ?? noTransitionFiresCause(input);
4468
+ const cause = stuckFromDocumentState(input) ?? missingDocumentCause(input.missingDocuments) ?? noTransitionFiresCause(input);
3972
4469
  return cause !== void 0 ? {
3973
4470
  state: "stuck",
3974
4471
  cause: cause
@@ -3977,10 +4474,27 @@ function diagnoseInstance(input) {
3977
4474
  };
3978
4475
  }
3979
4476
 
4477
+ function missingDocumentCause(documents = []) {
4478
+ return documents.length > 0 ? {
4479
+ kind: "document-missing",
4480
+ documents: documents
4481
+ } : void 0;
4482
+ }
4483
+
4484
+ function _additionalMissingDocuments(diagnosis, documents) {
4485
+ return diagnosis.state === "stuck" && diagnosis.cause.kind !== "document-missing" ? documents ?? [] : [];
4486
+ }
4487
+
3980
4488
  const RUNNABLE_VERBS = /* @__PURE__ */ new Set([ "set-stage", "abort", "drain-effects", "reset-activity" ]);
3981
4489
 
3982
4490
  function remediationsForCause(cause) {
3983
4491
  switch (cause.kind) {
4492
+ case "document-missing":
4493
+ return [ {
4494
+ verb: "abort",
4495
+ rationale: "Required content cannot be read. Investigate the referenced documents, or abort the instance if it is no longer needed."
4496
+ } ];
4497
+
3984
4498
  case "failed-effect":
3985
4499
  return [ {
3986
4500
  verb: "retry-effect",
@@ -4030,7 +4544,7 @@ function isClaimExpired(claim, now) {
4030
4544
  return claim.leaseExpiresAt === void 0 || hasPassed(claim.leaseExpiresAt, now);
4031
4545
  }
4032
4546
 
4033
- const EFFECT_RUN_STATUSES = [ "done", "failed", "cancelled" ];
4547
+ const EFFECT_RUN_STATUSES = _exhaustiveOptions()([ "done", "failed", "cancelled" ]);
4034
4548
 
4035
4549
  function userLoginProvider(user) {
4036
4550
  return user?.provider ?? user?.loginProvider;
@@ -4075,12 +4589,12 @@ function joinCacheFor(client) {
4075
4589
  }
4076
4590
 
4077
4591
  function isClientProjectMember(value) {
4078
- return isRecord(value) && typeof value.id == "string";
4592
+ return isRecord(value) && typeof value.id == "string" && (value.isRobot === void 0 || typeof value.isRobot == "boolean");
4079
4593
  }
4080
4594
 
4081
- async function requestProjectMembers(request, projectId) {
4595
+ async function _requestProjectMembers(request, projectId) {
4082
4596
  const project = await request({
4083
- uri: `/projects/${encodeURIComponent(projectId)}`
4597
+ url: `/projects/${encodeURIComponent(projectId)}`
4084
4598
  }), members = isRecord(project) ? project.members : void 0;
4085
4599
  if (!Array.isArray(members) || !members.every(isClientProjectMember)) throw new Error(`Project member directory response is invalid for project "${projectId}"`);
4086
4600
  return members;
@@ -4088,7 +4602,7 @@ async function requestProjectMembers(request, projectId) {
4088
4602
 
4089
4603
  async function requestProjectUsers(args) {
4090
4604
  const {request: request, projectId: projectId, memberIds: memberIds} = args, response = await request({
4091
- uri: `/projects/${encodeURIComponent(projectId)}/users/${memberIds.map(encodeURIComponent).join(",")}`
4605
+ url: `/projects/${encodeURIComponent(projectId)}/users/${memberIds.map(encodeURIComponent).join(",")}`
4092
4606
  }), rows = Array.isArray(response) ? response : [ response ];
4093
4607
  if (!rows.every(isClientProjectUser)) throw new Error(`Project user directory response is invalid for project "${projectId}"`);
4094
4608
  const requested = new Set(memberIds);
@@ -4096,7 +4610,7 @@ async function requestProjectUsers(args) {
4096
4610
  }
4097
4611
 
4098
4612
  async function requestProjectMemberDirectory(args) {
4099
- const {request: request, projectId: projectId} = args, members = await requestProjectMembers(request, projectId), memberIds = members.map(member => member.id).filter(id => classifyPrincipalId(id).namespace === "project");
4613
+ const {request: request, projectId: projectId} = args, members = await _requestProjectMembers(request, projectId), memberIds = members.map(member => member.id).filter(id => classifyPrincipalId(id).namespace === "project");
4100
4614
  if (memberIds.length === 0) return {
4101
4615
  members: members,
4102
4616
  globalUsers: []
@@ -4171,7 +4685,7 @@ function clientProjectUserDirectory(client, projectId) {
4171
4685
  });
4172
4686
  try {
4173
4687
  const response = await client.request({
4174
- uri: `/projects/${encodeURIComponent(projectId)}/users/${encodeURIComponent(id)}`
4688
+ url: `/projects/${encodeURIComponent(projectId)}/users/${encodeURIComponent(id)}`
4175
4689
  }), candidate = Array.isArray(response) ? response[0] : response;
4176
4690
  return candidate == null ? {
4177
4691
  status: "missing"
@@ -4283,8 +4797,30 @@ function collectWatchRefs(instance) {
4283
4797
  }
4284
4798
 
4285
4799
  function collectSubjectRefs(instance) {
4286
- const stage = findOpenStageEntry(instance), activityFields = stage?.activities.map(activity => activity.fields) ?? [];
4287
- return [ ...entryDocRefs(instance.fields), ...entryDocRefs(stage?.fields), ...activityFields.flatMap(entryDocRefs), ...entryReleaseRefs(instance.fields), ...entryReleaseRefs(stage?.fields), ...activityFields.flatMap(entryReleaseRefs) ];
4800
+ const entries = activeFieldEntries(instance).map(({entry: entry}) => entry);
4801
+ return [ ...entryDocRefs(entries), ...entryReleaseRefs(entries) ];
4802
+ }
4803
+
4804
+ function activeFieldEntries(instance) {
4805
+ const stage = findOpenStageEntry(instance);
4806
+ return [ ...scopedEntries(instance.fields, {
4807
+ scope: "workflow"
4808
+ }), ...scopedEntries(stage?.fields, {
4809
+ scope: "stage"
4810
+ }), ...(stage?.activities ?? []).flatMap(activity => scopedEntries(activity.fields, {
4811
+ scope: "activity",
4812
+ activity: activity.name
4813
+ })) ];
4814
+ }
4815
+
4816
+ function scopedEntries(entries, scope) {
4817
+ return (entries ?? []).map(entry => ({
4818
+ target: {
4819
+ ...scope,
4820
+ field: entry.name
4821
+ },
4822
+ entry: entry
4823
+ }));
4288
4824
  }
4289
4825
 
4290
4826
  function foreignSubjectRefs(instance) {
@@ -4385,6 +4921,10 @@ function entrySubjectRefs(entries) {
4385
4921
  return fieldEntries(entries).flatMap(entry => (typeof entry._type == "string" && isSingleDocRefKind(entry._type) || entry._type === "release.ref") && isGdr(entry.value) ? [ entry.value ] : []);
4386
4922
  }
4387
4923
 
4924
+ function missingEntryDocRefs(entries, knownIds) {
4925
+ return entryDocRefs(entries).filter(reference => !knownIds.has(reference.id));
4926
+ }
4927
+
4388
4928
  function fieldEntries(entries) {
4389
4929
  return Array.isArray(entries) ? entries.filter(entry => !!entry && typeof entry == "object") : [];
4390
4930
  }
@@ -4812,6 +5352,96 @@ function coerceToGdr(raw, workflowResource) {
4812
5352
  } : null;
4813
5353
  }
4814
5354
 
5355
+ const NOW = {
5356
+ variable: "now",
5357
+ path: []
5358
+ }, LAST_REPRESENTABLE_MS = 864e13;
5359
+
5360
+ function readsNowIn(reads) {
5361
+ return reads.some(read => read.variable === "now");
5362
+ }
5363
+
5364
+ function readsNow(condition) {
5365
+ return readsNowIn(analyzeCondition(condition).reads);
5366
+ }
5367
+
5368
+ async function stageTimeSites(args) {
5369
+ const {definition: definition, stage: stage, scopeFor: scopeFor} = args, sites = [];
5370
+ for (const site of conditionSitesOf(definition)) {
5371
+ if (site.stage !== stage || site.address.kind === "editable-override" || !readsNow(site.condition)) continue;
5372
+ const activity = "activity" in site.address ? site.address.activity : void 0;
5373
+ sites.push({
5374
+ condition: site.condition,
5375
+ params: await scopeFor(activity)
5376
+ });
5377
+ }
5378
+ return sites;
5379
+ }
5380
+
5381
+ async function nextEvaluationInstant(args) {
5382
+ const {sites: sites, dataset: dataset, now: now} = args, boundaries = [];
5383
+ for (const site of sites) {
5384
+ const boundary = await siteBoundary({
5385
+ ...site,
5386
+ dataset: dataset,
5387
+ now: now
5388
+ });
5389
+ boundary !== void 0 && boundaries.push(boundary);
5390
+ }
5391
+ return [ ...boundaries ].sort(byInstant)[0];
5392
+ }
5393
+
5394
+ function byInstant(a, b) {
5395
+ return Date.parse(a) - Date.parse(b);
5396
+ }
5397
+
5398
+ async function siteBoundary(args) {
5399
+ const {condition: condition, params: params, dataset: dataset, now: now} = args, insight = await explainCondition({
5400
+ condition: condition,
5401
+ dataset: dataset,
5402
+ params: params
5403
+ });
5404
+ for (const candidate of await candidateInstants({
5405
+ insight: insight,
5406
+ params: params,
5407
+ dataset: dataset,
5408
+ now: now
5409
+ })) if ((await whatIfCondition({
5410
+ condition: condition,
5411
+ dataset: dataset,
5412
+ params: params,
5413
+ assign: {
5414
+ target: NOW,
5415
+ value: candidate
5416
+ }
5417
+ })).changed) return candidate;
5418
+ }
5419
+
5420
+ async function candidateInstants(args) {
5421
+ const {insight: insight, params: params, dataset: dataset, now: now} = args, comparands = [];
5422
+ for (const atom of insight.atoms) readsNowIn(atom.atom.reads) && comparands.push(...await comparandsOf({
5423
+ atom: atom,
5424
+ params: params,
5425
+ dataset: dataset
5426
+ }));
5427
+ const instants = comparands.filter(isParseableInstant).map(normalizeInstant).filter(instant => Date.parse(instant) < LAST_REPRESENTABLE_MS).flatMap(instant => [ instant, addMs(instant, 1) ]).filter(instant => !hasPassed(instant, now));
5428
+ return [ ...new Set(instants) ].sort(byInstant);
5429
+ }
5430
+
5431
+ function normalizeInstant(value) {
5432
+ return new Date(Date.parse(value)).toISOString();
5433
+ }
5434
+
5435
+ async function comparandsOf(args) {
5436
+ const {atom: atom, params: params, dataset: dataset} = args, requirement = atom.requirement, values = requirement !== void 0 && requirement.target.variable === "now" && "value" in requirement ? [ requirement.value ] : [];
5437
+ for (const read of atom.atom.reads) read.variable !== "now" && values.push(await runGroq({
5438
+ groq: formatRead(read),
5439
+ params: params,
5440
+ dataset: dataset
5441
+ }));
5442
+ return values;
5443
+ }
5444
+
4815
5445
  const NonEmpty = NonEmptyString, PersistedAssignmentRolesSchema = v.pipe(v.array(NonEmptyString), v.minLength(1, "assignment roles must not be empty")), UnknownRecord = v.record(v.string(), v.unknown()), PersistedChoiceOptionsSchema = v.looseObject({
4816
5446
  list: v.array(v.looseObject({
4817
5447
  title: v.string(),
@@ -4864,7 +5494,7 @@ function fieldArm(kind, value) {
4864
5494
  };
4865
5495
  }
4866
5496
 
4867
- const OptionalRefTypes = v.exactOptional(v.array(v.string())), ResolvedFieldEntrySchema = v.variant("_type", [ v.looseObject(tolerantEntries()({
5497
+ const OptionalRefTypes = v.exactOptional(v.array(v.string())), RawResolvedFieldEntrySchema = v.variant("_type", [ v.looseObject(tolerantEntries()({
4868
5498
  ...fieldArm("doc.ref", fieldValueSchemas["doc.ref"]),
4869
5499
  types: OptionalRefTypes
4870
5500
  })), v.looseObject(tolerantEntries()({
@@ -4885,7 +5515,7 @@ const OptionalRefTypes = v.exactOptional(v.array(v.string())), ResolvedFieldEntr
4885
5515
  })), v.looseObject(tolerantEntries()({
4886
5516
  ...fieldArm("array", v.array(UnknownRecord)),
4887
5517
  of: v.array(PersistedFieldShapeSchema)
4888
- })) ]), EffectOriginSchema = tolerantObject()({
5518
+ })) ]), ResolvedFieldEntrySchema = v.pipe(RawResolvedFieldEntrySchema, v.transform(normalizeNestedAssignmentValues)), EffectOriginSchema = tolerantObject()({
4889
5519
  kind: v.literal("action"),
4890
5520
  name: NonEmpty
4891
5521
  }), PendingEffectClaimSchema = tolerantObject()({
@@ -5565,7 +6195,7 @@ async function resolveThroughDirectory(args) {
5565
6195
  if (safeMisses.length === 0 || client.request === void 0) return out;
5566
6196
  try {
5567
6197
  const response = await client.request({
5568
- uri: `/projects/${encodeURIComponent(projectId)}/users/${safeMisses.map(encodeURIComponent).join(",")}`,
6198
+ url: `/projects/${encodeURIComponent(projectId)}/users/${safeMisses.map(encodeURIComponent).join(",")}`,
5569
6199
  tag: REQUEST_TAG.accessResolveActor
5570
6200
  });
5571
6201
  ingestDirectoryRows({
@@ -5746,6 +6376,31 @@ function buildInstanceBase(args) {
5746
6376
  };
5747
6377
  }
5748
6378
 
6379
+ function instanceDocumentForWrite(instance) {
6380
+ const listModel = writesSingularAssignmentLists(instance);
6381
+ return {
6382
+ ...instance,
6383
+ fields: assignmentFieldsForWrite(instance.fields, listModel),
6384
+ stages: assignmentStagesForWrite(instance.stages, listModel)
6385
+ };
6386
+ }
6387
+
6388
+ function memoizedBy(compute) {
6389
+ const cache = /* @__PURE__ */ new Map;
6390
+ return key => {
6391
+ const hit = cache.get(key);
6392
+ if (hit !== void 0) return hit;
6393
+ const value = (async () => compute(key))();
6394
+ return cache.set(key, value), value;
6395
+ };
6396
+ }
6397
+
6398
+ function chunks(values, size) {
6399
+ return Array.from({
6400
+ length: Math.ceil(values.length / size)
6401
+ }, (_, index) => values.slice(index * size, (index + 1) * size));
6402
+ }
6403
+
5749
6404
  async function hydrateSnapshot(args) {
5750
6405
  const {client: client, clientForGdr: clientForGdr, instance: instance, overlay: overlay} = args, loaded = [], visited = /* @__PURE__ */ new Set;
5751
6406
  loaded.push({
@@ -5856,12 +6511,6 @@ function groupReads(reads) {
5856
6511
  return [ ...byClient.values() ].flatMap(groups => [ ...groups.values() ]);
5857
6512
  }
5858
6513
 
5859
- function chunks(values, size) {
5860
- return Array.from({
5861
- length: Math.ceil(values.length / size)
5862
- }, (_, index) => values.slice(index * size, (index + 1) * size));
5863
- }
5864
-
5865
6514
  function collectEntryDocUris(resolvedFieldEntries) {
5866
6515
  return entryDocRefs(resolvedFieldEntries).map(ref => ref.id);
5867
6516
  }
@@ -5896,16 +6545,26 @@ function loadCallContext({client: client, instanceId: instanceId, options: optio
5896
6545
 
5897
6546
  async function retryOnRevisionConflict(args) {
5898
6547
  const {client: client, instanceId: instanceId, options: options, commit: commit, onExhausted: onExhausted} = args;
5899
- for (let attempt = 1; attempt <= CONCURRENT_COMMIT_MAX_ATTEMPTS; attempt++) {
5900
- const ctx = await loadCallContext({
6548
+ return retryCommit({
6549
+ load: () => loadCallContext({
5901
6550
  client: client,
5902
6551
  instanceId: instanceId,
5903
6552
  options: options
5904
- });
6553
+ }),
6554
+ commit: commit,
6555
+ shouldRetry: isInstanceCommitRevisionConflict,
6556
+ onExhausted: onExhausted
6557
+ });
6558
+ }
6559
+
6560
+ async function retryCommit(args) {
6561
+ const {load: load, commit: commit, shouldRetry: shouldRetry, onExhausted: onExhausted} = args;
6562
+ for (let attempt = 1; attempt <= CONCURRENT_COMMIT_MAX_ATTEMPTS; attempt++) {
6563
+ const ctx = await load();
5905
6564
  try {
5906
6565
  return await commit(ctx);
5907
6566
  } catch (error) {
5908
- if (!isRevisionConflict(error)) throw error;
6567
+ if (!shouldRetry(error)) throw error;
5909
6568
  }
5910
6569
  }
5911
6570
  throw onExhausted();
@@ -5953,6 +6612,22 @@ async function ctxConditionParams(ctx, opts) {
5953
6612
  }, opts);
5954
6613
  }
5955
6614
 
6615
+ async function ctxNextEvaluationAt(ctx) {
6616
+ if (ctx.instance.completedAt !== void 0) return;
6617
+ const scopeFor = memoizedBy(activityName => ctxConditionParams(ctx, activityName === void 0 ? void 0 : {
6618
+ activityName: activityName
6619
+ })), sites = await stageTimeSites({
6620
+ definition: ctx.definition,
6621
+ stage: ctx.instance.currentStage,
6622
+ scopeFor: scopeFor
6623
+ });
6624
+ if (sites.length !== 0) return nextEvaluationInstant({
6625
+ sites: sites,
6626
+ dataset: ctx.snapshot.docs,
6627
+ now: ctx.now
6628
+ });
6629
+ }
6630
+
5956
6631
  async function ctxEvaluateConditionOutcome({ctx: ctx, condition: condition, opts: opts}) {
5957
6632
  return condition === void 0 ? "satisfied" : evaluateConditionOutcome({
5958
6633
  condition: condition,
@@ -6124,7 +6799,7 @@ async function resolveActivityFieldEntries(args) {
6124
6799
 
6125
6800
  async function resolveBindings(args) {
6126
6801
  const resolved = {};
6127
- for (const [key, groq] of Object.entries(args.bindings ?? {})) resolved[key] = await runGroq({
6802
+ for (const [key, groq] of Object.entries(args.bindings ?? {})) resolved[key] = await runGroq$1({
6128
6803
  groq: groq,
6129
6804
  params: args.params,
6130
6805
  snapshot: args.snapshot
@@ -6581,7 +7256,7 @@ function resolveGuard({guard: guard, instance: instance, stageName: stageName, n
6581
7256
  if (route === null) return null;
6582
7257
  const resource = assertSingleResource(route.targets), types = resolveMatchTypes(route.targets, guard.match.types);
6583
7258
  return {
6584
- doc: compileGuard({
7259
+ docs: compileGuards({
6585
7260
  id: route.guardId,
6586
7261
  resourceType: resource.type,
6587
7262
  resourceId: resource.id,
@@ -6613,19 +7288,63 @@ function resolveGuard({guard: guard, instance: instance, stageName: stageName, n
6613
7288
  async function upsertGuard(args) {
6614
7289
  const {client: client, doc: doc, exists: exists} = args;
6615
7290
  if (!exists) try {
6616
- await client.create(doc, {
6617
- ...SYNC_COMMIT,
6618
- tag: REQUEST_TAG.guardDeploy
6619
- });
6620
- return;
7291
+ return {
7292
+ created: await client.create(doc, {
7293
+ ...SYNC_COMMIT,
7294
+ tag: REQUEST_TAG.guardDeploy
7295
+ })
7296
+ };
6621
7297
  } catch (error) {
6622
7298
  if (!isCreateIdCollision(error)) throw error;
6623
7299
  }
6624
7300
  const {_id: _id, _type: _type, _rev: _rev, _createdAt: _createdAt, _updatedAt: _updatedAt, ...body} = doc;
6625
- await client.patch(doc._id).set(body).commit({
7301
+ return await client.patch(doc._id).set(body).commit({
6626
7302
  ...SYNC_COMMIT,
6627
7303
  tag: REQUEST_TAG.guardRefresh
7304
+ }), {
7305
+ created: void 0
7306
+ };
7307
+ }
7308
+
7309
+ async function retractCreatedGuards(guards) {
7310
+ for (const {client: client, doc: doc} of guards.toReversed()) {
7311
+ if (doc._rev === void 0) return new Error(`Cannot roll back guard ${doc._id}: created document has no revision`);
7312
+ try {
7313
+ const revisionCheck = client.patch(doc._id).set({
7314
+ predicate: doc.predicate
7315
+ }).ifRevisionId(doc._rev);
7316
+ await client.transaction().patch(revisionCheck).delete(doc._id).commit({
7317
+ tag: REQUEST_TAG.guardDeploy
7318
+ });
7319
+ } catch (error) {
7320
+ return error;
7321
+ }
7322
+ }
7323
+ }
7324
+
7325
+ async function deployResolvedGuard(args) {
7326
+ const {client: client, resourceKey: resourceKey, doc: doc, guard: guard, stageArgs: stageArgs, existingByResource: existingByResource} = args, localized = await localizeGuardMetadata({
7327
+ doc: doc,
7328
+ guard: guard,
7329
+ client: client,
7330
+ instance: stageArgs.instance,
7331
+ definition: stageArgs.definition
6628
7332
  });
7333
+ return (await upsertGuard({
7334
+ client: client,
7335
+ doc: localized,
7336
+ exists: existingByResource.get(resourceKey)?.has(doc._id) ?? !1
7337
+ })).created;
7338
+ }
7339
+
7340
+ async function failGuardDeploy(args) {
7341
+ const {cause: cause, stageName: stageName, deployed: deployed, createdGuards: createdGuards, updatedExistingGuard: updatedExistingGuard} = args, rollbackError = updatedExistingGuard ? void 0 : await retractCreatedGuards(createdGuards);
7342
+ throw deployed > 0 && (updatedExistingGuard || rollbackError !== void 0) ? new PartialGuardDeployError({
7343
+ stageName: stageName,
7344
+ deployed: deployed,
7345
+ cause: cause,
7346
+ rollbackError: rollbackError
7347
+ }) : cause;
6629
7348
  }
6630
7349
 
6631
7350
  function resolvedStageGuards(args) {
@@ -6638,10 +7357,12 @@ function resolvedStageGuards(args) {
6638
7357
  now: args.now,
6639
7358
  snapshot: args.snapshot
6640
7359
  });
6641
- resolved !== null && out.push({
6642
- client: args.clientForGdr(resolved.routeGdr),
6643
- resourceKey: resourceGdr(resourceFromParsed(resolved.routeGdr)),
6644
- value: resolved.doc,
7360
+ if (resolved === null) continue;
7361
+ const client = args.clientForGdr(resolved.routeGdr), resourceKey = resourceGdr(resourceFromParsed(resolved.routeGdr));
7362
+ for (const doc of resolved.docs) out.push({
7363
+ client: client,
7364
+ resourceKey: resourceKey,
7365
+ value: doc,
6645
7366
  guard: guard
6646
7367
  });
6647
7368
  }
@@ -6657,10 +7378,15 @@ function resolvedStageGuardRoutes(args) {
6657
7378
  }, out = [];
6658
7379
  for (const guard of stage?.guards ?? []) {
6659
7380
  const route = resolveGuardRoute(guard, ctx);
6660
- route !== null && out.push({
6661
- client: args.clientForGdr(route.routeGdr),
6662
- resourceKey: resourceGdr(resourceFromParsed(route.routeGdr)),
6663
- value: route.guardId
7381
+ if (route === null) continue;
7382
+ const client = args.clientForGdr(route.routeGdr), resourceKey = resourceGdr(resourceFromParsed(route.routeGdr));
7383
+ for (const guardId of compiledGuardIds({
7384
+ id: route.guardId,
7385
+ actions: guard.match.actions
7386
+ })) out.push({
7387
+ client: client,
7388
+ resourceKey: resourceKey,
7389
+ value: guardId
6664
7390
  });
6665
7391
  }
6666
7392
  return out;
@@ -6716,26 +7442,30 @@ async function deployStageGuards(args) {
6716
7442
  if (live === void 0 || live.currentStage !== args.stageName || live.abortedAt !== void 0) return;
6717
7443
  const routed = resolvedStageGuards(args), existingByResource = await existingGuardIds(groupByResource(routed));
6718
7444
  let deployed = 0;
7445
+ const createdGuards = [];
7446
+ let updatedExistingGuard = !1;
6719
7447
  for (const {client: client, resourceKey: resourceKey, value: doc, guard: guard} of routed) {
6720
7448
  try {
6721
- const localized = await localizeGuardMetadata({
7449
+ const created = await deployResolvedGuard({
7450
+ client: client,
7451
+ resourceKey: resourceKey,
6722
7452
  doc: doc,
6723
7453
  guard: guard,
6724
- client: client,
6725
- instance: args.instance,
6726
- definition: args.definition
7454
+ stageArgs: args,
7455
+ existingByResource: existingByResource
6727
7456
  });
6728
- await upsertGuard({
7457
+ created === void 0 ? updatedExistingGuard = !0 : createdGuards.push({
6729
7458
  client: client,
6730
- doc: localized,
6731
- exists: existingByResource.get(resourceKey)?.has(doc._id) ?? !1
7459
+ doc: created
6732
7460
  });
6733
7461
  } catch (cause) {
6734
- throw deployed > 0 ? new PartialGuardDeployError({
7462
+ await failGuardDeploy({
7463
+ cause: cause,
6735
7464
  stageName: args.stageName,
6736
7465
  deployed: deployed,
6737
- cause: cause
6738
- }) : cause;
7466
+ createdGuards: createdGuards,
7467
+ updatedExistingGuard: updatedExistingGuard
7468
+ });
6739
7469
  }
6740
7470
  deployed += 1;
6741
7471
  }
@@ -6967,7 +7697,8 @@ async function persistThenDeploy({ctx: ctx, mutation: mutation, deploy: deploy})
6967
7697
  committedRev: committed._rev,
6968
7698
  restore: instanceStateFields({
6969
7699
  ...ctx.instance,
6970
- minReaderModel: minReaderModelOf(ctx.instance)
7700
+ minReaderModel: minReaderModelOf(ctx.instance),
7701
+ definitionModelVersion: definitionAssignmentModel(ctx.instance)
6971
7702
  }),
6972
7703
  unset: ctx.instance.completedAt === void 0 ? [ "completedAt" ] : [],
6973
7704
  reversible: !spawned,
@@ -6999,6 +7730,29 @@ async function refreshStageGuards({ctx: ctx, mutation: mutation, stageName: stag
6999
7730
  });
7000
7731
  }
7001
7732
 
7733
+ function missingRequiredDocuments({instance: instance, definition: definition, snapshot: snapshot}) {
7734
+ return (definition.fields ?? []).filter(field => field.required === !0 && refKindAcceptsTypes(field.type)).flatMap(field => {
7735
+ const entry = instance.fields.find(candidate => candidate.name === field.name && candidate._type === field.type);
7736
+ return missingEntryDocRefs(entry === void 0 ? [] : [ entry ], snapshot.knownIds).map(reference => ({
7737
+ target: {
7738
+ scope: "workflow",
7739
+ field: field.name
7740
+ },
7741
+ reference: reference
7742
+ }));
7743
+ });
7744
+ }
7745
+
7746
+ function requiredDocumentRequirements(args) {
7747
+ return [ ...new Set(missingRequiredDocuments(args).map(({target: target}) => target.field)) ].map(field => {
7748
+ const title = args.definition.fields?.find(entry => entry.name === field)?.title ?? field;
7749
+ return {
7750
+ name: `field:${field}`,
7751
+ title: `Documents selected for “${title}” must be readable`
7752
+ };
7753
+ });
7754
+ }
7755
+
7002
7756
  async function discoverItems(args) {
7003
7757
  const {client: client, groq: groq, params: params, perspective: perspective} = args;
7004
7758
  return client.fetch(groq, paramsForLake(params), {
@@ -7232,7 +7986,7 @@ async function projectRowFields({ctx: ctx, sub: sub, childDefinition: childDefin
7232
7986
  for (const [name, groq] of Object.entries(sub.with ?? {})) {
7233
7987
  const declared = (childDefinition.fields ?? []).find(e => e.name === name);
7234
7988
  if (declared === void 0) throw new Error(`spawn.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`);
7235
- const raw = await runGroq({
7989
+ const raw = await runGroq$1({
7236
7990
  groq: groq,
7237
7991
  params: {
7238
7992
  ...parentScope,
@@ -7275,7 +8029,7 @@ async function projectRowFields({ctx: ctx, sub: sub, childDefinition: childDefin
7275
8029
  async function resolveContextHandoff({ctx: ctx, sub: sub, parentScope: parentScope}) {
7276
8030
  const out = {};
7277
8031
  for (const [name, groq] of Object.entries(sub.context ?? {})) {
7278
- const v2 = await runGroq({
8032
+ const v2 = await runGroq$1({
7279
8033
  groq: groq,
7280
8034
  params: parentScope,
7281
8035
  snapshot: ctx.snapshot
@@ -7574,7 +8328,7 @@ async function persistStageMove({ctx: ctx, mutation: mutation, exitedStage: exit
7574
8328
  }
7575
8329
 
7576
8330
  async function pickTransition(ctx, stage) {
7577
- for (const candidate of stage.transitions ?? []) {
8331
+ if (!(missingRequiredDocuments(ctx).length > 0)) for (const candidate of stage.transitions ?? []) {
7578
8332
  const outcome = await ctxEvaluateConditionOutcome({
7579
8333
  ctx: ctx,
7580
8334
  condition: candidate.when
@@ -7676,7 +8430,7 @@ const actorCache = /* @__PURE__ */ new WeakMap, grantsCache = /* @__PURE__ */ ne
7676
8430
 
7677
8431
  async function resolveAccess(taggedClient, args = {}) {
7678
8432
  const client = unwrapRequestTag(taggedClient), requestFn = lazyRequest(client);
7679
- if (requestFn === void 0) throw new 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).");
8433
+ if (requestFn === void 0) throw new ContractViolationError("workflow: no actor available. The engine resolves the actor from the client's token via `client.request({url: '/users/me'})`. Supply a real `@sanity/client` configured with a token (the test bench serves these endpoints per registered token).");
7680
8434
  const grantsPromise = args.grantsFromPath !== void 0 ? cachedGrants({
7681
8435
  client: client,
7682
8436
  requestFn: requestFn,
@@ -7745,20 +8499,32 @@ function cachedByKey(args) {
7745
8499
  }
7746
8500
 
7747
8501
  async function fetchActor(client, requestFn) {
7748
- const [resourceUser, globalUser] = await Promise.all([ fetchCurrentUser(requestFn, "the workflow resource host"), fetchGlobalUser(client) ]), resourceId = usableId(resourceUser), globalHostId = usableId(globalUser.user), carriedId = firstCarriedGlobalId([ globalHostId, resourceId ]), sessionId = carriedId ?? globalHostId ?? resourceId;
8502
+ const resourceUser = await fetchCurrentUser(requestFn, "the workflow resource host"), resourceId = usableId(resourceUser), resourceGlobalId = firstCarriedGlobalId([ resourceId ]) ?? directoryBridgeId(resourceUser?.sanityUserId);
8503
+ if (resourceGlobalId !== void 0) return identityFromUsers({
8504
+ id: resourceGlobalId,
8505
+ resourceUser: resourceUser
8506
+ });
8507
+ const globalUser = await fetchGlobalUser(client), globalHostId = usableId(globalUser.user), carriedId = firstCarriedGlobalId([ globalHostId ]), sessionId = carriedId ?? globalHostId ?? resourceId;
7749
8508
  if (sessionId === void 0) return;
7750
8509
  const bridge = carriedId === void 0 ? await bridgeProjectPrincipal({
7751
8510
  client: client,
7752
8511
  sessionId: sessionId,
7753
8512
  resourceId: resourceId
7754
8513
  }) : NO_BRIDGE, id = bridge.status === "resolved" ? bridge.globalId : sessionId;
7755
- refuseProjectScopedActor({
8514
+ return refuseProjectScopedActor({
7756
8515
  id: id,
7757
8516
  globalUser: globalUser,
7758
8517
  globalHostId: globalHostId,
7759
8518
  bridgeFailure: bridge.status === "unavailable" ? bridge : void 0
8519
+ }), identityFromUsers({
8520
+ id: id,
8521
+ resourceUser: resourceUser,
8522
+ globalUser: globalUser.user
7760
8523
  });
7761
- const roleNames = roleNamesFor(resourceUser, globalUser);
8524
+ }
8525
+
8526
+ function identityFromUsers(args) {
8527
+ const {id: id, resourceUser: resourceUser, globalUser: globalUser} = args, resourceId = usableId(resourceUser), roleNames = roleNamesFor(resourceUser, globalUser);
7762
8528
  return {
7763
8529
  actor: {
7764
8530
  kind: "person",
@@ -7774,7 +8540,7 @@ async function fetchActor(client, requestFn) {
7774
8540
  }
7775
8541
 
7776
8542
  function roleNamesFor(resourceUser, globalUser) {
7777
- return (resourceUser?.roles?.length ? resourceUser : globalUser.user)?.roles?.map(r => r.name).filter(n => !!n) ?? [];
8543
+ return (resourceUser?.roles?.length ? resourceUser : globalUser)?.roles?.map(r => r.name).filter(n => !!n) ?? [];
7778
8544
  }
7779
8545
 
7780
8546
  const NO_BRIDGE = {
@@ -7838,11 +8604,11 @@ function usableId(user) {
7838
8604
  async function fetchCurrentUser(requestFn, hostDescription) {
7839
8605
  try {
7840
8606
  return await requestFn({
7841
- uri: "/users/me",
8607
+ url: "/users/me",
7842
8608
  tag: REQUEST_TAG.accessResolveActor
7843
8609
  });
7844
8610
  } catch (err) {
7845
- throw new Error(`workflow: /users/me request against ${hostDescription} failed. The engine resolves the actor from the client's token via \`client.request({ uri: '/users/me' })\`. Check the token/connectivity.`, {
8611
+ throw new Error(`workflow: /users/me request against ${hostDescription} failed. The engine resolves the actor from the client's token via \`client.request({url: '/users/me'})\`. Check the token/connectivity.`, {
7846
8612
  cause: err
7847
8613
  });
7848
8614
  }
@@ -7859,7 +8625,7 @@ async function fetchGlobalUser(client) {
7859
8625
  };
7860
8626
  try {
7861
8627
  const user = await sibling.request({
7862
- uri: "/users/me",
8628
+ url: "/users/me",
7863
8629
  tag: REQUEST_TAG.accessResolveActor
7864
8630
  }), id = usableId(user);
7865
8631
  return sibling.requireGlobalPrincipal && id !== void 0 && classifyPrincipalId(id).namespace === "project" ? {
@@ -7982,7 +8748,7 @@ async function resolveOrganizationId(args) {
7982
8748
  load: async () => {
7983
8749
  try {
7984
8750
  const project = await requestFn({
7985
- uri: `/projects/${encodeURIComponent(projectId)}`,
8751
+ url: `/projects/${encodeURIComponent(projectId)}`,
7986
8752
  tag: REQUEST_TAG.accessResolveOrg
7987
8753
  });
7988
8754
  if (!isRecord(project)) return;
@@ -8007,15 +8773,17 @@ function httpStatusOf(err) {
8007
8773
  return typeof status == "number" ? status : void 0;
8008
8774
  }
8009
8775
 
8776
+ function attributesRequestOf(client) {
8777
+ const version = client.config?.().apiVersion, request = typeof client.withConfig == "function" || version === void 0 || version === ENGINE_API_VERSION ? lazyRequest(pinApiVersion(client)) : void 0;
8778
+ return request === void 0 && console.warn("workflow: skipped org attributes fetch; the client cannot issue API-version-pinned requests. Advisory $attributes stays undefined (conditions referencing it fail closed)."),
8779
+ request;
8780
+ }
8781
+
8010
8782
  async function fetchAttributesCached(args) {
8011
- const {client: client, orgId: orgId} = args, sibling = globalHostRequestOf(client);
8012
- if ("reason" in sibling) {
8013
- console.warn(`workflow: skipped org attributes fetch (${sibling.reason}); advisory $attributes stays undefined (conditions referencing it fail closed).`);
8014
- return;
8015
- }
8016
- try {
8017
- const response = await sibling.request({
8018
- uri: `/organizations/${encodeURIComponent(orgId)}/users/me/attributes?limit=${ATTRIBUTES_FETCH_LIMIT}`,
8783
+ const {client: client, orgId: orgId} = args, request = attributesRequestOf(client);
8784
+ if (request !== void 0) try {
8785
+ const response = await request({
8786
+ url: `/organizations/${encodeURIComponent(orgId)}/users/me/attributes?limit=${ATTRIBUTES_FETCH_LIMIT}`,
8019
8787
  tag: REQUEST_TAG.accessAttributes
8020
8788
  }), envelope = isRecord(response) ? response : void 0;
8021
8789
  if (envelope?.hasMore === !0) {
@@ -8061,91 +8829,6 @@ async function callerBoundVarsForCall(args) {
8061
8829
  });
8062
8830
  }
8063
8831
 
8064
- function requirementDescriptor(requirement) {
8065
- return {
8066
- name: requirement.name,
8067
- ...requirement.title !== void 0 ? {
8068
- title: requirement.title
8069
- } : {},
8070
- ...requirement.description !== void 0 ? {
8071
- description: requirement.description
8072
- } : {}
8073
- };
8074
- }
8075
-
8076
- function subjectDenialLabels(denied) {
8077
- return denied.map(d => `${d.permission} on ${d.subject} (${d.resource})`);
8078
- }
8079
-
8080
- class ActionDisabledError extends WorkflowError {
8081
- reason;
8082
- activity;
8083
- action;
8084
- constructor(args) {
8085
- super("action-disabled", formatDisabledReason({
8086
- activity: args.activity,
8087
- action: args.action,
8088
- reason: args.reason
8089
- })), this.name = "ActionDisabledError", this.reason = args.reason, this.activity = args.activity,
8090
- this.action = args.action;
8091
- }
8092
- }
8093
-
8094
- class StartNotAllowedError extends WorkflowError {
8095
- definition;
8096
- unmetRequirements;
8097
- constructor(args) {
8098
- super("start-not-allowed", `startInstance refused definition "${args.definition}": ${args.unmetRequirements.map(requirement => requirement.title ?? requirement.name).join(", ")}. Pre-flight the verdict with evaluateStart.`),
8099
- this.name = "StartNotAllowedError", this.definition = args.definition, this.unmetRequirements = args.unmetRequirements;
8100
- }
8101
- }
8102
-
8103
- function actionRendering(action) {
8104
- const kind = action.disabledReason?.kind;
8105
- return kind === "filter-failed" ? "absent" : action.triggered === !0 || kind === "cascade-fired" ? "automation" : "button";
8106
- }
8107
-
8108
- const disabledReasonDetail = {
8109
- "filter-failed": r => `action filter returned false${r.detail ? ` (${r.detail})` : ""}`,
8110
- "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`,
8111
- "activity-not-active": r => `activity status is "${r.status}"`,
8112
- "stage-terminal": r => `stage "${r.stage}" is terminal`,
8113
- "instance-completed": r => `instance completed at ${r.completedAt}`,
8114
- "instance-aborted": r => `instance aborted at ${r.abortedAt}`,
8115
- "requirements-unmet": r => `unmet requirement(s): ${r.unmetRequirements.map(requirement => requirement.name).join(", ")}`,
8116
- "subject-permission-denied": r => `missing subject permission(s): ${subjectDenialLabels(r.denied).join(", ")}`
8117
- };
8118
-
8119
- function actionDisabledDetail(reason) {
8120
- return disabledReasonDetail[reason.kind](reason);
8121
- }
8122
-
8123
- function formatDisabledReason({activity: activity, action: action, reason: reason}) {
8124
- return `Action "${activity}:${action}" is not allowed: ${actionDisabledDetail(reason)}`;
8125
- }
8126
-
8127
- class EditFieldDeniedError extends WorkflowError {
8128
- reason;
8129
- target;
8130
- constructor(args) {
8131
- super("edit-field-denied", formatEditDisabledReason(args.target, args.reason)),
8132
- this.name = "EditFieldDeniedError", this.reason = args.reason, this.target = args.target;
8133
- }
8134
- }
8135
-
8136
- const editDisabledReasonDetail = {
8137
- "not-editable": () => "field is not declared editable",
8138
- "instance-completed": r => `instance completed at ${r.completedAt}`,
8139
- "instance-aborted": r => `instance aborted at ${r.abortedAt}`,
8140
- "edit-window-closed": r => `edit window closed (${r.detail})`,
8141
- "editor-not-permitted": r => `editor not permitted (${r.predicate})`
8142
- };
8143
-
8144
- function formatEditDisabledReason(target, reason) {
8145
- const detail = editDisabledReasonDetail[reason.kind](reason), where = target.activity !== void 0 ? `${target.activity}.${target.field}` : target.field;
8146
- return `Field "${target.scope}:${where}" is not editable: ${detail}`;
8147
- }
8148
-
8149
8832
  async function fireAction(args) {
8150
8833
  const {client: client, instanceId: instanceId, activity: activity, action: action, params: params, requestRecord: requestRecord, options: options} = args;
8151
8834
  return retryOnRevisionConflict({
@@ -8214,6 +8897,15 @@ async function resolveActionCommit({ctx: ctx, activityName: activityName, action
8214
8897
  actionName: actionName,
8215
8898
  status: entry?.status
8216
8899
  });
8900
+ const unmetRequirements = requiredDocumentRequirements(ctx);
8901
+ if (unmetRequirements.length > 0) throw new ActionDisabledError({
8902
+ activity: activityName,
8903
+ action: actionName,
8904
+ reason: {
8905
+ kind: "requirements-unmet",
8906
+ unmetRequirements: unmetRequirements
8907
+ }
8908
+ });
8217
8909
  const params = validateActionParams({
8218
8910
  action: action,
8219
8911
  activityName: activityName,
@@ -8313,7 +9005,7 @@ async function commitAction({ctx: ctx, activityName: activityName, actionName: a
8313
9005
  actionName: actionName,
8314
9006
  callerParams: callerParams,
8315
9007
  options: options
8316
- }), mutation = startMutation(ctx.instance);
9008
+ }), mutation = startRevisionRetryMutation(ctx.instance);
8317
9009
  recordProcessedRequest({
8318
9010
  mutation: mutation,
8319
9011
  record: requestRecord,
@@ -8370,13 +9062,13 @@ async function runTriggeredActions({ctx: ctx, mutation: mutation, stage: stage})
8370
9062
  async function runActivityTriggers({ctx: ctx, mutation: mutation, activity: activity}) {
8371
9063
  let fires = 0, ranFieldOps = !1;
8372
9064
  for (const action of activity.actions ?? []) {
8373
- if (!isCascadeFired(action)) continue;
8374
9065
  const entry = currentActivities(mutation).find(e => e.name === activity.name);
8375
9066
  if (entry === void 0 || entry.status !== "active") break;
8376
- if ((entry.firedActions ?? []).includes(action.name) || !await triggerIsLive({
9067
+ if (!await triggerIsLive({
8377
9068
  ctx: ctx,
8378
9069
  activity: activity,
8379
- action: action
9070
+ action: action,
9071
+ entry: entry
8380
9072
  })) continue;
8381
9073
  const result = await fireTriggeredAction({
8382
9074
  ctx: ctx,
@@ -8393,8 +9085,12 @@ async function runActivityTriggers({ctx: ctx, mutation: mutation, activity: acti
8393
9085
  };
8394
9086
  }
8395
9087
 
8396
- async function triggerIsLive({ctx: ctx, activity: activity, action: action}) {
8397
- if (!tokenMayExecute({
9088
+ function isUnfiredTrigger(action, entry) {
9089
+ return isCascadeFired(action) && !(entry.firedActions ?? []).includes(action.name);
9090
+ }
9091
+
9092
+ async function triggerIsLive({ctx: ctx, activity: activity, action: action, entry: entry}) {
9093
+ if (!isUnfiredTrigger(action, entry) || missingRequiredDocuments(ctx).length > 0 || !tokenMayExecute({
8398
9094
  actor: ctx.actor,
8399
9095
  action: action,
8400
9096
  ctx: ctx
@@ -8479,7 +9175,7 @@ async function primeInitialStage(args) {
8479
9175
  })
8480
9176
  });
8481
9177
  const terminal = isTerminalStage(stage), committed = await client.patch(instance._id).set({
8482
- stages: [ initialStageEntry ],
9178
+ stages: assignmentStagesForWrite([ initialStageEntry ], writesSingularAssignmentLists(instance)),
8483
9179
  lastChangedAt: now,
8484
9180
  ...terminal ? {
8485
9181
  completedAt: now
@@ -8537,32 +9233,59 @@ async function runCascadeHop({client: client, instanceId: instanceId, actor: act
8537
9233
  settlingCohorts: settlingCohorts
8538
9234
  } : {},
8539
9235
  memberRolesLoader: memberRolesLoader
8540
- }, ctx = instance === void 0 ? await loadContext({
8541
- client: client,
8542
- instanceId: instanceId,
8543
- options: contextOptions
8544
- }) : await buildEngineContext({
8545
- client: client,
8546
- instance: instance,
8547
- definition: parseDefinitionSnapshot(instance),
8548
- ...contextOptions
9236
+ };
9237
+ let preloadedInstance = instance;
9238
+ return retryCommit({
9239
+ load: async () => {
9240
+ if (preloadedInstance === void 0) return loadContext({
9241
+ client: client,
9242
+ instanceId: instanceId,
9243
+ options: contextOptions
9244
+ });
9245
+ const loaded = preloadedInstance;
9246
+ return preloadedInstance = void 0, buildEngineContext({
9247
+ client: client,
9248
+ instance: loaded,
9249
+ definition: parseDefinitionSnapshot(loaded),
9250
+ ...contextOptions
9251
+ });
9252
+ },
9253
+ commit: ctx => commitCascadeHop({
9254
+ ctx: ctx,
9255
+ actor: actor
9256
+ }),
9257
+ shouldRetry: isInstanceCommitRevisionConflict,
9258
+ onExhausted: () => new ConcurrentCascadeError({
9259
+ instanceId: instanceId,
9260
+ attempts: CONCURRENT_COMMIT_MAX_ATTEMPTS
9261
+ })
8549
9262
  });
9263
+ }
9264
+
9265
+ async function commitCascadeHop({ctx: ctx, actor: actor}) {
8550
9266
  if (isTerminal(ctx)) return {
8551
9267
  moved: !1
8552
9268
  };
8553
- const stage = findStage(ctx.definition, ctx.instance.currentStage), mutation = startMutation(ctx.instance), fired = await runTriggeredActions({
9269
+ const stage = findStage(ctx.definition, ctx.instance.currentStage), mutation = startRevisionRetryMutation(ctx.instance), fired = await runTriggeredActions({
8554
9270
  ctx: ctx,
8555
9271
  mutation: mutation,
8556
9272
  stage: stage
8557
9273
  }), hopCtx = liveViewContext(ctx, mutation), transition = await pickTransition(hopCtx, stage);
8558
- return transition === void 0 ? (fired.fires > 0 && await persistThenMaybeRefresh({
8559
- ctx: ctx,
8560
- mutation: mutation,
8561
- stageName: stage.name,
8562
- didChangeState: fired.ranFieldOps
8563
- }), {
8564
- moved: !1
8565
- }) : (await commitStageMove({
9274
+ if (transition === void 0) {
9275
+ const nextEvaluationAt = await ctxNextEvaluationAt(hopCtx);
9276
+ return fired.fires > 0 && await persistThenMaybeRefresh({
9277
+ ctx: ctx,
9278
+ mutation: mutation,
9279
+ stageName: stage.name,
9280
+ didChangeState: fired.ranFieldOps
9281
+ }), {
9282
+ moved: !1,
9283
+ ...nextEvaluationAt !== void 0 ? {
9284
+ nextEvaluationAt: nextEvaluationAt
9285
+ } : {}
9286
+ };
9287
+ }
9288
+ return await commitStageMove({
8566
9289
  ctx: ctx,
8567
9290
  mutation: mutation,
8568
9291
  fromStage: stage,
@@ -8572,7 +9295,7 @@ async function runCascadeHop({client: client, instanceId: instanceId, actor: act
8572
9295
  actor: actor
8573
9296
  }), {
8574
9297
  moved: !0
8575
- });
9298
+ };
8576
9299
  }
8577
9300
 
8578
9301
  async function cascadeAutoTransitions({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, memberRolesLoader: memberRolesLoader, clock: clock, executionContext: executionContext, telemetry: telemetry, overlay: overlay, settlingCohorts: settlingCohorts}) {
@@ -8588,8 +9311,7 @@ async function cascadeAutoTransitions({client: client, instanceId: instanceId, a
8588
9311
  clock: clock,
8589
9312
  executionContext: executionContext,
8590
9313
  telemetry: telemetry
8591
- });
8592
- if (!(await runCascadeHop({
9314
+ }), hop = await runCascadeHop({
8593
9315
  client: client,
8594
9316
  instanceId: instanceId,
8595
9317
  actor: actor,
@@ -8604,7 +9326,13 @@ async function cascadeAutoTransitions({client: client, instanceId: instanceId, a
8604
9326
  ...drained.drained ? {} : {
8605
9327
  instance: drained.instance
8606
9328
  }
8607
- })).moved) return count;
9329
+ });
9330
+ if (!hop.moved) return {
9331
+ cascaded: count,
9332
+ ...hop.nextEvaluationAt !== void 0 ? {
9333
+ nextEvaluationAt: hop.nextEvaluationAt
9334
+ } : {}
9335
+ };
8608
9336
  if (count++, count >= CASCADE_LIMIT) throw new CascadeLimitError({
8609
9337
  instanceId: instanceId,
8610
9338
  limit: CASCADE_LIMIT
@@ -8904,6 +9632,8 @@ async function recordOrphanedPropagation({ctx: ctx, mutation: mutation, child: c
8904
9632
  function startMutation(instance) {
8905
9633
  return {
8906
9634
  minReaderModel: minReaderModelOf(instance),
9635
+ definitionSnapshot: instance.definitionSnapshot,
9636
+ definitionModelVersion: definitionAssignmentModel(instance),
8907
9637
  currentStage: instance.currentStage,
8908
9638
  fields: (instance.fields ?? []).map(s => ({
8909
9639
  ...s
@@ -8951,11 +9681,17 @@ function startMutation(instance) {
8951
9681
  };
8952
9682
  }
8953
9683
 
9684
+ function startRevisionRetryMutation(instance) {
9685
+ const mutation = startMutation(instance);
9686
+ return mutation.commitErrorMapper = error => isRetryableInstanceCommitConflict(error) ? new InstanceCommitRevisionConflict(error) : error,
9687
+ mutation;
9688
+ }
9689
+
8954
9690
  function instanceStateFields(src) {
8955
- const state = {
9691
+ const listModel = isAssignmentListModel(src.definitionModelVersion), state = {
8956
9692
  currentStage: src.currentStage,
8957
- fields: src.fields,
8958
- stages: src.stages,
9693
+ fields: assignmentFieldsForWrite(src.fields, listModel),
9694
+ stages: assignmentStagesForWrite(src.stages, listModel),
8959
9695
  subworkflows: src.subworkflows ?? [],
8960
9696
  pendingEffects: src.pendingEffects,
8961
9697
  effectHistory: src.effectHistory,
@@ -8972,7 +9708,10 @@ function instanceStateFields(src) {
8972
9708
  return {
8973
9709
  ...modelStampFor({
8974
9710
  documentType: "instance",
8975
- document: state,
9711
+ document: {
9712
+ ...state,
9713
+ definitionSnapshot: src.definitionSnapshot
9714
+ },
8976
9715
  storedMinReaderModel: src.minReaderModel
8977
9716
  }),
8978
9717
  ...state
@@ -9007,11 +9746,11 @@ async function persist(ctx, mutation) {
9007
9746
  ...instanceStateFields(mutation),
9008
9747
  lastChangedAt: ctx.now
9009
9748
  }, pendingCreates = mutation.pendingCreates;
9010
- if (pendingCreates.length === 0) return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit(SYNC_COMMIT);
9749
+ if (pendingCreates.length === 0) return commitInstanceWrite(() => ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit(SYNC_COMMIT), mutation.commitErrorMapper);
9011
9750
  const tx = ctx.client.transaction();
9012
- for (const {body: body} of pendingCreates) tx.create(body);
9751
+ for (const {body: body} of pendingCreates) tx.create(instanceDocumentForWrite(body));
9013
9752
  tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)),
9014
- await tx.commit(), mutation.pendingCreates = [];
9753
+ await commitInstanceWrite(() => tx.commit(), mutation.commitErrorMapper), mutation.pendingCreates = [];
9015
9754
  const actorForPriming = ctx.actor, settlingCohorts = new Set(ctx.settlingCohorts).add(ctx.instance._id);
9016
9755
  for (const {body: body} of pendingCreates) try {
9017
9756
  const stepArgs = spawnStepArgs(ctx, {
@@ -9038,6 +9777,14 @@ async function persist(ctx, mutation) {
9038
9777
  return reloaded;
9039
9778
  }
9040
9779
 
9780
+ async function commitInstanceWrite(write, mapError) {
9781
+ try {
9782
+ return await write();
9783
+ } catch (error) {
9784
+ throw mapError?.(error) ?? error;
9785
+ }
9786
+ }
9787
+
9041
9788
  async function settleSpawnBatch(ctx, args) {
9042
9789
  try {
9043
9790
  await propagateSpawnBatch({
@@ -9300,7 +10047,7 @@ async function commitCompleteEffect({ctx: ctx, effectKey: effectKey, status: sta
9300
10047
  outputs: outputs,
9301
10048
  memberRoles: ctx.memberRoles,
9302
10049
  roleAliases: ctx.definition.roleAliases
9303
- }), mutation = startMutation(ctx.instance);
10050
+ }), mutation = startRevisionRetryMutation(ctx.instance);
9304
10051
  recordProcessedRequest({
9305
10052
  mutation: mutation,
9306
10053
  record: requestRecord,
@@ -9447,7 +10194,7 @@ function staleClaimReason(args) {
9447
10194
  if (isClaimExpired(claim, args.now)) return "lease-expired";
9448
10195
  }
9449
10196
 
9450
- function renewClaimLease(args) {
10197
+ function renewClaimLease$1(args) {
9451
10198
  const {mutation: mutation, effectKey: effectKey, now: now, leaseMs: leaseMs} = args;
9452
10199
  mutation.pendingEffects = mutation.pendingEffects.map(entry => entry._key === effectKey && entry.claim !== void 0 ? {
9453
10200
  ...entry,
@@ -9479,12 +10226,12 @@ async function commitReport({ctx: ctx, effectKey: effectKey, claimToken: claimTo
9479
10226
  effect: pending.name,
9480
10227
  issues: [ "a mid-dispatch report must carry at least one field op — there is nothing to commit" ]
9481
10228
  });
9482
- const mutation = startMutation(ctx.instance);
10229
+ const mutation = startRevisionRetryMutation(ctx.instance);
9483
10230
  return recordProcessedRequest({
9484
10231
  mutation: mutation,
9485
10232
  record: requestRecord,
9486
10233
  now: ctx.now
9487
- }), renewClaimLease({
10234
+ }), renewClaimLease$1({
9488
10235
  mutation: mutation,
9489
10236
  effectKey: effectKey,
9490
10237
  now: ctx.now,
@@ -9515,7 +10262,7 @@ async function commitReport({ctx: ctx, effectKey: effectKey, claimToken: claimTo
9515
10262
  };
9516
10263
  }
9517
10264
 
9518
- const RESET_ACTIVITY_TARGETS = [ "active", "skipped" ];
10265
+ const RESET_ACTIVITY_TARGETS = _exhaustiveOptions()([ "active", "skipped" ]);
9519
10266
 
9520
10267
  function isResetActivityTarget(value) {
9521
10268
  return RESET_ACTIVITY_TARGETS.includes(value);
@@ -9573,6 +10320,100 @@ async function commitResetActivity({ctx: ctx, activity: activity, to: to, reques
9573
10320
  };
9574
10321
  }
9575
10322
 
10323
+ const AvailabilityResponse = v.object({
10324
+ documents: v.array(v.object({
10325
+ _id: v.string()
10326
+ })),
10327
+ omitted: v.array(v.object({
10328
+ id: v.string(),
10329
+ reason: v.picklist([ "existence", "permission" ])
10330
+ }))
10331
+ }), AVAILABILITY_BATCH_SIZE = 100, AVAILABILITY_CONCURRENCY = 4, pendingReads = /* @__PURE__ */ new WeakMap;
10332
+
10333
+ function groupsFor(documents, clientForGdr) {
10334
+ const groups = /* @__PURE__ */ new Map;
10335
+ for (const {reference: reference} of documents) {
10336
+ const parsed = parseGdr(reference.id), resource = resourceFromParsed(parsed), key = resourceGdr(resource), group = groups.get(key) ?? {
10337
+ client: clientForGdr(parsed),
10338
+ resource: resource,
10339
+ documents: /* @__PURE__ */ new Map
10340
+ };
10341
+ group.documents.set(reference.id, parsed.documentId), groups.set(key, group);
10342
+ }
10343
+ return [ ...groups.values() ];
10344
+ }
10345
+
10346
+ async function readGroup(group) {
10347
+ let response;
10348
+ try {
10349
+ const url = documentUrl(group);
10350
+ url !== void 0 && group.client.request !== void 0 ? response = await readResponse(group.client, url) : logUnavailable({
10351
+ resource: group.resource,
10352
+ reason: "unsupported-client"
10353
+ });
10354
+ } catch (error) {
10355
+ logUnavailable({
10356
+ resource: group.resource,
10357
+ reason: v.isValiError(error) ? "invalid-response" : "request-failed",
10358
+ error: error
10359
+ });
10360
+ }
10361
+ return [ ...group.documents ].map(([uri, id]) => [ uri, availabilityOf(response, id) ]);
10362
+ }
10363
+
10364
+ function logUnavailable({resource: resource, reason: reason, error: error}) {
10365
+ const statusCode = typeof error == "object" && error !== null && "statusCode" in error ? error.statusCode : void 0;
10366
+ console.warn("[workflows] Document availability could not be confirmed.", {
10367
+ resource: resourceGdr(resource),
10368
+ reason: reason,
10369
+ ...typeof statusCode == "number" ? {
10370
+ statusCode: statusCode
10371
+ } : {}
10372
+ });
10373
+ }
10374
+
10375
+ function availabilityOf(response, id) {
10376
+ if (response === void 0) return "unreadable";
10377
+ const omissions = response.omitted.filter(entry => getPublishedId(DocumentId(entry.id)) === id);
10378
+ return omissions.some(entry => entry.reason === "permission") ? "unreadable" : response.documents.some(doc => getPublishedId(DocumentId(doc._id)) === id) ? "outside-perspective" : omissions.some(entry => entry.id === id && entry.reason === "existence") ? "deleted" : "unreadable";
10379
+ }
10380
+
10381
+ function documentUrl(group) {
10382
+ const ids = [ ...new Set(group.documents.values()) ].sort().map(encodeURIComponent).join(",");
10383
+ let url;
10384
+ if ("getDataUrl" in group.client && group.client.getDataUrl !== void 0) url = group.client.getDataUrl("doc", ids); else {
10385
+ if (group.resource.type !== "dataset") return;
10386
+ const {dataset: dataset} = datasetResourceParts(group.resource.id);
10387
+ url = `/data/doc/${dataset}/${ids}`;
10388
+ }
10389
+ return `${url}?includeAllVersions=true`;
10390
+ }
10391
+
10392
+ function readResponse(client, url) {
10393
+ const identity = unwrapRequestTag(client), pending = pendingReads.get(identity) ?? /* @__PURE__ */ new Map;
10394
+ pendingReads.set(identity, pending);
10395
+ const existing = pending.get(url);
10396
+ if (existing !== void 0) return existing;
10397
+ const result = client.request({
10398
+ url: url
10399
+ }).then(response => v.parse(AvailabilityResponse, response)).finally(() => pending.delete(url));
10400
+ return pending.set(url, result), result;
10401
+ }
10402
+
10403
+ function documentAvailabilityReader(args) {
10404
+ return async documents => {
10405
+ const result = /* @__PURE__ */ new Map, groups = groupsFor(documents, args.clientForGdr).flatMap(group => chunks([ ...group.documents ], AVAILABILITY_BATCH_SIZE).map(entries => ({
10406
+ ...group,
10407
+ documents: new Map(entries)
10408
+ })));
10409
+ for (const wave of chunks(groups, AVAILABILITY_CONCURRENCY)) {
10410
+ const rows = await Promise.all(wave.map(readGroup));
10411
+ for (const [uri, availability] of rows.flat()) result.set(uri, availability);
10412
+ }
10413
+ return result;
10414
+ };
10415
+ }
10416
+
9576
10417
  const REPLAY_SURFACE = {
9577
10418
  allows: () => !0,
9578
10419
  description: "any resource (in-memory replay; the committing surface enforces)"
@@ -9627,6 +10468,10 @@ function replayable(args) {
9627
10468
  return (stage.activities ?? []).every(declared => primed.has(declared.name)) ? !(stage.activities ?? []).flatMap(declared => declared.actions ?? []).some(sibling => isCascadeFired(sibling) && sibling.spawn !== void 0) : !1;
9628
10469
  }
9629
10470
 
10471
+ function clientGuardDereference(client) {
10472
+ return async ({_ref: _ref}) => await client.getDocument(toBareId(_ref)) ?? null;
10473
+ }
10474
+
9630
10475
  async function buildFieldInsights({sites: sites, snapshot: snapshot}) {
9631
10476
  const insights = [];
9632
10477
  for (const field of fieldsReadAcross(sites)) {
@@ -9720,6 +10565,80 @@ function fabricatable(assignment) {
9720
10565
  return assignment.target.path.every(segment => typeof segment != "number" || segment <= MAX_COUNTERFACTUAL_INDEX);
9721
10566
  }
9722
10567
 
10568
+ function missingDocumentsOf({instance: instance, snapshot: snapshot, definition: definition}) {
10569
+ const stage = findStageNode({
10570
+ definition: definition,
10571
+ stageName: instance.currentStage
10572
+ });
10573
+ return activeFieldEntries(instance).flatMap(({target: target, entry: entry}) => {
10574
+ const declarations = {
10575
+ workflow: definition?.fields,
10576
+ stage: stage?.fields,
10577
+ activity: stage?.activities?.find(activity => activity.name === target.activity)?.fields
10578
+ }, title = entry.title ?? declarations[target.scope ?? "workflow"]?.find(field => field.name === target.field)?.title;
10579
+ return missingEntryDocRefs([ entry ], snapshot.knownIds).map(reference => ({
10580
+ target: target,
10581
+ reference: reference,
10582
+ ...title !== void 0 ? {
10583
+ title: title
10584
+ } : {}
10585
+ }));
10586
+ });
10587
+ }
10588
+
10589
+ function blockingConditions(evaluation) {
10590
+ const transitions = evaluation.currentStage.transitions.some(entry => entry.whenSatisfied) ? [] : evaluation.currentStage.transitions;
10591
+ return [ ...evaluation.currentStage.activities.filter(entry => entry.status === "active" && (entry.activity.actions ?? []).length > 0).flatMap(entry => (entry.unmetRequirements ?? []).map(requirement => ({
10592
+ insight: entry.requirementInsights?.[requirement.name],
10593
+ activity: entry.activity.name
10594
+ }))), ...transitions.map(entry => ({
10595
+ insight: entry.insight
10596
+ })) ];
10597
+ }
10598
+
10599
+ function readsDocumentContent({entry: entry, read: read, atom: atom}) {
10600
+ return atomReadsDataset(atom) ? !0 : isSingleDocRefKind(entry._type) && read.path.length > 1 && read.path[1] !== "_id" && read.path[1] !== "_type";
10601
+ }
10602
+
10603
+ function contentTargets({atoms: atoms, fields: fields, activity: activity}) {
10604
+ return atoms.flatMap(atom => atom.reads.flatMap(read => {
10605
+ if (read.variable !== "fields") return [];
10606
+ const field = fields.findLast(({entry: entry, target: target}) => entry.name === read.path[0] && (target.scope !== "activity" || target.activity === activity));
10607
+ return field !== void 0 && readsDocumentContent({
10608
+ entry: field.entry,
10609
+ read: read,
10610
+ atom: atom
10611
+ }) ? [ _fieldTargetLabel(field.target) ] : [];
10612
+ }));
10613
+ }
10614
+
10615
+ async function blockingAtoms(condition, args) {
10616
+ const atoms = condition.insight?.blockedBy.map(({atom: atom}) => atom) ?? [], names = [ ...new Set(atoms.flatMap(atom => atom.reads).filter(read => read.path.length === 0 && args.definition.predicates?.[read.variable] !== void 0).map(read => read.variable)) ];
10617
+ if (names.length === 0) return atoms;
10618
+ const site = args.sites.find(entry => entry.insight === condition.insight);
10619
+ if (site === void 0) throw new Error("Missing evaluated scope for a blocking condition");
10620
+ const dependencies = await Promise.all(names.map(async name => {
10621
+ const predicate = args.definition.predicates?.[name];
10622
+ if (predicate === void 0) throw new Error(`Missing predicate "${name}"`);
10623
+ return (await explainCondition({
10624
+ condition: site.params[name] === !0 ? `!(${predicate})` : predicate,
10625
+ dataset: args.snapshot.docs,
10626
+ params: site.params
10627
+ })).blockedBy.map(({atom: atom}) => atom);
10628
+ }));
10629
+ return [ ...atoms, ...dependencies.flat() ];
10630
+ }
10631
+
10632
+ async function blockingMissingDocuments(args) {
10633
+ if (!args.missingDocuments?.length) return [];
10634
+ const fields = activeFieldEntries(args.instance), targets = (await Promise.all(blockingConditions(args).map(async condition => contentTargets({
10635
+ atoms: await blockingAtoms(condition, args),
10636
+ fields: fields,
10637
+ activity: condition.activity
10638
+ })))).flat(), blocked = /* @__PURE__ */ new Set([ ...targets, ...missingRequiredDocuments(args).map(({target: target}) => _fieldTargetLabel(target)) ]);
10639
+ return args.missingDocuments.filter(({target: target}) => blocked.has(_fieldTargetLabel(target)));
10640
+ }
10641
+
9723
10642
  async function subjectResourceGrants(args) {
9724
10643
  const {clientForGdr: clientForGdr, instance: instance} = args, clients = /* @__PURE__ */ new Map;
9725
10644
  for (const {parsed: parsed, resource: resource} of foreignSubjectRefs(instance)) {
@@ -9782,6 +10701,10 @@ async function evaluateInstance(args) {
9782
10701
  guards: guards,
9783
10702
  now: now,
9784
10703
  resourceGrants: resourceGrants,
10704
+ documentAvailability: documentAvailabilityReader({
10705
+ clientForGdr: clientForGdr
10706
+ }),
10707
+ guardDereference: clientGuardDereference(client),
9785
10708
  ...localPrincipalId !== void 0 ? {
9786
10709
  localPrincipalId: localPrincipalId
9787
10710
  } : {},
@@ -9794,16 +10717,6 @@ async function evaluateInstance(args) {
9794
10717
  });
9795
10718
  }
9796
10719
 
9797
- function memoizedByName(render) {
9798
- const rendered = /* @__PURE__ */ new Map;
9799
- return activityName => {
9800
- const hit = rendered.get(activityName);
9801
- if (hit !== void 0) return hit;
9802
- const scope = render(activityName);
9803
- return rendered.set(activityName, scope), scope;
9804
- };
9805
- }
9806
-
9807
10720
  async function callerBoundProjectionScopes(args) {
9808
10721
  const can = await advisoryCan({
9809
10722
  instance: args.instance,
@@ -9824,13 +10737,17 @@ async function callerBoundProjectionScopes(args) {
9824
10737
  };
9825
10738
  return {
9826
10739
  scope: await renderConditionScope(args.scopeSource, opts),
9827
- scopeForActivity: memoizedByName(activityName => renderConditionScope(args.scopeSource, {
10740
+ scopeForActivity: memoizedBy(activityName => renderConditionScope(args.scopeSource, {
9828
10741
  activityName: activityName,
9829
10742
  ...opts
9830
10743
  }))
9831
10744
  };
9832
10745
  }
9833
10746
 
10747
+ function clockSitesOf(instance, sites) {
10748
+ return terminalState(instance) !== "in-flight" ? [] : sites.filter(entry => readsNowIn(entry.insight.analysis.reads));
10749
+ }
10750
+
9834
10751
  function currentStageOf(instance, definition) {
9835
10752
  try {
9836
10753
  return findStage(definition, instance.currentStage);
@@ -9880,12 +10797,13 @@ async function evaluateFromSnapshot(args) {
9880
10797
  grants: grants,
9881
10798
  attributes: attributes,
9882
10799
  identity: anchorIdentity
9883
- }), cascadeScopeForActivity = memoizedByName(activityName => renderConditionScope(scopeSource, {
10800
+ }), cascadeScopeForActivity = memoizedBy(activityName => renderConditionScope(scopeSource, {
9884
10801
  activityName: activityName
9885
10802
  })), currentActivityEntries = findOpenStageEntry(instance)?.activities ?? [], guardDenial = await instanceGuardReason({
9886
10803
  instance: instance,
9887
10804
  identity: anchorIdentity,
9888
- guards: args.guards
10805
+ guards: args.guards,
10806
+ dereference: args.guardDereference
9889
10807
  }), subjectDenials = await forecastSubjectDenials({
9890
10808
  instance: instance,
9891
10809
  snapshot: snapshot,
@@ -9893,7 +10811,11 @@ async function evaluateFromSnapshot(args) {
9893
10811
  }), subjectPermissionReason = subjectDenials.length > 0 ? {
9894
10812
  kind: "subject-permission-denied",
9895
10813
  denied: subjectDenials
9896
- } : void 0, sites = [], activityEvaluations = [];
10814
+ } : void 0, sites = [], activityEvaluations = [], requiredDocuments = requiredDocumentRequirements({
10815
+ instance: instance,
10816
+ definition: definition,
10817
+ snapshot: snapshot
10818
+ });
9897
10819
  for (const activity of stage.activities ?? []) activityEvaluations.push(await evaluateActivity({
9898
10820
  activity: activity,
9899
10821
  statusEntry: currentActivityEntries.find(t => t.name === activity.name),
@@ -9904,36 +10826,23 @@ async function evaluateFromSnapshot(args) {
9904
10826
  stageHasExits: !isTerminalStage(stage),
9905
10827
  guardDenial: guardDenial,
9906
10828
  subjectPermissionReason: subjectPermissionReason,
10829
+ requiredDocuments: requiredDocuments,
9907
10830
  sites: sites,
9908
10831
  autonomy: activityAutonomyOf(stageAutonomy2, activity.name),
9909
10832
  fireConsequence: fireConsequence
9910
10833
  }));
9911
- const cascadeParams = await renderConditionScope(scopeSource), transitionEvaluations = [];
9912
- for (const transition of stage.transitions ?? []) {
9913
- const insight = await explainSite({
9914
- site: {
9915
- kind: "transition",
9916
- transition: transition.name
9917
- },
9918
- condition: transition.when,
9919
- params: cascadeParams,
9920
- snapshot: snapshot,
9921
- sites: sites
9922
- });
9923
- transitionEvaluations.push({
9924
- transition: transition,
9925
- whenSatisfied: insight.outcome === "satisfied",
9926
- unevaluable: insight.outcome === "unevaluable",
9927
- insight: insight
9928
- });
9929
- }
9930
- const currentStage = {
10834
+ const cascadeParams = await renderConditionScope(scopeSource), transitionEvaluations = await evaluateTransitions({
10835
+ stage: stage,
10836
+ cascadeParams: cascadeParams,
10837
+ snapshot: snapshot,
10838
+ sites: sites
10839
+ }), currentStage = {
9931
10840
  stage: stage,
9932
10841
  ...semanticsOf(stage),
9933
10842
  activities: activityEvaluations,
9934
10843
  transitions: transitionEvaluations,
9935
10844
  autonomy: stageAutonomy2
9936
- }, pendingOnYou = activityEvaluations.filter(t => t.pendingOnActor), canInteract = activityEvaluations.some(t => t.actions.some(a => a.allowed)), editGuardDenial = guardDenial?.kind === "mutation-guard-denied" ? guardDenial : void 0, editableFields = await evaluateEditableFields({
10845
+ }, missingDocuments = await readMissingDocuments(args), pendingOnYou = activityEvaluations.filter(t => t.pendingOnActor), canInteract = activityEvaluations.some(t => t.actions.some(a => a.allowed)), editGuardDenial = guardDenial?.kind === "mutation-guard-denied" ? guardDenial : void 0, editableFields = await evaluateEditableFields({
9937
10846
  instance: instance,
9938
10847
  definition: definition,
9939
10848
  stage: stage,
@@ -9942,6 +10851,10 @@ async function evaluateFromSnapshot(args) {
9942
10851
  scopeForActivity: scopeForActivity,
9943
10852
  guardDenial: editGuardDenial,
9944
10853
  sites: sites
10854
+ }), nextEvaluationAt = await nextEvaluationInstant({
10855
+ sites: clockSitesOf(instance, sites),
10856
+ dataset: snapshot.docs,
10857
+ now: now
9945
10858
  });
9946
10859
  return {
9947
10860
  instance: instance,
@@ -9956,10 +10869,60 @@ async function evaluateFromSnapshot(args) {
9956
10869
  sites: sites,
9957
10870
  snapshot: snapshot
9958
10871
  }),
9959
- autonomy: autonomy
10872
+ ...missingDocuments.length > 0 ? {
10873
+ missingDocuments: missingDocuments,
10874
+ blockingMissingDocuments: await blockingMissingDocuments({
10875
+ instance: instance,
10876
+ definition: definition,
10877
+ currentStage: currentStage,
10878
+ missingDocuments: missingDocuments,
10879
+ snapshot: snapshot,
10880
+ sites: sites
10881
+ })
10882
+ } : {},
10883
+ autonomy: autonomy,
10884
+ ...nextEvaluationAt !== void 0 ? {
10885
+ nextEvaluationAt: nextEvaluationAt
10886
+ } : {}
9960
10887
  };
9961
10888
  }
9962
10889
 
10890
+ async function readMissingDocuments(args) {
10891
+ const missingDocuments = missingDocumentsOf(args);
10892
+ if (args.documentAvailability === void 0 || missingDocuments.length === 0) return missingDocuments;
10893
+ const availability = await args.documentAvailability(missingDocuments);
10894
+ return missingDocuments.map(document => {
10895
+ const verdict = availability.get(document.reference.id);
10896
+ return verdict === void 0 ? document : {
10897
+ ...document,
10898
+ availability: verdict
10899
+ };
10900
+ });
10901
+ }
10902
+
10903
+ async function evaluateTransitions(args) {
10904
+ const {stage: stage, cascadeParams: cascadeParams, snapshot: snapshot, sites: sites} = args, evaluations = [];
10905
+ for (const transition of stage.transitions ?? []) {
10906
+ const insight = await explainSite({
10907
+ site: {
10908
+ kind: "transition",
10909
+ transition: transition.name
10910
+ },
10911
+ condition: transition.when,
10912
+ params: cascadeParams,
10913
+ snapshot: snapshot,
10914
+ sites: sites
10915
+ });
10916
+ evaluations.push({
10917
+ transition: transition,
10918
+ whenSatisfied: insight.outcome === "satisfied",
10919
+ unevaluable: insight.outcome === "unevaluable",
10920
+ insight: insight
10921
+ });
10922
+ }
10923
+ return evaluations;
10924
+ }
10925
+
9963
10926
  const AUTONOMY_CACHE = /* @__PURE__ */ new WeakMap;
9964
10927
 
9965
10928
  function autonomyOf(definition) {
@@ -9970,54 +10933,56 @@ function autonomyOf(definition) {
9970
10933
  }
9971
10934
 
9972
10935
  async function evaluateEditableFields(args) {
9973
- const {instance: instance, definition: definition, stage: stage, snapshot: snapshot, scope: scope, scopeForActivity: scopeForActivity, guardDenial: guardDenial, sites: sites} = args, fields = [];
9974
- for (const site of editableFieldsInStage(definition, stage)) {
9975
- const window = fieldWindowOpen(instance, site), insight = await editPredicateInsight({
9976
- site: site,
9977
- snapshot: snapshot,
9978
- scope: scope,
9979
- scopeForActivity: scopeForActivity,
9980
- sites: sites
9981
- }), predicateSatisfied = insight === void 0 || insight.outcome === "satisfied", reason = editDisabledReason({
9982
- effective: site.effective,
9983
- instance: instance,
9984
- window: window,
9985
- guardDenial: guardDenial,
9986
- predicateSatisfied: predicateSatisfied
9987
- }), value = readFieldValue(instance, site);
9988
- fields.push({
9989
- scope: site.scope,
9990
- ...site.activity !== void 0 ? {
9991
- activity: site.activity
9992
- } : {},
9993
- name: site.name,
9994
- type: site.type,
9995
- ...site.title !== void 0 ? {
9996
- title: site.title
9997
- } : {},
9998
- ...site.validation !== void 0 ? {
9999
- validation: site.validation
10000
- } : {},
10001
- ...site.roles !== void 0 ? {
10002
- roles: site.roles,
10003
- ...definition.roleAliases !== void 0 ? {
10004
- roleAliases: definition.roleAliases
10005
- } : {}
10006
- } : {},
10007
- value: value,
10008
- editable: reason === void 0,
10009
- ...reason !== void 0 ? {
10010
- disabledReason: reason
10011
- } : {},
10012
- ...fieldProvenance(instance, site.ref),
10013
- ...insight !== void 0 ? {
10014
- insight: insight
10015
- } : {}
10016
- });
10017
- }
10936
+ const fields = [];
10937
+ for (const site of editableFieldsInStage(args.definition, args.stage)) fields.push(await evaluateEditableField(args, site));
10018
10938
  return fields;
10019
10939
  }
10020
10940
 
10941
+ async function evaluateEditableField(args, site) {
10942
+ const {instance: instance, definition: definition, snapshot: snapshot, scope: scope, scopeForActivity: scopeForActivity, guardDenial: guardDenial, sites: sites} = args, window = fieldWindowOpen(instance, site), insight = await editPredicateInsight({
10943
+ site: site,
10944
+ snapshot: snapshot,
10945
+ scope: scope,
10946
+ scopeForActivity: scopeForActivity,
10947
+ sites: sites
10948
+ }), predicateSatisfied = insight === void 0 || insight.outcome === "satisfied", reason = editDisabledReason({
10949
+ effective: site.effective,
10950
+ instance: instance,
10951
+ window: window,
10952
+ guardDenial: guardDenial,
10953
+ predicateSatisfied: predicateSatisfied
10954
+ });
10955
+ return {
10956
+ scope: site.scope,
10957
+ ...site.activity !== void 0 ? {
10958
+ activity: site.activity
10959
+ } : {},
10960
+ name: site.name,
10961
+ type: site.type,
10962
+ ...site.title !== void 0 ? {
10963
+ title: site.title
10964
+ } : {},
10965
+ ...site.validation !== void 0 ? {
10966
+ validation: site.validation
10967
+ } : {},
10968
+ ...site.roles !== void 0 ? {
10969
+ roles: site.roles,
10970
+ ...definition.roleAliases !== void 0 ? {
10971
+ roleAliases: definition.roleAliases
10972
+ } : {}
10973
+ } : {},
10974
+ value: readFieldValue(instance, site),
10975
+ editable: reason === void 0,
10976
+ ...reason !== void 0 ? {
10977
+ disabledReason: reason
10978
+ } : {},
10979
+ ...fieldProvenance(instance, site.ref),
10980
+ ...insight !== void 0 ? {
10981
+ insight: insight
10982
+ } : {}
10983
+ };
10984
+ }
10985
+
10021
10986
  async function editPredicateInsight(args) {
10022
10987
  const {site: site, snapshot: snapshot, scope: scope, scopeForActivity: scopeForActivity, sites: sites} = args;
10023
10988
  if (typeof site.effective != "string") return;
@@ -10041,14 +11006,10 @@ async function editPredicateInsight(args) {
10041
11006
  async function forecastSubjectDenials(args) {
10042
11007
  const {instance: instance, snapshot: snapshot, resourceGrants: resourceGrants} = args;
10043
11008
  if (resourceGrants === void 0 || resourceGrants.size === 0) return [];
10044
- const denials = [], seen = /* @__PURE__ */ new Set;
10045
- for (const {ref: ref, parsed: parsed, resource: resource} of foreignSubjectRefs(instance)) {
10046
- if (seen.has(ref.id)) continue;
10047
- seen.add(ref.id);
10048
- const access = resourceGrants.get(resourceGdr(resource));
10049
- if (access === void 0) continue;
10050
- const doc = snapshot.docs.find(d => d._id === ref.id);
10051
- doc !== void 0 && await subjectUpdateAllowed({
11009
+ const denials = [], subjects = new Map(foreignSubjectRefs(instance).map(subject => [ subject.ref.id, subject ]));
11010
+ for (const {ref: ref, parsed: parsed, resource: resource} of subjects.values()) {
11011
+ const access = resourceGrants.get(resourceGdr(resource)), doc = snapshot.docs.find(d => d._id === ref.id);
11012
+ access === void 0 || doc === void 0 || await subjectUpdateAllowed({
10052
11013
  doc: doc,
10053
11014
  parsed: parsed,
10054
11015
  grants: access.grants,
@@ -10081,13 +11042,13 @@ async function subjectUpdateAllowed(args) {
10081
11042
  }
10082
11043
 
10083
11044
  async function evaluateActivity(args) {
10084
- const {activity: activity, statusEntry: statusEntry, instance: instance, snapshot: snapshot, activityScope: activityScope, cascadeActivityScope: cascadeActivityScope, stageHasExits: stageHasExits, guardDenial: guardDenial, subjectPermissionReason: subjectPermissionReason, sites: sites, autonomy: autonomy, fireConsequence: fireConsequence} = args, status = statusEntry?.status ?? "skipped", assigned = activityScope.assigned === !0, {unmetRequirements: unmetRequirements, ...conditionInsights} = await explainActivityConditions({
11045
+ const {activity: activity, statusEntry: statusEntry, instance: instance, snapshot: snapshot, activityScope: activityScope, cascadeActivityScope: cascadeActivityScope, stageHasExits: stageHasExits, guardDenial: guardDenial, subjectPermissionReason: subjectPermissionReason, requiredDocuments: requiredDocuments, sites: sites, autonomy: autonomy, fireConsequence: fireConsequence} = args, status = statusEntry?.status ?? "skipped", assigned = activityScope.assigned === !0, {unmetRequirements: activityRequirements, ...conditionInsights} = await explainActivityConditions({
10085
11046
  activity: activity,
10086
11047
  activityScope: activityScope,
10087
11048
  cascadeActivityScope: cascadeActivityScope,
10088
11049
  snapshot: snapshot,
10089
11050
  sites: sites
10090
- }), requirementsReason = unmetRequirements.length > 0 ? {
11051
+ }), unmetRequirements = [ ...requiredDocuments, ...activityRequirements ], requirementsReason = unmetRequirements.length > 0 ? {
10091
11052
  kind: "requirements-unmet",
10092
11053
  unmetRequirements: unmetRequirements
10093
11054
  } : void 0, actions = [];
@@ -10104,7 +11065,8 @@ async function evaluateActivity(args) {
10104
11065
  subjectPermissionReason: subjectPermissionReason,
10105
11066
  requirementsReason: requirementsReason,
10106
11067
  sites: sites,
10107
- fireConsequence: a => fireConsequence(activity, a)
11068
+ fireConsequence: a => fireConsequence(activity, a),
11069
+ assignment: assignmentMembers(statusEntry?.fields ?? [])
10108
11070
  }));
10109
11071
  return {
10110
11072
  activity: activity,
@@ -10261,19 +11223,31 @@ function fireableActionVerdict({args: args, insights: insights}) {
10261
11223
  reason: args.requirementsReason,
10262
11224
  ...insights
10263
11225
  });
10264
- const {insight: insight} = insights;
10265
- return action.filter !== void 0 && insight !== void 0 && insight.outcome !== "satisfied" ? disabled({
11226
+ const filterFailure = filterFailureVerdict(args, insights);
11227
+ return filterFailure !== void 0 ? filterFailure : {
11228
+ ...actionEvaluationIdentity(action),
11229
+ allowed: !0,
11230
+ ...insights
11231
+ };
11232
+ }
11233
+
11234
+ function filterFailureVerdict(args, insights) {
11235
+ const {action: action} = args, {insight: insight} = insights;
11236
+ if (action.filter === void 0 || insight === void 0 || insight.outcome === "satisfied") return;
11237
+ const holderGate = insight.blockedBy.some(entry => isAssignedGate(entry.atom)) ? {
11238
+ holders: [ ...activeAssignmentMembers(args.assignment) ]
11239
+ } : void 0;
11240
+ return disabled({
10266
11241
  action: action,
10267
11242
  reason: {
10268
11243
  kind: "filter-failed",
10269
11244
  filter: action.filter
10270
11245
  },
10271
- ...insights
10272
- }) : {
10273
- ...actionEvaluationIdentity(action),
10274
- allowed: !0,
10275
- ...insights
10276
- };
11246
+ ...insights,
11247
+ ...holderGate === void 0 ? {} : {
11248
+ holderGate: holderGate
11249
+ }
11250
+ });
10277
11251
  }
10278
11252
 
10279
11253
  function actionEvaluationIdentity(action) {
@@ -10289,12 +11263,13 @@ function semanticsOf(node) {
10289
11263
  };
10290
11264
  }
10291
11265
 
10292
- async function instanceGuardReason({instance: instance, identity: identity, guards: guards}) {
11266
+ async function instanceGuardReason({instance: instance, identity: identity, guards: guards, dereference: dereference}) {
10293
11267
  if (guards === void 0 || guards.length === 0) return;
10294
11268
  const denied = await instanceWriteDenials({
10295
11269
  instance: instance,
10296
11270
  guards: guards,
10297
- identity: identity
11271
+ identity: identity,
11272
+ dereference: dereference ?? (() => Promise.resolve(null))
10298
11273
  });
10299
11274
  if (denied.length !== 0) return {
10300
11275
  kind: "mutation-guard-denied",
@@ -10325,10 +11300,29 @@ function disabled(args) {
10325
11300
  } : {},
10326
11301
  ...args.whenInsight !== void 0 ? {
10327
11302
  whenInsight: args.whenInsight
11303
+ } : {},
11304
+ ...args.holderGate !== void 0 ? {
11305
+ holderGate: args.holderGate
10328
11306
  } : {}
10329
11307
  };
10330
11308
  }
10331
11309
 
11310
+ const ASSIGNMENT_KINDS = '["assignee", "assignees"]', HAS_USER = '(count(value[@.type == "user"]) > 0 || value.type == "user")', HAS_MEMBER = "(count(value) > 0 || defined(value.type))";
11311
+
11312
+ function assignmentActivityArm(state) {
11313
+ const assignmentFields = `fields[_type in ${ASSIGNMENT_KINDS}]`;
11314
+ return state === "unrouted" ? `count(${assignmentFields}[${HAS_MEMBER}]) == 0` : state === "held" ? `count(${assignmentFields}[${HAS_USER} && (count(value[@.type == "user" && id == $assignmentUserId]) > 0 || (value.type == "user" && value.id == $assignmentUserId))]) > 0` : `count(${assignmentFields}[${HAS_USER}]) == 0 && count(${assignmentFields}[count(value[@.type == "role" && role in $assignmentRoles]) > 0 || (value.type == "role" && value.role in $assignmentRoles)]) > 0`;
11315
+ }
11316
+
11317
+ function assignmentPrefilter(assignment, params) {
11318
+ if (assignment.userId.length === 0) throw new ContractViolationError("instancesQuery: assignment.userId must be non-empty");
11319
+ const states = [ ...new Set(assignment.states ?? [ "unrouted", "routed", "held" ]) ];
11320
+ if (states.length === 0) throw new ContractViolationError("instancesQuery: assignment.states must not be empty");
11321
+ if (states.some(state => state !== "unrouted" && state !== "routed" && state !== "held")) throw new ContractViolationError("instancesQuery: assignment.states contains an unknown state");
11322
+ return params.assignmentUserId = assignment.userId, params.assignmentRoles = [ ...new Set(assignment.roles ?? []) ].sort(),
11323
+ `count(stages[!defined(exitedAt)].activities[status == "active" && (${states.map(assignmentActivityArm).join(" || ")})]) > 0`;
11324
+ }
11325
+
10332
11326
  function inFlightFilter() {
10333
11327
  return "!defined(completedAt)";
10334
11328
  }
@@ -10368,7 +11362,7 @@ function compiledConditions(filter, params) {
10368
11362
  const conditions = [ `_type == "${WORKFLOW_INSTANCE_TYPE}"`, tagScopeFilter() ];
10369
11363
  filter.includeCompleted !== !0 && conditions.push(inFlightFilter()), filter.definition !== void 0 && (conditions.push("definition == $definition"),
10370
11364
  params.definition = filter.definition), filter.stage !== void 0 && (conditions.push("currentStage == $stage"),
10371
- params.stage = filter.stage);
11365
+ params.stage = filter.stage), filter.assignment !== void 0 && conditions.push(assignmentPrefilter(filter.assignment, params));
10372
11366
  const cursor = beforeArm(filter, params);
10373
11367
  cursor !== void 0 && conditions.push(cursor);
10374
11368
  const arms = [ documentArm(filter, params), idsArm(filter, params) ].filter(arm => arm !== void 0);
@@ -10430,6 +11424,46 @@ function claimReleasedEntry(args) {
10430
11424
  };
10431
11425
  }
10432
11426
 
11427
+ const RENEW_MAX_ATTEMPTS = 3;
11428
+
11429
+ async function claimOf(args) {
11430
+ const {client: client, tag: tag, instanceId: instanceId, effectKey: effectKey} = args, instance = await reload({
11431
+ client: client,
11432
+ instanceId: instanceId,
11433
+ tag: tag
11434
+ });
11435
+ return {
11436
+ instance: instance,
11437
+ claim: instance.pendingEffects.find(e => e._key === effectKey)?.claim
11438
+ };
11439
+ }
11440
+
11441
+ async function claimStillHeld(args) {
11442
+ const {claimToken: claimToken, ...where} = args, {claim: claim} = await claimOf(where);
11443
+ return claim?.claimToken === claimToken;
11444
+ }
11445
+
11446
+ async function renewClaimLease(args) {
11447
+ const {client: client, tag: tag, instanceId: instanceId, effectKey: effectKey, claimToken: claimToken, holdMs: holdMs, clock: clock} = args;
11448
+ for (let attempt = 1; attempt <= RENEW_MAX_ATTEMPTS; attempt++) {
11449
+ const {instance: instance, claim: claim} = await claimOf({
11450
+ client: client,
11451
+ tag: tag,
11452
+ instanceId: instanceId,
11453
+ effectKey: effectKey
11454
+ });
11455
+ if (claim?.claimToken !== claimToken) return;
11456
+ const until = addMs(clock(), holdMs);
11457
+ try {
11458
+ return await client.patch(instance._id).ifRevisionId(instance._rev).set({
11459
+ [`pendingEffects[_key=="${effectKey}"].claim.leaseExpiresAt`]: until
11460
+ }).commit(SYNC_COMMIT), until;
11461
+ } catch (error) {
11462
+ if (!isRevisionConflict(error)) throw error;
11463
+ }
11464
+ }
11465
+ }
11466
+
10433
11467
  const SWEEP_MAX_ATTEMPTS = 5;
10434
11468
 
10435
11469
  async function sweepStaleClaims(args) {
@@ -10471,6 +11505,222 @@ function stripClaim(entry) {
10471
11505
  return rest;
10472
11506
  }
10473
11507
 
11508
+ function isProjectRole(value) {
11509
+ return isRecord(value) && typeof value.name == "string";
11510
+ }
11511
+
11512
+ async function projectRoleNames(request, projectId) {
11513
+ const result = await request({
11514
+ url: `/projects/${encodeURIComponent(projectId)}/roles`
11515
+ });
11516
+ if (!Array.isArray(result) || !result.every(isProjectRole)) throw new Error(`Project role catalog response is invalid for project "${projectId}"`);
11517
+ return new Set(result.map(role => role.name));
11518
+ }
11519
+
11520
+ function memberRoleNames(member, projectId) {
11521
+ if (!Array.isArray(member.roles) || !member.roles.every(isProjectRole)) throw new Error(`Project member role directory response is invalid for project "${projectId}" and member "${member.id}"`);
11522
+ return member.roles.map(role => role.name);
11523
+ }
11524
+
11525
+ function heldRoleNames(members, projectId) {
11526
+ return new Set(members.flatMap(member => member.isRobot === !0 ? [] : memberRoleNames(member, projectId)));
11527
+ }
11528
+
11529
+ function addRoles(collector, args) {
11530
+ for (const role of args.roles ?? []) collector.references.push({
11531
+ definition: collector.definition,
11532
+ path: args.path,
11533
+ role: role
11534
+ });
11535
+ }
11536
+
11537
+ function visitAssigneeValue(collector, args) {
11538
+ const members = Array.isArray(args.value) ? args.value : [ args.value ];
11539
+ for (const [index, member] of members.entries()) !isRecord(member) || member.type !== "role" || typeof member.role != "string" || collector.references.push({
11540
+ definition: collector.definition,
11541
+ path: `${args.path}[${index}].role`,
11542
+ role: member.role
11543
+ });
11544
+ }
11545
+
11546
+ function visitObjectValue(collector, args) {
11547
+ for (const shape of args.fields ?? []) visitShapeValue(collector, {
11548
+ path: `${args.path}.${shape.name}`,
11549
+ shape: shape,
11550
+ value: args.value[shape.name]
11551
+ });
11552
+ }
11553
+
11554
+ function visitArrayValue(collector, args) {
11555
+ for (const [index, row] of args.rows.entries()) if (isRecord(row)) for (const shape of args.shapes ?? []) visitShapeValue(collector, {
11556
+ path: `${args.path}[${index}].${shape.name}`,
11557
+ shape: shape,
11558
+ value: row[shape.name]
11559
+ });
11560
+ }
11561
+
11562
+ function visitAssignmentValue(collector, args) {
11563
+ args.kind === "assignee" || args.kind === "assignees" ? visitAssigneeValue(collector, args) : args.kind === "object" && isRecord(args.value) ? visitObjectValue(collector, {
11564
+ ...args,
11565
+ value: args.value
11566
+ }) : args.kind === "array" && Array.isArray(args.value) && visitArrayValue(collector, {
11567
+ path: args.path,
11568
+ rows: args.value,
11569
+ shapes: args.of
11570
+ });
11571
+ }
11572
+
11573
+ function visitShapeValue(collector, args) {
11574
+ visitAssignmentValue(collector, {
11575
+ path: args.path,
11576
+ kind: args.shape.type,
11577
+ value: args.value,
11578
+ fields: args.shape.fields,
11579
+ of: args.shape.of
11580
+ });
11581
+ }
11582
+
11583
+ function visitField(collector, args) {
11584
+ addRoles(collector, {
11585
+ path: `${args.path}.roles`,
11586
+ roles: args.field.roles
11587
+ });
11588
+ for (const shape of args.field.fields ?? args.field.of ?? []) visitFieldShape(collector, {
11589
+ parentPath: args.path,
11590
+ shape: shape
11591
+ });
11592
+ args.field.initialValue?.type === "literal" && visitAssignmentValue(collector, {
11593
+ path: `${args.path}.initialValue.value`,
11594
+ kind: args.field.type,
11595
+ value: args.field.initialValue.value,
11596
+ fields: args.field.fields,
11597
+ of: args.field.of
11598
+ });
11599
+ }
11600
+
11601
+ function visitFieldShape(collector, args) {
11602
+ const path = `${args.parentPath}.${args.shape.name}`;
11603
+ addRoles(collector, {
11604
+ path: `${path}.roles`,
11605
+ roles: args.shape.roles
11606
+ });
11607
+ for (const child of args.shape.fields ?? args.shape.of ?? []) visitFieldShape(collector, {
11608
+ parentPath: path,
11609
+ shape: child
11610
+ });
11611
+ }
11612
+
11613
+ function rolesFromConditions(definition) {
11614
+ return conditionSitesOf(definition).flatMap(site => {
11615
+ try {
11616
+ return analyzeCondition(site.condition).atoms.flatMap(atom => rolesGateOf(atom) ?? []);
11617
+ } catch {
11618
+ return [];
11619
+ }
11620
+ });
11621
+ }
11622
+
11623
+ function visitRoleAliases(collector, aliases) {
11624
+ for (const [role, fulfillers] of Object.entries(aliases ?? {})) role !== UNIVERSAL_ROLE_ALIAS_KEY && collector.references.push({
11625
+ definition: collector.definition,
11626
+ path: `roleAliases.${role}`,
11627
+ role: role
11628
+ }), addRoles(collector, {
11629
+ path: `roleAliases.${role}`,
11630
+ roles: fulfillers
11631
+ });
11632
+ }
11633
+
11634
+ function visitFields(collector, args) {
11635
+ for (const [index, field] of (args.fields ?? []).entries()) visitField(collector, {
11636
+ path: `${args.parentPath}[${index}]`,
11637
+ field: field
11638
+ });
11639
+ }
11640
+
11641
+ function visitAction(collector, args) {
11642
+ addRoles(collector, {
11643
+ path: `${args.path}.roles`,
11644
+ roles: args.action.roles
11645
+ });
11646
+ for (const [effectIndex, effect] of (args.action.effects ?? []).entries()) for (const shape of effect.outputs ?? []) visitFieldShape(collector, {
11647
+ parentPath: `${args.path}.effects[${effectIndex}].outputs`,
11648
+ shape: shape
11649
+ });
11650
+ }
11651
+
11652
+ function visitStage(collector, args) {
11653
+ const stagePath = `stages[${args.stageIndex}]`;
11654
+ visitFields(collector, {
11655
+ fields: args.stage.fields,
11656
+ parentPath: `${stagePath}.fields`
11657
+ });
11658
+ for (const [activityIndex, activity] of (args.stage.activities ?? []).entries()) {
11659
+ const activityPath = `${stagePath}.activities[${activityIndex}]`;
11660
+ visitFields(collector, {
11661
+ fields: activity.fields,
11662
+ parentPath: `${activityPath}.fields`
11663
+ });
11664
+ for (const [actionIndex, action] of (activity.actions ?? []).entries()) visitAction(collector, {
11665
+ action: action,
11666
+ path: `${activityPath}.actions[${actionIndex}]`
11667
+ });
11668
+ }
11669
+ }
11670
+
11671
+ function addConditionReferences(collector, definition) {
11672
+ for (const role of rolesFromConditions(definition)) collector.references.push({
11673
+ definition: collector.definition,
11674
+ path: "generated role condition",
11675
+ role: role
11676
+ });
11677
+ }
11678
+
11679
+ function definitionRoleReferences(definition) {
11680
+ const collector = {
11681
+ references: [],
11682
+ definition: definition.name
11683
+ };
11684
+ visitRoleAliases(collector, definition.roleAliases), visitFields(collector, {
11685
+ fields: definition.fields,
11686
+ parentPath: "fields"
11687
+ });
11688
+ for (const [stageIndex, stage] of definition.stages.entries()) visitStage(collector, {
11689
+ stage: stage,
11690
+ stageIndex: stageIndex
11691
+ });
11692
+ return addConditionReferences(collector, definition), collector.references;
11693
+ }
11694
+
11695
+ function definitionRoleNames(definition) {
11696
+ return [ ...new Set(definitionRoleReferences(definition).map(reference => reference.role)) ];
11697
+ }
11698
+
11699
+ function roleCatalogProjectId(client, resource) {
11700
+ if (resource.type === "dataset") return datasetResourceParts(resource.id).projectId;
11701
+ const projectId = client.config?.().projectId;
11702
+ if (typeof projectId == "string" && projectId.length > 0) return projectId;
11703
+ throw new Error(`workflow.deployDefinitions: definitions reference project roles on ${resource.type} workflow resources, but the client does not expose a projectId for the project role catalog`);
11704
+ }
11705
+
11706
+ async function validateDefinitionRoles(args) {
11707
+ const references = args.definitions.flatMap(definitionRoleReferences);
11708
+ if (references.length === 0) /* @__PURE__ */ return new Map;
11709
+ if (args.client.request === void 0) throw new Error("workflow.deployDefinitions: definitions reference project roles, but the client cannot read the project role catalog");
11710
+ const projectId = roleCatalogProjectId(args.client, args.workflowResource), [known, members] = await Promise.all([ projectRoleNames(args.client.request, projectId), _requestProjectMembers(args.client.request, projectId) ]), unknown = references.filter(reference => !known.has(reference.role));
11711
+ if (unknown.length > 0) {
11712
+ const lines = unknown.map(({definition: definition, path: path, role: role}) => ` - ${definition}: ${path} references unknown role "${role}"`);
11713
+ throw new Error(`workflow.deployDefinitions: unknown project roles for project "${projectId}":\n${lines.join(`\n`)}`);
11714
+ }
11715
+ const held = heldRoleNames(members, projectId), warnings = /* @__PURE__ */ new Map;
11716
+ for (const {definition: definition, role: role} of references) {
11717
+ if (held.has(role)) continue;
11718
+ const message = `Project role "${role}" has no current user holder; work routed to it has no recipient.`, definitionWarnings = warnings.get(definition) ?? [];
11719
+ definitionWarnings.includes(message) || definitionWarnings.push(message), warnings.set(definition, definitionWarnings);
11720
+ }
11721
+ return warnings;
11722
+ }
11723
+
10474
11724
  async function sortByDependencies({client: client, definitions: definitions, tag: tag}) {
10475
11725
  const byName = /* @__PURE__ */ new Map;
10476
11726
  for (const def of definitions) {
@@ -10587,7 +11837,7 @@ function refsOf(def) {
10587
11837
  }
10588
11838
 
10589
11839
  function hashDefinitionContent(def) {
10590
- const canonical = stableStringify(def);
11840
+ const canonical = stableStringify(withoutAuthoringRuntime(def));
10591
11841
  let hash = 0xcbf29ce484222325n;
10592
11842
  for (let i = 0; i < canonical.length; i++) hash ^= BigInt(canonical.charCodeAt(i)),
10593
11843
  hash = hash * 0x100000001b3n & 0xffffffffffffffffn;
@@ -10600,7 +11850,7 @@ function planDefinitionDeploy({def: def, latest: latest, target: target}) {
10600
11850
  definition: def,
10601
11851
  workflowResource: target.workflowResource
10602
11852
  });
10603
- const expanded = expandResourceAliases(def, target.resourceAliases), contentHash = hashDefinitionContent(expanded), unchanged = latest !== void 0 && latest.contentHash === contentHash, version = unchanged ? latest.version : (latest?.version ?? 0) + 1, docId = definitionDocId({
11853
+ const expanded = withoutAuthoringRuntime(expandResourceAliases(def, target.resourceAliases)), contentHash = hashDefinitionContent(expanded), unchanged = latest !== void 0 && latest.contentHash === contentHash, version = unchanged ? latest.version : (latest?.version ?? 0) + 1, docId = definitionDocId({
10604
11854
  tag: target.tag,
10605
11855
  definition: expanded.name,
10606
11856
  version: version
@@ -10657,10 +11907,17 @@ function isDocumentEnvelopeKey(key) {
10657
11907
  return key.startsWith("_") || DEPLOY_STAMPED_KEYS.has(key);
10658
11908
  }
10659
11909
 
11910
+ const AUTHORING_RUNTIME_KEY = "runtime";
11911
+
11912
+ function withoutAuthoringRuntime(def) {
11913
+ const content = Object.entries(def).filter(([key]) => key !== AUTHORING_RUNTIME_KEY);
11914
+ return Object.fromEntries(content);
11915
+ }
11916
+
10660
11917
  function parseDefinitionInput(def, caller) {
10661
11918
  const label = labelFor(caller, def);
10662
11919
  if (def._type !== void 0 && def._type !== WORKFLOW_DEFINITION_TYPE) throw new Error(`${label}: expected a workflow definition or a "${WORKFLOW_DEFINITION_TYPE}" document, got _type "${String(def._type)}"`);
10663
- const content = Object.fromEntries(Object.entries(def).filter(([key]) => !isDocumentEnvelopeKey(key)));
11920
+ const content = Object.fromEntries(Object.entries(withoutAuthoringRuntime(def)).filter(([key]) => !isDocumentEnvelopeKey(key)));
10664
11921
  return parseStoredDefinition(content, label);
10665
11922
  }
10666
11923
 
@@ -10771,7 +12028,7 @@ async function commitEdit({ctx: ctx, target: target, mode: mode, value: value, r
10771
12028
  site: site,
10772
12029
  mode: mode,
10773
12030
  value: value
10774
- }), mutation = startMutation(ctx.instance);
12031
+ }), mutation = startRevisionRetryMutation(ctx.instance);
10775
12032
  recordProcessedRequest({
10776
12033
  mutation: mutation,
10777
12034
  record: requestRecord,
@@ -10915,14 +12172,14 @@ async function runCommitVerb(params) {
10915
12172
  client: client,
10916
12173
  instanceId: instanceId,
10917
12174
  tag: tag
10918
- });
12175
+ }), now = clock();
10919
12176
  return runDeduped({
10920
12177
  client: client,
10921
12178
  tag: tag,
10922
12179
  instanceId: instanceId,
10923
12180
  record: record,
10924
12181
  before: before,
10925
- now: clock(),
12182
+ now: now,
10926
12183
  actor: context.actor,
10927
12184
  clientForGdr: context.clientForGdr,
10928
12185
  refSurface: context.refSurface,
@@ -10933,6 +12190,7 @@ async function runCommitVerb(params) {
10933
12190
  run: () => run({
10934
12191
  ...context,
10935
12192
  clock: clock,
12193
+ now: now,
10936
12194
  record: record,
10937
12195
  before: before
10938
12196
  })
@@ -10956,7 +12214,7 @@ async function runDeduped(args) {
10956
12214
  client: client,
10957
12215
  instanceId: instanceId,
10958
12216
  tag: tag
10959
- }), {cascaded: cascaded, instance: instance} = await cascadeAndReload({
12217
+ }), settled = await cascadeAndReload({
10960
12218
  client: client,
10961
12219
  tag: tag,
10962
12220
  instanceId: instanceId,
@@ -10968,11 +12226,10 @@ async function runDeduped(args) {
10968
12226
  executionContext: executionContext,
10969
12227
  telemetry: telemetry
10970
12228
  });
10971
- return {
10972
- instance: instance,
10973
- cascaded: cascaded,
10974
- changed: instance._rev !== pre._rev
10975
- };
12229
+ return settledResult({
12230
+ ...settled,
12231
+ changed: settled.instance._rev !== pre._rev
12232
+ });
10976
12233
  };
10977
12234
  if (record !== void 0 && before !== void 0 && findProcessedRequest({
10978
12235
  instance: before,
@@ -10988,7 +12245,7 @@ async function runDeduped(args) {
10988
12245
  }
10989
12246
 
10990
12247
  async function cascade({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, memberRolesLoader: memberRolesLoader, clock: clock, executionContext: executionContext, telemetry: telemetry, overlay: overlay}) {
10991
- const count = await cascadeAutoTransitions({
12248
+ const settled = await cascadeAutoTransitions({
10992
12249
  client: client,
10993
12250
  instanceId: instanceId,
10994
12251
  actor: actor,
@@ -11024,13 +12281,13 @@ async function cascade({client: client, instanceId: instanceId, actor: actor, cl
11024
12281
  ...telemetry !== void 0 ? {
11025
12282
  telemetry: telemetry
11026
12283
  } : {}
11027
- }), count;
12284
+ }), settled;
11028
12285
  }
11029
12286
 
11030
12287
  async function cascadeAndReload(args) {
11031
12288
  const {client: client, tag: tag, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, memberRolesLoader: memberRolesLoader, clock: clock} = args;
11032
12289
  return {
11033
- cascaded: await cascade({
12290
+ ...await cascade({
11034
12291
  client: client,
11035
12292
  instanceId: instanceId,
11036
12293
  actor: actor,
@@ -11053,6 +12310,84 @@ async function cascadeAndReload(args) {
11053
12310
  };
11054
12311
  }
11055
12312
 
12313
+ async function unchangedNextEvaluationAt(args) {
12314
+ const {client: client, instance: instance, actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, memberRolesLoader: memberRolesLoader, now: now} = args;
12315
+ if (terminalState(instance) !== "in-flight") return;
12316
+ const ctx = await buildEngineContext({
12317
+ client: client,
12318
+ instance: instance,
12319
+ definition: parseDefinitionSnapshot(instance),
12320
+ clientForGdr: clientForGdr,
12321
+ refSurface: refSurface,
12322
+ memberRolesLoader: memberRolesLoader,
12323
+ clock: () => now,
12324
+ ...actor !== void 0 ? {
12325
+ actor: actor
12326
+ } : {},
12327
+ ...args.executionContext !== void 0 ? {
12328
+ executionContext: args.executionContext
12329
+ } : {},
12330
+ ...args.telemetry !== void 0 ? {
12331
+ telemetry: args.telemetry
12332
+ } : {}
12333
+ });
12334
+ return ctxNextEvaluationAt(ctx);
12335
+ }
12336
+
12337
+ async function adminOverrideResult(args) {
12338
+ const {client: client, tag: tag, instanceId: instanceId, fired: fired, now: now, executionContext: executionContext, telemetry: telemetry, ...scope} = args, optional = {
12339
+ ...executionContext !== void 0 ? {
12340
+ executionContext: executionContext
12341
+ } : {},
12342
+ ...telemetry !== void 0 ? {
12343
+ telemetry: telemetry
12344
+ } : {}
12345
+ }, settled = fired ? await cascade({
12346
+ client: client,
12347
+ instanceId: instanceId,
12348
+ ...scope,
12349
+ ...optional
12350
+ }) : {
12351
+ cascaded: 0
12352
+ }, instance = await reload({
12353
+ client: client,
12354
+ instanceId: instanceId,
12355
+ tag: tag
12356
+ });
12357
+ return settledResult({
12358
+ ...settled,
12359
+ instance: instance,
12360
+ changed: fired,
12361
+ ...fired ? {} : {
12362
+ nextEvaluationAt: await unchangedNextEvaluationAt({
12363
+ client: client,
12364
+ instance: instance,
12365
+ now: now,
12366
+ actor: scope.actor,
12367
+ clientForGdr: scope.clientForGdr,
12368
+ refSurface: scope.refSurface,
12369
+ memberRolesLoader: scope.memberRolesLoader,
12370
+ ...optional
12371
+ })
12372
+ }
12373
+ });
12374
+ }
12375
+
12376
+ function settledResult(args) {
12377
+ const {instance: instance, cascaded: cascaded, changed: changed, ranOps: ranOps, nextEvaluationAt: nextEvaluationAt} = args;
12378
+ return {
12379
+ instance: instance,
12380
+ cascaded: cascaded,
12381
+ changed: changed,
12382
+ ...ranOps !== void 0 ? {
12383
+ ranOps: ranOps
12384
+ } : {},
12385
+ ...nextEvaluationAt !== void 0 ? {
12386
+ nextEvaluationAt: nextEvaluationAt
12387
+ } : {}
12388
+ };
12389
+ }
12390
+
11056
12391
  async function settleStart(args) {
11057
12392
  const runArgs = {
11058
12393
  client: args.client,
@@ -11114,7 +12449,7 @@ async function resumeStart(args) {
11114
12449
  instance: existing,
11115
12450
  initialFieldCount: initialFieldCount,
11116
12451
  viaSpawn: !1
11117
- })), cascaded = await settleStart({
12452
+ })), settled = await settleStart({
11118
12453
  ...settlementArgs,
11119
12454
  instanceId: existing._id,
11120
12455
  ...completesStart ? {
@@ -11125,11 +12460,11 @@ async function resumeStart(args) {
11125
12460
  instanceId: existing._id,
11126
12461
  tag: args.tag
11127
12462
  });
11128
- return {
12463
+ return settledResult({
12464
+ ...settled,
11129
12465
  instance: instance,
11130
- cascaded: cascaded,
11131
12466
  changed: instance._rev !== existing._rev
11132
- };
12467
+ });
11133
12468
  }
11134
12469
 
11135
12470
  function engineOptionsForActor({actor: actor, clock: clock, clientForGdr: clientForGdr, refSurface: refSurface, memberRolesLoader: memberRolesLoader, executionContext: executionContext, telemetry: telemetry}) {
@@ -11228,7 +12563,7 @@ async function dispatchGatedWrite(args) {
11228
12563
  ...resourceClients !== void 0 ? {
11229
12564
  resourceClients: resourceClients
11230
12565
  } : {}
11231
- }), ranOps = await apply(args.before, evaluation), {cascaded: cascaded, instance: instance} = await cascadeAndReload({
12566
+ }), ranOps = await apply(args.before, evaluation), settled = await cascadeAndReload({
11232
12567
  client: client,
11233
12568
  tag: tag,
11234
12569
  instanceId: instanceId,
@@ -11240,14 +12575,13 @@ async function dispatchGatedWrite(args) {
11240
12575
  executionContext: args.executionContext,
11241
12576
  telemetry: args.telemetry
11242
12577
  });
11243
- return {
11244
- instance: instance,
11245
- cascaded: cascaded,
12578
+ return settledResult({
12579
+ ...settled,
11246
12580
  changed: !0,
11247
12581
  ...ranOps !== void 0 ? {
11248
12582
  ranOps: ranOps
11249
12583
  } : {}
11250
- };
12584
+ });
11251
12585
  }
11252
12586
 
11253
12587
  function guardDeniedError(instanceId, reason) {
@@ -11461,6 +12795,10 @@ async function abortInstances(args) {
11461
12795
  return aborted;
11462
12796
  }
11463
12797
 
12798
+ function lintDeadActivityInputSeeds(definition) {
12799
+ return definition.stages.flatMap(stage => (stage.activities ?? []).flatMap(activity => (activity.fields ?? []).filter(field => field.initialValue?.type === "input").map(field => `Activity field "${stage.name}.${activity.name}.${field.name}" uses an input seed, but start/spawn inputs address workflow-scope fields only; this field will always materialize empty. Use a literal, query, field read, or working-memory field instead.`)));
12800
+ }
12801
+
11464
12802
  function projectStartSliceRow(instance) {
11465
12803
  const subject = (instance.fields ?? []).find(field => field._type === "subject")?.value;
11466
12804
  return {
@@ -11488,14 +12826,18 @@ const workflow = {
11488
12826
  definitions.forEach(validateDefinition), assertReaderModelAcknowledgement(args.expectedMinReaderModel, {
11489
12827
  requiredMinReaderModel: requiredDefinitionReaderModel(definitions)
11490
12828
  });
11491
- const ordered = await sortByDependencies({
12829
+ const roleWarnings = await validateDefinitionRoles({
12830
+ client: client,
12831
+ definitions: definitions,
12832
+ workflowResource: workflowResource
12833
+ }), ordered = await sortByDependencies({
11492
12834
  client: client,
11493
12835
  definitions: definitions,
11494
12836
  tag: tag
11495
12837
  }), results = [], deployed = [], tx = client.transaction();
11496
12838
  let hasWrites = !1;
11497
12839
  for (const def of ordered) {
11498
- const lint = lintEffectOutputs(def), warnings = lint.length > 0 ? {
12840
+ const lint = [ ...lintEffectOutputs(def), ...lintDeadActivityInputSeeds(def), ...roleWarnings.get(def.name) ?? [] ], warnings = lint.length > 0 ? {
11499
12841
  warnings: lint
11500
12842
  } : {}, latest = await loadLatestDeployed({
11501
12843
  client: client,
@@ -11588,12 +12930,27 @@ const workflow = {
11588
12930
  return runCommitVerb({
11589
12931
  args: args,
11590
12932
  op: "fireAction",
11591
- run: async ({access: access, actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, memberRolesLoader: memberRolesLoader, clock: clock, record: record, before: before}) => {
11592
- if (findCurrentActivityEntry(before, activity) === void 0 && idempotent === !0) return {
12933
+ run: async ({access: access, actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, memberRolesLoader: memberRolesLoader, clock: clock, now: now, record: record, before: before}) => {
12934
+ if (findCurrentActivityEntry(before, activity) === void 0 && idempotent === !0) return settledResult({
11593
12935
  instance: before,
11594
12936
  cascaded: 0,
11595
- changed: !1
11596
- };
12937
+ changed: !1,
12938
+ nextEvaluationAt: await unchangedNextEvaluationAt({
12939
+ client: client,
12940
+ instance: before,
12941
+ actor: actor,
12942
+ clientForGdr: clientForGdr,
12943
+ refSurface: refSurface,
12944
+ memberRolesLoader: memberRolesLoader,
12945
+ now: now,
12946
+ ...executionContext !== void 0 ? {
12947
+ executionContext: executionContext
12948
+ } : {},
12949
+ ...args.telemetry !== void 0 ? {
12950
+ telemetry: args.telemetry
12951
+ } : {}
12952
+ })
12953
+ });
11597
12954
  const result = await dispatchGatedWrite({
11598
12955
  client: client,
11599
12956
  tag: tag,
@@ -11730,7 +13087,7 @@ const workflow = {
11730
13087
  });
11731
13088
  },
11732
13089
  completeEffect: async rawArgs => {
11733
- const args = taggedScope(rawArgs, REQUEST_TAG.completeEffect), {client: client, tag: tag, instanceId: instanceId, effectKey: effectKey, status: status, outputs: outputs, ops: ops, detail: detail, error: error, durationMs: durationMs, executionContext: executionContext} = args, operationContext = await resolveOperationContext(args), clock = args.clock ?? wallClock, record = requestRecordFor({
13090
+ const args = taggedScope(rawArgs, REQUEST_TAG.completeEffect), {client: client, tag: tag, instanceId: instanceId, effectKey: effectKey, status: status, outputs: outputs, ops: ops, detail: detail, error: error, durationMs: durationMs, executionContext: executionContext} = args, operationContext = await resolveOperationContext(args), clock = args.clock ?? wallClock, now = clock(), record = requestRecordFor({
11734
13091
  idempotencyKey: args.idempotencyKey,
11735
13092
  op: "completeEffect",
11736
13093
  idempotencyTtlMs: args.idempotencyTtlMs
@@ -11740,7 +13097,7 @@ const workflow = {
11740
13097
  tag: tag,
11741
13098
  instanceId: instanceId,
11742
13099
  record: record,
11743
- now: clock(),
13100
+ now: now,
11744
13101
  ...operationContext,
11745
13102
  clock: clock,
11746
13103
  executionContext: executionContext,
@@ -11775,7 +13132,7 @@ const workflow = {
11775
13132
  executionContext: executionContext,
11776
13133
  telemetry: args.telemetry
11777
13134
  })
11778
- }), {cascaded: cascaded, instance: instance} = await cascadeAndReload({
13135
+ }), settled = await cascadeAndReload({
11779
13136
  client: client,
11780
13137
  tag: tag,
11781
13138
  instanceId: instanceId,
@@ -11785,22 +13142,21 @@ const workflow = {
11785
13142
  telemetry: args.telemetry
11786
13143
  });
11787
13144
  return resolveTelemetry(args.telemetry).log(WorkflowEffectCompleted, {
11788
- ...definitionHashFragment(instance.pinnedContentHash),
13145
+ ...definitionHashFragment(settled.instance.pinnedContentHash),
11789
13146
  instanceId: instanceId,
11790
13147
  effect: completion.effect,
11791
13148
  status: status,
11792
13149
  origin: completion.origin,
11793
- cascaded: cascaded
11794
- }), {
11795
- instance: instance,
11796
- cascaded: cascaded,
13150
+ cascaded: settled.cascaded
13151
+ }), settledResult({
13152
+ ...settled,
11797
13153
  changed: !0
11798
- };
13154
+ });
11799
13155
  }
11800
13156
  });
11801
13157
  },
11802
13158
  commitEffectOps: async rawArgs => {
11803
- const args = taggedScope(rawArgs, REQUEST_TAG.commitEffectOps), {client: client, tag: tag, instanceId: instanceId, effectKey: effectKey, claimToken: claimToken, ops: ops, executionContext: executionContext} = args, operationContext = await resolveOperationContext(args), clock = args.clock ?? wallClock, record = requestRecordFor({
13159
+ const args = taggedScope(rawArgs, REQUEST_TAG.commitEffectOps), {client: client, tag: tag, instanceId: instanceId, effectKey: effectKey, claimToken: claimToken, ops: ops, executionContext: executionContext} = args, operationContext = await resolveOperationContext(args), clock = args.clock ?? wallClock, now = clock(), record = requestRecordFor({
11804
13160
  idempotencyKey: args.idempotencyKey,
11805
13161
  op: "commitEffectOps",
11806
13162
  idempotencyTtlMs: args.idempotencyTtlMs
@@ -11811,7 +13167,7 @@ const workflow = {
11811
13167
  tag: tag,
11812
13168
  instanceId: instanceId,
11813
13169
  record: record,
11814
- now: clock(),
13170
+ now: now,
11815
13171
  ...operationContext,
11816
13172
  clock: clock,
11817
13173
  executionContext: executionContext,
@@ -11831,7 +13187,7 @@ const workflow = {
11831
13187
  executionContext: executionContext,
11832
13188
  telemetry: args.telemetry
11833
13189
  })
11834
- }), {cascaded: cascaded, instance: instance} = await cascadeAndReload({
13190
+ }), settled = await cascadeAndReload({
11835
13191
  client: client,
11836
13192
  tag: tag,
11837
13193
  instanceId: instanceId,
@@ -11841,15 +13197,14 @@ const workflow = {
11841
13197
  telemetry: args.telemetry
11842
13198
  });
11843
13199
  return resolveTelemetry(args.telemetry).log(WorkflowEffectStateReported, {
11844
- ...definitionHashFragment(instance.pinnedContentHash),
13200
+ ...definitionHashFragment(settled.instance.pinnedContentHash),
11845
13201
  instanceId: instanceId,
11846
13202
  effect: report.effect,
11847
- cascaded: cascaded
11848
- }), {
11849
- instance: instance,
11850
- cascaded: cascaded,
13203
+ cascaded: settled.cascaded
13204
+ }), settledResult({
13205
+ ...settled,
11851
13206
  changed: !0
11852
- };
13207
+ });
11853
13208
  }
11854
13209
  });
11855
13210
  },
@@ -11862,9 +13217,10 @@ const workflow = {
11862
13217
  await assertInstanceWriteAllowed({
11863
13218
  instance: current,
11864
13219
  guards: guards,
11865
- identity: lakePrincipalId(access)
13220
+ identity: lakePrincipalId(access),
13221
+ dereference: clientGuardDereference(client)
11866
13222
  });
11867
- const {cascaded: cascaded, instance: instance} = await cascadeAndReload({
13223
+ const settled = await cascadeAndReload({
11868
13224
  client: client,
11869
13225
  tag: tag,
11870
13226
  instanceId: instanceId,
@@ -11874,21 +13230,20 @@ const workflow = {
11874
13230
  telemetry: args.telemetry
11875
13231
  });
11876
13232
  return resolveTelemetry(args.telemetry).log(WorkflowInstanceTicked, {
11877
- ...definitionHashFragment(instance.pinnedContentHash),
13233
+ ...definitionHashFragment(settled.instance.pinnedContentHash),
11878
13234
  instanceId: instanceId,
11879
- cascaded: cascaded
11880
- }), {
11881
- instance: instance,
11882
- cascaded: cascaded,
11883
- changed: instance._rev !== current._rev
11884
- };
13235
+ cascaded: settled.cascaded
13236
+ }), settledResult({
13237
+ ...settled,
13238
+ changed: settled.instance._rev !== current._rev
13239
+ });
11885
13240
  },
11886
13241
  setStage: async rawArgs => {
11887
13242
  const args = taggedScope(rawArgs, REQUEST_TAG.setStage), {client: client, tag: tag, instanceId: instanceId, targetStage: targetStage, reason: reason, executionContext: executionContext} = args;
11888
13243
  return runCommitVerb({
11889
13244
  args: args,
11890
13245
  op: "setStage",
11891
- run: async ({actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, memberRolesLoader: memberRolesLoader, clock: clock, record: record, before: before}) => {
13246
+ run: async ({actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, memberRolesLoader: memberRolesLoader, clock: clock, now: now, record: record, before: before}) => {
11892
13247
  const result = await setStage({
11893
13248
  client: client,
11894
13249
  instanceId: instanceId,
@@ -11908,34 +13263,29 @@ const workflow = {
11908
13263
  executionContext: executionContext,
11909
13264
  telemetry: args.telemetry
11910
13265
  })
11911
- }), cascaded = result.fired ? await cascade({
13266
+ });
13267
+ return resolveTelemetry(args.telemetry).log(WorkflowStageSet, {
13268
+ ...definitionHashFragment(before.pinnedContentHash),
13269
+ instanceId: instanceId,
13270
+ changed: result.fired
13271
+ }), adminOverrideResult({
11912
13272
  client: client,
13273
+ tag: tag,
11913
13274
  instanceId: instanceId,
13275
+ fired: result.fired,
11914
13276
  actor: actor,
11915
13277
  clientForGdr: clientForGdr,
11916
13278
  refSurface: refSurface,
11917
13279
  memberRolesLoader: memberRolesLoader,
11918
13280
  clock: clock,
13281
+ now: now,
11919
13282
  ...executionContext !== void 0 ? {
11920
13283
  executionContext: executionContext
11921
13284
  } : {},
11922
13285
  ...args.telemetry !== void 0 ? {
11923
13286
  telemetry: args.telemetry
11924
13287
  } : {}
11925
- }) : 0;
11926
- return resolveTelemetry(args.telemetry).log(WorkflowStageSet, {
11927
- ...definitionHashFragment(before.pinnedContentHash),
11928
- instanceId: instanceId,
11929
- changed: result.fired
11930
- }), {
11931
- instance: await reload({
11932
- client: client,
11933
- instanceId: instanceId,
11934
- tag: tag
11935
- }),
11936
- cascaded: cascaded,
11937
- changed: result.fired
11938
- };
13288
+ });
11939
13289
  }
11940
13290
  });
11941
13291
  },
@@ -11968,7 +13318,7 @@ const workflow = {
11968
13318
  ...definitionHashFragment(before.pinnedContentHash),
11969
13319
  instanceId: instanceId,
11970
13320
  changed: changed
11971
- }), {
13321
+ }), settledResult({
11972
13322
  instance: await reload({
11973
13323
  client: client,
11974
13324
  instanceId: instanceId,
@@ -11976,7 +13326,7 @@ const workflow = {
11976
13326
  }),
11977
13327
  cascaded: 0,
11978
13328
  changed: changed
11979
- };
13329
+ });
11980
13330
  }
11981
13331
  });
11982
13332
  },
@@ -11986,7 +13336,7 @@ const workflow = {
11986
13336
  return runCommitVerb({
11987
13337
  args: args,
11988
13338
  op: "resetActivity",
11989
- run: async ({actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, memberRolesLoader: memberRolesLoader, clock: clock, record: record, before: before}) => {
13339
+ run: async ({actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, memberRolesLoader: memberRolesLoader, clock: clock, now: now, record: record, before: before}) => {
11990
13340
  const result = await resetActivity({
11991
13341
  client: client,
11992
13342
  instanceId: instanceId,
@@ -12004,34 +13354,29 @@ const workflow = {
12004
13354
  executionContext: executionContext,
12005
13355
  telemetry: args.telemetry
12006
13356
  })
12007
- }), cascaded = result.fired ? await cascade({
13357
+ });
13358
+ return resolveTelemetry(args.telemetry).log(WorkflowActivityReset, {
13359
+ ...definitionHashFragment(before.pinnedContentHash),
13360
+ instanceId: instanceId,
13361
+ changed: result.fired
13362
+ }), adminOverrideResult({
12008
13363
  client: client,
13364
+ tag: tag,
12009
13365
  instanceId: instanceId,
13366
+ fired: result.fired,
12010
13367
  actor: actor,
12011
13368
  clientForGdr: clientForGdr,
12012
13369
  refSurface: refSurface,
12013
13370
  memberRolesLoader: memberRolesLoader,
12014
13371
  clock: clock,
13372
+ now: now,
12015
13373
  ...executionContext !== void 0 ? {
12016
13374
  executionContext: executionContext
12017
13375
  } : {},
12018
13376
  ...args.telemetry !== void 0 ? {
12019
13377
  telemetry: args.telemetry
12020
13378
  } : {}
12021
- }) : 0;
12022
- return resolveTelemetry(args.telemetry).log(WorkflowActivityReset, {
12023
- ...definitionHashFragment(before.pinnedContentHash),
12024
- instanceId: instanceId,
12025
- changed: result.fired
12026
- }), {
12027
- instance: await reload({
12028
- client: client,
12029
- instanceId: instanceId,
12030
- tag: tag
12031
- }),
12032
- cascaded: cascaded,
12033
- changed: result.fired
12034
- };
13379
+ });
12035
13380
  }
12036
13381
  });
12037
13382
  },
@@ -12364,8 +13709,8 @@ async function startFreshInstance(args) {
12364
13709
  extraHistory: fieldDiscards
12365
13710
  } : {}
12366
13711
  });
12367
- await client.create(base, SYNC_COMMIT);
12368
- const cascaded = await settleStart({
13712
+ await client.create(instanceDocumentForWrite(base), SYNC_COMMIT);
13713
+ const settled = await settleStart({
12369
13714
  client: client,
12370
13715
  tag: tag,
12371
13716
  instanceId: id,
@@ -12387,15 +13732,15 @@ async function startFreshInstance(args) {
12387
13732
  viaSpawn: !1
12388
13733
  }))
12389
13734
  });
12390
- return {
13735
+ return settledResult({
13736
+ ...settled,
12391
13737
  instance: await reload({
12392
13738
  client: client,
12393
13739
  instanceId: id,
12394
13740
  tag: tag
12395
13741
  }),
12396
- cascaded: cascaded,
12397
13742
  changed: !0
12398
- };
13743
+ });
12399
13744
  }
12400
13745
 
12401
13746
  function assertInitialStageExists(definition) {
@@ -12492,7 +13837,7 @@ function pendingEntry(args) {
12492
13837
  }
12493
13838
 
12494
13839
  function createEffectCommitQueue(args) {
12495
- const {effectKey: effectKey, claimToken: claimToken, commit: commit} = args, pending = [];
13840
+ const {effectKey: effectKey, claimToken: claimToken, attempt: attempt, commit: commit} = args, pending = [];
12496
13841
  let admitted = 0, dead, closed = !1, tail = Promise.resolve();
12497
13842
  function assertOpen() {
12498
13843
  if (dead !== void 0) throw dead;
@@ -12547,7 +13892,7 @@ function createEffectCommitQueue(args) {
12547
13892
  value: entry.latestValue
12548
13893
  }
12549
13894
  } ],
12550
- idempotencyKey: `effect:${effectKey}:${claimToken}:progress:${coalesceKey}:${admitted}`,
13895
+ idempotencyKey: `effect:${effectKey}:${claimToken}:${attempt}:progress:${coalesceKey}:${admitted}`,
12551
13896
  coalesceKey: coalesceKey,
12552
13897
  latestValue: value
12553
13898
  });
@@ -12557,6 +13902,29 @@ function createEffectCommitQueue(args) {
12557
13902
  };
12558
13903
  }
12559
13904
 
13905
+ function effectRetryPolicy(definition, effectName) {
13906
+ return findEffect(definition, effectName)?.retry;
13907
+ }
13908
+
13909
+ function retryWindowClosed(policy, elapsedMs) {
13910
+ return policy.expiryMs !== void 0 && elapsedMs >= policy.expiryMs;
13911
+ }
13912
+
13913
+ function nextRetryDelayMs(policy, at) {
13914
+ if (at.attempt >= policy.attempts) return;
13915
+ const delay = effectBackoffMs(policy, at.attempt);
13916
+ return retryWindowClosed(policy, at.elapsedMs + delay) ? void 0 : delay;
13917
+ }
13918
+
13919
+ function retryFailedDetail(args) {
13920
+ const window = args.windowClosed ? ", retry window expired" : "";
13921
+ return `failed after ${args.at.attempt} of ${args.attempts} attempts${window}: ${args.error.message}`;
13922
+ }
13923
+
13924
+ function retrySucceededDetail(args) {
13925
+ if (args.at.attempt !== 1) return `succeeded on attempt ${args.at.attempt} of ${args.attempts}`;
13926
+ }
13927
+
12560
13928
  class MissingHandlerError extends WorkflowError {
12561
13929
  info;
12562
13930
  constructor(args) {
@@ -12570,7 +13938,7 @@ function missingHandlerMessage(info) {
12570
13938
  }
12571
13939
 
12572
13940
  async function verifyDeployedDefinitionsInternal(args) {
12573
- const {tag: tag, effectHandlers: effectHandlers, missingHandler: missingHandler, logger: logger} = args, client = withRequestTag(args.client, REQUEST_TAG.verifyDefinitions);
13941
+ const {tag: tag, handlers: handlers, missingHandler: missingHandler, logger: logger} = args, client = withRequestTag(args.client, REQUEST_TAG.verifyDefinitions);
12574
13942
  validateTag(tag);
12575
13943
  const log = logger("verifyDeployedDefinitions"), definitions = (await client.fetch(definitionsListGroq("asc"), {
12576
13944
  tag: tag
@@ -12580,7 +13948,7 @@ async function verifyDeployedDefinitionsInternal(args) {
12580
13948
  version: def.version,
12581
13949
  _id: def._id
12582
13950
  }), walkEffectNames(def, (name, location) => {
12583
- if (effectHandlers[name] !== void 0) return;
13951
+ if (handlers[name] !== void 0) return;
12584
13952
  const key = `${name}@@${def._id}`;
12585
13953
  let bucket = missingByName.get(key);
12586
13954
  bucket === void 0 && (bucket = {
@@ -12639,7 +14007,7 @@ function isCancelledCompletion(data) {
12639
14007
  }
12640
14008
 
12641
14009
  async function drainEffectsInternal(args) {
12642
- const {tag: tag, workflowResource: workflowResource, instanceId: instanceId, effectHandlers: effectHandlers, missingHandler: missingHandler, logger: logger, handlerClient: rawHandlerClient, handlerResourceClients: rawHandlerResourceClients} = args, cascadeTelemetry = drainCascadeTelemetry(args.telemetry), {client: client, resourceClients: resourceClients} = taggedScope(args, REQUEST_TAG.drain), leaseMs = args.leaseMs ?? DEFAULT_EFFECT_LEASE_MS, clock = args.clock ?? wallClock, handlerClient = effectHandlerClient(rawHandlerClient), handlerResourceClients = effectHandlerResolver(rawHandlerResourceClients), routeGdr = buildClientForGdr({
14010
+ const {tag: tag, workflowResource: workflowResource, instanceId: instanceId, handlers: handlers, missingHandler: missingHandler, logger: logger, handlerClient: rawHandlerClient, handlerResourceClients: rawHandlerResourceClients} = args, cascadeTelemetry = drainCascadeTelemetry(args.telemetry), {client: client, resourceClients: resourceClients} = taggedScope(args, REQUEST_TAG.drain), leaseMs = args.leaseMs ?? DEFAULT_EFFECT_LEASE_MS, clock = args.clock ?? wallClock, sleep = args.sleep ?? wallSleeper, handlerClient = effectHandlerClient(rawHandlerClient), handlerResourceClients = effectHandlerResolver(rawHandlerResourceClients), routeGdr = buildClientForGdr({
12643
14011
  client: handlerClient,
12644
14012
  workflowResource: workflowResource,
12645
14013
  resourceClients: handlerResourceClients
@@ -12659,12 +14027,13 @@ async function drainEffectsInternal(args) {
12659
14027
  resourceClients: resourceClients
12660
14028
  } : {},
12661
14029
  instanceId: instanceId,
12662
- effectHandlers: effectHandlers,
14030
+ handlers: handlers,
12663
14031
  missingHandler: missingHandler,
12664
14032
  logger: logger,
12665
14033
  log: log,
12666
14034
  leaseMs: leaseMs,
12667
14035
  clock: clock,
14036
+ sleep: sleep,
12668
14037
  ...executionContext !== void 0 ? {
12669
14038
  executionContext: executionContext
12670
14039
  } : {},
@@ -12672,14 +14041,14 @@ async function drainEffectsInternal(args) {
12672
14041
  drainerActor: drainerActor,
12673
14042
  clientFor: clientFor,
12674
14043
  buckets: buckets,
12675
- skippedKeys: /* @__PURE__ */ new Set
14044
+ handledKeys: /* @__PURE__ */ new Set
12676
14045
  };
12677
14046
  for (;await drainOneCandidate(pass); ) ;
12678
14047
  return buckets;
12679
14048
  }
12680
14049
 
12681
14050
  async function drainOneCandidate(pass) {
12682
- const {client: client, tag: tag, instanceId: instanceId, buckets: buckets, skippedKeys: skippedKeys} = pass, now = pass.clock(), before = await reload({
14051
+ const {client: client, tag: tag, instanceId: instanceId, buckets: buckets, handledKeys: handledKeys} = pass, now = pass.clock(), before = await reload({
12683
14052
  client: client,
12684
14053
  instanceId: instanceId,
12685
14054
  tag: tag
@@ -12688,16 +14057,16 @@ async function drainOneCandidate(pass) {
12688
14057
  const candidate = findClaimableCandidate({
12689
14058
  instance: before,
12690
14059
  now: now,
12691
- skippedKeys: skippedKeys
14060
+ handledKeys: handledKeys
12692
14061
  });
12693
14062
  if (candidate === void 0) return !1;
12694
- const handler = pass.effectHandlers[candidate.name];
14063
+ const handler = pass.handlers[candidate.name];
12695
14064
  if (handler === void 0) return await assertSkippableOrThrow({
12696
14065
  missingHandler: pass.missingHandler,
12697
14066
  candidate: candidate,
12698
14067
  instanceId: instanceId,
12699
14068
  log: pass.log
12700
- }), buckets.skipped.push(candidate), skippedKeys.add(candidate._key), !0;
14069
+ }), buckets.skipped.push(candidate), handledKeys.add(candidate._key), !0;
12701
14070
  const claimed = await claimPendingEffect({
12702
14071
  client: client,
12703
14072
  instance: before,
@@ -12710,11 +14079,15 @@ async function drainOneCandidate(pass) {
12710
14079
  } : {}
12711
14080
  });
12712
14081
  if (claimed === void 0) return !0;
12713
- const outcome = await dispatchAndReport({
14082
+ const retry = effectRetryPolicy(parseDefinitionSnapshot(before), candidate.name), outcome = await dispatchAndReport({
12714
14083
  handler: handler,
12715
14084
  candidate: candidate,
12716
14085
  claimToken: claimed.claimToken,
14086
+ ...retry !== void 0 ? {
14087
+ retry: retry
14088
+ } : {},
12717
14089
  leaseMs: pass.leaseMs,
14090
+ sleep: pass.sleep,
12718
14091
  client: client,
12719
14092
  handlerClient: pass.handlerClient,
12720
14093
  tag: tag,
@@ -12732,50 +14105,80 @@ async function drainOneCandidate(pass) {
12732
14105
  telemetry: pass.telemetry,
12733
14106
  clock: pass.clock
12734
14107
  });
12735
- return buckets[outcome].push(candidate), !0;
14108
+ return buckets[outcome].push(candidate), outcome === "lost" && handledKeys.add(candidate._key),
14109
+ !0;
12736
14110
  }
12737
14111
 
12738
- function findClaimableCandidate({instance: instance, now: now, skippedKeys: skippedKeys}) {
12739
- return instance.pendingEffects.find(e => (e.claim === void 0 || isClaimExpired(e.claim, now)) && !skippedKeys.has(e._key));
14112
+ function findClaimableCandidate({instance: instance, now: now, handledKeys: handledKeys}) {
14113
+ return instance.pendingEffects.find(e => (e.claim === void 0 || isClaimExpired(e.claim, now)) && !handledKeys.has(e._key));
12740
14114
  }
12741
14115
 
12742
14116
  async function dispatchAndReport(args) {
12743
- const {handler: handler, candidate: candidate, claimToken: claimToken, leaseMs: leaseMs, client: client, handlerClient: handlerClient, tag: tag, workflowResource: workflowResource, resourceClients: resourceClients, instanceId: instanceId, clientFor: clientFor, logger: logger, log: log, executionContext: executionContext, telemetry: telemetry, clock: clock} = args, commitQueue = createEffectCommitQueue({
12744
- effectKey: candidate._key,
12745
- claimToken: claimToken,
12746
- commit: async req => {
12747
- await workflow.commitEffectOps({
12748
- client: client,
12749
- tag: tag,
12750
- workflowResource: workflowResource,
12751
- ...resourceClients !== void 0 ? {
12752
- resourceClients: resourceClients
12753
- } : {},
12754
- ...executionContext !== void 0 ? {
12755
- executionContext: executionContext
12756
- } : {},
12757
- ...telemetry !== void 0 ? {
12758
- telemetry: telemetry
12759
- } : {},
12760
- instanceId: instanceId,
12761
- effectKey: candidate._key,
12762
- claimToken: claimToken,
12763
- ops: req.ops,
12764
- idempotencyKey: req.idempotencyKey,
12765
- leaseMs: leaseMs,
12766
- clock: clock
12767
- });
12768
- }
12769
- }), {outputs: outputs, ops: ops, dispatchError: dispatchError} = await dispatchEffect({
14117
+ const {handler: handler, candidate: candidate, claimToken: claimToken, retry: retry, leaseMs: leaseMs, sleep: sleep, client: client, handlerClient: handlerClient, tag: tag, workflowResource: workflowResource, resourceClients: resourceClients, instanceId: instanceId, clientFor: clientFor, logger: logger, log: log, executionContext: executionContext, telemetry: telemetry, clock: clock} = args, attemptOnce = attempt => dispatchEffect({
12770
14118
  handler: handler,
12771
14119
  candidate: candidate,
12772
- queue: commitQueue,
14120
+ queue: createEffectCommitQueue({
14121
+ effectKey: candidate._key,
14122
+ claimToken: claimToken,
14123
+ attempt: attempt,
14124
+ commit: async req => {
14125
+ await workflow.commitEffectOps({
14126
+ client: client,
14127
+ tag: tag,
14128
+ workflowResource: workflowResource,
14129
+ ...resourceClients !== void 0 ? {
14130
+ resourceClients: resourceClients
14131
+ } : {},
14132
+ ...executionContext !== void 0 ? {
14133
+ executionContext: executionContext
14134
+ } : {},
14135
+ ...telemetry !== void 0 ? {
14136
+ telemetry: telemetry
14137
+ } : {},
14138
+ instanceId: instanceId,
14139
+ effectKey: candidate._key,
14140
+ claimToken: claimToken,
14141
+ ops: req.ops,
14142
+ idempotencyKey: req.idempotencyKey,
14143
+ leaseMs: leaseMs,
14144
+ clock: clock
14145
+ });
14146
+ }
14147
+ }),
12773
14148
  ctx: {
12774
14149
  client: handlerClient,
12775
14150
  clientFor: clientFor,
12776
14151
  instanceId: instanceId,
12777
14152
  logger: logger
12778
14153
  }
14154
+ }), claimed = {
14155
+ client: client,
14156
+ tag: tag,
14157
+ instanceId: instanceId,
14158
+ effectKey: candidate._key,
14159
+ claimToken: claimToken
14160
+ }, run = await runRetryPolicy({
14161
+ attemptOnce: attemptOnce,
14162
+ ...retry !== void 0 ? {
14163
+ retry: retry
14164
+ } : {},
14165
+ clock: clock,
14166
+ sleep: sleep,
14167
+ holdThrough: holdMs => renewClaimLease({
14168
+ ...claimed,
14169
+ holdMs: holdMs,
14170
+ clock: clock
14171
+ }),
14172
+ stillHeld: () => claimStillHeld(claimed),
14173
+ leaseMs: leaseMs
14174
+ });
14175
+ if (run.abandoned !== void 0) return log.warn(run.abandoned === "lease" ? `Effect "${candidate.name}" could not hold its claim between retry attempts — the run stopped with attempts left` : `Effect "${candidate.name}" was settled by another party during a retry backoff — the run stopped without dispatching again`, {
14176
+ effectKey: candidate._key,
14177
+ instanceId: instanceId
14178
+ }), "lost";
14179
+ const {outputs: outputs, ops: ops, dispatchError: dispatchError} = run.dispatch, detail = retry === void 0 ? void 0 : completionDetail({
14180
+ run: run,
14181
+ retry: retry
12779
14182
  });
12780
14183
  try {
12781
14184
  await reportEffectOutcome({
@@ -12796,6 +14199,9 @@ async function dispatchAndReport(args) {
12796
14199
  outputs: outputs,
12797
14200
  ops: ops,
12798
14201
  dispatchError: dispatchError,
14202
+ ...detail !== void 0 ? {
14203
+ detail: detail
14204
+ } : {},
12799
14205
  clock: clock
12800
14206
  });
12801
14207
  } catch (error) {
@@ -12808,6 +14214,71 @@ async function dispatchAndReport(args) {
12808
14214
  return dispatchError === void 0 ? "drained" : "failed";
12809
14215
  }
12810
14216
 
14217
+ async function runRetryPolicy(args) {
14218
+ const {attemptOnce: attemptOnce, retry: retry, clock: clock} = args, startedAt = Date.parse(clock()), elapsed = () => Date.parse(clock()) - startedAt;
14219
+ let attempt = 1, dispatch = await attemptOnce(attempt);
14220
+ for (;dispatch.dispatchError !== void 0 && retry !== void 0; ) {
14221
+ const at = {
14222
+ attempt: attempt,
14223
+ elapsedMs: elapsed()
14224
+ }, stop = await waitForNextAttempt({
14225
+ retry: retry,
14226
+ at: at,
14227
+ elapsed: elapsed,
14228
+ pace: args
14229
+ });
14230
+ if (stop !== void 0) return {
14231
+ ...stop,
14232
+ dispatch: dispatch,
14233
+ at: at
14234
+ };
14235
+ attempt += 1, dispatch = await attemptOnce(attempt);
14236
+ }
14237
+ return {
14238
+ dispatch: dispatch,
14239
+ at: {
14240
+ attempt: attempt,
14241
+ elapsedMs: elapsed()
14242
+ },
14243
+ windowClosed: !1
14244
+ };
14245
+ }
14246
+
14247
+ async function waitForNextAttempt(args) {
14248
+ const {retry: retry, at: at, elapsed: elapsed} = args, {clock: clock, sleep: sleep, holdThrough: holdThrough, stillHeld: stillHeld, leaseMs: leaseMs} = args.pace, delay = nextRetryDelayMs(retry, at);
14249
+ if (delay === void 0) return {
14250
+ windowClosed: at.attempt < retry.attempts
14251
+ };
14252
+ const heldUntil = await holdThrough(delay + leaseMs);
14253
+ if (heldUntil === void 0 || hasPassed(heldUntil, addMs(clock(), delay))) return {
14254
+ windowClosed: !1,
14255
+ abandoned: "lease"
14256
+ };
14257
+ if (retryWindowClosed(retry, elapsed() + delay)) return {
14258
+ windowClosed: !0
14259
+ };
14260
+ if (await sleep(delay), !await stillHeld()) return {
14261
+ windowClosed: !1,
14262
+ abandoned: "settled"
14263
+ };
14264
+ if (retryWindowClosed(retry, elapsed())) return {
14265
+ windowClosed: !0
14266
+ };
14267
+ }
14268
+
14269
+ function completionDetail(args) {
14270
+ const {run: run, retry: retry} = args, {dispatchError: dispatchError} = run.dispatch;
14271
+ return dispatchError === void 0 ? retrySucceededDetail({
14272
+ at: run.at,
14273
+ attempts: retry.attempts
14274
+ }) : retryFailedDetail({
14275
+ at: run.at,
14276
+ attempts: retry.attempts,
14277
+ error: dispatchError,
14278
+ windowClosed: run.windowClosed
14279
+ });
14280
+ }
14281
+
12811
14282
  async function reportEffectOutcome(args) {
12812
14283
  const {outputs: outputs, ops: ops, dispatchError: dispatchError, resourceClients: resourceClients, ...rest} = args;
12813
14284
  await workflow.completeEffect({
@@ -13098,8 +14569,12 @@ function createInstanceSession(args) {
13098
14569
  definition: definitionOf(),
13099
14570
  actor: actor,
13100
14571
  snapshot: snapshotFrom(held, normalizedSelf),
14572
+ documentAvailability: documentAvailabilityReader({
14573
+ clientForGdr: evalScope.clientForGdr
14574
+ }),
13101
14575
  guards: guards,
13102
14576
  resourceGrants: resourceGrants,
14577
+ guardDereference: clientGuardDereference(evalScope.client),
13103
14578
  ...localPrincipalId !== void 0 ? {
13104
14579
  localPrincipalId: localPrincipalId
13105
14580
  } : {},
@@ -13114,7 +14589,7 @@ function createInstanceSession(args) {
13114
14589
  } : {}
13115
14590
  });
13116
14591
  }, settleAfterApply = async ({actor: actor, held: held, ranOps: ranOps, scope: scope, memberRolesLoader: memberRolesLoader}) => {
13117
- const cascaded = await cascadeHeld({
14592
+ const settled = await cascadeHeld({
13118
14593
  scope: scope,
13119
14594
  actor: actor,
13120
14595
  held: held,
@@ -13124,14 +14599,14 @@ function createInstanceSession(args) {
13124
14599
  client: scope.client,
13125
14600
  instanceId: instance._id,
13126
14601
  tag: tag
13127
- }), {
14602
+ }), settledResult({
14603
+ ...settled,
13128
14604
  instance: instance,
13129
- cascaded: cascaded,
13130
14605
  changed: !0,
13131
14606
  ...ranOps !== void 0 ? {
13132
14607
  ranOps: ranOps
13133
14608
  } : {}
13134
- };
14609
+ });
13135
14610
  }, cascadeHeld = ({scope: scope, actor: actor, held: held, memberRolesLoader: memberRolesLoader}) => cascade({
13136
14611
  client: scope.client,
13137
14612
  instanceId: instance._id,
@@ -13242,13 +14717,14 @@ function createInstanceSession(args) {
13242
14717
  identity: lakePrincipalId({
13243
14718
  actor: actor,
13244
14719
  localPrincipalId: localPrincipalId
13245
- })
14720
+ }),
14721
+ dereference: clientGuardDereference(tickScope.client)
13246
14722
  });
13247
14723
  const before = await reload({
13248
14724
  client: tickScope.client,
13249
14725
  instanceId: instance._id,
13250
14726
  tag: tag
13251
- }), cascaded = await cascadeHeld({
14727
+ }), settled = await cascadeHeld({
13252
14728
  scope: tickScope,
13253
14729
  actor: actor,
13254
14730
  held: held,
@@ -13261,12 +14737,12 @@ function createInstanceSession(args) {
13261
14737
  }), telemetry.log(WorkflowInstanceTicked, {
13262
14738
  ...definitionHashFragment(instance.pinnedContentHash),
13263
14739
  instanceId: instance._id,
13264
- cascaded: cascaded
13265
- }), {
14740
+ cascaded: settled.cascaded
14741
+ }), settledResult({
14742
+ ...settled,
13266
14743
  instance: instance,
13267
- cascaded: cascaded,
13268
14744
  changed: instance._rev !== before._rev
13269
- };
14745
+ });
13270
14746
  });
13271
14747
  },
13272
14748
  fireAction({activity: activity, action: action, params: params}) {
@@ -13416,7 +14892,10 @@ const silentLogger = {
13416
14892
  function createEngine(args) {
13417
14893
  const {workflowResource: workflowResource, clock: clock, tag: tag, executionContext: executionContext} = args, {client: client, resourceClients: resourceClients} = taggedScope(args, REQUEST_TAG.engine);
13418
14894
  validateTag(tag);
13419
- const effectHandlers = args.effectHandlers ?? {}, missingHandler = args.missingHandler ?? "fail", logger = args.loggerFactory ?? defaultLoggerFactory, telemetry = resolveTelemetry(args.telemetry), EMPTY_DRAIN_LOG_INTERVAL_MS = 6e4;
14895
+ const effects = {
14896
+ handlers: args.effects?.handlers ?? {},
14897
+ missingHandler: args.effects?.missingHandler ?? "fail"
14898
+ }, logger = args.loggerFactory ?? defaultLoggerFactory, telemetry = resolveTelemetry(args.telemetry), EMPTY_DRAIN_LOG_INTERVAL_MS = 6e4;
13420
14899
  let lastEmptyDrainLoggedAt;
13421
14900
  const emptyDrainDue = () => {
13422
14901
  const now = Date.parse((clock ?? wallClock)());
@@ -13447,8 +14926,7 @@ function createEngine(args) {
13447
14926
  client: client,
13448
14927
  tag: tag,
13449
14928
  workflowResource: workflowResource,
13450
- effectHandlers: effectHandlers,
13451
- missingHandler: missingHandler,
14929
+ effects: effects,
13452
14930
  logger: logger,
13453
14931
  telemetry: telemetry,
13454
14932
  resolveActor: rest => resolveClientActor(client, rest),
@@ -13502,12 +14980,14 @@ function createEngine(args) {
13502
14980
  workflowResource: workflowResource,
13503
14981
  ...optionalScope,
13504
14982
  instanceId: instanceId,
13505
- effectHandlers: effectHandlers,
13506
- missingHandler: missingHandler,
14983
+ ...effects,
13507
14984
  logger: logger,
13508
14985
  telemetry: telemetry,
13509
- ...args.effectLeaseMs !== void 0 ? {
13510
- leaseMs: args.effectLeaseMs
14986
+ ...args.effects?.leaseMs !== void 0 ? {
14987
+ leaseMs: args.effects.leaseMs
14988
+ } : {},
14989
+ ...args.sleep !== void 0 ? {
14990
+ sleep: args.sleep
13511
14991
  } : {}
13512
14992
  }), drained = result.drained;
13513
14993
  return (drained.length > 0 || emptyDrainDue()) && telemetry.log(WorkflowEffectsDrained, {
@@ -13520,8 +15000,7 @@ function createEngine(args) {
13520
15000
  verifyDeployedDefinitions: () => verifyDeployedDefinitionsInternal({
13521
15001
  client: client,
13522
15002
  tag: tag,
13523
- effectHandlers: effectHandlers,
13524
- missingHandler: missingHandler,
15003
+ ...effects,
13525
15004
  logger: logger
13526
15005
  })
13527
15006
  };
@@ -13530,27 +15009,43 @@ function createEngine(args) {
13530
15009
  const CONSENT_TAG = "telemetry.consent", BATCH_TAG = "telemetry.batch";
13531
15010
 
13532
15011
  function createTelemetryIntake(args) {
13533
- const {client: client, projectId: projectId, denied: denied = !1} = args;
15012
+ const {client: client, projectId: projectId, denied: denied = !1, context: context} = args;
13534
15013
  let cachedConsent;
13535
15014
  return {
13536
15015
  resolveConsent: async () => denied ? {
13537
15016
  status: "denied"
13538
15017
  } : (cachedConsent !== void 0 || (cachedConsent = await client.request({
13539
- uri: "/intake/telemetry-status",
15018
+ url: "/intake/telemetry-status",
13540
15019
  tag: CONSENT_TAG
13541
15020
  })), cachedConsent),
13542
15021
  sendEvents: batch => client.request({
13543
- uri: "/intake/batch",
15022
+ url: "/intake/batch",
13544
15023
  method: "POST",
13545
15024
  body: {
13546
15025
  projectId: projectId,
13547
- batch: batch
15026
+ batch: context === void 0 ? batch : batch.map(event => withContext(event, context))
13548
15027
  },
13549
15028
  tag: BATCH_TAG
13550
15029
  })
13551
15030
  };
13552
15031
  }
13553
15032
 
15033
+ function withContext(event, context) {
15034
+ if (!isRecord(event)) return event;
15035
+ const existing = isRecord(event.context) ? event.context : {};
15036
+ return {
15037
+ ...event,
15038
+ context: {
15039
+ ...existing,
15040
+ ...context
15041
+ }
15042
+ };
15043
+ }
15044
+
15045
+ function _resolveTelemetryEnvironment(nodeEnv, fallback) {
15046
+ return nodeEnv === "production" ? "production" : nodeEnv === "development" || nodeEnv === "test" ? "development" : fallback;
15047
+ }
15048
+
13554
15049
  function isTelemetryEnvDenied(env) {
13555
15050
  return isTrueish(env.CI) || isTrueish(env.DO_NOT_TRACK);
13556
15051
  }
@@ -13573,6 +15068,16 @@ function isTrueish(value) {
13573
15068
  return normalized !== "0" && normalized !== "false";
13574
15069
  }
13575
15070
 
15071
+ function summary(documents, deleted) {
15072
+ if (documents.length === 0) return "";
15073
+ const titles = [ ...new Set(documents.map(document => document.title ?? document.target.field)) ].map(title => `“${title}”`).join(", ");
15074
+ return documents.length === 1 ? `A document selected for ${titles} ${deleted ? "has been deleted" : "can’t be read by this workflow"}` : `${documents.length} documents selected for ${titles} ${deleted ? "have been deleted" : "can’t be read by this workflow"}`;
15075
+ }
15076
+
15077
+ function _missingDocumentsSummary(documents) {
15078
+ return [ summary(documents.filter(document => document.availability === "deleted"), !0), summary(documents.filter(document => document.availability !== "deleted"), !1) ].filter(Boolean).join(". ");
15079
+ }
15080
+
13576
15081
  function groupSitesOf(definition) {
13577
15082
  const sites = [], workflow2 = declarationIndex({
13578
15083
  groups: definition.groups,
@@ -14060,4 +15565,4 @@ function displayDescription(typeKey) {
14060
15565
  if (typeKey) return DISPLAY[typeKey]?.description;
14061
15566
  }
14062
15567
 
14063
- export { ACTION_SEMANTICS, ACTIVITY_KINDS, ACTIVITY_KIND_DISPLAY, ACTOR_KINDS, ANONYMOUS_IDENTITY, AUTHORING_DISPLAY, ActionDisabledError, ActionParamsInvalidError, CONDITION_VARS, CONTEXT_ENTRY_DISPLAY, CascadeLimitError, ConcurrentCommitEffectOpsError, ConcurrentCompleteEffectError, ConcurrentEditFieldError, ConcurrentFireActionError, ContractViolationError, DATA_MODEL_CHANGES, DATA_MODEL_MAX_READER, DATA_MODEL_MIN_READER, DATA_MODEL_VERSION, DECISION_SEMANTICS, DEFAULT_CONTENT_PERSPECTIVE, DEFAULT_EFFECT_LEASE_MS, DEFAULT_IDEMPOTENCY_TTL_MS, DEFAULT_TRANSITION_WHEN, DISPLAY, DRIVER_KINDS, DRIVER_KIND_DISPLAY, DefinitionInUseError, DefinitionNotFoundError, EFFECT_COMMIT_DISPATCH_CAP, EFFECT_COMMIT_QUEUE_DEPTH, ENGINE_API_VERSION, EXECUTION_KINDS, EXECUTOR_CLASSIFICATIONS, EXECUTOR_CLASSIFICATION_DISPLAY, EditFieldDeniedError, EffectCommitQueueOverflowError, EffectNotFoundError, EffectOpsInvalidError, EffectOutputsInvalidError, FIELD_KIND_DISPLAY, FILTER_SCOPE_VARS, FieldValueShapeError, GROUP_KINDS, GROUP_KIND_DISPLAY, GUARD_DOC_TYPE, GUARD_OWNER, GUARD_PREDICATE_VARS, HISTORY_DISPLAY, InitialFieldsInvalidError, InstanceNotFoundError, MAX_COUNTERFACTUAL_INDEX2 as MAX_COUNTERFACTUAL_INDEX, MissingHandlerError, ModelVersionAheadError, MutationGuardDeniedError, MutationGuardDocSchema, NEUTRAL_MARK, OP_DISPLAY, OUTCOME_MARKS, PartialGuardDeployError, PersistedDocShapeError, READER_MODEL_ROLLOUT_URL, RESERVED_CONDITION_VARS, ReaderModelAcknowledgementError, RefResourceUndeclaredError, RequiredFieldNotProvidedError, SIGNAL_SEMANTICS, START_FILTER_VARS, START_REQUIREMENT_VARS, SYSTEM_IDENTITY, SpawnContractsInvalidError, StaleEffectClaimError, StartNotAllowedError, StartNotPrimedError, StartNotSettledError, WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, WorkflowActionFired, WorkflowActivityReset, WorkflowDefinitionDeleted, WorkflowDefinitionDeployed, WorkflowEffectCompleted, WorkflowEffectStateReported, WorkflowEffectsDrained, WorkflowError, WorkflowFieldEdited, WorkflowInstanceAborted, WorkflowInstancePreviewSchema, WorkflowInstanceSchema, WorkflowInstanceStarted, WorkflowInstanceTicked, WorkflowStageSet, WorkflowStageTransitioned, WorkflowStateDivergedError, abortReason, acceptsDocumentType, aclPathForResource, actionDisabledDetail, actionRendering, actionVerdict, activityAutonomyOf, actorFulfillsRole, analyzeCondition2 as analyzeCondition, applicableDefinitions, assertReadableModel, assertReaderModelAcknowledgement, atomReadsDataset2 as atomReadsDataset, autonomySummary, availableActions, buildInitialFields, buildSnapshot, checklistLines, classifyPrincipalId, clientConfigFromResource, clientProjectUserDirectory, compileGuard, computeDiffEntries, conditionFieldReadNames, conditionSitesOf, contentDocQuery, contentDraftFallback, contentReleaseName, contextMap, createEngine, createTelemetryIntake, datasetResourceParts, defaultLoggerFactory, definitionDeployedData, definitionLookupGroq, definitionTagsGroq, definitionsListGroq, deniedGuardLabels, deniedGuardRefs, denyingGuards, deployStageGuards, deployedTagsGroq, deriveActivityKind, deriveExecutorClassification, deriveWorkflowAutonomy, describeAtom, describeAutonomyWait, describeCondition, describeDefinition, describeFieldInsight, describeNode, describeSite, describeSiteHeading, diagnoseInputFromEvaluation, diagnoseInstance, diffEntry, displayDescription, displayTitle, documentActionDenials, documentPrefilter, documentStuckCause, driverKind, effectOutputsMap, entryDocRefs, errorMessage, evaluateFromSnapshot, evaluateMutationGuard, evaluateStartFilter, expandResourceAliases, explainCondition2 as explainCondition, explainStartRequirement, extractDocumentId, fieldTreeShape, findActivityNode, findCurrentActivityEntry, findOpenStageEntry, findStageNode, formatRead, gdrFromResource, gdrRef, gdrUri, groupMembershipNames, groupSitesOf, guardMatches, guardsForDefinition, guardsForInstance, guardsForResource, guillemets2 as guillemets, hasSingleSubjectRequirement, hashDefinitionContent, humanize2 as humanize, inFlightFilter, initialFieldIssues, instanceChangesQuery, instanceDocId, instanceGuardQuery, instancePreviewsQuery, instanceWatchesDocument, instancesGuardQuery, instancesQuery, isCascadeFired, isClaimExpired, isClientProjectUser, isComparisonOp, isDefinitionApplicable, isFilterScopedOut, isGdr, isInputSourced, isNotesEntry, isProjectUserNotFoundError, isRevisionConflict, isSingleDocRefEntry, isSingleDocRefKind, isStartableDefinition, isSubjectEntry, isTelemetryEnvDenied, isTerminalActivityStatus, isTerminalStage, isTodoListEntry, isTodoListItem, isUnprimed, lakeGuardId, lakePrincipalId, latestDefinitionsGroq, latestDeployedDefinitions, lintEffectOutputs, minReaderModelOf, missingRequiredInputs, modelVersionOf, narrateAutonomyWaits, noopTelemetry, parentRef, parseDefinitionInput, parseDefinitionSnapshot, parseDefinitionSnapshotValue, parseGdr, parseGuardDocument, parseInstanceDocument, parseInstancePreviewDocument, parseResourceGdr, processShellUserProperties, projectStartSliceRow, projectToWatchRef, quoted2 as quoted, readInstanceDoc, readInstancePreviewDoc, readsRaw, readsRootDocument, refCanvas, refDashboard, refDataset, refKindAcceptsTypes, refMediaLibrary, refsOf, rejectedRefTypes, releaseDocId, releaseRef, remediationsFor, requiredDefinitionReaderModel, requiredModelFeatures, requiredReaderModel, resolveAccess, resolveActor, resolveClientActor, resolveFieldEntry$1 as resolveFieldEntry, resolveUserAttributes, resourceAliasesToMap, resourceFromParsed, resourceGdr, retractStageGuards, sameResource, scalarValidationIssues, schemaTreeShape, sentenceCase, silentLogger, singleSubjectRequirementRefused, stageAutonomyOf, startFieldsParam, startKindOf, startRefusal, stripSystemFields, subjectDenialLabels, subscriptionDocument, subscriptionDocumentsForInstance, sweepStaleClaims, tagScopeFilter, terminalState, toBareId, tryParseGdr, unboundRequirementReads, unsatisfiedTransitionSummaries, userLoginProvider, validateDefinition, validateTag, verdictGuardsForInstance, wallClock, whatIfCondition2 as whatIfCondition, withAssignment, workflow };
15568
+ export { ACTION_SEMANTICS, ACTIVITY_KINDS, ACTIVITY_KIND_DISPLAY, ACTOR_KINDS, ANONYMOUS_IDENTITY, AUTHORING_DISPLAY, ActionDisabledError, ActionParamsInvalidError, CONDITION_VARS, CONTEXT_ENTRY_DISPLAY, CascadeLimitError, ConcurrentCascadeError, ConcurrentCommitEffectOpsError, ConcurrentCompleteEffectError, ConcurrentEditFieldError, ConcurrentFireActionError, ContractViolationError, DATA_MODEL_CHANGES, DATA_MODEL_MAX_READER, DATA_MODEL_MIN_READER, DATA_MODEL_VERSION, DECISION_SEMANTICS, DEFAULT_CONTENT_PERSPECTIVE, DEFAULT_EFFECT_LEASE_MS, DEFAULT_IDEMPOTENCY_TTL_MS, DEFAULT_TRANSITION_WHEN, DISPLAY, DOCUMENT_VALUE_PERMISSIONS, DRIVER_KINDS, DRIVER_KIND_DISPLAY, DefinitionInUseError, DefinitionNotFoundError, EFFECT_COMMIT_DISPATCH_CAP, EFFECT_COMMIT_QUEUE_DEPTH, ENGINE_API_VERSION, EXECUTION_KINDS, EXECUTOR_CLASSIFICATIONS, EXECUTOR_CLASSIFICATION_DISPLAY, EditFieldDeniedError, EffectCommitQueueOverflowError, EffectNotFoundError, EffectOpsInvalidError, EffectOutputsInvalidError, FIELD_KIND_DISPLAY, FILTER_SCOPE_VARS, FieldValueShapeError, GROUP_KINDS, GROUP_KIND_DISPLAY, GUARD_DOC_TYPE, GUARD_OWNER, GUARD_PREDICATE_VARS, HISTORY_DISPLAY, InitialFieldsInvalidError, InstanceNotFoundError, MAX_COUNTERFACTUAL_INDEX2 as MAX_COUNTERFACTUAL_INDEX, MissingHandlerError, ModelVersionAheadError, MutationGuardDeniedError, MutationGuardDocSchema, NEUTRAL_MARK, OP_DISPLAY, OUTCOME_MARKS, PartialGuardDeployError, PersistedDocShapeError, READER_MODEL_ROLLOUT_URL, RESERVED_CONDITION_VARS, ReaderModelAcknowledgementError, RefResourceUndeclaredError, RequiredFieldNotProvidedError, SIGNAL_SEMANTICS, START_FILTER_VARS, START_REQUIREMENT_VARS, SYSTEM_IDENTITY, SpawnContractsInvalidError, StaleEffectClaimError, StartNotAllowedError, StartNotPrimedError, StartNotSettledError, WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, WorkflowActionFired, WorkflowActivityReset, WorkflowDefinitionDeleted, WorkflowDefinitionDeployed, WorkflowEffectCompleted, WorkflowEffectStateReported, WorkflowEffectsDrained, WorkflowError, WorkflowFieldEdited, WorkflowInstanceAborted, WorkflowInstancePreviewSchema, WorkflowInstanceSchema, WorkflowInstanceStarted, WorkflowInstanceTicked, WorkflowStageSet, WorkflowStageTransitioned, WorkflowStateDivergedError, _additionalMissingDocuments, _fieldTargetLabel, _missingDocumentsSummary, _requestProjectMembers, _resolveTelemetryEnvironment, abortReason, acceptsDocumentType, aclPathForResource, actionDisabledDetail, actionRendering, actionVerdict, activeAssignmentMembers, activityAutonomyOf, actorFulfillsRole, actorMatchesAssignment, analyzeCondition2 as analyzeCondition, applicableDefinitions, assertReadableModel, assertReaderModelAcknowledgement, assignmentMatch, assignmentMembers, assignmentPrefilter, assignmentState, assignmentStateCounts, atomReadsDataset2 as atomReadsDataset, autonomySummary, availableActions, buildInitialFields, buildSnapshot, checklistLines, classifyPrincipalId, clientConfigFromResource, clientGuardDereference, clientProjectUserDirectory, compileGuards, computeDiffEntries, conditionFieldReadNames, conditionSitesOf, contentDocQuery, contentDraftFallback, contentReleaseName, contextMap, createEngine, createTelemetryIntake, datasetResourceParts, defaultLoggerFactory, definitionDeployedData, definitionLookupGroq, definitionRoleNames, definitionTagsGroq, definitionsListGroq, deniedGuardLabels, deniedGuardRefs, denyingGuards, deployStageGuards, deployedTagsGroq, deriveActivityKind, deriveExecutorClassification, deriveWorkflowAutonomy, describeAtom, describeAutonomyWait, describeCondition, describeDefinition, describeFieldInsight, describeNode, describeSite, describeSiteHeading, diagnoseInputFromEvaluation, diagnoseInstance, diffEntry, displayDescription, displayTitle, documentActionDenials, documentPrefilter, documentStuckCause, driverKind, effectOutputsMap, effectSites, entryDocRefs, errorMessage, evaluateFromSnapshot, evaluateMutationGuard, evaluateStartFilter, expandResourceAliases, explainCondition2 as explainCondition, explainStartRequirement, extractDocumentId, fieldTreeShape, findActivityNode, findCurrentActivityEntry, findOpenStageEntry, findStageNode, formatRead2 as formatRead, gdrFromResource, gdrRef, gdrUri, groupMembershipNames, groupSitesOf, guardMatches, guardsForDefinition, guardsForInstance, guardsForResource, guillemets2 as guillemets, hasSingleSubjectRequirement, hashDefinitionContent, humanize2 as humanize, identityMatchesAssignment, inFlightFilter, initialFieldIssues, instanceAssignmentStateCounts, instanceChangesQuery, instanceDocId, instanceGuardQuery, instancePreviewsQuery, instanceWatchesDocument, instancesGuardQuery, instancesQuery, isCascadeFired, isClaimExpired, isClientProjectUser, isComparisonOp, isDefinitionApplicable, isFilterScopedOut, isGdr, isInputSourced, isNotesEntry, isProjectUserNotFoundError, isRevisionConflict, isSingleDocRefEntry, isSingleDocRefKind, isStartableDefinition, isSubjectEntry, isTelemetryEnvDenied, isTerminalActivityStatus, isTerminalStage, isTodoListEntry, isTodoListItem, isUnprimed, lakeGuardId, lakePrincipalId, latestDefinitionsGroq, latestDeployedDefinitions, lintEffectOutputs, minReaderModelOf, missingRequiredInputs, modelVersionOf, narrateAutonomyWaits, noopTelemetry, openActivityAssignments, parentRef, parseDefinitionInput, parseDefinitionSnapshot, parseDefinitionSnapshotValue, parseGdr, parseGuardDocument, parseInstanceDocument, parseInstancePreviewDocument, parseResourceGdr, processShellUserProperties, projectStartSliceRow, projectToWatchRef, quoted2 as quoted, readInstanceDoc, readInstancePreviewDoc, readsRaw, readsRootDocument, refCanvas, refDashboard, refDataset, refKindAcceptsTypes, refMediaLibrary, refsOf, rejectedRefTypes, releaseDocId, releaseRef, remediationsFor, requiredDefinitionReaderModel, requiredModelFeatures, requiredReaderModel, resolveAccess, resolveActor, resolveClientActor, resolveFieldEntry$1 as resolveFieldEntry, resolveUserAttributes, resourceAliasesToMap, resourceFromParsed, resourceGdr, retractStageGuards, sameResource, scalarValidationIssues, schemaTreeShape, sentenceCase, silentLogger, singleSubjectRequirementRefused, snapshotGuardDereference, stageAutonomyOf, startFieldsParam, startKindOf, startRefusal, stripSystemFields, subjectDenialLabels, subscriptionDocument, subscriptionDocumentsForInstance, sweepStaleClaims, tagScopeFilter, terminalState, toBareId, tryParseGdr, unboundRequirementReads, unsatisfiedTransitionSummaries, userLoginProvider, validateDefinition, validateTag, verdictGuardsForInstance, wallClock, whatIfCondition2 as whatIfCondition, withAssignment, workflow };