@sanity/workflow-engine 0.3.0 → 0.5.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 +291 -317
- package/dist/_chunks-cjs/schema.cjs.map +1 -1
- package/dist/_chunks-es/schema.js +291 -317
- package/dist/_chunks-es/schema.js.map +1 -1
- package/dist/define.cjs +480 -74
- package/dist/define.cjs.map +1 -1
- package/dist/define.d.cts +7404 -4450
- package/dist/define.d.ts +7404 -4450
- package/dist/define.js +482 -76
- package/dist/define.js.map +1 -1
- package/dist/index.cjs +1986 -1604
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10627 -5009
- package/dist/index.d.ts +10627 -5009
- package/dist/index.js +1987 -1605
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -17,59 +17,15 @@ function _interopNamespaceCompat(e) {
|
|
|
17
17
|
}), n.default = e, Object.freeze(n);
|
|
18
18
|
}
|
|
19
19
|
var v__namespace = /* @__PURE__ */ _interopNamespaceCompat(v);
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
for (const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
validateStage(v2, stage);
|
|
28
|
-
if (v2.errors.length > 0)
|
|
29
|
-
throw new Error(
|
|
30
|
-
`defineWorkflow("${definition.workflowId}", v${definition.version}): ${v2.errors.length} validation error${v2.errors.length === 1 ? "" : "s"}:
|
|
31
|
-
` + v2.errors.join(`
|
|
32
|
-
`)
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
function createDefinitionValidator() {
|
|
36
|
-
const errors = [], tryParse = (groq, where) => {
|
|
37
|
-
try {
|
|
38
|
-
groqJs.parse(groq);
|
|
39
|
-
} catch (err) {
|
|
40
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
41
|
-
errors.push(` \xB7 ${where}: ${message}`);
|
|
42
|
-
}
|
|
43
|
-
}, rejectTypeScan = (groq, where) => {
|
|
44
|
-
/\*\s*\[\s*_type\b/.test(groq) && errors.push(
|
|
45
|
-
` \xB7 ${where}: filter scans by \`_type\` \u2014 that's a discovery query, not a predicate. Filters evaluate against the in-memory snapshot (instance + ancestors + state-declared docs). To bring extra docs in scope, declare a \`workflow.state.doc.ref\` (or \`doc.refs\`) slot on the workflow or this stage. For lake scans like "all articles in this release", use spawn \`forEach.groq\` instead.`
|
|
46
|
-
);
|
|
47
|
-
};
|
|
48
|
-
return { errors, tryParse, checkFilter: (groq, where) => {
|
|
49
|
-
tryParse(groq, where), rejectTypeScan(groq, where);
|
|
50
|
-
}, checkSlot: (slot, slotLabel) => {
|
|
51
|
-
slot.source.kind === "query" && tryParse(slot.source.query, `${slotLabel}.query`);
|
|
52
|
-
} };
|
|
20
|
+
function getPath(value, path) {
|
|
21
|
+
let current = value;
|
|
22
|
+
for (const part of path.split(".")) {
|
|
23
|
+
if (current == null || typeof current != "object") return;
|
|
24
|
+
current = current[part];
|
|
25
|
+
}
|
|
26
|
+
return current;
|
|
53
27
|
}
|
|
54
|
-
|
|
55
|
-
for (const slot of stage.state ?? [])
|
|
56
|
-
v2.checkSlot(slot, `stage "${stage.id}".state "${slot.id}"`);
|
|
57
|
-
typeof stage.completion == "string" && v2.tryParse(stage.completion, `stage "${stage.id}".completion`);
|
|
58
|
-
for (const t of stage.transitions ?? [])
|
|
59
|
-
typeof t.filter == "string" && v2.checkFilter(t.filter, `transition ${stage.id} \u2192 ${t.to} filter`);
|
|
60
|
-
for (const task of stage.tasks ?? [])
|
|
61
|
-
validateTask(v2, stage.id, task);
|
|
62
|
-
}
|
|
63
|
-
function validateTask(v2, stageId, task) {
|
|
64
|
-
const where = `stage "${stageId}" task "${task.id}"`;
|
|
65
|
-
for (const slot of task.state ?? [])
|
|
66
|
-
v2.checkSlot(slot, `${where}.state "${slot.id}"`);
|
|
67
|
-
typeof task.filter == "string" && v2.checkFilter(task.filter, `${where}.filter`), typeof task.completeWhen == "string" && v2.checkFilter(task.completeWhen, `${where}.completeWhen`);
|
|
68
|
-
for (const a of task.actions ?? [])
|
|
69
|
-
typeof a.filter == "string" && v2.checkFilter(a.filter, `task "${task.id}" action "${a.id}".filter`);
|
|
70
|
-
task.spawns?.forEach?.groq && v2.tryParse(task.spawns.forEach.groq, `task "${task.id}".spawns.forEach.groq`);
|
|
71
|
-
}
|
|
72
|
-
const wallClock = () => (/* @__PURE__ */ new Date()).toISOString(), KNOWN_SCHEMES = /* @__PURE__ */ new Set(["dataset", "canvas", "media-library", "dashboard"]);
|
|
28
|
+
const KNOWN_SCHEMES = /* @__PURE__ */ new Set(["dataset", "canvas", "media-library", "dashboard"]);
|
|
73
29
|
function parseGdr(uri) {
|
|
74
30
|
const colon = uri.indexOf(":");
|
|
75
31
|
if (colon < 0)
|
|
@@ -126,12 +82,15 @@ function refMediaLibrary(resourceId, documentId, type) {
|
|
|
126
82
|
function refDashboard(resourceId, documentId, type) {
|
|
127
83
|
return { id: gdrUri({ scheme: "dashboard", resourceId, documentId }), type };
|
|
128
84
|
}
|
|
85
|
+
function datasetResourceParts(id) {
|
|
86
|
+
const dot = id.indexOf(".");
|
|
87
|
+
if (dot <= 0 || dot === id.length - 1)
|
|
88
|
+
throw new Error(`Invalid dataset resource id "${id}": expected "<projectId>.<dataset>".`);
|
|
89
|
+
return { projectId: id.slice(0, dot), dataset: id.slice(dot + 1) };
|
|
90
|
+
}
|
|
129
91
|
function gdrFromResource(res, documentId) {
|
|
130
92
|
if (res.type === "dataset") {
|
|
131
|
-
const
|
|
132
|
-
if (dot < 0)
|
|
133
|
-
throw new Error(`Invalid dataset resource id "${res.id}": expected "<projectId>.<dataset>".`);
|
|
134
|
-
const projectId = res.id.slice(0, dot), dataset = res.id.slice(dot + 1);
|
|
93
|
+
const { projectId, dataset } = datasetResourceParts(res.id);
|
|
135
94
|
return gdrUri({ scheme: "dataset", projectId, dataset, documentId });
|
|
136
95
|
}
|
|
137
96
|
return gdrUri({ scheme: res.type, resourceId: res.id, documentId });
|
|
@@ -153,191 +112,171 @@ function gdrRef(res, documentId, type) {
|
|
|
153
112
|
function isGdr(value) {
|
|
154
113
|
return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
|
|
155
114
|
}
|
|
156
|
-
function
|
|
157
|
-
return {
|
|
158
|
-
self: selfGdr(instance),
|
|
159
|
-
state: stateMap(instance.state ?? []),
|
|
160
|
-
parent: instance.ancestors.at(-1)?.id ?? null,
|
|
161
|
-
ancestors: instance.ancestors.map((a) => a.id),
|
|
162
|
-
/** Current stage id — bind for filters that scope to the current stage. */
|
|
163
|
-
stage: instance.currentStageId,
|
|
164
|
-
now,
|
|
165
|
-
...extra
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
function stateMap(slots) {
|
|
169
|
-
const out = {};
|
|
170
|
-
for (const s of slots) out[s.id] = s;
|
|
171
|
-
return out;
|
|
172
|
-
}
|
|
173
|
-
function paramsForLake(params) {
|
|
174
|
-
return {
|
|
175
|
-
...params,
|
|
176
|
-
self: typeof params.self == "string" ? bareId(params.self) : params.self,
|
|
177
|
-
parent: typeof params.parent == "string" ? bareId(params.parent) : params.parent,
|
|
178
|
-
ancestors: Array.isArray(params.ancestors) ? params.ancestors.map((a) => typeof a == "string" ? bareId(a) : a) : params.ancestors,
|
|
179
|
-
state: stripStateForLake(params.state)
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
function stripStateForLake(value) {
|
|
183
|
-
if (value == null) return value;
|
|
184
|
-
if (typeof value == "string") return bareId(value);
|
|
185
|
-
if (Array.isArray(value)) return value.map(stripStateForLake);
|
|
186
|
-
if (typeof value == "object") {
|
|
187
|
-
const out = {};
|
|
188
|
-
for (const [k, v2] of Object.entries(value))
|
|
189
|
-
out[k] = stripStateForLake(v2);
|
|
190
|
-
return out;
|
|
191
|
-
}
|
|
192
|
-
return value;
|
|
115
|
+
function resourceOf(p) {
|
|
116
|
+
return p.scheme === "dataset" ? { type: "dataset", id: `${p.projectId}.${p.dataset}` } : { type: p.scheme, id: p.resourceId ?? "" };
|
|
193
117
|
}
|
|
194
|
-
|
|
195
|
-
|
|
118
|
+
const STATE_READ = /^\$state\.(\w+)(?:\.(.+))?$/;
|
|
119
|
+
function isGuardReadExpr(expr) {
|
|
120
|
+
return expr === "$self" || expr === "$now" || STATE_READ.test(expr);
|
|
196
121
|
}
|
|
197
|
-
function
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
122
|
+
function resolveGuardRead(expr, ctx) {
|
|
123
|
+
if (expr === "$self") return selfGdr(ctx.instance);
|
|
124
|
+
if (expr === "$now") return ctx.now;
|
|
125
|
+
const stateRead = STATE_READ.exec(expr);
|
|
126
|
+
if (stateRead !== null) {
|
|
127
|
+
const value = (ctx.instance.state ?? []).find((s) => s.name === stateRead[1])?.value;
|
|
128
|
+
return stateRead[2] !== void 0 ? getPath(value, stateRead[2]) : value;
|
|
202
129
|
}
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
function readStateSlot(instance, scope, slotId, stageId, taskId) {
|
|
206
|
-
if (scope === "workflow")
|
|
207
|
-
return slotValue$1(instance.state?.find((s) => s.id === slotId));
|
|
208
|
-
const stageEntry = instance.stages.find(
|
|
209
|
-
(s) => s.id === (stageId ?? instance.currentStageId) && s.exitedAt === void 0
|
|
130
|
+
throw new Error(
|
|
131
|
+
`Guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", or "$state.<name>[.path]" (guards store resolved values; the lake cannot see $state)`
|
|
210
132
|
);
|
|
211
|
-
if (stageEntry === void 0) return;
|
|
212
|
-
if (scope === "stage")
|
|
213
|
-
return slotValue$1(stageEntry.state?.find((s) => s.id === slotId));
|
|
214
|
-
const taskEntry = stageEntry.tasks.find((t) => t.id === taskId);
|
|
215
|
-
return slotValue$1(taskEntry?.state?.find((s) => s.id === slotId));
|
|
216
133
|
}
|
|
217
|
-
function
|
|
218
|
-
|
|
219
|
-
|
|
134
|
+
function resolveIdRefTarget(expr, ctx) {
|
|
135
|
+
const value = resolveGuardRead(expr, ctx);
|
|
136
|
+
if (typeof value == "string" && isGdrUri(value))
|
|
137
|
+
return { parsed: parseGdr(value) };
|
|
138
|
+
if (value && typeof value == "object") {
|
|
139
|
+
const v2 = value;
|
|
140
|
+
if (typeof v2.id == "string" && isGdrUri(v2.id))
|
|
141
|
+
return typeof v2.type == "string" ? { parsed: parseGdr(v2.id), type: v2.type } : { parsed: parseGdr(v2.id) };
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
220
144
|
}
|
|
221
|
-
function
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
case "actor":
|
|
228
|
-
return { handled: !0, value: ctx.actor };
|
|
229
|
-
case "now":
|
|
230
|
-
return { handled: !0, value: ctx.now };
|
|
231
|
-
default:
|
|
232
|
-
return { handled: !1 };
|
|
145
|
+
function resolveIdRefTargets(idRefs, ctx) {
|
|
146
|
+
const targets = [];
|
|
147
|
+
for (const expr of idRefs ?? []) {
|
|
148
|
+
const target = resolveIdRefTarget(expr, ctx);
|
|
149
|
+
if (target === null) return null;
|
|
150
|
+
targets.push(target);
|
|
233
151
|
}
|
|
152
|
+
return targets.length === 0 ? null : targets;
|
|
234
153
|
}
|
|
235
|
-
function
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
case "stateRead":
|
|
244
|
-
return resolveStateRead(src, ctx);
|
|
245
|
-
case "effectOutput":
|
|
246
|
-
return resolveEffectOutput(src, ctx);
|
|
247
|
-
case "object":
|
|
248
|
-
return resolveObject(src, ctx);
|
|
249
|
-
case "row":
|
|
250
|
-
case "parentState":
|
|
251
|
-
return;
|
|
252
|
-
default:
|
|
253
|
-
return;
|
|
154
|
+
function assertSingleResource(targets) {
|
|
155
|
+
const resource = resourceOf(targets[0].parsed);
|
|
156
|
+
for (const g of targets) {
|
|
157
|
+
const r = resourceOf(g.parsed);
|
|
158
|
+
if (r.type !== resource.type || r.id !== resource.id)
|
|
159
|
+
throw new Error(
|
|
160
|
+
`Guard targets span multiple resources (${resource.type}:${resource.id} vs ${r.type}:${r.id}); a guard is single-resource.`
|
|
161
|
+
);
|
|
254
162
|
}
|
|
163
|
+
return resource;
|
|
255
164
|
}
|
|
256
|
-
function
|
|
257
|
-
|
|
258
|
-
return src.path !== void 0 ? getPath(value, src.path) : value;
|
|
165
|
+
function bareIdRefs(targets) {
|
|
166
|
+
return targets.flatMap((g) => [g.parsed.documentId, `drafts.${g.parsed.documentId}`]);
|
|
259
167
|
}
|
|
260
|
-
function
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
return value === void 0 ? null : value;
|
|
168
|
+
function resolveMatchTypes(targets, authorTypes) {
|
|
169
|
+
if (authorTypes !== void 0) return authorTypes;
|
|
170
|
+
const inferred = [...new Set(targets.map((g) => g.type).filter((t) => !!t))];
|
|
171
|
+
return inferred.length > 0 ? inferred : void 0;
|
|
265
172
|
}
|
|
266
|
-
function
|
|
173
|
+
function resolveMetadata(metadata, ctx) {
|
|
267
174
|
const out = {};
|
|
268
|
-
for (const [
|
|
269
|
-
out[
|
|
175
|
+
for (const [k, expr] of Object.entries(metadata ?? {}))
|
|
176
|
+
out[k] = resolveGuardRead(expr, ctx);
|
|
270
177
|
return out;
|
|
271
178
|
}
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
for (const [key, src] of Object.entries(bindings))
|
|
282
|
-
resolved[key] = resolveSource(src, ctx);
|
|
283
|
-
}
|
|
284
|
-
return { ...resolved, ...staticInput };
|
|
285
|
-
}
|
|
286
|
-
async function evaluateFilter(args) {
|
|
287
|
-
const { filter, definition, snapshot, params } = args;
|
|
288
|
-
if (filter === void 0) return !0;
|
|
289
|
-
if (typeof filter == "string")
|
|
290
|
-
return runGroqAgainstSnapshot(filter, params, snapshot);
|
|
291
|
-
const predicate = (definition.predicates ?? []).find((p) => p.id === filter.ref);
|
|
292
|
-
if (predicate === void 0)
|
|
293
|
-
throw new Error(`Unknown predicate ref: ${filter.ref}`);
|
|
294
|
-
validatePredicateArgs(predicate, filter.args ?? {});
|
|
295
|
-
const merged = { ...params, ...filter.args };
|
|
296
|
-
return runGroqAgainstSnapshot(predicate.groq, merged, snapshot);
|
|
297
|
-
}
|
|
298
|
-
async function runGroqAgainstSnapshot(groq, params, snapshot) {
|
|
299
|
-
if (!groq.includes("*")) {
|
|
300
|
-
const tree2 = groqJs.parse(groq, { params });
|
|
301
|
-
return !!await (await groqJs.evaluate(tree2, { params })).get();
|
|
302
|
-
}
|
|
303
|
-
const tree = groqJs.parse(groq, { params });
|
|
304
|
-
return !!await (await groqJs.evaluate(tree, { dataset: snapshot.docs, params })).get();
|
|
305
|
-
}
|
|
306
|
-
function validatePredicateArgs(predicate, args) {
|
|
307
|
-
for (const param of predicate.params ?? [])
|
|
308
|
-
validatePredicateParam(predicate.id, param, args);
|
|
309
|
-
}
|
|
310
|
-
function validatePredicateParam(predicateId, param, args) {
|
|
311
|
-
if (!(param.id in args))
|
|
312
|
-
throw new Error(`Predicate "${predicateId}" requires param "${param.id}"`);
|
|
313
|
-
const value = args[param.id];
|
|
314
|
-
if (!matchesParamType(param.type, value))
|
|
179
|
+
function validateDefinition(definition) {
|
|
180
|
+
const v2 = createDefinitionValidator();
|
|
181
|
+
for (const entry of definition.state ?? [])
|
|
182
|
+
v2.checkEntry(entry, `workflow.state "${entry.name}"`);
|
|
183
|
+
for (const [name, groq] of Object.entries(definition.predicates ?? {}))
|
|
184
|
+
v2.checkCondition(groq, `predicate "${name}"`);
|
|
185
|
+
for (const stage of definition.stages)
|
|
186
|
+
validateStage(v2, stage);
|
|
187
|
+
if (v2.errors.length > 0)
|
|
315
188
|
throw new Error(
|
|
316
|
-
`
|
|
189
|
+
`defineWorkflow("${definition.name}", v${definition.version}): ${v2.errors.length} validation error${v2.errors.length === 1 ? "" : "s"}:
|
|
190
|
+
` + v2.errors.join(`
|
|
191
|
+
`)
|
|
317
192
|
);
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
193
|
+
}
|
|
194
|
+
function createDefinitionValidator() {
|
|
195
|
+
const errors = [], tryParse = (groq, where) => {
|
|
196
|
+
try {
|
|
197
|
+
groqJs.parse(groq);
|
|
198
|
+
} catch (err) {
|
|
199
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
200
|
+
errors.push(` \xB7 ${where}: ${message}`);
|
|
201
|
+
}
|
|
202
|
+
}, rejectTypeScan = (groq, where) => {
|
|
203
|
+
/\*\s*\[\s*_type\b/.test(groq) && errors.push(
|
|
204
|
+
` \xB7 ${where}: condition scans by \`_type\` \u2014 that's a discovery query, not a predicate. Conditions evaluate against the in-memory snapshot (instance + ancestors + state-declared docs). To bring extra docs in scope, declare a \`doc.ref\` (or \`doc.refs\`) state entry on the workflow or this stage. For lake scans like "all articles in this release", use \`subworkflows.forEach\` instead.`
|
|
205
|
+
);
|
|
206
|
+
};
|
|
207
|
+
return { errors, tryParse, checkCondition: (groq, where) => {
|
|
208
|
+
tryParse(groq, where), rejectTypeScan(groq, where);
|
|
209
|
+
}, checkEntry: (entry, label) => {
|
|
210
|
+
entry.source.type === "query" && tryParse(entry.source.query, `${label}.query`);
|
|
211
|
+
}, checkGuardRead: (expr, where) => {
|
|
212
|
+
isGuardReadExpr(expr) || errors.push(
|
|
213
|
+
` \xB7 ${where}: guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", or "$state.<name>[.path]" (guards store resolved values; the lake cannot see $state)`
|
|
321
214
|
);
|
|
215
|
+
} };
|
|
322
216
|
}
|
|
323
|
-
function
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
return Array.isArray(value) && value.every((v2) => typeof v2 == "string");
|
|
333
|
-
case "reference":
|
|
334
|
-
return isGdr(value);
|
|
217
|
+
function validateStage(v2, stage) {
|
|
218
|
+
for (const entry of stage.state ?? [])
|
|
219
|
+
v2.checkEntry(entry, `stage "${stage.name}".state "${entry.name}"`);
|
|
220
|
+
for (const guard of stage.guards ?? [])
|
|
221
|
+
validateGuardReads(v2, stage.name, guard);
|
|
222
|
+
for (const t of stage.transitions ?? []) {
|
|
223
|
+
v2.checkCondition(t.filter, `transition "${t.name}" (${stage.name} \u2192 ${t.to}) filter`);
|
|
224
|
+
for (const [key, groq] of effectBindings(t.effects))
|
|
225
|
+
v2.tryParse(groq, `transition "${t.name}" effect binding "${key}"`);
|
|
335
226
|
}
|
|
227
|
+
for (const task of stage.tasks ?? [])
|
|
228
|
+
validateTask(v2, stage.name, task);
|
|
229
|
+
}
|
|
230
|
+
function validateGuardReads(v2, stageName, guard) {
|
|
231
|
+
const where = `stage "${stageName}" guard "${guard.name}"`;
|
|
232
|
+
for (const expr of guard.match.idRefs ?? [])
|
|
233
|
+
v2.checkGuardRead(expr, `${where} match.idRefs`);
|
|
234
|
+
for (const [key, expr] of Object.entries(guard.metadata ?? {}))
|
|
235
|
+
v2.checkGuardRead(expr, `${where} metadata "${key}"`);
|
|
236
|
+
}
|
|
237
|
+
function validateTask(v2, stageName, task) {
|
|
238
|
+
const where = `stage "${stageName}" task "${task.name}"`;
|
|
239
|
+
for (const entry of task.state ?? [])
|
|
240
|
+
v2.checkEntry(entry, `${where}.state "${entry.name}"`);
|
|
241
|
+
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`);
|
|
242
|
+
for (const [key, groq] of effectBindings(task.effects))
|
|
243
|
+
v2.tryParse(groq, `${where} effect binding "${key}"`);
|
|
244
|
+
validateTaskActions(v2, task), task.subworkflows !== void 0 && validateSubworkflows(v2, where, task.subworkflows);
|
|
245
|
+
}
|
|
246
|
+
function validateTaskActions(v2, task) {
|
|
247
|
+
for (const a of task.actions ?? []) {
|
|
248
|
+
a.filter !== void 0 && v2.checkCondition(a.filter, `task "${task.name}" action "${a.name}".filter`);
|
|
249
|
+
for (const [key, groq] of effectBindings(a.effects))
|
|
250
|
+
v2.tryParse(groq, `task "${task.name}" action "${a.name}" effect binding "${key}"`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function validateSubworkflows(v2, where, sub) {
|
|
254
|
+
v2.tryParse(sub.forEach, `${where}.subworkflows.forEach`);
|
|
255
|
+
for (const [key, groq] of Object.entries(sub.with ?? {}))
|
|
256
|
+
v2.tryParse(groq, `${where}.subworkflows.with "${key}"`);
|
|
257
|
+
for (const [key, groq] of Object.entries(sub.context ?? {}))
|
|
258
|
+
v2.tryParse(groq, `${where}.subworkflows.context "${key}"`);
|
|
259
|
+
}
|
|
260
|
+
function effectBindings(effects) {
|
|
261
|
+
return (effects ?? []).flatMap(
|
|
262
|
+
(e) => Object.entries(e.bindings ?? {}).map(([k, groq]) => [`${e.name}.${k}`, groq])
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
function actionVerdict(task, action) {
|
|
266
|
+
return {
|
|
267
|
+
task: task.task.name,
|
|
268
|
+
taskStatus: task.status,
|
|
269
|
+
action: action.action.name,
|
|
270
|
+
title: action.action.title,
|
|
271
|
+
allowed: action.allowed,
|
|
272
|
+
disabledReason: action.disabledReason,
|
|
273
|
+
params: action.action.params ?? []
|
|
274
|
+
};
|
|
336
275
|
}
|
|
337
|
-
function
|
|
338
|
-
return
|
|
276
|
+
function availableActions(tasks) {
|
|
277
|
+
return tasks.flatMap((t) => t.actions.map((a) => actionVerdict(t, a)));
|
|
339
278
|
}
|
|
340
|
-
const GUARD_DOC_TYPE = "temp.system.guard";
|
|
279
|
+
const wallClock = () => (/* @__PURE__ */ new Date()).toISOString(), GUARD_DOC_TYPE = "temp.system.guard";
|
|
341
280
|
class MutationGuardDeniedError extends Error {
|
|
342
281
|
denied;
|
|
343
282
|
documentId;
|
|
@@ -346,9 +285,24 @@ class MutationGuardDeniedError extends Error {
|
|
|
346
285
|
const ids = args.denied.map((d) => d.guardId).join(", ");
|
|
347
286
|
super(`Mutation on "${args.documentId}" (${args.action}) denied by guard(s) [${ids}]`), this.name = "MutationGuardDeniedError", this.denied = args.denied, this.documentId = args.documentId, this.action = args.action;
|
|
348
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* Build the error straight from the denying guard docs — the one mapping
|
|
290
|
+
* from {@link MutationGuardDoc} to the structured `denied` payload, shared
|
|
291
|
+
* by every enforcement site (engine pre-flight, bench write seam).
|
|
292
|
+
*/
|
|
293
|
+
static fromGuards(args) {
|
|
294
|
+
return new MutationGuardDeniedError({
|
|
295
|
+
documentId: args.documentId,
|
|
296
|
+
action: args.action,
|
|
297
|
+
denied: args.guards.map((g) => ({
|
|
298
|
+
guardId: g._id,
|
|
299
|
+
...g.name !== void 0 ? { name: g.name } : {}
|
|
300
|
+
}))
|
|
301
|
+
});
|
|
302
|
+
}
|
|
349
303
|
}
|
|
350
304
|
function lakeGuardId(args) {
|
|
351
|
-
return `${GUARD_DOC_TYPE}.${args.instanceDocId}.${args.
|
|
305
|
+
return `${GUARD_DOC_TYPE}.${args.instanceDocId}.${args.guardName}`;
|
|
352
306
|
}
|
|
353
307
|
function compileGuard(args) {
|
|
354
308
|
return {
|
|
@@ -358,8 +312,8 @@ function compileGuard(args) {
|
|
|
358
312
|
resourceId: args.resourceId,
|
|
359
313
|
owner: args.owner,
|
|
360
314
|
sourceInstanceId: args.sourceInstanceId,
|
|
361
|
-
|
|
362
|
-
|
|
315
|
+
sourceDefinition: args.sourceDefinition,
|
|
316
|
+
sourceStage: args.sourceStage,
|
|
363
317
|
...args.name !== void 0 ? { name: args.name } : {},
|
|
364
318
|
...args.description !== void 0 ? { description: args.description } : {},
|
|
365
319
|
match: args.match,
|
|
@@ -407,112 +361,172 @@ async function denyingGuards(args) {
|
|
|
407
361
|
guardMatches(guard, doc, context.action) && (await evaluateMutationGuard({ guard, context }) || denied.push(guard));
|
|
408
362
|
return denied;
|
|
409
363
|
}
|
|
410
|
-
function
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
for (const src of idRefs ?? []) {
|
|
427
|
-
const target = resolveIdRefTarget(src, ctx);
|
|
428
|
-
if (target === null) return null;
|
|
429
|
-
targets.push(target);
|
|
430
|
-
}
|
|
431
|
-
return targets.length === 0 ? null : targets;
|
|
364
|
+
async function instanceWriteDenials(args) {
|
|
365
|
+
const { instance, guards, identity } = args, enforceable = guards.filter(
|
|
366
|
+
(g) => g.resourceType === instance.workflowResource.type && g.resourceId === instance.workflowResource.id
|
|
367
|
+
);
|
|
368
|
+
if (enforceable.length === 0) return [];
|
|
369
|
+
const doc = instance;
|
|
370
|
+
return denyingGuards({
|
|
371
|
+
guards: enforceable,
|
|
372
|
+
doc: { id: instance._id, type: instance._type },
|
|
373
|
+
context: {
|
|
374
|
+
action: "update",
|
|
375
|
+
before: doc,
|
|
376
|
+
after: doc,
|
|
377
|
+
...identity !== void 0 ? { identity } : {}
|
|
378
|
+
}
|
|
379
|
+
});
|
|
432
380
|
}
|
|
433
|
-
function
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
442
|
-
return resource;
|
|
381
|
+
async function assertInstanceWriteAllowed(args) {
|
|
382
|
+
const denied = await instanceWriteDenials(args);
|
|
383
|
+
if (denied.length !== 0)
|
|
384
|
+
throw MutationGuardDeniedError.fromGuards({
|
|
385
|
+
documentId: args.instance._id,
|
|
386
|
+
action: "update",
|
|
387
|
+
guards: denied
|
|
388
|
+
});
|
|
443
389
|
}
|
|
444
|
-
function
|
|
445
|
-
return
|
|
390
|
+
function findOpenStageEntry(host) {
|
|
391
|
+
return host.stages.find((s) => s.name === host.currentStage && s.exitedAt === void 0);
|
|
446
392
|
}
|
|
447
|
-
function
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
393
|
+
function buildParams(args) {
|
|
394
|
+
const { instance, now, snapshot, extra } = args, currentTasks2 = findOpenStageEntry(instance)?.tasks ?? [];
|
|
395
|
+
return {
|
|
396
|
+
self: selfGdr(instance),
|
|
397
|
+
state: renderedState(instance.state ?? [], snapshot),
|
|
398
|
+
parent: instance.ancestors.at(-1)?.id ?? null,
|
|
399
|
+
ancestors: instance.ancestors.map((a) => a.id),
|
|
400
|
+
stage: instance.currentStage,
|
|
401
|
+
now,
|
|
402
|
+
/** `$effects` — parent context handoff by entry name, completed-effect
|
|
403
|
+
* outputs namespaced under the effect's name (`$effects['x.y'].out`). */
|
|
404
|
+
effects: effectsContextMap(instance),
|
|
405
|
+
/** `$tasks` — the current stage's task rows, statuses included. */
|
|
406
|
+
tasks: currentTasks2,
|
|
407
|
+
allTasksDone: currentTasks2.every((t) => t.status === "done" || t.status === "skipped"),
|
|
408
|
+
anyTaskFailed: currentTasks2.some((t) => t.status === "failed"),
|
|
409
|
+
...extra
|
|
410
|
+
};
|
|
451
411
|
}
|
|
452
|
-
function
|
|
412
|
+
function renderedState(entries, snapshot) {
|
|
453
413
|
const out = {};
|
|
454
|
-
for (const [
|
|
455
|
-
out[k] = resolveSource(src, ctx);
|
|
414
|
+
for (const entry of entries) out[entry.name] = renderedValue(entry, snapshot);
|
|
456
415
|
return out;
|
|
457
416
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
const ctx = { instance, stageId, now }, targets = resolveIdRefTargets(guard.match.idRefs, ctx);
|
|
461
|
-
if (targets === null) return null;
|
|
462
|
-
const resource = assertSingleResource(targets), types = resolveMatchTypes(targets, guard.match.types);
|
|
463
|
-
return { doc: compileGuard({
|
|
464
|
-
id: lakeGuardId({ instanceDocId: instance._id, stageId, index }),
|
|
465
|
-
resourceType: resource.type,
|
|
466
|
-
resourceId: resource.id,
|
|
467
|
-
owner: GUARD_OWNER,
|
|
468
|
-
sourceInstanceId: instance._id,
|
|
469
|
-
sourceDefinitionId: instance.workflowId,
|
|
470
|
-
sourceStageId: stageId,
|
|
471
|
-
...guard.name !== void 0 ? { name: guard.name } : {},
|
|
472
|
-
...guard.description !== void 0 ? { description: guard.description } : {},
|
|
473
|
-
match: {
|
|
474
|
-
...types !== void 0 ? { types } : {},
|
|
475
|
-
idRefs: bareIdRefs(targets),
|
|
476
|
-
...guard.match.idPatterns !== void 0 ? { idPatterns: guard.match.idPatterns } : {},
|
|
477
|
-
actions: guard.match.actions
|
|
478
|
-
},
|
|
479
|
-
predicate: guard.predicate ?? "",
|
|
480
|
-
metadata: resolveMetadata(guard.metadata, ctx)
|
|
481
|
-
}), routeGdr: targets[0].parsed };
|
|
417
|
+
function renderedValue(entry, snapshot) {
|
|
418
|
+
return entry._type !== "doc.ref" || entry.value === null || snapshot === void 0 ? entry.value : snapshot.docs.find((d) => d._id === entry.value.id) ?? entry.value;
|
|
482
419
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
}
|
|
488
|
-
const { _id, _type, _rev, _createdAt, _updatedAt, ...body } = doc;
|
|
489
|
-
await client.patch(doc._id).set(body).commit();
|
|
420
|
+
function scopedStateOverlay(instance, snapshot, taskName) {
|
|
421
|
+
const stageEntry = findOpenStageEntry(instance);
|
|
422
|
+
if (stageEntry === void 0) return {};
|
|
423
|
+
const stageState = renderedState(stageEntry.state ?? [], snapshot), task = taskName ? stageEntry.tasks.find((t) => t.name === taskName) : void 0;
|
|
424
|
+
return { ...stageState, ...renderedState(task?.state ?? [], snapshot) };
|
|
490
425
|
}
|
|
491
|
-
function
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
426
|
+
function assignedFor(instance, taskName, actor) {
|
|
427
|
+
if (actor === void 0) return !1;
|
|
428
|
+
const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.state?.find((s) => s._type === "assignees");
|
|
429
|
+
return entry === void 0 ? !1 : entry.value.some(
|
|
430
|
+
(a) => a.type === "user" ? a.id === actor.id : (actor.roles ?? []).includes(a.role)
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
function effectsContextMap(instance) {
|
|
434
|
+
const out = {};
|
|
435
|
+
for (const entry of instance.effectsContext)
|
|
436
|
+
out[entry.name] = entry._type === "effectsContext.json" ? parseJsonContextEntry(entry) : entry.value;
|
|
497
437
|
return out;
|
|
498
438
|
}
|
|
499
|
-
|
|
500
|
-
|
|
439
|
+
function parseJsonContextEntry(entry) {
|
|
440
|
+
try {
|
|
441
|
+
return JSON.parse(entry.value);
|
|
442
|
+
} catch (err) {
|
|
443
|
+
throw new Error(
|
|
444
|
+
`effectsContext entry "${entry.name}" holds unparseable JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
445
|
+
{ cause: err }
|
|
446
|
+
);
|
|
447
|
+
}
|
|
501
448
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
449
|
+
function paramsForLake(params) {
|
|
450
|
+
return {
|
|
451
|
+
...params,
|
|
452
|
+
self: typeof params.self == "string" ? bareId(params.self) : params.self,
|
|
453
|
+
parent: typeof params.parent == "string" ? bareId(params.parent) : params.parent,
|
|
454
|
+
ancestors: Array.isArray(params.ancestors) ? params.ancestors.map((a) => typeof a == "string" ? bareId(a) : a) : params.ancestors,
|
|
455
|
+
state: stripStateForLake(params.state)
|
|
456
|
+
};
|
|
506
457
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
if (
|
|
510
|
-
|
|
511
|
-
|
|
458
|
+
function stripStateForLake(value) {
|
|
459
|
+
if (value == null) return value;
|
|
460
|
+
if (typeof value == "string") return bareId(value);
|
|
461
|
+
if (Array.isArray(value)) return value.map(stripStateForLake);
|
|
462
|
+
if (typeof value == "object") {
|
|
463
|
+
const out = {};
|
|
464
|
+
for (const [k, v2] of Object.entries(value))
|
|
465
|
+
out[k] = stripStateForLake(v2);
|
|
466
|
+
return out;
|
|
467
|
+
}
|
|
468
|
+
return value;
|
|
512
469
|
}
|
|
513
|
-
function
|
|
514
|
-
|
|
515
|
-
|
|
470
|
+
function bareId(id) {
|
|
471
|
+
return isGdrUri(id) ? extractDocumentId(id) : id;
|
|
472
|
+
}
|
|
473
|
+
function abortReason(instance) {
|
|
474
|
+
return instance.history.find(
|
|
475
|
+
(h) => h._type === "aborted"
|
|
476
|
+
)?.reason;
|
|
477
|
+
}
|
|
478
|
+
function diagnoseInputFromEvaluation(evaluation) {
|
|
479
|
+
return {
|
|
480
|
+
instance: evaluation.instance,
|
|
481
|
+
tasks: evaluation.currentStage.tasks,
|
|
482
|
+
transitions: evaluation.currentStage.transitions
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
function openStage(instance) {
|
|
486
|
+
return findOpenStageEntry(instance);
|
|
487
|
+
}
|
|
488
|
+
function assigneesOf(stage, taskName) {
|
|
489
|
+
return (stage?.tasks.find((t) => t.name === taskName)?.state ?? []).filter((s) => s._type === "assignees").flatMap((s) => s.value);
|
|
490
|
+
}
|
|
491
|
+
function isResolved(status) {
|
|
492
|
+
return status === "done" || status === "skipped";
|
|
493
|
+
}
|
|
494
|
+
function failedEffectCause(input) {
|
|
495
|
+
const stalled = new Set(input.tasks.filter((t) => !isResolved(t.status)).map((t) => t.task.name)), effect = [...input.instance.effectHistory].reverse().find((e) => e.status === "failed" && e.origin.kind === "task" && stalled.has(e.origin.name));
|
|
496
|
+
return effect ? { kind: "failed-effect", effect } : void 0;
|
|
497
|
+
}
|
|
498
|
+
function hungEffectCause(input) {
|
|
499
|
+
const effect = input.instance.pendingEffects.find((e) => e.claim !== void 0);
|
|
500
|
+
return effect ? { kind: "hung-effect", effect } : void 0;
|
|
501
|
+
}
|
|
502
|
+
function failedTaskCause(input) {
|
|
503
|
+
const failed = input.tasks.find((t) => t.status === "failed");
|
|
504
|
+
return failed ? { kind: "failed-task", task: failed.task.name } : void 0;
|
|
505
|
+
}
|
|
506
|
+
function waitingState(input) {
|
|
507
|
+
const active = input.tasks.find((t) => t.status === "active" && (t.task.actions ?? []).length > 0);
|
|
508
|
+
if (active !== void 0)
|
|
509
|
+
return {
|
|
510
|
+
state: "waiting",
|
|
511
|
+
task: active.task.name,
|
|
512
|
+
assignees: assigneesOf(openStage(input.instance), active.task.name),
|
|
513
|
+
actions: (active.task.actions ?? []).map((a) => a.name)
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
function noTransitionFiresCause(input) {
|
|
517
|
+
if (input.transitions.length !== 0 && !input.transitions.some((t) => t.filterSatisfied) && input.tasks.every((t) => isResolved(t.status)))
|
|
518
|
+
return { kind: "no-transition-fires" };
|
|
519
|
+
}
|
|
520
|
+
function diagnoseInstance(input) {
|
|
521
|
+
const { instance } = input;
|
|
522
|
+
if (instance.abortedAt !== void 0) {
|
|
523
|
+
const reason = abortReason(instance);
|
|
524
|
+
return { state: "aborted", at: instance.abortedAt, ...reason !== void 0 ? { reason } : {} };
|
|
525
|
+
}
|
|
526
|
+
if (instance.completedAt !== void 0)
|
|
527
|
+
return { state: "completed", at: instance.completedAt };
|
|
528
|
+
const cause = failedEffectCause(input) ?? failedTaskCause(input) ?? hungEffectCause(input) ?? noTransitionFiresCause(input);
|
|
529
|
+
return cause !== void 0 ? { state: "stuck", cause } : waitingState(input) ?? { state: "progressing" };
|
|
516
530
|
}
|
|
517
531
|
function buildSnapshot(args) {
|
|
518
532
|
const docs = [], knownIds = /* @__PURE__ */ new Set();
|
|
@@ -534,48 +548,59 @@ function rewriteRefsRecursive(value, resource) {
|
|
|
534
548
|
k === "_ref" && typeof v2 == "string" ? out[k] = v2.includes(":") ? v2 : gdrFromResource(resource, v2) : out[k] = rewriteRefsRecursive(v2, resource);
|
|
535
549
|
return out;
|
|
536
550
|
}
|
|
537
|
-
|
|
538
|
-
|
|
551
|
+
const WORKFLOW_INSTANCE_TYPE = "sanity.workflow.instance";
|
|
552
|
+
function parseDefinitionSnapshot(instance) {
|
|
553
|
+
try {
|
|
554
|
+
return JSON.parse(instance.definitionSnapshot);
|
|
555
|
+
} catch (err) {
|
|
556
|
+
throw new Error(
|
|
557
|
+
`Failed to parse definitionSnapshot on instance "${instance._id}": ${err instanceof Error ? err.message : String(err)}`,
|
|
558
|
+
{ cause: err }
|
|
559
|
+
);
|
|
560
|
+
}
|
|
539
561
|
}
|
|
540
562
|
function collectWatchRefs(instance) {
|
|
541
563
|
const stage = findOpenStageEntry(instance);
|
|
542
564
|
return [
|
|
543
565
|
gdrRef(instance.workflowResource, instance._id, instance._type),
|
|
544
566
|
...instance.ancestors,
|
|
545
|
-
...
|
|
546
|
-
...
|
|
547
|
-
...
|
|
548
|
-
...
|
|
567
|
+
...entryDocRefs(instance.state),
|
|
568
|
+
...entryDocRefs(stage?.state),
|
|
569
|
+
...entryReleaseRefs(instance.state),
|
|
570
|
+
...entryReleaseRefs(stage?.state)
|
|
549
571
|
];
|
|
550
572
|
}
|
|
551
573
|
function readsRaw(ref) {
|
|
552
|
-
return ref.type ===
|
|
574
|
+
return ref.type === WORKFLOW_INSTANCE_TYPE || ref.type === "system.release";
|
|
553
575
|
}
|
|
554
576
|
function contentReleaseName(args) {
|
|
555
577
|
const { ref, perspective } = args;
|
|
556
578
|
if (!readsRaw(ref) && Array.isArray(perspective))
|
|
557
579
|
return perspective.find((entry) => entry !== "drafts" && entry !== "published" && entry !== "raw");
|
|
558
580
|
}
|
|
581
|
+
function subscriptionDocument(ref) {
|
|
582
|
+
return { ...parseGdr(ref.id), globalDocumentId: ref.id, type: ref.type };
|
|
583
|
+
}
|
|
559
584
|
function subscriptionDocumentsForInstance(instance) {
|
|
560
585
|
const seen = /* @__PURE__ */ new Set(), documents = [];
|
|
561
586
|
for (const ref of collectWatchRefs(instance))
|
|
562
|
-
!isGdrUri(ref.id) || seen.has(ref.id) || (seen.add(ref.id), documents.push(
|
|
587
|
+
!isGdrUri(ref.id) || seen.has(ref.id) || (seen.add(ref.id), documents.push(subscriptionDocument(ref)));
|
|
563
588
|
return {
|
|
564
589
|
documents,
|
|
565
590
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
566
591
|
};
|
|
567
592
|
}
|
|
568
|
-
function
|
|
569
|
-
return
|
|
593
|
+
function entryDocRefs(state) {
|
|
594
|
+
return stateEntries(state).flatMap((entry) => entry._type === "doc.ref" ? isGdr(entry.value) ? [entry.value] : [] : entry._type === "doc.refs" ? Array.isArray(entry.value) ? entry.value.filter(isGdr) : [] : []);
|
|
570
595
|
}
|
|
571
|
-
function
|
|
572
|
-
return
|
|
573
|
-
(
|
|
596
|
+
function entryReleaseRefs(state) {
|
|
597
|
+
return stateEntries(state).flatMap(
|
|
598
|
+
(entry) => entry._type === "release.ref" && isGdr(entry.value) ? [entry.value] : []
|
|
574
599
|
);
|
|
575
600
|
}
|
|
576
|
-
function
|
|
577
|
-
return Array.isArray(
|
|
578
|
-
(
|
|
601
|
+
function stateEntries(state) {
|
|
602
|
+
return Array.isArray(state) ? state.filter(
|
|
603
|
+
(entry) => !!entry && typeof entry == "object"
|
|
579
604
|
) : [];
|
|
580
605
|
}
|
|
581
606
|
async function hydrateSnapshot(args) {
|
|
@@ -617,17 +642,219 @@ async function readDoc(client, id, perspective) {
|
|
|
617
642
|
function resourceFromParsed(parsed) {
|
|
618
643
|
return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
|
|
619
644
|
}
|
|
620
|
-
function
|
|
621
|
-
return
|
|
645
|
+
function collectEntryDocUris(resolvedStateEntries) {
|
|
646
|
+
return entryDocRefs(resolvedStateEntries).map((ref) => ref.id);
|
|
647
|
+
}
|
|
648
|
+
const resourceKey = (r) => `${r.type}.${r.id}`;
|
|
649
|
+
function guardsForResource(client) {
|
|
650
|
+
return client.fetch("*[_type == $t] | order(_id asc)", { t: GUARD_DOC_TYPE });
|
|
651
|
+
}
|
|
652
|
+
function instanceGuardQuery(instanceId) {
|
|
653
|
+
return {
|
|
654
|
+
query: "*[_type == $guardType && sourceInstanceId == $instanceId]",
|
|
655
|
+
params: { guardType: GUARD_DOC_TYPE, instanceId }
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
function verdictGuardsForInstance(client, instanceId) {
|
|
659
|
+
const { query, params } = instanceGuardQuery(instanceId);
|
|
660
|
+
return client.fetch(query, params);
|
|
661
|
+
}
|
|
662
|
+
async function guardsForInstance(args) {
|
|
663
|
+
const { client, clientForGdr, instance } = args, stateUris = [
|
|
664
|
+
...collectEntryDocUris(instance.state),
|
|
665
|
+
...instance.stages.flatMap((stage) => collectEntryDocUris(stage.state))
|
|
666
|
+
], clients = resourceClientMap(
|
|
667
|
+
instance.workflowResource,
|
|
668
|
+
client,
|
|
669
|
+
clientForGdr,
|
|
670
|
+
stateUris.map((uri) => tryParseGdr(uri))
|
|
671
|
+
), { query, params } = instanceGuardQuery(instance._id);
|
|
672
|
+
return queryGuardsAcross(clients.values(), query, params);
|
|
673
|
+
}
|
|
674
|
+
async function guardsForDefinition(args) {
|
|
675
|
+
const perClient = await guardsForDefinitionByClient(args);
|
|
676
|
+
return dedupById(perClient.flatMap((entry) => entry.guards));
|
|
677
|
+
}
|
|
678
|
+
async function guardsForDefinitionByClient(args) {
|
|
679
|
+
const { client, clientForGdr, workflowResource, definition, definitions } = args, gdrs = definitions.flatMap(
|
|
680
|
+
(def) => guardIdRefs(def).map(({ idRef, stage }) => staticIdRefGdr(idRef, stage, def))
|
|
681
|
+
), clients = resourceClientMap(workflowResource, client, clientForGdr, gdrs), query = "*[_type == $t && sourceDefinition == $definition]", params = { t: GUARD_DOC_TYPE, definition };
|
|
682
|
+
return Promise.all(
|
|
683
|
+
[...clients.values()].map(async (resourceClient) => ({
|
|
684
|
+
client: resourceClient,
|
|
685
|
+
guards: await resourceClient.fetch(query, params)
|
|
686
|
+
}))
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
function guardIdRefs(definition) {
|
|
690
|
+
return definition.stages.flatMap(
|
|
691
|
+
(stage) => (stage.guards ?? []).flatMap(
|
|
692
|
+
(guard) => (guard.match.idRefs ?? []).map((idRef) => ({ idRef, stage }))
|
|
693
|
+
)
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
function staticIdRefGdr(idRef, stage, definition) {
|
|
697
|
+
const read = /^\$state\.([\w-]+)/.exec(idRef);
|
|
698
|
+
if (read === null) return;
|
|
699
|
+
const source = entriesInScope(stage, definition).find((e) => e.name === read[1])?.source;
|
|
700
|
+
return source?.type === "literal" ? gdrFromValue(source.value) : void 0;
|
|
701
|
+
}
|
|
702
|
+
function entriesInScope(stage, definition) {
|
|
703
|
+
return [
|
|
704
|
+
...definition.state ?? [],
|
|
705
|
+
...stage.state ?? [],
|
|
706
|
+
...(stage.tasks ?? []).flatMap((task) => task.state ?? [])
|
|
707
|
+
];
|
|
708
|
+
}
|
|
709
|
+
function gdrFromValue(value) {
|
|
710
|
+
if (typeof value == "string") return tryParseGdr(value);
|
|
711
|
+
if (typeof value == "object" && value !== null && "id" in value) {
|
|
712
|
+
const id = value.id;
|
|
713
|
+
return typeof id == "string" ? tryParseGdr(id) : void 0;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
function tryParseGdr(uri) {
|
|
717
|
+
try {
|
|
718
|
+
return parseGdr(uri);
|
|
719
|
+
} catch {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
function resourceClientMap(base, baseClient, clientForGdr, gdrs) {
|
|
724
|
+
const map = /* @__PURE__ */ new Map([[resourceKey(base), baseClient]]);
|
|
725
|
+
for (const parsed of gdrs) {
|
|
726
|
+
if (parsed === void 0) continue;
|
|
727
|
+
const key = resourceKey(resourceFromParsed(parsed));
|
|
728
|
+
map.has(key) || map.set(key, clientForGdr(parsed));
|
|
729
|
+
}
|
|
730
|
+
return map;
|
|
731
|
+
}
|
|
732
|
+
async function queryGuardsAcross(clients, query, params) {
|
|
733
|
+
const perResource = await Promise.all(
|
|
734
|
+
[...clients].map((c) => c.fetch(query, params))
|
|
735
|
+
);
|
|
736
|
+
return dedupById(perResource.flat());
|
|
737
|
+
}
|
|
738
|
+
function dedupById(guards) {
|
|
739
|
+
return [...new Map(guards.map((g) => [g._id, g])).values()].sort((a, b) => a._id.localeCompare(b._id));
|
|
740
|
+
}
|
|
741
|
+
const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
742
|
+
function validateTags(tags) {
|
|
743
|
+
if (tags.length === 0)
|
|
744
|
+
throw new Error("tags: must be a non-empty array");
|
|
745
|
+
for (const tag of tags)
|
|
746
|
+
if (!TAG_RE.test(tag))
|
|
747
|
+
throw new Error(
|
|
748
|
+
`tags: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
function canonicalTag(tags) {
|
|
752
|
+
return tags[0];
|
|
753
|
+
}
|
|
754
|
+
function tagScopeFilter(param = "engineTags") {
|
|
755
|
+
return `count(tags[@ in $${param}]) > 0`;
|
|
756
|
+
}
|
|
757
|
+
const GUARD_OWNER = "robot:workflow-engine", GUARD_LIFTED_PREDICATE = "true";
|
|
758
|
+
function resolveGuard(guard, instance, stageName, now) {
|
|
759
|
+
const ctx = { instance, now }, targets = resolveIdRefTargets(guard.match.idRefs, ctx);
|
|
760
|
+
if (targets === null) return null;
|
|
761
|
+
const resource = assertSingleResource(targets), types = resolveMatchTypes(targets, guard.match.types);
|
|
762
|
+
return { doc: compileGuard({
|
|
763
|
+
id: lakeGuardId({ instanceDocId: instance._id, guardName: guard.name }),
|
|
764
|
+
resourceType: resource.type,
|
|
765
|
+
resourceId: resource.id,
|
|
766
|
+
owner: GUARD_OWNER,
|
|
767
|
+
sourceInstanceId: instance._id,
|
|
768
|
+
sourceDefinition: instance.definition,
|
|
769
|
+
sourceStage: stageName,
|
|
770
|
+
name: guard.name,
|
|
771
|
+
...guard.description !== void 0 ? { description: guard.description } : {},
|
|
772
|
+
match: {
|
|
773
|
+
...types !== void 0 ? { types } : {},
|
|
774
|
+
idRefs: bareIdRefs(targets),
|
|
775
|
+
...guard.match.idPatterns !== void 0 ? { idPatterns: guard.match.idPatterns } : {},
|
|
776
|
+
actions: guard.match.actions
|
|
777
|
+
},
|
|
778
|
+
predicate: guard.predicate ?? "",
|
|
779
|
+
metadata: resolveMetadata(guard.metadata, ctx)
|
|
780
|
+
}), routeGdr: targets[0].parsed };
|
|
781
|
+
}
|
|
782
|
+
async function upsertGuard(client, doc) {
|
|
783
|
+
if (!await client.getDocument(doc._id)) {
|
|
784
|
+
await client.create(doc);
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
const { _id, _type, _rev, _createdAt, _updatedAt, ...body } = doc;
|
|
788
|
+
await client.patch(doc._id).set(body).commit();
|
|
789
|
+
}
|
|
790
|
+
function resolvedStageGuards(args) {
|
|
791
|
+
const stage = args.definition.stages.find((s) => s.name === args.stageName), out = [];
|
|
792
|
+
for (const guard of stage?.guards ?? []) {
|
|
793
|
+
const resolved = resolveGuard(guard, args.instance, args.stageName, args.now);
|
|
794
|
+
resolved !== null && out.push({ client: args.clientForGdr(resolved.routeGdr), doc: resolved.doc });
|
|
795
|
+
}
|
|
796
|
+
return out;
|
|
797
|
+
}
|
|
798
|
+
async function committedInstance(args) {
|
|
799
|
+
return await args.client.getDocument(args.instance._id) ?? void 0;
|
|
800
|
+
}
|
|
801
|
+
async function deployStageGuards(args) {
|
|
802
|
+
const live = await committedInstance(args);
|
|
803
|
+
if (!(live === void 0 || live.currentStage !== args.stageName) && live.abortedAt === void 0)
|
|
804
|
+
for (const { client, doc } of resolvedStageGuards(args))
|
|
805
|
+
await upsertGuard(client, doc);
|
|
806
|
+
}
|
|
807
|
+
async function retractStageGuards(args) {
|
|
808
|
+
const live = await committedInstance(args);
|
|
809
|
+
if (live !== void 0 && !(live.currentStage === args.stageName && live.abortedAt === void 0))
|
|
810
|
+
for (const { client, doc } of resolvedStageGuards(args))
|
|
811
|
+
await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit();
|
|
812
|
+
}
|
|
813
|
+
async function deleteOrphanedDefinitionGuards(args) {
|
|
814
|
+
const { client, clientForGdr, workflowResource, definition, definitions, tags } = args, perClient = await guardsForDefinitionByClient({
|
|
815
|
+
client,
|
|
816
|
+
clientForGdr,
|
|
817
|
+
workflowResource,
|
|
818
|
+
definition,
|
|
819
|
+
definitions
|
|
820
|
+
}), ownPartitionPrefix = `${canonicalTag(tags)}.`;
|
|
821
|
+
let count = 0;
|
|
822
|
+
for (const { client: resourceClient, guards } of perClient) {
|
|
823
|
+
const own = guards.filter((guard) => guard.sourceInstanceId.startsWith(ownPartitionPrefix));
|
|
824
|
+
if (own.length === 0) continue;
|
|
825
|
+
const tx = resourceClient.transaction();
|
|
826
|
+
for (const guard of own) tx.delete(guard._id);
|
|
827
|
+
await tx.commit(), count += own.length;
|
|
828
|
+
}
|
|
829
|
+
return count;
|
|
830
|
+
}
|
|
831
|
+
function randomKey(length = 12) {
|
|
832
|
+
const bytes = new Uint8Array(length);
|
|
833
|
+
return globalThis.crypto.getRandomValues(bytes), [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, length);
|
|
834
|
+
}
|
|
835
|
+
async function evaluateCondition(args) {
|
|
836
|
+
const { condition, snapshot, params } = args;
|
|
837
|
+
return condition === void 0 ? !0 : !!await runGroq(condition, params, snapshot);
|
|
622
838
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
839
|
+
async function evaluatePredicates(args) {
|
|
840
|
+
const out = {};
|
|
841
|
+
for (const [name, groq] of Object.entries(args.predicates ?? {}))
|
|
842
|
+
out[name] = !!await runGroq(groq, args.params, args.snapshot);
|
|
843
|
+
return out;
|
|
844
|
+
}
|
|
845
|
+
async function runGroq(groq, params, snapshot) {
|
|
846
|
+
const tree = groqJs.parse(groq, { params });
|
|
847
|
+
return (await groqJs.evaluate(tree, { dataset: snapshot.docs, params })).get();
|
|
848
|
+
}
|
|
849
|
+
class StateValueShapeError extends Error {
|
|
850
|
+
entryType;
|
|
851
|
+
entryName;
|
|
627
852
|
issues;
|
|
628
853
|
constructor(args) {
|
|
629
854
|
const issueText = args.issues.join("; ");
|
|
630
|
-
super(
|
|
855
|
+
super(
|
|
856
|
+
`State entry ${args.mode} shape invalid for "${args.entryName}" (${args.entryType}): ${issueText}`
|
|
857
|
+
), this.name = "StateValueShapeError", this.entryType = args.entryType, this.entryName = args.entryName, this.issues = args.issues;
|
|
631
858
|
}
|
|
632
859
|
}
|
|
633
860
|
const GdrShape = v__namespace.looseObject({
|
|
@@ -649,8 +876,8 @@ const GdrShape = v__namespace.looseObject({
|
|
|
649
876
|
roles: v__namespace.optional(v__namespace.array(v__namespace.string())),
|
|
650
877
|
onBehalfOf: v__namespace.optional(v__namespace.string())
|
|
651
878
|
}), AssigneeShape = v__namespace.union([
|
|
652
|
-
v__namespace.looseObject({
|
|
653
|
-
v__namespace.looseObject({
|
|
879
|
+
v__namespace.looseObject({ type: v__namespace.literal("user"), id: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)) }),
|
|
880
|
+
v__namespace.looseObject({ type: v__namespace.literal("role"), role: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)) })
|
|
654
881
|
]), ChecklistItemShape = v__namespace.looseObject({
|
|
655
882
|
label: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)),
|
|
656
883
|
done: v__namespace.boolean(),
|
|
@@ -672,59 +899,59 @@ const GdrShape = v__namespace.looseObject({
|
|
|
672
899
|
v__namespace.check((s) => !Number.isNaN(Date.parse(s)), "must be an ISO-8601 datetime string")
|
|
673
900
|
)
|
|
674
901
|
]), NullableUrl = NullableString, valueSchemas = {
|
|
675
|
-
"
|
|
676
|
-
"
|
|
677
|
-
"
|
|
678
|
-
|
|
679
|
-
"
|
|
680
|
-
"
|
|
681
|
-
"
|
|
682
|
-
"
|
|
683
|
-
"
|
|
684
|
-
"
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
902
|
+
"doc.ref": v__namespace.union([v__namespace.null(), GdrShape]),
|
|
903
|
+
"doc.refs": v__namespace.array(GdrShape),
|
|
904
|
+
"release.ref": v__namespace.union([v__namespace.null(), ReleaseRefShape]),
|
|
905
|
+
query: v__namespace.any(),
|
|
906
|
+
"value.string": NullableString,
|
|
907
|
+
"value.url": NullableUrl,
|
|
908
|
+
"value.number": NullableNumber,
|
|
909
|
+
"value.boolean": NullableBoolean,
|
|
910
|
+
"value.dateTime": NullableDateTime,
|
|
911
|
+
"value.actor": v__namespace.union([v__namespace.null(), ActorShape]),
|
|
912
|
+
checklist: v__namespace.array(ChecklistItemShape),
|
|
913
|
+
notes: v__namespace.array(NoteItemShape),
|
|
914
|
+
assignees: v__namespace.array(AssigneeShape)
|
|
688
915
|
}, itemSchemas = {
|
|
689
|
-
"
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
916
|
+
"doc.refs": GdrShape,
|
|
917
|
+
checklist: ChecklistItemShape,
|
|
918
|
+
notes: NoteItemShape,
|
|
919
|
+
assignees: AssigneeShape
|
|
693
920
|
};
|
|
694
|
-
function isAppendable(
|
|
695
|
-
return
|
|
921
|
+
function isAppendable(entryType) {
|
|
922
|
+
return entryType in itemSchemas;
|
|
696
923
|
}
|
|
697
|
-
function
|
|
698
|
-
const schema2 = valueSchemas[args.
|
|
924
|
+
function validateStateValue(args) {
|
|
925
|
+
const schema2 = valueSchemas[args.entryType];
|
|
699
926
|
if (schema2 === void 0)
|
|
700
|
-
throw new
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
issues: [`unknown
|
|
927
|
+
throw new StateValueShapeError({
|
|
928
|
+
entryType: args.entryType,
|
|
929
|
+
entryName: args.entryName,
|
|
930
|
+
issues: [`unknown state entry type ${args.entryType}`],
|
|
704
931
|
mode: "value"
|
|
705
932
|
});
|
|
706
933
|
const result = v__namespace.safeParse(schema2, args.value);
|
|
707
934
|
if (!result.success)
|
|
708
|
-
throw new
|
|
709
|
-
|
|
710
|
-
|
|
935
|
+
throw new StateValueShapeError({
|
|
936
|
+
entryType: args.entryType,
|
|
937
|
+
entryName: args.entryName,
|
|
711
938
|
issues: formatIssues(result.issues),
|
|
712
939
|
mode: "value"
|
|
713
940
|
});
|
|
714
941
|
}
|
|
715
|
-
function
|
|
716
|
-
if (!isAppendable(args.
|
|
717
|
-
throw new
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
issues: [`
|
|
942
|
+
function validateStateAppendItem(args) {
|
|
943
|
+
if (!isAppendable(args.entryType))
|
|
944
|
+
throw new StateValueShapeError({
|
|
945
|
+
entryType: args.entryType,
|
|
946
|
+
entryName: args.entryName,
|
|
947
|
+
issues: [`state entry type ${args.entryType} does not support append`],
|
|
721
948
|
mode: "item"
|
|
722
949
|
});
|
|
723
|
-
const schema2 = itemSchemas[args.
|
|
950
|
+
const schema2 = itemSchemas[args.entryType], result = v__namespace.safeParse(schema2, args.item);
|
|
724
951
|
if (!result.success)
|
|
725
|
-
throw new
|
|
726
|
-
|
|
727
|
-
|
|
952
|
+
throw new StateValueShapeError({
|
|
953
|
+
entryType: args.entryType,
|
|
954
|
+
entryName: args.entryName,
|
|
728
955
|
issues: formatIssues(result.issues),
|
|
729
956
|
mode: "item"
|
|
730
957
|
});
|
|
@@ -735,115 +962,115 @@ function formatIssues(issues) {
|
|
|
735
962
|
return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${i.message}`;
|
|
736
963
|
});
|
|
737
964
|
}
|
|
738
|
-
function derivePerspectiveFromState(
|
|
739
|
-
if (
|
|
740
|
-
for (const
|
|
741
|
-
if (
|
|
742
|
-
const releaseName =
|
|
965
|
+
function derivePerspectiveFromState(entries) {
|
|
966
|
+
if (entries !== void 0) {
|
|
967
|
+
for (const entry of entries)
|
|
968
|
+
if (entry._type === "release.ref" && entry.value !== null) {
|
|
969
|
+
const releaseName = entry.value.releaseName;
|
|
743
970
|
if (typeof releaseName == "string" && releaseName.length > 0)
|
|
744
971
|
return [releaseName];
|
|
745
972
|
}
|
|
746
973
|
}
|
|
747
974
|
}
|
|
748
975
|
async function resolveDeclaredState(args) {
|
|
749
|
-
const {
|
|
750
|
-
if (
|
|
976
|
+
const { entryDefs, initialState, ctx, randomKey: randomKey2 } = args;
|
|
977
|
+
if (entryDefs === void 0 || entryDefs.length === 0) return [];
|
|
751
978
|
const out = [];
|
|
752
|
-
for (const
|
|
979
|
+
for (const entry of entryDefs) {
|
|
753
980
|
const scopedCtx = { ...ctx, resolvedState: out };
|
|
754
|
-
out.push(await
|
|
981
|
+
out.push(await resolveOneEntry(entry, initialState, scopedCtx, randomKey2));
|
|
755
982
|
}
|
|
756
983
|
return out;
|
|
757
984
|
}
|
|
758
985
|
const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set([
|
|
759
|
-
"
|
|
760
|
-
"
|
|
761
|
-
"
|
|
762
|
-
"
|
|
986
|
+
"doc.refs",
|
|
987
|
+
"checklist",
|
|
988
|
+
"notes",
|
|
989
|
+
"assignees"
|
|
763
990
|
]);
|
|
764
|
-
function
|
|
765
|
-
return ARRAY_SLOT_TYPES.has(
|
|
991
|
+
function defaultEntryValue(entryType) {
|
|
992
|
+
return ARRAY_SLOT_TYPES.has(entryType) ? [] : null;
|
|
766
993
|
}
|
|
767
|
-
function resolveInitValue(
|
|
768
|
-
const initMatch = initialState.find((s) => s.
|
|
769
|
-
return initMatch === void 0 ? defaultValue : (assertInitValueShape(
|
|
994
|
+
function resolveInitValue(entry, initialState, defaultValue) {
|
|
995
|
+
const initMatch = initialState.find((s) => s.name === entry.name && s.type === entry.type);
|
|
996
|
+
return initMatch === void 0 ? defaultValue : (assertInitValueShape(entry, initMatch.value), initMatch.value);
|
|
770
997
|
}
|
|
771
998
|
function resolveStateReadValue(source, ctx, defaultValue) {
|
|
772
|
-
const target = (source.scope === "workflow" ? ctx.workflowState ?? [] : ctx.resolvedState ?? []).find((s) => s.
|
|
999
|
+
const target = (source.scope === "workflow" ? ctx.workflowState ?? [] : ctx.resolvedState ?? []).find((s) => s.name === source.state), targetValue = target === void 0 ? void 0 : target.value;
|
|
773
1000
|
return (source.path !== void 0 ? getPath(targetValue, source.path) : targetValue) ?? defaultValue;
|
|
774
1001
|
}
|
|
775
|
-
async function resolveQueryValue(
|
|
1002
|
+
async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
776
1003
|
const params = paramsForLake({
|
|
777
1004
|
self: ctx.selfId ?? null,
|
|
778
1005
|
state: stateMapFromResolved(ctx.resolvedState ?? []),
|
|
779
|
-
stage: ctx.
|
|
780
|
-
task: ctx.
|
|
1006
|
+
stage: ctx.stageName ?? null,
|
|
1007
|
+
task: ctx.taskName ?? null,
|
|
781
1008
|
now: ctx.now,
|
|
782
1009
|
engineTags: ctx.tags ?? []
|
|
783
1010
|
});
|
|
784
1011
|
try {
|
|
785
1012
|
const fetchOptions = ctx.perspective !== void 0 ? { perspective: ctx.perspective } : void 0, result = await client.fetch(source.query, params, fetchOptions);
|
|
786
|
-
return normalizeQueryResult(
|
|
1013
|
+
return normalizeQueryResult(entry.type, result, ctx.workflowResource) ?? defaultValue;
|
|
787
1014
|
} catch (err) {
|
|
788
1015
|
throw new Error(
|
|
789
|
-
`Failed to resolve query
|
|
1016
|
+
`Failed to resolve query entry "${entry.name}" (${entry.type}): ${err instanceof Error ? err.message : String(err)}`,
|
|
790
1017
|
{ cause: err }
|
|
791
1018
|
);
|
|
792
1019
|
}
|
|
793
1020
|
}
|
|
794
|
-
async function
|
|
795
|
-
const source =
|
|
796
|
-
return source.
|
|
1021
|
+
async function resolveEntryValue(entry, initialState, ctx, defaultValue) {
|
|
1022
|
+
const source = entry.source;
|
|
1023
|
+
return source.type === "init" ? resolveInitValue(entry, initialState, defaultValue) : source.type === "literal" ? source.value ?? defaultValue : source.type === "stateRead" ? resolveStateReadValue(source, ctx, defaultValue) : source.type === "query" && ctx.client !== void 0 ? resolveQueryValue(entry, source, ctx, ctx.client, defaultValue) : defaultValue;
|
|
797
1024
|
}
|
|
798
|
-
function
|
|
799
|
-
const titleProp =
|
|
800
|
-
return
|
|
1025
|
+
function buildResolvedEntry(entry, value, _key, now) {
|
|
1026
|
+
const titleProp = entry.title !== void 0 ? { title: entry.title } : {}, descriptionProp = entry.description !== void 0 ? { description: entry.description } : {};
|
|
1027
|
+
return entry.type === "query" ? {
|
|
801
1028
|
_key,
|
|
802
|
-
_type: "
|
|
803
|
-
|
|
1029
|
+
_type: "query",
|
|
1030
|
+
name: entry.name,
|
|
804
1031
|
...titleProp,
|
|
805
1032
|
...descriptionProp,
|
|
806
1033
|
value,
|
|
807
1034
|
resolvedAt: now
|
|
808
1035
|
} : {
|
|
809
1036
|
_key,
|
|
810
|
-
_type:
|
|
811
|
-
|
|
1037
|
+
_type: entry.type,
|
|
1038
|
+
name: entry.name,
|
|
812
1039
|
...titleProp,
|
|
813
1040
|
...descriptionProp,
|
|
814
1041
|
value
|
|
815
1042
|
};
|
|
816
1043
|
}
|
|
817
|
-
async function
|
|
818
|
-
const defaultValue =
|
|
819
|
-
return
|
|
1044
|
+
async function resolveOneEntry(entry, initialState, ctx, randomKey2) {
|
|
1045
|
+
const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue(entry, initialState, ctx, defaultValue);
|
|
1046
|
+
return validateStateValue({ entryType: entry.type, entryName: entry.name, value }), buildResolvedEntry(entry, value, randomKey2(), ctx.now);
|
|
820
1047
|
}
|
|
821
|
-
function stateMapFromResolved(
|
|
1048
|
+
function stateMapFromResolved(entries) {
|
|
822
1049
|
const out = {};
|
|
823
|
-
for (const s of
|
|
1050
|
+
for (const s of entries) out[s.name] = s.value;
|
|
824
1051
|
return out;
|
|
825
1052
|
}
|
|
826
|
-
function assertInitValueShape(
|
|
827
|
-
if (
|
|
828
|
-
assertGdrShape(value, `state
|
|
1053
|
+
function assertInitValueShape(entry, value) {
|
|
1054
|
+
if (entry.type === "doc.ref") {
|
|
1055
|
+
assertGdrShape(value, `state entry "${entry.name}" (doc.ref)`);
|
|
829
1056
|
return;
|
|
830
1057
|
}
|
|
831
|
-
if (
|
|
832
|
-
assertGdrShape(value, `state
|
|
1058
|
+
if (entry.type === "release.ref") {
|
|
1059
|
+
assertGdrShape(value, `state entry "${entry.name}" (release.ref)`);
|
|
833
1060
|
const v2 = value;
|
|
834
1061
|
if (typeof v2.releaseName != "string" || v2.releaseName.length === 0)
|
|
835
1062
|
throw new Error(
|
|
836
|
-
`Invalid init value for state
|
|
1063
|
+
`Invalid init value for state entry "${entry.name}" (release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(v2.releaseName)}.`
|
|
837
1064
|
);
|
|
838
1065
|
return;
|
|
839
1066
|
}
|
|
840
|
-
if (
|
|
1067
|
+
if (entry.type === "doc.refs") {
|
|
841
1068
|
if (!Array.isArray(value))
|
|
842
1069
|
throw new Error(
|
|
843
|
-
`Invalid init value for state
|
|
1070
|
+
`Invalid init value for state entry "${entry.name}" (doc.refs): expected an array of GDRs, got ${typeof value}.`
|
|
844
1071
|
);
|
|
845
1072
|
for (const [i, item] of value.entries())
|
|
846
|
-
assertGdrShape(item, `state
|
|
1073
|
+
assertGdrShape(item, `state entry "${entry.name}" (doc.refs) item [${i}]`);
|
|
847
1074
|
}
|
|
848
1075
|
}
|
|
849
1076
|
function assertGdrShape(value, context) {
|
|
@@ -861,8 +1088,8 @@ function assertGdrShape(value, context) {
|
|
|
861
1088
|
`Invalid GDR for ${context}: \`type\` (schema name) must be a non-empty string. Got ${JSON.stringify(v2.type)}.`
|
|
862
1089
|
);
|
|
863
1090
|
}
|
|
864
|
-
function normalizeQueryResult(
|
|
865
|
-
return raw == null ? raw :
|
|
1091
|
+
function normalizeQueryResult(entryType, raw, workflowResource) {
|
|
1092
|
+
return raw == null ? raw : entryType === "doc.ref" ? coerceToGdr(raw, workflowResource) : entryType === "doc.refs" ? Array.isArray(raw) ? raw.map((item) => coerceToGdr(item, workflowResource)).filter((v2) => v2 !== null) : [] : raw;
|
|
866
1093
|
}
|
|
867
1094
|
function toGdrUri(docId, workflowResource) {
|
|
868
1095
|
return isGdrUri(docId) ? docId : gdrFromResource(workflowResource, docId);
|
|
@@ -887,7 +1114,7 @@ async function loadContext(client, instanceId, options) {
|
|
|
887
1114
|
const instance = await client.getDocument(instanceId);
|
|
888
1115
|
if (!instance)
|
|
889
1116
|
throw new Error(`Workflow instance ${instanceId} not found`);
|
|
890
|
-
const definition =
|
|
1117
|
+
const definition = parseDefinitionSnapshot(instance), clientForGdr = options?.clientForGdr ?? (() => client), clock = options?.clock ?? wallClock, snapshot = await hydrateSnapshot({
|
|
891
1118
|
client,
|
|
892
1119
|
clientForGdr,
|
|
893
1120
|
instance,
|
|
@@ -895,12 +1122,28 @@ async function loadContext(client, instanceId, options) {
|
|
|
895
1122
|
});
|
|
896
1123
|
return { client, clientForGdr, clock, now: clock(), instance, definition, snapshot };
|
|
897
1124
|
}
|
|
898
|
-
function
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1125
|
+
async function ctxConditionParams(ctx, opts) {
|
|
1126
|
+
const base = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), state = {
|
|
1127
|
+
...base.state,
|
|
1128
|
+
...scopedStateOverlay(ctx.instance, ctx.snapshot, opts?.taskName)
|
|
1129
|
+
}, params = {
|
|
1130
|
+
...base,
|
|
1131
|
+
state,
|
|
1132
|
+
actor: opts?.actor,
|
|
1133
|
+
assigned: opts?.taskName !== void 0 ? assignedFor(ctx.instance, opts.taskName, opts?.actor) : !1,
|
|
1134
|
+
...opts?.vars
|
|
1135
|
+
};
|
|
1136
|
+
return { ...await evaluatePredicates({
|
|
1137
|
+
predicates: ctx.definition.predicates,
|
|
1138
|
+
snapshot: ctx.snapshot,
|
|
1139
|
+
params
|
|
1140
|
+
}), ...params };
|
|
1141
|
+
}
|
|
1142
|
+
async function ctxEvaluateCondition(ctx, condition, opts) {
|
|
1143
|
+
return condition === void 0 ? !0 : evaluateCondition({
|
|
1144
|
+
condition,
|
|
902
1145
|
snapshot: ctx.snapshot,
|
|
903
|
-
params:
|
|
1146
|
+
params: await ctxConditionParams(ctx, opts)
|
|
904
1147
|
});
|
|
905
1148
|
}
|
|
906
1149
|
async function buildEngineContext(args) {
|
|
@@ -915,129 +1158,142 @@ async function buildEngineContext(args) {
|
|
|
915
1158
|
snapshot: await hydrateSnapshot({ client, clientForGdr, instance })
|
|
916
1159
|
};
|
|
917
1160
|
}
|
|
918
|
-
function findStage(definition,
|
|
919
|
-
const stage = definition.stages.find((s) => s.
|
|
1161
|
+
function findStage(definition, stageName) {
|
|
1162
|
+
const stage = definition.stages.find((s) => s.name === stageName);
|
|
920
1163
|
if (stage === void 0)
|
|
921
|
-
throw new Error(`Stage "${
|
|
1164
|
+
throw new Error(`Stage "${stageName}" not found in definition ${definition.name}`);
|
|
922
1165
|
return stage;
|
|
923
1166
|
}
|
|
924
|
-
async function
|
|
1167
|
+
async function resolveStageStateEntries(args) {
|
|
925
1168
|
const { client, instance, stage, now, initialState } = args;
|
|
926
1169
|
return resolveDeclaredState({
|
|
927
|
-
|
|
1170
|
+
entryDefs: stage.state,
|
|
928
1171
|
initialState: initialState ?? [],
|
|
929
1172
|
ctx: {
|
|
930
1173
|
client,
|
|
931
1174
|
now,
|
|
932
1175
|
selfId: instance._id,
|
|
933
1176
|
tags: instance.tags ?? [],
|
|
934
|
-
|
|
1177
|
+
stageName: stage.name,
|
|
935
1178
|
workflowResource: instance.workflowResource,
|
|
936
|
-
// Allow stage-scope
|
|
937
|
-
// "workflow", ... }` to see the already-resolved workflow-scope
|
|
938
|
-
// state.
|
|
1179
|
+
// Allow stage-scope entries' `source: { type: "stateRead", scope:
|
|
1180
|
+
// "workflow", ... }` to see the already-resolved workflow-scope state.
|
|
939
1181
|
workflowState: instance.state ?? [],
|
|
940
1182
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
941
1183
|
},
|
|
942
1184
|
randomKey
|
|
943
1185
|
});
|
|
944
1186
|
}
|
|
945
|
-
async function
|
|
1187
|
+
async function resolveTaskStateEntries(args) {
|
|
946
1188
|
const { client, instance, stage, task, now } = args;
|
|
947
1189
|
return resolveDeclaredState({
|
|
948
|
-
|
|
1190
|
+
entryDefs: task.state,
|
|
949
1191
|
initialState: [],
|
|
950
1192
|
ctx: {
|
|
951
1193
|
client,
|
|
952
1194
|
now,
|
|
953
1195
|
selfId: instance._id,
|
|
954
1196
|
tags: instance.tags ?? [],
|
|
955
|
-
|
|
1197
|
+
stageName: stage.name,
|
|
956
1198
|
workflowResource: instance.workflowResource,
|
|
957
|
-
|
|
1199
|
+
taskName: task.name,
|
|
958
1200
|
workflowState: instance.state ?? [],
|
|
959
1201
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
960
1202
|
},
|
|
961
1203
|
randomKey
|
|
962
1204
|
});
|
|
963
1205
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1206
|
+
class ActionParamsInvalidError extends Error {
|
|
1207
|
+
action;
|
|
1208
|
+
task;
|
|
1209
|
+
issues;
|
|
1210
|
+
constructor(args) {
|
|
1211
|
+
const lines = args.issues.map((i) => ` - ${i.param}: ${i.reason}`).join(`
|
|
1212
|
+
`);
|
|
1213
|
+
super(`Action "${args.action}" on task "${args.task}" rejected: invalid params
|
|
1214
|
+
${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.task = args.task, this.issues = args.issues;
|
|
1215
|
+
}
|
|
967
1216
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
definitionSnapshot: JSON.stringify(args.definition),
|
|
981
|
-
state: args.state,
|
|
982
|
-
effectsContext: args.effectsContext,
|
|
983
|
-
ancestors: args.ancestors,
|
|
984
|
-
...perspective !== void 0 ? { perspective } : {},
|
|
985
|
-
currentStageId: args.initialStageId,
|
|
986
|
-
stages: [],
|
|
987
|
-
pendingEffects: [],
|
|
988
|
-
effectHistory: [],
|
|
989
|
-
history: [
|
|
990
|
-
{
|
|
991
|
-
_key: randomKey(),
|
|
992
|
-
_type: "workflow.history.stageEntered",
|
|
993
|
-
at: now,
|
|
994
|
-
stageId: args.initialStageId,
|
|
995
|
-
...actor !== void 0 ? { actor } : {}
|
|
996
|
-
}
|
|
997
|
-
],
|
|
998
|
-
startedAt: now,
|
|
999
|
-
lastChangedAt: now
|
|
1000
|
-
};
|
|
1217
|
+
class WorkflowStateDivergedError extends Error {
|
|
1218
|
+
instanceId;
|
|
1219
|
+
/** The guard-deploy failure that triggered the (attempted) rollback. */
|
|
1220
|
+
guardError;
|
|
1221
|
+
/** The rollback failure, when rollback was attempted and itself failed. */
|
|
1222
|
+
rollbackError;
|
|
1223
|
+
constructor(args) {
|
|
1224
|
+
super(
|
|
1225
|
+
`Workflow "${args.instanceId}" state committed but its guards could not be reconciled: ${args.reason}.`,
|
|
1226
|
+
{ cause: args.guardError }
|
|
1227
|
+
), this.name = "WorkflowStateDivergedError", this.instanceId = args.instanceId, this.guardError = args.guardError, args.rollbackError !== void 0 && (this.rollbackError = args.rollbackError);
|
|
1228
|
+
}
|
|
1001
1229
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
...t.state !== void 0 ? { state: t.state.map((slot) => ({ ...slot })) } : {}
|
|
1014
|
-
}))
|
|
1015
|
-
})),
|
|
1016
|
-
pendingEffects: [...instance.pendingEffects],
|
|
1017
|
-
effectHistory: [...instance.effectHistory],
|
|
1018
|
-
effectsContext: [...instance.effectsContext],
|
|
1019
|
-
history: [...instance.history],
|
|
1020
|
-
lastChangedAt: instance.lastChangedAt,
|
|
1021
|
-
...instance.completedAt !== void 0 ? { completedAt: instance.completedAt } : {},
|
|
1022
|
-
pendingCreates: []
|
|
1023
|
-
};
|
|
1230
|
+
const CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS = 3;
|
|
1231
|
+
class ConcurrentFireActionError extends Error {
|
|
1232
|
+
instanceId;
|
|
1233
|
+
task;
|
|
1234
|
+
action;
|
|
1235
|
+
attempts;
|
|
1236
|
+
constructor(args) {
|
|
1237
|
+
super(
|
|
1238
|
+
`Action "${args.action}" on task "${args.task}" (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.`
|
|
1239
|
+
), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.task = args.task, this.action = args.action, this.attempts = args.attempts;
|
|
1240
|
+
}
|
|
1024
1241
|
}
|
|
1025
|
-
function
|
|
1026
|
-
return
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1242
|
+
function isRevisionConflict(error) {
|
|
1243
|
+
if (typeof error != "object" || error === null) return !1;
|
|
1244
|
+
const { statusCode, message } = error;
|
|
1245
|
+
return statusCode === 409 ? !0 : typeof message == "string" && message.includes("ifRevisionId check failed");
|
|
1246
|
+
}
|
|
1247
|
+
class CascadeLimitError extends Error {
|
|
1248
|
+
instanceId;
|
|
1249
|
+
limit;
|
|
1250
|
+
constructor(args) {
|
|
1251
|
+
super(
|
|
1252
|
+
`Cascade did not stabilise after ${args.limit} auto-transitions on ${args.instanceId} \u2014 likely a runaway loop (transition guards that stay mutually satisfied). Check that each transition's filter eventually goes false.`
|
|
1253
|
+
), this.name = "CascadeLimitError", this.instanceId = args.instanceId, this.limit = args.limit;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
async function deployOrRollback(args) {
|
|
1257
|
+
try {
|
|
1258
|
+
await args.deploy();
|
|
1259
|
+
} catch (guardError) {
|
|
1260
|
+
if (!args.reversible)
|
|
1261
|
+
throw new WorkflowStateDivergedError({
|
|
1262
|
+
instanceId: args.instanceId,
|
|
1263
|
+
guardError,
|
|
1264
|
+
reason: "the move created child instances a parent-only rollback cannot undo"
|
|
1265
|
+
});
|
|
1266
|
+
try {
|
|
1267
|
+
let patch = args.client.patch(args.instanceId).set(args.restore);
|
|
1268
|
+
args.unset && args.unset.length > 0 && (patch = patch.unset(args.unset)), args.committedRev !== void 0 && (patch = patch.ifRevisionId(args.committedRev)), await patch.commit();
|
|
1269
|
+
} catch (rollbackError) {
|
|
1270
|
+
throw new WorkflowStateDivergedError({
|
|
1271
|
+
instanceId: args.instanceId,
|
|
1272
|
+
guardError,
|
|
1273
|
+
rollbackError,
|
|
1274
|
+
reason: "rollback of the committed move failed"
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
throw guardError;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
function applyTaskStatusChange(args) {
|
|
1281
|
+
const { entry, history, stage, to, at, actor } = args, from = entry.status;
|
|
1282
|
+
entry.status = to, schema.isTerminalTaskStatus(to) && (entry.completedAt = at, actor !== void 0 && (entry.completedBy = actor.id)), history.push({
|
|
1283
|
+
_key: randomKey(),
|
|
1284
|
+
_type: "taskStatusChanged",
|
|
1285
|
+
at,
|
|
1286
|
+
stage,
|
|
1287
|
+
task: entry.name,
|
|
1288
|
+
from,
|
|
1289
|
+
to,
|
|
1290
|
+
...actor !== void 0 ? { actor } : {}
|
|
1291
|
+
});
|
|
1036
1292
|
}
|
|
1037
1293
|
function stageTransitionHistory(args) {
|
|
1038
|
-
const {
|
|
1294
|
+
const { fromStage, toStage, at, transition, actor, via, reason } = args, shared = {
|
|
1039
1295
|
at,
|
|
1040
|
-
...
|
|
1296
|
+
...transition !== void 0 ? { transition } : {},
|
|
1041
1297
|
...actor !== void 0 ? { actor } : {},
|
|
1042
1298
|
...via !== void 0 ? { via } : {},
|
|
1043
1299
|
...reason !== void 0 ? { reason } : {}
|
|
@@ -1045,30 +1301,54 @@ function stageTransitionHistory(args) {
|
|
|
1045
1301
|
return [
|
|
1046
1302
|
{
|
|
1047
1303
|
_key: randomKey(),
|
|
1048
|
-
_type: "
|
|
1049
|
-
|
|
1050
|
-
|
|
1304
|
+
_type: "stageExited",
|
|
1305
|
+
stage: fromStage,
|
|
1306
|
+
toStage,
|
|
1051
1307
|
...shared
|
|
1052
1308
|
},
|
|
1053
1309
|
{
|
|
1054
1310
|
_key: randomKey(),
|
|
1055
|
-
_type: "
|
|
1056
|
-
|
|
1057
|
-
|
|
1311
|
+
_type: "transitionFired",
|
|
1312
|
+
fromStage,
|
|
1313
|
+
toStage,
|
|
1058
1314
|
...shared
|
|
1059
1315
|
},
|
|
1060
1316
|
{
|
|
1061
1317
|
_key: randomKey(),
|
|
1062
|
-
_type: "
|
|
1063
|
-
|
|
1064
|
-
|
|
1318
|
+
_type: "stageEntered",
|
|
1319
|
+
stage: toStage,
|
|
1320
|
+
fromStage,
|
|
1065
1321
|
...shared
|
|
1066
1322
|
}
|
|
1067
1323
|
];
|
|
1068
1324
|
}
|
|
1325
|
+
function startMutation(instance) {
|
|
1326
|
+
return {
|
|
1327
|
+
currentStage: instance.currentStage,
|
|
1328
|
+
// Deep-ish copy. Per-scope state entry arrays need their own copies
|
|
1329
|
+
// so ops can mutate without leaking into the source.
|
|
1330
|
+
state: (instance.state ?? []).map((s) => ({ ...s })),
|
|
1331
|
+
stages: instance.stages.map((s) => ({
|
|
1332
|
+
...s,
|
|
1333
|
+
state: (s.state ?? []).map((entry) => ({ ...entry })),
|
|
1334
|
+
tasks: s.tasks.map((t) => ({
|
|
1335
|
+
...t,
|
|
1336
|
+
...t.state !== void 0 ? { state: t.state.map((entry) => ({ ...entry })) } : {}
|
|
1337
|
+
}))
|
|
1338
|
+
})),
|
|
1339
|
+
pendingEffects: [...instance.pendingEffects],
|
|
1340
|
+
effectHistory: [...instance.effectHistory],
|
|
1341
|
+
effectsContext: [...instance.effectsContext],
|
|
1342
|
+
history: [...instance.history],
|
|
1343
|
+
lastChangedAt: instance.lastChangedAt,
|
|
1344
|
+
...instance.completedAt !== void 0 ? { completedAt: instance.completedAt } : {},
|
|
1345
|
+
...instance.abortedAt !== void 0 ? { abortedAt: instance.abortedAt } : {},
|
|
1346
|
+
pendingCreates: []
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1069
1349
|
function instanceStateFields(src) {
|
|
1070
1350
|
const fields = {
|
|
1071
|
-
|
|
1351
|
+
currentStage: src.currentStage,
|
|
1072
1352
|
state: src.state,
|
|
1073
1353
|
stages: src.stages,
|
|
1074
1354
|
pendingEffects: src.pendingEffects,
|
|
@@ -1076,12 +1356,12 @@ function instanceStateFields(src) {
|
|
|
1076
1356
|
effectsContext: src.effectsContext,
|
|
1077
1357
|
history: src.history
|
|
1078
1358
|
};
|
|
1079
|
-
return src.completedAt !== void 0 && (fields.completedAt = src.completedAt), fields;
|
|
1359
|
+
return src.completedAt !== void 0 && (fields.completedAt = src.completedAt), src.abortedAt !== void 0 && (fields.abortedAt = src.abortedAt), fields;
|
|
1080
1360
|
}
|
|
1081
1361
|
function materializeInstance(base, mutation) {
|
|
1082
1362
|
return {
|
|
1083
1363
|
...base,
|
|
1084
|
-
|
|
1364
|
+
currentStage: mutation.currentStage,
|
|
1085
1365
|
state: mutation.state,
|
|
1086
1366
|
stages: mutation.stages
|
|
1087
1367
|
};
|
|
@@ -1108,25 +1388,25 @@ function currentStageEntry(mutation) {
|
|
|
1108
1388
|
const entry = findOpenStageEntry(mutation);
|
|
1109
1389
|
if (entry === void 0)
|
|
1110
1390
|
throw new Error(
|
|
1111
|
-
`Mutation invariant broken: no current (un-exited) StageEntry for
|
|
1391
|
+
`Mutation invariant broken: no current (un-exited) StageEntry for currentStage "${mutation.currentStage}"`
|
|
1112
1392
|
);
|
|
1113
1393
|
return entry;
|
|
1114
1394
|
}
|
|
1115
1395
|
function currentTasks(mutation) {
|
|
1116
1396
|
return currentStageEntry(mutation).tasks;
|
|
1117
1397
|
}
|
|
1118
|
-
function findTaskInCurrentStage(ctx,
|
|
1119
|
-
const stage = findStage(ctx.definition, ctx.instance.
|
|
1398
|
+
function findTaskInCurrentStage(ctx, taskName) {
|
|
1399
|
+
const stage = findStage(ctx.definition, ctx.instance.currentStage), task = (stage.tasks ?? []).find((t) => t.name === taskName);
|
|
1120
1400
|
if (task === void 0)
|
|
1121
1401
|
throw new Error(
|
|
1122
|
-
`Task "${
|
|
1402
|
+
`Task "${taskName}" not found in current stage "${stage.name}" of ${ctx.definition.name}`
|
|
1123
1403
|
);
|
|
1124
1404
|
return { stage, task };
|
|
1125
1405
|
}
|
|
1126
|
-
function requireMutationTaskEntry(mutation,
|
|
1127
|
-
const mutEntry = currentTasks(mutation).find((t) => t.
|
|
1406
|
+
function requireMutationTaskEntry(mutation, task) {
|
|
1407
|
+
const mutEntry = currentTasks(mutation).find((t) => t.name === task);
|
|
1128
1408
|
if (mutEntry === void 0)
|
|
1129
|
-
throw new Error(`Task "${
|
|
1409
|
+
throw new Error(`Task "${task}" disappeared from mutation copy \u2014 invariant broken`);
|
|
1130
1410
|
return mutEntry;
|
|
1131
1411
|
}
|
|
1132
1412
|
function findCurrentStageEntry(instance) {
|
|
@@ -1135,9 +1415,251 @@ function findCurrentStageEntry(instance) {
|
|
|
1135
1415
|
function findCurrentTasks(instance) {
|
|
1136
1416
|
return findCurrentStageEntry(instance)?.tasks ?? [];
|
|
1137
1417
|
}
|
|
1418
|
+
function definitionLookupGroq(explicit) {
|
|
1419
|
+
const scoped = `_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}`;
|
|
1420
|
+
return explicit ? `*[${scoped} && version == $version][0]` : `*[${scoped}] | order(version desc)[0]`;
|
|
1421
|
+
}
|
|
1422
|
+
async function discoverItems(args) {
|
|
1423
|
+
const { client, groq, params, perspective } = args, fetchOptions = perspective !== void 0 ? { perspective } : void 0;
|
|
1424
|
+
return client.fetch(groq, paramsForLake(params), fetchOptions);
|
|
1425
|
+
}
|
|
1426
|
+
function clientForDiscovery(defaultClient, clientForGdr, subjectGdrUri) {
|
|
1427
|
+
if (!subjectGdrUri || !subjectGdrUri.includes(":")) return defaultClient;
|
|
1428
|
+
try {
|
|
1429
|
+
return clientForGdr(parseGdr(subjectGdrUri));
|
|
1430
|
+
} catch {
|
|
1431
|
+
return defaultClient;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
function effectsContextEntry(name, value) {
|
|
1435
|
+
const _key = randomKey();
|
|
1436
|
+
return typeof value == "string" ? { _key, _type: "effectsContext.string", name, value } : typeof value == "number" ? { _key, _type: "effectsContext.number", name, value } : typeof value == "boolean" ? { _key, _type: "effectsContext.boolean", name, value } : { _key, _type: "effectsContext.ref", name, value };
|
|
1437
|
+
}
|
|
1438
|
+
function effectsContextJsonEntry(name, value) {
|
|
1439
|
+
return { _key: randomKey(), _type: "effectsContext.json", name, value: JSON.stringify(value) };
|
|
1440
|
+
}
|
|
1441
|
+
function buildInstanceBase(args) {
|
|
1442
|
+
const { id, now, actor, perspective } = args;
|
|
1443
|
+
return {
|
|
1444
|
+
_id: id,
|
|
1445
|
+
_type: WORKFLOW_INSTANCE_TYPE,
|
|
1446
|
+
_rev: "",
|
|
1447
|
+
_createdAt: now,
|
|
1448
|
+
_updatedAt: now,
|
|
1449
|
+
tags: args.tags,
|
|
1450
|
+
workflowResource: args.workflowResource,
|
|
1451
|
+
definition: args.definitionName,
|
|
1452
|
+
pinnedVersion: args.pinnedVersion,
|
|
1453
|
+
definitionSnapshot: JSON.stringify(args.definition),
|
|
1454
|
+
state: args.state,
|
|
1455
|
+
effectsContext: args.effectsContext,
|
|
1456
|
+
ancestors: args.ancestors,
|
|
1457
|
+
...perspective !== void 0 ? { perspective } : {},
|
|
1458
|
+
currentStage: args.initialStage,
|
|
1459
|
+
stages: [],
|
|
1460
|
+
pendingEffects: [],
|
|
1461
|
+
effectHistory: [],
|
|
1462
|
+
history: [
|
|
1463
|
+
{
|
|
1464
|
+
_key: randomKey(),
|
|
1465
|
+
_type: "stageEntered",
|
|
1466
|
+
at: now,
|
|
1467
|
+
stage: args.initialStage,
|
|
1468
|
+
...actor !== void 0 ? { actor } : {}
|
|
1469
|
+
}
|
|
1470
|
+
],
|
|
1471
|
+
startedAt: now,
|
|
1472
|
+
lastChangedAt: now
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
const MAX_SPAWN_DEPTH = 6, SUBWORKFLOWS_ALL_DONE = "count($subworkflows[status != 'done']) == 0", FAIL_WHEN_SYSTEM_ID = "engine.failWhen", COMPLETE_WHEN_SYSTEM_ID = "engine.completeWhen";
|
|
1476
|
+
function gateActor(outcome) {
|
|
1477
|
+
return {
|
|
1478
|
+
kind: "system",
|
|
1479
|
+
id: outcome === "failed" ? FAIL_WHEN_SYSTEM_ID : COMPLETE_WHEN_SYSTEM_ID
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
function effectiveCompleteWhen(task) {
|
|
1483
|
+
return task.completeWhen ?? (task.subworkflows !== void 0 ? SUBWORKFLOWS_ALL_DONE : void 0);
|
|
1484
|
+
}
|
|
1485
|
+
async function evaluateResolutionGate(ctx, task, vars) {
|
|
1486
|
+
const scope = { taskName: task.name, vars };
|
|
1487
|
+
if (task.failWhen !== void 0 && await ctxEvaluateCondition(ctx, task.failWhen, scope))
|
|
1488
|
+
return "failed";
|
|
1489
|
+
const completeWhen = effectiveCompleteWhen(task);
|
|
1490
|
+
if (completeWhen !== void 0 && await ctxEvaluateCondition(ctx, completeWhen, scope))
|
|
1491
|
+
return "done";
|
|
1492
|
+
}
|
|
1493
|
+
async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
1494
|
+
if (ctx.instance.ancestors.length + 1 > MAX_SPAWN_DEPTH) {
|
|
1495
|
+
const chain = [...ctx.instance.ancestors.map((a) => a.id), selfGdr(ctx.instance)].join(" \u2192 ");
|
|
1496
|
+
throw new Error(
|
|
1497
|
+
`Subworkflow depth limit (${MAX_SPAWN_DEPTH}) exceeded on task "${task.name}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tags ?? []);
|
|
1501
|
+
if (definition === null) {
|
|
1502
|
+
const versionLabel = sub.definition.version === void 0 || sub.definition.version === "latest" ? "latest" : `v${sub.definition.version}`;
|
|
1503
|
+
throw new Error(
|
|
1504
|
+
`Subworkflow definition "${sub.definition.name}" ${versionLabel} not deployed (parent ${ctx.instance._id}, task ${task.name})`
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
const parentScope = await ctxConditionParams(ctx, {
|
|
1508
|
+
taskName: task.name,
|
|
1509
|
+
...actor ? { actor } : {}
|
|
1510
|
+
}), rows = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
|
|
1511
|
+
for (const row of rows) {
|
|
1512
|
+
const initialState = await projectRowState(ctx, sub, definition, parentScope, row), { ref, body } = await prepareChildInstance({
|
|
1513
|
+
client: ctx.client,
|
|
1514
|
+
parent: ctx.instance,
|
|
1515
|
+
definition,
|
|
1516
|
+
initialState,
|
|
1517
|
+
effectsContext,
|
|
1518
|
+
actor,
|
|
1519
|
+
now
|
|
1520
|
+
});
|
|
1521
|
+
refs.push(ref), mutation.pendingCreates.push(body);
|
|
1522
|
+
}
|
|
1523
|
+
return refs;
|
|
1524
|
+
}
|
|
1525
|
+
async function resolveSpawnRows(ctx, sub) {
|
|
1526
|
+
const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
|
|
1527
|
+
let value;
|
|
1528
|
+
if (groq.includes("*")) {
|
|
1529
|
+
const routingUri = firstDocRefGdrUri(ctx.instance.state), client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
|
|
1530
|
+
value = await discoverItems({
|
|
1531
|
+
client,
|
|
1532
|
+
groq,
|
|
1533
|
+
params,
|
|
1534
|
+
...ctx.instance.perspective !== void 0 ? { perspective: ctx.instance.perspective } : {}
|
|
1535
|
+
});
|
|
1536
|
+
} else {
|
|
1537
|
+
const tree = groqJs.parse(groq, { params });
|
|
1538
|
+
value = await (await groqJs.evaluate(tree, { dataset: [ctx.instance], params, root: ctx.instance })).get();
|
|
1539
|
+
}
|
|
1540
|
+
return Array.isArray(value) ? value : value == null ? [] : [value];
|
|
1541
|
+
}
|
|
1542
|
+
function firstDocRefGdrUri(entries) {
|
|
1543
|
+
if (entries) {
|
|
1544
|
+
for (const s of entries)
|
|
1545
|
+
if (s._type === "doc.ref" && s.value !== null) return s.value.id;
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
async function projectRowState(ctx, sub, childDefinition, parentScope, row) {
|
|
1549
|
+
const out = [];
|
|
1550
|
+
for (const [name, groq] of Object.entries(sub.with ?? {})) {
|
|
1551
|
+
const declared = (childDefinition.state ?? []).find((e) => e.name === name);
|
|
1552
|
+
if (declared === void 0)
|
|
1553
|
+
throw new Error(
|
|
1554
|
+
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no state entry "${name}"`
|
|
1555
|
+
);
|
|
1556
|
+
const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, ctx.instance.workflowResource);
|
|
1557
|
+
value != null && out.push({
|
|
1558
|
+
type: declared.type,
|
|
1559
|
+
name,
|
|
1560
|
+
value
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
return out;
|
|
1564
|
+
}
|
|
1565
|
+
async function resolveContextHandoff(ctx, sub, parentScope) {
|
|
1566
|
+
const out = {};
|
|
1567
|
+
for (const [name, groq] of Object.entries(sub.context ?? {})) {
|
|
1568
|
+
const v2 = await runGroq(groq, parentScope, ctx.snapshot);
|
|
1569
|
+
v2 != null && (typeof v2 == "string" || typeof v2 == "number" || typeof v2 == "boolean" || typeof v2 == "object" && "id" in v2 && "type" in v2) && (out[name] = v2);
|
|
1570
|
+
}
|
|
1571
|
+
return out;
|
|
1572
|
+
}
|
|
1573
|
+
function canonicaliseSpawnValue(kind, value, workflowResource) {
|
|
1574
|
+
return kind === "doc.ref" ? coerceSpawnGdr(value, workflowResource) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, workflowResource)).filter((v2) => v2 !== null) : value;
|
|
1575
|
+
}
|
|
1576
|
+
function coerceSpawnGdr(raw, workflowResource) {
|
|
1577
|
+
const toUri = (id) => isGdrUri(id) ? id : gdrFromResource(workflowResource, id);
|
|
1578
|
+
if (raw == null) return null;
|
|
1579
|
+
if (typeof raw == "object" && "id" in raw && "type" in raw) {
|
|
1580
|
+
const r = raw;
|
|
1581
|
+
if (typeof r.id == "string" && typeof r.type == "string")
|
|
1582
|
+
return { id: toUri(r.id), type: r.type };
|
|
1583
|
+
}
|
|
1584
|
+
if (typeof raw == "object" && "_ref" in raw) {
|
|
1585
|
+
const r = raw;
|
|
1586
|
+
if (typeof r._ref == "string") return { id: toUri(r._ref), type: "document" };
|
|
1587
|
+
}
|
|
1588
|
+
return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
|
|
1589
|
+
}
|
|
1590
|
+
async function resolveDefinitionRef(client, ref, tags) {
|
|
1591
|
+
const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, engineTags: tags };
|
|
1592
|
+
return wantsExplicit && (params.version = ref.version), await client.fetch(
|
|
1593
|
+
definitionLookupGroq(wantsExplicit),
|
|
1594
|
+
params
|
|
1595
|
+
) ?? null;
|
|
1596
|
+
}
|
|
1597
|
+
async function prepareChildInstance(args) {
|
|
1598
|
+
const { client, parent, definition, initialState, effectsContext, actor, now } = args, childTags = parent.tags ?? [], childPrefix = childTags[0] ?? "wf", workflowResource = parent.workflowResource, childDocId = `${childPrefix}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type: WORKFLOW_INSTANCE_TYPE }, ancestors = [
|
|
1599
|
+
...parent.ancestors,
|
|
1600
|
+
{
|
|
1601
|
+
id: selfGdr(parent),
|
|
1602
|
+
type: WORKFLOW_INSTANCE_TYPE
|
|
1603
|
+
}
|
|
1604
|
+
], inheritedPerspective = parent.perspective, childState = await resolveDeclaredState({
|
|
1605
|
+
entryDefs: definition.state,
|
|
1606
|
+
initialState,
|
|
1607
|
+
ctx: {
|
|
1608
|
+
client,
|
|
1609
|
+
now,
|
|
1610
|
+
selfId: childDocId,
|
|
1611
|
+
tags: childTags,
|
|
1612
|
+
workflowResource,
|
|
1613
|
+
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
|
|
1614
|
+
},
|
|
1615
|
+
randomKey
|
|
1616
|
+
}), childPerspective = derivePerspectiveFromState(childState) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
|
|
1617
|
+
([key, value]) => {
|
|
1618
|
+
if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
|
|
1619
|
+
if (!isGdrUri(value.id))
|
|
1620
|
+
throw new Error(
|
|
1621
|
+
`subworkflows.context["${key}"]: GDR id must be a "<scheme>:..." URI, got ${JSON.stringify(value.id)}.`
|
|
1622
|
+
);
|
|
1623
|
+
return effectsContextEntry(key, { id: value.id, type: value.type });
|
|
1624
|
+
}
|
|
1625
|
+
return effectsContextEntry(key, value);
|
|
1626
|
+
}
|
|
1627
|
+
), base = buildInstanceBase({
|
|
1628
|
+
id: childDocId,
|
|
1629
|
+
now,
|
|
1630
|
+
tags: childTags,
|
|
1631
|
+
workflowResource,
|
|
1632
|
+
definitionName: definition.name,
|
|
1633
|
+
pinnedVersion: definition.version,
|
|
1634
|
+
definition,
|
|
1635
|
+
state: childState,
|
|
1636
|
+
effectsContext: effectsContextEntries,
|
|
1637
|
+
ancestors,
|
|
1638
|
+
perspective: childPerspective,
|
|
1639
|
+
initialStage: definition.initialStage,
|
|
1640
|
+
actor
|
|
1641
|
+
});
|
|
1642
|
+
return { ref: childRef, body: base };
|
|
1643
|
+
}
|
|
1644
|
+
async function subworkflowRows(ctx, refs) {
|
|
1645
|
+
if (refs.length === 0) return [];
|
|
1646
|
+
const ids = refs.map((r) => gdrToBareId(r.id));
|
|
1647
|
+
return (await ctx.client.fetch("*[_id in $ids]{_id, currentStage, completedAt}", { ids })).map((c) => ({
|
|
1648
|
+
_id: c._id,
|
|
1649
|
+
stage: c.currentStage,
|
|
1650
|
+
status: c.completedAt !== void 0 && c.completedAt !== null ? "done" : "active"
|
|
1651
|
+
}));
|
|
1652
|
+
}
|
|
1653
|
+
async function resolveBindings(args) {
|
|
1654
|
+
const resolved = {};
|
|
1655
|
+
for (const [key, groq] of Object.entries(args.bindings ?? {}))
|
|
1656
|
+
resolved[key] = await runGroq(groq, args.params, args.snapshot);
|
|
1657
|
+
return { ...resolved, ...args.staticInput };
|
|
1658
|
+
}
|
|
1138
1659
|
async function completeEffect(args) {
|
|
1139
1660
|
const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1140
|
-
...options?.clock ? { clock: options.clock } : {}
|
|
1661
|
+
...options?.clock ? { clock: options.clock } : {},
|
|
1662
|
+
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
1141
1663
|
});
|
|
1142
1664
|
return commitCompleteEffect(
|
|
1143
1665
|
ctx,
|
|
@@ -1154,7 +1676,7 @@ function buildEffectHistoryEntry(pending, outcome) {
|
|
|
1154
1676
|
const { status, ranAt, actor, detail, error, durationMs, outputs } = outcome, resolvedActor = actor ?? pending.actor;
|
|
1155
1677
|
return {
|
|
1156
1678
|
_key: pending._key,
|
|
1157
|
-
|
|
1679
|
+
name: pending.name,
|
|
1158
1680
|
...pending.title !== void 0 ? { title: pending.title } : {},
|
|
1159
1681
|
...pending.description !== void 0 ? { description: pending.description } : {},
|
|
1160
1682
|
params: pending.params,
|
|
@@ -1168,11 +1690,9 @@ function buildEffectHistoryEntry(pending, outcome) {
|
|
|
1168
1690
|
...outputs !== void 0 ? { outputs } : {}
|
|
1169
1691
|
};
|
|
1170
1692
|
}
|
|
1171
|
-
function upsertEffectsContext(mutation, outputs) {
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
existingIndex >= 0 ? mutation.effectsContext[existingIndex] = entry : mutation.effectsContext.push(entry);
|
|
1175
|
-
}
|
|
1693
|
+
function upsertEffectsContext(mutation, effectName, outputs) {
|
|
1694
|
+
const existingIndex = mutation.effectsContext.findIndex((e) => e.name === effectName), entry = effectsContextJsonEntry(effectName, outputs);
|
|
1695
|
+
existingIndex >= 0 ? mutation.effectsContext[existingIndex] = entry : mutation.effectsContext.push(entry);
|
|
1176
1696
|
}
|
|
1177
1697
|
async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, error, durationMs, actor) {
|
|
1178
1698
|
const pending = ctx.instance.pendingEffects.find((e) => e._key === effectKey);
|
|
@@ -1183,12 +1703,12 @@ async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, err
|
|
|
1183
1703
|
const ranAt = ctx.now;
|
|
1184
1704
|
return mutation.effectHistory.push(
|
|
1185
1705
|
buildEffectHistoryEntry(pending, { status, ranAt, actor, detail, error, durationMs, outputs })
|
|
1186
|
-
), status === "done" && outputs !== void 0 && upsertEffectsContext(mutation, outputs), mutation.history.push({
|
|
1706
|
+
), status === "done" && outputs !== void 0 && upsertEffectsContext(mutation, pending.name, outputs), mutation.history.push({
|
|
1187
1707
|
_key: randomKey(),
|
|
1188
|
-
_type: "
|
|
1708
|
+
_type: "effectCompleted",
|
|
1189
1709
|
at: ranAt,
|
|
1190
1710
|
effectKey,
|
|
1191
|
-
|
|
1711
|
+
effect: pending.name,
|
|
1192
1712
|
status,
|
|
1193
1713
|
...outputs !== void 0 ? { outputs } : {},
|
|
1194
1714
|
...detail !== void 0 ? { detail } : {},
|
|
@@ -1198,8 +1718,8 @@ async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, err
|
|
|
1198
1718
|
function buildQueuedEffect(effect, origin, params, actor, now) {
|
|
1199
1719
|
const key = randomKey(), pending = {
|
|
1200
1720
|
_key: key,
|
|
1201
|
-
_type: "
|
|
1202
|
-
|
|
1721
|
+
_type: "pendingEffect",
|
|
1722
|
+
name: effect.name,
|
|
1203
1723
|
...effect.title !== void 0 ? { title: effect.title } : {},
|
|
1204
1724
|
...effect.description !== void 0 ? { description: effect.description } : {},
|
|
1205
1725
|
...effect.bindings !== void 0 ? { bindings: effect.bindings } : {},
|
|
@@ -1209,370 +1729,280 @@ function buildQueuedEffect(effect, origin, params, actor, now) {
|
|
|
1209
1729
|
queuedAt: now
|
|
1210
1730
|
}, history = {
|
|
1211
1731
|
_key: randomKey(),
|
|
1212
|
-
_type: "
|
|
1732
|
+
_type: "effectQueued",
|
|
1213
1733
|
at: now,
|
|
1214
1734
|
effectKey: key,
|
|
1215
|
-
|
|
1735
|
+
effect: effect.name,
|
|
1216
1736
|
origin
|
|
1217
1737
|
};
|
|
1218
1738
|
return { pending, history };
|
|
1219
1739
|
}
|
|
1220
|
-
async function queueEffects(ctx, mutation, effects, origin, actor,
|
|
1221
|
-
if (!effects) return;
|
|
1222
|
-
const now = ctx.now,
|
|
1223
|
-
...
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
};
|
|
1740
|
+
async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
|
|
1741
|
+
if (!effects || effects.length === 0) return;
|
|
1742
|
+
const now = ctx.now, liveCtx = { ...ctx, instance: materializeInstance(ctx.instance, mutation) }, params = await ctxConditionParams(liveCtx, {
|
|
1743
|
+
...opts?.taskName !== void 0 ? { taskName: opts.taskName } : {},
|
|
1744
|
+
...actor !== void 0 ? { actor } : {},
|
|
1745
|
+
// Caller-supplied action params, readable in bindings as `$params.<name>`.
|
|
1746
|
+
vars: { params: opts?.callerParams ?? {} }
|
|
1747
|
+
});
|
|
1228
1748
|
for (const effect of effects) {
|
|
1229
|
-
const
|
|
1749
|
+
const resolved = await resolveBindings({
|
|
1230
1750
|
bindings: effect.bindings,
|
|
1231
1751
|
staticInput: effect.input,
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
...actor !== void 0 ? { actor } : {}
|
|
1236
|
-
}), { pending, history } = buildQueuedEffect(effect, origin, params, actor, now);
|
|
1752
|
+
snapshot: ctx.snapshot,
|
|
1753
|
+
params
|
|
1754
|
+
}), { pending, history } = buildQueuedEffect(effect, origin, resolved, actor, now);
|
|
1237
1755
|
mutation.pendingEffects.push(pending), mutation.history.push(history);
|
|
1238
1756
|
}
|
|
1239
1757
|
}
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1758
|
+
function resolveStaticSource(src, ctx) {
|
|
1759
|
+
switch (src.type) {
|
|
1760
|
+
case "literal":
|
|
1761
|
+
return { handled: !0, value: src.value };
|
|
1762
|
+
case "param":
|
|
1763
|
+
return { handled: !0, value: ctx.params?.[src.param] };
|
|
1764
|
+
case "actor":
|
|
1765
|
+
return { handled: !0, value: ctx.actor };
|
|
1766
|
+
case "now":
|
|
1767
|
+
return { handled: !0, value: ctx.now };
|
|
1768
|
+
default:
|
|
1769
|
+
return { handled: !1 };
|
|
1249
1770
|
}
|
|
1250
1771
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
{ cause: args.guardError }
|
|
1261
|
-
), this.name = "WorkflowStateDivergedError", this.instanceId = args.instanceId, this.guardError = args.guardError, args.rollbackError !== void 0 && (this.rollbackError = args.rollbackError);
|
|
1262
|
-
}
|
|
1772
|
+
const STATE_OP_TYPES = [
|
|
1773
|
+
"state.set",
|
|
1774
|
+
"state.unset",
|
|
1775
|
+
"state.append",
|
|
1776
|
+
"state.updateWhere",
|
|
1777
|
+
"state.removeWhere"
|
|
1778
|
+
];
|
|
1779
|
+
function isStateOp(summary) {
|
|
1780
|
+
return STATE_OP_TYPES.includes(summary.opType);
|
|
1263
1781
|
}
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1782
|
+
function validateActionParams(action, taskName, callerParams) {
|
|
1783
|
+
const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
|
|
1784
|
+
for (const decl of declared) {
|
|
1785
|
+
const value = params[decl.name], present = decl.name in params && value !== void 0 && value !== null;
|
|
1786
|
+
if (decl.required === !0 && !present) {
|
|
1787
|
+
issues.push({ param: decl.name, reason: "required but missing" });
|
|
1788
|
+
continue;
|
|
1789
|
+
}
|
|
1790
|
+
present && (checkParamType(value, decl) || issues.push({
|
|
1791
|
+
param: decl.name,
|
|
1792
|
+
reason: `expected type=${decl.type}, got ${typeof value}`
|
|
1793
|
+
}));
|
|
1274
1794
|
}
|
|
1795
|
+
if (issues.length > 0)
|
|
1796
|
+
throw new ActionParamsInvalidError({ action: action.name, task: taskName, issues });
|
|
1797
|
+
return params;
|
|
1275
1798
|
}
|
|
1276
|
-
function
|
|
1277
|
-
|
|
1278
|
-
const { statusCode, message } = error;
|
|
1279
|
-
return statusCode === 409 ? !0 : typeof message == "string" && message.includes("ifRevisionId check failed");
|
|
1799
|
+
function hasStringField(value, field) {
|
|
1800
|
+
return typeof value == "object" && value !== null && field in value && typeof value[field] == "string";
|
|
1280
1801
|
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1802
|
+
function checkParamType(value, decl) {
|
|
1803
|
+
switch (decl.type) {
|
|
1804
|
+
case "string":
|
|
1805
|
+
case "url":
|
|
1806
|
+
case "dateTime":
|
|
1807
|
+
return typeof value == "string";
|
|
1808
|
+
case "number":
|
|
1809
|
+
return typeof value == "number" && Number.isFinite(value);
|
|
1810
|
+
case "boolean":
|
|
1811
|
+
return typeof value == "boolean";
|
|
1812
|
+
case "actor":
|
|
1813
|
+
return hasStringField(value, "kind");
|
|
1814
|
+
case "doc.ref":
|
|
1815
|
+
return hasStringField(value, "id");
|
|
1816
|
+
case "doc.refs":
|
|
1817
|
+
return Array.isArray(value) && value.every((v2) => checkParamType(v2, { type: "doc.ref" }));
|
|
1818
|
+
case "json":
|
|
1819
|
+
return !0;
|
|
1820
|
+
default:
|
|
1821
|
+
return !1;
|
|
1288
1822
|
}
|
|
1289
1823
|
}
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
});
|
|
1310
|
-
}
|
|
1311
|
-
throw guardError;
|
|
1824
|
+
function runOps(args) {
|
|
1825
|
+
const { ops, mutation, stage, origin, params, actor, self, now } = args;
|
|
1826
|
+
if (ops === void 0 || ops.length === 0) return [];
|
|
1827
|
+
const summaries = [];
|
|
1828
|
+
for (const op of ops) {
|
|
1829
|
+
const summary = applyOp(op, { mutation, stage, taskName: origin.task, params, actor, self, now });
|
|
1830
|
+
summaries.push(summary), mutation.history.push({
|
|
1831
|
+
_key: randomKey(),
|
|
1832
|
+
_type: "opApplied",
|
|
1833
|
+
at: now,
|
|
1834
|
+
stage,
|
|
1835
|
+
...origin.task !== void 0 ? { task: origin.task } : {},
|
|
1836
|
+
...origin.action !== void 0 ? { action: origin.action } : {},
|
|
1837
|
+
...origin.transition !== void 0 ? { transition: origin.transition } : {},
|
|
1838
|
+
opType: summary.opType,
|
|
1839
|
+
...summary.target !== void 0 ? { target: summary.target } : {},
|
|
1840
|
+
...summary.resolved !== void 0 ? { resolved: summary.resolved } : {},
|
|
1841
|
+
...actor !== void 0 ? { actor } : {}
|
|
1842
|
+
});
|
|
1312
1843
|
}
|
|
1844
|
+
return summaries;
|
|
1313
1845
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1846
|
+
function applyOp(op, ctx) {
|
|
1847
|
+
switch (op.type) {
|
|
1848
|
+
case "state.set":
|
|
1849
|
+
return applyStateSet(op, ctx);
|
|
1850
|
+
case "state.unset":
|
|
1851
|
+
return applyStateUnset(op, ctx);
|
|
1852
|
+
case "state.append":
|
|
1853
|
+
return applyStateAppend(op, ctx);
|
|
1854
|
+
case "state.updateWhere":
|
|
1855
|
+
return applyStateUpdateWhere(op, ctx);
|
|
1856
|
+
case "state.removeWhere":
|
|
1857
|
+
return applyStateRemoveWhere(op, ctx);
|
|
1858
|
+
case "status.set":
|
|
1859
|
+
return applyStatusSet(op, ctx);
|
|
1860
|
+
}
|
|
1326
1861
|
}
|
|
1327
|
-
function
|
|
1328
|
-
|
|
1862
|
+
function applyStateSet(op, ctx) {
|
|
1863
|
+
const value = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
|
|
1864
|
+
return validateStateValue({ entryType: entry._type, entryName: entry.name, value }), setEntryValue(entry, value), { opType: op.type, target: op.target, resolved: { value } };
|
|
1865
|
+
}
|
|
1866
|
+
const EMPTY_BY_KIND = {
|
|
1867
|
+
"doc.refs": [],
|
|
1868
|
+
checklist: [],
|
|
1869
|
+
notes: [],
|
|
1870
|
+
assignees: []
|
|
1871
|
+
};
|
|
1872
|
+
function applyStateUnset(op, ctx) {
|
|
1873
|
+
const entry = locateEntry(ctx, op.target);
|
|
1874
|
+
return setEntryValue(entry, EMPTY_BY_KIND[entry._type] ?? null), { opType: op.type, target: op.target };
|
|
1329
1875
|
}
|
|
1330
|
-
function
|
|
1331
|
-
const
|
|
1332
|
-
|
|
1876
|
+
function applyStateAppend(op, ctx) {
|
|
1877
|
+
const item = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
|
|
1878
|
+
validateStateAppendItem({ entryType: entry._type, entryName: entry.name, item });
|
|
1879
|
+
const current = Array.isArray(entry.value) ? entry.value : [];
|
|
1880
|
+
return setEntryValue(entry, [...current, withRowKey(item)]), { opType: op.type, target: op.target, resolved: { item } };
|
|
1333
1881
|
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
return client.fetch(groq, paramsForLake(params), fetchOptions);
|
|
1882
|
+
function withRowKey(item) {
|
|
1883
|
+
return typeof item == "object" && item !== null && !Array.isArray(item) && !("_key" in item) ? { _key: randomKey(), ...item } : item;
|
|
1337
1884
|
}
|
|
1338
|
-
function
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1885
|
+
function applyStateUpdateWhere(op, ctx) {
|
|
1886
|
+
const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op), merge = resolveOpSource(op.value, ctx);
|
|
1887
|
+
if (merge === null || typeof merge != "object" || Array.isArray(merge))
|
|
1888
|
+
throw new Error(
|
|
1889
|
+
`state.updateWhere value must resolve to an object of fields to merge (target "${op.target.state}")`
|
|
1890
|
+
);
|
|
1891
|
+
return setEntryValue(
|
|
1892
|
+
entry,
|
|
1893
|
+
rows.map(
|
|
1894
|
+
(row) => evalOpPredicate(op.where, row, ctx) ? { ...row, ...merge } : row
|
|
1895
|
+
)
|
|
1896
|
+
), { opType: op.type, target: op.target, resolved: { merge } };
|
|
1345
1897
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1898
|
+
function applyStateRemoveWhere(op, ctx) {
|
|
1899
|
+
const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op);
|
|
1900
|
+
return setEntryValue(
|
|
1901
|
+
entry,
|
|
1902
|
+
rows.filter((row) => !evalOpPredicate(op.where, row, ctx))
|
|
1903
|
+
), { opType: op.type, target: op.target };
|
|
1904
|
+
}
|
|
1905
|
+
function requireArrayValue(entry, op) {
|
|
1906
|
+
if (!Array.isArray(entry.value))
|
|
1350
1907
|
throw new Error(
|
|
1351
|
-
|
|
1908
|
+
`${op.type} target ${op.target.scope}:"${op.target.state}" holds a non-array ${entry._type} value \u2014 where-ops operate on array entries`
|
|
1352
1909
|
);
|
|
1353
|
-
|
|
1354
|
-
const now = ctx.now, rows = await resolveSpawnRows(ctx, spawn), refs = [];
|
|
1355
|
-
for (const row of rows) {
|
|
1356
|
-
const projected = projectSpawnRow(ctx.instance, spawn, row, now), { ref, body } = await prepareChildInstance({
|
|
1357
|
-
client: ctx.client,
|
|
1358
|
-
parent: ctx.instance,
|
|
1359
|
-
task,
|
|
1360
|
-
spawn,
|
|
1361
|
-
initialState: projected.initialState,
|
|
1362
|
-
effectsContext: projected.effectsContext,
|
|
1363
|
-
actor,
|
|
1364
|
-
now
|
|
1365
|
-
});
|
|
1366
|
-
refs.push(ref), mutation.pendingCreates.push(body);
|
|
1367
|
-
}
|
|
1368
|
-
return refs;
|
|
1910
|
+
return entry.value;
|
|
1369
1911
|
}
|
|
1370
|
-
|
|
1371
|
-
const
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1912
|
+
function applyStatusSet(op, ctx) {
|
|
1913
|
+
const entry = findOpenStageEntry(ctx.mutation)?.tasks.find((t) => t.name === op.task);
|
|
1914
|
+
if (entry === void 0)
|
|
1915
|
+
throw new Error(`status.set targets task "${op.task}" which has no entry in the open stage`);
|
|
1916
|
+
return applyTaskStatusChange({
|
|
1917
|
+
entry,
|
|
1918
|
+
history: ctx.mutation.history,
|
|
1919
|
+
stage: ctx.stage,
|
|
1920
|
+
to: op.status,
|
|
1921
|
+
at: ctx.now,
|
|
1922
|
+
...ctx.actor !== void 0 ? { actor: ctx.actor } : {}
|
|
1923
|
+
}), { opType: op.type, resolved: { task: op.task, status: op.status } };
|
|
1924
|
+
}
|
|
1925
|
+
function setEntryValue(entry, value) {
|
|
1926
|
+
entry.value = value;
|
|
1927
|
+
}
|
|
1928
|
+
function locateEntry(ctx, target) {
|
|
1929
|
+
const entry = stateHost(ctx, target.scope)?.find((s) => s.name === target.state);
|
|
1930
|
+
if (entry === void 0)
|
|
1931
|
+
throw new Error(
|
|
1932
|
+
`Op target ${target.scope}:"${target.state}" has no resolved state entry on this instance \u2014 the deployed definition and the instance state have diverged`
|
|
1933
|
+
);
|
|
1934
|
+
return entry;
|
|
1386
1935
|
}
|
|
1387
|
-
function
|
|
1388
|
-
if (
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1936
|
+
function stateHost(ctx, scope) {
|
|
1937
|
+
if (scope === "workflow") return ctx.mutation.state;
|
|
1938
|
+
const stageEntry = findOpenStageEntry(ctx.mutation);
|
|
1939
|
+
if (scope === "stage") return stageEntry?.state;
|
|
1940
|
+
const task = stageEntry?.tasks.find((t) => t.name === ctx.taskName);
|
|
1941
|
+
return task !== void 0 && task.state === void 0 && (task.state = []), task?.state;
|
|
1392
1942
|
}
|
|
1393
|
-
function
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
case "row":
|
|
1398
|
-
return source.path !== void 0 ? getPath(row, source.path) : row;
|
|
1399
|
-
case "parentState": {
|
|
1400
|
-
const slot = parent.state?.find((s) => s.id === source.slotId);
|
|
1401
|
-
return slot === void 0 ? null : source.path !== void 0 ? getPath(slot, source.path) : slot;
|
|
1402
|
-
}
|
|
1943
|
+
function resolveOpSource(src, ctx) {
|
|
1944
|
+
const staticValue = resolveStaticSource(src, ctx);
|
|
1945
|
+
if (staticValue.handled) return staticValue.value;
|
|
1946
|
+
switch (src.type) {
|
|
1403
1947
|
case "self":
|
|
1404
|
-
return
|
|
1405
|
-
case "
|
|
1406
|
-
return
|
|
1407
|
-
case "
|
|
1408
|
-
|
|
1948
|
+
return ctx.self;
|
|
1949
|
+
case "stage":
|
|
1950
|
+
return ctx.stage;
|
|
1951
|
+
case "stateRead": {
|
|
1952
|
+
const value = readEntryFromMutation(ctx, src);
|
|
1953
|
+
return src.path !== void 0 ? getPath(value, src.path) : value;
|
|
1954
|
+
}
|
|
1409
1955
|
case "object": {
|
|
1410
1956
|
const out = {};
|
|
1411
|
-
for (const [
|
|
1412
|
-
out[
|
|
1957
|
+
for (const [field, fieldSrc] of Object.entries(src.fields))
|
|
1958
|
+
out[field] = resolveOpSource(fieldSrc, ctx);
|
|
1413
1959
|
return out;
|
|
1414
1960
|
}
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
case "stateRead":
|
|
1418
|
-
case "effectOutput":
|
|
1419
|
-
return null;
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
function projectSpawnRow(parent, spawn, row, now) {
|
|
1423
|
-
const initialState = [];
|
|
1424
|
-
for (const entry of spawn.forEach.as.initialState ?? []) {
|
|
1425
|
-
const raw = resolveSpawnSource(entry.value, parent, row, now), value = canonicaliseSpawnValue(entry.type, raw, parent.workflowResource);
|
|
1426
|
-
initialState.push({
|
|
1427
|
-
type: entry.type,
|
|
1428
|
-
id: entry.id,
|
|
1429
|
-
value
|
|
1430
|
-
});
|
|
1431
|
-
}
|
|
1432
|
-
const effectsContext = {};
|
|
1433
|
-
for (const [k, src] of Object.entries(spawn.forEach.as.effectsContext ?? {})) {
|
|
1434
|
-
const v2 = resolveSpawnSource(src, parent, row, now);
|
|
1435
|
-
v2 != null && (typeof v2 == "string" || typeof v2 == "number" || typeof v2 == "boolean" || typeof v2 == "object" && "id" in v2 && "type" in v2) && (effectsContext[k] = v2);
|
|
1436
|
-
}
|
|
1437
|
-
return { initialState, effectsContext };
|
|
1438
|
-
}
|
|
1439
|
-
function canonicaliseSpawnValue(slotType, value, workflowResource) {
|
|
1440
|
-
return slotType === "workflow.state.doc.ref" ? coerceSpawnGdr(value, workflowResource) : slotType === "workflow.state.doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, workflowResource)).filter((v2) => v2 !== null) : value;
|
|
1441
|
-
}
|
|
1442
|
-
function coerceSpawnGdr(raw, workflowResource) {
|
|
1443
|
-
const toUri = (id) => isGdrUri(id) ? id : gdrFromResource(workflowResource, id);
|
|
1444
|
-
if (raw == null) return null;
|
|
1445
|
-
if (typeof raw == "object" && "id" in raw && "type" in raw) {
|
|
1446
|
-
const r = raw;
|
|
1447
|
-
if (typeof r.id == "string" && typeof r.type == "string")
|
|
1448
|
-
return { id: toUri(r.id), type: r.type };
|
|
1449
|
-
}
|
|
1450
|
-
if (typeof raw == "object" && "_ref" in raw) {
|
|
1451
|
-
const r = raw;
|
|
1452
|
-
if (typeof r._ref == "string") return { id: toUri(r._ref), type: "document" };
|
|
1961
|
+
default:
|
|
1962
|
+
throw new Error(`Source type "${src.type}" is a state-entry origin, not a value`);
|
|
1453
1963
|
}
|
|
1454
|
-
return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
|
|
1455
1964
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
return wantsExplicit && (params.version = ref.version), await client.fetch(
|
|
1459
|
-
definitionLookupGroq(wantsExplicit),
|
|
1460
|
-
params
|
|
1461
|
-
) ?? null;
|
|
1965
|
+
function entryValue(entries, name) {
|
|
1966
|
+
return entries?.find((s) => s.name === name)?.value;
|
|
1462
1967
|
}
|
|
1463
|
-
|
|
1464
|
-
const
|
|
1465
|
-
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1468
|
-
`Spawn target workflowId="${spawn.definitionRef.workflowId}" ${versionLabel} not deployed (parent ${parent._id}, task ${task.id})`
|
|
1469
|
-
);
|
|
1968
|
+
function readEntryFromMutation(ctx, src) {
|
|
1969
|
+
const scopes = src.scope !== void 0 ? [src.scope] : ["stage", "workflow"], stageEntry = findOpenStageEntry(ctx.mutation);
|
|
1970
|
+
for (const scope of scopes) {
|
|
1971
|
+
const host = scope === "workflow" ? ctx.mutation.state : stageEntry?.state, value = entryValue(host, src.state);
|
|
1972
|
+
if (value !== void 0) return value;
|
|
1470
1973
|
}
|
|
1471
|
-
const childTags = parent.tags ?? [], childPrefix = childTags[0] ?? "wf", workflowResource = parent.workflowResource, childDocId = `${childPrefix}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type: WORKFLOW_INSTANCE_TYPE }, ancestors = [
|
|
1472
|
-
...parent.ancestors,
|
|
1473
|
-
{
|
|
1474
|
-
id: selfGdr(parent),
|
|
1475
|
-
type: WORKFLOW_INSTANCE_TYPE
|
|
1476
|
-
}
|
|
1477
|
-
], inheritedPerspective = parent.perspective, childState = await resolveDeclaredState({
|
|
1478
|
-
slotDefs: definition.state,
|
|
1479
|
-
initialState,
|
|
1480
|
-
ctx: {
|
|
1481
|
-
client,
|
|
1482
|
-
now,
|
|
1483
|
-
selfId: childDocId,
|
|
1484
|
-
tags: childTags,
|
|
1485
|
-
workflowResource,
|
|
1486
|
-
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
|
|
1487
|
-
},
|
|
1488
|
-
randomKey
|
|
1489
|
-
}), childPerspective = derivePerspectiveFromState(childState) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
|
|
1490
|
-
([key, value]) => {
|
|
1491
|
-
if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
|
|
1492
|
-
if (!isGdrUri(value.id))
|
|
1493
|
-
throw new Error(
|
|
1494
|
-
`spawn.forEach.as.effectsContext["${key}"]: GDR id must be a "<scheme>:..." URI, got ${JSON.stringify(value.id)}.`
|
|
1495
|
-
);
|
|
1496
|
-
return effectsContextEntry(key, { id: value.id, type: value.type });
|
|
1497
|
-
}
|
|
1498
|
-
return effectsContextEntry(key, value);
|
|
1499
|
-
}
|
|
1500
|
-
), base = buildInstanceBase({
|
|
1501
|
-
id: childDocId,
|
|
1502
|
-
now,
|
|
1503
|
-
tags: childTags,
|
|
1504
|
-
workflowResource,
|
|
1505
|
-
workflowId: definition.workflowId,
|
|
1506
|
-
pinnedVersion: definition.version,
|
|
1507
|
-
definition,
|
|
1508
|
-
state: childState,
|
|
1509
|
-
effectsContext: effectsContextEntries,
|
|
1510
|
-
ancestors,
|
|
1511
|
-
perspective: childPerspective,
|
|
1512
|
-
initialStageId: definition.initialStageId,
|
|
1513
|
-
actor
|
|
1514
|
-
});
|
|
1515
|
-
return { ref: childRef, body: base };
|
|
1516
1974
|
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
if (refs.length === 0) {
|
|
1520
|
-
const policy2 = spawn.completionPolicy ?? { kind: "all" };
|
|
1521
|
-
return policy2.kind === "all" || policy2.kind === "count" && policy2.n === 0;
|
|
1522
|
-
}
|
|
1523
|
-
const ids = refs.map((r) => r.id.includes(":") ? gdrToBareId(r.id) : r.id), children = await client.fetch("*[_id in $ids]{_id, currentStageId, definitionSnapshot}", { ids });
|
|
1524
|
-
let terminalCount = 0;
|
|
1525
|
-
for (const child of children)
|
|
1526
|
-
JSON.parse(child.definitionSnapshot).stages.find((s) => s.id === child.currentStageId)?.kind === "terminal" && terminalCount++;
|
|
1527
|
-
const policy = spawn.completionPolicy ?? { kind: "all" };
|
|
1528
|
-
switch (policy.kind) {
|
|
1975
|
+
function evalOpPredicate(p, row, ctx) {
|
|
1976
|
+
switch (p.type) {
|
|
1529
1977
|
case "all":
|
|
1530
|
-
return
|
|
1978
|
+
return p.of.every((sub) => evalOpPredicate(sub, row, ctx));
|
|
1531
1979
|
case "any":
|
|
1532
|
-
return
|
|
1533
|
-
case "
|
|
1534
|
-
return
|
|
1980
|
+
return p.of.some((sub) => evalOpPredicate(sub, row, ctx));
|
|
1981
|
+
case "field":
|
|
1982
|
+
return row[p.field] === resolveOpSource(p.equals, ctx);
|
|
1535
1983
|
}
|
|
1536
1984
|
}
|
|
1537
1985
|
async function invokeTask(args) {
|
|
1538
|
-
const { client, instanceId,
|
|
1539
|
-
...options?.clock ? { clock: options.clock } : {}
|
|
1986
|
+
const { client, instanceId, task: taskName, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1987
|
+
...options?.clock ? { clock: options.clock } : {},
|
|
1988
|
+
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
1540
1989
|
});
|
|
1541
|
-
return commitInvoke(ctx,
|
|
1990
|
+
return commitInvoke(ctx, taskName, options?.actor);
|
|
1542
1991
|
}
|
|
1543
|
-
async function commitInvoke(ctx,
|
|
1544
|
-
const { task } = findTaskInCurrentStage(ctx,
|
|
1992
|
+
async function commitInvoke(ctx, taskName, actor) {
|
|
1993
|
+
const { task } = findTaskInCurrentStage(ctx, taskName), entry = findCurrentTasks(ctx.instance).find((t) => t.name === taskName);
|
|
1545
1994
|
if (entry === void 0)
|
|
1546
|
-
throw new Error(`Task "${
|
|
1995
|
+
throw new Error(`Task "${taskName}" has no status entry on instance ${ctx.instance._id}`);
|
|
1547
1996
|
if (entry.status !== "pending")
|
|
1548
|
-
return { invoked: !1,
|
|
1549
|
-
const mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation,
|
|
1550
|
-
return await activateTask(ctx, mutation, task, mutEntry, actor), await persist(ctx, mutation), { invoked: !0,
|
|
1551
|
-
}
|
|
1552
|
-
async function autoResolveOnActivate(ctx, mutation, task, entry, now) {
|
|
1553
|
-
const checks = [
|
|
1554
|
-
{ filter: task.failWhen, outcome: "failed", systemId: "engine.failWhen" },
|
|
1555
|
-
{ filter: task.completeWhen, outcome: "done", systemId: "engine.completeWhen" }
|
|
1556
|
-
];
|
|
1557
|
-
for (const { filter, outcome, systemId } of checks)
|
|
1558
|
-
if (filter !== void 0 && await ctxEvaluateFilter(ctx, filter))
|
|
1559
|
-
return entry.status = outcome, entry.completedAt = now, entry.completedBy = systemId, mutation.history.push(
|
|
1560
|
-
taskStatusChangedEntry({
|
|
1561
|
-
stageId: mutation.currentStageId,
|
|
1562
|
-
taskId: task.id,
|
|
1563
|
-
from: "active",
|
|
1564
|
-
to: outcome,
|
|
1565
|
-
at: now,
|
|
1566
|
-
actor: { kind: "system", id: systemId }
|
|
1567
|
-
})
|
|
1568
|
-
), !0;
|
|
1569
|
-
return !1;
|
|
1997
|
+
return { invoked: !1, task: taskName };
|
|
1998
|
+
const mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation, taskName);
|
|
1999
|
+
return await activateTask(ctx, mutation, task, mutEntry, actor), await persist(ctx, mutation), { invoked: !0, task: taskName };
|
|
1570
2000
|
}
|
|
1571
2001
|
async function activateTask(ctx, mutation, task, entry, actor) {
|
|
1572
2002
|
const now = ctx.now;
|
|
1573
2003
|
if (entry.status = "active", entry.startedAt = now, task.state !== void 0 && task.state.length > 0) {
|
|
1574
|
-
const stage = findStage(ctx.definition, mutation.
|
|
1575
|
-
entry.state = await
|
|
2004
|
+
const stage = findStage(ctx.definition, mutation.currentStage);
|
|
2005
|
+
entry.state = await resolveTaskStateEntries({
|
|
1576
2006
|
client: ctx.client,
|
|
1577
2007
|
instance: ctx.instance,
|
|
1578
2008
|
stage,
|
|
@@ -1580,55 +2010,73 @@ async function activateTask(ctx, mutation, task, entry, actor) {
|
|
|
1580
2010
|
now
|
|
1581
2011
|
});
|
|
1582
2012
|
}
|
|
1583
|
-
|
|
2013
|
+
runOps({
|
|
2014
|
+
ops: task.ops,
|
|
2015
|
+
mutation,
|
|
2016
|
+
stage: mutation.currentStage,
|
|
2017
|
+
origin: { task: task.name },
|
|
2018
|
+
params: {},
|
|
2019
|
+
actor,
|
|
2020
|
+
self: selfGdr(ctx.instance),
|
|
2021
|
+
now
|
|
2022
|
+
}), mutation.history.push({
|
|
1584
2023
|
_key: randomKey(),
|
|
1585
|
-
_type: "
|
|
2024
|
+
_type: "taskActivated",
|
|
1586
2025
|
at: now,
|
|
1587
|
-
|
|
1588
|
-
|
|
2026
|
+
stage: mutation.currentStage,
|
|
2027
|
+
task: task.name,
|
|
1589
2028
|
...actor !== void 0 ? { actor } : {}
|
|
1590
|
-
}), await queueEffects(
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
},
|
|
1598
|
-
actor
|
|
1599
|
-
), task.spawns !== void 0)) {
|
|
1600
|
-
const refs = await spawnChildren(ctx, mutation, task, task.spawns, actor);
|
|
1601
|
-
entry.spawnedInstances = refs;
|
|
2029
|
+
}), await queueEffects(ctx, mutation, task.effects, { kind: "task", name: task.name }, actor, {
|
|
2030
|
+
taskName: task.name
|
|
2031
|
+
});
|
|
2032
|
+
let pendingChildren;
|
|
2033
|
+
if (task.subworkflows !== void 0) {
|
|
2034
|
+
const createsBefore = mutation.pendingCreates.length, refs = await spawnSubworkflows(ctx, mutation, task, task.subworkflows, actor);
|
|
2035
|
+
entry.spawnedInstances = refs, pendingChildren = mutation.pendingCreates.slice(createsBefore).map((c) => ({ _id: c._id, stage: c.currentStage, status: "active" }));
|
|
1602
2036
|
for (const ref of refs)
|
|
1603
2037
|
mutation.history.push({
|
|
1604
2038
|
_key: randomKey(),
|
|
1605
|
-
_type: "
|
|
2039
|
+
_type: "spawned",
|
|
1606
2040
|
at: now,
|
|
1607
|
-
|
|
2041
|
+
task: task.name,
|
|
1608
2042
|
instanceRef: ref
|
|
1609
2043
|
});
|
|
1610
|
-
if (await checkSpawnCompletion(ctx.client, entry, task.spawns)) {
|
|
1611
|
-
const previousStatus = entry.status;
|
|
1612
|
-
entry.status = "done", entry.completedAt = now, mutation.history.push(
|
|
1613
|
-
taskStatusChangedEntry({
|
|
1614
|
-
stageId: mutation.currentStageId,
|
|
1615
|
-
taskId: task.id,
|
|
1616
|
-
from: previousStatus,
|
|
1617
|
-
to: "done",
|
|
1618
|
-
at: now,
|
|
1619
|
-
...actor !== void 0 ? { actor } : {}
|
|
1620
|
-
})
|
|
1621
|
-
);
|
|
1622
|
-
}
|
|
1623
2044
|
}
|
|
2045
|
+
await autoResolveOnActivate(ctx, mutation, task, entry, now, pendingChildren) || resolveMachineStep(mutation, task, entry, now);
|
|
2046
|
+
}
|
|
2047
|
+
async function autoResolveOnActivate(ctx, mutation, task, entry, now, pendingChildren) {
|
|
2048
|
+
if (task.failWhen === void 0 && effectiveCompleteWhen(task) === void 0) return !1;
|
|
2049
|
+
const vars = pendingChildren !== void 0 ? { subworkflows: pendingChildren } : await taskConditionVars(ctx, entry), outcome = await evaluateResolutionGate(ctx, task, vars);
|
|
2050
|
+
return outcome === void 0 ? !1 : (applyTaskStatusChange({
|
|
2051
|
+
entry,
|
|
2052
|
+
history: mutation.history,
|
|
2053
|
+
stage: mutation.currentStage,
|
|
2054
|
+
to: outcome,
|
|
2055
|
+
at: now,
|
|
2056
|
+
actor: gateActor(outcome)
|
|
2057
|
+
}), !0);
|
|
2058
|
+
}
|
|
2059
|
+
async function taskConditionVars(ctx, entry) {
|
|
2060
|
+
return entry.spawnedInstances === void 0 || entry.spawnedInstances.length === 0 ? { subworkflows: [] } : { subworkflows: await subworkflowRows(ctx, entry.spawnedInstances) };
|
|
2061
|
+
}
|
|
2062
|
+
function resolveMachineStep(mutation, task, entry, now) {
|
|
2063
|
+
const waitsForActor = task.actions !== void 0 && task.actions.length > 0, waitsForCondition = task.completeWhen !== void 0 || task.subworkflows !== void 0;
|
|
2064
|
+
waitsForActor || waitsForCondition || applyTaskStatusChange({
|
|
2065
|
+
entry,
|
|
2066
|
+
history: mutation.history,
|
|
2067
|
+
stage: mutation.currentStage,
|
|
2068
|
+
to: "done",
|
|
2069
|
+
at: now,
|
|
2070
|
+
actor: { kind: "system", id: "engine.machineStep" }
|
|
2071
|
+
});
|
|
1624
2072
|
}
|
|
1625
2073
|
async function buildStageTasks(ctx, stage) {
|
|
1626
2074
|
const entries = [];
|
|
1627
2075
|
for (const task of stage.tasks ?? []) {
|
|
1628
|
-
const inScope = await
|
|
2076
|
+
const inScope = await ctxEvaluateCondition(ctx, task.filter, { taskName: task.name });
|
|
1629
2077
|
entries.push({
|
|
1630
2078
|
_key: randomKey(),
|
|
1631
|
-
|
|
2079
|
+
name: task.name,
|
|
1632
2080
|
status: inScope ? "pending" : "skipped",
|
|
1633
2081
|
...task.filter !== void 0 ? {
|
|
1634
2082
|
filterEvaluation: {
|
|
@@ -1640,22 +2088,23 @@ async function buildStageTasks(ctx, stage) {
|
|
|
1640
2088
|
}
|
|
1641
2089
|
return entries;
|
|
1642
2090
|
}
|
|
1643
|
-
function
|
|
1644
|
-
return
|
|
2091
|
+
function isTerminalStage(stage) {
|
|
2092
|
+
return (stage.transitions ?? []).length === 0;
|
|
1645
2093
|
}
|
|
1646
2094
|
async function setStage(args) {
|
|
1647
|
-
const { client, instanceId,
|
|
1648
|
-
...options?.clock ? { clock: options.clock } : {}
|
|
2095
|
+
const { client, instanceId, targetStage, reason, options } = args, ctx = await loadContext(client, instanceId, {
|
|
2096
|
+
...options?.clock ? { clock: options.clock } : {},
|
|
2097
|
+
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
1649
2098
|
});
|
|
1650
|
-
return commitSetStage(ctx,
|
|
2099
|
+
return commitSetStage(ctx, targetStage, reason, options?.actor);
|
|
1651
2100
|
}
|
|
1652
2101
|
async function enterStage(ctx, mutation, nextStage, actor, at) {
|
|
1653
|
-
mutation.
|
|
2102
|
+
mutation.currentStage = nextStage.name;
|
|
1654
2103
|
const nextStageEntry = {
|
|
1655
2104
|
_key: randomKey(),
|
|
1656
|
-
|
|
2105
|
+
name: nextStage.name,
|
|
1657
2106
|
enteredAt: at,
|
|
1658
|
-
state: await
|
|
2107
|
+
state: await resolveStageStateEntries({
|
|
1659
2108
|
client: ctx.client,
|
|
1660
2109
|
instance: ctx.instance,
|
|
1661
2110
|
stage: nextStage,
|
|
@@ -1663,55 +2112,44 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
|
|
|
1663
2112
|
}),
|
|
1664
2113
|
tasks: await buildStageTasks(ctx, nextStage)
|
|
1665
2114
|
};
|
|
1666
|
-
mutation.stages.push(nextStageEntry)
|
|
1667
|
-
ctx,
|
|
1668
|
-
mutation,
|
|
1669
|
-
nextStage.onEnter,
|
|
1670
|
-
{ kind: "stageEnter", id: nextStage.id },
|
|
1671
|
-
actor
|
|
1672
|
-
);
|
|
2115
|
+
mutation.stages.push(nextStageEntry);
|
|
1673
2116
|
for (const task of nextStage.tasks ?? []) {
|
|
1674
|
-
if (
|
|
1675
|
-
const entry = nextStageEntry.tasks.find((t) => t.
|
|
2117
|
+
if (task.activation !== "auto") continue;
|
|
2118
|
+
const entry = nextStageEntry.tasks.find((t) => t.name === task.name);
|
|
1676
2119
|
entry && entry.status === "pending" && await activateTask(ctx, mutation, task, entry, actor);
|
|
1677
2120
|
}
|
|
1678
|
-
nextStage
|
|
2121
|
+
isTerminalStage(nextStage) && (mutation.completedAt = at);
|
|
1679
2122
|
}
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
return await queueEffects(
|
|
1683
|
-
ctx,
|
|
1684
|
-
mutation,
|
|
1685
|
-
currentStage.onExit,
|
|
1686
|
-
{ kind: "stageExit", id: currentStage.id },
|
|
1687
|
-
actor
|
|
1688
|
-
), { mutation, at };
|
|
2123
|
+
function isTerminal(ctx) {
|
|
2124
|
+
return ctx.instance.completedAt !== void 0;
|
|
1689
2125
|
}
|
|
1690
|
-
async function commitSetStage(ctx,
|
|
1691
|
-
|
|
1692
|
-
|
|
2126
|
+
async function commitSetStage(ctx, targetStage, reason, actor) {
|
|
2127
|
+
if (isTerminal(ctx))
|
|
2128
|
+
return { fired: !1 };
|
|
2129
|
+
const currentStage = findStage(ctx.definition, ctx.instance.currentStage), nextStage = findStage(ctx.definition, targetStage);
|
|
2130
|
+
if (currentStage.name === nextStage.name)
|
|
1693
2131
|
return { fired: !1 };
|
|
1694
|
-
const
|
|
2132
|
+
const mutation = startMutation(ctx.instance), at = ctx.now, transitionName = `setStage:${currentStage.name}->${nextStage.name}`;
|
|
1695
2133
|
mutation.history.push(
|
|
1696
2134
|
...stageTransitionHistory({
|
|
1697
|
-
|
|
1698
|
-
|
|
2135
|
+
fromStage: currentStage.name,
|
|
2136
|
+
toStage: nextStage.name,
|
|
1699
2137
|
at,
|
|
1700
|
-
|
|
2138
|
+
transition: transitionName,
|
|
1701
2139
|
via: "setStage",
|
|
1702
2140
|
...actor !== void 0 ? { actor } : {},
|
|
1703
2141
|
...reason !== void 0 ? { reason } : {}
|
|
1704
2142
|
})
|
|
1705
2143
|
);
|
|
1706
2144
|
const priorEntry = findOpenStageEntry(mutation);
|
|
1707
|
-
return priorEntry !== void 0 && (priorEntry.exitedAt = at), await enterStage(ctx, mutation, nextStage, actor, at), await persistStageMove(ctx, mutation, currentStage.
|
|
2145
|
+
return priorEntry !== void 0 && (priorEntry.exitedAt = at), await enterStage(ctx, mutation, nextStage, actor, at), await persistStageMove(ctx, mutation, currentStage.name, nextStage.name), {
|
|
1708
2146
|
fired: !0,
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
2147
|
+
fromStage: currentStage.name,
|
|
2148
|
+
toStage: nextStage.name,
|
|
2149
|
+
transition: transitionName
|
|
1712
2150
|
};
|
|
1713
2151
|
}
|
|
1714
|
-
async function persistStageMove(ctx, mutation,
|
|
2152
|
+
async function persistStageMove(ctx, mutation, exitedStage, enteredStage) {
|
|
1715
2153
|
await persistThenDeploy(
|
|
1716
2154
|
ctx,
|
|
1717
2155
|
mutation,
|
|
@@ -1720,7 +2158,7 @@ async function persistStageMove(ctx, mutation, exitedStageId, enteredStageId) {
|
|
|
1720
2158
|
clientForGdr: ctx.clientForGdr,
|
|
1721
2159
|
instance: materializeInstance(ctx.instance, mutation),
|
|
1722
2160
|
definition: ctx.definition,
|
|
1723
|
-
|
|
2161
|
+
stageName: enteredStage,
|
|
1724
2162
|
now: ctx.now
|
|
1725
2163
|
})
|
|
1726
2164
|
), await retractStageGuards({
|
|
@@ -1728,7 +2166,7 @@ async function persistStageMove(ctx, mutation, exitedStageId, enteredStageId) {
|
|
|
1728
2166
|
clientForGdr: ctx.clientForGdr,
|
|
1729
2167
|
instance: ctx.instance,
|
|
1730
2168
|
definition: ctx.definition,
|
|
1731
|
-
|
|
2169
|
+
stageName: exitedStage,
|
|
1732
2170
|
now: ctx.now
|
|
1733
2171
|
});
|
|
1734
2172
|
}
|
|
@@ -1744,53 +2182,63 @@ async function persistThenDeploy(ctx, mutation, deploy) {
|
|
|
1744
2182
|
deploy
|
|
1745
2183
|
});
|
|
1746
2184
|
}
|
|
1747
|
-
async function refreshStageGuards(ctx, mutation,
|
|
2185
|
+
async function refreshStageGuards(ctx, mutation, stageName) {
|
|
1748
2186
|
await deployStageGuards({
|
|
1749
2187
|
client: ctx.client,
|
|
1750
2188
|
clientForGdr: ctx.clientForGdr,
|
|
1751
2189
|
instance: materializeInstance(ctx.instance, mutation),
|
|
1752
2190
|
definition: ctx.definition,
|
|
1753
|
-
|
|
2191
|
+
stageName,
|
|
1754
2192
|
now: ctx.now
|
|
1755
2193
|
});
|
|
1756
2194
|
}
|
|
1757
|
-
async function commitTransition(ctx,
|
|
1758
|
-
|
|
2195
|
+
async function commitTransition(ctx, actor) {
|
|
2196
|
+
if (isTerminal(ctx))
|
|
2197
|
+
return { fired: !1 };
|
|
2198
|
+
const currentStage = findStage(ctx.definition, ctx.instance.currentStage), transition = await pickTransition(ctx, currentStage);
|
|
1759
2199
|
if (transition === void 0)
|
|
1760
2200
|
return { fired: !1 };
|
|
1761
|
-
const
|
|
1762
|
-
|
|
2201
|
+
const mutation = startMutation(ctx.instance), at = ctx.now;
|
|
2202
|
+
runOps({
|
|
2203
|
+
ops: transition.ops,
|
|
2204
|
+
mutation,
|
|
2205
|
+
stage: currentStage.name,
|
|
2206
|
+
origin: { transition: transition.name },
|
|
2207
|
+
params: {},
|
|
2208
|
+
actor,
|
|
2209
|
+
self: selfGdr(ctx.instance),
|
|
2210
|
+
now: at
|
|
2211
|
+
}), await queueEffects(
|
|
1763
2212
|
ctx,
|
|
1764
2213
|
mutation,
|
|
1765
2214
|
transition.effects,
|
|
1766
2215
|
{
|
|
1767
2216
|
kind: "transition",
|
|
1768
|
-
|
|
2217
|
+
name: transition.name
|
|
1769
2218
|
},
|
|
1770
2219
|
actor
|
|
1771
2220
|
), mutation.history.push(
|
|
1772
2221
|
...stageTransitionHistory({
|
|
1773
|
-
|
|
1774
|
-
|
|
2222
|
+
fromStage: currentStage.name,
|
|
2223
|
+
toStage: transition.to,
|
|
1775
2224
|
at,
|
|
1776
|
-
|
|
2225
|
+
transition: transition.name,
|
|
1777
2226
|
...actor !== void 0 ? { actor } : {}
|
|
1778
2227
|
})
|
|
1779
2228
|
);
|
|
1780
2229
|
const priorEntry = findOpenStageEntry(mutation);
|
|
1781
2230
|
priorEntry !== void 0 && (priorEntry.exitedAt = at);
|
|
1782
2231
|
const nextStage = findStage(ctx.definition, transition.to);
|
|
1783
|
-
return await enterStage(ctx, mutation, nextStage, actor, at), await persistStageMove(ctx, mutation, currentStage.
|
|
2232
|
+
return await enterStage(ctx, mutation, nextStage, actor, at), await persistStageMove(ctx, mutation, currentStage.name, nextStage.name), {
|
|
1784
2233
|
fired: !0,
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
2234
|
+
fromStage: currentStage.name,
|
|
2235
|
+
toStage: transition.to,
|
|
2236
|
+
transition: transition.name
|
|
1788
2237
|
};
|
|
1789
2238
|
}
|
|
1790
|
-
async function pickTransition(ctx, stage
|
|
1791
|
-
const
|
|
1792
|
-
|
|
1793
|
-
if (await ctxEvaluateFilter(ctx, candidate.filter)) return candidate;
|
|
2239
|
+
async function pickTransition(ctx, stage) {
|
|
2240
|
+
for (const candidate of stage.transitions ?? [])
|
|
2241
|
+
if (await ctxEvaluateCondition(ctx, candidate.filter)) return candidate;
|
|
1794
2242
|
}
|
|
1795
2243
|
async function evaluateAutoTransitions(args) {
|
|
1796
2244
|
const { client, instanceId, options, clientForGdr, clock, overlay } = args, ctx = await loadContext(client, instanceId, {
|
|
@@ -1798,58 +2246,27 @@ async function evaluateAutoTransitions(args) {
|
|
|
1798
2246
|
...clock ? { clock } : {},
|
|
1799
2247
|
...overlay ? { overlay } : {}
|
|
1800
2248
|
});
|
|
1801
|
-
return commitTransition(ctx,
|
|
2249
|
+
return commitTransition(ctx, options?.actor);
|
|
1802
2250
|
}
|
|
1803
2251
|
async function primeInitialStage(client, instanceId, actor, clientForGdr, clock) {
|
|
1804
2252
|
const instance = await client.getDocument(instanceId);
|
|
1805
|
-
if (!instance || instance.stages.length > 0
|
|
1806
|
-
const definition =
|
|
2253
|
+
if (!instance || instance.stages.length > 0) return;
|
|
2254
|
+
const definition = parseDefinitionSnapshot(instance), stage = definition.stages.find((s) => s.name === instance.currentStage);
|
|
1807
2255
|
if (stage === void 0) return;
|
|
1808
|
-
const
|
|
2256
|
+
const ctx = await buildEngineContext({
|
|
1809
2257
|
client,
|
|
1810
2258
|
clientForGdr: clientForGdr ?? (() => client),
|
|
1811
|
-
instance
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
filter: task.filter,
|
|
1816
|
-
definition,
|
|
1817
|
-
snapshot,
|
|
1818
|
-
params: buildParams(instance, now)
|
|
1819
|
-
});
|
|
1820
|
-
tasks.push({
|
|
1821
|
-
_key: randomKey(),
|
|
1822
|
-
id: task.id,
|
|
1823
|
-
status: inScope ? "pending" : "skipped"
|
|
1824
|
-
});
|
|
1825
|
-
}
|
|
1826
|
-
const initialStageEntry = {
|
|
2259
|
+
instance,
|
|
2260
|
+
definition,
|
|
2261
|
+
...clock ? { clock } : {}
|
|
2262
|
+
}), now = ctx.now, initialStageEntry = {
|
|
1827
2263
|
_key: randomKey(),
|
|
1828
|
-
|
|
2264
|
+
name: stage.name,
|
|
1829
2265
|
enteredAt: now,
|
|
1830
|
-
state: await
|
|
1831
|
-
tasks
|
|
1832
|
-
},
|
|
1833
|
-
for (const effect of stage.onEnter ?? []) {
|
|
1834
|
-
const params = await resolveBindings({
|
|
1835
|
-
bindings: effect.bindings,
|
|
1836
|
-
staticInput: effect.input,
|
|
1837
|
-
instance,
|
|
1838
|
-
now,
|
|
1839
|
-
...actor !== void 0 ? { actor } : {}
|
|
1840
|
-
}), { pending, history } = buildQueuedEffect(
|
|
1841
|
-
effect,
|
|
1842
|
-
{ kind: "stageEnter", id: stage.id },
|
|
1843
|
-
params,
|
|
1844
|
-
actor,
|
|
1845
|
-
now
|
|
1846
|
-
);
|
|
1847
|
-
pendingEffects.push(pending), newHistory.push(history);
|
|
1848
|
-
}
|
|
1849
|
-
const committed = await client.patch(instance._id).set({
|
|
2266
|
+
state: await resolveStageStateEntries({ client, instance, stage, now }),
|
|
2267
|
+
tasks: await buildStageTasks(ctx, stage)
|
|
2268
|
+
}, committed = await client.patch(instance._id).set({
|
|
1850
2269
|
stages: [initialStageEntry],
|
|
1851
|
-
pendingEffects,
|
|
1852
|
-
history: newHistory,
|
|
1853
2270
|
lastChangedAt: now
|
|
1854
2271
|
}).ifRevisionId(instance._rev).commit();
|
|
1855
2272
|
await deployOrRollback({
|
|
@@ -1858,7 +2275,6 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
1858
2275
|
committedRev: committed._rev,
|
|
1859
2276
|
restore: {
|
|
1860
2277
|
stages: instance.stages,
|
|
1861
|
-
pendingEffects: instance.pendingEffects,
|
|
1862
2278
|
history: instance.history
|
|
1863
2279
|
},
|
|
1864
2280
|
reversible: !0,
|
|
@@ -1867,7 +2283,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
1867
2283
|
clientForGdr: clientForGdr ?? (() => client),
|
|
1868
2284
|
instance,
|
|
1869
2285
|
definition,
|
|
1870
|
-
|
|
2286
|
+
stageName: stage.name,
|
|
1871
2287
|
now
|
|
1872
2288
|
})
|
|
1873
2289
|
}), await autoActivatePrimedTasks(client, instanceId, definition, stage, actor, clientForGdr, clock);
|
|
@@ -1875,7 +2291,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
1875
2291
|
async function autoActivatePrimedTasks(client, instanceId, definition, stage, actor, clientForGdr, clock) {
|
|
1876
2292
|
const resolvedClientForGdr = clientForGdr ?? (() => client);
|
|
1877
2293
|
for (const task of stage.tasks ?? []) {
|
|
1878
|
-
if (
|
|
2294
|
+
if (task.activation !== "auto") continue;
|
|
1879
2295
|
const updated = await client.getDocument(instanceId);
|
|
1880
2296
|
if (!updated) continue;
|
|
1881
2297
|
const updatedCtx = await buildEngineContext({
|
|
@@ -1884,21 +2300,19 @@ async function autoActivatePrimedTasks(client, instanceId, definition, stage, ac
|
|
|
1884
2300
|
instance: updated,
|
|
1885
2301
|
definition,
|
|
1886
2302
|
...clock ? { clock } : {}
|
|
1887
|
-
}), mutation = startMutation(updated), mutEntry = currentTasks(mutation).find((t) => t.
|
|
2303
|
+
}), mutation = startMutation(updated), mutEntry = currentTasks(mutation).find((t) => t.name === task.name);
|
|
1888
2304
|
mutEntry && mutEntry.status === "pending" && (await activateTask(updatedCtx, mutation, task, mutEntry, actor), await persist(updatedCtx, mutation));
|
|
1889
2305
|
}
|
|
1890
2306
|
}
|
|
1891
2307
|
const CASCADE_LIMIT = 100;
|
|
1892
2308
|
async function gatherAutoResolveCandidates(ctx, tasks) {
|
|
1893
2309
|
const currentTasksList = findCurrentTasks(ctx.instance), candidates = [];
|
|
1894
|
-
for (const task of tasks)
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
task.completeWhen !== void 0 && await ctxEvaluateFilter(ctx, task.completeWhen) && candidates.push({ task, outcome: "done" });
|
|
1901
|
-
}
|
|
2310
|
+
for (const task of tasks) {
|
|
2311
|
+
const entry = currentTasksList.find((e) => e.name === task.name);
|
|
2312
|
+
if (entry?.status !== "active") continue;
|
|
2313
|
+
const outcome = await evaluateResolutionGate(ctx, task, await taskConditionVars(ctx, entry));
|
|
2314
|
+
outcome !== void 0 && candidates.push({ task, outcome });
|
|
2315
|
+
}
|
|
1902
2316
|
return candidates;
|
|
1903
2317
|
}
|
|
1904
2318
|
async function resolveCompleteWhen(client, instanceId, clientForGdr, clock, overlay) {
|
|
@@ -1906,31 +2320,24 @@ async function resolveCompleteWhen(client, instanceId, clientForGdr, clock, over
|
|
|
1906
2320
|
...clientForGdr ? { clientForGdr } : {},
|
|
1907
2321
|
...clock ? { clock } : {},
|
|
1908
2322
|
...overlay ? { overlay } : {}
|
|
1909
|
-
}), stage = findStage(ctx.definition, ctx.instance.
|
|
1910
|
-
(t) => t
|
|
2323
|
+
}), stage = findStage(ctx.definition, ctx.instance.currentStage), gatedTasks = (stage.tasks ?? []).filter(
|
|
2324
|
+
(t) => effectiveCompleteWhen(t) !== void 0 || t.failWhen !== void 0
|
|
1911
2325
|
);
|
|
1912
|
-
if (
|
|
1913
|
-
const candidates = await gatherAutoResolveCandidates(ctx,
|
|
2326
|
+
if (gatedTasks.length === 0) return 0;
|
|
2327
|
+
const candidates = await gatherAutoResolveCandidates(ctx, gatedTasks);
|
|
1914
2328
|
if (candidates.length === 0) return 0;
|
|
1915
2329
|
const mutation = startMutation(ctx.instance);
|
|
1916
2330
|
let count = 0;
|
|
1917
2331
|
for (const { task, outcome } of candidates) {
|
|
1918
|
-
const mutEntry = currentTasks(mutation).find((t) => t.
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
taskId: task.id,
|
|
1928
|
-
from: previousStatus,
|
|
1929
|
-
to: outcome,
|
|
1930
|
-
at: now,
|
|
1931
|
-
actor
|
|
1932
|
-
})
|
|
1933
|
-
), count++;
|
|
2332
|
+
const mutEntry = currentTasks(mutation).find((t) => t.name === task.name);
|
|
2333
|
+
mutEntry === void 0 || mutEntry.status !== "active" || (applyTaskStatusChange({
|
|
2334
|
+
entry: mutEntry,
|
|
2335
|
+
history: mutation.history,
|
|
2336
|
+
stage: stage.name,
|
|
2337
|
+
to: outcome,
|
|
2338
|
+
at: ctx.now,
|
|
2339
|
+
actor: gateActor(outcome)
|
|
2340
|
+
}), count++);
|
|
1934
2341
|
}
|
|
1935
2342
|
return count > 0 && await persist(ctx, mutation), count;
|
|
1936
2343
|
}
|
|
@@ -1949,43 +2356,47 @@ async function cascadeAutoTransitions(client, instanceId, actor, clientForGdr, c
|
|
|
1949
2356
|
throw new CascadeLimitError({ instanceId, limit: CASCADE_LIMIT });
|
|
1950
2357
|
}
|
|
1951
2358
|
}
|
|
1952
|
-
async function
|
|
2359
|
+
async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
|
|
1953
2360
|
const parentRef = child.ancestors.at(-1);
|
|
1954
2361
|
if (parentRef === void 0) return;
|
|
1955
2362
|
const parent = await client.getDocument(gdrToBareId(parentRef.id));
|
|
1956
2363
|
if (!parent || parent._type !== WORKFLOW_INSTANCE_TYPE || typeof parent.definitionSnapshot != "string") return;
|
|
1957
|
-
const definition =
|
|
2364
|
+
const definition = parseDefinitionSnapshot(parent), stage = definition.stages.find((s) => s.name === parent.currentStage);
|
|
1958
2365
|
if (stage === void 0) return;
|
|
1959
|
-
const parentCurrentTasks = findCurrentTasks(parent), task = (stage.tasks ?? []).find((t) => t.
|
|
1960
|
-
if (task?.
|
|
1961
|
-
const entry = parentCurrentTasks.find((e) => e.
|
|
1962
|
-
if (
|
|
1963
|
-
|
|
2366
|
+
const parentCurrentTasks = findCurrentTasks(parent), task = (stage.tasks ?? []).find((t) => t.subworkflows === void 0 ? !1 : parentCurrentTasks.find((e) => e.name === t.name)?.spawnedInstances?.some((r) => gdrToBareId(r.id) === child._id) ?? !1);
|
|
2367
|
+
if (task?.subworkflows === void 0) return;
|
|
2368
|
+
const entry = parentCurrentTasks.find((e) => e.name === task.name);
|
|
2369
|
+
if (entry === void 0 || entry.status !== "active") return;
|
|
2370
|
+
const ctx = await buildEngineContext({
|
|
2371
|
+
client,
|
|
2372
|
+
clientForGdr,
|
|
2373
|
+
instance: parent,
|
|
2374
|
+
definition,
|
|
2375
|
+
...clock ? { clock } : {}
|
|
2376
|
+
}), outcome = await evaluateResolutionGate(ctx, task, await taskConditionVars(ctx, entry));
|
|
2377
|
+
if (outcome !== void 0)
|
|
2378
|
+
return { parent, definition, stage, task, outcome };
|
|
1964
2379
|
}
|
|
1965
2380
|
async function propagateToAncestors(client, instanceId, actor, clientForGdr, clock) {
|
|
1966
2381
|
const instance = await client.getDocument(instanceId);
|
|
1967
2382
|
if (!instance) return;
|
|
1968
|
-
const found = await
|
|
2383
|
+
const resolvedClientForGdr = clientForGdr ?? (() => client), found = await findResolvedParentSpawn(client, instance, resolvedClientForGdr, clock);
|
|
1969
2384
|
if (found === void 0) return;
|
|
1970
|
-
const { parent, definition, stage, task } = found, ctx = await buildEngineContext({
|
|
2385
|
+
const { parent, definition, stage, task, outcome } = found, ctx = await buildEngineContext({
|
|
1971
2386
|
client,
|
|
1972
|
-
clientForGdr:
|
|
2387
|
+
clientForGdr: resolvedClientForGdr,
|
|
1973
2388
|
instance: parent,
|
|
1974
2389
|
definition,
|
|
1975
2390
|
...clock ? { clock } : {}
|
|
1976
|
-
}), mutation = startMutation(parent), mutEntry = currentTasks(mutation).find((e) => e.
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
at: now,
|
|
1986
|
-
...actor !== void 0 ? { actor } : {}
|
|
1987
|
-
})
|
|
1988
|
-
), await persist(ctx, mutation), await cascadeAutoTransitions(client, parent._id, actor, clientForGdr, clock), await propagateToAncestors(client, parent._id, actor, clientForGdr, clock);
|
|
2391
|
+
}), mutation = startMutation(parent), mutEntry = currentTasks(mutation).find((e) => e.name === task.name);
|
|
2392
|
+
mutEntry !== void 0 && (applyTaskStatusChange({
|
|
2393
|
+
entry: mutEntry,
|
|
2394
|
+
history: mutation.history,
|
|
2395
|
+
stage: stage.name,
|
|
2396
|
+
to: outcome,
|
|
2397
|
+
at: ctx.now,
|
|
2398
|
+
actor: gateActor(outcome)
|
|
2399
|
+
}), await persist(ctx, mutation), await cascadeAutoTransitions(client, parent._id, actor, clientForGdr, clock), await propagateToAncestors(client, parent._id, actor, clientForGdr, clock));
|
|
1989
2400
|
}
|
|
1990
2401
|
const parsedFilters = /* @__PURE__ */ new Map();
|
|
1991
2402
|
async function matchesFilter(args) {
|
|
@@ -2081,7 +2492,6 @@ async function fetchGrantsCached(requestFn, resourcePath) {
|
|
|
2081
2492
|
return;
|
|
2082
2493
|
}
|
|
2083
2494
|
}
|
|
2084
|
-
const WILDCARD_ROLE = "*";
|
|
2085
2495
|
async function evaluateInstance(args) {
|
|
2086
2496
|
const { client, tags, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
|
|
2087
2497
|
validateTags(tags);
|
|
@@ -2093,45 +2503,45 @@ async function evaluateInstance(args) {
|
|
|
2093
2503
|
throw new Error(`Workflow instance "${instanceId}" not found`);
|
|
2094
2504
|
if (!tags.some((t) => instance.tags?.includes(t)))
|
|
2095
2505
|
throw new Error(`Workflow instance "${instanceId}" not visible to this engine (tag mismatch)`);
|
|
2096
|
-
const definition = parseDefinitionSnapshot(instance), snapshot = await hydrateSnapshot({ client, clientForGdr: resourceClients !== void 0 ? (parsed) => resourceClients(parsed) ?? client : () => client, instance });
|
|
2506
|
+
const definition = parseDefinitionSnapshot(instance), snapshot = await hydrateSnapshot({ client, clientForGdr: resourceClients !== void 0 ? (parsed) => resourceClients(parsed) ?? client : () => client, instance }), guards = await verdictGuardsForInstance(client, instance._id);
|
|
2097
2507
|
return evaluateFromSnapshot({
|
|
2098
2508
|
instance,
|
|
2099
2509
|
definition,
|
|
2100
2510
|
actor,
|
|
2101
2511
|
snapshot,
|
|
2512
|
+
guards,
|
|
2102
2513
|
now,
|
|
2103
2514
|
...grants !== void 0 ? { grants } : {}
|
|
2104
2515
|
});
|
|
2105
2516
|
}
|
|
2106
2517
|
async function evaluateFromSnapshot(args) {
|
|
2107
|
-
const { instance, definition, actor, grants, snapshot } = args, now = args.now ?? wallClock(), stage = definition.stages.find((s) => s.
|
|
2518
|
+
const { instance, definition, actor, grants, snapshot } = args, now = args.now ?? wallClock(), stage = definition.stages.find((s) => s.name === instance.currentStage);
|
|
2108
2519
|
if (stage === void 0)
|
|
2109
2520
|
throw new Error(
|
|
2110
|
-
`Instance "${instance._id}"
|
|
2521
|
+
`Instance "${instance._id}" currentStage "${instance.currentStage}" not in definition`
|
|
2111
2522
|
);
|
|
2112
|
-
const currentTaskEntries = findOpenStageEntry(instance)?.tasks ?? [], taskEvaluations = [];
|
|
2523
|
+
const scope = await renderScope({ instance, definition, actor, grants, snapshot, now }), currentTaskEntries = findOpenStageEntry(instance)?.tasks ?? [], guardDenial = await instanceGuardReason(instance, actor, args.guards), taskEvaluations = [];
|
|
2113
2524
|
for (const task of stage.tasks ?? [])
|
|
2114
2525
|
taskEvaluations.push(
|
|
2115
2526
|
await evaluateTask({
|
|
2116
2527
|
task,
|
|
2117
|
-
statusEntry: currentTaskEntries.find((t) => t.
|
|
2528
|
+
statusEntry: currentTaskEntries.find((t) => t.name === task.name),
|
|
2118
2529
|
instance,
|
|
2119
|
-
definition,
|
|
2120
2530
|
actor,
|
|
2121
|
-
grants,
|
|
2122
2531
|
snapshot,
|
|
2123
|
-
|
|
2532
|
+
scope,
|
|
2533
|
+
stageHasExits: !isTerminalStage(stage),
|
|
2534
|
+
guardDenial
|
|
2124
2535
|
})
|
|
2125
2536
|
);
|
|
2126
2537
|
const transitionEvaluations = [];
|
|
2127
2538
|
for (const transition of stage.transitions ?? [])
|
|
2128
2539
|
transitionEvaluations.push({
|
|
2129
2540
|
transition,
|
|
2130
|
-
filterSatisfied: await
|
|
2131
|
-
|
|
2132
|
-
definition,
|
|
2541
|
+
filterSatisfied: await evaluateCondition({
|
|
2542
|
+
condition: transition.filter,
|
|
2133
2543
|
snapshot,
|
|
2134
|
-
params:
|
|
2544
|
+
params: scope
|
|
2135
2545
|
})
|
|
2136
2546
|
});
|
|
2137
2547
|
const currentStage = {
|
|
@@ -2148,142 +2558,102 @@ async function evaluateFromSnapshot(args) {
|
|
|
2148
2558
|
canInteract
|
|
2149
2559
|
};
|
|
2150
2560
|
}
|
|
2561
|
+
async function renderScope(args) {
|
|
2562
|
+
const { instance, definition, actor, grants, snapshot, now } = args, params = {
|
|
2563
|
+
...buildParams({ instance, now, snapshot }),
|
|
2564
|
+
actor,
|
|
2565
|
+
assigned: !1,
|
|
2566
|
+
can: await advisoryCan(instance, actor, grants)
|
|
2567
|
+
};
|
|
2568
|
+
return { ...await evaluatePredicates({
|
|
2569
|
+
predicates: definition.predicates,
|
|
2570
|
+
snapshot,
|
|
2571
|
+
params
|
|
2572
|
+
}), ...params };
|
|
2573
|
+
}
|
|
2574
|
+
async function advisoryCan(instance, actor, grants) {
|
|
2575
|
+
if (grants === void 0) return;
|
|
2576
|
+
const can = {};
|
|
2577
|
+
for (const permission of schema.DOCUMENT_VALUE_PERMISSIONS)
|
|
2578
|
+
can[permission] = await grantsPermissionOn({
|
|
2579
|
+
document: instance,
|
|
2580
|
+
grants,
|
|
2581
|
+
permission,
|
|
2582
|
+
userId: actor.id
|
|
2583
|
+
});
|
|
2584
|
+
return can;
|
|
2585
|
+
}
|
|
2151
2586
|
async function evaluateTask(args) {
|
|
2152
|
-
const { task, statusEntry, instance,
|
|
2587
|
+
const { task, statusEntry, instance, actor, snapshot, scope, stageHasExits, guardDenial } = args, status = statusEntry?.status ?? "pending", assigned = assignedFor(instance, task.name, actor), taskScope = {
|
|
2588
|
+
...scope,
|
|
2589
|
+
state: {
|
|
2590
|
+
...scope.state,
|
|
2591
|
+
...scopedStateOverlay(instance, snapshot, task.name)
|
|
2592
|
+
},
|
|
2593
|
+
assigned
|
|
2594
|
+
}, actions = [];
|
|
2153
2595
|
for (const action of task.actions ?? [])
|
|
2154
2596
|
actions.push(
|
|
2155
2597
|
await evaluateAction({
|
|
2156
2598
|
action,
|
|
2157
|
-
task,
|
|
2158
|
-
...statusEntry !== void 0 ? { statusEntry } : {},
|
|
2159
2599
|
status,
|
|
2160
2600
|
instance,
|
|
2161
|
-
definition,
|
|
2162
|
-
actor,
|
|
2163
|
-
...grants !== void 0 ? { grants } : {},
|
|
2164
2601
|
snapshot,
|
|
2165
|
-
|
|
2602
|
+
taskScope,
|
|
2603
|
+
stageHasExits,
|
|
2604
|
+
guardDenial
|
|
2166
2605
|
})
|
|
2167
2606
|
);
|
|
2168
2607
|
return {
|
|
2169
2608
|
task,
|
|
2170
|
-
status,
|
|
2171
|
-
// "pending on actor" treats both `pending` and `active` as waiting on
|
|
2172
|
-
// the assignee — pending tasks just haven't been
|
|
2173
|
-
// they're still inbox items.
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
}
|
|
2178
|
-
async function evaluateAction(args) {
|
|
2179
|
-
const { action, task, status, instance, definition, actor, grants, snapshot, now } = args, syncReason = lifecycleReason(instance, definition, status) ?? actorReason(action, task, actor);
|
|
2180
|
-
if (syncReason !== void 0) return disabled(action, syncReason);
|
|
2181
|
-
const asyncReason = await permissionReason(action, instance, actor, grants) ?? await filterReason(action, instance, definition, snapshot, now);
|
|
2182
|
-
return asyncReason !== void 0 ? disabled(action, asyncReason) : { action, allowed: !0 };
|
|
2183
|
-
}
|
|
2184
|
-
function lifecycleReason(instance, definition, status) {
|
|
2185
|
-
if (instance.completedAt !== void 0)
|
|
2186
|
-
return { kind: "instance-completed", completedAt: instance.completedAt };
|
|
2187
|
-
if (definition.stages.find((s) => s.id === instance.currentStageId)?.kind === "terminal")
|
|
2188
|
-
return { kind: "stage-terminal", stageId: instance.currentStageId };
|
|
2189
|
-
if (status === "done" || status === "skipped" || status === "failed")
|
|
2190
|
-
return { kind: "task-not-active", status };
|
|
2191
|
-
}
|
|
2192
|
-
function actorReason(action, task, actor) {
|
|
2193
|
-
const actorRoles = actor.roles ?? [];
|
|
2194
|
-
if (action.roles !== void 0 && action.roles.length > 0 && !(actorRoles.includes(WILDCARD_ROLE) || action.roles.some((r) => actorRoles.includes(r))))
|
|
2195
|
-
return { kind: "role-required", required: [...action.roles], actorRoles: [...actorRoles] };
|
|
2196
|
-
if (action.requireAssignment === !0 && !actorIsAssignee(actor, task.assignees ?? []))
|
|
2197
|
-
return { kind: "not-assigned", assignees: [...task.assignees ?? []], actorId: actor.id };
|
|
2198
|
-
}
|
|
2199
|
-
async function permissionReason(action, instance, actor, grants) {
|
|
2200
|
-
if (grants === void 0) return;
|
|
2201
|
-
const permission = action.requiredPermission ?? "update";
|
|
2202
|
-
if (!await grantsPermissionOn({
|
|
2203
|
-
document: instance,
|
|
2204
|
-
grants,
|
|
2205
|
-
permission,
|
|
2206
|
-
userId: actor.id
|
|
2207
|
-
}))
|
|
2208
|
-
return { kind: "permission-denied", permission, matchingGrants: grants.length };
|
|
2209
|
-
}
|
|
2210
|
-
async function filterReason(action, instance, definition, snapshot, now) {
|
|
2211
|
-
if (!(action.filter === void 0 || await evaluateFilter({
|
|
2212
|
-
filter: action.filter,
|
|
2213
|
-
definition,
|
|
2214
|
-
snapshot,
|
|
2215
|
-
params: buildParams(instance, now)
|
|
2216
|
-
})))
|
|
2217
|
-
return { kind: "filter-failed", filter: action.filter };
|
|
2218
|
-
}
|
|
2219
|
-
function disabled(action, reason) {
|
|
2220
|
-
return { action, allowed: !1, disabledReason: reason };
|
|
2221
|
-
}
|
|
2222
|
-
function actorIsAssignee(actor, assignees) {
|
|
2223
|
-
if (assignees.length === 0) return !1;
|
|
2224
|
-
const actorRoles = actor.roles ?? [], wildcard = actorRoles.includes(WILDCARD_ROLE);
|
|
2225
|
-
for (const assignee of assignees)
|
|
2226
|
-
if (assignee.kind === "user" && assignee.id === actor.id || assignee.kind === "role" && (wildcard || actorRoles.includes(assignee.role)))
|
|
2227
|
-
return !0;
|
|
2228
|
-
return !1;
|
|
2229
|
-
}
|
|
2230
|
-
function parseDefinitionSnapshot(instance) {
|
|
2231
|
-
try {
|
|
2232
|
-
return JSON.parse(instance.definitionSnapshot);
|
|
2233
|
-
} catch (err) {
|
|
2234
|
-
throw new Error(
|
|
2235
|
-
`Failed to parse definitionSnapshot on instance "${instance._id}": ${err instanceof Error ? err.message : String(err)}`,
|
|
2236
|
-
{ cause: err }
|
|
2237
|
-
);
|
|
2238
|
-
}
|
|
2609
|
+
status,
|
|
2610
|
+
// "pending on actor" treats both `pending` and `active` as waiting on
|
|
2611
|
+
// the assignee — pending tasks just haven't been activated yet, but
|
|
2612
|
+
// they're still inbox items. The inbox reads the assignees-kind state
|
|
2613
|
+
// entry BY KIND (who-data is state).
|
|
2614
|
+
pendingOnActor: (status === "active" || status === "pending") && assigned,
|
|
2615
|
+
actions
|
|
2616
|
+
};
|
|
2239
2617
|
}
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
return
|
|
2618
|
+
async function evaluateAction(args) {
|
|
2619
|
+
const { action, status, instance, snapshot, taskScope, stageHasExits, guardDenial } = args, lifecycle = lifecycleReason(instance, status, stageHasExits);
|
|
2620
|
+
return lifecycle !== void 0 ? disabled(action, lifecycle) : guardDenial !== void 0 ? disabled(action, guardDenial) : action.filter !== void 0 && !await evaluateCondition({
|
|
2621
|
+
condition: action.filter,
|
|
2622
|
+
snapshot,
|
|
2623
|
+
params: taskScope
|
|
2624
|
+
}) ? disabled(action, { kind: "filter-failed", filter: action.filter }) : { action, allowed: !0 };
|
|
2243
2625
|
}
|
|
2244
|
-
async function
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
if (parsed === void 0) continue;
|
|
2254
|
-
const key = resourceKey(resourceFromParsed(parsed));
|
|
2255
|
-
clientsByResource.has(key) || clientsByResource.set(key, clientForGdr(parsed));
|
|
2256
|
-
}
|
|
2257
|
-
const perResource = await Promise.all(
|
|
2258
|
-
[...clientsByResource.values()].map(
|
|
2259
|
-
(c) => c.fetch("*[_type == $t && sourceInstanceId == $id]", {
|
|
2260
|
-
t: GUARD_DOC_TYPE,
|
|
2261
|
-
id: instance._id
|
|
2262
|
-
})
|
|
2263
|
-
)
|
|
2264
|
-
);
|
|
2265
|
-
return dedupById(perResource.flat());
|
|
2626
|
+
async function instanceGuardReason(instance, actor, guards) {
|
|
2627
|
+
if (guards === void 0 || guards.length === 0) return;
|
|
2628
|
+
const denied = await instanceWriteDenials({ instance, guards, identity: actor.id });
|
|
2629
|
+
if (denied.length !== 0)
|
|
2630
|
+
return {
|
|
2631
|
+
kind: "mutation-guard-denied",
|
|
2632
|
+
guardIds: denied.map((g) => g._id),
|
|
2633
|
+
detail: denied.map((g) => g.name ?? g._id).join(", ")
|
|
2634
|
+
};
|
|
2266
2635
|
}
|
|
2267
|
-
function
|
|
2268
|
-
|
|
2269
|
-
return
|
|
2270
|
-
|
|
2271
|
-
return;
|
|
2272
|
-
|
|
2636
|
+
function lifecycleReason(instance, status, stageHasExits) {
|
|
2637
|
+
if (instance.completedAt !== void 0)
|
|
2638
|
+
return { kind: "instance-completed", completedAt: instance.completedAt };
|
|
2639
|
+
if (!stageHasExits)
|
|
2640
|
+
return { kind: "stage-terminal", stage: instance.currentStage };
|
|
2641
|
+
if (schema.isTerminalTaskStatus(status))
|
|
2642
|
+
return { kind: "task-not-active", status };
|
|
2273
2643
|
}
|
|
2274
|
-
function
|
|
2275
|
-
return
|
|
2644
|
+
function disabled(action, reason) {
|
|
2645
|
+
return { action, allowed: !1, disabledReason: reason };
|
|
2276
2646
|
}
|
|
2277
|
-
function definitionDocId(_workflowResource, prefix,
|
|
2278
|
-
return `${prefix}.${
|
|
2647
|
+
function definitionDocId(_workflowResource, prefix, definition, version) {
|
|
2648
|
+
return `${prefix}.${definition}.v${version}`;
|
|
2279
2649
|
}
|
|
2280
2650
|
async function sortByDependencies(client, definitions, tags, workflowResource) {
|
|
2281
|
-
const prefix = canonicalTag(tags),
|
|
2651
|
+
const prefix = canonicalTag(tags), byName = /* @__PURE__ */ new Map();
|
|
2282
2652
|
for (const def of definitions) {
|
|
2283
|
-
const list =
|
|
2284
|
-
list.push(def),
|
|
2653
|
+
const list = byName.get(def.name) ?? [];
|
|
2654
|
+
list.push(def), byName.set(def.name, list);
|
|
2285
2655
|
}
|
|
2286
|
-
const findInBatch = (ref) => findRefInBatch(
|
|
2656
|
+
const findInBatch = (ref) => findRefInBatch(byName, ref);
|
|
2287
2657
|
return await assertCrossBatchRefsDeployed(client, definitions, {
|
|
2288
2658
|
tags,
|
|
2289
2659
|
workflowResource,
|
|
@@ -2291,8 +2661,8 @@ async function sortByDependencies(client, definitions, tags, workflowResource) {
|
|
|
2291
2661
|
findInBatch
|
|
2292
2662
|
}), topoSortDefinitions(definitions, { workflowResource, prefix, findInBatch });
|
|
2293
2663
|
}
|
|
2294
|
-
function findRefInBatch(
|
|
2295
|
-
const candidates =
|
|
2664
|
+
function findRefInBatch(byName, ref) {
|
|
2665
|
+
const candidates = byName.get(ref.name);
|
|
2296
2666
|
if (!(candidates === void 0 || candidates.length === 0))
|
|
2297
2667
|
return typeof ref.version == "number" ? candidates.find((c) => c.version === ref.version) : candidates.reduce(
|
|
2298
2668
|
(best, c) => best === void 0 || c.version > best.version ? c : best,
|
|
@@ -2302,7 +2672,7 @@ function findRefInBatch(byWorkflowId, ref) {
|
|
|
2302
2672
|
async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
|
|
2303
2673
|
const missing = [];
|
|
2304
2674
|
for (const def of definitions) {
|
|
2305
|
-
const fromId = definitionDocId(ctx.workflowResource, ctx.prefix, def.
|
|
2675
|
+
const fromId = definitionDocId(ctx.workflowResource, ctx.prefix, def.name, def.version);
|
|
2306
2676
|
for (const ref of refsOf(def)) {
|
|
2307
2677
|
if (ctx.findInBatch(ref) !== void 0) continue;
|
|
2308
2678
|
const label = await resolveDeployedRefLabel(client, ref, ctx.tags);
|
|
@@ -2318,16 +2688,16 @@ async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
|
|
|
2318
2688
|
);
|
|
2319
2689
|
}
|
|
2320
2690
|
async function resolveDeployedRefLabel(client, ref, tags) {
|
|
2321
|
-
const wantsExplicit = typeof ref.version == "number", params = {
|
|
2691
|
+
const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, engineTags: tags };
|
|
2322
2692
|
if (wantsExplicit && (params.version = ref.version), !await client.fetch(
|
|
2323
2693
|
definitionLookupGroq(wantsExplicit),
|
|
2324
2694
|
params
|
|
2325
2695
|
))
|
|
2326
|
-
return wantsExplicit ? `${ref.
|
|
2696
|
+
return wantsExplicit ? `${ref.name} v${ref.version}` : ref.name;
|
|
2327
2697
|
}
|
|
2328
2698
|
function topoSortDefinitions(definitions, ctx) {
|
|
2329
2699
|
const visited = /* @__PURE__ */ new Set(), visiting = /* @__PURE__ */ new Set(), ordered = [], visit = (def) => {
|
|
2330
|
-
const id = definitionDocId(ctx.workflowResource, ctx.prefix, def.
|
|
2700
|
+
const id = definitionDocId(ctx.workflowResource, ctx.prefix, def.name, def.version);
|
|
2331
2701
|
if (!visited.has(id)) {
|
|
2332
2702
|
if (visiting.has(id))
|
|
2333
2703
|
throw new Error(`workflow.deployDefinitions: dependency cycle involving "${id}"`);
|
|
@@ -2346,9 +2716,9 @@ function refsOf(def) {
|
|
|
2346
2716
|
const out = [];
|
|
2347
2717
|
for (const stage of def.stages)
|
|
2348
2718
|
for (const task of stage.tasks ?? []) {
|
|
2349
|
-
const ref = task.
|
|
2719
|
+
const ref = task.subworkflows?.definition;
|
|
2350
2720
|
ref !== void 0 && out.push({
|
|
2351
|
-
|
|
2721
|
+
name: ref.name,
|
|
2352
2722
|
...ref.version !== void 0 ? { version: ref.version } : {}
|
|
2353
2723
|
});
|
|
2354
2724
|
}
|
|
@@ -2368,324 +2738,145 @@ function stableStringify(value) {
|
|
|
2368
2738
|
([a], [b]) => a.localeCompare(b)
|
|
2369
2739
|
).map(([k, v2]) => `${JSON.stringify(k)}:${stableStringify(v2)}`).join(",")}}`;
|
|
2370
2740
|
}
|
|
2371
|
-
async function loadDefinition(client,
|
|
2741
|
+
async function loadDefinition(client, definition, version, tags) {
|
|
2372
2742
|
if (version !== void 0) {
|
|
2373
2743
|
const doc = await client.fetch(
|
|
2374
2744
|
definitionLookupGroq(!0),
|
|
2375
|
-
{
|
|
2745
|
+
{ definition, version, engineTags: tags }
|
|
2376
2746
|
);
|
|
2377
2747
|
if (!doc)
|
|
2378
|
-
throw new Error(`Workflow definition ${
|
|
2748
|
+
throw new Error(`Workflow definition ${definition} v${version} not deployed`);
|
|
2379
2749
|
return doc;
|
|
2380
2750
|
}
|
|
2381
2751
|
const latest = await client.fetch(definitionLookupGroq(!1), {
|
|
2382
|
-
|
|
2752
|
+
definition,
|
|
2383
2753
|
engineTags: tags
|
|
2384
2754
|
});
|
|
2385
2755
|
if (!latest)
|
|
2386
|
-
throw new Error(`No deployed definition for workflow ${
|
|
2756
|
+
throw new Error(`No deployed definition for workflow ${definition}`);
|
|
2387
2757
|
return latest;
|
|
2388
2758
|
}
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
"workflow.op.state.removeWhere"
|
|
2394
|
-
];
|
|
2395
|
-
function isStateOp(summary) {
|
|
2396
|
-
return STATE_OP_TYPES.includes(summary.opType);
|
|
2397
|
-
}
|
|
2398
|
-
function validateActionParams(action, taskId, callerParams) {
|
|
2399
|
-
const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
|
|
2400
|
-
for (const decl of declared) {
|
|
2401
|
-
const value = params[decl.id], present = decl.id in params && value !== void 0 && value !== null;
|
|
2402
|
-
if (decl.required === !0 && !present) {
|
|
2403
|
-
issues.push({ paramId: decl.id, reason: "required but missing" });
|
|
2404
|
-
continue;
|
|
2405
|
-
}
|
|
2406
|
-
present && (checkParamType(value, decl) || issues.push({
|
|
2407
|
-
paramId: decl.id,
|
|
2408
|
-
reason: `expected paramType=${decl.paramType}, got ${typeof value}`
|
|
2409
|
-
}));
|
|
2410
|
-
}
|
|
2411
|
-
if (issues.length > 0)
|
|
2412
|
-
throw new ActionParamsInvalidError({ action: action.id, taskId, issues });
|
|
2413
|
-
return params;
|
|
2414
|
-
}
|
|
2415
|
-
function hasStringField(value, field) {
|
|
2416
|
-
return typeof value == "object" && value !== null && field in value && typeof value[field] == "string";
|
|
2417
|
-
}
|
|
2418
|
-
function checkParamType(value, decl) {
|
|
2419
|
-
switch (decl.paramType) {
|
|
2420
|
-
case "string":
|
|
2421
|
-
case "url":
|
|
2422
|
-
case "dateTime":
|
|
2423
|
-
return typeof value == "string";
|
|
2424
|
-
case "number":
|
|
2425
|
-
return typeof value == "number" && Number.isFinite(value);
|
|
2426
|
-
case "boolean":
|
|
2427
|
-
return typeof value == "boolean";
|
|
2428
|
-
case "actor":
|
|
2429
|
-
return hasStringField(value, "kind");
|
|
2430
|
-
case "doc.ref":
|
|
2431
|
-
return hasStringField(value, "id");
|
|
2432
|
-
case "doc.refs":
|
|
2433
|
-
return Array.isArray(value) && value.every((v2) => checkParamType(v2, { paramType: "doc.ref" }));
|
|
2434
|
-
case "json":
|
|
2435
|
-
return !0;
|
|
2436
|
-
default:
|
|
2437
|
-
return !1;
|
|
2438
|
-
}
|
|
2439
|
-
}
|
|
2440
|
-
async function runActionOps(args) {
|
|
2441
|
-
const { ops, mutation, stageId, taskId, actionName, params, actor, self, now } = args;
|
|
2442
|
-
if (ops === void 0 || ops.length === 0) return [];
|
|
2443
|
-
const summaries = [];
|
|
2444
|
-
for (const op of ops) {
|
|
2445
|
-
const summary = applyOp(op, { mutation, stageId, taskId, params, actor, self, now });
|
|
2446
|
-
summaries.push(summary), mutation.history.push({
|
|
2447
|
-
_key: randomKey(),
|
|
2448
|
-
_type: "workflow.history.opApplied",
|
|
2449
|
-
at: now,
|
|
2450
|
-
stageId,
|
|
2451
|
-
taskId,
|
|
2452
|
-
actionId: actionName,
|
|
2453
|
-
opType: summary.opType,
|
|
2454
|
-
...summary.target !== void 0 ? { target: summary.target } : {},
|
|
2455
|
-
...summary.resolved !== void 0 ? { resolved: summary.resolved } : {},
|
|
2456
|
-
...actor !== void 0 ? { actor } : {}
|
|
2457
|
-
});
|
|
2458
|
-
}
|
|
2459
|
-
return summaries;
|
|
2460
|
-
}
|
|
2461
|
-
function applyOp(op, ctx) {
|
|
2462
|
-
switch (op.type) {
|
|
2463
|
-
case "workflow.op.state.set":
|
|
2464
|
-
return applyStateSet(op, ctx);
|
|
2465
|
-
case "workflow.op.state.append":
|
|
2466
|
-
return applyStateAppend(op, ctx);
|
|
2467
|
-
case "workflow.op.state.updateWhere":
|
|
2468
|
-
return applyStateUpdateWhere(op, ctx);
|
|
2469
|
-
case "workflow.op.state.removeWhere":
|
|
2470
|
-
return applyStateRemoveWhere(op, ctx);
|
|
2471
|
-
case "workflow.op.status.set":
|
|
2472
|
-
return applyStatusSet(op, ctx);
|
|
2473
|
-
}
|
|
2474
|
-
}
|
|
2475
|
-
function applyStateSet(op, ctx) {
|
|
2476
|
-
const value = resolveOpSource(op.value, ctx), slot = locateSlot(
|
|
2477
|
-
ctx,
|
|
2478
|
-
op.target,
|
|
2479
|
-
/*createIfMissing*/
|
|
2480
|
-
!0
|
|
2481
|
-
);
|
|
2482
|
-
return slot !== void 0 && (validateSlotValue({ slotType: slot._type, slotId: slot.id, value }), slot.value = value), { opType: op.type, target: op.target, resolved: { value } };
|
|
2483
|
-
}
|
|
2484
|
-
function applyStateAppend(op, ctx) {
|
|
2485
|
-
const item = resolveOpSource(op.item, ctx), slot = locateSlot(
|
|
2486
|
-
ctx,
|
|
2487
|
-
op.target,
|
|
2488
|
-
/*createIfMissing*/
|
|
2489
|
-
!0
|
|
2759
|
+
async function loadDefinitionVersions(client, definition, tags) {
|
|
2760
|
+
return client.fetch(
|
|
2761
|
+
`*[_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && name == $definition && count(tags[@ in $engineTags]) > 0] | order(version desc)`,
|
|
2762
|
+
{ definition, engineTags: tags }
|
|
2490
2763
|
);
|
|
2491
|
-
if (slot !== void 0) {
|
|
2492
|
-
validateSlotAppendItem({ slotType: slot._type, slotId: slot.id, item });
|
|
2493
|
-
const current = Array.isArray(slot.value) ? slot.value : [];
|
|
2494
|
-
slot.value = [...current, withRowKey(item)];
|
|
2495
|
-
}
|
|
2496
|
-
return { opType: op.type, target: op.target, resolved: { item } };
|
|
2497
2764
|
}
|
|
2498
|
-
function
|
|
2499
|
-
|
|
2500
|
-
}
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
op.target,
|
|
2505
|
-
/*createIfMissing*/
|
|
2506
|
-
!1
|
|
2507
|
-
), resolvedMerge = {};
|
|
2508
|
-
for (const [k, v2] of Object.entries(op.merge))
|
|
2509
|
-
resolvedMerge[k] = resolveOpSource(v2, ctx);
|
|
2510
|
-
return slot !== void 0 && Array.isArray(slot.value) && (slot.value = slot.value.map(
|
|
2511
|
-
(row) => evalOpPredicate(op.where, row, ctx) ? { ...row, ...resolvedMerge } : row
|
|
2512
|
-
)), { opType: op.type, target: op.target, resolved: { merge: resolvedMerge } };
|
|
2513
|
-
}
|
|
2514
|
-
function applyStateRemoveWhere(op, ctx) {
|
|
2515
|
-
const slot = locateSlot(
|
|
2516
|
-
ctx,
|
|
2517
|
-
op.target,
|
|
2518
|
-
/*createIfMissing*/
|
|
2519
|
-
!1
|
|
2520
|
-
);
|
|
2521
|
-
return slot !== void 0 && Array.isArray(slot.value) && (slot.value = slot.value.filter(
|
|
2522
|
-
(row) => !evalOpPredicate(op.where, row, ctx)
|
|
2523
|
-
)), { opType: op.type, target: op.target };
|
|
2765
|
+
async function abortInstance(args) {
|
|
2766
|
+
const { client, instanceId, reason, options } = args, ctx = await loadContext(client, instanceId, {
|
|
2767
|
+
...options?.clock ? { clock: options.clock } : {},
|
|
2768
|
+
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
2769
|
+
});
|
|
2770
|
+
return commitAbort(ctx, reason, options?.actor);
|
|
2524
2771
|
}
|
|
2525
|
-
function
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
);
|
|
2543
|
-
let slot = host.find((s) => s.id === target.slotId);
|
|
2544
|
-
return slot === void 0 && createIfMissing && (slot = {
|
|
2772
|
+
async function commitAbort(ctx, reason, actor) {
|
|
2773
|
+
if (ctx.instance.completedAt !== void 0)
|
|
2774
|
+
return { fired: !1 };
|
|
2775
|
+
const stage = findStage(ctx.definition, ctx.instance.currentStage), mutation = startMutation(ctx.instance), at = ctx.now, openEntry = findOpenStageEntry(mutation);
|
|
2776
|
+
return openEntry !== void 0 && (openEntry.exitedAt = at), mutation.effectHistory.push(
|
|
2777
|
+
...mutation.pendingEffects.map(
|
|
2778
|
+
(pending) => buildEffectHistoryEntry(pending, {
|
|
2779
|
+
status: "failed",
|
|
2780
|
+
ranAt: at,
|
|
2781
|
+
actor,
|
|
2782
|
+
detail: "instance aborted before dispatch",
|
|
2783
|
+
error: void 0,
|
|
2784
|
+
durationMs: void 0,
|
|
2785
|
+
outputs: void 0
|
|
2786
|
+
})
|
|
2787
|
+
)
|
|
2788
|
+
), mutation.pendingEffects = [], mutation.history.push({
|
|
2545
2789
|
_key: randomKey(),
|
|
2546
|
-
_type: "
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
}
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
case "stateRead": {
|
|
2560
|
-
const value = readSlotFromMutation(ctx, src.scope, src.slotId);
|
|
2561
|
-
return src.path !== void 0 ? getPath(value, src.path) : value;
|
|
2562
|
-
}
|
|
2563
|
-
case "effectOutput": {
|
|
2564
|
-
const entry = ctx.mutation.effectsContext.find((e) => e.id === src.contextId);
|
|
2565
|
-
if (entry === void 0) return null;
|
|
2566
|
-
const v2 = src.path !== void 0 ? getPath(entry.value, src.path) : entry.value;
|
|
2567
|
-
return v2 === void 0 ? null : v2;
|
|
2568
|
-
}
|
|
2569
|
-
case "object": {
|
|
2570
|
-
const out = {};
|
|
2571
|
-
for (const [field, fieldSrc] of Object.entries(src.fields))
|
|
2572
|
-
out[field] = resolveOpSource(fieldSrc, ctx);
|
|
2573
|
-
return out;
|
|
2574
|
-
}
|
|
2575
|
-
case "row":
|
|
2576
|
-
case "parentState":
|
|
2577
|
-
return;
|
|
2578
|
-
default:
|
|
2579
|
-
return;
|
|
2580
|
-
}
|
|
2581
|
-
}
|
|
2582
|
-
function slotValue(slots, slotId) {
|
|
2583
|
-
return slots?.find((s) => s.id === slotId)?.value;
|
|
2584
|
-
}
|
|
2585
|
-
function readSlotFromMutation(ctx, scope, slotId) {
|
|
2586
|
-
if (scope === "workflow") return slotValue(ctx.mutation.state, slotId);
|
|
2587
|
-
const stageEntry = findOpenStageEntry(ctx.mutation);
|
|
2588
|
-
if (scope === "stage") return slotValue(stageEntry?.state, slotId);
|
|
2589
|
-
const task = stageEntry?.tasks.find((t) => t.id === ctx.taskId);
|
|
2590
|
-
return slotValue(task?.state, slotId);
|
|
2591
|
-
}
|
|
2592
|
-
function evalOpPredicate(p, row, ctx) {
|
|
2593
|
-
if ("all" in p) return p.all.every((sub) => evalOpPredicate(sub, row, ctx));
|
|
2594
|
-
if ("any" in p) return p.any.some((sub) => evalOpPredicate(sub, row, ctx));
|
|
2595
|
-
const target = resolveOpSource(p.equals, ctx);
|
|
2596
|
-
return row[p.field] === target;
|
|
2790
|
+
_type: "aborted",
|
|
2791
|
+
at,
|
|
2792
|
+
stage: stage.name,
|
|
2793
|
+
...reason !== void 0 ? { reason } : {},
|
|
2794
|
+
...actor !== void 0 ? { actor } : {}
|
|
2795
|
+
}), mutation.completedAt = at, mutation.abortedAt = at, await persist(ctx, mutation), await retractStageGuards({
|
|
2796
|
+
client: ctx.client,
|
|
2797
|
+
clientForGdr: ctx.clientForGdr,
|
|
2798
|
+
instance: ctx.instance,
|
|
2799
|
+
definition: ctx.definition,
|
|
2800
|
+
stageName: stage.name,
|
|
2801
|
+
now: ctx.now
|
|
2802
|
+
}), { fired: !0, stage: stage.name };
|
|
2597
2803
|
}
|
|
2598
2804
|
async function fireAction(args) {
|
|
2599
|
-
const { client, instanceId,
|
|
2805
|
+
const { client, instanceId, task, action, params, options } = args;
|
|
2600
2806
|
for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
|
|
2601
2807
|
const ctx = await loadContext(client, instanceId, {
|
|
2602
|
-
...options?.clock ? { clock: options.clock } : {}
|
|
2808
|
+
...options?.clock ? { clock: options.clock } : {},
|
|
2809
|
+
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
2603
2810
|
});
|
|
2604
2811
|
try {
|
|
2605
|
-
return await commitAction(ctx,
|
|
2812
|
+
return await commitAction(ctx, task, action, params, options);
|
|
2606
2813
|
} catch (error) {
|
|
2607
2814
|
if (!isRevisionConflict(error)) throw error;
|
|
2608
2815
|
}
|
|
2609
2816
|
}
|
|
2610
2817
|
throw new ConcurrentFireActionError({
|
|
2611
2818
|
instanceId,
|
|
2612
|
-
|
|
2819
|
+
task,
|
|
2613
2820
|
action,
|
|
2614
2821
|
attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
|
|
2615
2822
|
});
|
|
2616
2823
|
}
|
|
2617
|
-
async function resolveActionCommit(ctx,
|
|
2618
|
-
const { stage, task } = findTaskInCurrentStage(ctx,
|
|
2824
|
+
async function resolveActionCommit(ctx, taskName, actionName, callerParams, options) {
|
|
2825
|
+
const actor = options?.actor, { stage, task } = findTaskInCurrentStage(ctx, taskName), action = (task.actions ?? []).find((a) => a.name === actionName);
|
|
2619
2826
|
if (action === void 0)
|
|
2620
|
-
throw new Error(`Action "${actionName}" not declared on task "${
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2827
|
+
throw new Error(`Action "${actionName}" not declared on task "${taskName}"`);
|
|
2828
|
+
const can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan(ctx.instance, actor, options.grants) : void 0;
|
|
2829
|
+
if (!await ctxEvaluateCondition(ctx, action.filter, {
|
|
2830
|
+
taskName,
|
|
2831
|
+
...actor !== void 0 ? { actor } : {},
|
|
2832
|
+
...can !== void 0 ? { vars: { can } } : {}
|
|
2833
|
+
}))
|
|
2834
|
+
throw new Error(`Action "${actionName}" filter rejected on task "${taskName}"`);
|
|
2835
|
+
const entry = findCurrentTasks(ctx.instance).find((t) => t.name === taskName);
|
|
2624
2836
|
if (entry === void 0 || entry.status !== "active")
|
|
2625
2837
|
throw new Error(
|
|
2626
|
-
`Task "${
|
|
2838
|
+
`Task "${taskName}" must be active to fire action "${actionName}"; status is ${entry?.status ?? "missing"}`
|
|
2627
2839
|
);
|
|
2628
|
-
const params = validateActionParams(action,
|
|
2840
|
+
const params = validateActionParams(action, taskName, callerParams);
|
|
2629
2841
|
return { stage, task, action, params };
|
|
2630
2842
|
}
|
|
2631
|
-
function
|
|
2632
|
-
|
|
2633
|
-
const initialStatus = mutEntry.status, newStatus = action.setStatus;
|
|
2634
|
-
return mutEntry.status = newStatus, mutEntry.completedAt = now, actor && (mutEntry.completedBy = actor.id), mutation.history.push(
|
|
2635
|
-
taskStatusChangedEntry({
|
|
2636
|
-
stageId,
|
|
2637
|
-
taskId,
|
|
2638
|
-
from: initialStatus,
|
|
2639
|
-
to: newStatus,
|
|
2640
|
-
at: now,
|
|
2641
|
-
...actor !== void 0 ? { actor } : {}
|
|
2642
|
-
})
|
|
2643
|
-
), newStatus;
|
|
2644
|
-
}
|
|
2645
|
-
async function commitAction(ctx, taskId, actionName, callerParams, actor) {
|
|
2646
|
-
const { stage, task, action, params } = await resolveActionCommit(
|
|
2843
|
+
async function commitAction(ctx, taskName, actionName, callerParams, options) {
|
|
2844
|
+
const actor = options?.actor, { stage, action, params } = await resolveActionCommit(
|
|
2647
2845
|
ctx,
|
|
2648
|
-
|
|
2846
|
+
taskName,
|
|
2649
2847
|
actionName,
|
|
2650
|
-
callerParams
|
|
2651
|
-
|
|
2848
|
+
callerParams,
|
|
2849
|
+
options
|
|
2850
|
+
), mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation, taskName), now = ctx.now;
|
|
2652
2851
|
mutation.history.push({
|
|
2653
2852
|
_key: randomKey(),
|
|
2654
|
-
_type: "
|
|
2853
|
+
_type: "actionFired",
|
|
2655
2854
|
at: now,
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2855
|
+
stage: stage.name,
|
|
2856
|
+
task: taskName,
|
|
2857
|
+
action: actionName,
|
|
2659
2858
|
...actor !== void 0 ? { actor } : {}
|
|
2660
2859
|
});
|
|
2661
|
-
const ranOps = await
|
|
2860
|
+
const statusBefore = mutEntry.status, ranOps = await runOps({
|
|
2662
2861
|
ops: action.ops,
|
|
2663
2862
|
mutation,
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
actionName,
|
|
2863
|
+
stage: stage.name,
|
|
2864
|
+
origin: { task: taskName, action: actionName },
|
|
2667
2865
|
params,
|
|
2668
2866
|
actor,
|
|
2669
2867
|
self: selfGdr(ctx.instance),
|
|
2670
2868
|
now
|
|
2671
|
-
}), newStatus =
|
|
2672
|
-
return await queueEffects(
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
{
|
|
2677
|
-
kind: "action",
|
|
2678
|
-
id: `${task.id}:${actionName}`
|
|
2679
|
-
},
|
|
2680
|
-
actor,
|
|
2681
|
-
params
|
|
2682
|
-
), await persistThenDeploy(
|
|
2869
|
+
}), newStatus = mutEntry.status !== statusBefore ? mutEntry.status : void 0;
|
|
2870
|
+
return await queueEffects(ctx, mutation, action.effects, { kind: "action", name: actionName }, actor, {
|
|
2871
|
+
callerParams: params,
|
|
2872
|
+
taskName
|
|
2873
|
+
}), await persistThenDeploy(
|
|
2683
2874
|
ctx,
|
|
2684
2875
|
mutation,
|
|
2685
|
-
() => ranOps.some(isStateOp) ? refreshStageGuards(ctx, mutation, stage.
|
|
2876
|
+
() => ranOps.some(isStateOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
|
|
2686
2877
|
), {
|
|
2687
2878
|
fired: !0,
|
|
2688
|
-
|
|
2879
|
+
task: taskName,
|
|
2689
2880
|
action: actionName,
|
|
2690
2881
|
...newStatus !== void 0 ? { newStatus } : {},
|
|
2691
2882
|
...ranOps.length > 0 ? { ranOps } : {}
|
|
@@ -2693,25 +2884,22 @@ async function commitAction(ctx, taskId, actionName, callerParams, actor) {
|
|
|
2693
2884
|
}
|
|
2694
2885
|
class ActionDisabledError extends Error {
|
|
2695
2886
|
reason;
|
|
2696
|
-
|
|
2887
|
+
task;
|
|
2697
2888
|
action;
|
|
2698
2889
|
constructor(args) {
|
|
2699
|
-
super(formatDisabledReason(args.
|
|
2890
|
+
super(formatDisabledReason(args.task, args.action, args.reason)), this.name = "ActionDisabledError", this.reason = args.reason, this.task = args.task, this.action = args.action;
|
|
2700
2891
|
}
|
|
2701
2892
|
}
|
|
2702
2893
|
const disabledReasonDetail = {
|
|
2703
|
-
"role-required": (r) => `requires one of [${r.required.join(", ")}]; actor has [${r.actorRoles.join(", ") || "(none)"}]`,
|
|
2704
|
-
"not-assigned": (r) => `actor "${r.actorId}" is not in the task assignees`,
|
|
2705
|
-
"permission-denied": (r) => `actor lacks "${r.permission}" permission on the workflow instance`,
|
|
2706
2894
|
"filter-failed": (r) => `action filter returned false${r.detail ? ` (${r.detail})` : ""}`,
|
|
2707
2895
|
"task-not-active": (r) => `task status is "${r.status}"`,
|
|
2708
|
-
"stage-terminal": (r) => `stage "${r.
|
|
2896
|
+
"stage-terminal": (r) => `stage "${r.stage}" is terminal`,
|
|
2709
2897
|
"instance-completed": (r) => `instance completed at ${r.completedAt}`,
|
|
2710
2898
|
"mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`
|
|
2711
2899
|
};
|
|
2712
|
-
function formatDisabledReason(
|
|
2900
|
+
function formatDisabledReason(task, action, reason) {
|
|
2713
2901
|
const detail = disabledReasonDetail[reason.kind](reason);
|
|
2714
|
-
return `Action "${
|
|
2902
|
+
return `Action "${task}:${action}" is not allowed: ${detail}`;
|
|
2715
2903
|
}
|
|
2716
2904
|
function bareIdFromSpawnRef(uri) {
|
|
2717
2905
|
if (!uri.includes(":")) return [uri];
|
|
@@ -2744,6 +2932,15 @@ async function cascade(client, instanceId, actor, clientForGdr, clock, overlay)
|
|
|
2744
2932
|
);
|
|
2745
2933
|
return await propagateToAncestors(client, instanceId, actor, clientForGdr, clock), count;
|
|
2746
2934
|
}
|
|
2935
|
+
async function abortAndPropagate(args) {
|
|
2936
|
+
const { client, instanceId, reason, actor, clientForGdr, clock } = args, result = await abortInstance({
|
|
2937
|
+
client,
|
|
2938
|
+
instanceId,
|
|
2939
|
+
...reason !== void 0 ? { reason } : {},
|
|
2940
|
+
options: { ...actor !== void 0 ? { actor } : {}, clock, clientForGdr }
|
|
2941
|
+
});
|
|
2942
|
+
return result.fired && await propagateToAncestors(client, instanceId, actor, clientForGdr, clock), result.fired;
|
|
2943
|
+
}
|
|
2747
2944
|
function buildClientForGdr(defaultClient, resolver) {
|
|
2748
2945
|
return resolver === void 0 ? () => defaultClient : (parsed) => resolver(parsed) ?? defaultClient;
|
|
2749
2946
|
}
|
|
@@ -2760,36 +2957,109 @@ function intersectsTags(docTags, engineTags) {
|
|
|
2760
2957
|
function toEffectsContextEntries(ctx) {
|
|
2761
2958
|
return Object.entries(ctx).map(([id, value]) => effectsContextEntry(id, value));
|
|
2762
2959
|
}
|
|
2763
|
-
function assertActionAllowed(evaluation,
|
|
2764
|
-
const actionEval = evaluation.currentStage.tasks.find((t) => t.task.
|
|
2960
|
+
function assertActionAllowed(evaluation, task, action) {
|
|
2961
|
+
const actionEval = evaluation.currentStage.tasks.find((t) => t.task.name === task)?.actions.find((a) => a.action.name === action);
|
|
2765
2962
|
if (actionEval?.allowed === !1 && actionEval.disabledReason)
|
|
2766
|
-
throw new ActionDisabledError({
|
|
2963
|
+
throw new ActionDisabledError({ task, action, reason: actionEval.disabledReason });
|
|
2767
2964
|
}
|
|
2768
2965
|
async function applyAction(args) {
|
|
2769
|
-
const { client, instance,
|
|
2770
|
-
|
|
2966
|
+
const { client, instance, task, action, params, actor, grants, clock, clientForGdr } = args, options = {
|
|
2967
|
+
actor,
|
|
2968
|
+
...grants !== void 0 ? { grants } : {},
|
|
2969
|
+
...clock !== void 0 ? { clock } : {},
|
|
2970
|
+
...clientForGdr !== void 0 ? { clientForGdr } : {}
|
|
2971
|
+
};
|
|
2972
|
+
return findOpenStageEntry(instance)?.tasks.find((t) => t.name === task)?.status === "pending" && await invokeTask({ client, instanceId: instance._id, task, options }), (await fireAction({
|
|
2771
2973
|
client,
|
|
2772
2974
|
instanceId: instance._id,
|
|
2773
|
-
|
|
2975
|
+
task,
|
|
2774
2976
|
action,
|
|
2775
2977
|
...params !== void 0 ? { params } : {},
|
|
2776
2978
|
options
|
|
2777
2979
|
})).ranOps;
|
|
2778
2980
|
}
|
|
2981
|
+
async function deleteDefinition(args) {
|
|
2982
|
+
const { client, tags, definition, version, cascade: cascade2, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, versions = await loadDefinitionVersions(client, definition, tags);
|
|
2983
|
+
if (versions.length === 0)
|
|
2984
|
+
throw new Error(`No deployed definition for workflow ${definition}`);
|
|
2985
|
+
const targets = version === void 0 ? versions : versions.filter((d) => d.version === version);
|
|
2986
|
+
if (targets.length === 0)
|
|
2987
|
+
throw new Error(`Workflow definition ${definition} v${version} not deployed`);
|
|
2988
|
+
const lastVersionGoes = targets.length === versions.length;
|
|
2989
|
+
await assertNoSpawnReferrers(client, tags, definition, targets, lastVersionGoes);
|
|
2990
|
+
const instanceIds = await nonTerminalInstanceIds(client, tags, definition, version);
|
|
2991
|
+
if (instanceIds.length > 0 && cascade2 !== !0)
|
|
2992
|
+
throw new Error(
|
|
2993
|
+
`Cannot delete ${definition}: ${instanceIds.length} non-terminal instance(s) exist (${previewIds(instanceIds)}). Pass cascade to abort them first \u2014 instances are aborted in place, never deleted.`
|
|
2994
|
+
);
|
|
2995
|
+
const abortedInstanceIds = await abortInstances({
|
|
2996
|
+
client,
|
|
2997
|
+
instanceIds,
|
|
2998
|
+
reason,
|
|
2999
|
+
actor,
|
|
3000
|
+
clientForGdr,
|
|
3001
|
+
clock
|
|
3002
|
+
}), tx = client.transaction();
|
|
3003
|
+
for (const target of targets) tx.delete(target._id);
|
|
3004
|
+
await tx.commit();
|
|
3005
|
+
const deletedGuardCount = lastVersionGoes ? await deleteOrphanedDefinitionGuards({
|
|
3006
|
+
client,
|
|
3007
|
+
clientForGdr,
|
|
3008
|
+
workflowResource: args.workflowResource,
|
|
3009
|
+
definition,
|
|
3010
|
+
definitions: versions,
|
|
3011
|
+
tags
|
|
3012
|
+
}) : 0;
|
|
3013
|
+
return {
|
|
3014
|
+
name: definition,
|
|
3015
|
+
deletedVersions: targets.map((d) => d.version),
|
|
3016
|
+
abortedInstanceIds,
|
|
3017
|
+
deletedGuardCount
|
|
3018
|
+
};
|
|
3019
|
+
}
|
|
3020
|
+
async function assertNoSpawnReferrers(client, tags, definition, targets, lastVersionGoes) {
|
|
3021
|
+
const deployed = await client.fetch(
|
|
3022
|
+
`*[_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && ${tagScopeFilter()}]`,
|
|
3023
|
+
{ engineTags: tags }
|
|
3024
|
+
), targetIds = new Set(targets.map((d) => d._id)), targetVersions = new Set(targets.map((d) => d.version)), referrers = deployed.filter((d) => !targetIds.has(d._id)).filter(
|
|
3025
|
+
(d) => refsOf(d).some(
|
|
3026
|
+
(ref) => ref.name === definition && (typeof ref.version == "number" ? targetVersions.has(ref.version) : lastVersionGoes)
|
|
3027
|
+
)
|
|
3028
|
+
);
|
|
3029
|
+
if (referrers.length > 0) {
|
|
3030
|
+
const names = referrers.map((d) => `${d.name} v${d.version}`).join(", ");
|
|
3031
|
+
throw new Error(
|
|
3032
|
+
`Cannot delete ${definition}: still spawn-referenced by deployed definition(s) ${names}. Delete or redeploy the referrer(s) first.`
|
|
3033
|
+
);
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
async function nonTerminalInstanceIds(client, tags, definition, version) {
|
|
3037
|
+
const versionFilter = version === void 0 ? "" : " && pinnedVersion == $version";
|
|
3038
|
+
return (await client.fetch(
|
|
3039
|
+
`*[_type == "${WORKFLOW_INSTANCE_TYPE}" && definition == $definition && ${tagScopeFilter()} && !defined(completedAt)${versionFilter}] | order(_id asc){_id}`,
|
|
3040
|
+
{ definition, engineTags: tags, ...version !== void 0 ? { version } : {} }
|
|
3041
|
+
)).map((doc) => doc._id);
|
|
3042
|
+
}
|
|
3043
|
+
async function abortInstances(args) {
|
|
3044
|
+
const { client, instanceIds, reason, actor, clientForGdr, clock } = args, aborted = [];
|
|
3045
|
+
for (const instanceId of instanceIds)
|
|
3046
|
+
await abortAndPropagate({ client, instanceId, reason, actor, clientForGdr, clock }) && aborted.push(instanceId);
|
|
3047
|
+
return aborted;
|
|
3048
|
+
}
|
|
3049
|
+
function previewIds(ids) {
|
|
3050
|
+
const head = ids.slice(0, 3).join(", ");
|
|
3051
|
+
return ids.length > 3 ? `${head}, \u2026` : head;
|
|
3052
|
+
}
|
|
2779
3053
|
const workflow = {
|
|
2780
3054
|
/**
|
|
2781
|
-
*
|
|
2782
|
-
*
|
|
2783
|
-
* `
|
|
2784
|
-
*
|
|
2785
|
-
*
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
*
|
|
2789
|
-
* out the dependency order itself (children before parents that spawn
|
|
2790
|
-
* them via `task.spawns.definitionRef`), idempotently writes any
|
|
2791
|
-
* definition whose content has changed, and reports a per-definition
|
|
2792
|
-
* outcome (`created` / `updated` / `unchanged`).
|
|
3055
|
+
* Deploy a set of workflow definitions as one call. Each lands as a
|
|
3056
|
+
* {@link WORKFLOW_DEFINITION_TYPE} document; subsequent versions are
|
|
3057
|
+
* stored alongside earlier ones — `startInstance` picks the highest
|
|
3058
|
+
* version by default. The SDK figures out the dependency order itself
|
|
3059
|
+
* (children before parents that spawn them via
|
|
3060
|
+
* `subworkflows.definition`), idempotently writes any definition
|
|
3061
|
+
* whose content has changed, and reports a per-definition outcome
|
|
3062
|
+
* (`created` / `updated` / `unchanged`).
|
|
2793
3063
|
*
|
|
2794
3064
|
* Refs may point inside the batch OR at already-deployed definitions
|
|
2795
3065
|
* in the lake — both are valid. A ref pointing at neither errors with
|
|
@@ -2805,14 +3075,14 @@ const workflow = {
|
|
|
2805
3075
|
const ordered = await sortByDependencies(client, definitions, tags, workflowResource), results = [], prefix = canonicalTag(tags), tx = client.transaction();
|
|
2806
3076
|
let hasWrites = !1;
|
|
2807
3077
|
for (const def of ordered) {
|
|
2808
|
-
const id = definitionDocId(workflowResource, prefix, def.
|
|
3078
|
+
const id = definitionDocId(workflowResource, prefix, def.name, def.version), expected = {
|
|
2809
3079
|
...def,
|
|
2810
3080
|
_id: id,
|
|
2811
3081
|
_type: schema.WORKFLOW_DEFINITION_TYPE,
|
|
2812
3082
|
tags
|
|
2813
3083
|
}, existing = await client.getDocument(id);
|
|
2814
3084
|
if (existing && intersectsTags(existing.tags, tags) && isDefinitionUnchanged(existing, expected)) {
|
|
2815
|
-
results.push({
|
|
3085
|
+
results.push({ definition: def.name, version: def.version, status: "unchanged" });
|
|
2816
3086
|
continue;
|
|
2817
3087
|
}
|
|
2818
3088
|
if (existing) {
|
|
@@ -2823,13 +3093,21 @@ const workflow = {
|
|
|
2823
3093
|
} else
|
|
2824
3094
|
tx.create(expected);
|
|
2825
3095
|
hasWrites = !0, results.push({
|
|
2826
|
-
|
|
3096
|
+
definition: def.name,
|
|
2827
3097
|
version: def.version,
|
|
2828
3098
|
status: existing ? "updated" : "created"
|
|
2829
3099
|
});
|
|
2830
3100
|
}
|
|
2831
3101
|
return hasWrites && await tx.commit(), { results };
|
|
2832
3102
|
},
|
|
3103
|
+
/**
|
|
3104
|
+
* Remove a deployed workflow definition (all versions, or one via
|
|
3105
|
+
* `version`). Refuses while non-terminal instances exist unless
|
|
3106
|
+
* `cascade` aborts them first — instances are never deleted, only
|
|
3107
|
+
* aborted in place; see {@link deleteDefinitionInternal} for the
|
|
3108
|
+
* full contract (spawn-referrer check, guard-doc housekeeping).
|
|
3109
|
+
*/
|
|
3110
|
+
deleteDefinition: async (args) => deleteDefinition(args),
|
|
2833
3111
|
/**
|
|
2834
3112
|
* Spawn a new workflow instance from a deployed definition.
|
|
2835
3113
|
*
|
|
@@ -2843,20 +3121,20 @@ const workflow = {
|
|
|
2843
3121
|
client,
|
|
2844
3122
|
tags,
|
|
2845
3123
|
workflowResource,
|
|
2846
|
-
|
|
3124
|
+
definition: definitionName,
|
|
2847
3125
|
version,
|
|
2848
3126
|
initialState,
|
|
2849
3127
|
ancestors,
|
|
2850
3128
|
effectsContext,
|
|
2851
3129
|
instanceId,
|
|
2852
3130
|
perspective
|
|
2853
|
-
} = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, definition = await loadDefinition(client,
|
|
2854
|
-
if (definition.stages.find((s) => s.
|
|
3131
|
+
} = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, definition = await loadDefinition(client, definitionName, version, tags), id = instanceId ?? `${canonicalTag(tags)}.wf-instance.${randomKey()}`;
|
|
3132
|
+
if (definition.stages.find((s) => s.name === definition.initialStage) === void 0)
|
|
2855
3133
|
throw new Error(
|
|
2856
|
-
`Initial stage "${definition.
|
|
3134
|
+
`Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
|
|
2857
3135
|
);
|
|
2858
3136
|
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedState = await resolveDeclaredState({
|
|
2859
|
-
|
|
3137
|
+
entryDefs: definition.state ?? [],
|
|
2860
3138
|
initialState: initialState ?? [],
|
|
2861
3139
|
ctx: {
|
|
2862
3140
|
client,
|
|
@@ -2872,14 +3150,14 @@ const workflow = {
|
|
|
2872
3150
|
now,
|
|
2873
3151
|
tags,
|
|
2874
3152
|
workflowResource,
|
|
2875
|
-
|
|
3153
|
+
definitionName: definition.name,
|
|
2876
3154
|
pinnedVersion: definition.version,
|
|
2877
3155
|
definition,
|
|
2878
3156
|
state: resolvedState,
|
|
2879
3157
|
effectsContext: effectsContextEntries,
|
|
2880
3158
|
ancestors: ancestors ?? [],
|
|
2881
3159
|
perspective: effectivePerspective,
|
|
2882
|
-
|
|
3160
|
+
initialStage: definition.initialStage,
|
|
2883
3161
|
actor
|
|
2884
3162
|
});
|
|
2885
3163
|
return await client.create(base), await primeInitialStage(client, id, actor, clientForGdr, clock), await cascade(client, id, actor, clientForGdr, clock), reload(client, id, tags);
|
|
@@ -2899,13 +3177,13 @@ const workflow = {
|
|
|
2899
3177
|
client,
|
|
2900
3178
|
tags,
|
|
2901
3179
|
instanceId,
|
|
2902
|
-
|
|
3180
|
+
task,
|
|
2903
3181
|
action,
|
|
2904
3182
|
params,
|
|
2905
3183
|
idempotent,
|
|
2906
3184
|
resourceClients
|
|
2907
3185
|
} = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tags);
|
|
2908
|
-
if (findOpenStageEntry(before)?.tasks.find((t) => t.
|
|
3186
|
+
if (findOpenStageEntry(before)?.tasks.find((t) => t.name === task) === void 0 && idempotent === !0)
|
|
2909
3187
|
return { instance: before, cascaded: 0, fired: !1 };
|
|
2910
3188
|
const evaluation = await evaluateInstance({
|
|
2911
3189
|
client,
|
|
@@ -2915,14 +3193,16 @@ const workflow = {
|
|
|
2915
3193
|
clock,
|
|
2916
3194
|
...resourceClients !== void 0 ? { resourceClients } : {}
|
|
2917
3195
|
});
|
|
2918
|
-
assertActionAllowed(evaluation,
|
|
3196
|
+
assertActionAllowed(evaluation, task, action);
|
|
2919
3197
|
const ranOps = await applyAction({
|
|
2920
3198
|
client,
|
|
2921
3199
|
instance: before,
|
|
2922
|
-
|
|
3200
|
+
task,
|
|
2923
3201
|
action,
|
|
2924
3202
|
actor,
|
|
3203
|
+
...access.grants !== void 0 ? { grants: access.grants } : {},
|
|
2925
3204
|
clock,
|
|
3205
|
+
clientForGdr,
|
|
2926
3206
|
...params !== void 0 ? { params } : {}
|
|
2927
3207
|
}), cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
|
|
2928
3208
|
return {
|
|
@@ -2950,7 +3230,7 @@ const workflow = {
|
|
|
2950
3230
|
...detail !== void 0 ? { detail } : {},
|
|
2951
3231
|
...error !== void 0 ? { error } : {},
|
|
2952
3232
|
...durationMs !== void 0 ? { durationMs } : {},
|
|
2953
|
-
options: { ...actor !== void 0 ? { actor } : {}, clock }
|
|
3233
|
+
options: { ...actor !== void 0 ? { actor } : {}, clock, clientForGdr }
|
|
2954
3234
|
});
|
|
2955
3235
|
const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
|
|
2956
3236
|
return {
|
|
@@ -2969,8 +3249,8 @@ const workflow = {
|
|
|
2969
3249
|
* affected instances and the engine re-evaluates.
|
|
2970
3250
|
*/
|
|
2971
3251
|
tick: async (args) => {
|
|
2972
|
-
const { client, tags, instanceId } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
|
|
2973
|
-
await
|
|
3252
|
+
const { client, tags, instanceId } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, current = await reload(client, instanceId, tags), guards = await verdictGuardsForInstance(client, current._id);
|
|
3253
|
+
await assertInstanceWriteAllowed({ instance: current, guards, identity: actor.id });
|
|
2974
3254
|
const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
|
|
2975
3255
|
return {
|
|
2976
3256
|
instance: await reload(client, instanceId, tags),
|
|
@@ -2979,19 +3259,19 @@ const workflow = {
|
|
|
2979
3259
|
};
|
|
2980
3260
|
},
|
|
2981
3261
|
/**
|
|
2982
|
-
* Admin override — force the instance into `
|
|
3262
|
+
* Admin override — force the instance into `targetStage` regardless
|
|
2983
3263
|
* of filters or declared transitions. ACL gating should be enforced
|
|
2984
3264
|
* upstream; this verb performs the mechanical move.
|
|
2985
3265
|
*/
|
|
2986
3266
|
setStage: async (args) => {
|
|
2987
|
-
const { client, tags, instanceId,
|
|
3267
|
+
const { client, tags, instanceId, targetStage, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
|
|
2988
3268
|
await reload(client, instanceId, tags);
|
|
2989
3269
|
const result = await setStage({
|
|
2990
3270
|
client,
|
|
2991
3271
|
instanceId,
|
|
2992
|
-
|
|
3272
|
+
targetStage,
|
|
2993
3273
|
...reason !== void 0 ? { reason } : {},
|
|
2994
|
-
options: { ...actor !== void 0 ? { actor } : {}, clock }
|
|
3274
|
+
options: { ...actor !== void 0 ? { actor } : {}, clock, clientForGdr }
|
|
2995
3275
|
}), cascaded = result.fired ? await cascade(client, instanceId, actor, clientForGdr, clock) : 0;
|
|
2996
3276
|
return {
|
|
2997
3277
|
instance: await reload(client, instanceId, tags),
|
|
@@ -2999,6 +3279,22 @@ const workflow = {
|
|
|
2999
3279
|
fired: result.fired
|
|
3000
3280
|
};
|
|
3001
3281
|
},
|
|
3282
|
+
/**
|
|
3283
|
+
* Admin override — hard-stop an in-flight instance where it stands.
|
|
3284
|
+
* No stage move, no transition effects, pending effects cancelled;
|
|
3285
|
+
* see {@link abortAndPropagate} for the abort + ancestor-propagation
|
|
3286
|
+
* contract (propagated, not cascaded — the instance is terminal).
|
|
3287
|
+
*/
|
|
3288
|
+
abortInstance: async (args) => {
|
|
3289
|
+
const { client, tags, instanceId, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
|
|
3290
|
+
await reload(client, instanceId, tags);
|
|
3291
|
+
const fired = await abortAndPropagate({ client, instanceId, reason, actor, clientForGdr, clock });
|
|
3292
|
+
return {
|
|
3293
|
+
instance: await reload(client, instanceId, tags),
|
|
3294
|
+
cascaded: 0,
|
|
3295
|
+
fired
|
|
3296
|
+
};
|
|
3297
|
+
},
|
|
3002
3298
|
/**
|
|
3003
3299
|
* Fetch a workflow instance by id, scoped to the engine's tags.
|
|
3004
3300
|
* Throws when the instance doesn't exist or isn't visible to this
|
|
@@ -3018,6 +3314,20 @@ const workflow = {
|
|
|
3018
3314
|
instance
|
|
3019
3315
|
});
|
|
3020
3316
|
},
|
|
3317
|
+
guardsForDefinition: async (args) => {
|
|
3318
|
+
const { client, tags, workflowResource, definition, resourceClients } = args;
|
|
3319
|
+
validateTags(tags);
|
|
3320
|
+
const definitions = await loadDefinitionVersions(client, definition, tags);
|
|
3321
|
+
if (definitions.length === 0)
|
|
3322
|
+
throw new Error(`No deployed definition for workflow ${definition}`);
|
|
3323
|
+
return guardsForDefinition({
|
|
3324
|
+
client,
|
|
3325
|
+
clientForGdr: buildClientForGdr(client, resourceClients),
|
|
3326
|
+
workflowResource,
|
|
3327
|
+
definition,
|
|
3328
|
+
definitions
|
|
3329
|
+
});
|
|
3330
|
+
},
|
|
3021
3331
|
/**
|
|
3022
3332
|
* Run a caller-supplied GROQ query with the engine's tags bound as
|
|
3023
3333
|
* `$engineTags`. This does NOT rewrite the query — arbitrary GROQ
|
|
@@ -3040,12 +3350,13 @@ const workflow = {
|
|
|
3040
3350
|
* Snapshot-aware GROQ — runs against the same in-memory view that
|
|
3041
3351
|
* filters see for a given instance.
|
|
3042
3352
|
*
|
|
3043
|
-
* Hydrates the instance's snapshot (instance +
|
|
3044
|
-
*
|
|
3045
|
-
*
|
|
3046
|
-
*
|
|
3047
|
-
* `$ancestors`, `$stage`, `$now
|
|
3048
|
-
*
|
|
3353
|
+
* Hydrates the instance's snapshot (instance + ancestors + every doc
|
|
3354
|
+
* declared by a `doc.ref` / `doc.refs` entry in scope), then
|
|
3355
|
+
* evaluates the supplied GROQ in groq-js against that dataset. The
|
|
3356
|
+
* rendered scope is auto-bound: `$self`, `$state`, `$parent`,
|
|
3357
|
+
* `$ancestors`, `$stage`, `$now`, `$effects`, `$tasks`,
|
|
3358
|
+
* `$allTasksDone`, `$anyTaskFailed` — ids in GDR URI form to match
|
|
3359
|
+
* the snapshot's keying.
|
|
3049
3360
|
*
|
|
3050
3361
|
* Use when an external consumer (a UI, a debug pane, a test) wants
|
|
3051
3362
|
* to ask "what does the engine see for this workflow right now?"
|
|
@@ -3054,7 +3365,7 @@ const workflow = {
|
|
|
3054
3365
|
queryInScope: async (args) => {
|
|
3055
3366
|
const { client, tags, instanceId, groq, params, resourceClients } = args, clock = args.clock ?? wallClock;
|
|
3056
3367
|
validateTags(tags);
|
|
3057
|
-
const instance = await reload(client, instanceId, tags), clientForGdr = buildClientForGdr(client, resourceClients), snapshot = await hydrateSnapshot({ client, clientForGdr, instance }), reserved = buildParams(instance, clock()), tree = groqJs.parse(groq, { params: { ...reserved, ...params } });
|
|
3368
|
+
const instance = await reload(client, instanceId, tags), clientForGdr = buildClientForGdr(client, resourceClients), snapshot = await hydrateSnapshot({ client, clientForGdr, instance }), reserved = buildParams({ instance, now: clock(), snapshot }), tree = groqJs.parse(groq, { params: { ...reserved, ...params } });
|
|
3058
3369
|
return await (await groqJs.evaluate(tree, {
|
|
3059
3370
|
dataset: snapshot.docs,
|
|
3060
3371
|
params: { ...reserved, ...params }
|
|
@@ -3070,7 +3381,7 @@ const workflow = {
|
|
|
3070
3381
|
* filters on claim presence; `names` restricts to specific effect
|
|
3071
3382
|
* names. Both filters compose (AND).
|
|
3072
3383
|
*/
|
|
3073
|
-
findPendingEffects: async (args) => (await reload(args.client, args.instanceId, args.tags)).pendingEffects.filter((e) => !(args.claimed === !0 && e.claim === void 0 || args.claimed === !1 && e.claim !== void 0 || args.names !== void 0 && !args.names.includes(e.
|
|
3384
|
+
findPendingEffects: async (args) => (await reload(args.client, args.instanceId, args.tags)).pendingEffects.filter((e) => !(args.claimed === !0 && e.claim === void 0 || args.claimed === !1 && e.claim !== void 0 || args.names !== void 0 && !args.names.includes(e.name))),
|
|
3074
3385
|
/**
|
|
3075
3386
|
* Project the instance from a given actor's perspective. Returns a
|
|
3076
3387
|
* `WorkflowEvaluation` with per-action verdicts (`allowed` + a
|
|
@@ -3080,22 +3391,46 @@ const workflow = {
|
|
|
3080
3391
|
* `fireAction` to gate writes via the same logic.
|
|
3081
3392
|
*/
|
|
3082
3393
|
evaluate: async (args) => evaluateInstance(args),
|
|
3394
|
+
/**
|
|
3395
|
+
* Diagnose why an instance is or isn't progressing. Projects the
|
|
3396
|
+
* instance (the same read as `evaluate`) and classifies it — terminal,
|
|
3397
|
+
* `progressing`, `waiting` (an action is available — healthy), or `stuck`
|
|
3398
|
+
* with a structured cause — returning that verdict as a
|
|
3399
|
+
* {@link DiagnoseResult} alongside the evaluation it came from, so a
|
|
3400
|
+
* consumer can render the supporting evidence without a second projection.
|
|
3401
|
+
* Pure read.
|
|
3402
|
+
*/
|
|
3403
|
+
diagnose: async (args) => {
|
|
3404
|
+
const evaluation = await evaluateInstance(args);
|
|
3405
|
+
return { evaluation, diagnosis: diagnoseInstance(diagnoseInputFromEvaluation(evaluation)) };
|
|
3406
|
+
},
|
|
3407
|
+
/**
|
|
3408
|
+
* List the actions an actor could fire on an instance's current stage,
|
|
3409
|
+
* each flagged `allowed` (with a structured `disabledReason` when not).
|
|
3410
|
+
* Projects the instance from the actor's perspective and flattens its
|
|
3411
|
+
* tasks' actions. Returns the evaluation alongside the actions so a
|
|
3412
|
+
* consumer can read the instance/stage context. Pure read.
|
|
3413
|
+
*/
|
|
3414
|
+
availableActions: async (args) => {
|
|
3415
|
+
const evaluation = await evaluateInstance(args);
|
|
3416
|
+
return { evaluation, actions: availableActions(evaluation.currentStage.tasks) };
|
|
3417
|
+
},
|
|
3083
3418
|
/**
|
|
3084
3419
|
* Materialised spawned children of a parent instance.
|
|
3085
3420
|
*
|
|
3086
|
-
* Walks `history` for `
|
|
3421
|
+
* Walks `history` for `spawned` events — the durable
|
|
3087
3422
|
* record. (The per-task `spawnedInstances` field on the active stage
|
|
3088
3423
|
* only exists while that stage is current; history survives.) Strips
|
|
3089
3424
|
* the GDR URI on each `instanceRef` to a bare `_id`, fetches the
|
|
3090
3425
|
* instances, drops any that aren't visible to this engine's tags, and
|
|
3091
3426
|
* returns them sorted by `startedAt` ascending.
|
|
3092
3427
|
*
|
|
3093
|
-
* Pass `
|
|
3428
|
+
* Pass `task` to restrict to a single spawning task on the parent.
|
|
3094
3429
|
*/
|
|
3095
3430
|
children: async (args) => {
|
|
3096
|
-
const { client, tags, instanceId,
|
|
3431
|
+
const { client, tags, instanceId, task } = args;
|
|
3097
3432
|
validateTags(tags);
|
|
3098
|
-
const parent = await reload(client, instanceId, tags), isSpawned = (h) => h._type === "
|
|
3433
|
+
const parent = await reload(client, instanceId, tags), isSpawned = (h) => h._type === "spawned", ids = parent.history.filter(isSpawned).filter((h) => task === void 0 || h.task === task).flatMap((h) => bareIdFromSpawnRef(h.instanceRef.id));
|
|
3099
3434
|
return ids.length === 0 ? [] : client.fetch(
|
|
3100
3435
|
`*[_id in $ids && ${tagScopeFilter()}] | order(startedAt asc)`,
|
|
3101
3436
|
{ ids, engineTags: tags }
|
|
@@ -3116,11 +3451,11 @@ async function verifyDeployedDefinitionsInternal(args) {
|
|
|
3116
3451
|
const { client, tags, effectHandlers, missingHandler, logger } = args;
|
|
3117
3452
|
validateTags(tags);
|
|
3118
3453
|
const log = logger("verifyDeployedDefinitions"), definitions = await client.fetch(
|
|
3119
|
-
`*[_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && ${tagScopeFilter()}] | order(
|
|
3454
|
+
`*[_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && ${tagScopeFilter()}] | order(name asc, version asc)`,
|
|
3120
3455
|
{ engineTags: tags }
|
|
3121
3456
|
), seen = [], missingByName = /* @__PURE__ */ new Map();
|
|
3122
3457
|
for (const def of definitions)
|
|
3123
|
-
seen.push({
|
|
3458
|
+
seen.push({ name: def.name, version: def.version, _id: def._id }), walkEffectNames(def, (name, location) => {
|
|
3124
3459
|
if (effectHandlers[name] !== void 0) return;
|
|
3125
3460
|
const key = `${name}@@${def._id}`;
|
|
3126
3461
|
let bucket = missingByName.get(key);
|
|
@@ -3151,17 +3486,16 @@ async function verifyDeployedDefinitionsInternal(args) {
|
|
|
3151
3486
|
}
|
|
3152
3487
|
function walkEffectNames(def, visit) {
|
|
3153
3488
|
const visitEffects = (effects, location) => {
|
|
3154
|
-
for (const e of effects ?? []) visit(e.
|
|
3489
|
+
for (const e of effects ?? []) visit(e.name, location);
|
|
3155
3490
|
};
|
|
3156
3491
|
for (const stage of def.stages ?? []) {
|
|
3157
|
-
visitEffects(stage.onEnter, `stage[${stage.id}].onEnter`), visitEffects(stage.onExit, `stage[${stage.id}].onExit`);
|
|
3158
3492
|
for (const t of stage.tasks ?? []) {
|
|
3159
|
-
visitEffects(t.effects, `stage[${stage.
|
|
3493
|
+
visitEffects(t.effects, `stage[${stage.name}].task[${t.name}].effects`);
|
|
3160
3494
|
for (const a of t.actions ?? [])
|
|
3161
|
-
visitEffects(a.effects, `stage[${stage.
|
|
3495
|
+
visitEffects(a.effects, `stage[${stage.name}].task[${t.name}].action[${a.name}].effects`);
|
|
3162
3496
|
}
|
|
3163
3497
|
for (const tr of stage.transitions ?? [])
|
|
3164
|
-
visitEffects(tr.effects, `stage[${stage.
|
|
3498
|
+
visitEffects(tr.effects, `stage[${stage.name}].transition[${tr.name}].effects`);
|
|
3165
3499
|
}
|
|
3166
3500
|
}
|
|
3167
3501
|
async function drainEffectsInternal(args) {
|
|
@@ -3185,7 +3519,7 @@ async function drainEffectsInternal(args) {
|
|
|
3185
3519
|
(e) => e.claim === void 0 && !skippedKeys.has(e._key)
|
|
3186
3520
|
);
|
|
3187
3521
|
if (candidate === void 0) break;
|
|
3188
|
-
const handler = effectHandlers[candidate.
|
|
3522
|
+
const handler = effectHandlers[candidate.name];
|
|
3189
3523
|
if (handler === void 0) {
|
|
3190
3524
|
await assertSkippableOrThrow(missingHandler, candidate, instanceId, log), skipped.push(candidate), skippedKeys.add(candidate._key);
|
|
3191
3525
|
continue;
|
|
@@ -3223,16 +3557,16 @@ async function reportEffectOutcome(args) {
|
|
|
3223
3557
|
async function assertSkippableOrThrow(missingHandler, candidate, instanceId, log) {
|
|
3224
3558
|
if (await applyMissingHandler(
|
|
3225
3559
|
missingHandler,
|
|
3226
|
-
{ phase: "drain", name: candidate.
|
|
3560
|
+
{ phase: "drain", name: candidate.name, effectKey: candidate._key, instanceId },
|
|
3227
3561
|
log
|
|
3228
3562
|
) === "fail")
|
|
3229
3563
|
throw new Error(
|
|
3230
|
-
`drainEffects: no handler registered for "${candidate.
|
|
3564
|
+
`drainEffects: no handler registered for "${candidate.name}" (effectKey=${candidate._key}, instanceId=${instanceId})`
|
|
3231
3565
|
);
|
|
3232
3566
|
}
|
|
3233
3567
|
async function claimPendingEffect(client, instance, effectKey, drainerActor) {
|
|
3234
3568
|
const claim = {
|
|
3235
|
-
_type: "
|
|
3569
|
+
_type: "pendingEffect.claim",
|
|
3236
3570
|
claimedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3237
3571
|
claimedBy: drainerActor
|
|
3238
3572
|
};
|
|
@@ -3249,7 +3583,7 @@ async function dispatchEffect(handler, candidate, ctx) {
|
|
|
3249
3583
|
client: ctx.client,
|
|
3250
3584
|
instanceId: ctx.instanceId,
|
|
3251
3585
|
effectKey: candidate._key,
|
|
3252
|
-
log: (message, extra) => ctx.logger(`effect.${candidate.
|
|
3586
|
+
log: (message, extra) => ctx.logger(`effect.${candidate.name}`).info(message, extra)
|
|
3253
3587
|
});
|
|
3254
3588
|
return result?.outputs !== void 0 ? { outputs: result.outputs } : {};
|
|
3255
3589
|
} catch (err) {
|
|
@@ -3269,7 +3603,7 @@ async function applyMissingHandler(policy, info, log) {
|
|
|
3269
3603
|
}
|
|
3270
3604
|
function createInstanceSession(args) {
|
|
3271
3605
|
const { client, tags, clock } = args, clientForGdr = buildClientForGdr(client, args.resourceClients);
|
|
3272
|
-
let instance = asHeldInstance(args.instance), overlay = /* @__PURE__ */ new Map(), committing = !1, buffered;
|
|
3606
|
+
let instance = asHeldInstance(args.instance), overlay = /* @__PURE__ */ new Map(), heldGuards = [], committing = !1, buffered;
|
|
3273
3607
|
const selfUri = () => gdrFromResource(instance.workflowResource, instance._id), applyUpdate = (docs) => {
|
|
3274
3608
|
const next = /* @__PURE__ */ new Map();
|
|
3275
3609
|
for (const ld of docs) {
|
|
@@ -3284,13 +3618,14 @@ function createInstanceSession(args) {
|
|
|
3284
3618
|
}, access = () => resolveAccess(client, {
|
|
3285
3619
|
...args.access !== void 0 ? { override: args.access } : {},
|
|
3286
3620
|
...args.grantsFromPath !== void 0 ? { grantsFromPath: args.grantsFromPath } : {}
|
|
3287
|
-
}), snapshotFrom = (held) => buildSnapshot({ docs: [{ doc: instance, resource: instance.workflowResource }, ...held.values()] }), evaluateWith = async (held) => {
|
|
3621
|
+
}), snapshotFrom = (held) => buildSnapshot({ docs: [{ doc: instance, resource: instance.workflowResource }, ...held.values()] }), evaluateWith = async (held, guards) => {
|
|
3288
3622
|
const { actor, grants } = await access();
|
|
3289
3623
|
return evaluateFromSnapshot({
|
|
3290
3624
|
instance,
|
|
3291
3625
|
definition: parseDefinitionSnapshot(instance),
|
|
3292
3626
|
actor,
|
|
3293
3627
|
snapshot: snapshotFrom(held),
|
|
3628
|
+
guards,
|
|
3294
3629
|
...clock !== void 0 ? { now: clock() } : {},
|
|
3295
3630
|
...grants !== void 0 ? { grants } : {}
|
|
3296
3631
|
});
|
|
@@ -3318,24 +3653,30 @@ function createInstanceSession(args) {
|
|
|
3318
3653
|
}
|
|
3319
3654
|
applyUpdate(docs);
|
|
3320
3655
|
},
|
|
3656
|
+
updateGuards(guards) {
|
|
3657
|
+
heldGuards = guards.map((guard) => structuredClone(guard));
|
|
3658
|
+
},
|
|
3321
3659
|
evaluate() {
|
|
3322
|
-
return evaluateWith(overlay);
|
|
3660
|
+
return evaluateWith(overlay, heldGuards);
|
|
3323
3661
|
},
|
|
3324
3662
|
tick() {
|
|
3325
3663
|
return commit(async (actor, held) => {
|
|
3664
|
+
await assertInstanceWriteAllowed({ instance, guards: heldGuards, identity: actor.id });
|
|
3326
3665
|
const cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
|
|
3327
3666
|
return instance = await reload(client, instance._id, tags), { instance, cascaded, fired: cascaded > 0 };
|
|
3328
3667
|
});
|
|
3329
3668
|
},
|
|
3330
|
-
fireAction({
|
|
3669
|
+
fireAction({ task, action, params }) {
|
|
3331
3670
|
return commit(async (actor, held) => {
|
|
3332
|
-
assertActionAllowed(await evaluateWith(held),
|
|
3333
|
-
const ranOps = await applyAction({
|
|
3671
|
+
assertActionAllowed(await evaluateWith(held, heldGuards), task, action);
|
|
3672
|
+
const { grants } = await access(), ranOps = await applyAction({
|
|
3334
3673
|
client,
|
|
3335
3674
|
instance,
|
|
3336
|
-
|
|
3675
|
+
task,
|
|
3337
3676
|
action,
|
|
3338
3677
|
actor,
|
|
3678
|
+
clientForGdr,
|
|
3679
|
+
...grants !== void 0 ? { grants } : {},
|
|
3339
3680
|
...clock !== void 0 ? { clock } : {},
|
|
3340
3681
|
...params !== void 0 ? { params } : {}
|
|
3341
3682
|
}), cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
|
|
@@ -3346,7 +3687,7 @@ function createInstanceSession(args) {
|
|
|
3346
3687
|
}
|
|
3347
3688
|
function asHeldInstance(doc) {
|
|
3348
3689
|
const candidate = doc;
|
|
3349
|
-
if (candidate._type !==
|
|
3690
|
+
if (candidate._type !== WORKFLOW_INSTANCE_TYPE || typeof candidate.currentStage != "string" || !Array.isArray(candidate.stages))
|
|
3350
3691
|
throw new Error(`update: self-doc ${doc._id} is not a valid workflow.instance`);
|
|
3351
3692
|
return doc;
|
|
3352
3693
|
}
|
|
@@ -3390,7 +3731,11 @@ function createEngine(args) {
|
|
|
3390
3731
|
completeEffect: (rest) => workflow.completeEffect(bind(rest)),
|
|
3391
3732
|
tick: (rest) => workflow.tick(bind(rest)),
|
|
3392
3733
|
evaluateInstance: (rest) => evaluateInstance(bind(rest)),
|
|
3734
|
+
diagnose: (rest) => workflow.diagnose(bind(rest)),
|
|
3735
|
+
availableActions: (rest) => workflow.availableActions(bind(rest)),
|
|
3393
3736
|
setStage: (rest) => workflow.setStage(bind(rest)),
|
|
3737
|
+
abortInstance: (rest) => workflow.abortInstance(bind(rest)),
|
|
3738
|
+
deleteDefinition: (rest) => workflow.deleteDefinition(bind(rest)),
|
|
3394
3739
|
getInstance: ({ instanceId }) => workflow.getInstance({ client, tags, workflowResource, instanceId }),
|
|
3395
3740
|
subscriptionDocumentsForInstance: ({ instanceId }) => workflow.getInstance({ client, tags, workflowResource, instanceId }).then(subscriptionDocumentsForInstance),
|
|
3396
3741
|
instance: (instanceDoc, opts) => createInstanceSession({
|
|
@@ -3409,12 +3754,19 @@ function createEngine(args) {
|
|
|
3409
3754
|
instanceId,
|
|
3410
3755
|
...resourceClients !== void 0 ? { resourceClients } : {}
|
|
3411
3756
|
}),
|
|
3412
|
-
|
|
3757
|
+
guardsForDefinition: ({ definition }) => workflow.guardsForDefinition({
|
|
3758
|
+
client,
|
|
3759
|
+
tags,
|
|
3760
|
+
workflowResource,
|
|
3761
|
+
definition,
|
|
3762
|
+
...resourceClients !== void 0 ? { resourceClients } : {}
|
|
3763
|
+
}),
|
|
3764
|
+
children: ({ instanceId, task }) => workflow.children({
|
|
3413
3765
|
client,
|
|
3414
3766
|
tags,
|
|
3415
3767
|
workflowResource,
|
|
3416
3768
|
instanceId,
|
|
3417
|
-
...
|
|
3769
|
+
...task !== void 0 ? { task } : {}
|
|
3418
3770
|
}),
|
|
3419
3771
|
query: ({ groq, params }) => workflow.query({
|
|
3420
3772
|
client,
|
|
@@ -3461,112 +3813,158 @@ function createEngine(args) {
|
|
|
3461
3813
|
})
|
|
3462
3814
|
};
|
|
3463
3815
|
}
|
|
3816
|
+
function docIdFor(def, target) {
|
|
3817
|
+
return definitionDocId(target.workflowResource, canonicalTag(target.tags), def.name, def.version);
|
|
3818
|
+
}
|
|
3819
|
+
function diffStatus(existing, expected) {
|
|
3820
|
+
return existing === void 0 ? "create" : isDefinitionUnchanged(existing, expected) ? "unchanged" : "update";
|
|
3821
|
+
}
|
|
3822
|
+
function diffEntry(def, existingRaw, target) {
|
|
3823
|
+
const docId = docIdFor(def, target), expected = {
|
|
3824
|
+
...def,
|
|
3825
|
+
_id: docId,
|
|
3826
|
+
_type: schema.WORKFLOW_DEFINITION_TYPE,
|
|
3827
|
+
tags: target.tags
|
|
3828
|
+
}, status = diffStatus(existingRaw, expected), existing = existingRaw ? stripSystemFields(existingRaw) : void 0;
|
|
3829
|
+
return {
|
|
3830
|
+
name: def.name,
|
|
3831
|
+
version: def.version,
|
|
3832
|
+
status,
|
|
3833
|
+
docId,
|
|
3834
|
+
expected,
|
|
3835
|
+
...existing !== void 0 ? { existing } : {}
|
|
3836
|
+
};
|
|
3837
|
+
}
|
|
3838
|
+
async function computeDiffEntries(client, defs, target) {
|
|
3839
|
+
const entries = [];
|
|
3840
|
+
for (const def of defs) {
|
|
3841
|
+
const existingRaw = await client.getDocument(docIdFor(def, target)) ?? void 0;
|
|
3842
|
+
entries.push(diffEntry(def, existingRaw, target));
|
|
3843
|
+
}
|
|
3844
|
+
return entries;
|
|
3845
|
+
}
|
|
3464
3846
|
const HISTORY_DISPLAY = {
|
|
3465
|
-
|
|
3847
|
+
stageEntered: {
|
|
3466
3848
|
title: "Stage entered",
|
|
3467
3849
|
description: "The instance crossed into a new stage."
|
|
3468
3850
|
},
|
|
3469
|
-
|
|
3851
|
+
stageExited: {
|
|
3470
3852
|
title: "Stage exited",
|
|
3471
3853
|
description: "The instance left a stage on the way to the next one."
|
|
3472
3854
|
},
|
|
3473
|
-
|
|
3855
|
+
taskActivated: {
|
|
3474
3856
|
title: "Task activated",
|
|
3475
3857
|
description: "A task flipped from `pending` to `active` and its onEnter effects queued."
|
|
3476
3858
|
},
|
|
3477
|
-
|
|
3859
|
+
taskStatusChanged: {
|
|
3478
3860
|
title: "Task status changed",
|
|
3479
3861
|
description: "An action or op flipped a task's status (active \u2192 done / skipped / failed)."
|
|
3480
3862
|
},
|
|
3481
|
-
|
|
3863
|
+
actionFired: {
|
|
3482
3864
|
title: "Action fired",
|
|
3483
3865
|
description: "An action was invoked against a task \u2014 ops ran, status may have flipped, effects queued."
|
|
3484
3866
|
},
|
|
3485
|
-
|
|
3867
|
+
transitionFired: {
|
|
3486
3868
|
title: "Transition fired",
|
|
3487
3869
|
description: "A transition committed and the instance moved to a new stage."
|
|
3488
3870
|
},
|
|
3489
|
-
|
|
3871
|
+
effectQueued: {
|
|
3490
3872
|
title: "Effect queued",
|
|
3491
3873
|
description: "A pending effect was added to the queue, waiting for a drainer to dispatch it."
|
|
3492
3874
|
},
|
|
3493
|
-
|
|
3875
|
+
effectCompleted: {
|
|
3494
3876
|
title: "Effect completed",
|
|
3495
3877
|
description: "A drainer ran an effect handler to completion (done or failed) and merged its outputs into effectsContext."
|
|
3496
3878
|
},
|
|
3497
|
-
|
|
3879
|
+
spawned: {
|
|
3498
3880
|
title: "Spawned child",
|
|
3499
|
-
description: "A
|
|
3881
|
+
description: "A task's `subworkflows` declaration spawned a child workflow instance."
|
|
3500
3882
|
},
|
|
3501
|
-
|
|
3883
|
+
aborted: {
|
|
3884
|
+
title: "Instance aborted",
|
|
3885
|
+
description: "An admin hard-stopped the instance \u2014 pending effects cancelled, stage guards lifted, abortedAt + completedAt stamped."
|
|
3886
|
+
},
|
|
3887
|
+
opApplied: {
|
|
3502
3888
|
title: "Op applied",
|
|
3503
3889
|
description: "An inline state-mutation op (state.set / state.append / status.set / etc.) ran during an action commit."
|
|
3504
3890
|
}
|
|
3505
3891
|
}, STATE_SLOT_DISPLAY = {
|
|
3506
|
-
"
|
|
3892
|
+
"doc.ref": {
|
|
3507
3893
|
title: "Document reference",
|
|
3508
3894
|
description: "Single GDR pointer at a document in this or another resource."
|
|
3509
3895
|
},
|
|
3510
|
-
"
|
|
3896
|
+
"doc.refs": {
|
|
3511
3897
|
title: "Document references",
|
|
3512
3898
|
description: "Ordered list of GDR pointers \u2014 multi-doc selection."
|
|
3513
3899
|
},
|
|
3514
|
-
"
|
|
3900
|
+
"release.ref": {
|
|
3901
|
+
title: "Content Release reference",
|
|
3902
|
+
description: "GDR pointer at a Content Release system doc, carrying the release name the engine derives the instance's read perspective from."
|
|
3903
|
+
},
|
|
3904
|
+
query: {
|
|
3515
3905
|
title: "Query result",
|
|
3516
|
-
description: "Snapshot of a GROQ projection captured at
|
|
3906
|
+
description: "Snapshot of a GROQ projection captured at entry-resolve time; resolvedAt is stamped on the value."
|
|
3517
3907
|
},
|
|
3518
|
-
"
|
|
3908
|
+
"value.string": {
|
|
3519
3909
|
title: "String value",
|
|
3520
|
-
description: "Free-form string
|
|
3910
|
+
description: "Free-form string entry."
|
|
3521
3911
|
},
|
|
3522
|
-
"
|
|
3912
|
+
"value.url": {
|
|
3523
3913
|
title: "URL value",
|
|
3524
|
-
description: "Validated URL
|
|
3914
|
+
description: "Validated URL entry."
|
|
3525
3915
|
},
|
|
3526
|
-
"
|
|
3916
|
+
"value.number": {
|
|
3527
3917
|
title: "Number value",
|
|
3528
|
-
description: "Numeric
|
|
3918
|
+
description: "Numeric entry."
|
|
3529
3919
|
},
|
|
3530
|
-
"
|
|
3920
|
+
"value.boolean": {
|
|
3531
3921
|
title: "Boolean value",
|
|
3532
|
-
description: "True/false
|
|
3922
|
+
description: "True/false entry."
|
|
3533
3923
|
},
|
|
3534
|
-
"
|
|
3924
|
+
"value.dateTime": {
|
|
3535
3925
|
title: "Date / time value",
|
|
3536
|
-
description: "ISO-timestamp
|
|
3926
|
+
description: "ISO-timestamp entry."
|
|
3537
3927
|
},
|
|
3538
|
-
"
|
|
3928
|
+
"value.actor": {
|
|
3929
|
+
title: "Actor value",
|
|
3930
|
+
description: "A typed (user|ai|system) actor identity."
|
|
3931
|
+
},
|
|
3932
|
+
checklist: {
|
|
3539
3933
|
title: "Checklist",
|
|
3540
3934
|
description: "Append-only list of checkable items; ops add/update/remove entries."
|
|
3541
3935
|
},
|
|
3542
|
-
|
|
3936
|
+
notes: {
|
|
3543
3937
|
title: "Notes log",
|
|
3544
3938
|
description: "Append-only structured-note log (signoffs, comments, audit rows)."
|
|
3545
3939
|
},
|
|
3546
|
-
|
|
3940
|
+
assignees: {
|
|
3547
3941
|
title: "Assignees",
|
|
3548
3942
|
description: "Ordered list of typed (user|role) assignees."
|
|
3549
3943
|
}
|
|
3550
3944
|
}, OP_DISPLAY = {
|
|
3551
|
-
"
|
|
3552
|
-
title: "Set
|
|
3553
|
-
description: "Overwrite a state
|
|
3945
|
+
"state.set": {
|
|
3946
|
+
title: "Set state entry",
|
|
3947
|
+
description: "Overwrite a state entry's value with a resolved Source."
|
|
3948
|
+
},
|
|
3949
|
+
"state.unset": {
|
|
3950
|
+
title: "Unset state entry",
|
|
3951
|
+
description: "Reset a state entry to its default (null for scalars, [] for array kinds)."
|
|
3554
3952
|
},
|
|
3555
|
-
"
|
|
3556
|
-
title: "Append to
|
|
3557
|
-
description: "Push a resolved item onto an array-
|
|
3953
|
+
"state.append": {
|
|
3954
|
+
title: "Append to state entry",
|
|
3955
|
+
description: "Push a resolved item onto an array-kind entry (notes, checklist, assignees, refs)."
|
|
3558
3956
|
},
|
|
3559
|
-
"
|
|
3957
|
+
"state.updateWhere": {
|
|
3560
3958
|
title: "Update matching rows",
|
|
3561
|
-
description: "Merge fields into rows of an array-
|
|
3959
|
+
description: "Merge fields into rows of an array-kind entry that match the predicate."
|
|
3562
3960
|
},
|
|
3563
|
-
"
|
|
3961
|
+
"state.removeWhere": {
|
|
3564
3962
|
title: "Remove matching rows",
|
|
3565
|
-
description: "Drop rows of an array-
|
|
3963
|
+
description: "Drop rows of an array-kind entry that match the predicate."
|
|
3566
3964
|
},
|
|
3567
|
-
"
|
|
3965
|
+
"status.set": {
|
|
3568
3966
|
title: "Set task status",
|
|
3569
|
-
description: "Flip a task's status explicitly (action
|
|
3967
|
+
description: "Flip a task's status explicitly (the action-level `status:` sugar desugars to this)."
|
|
3570
3968
|
}
|
|
3571
3969
|
}, EFFECTS_CONTEXT_DISPLAY = {
|
|
3572
3970
|
"effectsContext.string": {
|
|
@@ -3590,53 +3988,21 @@ const HISTORY_DISPLAY = {
|
|
|
3590
3988
|
description: "Stable JSON payload for effect handlers."
|
|
3591
3989
|
}
|
|
3592
3990
|
}, AUTHORING_DISPLAY = {
|
|
3593
|
-
|
|
3594
|
-
title: "Stage",
|
|
3595
|
-
description: "A bucket of tasks the workflow is currently in."
|
|
3596
|
-
},
|
|
3597
|
-
"workflow.task": {
|
|
3598
|
-
title: "Task",
|
|
3599
|
-
description: "A unit of work inside a stage; carries actions and may spawn children."
|
|
3600
|
-
},
|
|
3601
|
-
"workflow.action": {
|
|
3602
|
-
title: "Action",
|
|
3603
|
-
description: "An invocable transition on a task \u2014 runs ops, queues effects, may flip status."
|
|
3604
|
-
},
|
|
3605
|
-
"workflow.action.param": {
|
|
3606
|
-
title: "Action param",
|
|
3607
|
-
description: "Caller-supplied parameter on an action; validated before any commit."
|
|
3608
|
-
},
|
|
3609
|
-
"workflow.transition": {
|
|
3610
|
-
title: "Transition",
|
|
3611
|
-
description: "An edge between stages, gated by a filter."
|
|
3612
|
-
},
|
|
3613
|
-
"workflow.predicate": {
|
|
3614
|
-
title: "Predicate",
|
|
3615
|
-
description: "Named GROQ filter, reusable across transitions / tasks / actions."
|
|
3616
|
-
},
|
|
3617
|
-
"workflow.effect": {
|
|
3618
|
-
title: "Effect",
|
|
3619
|
-
description: "Externally-dispatched side effect; queued on commit, drained by a runtime, completed asynchronously."
|
|
3620
|
-
},
|
|
3621
|
-
"workflow.spawn": {
|
|
3622
|
-
title: "Spawn",
|
|
3623
|
-
description: "Declares a task that fans out one or more child workflow instances."
|
|
3624
|
-
},
|
|
3625
|
-
"workflow.pendingEffect": {
|
|
3991
|
+
pendingEffect: {
|
|
3626
3992
|
title: "Pending effect",
|
|
3627
3993
|
description: "An effect that has been queued but not yet dispatched."
|
|
3628
3994
|
},
|
|
3629
|
-
"
|
|
3995
|
+
"pendingEffect.claim": {
|
|
3630
3996
|
title: "Claim",
|
|
3631
3997
|
description: "A drainer's lock on a pending effect while it dispatches."
|
|
3632
3998
|
},
|
|
3633
|
-
|
|
3999
|
+
[schema.WORKFLOW_DEFINITION_TYPE]: {
|
|
3634
4000
|
title: "Workflow definition",
|
|
3635
|
-
description: "An immutable workflow blueprint
|
|
4001
|
+
description: "An immutable workflow blueprint, addressed by `name` + `version`."
|
|
3636
4002
|
},
|
|
3637
|
-
|
|
4003
|
+
[WORKFLOW_INSTANCE_TYPE]: {
|
|
3638
4004
|
title: "Workflow instance",
|
|
3639
|
-
description: "A running (or finished) workflow against
|
|
4005
|
+
description: "A running (or finished) workflow against its declared state."
|
|
3640
4006
|
}
|
|
3641
4007
|
}, DISPLAY = {
|
|
3642
4008
|
...HISTORY_DISPLAY,
|
|
@@ -3669,16 +4035,26 @@ exports.OP_DISPLAY = OP_DISPLAY;
|
|
|
3669
4035
|
exports.STATE_SLOT_DISPLAY = STATE_SLOT_DISPLAY;
|
|
3670
4036
|
exports.WORKFLOW_INSTANCE_TYPE = WORKFLOW_INSTANCE_TYPE;
|
|
3671
4037
|
exports.WorkflowStateDivergedError = WorkflowStateDivergedError;
|
|
4038
|
+
exports.abortReason = abortReason;
|
|
4039
|
+
exports.actionVerdict = actionVerdict;
|
|
4040
|
+
exports.assigneesOf = assigneesOf;
|
|
4041
|
+
exports.availableActions = availableActions;
|
|
3672
4042
|
exports.buildSnapshot = buildSnapshot;
|
|
3673
4043
|
exports.canonicalTag = canonicalTag;
|
|
3674
4044
|
exports.compileGuard = compileGuard;
|
|
4045
|
+
exports.computeDiffEntries = computeDiffEntries;
|
|
3675
4046
|
exports.contentReleaseName = contentReleaseName;
|
|
3676
4047
|
exports.createEngine = createEngine;
|
|
4048
|
+
exports.datasetResourceParts = datasetResourceParts;
|
|
3677
4049
|
exports.defaultLoggerFactory = defaultLoggerFactory;
|
|
3678
4050
|
exports.denyingGuards = denyingGuards;
|
|
3679
4051
|
exports.deployStageGuards = deployStageGuards;
|
|
4052
|
+
exports.diagnoseInputFromEvaluation = diagnoseInputFromEvaluation;
|
|
4053
|
+
exports.diagnoseInstance = diagnoseInstance;
|
|
4054
|
+
exports.diffEntry = diffEntry;
|
|
3680
4055
|
exports.displayDescription = displayDescription;
|
|
3681
4056
|
exports.displayTitle = displayTitle;
|
|
4057
|
+
exports.effectsContextMap = effectsContextMap;
|
|
3682
4058
|
exports.evaluateFromSnapshot = evaluateFromSnapshot;
|
|
3683
4059
|
exports.evaluateMutationGuard = evaluateMutationGuard;
|
|
3684
4060
|
exports.extractDocumentId = extractDocumentId;
|
|
@@ -3686,11 +4062,15 @@ exports.gdrFromResource = gdrFromResource;
|
|
|
3686
4062
|
exports.gdrRef = gdrRef;
|
|
3687
4063
|
exports.gdrUri = gdrUri;
|
|
3688
4064
|
exports.guardMatches = guardMatches;
|
|
4065
|
+
exports.guardsForDefinition = guardsForDefinition;
|
|
3689
4066
|
exports.guardsForInstance = guardsForInstance;
|
|
3690
4067
|
exports.guardsForResource = guardsForResource;
|
|
4068
|
+
exports.instanceGuardQuery = instanceGuardQuery;
|
|
3691
4069
|
exports.isDefinitionUnchanged = isDefinitionUnchanged;
|
|
3692
4070
|
exports.isGdr = isGdr;
|
|
4071
|
+
exports.isTerminalStage = isTerminalStage;
|
|
3693
4072
|
exports.lakeGuardId = lakeGuardId;
|
|
4073
|
+
exports.openStage = openStage;
|
|
3694
4074
|
exports.parseGdr = parseGdr;
|
|
3695
4075
|
exports.readsRaw = readsRaw;
|
|
3696
4076
|
exports.refCanvas = refCanvas;
|
|
@@ -3702,10 +4082,12 @@ exports.resourceFromParsed = resourceFromParsed;
|
|
|
3702
4082
|
exports.retractStageGuards = retractStageGuards;
|
|
3703
4083
|
exports.silentLogger = silentLogger;
|
|
3704
4084
|
exports.stripSystemFields = stripSystemFields;
|
|
4085
|
+
exports.subscriptionDocument = subscriptionDocument;
|
|
3705
4086
|
exports.subscriptionDocumentsForInstance = subscriptionDocumentsForInstance;
|
|
3706
4087
|
exports.tagScopeFilter = tagScopeFilter;
|
|
3707
4088
|
exports.validateDefinition = validateDefinition;
|
|
3708
4089
|
exports.validateTags = validateTags;
|
|
4090
|
+
exports.verdictGuardsForInstance = verdictGuardsForInstance;
|
|
3709
4091
|
exports.wallClock = wallClock;
|
|
3710
4092
|
exports.workflow = workflow;
|
|
3711
4093
|
//# sourceMappingURL=index.cjs.map
|