@sanity/workflow-engine 0.8.0 → 0.10.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,5 +1,5 @@
1
1
  import { parse, evaluate } from "groq-js";
2
- import { isTerminalTaskStatus, WORKFLOW_DEFINITION_TYPE, DOCUMENT_VALUE_PERMISSIONS } from "./_chunks-es/schema.js";
2
+ import { actorFulfillsRole, isTerminalTaskStatus, WORKFLOW_DEFINITION_TYPE, andConditions, DOCUMENT_VALUE_PERMISSIONS } from "./_chunks-es/schema.js";
3
3
  import { isStartableDefinition } from "./_chunks-es/schema.js";
4
4
  import * as v from "valibot";
5
5
  function findOpenStageEntry(host) {
@@ -111,7 +111,7 @@ function buildParams(args) {
111
111
  const { instance, now, snapshot, extra } = args, currentTasks2 = findOpenStageEntry(instance)?.tasks ?? [];
112
112
  return {
113
113
  self: selfGdr(instance),
114
- state: renderedState(instance.state ?? [], snapshot),
114
+ fields: renderedFields(instance.fields ?? [], snapshot),
115
115
  parent: instance.ancestors.at(-1)?.id ?? null,
116
116
  ancestors: instance.ancestors.map((a) => a.id),
117
117
  stage: instance.currentStage,
@@ -126,7 +126,7 @@ function buildParams(args) {
126
126
  ...extra
127
127
  };
128
128
  }
129
- function renderedState(entries, snapshot) {
129
+ function renderedFields(entries, snapshot) {
130
130
  const out = {};
131
131
  for (const entry of entries) out[entry.name] = renderedValue(entry, snapshot);
132
132
  return out;
@@ -134,17 +134,17 @@ function renderedState(entries, snapshot) {
134
134
  function renderedValue(entry, snapshot) {
135
135
  return entry._type !== "doc.ref" || entry.value === null || snapshot === void 0 ? entry.value : snapshot.docs.find((d) => d._id === entry.value.id) ?? entry.value;
136
136
  }
137
- function scopedStateOverlay(instance, snapshot, taskName) {
137
+ function scopedFieldOverlay(instance, snapshot, taskName) {
138
138
  const stageEntry = findOpenStageEntry(instance);
139
139
  if (stageEntry === void 0) return {};
140
- const stageState = renderedState(stageEntry.state ?? [], snapshot), task = taskName ? stageEntry.tasks.find((t) => t.name === taskName) : void 0;
141
- return { ...stageState, ...renderedState(task?.state ?? [], snapshot) };
140
+ const stageFields = renderedFields(stageEntry.fields ?? [], snapshot), task = taskName ? stageEntry.tasks.find((t) => t.name === taskName) : void 0;
141
+ return { ...stageFields, ...renderedFields(task?.fields ?? [], snapshot) };
142
142
  }
143
- function assignedFor(instance, taskName, actor) {
143
+ function assignedFor(instance, taskName, actor, roleAliases) {
144
144
  if (actor === void 0) return !1;
145
- const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.state?.find((s) => s._type === "assignees");
145
+ const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.fields?.find((s) => s._type === "assignees");
146
146
  return entry === void 0 ? !1 : entry.value.some(
147
- (a) => a.type === "user" ? a.id === actor.id : (actor.roles ?? []).includes(a.role)
147
+ (a) => a.type === "user" ? a.id === actor.id : actorFulfillsRole(actor.roles, a.role, roleAliases)
148
148
  );
149
149
  }
150
150
  function effectsContextMap(instance) {
@@ -169,17 +169,17 @@ function paramsForLake(params) {
169
169
  self: typeof params.self == "string" ? bareId(params.self) : params.self,
170
170
  parent: typeof params.parent == "string" ? bareId(params.parent) : params.parent,
171
171
  ancestors: Array.isArray(params.ancestors) ? params.ancestors.map((a) => typeof a == "string" ? bareId(a) : a) : params.ancestors,
172
- state: stripStateForLake(params.state)
172
+ fields: stripFieldsForLake(params.fields)
173
173
  };
174
174
  }
175
- function stripStateForLake(value) {
175
+ function stripFieldsForLake(value) {
176
176
  if (value == null) return value;
177
177
  if (typeof value == "string") return bareId(value);
178
- if (Array.isArray(value)) return value.map(stripStateForLake);
178
+ if (Array.isArray(value)) return value.map(stripFieldsForLake);
179
179
  if (typeof value == "object") {
180
180
  const out = {};
181
181
  for (const [k, v2] of Object.entries(value))
182
- out[k] = stripStateForLake(v2);
182
+ out[k] = stripFieldsForLake(v2);
183
183
  return out;
184
184
  }
185
185
  return value;
@@ -198,17 +198,17 @@ function getPath(value, path) {
198
198
  function resourceOf(p) {
199
199
  return p.scheme === "dataset" ? { type: "dataset", id: `${p.projectId}.${p.dataset}` } : { type: p.scheme, id: p.resourceId ?? "" };
200
200
  }
201
- const STATE_READ = /^\$state\.(\w+)(?:\.(.+))?$/, EFFECTS_READ = /^\$effects\['([^']+)'\](?:\.(.+))?$/;
201
+ const FIELD_READ = /^\$fields\.(\w+)(?:\.(.+))?$/, EFFECTS_READ = /^\$effects\['([^']+)'\](?:\.(.+))?$/;
202
202
  function isGuardReadExpr(expr) {
203
- return expr === "$self" || expr === "$now" || STATE_READ.test(expr) || EFFECTS_READ.test(expr);
203
+ return expr === "$self" || expr === "$now" || FIELD_READ.test(expr) || EFFECTS_READ.test(expr);
204
204
  }
205
205
  function resolveGuardRead(expr, ctx) {
206
206
  if (expr === "$self") return selfGdr(ctx.instance);
207
207
  if (expr === "$now") return ctx.now;
208
- const stateRead = STATE_READ.exec(expr);
209
- if (stateRead !== null) {
210
- const value = (ctx.instance.state ?? []).find((s) => s.name === stateRead[1])?.value;
211
- return stateRead[2] !== void 0 ? getPath(value, stateRead[2]) : value;
208
+ const fieldRead = FIELD_READ.exec(expr);
209
+ if (fieldRead !== null) {
210
+ const value = (ctx.instance.fields ?? []).find((s) => s.name === fieldRead[1])?.value;
211
+ return fieldRead[2] !== void 0 ? getPath(value, fieldRead[2]) : value;
212
212
  }
213
213
  const effectsRead = EFFECTS_READ.exec(expr);
214
214
  if (effectsRead !== null) {
@@ -216,7 +216,7 @@ function resolveGuardRead(expr, ctx) {
216
216
  return effectsRead[2] !== void 0 ? getPath(outputs, effectsRead[2]) : outputs;
217
217
  }
218
218
  throw new Error(
219
- `Guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$state.<name>[.path]", or "$effects['<name>'][.path]" (guards store resolved values; the lake cannot see $state or $effects)`
219
+ `Guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$fields.<name>[.path]", or "$effects['<name>'][.path]" (guards store resolved values; the lake cannot see $fields or $effects)`
220
220
  );
221
221
  }
222
222
  function resolveIdRefTarget(expr, ctx) {
@@ -266,8 +266,8 @@ function resolveMetadata(metadata, ctx) {
266
266
  }
267
267
  function validateDefinition(definition) {
268
268
  const v2 = createDefinitionValidator();
269
- for (const entry of definition.state ?? [])
270
- v2.checkEntry(entry, `workflow.state "${entry.name}"`);
269
+ for (const entry of definition.fields ?? [])
270
+ v2.checkEntry(entry, `workflow.fields "${entry.name}"`);
271
271
  for (const [name, groq] of Object.entries(definition.predicates ?? {}))
272
272
  v2.checkCondition(groq, `predicate "${name}"`);
273
273
  for (const stage of definition.stages)
@@ -289,7 +289,7 @@ function createDefinitionValidator() {
289
289
  }
290
290
  }, rejectTypeScan = (groq, where) => {
291
291
  /\*\s*\[\s*_type\b/.test(groq) && errors.push(
292
- ` \xB7 ${where}: condition scans by \`_type\` \u2014 that's a discovery query, not a predicate. Conditions evaluate against the in-memory snapshot (instance + ancestors + state-declared docs). To bring extra docs in scope, declare a \`doc.ref\` (or \`doc.refs\`) state entry on the workflow or this stage. For lake scans like "all articles in this release", use \`subworkflows.forEach\` instead.`
292
+ ` \xB7 ${where}: condition scans by \`_type\` \u2014 that's a discovery query, not a predicate. Conditions evaluate against the in-memory snapshot (instance + ancestors + field-declared docs). To bring extra docs in scope, declare a \`doc.ref\` (or \`doc.refs\`) field entry on the workflow or this stage. For lake scans like "all articles in this release", use \`subworkflows.forEach\` instead.`
293
293
  );
294
294
  };
295
295
  return { errors, tryParse, checkCondition: (groq, where) => {
@@ -298,13 +298,13 @@ function createDefinitionValidator() {
298
298
  entry.source.type === "query" && tryParse(entry.source.query, `${label}.query`);
299
299
  }, checkGuardRead: (expr, where) => {
300
300
  isGuardReadExpr(expr) || errors.push(
301
- ` \xB7 ${where}: guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$state.<name>[.path]", or "$effects['<name>'][.path]" (guards store resolved values; the lake cannot see $state or $effects)`
301
+ ` \xB7 ${where}: guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$fields.<name>[.path]", or "$effects['<name>'][.path]" (guards store resolved values; the lake cannot see $fields or $effects)`
302
302
  );
303
303
  } };
304
304
  }
305
305
  function validateStage(v2, stage) {
306
- for (const entry of stage.state ?? [])
307
- v2.checkEntry(entry, `stage "${stage.name}".state "${entry.name}"`);
306
+ for (const entry of stage.fields ?? [])
307
+ v2.checkEntry(entry, `stage "${stage.name}".fields "${entry.name}"`);
308
308
  for (const guard of stage.guards ?? [])
309
309
  validateGuardReads(v2, stage.name, guard);
310
310
  for (const t of stage.transitions ?? []) {
@@ -324,8 +324,8 @@ function validateGuardReads(v2, stageName, guard) {
324
324
  }
325
325
  function validateTask(v2, stageName, task) {
326
326
  const where = `stage "${stageName}" task "${task.name}"`;
327
- for (const entry of task.state ?? [])
328
- v2.checkEntry(entry, `${where}.state "${entry.name}"`);
327
+ for (const entry of task.fields ?? [])
328
+ v2.checkEntry(entry, `${where}.fields "${entry.name}"`);
329
329
  task.filter !== void 0 && v2.checkCondition(task.filter, `${where}.filter`), task.completeWhen !== void 0 && v2.checkCondition(task.completeWhen, `${where}.completeWhen`), task.failWhen !== void 0 && v2.checkCondition(task.failWhen, `${where}.failWhen`);
330
330
  for (const [name, groq] of Object.entries(task.requirements ?? {}))
331
331
  v2.checkCondition(groq, `${where}.requirements "${name}"`);
@@ -497,7 +497,7 @@ function openStage(instance) {
497
497
  return findOpenStageEntry(instance);
498
498
  }
499
499
  function assigneesOf(stage, taskName) {
500
- return (stage?.tasks.find((t) => t.name === taskName)?.state ?? []).filter((s) => s._type === "assignees").flatMap((s) => s.value);
500
+ return (stage?.tasks.find((t) => t.name === taskName)?.fields ?? []).filter((s) => s._type === "assignees").flatMap((s) => s.value);
501
501
  }
502
502
  function isResolved(status) {
503
503
  return status === "done" || status === "skipped";
@@ -610,17 +610,17 @@ class ActionParamsInvalidError extends Error {
610
610
  ${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.task = args.task, this.issues = args.issues;
611
611
  }
612
612
  }
613
- class RequiredStateNotProvidedError extends Error {
613
+ class RequiredFieldNotProvidedError extends Error {
614
614
  definition;
615
615
  missing;
616
616
  constructor(args) {
617
617
  const lines = args.missing.map((m) => ` - ${m.name} (${m.type})`).join(`
618
618
  `), where = args.definition !== void 0 ? ` for workflow "${args.definition}"` : "";
619
619
  super(
620
- `Required init state${where} was not provided:
620
+ `Required init fields${where} was not provided:
621
621
  ${lines}
622
- Provide each via \`initialState\` when starting standalone, or via the parent's \`subworkflows.with\` when spawned.`
623
- ), this.name = "RequiredStateNotProvidedError", args.definition !== void 0 && (this.definition = args.definition), this.missing = args.missing;
622
+ Provide each via \`initialFields\` when starting standalone, or via the parent's \`subworkflows.with\` when spawned.`
623
+ ), this.name = "RequiredFieldNotProvidedError", args.definition !== void 0 && (this.definition = args.definition), this.missing = args.missing;
624
624
  }
625
625
  }
626
626
  class WorkflowStateDivergedError extends Error {
@@ -663,6 +663,17 @@ function isRevisionConflict(error) {
663
663
  const { statusCode, message } = error;
664
664
  return statusCode === 409 ? !0 : typeof message == "string" && message.includes("ifRevisionId check failed");
665
665
  }
666
+ class ConcurrentEditFieldError extends Error {
667
+ instanceId;
668
+ target;
669
+ attempts;
670
+ constructor(args) {
671
+ const where = args.target.task !== void 0 ? `${args.target.task}.${args.target.field}` : args.target.field;
672
+ super(
673
+ `Edit of "${args.target.scope}:${where}" (instance ${args.instanceId}) lost the optimistic-locking race ${args.attempts} attempts running \u2014 a concurrent writer kept committing first. Re-read and retry, or investigate a write storm on this instance.`
674
+ ), this.name = "ConcurrentEditFieldError", this.instanceId = args.instanceId, this.target = args.target, this.attempts = args.attempts;
675
+ }
676
+ }
666
677
  class CascadeLimitError extends Error {
667
678
  instanceId;
668
679
  limit;
@@ -708,10 +719,10 @@ function collectWatchRefs(instance) {
708
719
  return [
709
720
  gdrRef(instance.workflowResource, instance._id, instance._type),
710
721
  ...instance.ancestors,
711
- ...entryDocRefs(instance.state),
712
- ...entryDocRefs(stage?.state),
713
- ...entryReleaseRefs(instance.state),
714
- ...entryReleaseRefs(stage?.state)
722
+ ...entryDocRefs(instance.fields),
723
+ ...entryDocRefs(stage?.fields),
724
+ ...entryReleaseRefs(instance.fields),
725
+ ...entryReleaseRefs(stage?.fields)
715
726
  ];
716
727
  }
717
728
  function readsRaw(ref) {
@@ -735,21 +746,21 @@ function subscriptionDocumentsForInstance(instance) {
735
746
  ...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
736
747
  };
737
748
  }
738
- function entryDocRefs(state) {
739
- return stateEntries(state).flatMap((entry) => entry._type === "doc.ref" ? isGdr(entry.value) ? [entry.value] : [] : entry._type === "doc.refs" ? Array.isArray(entry.value) ? entry.value.filter(isGdr) : [] : []);
749
+ function entryDocRefs(entries) {
750
+ return fieldEntries(entries).flatMap((entry) => entry._type === "doc.ref" ? isGdr(entry.value) ? [entry.value] : [] : entry._type === "doc.refs" ? Array.isArray(entry.value) ? entry.value.filter(isGdr) : [] : []);
740
751
  }
741
- function entryReleaseRefs(state) {
742
- return stateEntries(state).flatMap(
752
+ function entryReleaseRefs(entries) {
753
+ return fieldEntries(entries).flatMap(
743
754
  (entry) => entry._type === "release.ref" && isGdr(entry.value) ? [entry.value] : []
744
755
  );
745
756
  }
746
- function entrySubjectRefs(state) {
747
- return stateEntries(state).flatMap(
757
+ function entrySubjectRefs(entries) {
758
+ return fieldEntries(entries).flatMap(
748
759
  (entry) => (entry._type === "doc.ref" || entry._type === "release.ref") && isGdr(entry.value) ? [entry.value] : []
749
760
  );
750
761
  }
751
- function stateEntries(state) {
752
- return Array.isArray(state) ? state.filter(
762
+ function fieldEntries(entries) {
763
+ return Array.isArray(entries) ? entries.filter(
753
764
  (entry) => !!entry && typeof entry == "object"
754
765
  ) : [];
755
766
  }
@@ -795,8 +806,8 @@ async function readDoc(client, id, perspective) {
795
806
  function resourceFromParsed(parsed) {
796
807
  return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
797
808
  }
798
- function collectEntryDocUris(resolvedStateEntries) {
799
- return entryDocRefs(resolvedStateEntries).map((ref) => ref.id);
809
+ function collectEntryDocUris(resolvedFieldEntries) {
810
+ return entryDocRefs(resolvedFieldEntries).map((ref) => ref.id);
800
811
  }
801
812
  const resourceKey = (r) => `${r.type}.${r.id}`;
802
813
  function guardsForResource(client) {
@@ -814,8 +825,8 @@ function verdictGuardsForInstance(client, instanceId) {
814
825
  }
815
826
  async function guardsForInstance(args) {
816
827
  const { client, clientForGdr, instance } = args, stateUris = [
817
- ...collectEntryDocUris(instance.state),
818
- ...instance.stages.flatMap((stage) => collectEntryDocUris(stage.state))
828
+ ...collectEntryDocUris(instance.fields),
829
+ ...instance.stages.flatMap((stage) => collectEntryDocUris(stage.fields))
819
830
  ], clients = resourceClientMap(
820
831
  instance.workflowResource,
821
832
  client,
@@ -847,16 +858,16 @@ function guardIdRefs(definition) {
847
858
  );
848
859
  }
849
860
  function staticIdRefGdr(idRef, stage, definition) {
850
- const read = /^\$state\.([\w-]+)/.exec(idRef);
861
+ const read = /^\$fields\.([\w-]+)/.exec(idRef);
851
862
  if (read === null) return;
852
863
  const source = entriesInScope(stage, definition).find((e) => e.name === read[1])?.source;
853
864
  return source?.type === "literal" ? gdrFromValue(source.value) : void 0;
854
865
  }
855
866
  function entriesInScope(stage, definition) {
856
867
  return [
857
- ...definition.state ?? [],
858
- ...stage.state ?? [],
859
- ...(stage.tasks ?? []).flatMap((task) => task.state ?? [])
868
+ ...definition.fields ?? [],
869
+ ...stage.fields ?? [],
870
+ ...(stage.tasks ?? []).flatMap((task) => task.fields ?? [])
860
871
  ];
861
872
  }
862
873
  function gdrFromValue(value) {
@@ -1006,15 +1017,15 @@ async function runGroq(groq, params, snapshot) {
1006
1017
  const tree = parse(groq, { params });
1007
1018
  return (await evaluate(tree, { dataset: snapshot.docs, params })).get();
1008
1019
  }
1009
- class StateValueShapeError extends Error {
1020
+ class FieldValueShapeError extends Error {
1010
1021
  entryType;
1011
1022
  entryName;
1012
1023
  issues;
1013
1024
  constructor(args) {
1014
1025
  const issueText = args.issues.join("; ");
1015
1026
  super(
1016
- `State entry ${args.mode} shape invalid for "${args.entryName}" (${args.entryType}): ${issueText}`
1017
- ), this.name = "StateValueShapeError", this.entryType = args.entryType, this.entryName = args.entryName, this.issues = args.issues;
1027
+ `Field entry ${args.mode} shape invalid for "${args.entryName}" (${args.entryType}): ${issueText}`
1028
+ ), this.name = "FieldValueShapeError", this.entryType = args.entryType, this.entryName = args.entryName, this.issues = args.issues;
1018
1029
  }
1019
1030
  }
1020
1031
  const GdrShape = v.looseObject({
@@ -1081,35 +1092,35 @@ const GdrShape = v.looseObject({
1081
1092
  function isAppendable(entryType) {
1082
1093
  return entryType in itemSchemas;
1083
1094
  }
1084
- function validateStateValue(args) {
1095
+ function validateFieldValue(args) {
1085
1096
  const schema = valueSchemas[args.entryType];
1086
1097
  if (schema === void 0)
1087
- throw new StateValueShapeError({
1098
+ throw new FieldValueShapeError({
1088
1099
  entryType: args.entryType,
1089
1100
  entryName: args.entryName,
1090
- issues: [`unknown state entry type ${args.entryType}`],
1101
+ issues: [`unknown field entry type ${args.entryType}`],
1091
1102
  mode: "value"
1092
1103
  });
1093
1104
  const result = v.safeParse(schema, args.value);
1094
1105
  if (!result.success)
1095
- throw new StateValueShapeError({
1106
+ throw new FieldValueShapeError({
1096
1107
  entryType: args.entryType,
1097
1108
  entryName: args.entryName,
1098
1109
  issues: formatIssues(result.issues),
1099
1110
  mode: "value"
1100
1111
  });
1101
1112
  }
1102
- function validateStateAppendItem(args) {
1113
+ function validateFieldAppendItem(args) {
1103
1114
  if (!isAppendable(args.entryType))
1104
- throw new StateValueShapeError({
1115
+ throw new FieldValueShapeError({
1105
1116
  entryType: args.entryType,
1106
1117
  entryName: args.entryName,
1107
- issues: [`state entry type ${args.entryType} does not support append`],
1118
+ issues: [`field entry type ${args.entryType} does not support append`],
1108
1119
  mode: "item"
1109
1120
  });
1110
1121
  const schema = itemSchemas[args.entryType], result = v.safeParse(schema, args.item);
1111
1122
  if (!result.success)
1112
- throw new StateValueShapeError({
1123
+ throw new FieldValueShapeError({
1113
1124
  entryType: args.entryType,
1114
1125
  entryName: args.entryName,
1115
1126
  issues: formatIssues(result.issues),
@@ -1122,7 +1133,7 @@ function formatIssues(issues) {
1122
1133
  return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${i.message}`;
1123
1134
  });
1124
1135
  }
1125
- function derivePerspectiveFromState(entries) {
1136
+ function derivePerspectiveFromFields(entries) {
1126
1137
  if (entries !== void 0) {
1127
1138
  for (const entry of entries)
1128
1139
  if (entry._type === "release.ref" && entry.value !== null) {
@@ -1132,24 +1143,24 @@ function derivePerspectiveFromState(entries) {
1132
1143
  }
1133
1144
  }
1134
1145
  }
1135
- async function resolveDeclaredState(args) {
1136
- const { entryDefs, initialState, ctx, randomKey: randomKey2 } = args;
1146
+ async function resolveDeclaredFields(args) {
1147
+ const { entryDefs, initialFields, ctx, randomKey: randomKey2 } = args;
1137
1148
  if (entryDefs === void 0 || entryDefs.length === 0) return [];
1138
- assertRequiredInitProvided(entryDefs, initialState, ctx.definitionName);
1149
+ assertRequiredInitProvided(entryDefs, initialFields, ctx.definitionName);
1139
1150
  const out = [];
1140
1151
  for (const entry of entryDefs) {
1141
- const scopedCtx = { ...ctx, resolvedState: out };
1142
- out.push(await resolveOneEntry(entry, initialState, scopedCtx, randomKey2));
1152
+ const scopedCtx = { ...ctx, resolvedFields: out };
1153
+ out.push(await resolveOneEntry(entry, initialFields, scopedCtx, randomKey2));
1143
1154
  }
1144
1155
  return out;
1145
1156
  }
1146
- function assertRequiredInitProvided(entryDefs, initialState, definitionName) {
1157
+ function assertRequiredInitProvided(entryDefs, initialFields, definitionName) {
1147
1158
  const missing = entryDefs.filter((entry) => entry.required === !0 && entry.source.type === "init").filter((entry) => {
1148
- const match = initialState.find((s) => s.name === entry.name && s.type === entry.type);
1159
+ const match = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
1149
1160
  return match === void 0 || match.value === void 0 || match.value === null;
1150
1161
  }).map((entry) => ({ name: entry.name, type: entry.type }));
1151
1162
  if (missing.length !== 0)
1152
- throw new RequiredStateNotProvidedError({
1163
+ throw new RequiredFieldNotProvidedError({
1153
1164
  ...definitionName !== void 0 ? { definition: definitionName } : {},
1154
1165
  missing
1155
1166
  });
@@ -1163,18 +1174,18 @@ const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set([
1163
1174
  function defaultEntryValue(entryType) {
1164
1175
  return ARRAY_SLOT_TYPES.has(entryType) ? [] : null;
1165
1176
  }
1166
- function resolveInitValue(entry, initialState, defaultValue) {
1167
- const initMatch = initialState.find((s) => s.name === entry.name && s.type === entry.type);
1177
+ function resolveInitValue(entry, initialFields, defaultValue) {
1178
+ const initMatch = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
1168
1179
  return initMatch === void 0 ? defaultValue : (assertInitValueShape(entry, initMatch.value), initMatch.value);
1169
1180
  }
1170
- function resolveStateReadValue(source, ctx, defaultValue) {
1171
- const target = (source.scope === "workflow" ? ctx.workflowState ?? [] : ctx.resolvedState ?? []).find((s) => s.name === source.state), targetValue = target === void 0 ? void 0 : target.value;
1181
+ function resolveFieldReadValue(source, ctx, defaultValue) {
1182
+ const target = (source.scope === "workflow" ? ctx.workflowFields ?? [] : ctx.resolvedFields ?? []).find((s) => s.name === source.field), targetValue = target === void 0 ? void 0 : target.value;
1172
1183
  return (source.path !== void 0 ? getPath(targetValue, source.path) : targetValue) ?? defaultValue;
1173
1184
  }
1174
1185
  async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
1175
1186
  const params = paramsForLake({
1176
1187
  self: ctx.selfId ?? null,
1177
- state: stateMapFromResolved(ctx.resolvedState ?? []),
1188
+ fields: fieldMapFromResolved(ctx.resolvedFields ?? []),
1178
1189
  stage: ctx.stageName ?? null,
1179
1190
  task: ctx.taskName ?? null,
1180
1191
  now: ctx.now,
@@ -1190,9 +1201,9 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
1190
1201
  );
1191
1202
  }
1192
1203
  }
1193
- async function resolveEntryValue(entry, initialState, ctx, defaultValue) {
1204
+ async function resolveEntryValue(entry, initialFields, ctx, defaultValue) {
1194
1205
  const source = entry.source;
1195
- return source.type === "init" ? resolveInitValue(entry, initialState, defaultValue) : source.type === "literal" ? source.value ?? defaultValue : source.type === "stateRead" ? resolveStateReadValue(source, ctx, defaultValue) : source.type === "query" && ctx.client !== void 0 ? resolveQueryValue(entry, source, ctx, ctx.client, defaultValue) : defaultValue;
1206
+ return source.type === "init" ? resolveInitValue(entry, initialFields, defaultValue) : source.type === "literal" ? source.value ?? defaultValue : source.type === "fieldRead" ? resolveFieldReadValue(source, ctx, defaultValue) : source.type === "query" && ctx.client !== void 0 ? resolveQueryValue(entry, source, ctx, ctx.client, defaultValue) : defaultValue;
1196
1207
  }
1197
1208
  function buildResolvedEntry(entry, value, _key, now) {
1198
1209
  const titleProp = entry.title !== void 0 ? { title: entry.title } : {}, descriptionProp = entry.description !== void 0 ? { description: entry.description } : {};
@@ -1213,36 +1224,36 @@ function buildResolvedEntry(entry, value, _key, now) {
1213
1224
  value
1214
1225
  };
1215
1226
  }
1216
- async function resolveOneEntry(entry, initialState, ctx, randomKey2) {
1217
- const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue(entry, initialState, ctx, defaultValue);
1218
- return validateStateValue({ entryType: entry.type, entryName: entry.name, value }), buildResolvedEntry(entry, value, randomKey2(), ctx.now);
1227
+ async function resolveOneEntry(entry, initialFields, ctx, randomKey2) {
1228
+ const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue(entry, initialFields, ctx, defaultValue);
1229
+ return validateFieldValue({ entryType: entry.type, entryName: entry.name, value }), buildResolvedEntry(entry, value, randomKey2(), ctx.now);
1219
1230
  }
1220
- function stateMapFromResolved(entries) {
1231
+ function fieldMapFromResolved(entries) {
1221
1232
  const out = {};
1222
1233
  for (const s of entries) out[s.name] = s.value;
1223
1234
  return out;
1224
1235
  }
1225
1236
  function assertInitValueShape(entry, value) {
1226
1237
  if (entry.type === "doc.ref") {
1227
- assertGdrShape(value, `state entry "${entry.name}" (doc.ref)`);
1238
+ assertGdrShape(value, `field entry "${entry.name}" (doc.ref)`);
1228
1239
  return;
1229
1240
  }
1230
1241
  if (entry.type === "release.ref") {
1231
- assertGdrShape(value, `state entry "${entry.name}" (release.ref)`);
1242
+ assertGdrShape(value, `field entry "${entry.name}" (release.ref)`);
1232
1243
  const v2 = value;
1233
1244
  if (typeof v2.releaseName != "string" || v2.releaseName.length === 0)
1234
1245
  throw new Error(
1235
- `Invalid init value for state entry "${entry.name}" (release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(v2.releaseName)}.`
1246
+ `Invalid init value for field entry "${entry.name}" (release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(v2.releaseName)}.`
1236
1247
  );
1237
1248
  return;
1238
1249
  }
1239
1250
  if (entry.type === "doc.refs") {
1240
1251
  if (!Array.isArray(value))
1241
1252
  throw new Error(
1242
- `Invalid init value for state entry "${entry.name}" (doc.refs): expected an array of GDRs, got ${typeof value}.`
1253
+ `Invalid init value for field entry "${entry.name}" (doc.refs): expected an array of GDRs, got ${typeof value}.`
1243
1254
  );
1244
1255
  for (const [i, item] of value.entries())
1245
- assertGdrShape(item, `state entry "${entry.name}" (doc.refs) item [${i}]`);
1256
+ assertGdrShape(item, `field entry "${entry.name}" (doc.refs) item [${i}]`);
1246
1257
  }
1247
1258
  }
1248
1259
  function assertGdrShape(value, context) {
@@ -1282,6 +1293,12 @@ function coerceToGdr(raw, workflowResource) {
1282
1293
  function gdrToBareId(uri) {
1283
1294
  return uri.includes(":") ? extractDocumentId(uri) : uri;
1284
1295
  }
1296
+ function loadCallContext(client, instanceId, options) {
1297
+ return loadContext(client, instanceId, {
1298
+ ...options?.clock ? { clock: options.clock } : {},
1299
+ ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
1300
+ });
1301
+ }
1285
1302
  async function loadContext(client, instanceId, options) {
1286
1303
  const instance = await client.getDocument(instanceId);
1287
1304
  if (!instance)
@@ -1295,14 +1312,14 @@ async function loadContext(client, instanceId, options) {
1295
1312
  return { client, clientForGdr, clock, now: clock(), instance, definition, snapshot };
1296
1313
  }
1297
1314
  async function ctxConditionParams(ctx, opts) {
1298
- const base = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), state = {
1299
- ...base.state,
1300
- ...scopedStateOverlay(ctx.instance, ctx.snapshot, opts?.taskName)
1315
+ const base = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), fields = {
1316
+ ...base.fields,
1317
+ ...scopedFieldOverlay(ctx.instance, ctx.snapshot, opts?.taskName)
1301
1318
  }, params = {
1302
1319
  ...base,
1303
- state,
1320
+ fields,
1304
1321
  actor: opts?.actor,
1305
- assigned: opts?.taskName !== void 0 ? assignedFor(ctx.instance, opts.taskName, opts?.actor) : !1,
1322
+ assigned: opts?.taskName !== void 0 ? assignedFor(ctx.instance, opts.taskName, opts?.actor, ctx.definition.roleAliases) : !1,
1306
1323
  ...opts?.vars
1307
1324
  };
1308
1325
  return { ...await evaluatePredicates({
@@ -1339,11 +1356,11 @@ function findStage(definition, stageName) {
1339
1356
  throw new Error(`Stage "${stageName}" not found in definition ${definition.name}`);
1340
1357
  return stage;
1341
1358
  }
1342
- async function resolveStageStateEntries(args) {
1343
- const { client, instance, stage, now, initialState } = args;
1344
- return resolveDeclaredState({
1345
- entryDefs: stage.state,
1346
- initialState: initialState ?? [],
1359
+ async function resolveStageFieldEntries(args) {
1360
+ const { client, instance, stage, now, initialFields } = args;
1361
+ return resolveDeclaredFields({
1362
+ entryDefs: stage.fields,
1363
+ initialFields: initialFields ?? [],
1347
1364
  ctx: {
1348
1365
  client,
1349
1366
  now,
@@ -1351,19 +1368,19 @@ async function resolveStageStateEntries(args) {
1351
1368
  tag: instance.tag,
1352
1369
  stageName: stage.name,
1353
1370
  workflowResource: instance.workflowResource,
1354
- // Allow stage-scope entries' `source: { type: "stateRead", scope:
1355
- // "workflow", ... }` to see the already-resolved workflow-scope state.
1356
- workflowState: instance.state ?? [],
1371
+ // Allow stage-scope entries' `source: { type: "fieldRead", scope:
1372
+ // "workflow", ... }` to see the already-resolved workflow-scope fields.
1373
+ workflowFields: instance.fields ?? [],
1357
1374
  ...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
1358
1375
  },
1359
1376
  randomKey
1360
1377
  });
1361
1378
  }
1362
- async function resolveTaskStateEntries(args) {
1379
+ async function resolveTaskFieldEntries(args) {
1363
1380
  const { client, instance, stage, task, now } = args;
1364
- return resolveDeclaredState({
1365
- entryDefs: task.state,
1366
- initialState: [],
1381
+ return resolveDeclaredFields({
1382
+ entryDefs: task.fields,
1383
+ initialFields: [],
1367
1384
  ctx: {
1368
1385
  client,
1369
1386
  now,
@@ -1372,7 +1389,7 @@ async function resolveTaskStateEntries(args) {
1372
1389
  stageName: stage.name,
1373
1390
  workflowResource: instance.workflowResource,
1374
1391
  taskName: task.name,
1375
- workflowState: instance.state ?? [],
1392
+ workflowFields: instance.fields ?? [],
1376
1393
  ...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
1377
1394
  },
1378
1395
  randomKey
@@ -1454,15 +1471,15 @@ function stageTransitionHistory(args) {
1454
1471
  function startMutation(instance) {
1455
1472
  return {
1456
1473
  currentStage: instance.currentStage,
1457
- // Deep-ish copy. Per-scope state entry arrays need their own copies
1474
+ // Deep-ish copy. Per-scope field entry arrays need their own copies
1458
1475
  // so ops can mutate without leaking into the source.
1459
- state: (instance.state ?? []).map((s) => ({ ...s })),
1476
+ fields: (instance.fields ?? []).map((s) => ({ ...s })),
1460
1477
  stages: instance.stages.map((s) => ({
1461
1478
  ...s,
1462
- state: (s.state ?? []).map((entry) => ({ ...entry })),
1479
+ fields: (s.fields ?? []).map((entry) => ({ ...entry })),
1463
1480
  tasks: s.tasks.map((t) => ({
1464
1481
  ...t,
1465
- ...t.state !== void 0 ? { state: t.state.map((entry) => ({ ...entry })) } : {}
1482
+ ...t.fields !== void 0 ? { fields: t.fields.map((entry) => ({ ...entry })) } : {}
1466
1483
  }))
1467
1484
  })),
1468
1485
  pendingEffects: [...instance.pendingEffects],
@@ -1478,7 +1495,7 @@ function startMutation(instance) {
1478
1495
  function instanceStateFields(src) {
1479
1496
  const fields = {
1480
1497
  currentStage: src.currentStage,
1481
- state: src.state,
1498
+ fields: src.fields,
1482
1499
  stages: src.stages,
1483
1500
  pendingEffects: src.pendingEffects,
1484
1501
  effectHistory: src.effectHistory,
@@ -1491,7 +1508,7 @@ function materializeInstance(base, mutation) {
1491
1508
  return {
1492
1509
  ...base,
1493
1510
  currentStage: mutation.currentStage,
1494
- state: mutation.state,
1511
+ fields: mutation.fields,
1495
1512
  stages: mutation.stages,
1496
1513
  effectsContext: mutation.effectsContext
1497
1514
  };
@@ -1613,7 +1630,7 @@ function buildInstanceBase(args) {
1613
1630
  definition: args.definitionName,
1614
1631
  pinnedVersion: args.pinnedVersion,
1615
1632
  definitionSnapshot: JSON.stringify(args.definition),
1616
- state: args.state,
1633
+ fields: args.fields,
1617
1634
  effectsContext: args.effectsContext,
1618
1635
  ancestors: args.ancestors,
1619
1636
  ...perspective !== void 0 ? { perspective } : {},
@@ -1671,7 +1688,7 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
1671
1688
  ...actor ? { actor } : {}
1672
1689
  }), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
1673
1690
  for (const row of rows) {
1674
- const initialState = await projectRowState(
1691
+ const initialFields = await projectRowFields(
1675
1692
  ctx,
1676
1693
  sub,
1677
1694
  definition,
@@ -1682,7 +1699,7 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
1682
1699
  client: ctx.client,
1683
1700
  parent: ctx.instance,
1684
1701
  definition,
1685
- initialState,
1702
+ initialFields,
1686
1703
  effectsContext,
1687
1704
  actor,
1688
1705
  now
@@ -1695,7 +1712,7 @@ async function resolveSpawnRows(ctx, sub) {
1695
1712
  const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
1696
1713
  let value, discoveryResource;
1697
1714
  if (groq.includes("*")) {
1698
- const routingUri = entrySubjectRefs(ctx.instance.state)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
1715
+ const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
1699
1716
  client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
1700
1717
  client,
1701
1718
  groq,
@@ -1711,13 +1728,13 @@ async function resolveSpawnRows(ctx, sub) {
1711
1728
  }
1712
1729
  return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
1713
1730
  }
1714
- async function projectRowState(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
1731
+ async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
1715
1732
  const out = [];
1716
1733
  for (const [name, groq] of Object.entries(sub.with ?? {})) {
1717
- const declared = (childDefinition.state ?? []).find((e) => e.name === name);
1734
+ const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
1718
1735
  if (declared === void 0)
1719
1736
  throw new Error(
1720
- `subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no state entry "${name}"`
1737
+ `subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
1721
1738
  );
1722
1739
  const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
1723
1740
  parentResource: ctx.instance.workflowResource,
@@ -1772,15 +1789,15 @@ async function resolveDefinitionRef(client, ref, tag) {
1772
1789
  ) ?? null;
1773
1790
  }
1774
1791
  async function prepareChildInstance(args) {
1775
- const { client, parent, definition, initialState, effectsContext, actor, now } = args, childTag = parent.tag, workflowResource = parent.workflowResource, childDocId = `${childTag}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type: WORKFLOW_INSTANCE_TYPE }, ancestors = [
1792
+ const { client, parent, definition, initialFields, effectsContext, actor, now } = args, childTag = parent.tag, workflowResource = parent.workflowResource, childDocId = `${childTag}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type: WORKFLOW_INSTANCE_TYPE }, ancestors = [
1776
1793
  ...parent.ancestors,
1777
1794
  {
1778
1795
  id: selfGdr(parent),
1779
1796
  type: WORKFLOW_INSTANCE_TYPE
1780
1797
  }
1781
- ], inheritedPerspective = parent.perspective, childState = await resolveDeclaredState({
1782
- entryDefs: definition.state,
1783
- initialState,
1798
+ ], inheritedPerspective = parent.perspective, childFields = await resolveDeclaredFields({
1799
+ entryDefs: definition.fields,
1800
+ initialFields,
1784
1801
  ctx: {
1785
1802
  client,
1786
1803
  now,
@@ -1791,7 +1808,7 @@ async function prepareChildInstance(args) {
1791
1808
  ...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
1792
1809
  },
1793
1810
  randomKey
1794
- }), childPerspective = derivePerspectiveFromState(childState) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
1811
+ }), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
1795
1812
  ([key, value]) => {
1796
1813
  if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
1797
1814
  if (!isGdrUri(value.id))
@@ -1810,7 +1827,7 @@ async function prepareChildInstance(args) {
1810
1827
  definitionName: definition.name,
1811
1828
  pinnedVersion: definition.version,
1812
1829
  definition,
1813
- state: childState,
1830
+ fields: childFields,
1814
1831
  effectsContext: effectsContextEntries,
1815
1832
  ancestors,
1816
1833
  perspective: childPerspective,
@@ -1975,15 +1992,15 @@ function resolveStaticSource(src, ctx) {
1975
1992
  return { handled: !1 };
1976
1993
  }
1977
1994
  }
1978
- const STATE_OP_TYPES = [
1979
- "state.set",
1980
- "state.unset",
1981
- "state.append",
1982
- "state.updateWhere",
1983
- "state.removeWhere"
1995
+ const FIELD_OP_TYPES = [
1996
+ "field.set",
1997
+ "field.unset",
1998
+ "field.append",
1999
+ "field.updateWhere",
2000
+ "field.removeWhere"
1984
2001
  ];
1985
- function isStateOp(summary) {
1986
- return STATE_OP_TYPES.includes(summary.opType);
2002
+ function isFieldOp(summary) {
2003
+ return FIELD_OP_TYPES.includes(summary.opType);
1987
2004
  }
1988
2005
  function validateActionParams(action, taskName, callerParams) {
1989
2006
  const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
@@ -2033,41 +2050,46 @@ function runOps(args) {
2033
2050
  const summaries = [];
2034
2051
  for (const op of ops) {
2035
2052
  const summary = applyOp(op, { mutation, stage, taskName: origin.task, params, actor, self, now });
2036
- summaries.push(summary), mutation.history.push({
2037
- _key: randomKey(),
2038
- _type: "opApplied",
2039
- at: now,
2040
- stage,
2041
- ...origin.task !== void 0 ? { task: origin.task } : {},
2042
- ...origin.action !== void 0 ? { action: origin.action } : {},
2043
- ...origin.transition !== void 0 ? { transition: origin.transition } : {},
2044
- opType: summary.opType,
2045
- ...summary.target !== void 0 ? { target: summary.target } : {},
2046
- ...summary.resolved !== void 0 ? { resolved: summary.resolved } : {},
2047
- ...actor !== void 0 ? { actor } : {}
2048
- });
2053
+ summaries.push(summary), mutation.history.push(opAppliedEntry({ origin, summary, stage, actor, now }));
2049
2054
  }
2050
2055
  return summaries;
2051
2056
  }
2057
+ function opAppliedEntry(args) {
2058
+ const { origin, summary, stage, actor, now } = args;
2059
+ return {
2060
+ _key: randomKey(),
2061
+ _type: "opApplied",
2062
+ at: now,
2063
+ stage,
2064
+ ...origin.task !== void 0 ? { task: origin.task } : {},
2065
+ ...origin.action !== void 0 ? { action: origin.action } : {},
2066
+ ...origin.transition !== void 0 ? { transition: origin.transition } : {},
2067
+ ...origin.edit === !0 ? { edit: !0 } : {},
2068
+ opType: summary.opType,
2069
+ ...summary.target !== void 0 ? { target: summary.target } : {},
2070
+ ...summary.resolved !== void 0 ? { resolved: summary.resolved } : {},
2071
+ ...actor !== void 0 ? { actor } : {}
2072
+ };
2073
+ }
2052
2074
  function applyOp(op, ctx) {
2053
2075
  switch (op.type) {
2054
- case "state.set":
2055
- return applyStateSet(op, ctx);
2056
- case "state.unset":
2057
- return applyStateUnset(op, ctx);
2058
- case "state.append":
2059
- return applyStateAppend(op, ctx);
2060
- case "state.updateWhere":
2061
- return applyStateUpdateWhere(op, ctx);
2062
- case "state.removeWhere":
2063
- return applyStateRemoveWhere(op, ctx);
2076
+ case "field.set":
2077
+ return applyFieldSet(op, ctx);
2078
+ case "field.unset":
2079
+ return applyFieldUnset(op, ctx);
2080
+ case "field.append":
2081
+ return applyFieldAppend(op, ctx);
2082
+ case "field.updateWhere":
2083
+ return applyFieldUpdateWhere(op, ctx);
2084
+ case "field.removeWhere":
2085
+ return applyFieldRemoveWhere(op, ctx);
2064
2086
  case "status.set":
2065
2087
  return applyStatusSet(op, ctx);
2066
2088
  }
2067
2089
  }
2068
- function applyStateSet(op, ctx) {
2090
+ function applyFieldSet(op, ctx) {
2069
2091
  const value = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
2070
- return validateStateValue({ entryType: entry._type, entryName: entry.name, value }), setEntryValue(entry, value), { opType: op.type, target: op.target, resolved: { value } };
2092
+ return validateFieldValue({ entryType: entry._type, entryName: entry.name, value }), setEntryValue(entry, value), { opType: op.type, target: op.target, resolved: { value } };
2071
2093
  }
2072
2094
  const EMPTY_BY_KIND = {
2073
2095
  "doc.refs": [],
@@ -2075,24 +2097,24 @@ const EMPTY_BY_KIND = {
2075
2097
  notes: [],
2076
2098
  assignees: []
2077
2099
  };
2078
- function applyStateUnset(op, ctx) {
2100
+ function applyFieldUnset(op, ctx) {
2079
2101
  const entry = locateEntry(ctx, op.target);
2080
2102
  return setEntryValue(entry, EMPTY_BY_KIND[entry._type] ?? null), { opType: op.type, target: op.target };
2081
2103
  }
2082
- function applyStateAppend(op, ctx) {
2104
+ function applyFieldAppend(op, ctx) {
2083
2105
  const item = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
2084
- validateStateAppendItem({ entryType: entry._type, entryName: entry.name, item });
2106
+ validateFieldAppendItem({ entryType: entry._type, entryName: entry.name, item });
2085
2107
  const current = Array.isArray(entry.value) ? entry.value : [];
2086
2108
  return setEntryValue(entry, [...current, withRowKey(item)]), { opType: op.type, target: op.target, resolved: { item } };
2087
2109
  }
2088
2110
  function withRowKey(item) {
2089
2111
  return typeof item == "object" && item !== null && !Array.isArray(item) && !("_key" in item) ? { _key: randomKey(), ...item } : item;
2090
2112
  }
2091
- function applyStateUpdateWhere(op, ctx) {
2113
+ function applyFieldUpdateWhere(op, ctx) {
2092
2114
  const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op), merge = resolveOpSource(op.value, ctx);
2093
2115
  if (merge === null || typeof merge != "object" || Array.isArray(merge))
2094
2116
  throw new Error(
2095
- `state.updateWhere value must resolve to an object of fields to merge (target "${op.target.state}")`
2117
+ `field.updateWhere value must resolve to an object of fields to merge (target "${op.target.field}")`
2096
2118
  );
2097
2119
  return setEntryValue(
2098
2120
  entry,
@@ -2101,7 +2123,7 @@ function applyStateUpdateWhere(op, ctx) {
2101
2123
  )
2102
2124
  ), { opType: op.type, target: op.target, resolved: { merge } };
2103
2125
  }
2104
- function applyStateRemoveWhere(op, ctx) {
2126
+ function applyFieldRemoveWhere(op, ctx) {
2105
2127
  const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op);
2106
2128
  return setEntryValue(
2107
2129
  entry,
@@ -2111,7 +2133,7 @@ function applyStateRemoveWhere(op, ctx) {
2111
2133
  function requireArrayValue(entry, op) {
2112
2134
  if (!Array.isArray(entry.value))
2113
2135
  throw new Error(
2114
- `${op.type} target ${op.target.scope}:"${op.target.state}" holds a non-array ${entry._type} value \u2014 where-ops operate on array entries`
2136
+ `${op.type} target ${op.target.scope}:"${op.target.field}" holds a non-array ${entry._type} value \u2014 where-ops operate on array entries`
2115
2137
  );
2116
2138
  return entry.value;
2117
2139
  }
@@ -2132,19 +2154,19 @@ function setEntryValue(entry, value) {
2132
2154
  entry.value = value;
2133
2155
  }
2134
2156
  function locateEntry(ctx, target) {
2135
- const entry = stateHost(ctx, target.scope)?.find((s) => s.name === target.state);
2157
+ const entry = fieldHost(ctx, target.scope)?.find((s) => s.name === target.field);
2136
2158
  if (entry === void 0)
2137
2159
  throw new Error(
2138
- `Op target ${target.scope}:"${target.state}" has no resolved state entry on this instance \u2014 the deployed definition and the instance state have diverged`
2160
+ `Op target ${target.scope}:"${target.field}" has no resolved field entry on this instance \u2014 the deployed definition and the instance state have diverged`
2139
2161
  );
2140
2162
  return entry;
2141
2163
  }
2142
- function stateHost(ctx, scope) {
2143
- if (scope === "workflow") return ctx.mutation.state;
2164
+ function fieldHost(ctx, scope) {
2165
+ if (scope === "workflow") return ctx.mutation.fields;
2144
2166
  const stageEntry = findOpenStageEntry(ctx.mutation);
2145
- if (scope === "stage") return stageEntry?.state;
2167
+ if (scope === "stage") return stageEntry?.fields;
2146
2168
  const task = stageEntry?.tasks.find((t) => t.name === ctx.taskName);
2147
- return task !== void 0 && task.state === void 0 && (task.state = []), task?.state;
2169
+ return task !== void 0 && task.fields === void 0 && (task.fields = []), task?.fields;
2148
2170
  }
2149
2171
  function resolveOpSource(src, ctx) {
2150
2172
  const staticValue = resolveStaticSource(src, ctx);
@@ -2154,7 +2176,7 @@ function resolveOpSource(src, ctx) {
2154
2176
  return ctx.self;
2155
2177
  case "stage":
2156
2178
  return ctx.stage;
2157
- case "stateRead": {
2179
+ case "fieldRead": {
2158
2180
  const value = readEntryFromMutation(ctx, src);
2159
2181
  return src.path !== void 0 ? getPath(value, src.path) : value;
2160
2182
  }
@@ -2165,7 +2187,7 @@ function resolveOpSource(src, ctx) {
2165
2187
  return out;
2166
2188
  }
2167
2189
  default:
2168
- throw new Error(`Source type "${src.type}" is a state-entry origin, not a value`);
2190
+ throw new Error(`Source type "${src.type}" is a field-entry origin, not a value`);
2169
2191
  }
2170
2192
  }
2171
2193
  function entryValue(entries, name) {
@@ -2174,7 +2196,7 @@ function entryValue(entries, name) {
2174
2196
  function readEntryFromMutation(ctx, src) {
2175
2197
  const scopes = src.scope !== void 0 ? [src.scope] : ["stage", "workflow"], stageEntry = findOpenStageEntry(ctx.mutation);
2176
2198
  for (const scope of scopes) {
2177
- const host = scope === "workflow" ? ctx.mutation.state : stageEntry?.state, value = entryValue(host, src.state);
2199
+ const host = scope === "workflow" ? ctx.mutation.fields : stageEntry?.fields, value = entryValue(host, src.field);
2178
2200
  if (value !== void 0) return value;
2179
2201
  }
2180
2202
  }
@@ -2206,9 +2228,9 @@ async function commitInvoke(ctx, taskName, actor) {
2206
2228
  }
2207
2229
  async function activateTask(ctx, mutation, task, entry, actor) {
2208
2230
  const now = ctx.now;
2209
- if (entry.status = "active", entry.startedAt = now, task.state !== void 0 && task.state.length > 0) {
2231
+ if (entry.status = "active", entry.startedAt = now, task.fields !== void 0 && task.fields.length > 0) {
2210
2232
  const stage = findStage(ctx.definition, mutation.currentStage);
2211
- entry.state = await resolveTaskStateEntries({
2233
+ entry.fields = await resolveTaskFieldEntries({
2212
2234
  client: ctx.client,
2213
2235
  instance: ctx.instance,
2214
2236
  stage,
@@ -2313,7 +2335,7 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
2313
2335
  _key: randomKey(),
2314
2336
  name: nextStage.name,
2315
2337
  enteredAt: at,
2316
- state: await resolveStageStateEntries({
2338
+ fields: await resolveStageFieldEntries({
2317
2339
  client: ctx.client,
2318
2340
  instance: ctx.instance,
2319
2341
  stage: nextStage,
@@ -2453,7 +2475,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
2453
2475
  _key: randomKey(),
2454
2476
  name: stage.name,
2455
2477
  enteredAt: now,
2456
- state: await resolveStageStateEntries({ client, instance, stage, now }),
2478
+ fields: await resolveStageFieldEntries({ client, instance, stage, now }),
2457
2479
  tasks: await buildStageTasks(ctx, stage)
2458
2480
  }, committed = await client.patch(instance._id).set({
2459
2481
  stages: [initialStageEntry],
@@ -2682,6 +2704,85 @@ async function fetchGrantsCached(requestFn, resourcePath) {
2682
2704
  return;
2683
2705
  }
2684
2706
  }
2707
+ function effectiveEditable(baseline, override) {
2708
+ if (baseline === void 0) return;
2709
+ if (override === void 0) return baseline;
2710
+ if (baseline === !0 && override === !0) return !0;
2711
+ const parts = [baseline, override].map((e) => e === !0 ? void 0 : e);
2712
+ return andConditions(parts) ?? !0;
2713
+ }
2714
+ function slotSites(definition, stage) {
2715
+ const sites = [];
2716
+ for (const entry of definition.fields ?? []) sites.push({ scope: "workflow", entry });
2717
+ for (const entry of stage.fields ?? []) sites.push({ scope: "stage", entry });
2718
+ for (const task of stage.tasks ?? [])
2719
+ for (const entry of task.fields ?? []) sites.push({ scope: "task", task: task.name, entry });
2720
+ return sites;
2721
+ }
2722
+ function toResolvedSlot(site, stage) {
2723
+ return {
2724
+ scope: site.scope,
2725
+ ...site.task !== void 0 ? { task: site.task } : {},
2726
+ name: site.entry.name,
2727
+ type: site.entry.type,
2728
+ ...site.entry.title !== void 0 ? { title: site.entry.title } : {},
2729
+ ref: { scope: site.scope, field: site.entry.name },
2730
+ effective: effectiveEditable(site.entry.editable, stage.editable?.[site.entry.name])
2731
+ };
2732
+ }
2733
+ function editableSlotsInStage(definition, stage) {
2734
+ return slotSites(definition, stage).filter((site) => site.entry.editable !== void 0).map((site) => toResolvedSlot(site, stage));
2735
+ }
2736
+ function editTargetMatches(slot, target) {
2737
+ return slot.name !== target.field ? !1 : target.task !== void 0 ? slot.scope === "task" && slot.task === target.task : target.scope !== void 0 ? slot.scope === target.scope : !0;
2738
+ }
2739
+ const SCOPE_PRECEDENCE = { task: 0, stage: 1, workflow: 2 };
2740
+ function resolveEditTarget(args) {
2741
+ const { definition, stage, target } = args, found = slotSites(definition, stage).filter(
2742
+ (site) => editTargetMatches(
2743
+ {
2744
+ scope: site.scope,
2745
+ name: site.entry.name,
2746
+ ...site.task !== void 0 ? { task: site.task } : {}
2747
+ },
2748
+ target
2749
+ )
2750
+ ).sort((a, b) => SCOPE_PRECEDENCE[a.scope] - SCOPE_PRECEDENCE[b.scope])[0];
2751
+ return found ? toResolvedSlot(found, stage) : void 0;
2752
+ }
2753
+ function slotWindowOpen(instance, slot) {
2754
+ if (instance.completedAt !== void 0) return { open: !1, detail: "instance completed" };
2755
+ if (instance.abortedAt !== void 0) return { open: !1, detail: "instance aborted" };
2756
+ if (slot.scope !== "task") return { open: !0 };
2757
+ const status = findOpenStageEntry(instance)?.tasks.find((t) => t.name === slot.task)?.status;
2758
+ return status === "active" ? { open: !0 } : { open: !1, detail: `task "${slot.task}" is ${status ?? "not active"}` };
2759
+ }
2760
+ function readSlotValue(instance, slot) {
2761
+ return slotFieldHost(instance, slot)?.find((e) => e.name === slot.name)?.value;
2762
+ }
2763
+ function slotFieldHost(instance, slot) {
2764
+ if (slot.scope === "workflow") return instance.fields;
2765
+ const stageEntry = findOpenStageEntry(instance);
2766
+ return slot.scope === "stage" ? stageEntry?.fields : stageEntry?.tasks.find((t) => t.name === slot.task)?.fields;
2767
+ }
2768
+ function slotProvenance(instance, ref) {
2769
+ let latest;
2770
+ for (const entry of instance.history)
2771
+ entry._type === "opApplied" && (entry.target?.scope !== ref.scope || entry.target.field !== ref.field || (latest = { at: entry.at, ...entry.actor !== void 0 ? { actor: entry.actor } : {} }));
2772
+ return latest === void 0 ? {} : {
2773
+ ...latest.actor !== void 0 ? { setBy: latest.actor } : {},
2774
+ setAt: latest.at
2775
+ };
2776
+ }
2777
+ function editDisabledReason(args) {
2778
+ const { effective, instance, window, guardDenial, predicateSatisfied } = args;
2779
+ if (effective === void 0) return { kind: "not-editable" };
2780
+ if (!window.open)
2781
+ return instance.completedAt !== void 0 ? { kind: "instance-completed", completedAt: instance.completedAt } : { kind: "edit-window-closed", detail: window.detail ?? "closed" };
2782
+ if (guardDenial !== void 0) return guardDenial;
2783
+ if (!predicateSatisfied)
2784
+ return { kind: "editor-not-permitted", predicate: effective === !0 ? "true" : effective };
2785
+ }
2685
2786
  async function evaluateInstance(args) {
2686
2787
  const { client, tag, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
2687
2788
  validateTag(tag);
@@ -2715,6 +2816,7 @@ async function evaluateFromSnapshot(args) {
2715
2816
  actor,
2716
2817
  snapshot,
2717
2818
  scope,
2819
+ roleAliases: definition.roleAliases,
2718
2820
  stageHasExits: !isTerminalStage(stage),
2719
2821
  guardDenial
2720
2822
  })
@@ -2736,16 +2838,65 @@ async function evaluateFromSnapshot(args) {
2736
2838
  stage,
2737
2839
  tasks: taskEvaluations,
2738
2840
  transitions: transitionEvaluations
2739
- }, pendingOnYou = taskEvaluations.filter((t) => t.pendingOnActor), canInteract = taskEvaluations.some((t) => t.actions.some((a) => a.allowed));
2841
+ }, pendingOnYou = taskEvaluations.filter((t) => t.pendingOnActor), canInteract = taskEvaluations.some((t) => t.actions.some((a) => a.allowed)), editGuardDenial = guardDenial?.kind === "mutation-guard-denied" ? guardDenial : void 0, editableSlots = await evaluateEditableSlots({
2842
+ instance,
2843
+ definition,
2844
+ stage,
2845
+ actor,
2846
+ snapshot,
2847
+ scope,
2848
+ guardDenial: editGuardDenial
2849
+ });
2740
2850
  return {
2741
2851
  instance,
2742
2852
  definition,
2743
2853
  actor,
2744
2854
  currentStage,
2745
2855
  pendingOnYou,
2746
- canInteract
2856
+ canInteract,
2857
+ editableSlots
2747
2858
  };
2748
2859
  }
2860
+ async function evaluateEditableSlots(args) {
2861
+ const { instance, definition, stage, actor, snapshot, scope, guardDenial } = args, slots = [];
2862
+ for (const slot of editableSlotsInStage(definition, stage)) {
2863
+ const window = slotWindowOpen(instance, slot), predicateSatisfied = await editPredicateSatisfied({
2864
+ slot,
2865
+ window,
2866
+ guardDenial,
2867
+ instance,
2868
+ snapshot,
2869
+ scope,
2870
+ actor,
2871
+ roleAliases: definition.roleAliases
2872
+ }), reason = editDisabledReason({
2873
+ effective: slot.effective,
2874
+ instance,
2875
+ window,
2876
+ guardDenial,
2877
+ predicateSatisfied
2878
+ }), value = readSlotValue(instance, slot);
2879
+ slots.push({
2880
+ scope: slot.scope,
2881
+ ...slot.task !== void 0 ? { task: slot.task } : {},
2882
+ name: slot.name,
2883
+ type: slot.type,
2884
+ ...slot.title !== void 0 ? { title: slot.title } : {},
2885
+ value,
2886
+ editable: reason === void 0,
2887
+ ...reason !== void 0 ? { disabledReason: reason } : {},
2888
+ ...slotProvenance(instance, slot.ref)
2889
+ });
2890
+ }
2891
+ return slots;
2892
+ }
2893
+ async function editPredicateSatisfied(args) {
2894
+ const { slot, window, guardDenial, instance, snapshot, scope, actor, roleAliases } = args;
2895
+ if (!window.open || guardDenial !== void 0) return !1;
2896
+ if (slot.effective === !0) return !0;
2897
+ const params = slot.scope === "task" && slot.task !== void 0 ? taskScopeFor({ scope, instance, snapshot, actor, taskName: slot.task, roleAliases }) : scope;
2898
+ return evaluateCondition({ condition: slot.effective, snapshot, params });
2899
+ }
2749
2900
  async function renderScope(args) {
2750
2901
  const { instance, definition, actor, grants, snapshot, now } = args, params = {
2751
2902
  ...buildParams({ instance, now, snapshot }),
@@ -2771,15 +2922,36 @@ async function advisoryCan(instance, actor, grants) {
2771
2922
  });
2772
2923
  return can;
2773
2924
  }
2774
- async function evaluateTask(args) {
2775
- const { task, statusEntry, instance, actor, snapshot, scope, stageHasExits, guardDenial } = args, status = statusEntry?.status ?? "pending", assigned = assignedFor(instance, task.name, actor), taskScope = {
2925
+ function taskScopeFor(args) {
2926
+ const { scope, instance, snapshot, actor, taskName, roleAliases } = args;
2927
+ return {
2776
2928
  ...scope,
2777
- state: {
2778
- ...scope.state,
2779
- ...scopedStateOverlay(instance, snapshot, task.name)
2929
+ fields: {
2930
+ ...scope.fields,
2931
+ ...scopedFieldOverlay(instance, snapshot, taskName)
2780
2932
  },
2781
- assigned
2782
- }, unmetRequirements = await evaluateRequirements({
2933
+ assigned: assignedFor(instance, taskName, actor, roleAliases)
2934
+ };
2935
+ }
2936
+ async function evaluateTask(args) {
2937
+ const {
2938
+ task,
2939
+ statusEntry,
2940
+ instance,
2941
+ actor,
2942
+ snapshot,
2943
+ scope,
2944
+ roleAliases,
2945
+ stageHasExits,
2946
+ guardDenial
2947
+ } = args, status = statusEntry?.status ?? "pending", taskScope = taskScopeFor({
2948
+ scope,
2949
+ instance,
2950
+ snapshot,
2951
+ actor,
2952
+ taskName: task.name,
2953
+ roleAliases
2954
+ }), assigned = taskScope.assigned === !0, unmetRequirements = await evaluateRequirements({
2783
2955
  requirements: task.requirements,
2784
2956
  snapshot,
2785
2957
  params: taskScope
@@ -2802,8 +2974,8 @@ async function evaluateTask(args) {
2802
2974
  status,
2803
2975
  // "pending on actor" treats both `pending` and `active` as waiting on
2804
2976
  // the assignee — pending tasks just haven't been activated yet, but
2805
- // they're still inbox items. The inbox reads the assignees-kind state
2806
- // entry BY KIND (who-data is state).
2977
+ // they're still inbox items. The inbox reads the assignees-kind field
2978
+ // entry BY KIND (who-data is fields).
2807
2979
  pendingOnActor: (status === "active" || status === "pending") && assigned,
2808
2980
  ...unmetRequirements.length > 0 ? { unmetRequirements } : {},
2809
2981
  actions
@@ -3006,10 +3178,7 @@ async function commitAbort(ctx, reason, actor) {
3006
3178
  async function fireAction(args) {
3007
3179
  const { client, instanceId, task, action, params, options } = args;
3008
3180
  for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
3009
- const ctx = await loadContext(client, instanceId, {
3010
- ...options?.clock ? { clock: options.clock } : {},
3011
- ...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
3012
- });
3181
+ const ctx = await loadCallContext(client, instanceId, options);
3013
3182
  try {
3014
3183
  return await commitAction(ctx, task, action, params, options);
3015
3184
  } catch (error) {
@@ -3075,7 +3244,7 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
3075
3244
  }), await persistThenDeploy(
3076
3245
  ctx,
3077
3246
  mutation,
3078
- () => ranOps.some(isStateOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
3247
+ () => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
3079
3248
  ), {
3080
3249
  fired: !0,
3081
3250
  task: taskName,
@@ -3104,6 +3273,114 @@ function formatDisabledReason(task, action, reason) {
3104
3273
  const detail = disabledReasonDetail[reason.kind](reason);
3105
3274
  return `Action "${task}:${action}" is not allowed: ${detail}`;
3106
3275
  }
3276
+ class EditFieldDeniedError extends Error {
3277
+ reason;
3278
+ target;
3279
+ constructor(args) {
3280
+ super(formatEditDisabledReason(args.target, args.reason)), this.name = "EditFieldDeniedError", this.reason = args.reason, this.target = args.target;
3281
+ }
3282
+ }
3283
+ const editDisabledReasonDetail = {
3284
+ "not-editable": () => "slot is not declared editable",
3285
+ "instance-completed": (r) => `instance completed at ${r.completedAt}`,
3286
+ "edit-window-closed": (r) => `edit window closed (${r.detail})`,
3287
+ "editor-not-permitted": (r) => `editor not permitted (${r.predicate})`,
3288
+ "mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`
3289
+ };
3290
+ function formatEditDisabledReason(target, reason) {
3291
+ const detail = editDisabledReasonDetail[reason.kind](
3292
+ reason
3293
+ ), where = target.task !== void 0 ? `${target.task}.${target.field}` : target.field;
3294
+ return `Field "${target.scope}:${where}" is not editable: ${detail}`;
3295
+ }
3296
+ async function editField(args) {
3297
+ const { client, instanceId, target, mode = "set", value, options } = args;
3298
+ for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
3299
+ const ctx = await loadCallContext(client, instanceId, options);
3300
+ try {
3301
+ return await commitEdit(ctx, target, mode, value, options);
3302
+ } catch (error) {
3303
+ if (!isRevisionConflict(error)) throw error;
3304
+ }
3305
+ }
3306
+ throw new ConcurrentEditFieldError({
3307
+ instanceId,
3308
+ target: labelTarget(target),
3309
+ attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
3310
+ });
3311
+ }
3312
+ async function commitEdit(ctx, target, mode, value, options) {
3313
+ const actor = options?.actor, stage = findStage(ctx.definition, ctx.instance.currentStage), slot = resolveEditTarget({ definition: ctx.definition, stage, target });
3314
+ if (slot === void 0)
3315
+ throw new Error(
3316
+ `No field slot "${describeTarget(target)}" resolves in current stage "${stage.name}" of ${ctx.definition.name}`
3317
+ );
3318
+ await assertSlotEditable(ctx, slot, options);
3319
+ const op = buildEditOp(slot, mode, value), mutation = startMutation(ctx.instance), ranOps = runOps({
3320
+ ops: [op],
3321
+ mutation,
3322
+ stage: stage.name,
3323
+ origin: {
3324
+ edit: !0,
3325
+ ...slot.scope === "task" && slot.task !== void 0 ? { task: slot.task } : {}
3326
+ },
3327
+ params: {},
3328
+ actor,
3329
+ self: selfGdr(ctx.instance),
3330
+ now: ctx.now
3331
+ });
3332
+ return await persistThenDeploy(
3333
+ ctx,
3334
+ mutation,
3335
+ () => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
3336
+ ), {
3337
+ edited: !0,
3338
+ target: slotTarget(slot),
3339
+ ...ranOps.length > 0 ? { ranOps } : {}
3340
+ };
3341
+ }
3342
+ async function assertSlotEditable(ctx, slot, options) {
3343
+ const actor = options?.actor, window = slotWindowOpen(ctx.instance, slot), can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan(ctx.instance, actor, options.grants) : void 0, predicateSatisfied = window.open && slot.effective !== !0 && slot.effective !== void 0 ? await ctxEvaluateCondition(ctx, slot.effective, {
3344
+ ...slot.task !== void 0 ? { taskName: slot.task } : {},
3345
+ ...actor !== void 0 ? { actor } : {},
3346
+ ...can !== void 0 ? { vars: { can } } : {}
3347
+ }) : slot.effective === !0, reason = editDisabledReason({
3348
+ effective: slot.effective,
3349
+ instance: ctx.instance,
3350
+ window,
3351
+ guardDenial: void 0,
3352
+ predicateSatisfied
3353
+ });
3354
+ if (reason !== void 0) throw new EditFieldDeniedError({ target: slotTarget(slot), reason });
3355
+ }
3356
+ function buildEditOp(slot, mode, value) {
3357
+ if (mode === "unset") return { type: "field.unset", target: slot.ref };
3358
+ if (value === void 0)
3359
+ throw new Error(`editField mode "${mode}" requires a value for slot "${slot.name}"`);
3360
+ return {
3361
+ type: mode === "append" ? "field.append" : "field.set",
3362
+ target: slot.ref,
3363
+ value: { type: "literal", value }
3364
+ };
3365
+ }
3366
+ function slotTarget(slot) {
3367
+ return {
3368
+ scope: slot.scope,
3369
+ field: slot.name,
3370
+ ...slot.task !== void 0 ? { task: slot.task } : {}
3371
+ };
3372
+ }
3373
+ function labelTarget(target) {
3374
+ return {
3375
+ scope: target.scope ?? (target.task !== void 0 ? "task" : "workflow"),
3376
+ field: target.field,
3377
+ ...target.task !== void 0 ? { task: target.task } : {}
3378
+ };
3379
+ }
3380
+ function describeTarget(target) {
3381
+ const where = target.task !== void 0 ? `${target.task}.${target.field}` : target.field;
3382
+ return target.scope !== void 0 ? `${target.scope}:${where}` : where;
3383
+ }
3107
3384
  function bareIdFromSpawnRef(uri) {
3108
3385
  if (!uri.includes(":")) return [uri];
3109
3386
  try {
@@ -3150,18 +3427,60 @@ function buildClientForGdr(defaultClient, resolver) {
3150
3427
  function toEffectsContextEntries(ctx) {
3151
3428
  return Object.entries(ctx).map(([id, value]) => effectsContextEntry(id, value));
3152
3429
  }
3430
+ async function dispatchGatedWrite(args) {
3431
+ const { client, tag, instanceId, resourceClients, access, apply } = args, evaluation = await evaluateInstance({
3432
+ client,
3433
+ tag,
3434
+ instanceId,
3435
+ access,
3436
+ clock: args.clock,
3437
+ ...resourceClients !== void 0 ? { resourceClients } : {}
3438
+ }), ranOps = await apply(args.before, evaluation), cascaded = await cascade(client, instanceId, args.actor, args.clientForGdr, args.clock);
3439
+ return {
3440
+ instance: await reload(client, instanceId, tag),
3441
+ cascaded,
3442
+ fired: !0,
3443
+ ...ranOps !== void 0 ? { ranOps } : {}
3444
+ };
3445
+ }
3153
3446
  function assertActionAllowed(evaluation, task, action) {
3154
3447
  const actionEval = evaluation.currentStage.tasks.find((t) => t.task.name === task)?.actions.find((a) => a.action.name === action);
3155
3448
  if (actionEval?.allowed === !1 && actionEval.disabledReason)
3156
3449
  throw new ActionDisabledError({ task, action, reason: actionEval.disabledReason });
3157
3450
  }
3158
- async function applyAction(args) {
3159
- const { client, instance, task, action, params, actor, grants, clock, clientForGdr } = args, options = {
3160
- actor,
3161
- ...grants !== void 0 ? { grants } : {},
3162
- ...clock !== void 0 ? { clock } : {},
3163
- ...clientForGdr !== void 0 ? { clientForGdr } : {}
3451
+ function assertEditAllowed(evaluation, target) {
3452
+ const slot = evaluation.editableSlots.filter((s) => editTargetMatches(s, target)).sort((a, b) => SCOPE_PRECEDENCE[a.scope] - SCOPE_PRECEDENCE[b.scope])[0];
3453
+ if (slot !== void 0 && !slot.editable && slot.disabledReason !== void 0)
3454
+ throw new EditFieldDeniedError({
3455
+ target: {
3456
+ scope: slot.scope,
3457
+ field: slot.name,
3458
+ ...slot.task !== void 0 ? { task: slot.task } : {}
3459
+ },
3460
+ reason: slot.disabledReason
3461
+ });
3462
+ }
3463
+ async function applyEdit(args) {
3464
+ const { client, instance, target, mode, value, actor, grants, clock, clientForGdr } = args;
3465
+ return (await editField({
3466
+ client,
3467
+ instanceId: instance._id,
3468
+ target,
3469
+ ...mode !== void 0 ? { mode } : {},
3470
+ ...value !== void 0 ? { value } : {},
3471
+ options: buildEngineCallOptions({ actor, grants, clock, clientForGdr })
3472
+ })).ranOps;
3473
+ }
3474
+ function buildEngineCallOptions(args) {
3475
+ return {
3476
+ actor: args.actor,
3477
+ ...args.grants !== void 0 ? { grants: args.grants } : {},
3478
+ ...args.clock !== void 0 ? { clock: args.clock } : {},
3479
+ ...args.clientForGdr !== void 0 ? { clientForGdr: args.clientForGdr } : {}
3164
3480
  };
3481
+ }
3482
+ async function applyAction(args) {
3483
+ const { client, instance, task, action, params, actor, grants, clock, clientForGdr } = args, options = buildEngineCallOptions({ actor, grants, clock, clientForGdr });
3165
3484
  return findOpenStageEntry(instance)?.tasks.find((t) => t.name === task)?.status === "pending" && await invokeTask({ client, instanceId: instance._id, task, options }), (await fireAction({
3166
3485
  client,
3167
3486
  instanceId: instance._id,
@@ -3316,7 +3635,7 @@ const workflow = {
3316
3635
  workflowResource,
3317
3636
  definition: definitionName,
3318
3637
  version,
3319
- initialState,
3638
+ initialFields,
3320
3639
  ancestors,
3321
3640
  effectsContext,
3322
3641
  instanceId,
@@ -3326,9 +3645,9 @@ const workflow = {
3326
3645
  throw new Error(
3327
3646
  `Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
3328
3647
  );
3329
- const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedState = await resolveDeclaredState({
3330
- entryDefs: definition.state ?? [],
3331
- initialState: initialState ?? [],
3648
+ const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedFields = await resolveDeclaredFields({
3649
+ entryDefs: definition.fields ?? [],
3650
+ initialFields: initialFields ?? [],
3332
3651
  ctx: {
3333
3652
  client,
3334
3653
  now,
@@ -3339,7 +3658,7 @@ const workflow = {
3339
3658
  ...perspective !== void 0 ? { perspective } : {}
3340
3659
  },
3341
3660
  randomKey
3342
- }), effectivePerspective = perspective ?? derivePerspectiveFromState(resolvedState), base = buildInstanceBase({
3661
+ }), effectivePerspective = perspective ?? derivePerspectiveFromFields(resolvedFields), base = buildInstanceBase({
3343
3662
  id,
3344
3663
  now,
3345
3664
  tag,
@@ -3347,7 +3666,7 @@ const workflow = {
3347
3666
  definitionName: definition.name,
3348
3667
  pinnedVersion: definition.version,
3349
3668
  definition,
3350
- state: resolvedState,
3669
+ fields: resolvedFields,
3351
3670
  effectsContext: effectsContextEntries,
3352
3671
  ancestors: ancestors ?? [],
3353
3672
  perspective: effectivePerspective,
@@ -3377,34 +3696,68 @@ const workflow = {
3377
3696
  idempotent,
3378
3697
  resourceClients
3379
3698
  } = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
3380
- if (findOpenStageEntry(before)?.tasks.find((t) => t.name === task) === void 0 && idempotent === !0)
3381
- return { instance: before, cascaded: 0, fired: !1 };
3382
- const evaluation = await evaluateInstance({
3699
+ return findOpenStageEntry(before)?.tasks.find((t) => t.name === task) === void 0 && idempotent === !0 ? { instance: before, cascaded: 0, fired: !1 } : dispatchGatedWrite({
3383
3700
  client,
3384
3701
  tag,
3385
3702
  instanceId,
3386
3703
  access,
3704
+ actor,
3705
+ clientForGdr,
3387
3706
  clock,
3388
- ...resourceClients !== void 0 ? { resourceClients } : {}
3707
+ before,
3708
+ ...resourceClients !== void 0 ? { resourceClients } : {},
3709
+ apply: (instance, evaluation) => (assertActionAllowed(evaluation, task, action), applyAction({
3710
+ client,
3711
+ instance,
3712
+ task,
3713
+ action,
3714
+ actor,
3715
+ ...access.grants !== void 0 ? { grants: access.grants } : {},
3716
+ clock,
3717
+ clientForGdr,
3718
+ ...params !== void 0 ? { params } : {}
3719
+ }))
3389
3720
  });
3390
- assertActionAllowed(evaluation, task, action);
3391
- const ranOps = await applyAction({
3721
+ },
3722
+ /**
3723
+ * Edit a declared-editable field slot directly — reassign, reschedule,
3724
+ * claim-by-hand, append to a running log — through the generic edit seam,
3725
+ * instead of a bespoke action per field. Soft-gates on the slot's declared
3726
+ * editability (the same projection a UI renders), applies the edit as a
3727
+ * `field.*` op (so provenance + history are stamped by the op path),
3728
+ * refreshes the stage's guards, then cascades — an edit to a value a
3729
+ * transition reads can and should move the instance. Advisory like every
3730
+ * engine gate; the lake ACL + guards are the only hard locks.
3731
+ *
3732
+ * Each call is a discrete COMMIT (a history entry, a guard refresh, a
3733
+ * cascade, an `ifRevisionId` write), not a draft patch — so an inline-field
3734
+ * UI must bind it to a deliberate boundary (blur / Enter / Save / debounce),
3735
+ * never an `onChange` per keystroke.
3736
+ */
3737
+ editField: async (args) => {
3738
+ const { client, tag, instanceId, target, mode, value, resourceClients } = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
3739
+ return dispatchGatedWrite({
3392
3740
  client,
3393
- instance: before,
3394
- task,
3395
- action,
3741
+ tag,
3742
+ instanceId,
3743
+ access,
3396
3744
  actor,
3397
- ...access.grants !== void 0 ? { grants: access.grants } : {},
3398
- clock,
3399
3745
  clientForGdr,
3400
- ...params !== void 0 ? { params } : {}
3401
- }), cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
3402
- return {
3403
- instance: await reload(client, instanceId, tag),
3404
- cascaded,
3405
- fired: !0,
3406
- ...ranOps !== void 0 ? { ranOps } : {}
3407
- };
3746
+ clock,
3747
+ before,
3748
+ ...resourceClients !== void 0 ? { resourceClients } : {},
3749
+ apply: (instance, evaluation) => (assertEditAllowed(evaluation, target), applyEdit({
3750
+ client,
3751
+ instance,
3752
+ target,
3753
+ ...mode !== void 0 ? { mode } : {},
3754
+ ...value !== void 0 ? { value } : {},
3755
+ actor,
3756
+ ...access.grants !== void 0 ? { grants: access.grants } : {},
3757
+ clock,
3758
+ clientForGdr
3759
+ }))
3760
+ });
3408
3761
  },
3409
3762
  /**
3410
3763
  * Report a queued effect's outcome. Drains it from `pendingEffects`,
@@ -3546,7 +3899,7 @@ const workflow = {
3546
3899
  * Hydrates the instance's snapshot (instance + ancestors + every doc
3547
3900
  * declared by a `doc.ref` / `doc.refs` entry in scope), then
3548
3901
  * evaluates the supplied GROQ in groq-js against that dataset. The
3549
- * rendered scope is auto-bound: `$self`, `$state`, `$parent`,
3902
+ * rendered scope is auto-bound: `$self`, `$fields`, `$parent`,
3550
3903
  * `$ancestors`, `$stage`, `$now`, `$effects`, `$tasks`,
3551
3904
  * `$allTasksDone`, `$anyTaskFailed` — ids in GDR URI form to match
3552
3905
  * the snapshot's keying.
@@ -3822,6 +4175,9 @@ function createInstanceSession(args) {
3822
4175
  ...clock !== void 0 ? { now: clock() } : {},
3823
4176
  ...grants !== void 0 ? { grants } : {}
3824
4177
  });
4178
+ }, settleAfterApply = async (actor, held, ranOps) => {
4179
+ const cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
4180
+ return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: !0, ...ranOps !== void 0 ? { ranOps } : {} };
3825
4181
  }, commit = async (run) => {
3826
4182
  committing = !0;
3827
4183
  const held = new Map(overlay);
@@ -3872,8 +4228,25 @@ function createInstanceSession(args) {
3872
4228
  ...grants !== void 0 ? { grants } : {},
3873
4229
  ...clock !== void 0 ? { clock } : {},
3874
4230
  ...params !== void 0 ? { params } : {}
3875
- }), cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
3876
- return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: !0, ...ranOps !== void 0 ? { ranOps } : {} };
4231
+ });
4232
+ return settleAfterApply(actor, held, ranOps);
4233
+ });
4234
+ },
4235
+ editField({ target, mode, value }) {
4236
+ return commit(async (actor, held) => {
4237
+ assertEditAllowed(await evaluateWith(held, heldGuards), target);
4238
+ const { grants } = await access(), ranOps = await applyEdit({
4239
+ client,
4240
+ instance,
4241
+ target,
4242
+ ...mode !== void 0 ? { mode } : {},
4243
+ ...value !== void 0 ? { value } : {},
4244
+ actor,
4245
+ clientForGdr,
4246
+ ...grants !== void 0 ? { grants } : {},
4247
+ ...clock !== void 0 ? { clock } : {}
4248
+ });
4249
+ return settleAfterApply(actor, held, ranOps);
3877
4250
  });
3878
4251
  }
3879
4252
  };
@@ -3921,6 +4294,7 @@ function createEngine(args) {
3921
4294
  deployDefinitions: (rest) => workflow.deployDefinitions(bind(rest)),
3922
4295
  startInstance: (rest) => workflow.startInstance(bind(rest)),
3923
4296
  fireAction: (rest) => workflow.fireAction(bind(rest)),
4297
+ editField: (rest) => workflow.editField(bind(rest)),
3924
4298
  completeEffect: (rest) => workflow.completeEffect(bind(rest)),
3925
4299
  tick: (rest) => workflow.tick(bind(rest)),
3926
4300
  evaluateInstance: (rest) => evaluateInstance(bind(rest)),
@@ -4079,9 +4453,9 @@ const HISTORY_DISPLAY = {
4079
4453
  },
4080
4454
  opApplied: {
4081
4455
  title: "Op applied",
4082
- description: "An inline state-mutation op (state.set / state.append / status.set / etc.) ran during an action commit."
4456
+ description: "An inline field-mutation op (field.set / field.append / status.set / etc.) ran during an action commit."
4083
4457
  }
4084
- }, STATE_SLOT_DISPLAY = {
4458
+ }, FIELD_SLOT_DISPLAY = {
4085
4459
  "doc.ref": {
4086
4460
  title: "Document reference",
4087
4461
  description: "Single GDR pointer at a document in this or another resource."
@@ -4135,23 +4509,23 @@ const HISTORY_DISPLAY = {
4135
4509
  description: "Ordered list of typed (user|role) assignees."
4136
4510
  }
4137
4511
  }, OP_DISPLAY = {
4138
- "state.set": {
4139
- title: "Set state entry",
4140
- description: "Overwrite a state entry's value with a resolved Source."
4512
+ "field.set": {
4513
+ title: "Set field entry",
4514
+ description: "Overwrite a field entry's value with a resolved Source."
4141
4515
  },
4142
- "state.unset": {
4143
- title: "Unset state entry",
4144
- description: "Reset a state entry to its default (null for scalars, [] for array kinds)."
4516
+ "field.unset": {
4517
+ title: "Unset field entry",
4518
+ description: "Reset a field entry to its default (null for scalars, [] for array kinds)."
4145
4519
  },
4146
- "state.append": {
4147
- title: "Append to state entry",
4520
+ "field.append": {
4521
+ title: "Append to field entry",
4148
4522
  description: "Push a resolved item onto an array-kind entry (notes, checklist, assignees, refs)."
4149
4523
  },
4150
- "state.updateWhere": {
4524
+ "field.updateWhere": {
4151
4525
  title: "Update matching rows",
4152
4526
  description: "Merge fields into rows of an array-kind entry that match the predicate."
4153
4527
  },
4154
- "state.removeWhere": {
4528
+ "field.removeWhere": {
4155
4529
  title: "Remove matching rows",
4156
4530
  description: "Drop rows of an array-kind entry that match the predicate."
4157
4531
  },
@@ -4195,11 +4569,11 @@ const HISTORY_DISPLAY = {
4195
4569
  },
4196
4570
  [WORKFLOW_INSTANCE_TYPE]: {
4197
4571
  title: "Workflow instance",
4198
- description: "A running (or finished) workflow against its declared state."
4572
+ description: "A running (or finished) workflow against its declared fields."
4199
4573
  }
4200
4574
  }, DISPLAY = {
4201
4575
  ...HISTORY_DISPLAY,
4202
- ...STATE_SLOT_DISPLAY,
4576
+ ...FIELD_SLOT_DISPLAY,
4203
4577
  ...OP_DISPLAY,
4204
4578
  ...EFFECTS_CONTEXT_DISPLAY,
4205
4579
  ...AUTHORING_DISPLAY
@@ -4216,10 +4590,13 @@ export {
4216
4590
  ActionDisabledError,
4217
4591
  ActionParamsInvalidError,
4218
4592
  CascadeLimitError,
4593
+ ConcurrentEditFieldError,
4219
4594
  ConcurrentFireActionError,
4220
4595
  DEFAULT_CONTENT_PERSPECTIVE,
4221
4596
  DISPLAY,
4222
4597
  EFFECTS_CONTEXT_DISPLAY,
4598
+ EditFieldDeniedError,
4599
+ FIELD_SLOT_DISPLAY,
4223
4600
  GUARD_DOC_TYPE,
4224
4601
  GUARD_LIFTED_PREDICATE,
4225
4602
  GUARD_OWNER,
@@ -4227,8 +4604,7 @@ export {
4227
4604
  MutationGuardDeniedError,
4228
4605
  OP_DISPLAY,
4229
4606
  PartialGuardDeployError,
4230
- RequiredStateNotProvidedError,
4231
- STATE_SLOT_DISPLAY,
4607
+ RequiredFieldNotProvidedError,
4232
4608
  WORKFLOW_DEFINITION_TYPE,
4233
4609
  WORKFLOW_INSTANCE_TYPE,
4234
4610
  WorkflowStateDivergedError,