@sanity/workflow-engine 0.6.0 → 0.7.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 +10 -0
- package/dist/_chunks-cjs/schema.cjs.map +1 -1
- package/dist/_chunks-es/schema.js +10 -0
- package/dist/_chunks-es/schema.js.map +1 -1
- package/dist/define.cjs +1 -0
- package/dist/define.cjs.map +1 -1
- package/dist/define.d.cts +134 -0
- package/dist/define.d.ts +134 -0
- package/dist/define.js +1 -0
- package/dist/define.js.map +1 -1
- package/dist/index.cjs +216 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +323 -19
- package/dist/index.d.ts +323 -19
- package/dist/index.js +216 -67
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -95,6 +95,21 @@ function gdrFromResource(res, documentId) {
|
|
|
95
95
|
}
|
|
96
96
|
return gdrUri({ scheme: res.type, resourceId: res.id, documentId });
|
|
97
97
|
}
|
|
98
|
+
function resourceFromGdrUri(uri) {
|
|
99
|
+
let parsed;
|
|
100
|
+
try {
|
|
101
|
+
parsed = parseGdr(uri);
|
|
102
|
+
} catch {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (parsed.scheme === "dataset")
|
|
106
|
+
return parsed.projectId === void 0 || parsed.dataset === void 0 ? void 0 : { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` };
|
|
107
|
+
if (parsed.resourceId !== void 0)
|
|
108
|
+
return { type: parsed.scheme, id: parsed.resourceId };
|
|
109
|
+
}
|
|
110
|
+
function sameResource(a, b) {
|
|
111
|
+
return a.type === b.type && a.id === b.id;
|
|
112
|
+
}
|
|
98
113
|
function selfGdr(doc) {
|
|
99
114
|
return gdrFromResource(doc.workflowResource, doc._id);
|
|
100
115
|
}
|
|
@@ -239,6 +254,8 @@ function validateTask(v2, stageName, task) {
|
|
|
239
254
|
for (const entry of task.state ?? [])
|
|
240
255
|
v2.checkEntry(entry, `${where}.state "${entry.name}"`);
|
|
241
256
|
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`);
|
|
257
|
+
for (const [name, groq] of Object.entries(task.requirements ?? {}))
|
|
258
|
+
v2.checkCondition(groq, `${where}.requirements "${name}"`);
|
|
242
259
|
for (const [key, groq] of effectBindings(task.effects))
|
|
243
260
|
v2.tryParse(groq, `${where} effect binding "${key}"`);
|
|
244
261
|
validateTaskActions(v2, task), task.subworkflows !== void 0 && validateSubworkflows(v2, where, task.subworkflows);
|
|
@@ -476,10 +493,14 @@ function abortReason(instance) {
|
|
|
476
493
|
)?.reason;
|
|
477
494
|
}
|
|
478
495
|
function diagnoseInputFromEvaluation(evaluation) {
|
|
496
|
+
const stage = openStage(evaluation.instance);
|
|
479
497
|
return {
|
|
480
498
|
instance: evaluation.instance,
|
|
481
499
|
tasks: evaluation.currentStage.tasks,
|
|
482
|
-
transitions: evaluation.currentStage.transitions
|
|
500
|
+
transitions: evaluation.currentStage.transitions,
|
|
501
|
+
assignees: Object.fromEntries(
|
|
502
|
+
evaluation.currentStage.tasks.map((t) => [t.task.name, assigneesOf(stage, t.task.name)])
|
|
503
|
+
)
|
|
483
504
|
};
|
|
484
505
|
}
|
|
485
506
|
function openStage(instance) {
|
|
@@ -504,18 +525,34 @@ function failedTaskCause(input) {
|
|
|
504
525
|
return failed ? { kind: "failed-task", task: failed.task.name } : void 0;
|
|
505
526
|
}
|
|
506
527
|
function waitingState(input) {
|
|
507
|
-
const active = input.tasks.find(
|
|
528
|
+
const active = input.tasks.find(
|
|
529
|
+
(t) => t.status === "active" && (t.task.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length === 0
|
|
530
|
+
);
|
|
508
531
|
if (active !== void 0)
|
|
509
532
|
return {
|
|
510
533
|
state: "waiting",
|
|
511
534
|
task: active.task.name,
|
|
512
|
-
assignees:
|
|
535
|
+
assignees: input.assignees[active.task.name] ?? [],
|
|
513
536
|
actions: (active.task.actions ?? []).map((a) => a.name)
|
|
514
537
|
};
|
|
515
538
|
}
|
|
539
|
+
function blockedState(input) {
|
|
540
|
+
const blocked = input.tasks.find(
|
|
541
|
+
(t) => t.status === "active" && (t.task.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length > 0
|
|
542
|
+
);
|
|
543
|
+
if (blocked !== void 0)
|
|
544
|
+
return {
|
|
545
|
+
state: "blocked",
|
|
546
|
+
task: blocked.task.name,
|
|
547
|
+
requirements: blocked.unmetRequirements ?? [],
|
|
548
|
+
assignees: input.assignees[blocked.task.name] ?? []
|
|
549
|
+
};
|
|
550
|
+
}
|
|
516
551
|
function noTransitionFiresCause(input) {
|
|
517
|
-
if (input.transitions.length
|
|
518
|
-
return
|
|
552
|
+
if (input.transitions.length === 0 || input.transitions.some((t) => t.filterSatisfied) || !input.tasks.every((t) => isResolved(t.status)))
|
|
553
|
+
return;
|
|
554
|
+
const undecidable = input.transitions.filter((t) => t.unevaluable);
|
|
555
|
+
return undecidable.length > 0 ? { kind: "transition-unevaluable", transitions: undecidable.map((t) => t.transition.name) } : { kind: "no-transition-fires" };
|
|
519
556
|
}
|
|
520
557
|
function diagnoseInstance(input) {
|
|
521
558
|
const { instance } = input;
|
|
@@ -526,7 +563,51 @@ function diagnoseInstance(input) {
|
|
|
526
563
|
if (instance.completedAt !== void 0)
|
|
527
564
|
return { state: "completed", at: instance.completedAt };
|
|
528
565
|
const cause = failedEffectCause(input) ?? failedTaskCause(input) ?? hungEffectCause(input) ?? noTransitionFiresCause(input);
|
|
529
|
-
return cause !== void 0 ? { state: "stuck", cause } : waitingState(input) ?? { state: "progressing" };
|
|
566
|
+
return cause !== void 0 ? { state: "stuck", cause } : waitingState(input) ?? blockedState(input) ?? { state: "progressing" };
|
|
567
|
+
}
|
|
568
|
+
const RUNNABLE_VERBS = /* @__PURE__ */ new Set(["set-stage", "abort"]);
|
|
569
|
+
function remediationsForCause(cause) {
|
|
570
|
+
switch (cause.kind) {
|
|
571
|
+
case "failed-effect":
|
|
572
|
+
return [
|
|
573
|
+
{
|
|
574
|
+
verb: "retry-effect",
|
|
575
|
+
rationale: "Re-run the failed effect once the upstream system is healthy."
|
|
576
|
+
},
|
|
577
|
+
{ verb: "abort", rationale: "Abort the instance if it can no longer recover." }
|
|
578
|
+
];
|
|
579
|
+
case "hung-effect":
|
|
580
|
+
return [
|
|
581
|
+
{
|
|
582
|
+
verb: "drain-effects",
|
|
583
|
+
rationale: "Re-run the effect drainer to re-pick the claimed effect."
|
|
584
|
+
},
|
|
585
|
+
{ verb: "abort", rationale: "Abort the instance if the effect never drains." }
|
|
586
|
+
];
|
|
587
|
+
case "failed-task":
|
|
588
|
+
return [
|
|
589
|
+
{ verb: "reset-task", rationale: "Reset or skip the failed task." },
|
|
590
|
+
{
|
|
591
|
+
verb: "set-stage",
|
|
592
|
+
rationale: "Move the instance past the failed task to the intended next stage."
|
|
593
|
+
}
|
|
594
|
+
];
|
|
595
|
+
case "no-transition-fires":
|
|
596
|
+
return [
|
|
597
|
+
{
|
|
598
|
+
verb: "set-stage",
|
|
599
|
+
rationale: "Move the instance to the intended next stage to force it forward."
|
|
600
|
+
}
|
|
601
|
+
];
|
|
602
|
+
case "transition-unevaluable":
|
|
603
|
+
return [];
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
function remediationsFor(diagnosis) {
|
|
607
|
+
return diagnosis.state !== "stuck" ? [] : remediationsForCause(diagnosis.cause).map((seed) => ({
|
|
608
|
+
...seed,
|
|
609
|
+
available: RUNNABLE_VERBS.has(seed.verb)
|
|
610
|
+
}));
|
|
530
611
|
}
|
|
531
612
|
function buildSnapshot(args) {
|
|
532
613
|
const docs = [], knownIds = /* @__PURE__ */ new Set();
|
|
@@ -573,6 +654,7 @@ function collectWatchRefs(instance) {
|
|
|
573
654
|
function readsRaw(ref) {
|
|
574
655
|
return ref.type === WORKFLOW_INSTANCE_TYPE || ref.type === "system.release";
|
|
575
656
|
}
|
|
657
|
+
const DEFAULT_CONTENT_PERSPECTIVE = "drafts";
|
|
576
658
|
function contentReleaseName(args) {
|
|
577
659
|
const { ref, perspective } = args;
|
|
578
660
|
if (!readsRaw(ref) && Array.isArray(perspective))
|
|
@@ -598,6 +680,11 @@ function entryReleaseRefs(state) {
|
|
|
598
680
|
(entry) => entry._type === "release.ref" && isGdr(entry.value) ? [entry.value] : []
|
|
599
681
|
);
|
|
600
682
|
}
|
|
683
|
+
function entrySubjectRefs(state) {
|
|
684
|
+
return stateEntries(state).flatMap(
|
|
685
|
+
(entry) => (entry._type === "doc.ref" || entry._type === "release.ref") && isGdr(entry.value) ? [entry.value] : []
|
|
686
|
+
);
|
|
687
|
+
}
|
|
601
688
|
function stateEntries(state) {
|
|
602
689
|
return Array.isArray(state) ? state.filter(
|
|
603
690
|
(entry) => !!entry && typeof entry == "object"
|
|
@@ -622,7 +709,10 @@ async function hydrateSnapshot(args) {
|
|
|
622
709
|
};
|
|
623
710
|
loaded.push({ doc: instance, resource: instance.workflowResource }), visited.add(selfGdr(instance));
|
|
624
711
|
for (const ref of collectWatchRefs(instance))
|
|
625
|
-
await loadInto(
|
|
712
|
+
await loadInto(
|
|
713
|
+
ref.id,
|
|
714
|
+
readsRaw(ref) ? "raw" : instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
|
|
715
|
+
);
|
|
626
716
|
return buildSnapshot({ docs: loaded });
|
|
627
717
|
}
|
|
628
718
|
async function loadByGdr(defaultClient, clientForGdr, defaultResource, uri, perspective) {
|
|
@@ -637,7 +727,7 @@ async function loadByGdr(defaultClient, clientForGdr, defaultResource, uri, pers
|
|
|
637
727
|
return doc ? { doc, resource: resourceFromParsed(parsed) } : null;
|
|
638
728
|
}
|
|
639
729
|
async function readDoc(client, id, perspective) {
|
|
640
|
-
return perspective ===
|
|
730
|
+
return perspective === "raw" ? await client.getDocument(id) ?? null : await client.fetch("*[_id == $id][0]", { id }, { perspective }) ?? null;
|
|
641
731
|
}
|
|
642
732
|
function resourceFromParsed(parsed) {
|
|
643
733
|
return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
|
|
@@ -738,7 +828,7 @@ async function queryGuardsAcross(clients, query, params) {
|
|
|
738
828
|
function dedupById(guards) {
|
|
739
829
|
return [...new Map(guards.map((g) => [g._id, g])).values()].sort((a, b) => a._id.localeCompare(b._id));
|
|
740
830
|
}
|
|
741
|
-
const GUARD_OWNER = "robot:workflow-engine", GUARD_LIFTED_PREDICATE = "true";
|
|
831
|
+
const SYNC_COMMIT = { visibility: "sync" }, GUARD_OWNER = "robot:workflow-engine", GUARD_LIFTED_PREDICATE = "true";
|
|
742
832
|
function resolveGuard(guard, instance, stageName, now) {
|
|
743
833
|
const ctx = { instance, now }, targets = resolveIdRefTargets(guard.match.idRefs, ctx);
|
|
744
834
|
if (targets === null) return null;
|
|
@@ -765,11 +855,11 @@ function resolveGuard(guard, instance, stageName, now) {
|
|
|
765
855
|
}
|
|
766
856
|
async function upsertGuard(client, doc) {
|
|
767
857
|
if (!await client.getDocument(doc._id)) {
|
|
768
|
-
await client.create(doc);
|
|
858
|
+
await client.create(doc, SYNC_COMMIT);
|
|
769
859
|
return;
|
|
770
860
|
}
|
|
771
861
|
const { _id, _type, _rev, _createdAt, _updatedAt, ...body } = doc;
|
|
772
|
-
await client.patch(doc._id).set(body).commit();
|
|
862
|
+
await client.patch(doc._id).set(body).commit(SYNC_COMMIT);
|
|
773
863
|
}
|
|
774
864
|
function resolvedStageGuards(args) {
|
|
775
865
|
const stage = args.definition.stages.find((s) => s.name === args.stageName), out = [];
|
|
@@ -792,7 +882,7 @@ async function retractStageGuards(args) {
|
|
|
792
882
|
const live = await committedInstance(args);
|
|
793
883
|
if (live !== void 0 && !(live.currentStage === args.stageName && live.abortedAt === void 0))
|
|
794
884
|
for (const { client, doc } of resolvedStageGuards(args))
|
|
795
|
-
await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit();
|
|
885
|
+
await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit(SYNC_COMMIT);
|
|
796
886
|
}
|
|
797
887
|
async function deleteOrphanedDefinitionGuards(args) {
|
|
798
888
|
const { client, clientForGdr, workflowResource, definition, definitions, tag } = args, perClient = await guardsForDefinitionByClient({
|
|
@@ -816,16 +906,32 @@ function randomKey(length = 12) {
|
|
|
816
906
|
const bytes = new Uint8Array(length);
|
|
817
907
|
return globalThis.crypto.getRandomValues(bytes), [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, length);
|
|
818
908
|
}
|
|
819
|
-
|
|
909
|
+
function isUnevaluable(result) {
|
|
910
|
+
return result == null;
|
|
911
|
+
}
|
|
912
|
+
async function evaluateConditionOutcome(args) {
|
|
820
913
|
const { condition, snapshot, params } = args;
|
|
821
|
-
|
|
914
|
+
if (condition === void 0) return "satisfied";
|
|
915
|
+
const result = await runGroq(condition, params, snapshot);
|
|
916
|
+
return isUnevaluable(result) ? "unevaluable" : result ? "satisfied" : "unsatisfied";
|
|
917
|
+
}
|
|
918
|
+
async function evaluateCondition(args) {
|
|
919
|
+
return await evaluateConditionOutcome(args) === "satisfied";
|
|
822
920
|
}
|
|
823
921
|
async function evaluatePredicates(args) {
|
|
824
922
|
const out = {};
|
|
825
|
-
for (const [name, groq] of Object.entries(args.predicates ?? {}))
|
|
826
|
-
|
|
923
|
+
for (const [name, groq] of Object.entries(args.predicates ?? {})) {
|
|
924
|
+
const result = await runGroq(groq, args.params, args.snapshot);
|
|
925
|
+
out[name] = isUnevaluable(result) ? null : !!result;
|
|
926
|
+
}
|
|
827
927
|
return out;
|
|
828
928
|
}
|
|
929
|
+
async function evaluateRequirements(args) {
|
|
930
|
+
const unmet = [];
|
|
931
|
+
for (const [name, condition] of Object.entries(args.requirements ?? {}))
|
|
932
|
+
await evaluateCondition({ condition, snapshot: args.snapshot, params: args.params }) || unmet.push(name);
|
|
933
|
+
return unmet;
|
|
934
|
+
}
|
|
829
935
|
async function runGroq(groq, params, snapshot) {
|
|
830
936
|
const tree = groqJs.parse(groq, { params });
|
|
831
937
|
return (await groqJs.evaluate(tree, { dataset: snapshot.docs, params })).get();
|
|
@@ -993,7 +1099,7 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
|
993
1099
|
tag: ctx.tag
|
|
994
1100
|
});
|
|
995
1101
|
try {
|
|
996
|
-
const fetchOptions =
|
|
1102
|
+
const fetchOptions = { perspective: ctx.perspective ?? DEFAULT_CONTENT_PERSPECTIVE }, result = await client.fetch(source.query, params, fetchOptions);
|
|
997
1103
|
return normalizeQueryResult(entry.type, result, ctx.workflowResource) ?? defaultValue;
|
|
998
1104
|
} catch (err) {
|
|
999
1105
|
throw new Error(
|
|
@@ -1123,13 +1229,16 @@ async function ctxConditionParams(ctx, opts) {
|
|
|
1123
1229
|
params
|
|
1124
1230
|
}), ...params };
|
|
1125
1231
|
}
|
|
1126
|
-
async function
|
|
1127
|
-
return condition === void 0 ?
|
|
1232
|
+
async function ctxEvaluateConditionOutcome(ctx, condition, opts) {
|
|
1233
|
+
return condition === void 0 ? "satisfied" : evaluateConditionOutcome({
|
|
1128
1234
|
condition,
|
|
1129
1235
|
snapshot: ctx.snapshot,
|
|
1130
1236
|
params: await ctxConditionParams(ctx, opts)
|
|
1131
1237
|
});
|
|
1132
1238
|
}
|
|
1239
|
+
async function ctxEvaluateCondition(ctx, condition, opts) {
|
|
1240
|
+
return await ctxEvaluateConditionOutcome(ctx, condition, opts) === "satisfied";
|
|
1241
|
+
}
|
|
1133
1242
|
async function buildEngineContext(args) {
|
|
1134
1243
|
const { client, clientForGdr, instance, definition } = args, clock = args.clock ?? wallClock;
|
|
1135
1244
|
return {
|
|
@@ -1249,7 +1358,7 @@ async function deployOrRollback(args) {
|
|
|
1249
1358
|
});
|
|
1250
1359
|
try {
|
|
1251
1360
|
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();
|
|
1361
|
+
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
1362
|
} catch (rollbackError) {
|
|
1254
1363
|
throw new WorkflowStateDivergedError({
|
|
1255
1364
|
instanceId: args.instanceId,
|
|
@@ -1356,7 +1465,7 @@ async function persist(ctx, mutation) {
|
|
|
1356
1465
|
lastChangedAt: ctx.now
|
|
1357
1466
|
}, pendingCreates = mutation.pendingCreates;
|
|
1358
1467
|
if (pendingCreates.length === 0)
|
|
1359
|
-
return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit();
|
|
1468
|
+
return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit(SYNC_COMMIT);
|
|
1360
1469
|
const tx = ctx.client.transaction();
|
|
1361
1470
|
for (const body of pendingCreates) tx.create(body);
|
|
1362
1471
|
tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)), await tx.commit(), mutation.pendingCreates = [];
|
|
@@ -1509,9 +1618,16 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1509
1618
|
const parentScope = await ctxConditionParams(ctx, {
|
|
1510
1619
|
taskName: task.name,
|
|
1511
1620
|
...actor ? { actor } : {}
|
|
1512
|
-
}), rows = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
|
|
1621
|
+
}), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
|
|
1513
1622
|
for (const row of rows) {
|
|
1514
|
-
const initialState = await projectRowState(
|
|
1623
|
+
const initialState = await projectRowState(
|
|
1624
|
+
ctx,
|
|
1625
|
+
sub,
|
|
1626
|
+
definition,
|
|
1627
|
+
parentScope,
|
|
1628
|
+
row,
|
|
1629
|
+
discoveryResource
|
|
1630
|
+
), { ref, body } = await prepareChildInstance({
|
|
1515
1631
|
client: ctx.client,
|
|
1516
1632
|
parent: ctx.instance,
|
|
1517
1633
|
definition,
|
|
@@ -1526,28 +1642,25 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1526
1642
|
}
|
|
1527
1643
|
async function resolveSpawnRows(ctx, sub) {
|
|
1528
1644
|
const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
|
|
1529
|
-
let value;
|
|
1645
|
+
let value, discoveryResource;
|
|
1530
1646
|
if (groq.includes("*")) {
|
|
1531
|
-
const routingUri =
|
|
1532
|
-
value = await discoverItems({
|
|
1647
|
+
const routingUri = entrySubjectRefs(ctx.instance.state)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
|
|
1648
|
+
client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
|
|
1533
1649
|
client,
|
|
1534
1650
|
groq,
|
|
1535
1651
|
params,
|
|
1536
|
-
|
|
1652
|
+
// Draft-aware by default (drafts overlay published) when there's no
|
|
1653
|
+
// release, so a `forEach` discovers in-flight items the same way the
|
|
1654
|
+
// engine reads any other content.
|
|
1655
|
+
perspective: ctx.instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
|
|
1537
1656
|
});
|
|
1538
1657
|
} else {
|
|
1539
1658
|
const tree = groqJs.parse(groq, { params });
|
|
1540
1659
|
value = await (await groqJs.evaluate(tree, { dataset: [ctx.instance], params, root: ctx.instance })).get();
|
|
1541
1660
|
}
|
|
1542
|
-
return Array.isArray(value) ? value : value == null ? [] : [value];
|
|
1543
|
-
}
|
|
1544
|
-
function firstDocRefGdrUri(entries) {
|
|
1545
|
-
if (entries) {
|
|
1546
|
-
for (const s of entries)
|
|
1547
|
-
if (s._type === "doc.ref" && s.value !== null) return s.value.id;
|
|
1548
|
-
}
|
|
1661
|
+
return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
|
|
1549
1662
|
}
|
|
1550
|
-
async function projectRowState(ctx, sub, childDefinition, parentScope, row) {
|
|
1663
|
+
async function projectRowState(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
|
|
1551
1664
|
const out = [];
|
|
1552
1665
|
for (const [name, groq] of Object.entries(sub.with ?? {})) {
|
|
1553
1666
|
const declared = (childDefinition.state ?? []).find((e) => e.name === name);
|
|
@@ -1555,7 +1668,12 @@ async function projectRowState(ctx, sub, childDefinition, parentScope, row) {
|
|
|
1555
1668
|
throw new Error(
|
|
1556
1669
|
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no state entry "${name}"`
|
|
1557
1670
|
);
|
|
1558
|
-
const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw,
|
|
1671
|
+
const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
|
|
1672
|
+
parentResource: ctx.instance.workflowResource,
|
|
1673
|
+
discoveryResource,
|
|
1674
|
+
entryName: name,
|
|
1675
|
+
childName: childDefinition.name
|
|
1676
|
+
});
|
|
1559
1677
|
value != null && out.push({
|
|
1560
1678
|
type: declared.type,
|
|
1561
1679
|
name,
|
|
@@ -1572,11 +1690,17 @@ async function resolveContextHandoff(ctx, sub, parentScope) {
|
|
|
1572
1690
|
}
|
|
1573
1691
|
return out;
|
|
1574
1692
|
}
|
|
1575
|
-
function canonicaliseSpawnValue(kind, value,
|
|
1576
|
-
return kind === "doc.ref" ? coerceSpawnGdr(value,
|
|
1693
|
+
function canonicaliseSpawnValue(kind, value, cx) {
|
|
1694
|
+
return kind === "doc.ref" ? coerceSpawnGdr(value, cx) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, cx)).filter((v2) => v2 !== null) : value;
|
|
1695
|
+
}
|
|
1696
|
+
function assertBareIdRootsCorrectly(id, cx) {
|
|
1697
|
+
if (!(cx.discoveryResource === void 0 || sameResource(cx.discoveryResource, cx.parentResource)))
|
|
1698
|
+
throw new Error(
|
|
1699
|
+
`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.`
|
|
1700
|
+
);
|
|
1577
1701
|
}
|
|
1578
|
-
function coerceSpawnGdr(raw,
|
|
1579
|
-
const toUri = (id) => isGdrUri(id) ? id : gdrFromResource(
|
|
1702
|
+
function coerceSpawnGdr(raw, cx) {
|
|
1703
|
+
const toUri = (id) => isGdrUri(id) ? id : (assertBareIdRootsCorrectly(id, cx), gdrFromResource(cx.parentResource, id));
|
|
1580
1704
|
if (raw == null) return null;
|
|
1581
1705
|
if (typeof raw == "object" && "id" in raw && "type" in raw) {
|
|
1582
1706
|
const r = raw;
|
|
@@ -2075,21 +2199,24 @@ function resolveMachineStep(mutation, task, entry, now) {
|
|
|
2075
2199
|
async function buildStageTasks(ctx, stage) {
|
|
2076
2200
|
const entries = [];
|
|
2077
2201
|
for (const task of stage.tasks ?? []) {
|
|
2078
|
-
const
|
|
2202
|
+
const outcome = await ctxEvaluateConditionOutcome(ctx, task.filter, { taskName: task.name }), inScope = outcome !== "unsatisfied";
|
|
2079
2203
|
entries.push({
|
|
2080
2204
|
_key: randomKey(),
|
|
2081
2205
|
name: task.name,
|
|
2082
2206
|
status: inScope ? "pending" : "skipped",
|
|
2083
|
-
...task.filter !== void 0 ? {
|
|
2084
|
-
filterEvaluation: {
|
|
2085
|
-
at: ctx.now,
|
|
2086
|
-
truthy: inScope
|
|
2087
|
-
}
|
|
2088
|
-
} : {}
|
|
2207
|
+
...task.filter !== void 0 ? { filterEvaluation: buildFilterEvaluation(ctx.now, task.filter, outcome) } : {}
|
|
2089
2208
|
});
|
|
2090
2209
|
}
|
|
2091
2210
|
return entries;
|
|
2092
2211
|
}
|
|
2212
|
+
function buildFilterEvaluation(at, filter, outcome) {
|
|
2213
|
+
return outcome === "unevaluable" ? {
|
|
2214
|
+
at,
|
|
2215
|
+
truthy: !1,
|
|
2216
|
+
unevaluable: !0,
|
|
2217
|
+
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.`
|
|
2218
|
+
} : { at, truthy: outcome === "satisfied" };
|
|
2219
|
+
}
|
|
2093
2220
|
function isTerminalStage(stage) {
|
|
2094
2221
|
return (stage.transitions ?? []).length === 0;
|
|
2095
2222
|
}
|
|
@@ -2239,8 +2366,11 @@ async function commitTransition(ctx, actor) {
|
|
|
2239
2366
|
};
|
|
2240
2367
|
}
|
|
2241
2368
|
async function pickTransition(ctx, stage) {
|
|
2242
|
-
for (const candidate of stage.transitions ?? [])
|
|
2243
|
-
|
|
2369
|
+
for (const candidate of stage.transitions ?? []) {
|
|
2370
|
+
const outcome = await ctxEvaluateConditionOutcome(ctx, candidate.filter);
|
|
2371
|
+
if (outcome === "satisfied") return candidate;
|
|
2372
|
+
if (outcome === "unevaluable") return;
|
|
2373
|
+
}
|
|
2244
2374
|
}
|
|
2245
2375
|
async function evaluateAutoTransitions(args) {
|
|
2246
2376
|
const { client, instanceId, options, clientForGdr, clock, overlay } = args, ctx = await loadContext(client, instanceId, {
|
|
@@ -2270,7 +2400,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
2270
2400
|
}, committed = await client.patch(instance._id).set({
|
|
2271
2401
|
stages: [initialStageEntry],
|
|
2272
2402
|
lastChangedAt: now
|
|
2273
|
-
}).ifRevisionId(instance._rev).commit();
|
|
2403
|
+
}).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
|
|
2274
2404
|
await deployOrRollback({
|
|
2275
2405
|
client,
|
|
2276
2406
|
instanceId: instance._id,
|
|
@@ -2532,15 +2662,18 @@ async function evaluateFromSnapshot(args) {
|
|
|
2532
2662
|
})
|
|
2533
2663
|
);
|
|
2534
2664
|
const transitionEvaluations = [];
|
|
2535
|
-
for (const transition of stage.transitions ?? [])
|
|
2665
|
+
for (const transition of stage.transitions ?? []) {
|
|
2666
|
+
const outcome = await evaluateConditionOutcome({
|
|
2667
|
+
condition: transition.filter,
|
|
2668
|
+
snapshot,
|
|
2669
|
+
params: scope
|
|
2670
|
+
});
|
|
2536
2671
|
transitionEvaluations.push({
|
|
2537
2672
|
transition,
|
|
2538
|
-
filterSatisfied:
|
|
2539
|
-
|
|
2540
|
-
snapshot,
|
|
2541
|
-
params: scope
|
|
2542
|
-
})
|
|
2673
|
+
filterSatisfied: outcome === "satisfied",
|
|
2674
|
+
unevaluable: outcome === "unevaluable"
|
|
2543
2675
|
});
|
|
2676
|
+
}
|
|
2544
2677
|
const currentStage = {
|
|
2545
2678
|
stage,
|
|
2546
2679
|
tasks: taskEvaluations,
|
|
@@ -2588,7 +2721,11 @@ async function evaluateTask(args) {
|
|
|
2588
2721
|
...scopedStateOverlay(instance, snapshot, task.name)
|
|
2589
2722
|
},
|
|
2590
2723
|
assigned
|
|
2591
|
-
},
|
|
2724
|
+
}, unmetRequirements = await evaluateRequirements({
|
|
2725
|
+
requirements: task.requirements,
|
|
2726
|
+
snapshot,
|
|
2727
|
+
params: taskScope
|
|
2728
|
+
}), requirementsReason = unmetRequirements.length > 0 ? { kind: "requirements-unmet", unmetRequirements } : void 0, actions = [];
|
|
2592
2729
|
for (const action of task.actions ?? [])
|
|
2593
2730
|
actions.push(
|
|
2594
2731
|
await evaluateAction({
|
|
@@ -2598,7 +2735,8 @@ async function evaluateTask(args) {
|
|
|
2598
2735
|
snapshot,
|
|
2599
2736
|
taskScope,
|
|
2600
2737
|
stageHasExits,
|
|
2601
|
-
guardDenial
|
|
2738
|
+
guardDenial,
|
|
2739
|
+
requirementsReason
|
|
2602
2740
|
})
|
|
2603
2741
|
);
|
|
2604
2742
|
return {
|
|
@@ -2609,12 +2747,22 @@ async function evaluateTask(args) {
|
|
|
2609
2747
|
// they're still inbox items. The inbox reads the assignees-kind state
|
|
2610
2748
|
// entry BY KIND (who-data is state).
|
|
2611
2749
|
pendingOnActor: (status === "active" || status === "pending") && assigned,
|
|
2750
|
+
...unmetRequirements.length > 0 ? { unmetRequirements } : {},
|
|
2612
2751
|
actions
|
|
2613
2752
|
};
|
|
2614
2753
|
}
|
|
2615
2754
|
async function evaluateAction(args) {
|
|
2616
|
-
const {
|
|
2617
|
-
|
|
2755
|
+
const {
|
|
2756
|
+
action,
|
|
2757
|
+
status,
|
|
2758
|
+
instance,
|
|
2759
|
+
snapshot,
|
|
2760
|
+
taskScope,
|
|
2761
|
+
stageHasExits,
|
|
2762
|
+
guardDenial,
|
|
2763
|
+
requirementsReason
|
|
2764
|
+
} = args, lifecycle = lifecycleReason(instance, status, stageHasExits);
|
|
2765
|
+
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
2766
|
condition: action.filter,
|
|
2619
2767
|
snapshot,
|
|
2620
2768
|
params: taskScope
|
|
@@ -2891,7 +3039,8 @@ const disabledReasonDetail = {
|
|
|
2891
3039
|
"task-not-active": (r) => `task status is "${r.status}"`,
|
|
2892
3040
|
"stage-terminal": (r) => `stage "${r.stage}" is terminal`,
|
|
2893
3041
|
"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})` : ""}
|
|
3042
|
+
"mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`,
|
|
3043
|
+
"requirements-unmet": (r) => `unmet requirement(s): ${r.unmetRequirements.join(", ")}`
|
|
2895
3044
|
};
|
|
2896
3045
|
function formatDisabledReason(task, action, reason) {
|
|
2897
3046
|
const detail = disabledReasonDetail[reason.kind](reason);
|
|
@@ -3146,7 +3295,7 @@ const workflow = {
|
|
|
3146
3295
|
initialStage: definition.initialStage,
|
|
3147
3296
|
actor
|
|
3148
3297
|
});
|
|
3149
|
-
return await client.create(base), await primeInitialStage(client, id, actor, clientForGdr, clock), await cascade(client, id, actor, clientForGdr, clock), reload(client, id, tag);
|
|
3298
|
+
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
3299
|
},
|
|
3151
3300
|
/**
|
|
3152
3301
|
* Fire an action against a task. If the task is pending, it is
|
|
@@ -3386,8 +3535,8 @@ const workflow = {
|
|
|
3386
3535
|
* Pure read.
|
|
3387
3536
|
*/
|
|
3388
3537
|
diagnose: async (args) => {
|
|
3389
|
-
const evaluation = await evaluateInstance(args);
|
|
3390
|
-
return { evaluation, diagnosis:
|
|
3538
|
+
const evaluation = await evaluateInstance(args), diagnosis = diagnoseInstance(diagnoseInputFromEvaluation(evaluation));
|
|
3539
|
+
return { evaluation, diagnosis, remediations: remediationsFor(diagnosis) };
|
|
3391
3540
|
},
|
|
3392
3541
|
/**
|
|
3393
3542
|
* List the actions an actor could fire on an instance's current stage,
|
|
@@ -3556,7 +3705,7 @@ async function claimPendingEffect(client, instance, effectKey, drainerActor) {
|
|
|
3556
3705
|
claimedBy: drainerActor
|
|
3557
3706
|
};
|
|
3558
3707
|
try {
|
|
3559
|
-
return await client.patch(instance._id).ifRevisionId(instance._rev).set({ [`pendingEffects[_key=="${effectKey}"].claim`]: claim }).commit(), !0;
|
|
3708
|
+
return await client.patch(instance._id).ifRevisionId(instance._rev).set({ [`pendingEffects[_key=="${effectKey}"].claim`]: claim }).commit(SYNC_COMMIT), !0;
|
|
3560
3709
|
} catch (error) {
|
|
3561
3710
|
if (!isRevisionConflict(error)) throw error;
|
|
3562
3711
|
return !1;
|
|
@@ -4009,6 +4158,7 @@ exports.ActionDisabledError = ActionDisabledError;
|
|
|
4009
4158
|
exports.ActionParamsInvalidError = ActionParamsInvalidError;
|
|
4010
4159
|
exports.CascadeLimitError = CascadeLimitError;
|
|
4011
4160
|
exports.ConcurrentFireActionError = ConcurrentFireActionError;
|
|
4161
|
+
exports.DEFAULT_CONTENT_PERSPECTIVE = DEFAULT_CONTENT_PERSPECTIVE;
|
|
4012
4162
|
exports.DISPLAY = DISPLAY;
|
|
4013
4163
|
exports.EFFECTS_CONTEXT_DISPLAY = EFFECTS_CONTEXT_DISPLAY;
|
|
4014
4164
|
exports.GUARD_DOC_TYPE = GUARD_DOC_TYPE;
|
|
@@ -4022,7 +4172,6 @@ exports.WORKFLOW_INSTANCE_TYPE = WORKFLOW_INSTANCE_TYPE;
|
|
|
4022
4172
|
exports.WorkflowStateDivergedError = WorkflowStateDivergedError;
|
|
4023
4173
|
exports.abortReason = abortReason;
|
|
4024
4174
|
exports.actionVerdict = actionVerdict;
|
|
4025
|
-
exports.assigneesOf = assigneesOf;
|
|
4026
4175
|
exports.availableActions = availableActions;
|
|
4027
4176
|
exports.buildSnapshot = buildSnapshot;
|
|
4028
4177
|
exports.compileGuard = compileGuard;
|
|
@@ -4054,13 +4203,13 @@ exports.isDefinitionUnchanged = isDefinitionUnchanged;
|
|
|
4054
4203
|
exports.isGdr = isGdr;
|
|
4055
4204
|
exports.isTerminalStage = isTerminalStage;
|
|
4056
4205
|
exports.lakeGuardId = lakeGuardId;
|
|
4057
|
-
exports.openStage = openStage;
|
|
4058
4206
|
exports.parseGdr = parseGdr;
|
|
4059
4207
|
exports.readsRaw = readsRaw;
|
|
4060
4208
|
exports.refCanvas = refCanvas;
|
|
4061
4209
|
exports.refDashboard = refDashboard;
|
|
4062
4210
|
exports.refDataset = refDataset;
|
|
4063
4211
|
exports.refMediaLibrary = refMediaLibrary;
|
|
4212
|
+
exports.remediationsFor = remediationsFor;
|
|
4064
4213
|
exports.resolveAccess = resolveAccess;
|
|
4065
4214
|
exports.resourceFromParsed = resourceFromParsed;
|
|
4066
4215
|
exports.retractStageGuards = retractStageGuards;
|