@sanity/workflow-engine 0.9.0 → 0.11.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/_chunks-cjs/schema.cjs +94 -58
- package/dist/_chunks-cjs/schema.cjs.map +1 -1
- package/dist/_chunks-es/schema.js +94 -58
- package/dist/_chunks-es/schema.js.map +1 -1
- package/dist/define.cjs +109 -58
- package/dist/define.cjs.map +1 -1
- package/dist/define.d.cts +762 -384
- package/dist/define.d.ts +762 -384
- package/dist/define.js +110 -59
- package/dist/define.js.map +1 -1
- package/dist/index.cjs +290 -237
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1481 -765
- package/dist/index.d.ts +1481 -765
- package/dist/index.js +291 -238
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -126,7 +126,7 @@ function buildParams(args) {
|
|
|
126
126
|
const { instance, now, snapshot, extra } = args, currentTasks2 = findOpenStageEntry(instance)?.tasks ?? [];
|
|
127
127
|
return {
|
|
128
128
|
self: selfGdr(instance),
|
|
129
|
-
|
|
129
|
+
fields: renderedFields(instance.fields ?? [], snapshot),
|
|
130
130
|
parent: instance.ancestors.at(-1)?.id ?? null,
|
|
131
131
|
ancestors: instance.ancestors.map((a) => a.id),
|
|
132
132
|
stage: instance.currentStage,
|
|
@@ -141,7 +141,7 @@ function buildParams(args) {
|
|
|
141
141
|
...extra
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
|
-
function
|
|
144
|
+
function renderedFields(entries, snapshot) {
|
|
145
145
|
const out = {};
|
|
146
146
|
for (const entry of entries) out[entry.name] = renderedValue(entry, snapshot);
|
|
147
147
|
return out;
|
|
@@ -149,15 +149,15 @@ function renderedState(entries, snapshot) {
|
|
|
149
149
|
function renderedValue(entry, snapshot) {
|
|
150
150
|
return entry._type !== "doc.ref" || entry.value === null || snapshot === void 0 ? entry.value : snapshot.docs.find((d) => d._id === entry.value.id) ?? entry.value;
|
|
151
151
|
}
|
|
152
|
-
function
|
|
152
|
+
function scopedFieldOverlay(instance, snapshot, taskName) {
|
|
153
153
|
const stageEntry = findOpenStageEntry(instance);
|
|
154
154
|
if (stageEntry === void 0) return {};
|
|
155
|
-
const
|
|
156
|
-
return { ...
|
|
155
|
+
const stageFields = renderedFields(stageEntry.fields ?? [], snapshot), task = taskName ? stageEntry.tasks.find((t) => t.name === taskName) : void 0;
|
|
156
|
+
return { ...stageFields, ...renderedFields(task?.fields ?? [], snapshot) };
|
|
157
157
|
}
|
|
158
158
|
function assignedFor(instance, taskName, actor, roleAliases) {
|
|
159
159
|
if (actor === void 0) return !1;
|
|
160
|
-
const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.
|
|
160
|
+
const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.fields?.find((s) => s._type === "assignees");
|
|
161
161
|
return entry === void 0 ? !1 : entry.value.some(
|
|
162
162
|
(a) => a.type === "user" ? a.id === actor.id : schema.actorFulfillsRole(actor.roles, a.role, roleAliases)
|
|
163
163
|
);
|
|
@@ -184,17 +184,17 @@ function paramsForLake(params) {
|
|
|
184
184
|
self: typeof params.self == "string" ? bareId(params.self) : params.self,
|
|
185
185
|
parent: typeof params.parent == "string" ? bareId(params.parent) : params.parent,
|
|
186
186
|
ancestors: Array.isArray(params.ancestors) ? params.ancestors.map((a) => typeof a == "string" ? bareId(a) : a) : params.ancestors,
|
|
187
|
-
|
|
187
|
+
fields: stripFieldsForLake(params.fields)
|
|
188
188
|
};
|
|
189
189
|
}
|
|
190
|
-
function
|
|
190
|
+
function stripFieldsForLake(value) {
|
|
191
191
|
if (value == null) return value;
|
|
192
192
|
if (typeof value == "string") return bareId(value);
|
|
193
|
-
if (Array.isArray(value)) return value.map(
|
|
193
|
+
if (Array.isArray(value)) return value.map(stripFieldsForLake);
|
|
194
194
|
if (typeof value == "object") {
|
|
195
195
|
const out = {};
|
|
196
196
|
for (const [k, v2] of Object.entries(value))
|
|
197
|
-
out[k] =
|
|
197
|
+
out[k] = stripFieldsForLake(v2);
|
|
198
198
|
return out;
|
|
199
199
|
}
|
|
200
200
|
return value;
|
|
@@ -213,17 +213,17 @@ function getPath(value, path) {
|
|
|
213
213
|
function resourceOf(p) {
|
|
214
214
|
return p.scheme === "dataset" ? { type: "dataset", id: `${p.projectId}.${p.dataset}` } : { type: p.scheme, id: p.resourceId ?? "" };
|
|
215
215
|
}
|
|
216
|
-
const
|
|
216
|
+
const FIELD_READ = /^\$fields\.(\w+)(?:\.(.+))?$/, EFFECTS_READ = /^\$effects\['([^']+)'\](?:\.(.+))?$/;
|
|
217
217
|
function isGuardReadExpr(expr) {
|
|
218
|
-
return expr === "$self" || expr === "$now" ||
|
|
218
|
+
return expr === "$self" || expr === "$now" || FIELD_READ.test(expr) || EFFECTS_READ.test(expr);
|
|
219
219
|
}
|
|
220
220
|
function resolveGuardRead(expr, ctx) {
|
|
221
221
|
if (expr === "$self") return selfGdr(ctx.instance);
|
|
222
222
|
if (expr === "$now") return ctx.now;
|
|
223
|
-
const
|
|
224
|
-
if (
|
|
225
|
-
const value = (ctx.instance.
|
|
226
|
-
return
|
|
223
|
+
const fieldRead = FIELD_READ.exec(expr);
|
|
224
|
+
if (fieldRead !== null) {
|
|
225
|
+
const value = (ctx.instance.fields ?? []).find((s) => s.name === fieldRead[1])?.value;
|
|
226
|
+
return fieldRead[2] !== void 0 ? getPath(value, fieldRead[2]) : value;
|
|
227
227
|
}
|
|
228
228
|
const effectsRead = EFFECTS_READ.exec(expr);
|
|
229
229
|
if (effectsRead !== null) {
|
|
@@ -231,7 +231,7 @@ function resolveGuardRead(expr, ctx) {
|
|
|
231
231
|
return effectsRead[2] !== void 0 ? getPath(outputs, effectsRead[2]) : outputs;
|
|
232
232
|
}
|
|
233
233
|
throw new Error(
|
|
234
|
-
`Guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$
|
|
234
|
+
`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)`
|
|
235
235
|
);
|
|
236
236
|
}
|
|
237
237
|
function resolveIdRefTarget(expr, ctx) {
|
|
@@ -281,8 +281,8 @@ function resolveMetadata(metadata, ctx) {
|
|
|
281
281
|
}
|
|
282
282
|
function validateDefinition(definition) {
|
|
283
283
|
const v2 = createDefinitionValidator();
|
|
284
|
-
for (const entry of definition.
|
|
285
|
-
v2.checkEntry(entry, `workflow.
|
|
284
|
+
for (const entry of definition.fields ?? [])
|
|
285
|
+
v2.checkEntry(entry, `workflow.fields "${entry.name}"`);
|
|
286
286
|
for (const [name, groq] of Object.entries(definition.predicates ?? {}))
|
|
287
287
|
v2.checkCondition(groq, `predicate "${name}"`);
|
|
288
288
|
for (const stage of definition.stages)
|
|
@@ -304,7 +304,7 @@ function createDefinitionValidator() {
|
|
|
304
304
|
}
|
|
305
305
|
}, rejectTypeScan = (groq, where) => {
|
|
306
306
|
/\*\s*\[\s*_type\b/.test(groq) && errors.push(
|
|
307
|
-
` \xB7 ${where}: condition scans by \`_type\` \u2014 that's a discovery query, not a predicate. Conditions evaluate against the in-memory snapshot (instance + ancestors +
|
|
307
|
+
` \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.`
|
|
308
308
|
);
|
|
309
309
|
};
|
|
310
310
|
return { errors, tryParse, checkCondition: (groq, where) => {
|
|
@@ -313,13 +313,13 @@ function createDefinitionValidator() {
|
|
|
313
313
|
entry.source.type === "query" && tryParse(entry.source.query, `${label}.query`);
|
|
314
314
|
}, checkGuardRead: (expr, where) => {
|
|
315
315
|
isGuardReadExpr(expr) || errors.push(
|
|
316
|
-
` \xB7 ${where}: guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$
|
|
316
|
+
` \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)`
|
|
317
317
|
);
|
|
318
318
|
} };
|
|
319
319
|
}
|
|
320
320
|
function validateStage(v2, stage) {
|
|
321
|
-
for (const entry of stage.
|
|
322
|
-
v2.checkEntry(entry, `stage "${stage.name}".
|
|
321
|
+
for (const entry of stage.fields ?? [])
|
|
322
|
+
v2.checkEntry(entry, `stage "${stage.name}".fields "${entry.name}"`);
|
|
323
323
|
for (const guard of stage.guards ?? [])
|
|
324
324
|
validateGuardReads(v2, stage.name, guard);
|
|
325
325
|
for (const t of stage.transitions ?? []) {
|
|
@@ -339,8 +339,8 @@ function validateGuardReads(v2, stageName, guard) {
|
|
|
339
339
|
}
|
|
340
340
|
function validateTask(v2, stageName, task) {
|
|
341
341
|
const where = `stage "${stageName}" task "${task.name}"`;
|
|
342
|
-
for (const entry of task.
|
|
343
|
-
v2.checkEntry(entry, `${where}.
|
|
342
|
+
for (const entry of task.fields ?? [])
|
|
343
|
+
v2.checkEntry(entry, `${where}.fields "${entry.name}"`);
|
|
344
344
|
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`);
|
|
345
345
|
for (const [name, groq] of Object.entries(task.requirements ?? {}))
|
|
346
346
|
v2.checkCondition(groq, `${where}.requirements "${name}"`);
|
|
@@ -512,7 +512,7 @@ function openStage(instance) {
|
|
|
512
512
|
return findOpenStageEntry(instance);
|
|
513
513
|
}
|
|
514
514
|
function assigneesOf(stage, taskName) {
|
|
515
|
-
return (stage?.tasks.find((t) => t.name === taskName)?.
|
|
515
|
+
return (stage?.tasks.find((t) => t.name === taskName)?.fields ?? []).filter((s) => s._type === "assignees").flatMap((s) => s.value);
|
|
516
516
|
}
|
|
517
517
|
function isResolved(status) {
|
|
518
518
|
return status === "done" || status === "skipped";
|
|
@@ -625,17 +625,17 @@ class ActionParamsInvalidError extends Error {
|
|
|
625
625
|
${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.task = args.task, this.issues = args.issues;
|
|
626
626
|
}
|
|
627
627
|
}
|
|
628
|
-
class
|
|
628
|
+
class RequiredFieldNotProvidedError extends Error {
|
|
629
629
|
definition;
|
|
630
630
|
missing;
|
|
631
631
|
constructor(args) {
|
|
632
632
|
const lines = args.missing.map((m) => ` - ${m.name} (${m.type})`).join(`
|
|
633
633
|
`), where = args.definition !== void 0 ? ` for workflow "${args.definition}"` : "";
|
|
634
634
|
super(
|
|
635
|
-
`Required init
|
|
635
|
+
`Required init fields${where} was not provided:
|
|
636
636
|
${lines}
|
|
637
|
-
Provide each via \`
|
|
638
|
-
), this.name = "
|
|
637
|
+
Provide each via \`initialFields\` when starting standalone, or via the parent's \`subworkflows.with\` when spawned.`
|
|
638
|
+
), this.name = "RequiredFieldNotProvidedError", args.definition !== void 0 && (this.definition = args.definition), this.missing = args.missing;
|
|
639
639
|
}
|
|
640
640
|
}
|
|
641
641
|
class WorkflowStateDivergedError extends Error {
|
|
@@ -678,15 +678,15 @@ function isRevisionConflict(error) {
|
|
|
678
678
|
const { statusCode, message } = error;
|
|
679
679
|
return statusCode === 409 ? !0 : typeof message == "string" && message.includes("ifRevisionId check failed");
|
|
680
680
|
}
|
|
681
|
-
class
|
|
681
|
+
class ConcurrentEditFieldError extends Error {
|
|
682
682
|
instanceId;
|
|
683
683
|
target;
|
|
684
684
|
attempts;
|
|
685
685
|
constructor(args) {
|
|
686
|
-
const where = args.target.task !== void 0 ? `${args.target.task}.${args.target.
|
|
686
|
+
const where = args.target.task !== void 0 ? `${args.target.task}.${args.target.field}` : args.target.field;
|
|
687
687
|
super(
|
|
688
688
|
`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.`
|
|
689
|
-
), this.name = "
|
|
689
|
+
), this.name = "ConcurrentEditFieldError", this.instanceId = args.instanceId, this.target = args.target, this.attempts = args.attempts;
|
|
690
690
|
}
|
|
691
691
|
}
|
|
692
692
|
class CascadeLimitError extends Error {
|
|
@@ -734,10 +734,10 @@ function collectWatchRefs(instance) {
|
|
|
734
734
|
return [
|
|
735
735
|
gdrRef(instance.workflowResource, instance._id, instance._type),
|
|
736
736
|
...instance.ancestors,
|
|
737
|
-
...entryDocRefs(instance.
|
|
738
|
-
...entryDocRefs(stage?.
|
|
739
|
-
...entryReleaseRefs(instance.
|
|
740
|
-
...entryReleaseRefs(stage?.
|
|
737
|
+
...entryDocRefs(instance.fields),
|
|
738
|
+
...entryDocRefs(stage?.fields),
|
|
739
|
+
...entryReleaseRefs(instance.fields),
|
|
740
|
+
...entryReleaseRefs(stage?.fields)
|
|
741
741
|
];
|
|
742
742
|
}
|
|
743
743
|
function readsRaw(ref) {
|
|
@@ -761,21 +761,21 @@ function subscriptionDocumentsForInstance(instance) {
|
|
|
761
761
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
762
762
|
};
|
|
763
763
|
}
|
|
764
|
-
function entryDocRefs(
|
|
765
|
-
return
|
|
764
|
+
function entryDocRefs(entries) {
|
|
765
|
+
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) : [] : []);
|
|
766
766
|
}
|
|
767
|
-
function entryReleaseRefs(
|
|
768
|
-
return
|
|
767
|
+
function entryReleaseRefs(entries) {
|
|
768
|
+
return fieldEntries(entries).flatMap(
|
|
769
769
|
(entry) => entry._type === "release.ref" && isGdr(entry.value) ? [entry.value] : []
|
|
770
770
|
);
|
|
771
771
|
}
|
|
772
|
-
function entrySubjectRefs(
|
|
773
|
-
return
|
|
772
|
+
function entrySubjectRefs(entries) {
|
|
773
|
+
return fieldEntries(entries).flatMap(
|
|
774
774
|
(entry) => (entry._type === "doc.ref" || entry._type === "release.ref") && isGdr(entry.value) ? [entry.value] : []
|
|
775
775
|
);
|
|
776
776
|
}
|
|
777
|
-
function
|
|
778
|
-
return Array.isArray(
|
|
777
|
+
function fieldEntries(entries) {
|
|
778
|
+
return Array.isArray(entries) ? entries.filter(
|
|
779
779
|
(entry) => !!entry && typeof entry == "object"
|
|
780
780
|
) : [];
|
|
781
781
|
}
|
|
@@ -821,8 +821,8 @@ async function readDoc(client, id, perspective) {
|
|
|
821
821
|
function resourceFromParsed(parsed) {
|
|
822
822
|
return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
|
|
823
823
|
}
|
|
824
|
-
function collectEntryDocUris(
|
|
825
|
-
return entryDocRefs(
|
|
824
|
+
function collectEntryDocUris(resolvedFieldEntries) {
|
|
825
|
+
return entryDocRefs(resolvedFieldEntries).map((ref) => ref.id);
|
|
826
826
|
}
|
|
827
827
|
const resourceKey = (r) => `${r.type}.${r.id}`;
|
|
828
828
|
function guardsForResource(client) {
|
|
@@ -840,8 +840,8 @@ function verdictGuardsForInstance(client, instanceId) {
|
|
|
840
840
|
}
|
|
841
841
|
async function guardsForInstance(args) {
|
|
842
842
|
const { client, clientForGdr, instance } = args, stateUris = [
|
|
843
|
-
...collectEntryDocUris(instance.
|
|
844
|
-
...instance.stages.flatMap((stage) => collectEntryDocUris(stage.
|
|
843
|
+
...collectEntryDocUris(instance.fields),
|
|
844
|
+
...instance.stages.flatMap((stage) => collectEntryDocUris(stage.fields))
|
|
845
845
|
], clients = resourceClientMap(
|
|
846
846
|
instance.workflowResource,
|
|
847
847
|
client,
|
|
@@ -873,16 +873,16 @@ function guardIdRefs(definition) {
|
|
|
873
873
|
);
|
|
874
874
|
}
|
|
875
875
|
function staticIdRefGdr(idRef, stage, definition) {
|
|
876
|
-
const read = /^\$
|
|
876
|
+
const read = /^\$fields\.([\w-]+)/.exec(idRef);
|
|
877
877
|
if (read === null) return;
|
|
878
878
|
const source = entriesInScope(stage, definition).find((e) => e.name === read[1])?.source;
|
|
879
879
|
return source?.type === "literal" ? gdrFromValue(source.value) : void 0;
|
|
880
880
|
}
|
|
881
881
|
function entriesInScope(stage, definition) {
|
|
882
882
|
return [
|
|
883
|
-
...definition.
|
|
884
|
-
...stage.
|
|
885
|
-
...(stage.tasks ?? []).flatMap((task) => task.
|
|
883
|
+
...definition.fields ?? [],
|
|
884
|
+
...stage.fields ?? [],
|
|
885
|
+
...(stage.tasks ?? []).flatMap((task) => task.fields ?? [])
|
|
886
886
|
];
|
|
887
887
|
}
|
|
888
888
|
function gdrFromValue(value) {
|
|
@@ -1032,15 +1032,15 @@ async function runGroq(groq, params, snapshot) {
|
|
|
1032
1032
|
const tree = groqJs.parse(groq, { params });
|
|
1033
1033
|
return (await groqJs.evaluate(tree, { dataset: snapshot.docs, params })).get();
|
|
1034
1034
|
}
|
|
1035
|
-
class
|
|
1035
|
+
class FieldValueShapeError extends Error {
|
|
1036
1036
|
entryType;
|
|
1037
1037
|
entryName;
|
|
1038
1038
|
issues;
|
|
1039
1039
|
constructor(args) {
|
|
1040
1040
|
const issueText = args.issues.join("; ");
|
|
1041
1041
|
super(
|
|
1042
|
-
`
|
|
1043
|
-
), this.name = "
|
|
1042
|
+
`Field entry ${args.mode} shape invalid for "${args.entryName}" (${args.entryType}): ${issueText}`
|
|
1043
|
+
), this.name = "FieldValueShapeError", this.entryType = args.entryType, this.entryName = args.entryName, this.issues = args.issues;
|
|
1044
1044
|
}
|
|
1045
1045
|
}
|
|
1046
1046
|
const GdrShape = v__namespace.looseObject({
|
|
@@ -1107,35 +1107,35 @@ const GdrShape = v__namespace.looseObject({
|
|
|
1107
1107
|
function isAppendable(entryType) {
|
|
1108
1108
|
return entryType in itemSchemas;
|
|
1109
1109
|
}
|
|
1110
|
-
function
|
|
1110
|
+
function validateFieldValue(args) {
|
|
1111
1111
|
const schema2 = valueSchemas[args.entryType];
|
|
1112
1112
|
if (schema2 === void 0)
|
|
1113
|
-
throw new
|
|
1113
|
+
throw new FieldValueShapeError({
|
|
1114
1114
|
entryType: args.entryType,
|
|
1115
1115
|
entryName: args.entryName,
|
|
1116
|
-
issues: [`unknown
|
|
1116
|
+
issues: [`unknown field entry type ${args.entryType}`],
|
|
1117
1117
|
mode: "value"
|
|
1118
1118
|
});
|
|
1119
1119
|
const result = v__namespace.safeParse(schema2, args.value);
|
|
1120
1120
|
if (!result.success)
|
|
1121
|
-
throw new
|
|
1121
|
+
throw new FieldValueShapeError({
|
|
1122
1122
|
entryType: args.entryType,
|
|
1123
1123
|
entryName: args.entryName,
|
|
1124
1124
|
issues: formatIssues(result.issues),
|
|
1125
1125
|
mode: "value"
|
|
1126
1126
|
});
|
|
1127
1127
|
}
|
|
1128
|
-
function
|
|
1128
|
+
function validateFieldAppendItem(args) {
|
|
1129
1129
|
if (!isAppendable(args.entryType))
|
|
1130
|
-
throw new
|
|
1130
|
+
throw new FieldValueShapeError({
|
|
1131
1131
|
entryType: args.entryType,
|
|
1132
1132
|
entryName: args.entryName,
|
|
1133
|
-
issues: [`
|
|
1133
|
+
issues: [`field entry type ${args.entryType} does not support append`],
|
|
1134
1134
|
mode: "item"
|
|
1135
1135
|
});
|
|
1136
1136
|
const schema2 = itemSchemas[args.entryType], result = v__namespace.safeParse(schema2, args.item);
|
|
1137
1137
|
if (!result.success)
|
|
1138
|
-
throw new
|
|
1138
|
+
throw new FieldValueShapeError({
|
|
1139
1139
|
entryType: args.entryType,
|
|
1140
1140
|
entryName: args.entryName,
|
|
1141
1141
|
issues: formatIssues(result.issues),
|
|
@@ -1148,7 +1148,7 @@ function formatIssues(issues) {
|
|
|
1148
1148
|
return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${i.message}`;
|
|
1149
1149
|
});
|
|
1150
1150
|
}
|
|
1151
|
-
function
|
|
1151
|
+
function derivePerspectiveFromFields(entries) {
|
|
1152
1152
|
if (entries !== void 0) {
|
|
1153
1153
|
for (const entry of entries)
|
|
1154
1154
|
if (entry._type === "release.ref" && entry.value !== null) {
|
|
@@ -1158,24 +1158,24 @@ function derivePerspectiveFromState(entries) {
|
|
|
1158
1158
|
}
|
|
1159
1159
|
}
|
|
1160
1160
|
}
|
|
1161
|
-
async function
|
|
1162
|
-
const { entryDefs,
|
|
1161
|
+
async function resolveDeclaredFields(args) {
|
|
1162
|
+
const { entryDefs, initialFields, ctx, randomKey: randomKey2 } = args;
|
|
1163
1163
|
if (entryDefs === void 0 || entryDefs.length === 0) return [];
|
|
1164
|
-
assertRequiredInitProvided(entryDefs,
|
|
1164
|
+
assertRequiredInitProvided(entryDefs, initialFields, ctx.definitionName);
|
|
1165
1165
|
const out = [];
|
|
1166
1166
|
for (const entry of entryDefs) {
|
|
1167
|
-
const scopedCtx = { ...ctx,
|
|
1168
|
-
out.push(await resolveOneEntry(entry,
|
|
1167
|
+
const scopedCtx = { ...ctx, resolvedFields: out };
|
|
1168
|
+
out.push(await resolveOneEntry(entry, initialFields, scopedCtx, randomKey2));
|
|
1169
1169
|
}
|
|
1170
1170
|
return out;
|
|
1171
1171
|
}
|
|
1172
|
-
function assertRequiredInitProvided(entryDefs,
|
|
1172
|
+
function assertRequiredInitProvided(entryDefs, initialFields, definitionName) {
|
|
1173
1173
|
const missing = entryDefs.filter((entry) => entry.required === !0 && entry.source.type === "init").filter((entry) => {
|
|
1174
|
-
const match =
|
|
1174
|
+
const match = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1175
1175
|
return match === void 0 || match.value === void 0 || match.value === null;
|
|
1176
1176
|
}).map((entry) => ({ name: entry.name, type: entry.type }));
|
|
1177
1177
|
if (missing.length !== 0)
|
|
1178
|
-
throw new
|
|
1178
|
+
throw new RequiredFieldNotProvidedError({
|
|
1179
1179
|
...definitionName !== void 0 ? { definition: definitionName } : {},
|
|
1180
1180
|
missing
|
|
1181
1181
|
});
|
|
@@ -1189,18 +1189,18 @@ const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set([
|
|
|
1189
1189
|
function defaultEntryValue(entryType) {
|
|
1190
1190
|
return ARRAY_SLOT_TYPES.has(entryType) ? [] : null;
|
|
1191
1191
|
}
|
|
1192
|
-
function resolveInitValue(entry,
|
|
1193
|
-
const initMatch =
|
|
1192
|
+
function resolveInitValue(entry, initialFields, defaultValue) {
|
|
1193
|
+
const initMatch = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1194
1194
|
return initMatch === void 0 ? defaultValue : (assertInitValueShape(entry, initMatch.value), initMatch.value);
|
|
1195
1195
|
}
|
|
1196
|
-
function
|
|
1197
|
-
const target = (source.scope === "workflow" ? ctx.
|
|
1196
|
+
function resolveFieldReadValue(source, ctx, defaultValue) {
|
|
1197
|
+
const target = (source.scope === "workflow" ? ctx.workflowFields ?? [] : ctx.resolvedFields ?? []).find((s) => s.name === source.field), targetValue = target === void 0 ? void 0 : target.value;
|
|
1198
1198
|
return (source.path !== void 0 ? getPath(targetValue, source.path) : targetValue) ?? defaultValue;
|
|
1199
1199
|
}
|
|
1200
1200
|
async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
1201
1201
|
const params = paramsForLake({
|
|
1202
1202
|
self: ctx.selfId ?? null,
|
|
1203
|
-
|
|
1203
|
+
fields: fieldMapFromResolved(ctx.resolvedFields ?? []),
|
|
1204
1204
|
stage: ctx.stageName ?? null,
|
|
1205
1205
|
task: ctx.taskName ?? null,
|
|
1206
1206
|
now: ctx.now,
|
|
@@ -1216,9 +1216,9 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
|
1216
1216
|
);
|
|
1217
1217
|
}
|
|
1218
1218
|
}
|
|
1219
|
-
async function resolveEntryValue(entry,
|
|
1219
|
+
async function resolveEntryValue(entry, initialFields, ctx, defaultValue) {
|
|
1220
1220
|
const source = entry.source;
|
|
1221
|
-
return source.type === "init" ? resolveInitValue(entry,
|
|
1221
|
+
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;
|
|
1222
1222
|
}
|
|
1223
1223
|
function buildResolvedEntry(entry, value, _key, now) {
|
|
1224
1224
|
const titleProp = entry.title !== void 0 ? { title: entry.title } : {}, descriptionProp = entry.description !== void 0 ? { description: entry.description } : {};
|
|
@@ -1239,36 +1239,36 @@ function buildResolvedEntry(entry, value, _key, now) {
|
|
|
1239
1239
|
value
|
|
1240
1240
|
};
|
|
1241
1241
|
}
|
|
1242
|
-
async function resolveOneEntry(entry,
|
|
1243
|
-
const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue(entry,
|
|
1244
|
-
return
|
|
1242
|
+
async function resolveOneEntry(entry, initialFields, ctx, randomKey2) {
|
|
1243
|
+
const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue(entry, initialFields, ctx, defaultValue);
|
|
1244
|
+
return validateFieldValue({ entryType: entry.type, entryName: entry.name, value }), buildResolvedEntry(entry, value, randomKey2(), ctx.now);
|
|
1245
1245
|
}
|
|
1246
|
-
function
|
|
1246
|
+
function fieldMapFromResolved(entries) {
|
|
1247
1247
|
const out = {};
|
|
1248
1248
|
for (const s of entries) out[s.name] = s.value;
|
|
1249
1249
|
return out;
|
|
1250
1250
|
}
|
|
1251
1251
|
function assertInitValueShape(entry, value) {
|
|
1252
1252
|
if (entry.type === "doc.ref") {
|
|
1253
|
-
assertGdrShape(value, `
|
|
1253
|
+
assertGdrShape(value, `field entry "${entry.name}" (doc.ref)`);
|
|
1254
1254
|
return;
|
|
1255
1255
|
}
|
|
1256
1256
|
if (entry.type === "release.ref") {
|
|
1257
|
-
assertGdrShape(value, `
|
|
1257
|
+
assertGdrShape(value, `field entry "${entry.name}" (release.ref)`);
|
|
1258
1258
|
const v2 = value;
|
|
1259
1259
|
if (typeof v2.releaseName != "string" || v2.releaseName.length === 0)
|
|
1260
1260
|
throw new Error(
|
|
1261
|
-
`Invalid init value for
|
|
1261
|
+
`Invalid init value for field entry "${entry.name}" (release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(v2.releaseName)}.`
|
|
1262
1262
|
);
|
|
1263
1263
|
return;
|
|
1264
1264
|
}
|
|
1265
1265
|
if (entry.type === "doc.refs") {
|
|
1266
1266
|
if (!Array.isArray(value))
|
|
1267
1267
|
throw new Error(
|
|
1268
|
-
`Invalid init value for
|
|
1268
|
+
`Invalid init value for field entry "${entry.name}" (doc.refs): expected an array of GDRs, got ${typeof value}.`
|
|
1269
1269
|
);
|
|
1270
1270
|
for (const [i, item] of value.entries())
|
|
1271
|
-
assertGdrShape(item, `
|
|
1271
|
+
assertGdrShape(item, `field entry "${entry.name}" (doc.refs) item [${i}]`);
|
|
1272
1272
|
}
|
|
1273
1273
|
}
|
|
1274
1274
|
function assertGdrShape(value, context) {
|
|
@@ -1327,12 +1327,12 @@ async function loadContext(client, instanceId, options) {
|
|
|
1327
1327
|
return { client, clientForGdr, clock, now: clock(), instance, definition, snapshot };
|
|
1328
1328
|
}
|
|
1329
1329
|
async function ctxConditionParams(ctx, opts) {
|
|
1330
|
-
const base = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }),
|
|
1331
|
-
...base.
|
|
1332
|
-
...
|
|
1330
|
+
const base = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), fields = {
|
|
1331
|
+
...base.fields,
|
|
1332
|
+
...scopedFieldOverlay(ctx.instance, ctx.snapshot, opts?.taskName)
|
|
1333
1333
|
}, params = {
|
|
1334
1334
|
...base,
|
|
1335
|
-
|
|
1335
|
+
fields,
|
|
1336
1336
|
actor: opts?.actor,
|
|
1337
1337
|
assigned: opts?.taskName !== void 0 ? assignedFor(ctx.instance, opts.taskName, opts?.actor, ctx.definition.roleAliases) : !1,
|
|
1338
1338
|
...opts?.vars
|
|
@@ -1371,11 +1371,11 @@ function findStage(definition, stageName) {
|
|
|
1371
1371
|
throw new Error(`Stage "${stageName}" not found in definition ${definition.name}`);
|
|
1372
1372
|
return stage;
|
|
1373
1373
|
}
|
|
1374
|
-
async function
|
|
1375
|
-
const { client, instance, stage, now,
|
|
1376
|
-
return
|
|
1377
|
-
entryDefs: stage.
|
|
1378
|
-
|
|
1374
|
+
async function resolveStageFieldEntries(args) {
|
|
1375
|
+
const { client, instance, stage, now, initialFields } = args;
|
|
1376
|
+
return resolveDeclaredFields({
|
|
1377
|
+
entryDefs: stage.fields,
|
|
1378
|
+
initialFields: initialFields ?? [],
|
|
1379
1379
|
ctx: {
|
|
1380
1380
|
client,
|
|
1381
1381
|
now,
|
|
@@ -1383,19 +1383,19 @@ async function resolveStageStateEntries(args) {
|
|
|
1383
1383
|
tag: instance.tag,
|
|
1384
1384
|
stageName: stage.name,
|
|
1385
1385
|
workflowResource: instance.workflowResource,
|
|
1386
|
-
// Allow stage-scope entries' `source: { type: "
|
|
1387
|
-
// "workflow", ... }` to see the already-resolved workflow-scope
|
|
1388
|
-
|
|
1386
|
+
// Allow stage-scope entries' `source: { type: "fieldRead", scope:
|
|
1387
|
+
// "workflow", ... }` to see the already-resolved workflow-scope fields.
|
|
1388
|
+
workflowFields: instance.fields ?? [],
|
|
1389
1389
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
1390
1390
|
},
|
|
1391
1391
|
randomKey
|
|
1392
1392
|
});
|
|
1393
1393
|
}
|
|
1394
|
-
async function
|
|
1394
|
+
async function resolveTaskFieldEntries(args) {
|
|
1395
1395
|
const { client, instance, stage, task, now } = args;
|
|
1396
|
-
return
|
|
1397
|
-
entryDefs: task.
|
|
1398
|
-
|
|
1396
|
+
return resolveDeclaredFields({
|
|
1397
|
+
entryDefs: task.fields,
|
|
1398
|
+
initialFields: [],
|
|
1399
1399
|
ctx: {
|
|
1400
1400
|
client,
|
|
1401
1401
|
now,
|
|
@@ -1404,7 +1404,7 @@ async function resolveTaskStateEntries(args) {
|
|
|
1404
1404
|
stageName: stage.name,
|
|
1405
1405
|
workflowResource: instance.workflowResource,
|
|
1406
1406
|
taskName: task.name,
|
|
1407
|
-
|
|
1407
|
+
workflowFields: instance.fields ?? [],
|
|
1408
1408
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
1409
1409
|
},
|
|
1410
1410
|
randomKey
|
|
@@ -1486,15 +1486,15 @@ function stageTransitionHistory(args) {
|
|
|
1486
1486
|
function startMutation(instance) {
|
|
1487
1487
|
return {
|
|
1488
1488
|
currentStage: instance.currentStage,
|
|
1489
|
-
// Deep-ish copy. Per-scope
|
|
1489
|
+
// Deep-ish copy. Per-scope field entry arrays need their own copies
|
|
1490
1490
|
// so ops can mutate without leaking into the source.
|
|
1491
|
-
|
|
1491
|
+
fields: (instance.fields ?? []).map((s) => ({ ...s })),
|
|
1492
1492
|
stages: instance.stages.map((s) => ({
|
|
1493
1493
|
...s,
|
|
1494
|
-
|
|
1494
|
+
fields: (s.fields ?? []).map((entry) => ({ ...entry })),
|
|
1495
1495
|
tasks: s.tasks.map((t) => ({
|
|
1496
1496
|
...t,
|
|
1497
|
-
...t.
|
|
1497
|
+
...t.fields !== void 0 ? { fields: t.fields.map((entry) => ({ ...entry })) } : {}
|
|
1498
1498
|
}))
|
|
1499
1499
|
})),
|
|
1500
1500
|
pendingEffects: [...instance.pendingEffects],
|
|
@@ -1510,7 +1510,7 @@ function startMutation(instance) {
|
|
|
1510
1510
|
function instanceStateFields(src) {
|
|
1511
1511
|
const fields = {
|
|
1512
1512
|
currentStage: src.currentStage,
|
|
1513
|
-
|
|
1513
|
+
fields: src.fields,
|
|
1514
1514
|
stages: src.stages,
|
|
1515
1515
|
pendingEffects: src.pendingEffects,
|
|
1516
1516
|
effectHistory: src.effectHistory,
|
|
@@ -1523,7 +1523,7 @@ function materializeInstance(base, mutation) {
|
|
|
1523
1523
|
return {
|
|
1524
1524
|
...base,
|
|
1525
1525
|
currentStage: mutation.currentStage,
|
|
1526
|
-
|
|
1526
|
+
fields: mutation.fields,
|
|
1527
1527
|
stages: mutation.stages,
|
|
1528
1528
|
effectsContext: mutation.effectsContext
|
|
1529
1529
|
};
|
|
@@ -1645,7 +1645,7 @@ function buildInstanceBase(args) {
|
|
|
1645
1645
|
definition: args.definitionName,
|
|
1646
1646
|
pinnedVersion: args.pinnedVersion,
|
|
1647
1647
|
definitionSnapshot: JSON.stringify(args.definition),
|
|
1648
|
-
|
|
1648
|
+
fields: args.fields,
|
|
1649
1649
|
effectsContext: args.effectsContext,
|
|
1650
1650
|
ancestors: args.ancestors,
|
|
1651
1651
|
...perspective !== void 0 ? { perspective } : {},
|
|
@@ -1666,12 +1666,16 @@ function buildInstanceBase(args) {
|
|
|
1666
1666
|
lastChangedAt: now
|
|
1667
1667
|
};
|
|
1668
1668
|
}
|
|
1669
|
-
const
|
|
1669
|
+
const ENGINE_ACTOR_ID_PREFIX = "engine.";
|
|
1670
|
+
function engineSystemActor(name) {
|
|
1671
|
+
return { kind: "system", id: `${ENGINE_ACTOR_ID_PREFIX}${name}` };
|
|
1672
|
+
}
|
|
1673
|
+
function isEngineActor(actor) {
|
|
1674
|
+
return actor.kind === "system" && actor.id.startsWith(ENGINE_ACTOR_ID_PREFIX);
|
|
1675
|
+
}
|
|
1676
|
+
const MAX_SPAWN_DEPTH = 6, SUBWORKFLOWS_ALL_DONE = "count($subworkflows[status != 'done']) == 0";
|
|
1670
1677
|
function gateActor(outcome) {
|
|
1671
|
-
return
|
|
1672
|
-
kind: "system",
|
|
1673
|
-
id: outcome === "failed" ? FAIL_WHEN_SYSTEM_ID : COMPLETE_WHEN_SYSTEM_ID
|
|
1674
|
-
};
|
|
1678
|
+
return engineSystemActor(outcome === "failed" ? "failWhen" : "completeWhen");
|
|
1675
1679
|
}
|
|
1676
1680
|
function effectiveCompleteWhen(task) {
|
|
1677
1681
|
return task.completeWhen ?? (task.subworkflows !== void 0 ? SUBWORKFLOWS_ALL_DONE : void 0);
|
|
@@ -1703,7 +1707,7 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1703
1707
|
...actor ? { actor } : {}
|
|
1704
1708
|
}), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
|
|
1705
1709
|
for (const row of rows) {
|
|
1706
|
-
const
|
|
1710
|
+
const initialFields = await projectRowFields(
|
|
1707
1711
|
ctx,
|
|
1708
1712
|
sub,
|
|
1709
1713
|
definition,
|
|
@@ -1714,7 +1718,7 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1714
1718
|
client: ctx.client,
|
|
1715
1719
|
parent: ctx.instance,
|
|
1716
1720
|
definition,
|
|
1717
|
-
|
|
1721
|
+
initialFields,
|
|
1718
1722
|
effectsContext,
|
|
1719
1723
|
actor,
|
|
1720
1724
|
now
|
|
@@ -1727,7 +1731,7 @@ async function resolveSpawnRows(ctx, sub) {
|
|
|
1727
1731
|
const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
|
|
1728
1732
|
let value, discoveryResource;
|
|
1729
1733
|
if (groq.includes("*")) {
|
|
1730
|
-
const routingUri = entrySubjectRefs(ctx.instance.
|
|
1734
|
+
const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
|
|
1731
1735
|
client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
|
|
1732
1736
|
client,
|
|
1733
1737
|
groq,
|
|
@@ -1743,13 +1747,13 @@ async function resolveSpawnRows(ctx, sub) {
|
|
|
1743
1747
|
}
|
|
1744
1748
|
return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
|
|
1745
1749
|
}
|
|
1746
|
-
async function
|
|
1750
|
+
async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
|
|
1747
1751
|
const out = [];
|
|
1748
1752
|
for (const [name, groq] of Object.entries(sub.with ?? {})) {
|
|
1749
|
-
const declared = (childDefinition.
|
|
1753
|
+
const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
|
|
1750
1754
|
if (declared === void 0)
|
|
1751
1755
|
throw new Error(
|
|
1752
|
-
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no
|
|
1756
|
+
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
|
|
1753
1757
|
);
|
|
1754
1758
|
const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
|
|
1755
1759
|
parentResource: ctx.instance.workflowResource,
|
|
@@ -1804,15 +1808,15 @@ async function resolveDefinitionRef(client, ref, tag) {
|
|
|
1804
1808
|
) ?? null;
|
|
1805
1809
|
}
|
|
1806
1810
|
async function prepareChildInstance(args) {
|
|
1807
|
-
const { client, parent, definition,
|
|
1811
|
+
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 = [
|
|
1808
1812
|
...parent.ancestors,
|
|
1809
1813
|
{
|
|
1810
1814
|
id: selfGdr(parent),
|
|
1811
1815
|
type: WORKFLOW_INSTANCE_TYPE
|
|
1812
1816
|
}
|
|
1813
|
-
], inheritedPerspective = parent.perspective,
|
|
1814
|
-
entryDefs: definition.
|
|
1815
|
-
|
|
1817
|
+
], inheritedPerspective = parent.perspective, childFields = await resolveDeclaredFields({
|
|
1818
|
+
entryDefs: definition.fields,
|
|
1819
|
+
initialFields,
|
|
1816
1820
|
ctx: {
|
|
1817
1821
|
client,
|
|
1818
1822
|
now,
|
|
@@ -1823,7 +1827,7 @@ async function prepareChildInstance(args) {
|
|
|
1823
1827
|
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
|
|
1824
1828
|
},
|
|
1825
1829
|
randomKey
|
|
1826
|
-
}), childPerspective =
|
|
1830
|
+
}), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
|
|
1827
1831
|
([key, value]) => {
|
|
1828
1832
|
if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
|
|
1829
1833
|
if (!isGdrUri(value.id))
|
|
@@ -1842,7 +1846,7 @@ async function prepareChildInstance(args) {
|
|
|
1842
1846
|
definitionName: definition.name,
|
|
1843
1847
|
pinnedVersion: definition.version,
|
|
1844
1848
|
definition,
|
|
1845
|
-
|
|
1849
|
+
fields: childFields,
|
|
1846
1850
|
effectsContext: effectsContextEntries,
|
|
1847
1851
|
ancestors,
|
|
1848
1852
|
perspective: childPerspective,
|
|
@@ -2007,15 +2011,15 @@ function resolveStaticSource(src, ctx) {
|
|
|
2007
2011
|
return { handled: !1 };
|
|
2008
2012
|
}
|
|
2009
2013
|
}
|
|
2010
|
-
const
|
|
2011
|
-
"
|
|
2012
|
-
"
|
|
2013
|
-
"
|
|
2014
|
-
"
|
|
2015
|
-
"
|
|
2014
|
+
const FIELD_OP_TYPES = [
|
|
2015
|
+
"field.set",
|
|
2016
|
+
"field.unset",
|
|
2017
|
+
"field.append",
|
|
2018
|
+
"field.updateWhere",
|
|
2019
|
+
"field.removeWhere"
|
|
2016
2020
|
];
|
|
2017
|
-
function
|
|
2018
|
-
return
|
|
2021
|
+
function isFieldOp(summary) {
|
|
2022
|
+
return FIELD_OP_TYPES.includes(summary.opType);
|
|
2019
2023
|
}
|
|
2020
2024
|
function validateActionParams(action, taskName, callerParams) {
|
|
2021
2025
|
const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
|
|
@@ -2088,23 +2092,23 @@ function opAppliedEntry(args) {
|
|
|
2088
2092
|
}
|
|
2089
2093
|
function applyOp(op, ctx) {
|
|
2090
2094
|
switch (op.type) {
|
|
2091
|
-
case "
|
|
2092
|
-
return
|
|
2093
|
-
case "
|
|
2094
|
-
return
|
|
2095
|
-
case "
|
|
2096
|
-
return
|
|
2097
|
-
case "
|
|
2098
|
-
return
|
|
2099
|
-
case "
|
|
2100
|
-
return
|
|
2095
|
+
case "field.set":
|
|
2096
|
+
return applyFieldSet(op, ctx);
|
|
2097
|
+
case "field.unset":
|
|
2098
|
+
return applyFieldUnset(op, ctx);
|
|
2099
|
+
case "field.append":
|
|
2100
|
+
return applyFieldAppend(op, ctx);
|
|
2101
|
+
case "field.updateWhere":
|
|
2102
|
+
return applyFieldUpdateWhere(op, ctx);
|
|
2103
|
+
case "field.removeWhere":
|
|
2104
|
+
return applyFieldRemoveWhere(op, ctx);
|
|
2101
2105
|
case "status.set":
|
|
2102
2106
|
return applyStatusSet(op, ctx);
|
|
2103
2107
|
}
|
|
2104
2108
|
}
|
|
2105
|
-
function
|
|
2109
|
+
function applyFieldSet(op, ctx) {
|
|
2106
2110
|
const value = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
|
|
2107
|
-
return
|
|
2111
|
+
return validateFieldValue({ entryType: entry._type, entryName: entry.name, value }), setEntryValue(entry, value), { opType: op.type, target: op.target, resolved: { value } };
|
|
2108
2112
|
}
|
|
2109
2113
|
const EMPTY_BY_KIND = {
|
|
2110
2114
|
"doc.refs": [],
|
|
@@ -2112,24 +2116,24 @@ const EMPTY_BY_KIND = {
|
|
|
2112
2116
|
notes: [],
|
|
2113
2117
|
assignees: []
|
|
2114
2118
|
};
|
|
2115
|
-
function
|
|
2119
|
+
function applyFieldUnset(op, ctx) {
|
|
2116
2120
|
const entry = locateEntry(ctx, op.target);
|
|
2117
2121
|
return setEntryValue(entry, EMPTY_BY_KIND[entry._type] ?? null), { opType: op.type, target: op.target };
|
|
2118
2122
|
}
|
|
2119
|
-
function
|
|
2123
|
+
function applyFieldAppend(op, ctx) {
|
|
2120
2124
|
const item = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
|
|
2121
|
-
|
|
2125
|
+
validateFieldAppendItem({ entryType: entry._type, entryName: entry.name, item });
|
|
2122
2126
|
const current = Array.isArray(entry.value) ? entry.value : [];
|
|
2123
2127
|
return setEntryValue(entry, [...current, withRowKey(item)]), { opType: op.type, target: op.target, resolved: { item } };
|
|
2124
2128
|
}
|
|
2125
2129
|
function withRowKey(item) {
|
|
2126
2130
|
return typeof item == "object" && item !== null && !Array.isArray(item) && !("_key" in item) ? { _key: randomKey(), ...item } : item;
|
|
2127
2131
|
}
|
|
2128
|
-
function
|
|
2132
|
+
function applyFieldUpdateWhere(op, ctx) {
|
|
2129
2133
|
const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op), merge = resolveOpSource(op.value, ctx);
|
|
2130
2134
|
if (merge === null || typeof merge != "object" || Array.isArray(merge))
|
|
2131
2135
|
throw new Error(
|
|
2132
|
-
`
|
|
2136
|
+
`field.updateWhere value must resolve to an object of fields to merge (target "${op.target.field}")`
|
|
2133
2137
|
);
|
|
2134
2138
|
return setEntryValue(
|
|
2135
2139
|
entry,
|
|
@@ -2138,7 +2142,7 @@ function applyStateUpdateWhere(op, ctx) {
|
|
|
2138
2142
|
)
|
|
2139
2143
|
), { opType: op.type, target: op.target, resolved: { merge } };
|
|
2140
2144
|
}
|
|
2141
|
-
function
|
|
2145
|
+
function applyFieldRemoveWhere(op, ctx) {
|
|
2142
2146
|
const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op);
|
|
2143
2147
|
return setEntryValue(
|
|
2144
2148
|
entry,
|
|
@@ -2148,7 +2152,7 @@ function applyStateRemoveWhere(op, ctx) {
|
|
|
2148
2152
|
function requireArrayValue(entry, op) {
|
|
2149
2153
|
if (!Array.isArray(entry.value))
|
|
2150
2154
|
throw new Error(
|
|
2151
|
-
`${op.type} target ${op.target.scope}:"${op.target.
|
|
2155
|
+
`${op.type} target ${op.target.scope}:"${op.target.field}" holds a non-array ${entry._type} value \u2014 where-ops operate on array entries`
|
|
2152
2156
|
);
|
|
2153
2157
|
return entry.value;
|
|
2154
2158
|
}
|
|
@@ -2169,19 +2173,19 @@ function setEntryValue(entry, value) {
|
|
|
2169
2173
|
entry.value = value;
|
|
2170
2174
|
}
|
|
2171
2175
|
function locateEntry(ctx, target) {
|
|
2172
|
-
const entry =
|
|
2176
|
+
const entry = fieldHost(ctx, target.scope)?.find((s) => s.name === target.field);
|
|
2173
2177
|
if (entry === void 0)
|
|
2174
2178
|
throw new Error(
|
|
2175
|
-
`Op target ${target.scope}:"${target.
|
|
2179
|
+
`Op target ${target.scope}:"${target.field}" has no resolved field entry on this instance \u2014 the deployed definition and the instance state have diverged`
|
|
2176
2180
|
);
|
|
2177
2181
|
return entry;
|
|
2178
2182
|
}
|
|
2179
|
-
function
|
|
2180
|
-
if (scope === "workflow") return ctx.mutation.
|
|
2183
|
+
function fieldHost(ctx, scope) {
|
|
2184
|
+
if (scope === "workflow") return ctx.mutation.fields;
|
|
2181
2185
|
const stageEntry = findOpenStageEntry(ctx.mutation);
|
|
2182
|
-
if (scope === "stage") return stageEntry?.
|
|
2186
|
+
if (scope === "stage") return stageEntry?.fields;
|
|
2183
2187
|
const task = stageEntry?.tasks.find((t) => t.name === ctx.taskName);
|
|
2184
|
-
return task !== void 0 && task.
|
|
2188
|
+
return task !== void 0 && task.fields === void 0 && (task.fields = []), task?.fields;
|
|
2185
2189
|
}
|
|
2186
2190
|
function resolveOpSource(src, ctx) {
|
|
2187
2191
|
const staticValue = resolveStaticSource(src, ctx);
|
|
@@ -2191,7 +2195,7 @@ function resolveOpSource(src, ctx) {
|
|
|
2191
2195
|
return ctx.self;
|
|
2192
2196
|
case "stage":
|
|
2193
2197
|
return ctx.stage;
|
|
2194
|
-
case "
|
|
2198
|
+
case "fieldRead": {
|
|
2195
2199
|
const value = readEntryFromMutation(ctx, src);
|
|
2196
2200
|
return src.path !== void 0 ? getPath(value, src.path) : value;
|
|
2197
2201
|
}
|
|
@@ -2202,7 +2206,7 @@ function resolveOpSource(src, ctx) {
|
|
|
2202
2206
|
return out;
|
|
2203
2207
|
}
|
|
2204
2208
|
default:
|
|
2205
|
-
throw new Error(`Source type "${src.type}" is a
|
|
2209
|
+
throw new Error(`Source type "${src.type}" is a field-entry origin, not a value`);
|
|
2206
2210
|
}
|
|
2207
2211
|
}
|
|
2208
2212
|
function entryValue(entries, name) {
|
|
@@ -2211,7 +2215,7 @@ function entryValue(entries, name) {
|
|
|
2211
2215
|
function readEntryFromMutation(ctx, src) {
|
|
2212
2216
|
const scopes = src.scope !== void 0 ? [src.scope] : ["stage", "workflow"], stageEntry = findOpenStageEntry(ctx.mutation);
|
|
2213
2217
|
for (const scope of scopes) {
|
|
2214
|
-
const host = scope === "workflow" ? ctx.mutation.
|
|
2218
|
+
const host = scope === "workflow" ? ctx.mutation.fields : stageEntry?.fields, value = entryValue(host, src.field);
|
|
2215
2219
|
if (value !== void 0) return value;
|
|
2216
2220
|
}
|
|
2217
2221
|
}
|
|
@@ -2243,9 +2247,9 @@ async function commitInvoke(ctx, taskName, actor) {
|
|
|
2243
2247
|
}
|
|
2244
2248
|
async function activateTask(ctx, mutation, task, entry, actor) {
|
|
2245
2249
|
const now = ctx.now;
|
|
2246
|
-
if (entry.status = "active", entry.startedAt = now, task.
|
|
2250
|
+
if (entry.status = "active", entry.startedAt = now, task.fields !== void 0 && task.fields.length > 0) {
|
|
2247
2251
|
const stage = findStage(ctx.definition, mutation.currentStage);
|
|
2248
|
-
entry.
|
|
2252
|
+
entry.fields = await resolveTaskFieldEntries({
|
|
2249
2253
|
client: ctx.client,
|
|
2250
2254
|
instance: ctx.instance,
|
|
2251
2255
|
stage,
|
|
@@ -2310,7 +2314,7 @@ function resolveMachineStep(mutation, task, entry, now) {
|
|
|
2310
2314
|
stage: mutation.currentStage,
|
|
2311
2315
|
to: "done",
|
|
2312
2316
|
at: now,
|
|
2313
|
-
actor:
|
|
2317
|
+
actor: engineSystemActor("machineStep")
|
|
2314
2318
|
});
|
|
2315
2319
|
}
|
|
2316
2320
|
async function buildStageTasks(ctx, stage) {
|
|
@@ -2350,7 +2354,7 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
|
|
|
2350
2354
|
_key: randomKey(),
|
|
2351
2355
|
name: nextStage.name,
|
|
2352
2356
|
enteredAt: at,
|
|
2353
|
-
|
|
2357
|
+
fields: await resolveStageFieldEntries({
|
|
2354
2358
|
client: ctx.client,
|
|
2355
2359
|
instance: ctx.instance,
|
|
2356
2360
|
stage: nextStage,
|
|
@@ -2490,7 +2494,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
2490
2494
|
_key: randomKey(),
|
|
2491
2495
|
name: stage.name,
|
|
2492
2496
|
enteredAt: now,
|
|
2493
|
-
|
|
2497
|
+
fields: await resolveStageFieldEntries({ client, instance, stage, now }),
|
|
2494
2498
|
tasks: await buildStageTasks(ctx, stage)
|
|
2495
2499
|
}, committed = await client.patch(instance._id).set({
|
|
2496
2500
|
stages: [initialStageEntry],
|
|
@@ -2728,10 +2732,10 @@ function effectiveEditable(baseline, override) {
|
|
|
2728
2732
|
}
|
|
2729
2733
|
function slotSites(definition, stage) {
|
|
2730
2734
|
const sites = [];
|
|
2731
|
-
for (const entry of definition.
|
|
2732
|
-
for (const entry of stage.
|
|
2735
|
+
for (const entry of definition.fields ?? []) sites.push({ scope: "workflow", entry });
|
|
2736
|
+
for (const entry of stage.fields ?? []) sites.push({ scope: "stage", entry });
|
|
2733
2737
|
for (const task of stage.tasks ?? [])
|
|
2734
|
-
for (const entry of task.
|
|
2738
|
+
for (const entry of task.fields ?? []) sites.push({ scope: "task", task: task.name, entry });
|
|
2735
2739
|
return sites;
|
|
2736
2740
|
}
|
|
2737
2741
|
function toResolvedSlot(site, stage) {
|
|
@@ -2741,7 +2745,7 @@ function toResolvedSlot(site, stage) {
|
|
|
2741
2745
|
name: site.entry.name,
|
|
2742
2746
|
type: site.entry.type,
|
|
2743
2747
|
...site.entry.title !== void 0 ? { title: site.entry.title } : {},
|
|
2744
|
-
ref: { scope: site.scope,
|
|
2748
|
+
ref: { scope: site.scope, field: site.entry.name },
|
|
2745
2749
|
effective: effectiveEditable(site.entry.editable, stage.editable?.[site.entry.name])
|
|
2746
2750
|
};
|
|
2747
2751
|
}
|
|
@@ -2749,7 +2753,7 @@ function editableSlotsInStage(definition, stage) {
|
|
|
2749
2753
|
return slotSites(definition, stage).filter((site) => site.entry.editable !== void 0).map((site) => toResolvedSlot(site, stage));
|
|
2750
2754
|
}
|
|
2751
2755
|
function editTargetMatches(slot, target) {
|
|
2752
|
-
return slot.name !== target.
|
|
2756
|
+
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;
|
|
2753
2757
|
}
|
|
2754
2758
|
const SCOPE_PRECEDENCE = { task: 0, stage: 1, workflow: 2 };
|
|
2755
2759
|
function resolveEditTarget(args) {
|
|
@@ -2773,17 +2777,17 @@ function slotWindowOpen(instance, slot) {
|
|
|
2773
2777
|
return status === "active" ? { open: !0 } : { open: !1, detail: `task "${slot.task}" is ${status ?? "not active"}` };
|
|
2774
2778
|
}
|
|
2775
2779
|
function readSlotValue(instance, slot) {
|
|
2776
|
-
return
|
|
2780
|
+
return slotFieldHost(instance, slot)?.find((e) => e.name === slot.name)?.value;
|
|
2777
2781
|
}
|
|
2778
|
-
function
|
|
2779
|
-
if (slot.scope === "workflow") return instance.
|
|
2782
|
+
function slotFieldHost(instance, slot) {
|
|
2783
|
+
if (slot.scope === "workflow") return instance.fields;
|
|
2780
2784
|
const stageEntry = findOpenStageEntry(instance);
|
|
2781
|
-
return slot.scope === "stage" ? stageEntry?.
|
|
2785
|
+
return slot.scope === "stage" ? stageEntry?.fields : stageEntry?.tasks.find((t) => t.name === slot.task)?.fields;
|
|
2782
2786
|
}
|
|
2783
2787
|
function slotProvenance(instance, ref) {
|
|
2784
2788
|
let latest;
|
|
2785
2789
|
for (const entry of instance.history)
|
|
2786
|
-
entry._type === "opApplied" && (entry.target?.scope !== ref.scope || entry.target.
|
|
2790
|
+
entry._type === "opApplied" && (entry.target?.scope !== ref.scope || entry.target.field !== ref.field || (latest = { at: entry.at, ...entry.actor !== void 0 ? { actor: entry.actor } : {} }));
|
|
2787
2791
|
return latest === void 0 ? {} : {
|
|
2788
2792
|
...latest.actor !== void 0 ? { setBy: latest.actor } : {},
|
|
2789
2793
|
setAt: latest.at
|
|
@@ -2798,6 +2802,18 @@ function editDisabledReason(args) {
|
|
|
2798
2802
|
if (!predicateSatisfied)
|
|
2799
2803
|
return { kind: "editor-not-permitted", predicate: effective === !0 ? "true" : effective };
|
|
2800
2804
|
}
|
|
2805
|
+
function hasItems(arr) {
|
|
2806
|
+
return arr !== void 0 && arr.length > 0;
|
|
2807
|
+
}
|
|
2808
|
+
function deriveTaskKind(task) {
|
|
2809
|
+
return hasItems(task.actions) ? "user" : hasItems(task.effects) ? "service" : task.completeWhen !== void 0 || task.subworkflows !== void 0 ? "receive" : "script";
|
|
2810
|
+
}
|
|
2811
|
+
function taskKind(task) {
|
|
2812
|
+
return task.kind ?? deriveTaskKind(task);
|
|
2813
|
+
}
|
|
2814
|
+
function driverKind(actor) {
|
|
2815
|
+
return actor.kind === "user" ? "person" : actor.kind === "ai" ? "agent" : isEngineActor(actor) ? "engine" : "service";
|
|
2816
|
+
}
|
|
2801
2817
|
async function evaluateInstance(args) {
|
|
2802
2818
|
const { client, tag, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
|
|
2803
2819
|
validateTag(tag);
|
|
@@ -2941,9 +2957,9 @@ function taskScopeFor(args) {
|
|
|
2941
2957
|
const { scope, instance, snapshot, actor, taskName, roleAliases } = args;
|
|
2942
2958
|
return {
|
|
2943
2959
|
...scope,
|
|
2944
|
-
|
|
2945
|
-
...scope.
|
|
2946
|
-
...
|
|
2960
|
+
fields: {
|
|
2961
|
+
...scope.fields,
|
|
2962
|
+
...scopedFieldOverlay(instance, snapshot, taskName)
|
|
2947
2963
|
},
|
|
2948
2964
|
assigned: assignedFor(instance, taskName, actor, roleAliases)
|
|
2949
2965
|
};
|
|
@@ -2987,10 +3003,11 @@ async function evaluateTask(args) {
|
|
|
2987
3003
|
return {
|
|
2988
3004
|
task,
|
|
2989
3005
|
status,
|
|
3006
|
+
kind: taskKind(task),
|
|
2990
3007
|
// "pending on actor" treats both `pending` and `active` as waiting on
|
|
2991
3008
|
// the assignee — pending tasks just haven't been activated yet, but
|
|
2992
|
-
// they're still inbox items. The inbox reads the assignees-kind
|
|
2993
|
-
// entry BY KIND (who-data is
|
|
3009
|
+
// they're still inbox items. The inbox reads the assignees-kind field
|
|
3010
|
+
// entry BY KIND (who-data is fields).
|
|
2994
3011
|
pendingOnActor: (status === "active" || status === "pending") && assigned,
|
|
2995
3012
|
...unmetRequirements.length > 0 ? { unmetRequirements } : {},
|
|
2996
3013
|
actions
|
|
@@ -3241,7 +3258,7 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
|
|
|
3241
3258
|
stage: stage.name,
|
|
3242
3259
|
task: taskName,
|
|
3243
3260
|
action: actionName,
|
|
3244
|
-
...actor !== void 0 ? { actor } : {}
|
|
3261
|
+
...actor !== void 0 ? { actor, driverKind: driverKind(actor) } : {}
|
|
3245
3262
|
});
|
|
3246
3263
|
const statusBefore = mutEntry.status, ranOps = await runOps({
|
|
3247
3264
|
ops: action.ops,
|
|
@@ -3259,7 +3276,7 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
|
|
|
3259
3276
|
}), await persistThenDeploy(
|
|
3260
3277
|
ctx,
|
|
3261
3278
|
mutation,
|
|
3262
|
-
() => ranOps.some(
|
|
3279
|
+
() => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
|
|
3263
3280
|
), {
|
|
3264
3281
|
fired: !0,
|
|
3265
3282
|
task: taskName,
|
|
@@ -3288,11 +3305,11 @@ function formatDisabledReason(task, action, reason) {
|
|
|
3288
3305
|
const detail = disabledReasonDetail[reason.kind](reason);
|
|
3289
3306
|
return `Action "${task}:${action}" is not allowed: ${detail}`;
|
|
3290
3307
|
}
|
|
3291
|
-
class
|
|
3308
|
+
class EditFieldDeniedError extends Error {
|
|
3292
3309
|
reason;
|
|
3293
3310
|
target;
|
|
3294
3311
|
constructor(args) {
|
|
3295
|
-
super(formatEditDisabledReason(args.target, args.reason)), this.name = "
|
|
3312
|
+
super(formatEditDisabledReason(args.target, args.reason)), this.name = "EditFieldDeniedError", this.reason = args.reason, this.target = args.target;
|
|
3296
3313
|
}
|
|
3297
3314
|
}
|
|
3298
3315
|
const editDisabledReasonDetail = {
|
|
@@ -3305,10 +3322,10 @@ const editDisabledReasonDetail = {
|
|
|
3305
3322
|
function formatEditDisabledReason(target, reason) {
|
|
3306
3323
|
const detail = editDisabledReasonDetail[reason.kind](
|
|
3307
3324
|
reason
|
|
3308
|
-
), where = target.task !== void 0 ? `${target.task}.${target.
|
|
3309
|
-
return `
|
|
3325
|
+
), where = target.task !== void 0 ? `${target.task}.${target.field}` : target.field;
|
|
3326
|
+
return `Field "${target.scope}:${where}" is not editable: ${detail}`;
|
|
3310
3327
|
}
|
|
3311
|
-
async function
|
|
3328
|
+
async function editField(args) {
|
|
3312
3329
|
const { client, instanceId, target, mode = "set", value, options } = args;
|
|
3313
3330
|
for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
|
|
3314
3331
|
const ctx = await loadCallContext(client, instanceId, options);
|
|
@@ -3318,7 +3335,7 @@ async function editState(args) {
|
|
|
3318
3335
|
if (!isRevisionConflict(error)) throw error;
|
|
3319
3336
|
}
|
|
3320
3337
|
}
|
|
3321
|
-
throw new
|
|
3338
|
+
throw new ConcurrentEditFieldError({
|
|
3322
3339
|
instanceId,
|
|
3323
3340
|
target: labelTarget(target),
|
|
3324
3341
|
attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
|
|
@@ -3328,7 +3345,7 @@ async function commitEdit(ctx, target, mode, value, options) {
|
|
|
3328
3345
|
const actor = options?.actor, stage = findStage(ctx.definition, ctx.instance.currentStage), slot = resolveEditTarget({ definition: ctx.definition, stage, target });
|
|
3329
3346
|
if (slot === void 0)
|
|
3330
3347
|
throw new Error(
|
|
3331
|
-
`No
|
|
3348
|
+
`No field slot "${describeTarget(target)}" resolves in current stage "${stage.name}" of ${ctx.definition.name}`
|
|
3332
3349
|
);
|
|
3333
3350
|
await assertSlotEditable(ctx, slot, options);
|
|
3334
3351
|
const op = buildEditOp(slot, mode, value), mutation = startMutation(ctx.instance), ranOps = runOps({
|
|
@@ -3347,7 +3364,7 @@ async function commitEdit(ctx, target, mode, value, options) {
|
|
|
3347
3364
|
return await persistThenDeploy(
|
|
3348
3365
|
ctx,
|
|
3349
3366
|
mutation,
|
|
3350
|
-
() => ranOps.some(
|
|
3367
|
+
() => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
|
|
3351
3368
|
), {
|
|
3352
3369
|
edited: !0,
|
|
3353
3370
|
target: slotTarget(slot),
|
|
@@ -3366,14 +3383,14 @@ async function assertSlotEditable(ctx, slot, options) {
|
|
|
3366
3383
|
guardDenial: void 0,
|
|
3367
3384
|
predicateSatisfied
|
|
3368
3385
|
});
|
|
3369
|
-
if (reason !== void 0) throw new
|
|
3386
|
+
if (reason !== void 0) throw new EditFieldDeniedError({ target: slotTarget(slot), reason });
|
|
3370
3387
|
}
|
|
3371
3388
|
function buildEditOp(slot, mode, value) {
|
|
3372
|
-
if (mode === "unset") return { type: "
|
|
3389
|
+
if (mode === "unset") return { type: "field.unset", target: slot.ref };
|
|
3373
3390
|
if (value === void 0)
|
|
3374
|
-
throw new Error(`
|
|
3391
|
+
throw new Error(`editField mode "${mode}" requires a value for slot "${slot.name}"`);
|
|
3375
3392
|
return {
|
|
3376
|
-
type: mode === "append" ? "
|
|
3393
|
+
type: mode === "append" ? "field.append" : "field.set",
|
|
3377
3394
|
target: slot.ref,
|
|
3378
3395
|
value: { type: "literal", value }
|
|
3379
3396
|
};
|
|
@@ -3381,19 +3398,19 @@ function buildEditOp(slot, mode, value) {
|
|
|
3381
3398
|
function slotTarget(slot) {
|
|
3382
3399
|
return {
|
|
3383
3400
|
scope: slot.scope,
|
|
3384
|
-
|
|
3401
|
+
field: slot.name,
|
|
3385
3402
|
...slot.task !== void 0 ? { task: slot.task } : {}
|
|
3386
3403
|
};
|
|
3387
3404
|
}
|
|
3388
3405
|
function labelTarget(target) {
|
|
3389
3406
|
return {
|
|
3390
3407
|
scope: target.scope ?? (target.task !== void 0 ? "task" : "workflow"),
|
|
3391
|
-
|
|
3408
|
+
field: target.field,
|
|
3392
3409
|
...target.task !== void 0 ? { task: target.task } : {}
|
|
3393
3410
|
};
|
|
3394
3411
|
}
|
|
3395
3412
|
function describeTarget(target) {
|
|
3396
|
-
const where = target.task !== void 0 ? `${target.task}.${target.
|
|
3413
|
+
const where = target.task !== void 0 ? `${target.task}.${target.field}` : target.field;
|
|
3397
3414
|
return target.scope !== void 0 ? `${target.scope}:${where}` : where;
|
|
3398
3415
|
}
|
|
3399
3416
|
function bareIdFromSpawnRef(uri) {
|
|
@@ -3466,10 +3483,10 @@ function assertActionAllowed(evaluation, task, action) {
|
|
|
3466
3483
|
function assertEditAllowed(evaluation, target) {
|
|
3467
3484
|
const slot = evaluation.editableSlots.filter((s) => editTargetMatches(s, target)).sort((a, b) => SCOPE_PRECEDENCE[a.scope] - SCOPE_PRECEDENCE[b.scope])[0];
|
|
3468
3485
|
if (slot !== void 0 && !slot.editable && slot.disabledReason !== void 0)
|
|
3469
|
-
throw new
|
|
3486
|
+
throw new EditFieldDeniedError({
|
|
3470
3487
|
target: {
|
|
3471
3488
|
scope: slot.scope,
|
|
3472
|
-
|
|
3489
|
+
field: slot.name,
|
|
3473
3490
|
...slot.task !== void 0 ? { task: slot.task } : {}
|
|
3474
3491
|
},
|
|
3475
3492
|
reason: slot.disabledReason
|
|
@@ -3477,7 +3494,7 @@ function assertEditAllowed(evaluation, target) {
|
|
|
3477
3494
|
}
|
|
3478
3495
|
async function applyEdit(args) {
|
|
3479
3496
|
const { client, instance, target, mode, value, actor, grants, clock, clientForGdr } = args;
|
|
3480
|
-
return (await
|
|
3497
|
+
return (await editField({
|
|
3481
3498
|
client,
|
|
3482
3499
|
instanceId: instance._id,
|
|
3483
3500
|
target,
|
|
@@ -3650,7 +3667,7 @@ const workflow = {
|
|
|
3650
3667
|
workflowResource,
|
|
3651
3668
|
definition: definitionName,
|
|
3652
3669
|
version,
|
|
3653
|
-
|
|
3670
|
+
initialFields,
|
|
3654
3671
|
ancestors,
|
|
3655
3672
|
effectsContext,
|
|
3656
3673
|
instanceId,
|
|
@@ -3660,9 +3677,9 @@ const workflow = {
|
|
|
3660
3677
|
throw new Error(
|
|
3661
3678
|
`Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
|
|
3662
3679
|
);
|
|
3663
|
-
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [],
|
|
3664
|
-
entryDefs: definition.
|
|
3665
|
-
|
|
3680
|
+
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedFields = await resolveDeclaredFields({
|
|
3681
|
+
entryDefs: definition.fields ?? [],
|
|
3682
|
+
initialFields: initialFields ?? [],
|
|
3666
3683
|
ctx: {
|
|
3667
3684
|
client,
|
|
3668
3685
|
now,
|
|
@@ -3673,7 +3690,7 @@ const workflow = {
|
|
|
3673
3690
|
...perspective !== void 0 ? { perspective } : {}
|
|
3674
3691
|
},
|
|
3675
3692
|
randomKey
|
|
3676
|
-
}), effectivePerspective = perspective ??
|
|
3693
|
+
}), effectivePerspective = perspective ?? derivePerspectiveFromFields(resolvedFields), base = buildInstanceBase({
|
|
3677
3694
|
id,
|
|
3678
3695
|
now,
|
|
3679
3696
|
tag,
|
|
@@ -3681,7 +3698,7 @@ const workflow = {
|
|
|
3681
3698
|
definitionName: definition.name,
|
|
3682
3699
|
pinnedVersion: definition.version,
|
|
3683
3700
|
definition,
|
|
3684
|
-
|
|
3701
|
+
fields: resolvedFields,
|
|
3685
3702
|
effectsContext: effectsContextEntries,
|
|
3686
3703
|
ancestors: ancestors ?? [],
|
|
3687
3704
|
perspective: effectivePerspective,
|
|
@@ -3735,11 +3752,11 @@ const workflow = {
|
|
|
3735
3752
|
});
|
|
3736
3753
|
},
|
|
3737
3754
|
/**
|
|
3738
|
-
* Edit a declared-editable
|
|
3755
|
+
* Edit a declared-editable field slot directly — reassign, reschedule,
|
|
3739
3756
|
* claim-by-hand, append to a running log — through the generic edit seam,
|
|
3740
3757
|
* instead of a bespoke action per field. Soft-gates on the slot's declared
|
|
3741
3758
|
* editability (the same projection a UI renders), applies the edit as a
|
|
3742
|
-
* `
|
|
3759
|
+
* `field.*` op (so provenance + history are stamped by the op path),
|
|
3743
3760
|
* refreshes the stage's guards, then cascades — an edit to a value a
|
|
3744
3761
|
* transition reads can and should move the instance. Advisory like every
|
|
3745
3762
|
* engine gate; the lake ACL + guards are the only hard locks.
|
|
@@ -3749,7 +3766,7 @@ const workflow = {
|
|
|
3749
3766
|
* UI must bind it to a deliberate boundary (blur / Enter / Save / debounce),
|
|
3750
3767
|
* never an `onChange` per keystroke.
|
|
3751
3768
|
*/
|
|
3752
|
-
|
|
3769
|
+
editField: async (args) => {
|
|
3753
3770
|
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);
|
|
3754
3771
|
return dispatchGatedWrite({
|
|
3755
3772
|
client,
|
|
@@ -3914,7 +3931,7 @@ const workflow = {
|
|
|
3914
3931
|
* Hydrates the instance's snapshot (instance + ancestors + every doc
|
|
3915
3932
|
* declared by a `doc.ref` / `doc.refs` entry in scope), then
|
|
3916
3933
|
* evaluates the supplied GROQ in groq-js against that dataset. The
|
|
3917
|
-
* rendered scope is auto-bound: `$self`, `$
|
|
3934
|
+
* rendered scope is auto-bound: `$self`, `$fields`, `$parent`,
|
|
3918
3935
|
* `$ancestors`, `$stage`, `$now`, `$effects`, `$tasks`,
|
|
3919
3936
|
* `$allTasksDone`, `$anyTaskFailed` — ids in GDR URI form to match
|
|
3920
3937
|
* the snapshot's keying.
|
|
@@ -4069,7 +4086,7 @@ async function drainEffectsInternal(args) {
|
|
|
4069
4086
|
effectHandlers,
|
|
4070
4087
|
missingHandler,
|
|
4071
4088
|
logger
|
|
4072
|
-
} = args, drainerActor = args.access?.actor ??
|
|
4089
|
+
} = args, drainerActor = args.access?.actor ?? engineSystemActor("drainEffects"), completionAccess = {
|
|
4073
4090
|
actor: drainerActor,
|
|
4074
4091
|
...args.access?.grants !== void 0 ? { grants: args.access.grants } : {}
|
|
4075
4092
|
};
|
|
@@ -4247,7 +4264,7 @@ function createInstanceSession(args) {
|
|
|
4247
4264
|
return settleAfterApply(actor, held, ranOps);
|
|
4248
4265
|
});
|
|
4249
4266
|
},
|
|
4250
|
-
|
|
4267
|
+
editField({ target, mode, value }) {
|
|
4251
4268
|
return commit(async (actor, held) => {
|
|
4252
4269
|
assertEditAllowed(await evaluateWith(held, heldGuards), target);
|
|
4253
4270
|
const { grants } = await access(), ranOps = await applyEdit({
|
|
@@ -4309,7 +4326,7 @@ function createEngine(args) {
|
|
|
4309
4326
|
deployDefinitions: (rest) => workflow.deployDefinitions(bind(rest)),
|
|
4310
4327
|
startInstance: (rest) => workflow.startInstance(bind(rest)),
|
|
4311
4328
|
fireAction: (rest) => workflow.fireAction(bind(rest)),
|
|
4312
|
-
|
|
4329
|
+
editField: (rest) => workflow.editField(bind(rest)),
|
|
4313
4330
|
completeEffect: (rest) => workflow.completeEffect(bind(rest)),
|
|
4314
4331
|
tick: (rest) => workflow.tick(bind(rest)),
|
|
4315
4332
|
evaluateInstance: (rest) => evaluateInstance(bind(rest)),
|
|
@@ -4468,9 +4485,9 @@ const HISTORY_DISPLAY = {
|
|
|
4468
4485
|
},
|
|
4469
4486
|
opApplied: {
|
|
4470
4487
|
title: "Op applied",
|
|
4471
|
-
description: "An inline
|
|
4488
|
+
description: "An inline field-mutation op (field.set / field.append / status.set / etc.) ran during an action commit."
|
|
4472
4489
|
}
|
|
4473
|
-
},
|
|
4490
|
+
}, FIELD_SLOT_DISPLAY = {
|
|
4474
4491
|
"doc.ref": {
|
|
4475
4492
|
title: "Document reference",
|
|
4476
4493
|
description: "Single GDR pointer at a document in this or another resource."
|
|
@@ -4524,23 +4541,23 @@ const HISTORY_DISPLAY = {
|
|
|
4524
4541
|
description: "Ordered list of typed (user|role) assignees."
|
|
4525
4542
|
}
|
|
4526
4543
|
}, OP_DISPLAY = {
|
|
4527
|
-
"
|
|
4528
|
-
title: "Set
|
|
4529
|
-
description: "Overwrite a
|
|
4544
|
+
"field.set": {
|
|
4545
|
+
title: "Set field entry",
|
|
4546
|
+
description: "Overwrite a field entry's value with a resolved Source."
|
|
4530
4547
|
},
|
|
4531
|
-
"
|
|
4532
|
-
title: "Unset
|
|
4533
|
-
description: "Reset a
|
|
4548
|
+
"field.unset": {
|
|
4549
|
+
title: "Unset field entry",
|
|
4550
|
+
description: "Reset a field entry to its default (null for scalars, [] for array kinds)."
|
|
4534
4551
|
},
|
|
4535
|
-
"
|
|
4536
|
-
title: "Append to
|
|
4552
|
+
"field.append": {
|
|
4553
|
+
title: "Append to field entry",
|
|
4537
4554
|
description: "Push a resolved item onto an array-kind entry (notes, checklist, assignees, refs)."
|
|
4538
4555
|
},
|
|
4539
|
-
"
|
|
4556
|
+
"field.updateWhere": {
|
|
4540
4557
|
title: "Update matching rows",
|
|
4541
4558
|
description: "Merge fields into rows of an array-kind entry that match the predicate."
|
|
4542
4559
|
},
|
|
4543
|
-
"
|
|
4560
|
+
"field.removeWhere": {
|
|
4544
4561
|
title: "Remove matching rows",
|
|
4545
4562
|
description: "Drop rows of an array-kind entry that match the predicate."
|
|
4546
4563
|
},
|
|
@@ -4584,11 +4601,40 @@ const HISTORY_DISPLAY = {
|
|
|
4584
4601
|
},
|
|
4585
4602
|
[WORKFLOW_INSTANCE_TYPE]: {
|
|
4586
4603
|
title: "Workflow instance",
|
|
4587
|
-
description: "A running (or finished) workflow against its declared
|
|
4604
|
+
description: "A running (or finished) workflow against its declared fields."
|
|
4605
|
+
}
|
|
4606
|
+
}, TASK_KIND_DISPLAY = {
|
|
4607
|
+
user: {
|
|
4608
|
+
title: "User task",
|
|
4609
|
+
description: "A person acts via the app \u2014 renders action buttons ranked by outcome."
|
|
4610
|
+
},
|
|
4611
|
+
service: {
|
|
4612
|
+
title: "Service task",
|
|
4613
|
+
description: "An automated system or effect does the work \u2014 renders effect drain status."
|
|
4614
|
+
},
|
|
4615
|
+
script: {
|
|
4616
|
+
title: "Script task",
|
|
4617
|
+
description: "The engine runs a step inline and resolves on activation \u2014 an audit row."
|
|
4618
|
+
},
|
|
4619
|
+
manual: {
|
|
4620
|
+
title: "Manual task",
|
|
4621
|
+
description: "Work done off-system, acknowledged by a person or verified by a predicate \u2014 renders an instruction card with a deep-link."
|
|
4622
|
+
},
|
|
4623
|
+
receive: {
|
|
4624
|
+
title: "Receive task",
|
|
4625
|
+
description: 'No actor; the engine waits on a condition \u2014 renders "waiting until \u2026", no buttons.'
|
|
4626
|
+
}
|
|
4627
|
+
}, DRIVER_KIND_DISPLAY = {
|
|
4628
|
+
person: { title: "Person", description: "A human fired this action." },
|
|
4629
|
+
agent: { title: "Agent", description: "An AI agent fired this action." },
|
|
4630
|
+
service: { title: "Service", description: "An external system or Function fired this action." },
|
|
4631
|
+
engine: {
|
|
4632
|
+
title: "Engine",
|
|
4633
|
+
description: "The workflow engine itself drove this \u2014 a gate, machine step, or housekeeping."
|
|
4588
4634
|
}
|
|
4589
4635
|
}, DISPLAY = {
|
|
4590
4636
|
...HISTORY_DISPLAY,
|
|
4591
|
-
...
|
|
4637
|
+
...FIELD_SLOT_DISPLAY,
|
|
4592
4638
|
...OP_DISPLAY,
|
|
4593
4639
|
...EFFECTS_CONTEXT_DISPLAY,
|
|
4594
4640
|
...AUTHORING_DISPLAY
|
|
@@ -4600,18 +4646,22 @@ function displayDescription(typeKey) {
|
|
|
4600
4646
|
if (typeKey)
|
|
4601
4647
|
return DISPLAY[typeKey]?.description;
|
|
4602
4648
|
}
|
|
4649
|
+
exports.DRIVER_KINDS = schema.DRIVER_KINDS;
|
|
4650
|
+
exports.TASK_KINDS = schema.TASK_KINDS;
|
|
4603
4651
|
exports.WORKFLOW_DEFINITION_TYPE = schema.WORKFLOW_DEFINITION_TYPE;
|
|
4604
4652
|
exports.isStartableDefinition = schema.isStartableDefinition;
|
|
4605
4653
|
exports.AUTHORING_DISPLAY = AUTHORING_DISPLAY;
|
|
4606
4654
|
exports.ActionDisabledError = ActionDisabledError;
|
|
4607
4655
|
exports.ActionParamsInvalidError = ActionParamsInvalidError;
|
|
4608
4656
|
exports.CascadeLimitError = CascadeLimitError;
|
|
4609
|
-
exports.
|
|
4657
|
+
exports.ConcurrentEditFieldError = ConcurrentEditFieldError;
|
|
4610
4658
|
exports.ConcurrentFireActionError = ConcurrentFireActionError;
|
|
4611
4659
|
exports.DEFAULT_CONTENT_PERSPECTIVE = DEFAULT_CONTENT_PERSPECTIVE;
|
|
4612
4660
|
exports.DISPLAY = DISPLAY;
|
|
4661
|
+
exports.DRIVER_KIND_DISPLAY = DRIVER_KIND_DISPLAY;
|
|
4613
4662
|
exports.EFFECTS_CONTEXT_DISPLAY = EFFECTS_CONTEXT_DISPLAY;
|
|
4614
|
-
exports.
|
|
4663
|
+
exports.EditFieldDeniedError = EditFieldDeniedError;
|
|
4664
|
+
exports.FIELD_SLOT_DISPLAY = FIELD_SLOT_DISPLAY;
|
|
4615
4665
|
exports.GUARD_DOC_TYPE = GUARD_DOC_TYPE;
|
|
4616
4666
|
exports.GUARD_LIFTED_PREDICATE = GUARD_LIFTED_PREDICATE;
|
|
4617
4667
|
exports.GUARD_OWNER = GUARD_OWNER;
|
|
@@ -4619,8 +4669,8 @@ exports.HISTORY_DISPLAY = HISTORY_DISPLAY;
|
|
|
4619
4669
|
exports.MutationGuardDeniedError = MutationGuardDeniedError;
|
|
4620
4670
|
exports.OP_DISPLAY = OP_DISPLAY;
|
|
4621
4671
|
exports.PartialGuardDeployError = PartialGuardDeployError;
|
|
4622
|
-
exports.
|
|
4623
|
-
exports.
|
|
4672
|
+
exports.RequiredFieldNotProvidedError = RequiredFieldNotProvidedError;
|
|
4673
|
+
exports.TASK_KIND_DISPLAY = TASK_KIND_DISPLAY;
|
|
4624
4674
|
exports.WORKFLOW_INSTANCE_TYPE = WORKFLOW_INSTANCE_TYPE;
|
|
4625
4675
|
exports.WorkflowStateDivergedError = WorkflowStateDivergedError;
|
|
4626
4676
|
exports.abortReason = abortReason;
|
|
@@ -4635,11 +4685,13 @@ exports.datasetResourceParts = datasetResourceParts;
|
|
|
4635
4685
|
exports.defaultLoggerFactory = defaultLoggerFactory;
|
|
4636
4686
|
exports.denyingGuards = denyingGuards;
|
|
4637
4687
|
exports.deployStageGuards = deployStageGuards;
|
|
4688
|
+
exports.deriveTaskKind = deriveTaskKind;
|
|
4638
4689
|
exports.diagnoseInputFromEvaluation = diagnoseInputFromEvaluation;
|
|
4639
4690
|
exports.diagnoseInstance = diagnoseInstance;
|
|
4640
4691
|
exports.diffEntry = diffEntry;
|
|
4641
4692
|
exports.displayDescription = displayDescription;
|
|
4642
4693
|
exports.displayTitle = displayTitle;
|
|
4694
|
+
exports.driverKind = driverKind;
|
|
4643
4695
|
exports.effectsContextMap = effectsContextMap;
|
|
4644
4696
|
exports.evaluateFromSnapshot = evaluateFromSnapshot;
|
|
4645
4697
|
exports.evaluateMutationGuard = evaluateMutationGuard;
|
|
@@ -4671,6 +4723,7 @@ exports.stripSystemFields = stripSystemFields;
|
|
|
4671
4723
|
exports.subscriptionDocument = subscriptionDocument;
|
|
4672
4724
|
exports.subscriptionDocumentsForInstance = subscriptionDocumentsForInstance;
|
|
4673
4725
|
exports.tagScopeFilter = tagScopeFilter;
|
|
4726
|
+
exports.taskKind = taskKind;
|
|
4674
4727
|
exports.validateDefinition = validateDefinition;
|
|
4675
4728
|
exports.validateTag = validateTag;
|
|
4676
4729
|
exports.verdictGuardsForInstance = verdictGuardsForInstance;
|