@sanity/workflow-engine 0.6.0 → 0.8.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 +33 -1
- package/dist/_chunks-cjs/schema.cjs.map +1 -1
- package/dist/_chunks-es/schema.js +33 -1
- package/dist/_chunks-es/schema.js.map +1 -1
- package/dist/define.cjs +19 -2
- package/dist/define.cjs.map +1 -1
- package/dist/define.d.cts +280 -0
- package/dist/define.d.ts +280 -0
- package/dist/define.js +19 -2
- package/dist/define.js.map +1 -1
- package/dist/index.cjs +467 -241
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +570 -24
- package/dist/index.d.ts +570 -24
- package/dist/index.js +468 -241
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -17,13 +17,8 @@ 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 part of path.split(".")) {
|
|
23
|
-
if (current == null || typeof current != "object") return;
|
|
24
|
-
current = current[part];
|
|
25
|
-
}
|
|
26
|
-
return current;
|
|
20
|
+
function findOpenStageEntry(host) {
|
|
21
|
+
return host.stages.find((s) => s.name === host.currentStage && s.exitedAt === void 0);
|
|
27
22
|
}
|
|
28
23
|
const KNOWN_SCHEMES = /* @__PURE__ */ new Set(["dataset", "canvas", "media-library", "dashboard"]);
|
|
29
24
|
function parseGdr(uri) {
|
|
@@ -95,6 +90,21 @@ function gdrFromResource(res, documentId) {
|
|
|
95
90
|
}
|
|
96
91
|
return gdrUri({ scheme: res.type, resourceId: res.id, documentId });
|
|
97
92
|
}
|
|
93
|
+
function resourceFromGdrUri(uri) {
|
|
94
|
+
let parsed;
|
|
95
|
+
try {
|
|
96
|
+
parsed = parseGdr(uri);
|
|
97
|
+
} catch {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (parsed.scheme === "dataset")
|
|
101
|
+
return parsed.projectId === void 0 || parsed.dataset === void 0 ? void 0 : { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` };
|
|
102
|
+
if (parsed.resourceId !== void 0)
|
|
103
|
+
return { type: parsed.scheme, id: parsed.resourceId };
|
|
104
|
+
}
|
|
105
|
+
function sameResource(a, b) {
|
|
106
|
+
return a.type === b.type && a.id === b.id;
|
|
107
|
+
}
|
|
98
108
|
function selfGdr(doc) {
|
|
99
109
|
return gdrFromResource(doc.workflowResource, doc._id);
|
|
100
110
|
}
|
|
@@ -112,12 +122,100 @@ function gdrRef(res, documentId, type) {
|
|
|
112
122
|
function isGdr(value) {
|
|
113
123
|
return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
|
|
114
124
|
}
|
|
125
|
+
function buildParams(args) {
|
|
126
|
+
const { instance, now, snapshot, extra } = args, currentTasks2 = findOpenStageEntry(instance)?.tasks ?? [];
|
|
127
|
+
return {
|
|
128
|
+
self: selfGdr(instance),
|
|
129
|
+
state: renderedState(instance.state ?? [], snapshot),
|
|
130
|
+
parent: instance.ancestors.at(-1)?.id ?? null,
|
|
131
|
+
ancestors: instance.ancestors.map((a) => a.id),
|
|
132
|
+
stage: instance.currentStage,
|
|
133
|
+
now,
|
|
134
|
+
/** `$effects` — parent context handoff by entry name, completed-effect
|
|
135
|
+
* outputs namespaced under the effect's name (`$effects['x.y'].out`). */
|
|
136
|
+
effects: effectsContextMap(instance),
|
|
137
|
+
/** `$tasks` — the current stage's task rows, statuses included. */
|
|
138
|
+
tasks: currentTasks2,
|
|
139
|
+
allTasksDone: currentTasks2.every((t) => t.status === "done" || t.status === "skipped"),
|
|
140
|
+
anyTaskFailed: currentTasks2.some((t) => t.status === "failed"),
|
|
141
|
+
...extra
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function renderedState(entries, snapshot) {
|
|
145
|
+
const out = {};
|
|
146
|
+
for (const entry of entries) out[entry.name] = renderedValue(entry, snapshot);
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
function renderedValue(entry, snapshot) {
|
|
150
|
+
return entry._type !== "doc.ref" || entry.value === null || snapshot === void 0 ? entry.value : snapshot.docs.find((d) => d._id === entry.value.id) ?? entry.value;
|
|
151
|
+
}
|
|
152
|
+
function scopedStateOverlay(instance, snapshot, taskName) {
|
|
153
|
+
const stageEntry = findOpenStageEntry(instance);
|
|
154
|
+
if (stageEntry === void 0) return {};
|
|
155
|
+
const stageState = renderedState(stageEntry.state ?? [], snapshot), task = taskName ? stageEntry.tasks.find((t) => t.name === taskName) : void 0;
|
|
156
|
+
return { ...stageState, ...renderedState(task?.state ?? [], snapshot) };
|
|
157
|
+
}
|
|
158
|
+
function assignedFor(instance, taskName, actor) {
|
|
159
|
+
if (actor === void 0) return !1;
|
|
160
|
+
const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.state?.find((s) => s._type === "assignees");
|
|
161
|
+
return entry === void 0 ? !1 : entry.value.some(
|
|
162
|
+
(a) => a.type === "user" ? a.id === actor.id : (actor.roles ?? []).includes(a.role)
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
function effectsContextMap(instance) {
|
|
166
|
+
const out = {};
|
|
167
|
+
for (const entry of instance.effectsContext)
|
|
168
|
+
out[entry.name] = entry._type === "effectsContext.json" ? parseJsonContextEntry(entry) : entry.value;
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
171
|
+
function parseJsonContextEntry(entry) {
|
|
172
|
+
try {
|
|
173
|
+
return JSON.parse(entry.value);
|
|
174
|
+
} catch (err) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`effectsContext entry "${entry.name}" holds unparseable JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
177
|
+
{ cause: err }
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function paramsForLake(params) {
|
|
182
|
+
return {
|
|
183
|
+
...params,
|
|
184
|
+
self: typeof params.self == "string" ? bareId(params.self) : params.self,
|
|
185
|
+
parent: typeof params.parent == "string" ? bareId(params.parent) : params.parent,
|
|
186
|
+
ancestors: Array.isArray(params.ancestors) ? params.ancestors.map((a) => typeof a == "string" ? bareId(a) : a) : params.ancestors,
|
|
187
|
+
state: stripStateForLake(params.state)
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function stripStateForLake(value) {
|
|
191
|
+
if (value == null) return value;
|
|
192
|
+
if (typeof value == "string") return bareId(value);
|
|
193
|
+
if (Array.isArray(value)) return value.map(stripStateForLake);
|
|
194
|
+
if (typeof value == "object") {
|
|
195
|
+
const out = {};
|
|
196
|
+
for (const [k, v2] of Object.entries(value))
|
|
197
|
+
out[k] = stripStateForLake(v2);
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
202
|
+
function bareId(id) {
|
|
203
|
+
return isGdrUri(id) ? extractDocumentId(id) : id;
|
|
204
|
+
}
|
|
205
|
+
function getPath(value, path) {
|
|
206
|
+
let current = value;
|
|
207
|
+
for (const part of path.split(".")) {
|
|
208
|
+
if (current == null || typeof current != "object") return;
|
|
209
|
+
current = current[part];
|
|
210
|
+
}
|
|
211
|
+
return current;
|
|
212
|
+
}
|
|
115
213
|
function resourceOf(p) {
|
|
116
214
|
return p.scheme === "dataset" ? { type: "dataset", id: `${p.projectId}.${p.dataset}` } : { type: p.scheme, id: p.resourceId ?? "" };
|
|
117
215
|
}
|
|
118
|
-
const STATE_READ = /^\$state\.(\w+)(?:\.(.+))?$/;
|
|
216
|
+
const STATE_READ = /^\$state\.(\w+)(?:\.(.+))?$/, EFFECTS_READ = /^\$effects\['([^']+)'\](?:\.(.+))?$/;
|
|
119
217
|
function isGuardReadExpr(expr) {
|
|
120
|
-
return expr === "$self" || expr === "$now" || STATE_READ.test(expr);
|
|
218
|
+
return expr === "$self" || expr === "$now" || STATE_READ.test(expr) || EFFECTS_READ.test(expr);
|
|
121
219
|
}
|
|
122
220
|
function resolveGuardRead(expr, ctx) {
|
|
123
221
|
if (expr === "$self") return selfGdr(ctx.instance);
|
|
@@ -127,8 +225,13 @@ function resolveGuardRead(expr, ctx) {
|
|
|
127
225
|
const value = (ctx.instance.state ?? []).find((s) => s.name === stateRead[1])?.value;
|
|
128
226
|
return stateRead[2] !== void 0 ? getPath(value, stateRead[2]) : value;
|
|
129
227
|
}
|
|
228
|
+
const effectsRead = EFFECTS_READ.exec(expr);
|
|
229
|
+
if (effectsRead !== null) {
|
|
230
|
+
const outputs = effectsContextMap(ctx.instance)[effectsRead[1]];
|
|
231
|
+
return effectsRead[2] !== void 0 ? getPath(outputs, effectsRead[2]) : outputs;
|
|
232
|
+
}
|
|
130
233
|
throw new Error(
|
|
131
|
-
`Guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now",
|
|
234
|
+
`Guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$state.<name>[.path]", or "$effects['<name>'][.path]" (guards store resolved values; the lake cannot see $state or $effects)`
|
|
132
235
|
);
|
|
133
236
|
}
|
|
134
237
|
function resolveIdRefTarget(expr, ctx) {
|
|
@@ -210,7 +313,7 @@ function createDefinitionValidator() {
|
|
|
210
313
|
entry.source.type === "query" && tryParse(entry.source.query, `${label}.query`);
|
|
211
314
|
}, checkGuardRead: (expr, where) => {
|
|
212
315
|
isGuardReadExpr(expr) || errors.push(
|
|
213
|
-
` \xB7 ${where}: guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now",
|
|
316
|
+
` \xB7 ${where}: guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$state.<name>[.path]", or "$effects['<name>'][.path]" (guards store resolved values; the lake cannot see $state or $effects)`
|
|
214
317
|
);
|
|
215
318
|
} };
|
|
216
319
|
}
|
|
@@ -239,6 +342,8 @@ function validateTask(v2, stageName, task) {
|
|
|
239
342
|
for (const entry of task.state ?? [])
|
|
240
343
|
v2.checkEntry(entry, `${where}.state "${entry.name}"`);
|
|
241
344
|
task.filter !== void 0 && v2.checkCondition(task.filter, `${where}.filter`), task.completeWhen !== void 0 && v2.checkCondition(task.completeWhen, `${where}.completeWhen`), task.failWhen !== void 0 && v2.checkCondition(task.failWhen, `${where}.failWhen`);
|
|
345
|
+
for (const [name, groq] of Object.entries(task.requirements ?? {}))
|
|
346
|
+
v2.checkCondition(groq, `${where}.requirements "${name}"`);
|
|
242
347
|
for (const [key, groq] of effectBindings(task.effects))
|
|
243
348
|
v2.tryParse(groq, `${where} effect binding "${key}"`);
|
|
244
349
|
validateTaskActions(v2, task), task.subworkflows !== void 0 && validateSubworkflows(v2, where, task.subworkflows);
|
|
@@ -387,99 +492,20 @@ async function assertInstanceWriteAllowed(args) {
|
|
|
387
492
|
guards: denied
|
|
388
493
|
});
|
|
389
494
|
}
|
|
390
|
-
function findOpenStageEntry(host) {
|
|
391
|
-
return host.stages.find((s) => s.name === host.currentStage && s.exitedAt === void 0);
|
|
392
|
-
}
|
|
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
|
-
};
|
|
411
|
-
}
|
|
412
|
-
function renderedState(entries, snapshot) {
|
|
413
|
-
const out = {};
|
|
414
|
-
for (const entry of entries) out[entry.name] = renderedValue(entry, snapshot);
|
|
415
|
-
return out;
|
|
416
|
-
}
|
|
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;
|
|
419
|
-
}
|
|
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) };
|
|
425
|
-
}
|
|
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;
|
|
437
|
-
return out;
|
|
438
|
-
}
|
|
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
|
-
}
|
|
448
|
-
}
|
|
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
|
-
};
|
|
457
|
-
}
|
|
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;
|
|
469
|
-
}
|
|
470
|
-
function bareId(id) {
|
|
471
|
-
return isGdrUri(id) ? extractDocumentId(id) : id;
|
|
472
|
-
}
|
|
473
495
|
function abortReason(instance) {
|
|
474
496
|
return instance.history.find(
|
|
475
497
|
(h) => h._type === "aborted"
|
|
476
498
|
)?.reason;
|
|
477
499
|
}
|
|
478
500
|
function diagnoseInputFromEvaluation(evaluation) {
|
|
501
|
+
const stage = openStage(evaluation.instance);
|
|
479
502
|
return {
|
|
480
503
|
instance: evaluation.instance,
|
|
481
504
|
tasks: evaluation.currentStage.tasks,
|
|
482
|
-
transitions: evaluation.currentStage.transitions
|
|
505
|
+
transitions: evaluation.currentStage.transitions,
|
|
506
|
+
assignees: Object.fromEntries(
|
|
507
|
+
evaluation.currentStage.tasks.map((t) => [t.task.name, assigneesOf(stage, t.task.name)])
|
|
508
|
+
)
|
|
483
509
|
};
|
|
484
510
|
}
|
|
485
511
|
function openStage(instance) {
|
|
@@ -504,18 +530,34 @@ function failedTaskCause(input) {
|
|
|
504
530
|
return failed ? { kind: "failed-task", task: failed.task.name } : void 0;
|
|
505
531
|
}
|
|
506
532
|
function waitingState(input) {
|
|
507
|
-
const active = input.tasks.find(
|
|
533
|
+
const active = input.tasks.find(
|
|
534
|
+
(t) => t.status === "active" && (t.task.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length === 0
|
|
535
|
+
);
|
|
508
536
|
if (active !== void 0)
|
|
509
537
|
return {
|
|
510
538
|
state: "waiting",
|
|
511
539
|
task: active.task.name,
|
|
512
|
-
assignees:
|
|
540
|
+
assignees: input.assignees[active.task.name] ?? [],
|
|
513
541
|
actions: (active.task.actions ?? []).map((a) => a.name)
|
|
514
542
|
};
|
|
515
543
|
}
|
|
544
|
+
function blockedState(input) {
|
|
545
|
+
const blocked = input.tasks.find(
|
|
546
|
+
(t) => t.status === "active" && (t.task.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length > 0
|
|
547
|
+
);
|
|
548
|
+
if (blocked !== void 0)
|
|
549
|
+
return {
|
|
550
|
+
state: "blocked",
|
|
551
|
+
task: blocked.task.name,
|
|
552
|
+
requirements: blocked.unmetRequirements ?? [],
|
|
553
|
+
assignees: input.assignees[blocked.task.name] ?? []
|
|
554
|
+
};
|
|
555
|
+
}
|
|
516
556
|
function noTransitionFiresCause(input) {
|
|
517
|
-
if (input.transitions.length
|
|
518
|
-
return
|
|
557
|
+
if (input.transitions.length === 0 || input.transitions.some((t) => t.filterSatisfied) || !input.tasks.every((t) => isResolved(t.status)))
|
|
558
|
+
return;
|
|
559
|
+
const undecidable = input.transitions.filter((t) => t.unevaluable);
|
|
560
|
+
return undecidable.length > 0 ? { kind: "transition-unevaluable", transitions: undecidable.map((t) => t.transition.name) } : { kind: "no-transition-fires" };
|
|
519
561
|
}
|
|
520
562
|
function diagnoseInstance(input) {
|
|
521
563
|
const { instance } = input;
|
|
@@ -526,7 +568,124 @@ function diagnoseInstance(input) {
|
|
|
526
568
|
if (instance.completedAt !== void 0)
|
|
527
569
|
return { state: "completed", at: instance.completedAt };
|
|
528
570
|
const cause = failedEffectCause(input) ?? failedTaskCause(input) ?? hungEffectCause(input) ?? noTransitionFiresCause(input);
|
|
529
|
-
return cause !== void 0 ? { state: "stuck", cause } : waitingState(input) ?? { state: "progressing" };
|
|
571
|
+
return cause !== void 0 ? { state: "stuck", cause } : waitingState(input) ?? blockedState(input) ?? { state: "progressing" };
|
|
572
|
+
}
|
|
573
|
+
const RUNNABLE_VERBS = /* @__PURE__ */ new Set(["set-stage", "abort"]);
|
|
574
|
+
function remediationsForCause(cause) {
|
|
575
|
+
switch (cause.kind) {
|
|
576
|
+
case "failed-effect":
|
|
577
|
+
return [
|
|
578
|
+
{
|
|
579
|
+
verb: "retry-effect",
|
|
580
|
+
rationale: "Re-run the failed effect once the upstream system is healthy."
|
|
581
|
+
},
|
|
582
|
+
{ verb: "abort", rationale: "Abort the instance if it can no longer recover." }
|
|
583
|
+
];
|
|
584
|
+
case "hung-effect":
|
|
585
|
+
return [
|
|
586
|
+
{
|
|
587
|
+
verb: "drain-effects",
|
|
588
|
+
rationale: "Re-run the effect drainer to re-pick the claimed effect."
|
|
589
|
+
},
|
|
590
|
+
{ verb: "abort", rationale: "Abort the instance if the effect never drains." }
|
|
591
|
+
];
|
|
592
|
+
case "failed-task":
|
|
593
|
+
return [
|
|
594
|
+
{ verb: "reset-task", rationale: "Reset or skip the failed task." },
|
|
595
|
+
{
|
|
596
|
+
verb: "set-stage",
|
|
597
|
+
rationale: "Move the instance past the failed task to the intended next stage."
|
|
598
|
+
}
|
|
599
|
+
];
|
|
600
|
+
case "no-transition-fires":
|
|
601
|
+
return [
|
|
602
|
+
{
|
|
603
|
+
verb: "set-stage",
|
|
604
|
+
rationale: "Move the instance to the intended next stage to force it forward."
|
|
605
|
+
}
|
|
606
|
+
];
|
|
607
|
+
case "transition-unevaluable":
|
|
608
|
+
return [];
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
function remediationsFor(diagnosis) {
|
|
612
|
+
return diagnosis.state !== "stuck" ? [] : remediationsForCause(diagnosis.cause).map((seed) => ({
|
|
613
|
+
...seed,
|
|
614
|
+
available: RUNNABLE_VERBS.has(seed.verb)
|
|
615
|
+
}));
|
|
616
|
+
}
|
|
617
|
+
class ActionParamsInvalidError extends Error {
|
|
618
|
+
action;
|
|
619
|
+
task;
|
|
620
|
+
issues;
|
|
621
|
+
constructor(args) {
|
|
622
|
+
const lines = args.issues.map((i) => ` - ${i.param}: ${i.reason}`).join(`
|
|
623
|
+
`);
|
|
624
|
+
super(`Action "${args.action}" on task "${args.task}" rejected: invalid params
|
|
625
|
+
${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.task = args.task, this.issues = args.issues;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
class RequiredStateNotProvidedError extends Error {
|
|
629
|
+
definition;
|
|
630
|
+
missing;
|
|
631
|
+
constructor(args) {
|
|
632
|
+
const lines = args.missing.map((m) => ` - ${m.name} (${m.type})`).join(`
|
|
633
|
+
`), where = args.definition !== void 0 ? ` for workflow "${args.definition}"` : "";
|
|
634
|
+
super(
|
|
635
|
+
`Required init state${where} was not provided:
|
|
636
|
+
${lines}
|
|
637
|
+
Provide each via \`initialState\` when starting standalone, or via the parent's \`subworkflows.with\` when spawned.`
|
|
638
|
+
), this.name = "RequiredStateNotProvidedError", args.definition !== void 0 && (this.definition = args.definition), this.missing = args.missing;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
class WorkflowStateDivergedError extends Error {
|
|
642
|
+
instanceId;
|
|
643
|
+
/** The guard-deploy failure that triggered the (attempted) rollback. */
|
|
644
|
+
guardError;
|
|
645
|
+
/** The rollback failure, when rollback was attempted and itself failed. */
|
|
646
|
+
rollbackError;
|
|
647
|
+
constructor(args) {
|
|
648
|
+
super(
|
|
649
|
+
`Workflow "${args.instanceId}" state committed but its guards could not be reconciled: ${args.reason}.`,
|
|
650
|
+
{ cause: args.guardError }
|
|
651
|
+
), this.name = "WorkflowStateDivergedError", this.instanceId = args.instanceId, this.guardError = args.guardError, args.rollbackError !== void 0 && (this.rollbackError = args.rollbackError);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
class PartialGuardDeployError extends Error {
|
|
655
|
+
stageName;
|
|
656
|
+
deployed;
|
|
657
|
+
constructor(args) {
|
|
658
|
+
super(
|
|
659
|
+
`Partial guard deploy on stage "${args.stageName}": ${args.deployed} guard(s) deployed before a later one failed.`,
|
|
660
|
+
{ cause: args.cause }
|
|
661
|
+
), this.name = "PartialGuardDeployError", this.stageName = args.stageName, this.deployed = args.deployed;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
const CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS = 3;
|
|
665
|
+
class ConcurrentFireActionError extends Error {
|
|
666
|
+
instanceId;
|
|
667
|
+
task;
|
|
668
|
+
action;
|
|
669
|
+
attempts;
|
|
670
|
+
constructor(args) {
|
|
671
|
+
super(
|
|
672
|
+
`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.`
|
|
673
|
+
), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.task = args.task, this.action = args.action, this.attempts = args.attempts;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function isRevisionConflict(error) {
|
|
677
|
+
if (typeof error != "object" || error === null) return !1;
|
|
678
|
+
const { statusCode, message } = error;
|
|
679
|
+
return statusCode === 409 ? !0 : typeof message == "string" && message.includes("ifRevisionId check failed");
|
|
680
|
+
}
|
|
681
|
+
class CascadeLimitError extends Error {
|
|
682
|
+
instanceId;
|
|
683
|
+
limit;
|
|
684
|
+
constructor(args) {
|
|
685
|
+
super(
|
|
686
|
+
`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.`
|
|
687
|
+
), this.name = "CascadeLimitError", this.instanceId = args.instanceId, this.limit = args.limit;
|
|
688
|
+
}
|
|
530
689
|
}
|
|
531
690
|
function buildSnapshot(args) {
|
|
532
691
|
const docs = [], knownIds = /* @__PURE__ */ new Set();
|
|
@@ -573,6 +732,7 @@ function collectWatchRefs(instance) {
|
|
|
573
732
|
function readsRaw(ref) {
|
|
574
733
|
return ref.type === WORKFLOW_INSTANCE_TYPE || ref.type === "system.release";
|
|
575
734
|
}
|
|
735
|
+
const DEFAULT_CONTENT_PERSPECTIVE = "drafts";
|
|
576
736
|
function contentReleaseName(args) {
|
|
577
737
|
const { ref, perspective } = args;
|
|
578
738
|
if (!readsRaw(ref) && Array.isArray(perspective))
|
|
@@ -598,6 +758,11 @@ function entryReleaseRefs(state) {
|
|
|
598
758
|
(entry) => entry._type === "release.ref" && isGdr(entry.value) ? [entry.value] : []
|
|
599
759
|
);
|
|
600
760
|
}
|
|
761
|
+
function entrySubjectRefs(state) {
|
|
762
|
+
return stateEntries(state).flatMap(
|
|
763
|
+
(entry) => (entry._type === "doc.ref" || entry._type === "release.ref") && isGdr(entry.value) ? [entry.value] : []
|
|
764
|
+
);
|
|
765
|
+
}
|
|
601
766
|
function stateEntries(state) {
|
|
602
767
|
return Array.isArray(state) ? state.filter(
|
|
603
768
|
(entry) => !!entry && typeof entry == "object"
|
|
@@ -622,7 +787,10 @@ async function hydrateSnapshot(args) {
|
|
|
622
787
|
};
|
|
623
788
|
loaded.push({ doc: instance, resource: instance.workflowResource }), visited.add(selfGdr(instance));
|
|
624
789
|
for (const ref of collectWatchRefs(instance))
|
|
625
|
-
await loadInto(
|
|
790
|
+
await loadInto(
|
|
791
|
+
ref.id,
|
|
792
|
+
readsRaw(ref) ? "raw" : instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
|
|
793
|
+
);
|
|
626
794
|
return buildSnapshot({ docs: loaded });
|
|
627
795
|
}
|
|
628
796
|
async function loadByGdr(defaultClient, clientForGdr, defaultResource, uri, perspective) {
|
|
@@ -637,7 +805,7 @@ async function loadByGdr(defaultClient, clientForGdr, defaultResource, uri, pers
|
|
|
637
805
|
return doc ? { doc, resource: resourceFromParsed(parsed) } : null;
|
|
638
806
|
}
|
|
639
807
|
async function readDoc(client, id, perspective) {
|
|
640
|
-
return perspective ===
|
|
808
|
+
return perspective === "raw" ? await client.getDocument(id) ?? null : await client.fetch("*[_id == $id][0]", { id }, { perspective }) ?? null;
|
|
641
809
|
}
|
|
642
810
|
function resourceFromParsed(parsed) {
|
|
643
811
|
return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
|
|
@@ -738,7 +906,7 @@ async function queryGuardsAcross(clients, query, params) {
|
|
|
738
906
|
function dedupById(guards) {
|
|
739
907
|
return [...new Map(guards.map((g) => [g._id, g])).values()].sort((a, b) => a._id.localeCompare(b._id));
|
|
740
908
|
}
|
|
741
|
-
const GUARD_OWNER = "robot:workflow-engine", GUARD_LIFTED_PREDICATE = "true";
|
|
909
|
+
const SYNC_COMMIT = { visibility: "sync" }, GUARD_OWNER = "robot:workflow-engine", GUARD_LIFTED_PREDICATE = "true";
|
|
742
910
|
function resolveGuard(guard, instance, stageName, now) {
|
|
743
911
|
const ctx = { instance, now }, targets = resolveIdRefTargets(guard.match.idRefs, ctx);
|
|
744
912
|
if (targets === null) return null;
|
|
@@ -765,11 +933,11 @@ function resolveGuard(guard, instance, stageName, now) {
|
|
|
765
933
|
}
|
|
766
934
|
async function upsertGuard(client, doc) {
|
|
767
935
|
if (!await client.getDocument(doc._id)) {
|
|
768
|
-
await client.create(doc);
|
|
936
|
+
await client.create(doc, SYNC_COMMIT);
|
|
769
937
|
return;
|
|
770
938
|
}
|
|
771
939
|
const { _id, _type, _rev, _createdAt, _updatedAt, ...body } = doc;
|
|
772
|
-
await client.patch(doc._id).set(body).commit();
|
|
940
|
+
await client.patch(doc._id).set(body).commit(SYNC_COMMIT);
|
|
773
941
|
}
|
|
774
942
|
function resolvedStageGuards(args) {
|
|
775
943
|
const stage = args.definition.stages.find((s) => s.name === args.stageName), out = [];
|
|
@@ -784,15 +952,22 @@ async function committedInstance(args) {
|
|
|
784
952
|
}
|
|
785
953
|
async function deployStageGuards(args) {
|
|
786
954
|
const live = await committedInstance(args);
|
|
787
|
-
if (
|
|
788
|
-
|
|
955
|
+
if (live === void 0 || live.currentStage !== args.stageName || live.abortedAt !== void 0) return;
|
|
956
|
+
let deployed = 0;
|
|
957
|
+
for (const { client, doc } of resolvedStageGuards(args)) {
|
|
958
|
+
try {
|
|
789
959
|
await upsertGuard(client, doc);
|
|
960
|
+
} catch (cause) {
|
|
961
|
+
throw deployed > 0 ? new PartialGuardDeployError({ stageName: args.stageName, deployed, cause }) : cause;
|
|
962
|
+
}
|
|
963
|
+
deployed += 1;
|
|
964
|
+
}
|
|
790
965
|
}
|
|
791
966
|
async function retractStageGuards(args) {
|
|
792
967
|
const live = await committedInstance(args);
|
|
793
968
|
if (live !== void 0 && !(live.currentStage === args.stageName && live.abortedAt === void 0))
|
|
794
969
|
for (const { client, doc } of resolvedStageGuards(args))
|
|
795
|
-
await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit();
|
|
970
|
+
await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit(SYNC_COMMIT);
|
|
796
971
|
}
|
|
797
972
|
async function deleteOrphanedDefinitionGuards(args) {
|
|
798
973
|
const { client, clientForGdr, workflowResource, definition, definitions, tag } = args, perClient = await guardsForDefinitionByClient({
|
|
@@ -816,16 +991,32 @@ function randomKey(length = 12) {
|
|
|
816
991
|
const bytes = new Uint8Array(length);
|
|
817
992
|
return globalThis.crypto.getRandomValues(bytes), [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, length);
|
|
818
993
|
}
|
|
819
|
-
|
|
994
|
+
function isUnevaluable(result) {
|
|
995
|
+
return result == null;
|
|
996
|
+
}
|
|
997
|
+
async function evaluateConditionOutcome(args) {
|
|
820
998
|
const { condition, snapshot, params } = args;
|
|
821
|
-
|
|
999
|
+
if (condition === void 0) return "satisfied";
|
|
1000
|
+
const result = await runGroq(condition, params, snapshot);
|
|
1001
|
+
return isUnevaluable(result) ? "unevaluable" : result ? "satisfied" : "unsatisfied";
|
|
1002
|
+
}
|
|
1003
|
+
async function evaluateCondition(args) {
|
|
1004
|
+
return await evaluateConditionOutcome(args) === "satisfied";
|
|
822
1005
|
}
|
|
823
1006
|
async function evaluatePredicates(args) {
|
|
824
1007
|
const out = {};
|
|
825
|
-
for (const [name, groq] of Object.entries(args.predicates ?? {}))
|
|
826
|
-
|
|
1008
|
+
for (const [name, groq] of Object.entries(args.predicates ?? {})) {
|
|
1009
|
+
const result = await runGroq(groq, args.params, args.snapshot);
|
|
1010
|
+
out[name] = isUnevaluable(result) ? null : !!result;
|
|
1011
|
+
}
|
|
827
1012
|
return out;
|
|
828
1013
|
}
|
|
1014
|
+
async function evaluateRequirements(args) {
|
|
1015
|
+
const unmet = [];
|
|
1016
|
+
for (const [name, condition] of Object.entries(args.requirements ?? {}))
|
|
1017
|
+
await evaluateCondition({ condition, snapshot: args.snapshot, params: args.params }) || unmet.push(name);
|
|
1018
|
+
return unmet;
|
|
1019
|
+
}
|
|
829
1020
|
async function runGroq(groq, params, snapshot) {
|
|
830
1021
|
const tree = groqJs.parse(groq, { params });
|
|
831
1022
|
return (await groqJs.evaluate(tree, { dataset: snapshot.docs, params })).get();
|
|
@@ -959,6 +1150,7 @@ function derivePerspectiveFromState(entries) {
|
|
|
959
1150
|
async function resolveDeclaredState(args) {
|
|
960
1151
|
const { entryDefs, initialState, ctx, randomKey: randomKey2 } = args;
|
|
961
1152
|
if (entryDefs === void 0 || entryDefs.length === 0) return [];
|
|
1153
|
+
assertRequiredInitProvided(entryDefs, initialState, ctx.definitionName);
|
|
962
1154
|
const out = [];
|
|
963
1155
|
for (const entry of entryDefs) {
|
|
964
1156
|
const scopedCtx = { ...ctx, resolvedState: out };
|
|
@@ -966,6 +1158,17 @@ async function resolveDeclaredState(args) {
|
|
|
966
1158
|
}
|
|
967
1159
|
return out;
|
|
968
1160
|
}
|
|
1161
|
+
function assertRequiredInitProvided(entryDefs, initialState, definitionName) {
|
|
1162
|
+
const missing = entryDefs.filter((entry) => entry.required === !0 && entry.source.type === "init").filter((entry) => {
|
|
1163
|
+
const match = initialState.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1164
|
+
return match === void 0 || match.value === void 0 || match.value === null;
|
|
1165
|
+
}).map((entry) => ({ name: entry.name, type: entry.type }));
|
|
1166
|
+
if (missing.length !== 0)
|
|
1167
|
+
throw new RequiredStateNotProvidedError({
|
|
1168
|
+
...definitionName !== void 0 ? { definition: definitionName } : {},
|
|
1169
|
+
missing
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
969
1172
|
const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set([
|
|
970
1173
|
"doc.refs",
|
|
971
1174
|
"checklist",
|
|
@@ -993,7 +1196,7 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
|
993
1196
|
tag: ctx.tag
|
|
994
1197
|
});
|
|
995
1198
|
try {
|
|
996
|
-
const fetchOptions =
|
|
1199
|
+
const fetchOptions = { perspective: ctx.perspective ?? DEFAULT_CONTENT_PERSPECTIVE }, result = await client.fetch(source.query, params, fetchOptions);
|
|
997
1200
|
return normalizeQueryResult(entry.type, result, ctx.workflowResource) ?? defaultValue;
|
|
998
1201
|
} catch (err) {
|
|
999
1202
|
throw new Error(
|
|
@@ -1123,13 +1326,16 @@ async function ctxConditionParams(ctx, opts) {
|
|
|
1123
1326
|
params
|
|
1124
1327
|
}), ...params };
|
|
1125
1328
|
}
|
|
1126
|
-
async function
|
|
1127
|
-
return condition === void 0 ?
|
|
1329
|
+
async function ctxEvaluateConditionOutcome(ctx, condition, opts) {
|
|
1330
|
+
return condition === void 0 ? "satisfied" : evaluateConditionOutcome({
|
|
1128
1331
|
condition,
|
|
1129
1332
|
snapshot: ctx.snapshot,
|
|
1130
1333
|
params: await ctxConditionParams(ctx, opts)
|
|
1131
1334
|
});
|
|
1132
1335
|
}
|
|
1336
|
+
async function ctxEvaluateCondition(ctx, condition, opts) {
|
|
1337
|
+
return await ctxEvaluateConditionOutcome(ctx, condition, opts) === "satisfied";
|
|
1338
|
+
}
|
|
1133
1339
|
async function buildEngineContext(args) {
|
|
1134
1340
|
const { client, clientForGdr, instance, definition } = args, clock = args.clock ?? wallClock;
|
|
1135
1341
|
return {
|
|
@@ -1187,56 +1393,6 @@ async function resolveTaskStateEntries(args) {
|
|
|
1187
1393
|
randomKey
|
|
1188
1394
|
});
|
|
1189
1395
|
}
|
|
1190
|
-
class ActionParamsInvalidError extends Error {
|
|
1191
|
-
action;
|
|
1192
|
-
task;
|
|
1193
|
-
issues;
|
|
1194
|
-
constructor(args) {
|
|
1195
|
-
const lines = args.issues.map((i) => ` - ${i.param}: ${i.reason}`).join(`
|
|
1196
|
-
`);
|
|
1197
|
-
super(`Action "${args.action}" on task "${args.task}" rejected: invalid params
|
|
1198
|
-
${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.task = args.task, this.issues = args.issues;
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
class WorkflowStateDivergedError extends Error {
|
|
1202
|
-
instanceId;
|
|
1203
|
-
/** The guard-deploy failure that triggered the (attempted) rollback. */
|
|
1204
|
-
guardError;
|
|
1205
|
-
/** The rollback failure, when rollback was attempted and itself failed. */
|
|
1206
|
-
rollbackError;
|
|
1207
|
-
constructor(args) {
|
|
1208
|
-
super(
|
|
1209
|
-
`Workflow "${args.instanceId}" state committed but its guards could not be reconciled: ${args.reason}.`,
|
|
1210
|
-
{ cause: args.guardError }
|
|
1211
|
-
), this.name = "WorkflowStateDivergedError", this.instanceId = args.instanceId, this.guardError = args.guardError, args.rollbackError !== void 0 && (this.rollbackError = args.rollbackError);
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
const CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS = 3;
|
|
1215
|
-
class ConcurrentFireActionError extends Error {
|
|
1216
|
-
instanceId;
|
|
1217
|
-
task;
|
|
1218
|
-
action;
|
|
1219
|
-
attempts;
|
|
1220
|
-
constructor(args) {
|
|
1221
|
-
super(
|
|
1222
|
-
`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.`
|
|
1223
|
-
), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.task = args.task, this.action = args.action, this.attempts = args.attempts;
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
function isRevisionConflict(error) {
|
|
1227
|
-
if (typeof error != "object" || error === null) return !1;
|
|
1228
|
-
const { statusCode, message } = error;
|
|
1229
|
-
return statusCode === 409 ? !0 : typeof message == "string" && message.includes("ifRevisionId check failed");
|
|
1230
|
-
}
|
|
1231
|
-
class CascadeLimitError extends Error {
|
|
1232
|
-
instanceId;
|
|
1233
|
-
limit;
|
|
1234
|
-
constructor(args) {
|
|
1235
|
-
super(
|
|
1236
|
-
`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.`
|
|
1237
|
-
), this.name = "CascadeLimitError", this.instanceId = args.instanceId, this.limit = args.limit;
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
1396
|
async function deployOrRollback(args) {
|
|
1241
1397
|
try {
|
|
1242
1398
|
await args.deploy();
|
|
@@ -1249,7 +1405,7 @@ async function deployOrRollback(args) {
|
|
|
1249
1405
|
});
|
|
1250
1406
|
try {
|
|
1251
1407
|
let patch = args.client.patch(args.instanceId).set(args.restore);
|
|
1252
|
-
args.unset && args.unset.length > 0 && (patch = patch.unset(args.unset)), args.committedRev !== void 0 && (patch = patch.ifRevisionId(args.committedRev)), await patch.commit();
|
|
1408
|
+
args.unset && args.unset.length > 0 && (patch = patch.unset(args.unset)), args.committedRev !== void 0 && (patch = patch.ifRevisionId(args.committedRev)), await patch.commit(SYNC_COMMIT);
|
|
1253
1409
|
} catch (rollbackError) {
|
|
1254
1410
|
throw new WorkflowStateDivergedError({
|
|
1255
1411
|
instanceId: args.instanceId,
|
|
@@ -1258,7 +1414,11 @@ async function deployOrRollback(args) {
|
|
|
1258
1414
|
reason: "rollback of the committed move failed"
|
|
1259
1415
|
});
|
|
1260
1416
|
}
|
|
1261
|
-
throw guardError
|
|
1417
|
+
throw guardError instanceof PartialGuardDeployError ? new WorkflowStateDivergedError({
|
|
1418
|
+
instanceId: args.instanceId,
|
|
1419
|
+
guardError,
|
|
1420
|
+
reason: "a multi-guard deploy partially applied; the rolled-back move left orphaned guard locks"
|
|
1421
|
+
}) : guardError;
|
|
1262
1422
|
}
|
|
1263
1423
|
}
|
|
1264
1424
|
function applyTaskStatusChange(args) {
|
|
@@ -1347,7 +1507,8 @@ function materializeInstance(base, mutation) {
|
|
|
1347
1507
|
...base,
|
|
1348
1508
|
currentStage: mutation.currentStage,
|
|
1349
1509
|
state: mutation.state,
|
|
1350
|
-
stages: mutation.stages
|
|
1510
|
+
stages: mutation.stages,
|
|
1511
|
+
effectsContext: mutation.effectsContext
|
|
1351
1512
|
};
|
|
1352
1513
|
}
|
|
1353
1514
|
async function persist(ctx, mutation) {
|
|
@@ -1356,13 +1517,27 @@ async function persist(ctx, mutation) {
|
|
|
1356
1517
|
lastChangedAt: ctx.now
|
|
1357
1518
|
}, pendingCreates = mutation.pendingCreates;
|
|
1358
1519
|
if (pendingCreates.length === 0)
|
|
1359
|
-
return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit();
|
|
1520
|
+
return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit(SYNC_COMMIT);
|
|
1360
1521
|
const tx = ctx.client.transaction();
|
|
1361
1522
|
for (const body of pendingCreates) tx.create(body);
|
|
1362
1523
|
tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)), await tx.commit(), mutation.pendingCreates = [];
|
|
1363
1524
|
const actorForPriming = void 0;
|
|
1364
1525
|
for (const body of pendingCreates)
|
|
1365
|
-
|
|
1526
|
+
try {
|
|
1527
|
+
await primeInitialStage(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock), await cascadeAutoTransitions(
|
|
1528
|
+
ctx.client,
|
|
1529
|
+
body._id,
|
|
1530
|
+
actorForPriming,
|
|
1531
|
+
ctx.clientForGdr,
|
|
1532
|
+
ctx.clock
|
|
1533
|
+
), await propagateToAncestors(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock);
|
|
1534
|
+
} catch (cause) {
|
|
1535
|
+
throw cause instanceof WorkflowStateDivergedError ? cause : new WorkflowStateDivergedError({
|
|
1536
|
+
instanceId: ctx.instance._id,
|
|
1537
|
+
guardError: cause,
|
|
1538
|
+
reason: `spawned child "${body._id}" failed to settle after the spawn transaction committed`
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1366
1541
|
const reloaded = await ctx.client.getDocument(ctx.instance._id);
|
|
1367
1542
|
if (!reloaded)
|
|
1368
1543
|
throw new Error(`Instance ${ctx.instance._id} disappeared after transaction commit`);
|
|
@@ -1509,9 +1684,16 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1509
1684
|
const parentScope = await ctxConditionParams(ctx, {
|
|
1510
1685
|
taskName: task.name,
|
|
1511
1686
|
...actor ? { actor } : {}
|
|
1512
|
-
}), rows = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
|
|
1687
|
+
}), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
|
|
1513
1688
|
for (const row of rows) {
|
|
1514
|
-
const initialState = await projectRowState(
|
|
1689
|
+
const initialState = await projectRowState(
|
|
1690
|
+
ctx,
|
|
1691
|
+
sub,
|
|
1692
|
+
definition,
|
|
1693
|
+
parentScope,
|
|
1694
|
+
row,
|
|
1695
|
+
discoveryResource
|
|
1696
|
+
), { ref, body } = await prepareChildInstance({
|
|
1515
1697
|
client: ctx.client,
|
|
1516
1698
|
parent: ctx.instance,
|
|
1517
1699
|
definition,
|
|
@@ -1526,28 +1708,25 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1526
1708
|
}
|
|
1527
1709
|
async function resolveSpawnRows(ctx, sub) {
|
|
1528
1710
|
const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
|
|
1529
|
-
let value;
|
|
1711
|
+
let value, discoveryResource;
|
|
1530
1712
|
if (groq.includes("*")) {
|
|
1531
|
-
const routingUri =
|
|
1532
|
-
value = await discoverItems({
|
|
1713
|
+
const routingUri = entrySubjectRefs(ctx.instance.state)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
|
|
1714
|
+
client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
|
|
1533
1715
|
client,
|
|
1534
1716
|
groq,
|
|
1535
1717
|
params,
|
|
1536
|
-
|
|
1718
|
+
// Draft-aware by default (drafts overlay published) when there's no
|
|
1719
|
+
// release, so a `forEach` discovers in-flight items the same way the
|
|
1720
|
+
// engine reads any other content.
|
|
1721
|
+
perspective: ctx.instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
|
|
1537
1722
|
});
|
|
1538
1723
|
} else {
|
|
1539
1724
|
const tree = groqJs.parse(groq, { params });
|
|
1540
1725
|
value = await (await groqJs.evaluate(tree, { dataset: [ctx.instance], params, root: ctx.instance })).get();
|
|
1541
1726
|
}
|
|
1542
|
-
return Array.isArray(value) ? value : value == null ? [] : [value];
|
|
1727
|
+
return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
|
|
1543
1728
|
}
|
|
1544
|
-
function
|
|
1545
|
-
if (entries) {
|
|
1546
|
-
for (const s of entries)
|
|
1547
|
-
if (s._type === "doc.ref" && s.value !== null) return s.value.id;
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
async function projectRowState(ctx, sub, childDefinition, parentScope, row) {
|
|
1729
|
+
async function projectRowState(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
|
|
1551
1730
|
const out = [];
|
|
1552
1731
|
for (const [name, groq] of Object.entries(sub.with ?? {})) {
|
|
1553
1732
|
const declared = (childDefinition.state ?? []).find((e) => e.name === name);
|
|
@@ -1555,7 +1734,12 @@ async function projectRowState(ctx, sub, childDefinition, parentScope, row) {
|
|
|
1555
1734
|
throw new Error(
|
|
1556
1735
|
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no state entry "${name}"`
|
|
1557
1736
|
);
|
|
1558
|
-
const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw,
|
|
1737
|
+
const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
|
|
1738
|
+
parentResource: ctx.instance.workflowResource,
|
|
1739
|
+
discoveryResource,
|
|
1740
|
+
entryName: name,
|
|
1741
|
+
childName: childDefinition.name
|
|
1742
|
+
});
|
|
1559
1743
|
value != null && out.push({
|
|
1560
1744
|
type: declared.type,
|
|
1561
1745
|
name,
|
|
@@ -1572,11 +1756,17 @@ async function resolveContextHandoff(ctx, sub, parentScope) {
|
|
|
1572
1756
|
}
|
|
1573
1757
|
return out;
|
|
1574
1758
|
}
|
|
1575
|
-
function canonicaliseSpawnValue(kind, value,
|
|
1576
|
-
return kind === "doc.ref" ? coerceSpawnGdr(value,
|
|
1759
|
+
function canonicaliseSpawnValue(kind, value, cx) {
|
|
1760
|
+
return kind === "doc.ref" ? coerceSpawnGdr(value, cx) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, cx)).filter((v2) => v2 !== null) : value;
|
|
1577
1761
|
}
|
|
1578
|
-
function
|
|
1579
|
-
|
|
1762
|
+
function assertBareIdRootsCorrectly(id, cx) {
|
|
1763
|
+
if (!(cx.discoveryResource === void 0 || sameResource(cx.discoveryResource, cx.parentResource)))
|
|
1764
|
+
throw new Error(
|
|
1765
|
+
`subworkflows.with["${cx.entryName}"]: subworkflow "${cx.childName}" projected the bare id "${id}", which roots at the parent resource "${cx.parentResource.id}", but spawn discovery ran against "${cx.discoveryResource.id}". The child would reference a document that doesn't exist in the parent resource. Project an explicit GDR URI in the with-GROQ (e.g. "dataset:<projectId>:<dataset>:" + _id) so the child's subject points at the resource the discovered rows came from.`
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1768
|
+
function coerceSpawnGdr(raw, cx) {
|
|
1769
|
+
const toUri = (id) => isGdrUri(id) ? id : (assertBareIdRootsCorrectly(id, cx), gdrFromResource(cx.parentResource, id));
|
|
1580
1770
|
if (raw == null) return null;
|
|
1581
1771
|
if (typeof raw == "object" && "id" in raw && "type" in raw) {
|
|
1582
1772
|
const r = raw;
|
|
@@ -1612,6 +1802,7 @@ async function prepareChildInstance(args) {
|
|
|
1612
1802
|
selfId: childDocId,
|
|
1613
1803
|
tag: childTag,
|
|
1614
1804
|
workflowResource,
|
|
1805
|
+
definitionName: definition.name,
|
|
1615
1806
|
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
|
|
1616
1807
|
},
|
|
1617
1808
|
randomKey
|
|
@@ -1658,6 +1849,28 @@ async function resolveBindings(args) {
|
|
|
1658
1849
|
resolved[key] = await runGroq(groq, args.params, args.snapshot);
|
|
1659
1850
|
return { ...resolved, ...args.staticInput };
|
|
1660
1851
|
}
|
|
1852
|
+
async function persistThenDeploy(ctx, mutation, deploy) {
|
|
1853
|
+
const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
|
|
1854
|
+
await deployOrRollback({
|
|
1855
|
+
client: ctx.client,
|
|
1856
|
+
instanceId: ctx.instance._id,
|
|
1857
|
+
committedRev: committed._rev,
|
|
1858
|
+
restore: instanceStateFields(ctx.instance),
|
|
1859
|
+
unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
|
|
1860
|
+
reversible: !spawned,
|
|
1861
|
+
deploy
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
async function refreshStageGuards(ctx, mutation, stageName) {
|
|
1865
|
+
await deployStageGuards({
|
|
1866
|
+
client: ctx.client,
|
|
1867
|
+
clientForGdr: ctx.clientForGdr,
|
|
1868
|
+
instance: materializeInstance(ctx.instance, mutation),
|
|
1869
|
+
definition: ctx.definition,
|
|
1870
|
+
stageName,
|
|
1871
|
+
now: ctx.now
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1661
1874
|
async function completeEffect(args) {
|
|
1662
1875
|
const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1663
1876
|
...options?.clock ? { clock: options.clock } : {},
|
|
@@ -1703,9 +1916,11 @@ async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, err
|
|
|
1703
1916
|
const mutation = startMutation(ctx.instance);
|
|
1704
1917
|
mutation.pendingEffects = mutation.pendingEffects.filter((e) => e._key !== effectKey);
|
|
1705
1918
|
const ranAt = ctx.now;
|
|
1706
|
-
|
|
1919
|
+
mutation.effectHistory.push(
|
|
1707
1920
|
buildEffectHistoryEntry(pending, { status, ranAt, actor, detail, error, durationMs, outputs })
|
|
1708
|
-
)
|
|
1921
|
+
);
|
|
1922
|
+
const wroteEffectsContext = status === "done" && outputs !== void 0;
|
|
1923
|
+
return wroteEffectsContext && upsertEffectsContext(mutation, pending.name, outputs), mutation.history.push({
|
|
1709
1924
|
_key: randomKey(),
|
|
1710
1925
|
_type: "effectCompleted",
|
|
1711
1926
|
at: ranAt,
|
|
@@ -1715,7 +1930,11 @@ async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, err
|
|
|
1715
1930
|
...outputs !== void 0 ? { outputs } : {},
|
|
1716
1931
|
...detail !== void 0 ? { detail } : {},
|
|
1717
1932
|
...actor !== void 0 ? { actor } : {}
|
|
1718
|
-
}), await
|
|
1933
|
+
}), await persistThenDeploy(
|
|
1934
|
+
ctx,
|
|
1935
|
+
mutation,
|
|
1936
|
+
() => wroteEffectsContext ? refreshStageGuards(ctx, mutation, ctx.instance.currentStage) : Promise.resolve()
|
|
1937
|
+
), { effectKey, status };
|
|
1719
1938
|
}
|
|
1720
1939
|
function buildQueuedEffect(effect, origin, params, actor, now) {
|
|
1721
1940
|
const key = randomKey(), pending = {
|
|
@@ -2075,21 +2294,24 @@ function resolveMachineStep(mutation, task, entry, now) {
|
|
|
2075
2294
|
async function buildStageTasks(ctx, stage) {
|
|
2076
2295
|
const entries = [];
|
|
2077
2296
|
for (const task of stage.tasks ?? []) {
|
|
2078
|
-
const
|
|
2297
|
+
const outcome = await ctxEvaluateConditionOutcome(ctx, task.filter, { taskName: task.name }), inScope = outcome !== "unsatisfied";
|
|
2079
2298
|
entries.push({
|
|
2080
2299
|
_key: randomKey(),
|
|
2081
2300
|
name: task.name,
|
|
2082
2301
|
status: inScope ? "pending" : "skipped",
|
|
2083
|
-
...task.filter !== void 0 ? {
|
|
2084
|
-
filterEvaluation: {
|
|
2085
|
-
at: ctx.now,
|
|
2086
|
-
truthy: inScope
|
|
2087
|
-
}
|
|
2088
|
-
} : {}
|
|
2302
|
+
...task.filter !== void 0 ? { filterEvaluation: buildFilterEvaluation(ctx.now, task.filter, outcome) } : {}
|
|
2089
2303
|
});
|
|
2090
2304
|
}
|
|
2091
2305
|
return entries;
|
|
2092
2306
|
}
|
|
2307
|
+
function buildFilterEvaluation(at, filter, outcome) {
|
|
2308
|
+
return outcome === "unevaluable" ? {
|
|
2309
|
+
at,
|
|
2310
|
+
truthy: !1,
|
|
2311
|
+
unevaluable: !0,
|
|
2312
|
+
detail: `Filter "${filter}" could not be evaluated (GROQ null \u2014 a referenced operand was missing or incomparable). Task kept in scope so the gate is not silently skipped.`
|
|
2313
|
+
} : { at, truthy: outcome === "satisfied" };
|
|
2314
|
+
}
|
|
2093
2315
|
function isTerminalStage(stage) {
|
|
2094
2316
|
return (stage.transitions ?? []).length === 0;
|
|
2095
2317
|
}
|
|
@@ -2172,28 +2394,6 @@ async function persistStageMove(ctx, mutation, exitedStage, enteredStage) {
|
|
|
2172
2394
|
now: ctx.now
|
|
2173
2395
|
});
|
|
2174
2396
|
}
|
|
2175
|
-
async function persistThenDeploy(ctx, mutation, deploy) {
|
|
2176
|
-
const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
|
|
2177
|
-
await deployOrRollback({
|
|
2178
|
-
client: ctx.client,
|
|
2179
|
-
instanceId: ctx.instance._id,
|
|
2180
|
-
committedRev: committed._rev,
|
|
2181
|
-
restore: instanceStateFields(ctx.instance),
|
|
2182
|
-
unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
|
|
2183
|
-
reversible: !spawned,
|
|
2184
|
-
deploy
|
|
2185
|
-
});
|
|
2186
|
-
}
|
|
2187
|
-
async function refreshStageGuards(ctx, mutation, stageName) {
|
|
2188
|
-
await deployStageGuards({
|
|
2189
|
-
client: ctx.client,
|
|
2190
|
-
clientForGdr: ctx.clientForGdr,
|
|
2191
|
-
instance: materializeInstance(ctx.instance, mutation),
|
|
2192
|
-
definition: ctx.definition,
|
|
2193
|
-
stageName,
|
|
2194
|
-
now: ctx.now
|
|
2195
|
-
});
|
|
2196
|
-
}
|
|
2197
2397
|
async function commitTransition(ctx, actor) {
|
|
2198
2398
|
if (isTerminal(ctx))
|
|
2199
2399
|
return { fired: !1 };
|
|
@@ -2239,8 +2439,11 @@ async function commitTransition(ctx, actor) {
|
|
|
2239
2439
|
};
|
|
2240
2440
|
}
|
|
2241
2441
|
async function pickTransition(ctx, stage) {
|
|
2242
|
-
for (const candidate of stage.transitions ?? [])
|
|
2243
|
-
|
|
2442
|
+
for (const candidate of stage.transitions ?? []) {
|
|
2443
|
+
const outcome = await ctxEvaluateConditionOutcome(ctx, candidate.filter);
|
|
2444
|
+
if (outcome === "satisfied") return candidate;
|
|
2445
|
+
if (outcome === "unevaluable") return;
|
|
2446
|
+
}
|
|
2244
2447
|
}
|
|
2245
2448
|
async function evaluateAutoTransitions(args) {
|
|
2246
2449
|
const { client, instanceId, options, clientForGdr, clock, overlay } = args, ctx = await loadContext(client, instanceId, {
|
|
@@ -2270,7 +2473,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
2270
2473
|
}, committed = await client.patch(instance._id).set({
|
|
2271
2474
|
stages: [initialStageEntry],
|
|
2272
2475
|
lastChangedAt: now
|
|
2273
|
-
}).ifRevisionId(instance._rev).commit();
|
|
2476
|
+
}).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
|
|
2274
2477
|
await deployOrRollback({
|
|
2275
2478
|
client,
|
|
2276
2479
|
instanceId: instance._id,
|
|
@@ -2532,15 +2735,18 @@ async function evaluateFromSnapshot(args) {
|
|
|
2532
2735
|
})
|
|
2533
2736
|
);
|
|
2534
2737
|
const transitionEvaluations = [];
|
|
2535
|
-
for (const transition of stage.transitions ?? [])
|
|
2738
|
+
for (const transition of stage.transitions ?? []) {
|
|
2739
|
+
const outcome = await evaluateConditionOutcome({
|
|
2740
|
+
condition: transition.filter,
|
|
2741
|
+
snapshot,
|
|
2742
|
+
params: scope
|
|
2743
|
+
});
|
|
2536
2744
|
transitionEvaluations.push({
|
|
2537
2745
|
transition,
|
|
2538
|
-
filterSatisfied:
|
|
2539
|
-
|
|
2540
|
-
snapshot,
|
|
2541
|
-
params: scope
|
|
2542
|
-
})
|
|
2746
|
+
filterSatisfied: outcome === "satisfied",
|
|
2747
|
+
unevaluable: outcome === "unevaluable"
|
|
2543
2748
|
});
|
|
2749
|
+
}
|
|
2544
2750
|
const currentStage = {
|
|
2545
2751
|
stage,
|
|
2546
2752
|
tasks: taskEvaluations,
|
|
@@ -2588,7 +2794,11 @@ async function evaluateTask(args) {
|
|
|
2588
2794
|
...scopedStateOverlay(instance, snapshot, task.name)
|
|
2589
2795
|
},
|
|
2590
2796
|
assigned
|
|
2591
|
-
},
|
|
2797
|
+
}, unmetRequirements = await evaluateRequirements({
|
|
2798
|
+
requirements: task.requirements,
|
|
2799
|
+
snapshot,
|
|
2800
|
+
params: taskScope
|
|
2801
|
+
}), requirementsReason = unmetRequirements.length > 0 ? { kind: "requirements-unmet", unmetRequirements } : void 0, actions = [];
|
|
2592
2802
|
for (const action of task.actions ?? [])
|
|
2593
2803
|
actions.push(
|
|
2594
2804
|
await evaluateAction({
|
|
@@ -2598,7 +2808,8 @@ async function evaluateTask(args) {
|
|
|
2598
2808
|
snapshot,
|
|
2599
2809
|
taskScope,
|
|
2600
2810
|
stageHasExits,
|
|
2601
|
-
guardDenial
|
|
2811
|
+
guardDenial,
|
|
2812
|
+
requirementsReason
|
|
2602
2813
|
})
|
|
2603
2814
|
);
|
|
2604
2815
|
return {
|
|
@@ -2609,12 +2820,22 @@ async function evaluateTask(args) {
|
|
|
2609
2820
|
// they're still inbox items. The inbox reads the assignees-kind state
|
|
2610
2821
|
// entry BY KIND (who-data is state).
|
|
2611
2822
|
pendingOnActor: (status === "active" || status === "pending") && assigned,
|
|
2823
|
+
...unmetRequirements.length > 0 ? { unmetRequirements } : {},
|
|
2612
2824
|
actions
|
|
2613
2825
|
};
|
|
2614
2826
|
}
|
|
2615
2827
|
async function evaluateAction(args) {
|
|
2616
|
-
const {
|
|
2617
|
-
|
|
2828
|
+
const {
|
|
2829
|
+
action,
|
|
2830
|
+
status,
|
|
2831
|
+
instance,
|
|
2832
|
+
snapshot,
|
|
2833
|
+
taskScope,
|
|
2834
|
+
stageHasExits,
|
|
2835
|
+
guardDenial,
|
|
2836
|
+
requirementsReason
|
|
2837
|
+
} = args, lifecycle = lifecycleReason(instance, status, stageHasExits);
|
|
2838
|
+
return lifecycle !== void 0 ? disabled(action, lifecycle) : guardDenial !== void 0 ? disabled(action, guardDenial) : requirementsReason !== void 0 ? disabled(action, requirementsReason) : action.filter !== void 0 && !await evaluateCondition({
|
|
2618
2839
|
condition: action.filter,
|
|
2619
2840
|
snapshot,
|
|
2620
2841
|
params: taskScope
|
|
@@ -2891,7 +3112,8 @@ const disabledReasonDetail = {
|
|
|
2891
3112
|
"task-not-active": (r) => `task status is "${r.status}"`,
|
|
2892
3113
|
"stage-terminal": (r) => `stage "${r.stage}" is terminal`,
|
|
2893
3114
|
"instance-completed": (r) => `instance completed at ${r.completedAt}`,
|
|
2894
|
-
"mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}
|
|
3115
|
+
"mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`,
|
|
3116
|
+
"requirements-unmet": (r) => `unmet requirement(s): ${r.unmetRequirements.join(", ")}`
|
|
2895
3117
|
};
|
|
2896
3118
|
function formatDisabledReason(task, action, reason) {
|
|
2897
3119
|
const detail = disabledReasonDetail[reason.kind](reason);
|
|
@@ -3128,6 +3350,7 @@ const workflow = {
|
|
|
3128
3350
|
selfId: id,
|
|
3129
3351
|
tag,
|
|
3130
3352
|
workflowResource,
|
|
3353
|
+
definitionName: definition.name,
|
|
3131
3354
|
...perspective !== void 0 ? { perspective } : {}
|
|
3132
3355
|
},
|
|
3133
3356
|
randomKey
|
|
@@ -3146,7 +3369,7 @@ const workflow = {
|
|
|
3146
3369
|
initialStage: definition.initialStage,
|
|
3147
3370
|
actor
|
|
3148
3371
|
});
|
|
3149
|
-
return await client.create(base), await primeInitialStage(client, id, actor, clientForGdr, clock), await cascade(client, id, actor, clientForGdr, clock), reload(client, id, tag);
|
|
3372
|
+
return await client.create(base, SYNC_COMMIT), await primeInitialStage(client, id, actor, clientForGdr, clock), await cascade(client, id, actor, clientForGdr, clock), reload(client, id, tag);
|
|
3150
3373
|
},
|
|
3151
3374
|
/**
|
|
3152
3375
|
* Fire an action against a task. If the task is pending, it is
|
|
@@ -3386,8 +3609,8 @@ const workflow = {
|
|
|
3386
3609
|
* Pure read.
|
|
3387
3610
|
*/
|
|
3388
3611
|
diagnose: async (args) => {
|
|
3389
|
-
const evaluation = await evaluateInstance(args);
|
|
3390
|
-
return { evaluation, diagnosis:
|
|
3612
|
+
const evaluation = await evaluateInstance(args), diagnosis = diagnoseInstance(diagnoseInputFromEvaluation(evaluation));
|
|
3613
|
+
return { evaluation, diagnosis, remediations: remediationsFor(diagnosis) };
|
|
3391
3614
|
},
|
|
3392
3615
|
/**
|
|
3393
3616
|
* List the actions an actor could fire on an instance's current stage,
|
|
@@ -3556,7 +3779,7 @@ async function claimPendingEffect(client, instance, effectKey, drainerActor) {
|
|
|
3556
3779
|
claimedBy: drainerActor
|
|
3557
3780
|
};
|
|
3558
3781
|
try {
|
|
3559
|
-
return await client.patch(instance._id).ifRevisionId(instance._rev).set({ [`pendingEffects[_key=="${effectKey}"].claim`]: claim }).commit(), !0;
|
|
3782
|
+
return await client.patch(instance._id).ifRevisionId(instance._rev).set({ [`pendingEffects[_key=="${effectKey}"].claim`]: claim }).commit(SYNC_COMMIT), !0;
|
|
3560
3783
|
} catch (error) {
|
|
3561
3784
|
if (!isRevisionConflict(error)) throw error;
|
|
3562
3785
|
return !1;
|
|
@@ -4004,11 +4227,13 @@ function displayDescription(typeKey) {
|
|
|
4004
4227
|
return DISPLAY[typeKey]?.description;
|
|
4005
4228
|
}
|
|
4006
4229
|
exports.WORKFLOW_DEFINITION_TYPE = schema.WORKFLOW_DEFINITION_TYPE;
|
|
4230
|
+
exports.isStartableDefinition = schema.isStartableDefinition;
|
|
4007
4231
|
exports.AUTHORING_DISPLAY = AUTHORING_DISPLAY;
|
|
4008
4232
|
exports.ActionDisabledError = ActionDisabledError;
|
|
4009
4233
|
exports.ActionParamsInvalidError = ActionParamsInvalidError;
|
|
4010
4234
|
exports.CascadeLimitError = CascadeLimitError;
|
|
4011
4235
|
exports.ConcurrentFireActionError = ConcurrentFireActionError;
|
|
4236
|
+
exports.DEFAULT_CONTENT_PERSPECTIVE = DEFAULT_CONTENT_PERSPECTIVE;
|
|
4012
4237
|
exports.DISPLAY = DISPLAY;
|
|
4013
4238
|
exports.EFFECTS_CONTEXT_DISPLAY = EFFECTS_CONTEXT_DISPLAY;
|
|
4014
4239
|
exports.GUARD_DOC_TYPE = GUARD_DOC_TYPE;
|
|
@@ -4017,12 +4242,13 @@ exports.GUARD_OWNER = GUARD_OWNER;
|
|
|
4017
4242
|
exports.HISTORY_DISPLAY = HISTORY_DISPLAY;
|
|
4018
4243
|
exports.MutationGuardDeniedError = MutationGuardDeniedError;
|
|
4019
4244
|
exports.OP_DISPLAY = OP_DISPLAY;
|
|
4245
|
+
exports.PartialGuardDeployError = PartialGuardDeployError;
|
|
4246
|
+
exports.RequiredStateNotProvidedError = RequiredStateNotProvidedError;
|
|
4020
4247
|
exports.STATE_SLOT_DISPLAY = STATE_SLOT_DISPLAY;
|
|
4021
4248
|
exports.WORKFLOW_INSTANCE_TYPE = WORKFLOW_INSTANCE_TYPE;
|
|
4022
4249
|
exports.WorkflowStateDivergedError = WorkflowStateDivergedError;
|
|
4023
4250
|
exports.abortReason = abortReason;
|
|
4024
4251
|
exports.actionVerdict = actionVerdict;
|
|
4025
|
-
exports.assigneesOf = assigneesOf;
|
|
4026
4252
|
exports.availableActions = availableActions;
|
|
4027
4253
|
exports.buildSnapshot = buildSnapshot;
|
|
4028
4254
|
exports.compileGuard = compileGuard;
|
|
@@ -4054,13 +4280,13 @@ exports.isDefinitionUnchanged = isDefinitionUnchanged;
|
|
|
4054
4280
|
exports.isGdr = isGdr;
|
|
4055
4281
|
exports.isTerminalStage = isTerminalStage;
|
|
4056
4282
|
exports.lakeGuardId = lakeGuardId;
|
|
4057
|
-
exports.openStage = openStage;
|
|
4058
4283
|
exports.parseGdr = parseGdr;
|
|
4059
4284
|
exports.readsRaw = readsRaw;
|
|
4060
4285
|
exports.refCanvas = refCanvas;
|
|
4061
4286
|
exports.refDashboard = refDashboard;
|
|
4062
4287
|
exports.refDataset = refDataset;
|
|
4063
4288
|
exports.refMediaLibrary = refMediaLibrary;
|
|
4289
|
+
exports.remediationsFor = remediationsFor;
|
|
4064
4290
|
exports.resolveAccess = resolveAccess;
|
|
4065
4291
|
exports.resourceFromParsed = resourceFromParsed;
|
|
4066
4292
|
exports.retractStageGuards = retractStageGuards;
|