@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.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { parse, evaluate } from "groq-js";
|
|
2
2
|
import { isTerminalTaskStatus, WORKFLOW_DEFINITION_TYPE, DOCUMENT_VALUE_PERMISSIONS } from "./_chunks-es/schema.js";
|
|
3
|
+
import { isStartableDefinition } from "./_chunks-es/schema.js";
|
|
3
4
|
import * as v from "valibot";
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
for (const part of path.split(".")) {
|
|
7
|
-
if (current == null || typeof current != "object") return;
|
|
8
|
-
current = current[part];
|
|
9
|
-
}
|
|
10
|
-
return current;
|
|
5
|
+
function findOpenStageEntry(host) {
|
|
6
|
+
return host.stages.find((s) => s.name === host.currentStage && s.exitedAt === void 0);
|
|
11
7
|
}
|
|
12
8
|
const KNOWN_SCHEMES = /* @__PURE__ */ new Set(["dataset", "canvas", "media-library", "dashboard"]);
|
|
13
9
|
function parseGdr(uri) {
|
|
@@ -79,6 +75,21 @@ function gdrFromResource(res, documentId) {
|
|
|
79
75
|
}
|
|
80
76
|
return gdrUri({ scheme: res.type, resourceId: res.id, documentId });
|
|
81
77
|
}
|
|
78
|
+
function resourceFromGdrUri(uri) {
|
|
79
|
+
let parsed;
|
|
80
|
+
try {
|
|
81
|
+
parsed = parseGdr(uri);
|
|
82
|
+
} catch {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (parsed.scheme === "dataset")
|
|
86
|
+
return parsed.projectId === void 0 || parsed.dataset === void 0 ? void 0 : { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` };
|
|
87
|
+
if (parsed.resourceId !== void 0)
|
|
88
|
+
return { type: parsed.scheme, id: parsed.resourceId };
|
|
89
|
+
}
|
|
90
|
+
function sameResource(a, b) {
|
|
91
|
+
return a.type === b.type && a.id === b.id;
|
|
92
|
+
}
|
|
82
93
|
function selfGdr(doc) {
|
|
83
94
|
return gdrFromResource(doc.workflowResource, doc._id);
|
|
84
95
|
}
|
|
@@ -96,12 +107,100 @@ function gdrRef(res, documentId, type) {
|
|
|
96
107
|
function isGdr(value) {
|
|
97
108
|
return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
|
|
98
109
|
}
|
|
110
|
+
function buildParams(args) {
|
|
111
|
+
const { instance, now, snapshot, extra } = args, currentTasks2 = findOpenStageEntry(instance)?.tasks ?? [];
|
|
112
|
+
return {
|
|
113
|
+
self: selfGdr(instance),
|
|
114
|
+
state: renderedState(instance.state ?? [], snapshot),
|
|
115
|
+
parent: instance.ancestors.at(-1)?.id ?? null,
|
|
116
|
+
ancestors: instance.ancestors.map((a) => a.id),
|
|
117
|
+
stage: instance.currentStage,
|
|
118
|
+
now,
|
|
119
|
+
/** `$effects` — parent context handoff by entry name, completed-effect
|
|
120
|
+
* outputs namespaced under the effect's name (`$effects['x.y'].out`). */
|
|
121
|
+
effects: effectsContextMap(instance),
|
|
122
|
+
/** `$tasks` — the current stage's task rows, statuses included. */
|
|
123
|
+
tasks: currentTasks2,
|
|
124
|
+
allTasksDone: currentTasks2.every((t) => t.status === "done" || t.status === "skipped"),
|
|
125
|
+
anyTaskFailed: currentTasks2.some((t) => t.status === "failed"),
|
|
126
|
+
...extra
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function renderedState(entries, snapshot) {
|
|
130
|
+
const out = {};
|
|
131
|
+
for (const entry of entries) out[entry.name] = renderedValue(entry, snapshot);
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
function renderedValue(entry, snapshot) {
|
|
135
|
+
return entry._type !== "doc.ref" || entry.value === null || snapshot === void 0 ? entry.value : snapshot.docs.find((d) => d._id === entry.value.id) ?? entry.value;
|
|
136
|
+
}
|
|
137
|
+
function scopedStateOverlay(instance, snapshot, taskName) {
|
|
138
|
+
const stageEntry = findOpenStageEntry(instance);
|
|
139
|
+
if (stageEntry === void 0) return {};
|
|
140
|
+
const stageState = renderedState(stageEntry.state ?? [], snapshot), task = taskName ? stageEntry.tasks.find((t) => t.name === taskName) : void 0;
|
|
141
|
+
return { ...stageState, ...renderedState(task?.state ?? [], snapshot) };
|
|
142
|
+
}
|
|
143
|
+
function assignedFor(instance, taskName, actor) {
|
|
144
|
+
if (actor === void 0) return !1;
|
|
145
|
+
const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.state?.find((s) => s._type === "assignees");
|
|
146
|
+
return entry === void 0 ? !1 : entry.value.some(
|
|
147
|
+
(a) => a.type === "user" ? a.id === actor.id : (actor.roles ?? []).includes(a.role)
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
function effectsContextMap(instance) {
|
|
151
|
+
const out = {};
|
|
152
|
+
for (const entry of instance.effectsContext)
|
|
153
|
+
out[entry.name] = entry._type === "effectsContext.json" ? parseJsonContextEntry(entry) : entry.value;
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
function parseJsonContextEntry(entry) {
|
|
157
|
+
try {
|
|
158
|
+
return JSON.parse(entry.value);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`effectsContext entry "${entry.name}" holds unparseable JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
162
|
+
{ cause: err }
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function paramsForLake(params) {
|
|
167
|
+
return {
|
|
168
|
+
...params,
|
|
169
|
+
self: typeof params.self == "string" ? bareId(params.self) : params.self,
|
|
170
|
+
parent: typeof params.parent == "string" ? bareId(params.parent) : params.parent,
|
|
171
|
+
ancestors: Array.isArray(params.ancestors) ? params.ancestors.map((a) => typeof a == "string" ? bareId(a) : a) : params.ancestors,
|
|
172
|
+
state: stripStateForLake(params.state)
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function stripStateForLake(value) {
|
|
176
|
+
if (value == null) return value;
|
|
177
|
+
if (typeof value == "string") return bareId(value);
|
|
178
|
+
if (Array.isArray(value)) return value.map(stripStateForLake);
|
|
179
|
+
if (typeof value == "object") {
|
|
180
|
+
const out = {};
|
|
181
|
+
for (const [k, v2] of Object.entries(value))
|
|
182
|
+
out[k] = stripStateForLake(v2);
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
function bareId(id) {
|
|
188
|
+
return isGdrUri(id) ? extractDocumentId(id) : id;
|
|
189
|
+
}
|
|
190
|
+
function getPath(value, path) {
|
|
191
|
+
let current = value;
|
|
192
|
+
for (const part of path.split(".")) {
|
|
193
|
+
if (current == null || typeof current != "object") return;
|
|
194
|
+
current = current[part];
|
|
195
|
+
}
|
|
196
|
+
return current;
|
|
197
|
+
}
|
|
99
198
|
function resourceOf(p) {
|
|
100
199
|
return p.scheme === "dataset" ? { type: "dataset", id: `${p.projectId}.${p.dataset}` } : { type: p.scheme, id: p.resourceId ?? "" };
|
|
101
200
|
}
|
|
102
|
-
const STATE_READ = /^\$state\.(\w+)(?:\.(.+))?$/;
|
|
201
|
+
const STATE_READ = /^\$state\.(\w+)(?:\.(.+))?$/, EFFECTS_READ = /^\$effects\['([^']+)'\](?:\.(.+))?$/;
|
|
103
202
|
function isGuardReadExpr(expr) {
|
|
104
|
-
return expr === "$self" || expr === "$now" || STATE_READ.test(expr);
|
|
203
|
+
return expr === "$self" || expr === "$now" || STATE_READ.test(expr) || EFFECTS_READ.test(expr);
|
|
105
204
|
}
|
|
106
205
|
function resolveGuardRead(expr, ctx) {
|
|
107
206
|
if (expr === "$self") return selfGdr(ctx.instance);
|
|
@@ -111,8 +210,13 @@ function resolveGuardRead(expr, ctx) {
|
|
|
111
210
|
const value = (ctx.instance.state ?? []).find((s) => s.name === stateRead[1])?.value;
|
|
112
211
|
return stateRead[2] !== void 0 ? getPath(value, stateRead[2]) : value;
|
|
113
212
|
}
|
|
213
|
+
const effectsRead = EFFECTS_READ.exec(expr);
|
|
214
|
+
if (effectsRead !== null) {
|
|
215
|
+
const outputs = effectsContextMap(ctx.instance)[effectsRead[1]];
|
|
216
|
+
return effectsRead[2] !== void 0 ? getPath(outputs, effectsRead[2]) : outputs;
|
|
217
|
+
}
|
|
114
218
|
throw new Error(
|
|
115
|
-
`Guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now",
|
|
219
|
+
`Guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$state.<name>[.path]", or "$effects['<name>'][.path]" (guards store resolved values; the lake cannot see $state or $effects)`
|
|
116
220
|
);
|
|
117
221
|
}
|
|
118
222
|
function resolveIdRefTarget(expr, ctx) {
|
|
@@ -194,7 +298,7 @@ function createDefinitionValidator() {
|
|
|
194
298
|
entry.source.type === "query" && tryParse(entry.source.query, `${label}.query`);
|
|
195
299
|
}, checkGuardRead: (expr, where) => {
|
|
196
300
|
isGuardReadExpr(expr) || errors.push(
|
|
197
|
-
` \xB7 ${where}: guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now",
|
|
301
|
+
` \xB7 ${where}: guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$state.<name>[.path]", or "$effects['<name>'][.path]" (guards store resolved values; the lake cannot see $state or $effects)`
|
|
198
302
|
);
|
|
199
303
|
} };
|
|
200
304
|
}
|
|
@@ -223,6 +327,8 @@ function validateTask(v2, stageName, task) {
|
|
|
223
327
|
for (const entry of task.state ?? [])
|
|
224
328
|
v2.checkEntry(entry, `${where}.state "${entry.name}"`);
|
|
225
329
|
task.filter !== void 0 && v2.checkCondition(task.filter, `${where}.filter`), task.completeWhen !== void 0 && v2.checkCondition(task.completeWhen, `${where}.completeWhen`), task.failWhen !== void 0 && v2.checkCondition(task.failWhen, `${where}.failWhen`);
|
|
330
|
+
for (const [name, groq] of Object.entries(task.requirements ?? {}))
|
|
331
|
+
v2.checkCondition(groq, `${where}.requirements "${name}"`);
|
|
226
332
|
for (const [key, groq] of effectBindings(task.effects))
|
|
227
333
|
v2.tryParse(groq, `${where} effect binding "${key}"`);
|
|
228
334
|
validateTaskActions(v2, task), task.subworkflows !== void 0 && validateSubworkflows(v2, where, task.subworkflows);
|
|
@@ -371,99 +477,20 @@ async function assertInstanceWriteAllowed(args) {
|
|
|
371
477
|
guards: denied
|
|
372
478
|
});
|
|
373
479
|
}
|
|
374
|
-
function findOpenStageEntry(host) {
|
|
375
|
-
return host.stages.find((s) => s.name === host.currentStage && s.exitedAt === void 0);
|
|
376
|
-
}
|
|
377
|
-
function buildParams(args) {
|
|
378
|
-
const { instance, now, snapshot, extra } = args, currentTasks2 = findOpenStageEntry(instance)?.tasks ?? [];
|
|
379
|
-
return {
|
|
380
|
-
self: selfGdr(instance),
|
|
381
|
-
state: renderedState(instance.state ?? [], snapshot),
|
|
382
|
-
parent: instance.ancestors.at(-1)?.id ?? null,
|
|
383
|
-
ancestors: instance.ancestors.map((a) => a.id),
|
|
384
|
-
stage: instance.currentStage,
|
|
385
|
-
now,
|
|
386
|
-
/** `$effects` — parent context handoff by entry name, completed-effect
|
|
387
|
-
* outputs namespaced under the effect's name (`$effects['x.y'].out`). */
|
|
388
|
-
effects: effectsContextMap(instance),
|
|
389
|
-
/** `$tasks` — the current stage's task rows, statuses included. */
|
|
390
|
-
tasks: currentTasks2,
|
|
391
|
-
allTasksDone: currentTasks2.every((t) => t.status === "done" || t.status === "skipped"),
|
|
392
|
-
anyTaskFailed: currentTasks2.some((t) => t.status === "failed"),
|
|
393
|
-
...extra
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
function renderedState(entries, snapshot) {
|
|
397
|
-
const out = {};
|
|
398
|
-
for (const entry of entries) out[entry.name] = renderedValue(entry, snapshot);
|
|
399
|
-
return out;
|
|
400
|
-
}
|
|
401
|
-
function renderedValue(entry, snapshot) {
|
|
402
|
-
return entry._type !== "doc.ref" || entry.value === null || snapshot === void 0 ? entry.value : snapshot.docs.find((d) => d._id === entry.value.id) ?? entry.value;
|
|
403
|
-
}
|
|
404
|
-
function scopedStateOverlay(instance, snapshot, taskName) {
|
|
405
|
-
const stageEntry = findOpenStageEntry(instance);
|
|
406
|
-
if (stageEntry === void 0) return {};
|
|
407
|
-
const stageState = renderedState(stageEntry.state ?? [], snapshot), task = taskName ? stageEntry.tasks.find((t) => t.name === taskName) : void 0;
|
|
408
|
-
return { ...stageState, ...renderedState(task?.state ?? [], snapshot) };
|
|
409
|
-
}
|
|
410
|
-
function assignedFor(instance, taskName, actor) {
|
|
411
|
-
if (actor === void 0) return !1;
|
|
412
|
-
const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.state?.find((s) => s._type === "assignees");
|
|
413
|
-
return entry === void 0 ? !1 : entry.value.some(
|
|
414
|
-
(a) => a.type === "user" ? a.id === actor.id : (actor.roles ?? []).includes(a.role)
|
|
415
|
-
);
|
|
416
|
-
}
|
|
417
|
-
function effectsContextMap(instance) {
|
|
418
|
-
const out = {};
|
|
419
|
-
for (const entry of instance.effectsContext)
|
|
420
|
-
out[entry.name] = entry._type === "effectsContext.json" ? parseJsonContextEntry(entry) : entry.value;
|
|
421
|
-
return out;
|
|
422
|
-
}
|
|
423
|
-
function parseJsonContextEntry(entry) {
|
|
424
|
-
try {
|
|
425
|
-
return JSON.parse(entry.value);
|
|
426
|
-
} catch (err) {
|
|
427
|
-
throw new Error(
|
|
428
|
-
`effectsContext entry "${entry.name}" holds unparseable JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
429
|
-
{ cause: err }
|
|
430
|
-
);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
function paramsForLake(params) {
|
|
434
|
-
return {
|
|
435
|
-
...params,
|
|
436
|
-
self: typeof params.self == "string" ? bareId(params.self) : params.self,
|
|
437
|
-
parent: typeof params.parent == "string" ? bareId(params.parent) : params.parent,
|
|
438
|
-
ancestors: Array.isArray(params.ancestors) ? params.ancestors.map((a) => typeof a == "string" ? bareId(a) : a) : params.ancestors,
|
|
439
|
-
state: stripStateForLake(params.state)
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
function stripStateForLake(value) {
|
|
443
|
-
if (value == null) return value;
|
|
444
|
-
if (typeof value == "string") return bareId(value);
|
|
445
|
-
if (Array.isArray(value)) return value.map(stripStateForLake);
|
|
446
|
-
if (typeof value == "object") {
|
|
447
|
-
const out = {};
|
|
448
|
-
for (const [k, v2] of Object.entries(value))
|
|
449
|
-
out[k] = stripStateForLake(v2);
|
|
450
|
-
return out;
|
|
451
|
-
}
|
|
452
|
-
return value;
|
|
453
|
-
}
|
|
454
|
-
function bareId(id) {
|
|
455
|
-
return isGdrUri(id) ? extractDocumentId(id) : id;
|
|
456
|
-
}
|
|
457
480
|
function abortReason(instance) {
|
|
458
481
|
return instance.history.find(
|
|
459
482
|
(h) => h._type === "aborted"
|
|
460
483
|
)?.reason;
|
|
461
484
|
}
|
|
462
485
|
function diagnoseInputFromEvaluation(evaluation) {
|
|
486
|
+
const stage = openStage(evaluation.instance);
|
|
463
487
|
return {
|
|
464
488
|
instance: evaluation.instance,
|
|
465
489
|
tasks: evaluation.currentStage.tasks,
|
|
466
|
-
transitions: evaluation.currentStage.transitions
|
|
490
|
+
transitions: evaluation.currentStage.transitions,
|
|
491
|
+
assignees: Object.fromEntries(
|
|
492
|
+
evaluation.currentStage.tasks.map((t) => [t.task.name, assigneesOf(stage, t.task.name)])
|
|
493
|
+
)
|
|
467
494
|
};
|
|
468
495
|
}
|
|
469
496
|
function openStage(instance) {
|
|
@@ -488,18 +515,34 @@ function failedTaskCause(input) {
|
|
|
488
515
|
return failed ? { kind: "failed-task", task: failed.task.name } : void 0;
|
|
489
516
|
}
|
|
490
517
|
function waitingState(input) {
|
|
491
|
-
const active = input.tasks.find(
|
|
518
|
+
const active = input.tasks.find(
|
|
519
|
+
(t) => t.status === "active" && (t.task.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length === 0
|
|
520
|
+
);
|
|
492
521
|
if (active !== void 0)
|
|
493
522
|
return {
|
|
494
523
|
state: "waiting",
|
|
495
524
|
task: active.task.name,
|
|
496
|
-
assignees:
|
|
525
|
+
assignees: input.assignees[active.task.name] ?? [],
|
|
497
526
|
actions: (active.task.actions ?? []).map((a) => a.name)
|
|
498
527
|
};
|
|
499
528
|
}
|
|
529
|
+
function blockedState(input) {
|
|
530
|
+
const blocked = input.tasks.find(
|
|
531
|
+
(t) => t.status === "active" && (t.task.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length > 0
|
|
532
|
+
);
|
|
533
|
+
if (blocked !== void 0)
|
|
534
|
+
return {
|
|
535
|
+
state: "blocked",
|
|
536
|
+
task: blocked.task.name,
|
|
537
|
+
requirements: blocked.unmetRequirements ?? [],
|
|
538
|
+
assignees: input.assignees[blocked.task.name] ?? []
|
|
539
|
+
};
|
|
540
|
+
}
|
|
500
541
|
function noTransitionFiresCause(input) {
|
|
501
|
-
if (input.transitions.length
|
|
502
|
-
return
|
|
542
|
+
if (input.transitions.length === 0 || input.transitions.some((t) => t.filterSatisfied) || !input.tasks.every((t) => isResolved(t.status)))
|
|
543
|
+
return;
|
|
544
|
+
const undecidable = input.transitions.filter((t) => t.unevaluable);
|
|
545
|
+
return undecidable.length > 0 ? { kind: "transition-unevaluable", transitions: undecidable.map((t) => t.transition.name) } : { kind: "no-transition-fires" };
|
|
503
546
|
}
|
|
504
547
|
function diagnoseInstance(input) {
|
|
505
548
|
const { instance } = input;
|
|
@@ -510,7 +553,124 @@ function diagnoseInstance(input) {
|
|
|
510
553
|
if (instance.completedAt !== void 0)
|
|
511
554
|
return { state: "completed", at: instance.completedAt };
|
|
512
555
|
const cause = failedEffectCause(input) ?? failedTaskCause(input) ?? hungEffectCause(input) ?? noTransitionFiresCause(input);
|
|
513
|
-
return cause !== void 0 ? { state: "stuck", cause } : waitingState(input) ?? { state: "progressing" };
|
|
556
|
+
return cause !== void 0 ? { state: "stuck", cause } : waitingState(input) ?? blockedState(input) ?? { state: "progressing" };
|
|
557
|
+
}
|
|
558
|
+
const RUNNABLE_VERBS = /* @__PURE__ */ new Set(["set-stage", "abort"]);
|
|
559
|
+
function remediationsForCause(cause) {
|
|
560
|
+
switch (cause.kind) {
|
|
561
|
+
case "failed-effect":
|
|
562
|
+
return [
|
|
563
|
+
{
|
|
564
|
+
verb: "retry-effect",
|
|
565
|
+
rationale: "Re-run the failed effect once the upstream system is healthy."
|
|
566
|
+
},
|
|
567
|
+
{ verb: "abort", rationale: "Abort the instance if it can no longer recover." }
|
|
568
|
+
];
|
|
569
|
+
case "hung-effect":
|
|
570
|
+
return [
|
|
571
|
+
{
|
|
572
|
+
verb: "drain-effects",
|
|
573
|
+
rationale: "Re-run the effect drainer to re-pick the claimed effect."
|
|
574
|
+
},
|
|
575
|
+
{ verb: "abort", rationale: "Abort the instance if the effect never drains." }
|
|
576
|
+
];
|
|
577
|
+
case "failed-task":
|
|
578
|
+
return [
|
|
579
|
+
{ verb: "reset-task", rationale: "Reset or skip the failed task." },
|
|
580
|
+
{
|
|
581
|
+
verb: "set-stage",
|
|
582
|
+
rationale: "Move the instance past the failed task to the intended next stage."
|
|
583
|
+
}
|
|
584
|
+
];
|
|
585
|
+
case "no-transition-fires":
|
|
586
|
+
return [
|
|
587
|
+
{
|
|
588
|
+
verb: "set-stage",
|
|
589
|
+
rationale: "Move the instance to the intended next stage to force it forward."
|
|
590
|
+
}
|
|
591
|
+
];
|
|
592
|
+
case "transition-unevaluable":
|
|
593
|
+
return [];
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
function remediationsFor(diagnosis) {
|
|
597
|
+
return diagnosis.state !== "stuck" ? [] : remediationsForCause(diagnosis.cause).map((seed) => ({
|
|
598
|
+
...seed,
|
|
599
|
+
available: RUNNABLE_VERBS.has(seed.verb)
|
|
600
|
+
}));
|
|
601
|
+
}
|
|
602
|
+
class ActionParamsInvalidError extends Error {
|
|
603
|
+
action;
|
|
604
|
+
task;
|
|
605
|
+
issues;
|
|
606
|
+
constructor(args) {
|
|
607
|
+
const lines = args.issues.map((i) => ` - ${i.param}: ${i.reason}`).join(`
|
|
608
|
+
`);
|
|
609
|
+
super(`Action "${args.action}" on task "${args.task}" rejected: invalid params
|
|
610
|
+
${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.task = args.task, this.issues = args.issues;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
class RequiredStateNotProvidedError extends Error {
|
|
614
|
+
definition;
|
|
615
|
+
missing;
|
|
616
|
+
constructor(args) {
|
|
617
|
+
const lines = args.missing.map((m) => ` - ${m.name} (${m.type})`).join(`
|
|
618
|
+
`), where = args.definition !== void 0 ? ` for workflow "${args.definition}"` : "";
|
|
619
|
+
super(
|
|
620
|
+
`Required init state${where} was not provided:
|
|
621
|
+
${lines}
|
|
622
|
+
Provide each via \`initialState\` when starting standalone, or via the parent's \`subworkflows.with\` when spawned.`
|
|
623
|
+
), this.name = "RequiredStateNotProvidedError", args.definition !== void 0 && (this.definition = args.definition), this.missing = args.missing;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
class WorkflowStateDivergedError extends Error {
|
|
627
|
+
instanceId;
|
|
628
|
+
/** The guard-deploy failure that triggered the (attempted) rollback. */
|
|
629
|
+
guardError;
|
|
630
|
+
/** The rollback failure, when rollback was attempted and itself failed. */
|
|
631
|
+
rollbackError;
|
|
632
|
+
constructor(args) {
|
|
633
|
+
super(
|
|
634
|
+
`Workflow "${args.instanceId}" state committed but its guards could not be reconciled: ${args.reason}.`,
|
|
635
|
+
{ cause: args.guardError }
|
|
636
|
+
), this.name = "WorkflowStateDivergedError", this.instanceId = args.instanceId, this.guardError = args.guardError, args.rollbackError !== void 0 && (this.rollbackError = args.rollbackError);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
class PartialGuardDeployError extends Error {
|
|
640
|
+
stageName;
|
|
641
|
+
deployed;
|
|
642
|
+
constructor(args) {
|
|
643
|
+
super(
|
|
644
|
+
`Partial guard deploy on stage "${args.stageName}": ${args.deployed} guard(s) deployed before a later one failed.`,
|
|
645
|
+
{ cause: args.cause }
|
|
646
|
+
), this.name = "PartialGuardDeployError", this.stageName = args.stageName, this.deployed = args.deployed;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
const CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS = 3;
|
|
650
|
+
class ConcurrentFireActionError extends Error {
|
|
651
|
+
instanceId;
|
|
652
|
+
task;
|
|
653
|
+
action;
|
|
654
|
+
attempts;
|
|
655
|
+
constructor(args) {
|
|
656
|
+
super(
|
|
657
|
+
`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.`
|
|
658
|
+
), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.task = args.task, this.action = args.action, this.attempts = args.attempts;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
function isRevisionConflict(error) {
|
|
662
|
+
if (typeof error != "object" || error === null) return !1;
|
|
663
|
+
const { statusCode, message } = error;
|
|
664
|
+
return statusCode === 409 ? !0 : typeof message == "string" && message.includes("ifRevisionId check failed");
|
|
665
|
+
}
|
|
666
|
+
class CascadeLimitError extends Error {
|
|
667
|
+
instanceId;
|
|
668
|
+
limit;
|
|
669
|
+
constructor(args) {
|
|
670
|
+
super(
|
|
671
|
+
`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.`
|
|
672
|
+
), this.name = "CascadeLimitError", this.instanceId = args.instanceId, this.limit = args.limit;
|
|
673
|
+
}
|
|
514
674
|
}
|
|
515
675
|
function buildSnapshot(args) {
|
|
516
676
|
const docs = [], knownIds = /* @__PURE__ */ new Set();
|
|
@@ -557,6 +717,7 @@ function collectWatchRefs(instance) {
|
|
|
557
717
|
function readsRaw(ref) {
|
|
558
718
|
return ref.type === WORKFLOW_INSTANCE_TYPE || ref.type === "system.release";
|
|
559
719
|
}
|
|
720
|
+
const DEFAULT_CONTENT_PERSPECTIVE = "drafts";
|
|
560
721
|
function contentReleaseName(args) {
|
|
561
722
|
const { ref, perspective } = args;
|
|
562
723
|
if (!readsRaw(ref) && Array.isArray(perspective))
|
|
@@ -582,6 +743,11 @@ function entryReleaseRefs(state) {
|
|
|
582
743
|
(entry) => entry._type === "release.ref" && isGdr(entry.value) ? [entry.value] : []
|
|
583
744
|
);
|
|
584
745
|
}
|
|
746
|
+
function entrySubjectRefs(state) {
|
|
747
|
+
return stateEntries(state).flatMap(
|
|
748
|
+
(entry) => (entry._type === "doc.ref" || entry._type === "release.ref") && isGdr(entry.value) ? [entry.value] : []
|
|
749
|
+
);
|
|
750
|
+
}
|
|
585
751
|
function stateEntries(state) {
|
|
586
752
|
return Array.isArray(state) ? state.filter(
|
|
587
753
|
(entry) => !!entry && typeof entry == "object"
|
|
@@ -606,7 +772,10 @@ async function hydrateSnapshot(args) {
|
|
|
606
772
|
};
|
|
607
773
|
loaded.push({ doc: instance, resource: instance.workflowResource }), visited.add(selfGdr(instance));
|
|
608
774
|
for (const ref of collectWatchRefs(instance))
|
|
609
|
-
await loadInto(
|
|
775
|
+
await loadInto(
|
|
776
|
+
ref.id,
|
|
777
|
+
readsRaw(ref) ? "raw" : instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
|
|
778
|
+
);
|
|
610
779
|
return buildSnapshot({ docs: loaded });
|
|
611
780
|
}
|
|
612
781
|
async function loadByGdr(defaultClient, clientForGdr, defaultResource, uri, perspective) {
|
|
@@ -621,7 +790,7 @@ async function loadByGdr(defaultClient, clientForGdr, defaultResource, uri, pers
|
|
|
621
790
|
return doc ? { doc, resource: resourceFromParsed(parsed) } : null;
|
|
622
791
|
}
|
|
623
792
|
async function readDoc(client, id, perspective) {
|
|
624
|
-
return perspective ===
|
|
793
|
+
return perspective === "raw" ? await client.getDocument(id) ?? null : await client.fetch("*[_id == $id][0]", { id }, { perspective }) ?? null;
|
|
625
794
|
}
|
|
626
795
|
function resourceFromParsed(parsed) {
|
|
627
796
|
return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
|
|
@@ -722,7 +891,7 @@ async function queryGuardsAcross(clients, query, params) {
|
|
|
722
891
|
function dedupById(guards) {
|
|
723
892
|
return [...new Map(guards.map((g) => [g._id, g])).values()].sort((a, b) => a._id.localeCompare(b._id));
|
|
724
893
|
}
|
|
725
|
-
const GUARD_OWNER = "robot:workflow-engine", GUARD_LIFTED_PREDICATE = "true";
|
|
894
|
+
const SYNC_COMMIT = { visibility: "sync" }, GUARD_OWNER = "robot:workflow-engine", GUARD_LIFTED_PREDICATE = "true";
|
|
726
895
|
function resolveGuard(guard, instance, stageName, now) {
|
|
727
896
|
const ctx = { instance, now }, targets = resolveIdRefTargets(guard.match.idRefs, ctx);
|
|
728
897
|
if (targets === null) return null;
|
|
@@ -749,11 +918,11 @@ function resolveGuard(guard, instance, stageName, now) {
|
|
|
749
918
|
}
|
|
750
919
|
async function upsertGuard(client, doc) {
|
|
751
920
|
if (!await client.getDocument(doc._id)) {
|
|
752
|
-
await client.create(doc);
|
|
921
|
+
await client.create(doc, SYNC_COMMIT);
|
|
753
922
|
return;
|
|
754
923
|
}
|
|
755
924
|
const { _id, _type, _rev, _createdAt, _updatedAt, ...body } = doc;
|
|
756
|
-
await client.patch(doc._id).set(body).commit();
|
|
925
|
+
await client.patch(doc._id).set(body).commit(SYNC_COMMIT);
|
|
757
926
|
}
|
|
758
927
|
function resolvedStageGuards(args) {
|
|
759
928
|
const stage = args.definition.stages.find((s) => s.name === args.stageName), out = [];
|
|
@@ -768,15 +937,22 @@ async function committedInstance(args) {
|
|
|
768
937
|
}
|
|
769
938
|
async function deployStageGuards(args) {
|
|
770
939
|
const live = await committedInstance(args);
|
|
771
|
-
if (
|
|
772
|
-
|
|
940
|
+
if (live === void 0 || live.currentStage !== args.stageName || live.abortedAt !== void 0) return;
|
|
941
|
+
let deployed = 0;
|
|
942
|
+
for (const { client, doc } of resolvedStageGuards(args)) {
|
|
943
|
+
try {
|
|
773
944
|
await upsertGuard(client, doc);
|
|
945
|
+
} catch (cause) {
|
|
946
|
+
throw deployed > 0 ? new PartialGuardDeployError({ stageName: args.stageName, deployed, cause }) : cause;
|
|
947
|
+
}
|
|
948
|
+
deployed += 1;
|
|
949
|
+
}
|
|
774
950
|
}
|
|
775
951
|
async function retractStageGuards(args) {
|
|
776
952
|
const live = await committedInstance(args);
|
|
777
953
|
if (live !== void 0 && !(live.currentStage === args.stageName && live.abortedAt === void 0))
|
|
778
954
|
for (const { client, doc } of resolvedStageGuards(args))
|
|
779
|
-
await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit();
|
|
955
|
+
await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit(SYNC_COMMIT);
|
|
780
956
|
}
|
|
781
957
|
async function deleteOrphanedDefinitionGuards(args) {
|
|
782
958
|
const { client, clientForGdr, workflowResource, definition, definitions, tag } = args, perClient = await guardsForDefinitionByClient({
|
|
@@ -800,16 +976,32 @@ function randomKey(length = 12) {
|
|
|
800
976
|
const bytes = new Uint8Array(length);
|
|
801
977
|
return globalThis.crypto.getRandomValues(bytes), [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, length);
|
|
802
978
|
}
|
|
803
|
-
|
|
979
|
+
function isUnevaluable(result) {
|
|
980
|
+
return result == null;
|
|
981
|
+
}
|
|
982
|
+
async function evaluateConditionOutcome(args) {
|
|
804
983
|
const { condition, snapshot, params } = args;
|
|
805
|
-
|
|
984
|
+
if (condition === void 0) return "satisfied";
|
|
985
|
+
const result = await runGroq(condition, params, snapshot);
|
|
986
|
+
return isUnevaluable(result) ? "unevaluable" : result ? "satisfied" : "unsatisfied";
|
|
987
|
+
}
|
|
988
|
+
async function evaluateCondition(args) {
|
|
989
|
+
return await evaluateConditionOutcome(args) === "satisfied";
|
|
806
990
|
}
|
|
807
991
|
async function evaluatePredicates(args) {
|
|
808
992
|
const out = {};
|
|
809
|
-
for (const [name, groq] of Object.entries(args.predicates ?? {}))
|
|
810
|
-
|
|
993
|
+
for (const [name, groq] of Object.entries(args.predicates ?? {})) {
|
|
994
|
+
const result = await runGroq(groq, args.params, args.snapshot);
|
|
995
|
+
out[name] = isUnevaluable(result) ? null : !!result;
|
|
996
|
+
}
|
|
811
997
|
return out;
|
|
812
998
|
}
|
|
999
|
+
async function evaluateRequirements(args) {
|
|
1000
|
+
const unmet = [];
|
|
1001
|
+
for (const [name, condition] of Object.entries(args.requirements ?? {}))
|
|
1002
|
+
await evaluateCondition({ condition, snapshot: args.snapshot, params: args.params }) || unmet.push(name);
|
|
1003
|
+
return unmet;
|
|
1004
|
+
}
|
|
813
1005
|
async function runGroq(groq, params, snapshot) {
|
|
814
1006
|
const tree = parse(groq, { params });
|
|
815
1007
|
return (await evaluate(tree, { dataset: snapshot.docs, params })).get();
|
|
@@ -943,6 +1135,7 @@ function derivePerspectiveFromState(entries) {
|
|
|
943
1135
|
async function resolveDeclaredState(args) {
|
|
944
1136
|
const { entryDefs, initialState, ctx, randomKey: randomKey2 } = args;
|
|
945
1137
|
if (entryDefs === void 0 || entryDefs.length === 0) return [];
|
|
1138
|
+
assertRequiredInitProvided(entryDefs, initialState, ctx.definitionName);
|
|
946
1139
|
const out = [];
|
|
947
1140
|
for (const entry of entryDefs) {
|
|
948
1141
|
const scopedCtx = { ...ctx, resolvedState: out };
|
|
@@ -950,6 +1143,17 @@ async function resolveDeclaredState(args) {
|
|
|
950
1143
|
}
|
|
951
1144
|
return out;
|
|
952
1145
|
}
|
|
1146
|
+
function assertRequiredInitProvided(entryDefs, initialState, definitionName) {
|
|
1147
|
+
const missing = entryDefs.filter((entry) => entry.required === !0 && entry.source.type === "init").filter((entry) => {
|
|
1148
|
+
const match = initialState.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1149
|
+
return match === void 0 || match.value === void 0 || match.value === null;
|
|
1150
|
+
}).map((entry) => ({ name: entry.name, type: entry.type }));
|
|
1151
|
+
if (missing.length !== 0)
|
|
1152
|
+
throw new RequiredStateNotProvidedError({
|
|
1153
|
+
...definitionName !== void 0 ? { definition: definitionName } : {},
|
|
1154
|
+
missing
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
953
1157
|
const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set([
|
|
954
1158
|
"doc.refs",
|
|
955
1159
|
"checklist",
|
|
@@ -977,7 +1181,7 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
|
977
1181
|
tag: ctx.tag
|
|
978
1182
|
});
|
|
979
1183
|
try {
|
|
980
|
-
const fetchOptions =
|
|
1184
|
+
const fetchOptions = { perspective: ctx.perspective ?? DEFAULT_CONTENT_PERSPECTIVE }, result = await client.fetch(source.query, params, fetchOptions);
|
|
981
1185
|
return normalizeQueryResult(entry.type, result, ctx.workflowResource) ?? defaultValue;
|
|
982
1186
|
} catch (err) {
|
|
983
1187
|
throw new Error(
|
|
@@ -1107,13 +1311,16 @@ async function ctxConditionParams(ctx, opts) {
|
|
|
1107
1311
|
params
|
|
1108
1312
|
}), ...params };
|
|
1109
1313
|
}
|
|
1110
|
-
async function
|
|
1111
|
-
return condition === void 0 ?
|
|
1314
|
+
async function ctxEvaluateConditionOutcome(ctx, condition, opts) {
|
|
1315
|
+
return condition === void 0 ? "satisfied" : evaluateConditionOutcome({
|
|
1112
1316
|
condition,
|
|
1113
1317
|
snapshot: ctx.snapshot,
|
|
1114
1318
|
params: await ctxConditionParams(ctx, opts)
|
|
1115
1319
|
});
|
|
1116
1320
|
}
|
|
1321
|
+
async function ctxEvaluateCondition(ctx, condition, opts) {
|
|
1322
|
+
return await ctxEvaluateConditionOutcome(ctx, condition, opts) === "satisfied";
|
|
1323
|
+
}
|
|
1117
1324
|
async function buildEngineContext(args) {
|
|
1118
1325
|
const { client, clientForGdr, instance, definition } = args, clock = args.clock ?? wallClock;
|
|
1119
1326
|
return {
|
|
@@ -1171,56 +1378,6 @@ async function resolveTaskStateEntries(args) {
|
|
|
1171
1378
|
randomKey
|
|
1172
1379
|
});
|
|
1173
1380
|
}
|
|
1174
|
-
class ActionParamsInvalidError extends Error {
|
|
1175
|
-
action;
|
|
1176
|
-
task;
|
|
1177
|
-
issues;
|
|
1178
|
-
constructor(args) {
|
|
1179
|
-
const lines = args.issues.map((i) => ` - ${i.param}: ${i.reason}`).join(`
|
|
1180
|
-
`);
|
|
1181
|
-
super(`Action "${args.action}" on task "${args.task}" rejected: invalid params
|
|
1182
|
-
${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.task = args.task, this.issues = args.issues;
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
class WorkflowStateDivergedError extends Error {
|
|
1186
|
-
instanceId;
|
|
1187
|
-
/** The guard-deploy failure that triggered the (attempted) rollback. */
|
|
1188
|
-
guardError;
|
|
1189
|
-
/** The rollback failure, when rollback was attempted and itself failed. */
|
|
1190
|
-
rollbackError;
|
|
1191
|
-
constructor(args) {
|
|
1192
|
-
super(
|
|
1193
|
-
`Workflow "${args.instanceId}" state committed but its guards could not be reconciled: ${args.reason}.`,
|
|
1194
|
-
{ cause: args.guardError }
|
|
1195
|
-
), this.name = "WorkflowStateDivergedError", this.instanceId = args.instanceId, this.guardError = args.guardError, args.rollbackError !== void 0 && (this.rollbackError = args.rollbackError);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
const CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS = 3;
|
|
1199
|
-
class ConcurrentFireActionError extends Error {
|
|
1200
|
-
instanceId;
|
|
1201
|
-
task;
|
|
1202
|
-
action;
|
|
1203
|
-
attempts;
|
|
1204
|
-
constructor(args) {
|
|
1205
|
-
super(
|
|
1206
|
-
`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.`
|
|
1207
|
-
), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.task = args.task, this.action = args.action, this.attempts = args.attempts;
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
function isRevisionConflict(error) {
|
|
1211
|
-
if (typeof error != "object" || error === null) return !1;
|
|
1212
|
-
const { statusCode, message } = error;
|
|
1213
|
-
return statusCode === 409 ? !0 : typeof message == "string" && message.includes("ifRevisionId check failed");
|
|
1214
|
-
}
|
|
1215
|
-
class CascadeLimitError extends Error {
|
|
1216
|
-
instanceId;
|
|
1217
|
-
limit;
|
|
1218
|
-
constructor(args) {
|
|
1219
|
-
super(
|
|
1220
|
-
`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.`
|
|
1221
|
-
), this.name = "CascadeLimitError", this.instanceId = args.instanceId, this.limit = args.limit;
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
1381
|
async function deployOrRollback(args) {
|
|
1225
1382
|
try {
|
|
1226
1383
|
await args.deploy();
|
|
@@ -1233,7 +1390,7 @@ async function deployOrRollback(args) {
|
|
|
1233
1390
|
});
|
|
1234
1391
|
try {
|
|
1235
1392
|
let patch = args.client.patch(args.instanceId).set(args.restore);
|
|
1236
|
-
args.unset && args.unset.length > 0 && (patch = patch.unset(args.unset)), args.committedRev !== void 0 && (patch = patch.ifRevisionId(args.committedRev)), await patch.commit();
|
|
1393
|
+
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);
|
|
1237
1394
|
} catch (rollbackError) {
|
|
1238
1395
|
throw new WorkflowStateDivergedError({
|
|
1239
1396
|
instanceId: args.instanceId,
|
|
@@ -1242,7 +1399,11 @@ async function deployOrRollback(args) {
|
|
|
1242
1399
|
reason: "rollback of the committed move failed"
|
|
1243
1400
|
});
|
|
1244
1401
|
}
|
|
1245
|
-
throw guardError
|
|
1402
|
+
throw guardError instanceof PartialGuardDeployError ? new WorkflowStateDivergedError({
|
|
1403
|
+
instanceId: args.instanceId,
|
|
1404
|
+
guardError,
|
|
1405
|
+
reason: "a multi-guard deploy partially applied; the rolled-back move left orphaned guard locks"
|
|
1406
|
+
}) : guardError;
|
|
1246
1407
|
}
|
|
1247
1408
|
}
|
|
1248
1409
|
function applyTaskStatusChange(args) {
|
|
@@ -1331,7 +1492,8 @@ function materializeInstance(base, mutation) {
|
|
|
1331
1492
|
...base,
|
|
1332
1493
|
currentStage: mutation.currentStage,
|
|
1333
1494
|
state: mutation.state,
|
|
1334
|
-
stages: mutation.stages
|
|
1495
|
+
stages: mutation.stages,
|
|
1496
|
+
effectsContext: mutation.effectsContext
|
|
1335
1497
|
};
|
|
1336
1498
|
}
|
|
1337
1499
|
async function persist(ctx, mutation) {
|
|
@@ -1340,13 +1502,27 @@ async function persist(ctx, mutation) {
|
|
|
1340
1502
|
lastChangedAt: ctx.now
|
|
1341
1503
|
}, pendingCreates = mutation.pendingCreates;
|
|
1342
1504
|
if (pendingCreates.length === 0)
|
|
1343
|
-
return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit();
|
|
1505
|
+
return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit(SYNC_COMMIT);
|
|
1344
1506
|
const tx = ctx.client.transaction();
|
|
1345
1507
|
for (const body of pendingCreates) tx.create(body);
|
|
1346
1508
|
tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)), await tx.commit(), mutation.pendingCreates = [];
|
|
1347
1509
|
const actorForPriming = void 0;
|
|
1348
1510
|
for (const body of pendingCreates)
|
|
1349
|
-
|
|
1511
|
+
try {
|
|
1512
|
+
await primeInitialStage(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock), await cascadeAutoTransitions(
|
|
1513
|
+
ctx.client,
|
|
1514
|
+
body._id,
|
|
1515
|
+
actorForPriming,
|
|
1516
|
+
ctx.clientForGdr,
|
|
1517
|
+
ctx.clock
|
|
1518
|
+
), await propagateToAncestors(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock);
|
|
1519
|
+
} catch (cause) {
|
|
1520
|
+
throw cause instanceof WorkflowStateDivergedError ? cause : new WorkflowStateDivergedError({
|
|
1521
|
+
instanceId: ctx.instance._id,
|
|
1522
|
+
guardError: cause,
|
|
1523
|
+
reason: `spawned child "${body._id}" failed to settle after the spawn transaction committed`
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1350
1526
|
const reloaded = await ctx.client.getDocument(ctx.instance._id);
|
|
1351
1527
|
if (!reloaded)
|
|
1352
1528
|
throw new Error(`Instance ${ctx.instance._id} disappeared after transaction commit`);
|
|
@@ -1493,9 +1669,16 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1493
1669
|
const parentScope = await ctxConditionParams(ctx, {
|
|
1494
1670
|
taskName: task.name,
|
|
1495
1671
|
...actor ? { actor } : {}
|
|
1496
|
-
}), rows = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
|
|
1672
|
+
}), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
|
|
1497
1673
|
for (const row of rows) {
|
|
1498
|
-
const initialState = await projectRowState(
|
|
1674
|
+
const initialState = await projectRowState(
|
|
1675
|
+
ctx,
|
|
1676
|
+
sub,
|
|
1677
|
+
definition,
|
|
1678
|
+
parentScope,
|
|
1679
|
+
row,
|
|
1680
|
+
discoveryResource
|
|
1681
|
+
), { ref, body } = await prepareChildInstance({
|
|
1499
1682
|
client: ctx.client,
|
|
1500
1683
|
parent: ctx.instance,
|
|
1501
1684
|
definition,
|
|
@@ -1510,28 +1693,25 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1510
1693
|
}
|
|
1511
1694
|
async function resolveSpawnRows(ctx, sub) {
|
|
1512
1695
|
const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
|
|
1513
|
-
let value;
|
|
1696
|
+
let value, discoveryResource;
|
|
1514
1697
|
if (groq.includes("*")) {
|
|
1515
|
-
const routingUri =
|
|
1516
|
-
value = await discoverItems({
|
|
1698
|
+
const routingUri = entrySubjectRefs(ctx.instance.state)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
|
|
1699
|
+
client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
|
|
1517
1700
|
client,
|
|
1518
1701
|
groq,
|
|
1519
1702
|
params,
|
|
1520
|
-
|
|
1703
|
+
// Draft-aware by default (drafts overlay published) when there's no
|
|
1704
|
+
// release, so a `forEach` discovers in-flight items the same way the
|
|
1705
|
+
// engine reads any other content.
|
|
1706
|
+
perspective: ctx.instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
|
|
1521
1707
|
});
|
|
1522
1708
|
} else {
|
|
1523
1709
|
const tree = parse(groq, { params });
|
|
1524
1710
|
value = await (await evaluate(tree, { dataset: [ctx.instance], params, root: ctx.instance })).get();
|
|
1525
1711
|
}
|
|
1526
|
-
return Array.isArray(value) ? value : value == null ? [] : [value];
|
|
1712
|
+
return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
|
|
1527
1713
|
}
|
|
1528
|
-
function
|
|
1529
|
-
if (entries) {
|
|
1530
|
-
for (const s of entries)
|
|
1531
|
-
if (s._type === "doc.ref" && s.value !== null) return s.value.id;
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
async function projectRowState(ctx, sub, childDefinition, parentScope, row) {
|
|
1714
|
+
async function projectRowState(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
|
|
1535
1715
|
const out = [];
|
|
1536
1716
|
for (const [name, groq] of Object.entries(sub.with ?? {})) {
|
|
1537
1717
|
const declared = (childDefinition.state ?? []).find((e) => e.name === name);
|
|
@@ -1539,7 +1719,12 @@ async function projectRowState(ctx, sub, childDefinition, parentScope, row) {
|
|
|
1539
1719
|
throw new Error(
|
|
1540
1720
|
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no state entry "${name}"`
|
|
1541
1721
|
);
|
|
1542
|
-
const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw,
|
|
1722
|
+
const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
|
|
1723
|
+
parentResource: ctx.instance.workflowResource,
|
|
1724
|
+
discoveryResource,
|
|
1725
|
+
entryName: name,
|
|
1726
|
+
childName: childDefinition.name
|
|
1727
|
+
});
|
|
1543
1728
|
value != null && out.push({
|
|
1544
1729
|
type: declared.type,
|
|
1545
1730
|
name,
|
|
@@ -1556,11 +1741,17 @@ async function resolveContextHandoff(ctx, sub, parentScope) {
|
|
|
1556
1741
|
}
|
|
1557
1742
|
return out;
|
|
1558
1743
|
}
|
|
1559
|
-
function canonicaliseSpawnValue(kind, value,
|
|
1560
|
-
return kind === "doc.ref" ? coerceSpawnGdr(value,
|
|
1744
|
+
function canonicaliseSpawnValue(kind, value, cx) {
|
|
1745
|
+
return kind === "doc.ref" ? coerceSpawnGdr(value, cx) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, cx)).filter((v2) => v2 !== null) : value;
|
|
1561
1746
|
}
|
|
1562
|
-
function
|
|
1563
|
-
|
|
1747
|
+
function assertBareIdRootsCorrectly(id, cx) {
|
|
1748
|
+
if (!(cx.discoveryResource === void 0 || sameResource(cx.discoveryResource, cx.parentResource)))
|
|
1749
|
+
throw new Error(
|
|
1750
|
+
`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.`
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
function coerceSpawnGdr(raw, cx) {
|
|
1754
|
+
const toUri = (id) => isGdrUri(id) ? id : (assertBareIdRootsCorrectly(id, cx), gdrFromResource(cx.parentResource, id));
|
|
1564
1755
|
if (raw == null) return null;
|
|
1565
1756
|
if (typeof raw == "object" && "id" in raw && "type" in raw) {
|
|
1566
1757
|
const r = raw;
|
|
@@ -1596,6 +1787,7 @@ async function prepareChildInstance(args) {
|
|
|
1596
1787
|
selfId: childDocId,
|
|
1597
1788
|
tag: childTag,
|
|
1598
1789
|
workflowResource,
|
|
1790
|
+
definitionName: definition.name,
|
|
1599
1791
|
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
|
|
1600
1792
|
},
|
|
1601
1793
|
randomKey
|
|
@@ -1642,6 +1834,28 @@ async function resolveBindings(args) {
|
|
|
1642
1834
|
resolved[key] = await runGroq(groq, args.params, args.snapshot);
|
|
1643
1835
|
return { ...resolved, ...args.staticInput };
|
|
1644
1836
|
}
|
|
1837
|
+
async function persistThenDeploy(ctx, mutation, deploy) {
|
|
1838
|
+
const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
|
|
1839
|
+
await deployOrRollback({
|
|
1840
|
+
client: ctx.client,
|
|
1841
|
+
instanceId: ctx.instance._id,
|
|
1842
|
+
committedRev: committed._rev,
|
|
1843
|
+
restore: instanceStateFields(ctx.instance),
|
|
1844
|
+
unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
|
|
1845
|
+
reversible: !spawned,
|
|
1846
|
+
deploy
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1849
|
+
async function refreshStageGuards(ctx, mutation, stageName) {
|
|
1850
|
+
await deployStageGuards({
|
|
1851
|
+
client: ctx.client,
|
|
1852
|
+
clientForGdr: ctx.clientForGdr,
|
|
1853
|
+
instance: materializeInstance(ctx.instance, mutation),
|
|
1854
|
+
definition: ctx.definition,
|
|
1855
|
+
stageName,
|
|
1856
|
+
now: ctx.now
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1645
1859
|
async function completeEffect(args) {
|
|
1646
1860
|
const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1647
1861
|
...options?.clock ? { clock: options.clock } : {},
|
|
@@ -1687,9 +1901,11 @@ async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, err
|
|
|
1687
1901
|
const mutation = startMutation(ctx.instance);
|
|
1688
1902
|
mutation.pendingEffects = mutation.pendingEffects.filter((e) => e._key !== effectKey);
|
|
1689
1903
|
const ranAt = ctx.now;
|
|
1690
|
-
|
|
1904
|
+
mutation.effectHistory.push(
|
|
1691
1905
|
buildEffectHistoryEntry(pending, { status, ranAt, actor, detail, error, durationMs, outputs })
|
|
1692
|
-
)
|
|
1906
|
+
);
|
|
1907
|
+
const wroteEffectsContext = status === "done" && outputs !== void 0;
|
|
1908
|
+
return wroteEffectsContext && upsertEffectsContext(mutation, pending.name, outputs), mutation.history.push({
|
|
1693
1909
|
_key: randomKey(),
|
|
1694
1910
|
_type: "effectCompleted",
|
|
1695
1911
|
at: ranAt,
|
|
@@ -1699,7 +1915,11 @@ async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, err
|
|
|
1699
1915
|
...outputs !== void 0 ? { outputs } : {},
|
|
1700
1916
|
...detail !== void 0 ? { detail } : {},
|
|
1701
1917
|
...actor !== void 0 ? { actor } : {}
|
|
1702
|
-
}), await
|
|
1918
|
+
}), await persistThenDeploy(
|
|
1919
|
+
ctx,
|
|
1920
|
+
mutation,
|
|
1921
|
+
() => wroteEffectsContext ? refreshStageGuards(ctx, mutation, ctx.instance.currentStage) : Promise.resolve()
|
|
1922
|
+
), { effectKey, status };
|
|
1703
1923
|
}
|
|
1704
1924
|
function buildQueuedEffect(effect, origin, params, actor, now) {
|
|
1705
1925
|
const key = randomKey(), pending = {
|
|
@@ -2059,21 +2279,24 @@ function resolveMachineStep(mutation, task, entry, now) {
|
|
|
2059
2279
|
async function buildStageTasks(ctx, stage) {
|
|
2060
2280
|
const entries = [];
|
|
2061
2281
|
for (const task of stage.tasks ?? []) {
|
|
2062
|
-
const
|
|
2282
|
+
const outcome = await ctxEvaluateConditionOutcome(ctx, task.filter, { taskName: task.name }), inScope = outcome !== "unsatisfied";
|
|
2063
2283
|
entries.push({
|
|
2064
2284
|
_key: randomKey(),
|
|
2065
2285
|
name: task.name,
|
|
2066
2286
|
status: inScope ? "pending" : "skipped",
|
|
2067
|
-
...task.filter !== void 0 ? {
|
|
2068
|
-
filterEvaluation: {
|
|
2069
|
-
at: ctx.now,
|
|
2070
|
-
truthy: inScope
|
|
2071
|
-
}
|
|
2072
|
-
} : {}
|
|
2287
|
+
...task.filter !== void 0 ? { filterEvaluation: buildFilterEvaluation(ctx.now, task.filter, outcome) } : {}
|
|
2073
2288
|
});
|
|
2074
2289
|
}
|
|
2075
2290
|
return entries;
|
|
2076
2291
|
}
|
|
2292
|
+
function buildFilterEvaluation(at, filter, outcome) {
|
|
2293
|
+
return outcome === "unevaluable" ? {
|
|
2294
|
+
at,
|
|
2295
|
+
truthy: !1,
|
|
2296
|
+
unevaluable: !0,
|
|
2297
|
+
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.`
|
|
2298
|
+
} : { at, truthy: outcome === "satisfied" };
|
|
2299
|
+
}
|
|
2077
2300
|
function isTerminalStage(stage) {
|
|
2078
2301
|
return (stage.transitions ?? []).length === 0;
|
|
2079
2302
|
}
|
|
@@ -2156,28 +2379,6 @@ async function persistStageMove(ctx, mutation, exitedStage, enteredStage) {
|
|
|
2156
2379
|
now: ctx.now
|
|
2157
2380
|
});
|
|
2158
2381
|
}
|
|
2159
|
-
async function persistThenDeploy(ctx, mutation, deploy) {
|
|
2160
|
-
const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
|
|
2161
|
-
await deployOrRollback({
|
|
2162
|
-
client: ctx.client,
|
|
2163
|
-
instanceId: ctx.instance._id,
|
|
2164
|
-
committedRev: committed._rev,
|
|
2165
|
-
restore: instanceStateFields(ctx.instance),
|
|
2166
|
-
unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
|
|
2167
|
-
reversible: !spawned,
|
|
2168
|
-
deploy
|
|
2169
|
-
});
|
|
2170
|
-
}
|
|
2171
|
-
async function refreshStageGuards(ctx, mutation, stageName) {
|
|
2172
|
-
await deployStageGuards({
|
|
2173
|
-
client: ctx.client,
|
|
2174
|
-
clientForGdr: ctx.clientForGdr,
|
|
2175
|
-
instance: materializeInstance(ctx.instance, mutation),
|
|
2176
|
-
definition: ctx.definition,
|
|
2177
|
-
stageName,
|
|
2178
|
-
now: ctx.now
|
|
2179
|
-
});
|
|
2180
|
-
}
|
|
2181
2382
|
async function commitTransition(ctx, actor) {
|
|
2182
2383
|
if (isTerminal(ctx))
|
|
2183
2384
|
return { fired: !1 };
|
|
@@ -2223,8 +2424,11 @@ async function commitTransition(ctx, actor) {
|
|
|
2223
2424
|
};
|
|
2224
2425
|
}
|
|
2225
2426
|
async function pickTransition(ctx, stage) {
|
|
2226
|
-
for (const candidate of stage.transitions ?? [])
|
|
2227
|
-
|
|
2427
|
+
for (const candidate of stage.transitions ?? []) {
|
|
2428
|
+
const outcome = await ctxEvaluateConditionOutcome(ctx, candidate.filter);
|
|
2429
|
+
if (outcome === "satisfied") return candidate;
|
|
2430
|
+
if (outcome === "unevaluable") return;
|
|
2431
|
+
}
|
|
2228
2432
|
}
|
|
2229
2433
|
async function evaluateAutoTransitions(args) {
|
|
2230
2434
|
const { client, instanceId, options, clientForGdr, clock, overlay } = args, ctx = await loadContext(client, instanceId, {
|
|
@@ -2254,7 +2458,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
2254
2458
|
}, committed = await client.patch(instance._id).set({
|
|
2255
2459
|
stages: [initialStageEntry],
|
|
2256
2460
|
lastChangedAt: now
|
|
2257
|
-
}).ifRevisionId(instance._rev).commit();
|
|
2461
|
+
}).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
|
|
2258
2462
|
await deployOrRollback({
|
|
2259
2463
|
client,
|
|
2260
2464
|
instanceId: instance._id,
|
|
@@ -2516,15 +2720,18 @@ async function evaluateFromSnapshot(args) {
|
|
|
2516
2720
|
})
|
|
2517
2721
|
);
|
|
2518
2722
|
const transitionEvaluations = [];
|
|
2519
|
-
for (const transition of stage.transitions ?? [])
|
|
2723
|
+
for (const transition of stage.transitions ?? []) {
|
|
2724
|
+
const outcome = await evaluateConditionOutcome({
|
|
2725
|
+
condition: transition.filter,
|
|
2726
|
+
snapshot,
|
|
2727
|
+
params: scope
|
|
2728
|
+
});
|
|
2520
2729
|
transitionEvaluations.push({
|
|
2521
2730
|
transition,
|
|
2522
|
-
filterSatisfied:
|
|
2523
|
-
|
|
2524
|
-
snapshot,
|
|
2525
|
-
params: scope
|
|
2526
|
-
})
|
|
2731
|
+
filterSatisfied: outcome === "satisfied",
|
|
2732
|
+
unevaluable: outcome === "unevaluable"
|
|
2527
2733
|
});
|
|
2734
|
+
}
|
|
2528
2735
|
const currentStage = {
|
|
2529
2736
|
stage,
|
|
2530
2737
|
tasks: taskEvaluations,
|
|
@@ -2572,7 +2779,11 @@ async function evaluateTask(args) {
|
|
|
2572
2779
|
...scopedStateOverlay(instance, snapshot, task.name)
|
|
2573
2780
|
},
|
|
2574
2781
|
assigned
|
|
2575
|
-
},
|
|
2782
|
+
}, unmetRequirements = await evaluateRequirements({
|
|
2783
|
+
requirements: task.requirements,
|
|
2784
|
+
snapshot,
|
|
2785
|
+
params: taskScope
|
|
2786
|
+
}), requirementsReason = unmetRequirements.length > 0 ? { kind: "requirements-unmet", unmetRequirements } : void 0, actions = [];
|
|
2576
2787
|
for (const action of task.actions ?? [])
|
|
2577
2788
|
actions.push(
|
|
2578
2789
|
await evaluateAction({
|
|
@@ -2582,7 +2793,8 @@ async function evaluateTask(args) {
|
|
|
2582
2793
|
snapshot,
|
|
2583
2794
|
taskScope,
|
|
2584
2795
|
stageHasExits,
|
|
2585
|
-
guardDenial
|
|
2796
|
+
guardDenial,
|
|
2797
|
+
requirementsReason
|
|
2586
2798
|
})
|
|
2587
2799
|
);
|
|
2588
2800
|
return {
|
|
@@ -2593,12 +2805,22 @@ async function evaluateTask(args) {
|
|
|
2593
2805
|
// they're still inbox items. The inbox reads the assignees-kind state
|
|
2594
2806
|
// entry BY KIND (who-data is state).
|
|
2595
2807
|
pendingOnActor: (status === "active" || status === "pending") && assigned,
|
|
2808
|
+
...unmetRequirements.length > 0 ? { unmetRequirements } : {},
|
|
2596
2809
|
actions
|
|
2597
2810
|
};
|
|
2598
2811
|
}
|
|
2599
2812
|
async function evaluateAction(args) {
|
|
2600
|
-
const {
|
|
2601
|
-
|
|
2813
|
+
const {
|
|
2814
|
+
action,
|
|
2815
|
+
status,
|
|
2816
|
+
instance,
|
|
2817
|
+
snapshot,
|
|
2818
|
+
taskScope,
|
|
2819
|
+
stageHasExits,
|
|
2820
|
+
guardDenial,
|
|
2821
|
+
requirementsReason
|
|
2822
|
+
} = args, lifecycle = lifecycleReason(instance, status, stageHasExits);
|
|
2823
|
+
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({
|
|
2602
2824
|
condition: action.filter,
|
|
2603
2825
|
snapshot,
|
|
2604
2826
|
params: taskScope
|
|
@@ -2875,7 +3097,8 @@ const disabledReasonDetail = {
|
|
|
2875
3097
|
"task-not-active": (r) => `task status is "${r.status}"`,
|
|
2876
3098
|
"stage-terminal": (r) => `stage "${r.stage}" is terminal`,
|
|
2877
3099
|
"instance-completed": (r) => `instance completed at ${r.completedAt}`,
|
|
2878
|
-
"mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}
|
|
3100
|
+
"mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`,
|
|
3101
|
+
"requirements-unmet": (r) => `unmet requirement(s): ${r.unmetRequirements.join(", ")}`
|
|
2879
3102
|
};
|
|
2880
3103
|
function formatDisabledReason(task, action, reason) {
|
|
2881
3104
|
const detail = disabledReasonDetail[reason.kind](reason);
|
|
@@ -3112,6 +3335,7 @@ const workflow = {
|
|
|
3112
3335
|
selfId: id,
|
|
3113
3336
|
tag,
|
|
3114
3337
|
workflowResource,
|
|
3338
|
+
definitionName: definition.name,
|
|
3115
3339
|
...perspective !== void 0 ? { perspective } : {}
|
|
3116
3340
|
},
|
|
3117
3341
|
randomKey
|
|
@@ -3130,7 +3354,7 @@ const workflow = {
|
|
|
3130
3354
|
initialStage: definition.initialStage,
|
|
3131
3355
|
actor
|
|
3132
3356
|
});
|
|
3133
|
-
return await client.create(base), await primeInitialStage(client, id, actor, clientForGdr, clock), await cascade(client, id, actor, clientForGdr, clock), reload(client, id, tag);
|
|
3357
|
+
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);
|
|
3134
3358
|
},
|
|
3135
3359
|
/**
|
|
3136
3360
|
* Fire an action against a task. If the task is pending, it is
|
|
@@ -3370,8 +3594,8 @@ const workflow = {
|
|
|
3370
3594
|
* Pure read.
|
|
3371
3595
|
*/
|
|
3372
3596
|
diagnose: async (args) => {
|
|
3373
|
-
const evaluation = await evaluateInstance(args);
|
|
3374
|
-
return { evaluation, diagnosis:
|
|
3597
|
+
const evaluation = await evaluateInstance(args), diagnosis = diagnoseInstance(diagnoseInputFromEvaluation(evaluation));
|
|
3598
|
+
return { evaluation, diagnosis, remediations: remediationsFor(diagnosis) };
|
|
3375
3599
|
},
|
|
3376
3600
|
/**
|
|
3377
3601
|
* List the actions an actor could fire on an instance's current stage,
|
|
@@ -3540,7 +3764,7 @@ async function claimPendingEffect(client, instance, effectKey, drainerActor) {
|
|
|
3540
3764
|
claimedBy: drainerActor
|
|
3541
3765
|
};
|
|
3542
3766
|
try {
|
|
3543
|
-
return await client.patch(instance._id).ifRevisionId(instance._rev).set({ [`pendingEffects[_key=="${effectKey}"].claim`]: claim }).commit(), !0;
|
|
3767
|
+
return await client.patch(instance._id).ifRevisionId(instance._rev).set({ [`pendingEffects[_key=="${effectKey}"].claim`]: claim }).commit(SYNC_COMMIT), !0;
|
|
3544
3768
|
} catch (error) {
|
|
3545
3769
|
if (!isRevisionConflict(error)) throw error;
|
|
3546
3770
|
return !1;
|
|
@@ -3993,6 +4217,7 @@ export {
|
|
|
3993
4217
|
ActionParamsInvalidError,
|
|
3994
4218
|
CascadeLimitError,
|
|
3995
4219
|
ConcurrentFireActionError,
|
|
4220
|
+
DEFAULT_CONTENT_PERSPECTIVE,
|
|
3996
4221
|
DISPLAY,
|
|
3997
4222
|
EFFECTS_CONTEXT_DISPLAY,
|
|
3998
4223
|
GUARD_DOC_TYPE,
|
|
@@ -4001,13 +4226,14 @@ export {
|
|
|
4001
4226
|
HISTORY_DISPLAY,
|
|
4002
4227
|
MutationGuardDeniedError,
|
|
4003
4228
|
OP_DISPLAY,
|
|
4229
|
+
PartialGuardDeployError,
|
|
4230
|
+
RequiredStateNotProvidedError,
|
|
4004
4231
|
STATE_SLOT_DISPLAY,
|
|
4005
4232
|
WORKFLOW_DEFINITION_TYPE,
|
|
4006
4233
|
WORKFLOW_INSTANCE_TYPE,
|
|
4007
4234
|
WorkflowStateDivergedError,
|
|
4008
4235
|
abortReason,
|
|
4009
4236
|
actionVerdict,
|
|
4010
|
-
assigneesOf,
|
|
4011
4237
|
availableActions,
|
|
4012
4238
|
buildSnapshot,
|
|
4013
4239
|
compileGuard,
|
|
@@ -4037,15 +4263,16 @@ export {
|
|
|
4037
4263
|
instanceGuardQuery,
|
|
4038
4264
|
isDefinitionUnchanged,
|
|
4039
4265
|
isGdr,
|
|
4266
|
+
isStartableDefinition,
|
|
4040
4267
|
isTerminalStage,
|
|
4041
4268
|
lakeGuardId,
|
|
4042
|
-
openStage,
|
|
4043
4269
|
parseGdr,
|
|
4044
4270
|
readsRaw,
|
|
4045
4271
|
refCanvas,
|
|
4046
4272
|
refDashboard,
|
|
4047
4273
|
refDataset,
|
|
4048
4274
|
refMediaLibrary,
|
|
4275
|
+
remediationsFor,
|
|
4049
4276
|
resolveAccess,
|
|
4050
4277
|
resourceFromParsed,
|
|
4051
4278
|
retractStageGuards,
|