@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.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parse, evaluate } from "groq-js";
|
|
2
2
|
import { actorFulfillsRole, isTerminalTaskStatus, WORKFLOW_DEFINITION_TYPE, andConditions, DOCUMENT_VALUE_PERMISSIONS } from "./_chunks-es/schema.js";
|
|
3
|
-
import { isStartableDefinition } from "./_chunks-es/schema.js";
|
|
3
|
+
import { DRIVER_KINDS, TASK_KINDS, isStartableDefinition } from "./_chunks-es/schema.js";
|
|
4
4
|
import * as v from "valibot";
|
|
5
5
|
function findOpenStageEntry(host) {
|
|
6
6
|
return host.stages.find((s) => s.name === host.currentStage && s.exitedAt === void 0);
|
|
@@ -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
|
-
|
|
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
|
|
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,15 +134,15 @@ 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
|
|
137
|
+
function scopedFieldOverlay(instance, snapshot, taskName) {
|
|
138
138
|
const stageEntry = findOpenStageEntry(instance);
|
|
139
139
|
if (stageEntry === void 0) return {};
|
|
140
|
-
const
|
|
141
|
-
return { ...
|
|
140
|
+
const stageFields = renderedFields(stageEntry.fields ?? [], snapshot), task = taskName ? stageEntry.tasks.find((t) => t.name === taskName) : void 0;
|
|
141
|
+
return { ...stageFields, ...renderedFields(task?.fields ?? [], snapshot) };
|
|
142
142
|
}
|
|
143
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)?.
|
|
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
147
|
(a) => a.type === "user" ? a.id === actor.id : actorFulfillsRole(actor.roles, a.role, roleAliases)
|
|
148
148
|
);
|
|
@@ -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
|
-
|
|
172
|
+
fields: stripFieldsForLake(params.fields)
|
|
173
173
|
};
|
|
174
174
|
}
|
|
175
|
-
function
|
|
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(
|
|
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] =
|
|
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
|
|
201
|
+
const FIELD_READ = /^\$fields\.(\w+)(?:\.(.+))?$/, EFFECTS_READ = /^\$effects\['([^']+)'\](?:\.(.+))?$/;
|
|
202
202
|
function isGuardReadExpr(expr) {
|
|
203
|
-
return expr === "$self" || expr === "$now" ||
|
|
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
|
|
209
|
-
if (
|
|
210
|
-
const value = (ctx.instance.
|
|
211
|
-
return
|
|
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", "$
|
|
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.
|
|
270
|
-
v2.checkEntry(entry, `workflow.
|
|
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 +
|
|
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", "$
|
|
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.
|
|
307
|
-
v2.checkEntry(entry, `stage "${stage.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.
|
|
328
|
-
v2.checkEntry(entry, `${where}.
|
|
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)?.
|
|
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
|
|
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
|
|
620
|
+
`Required init fields${where} was not provided:
|
|
621
621
|
${lines}
|
|
622
|
-
Provide each via \`
|
|
623
|
-
), this.name = "
|
|
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,15 +663,15 @@ 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
|
|
666
|
+
class ConcurrentEditFieldError extends Error {
|
|
667
667
|
instanceId;
|
|
668
668
|
target;
|
|
669
669
|
attempts;
|
|
670
670
|
constructor(args) {
|
|
671
|
-
const where = args.target.task !== void 0 ? `${args.target.task}.${args.target.
|
|
671
|
+
const where = args.target.task !== void 0 ? `${args.target.task}.${args.target.field}` : args.target.field;
|
|
672
672
|
super(
|
|
673
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 = "
|
|
674
|
+
), this.name = "ConcurrentEditFieldError", this.instanceId = args.instanceId, this.target = args.target, this.attempts = args.attempts;
|
|
675
675
|
}
|
|
676
676
|
}
|
|
677
677
|
class CascadeLimitError extends Error {
|
|
@@ -719,10 +719,10 @@ function collectWatchRefs(instance) {
|
|
|
719
719
|
return [
|
|
720
720
|
gdrRef(instance.workflowResource, instance._id, instance._type),
|
|
721
721
|
...instance.ancestors,
|
|
722
|
-
...entryDocRefs(instance.
|
|
723
|
-
...entryDocRefs(stage?.
|
|
724
|
-
...entryReleaseRefs(instance.
|
|
725
|
-
...entryReleaseRefs(stage?.
|
|
722
|
+
...entryDocRefs(instance.fields),
|
|
723
|
+
...entryDocRefs(stage?.fields),
|
|
724
|
+
...entryReleaseRefs(instance.fields),
|
|
725
|
+
...entryReleaseRefs(stage?.fields)
|
|
726
726
|
];
|
|
727
727
|
}
|
|
728
728
|
function readsRaw(ref) {
|
|
@@ -746,21 +746,21 @@ function subscriptionDocumentsForInstance(instance) {
|
|
|
746
746
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
747
747
|
};
|
|
748
748
|
}
|
|
749
|
-
function entryDocRefs(
|
|
750
|
-
return
|
|
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) : [] : []);
|
|
751
751
|
}
|
|
752
|
-
function entryReleaseRefs(
|
|
753
|
-
return
|
|
752
|
+
function entryReleaseRefs(entries) {
|
|
753
|
+
return fieldEntries(entries).flatMap(
|
|
754
754
|
(entry) => entry._type === "release.ref" && isGdr(entry.value) ? [entry.value] : []
|
|
755
755
|
);
|
|
756
756
|
}
|
|
757
|
-
function entrySubjectRefs(
|
|
758
|
-
return
|
|
757
|
+
function entrySubjectRefs(entries) {
|
|
758
|
+
return fieldEntries(entries).flatMap(
|
|
759
759
|
(entry) => (entry._type === "doc.ref" || entry._type === "release.ref") && isGdr(entry.value) ? [entry.value] : []
|
|
760
760
|
);
|
|
761
761
|
}
|
|
762
|
-
function
|
|
763
|
-
return Array.isArray(
|
|
762
|
+
function fieldEntries(entries) {
|
|
763
|
+
return Array.isArray(entries) ? entries.filter(
|
|
764
764
|
(entry) => !!entry && typeof entry == "object"
|
|
765
765
|
) : [];
|
|
766
766
|
}
|
|
@@ -806,8 +806,8 @@ async function readDoc(client, id, perspective) {
|
|
|
806
806
|
function resourceFromParsed(parsed) {
|
|
807
807
|
return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
|
|
808
808
|
}
|
|
809
|
-
function collectEntryDocUris(
|
|
810
|
-
return entryDocRefs(
|
|
809
|
+
function collectEntryDocUris(resolvedFieldEntries) {
|
|
810
|
+
return entryDocRefs(resolvedFieldEntries).map((ref) => ref.id);
|
|
811
811
|
}
|
|
812
812
|
const resourceKey = (r) => `${r.type}.${r.id}`;
|
|
813
813
|
function guardsForResource(client) {
|
|
@@ -825,8 +825,8 @@ function verdictGuardsForInstance(client, instanceId) {
|
|
|
825
825
|
}
|
|
826
826
|
async function guardsForInstance(args) {
|
|
827
827
|
const { client, clientForGdr, instance } = args, stateUris = [
|
|
828
|
-
...collectEntryDocUris(instance.
|
|
829
|
-
...instance.stages.flatMap((stage) => collectEntryDocUris(stage.
|
|
828
|
+
...collectEntryDocUris(instance.fields),
|
|
829
|
+
...instance.stages.flatMap((stage) => collectEntryDocUris(stage.fields))
|
|
830
830
|
], clients = resourceClientMap(
|
|
831
831
|
instance.workflowResource,
|
|
832
832
|
client,
|
|
@@ -858,16 +858,16 @@ function guardIdRefs(definition) {
|
|
|
858
858
|
);
|
|
859
859
|
}
|
|
860
860
|
function staticIdRefGdr(idRef, stage, definition) {
|
|
861
|
-
const read = /^\$
|
|
861
|
+
const read = /^\$fields\.([\w-]+)/.exec(idRef);
|
|
862
862
|
if (read === null) return;
|
|
863
863
|
const source = entriesInScope(stage, definition).find((e) => e.name === read[1])?.source;
|
|
864
864
|
return source?.type === "literal" ? gdrFromValue(source.value) : void 0;
|
|
865
865
|
}
|
|
866
866
|
function entriesInScope(stage, definition) {
|
|
867
867
|
return [
|
|
868
|
-
...definition.
|
|
869
|
-
...stage.
|
|
870
|
-
...(stage.tasks ?? []).flatMap((task) => task.
|
|
868
|
+
...definition.fields ?? [],
|
|
869
|
+
...stage.fields ?? [],
|
|
870
|
+
...(stage.tasks ?? []).flatMap((task) => task.fields ?? [])
|
|
871
871
|
];
|
|
872
872
|
}
|
|
873
873
|
function gdrFromValue(value) {
|
|
@@ -1017,15 +1017,15 @@ async function runGroq(groq, params, snapshot) {
|
|
|
1017
1017
|
const tree = parse(groq, { params });
|
|
1018
1018
|
return (await evaluate(tree, { dataset: snapshot.docs, params })).get();
|
|
1019
1019
|
}
|
|
1020
|
-
class
|
|
1020
|
+
class FieldValueShapeError extends Error {
|
|
1021
1021
|
entryType;
|
|
1022
1022
|
entryName;
|
|
1023
1023
|
issues;
|
|
1024
1024
|
constructor(args) {
|
|
1025
1025
|
const issueText = args.issues.join("; ");
|
|
1026
1026
|
super(
|
|
1027
|
-
`
|
|
1028
|
-
), this.name = "
|
|
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;
|
|
1029
1029
|
}
|
|
1030
1030
|
}
|
|
1031
1031
|
const GdrShape = v.looseObject({
|
|
@@ -1092,35 +1092,35 @@ const GdrShape = v.looseObject({
|
|
|
1092
1092
|
function isAppendable(entryType) {
|
|
1093
1093
|
return entryType in itemSchemas;
|
|
1094
1094
|
}
|
|
1095
|
-
function
|
|
1095
|
+
function validateFieldValue(args) {
|
|
1096
1096
|
const schema = valueSchemas[args.entryType];
|
|
1097
1097
|
if (schema === void 0)
|
|
1098
|
-
throw new
|
|
1098
|
+
throw new FieldValueShapeError({
|
|
1099
1099
|
entryType: args.entryType,
|
|
1100
1100
|
entryName: args.entryName,
|
|
1101
|
-
issues: [`unknown
|
|
1101
|
+
issues: [`unknown field entry type ${args.entryType}`],
|
|
1102
1102
|
mode: "value"
|
|
1103
1103
|
});
|
|
1104
1104
|
const result = v.safeParse(schema, args.value);
|
|
1105
1105
|
if (!result.success)
|
|
1106
|
-
throw new
|
|
1106
|
+
throw new FieldValueShapeError({
|
|
1107
1107
|
entryType: args.entryType,
|
|
1108
1108
|
entryName: args.entryName,
|
|
1109
1109
|
issues: formatIssues(result.issues),
|
|
1110
1110
|
mode: "value"
|
|
1111
1111
|
});
|
|
1112
1112
|
}
|
|
1113
|
-
function
|
|
1113
|
+
function validateFieldAppendItem(args) {
|
|
1114
1114
|
if (!isAppendable(args.entryType))
|
|
1115
|
-
throw new
|
|
1115
|
+
throw new FieldValueShapeError({
|
|
1116
1116
|
entryType: args.entryType,
|
|
1117
1117
|
entryName: args.entryName,
|
|
1118
|
-
issues: [`
|
|
1118
|
+
issues: [`field entry type ${args.entryType} does not support append`],
|
|
1119
1119
|
mode: "item"
|
|
1120
1120
|
});
|
|
1121
1121
|
const schema = itemSchemas[args.entryType], result = v.safeParse(schema, args.item);
|
|
1122
1122
|
if (!result.success)
|
|
1123
|
-
throw new
|
|
1123
|
+
throw new FieldValueShapeError({
|
|
1124
1124
|
entryType: args.entryType,
|
|
1125
1125
|
entryName: args.entryName,
|
|
1126
1126
|
issues: formatIssues(result.issues),
|
|
@@ -1133,7 +1133,7 @@ function formatIssues(issues) {
|
|
|
1133
1133
|
return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${i.message}`;
|
|
1134
1134
|
});
|
|
1135
1135
|
}
|
|
1136
|
-
function
|
|
1136
|
+
function derivePerspectiveFromFields(entries) {
|
|
1137
1137
|
if (entries !== void 0) {
|
|
1138
1138
|
for (const entry of entries)
|
|
1139
1139
|
if (entry._type === "release.ref" && entry.value !== null) {
|
|
@@ -1143,24 +1143,24 @@ function derivePerspectiveFromState(entries) {
|
|
|
1143
1143
|
}
|
|
1144
1144
|
}
|
|
1145
1145
|
}
|
|
1146
|
-
async function
|
|
1147
|
-
const { entryDefs,
|
|
1146
|
+
async function resolveDeclaredFields(args) {
|
|
1147
|
+
const { entryDefs, initialFields, ctx, randomKey: randomKey2 } = args;
|
|
1148
1148
|
if (entryDefs === void 0 || entryDefs.length === 0) return [];
|
|
1149
|
-
assertRequiredInitProvided(entryDefs,
|
|
1149
|
+
assertRequiredInitProvided(entryDefs, initialFields, ctx.definitionName);
|
|
1150
1150
|
const out = [];
|
|
1151
1151
|
for (const entry of entryDefs) {
|
|
1152
|
-
const scopedCtx = { ...ctx,
|
|
1153
|
-
out.push(await resolveOneEntry(entry,
|
|
1152
|
+
const scopedCtx = { ...ctx, resolvedFields: out };
|
|
1153
|
+
out.push(await resolveOneEntry(entry, initialFields, scopedCtx, randomKey2));
|
|
1154
1154
|
}
|
|
1155
1155
|
return out;
|
|
1156
1156
|
}
|
|
1157
|
-
function assertRequiredInitProvided(entryDefs,
|
|
1157
|
+
function assertRequiredInitProvided(entryDefs, initialFields, definitionName) {
|
|
1158
1158
|
const missing = entryDefs.filter((entry) => entry.required === !0 && entry.source.type === "init").filter((entry) => {
|
|
1159
|
-
const match =
|
|
1159
|
+
const match = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1160
1160
|
return match === void 0 || match.value === void 0 || match.value === null;
|
|
1161
1161
|
}).map((entry) => ({ name: entry.name, type: entry.type }));
|
|
1162
1162
|
if (missing.length !== 0)
|
|
1163
|
-
throw new
|
|
1163
|
+
throw new RequiredFieldNotProvidedError({
|
|
1164
1164
|
...definitionName !== void 0 ? { definition: definitionName } : {},
|
|
1165
1165
|
missing
|
|
1166
1166
|
});
|
|
@@ -1174,18 +1174,18 @@ const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set([
|
|
|
1174
1174
|
function defaultEntryValue(entryType) {
|
|
1175
1175
|
return ARRAY_SLOT_TYPES.has(entryType) ? [] : null;
|
|
1176
1176
|
}
|
|
1177
|
-
function resolveInitValue(entry,
|
|
1178
|
-
const initMatch =
|
|
1177
|
+
function resolveInitValue(entry, initialFields, defaultValue) {
|
|
1178
|
+
const initMatch = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1179
1179
|
return initMatch === void 0 ? defaultValue : (assertInitValueShape(entry, initMatch.value), initMatch.value);
|
|
1180
1180
|
}
|
|
1181
|
-
function
|
|
1182
|
-
const target = (source.scope === "workflow" ? ctx.
|
|
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;
|
|
1183
1183
|
return (source.path !== void 0 ? getPath(targetValue, source.path) : targetValue) ?? defaultValue;
|
|
1184
1184
|
}
|
|
1185
1185
|
async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
1186
1186
|
const params = paramsForLake({
|
|
1187
1187
|
self: ctx.selfId ?? null,
|
|
1188
|
-
|
|
1188
|
+
fields: fieldMapFromResolved(ctx.resolvedFields ?? []),
|
|
1189
1189
|
stage: ctx.stageName ?? null,
|
|
1190
1190
|
task: ctx.taskName ?? null,
|
|
1191
1191
|
now: ctx.now,
|
|
@@ -1201,9 +1201,9 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
|
1201
1201
|
);
|
|
1202
1202
|
}
|
|
1203
1203
|
}
|
|
1204
|
-
async function resolveEntryValue(entry,
|
|
1204
|
+
async function resolveEntryValue(entry, initialFields, ctx, defaultValue) {
|
|
1205
1205
|
const source = entry.source;
|
|
1206
|
-
return source.type === "init" ? resolveInitValue(entry,
|
|
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;
|
|
1207
1207
|
}
|
|
1208
1208
|
function buildResolvedEntry(entry, value, _key, now) {
|
|
1209
1209
|
const titleProp = entry.title !== void 0 ? { title: entry.title } : {}, descriptionProp = entry.description !== void 0 ? { description: entry.description } : {};
|
|
@@ -1224,36 +1224,36 @@ function buildResolvedEntry(entry, value, _key, now) {
|
|
|
1224
1224
|
value
|
|
1225
1225
|
};
|
|
1226
1226
|
}
|
|
1227
|
-
async function resolveOneEntry(entry,
|
|
1228
|
-
const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue(entry,
|
|
1229
|
-
return
|
|
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);
|
|
1230
1230
|
}
|
|
1231
|
-
function
|
|
1231
|
+
function fieldMapFromResolved(entries) {
|
|
1232
1232
|
const out = {};
|
|
1233
1233
|
for (const s of entries) out[s.name] = s.value;
|
|
1234
1234
|
return out;
|
|
1235
1235
|
}
|
|
1236
1236
|
function assertInitValueShape(entry, value) {
|
|
1237
1237
|
if (entry.type === "doc.ref") {
|
|
1238
|
-
assertGdrShape(value, `
|
|
1238
|
+
assertGdrShape(value, `field entry "${entry.name}" (doc.ref)`);
|
|
1239
1239
|
return;
|
|
1240
1240
|
}
|
|
1241
1241
|
if (entry.type === "release.ref") {
|
|
1242
|
-
assertGdrShape(value, `
|
|
1242
|
+
assertGdrShape(value, `field entry "${entry.name}" (release.ref)`);
|
|
1243
1243
|
const v2 = value;
|
|
1244
1244
|
if (typeof v2.releaseName != "string" || v2.releaseName.length === 0)
|
|
1245
1245
|
throw new Error(
|
|
1246
|
-
`Invalid init value for
|
|
1246
|
+
`Invalid init value for field entry "${entry.name}" (release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(v2.releaseName)}.`
|
|
1247
1247
|
);
|
|
1248
1248
|
return;
|
|
1249
1249
|
}
|
|
1250
1250
|
if (entry.type === "doc.refs") {
|
|
1251
1251
|
if (!Array.isArray(value))
|
|
1252
1252
|
throw new Error(
|
|
1253
|
-
`Invalid init value for
|
|
1253
|
+
`Invalid init value for field entry "${entry.name}" (doc.refs): expected an array of GDRs, got ${typeof value}.`
|
|
1254
1254
|
);
|
|
1255
1255
|
for (const [i, item] of value.entries())
|
|
1256
|
-
assertGdrShape(item, `
|
|
1256
|
+
assertGdrShape(item, `field entry "${entry.name}" (doc.refs) item [${i}]`);
|
|
1257
1257
|
}
|
|
1258
1258
|
}
|
|
1259
1259
|
function assertGdrShape(value, context) {
|
|
@@ -1312,12 +1312,12 @@ async function loadContext(client, instanceId, options) {
|
|
|
1312
1312
|
return { client, clientForGdr, clock, now: clock(), instance, definition, snapshot };
|
|
1313
1313
|
}
|
|
1314
1314
|
async function ctxConditionParams(ctx, opts) {
|
|
1315
|
-
const base = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }),
|
|
1316
|
-
...base.
|
|
1317
|
-
...
|
|
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)
|
|
1318
1318
|
}, params = {
|
|
1319
1319
|
...base,
|
|
1320
|
-
|
|
1320
|
+
fields,
|
|
1321
1321
|
actor: opts?.actor,
|
|
1322
1322
|
assigned: opts?.taskName !== void 0 ? assignedFor(ctx.instance, opts.taskName, opts?.actor, ctx.definition.roleAliases) : !1,
|
|
1323
1323
|
...opts?.vars
|
|
@@ -1356,11 +1356,11 @@ function findStage(definition, stageName) {
|
|
|
1356
1356
|
throw new Error(`Stage "${stageName}" not found in definition ${definition.name}`);
|
|
1357
1357
|
return stage;
|
|
1358
1358
|
}
|
|
1359
|
-
async function
|
|
1360
|
-
const { client, instance, stage, now,
|
|
1361
|
-
return
|
|
1362
|
-
entryDefs: stage.
|
|
1363
|
-
|
|
1359
|
+
async function resolveStageFieldEntries(args) {
|
|
1360
|
+
const { client, instance, stage, now, initialFields } = args;
|
|
1361
|
+
return resolveDeclaredFields({
|
|
1362
|
+
entryDefs: stage.fields,
|
|
1363
|
+
initialFields: initialFields ?? [],
|
|
1364
1364
|
ctx: {
|
|
1365
1365
|
client,
|
|
1366
1366
|
now,
|
|
@@ -1368,19 +1368,19 @@ async function resolveStageStateEntries(args) {
|
|
|
1368
1368
|
tag: instance.tag,
|
|
1369
1369
|
stageName: stage.name,
|
|
1370
1370
|
workflowResource: instance.workflowResource,
|
|
1371
|
-
// Allow stage-scope entries' `source: { type: "
|
|
1372
|
-
// "workflow", ... }` to see the already-resolved workflow-scope
|
|
1373
|
-
|
|
1371
|
+
// Allow stage-scope entries' `source: { type: "fieldRead", scope:
|
|
1372
|
+
// "workflow", ... }` to see the already-resolved workflow-scope fields.
|
|
1373
|
+
workflowFields: instance.fields ?? [],
|
|
1374
1374
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
1375
1375
|
},
|
|
1376
1376
|
randomKey
|
|
1377
1377
|
});
|
|
1378
1378
|
}
|
|
1379
|
-
async function
|
|
1379
|
+
async function resolveTaskFieldEntries(args) {
|
|
1380
1380
|
const { client, instance, stage, task, now } = args;
|
|
1381
|
-
return
|
|
1382
|
-
entryDefs: task.
|
|
1383
|
-
|
|
1381
|
+
return resolveDeclaredFields({
|
|
1382
|
+
entryDefs: task.fields,
|
|
1383
|
+
initialFields: [],
|
|
1384
1384
|
ctx: {
|
|
1385
1385
|
client,
|
|
1386
1386
|
now,
|
|
@@ -1389,7 +1389,7 @@ async function resolveTaskStateEntries(args) {
|
|
|
1389
1389
|
stageName: stage.name,
|
|
1390
1390
|
workflowResource: instance.workflowResource,
|
|
1391
1391
|
taskName: task.name,
|
|
1392
|
-
|
|
1392
|
+
workflowFields: instance.fields ?? [],
|
|
1393
1393
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
1394
1394
|
},
|
|
1395
1395
|
randomKey
|
|
@@ -1471,15 +1471,15 @@ function stageTransitionHistory(args) {
|
|
|
1471
1471
|
function startMutation(instance) {
|
|
1472
1472
|
return {
|
|
1473
1473
|
currentStage: instance.currentStage,
|
|
1474
|
-
// Deep-ish copy. Per-scope
|
|
1474
|
+
// Deep-ish copy. Per-scope field entry arrays need their own copies
|
|
1475
1475
|
// so ops can mutate without leaking into the source.
|
|
1476
|
-
|
|
1476
|
+
fields: (instance.fields ?? []).map((s) => ({ ...s })),
|
|
1477
1477
|
stages: instance.stages.map((s) => ({
|
|
1478
1478
|
...s,
|
|
1479
|
-
|
|
1479
|
+
fields: (s.fields ?? []).map((entry) => ({ ...entry })),
|
|
1480
1480
|
tasks: s.tasks.map((t) => ({
|
|
1481
1481
|
...t,
|
|
1482
|
-
...t.
|
|
1482
|
+
...t.fields !== void 0 ? { fields: t.fields.map((entry) => ({ ...entry })) } : {}
|
|
1483
1483
|
}))
|
|
1484
1484
|
})),
|
|
1485
1485
|
pendingEffects: [...instance.pendingEffects],
|
|
@@ -1495,7 +1495,7 @@ function startMutation(instance) {
|
|
|
1495
1495
|
function instanceStateFields(src) {
|
|
1496
1496
|
const fields = {
|
|
1497
1497
|
currentStage: src.currentStage,
|
|
1498
|
-
|
|
1498
|
+
fields: src.fields,
|
|
1499
1499
|
stages: src.stages,
|
|
1500
1500
|
pendingEffects: src.pendingEffects,
|
|
1501
1501
|
effectHistory: src.effectHistory,
|
|
@@ -1508,7 +1508,7 @@ function materializeInstance(base, mutation) {
|
|
|
1508
1508
|
return {
|
|
1509
1509
|
...base,
|
|
1510
1510
|
currentStage: mutation.currentStage,
|
|
1511
|
-
|
|
1511
|
+
fields: mutation.fields,
|
|
1512
1512
|
stages: mutation.stages,
|
|
1513
1513
|
effectsContext: mutation.effectsContext
|
|
1514
1514
|
};
|
|
@@ -1630,7 +1630,7 @@ function buildInstanceBase(args) {
|
|
|
1630
1630
|
definition: args.definitionName,
|
|
1631
1631
|
pinnedVersion: args.pinnedVersion,
|
|
1632
1632
|
definitionSnapshot: JSON.stringify(args.definition),
|
|
1633
|
-
|
|
1633
|
+
fields: args.fields,
|
|
1634
1634
|
effectsContext: args.effectsContext,
|
|
1635
1635
|
ancestors: args.ancestors,
|
|
1636
1636
|
...perspective !== void 0 ? { perspective } : {},
|
|
@@ -1651,12 +1651,16 @@ function buildInstanceBase(args) {
|
|
|
1651
1651
|
lastChangedAt: now
|
|
1652
1652
|
};
|
|
1653
1653
|
}
|
|
1654
|
-
const
|
|
1654
|
+
const ENGINE_ACTOR_ID_PREFIX = "engine.";
|
|
1655
|
+
function engineSystemActor(name) {
|
|
1656
|
+
return { kind: "system", id: `${ENGINE_ACTOR_ID_PREFIX}${name}` };
|
|
1657
|
+
}
|
|
1658
|
+
function isEngineActor(actor) {
|
|
1659
|
+
return actor.kind === "system" && actor.id.startsWith(ENGINE_ACTOR_ID_PREFIX);
|
|
1660
|
+
}
|
|
1661
|
+
const MAX_SPAWN_DEPTH = 6, SUBWORKFLOWS_ALL_DONE = "count($subworkflows[status != 'done']) == 0";
|
|
1655
1662
|
function gateActor(outcome) {
|
|
1656
|
-
return
|
|
1657
|
-
kind: "system",
|
|
1658
|
-
id: outcome === "failed" ? FAIL_WHEN_SYSTEM_ID : COMPLETE_WHEN_SYSTEM_ID
|
|
1659
|
-
};
|
|
1663
|
+
return engineSystemActor(outcome === "failed" ? "failWhen" : "completeWhen");
|
|
1660
1664
|
}
|
|
1661
1665
|
function effectiveCompleteWhen(task) {
|
|
1662
1666
|
return task.completeWhen ?? (task.subworkflows !== void 0 ? SUBWORKFLOWS_ALL_DONE : void 0);
|
|
@@ -1688,7 +1692,7 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1688
1692
|
...actor ? { actor } : {}
|
|
1689
1693
|
}), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
|
|
1690
1694
|
for (const row of rows) {
|
|
1691
|
-
const
|
|
1695
|
+
const initialFields = await projectRowFields(
|
|
1692
1696
|
ctx,
|
|
1693
1697
|
sub,
|
|
1694
1698
|
definition,
|
|
@@ -1699,7 +1703,7 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1699
1703
|
client: ctx.client,
|
|
1700
1704
|
parent: ctx.instance,
|
|
1701
1705
|
definition,
|
|
1702
|
-
|
|
1706
|
+
initialFields,
|
|
1703
1707
|
effectsContext,
|
|
1704
1708
|
actor,
|
|
1705
1709
|
now
|
|
@@ -1712,7 +1716,7 @@ async function resolveSpawnRows(ctx, sub) {
|
|
|
1712
1716
|
const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
|
|
1713
1717
|
let value, discoveryResource;
|
|
1714
1718
|
if (groq.includes("*")) {
|
|
1715
|
-
const routingUri = entrySubjectRefs(ctx.instance.
|
|
1719
|
+
const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
|
|
1716
1720
|
client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
|
|
1717
1721
|
client,
|
|
1718
1722
|
groq,
|
|
@@ -1728,13 +1732,13 @@ async function resolveSpawnRows(ctx, sub) {
|
|
|
1728
1732
|
}
|
|
1729
1733
|
return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
|
|
1730
1734
|
}
|
|
1731
|
-
async function
|
|
1735
|
+
async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
|
|
1732
1736
|
const out = [];
|
|
1733
1737
|
for (const [name, groq] of Object.entries(sub.with ?? {})) {
|
|
1734
|
-
const declared = (childDefinition.
|
|
1738
|
+
const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
|
|
1735
1739
|
if (declared === void 0)
|
|
1736
1740
|
throw new Error(
|
|
1737
|
-
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no
|
|
1741
|
+
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
|
|
1738
1742
|
);
|
|
1739
1743
|
const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
|
|
1740
1744
|
parentResource: ctx.instance.workflowResource,
|
|
@@ -1789,15 +1793,15 @@ async function resolveDefinitionRef(client, ref, tag) {
|
|
|
1789
1793
|
) ?? null;
|
|
1790
1794
|
}
|
|
1791
1795
|
async function prepareChildInstance(args) {
|
|
1792
|
-
const { client, parent, definition,
|
|
1796
|
+
const { client, parent, definition, initialFields, effectsContext, actor, now } = args, childTag = parent.tag, workflowResource = parent.workflowResource, childDocId = `${childTag}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type: WORKFLOW_INSTANCE_TYPE }, ancestors = [
|
|
1793
1797
|
...parent.ancestors,
|
|
1794
1798
|
{
|
|
1795
1799
|
id: selfGdr(parent),
|
|
1796
1800
|
type: WORKFLOW_INSTANCE_TYPE
|
|
1797
1801
|
}
|
|
1798
|
-
], inheritedPerspective = parent.perspective,
|
|
1799
|
-
entryDefs: definition.
|
|
1800
|
-
|
|
1802
|
+
], inheritedPerspective = parent.perspective, childFields = await resolveDeclaredFields({
|
|
1803
|
+
entryDefs: definition.fields,
|
|
1804
|
+
initialFields,
|
|
1801
1805
|
ctx: {
|
|
1802
1806
|
client,
|
|
1803
1807
|
now,
|
|
@@ -1808,7 +1812,7 @@ async function prepareChildInstance(args) {
|
|
|
1808
1812
|
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
|
|
1809
1813
|
},
|
|
1810
1814
|
randomKey
|
|
1811
|
-
}), childPerspective =
|
|
1815
|
+
}), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
|
|
1812
1816
|
([key, value]) => {
|
|
1813
1817
|
if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
|
|
1814
1818
|
if (!isGdrUri(value.id))
|
|
@@ -1827,7 +1831,7 @@ async function prepareChildInstance(args) {
|
|
|
1827
1831
|
definitionName: definition.name,
|
|
1828
1832
|
pinnedVersion: definition.version,
|
|
1829
1833
|
definition,
|
|
1830
|
-
|
|
1834
|
+
fields: childFields,
|
|
1831
1835
|
effectsContext: effectsContextEntries,
|
|
1832
1836
|
ancestors,
|
|
1833
1837
|
perspective: childPerspective,
|
|
@@ -1992,15 +1996,15 @@ function resolveStaticSource(src, ctx) {
|
|
|
1992
1996
|
return { handled: !1 };
|
|
1993
1997
|
}
|
|
1994
1998
|
}
|
|
1995
|
-
const
|
|
1996
|
-
"
|
|
1997
|
-
"
|
|
1998
|
-
"
|
|
1999
|
-
"
|
|
2000
|
-
"
|
|
1999
|
+
const FIELD_OP_TYPES = [
|
|
2000
|
+
"field.set",
|
|
2001
|
+
"field.unset",
|
|
2002
|
+
"field.append",
|
|
2003
|
+
"field.updateWhere",
|
|
2004
|
+
"field.removeWhere"
|
|
2001
2005
|
];
|
|
2002
|
-
function
|
|
2003
|
-
return
|
|
2006
|
+
function isFieldOp(summary) {
|
|
2007
|
+
return FIELD_OP_TYPES.includes(summary.opType);
|
|
2004
2008
|
}
|
|
2005
2009
|
function validateActionParams(action, taskName, callerParams) {
|
|
2006
2010
|
const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
|
|
@@ -2073,23 +2077,23 @@ function opAppliedEntry(args) {
|
|
|
2073
2077
|
}
|
|
2074
2078
|
function applyOp(op, ctx) {
|
|
2075
2079
|
switch (op.type) {
|
|
2076
|
-
case "
|
|
2077
|
-
return
|
|
2078
|
-
case "
|
|
2079
|
-
return
|
|
2080
|
-
case "
|
|
2081
|
-
return
|
|
2082
|
-
case "
|
|
2083
|
-
return
|
|
2084
|
-
case "
|
|
2085
|
-
return
|
|
2080
|
+
case "field.set":
|
|
2081
|
+
return applyFieldSet(op, ctx);
|
|
2082
|
+
case "field.unset":
|
|
2083
|
+
return applyFieldUnset(op, ctx);
|
|
2084
|
+
case "field.append":
|
|
2085
|
+
return applyFieldAppend(op, ctx);
|
|
2086
|
+
case "field.updateWhere":
|
|
2087
|
+
return applyFieldUpdateWhere(op, ctx);
|
|
2088
|
+
case "field.removeWhere":
|
|
2089
|
+
return applyFieldRemoveWhere(op, ctx);
|
|
2086
2090
|
case "status.set":
|
|
2087
2091
|
return applyStatusSet(op, ctx);
|
|
2088
2092
|
}
|
|
2089
2093
|
}
|
|
2090
|
-
function
|
|
2094
|
+
function applyFieldSet(op, ctx) {
|
|
2091
2095
|
const value = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
|
|
2092
|
-
return
|
|
2096
|
+
return validateFieldValue({ entryType: entry._type, entryName: entry.name, value }), setEntryValue(entry, value), { opType: op.type, target: op.target, resolved: { value } };
|
|
2093
2097
|
}
|
|
2094
2098
|
const EMPTY_BY_KIND = {
|
|
2095
2099
|
"doc.refs": [],
|
|
@@ -2097,24 +2101,24 @@ const EMPTY_BY_KIND = {
|
|
|
2097
2101
|
notes: [],
|
|
2098
2102
|
assignees: []
|
|
2099
2103
|
};
|
|
2100
|
-
function
|
|
2104
|
+
function applyFieldUnset(op, ctx) {
|
|
2101
2105
|
const entry = locateEntry(ctx, op.target);
|
|
2102
2106
|
return setEntryValue(entry, EMPTY_BY_KIND[entry._type] ?? null), { opType: op.type, target: op.target };
|
|
2103
2107
|
}
|
|
2104
|
-
function
|
|
2108
|
+
function applyFieldAppend(op, ctx) {
|
|
2105
2109
|
const item = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
|
|
2106
|
-
|
|
2110
|
+
validateFieldAppendItem({ entryType: entry._type, entryName: entry.name, item });
|
|
2107
2111
|
const current = Array.isArray(entry.value) ? entry.value : [];
|
|
2108
2112
|
return setEntryValue(entry, [...current, withRowKey(item)]), { opType: op.type, target: op.target, resolved: { item } };
|
|
2109
2113
|
}
|
|
2110
2114
|
function withRowKey(item) {
|
|
2111
2115
|
return typeof item == "object" && item !== null && !Array.isArray(item) && !("_key" in item) ? { _key: randomKey(), ...item } : item;
|
|
2112
2116
|
}
|
|
2113
|
-
function
|
|
2117
|
+
function applyFieldUpdateWhere(op, ctx) {
|
|
2114
2118
|
const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op), merge = resolveOpSource(op.value, ctx);
|
|
2115
2119
|
if (merge === null || typeof merge != "object" || Array.isArray(merge))
|
|
2116
2120
|
throw new Error(
|
|
2117
|
-
`
|
|
2121
|
+
`field.updateWhere value must resolve to an object of fields to merge (target "${op.target.field}")`
|
|
2118
2122
|
);
|
|
2119
2123
|
return setEntryValue(
|
|
2120
2124
|
entry,
|
|
@@ -2123,7 +2127,7 @@ function applyStateUpdateWhere(op, ctx) {
|
|
|
2123
2127
|
)
|
|
2124
2128
|
), { opType: op.type, target: op.target, resolved: { merge } };
|
|
2125
2129
|
}
|
|
2126
|
-
function
|
|
2130
|
+
function applyFieldRemoveWhere(op, ctx) {
|
|
2127
2131
|
const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op);
|
|
2128
2132
|
return setEntryValue(
|
|
2129
2133
|
entry,
|
|
@@ -2133,7 +2137,7 @@ function applyStateRemoveWhere(op, ctx) {
|
|
|
2133
2137
|
function requireArrayValue(entry, op) {
|
|
2134
2138
|
if (!Array.isArray(entry.value))
|
|
2135
2139
|
throw new Error(
|
|
2136
|
-
`${op.type} target ${op.target.scope}:"${op.target.
|
|
2140
|
+
`${op.type} target ${op.target.scope}:"${op.target.field}" holds a non-array ${entry._type} value \u2014 where-ops operate on array entries`
|
|
2137
2141
|
);
|
|
2138
2142
|
return entry.value;
|
|
2139
2143
|
}
|
|
@@ -2154,19 +2158,19 @@ function setEntryValue(entry, value) {
|
|
|
2154
2158
|
entry.value = value;
|
|
2155
2159
|
}
|
|
2156
2160
|
function locateEntry(ctx, target) {
|
|
2157
|
-
const entry =
|
|
2161
|
+
const entry = fieldHost(ctx, target.scope)?.find((s) => s.name === target.field);
|
|
2158
2162
|
if (entry === void 0)
|
|
2159
2163
|
throw new Error(
|
|
2160
|
-
`Op target ${target.scope}:"${target.
|
|
2164
|
+
`Op target ${target.scope}:"${target.field}" has no resolved field entry on this instance \u2014 the deployed definition and the instance state have diverged`
|
|
2161
2165
|
);
|
|
2162
2166
|
return entry;
|
|
2163
2167
|
}
|
|
2164
|
-
function
|
|
2165
|
-
if (scope === "workflow") return ctx.mutation.
|
|
2168
|
+
function fieldHost(ctx, scope) {
|
|
2169
|
+
if (scope === "workflow") return ctx.mutation.fields;
|
|
2166
2170
|
const stageEntry = findOpenStageEntry(ctx.mutation);
|
|
2167
|
-
if (scope === "stage") return stageEntry?.
|
|
2171
|
+
if (scope === "stage") return stageEntry?.fields;
|
|
2168
2172
|
const task = stageEntry?.tasks.find((t) => t.name === ctx.taskName);
|
|
2169
|
-
return task !== void 0 && task.
|
|
2173
|
+
return task !== void 0 && task.fields === void 0 && (task.fields = []), task?.fields;
|
|
2170
2174
|
}
|
|
2171
2175
|
function resolveOpSource(src, ctx) {
|
|
2172
2176
|
const staticValue = resolveStaticSource(src, ctx);
|
|
@@ -2176,7 +2180,7 @@ function resolveOpSource(src, ctx) {
|
|
|
2176
2180
|
return ctx.self;
|
|
2177
2181
|
case "stage":
|
|
2178
2182
|
return ctx.stage;
|
|
2179
|
-
case "
|
|
2183
|
+
case "fieldRead": {
|
|
2180
2184
|
const value = readEntryFromMutation(ctx, src);
|
|
2181
2185
|
return src.path !== void 0 ? getPath(value, src.path) : value;
|
|
2182
2186
|
}
|
|
@@ -2187,7 +2191,7 @@ function resolveOpSource(src, ctx) {
|
|
|
2187
2191
|
return out;
|
|
2188
2192
|
}
|
|
2189
2193
|
default:
|
|
2190
|
-
throw new Error(`Source type "${src.type}" is a
|
|
2194
|
+
throw new Error(`Source type "${src.type}" is a field-entry origin, not a value`);
|
|
2191
2195
|
}
|
|
2192
2196
|
}
|
|
2193
2197
|
function entryValue(entries, name) {
|
|
@@ -2196,7 +2200,7 @@ function entryValue(entries, name) {
|
|
|
2196
2200
|
function readEntryFromMutation(ctx, src) {
|
|
2197
2201
|
const scopes = src.scope !== void 0 ? [src.scope] : ["stage", "workflow"], stageEntry = findOpenStageEntry(ctx.mutation);
|
|
2198
2202
|
for (const scope of scopes) {
|
|
2199
|
-
const host = scope === "workflow" ? ctx.mutation.
|
|
2203
|
+
const host = scope === "workflow" ? ctx.mutation.fields : stageEntry?.fields, value = entryValue(host, src.field);
|
|
2200
2204
|
if (value !== void 0) return value;
|
|
2201
2205
|
}
|
|
2202
2206
|
}
|
|
@@ -2228,9 +2232,9 @@ async function commitInvoke(ctx, taskName, actor) {
|
|
|
2228
2232
|
}
|
|
2229
2233
|
async function activateTask(ctx, mutation, task, entry, actor) {
|
|
2230
2234
|
const now = ctx.now;
|
|
2231
|
-
if (entry.status = "active", entry.startedAt = now, task.
|
|
2235
|
+
if (entry.status = "active", entry.startedAt = now, task.fields !== void 0 && task.fields.length > 0) {
|
|
2232
2236
|
const stage = findStage(ctx.definition, mutation.currentStage);
|
|
2233
|
-
entry.
|
|
2237
|
+
entry.fields = await resolveTaskFieldEntries({
|
|
2234
2238
|
client: ctx.client,
|
|
2235
2239
|
instance: ctx.instance,
|
|
2236
2240
|
stage,
|
|
@@ -2295,7 +2299,7 @@ function resolveMachineStep(mutation, task, entry, now) {
|
|
|
2295
2299
|
stage: mutation.currentStage,
|
|
2296
2300
|
to: "done",
|
|
2297
2301
|
at: now,
|
|
2298
|
-
actor:
|
|
2302
|
+
actor: engineSystemActor("machineStep")
|
|
2299
2303
|
});
|
|
2300
2304
|
}
|
|
2301
2305
|
async function buildStageTasks(ctx, stage) {
|
|
@@ -2335,7 +2339,7 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
|
|
|
2335
2339
|
_key: randomKey(),
|
|
2336
2340
|
name: nextStage.name,
|
|
2337
2341
|
enteredAt: at,
|
|
2338
|
-
|
|
2342
|
+
fields: await resolveStageFieldEntries({
|
|
2339
2343
|
client: ctx.client,
|
|
2340
2344
|
instance: ctx.instance,
|
|
2341
2345
|
stage: nextStage,
|
|
@@ -2475,7 +2479,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
2475
2479
|
_key: randomKey(),
|
|
2476
2480
|
name: stage.name,
|
|
2477
2481
|
enteredAt: now,
|
|
2478
|
-
|
|
2482
|
+
fields: await resolveStageFieldEntries({ client, instance, stage, now }),
|
|
2479
2483
|
tasks: await buildStageTasks(ctx, stage)
|
|
2480
2484
|
}, committed = await client.patch(instance._id).set({
|
|
2481
2485
|
stages: [initialStageEntry],
|
|
@@ -2713,10 +2717,10 @@ function effectiveEditable(baseline, override) {
|
|
|
2713
2717
|
}
|
|
2714
2718
|
function slotSites(definition, stage) {
|
|
2715
2719
|
const sites = [];
|
|
2716
|
-
for (const entry of definition.
|
|
2717
|
-
for (const entry of stage.
|
|
2720
|
+
for (const entry of definition.fields ?? []) sites.push({ scope: "workflow", entry });
|
|
2721
|
+
for (const entry of stage.fields ?? []) sites.push({ scope: "stage", entry });
|
|
2718
2722
|
for (const task of stage.tasks ?? [])
|
|
2719
|
-
for (const entry of task.
|
|
2723
|
+
for (const entry of task.fields ?? []) sites.push({ scope: "task", task: task.name, entry });
|
|
2720
2724
|
return sites;
|
|
2721
2725
|
}
|
|
2722
2726
|
function toResolvedSlot(site, stage) {
|
|
@@ -2726,7 +2730,7 @@ function toResolvedSlot(site, stage) {
|
|
|
2726
2730
|
name: site.entry.name,
|
|
2727
2731
|
type: site.entry.type,
|
|
2728
2732
|
...site.entry.title !== void 0 ? { title: site.entry.title } : {},
|
|
2729
|
-
ref: { scope: site.scope,
|
|
2733
|
+
ref: { scope: site.scope, field: site.entry.name },
|
|
2730
2734
|
effective: effectiveEditable(site.entry.editable, stage.editable?.[site.entry.name])
|
|
2731
2735
|
};
|
|
2732
2736
|
}
|
|
@@ -2734,7 +2738,7 @@ function editableSlotsInStage(definition, stage) {
|
|
|
2734
2738
|
return slotSites(definition, stage).filter((site) => site.entry.editable !== void 0).map((site) => toResolvedSlot(site, stage));
|
|
2735
2739
|
}
|
|
2736
2740
|
function editTargetMatches(slot, target) {
|
|
2737
|
-
return slot.name !== target.
|
|
2741
|
+
return slot.name !== target.field ? !1 : target.task !== void 0 ? slot.scope === "task" && slot.task === target.task : target.scope !== void 0 ? slot.scope === target.scope : !0;
|
|
2738
2742
|
}
|
|
2739
2743
|
const SCOPE_PRECEDENCE = { task: 0, stage: 1, workflow: 2 };
|
|
2740
2744
|
function resolveEditTarget(args) {
|
|
@@ -2758,17 +2762,17 @@ function slotWindowOpen(instance, slot) {
|
|
|
2758
2762
|
return status === "active" ? { open: !0 } : { open: !1, detail: `task "${slot.task}" is ${status ?? "not active"}` };
|
|
2759
2763
|
}
|
|
2760
2764
|
function readSlotValue(instance, slot) {
|
|
2761
|
-
return
|
|
2765
|
+
return slotFieldHost(instance, slot)?.find((e) => e.name === slot.name)?.value;
|
|
2762
2766
|
}
|
|
2763
|
-
function
|
|
2764
|
-
if (slot.scope === "workflow") return instance.
|
|
2767
|
+
function slotFieldHost(instance, slot) {
|
|
2768
|
+
if (slot.scope === "workflow") return instance.fields;
|
|
2765
2769
|
const stageEntry = findOpenStageEntry(instance);
|
|
2766
|
-
return slot.scope === "stage" ? stageEntry?.
|
|
2770
|
+
return slot.scope === "stage" ? stageEntry?.fields : stageEntry?.tasks.find((t) => t.name === slot.task)?.fields;
|
|
2767
2771
|
}
|
|
2768
2772
|
function slotProvenance(instance, ref) {
|
|
2769
2773
|
let latest;
|
|
2770
2774
|
for (const entry of instance.history)
|
|
2771
|
-
entry._type === "opApplied" && (entry.target?.scope !== ref.scope || entry.target.
|
|
2775
|
+
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
2776
|
return latest === void 0 ? {} : {
|
|
2773
2777
|
...latest.actor !== void 0 ? { setBy: latest.actor } : {},
|
|
2774
2778
|
setAt: latest.at
|
|
@@ -2783,6 +2787,18 @@ function editDisabledReason(args) {
|
|
|
2783
2787
|
if (!predicateSatisfied)
|
|
2784
2788
|
return { kind: "editor-not-permitted", predicate: effective === !0 ? "true" : effective };
|
|
2785
2789
|
}
|
|
2790
|
+
function hasItems(arr) {
|
|
2791
|
+
return arr !== void 0 && arr.length > 0;
|
|
2792
|
+
}
|
|
2793
|
+
function deriveTaskKind(task) {
|
|
2794
|
+
return hasItems(task.actions) ? "user" : hasItems(task.effects) ? "service" : task.completeWhen !== void 0 || task.subworkflows !== void 0 ? "receive" : "script";
|
|
2795
|
+
}
|
|
2796
|
+
function taskKind(task) {
|
|
2797
|
+
return task.kind ?? deriveTaskKind(task);
|
|
2798
|
+
}
|
|
2799
|
+
function driverKind(actor) {
|
|
2800
|
+
return actor.kind === "user" ? "person" : actor.kind === "ai" ? "agent" : isEngineActor(actor) ? "engine" : "service";
|
|
2801
|
+
}
|
|
2786
2802
|
async function evaluateInstance(args) {
|
|
2787
2803
|
const { client, tag, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
|
|
2788
2804
|
validateTag(tag);
|
|
@@ -2926,9 +2942,9 @@ function taskScopeFor(args) {
|
|
|
2926
2942
|
const { scope, instance, snapshot, actor, taskName, roleAliases } = args;
|
|
2927
2943
|
return {
|
|
2928
2944
|
...scope,
|
|
2929
|
-
|
|
2930
|
-
...scope.
|
|
2931
|
-
...
|
|
2945
|
+
fields: {
|
|
2946
|
+
...scope.fields,
|
|
2947
|
+
...scopedFieldOverlay(instance, snapshot, taskName)
|
|
2932
2948
|
},
|
|
2933
2949
|
assigned: assignedFor(instance, taskName, actor, roleAliases)
|
|
2934
2950
|
};
|
|
@@ -2972,10 +2988,11 @@ async function evaluateTask(args) {
|
|
|
2972
2988
|
return {
|
|
2973
2989
|
task,
|
|
2974
2990
|
status,
|
|
2991
|
+
kind: taskKind(task),
|
|
2975
2992
|
// "pending on actor" treats both `pending` and `active` as waiting on
|
|
2976
2993
|
// the assignee — pending tasks just haven't been activated yet, but
|
|
2977
|
-
// they're still inbox items. The inbox reads the assignees-kind
|
|
2978
|
-
// entry BY KIND (who-data is
|
|
2994
|
+
// they're still inbox items. The inbox reads the assignees-kind field
|
|
2995
|
+
// entry BY KIND (who-data is fields).
|
|
2979
2996
|
pendingOnActor: (status === "active" || status === "pending") && assigned,
|
|
2980
2997
|
...unmetRequirements.length > 0 ? { unmetRequirements } : {},
|
|
2981
2998
|
actions
|
|
@@ -3226,7 +3243,7 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
|
|
|
3226
3243
|
stage: stage.name,
|
|
3227
3244
|
task: taskName,
|
|
3228
3245
|
action: actionName,
|
|
3229
|
-
...actor !== void 0 ? { actor } : {}
|
|
3246
|
+
...actor !== void 0 ? { actor, driverKind: driverKind(actor) } : {}
|
|
3230
3247
|
});
|
|
3231
3248
|
const statusBefore = mutEntry.status, ranOps = await runOps({
|
|
3232
3249
|
ops: action.ops,
|
|
@@ -3244,7 +3261,7 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
|
|
|
3244
3261
|
}), await persistThenDeploy(
|
|
3245
3262
|
ctx,
|
|
3246
3263
|
mutation,
|
|
3247
|
-
() => ranOps.some(
|
|
3264
|
+
() => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
|
|
3248
3265
|
), {
|
|
3249
3266
|
fired: !0,
|
|
3250
3267
|
task: taskName,
|
|
@@ -3273,11 +3290,11 @@ function formatDisabledReason(task, action, reason) {
|
|
|
3273
3290
|
const detail = disabledReasonDetail[reason.kind](reason);
|
|
3274
3291
|
return `Action "${task}:${action}" is not allowed: ${detail}`;
|
|
3275
3292
|
}
|
|
3276
|
-
class
|
|
3293
|
+
class EditFieldDeniedError extends Error {
|
|
3277
3294
|
reason;
|
|
3278
3295
|
target;
|
|
3279
3296
|
constructor(args) {
|
|
3280
|
-
super(formatEditDisabledReason(args.target, args.reason)), this.name = "
|
|
3297
|
+
super(formatEditDisabledReason(args.target, args.reason)), this.name = "EditFieldDeniedError", this.reason = args.reason, this.target = args.target;
|
|
3281
3298
|
}
|
|
3282
3299
|
}
|
|
3283
3300
|
const editDisabledReasonDetail = {
|
|
@@ -3290,10 +3307,10 @@ const editDisabledReasonDetail = {
|
|
|
3290
3307
|
function formatEditDisabledReason(target, reason) {
|
|
3291
3308
|
const detail = editDisabledReasonDetail[reason.kind](
|
|
3292
3309
|
reason
|
|
3293
|
-
), where = target.task !== void 0 ? `${target.task}.${target.
|
|
3294
|
-
return `
|
|
3310
|
+
), where = target.task !== void 0 ? `${target.task}.${target.field}` : target.field;
|
|
3311
|
+
return `Field "${target.scope}:${where}" is not editable: ${detail}`;
|
|
3295
3312
|
}
|
|
3296
|
-
async function
|
|
3313
|
+
async function editField(args) {
|
|
3297
3314
|
const { client, instanceId, target, mode = "set", value, options } = args;
|
|
3298
3315
|
for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
|
|
3299
3316
|
const ctx = await loadCallContext(client, instanceId, options);
|
|
@@ -3303,7 +3320,7 @@ async function editState(args) {
|
|
|
3303
3320
|
if (!isRevisionConflict(error)) throw error;
|
|
3304
3321
|
}
|
|
3305
3322
|
}
|
|
3306
|
-
throw new
|
|
3323
|
+
throw new ConcurrentEditFieldError({
|
|
3307
3324
|
instanceId,
|
|
3308
3325
|
target: labelTarget(target),
|
|
3309
3326
|
attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
|
|
@@ -3313,7 +3330,7 @@ async function commitEdit(ctx, target, mode, value, options) {
|
|
|
3313
3330
|
const actor = options?.actor, stage = findStage(ctx.definition, ctx.instance.currentStage), slot = resolveEditTarget({ definition: ctx.definition, stage, target });
|
|
3314
3331
|
if (slot === void 0)
|
|
3315
3332
|
throw new Error(
|
|
3316
|
-
`No
|
|
3333
|
+
`No field slot "${describeTarget(target)}" resolves in current stage "${stage.name}" of ${ctx.definition.name}`
|
|
3317
3334
|
);
|
|
3318
3335
|
await assertSlotEditable(ctx, slot, options);
|
|
3319
3336
|
const op = buildEditOp(slot, mode, value), mutation = startMutation(ctx.instance), ranOps = runOps({
|
|
@@ -3332,7 +3349,7 @@ async function commitEdit(ctx, target, mode, value, options) {
|
|
|
3332
3349
|
return await persistThenDeploy(
|
|
3333
3350
|
ctx,
|
|
3334
3351
|
mutation,
|
|
3335
|
-
() => ranOps.some(
|
|
3352
|
+
() => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
|
|
3336
3353
|
), {
|
|
3337
3354
|
edited: !0,
|
|
3338
3355
|
target: slotTarget(slot),
|
|
@@ -3351,14 +3368,14 @@ async function assertSlotEditable(ctx, slot, options) {
|
|
|
3351
3368
|
guardDenial: void 0,
|
|
3352
3369
|
predicateSatisfied
|
|
3353
3370
|
});
|
|
3354
|
-
if (reason !== void 0) throw new
|
|
3371
|
+
if (reason !== void 0) throw new EditFieldDeniedError({ target: slotTarget(slot), reason });
|
|
3355
3372
|
}
|
|
3356
3373
|
function buildEditOp(slot, mode, value) {
|
|
3357
|
-
if (mode === "unset") return { type: "
|
|
3374
|
+
if (mode === "unset") return { type: "field.unset", target: slot.ref };
|
|
3358
3375
|
if (value === void 0)
|
|
3359
|
-
throw new Error(`
|
|
3376
|
+
throw new Error(`editField mode "${mode}" requires a value for slot "${slot.name}"`);
|
|
3360
3377
|
return {
|
|
3361
|
-
type: mode === "append" ? "
|
|
3378
|
+
type: mode === "append" ? "field.append" : "field.set",
|
|
3362
3379
|
target: slot.ref,
|
|
3363
3380
|
value: { type: "literal", value }
|
|
3364
3381
|
};
|
|
@@ -3366,19 +3383,19 @@ function buildEditOp(slot, mode, value) {
|
|
|
3366
3383
|
function slotTarget(slot) {
|
|
3367
3384
|
return {
|
|
3368
3385
|
scope: slot.scope,
|
|
3369
|
-
|
|
3386
|
+
field: slot.name,
|
|
3370
3387
|
...slot.task !== void 0 ? { task: slot.task } : {}
|
|
3371
3388
|
};
|
|
3372
3389
|
}
|
|
3373
3390
|
function labelTarget(target) {
|
|
3374
3391
|
return {
|
|
3375
3392
|
scope: target.scope ?? (target.task !== void 0 ? "task" : "workflow"),
|
|
3376
|
-
|
|
3393
|
+
field: target.field,
|
|
3377
3394
|
...target.task !== void 0 ? { task: target.task } : {}
|
|
3378
3395
|
};
|
|
3379
3396
|
}
|
|
3380
3397
|
function describeTarget(target) {
|
|
3381
|
-
const where = target.task !== void 0 ? `${target.task}.${target.
|
|
3398
|
+
const where = target.task !== void 0 ? `${target.task}.${target.field}` : target.field;
|
|
3382
3399
|
return target.scope !== void 0 ? `${target.scope}:${where}` : where;
|
|
3383
3400
|
}
|
|
3384
3401
|
function bareIdFromSpawnRef(uri) {
|
|
@@ -3451,10 +3468,10 @@ function assertActionAllowed(evaluation, task, action) {
|
|
|
3451
3468
|
function assertEditAllowed(evaluation, target) {
|
|
3452
3469
|
const slot = evaluation.editableSlots.filter((s) => editTargetMatches(s, target)).sort((a, b) => SCOPE_PRECEDENCE[a.scope] - SCOPE_PRECEDENCE[b.scope])[0];
|
|
3453
3470
|
if (slot !== void 0 && !slot.editable && slot.disabledReason !== void 0)
|
|
3454
|
-
throw new
|
|
3471
|
+
throw new EditFieldDeniedError({
|
|
3455
3472
|
target: {
|
|
3456
3473
|
scope: slot.scope,
|
|
3457
|
-
|
|
3474
|
+
field: slot.name,
|
|
3458
3475
|
...slot.task !== void 0 ? { task: slot.task } : {}
|
|
3459
3476
|
},
|
|
3460
3477
|
reason: slot.disabledReason
|
|
@@ -3462,7 +3479,7 @@ function assertEditAllowed(evaluation, target) {
|
|
|
3462
3479
|
}
|
|
3463
3480
|
async function applyEdit(args) {
|
|
3464
3481
|
const { client, instance, target, mode, value, actor, grants, clock, clientForGdr } = args;
|
|
3465
|
-
return (await
|
|
3482
|
+
return (await editField({
|
|
3466
3483
|
client,
|
|
3467
3484
|
instanceId: instance._id,
|
|
3468
3485
|
target,
|
|
@@ -3635,7 +3652,7 @@ const workflow = {
|
|
|
3635
3652
|
workflowResource,
|
|
3636
3653
|
definition: definitionName,
|
|
3637
3654
|
version,
|
|
3638
|
-
|
|
3655
|
+
initialFields,
|
|
3639
3656
|
ancestors,
|
|
3640
3657
|
effectsContext,
|
|
3641
3658
|
instanceId,
|
|
@@ -3645,9 +3662,9 @@ const workflow = {
|
|
|
3645
3662
|
throw new Error(
|
|
3646
3663
|
`Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
|
|
3647
3664
|
);
|
|
3648
|
-
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [],
|
|
3649
|
-
entryDefs: definition.
|
|
3650
|
-
|
|
3665
|
+
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedFields = await resolveDeclaredFields({
|
|
3666
|
+
entryDefs: definition.fields ?? [],
|
|
3667
|
+
initialFields: initialFields ?? [],
|
|
3651
3668
|
ctx: {
|
|
3652
3669
|
client,
|
|
3653
3670
|
now,
|
|
@@ -3658,7 +3675,7 @@ const workflow = {
|
|
|
3658
3675
|
...perspective !== void 0 ? { perspective } : {}
|
|
3659
3676
|
},
|
|
3660
3677
|
randomKey
|
|
3661
|
-
}), effectivePerspective = perspective ??
|
|
3678
|
+
}), effectivePerspective = perspective ?? derivePerspectiveFromFields(resolvedFields), base = buildInstanceBase({
|
|
3662
3679
|
id,
|
|
3663
3680
|
now,
|
|
3664
3681
|
tag,
|
|
@@ -3666,7 +3683,7 @@ const workflow = {
|
|
|
3666
3683
|
definitionName: definition.name,
|
|
3667
3684
|
pinnedVersion: definition.version,
|
|
3668
3685
|
definition,
|
|
3669
|
-
|
|
3686
|
+
fields: resolvedFields,
|
|
3670
3687
|
effectsContext: effectsContextEntries,
|
|
3671
3688
|
ancestors: ancestors ?? [],
|
|
3672
3689
|
perspective: effectivePerspective,
|
|
@@ -3720,11 +3737,11 @@ const workflow = {
|
|
|
3720
3737
|
});
|
|
3721
3738
|
},
|
|
3722
3739
|
/**
|
|
3723
|
-
* Edit a declared-editable
|
|
3740
|
+
* Edit a declared-editable field slot directly — reassign, reschedule,
|
|
3724
3741
|
* claim-by-hand, append to a running log — through the generic edit seam,
|
|
3725
3742
|
* instead of a bespoke action per field. Soft-gates on the slot's declared
|
|
3726
3743
|
* editability (the same projection a UI renders), applies the edit as a
|
|
3727
|
-
* `
|
|
3744
|
+
* `field.*` op (so provenance + history are stamped by the op path),
|
|
3728
3745
|
* refreshes the stage's guards, then cascades — an edit to a value a
|
|
3729
3746
|
* transition reads can and should move the instance. Advisory like every
|
|
3730
3747
|
* engine gate; the lake ACL + guards are the only hard locks.
|
|
@@ -3734,7 +3751,7 @@ const workflow = {
|
|
|
3734
3751
|
* UI must bind it to a deliberate boundary (blur / Enter / Save / debounce),
|
|
3735
3752
|
* never an `onChange` per keystroke.
|
|
3736
3753
|
*/
|
|
3737
|
-
|
|
3754
|
+
editField: async (args) => {
|
|
3738
3755
|
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
3756
|
return dispatchGatedWrite({
|
|
3740
3757
|
client,
|
|
@@ -3899,7 +3916,7 @@ const workflow = {
|
|
|
3899
3916
|
* Hydrates the instance's snapshot (instance + ancestors + every doc
|
|
3900
3917
|
* declared by a `doc.ref` / `doc.refs` entry in scope), then
|
|
3901
3918
|
* evaluates the supplied GROQ in groq-js against that dataset. The
|
|
3902
|
-
* rendered scope is auto-bound: `$self`, `$
|
|
3919
|
+
* rendered scope is auto-bound: `$self`, `$fields`, `$parent`,
|
|
3903
3920
|
* `$ancestors`, `$stage`, `$now`, `$effects`, `$tasks`,
|
|
3904
3921
|
* `$allTasksDone`, `$anyTaskFailed` — ids in GDR URI form to match
|
|
3905
3922
|
* the snapshot's keying.
|
|
@@ -4054,7 +4071,7 @@ async function drainEffectsInternal(args) {
|
|
|
4054
4071
|
effectHandlers,
|
|
4055
4072
|
missingHandler,
|
|
4056
4073
|
logger
|
|
4057
|
-
} = args, drainerActor = args.access?.actor ??
|
|
4074
|
+
} = args, drainerActor = args.access?.actor ?? engineSystemActor("drainEffects"), completionAccess = {
|
|
4058
4075
|
actor: drainerActor,
|
|
4059
4076
|
...args.access?.grants !== void 0 ? { grants: args.access.grants } : {}
|
|
4060
4077
|
};
|
|
@@ -4232,7 +4249,7 @@ function createInstanceSession(args) {
|
|
|
4232
4249
|
return settleAfterApply(actor, held, ranOps);
|
|
4233
4250
|
});
|
|
4234
4251
|
},
|
|
4235
|
-
|
|
4252
|
+
editField({ target, mode, value }) {
|
|
4236
4253
|
return commit(async (actor, held) => {
|
|
4237
4254
|
assertEditAllowed(await evaluateWith(held, heldGuards), target);
|
|
4238
4255
|
const { grants } = await access(), ranOps = await applyEdit({
|
|
@@ -4294,7 +4311,7 @@ function createEngine(args) {
|
|
|
4294
4311
|
deployDefinitions: (rest) => workflow.deployDefinitions(bind(rest)),
|
|
4295
4312
|
startInstance: (rest) => workflow.startInstance(bind(rest)),
|
|
4296
4313
|
fireAction: (rest) => workflow.fireAction(bind(rest)),
|
|
4297
|
-
|
|
4314
|
+
editField: (rest) => workflow.editField(bind(rest)),
|
|
4298
4315
|
completeEffect: (rest) => workflow.completeEffect(bind(rest)),
|
|
4299
4316
|
tick: (rest) => workflow.tick(bind(rest)),
|
|
4300
4317
|
evaluateInstance: (rest) => evaluateInstance(bind(rest)),
|
|
@@ -4453,9 +4470,9 @@ const HISTORY_DISPLAY = {
|
|
|
4453
4470
|
},
|
|
4454
4471
|
opApplied: {
|
|
4455
4472
|
title: "Op applied",
|
|
4456
|
-
description: "An inline
|
|
4473
|
+
description: "An inline field-mutation op (field.set / field.append / status.set / etc.) ran during an action commit."
|
|
4457
4474
|
}
|
|
4458
|
-
},
|
|
4475
|
+
}, FIELD_SLOT_DISPLAY = {
|
|
4459
4476
|
"doc.ref": {
|
|
4460
4477
|
title: "Document reference",
|
|
4461
4478
|
description: "Single GDR pointer at a document in this or another resource."
|
|
@@ -4509,23 +4526,23 @@ const HISTORY_DISPLAY = {
|
|
|
4509
4526
|
description: "Ordered list of typed (user|role) assignees."
|
|
4510
4527
|
}
|
|
4511
4528
|
}, OP_DISPLAY = {
|
|
4512
|
-
"
|
|
4513
|
-
title: "Set
|
|
4514
|
-
description: "Overwrite a
|
|
4529
|
+
"field.set": {
|
|
4530
|
+
title: "Set field entry",
|
|
4531
|
+
description: "Overwrite a field entry's value with a resolved Source."
|
|
4515
4532
|
},
|
|
4516
|
-
"
|
|
4517
|
-
title: "Unset
|
|
4518
|
-
description: "Reset a
|
|
4533
|
+
"field.unset": {
|
|
4534
|
+
title: "Unset field entry",
|
|
4535
|
+
description: "Reset a field entry to its default (null for scalars, [] for array kinds)."
|
|
4519
4536
|
},
|
|
4520
|
-
"
|
|
4521
|
-
title: "Append to
|
|
4537
|
+
"field.append": {
|
|
4538
|
+
title: "Append to field entry",
|
|
4522
4539
|
description: "Push a resolved item onto an array-kind entry (notes, checklist, assignees, refs)."
|
|
4523
4540
|
},
|
|
4524
|
-
"
|
|
4541
|
+
"field.updateWhere": {
|
|
4525
4542
|
title: "Update matching rows",
|
|
4526
4543
|
description: "Merge fields into rows of an array-kind entry that match the predicate."
|
|
4527
4544
|
},
|
|
4528
|
-
"
|
|
4545
|
+
"field.removeWhere": {
|
|
4529
4546
|
title: "Remove matching rows",
|
|
4530
4547
|
description: "Drop rows of an array-kind entry that match the predicate."
|
|
4531
4548
|
},
|
|
@@ -4569,11 +4586,40 @@ const HISTORY_DISPLAY = {
|
|
|
4569
4586
|
},
|
|
4570
4587
|
[WORKFLOW_INSTANCE_TYPE]: {
|
|
4571
4588
|
title: "Workflow instance",
|
|
4572
|
-
description: "A running (or finished) workflow against its declared
|
|
4589
|
+
description: "A running (or finished) workflow against its declared fields."
|
|
4590
|
+
}
|
|
4591
|
+
}, TASK_KIND_DISPLAY = {
|
|
4592
|
+
user: {
|
|
4593
|
+
title: "User task",
|
|
4594
|
+
description: "A person acts via the app \u2014 renders action buttons ranked by outcome."
|
|
4595
|
+
},
|
|
4596
|
+
service: {
|
|
4597
|
+
title: "Service task",
|
|
4598
|
+
description: "An automated system or effect does the work \u2014 renders effect drain status."
|
|
4599
|
+
},
|
|
4600
|
+
script: {
|
|
4601
|
+
title: "Script task",
|
|
4602
|
+
description: "The engine runs a step inline and resolves on activation \u2014 an audit row."
|
|
4603
|
+
},
|
|
4604
|
+
manual: {
|
|
4605
|
+
title: "Manual task",
|
|
4606
|
+
description: "Work done off-system, acknowledged by a person or verified by a predicate \u2014 renders an instruction card with a deep-link."
|
|
4607
|
+
},
|
|
4608
|
+
receive: {
|
|
4609
|
+
title: "Receive task",
|
|
4610
|
+
description: 'No actor; the engine waits on a condition \u2014 renders "waiting until \u2026", no buttons.'
|
|
4611
|
+
}
|
|
4612
|
+
}, DRIVER_KIND_DISPLAY = {
|
|
4613
|
+
person: { title: "Person", description: "A human fired this action." },
|
|
4614
|
+
agent: { title: "Agent", description: "An AI agent fired this action." },
|
|
4615
|
+
service: { title: "Service", description: "An external system or Function fired this action." },
|
|
4616
|
+
engine: {
|
|
4617
|
+
title: "Engine",
|
|
4618
|
+
description: "The workflow engine itself drove this \u2014 a gate, machine step, or housekeeping."
|
|
4573
4619
|
}
|
|
4574
4620
|
}, DISPLAY = {
|
|
4575
4621
|
...HISTORY_DISPLAY,
|
|
4576
|
-
...
|
|
4622
|
+
...FIELD_SLOT_DISPLAY,
|
|
4577
4623
|
...OP_DISPLAY,
|
|
4578
4624
|
...EFFECTS_CONTEXT_DISPLAY,
|
|
4579
4625
|
...AUTHORING_DISPLAY
|
|
@@ -4590,12 +4636,15 @@ export {
|
|
|
4590
4636
|
ActionDisabledError,
|
|
4591
4637
|
ActionParamsInvalidError,
|
|
4592
4638
|
CascadeLimitError,
|
|
4593
|
-
|
|
4639
|
+
ConcurrentEditFieldError,
|
|
4594
4640
|
ConcurrentFireActionError,
|
|
4595
4641
|
DEFAULT_CONTENT_PERSPECTIVE,
|
|
4596
4642
|
DISPLAY,
|
|
4643
|
+
DRIVER_KINDS,
|
|
4644
|
+
DRIVER_KIND_DISPLAY,
|
|
4597
4645
|
EFFECTS_CONTEXT_DISPLAY,
|
|
4598
|
-
|
|
4646
|
+
EditFieldDeniedError,
|
|
4647
|
+
FIELD_SLOT_DISPLAY,
|
|
4599
4648
|
GUARD_DOC_TYPE,
|
|
4600
4649
|
GUARD_LIFTED_PREDICATE,
|
|
4601
4650
|
GUARD_OWNER,
|
|
@@ -4603,8 +4652,9 @@ export {
|
|
|
4603
4652
|
MutationGuardDeniedError,
|
|
4604
4653
|
OP_DISPLAY,
|
|
4605
4654
|
PartialGuardDeployError,
|
|
4606
|
-
|
|
4607
|
-
|
|
4655
|
+
RequiredFieldNotProvidedError,
|
|
4656
|
+
TASK_KINDS,
|
|
4657
|
+
TASK_KIND_DISPLAY,
|
|
4608
4658
|
WORKFLOW_DEFINITION_TYPE,
|
|
4609
4659
|
WORKFLOW_INSTANCE_TYPE,
|
|
4610
4660
|
WorkflowStateDivergedError,
|
|
@@ -4620,11 +4670,13 @@ export {
|
|
|
4620
4670
|
defaultLoggerFactory,
|
|
4621
4671
|
denyingGuards,
|
|
4622
4672
|
deployStageGuards,
|
|
4673
|
+
deriveTaskKind,
|
|
4623
4674
|
diagnoseInputFromEvaluation,
|
|
4624
4675
|
diagnoseInstance,
|
|
4625
4676
|
diffEntry,
|
|
4626
4677
|
displayDescription,
|
|
4627
4678
|
displayTitle,
|
|
4679
|
+
driverKind,
|
|
4628
4680
|
effectsContextMap,
|
|
4629
4681
|
evaluateFromSnapshot,
|
|
4630
4682
|
evaluateMutationGuard,
|
|
@@ -4657,6 +4709,7 @@ export {
|
|
|
4657
4709
|
subscriptionDocument,
|
|
4658
4710
|
subscriptionDocumentsForInstance,
|
|
4659
4711
|
tagScopeFilter,
|
|
4712
|
+
taskKind,
|
|
4660
4713
|
validateDefinition,
|
|
4661
4714
|
validateTag,
|
|
4662
4715
|
verdictGuardsForInstance,
|