@sanity/workflow-engine 0.4.0 → 0.6.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.map +1 -1
- package/dist/_chunks-es/schema.js.map +1 -1
- package/dist/index.cjs +876 -639
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +343 -58
- package/dist/index.d.ts +343 -58
- package/dist/index.js +876 -639
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -246,6 +246,20 @@ function effectBindings(effects) {
|
|
|
246
246
|
(e) => Object.entries(e.bindings ?? {}).map(([k, groq]) => [`${e.name}.${k}`, groq])
|
|
247
247
|
);
|
|
248
248
|
}
|
|
249
|
+
function actionVerdict(task, action) {
|
|
250
|
+
return {
|
|
251
|
+
task: task.task.name,
|
|
252
|
+
taskStatus: task.status,
|
|
253
|
+
action: action.action.name,
|
|
254
|
+
title: action.action.title,
|
|
255
|
+
allowed: action.allowed,
|
|
256
|
+
disabledReason: action.disabledReason,
|
|
257
|
+
params: action.action.params ?? []
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function availableActions(tasks) {
|
|
261
|
+
return tasks.flatMap((t) => t.actions.map((a) => actionVerdict(t, a)));
|
|
262
|
+
}
|
|
249
263
|
const wallClock = () => (/* @__PURE__ */ new Date()).toISOString(), GUARD_DOC_TYPE = "temp.system.guard";
|
|
250
264
|
class MutationGuardDeniedError extends Error {
|
|
251
265
|
denied;
|
|
@@ -440,79 +454,63 @@ function stripStateForLake(value) {
|
|
|
440
454
|
function bareId(id) {
|
|
441
455
|
return isGdrUri(id) ? extractDocumentId(id) : id;
|
|
442
456
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const resource = assertSingleResource(targets), types = resolveMatchTypes(targets, guard.match.types);
|
|
448
|
-
return { doc: compileGuard({
|
|
449
|
-
id: lakeGuardId({ instanceDocId: instance._id, guardName: guard.name }),
|
|
450
|
-
resourceType: resource.type,
|
|
451
|
-
resourceId: resource.id,
|
|
452
|
-
owner: GUARD_OWNER,
|
|
453
|
-
sourceInstanceId: instance._id,
|
|
454
|
-
sourceDefinition: instance.definition,
|
|
455
|
-
sourceStage: stageName,
|
|
456
|
-
name: guard.name,
|
|
457
|
-
...guard.description !== void 0 ? { description: guard.description } : {},
|
|
458
|
-
match: {
|
|
459
|
-
...types !== void 0 ? { types } : {},
|
|
460
|
-
idRefs: bareIdRefs(targets),
|
|
461
|
-
...guard.match.idPatterns !== void 0 ? { idPatterns: guard.match.idPatterns } : {},
|
|
462
|
-
actions: guard.match.actions
|
|
463
|
-
},
|
|
464
|
-
predicate: guard.predicate ?? "",
|
|
465
|
-
metadata: resolveMetadata(guard.metadata, ctx)
|
|
466
|
-
}), routeGdr: targets[0].parsed };
|
|
457
|
+
function abortReason(instance) {
|
|
458
|
+
return instance.history.find(
|
|
459
|
+
(h) => h._type === "aborted"
|
|
460
|
+
)?.reason;
|
|
467
461
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
await client.patch(doc._id).set(body).commit();
|
|
462
|
+
function diagnoseInputFromEvaluation(evaluation) {
|
|
463
|
+
return {
|
|
464
|
+
instance: evaluation.instance,
|
|
465
|
+
tasks: evaluation.currentStage.tasks,
|
|
466
|
+
transitions: evaluation.currentStage.transitions
|
|
467
|
+
};
|
|
475
468
|
}
|
|
476
|
-
function
|
|
477
|
-
|
|
478
|
-
for (const guard of stage?.guards ?? []) {
|
|
479
|
-
const resolved = resolveGuard(guard, args.instance, args.stageName, args.now);
|
|
480
|
-
resolved !== null && out.push({ client: args.clientForGdr(resolved.routeGdr), doc: resolved.doc });
|
|
481
|
-
}
|
|
482
|
-
return out;
|
|
469
|
+
function openStage(instance) {
|
|
470
|
+
return findOpenStageEntry(instance);
|
|
483
471
|
}
|
|
484
|
-
|
|
485
|
-
return
|
|
472
|
+
function assigneesOf(stage, taskName) {
|
|
473
|
+
return (stage?.tasks.find((t) => t.name === taskName)?.state ?? []).filter((s) => s._type === "assignees").flatMap((s) => s.value);
|
|
486
474
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
if (!(live === void 0 || live.currentStage !== args.stageName) && live.abortedAt === void 0)
|
|
490
|
-
for (const { client, doc } of resolvedStageGuards(args))
|
|
491
|
-
await upsertGuard(client, doc);
|
|
475
|
+
function isResolved(status) {
|
|
476
|
+
return status === "done" || status === "skipped";
|
|
492
477
|
}
|
|
493
|
-
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
for (const { client, doc } of resolvedStageGuards(args))
|
|
497
|
-
await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit();
|
|
478
|
+
function failedEffectCause(input) {
|
|
479
|
+
const stalled = new Set(input.tasks.filter((t) => !isResolved(t.status)).map((t) => t.task.name)), effect = [...input.instance.effectHistory].reverse().find((e) => e.status === "failed" && e.origin.kind === "task" && stalled.has(e.origin.name));
|
|
480
|
+
return effect ? { kind: "failed-effect", effect } : void 0;
|
|
498
481
|
}
|
|
499
|
-
function
|
|
500
|
-
const
|
|
501
|
-
return
|
|
482
|
+
function hungEffectCause(input) {
|
|
483
|
+
const effect = input.instance.pendingEffects.find((e) => e.claim !== void 0);
|
|
484
|
+
return effect ? { kind: "hung-effect", effect } : void 0;
|
|
502
485
|
}
|
|
503
|
-
|
|
504
|
-
const
|
|
505
|
-
return
|
|
486
|
+
function failedTaskCause(input) {
|
|
487
|
+
const failed = input.tasks.find((t) => t.status === "failed");
|
|
488
|
+
return failed ? { kind: "failed-task", task: failed.task.name } : void 0;
|
|
506
489
|
}
|
|
507
|
-
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
490
|
+
function waitingState(input) {
|
|
491
|
+
const active = input.tasks.find((t) => t.status === "active" && (t.task.actions ?? []).length > 0);
|
|
492
|
+
if (active !== void 0)
|
|
493
|
+
return {
|
|
494
|
+
state: "waiting",
|
|
495
|
+
task: active.task.name,
|
|
496
|
+
assignees: assigneesOf(openStage(input.instance), active.task.name),
|
|
497
|
+
actions: (active.task.actions ?? []).map((a) => a.name)
|
|
498
|
+
};
|
|
512
499
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
500
|
+
function noTransitionFiresCause(input) {
|
|
501
|
+
if (input.transitions.length !== 0 && !input.transitions.some((t) => t.filterSatisfied) && input.tasks.every((t) => isResolved(t.status)))
|
|
502
|
+
return { kind: "no-transition-fires" };
|
|
503
|
+
}
|
|
504
|
+
function diagnoseInstance(input) {
|
|
505
|
+
const { instance } = input;
|
|
506
|
+
if (instance.abortedAt !== void 0) {
|
|
507
|
+
const reason = abortReason(instance);
|
|
508
|
+
return { state: "aborted", at: instance.abortedAt, ...reason !== void 0 ? { reason } : {} };
|
|
509
|
+
}
|
|
510
|
+
if (instance.completedAt !== void 0)
|
|
511
|
+
return { state: "completed", at: instance.completedAt };
|
|
512
|
+
const cause = failedEffectCause(input) ?? failedTaskCause(input) ?? hungEffectCause(input) ?? noTransitionFiresCause(input);
|
|
513
|
+
return cause !== void 0 ? { state: "stuck", cause } : waitingState(input) ?? { state: "progressing" };
|
|
516
514
|
}
|
|
517
515
|
function buildSnapshot(args) {
|
|
518
516
|
const docs = [], knownIds = /* @__PURE__ */ new Set();
|
|
@@ -631,6 +629,191 @@ function resourceFromParsed(parsed) {
|
|
|
631
629
|
function collectEntryDocUris(resolvedStateEntries) {
|
|
632
630
|
return entryDocRefs(resolvedStateEntries).map((ref) => ref.id);
|
|
633
631
|
}
|
|
632
|
+
const resourceKey = (r) => `${r.type}.${r.id}`;
|
|
633
|
+
function guardsForResource(client) {
|
|
634
|
+
return client.fetch("*[_type == $t] | order(_id asc)", { t: GUARD_DOC_TYPE });
|
|
635
|
+
}
|
|
636
|
+
function instanceGuardQuery(instanceId) {
|
|
637
|
+
return {
|
|
638
|
+
query: "*[_type == $guardType && sourceInstanceId == $instanceId]",
|
|
639
|
+
params: { guardType: GUARD_DOC_TYPE, instanceId }
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
function verdictGuardsForInstance(client, instanceId) {
|
|
643
|
+
const { query, params } = instanceGuardQuery(instanceId);
|
|
644
|
+
return client.fetch(query, params);
|
|
645
|
+
}
|
|
646
|
+
async function guardsForInstance(args) {
|
|
647
|
+
const { client, clientForGdr, instance } = args, stateUris = [
|
|
648
|
+
...collectEntryDocUris(instance.state),
|
|
649
|
+
...instance.stages.flatMap((stage) => collectEntryDocUris(stage.state))
|
|
650
|
+
], clients = resourceClientMap(
|
|
651
|
+
instance.workflowResource,
|
|
652
|
+
client,
|
|
653
|
+
clientForGdr,
|
|
654
|
+
stateUris.map((uri) => tryParseGdr(uri))
|
|
655
|
+
), { query, params } = instanceGuardQuery(instance._id);
|
|
656
|
+
return queryGuardsAcross(clients.values(), query, params);
|
|
657
|
+
}
|
|
658
|
+
async function guardsForDefinition(args) {
|
|
659
|
+
const perClient = await guardsForDefinitionByClient(args);
|
|
660
|
+
return dedupById(perClient.flatMap((entry) => entry.guards));
|
|
661
|
+
}
|
|
662
|
+
async function guardsForDefinitionByClient(args) {
|
|
663
|
+
const { client, clientForGdr, workflowResource, definition, definitions } = args, gdrs = definitions.flatMap(
|
|
664
|
+
(def) => guardIdRefs(def).map(({ idRef, stage }) => staticIdRefGdr(idRef, stage, def))
|
|
665
|
+
), clients = resourceClientMap(workflowResource, client, clientForGdr, gdrs), query = "*[_type == $t && sourceDefinition == $definition]", params = { t: GUARD_DOC_TYPE, definition };
|
|
666
|
+
return Promise.all(
|
|
667
|
+
[...clients.values()].map(async (resourceClient) => ({
|
|
668
|
+
client: resourceClient,
|
|
669
|
+
guards: await resourceClient.fetch(query, params)
|
|
670
|
+
}))
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
function guardIdRefs(definition) {
|
|
674
|
+
return definition.stages.flatMap(
|
|
675
|
+
(stage) => (stage.guards ?? []).flatMap(
|
|
676
|
+
(guard) => (guard.match.idRefs ?? []).map((idRef) => ({ idRef, stage }))
|
|
677
|
+
)
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
function staticIdRefGdr(idRef, stage, definition) {
|
|
681
|
+
const read = /^\$state\.([\w-]+)/.exec(idRef);
|
|
682
|
+
if (read === null) return;
|
|
683
|
+
const source = entriesInScope(stage, definition).find((e) => e.name === read[1])?.source;
|
|
684
|
+
return source?.type === "literal" ? gdrFromValue(source.value) : void 0;
|
|
685
|
+
}
|
|
686
|
+
function entriesInScope(stage, definition) {
|
|
687
|
+
return [
|
|
688
|
+
...definition.state ?? [],
|
|
689
|
+
...stage.state ?? [],
|
|
690
|
+
...(stage.tasks ?? []).flatMap((task) => task.state ?? [])
|
|
691
|
+
];
|
|
692
|
+
}
|
|
693
|
+
function gdrFromValue(value) {
|
|
694
|
+
if (typeof value == "string") return tryParseGdr(value);
|
|
695
|
+
if (typeof value == "object" && value !== null && "id" in value) {
|
|
696
|
+
const id = value.id;
|
|
697
|
+
return typeof id == "string" ? tryParseGdr(id) : void 0;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
function tryParseGdr(uri) {
|
|
701
|
+
try {
|
|
702
|
+
return parseGdr(uri);
|
|
703
|
+
} catch {
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
function resourceClientMap(base, baseClient, clientForGdr, gdrs) {
|
|
708
|
+
const map = /* @__PURE__ */ new Map([[resourceKey(base), baseClient]]);
|
|
709
|
+
for (const parsed of gdrs) {
|
|
710
|
+
if (parsed === void 0) continue;
|
|
711
|
+
const key = resourceKey(resourceFromParsed(parsed));
|
|
712
|
+
map.has(key) || map.set(key, clientForGdr(parsed));
|
|
713
|
+
}
|
|
714
|
+
return map;
|
|
715
|
+
}
|
|
716
|
+
async function queryGuardsAcross(clients, query, params) {
|
|
717
|
+
const perResource = await Promise.all(
|
|
718
|
+
[...clients].map((c) => c.fetch(query, params))
|
|
719
|
+
);
|
|
720
|
+
return dedupById(perResource.flat());
|
|
721
|
+
}
|
|
722
|
+
function dedupById(guards) {
|
|
723
|
+
return [...new Map(guards.map((g) => [g._id, g])).values()].sort((a, b) => a._id.localeCompare(b._id));
|
|
724
|
+
}
|
|
725
|
+
const GUARD_OWNER = "robot:workflow-engine", GUARD_LIFTED_PREDICATE = "true";
|
|
726
|
+
function resolveGuard(guard, instance, stageName, now) {
|
|
727
|
+
const ctx = { instance, now }, targets = resolveIdRefTargets(guard.match.idRefs, ctx);
|
|
728
|
+
if (targets === null) return null;
|
|
729
|
+
const resource = assertSingleResource(targets), types = resolveMatchTypes(targets, guard.match.types);
|
|
730
|
+
return { doc: compileGuard({
|
|
731
|
+
id: lakeGuardId({ instanceDocId: instance._id, guardName: guard.name }),
|
|
732
|
+
resourceType: resource.type,
|
|
733
|
+
resourceId: resource.id,
|
|
734
|
+
owner: GUARD_OWNER,
|
|
735
|
+
sourceInstanceId: instance._id,
|
|
736
|
+
sourceDefinition: instance.definition,
|
|
737
|
+
sourceStage: stageName,
|
|
738
|
+
name: guard.name,
|
|
739
|
+
...guard.description !== void 0 ? { description: guard.description } : {},
|
|
740
|
+
match: {
|
|
741
|
+
...types !== void 0 ? { types } : {},
|
|
742
|
+
idRefs: bareIdRefs(targets),
|
|
743
|
+
...guard.match.idPatterns !== void 0 ? { idPatterns: guard.match.idPatterns } : {},
|
|
744
|
+
actions: guard.match.actions
|
|
745
|
+
},
|
|
746
|
+
predicate: guard.predicate ?? "",
|
|
747
|
+
metadata: resolveMetadata(guard.metadata, ctx)
|
|
748
|
+
}), routeGdr: targets[0].parsed };
|
|
749
|
+
}
|
|
750
|
+
async function upsertGuard(client, doc) {
|
|
751
|
+
if (!await client.getDocument(doc._id)) {
|
|
752
|
+
await client.create(doc);
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
const { _id, _type, _rev, _createdAt, _updatedAt, ...body } = doc;
|
|
756
|
+
await client.patch(doc._id).set(body).commit();
|
|
757
|
+
}
|
|
758
|
+
function resolvedStageGuards(args) {
|
|
759
|
+
const stage = args.definition.stages.find((s) => s.name === args.stageName), out = [];
|
|
760
|
+
for (const guard of stage?.guards ?? []) {
|
|
761
|
+
const resolved = resolveGuard(guard, args.instance, args.stageName, args.now);
|
|
762
|
+
resolved !== null && out.push({ client: args.clientForGdr(resolved.routeGdr), doc: resolved.doc });
|
|
763
|
+
}
|
|
764
|
+
return out;
|
|
765
|
+
}
|
|
766
|
+
async function committedInstance(args) {
|
|
767
|
+
return await args.client.getDocument(args.instance._id) ?? void 0;
|
|
768
|
+
}
|
|
769
|
+
async function deployStageGuards(args) {
|
|
770
|
+
const live = await committedInstance(args);
|
|
771
|
+
if (!(live === void 0 || live.currentStage !== args.stageName) && live.abortedAt === void 0)
|
|
772
|
+
for (const { client, doc } of resolvedStageGuards(args))
|
|
773
|
+
await upsertGuard(client, doc);
|
|
774
|
+
}
|
|
775
|
+
async function retractStageGuards(args) {
|
|
776
|
+
const live = await committedInstance(args);
|
|
777
|
+
if (live !== void 0 && !(live.currentStage === args.stageName && live.abortedAt === void 0))
|
|
778
|
+
for (const { client, doc } of resolvedStageGuards(args))
|
|
779
|
+
await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit();
|
|
780
|
+
}
|
|
781
|
+
async function deleteOrphanedDefinitionGuards(args) {
|
|
782
|
+
const { client, clientForGdr, workflowResource, definition, definitions, tag } = args, perClient = await guardsForDefinitionByClient({
|
|
783
|
+
client,
|
|
784
|
+
clientForGdr,
|
|
785
|
+
workflowResource,
|
|
786
|
+
definition,
|
|
787
|
+
definitions
|
|
788
|
+
}), ownPartitionPrefix = `${tag}.`;
|
|
789
|
+
let count = 0;
|
|
790
|
+
for (const { client: resourceClient, guards } of perClient) {
|
|
791
|
+
const own = guards.filter((guard) => guard.sourceInstanceId.startsWith(ownPartitionPrefix));
|
|
792
|
+
if (own.length === 0) continue;
|
|
793
|
+
const tx = resourceClient.transaction();
|
|
794
|
+
for (const guard of own) tx.delete(guard._id);
|
|
795
|
+
await tx.commit(), count += own.length;
|
|
796
|
+
}
|
|
797
|
+
return count;
|
|
798
|
+
}
|
|
799
|
+
function randomKey(length = 12) {
|
|
800
|
+
const bytes = new Uint8Array(length);
|
|
801
|
+
return globalThis.crypto.getRandomValues(bytes), [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, length);
|
|
802
|
+
}
|
|
803
|
+
async function evaluateCondition(args) {
|
|
804
|
+
const { condition, snapshot, params } = args;
|
|
805
|
+
return condition === void 0 ? !0 : !!await runGroq(condition, params, snapshot);
|
|
806
|
+
}
|
|
807
|
+
async function evaluatePredicates(args) {
|
|
808
|
+
const out = {};
|
|
809
|
+
for (const [name, groq] of Object.entries(args.predicates ?? {}))
|
|
810
|
+
out[name] = !!await runGroq(groq, args.params, args.snapshot);
|
|
811
|
+
return out;
|
|
812
|
+
}
|
|
813
|
+
async function runGroq(groq, params, snapshot) {
|
|
814
|
+
const tree = parse(groq, { params });
|
|
815
|
+
return (await evaluate(tree, { dataset: snapshot.docs, params })).get();
|
|
816
|
+
}
|
|
634
817
|
class StateValueShapeError extends Error {
|
|
635
818
|
entryType;
|
|
636
819
|
entryName;
|
|
@@ -791,7 +974,7 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
|
791
974
|
stage: ctx.stageName ?? null,
|
|
792
975
|
task: ctx.taskName ?? null,
|
|
793
976
|
now: ctx.now,
|
|
794
|
-
|
|
977
|
+
tag: ctx.tag
|
|
795
978
|
});
|
|
796
979
|
try {
|
|
797
980
|
const fetchOptions = ctx.perspective !== void 0 ? { perspective: ctx.perspective } : void 0, result = await client.fetch(source.query, params, fetchOptions);
|
|
@@ -958,7 +1141,7 @@ async function resolveStageStateEntries(args) {
|
|
|
958
1141
|
client,
|
|
959
1142
|
now,
|
|
960
1143
|
selfId: instance._id,
|
|
961
|
-
|
|
1144
|
+
tag: instance.tag,
|
|
962
1145
|
stageName: stage.name,
|
|
963
1146
|
workflowResource: instance.workflowResource,
|
|
964
1147
|
// Allow stage-scope entries' `source: { type: "stateRead", scope:
|
|
@@ -978,7 +1161,7 @@ async function resolveTaskStateEntries(args) {
|
|
|
978
1161
|
client,
|
|
979
1162
|
now,
|
|
980
1163
|
selfId: instance._id,
|
|
981
|
-
|
|
1164
|
+
tag: instance.tag,
|
|
982
1165
|
stageName: stage.name,
|
|
983
1166
|
workflowResource: instance.workflowResource,
|
|
984
1167
|
taskName: task.name,
|
|
@@ -988,53 +1171,6 @@ async function resolveTaskStateEntries(args) {
|
|
|
988
1171
|
randomKey
|
|
989
1172
|
});
|
|
990
1173
|
}
|
|
991
|
-
async function resolveBindings(args) {
|
|
992
|
-
const resolved = {};
|
|
993
|
-
for (const [key, groq] of Object.entries(args.bindings ?? {}))
|
|
994
|
-
resolved[key] = await runGroq(groq, args.params, args.snapshot);
|
|
995
|
-
return { ...resolved, ...args.staticInput };
|
|
996
|
-
}
|
|
997
|
-
function effectsContextEntry(name, value) {
|
|
998
|
-
const _key = randomKey();
|
|
999
|
-
return typeof value == "string" ? { _key, _type: "effectsContext.string", name, value } : typeof value == "number" ? { _key, _type: "effectsContext.number", name, value } : typeof value == "boolean" ? { _key, _type: "effectsContext.boolean", name, value } : { _key, _type: "effectsContext.ref", name, value };
|
|
1000
|
-
}
|
|
1001
|
-
function effectsContextJsonEntry(name, value) {
|
|
1002
|
-
return { _key: randomKey(), _type: "effectsContext.json", name, value: JSON.stringify(value) };
|
|
1003
|
-
}
|
|
1004
|
-
function buildInstanceBase(args) {
|
|
1005
|
-
const { id, now, actor, perspective } = args;
|
|
1006
|
-
return {
|
|
1007
|
-
_id: id,
|
|
1008
|
-
_type: WORKFLOW_INSTANCE_TYPE,
|
|
1009
|
-
_rev: "",
|
|
1010
|
-
_createdAt: now,
|
|
1011
|
-
_updatedAt: now,
|
|
1012
|
-
tags: args.tags,
|
|
1013
|
-
workflowResource: args.workflowResource,
|
|
1014
|
-
definition: args.definitionName,
|
|
1015
|
-
pinnedVersion: args.pinnedVersion,
|
|
1016
|
-
definitionSnapshot: JSON.stringify(args.definition),
|
|
1017
|
-
state: args.state,
|
|
1018
|
-
effectsContext: args.effectsContext,
|
|
1019
|
-
ancestors: args.ancestors,
|
|
1020
|
-
...perspective !== void 0 ? { perspective } : {},
|
|
1021
|
-
currentStage: args.initialStage,
|
|
1022
|
-
stages: [],
|
|
1023
|
-
pendingEffects: [],
|
|
1024
|
-
effectHistory: [],
|
|
1025
|
-
history: [
|
|
1026
|
-
{
|
|
1027
|
-
_key: randomKey(),
|
|
1028
|
-
_type: "stageEntered",
|
|
1029
|
-
at: now,
|
|
1030
|
-
stage: args.initialStage,
|
|
1031
|
-
...actor !== void 0 ? { actor } : {}
|
|
1032
|
-
}
|
|
1033
|
-
],
|
|
1034
|
-
startedAt: now,
|
|
1035
|
-
lastChangedAt: now
|
|
1036
|
-
};
|
|
1037
|
-
}
|
|
1038
1174
|
class ActionParamsInvalidError extends Error {
|
|
1039
1175
|
action;
|
|
1040
1176
|
task;
|
|
@@ -1154,21 +1290,108 @@ function stageTransitionHistory(args) {
|
|
|
1154
1290
|
}
|
|
1155
1291
|
];
|
|
1156
1292
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
)
|
|
1293
|
+
function startMutation(instance) {
|
|
1294
|
+
return {
|
|
1295
|
+
currentStage: instance.currentStage,
|
|
1296
|
+
// Deep-ish copy. Per-scope state entry arrays need their own copies
|
|
1297
|
+
// so ops can mutate without leaking into the source.
|
|
1298
|
+
state: (instance.state ?? []).map((s) => ({ ...s })),
|
|
1299
|
+
stages: instance.stages.map((s) => ({
|
|
1300
|
+
...s,
|
|
1301
|
+
state: (s.state ?? []).map((entry) => ({ ...entry })),
|
|
1302
|
+
tasks: s.tasks.map((t) => ({
|
|
1303
|
+
...t,
|
|
1304
|
+
...t.state !== void 0 ? { state: t.state.map((entry) => ({ ...entry })) } : {}
|
|
1305
|
+
}))
|
|
1306
|
+
})),
|
|
1307
|
+
pendingEffects: [...instance.pendingEffects],
|
|
1308
|
+
effectHistory: [...instance.effectHistory],
|
|
1309
|
+
effectsContext: [...instance.effectsContext],
|
|
1310
|
+
history: [...instance.history],
|
|
1311
|
+
lastChangedAt: instance.lastChangedAt,
|
|
1312
|
+
...instance.completedAt !== void 0 ? { completedAt: instance.completedAt } : {},
|
|
1313
|
+
...instance.abortedAt !== void 0 ? { abortedAt: instance.abortedAt } : {},
|
|
1314
|
+
pendingCreates: []
|
|
1315
|
+
};
|
|
1166
1316
|
}
|
|
1167
|
-
function
|
|
1168
|
-
|
|
1317
|
+
function instanceStateFields(src) {
|
|
1318
|
+
const fields = {
|
|
1319
|
+
currentStage: src.currentStage,
|
|
1320
|
+
state: src.state,
|
|
1321
|
+
stages: src.stages,
|
|
1322
|
+
pendingEffects: src.pendingEffects,
|
|
1323
|
+
effectHistory: src.effectHistory,
|
|
1324
|
+
effectsContext: src.effectsContext,
|
|
1325
|
+
history: src.history
|
|
1326
|
+
};
|
|
1327
|
+
return src.completedAt !== void 0 && (fields.completedAt = src.completedAt), src.abortedAt !== void 0 && (fields.abortedAt = src.abortedAt), fields;
|
|
1169
1328
|
}
|
|
1170
|
-
function
|
|
1171
|
-
return
|
|
1329
|
+
function materializeInstance(base, mutation) {
|
|
1330
|
+
return {
|
|
1331
|
+
...base,
|
|
1332
|
+
currentStage: mutation.currentStage,
|
|
1333
|
+
state: mutation.state,
|
|
1334
|
+
stages: mutation.stages
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
async function persist(ctx, mutation) {
|
|
1338
|
+
const set = {
|
|
1339
|
+
...instanceStateFields(mutation),
|
|
1340
|
+
lastChangedAt: ctx.now
|
|
1341
|
+
}, pendingCreates = mutation.pendingCreates;
|
|
1342
|
+
if (pendingCreates.length === 0)
|
|
1343
|
+
return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit();
|
|
1344
|
+
const tx = ctx.client.transaction();
|
|
1345
|
+
for (const body of pendingCreates) tx.create(body);
|
|
1346
|
+
tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)), await tx.commit(), mutation.pendingCreates = [];
|
|
1347
|
+
const actorForPriming = void 0;
|
|
1348
|
+
for (const body of pendingCreates)
|
|
1349
|
+
await primeInitialStage(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock), await cascadeAutoTransitions(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock), await propagateToAncestors(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock);
|
|
1350
|
+
const reloaded = await ctx.client.getDocument(ctx.instance._id);
|
|
1351
|
+
if (!reloaded)
|
|
1352
|
+
throw new Error(`Instance ${ctx.instance._id} disappeared after transaction commit`);
|
|
1353
|
+
return reloaded;
|
|
1354
|
+
}
|
|
1355
|
+
function currentStageEntry(mutation) {
|
|
1356
|
+
const entry = findOpenStageEntry(mutation);
|
|
1357
|
+
if (entry === void 0)
|
|
1358
|
+
throw new Error(
|
|
1359
|
+
`Mutation invariant broken: no current (un-exited) StageEntry for currentStage "${mutation.currentStage}"`
|
|
1360
|
+
);
|
|
1361
|
+
return entry;
|
|
1362
|
+
}
|
|
1363
|
+
function currentTasks(mutation) {
|
|
1364
|
+
return currentStageEntry(mutation).tasks;
|
|
1365
|
+
}
|
|
1366
|
+
function findTaskInCurrentStage(ctx, taskName) {
|
|
1367
|
+
const stage = findStage(ctx.definition, ctx.instance.currentStage), task = (stage.tasks ?? []).find((t) => t.name === taskName);
|
|
1368
|
+
if (task === void 0)
|
|
1369
|
+
throw new Error(
|
|
1370
|
+
`Task "${taskName}" not found in current stage "${stage.name}" of ${ctx.definition.name}`
|
|
1371
|
+
);
|
|
1372
|
+
return { stage, task };
|
|
1373
|
+
}
|
|
1374
|
+
function requireMutationTaskEntry(mutation, task) {
|
|
1375
|
+
const mutEntry = currentTasks(mutation).find((t) => t.name === task);
|
|
1376
|
+
if (mutEntry === void 0)
|
|
1377
|
+
throw new Error(`Task "${task}" disappeared from mutation copy \u2014 invariant broken`);
|
|
1378
|
+
return mutEntry;
|
|
1379
|
+
}
|
|
1380
|
+
function findCurrentStageEntry(instance) {
|
|
1381
|
+
return findOpenStageEntry(instance);
|
|
1382
|
+
}
|
|
1383
|
+
function findCurrentTasks(instance) {
|
|
1384
|
+
return findCurrentStageEntry(instance)?.tasks ?? [];
|
|
1385
|
+
}
|
|
1386
|
+
const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
1387
|
+
function validateTag(tag) {
|
|
1388
|
+
if (!TAG_RE.test(tag))
|
|
1389
|
+
throw new Error(
|
|
1390
|
+
`tag: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
function tagScopeFilter() {
|
|
1394
|
+
return "tag == $tag";
|
|
1172
1395
|
}
|
|
1173
1396
|
function definitionLookupGroq(explicit) {
|
|
1174
1397
|
const scoped = `_type == "${WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}`;
|
|
@@ -1186,6 +1409,55 @@ function clientForDiscovery(defaultClient, clientForGdr, subjectGdrUri) {
|
|
|
1186
1409
|
return defaultClient;
|
|
1187
1410
|
}
|
|
1188
1411
|
}
|
|
1412
|
+
async function reload(client, instanceId, tag) {
|
|
1413
|
+
const doc = await client.getDocument(instanceId);
|
|
1414
|
+
if (!doc)
|
|
1415
|
+
throw new Error(`Workflow instance ${instanceId} not found`);
|
|
1416
|
+
if (doc.tag !== tag)
|
|
1417
|
+
throw new Error(`Workflow instance ${instanceId} not visible to this engine (tag mismatch)`);
|
|
1418
|
+
return doc;
|
|
1419
|
+
}
|
|
1420
|
+
function effectsContextEntry(name, value) {
|
|
1421
|
+
const _key = randomKey();
|
|
1422
|
+
return typeof value == "string" ? { _key, _type: "effectsContext.string", name, value } : typeof value == "number" ? { _key, _type: "effectsContext.number", name, value } : typeof value == "boolean" ? { _key, _type: "effectsContext.boolean", name, value } : { _key, _type: "effectsContext.ref", name, value };
|
|
1423
|
+
}
|
|
1424
|
+
function effectsContextJsonEntry(name, value) {
|
|
1425
|
+
return { _key: randomKey(), _type: "effectsContext.json", name, value: JSON.stringify(value) };
|
|
1426
|
+
}
|
|
1427
|
+
function buildInstanceBase(args) {
|
|
1428
|
+
const { id, now, actor, perspective } = args;
|
|
1429
|
+
return {
|
|
1430
|
+
_id: id,
|
|
1431
|
+
_type: WORKFLOW_INSTANCE_TYPE,
|
|
1432
|
+
_rev: "",
|
|
1433
|
+
_createdAt: now,
|
|
1434
|
+
_updatedAt: now,
|
|
1435
|
+
tag: args.tag,
|
|
1436
|
+
workflowResource: args.workflowResource,
|
|
1437
|
+
definition: args.definitionName,
|
|
1438
|
+
pinnedVersion: args.pinnedVersion,
|
|
1439
|
+
definitionSnapshot: JSON.stringify(args.definition),
|
|
1440
|
+
state: args.state,
|
|
1441
|
+
effectsContext: args.effectsContext,
|
|
1442
|
+
ancestors: args.ancestors,
|
|
1443
|
+
...perspective !== void 0 ? { perspective } : {},
|
|
1444
|
+
currentStage: args.initialStage,
|
|
1445
|
+
stages: [],
|
|
1446
|
+
pendingEffects: [],
|
|
1447
|
+
effectHistory: [],
|
|
1448
|
+
history: [
|
|
1449
|
+
{
|
|
1450
|
+
_key: randomKey(),
|
|
1451
|
+
_type: "stageEntered",
|
|
1452
|
+
at: now,
|
|
1453
|
+
stage: args.initialStage,
|
|
1454
|
+
...actor !== void 0 ? { actor } : {}
|
|
1455
|
+
}
|
|
1456
|
+
],
|
|
1457
|
+
startedAt: now,
|
|
1458
|
+
lastChangedAt: now
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1189
1461
|
const MAX_SPAWN_DEPTH = 6, SUBWORKFLOWS_ALL_DONE = "count($subworkflows[status != 'done']) == 0", FAIL_WHEN_SYSTEM_ID = "engine.failWhen", COMPLETE_WHEN_SYSTEM_ID = "engine.completeWhen";
|
|
1190
1462
|
function gateActor(outcome) {
|
|
1191
1463
|
return {
|
|
@@ -1211,7 +1483,7 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1211
1483
|
`Subworkflow depth limit (${MAX_SPAWN_DEPTH}) exceeded on task "${task.name}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
|
|
1212
1484
|
);
|
|
1213
1485
|
}
|
|
1214
|
-
const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.
|
|
1486
|
+
const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tag);
|
|
1215
1487
|
if (definition === null) {
|
|
1216
1488
|
const versionLabel = sub.definition.version === void 0 || sub.definition.version === "latest" ? "latest" : `v${sub.definition.version}`;
|
|
1217
1489
|
throw new Error(
|
|
@@ -1301,15 +1573,15 @@ function coerceSpawnGdr(raw, workflowResource) {
|
|
|
1301
1573
|
}
|
|
1302
1574
|
return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
|
|
1303
1575
|
}
|
|
1304
|
-
async function resolveDefinitionRef(client, ref,
|
|
1305
|
-
const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name,
|
|
1576
|
+
async function resolveDefinitionRef(client, ref, tag) {
|
|
1577
|
+
const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
|
|
1306
1578
|
return wantsExplicit && (params.version = ref.version), await client.fetch(
|
|
1307
1579
|
definitionLookupGroq(wantsExplicit),
|
|
1308
1580
|
params
|
|
1309
1581
|
) ?? null;
|
|
1310
1582
|
}
|
|
1311
1583
|
async function prepareChildInstance(args) {
|
|
1312
|
-
const { client, parent, definition, initialState, effectsContext, actor, now } = args,
|
|
1584
|
+
const { client, parent, definition, initialState, effectsContext, actor, now } = args, childTag = parent.tag, workflowResource = parent.workflowResource, childDocId = `${childTag}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type: WORKFLOW_INSTANCE_TYPE }, ancestors = [
|
|
1313
1585
|
...parent.ancestors,
|
|
1314
1586
|
{
|
|
1315
1587
|
id: selfGdr(parent),
|
|
@@ -1322,7 +1594,7 @@ async function prepareChildInstance(args) {
|
|
|
1322
1594
|
client,
|
|
1323
1595
|
now,
|
|
1324
1596
|
selfId: childDocId,
|
|
1325
|
-
|
|
1597
|
+
tag: childTag,
|
|
1326
1598
|
workflowResource,
|
|
1327
1599
|
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
|
|
1328
1600
|
},
|
|
@@ -1341,7 +1613,7 @@ async function prepareChildInstance(args) {
|
|
|
1341
1613
|
), base = buildInstanceBase({
|
|
1342
1614
|
id: childDocId,
|
|
1343
1615
|
now,
|
|
1344
|
-
|
|
1616
|
+
tag: childTag,
|
|
1345
1617
|
workflowResource,
|
|
1346
1618
|
definitionName: definition.name,
|
|
1347
1619
|
pinnedVersion: definition.version,
|
|
@@ -1353,16 +1625,121 @@ async function prepareChildInstance(args) {
|
|
|
1353
1625
|
initialStage: definition.initialStage,
|
|
1354
1626
|
actor
|
|
1355
1627
|
});
|
|
1356
|
-
return { ref: childRef, body: base };
|
|
1357
|
-
}
|
|
1358
|
-
async function subworkflowRows(ctx, refs) {
|
|
1359
|
-
if (refs.length === 0) return [];
|
|
1360
|
-
const ids = refs.map((r) => gdrToBareId(r.id));
|
|
1361
|
-
return (await ctx.client.fetch("*[_id in $ids]{_id, currentStage, completedAt}", { ids })).map((c) => ({
|
|
1362
|
-
_id: c._id,
|
|
1363
|
-
stage: c.currentStage,
|
|
1364
|
-
status: c.completedAt !== void 0 && c.completedAt !== null ? "done" : "active"
|
|
1365
|
-
}));
|
|
1628
|
+
return { ref: childRef, body: base };
|
|
1629
|
+
}
|
|
1630
|
+
async function subworkflowRows(ctx, refs) {
|
|
1631
|
+
if (refs.length === 0) return [];
|
|
1632
|
+
const ids = refs.map((r) => gdrToBareId(r.id));
|
|
1633
|
+
return (await ctx.client.fetch("*[_id in $ids]{_id, currentStage, completedAt}", { ids })).map((c) => ({
|
|
1634
|
+
_id: c._id,
|
|
1635
|
+
stage: c.currentStage,
|
|
1636
|
+
status: c.completedAt !== void 0 && c.completedAt !== null ? "done" : "active"
|
|
1637
|
+
}));
|
|
1638
|
+
}
|
|
1639
|
+
async function resolveBindings(args) {
|
|
1640
|
+
const resolved = {};
|
|
1641
|
+
for (const [key, groq] of Object.entries(args.bindings ?? {}))
|
|
1642
|
+
resolved[key] = await runGroq(groq, args.params, args.snapshot);
|
|
1643
|
+
return { ...resolved, ...args.staticInput };
|
|
1644
|
+
}
|
|
1645
|
+
async function completeEffect(args) {
|
|
1646
|
+
const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1647
|
+
...options?.clock ? { clock: options.clock } : {},
|
|
1648
|
+
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
1649
|
+
});
|
|
1650
|
+
return commitCompleteEffect(
|
|
1651
|
+
ctx,
|
|
1652
|
+
effectKey,
|
|
1653
|
+
status,
|
|
1654
|
+
outputs,
|
|
1655
|
+
detail,
|
|
1656
|
+
error,
|
|
1657
|
+
durationMs,
|
|
1658
|
+
options?.actor
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
function buildEffectHistoryEntry(pending, outcome) {
|
|
1662
|
+
const { status, ranAt, actor, detail, error, durationMs, outputs } = outcome, resolvedActor = actor ?? pending.actor;
|
|
1663
|
+
return {
|
|
1664
|
+
_key: pending._key,
|
|
1665
|
+
name: pending.name,
|
|
1666
|
+
...pending.title !== void 0 ? { title: pending.title } : {},
|
|
1667
|
+
...pending.description !== void 0 ? { description: pending.description } : {},
|
|
1668
|
+
params: pending.params,
|
|
1669
|
+
origin: pending.origin,
|
|
1670
|
+
...resolvedActor !== void 0 ? { actor: resolvedActor } : {},
|
|
1671
|
+
ranAt,
|
|
1672
|
+
...durationMs !== void 0 ? { durationMs } : {},
|
|
1673
|
+
status,
|
|
1674
|
+
...detail !== void 0 ? { detail } : {},
|
|
1675
|
+
...error !== void 0 ? { error } : {},
|
|
1676
|
+
...outputs !== void 0 ? { outputs } : {}
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
function upsertEffectsContext(mutation, effectName, outputs) {
|
|
1680
|
+
const existingIndex = mutation.effectsContext.findIndex((e) => e.name === effectName), entry = effectsContextJsonEntry(effectName, outputs);
|
|
1681
|
+
existingIndex >= 0 ? mutation.effectsContext[existingIndex] = entry : mutation.effectsContext.push(entry);
|
|
1682
|
+
}
|
|
1683
|
+
async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, error, durationMs, actor) {
|
|
1684
|
+
const pending = ctx.instance.pendingEffects.find((e) => e._key === effectKey);
|
|
1685
|
+
if (pending === void 0)
|
|
1686
|
+
throw new Error(`Pending effect "${effectKey}" not found on instance ${ctx.instance._id}`);
|
|
1687
|
+
const mutation = startMutation(ctx.instance);
|
|
1688
|
+
mutation.pendingEffects = mutation.pendingEffects.filter((e) => e._key !== effectKey);
|
|
1689
|
+
const ranAt = ctx.now;
|
|
1690
|
+
return mutation.effectHistory.push(
|
|
1691
|
+
buildEffectHistoryEntry(pending, { status, ranAt, actor, detail, error, durationMs, outputs })
|
|
1692
|
+
), status === "done" && outputs !== void 0 && upsertEffectsContext(mutation, pending.name, outputs), mutation.history.push({
|
|
1693
|
+
_key: randomKey(),
|
|
1694
|
+
_type: "effectCompleted",
|
|
1695
|
+
at: ranAt,
|
|
1696
|
+
effectKey,
|
|
1697
|
+
effect: pending.name,
|
|
1698
|
+
status,
|
|
1699
|
+
...outputs !== void 0 ? { outputs } : {},
|
|
1700
|
+
...detail !== void 0 ? { detail } : {},
|
|
1701
|
+
...actor !== void 0 ? { actor } : {}
|
|
1702
|
+
}), await persist(ctx, mutation), { effectKey, status };
|
|
1703
|
+
}
|
|
1704
|
+
function buildQueuedEffect(effect, origin, params, actor, now) {
|
|
1705
|
+
const key = randomKey(), pending = {
|
|
1706
|
+
_key: key,
|
|
1707
|
+
_type: "pendingEffect",
|
|
1708
|
+
name: effect.name,
|
|
1709
|
+
...effect.title !== void 0 ? { title: effect.title } : {},
|
|
1710
|
+
...effect.description !== void 0 ? { description: effect.description } : {},
|
|
1711
|
+
...effect.bindings !== void 0 ? { bindings: effect.bindings } : {},
|
|
1712
|
+
params,
|
|
1713
|
+
origin,
|
|
1714
|
+
...actor !== void 0 ? { actor } : {},
|
|
1715
|
+
queuedAt: now
|
|
1716
|
+
}, history = {
|
|
1717
|
+
_key: randomKey(),
|
|
1718
|
+
_type: "effectQueued",
|
|
1719
|
+
at: now,
|
|
1720
|
+
effectKey: key,
|
|
1721
|
+
effect: effect.name,
|
|
1722
|
+
origin
|
|
1723
|
+
};
|
|
1724
|
+
return { pending, history };
|
|
1725
|
+
}
|
|
1726
|
+
async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
|
|
1727
|
+
if (!effects || effects.length === 0) return;
|
|
1728
|
+
const now = ctx.now, liveCtx = { ...ctx, instance: materializeInstance(ctx.instance, mutation) }, params = await ctxConditionParams(liveCtx, {
|
|
1729
|
+
...opts?.taskName !== void 0 ? { taskName: opts.taskName } : {},
|
|
1730
|
+
...actor !== void 0 ? { actor } : {},
|
|
1731
|
+
// Caller-supplied action params, readable in bindings as `$params.<name>`.
|
|
1732
|
+
vars: { params: opts?.callerParams ?? {} }
|
|
1733
|
+
});
|
|
1734
|
+
for (const effect of effects) {
|
|
1735
|
+
const resolved = await resolveBindings({
|
|
1736
|
+
bindings: effect.bindings,
|
|
1737
|
+
staticInput: effect.input,
|
|
1738
|
+
snapshot: ctx.snapshot,
|
|
1739
|
+
params
|
|
1740
|
+
}), { pending, history } = buildQueuedEffect(effect, origin, resolved, actor, now);
|
|
1741
|
+
mutation.pendingEffects.push(pending), mutation.history.push(history);
|
|
1742
|
+
}
|
|
1366
1743
|
}
|
|
1367
1744
|
function resolveStaticSource(src, ctx) {
|
|
1368
1745
|
switch (src.type) {
|
|
@@ -1593,7 +1970,8 @@ function evalOpPredicate(p, row, ctx) {
|
|
|
1593
1970
|
}
|
|
1594
1971
|
async function invokeTask(args) {
|
|
1595
1972
|
const { client, instanceId, task: taskName, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1596
|
-
...options?.clock ? { clock: options.clock } : {}
|
|
1973
|
+
...options?.clock ? { clock: options.clock } : {},
|
|
1974
|
+
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
1597
1975
|
});
|
|
1598
1976
|
return commitInvoke(ctx, taskName, options?.actor);
|
|
1599
1977
|
}
|
|
@@ -1701,7 +2079,8 @@ function isTerminalStage(stage) {
|
|
|
1701
2079
|
}
|
|
1702
2080
|
async function setStage(args) {
|
|
1703
2081
|
const { client, instanceId, targetStage, reason, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1704
|
-
...options?.clock ? { clock: options.clock } : {}
|
|
2082
|
+
...options?.clock ? { clock: options.clock } : {},
|
|
2083
|
+
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
1705
2084
|
});
|
|
1706
2085
|
return commitSetStage(ctx, targetStage, reason, options?.actor);
|
|
1707
2086
|
}
|
|
@@ -1957,282 +2336,53 @@ async function cascadeAutoTransitions(client, instanceId, actor, clientForGdr, c
|
|
|
1957
2336
|
...actor !== void 0 ? { options: { actor } } : {},
|
|
1958
2337
|
...clientForGdr ? { clientForGdr } : {},
|
|
1959
2338
|
...clock ? { clock } : {},
|
|
1960
|
-
...overlay ? { overlay } : {}
|
|
1961
|
-
})).fired) return count;
|
|
1962
|
-
if (count++, count >= CASCADE_LIMIT)
|
|
1963
|
-
throw new CascadeLimitError({ instanceId, limit: CASCADE_LIMIT });
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
|
|
1967
|
-
const parentRef = child.ancestors.at(-1);
|
|
1968
|
-
if (parentRef === void 0) return;
|
|
1969
|
-
const parent = await client.getDocument(gdrToBareId(parentRef.id));
|
|
1970
|
-
if (!parent || parent._type !== WORKFLOW_INSTANCE_TYPE || typeof parent.definitionSnapshot != "string") return;
|
|
1971
|
-
const definition = parseDefinitionSnapshot(parent), stage = definition.stages.find((s) => s.name === parent.currentStage);
|
|
1972
|
-
if (stage === void 0) return;
|
|
1973
|
-
const parentCurrentTasks = findCurrentTasks(parent), task = (stage.tasks ?? []).find((t) => t.subworkflows === void 0 ? !1 : parentCurrentTasks.find((e) => e.name === t.name)?.spawnedInstances?.some((r) => gdrToBareId(r.id) === child._id) ?? !1);
|
|
1974
|
-
if (task?.subworkflows === void 0) return;
|
|
1975
|
-
const entry = parentCurrentTasks.find((e) => e.name === task.name);
|
|
1976
|
-
if (entry === void 0 || entry.status !== "active") return;
|
|
1977
|
-
const ctx = await buildEngineContext({
|
|
1978
|
-
client,
|
|
1979
|
-
clientForGdr,
|
|
1980
|
-
instance: parent,
|
|
1981
|
-
definition,
|
|
1982
|
-
...clock ? { clock } : {}
|
|
1983
|
-
}), outcome = await evaluateResolutionGate(ctx, task, await taskConditionVars(ctx, entry));
|
|
1984
|
-
if (outcome !== void 0)
|
|
1985
|
-
return { parent, definition, stage, task, outcome };
|
|
1986
|
-
}
|
|
1987
|
-
async function propagateToAncestors(client, instanceId, actor, clientForGdr, clock) {
|
|
1988
|
-
const instance = await client.getDocument(instanceId);
|
|
1989
|
-
if (!instance) return;
|
|
1990
|
-
const resolvedClientForGdr = clientForGdr ?? (() => client), found = await findResolvedParentSpawn(client, instance, resolvedClientForGdr, clock);
|
|
1991
|
-
if (found === void 0) return;
|
|
1992
|
-
const { parent, definition, stage, task, outcome } = found, ctx = await buildEngineContext({
|
|
1993
|
-
client,
|
|
1994
|
-
clientForGdr: resolvedClientForGdr,
|
|
1995
|
-
instance: parent,
|
|
1996
|
-
definition,
|
|
1997
|
-
...clock ? { clock } : {}
|
|
1998
|
-
}), mutation = startMutation(parent), mutEntry = currentTasks(mutation).find((e) => e.name === task.name);
|
|
1999
|
-
mutEntry !== void 0 && (applyTaskStatusChange({
|
|
2000
|
-
entry: mutEntry,
|
|
2001
|
-
history: mutation.history,
|
|
2002
|
-
stage: stage.name,
|
|
2003
|
-
to: outcome,
|
|
2004
|
-
at: ctx.now,
|
|
2005
|
-
actor: gateActor(outcome)
|
|
2006
|
-
}), await persist(ctx, mutation), await cascadeAutoTransitions(client, parent._id, actor, clientForGdr, clock), await propagateToAncestors(client, parent._id, actor, clientForGdr, clock));
|
|
2007
|
-
}
|
|
2008
|
-
function startMutation(instance) {
|
|
2009
|
-
return {
|
|
2010
|
-
currentStage: instance.currentStage,
|
|
2011
|
-
// Deep-ish copy. Per-scope state entry arrays need their own copies
|
|
2012
|
-
// so ops can mutate without leaking into the source.
|
|
2013
|
-
state: (instance.state ?? []).map((s) => ({ ...s })),
|
|
2014
|
-
stages: instance.stages.map((s) => ({
|
|
2015
|
-
...s,
|
|
2016
|
-
state: (s.state ?? []).map((entry) => ({ ...entry })),
|
|
2017
|
-
tasks: s.tasks.map((t) => ({
|
|
2018
|
-
...t,
|
|
2019
|
-
...t.state !== void 0 ? { state: t.state.map((entry) => ({ ...entry })) } : {}
|
|
2020
|
-
}))
|
|
2021
|
-
})),
|
|
2022
|
-
pendingEffects: [...instance.pendingEffects],
|
|
2023
|
-
effectHistory: [...instance.effectHistory],
|
|
2024
|
-
effectsContext: [...instance.effectsContext],
|
|
2025
|
-
history: [...instance.history],
|
|
2026
|
-
lastChangedAt: instance.lastChangedAt,
|
|
2027
|
-
...instance.completedAt !== void 0 ? { completedAt: instance.completedAt } : {},
|
|
2028
|
-
...instance.abortedAt !== void 0 ? { abortedAt: instance.abortedAt } : {},
|
|
2029
|
-
pendingCreates: []
|
|
2030
|
-
};
|
|
2031
|
-
}
|
|
2032
|
-
function instanceStateFields(src) {
|
|
2033
|
-
const fields = {
|
|
2034
|
-
currentStage: src.currentStage,
|
|
2035
|
-
state: src.state,
|
|
2036
|
-
stages: src.stages,
|
|
2037
|
-
pendingEffects: src.pendingEffects,
|
|
2038
|
-
effectHistory: src.effectHistory,
|
|
2039
|
-
effectsContext: src.effectsContext,
|
|
2040
|
-
history: src.history
|
|
2041
|
-
};
|
|
2042
|
-
return src.completedAt !== void 0 && (fields.completedAt = src.completedAt), src.abortedAt !== void 0 && (fields.abortedAt = src.abortedAt), fields;
|
|
2043
|
-
}
|
|
2044
|
-
function materializeInstance(base, mutation) {
|
|
2045
|
-
return {
|
|
2046
|
-
...base,
|
|
2047
|
-
currentStage: mutation.currentStage,
|
|
2048
|
-
state: mutation.state,
|
|
2049
|
-
stages: mutation.stages
|
|
2050
|
-
};
|
|
2051
|
-
}
|
|
2052
|
-
async function persist(ctx, mutation) {
|
|
2053
|
-
const set = {
|
|
2054
|
-
...instanceStateFields(mutation),
|
|
2055
|
-
lastChangedAt: ctx.now
|
|
2056
|
-
}, pendingCreates = mutation.pendingCreates;
|
|
2057
|
-
if (pendingCreates.length === 0)
|
|
2058
|
-
return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit();
|
|
2059
|
-
const tx = ctx.client.transaction();
|
|
2060
|
-
for (const body of pendingCreates) tx.create(body);
|
|
2061
|
-
tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)), await tx.commit(), mutation.pendingCreates = [];
|
|
2062
|
-
const actorForPriming = void 0;
|
|
2063
|
-
for (const body of pendingCreates)
|
|
2064
|
-
await primeInitialStage(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock), await cascadeAutoTransitions(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock), await propagateToAncestors(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock);
|
|
2065
|
-
const reloaded = await ctx.client.getDocument(ctx.instance._id);
|
|
2066
|
-
if (!reloaded)
|
|
2067
|
-
throw new Error(`Instance ${ctx.instance._id} disappeared after transaction commit`);
|
|
2068
|
-
return reloaded;
|
|
2069
|
-
}
|
|
2070
|
-
function currentStageEntry(mutation) {
|
|
2071
|
-
const entry = findOpenStageEntry(mutation);
|
|
2072
|
-
if (entry === void 0)
|
|
2073
|
-
throw new Error(
|
|
2074
|
-
`Mutation invariant broken: no current (un-exited) StageEntry for currentStage "${mutation.currentStage}"`
|
|
2075
|
-
);
|
|
2076
|
-
return entry;
|
|
2077
|
-
}
|
|
2078
|
-
function currentTasks(mutation) {
|
|
2079
|
-
return currentStageEntry(mutation).tasks;
|
|
2080
|
-
}
|
|
2081
|
-
function findTaskInCurrentStage(ctx, taskName) {
|
|
2082
|
-
const stage = findStage(ctx.definition, ctx.instance.currentStage), task = (stage.tasks ?? []).find((t) => t.name === taskName);
|
|
2083
|
-
if (task === void 0)
|
|
2084
|
-
throw new Error(
|
|
2085
|
-
`Task "${taskName}" not found in current stage "${stage.name}" of ${ctx.definition.name}`
|
|
2086
|
-
);
|
|
2087
|
-
return { stage, task };
|
|
2088
|
-
}
|
|
2089
|
-
function requireMutationTaskEntry(mutation, task) {
|
|
2090
|
-
const mutEntry = currentTasks(mutation).find((t) => t.name === task);
|
|
2091
|
-
if (mutEntry === void 0)
|
|
2092
|
-
throw new Error(`Task "${task}" disappeared from mutation copy \u2014 invariant broken`);
|
|
2093
|
-
return mutEntry;
|
|
2094
|
-
}
|
|
2095
|
-
function findCurrentStageEntry(instance) {
|
|
2096
|
-
return findOpenStageEntry(instance);
|
|
2097
|
-
}
|
|
2098
|
-
function findCurrentTasks(instance) {
|
|
2099
|
-
return findCurrentStageEntry(instance)?.tasks ?? [];
|
|
2100
|
-
}
|
|
2101
|
-
async function completeEffect(args) {
|
|
2102
|
-
const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
|
|
2103
|
-
...options?.clock ? { clock: options.clock } : {}
|
|
2104
|
-
});
|
|
2105
|
-
return commitCompleteEffect(
|
|
2106
|
-
ctx,
|
|
2107
|
-
effectKey,
|
|
2108
|
-
status,
|
|
2109
|
-
outputs,
|
|
2110
|
-
detail,
|
|
2111
|
-
error,
|
|
2112
|
-
durationMs,
|
|
2113
|
-
options?.actor
|
|
2114
|
-
);
|
|
2115
|
-
}
|
|
2116
|
-
function buildEffectHistoryEntry(pending, outcome) {
|
|
2117
|
-
const { status, ranAt, actor, detail, error, durationMs, outputs } = outcome, resolvedActor = actor ?? pending.actor;
|
|
2118
|
-
return {
|
|
2119
|
-
_key: pending._key,
|
|
2120
|
-
name: pending.name,
|
|
2121
|
-
...pending.title !== void 0 ? { title: pending.title } : {},
|
|
2122
|
-
...pending.description !== void 0 ? { description: pending.description } : {},
|
|
2123
|
-
params: pending.params,
|
|
2124
|
-
origin: pending.origin,
|
|
2125
|
-
...resolvedActor !== void 0 ? { actor: resolvedActor } : {},
|
|
2126
|
-
ranAt,
|
|
2127
|
-
...durationMs !== void 0 ? { durationMs } : {},
|
|
2128
|
-
status,
|
|
2129
|
-
...detail !== void 0 ? { detail } : {},
|
|
2130
|
-
...error !== void 0 ? { error } : {},
|
|
2131
|
-
...outputs !== void 0 ? { outputs } : {}
|
|
2132
|
-
};
|
|
2133
|
-
}
|
|
2134
|
-
function upsertEffectsContext(mutation, effectName, outputs) {
|
|
2135
|
-
const existingIndex = mutation.effectsContext.findIndex((e) => e.name === effectName), entry = effectsContextJsonEntry(effectName, outputs);
|
|
2136
|
-
existingIndex >= 0 ? mutation.effectsContext[existingIndex] = entry : mutation.effectsContext.push(entry);
|
|
2137
|
-
}
|
|
2138
|
-
async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, error, durationMs, actor) {
|
|
2139
|
-
const pending = ctx.instance.pendingEffects.find((e) => e._key === effectKey);
|
|
2140
|
-
if (pending === void 0)
|
|
2141
|
-
throw new Error(`Pending effect "${effectKey}" not found on instance ${ctx.instance._id}`);
|
|
2142
|
-
const mutation = startMutation(ctx.instance);
|
|
2143
|
-
mutation.pendingEffects = mutation.pendingEffects.filter((e) => e._key !== effectKey);
|
|
2144
|
-
const ranAt = ctx.now;
|
|
2145
|
-
return mutation.effectHistory.push(
|
|
2146
|
-
buildEffectHistoryEntry(pending, { status, ranAt, actor, detail, error, durationMs, outputs })
|
|
2147
|
-
), status === "done" && outputs !== void 0 && upsertEffectsContext(mutation, pending.name, outputs), mutation.history.push({
|
|
2148
|
-
_key: randomKey(),
|
|
2149
|
-
_type: "effectCompleted",
|
|
2150
|
-
at: ranAt,
|
|
2151
|
-
effectKey,
|
|
2152
|
-
effect: pending.name,
|
|
2153
|
-
status,
|
|
2154
|
-
...outputs !== void 0 ? { outputs } : {},
|
|
2155
|
-
...detail !== void 0 ? { detail } : {},
|
|
2156
|
-
...actor !== void 0 ? { actor } : {}
|
|
2157
|
-
}), await persist(ctx, mutation), { effectKey, status };
|
|
2158
|
-
}
|
|
2159
|
-
function buildQueuedEffect(effect, origin, params, actor, now) {
|
|
2160
|
-
const key = randomKey(), pending = {
|
|
2161
|
-
_key: key,
|
|
2162
|
-
_type: "pendingEffect",
|
|
2163
|
-
name: effect.name,
|
|
2164
|
-
...effect.title !== void 0 ? { title: effect.title } : {},
|
|
2165
|
-
...effect.description !== void 0 ? { description: effect.description } : {},
|
|
2166
|
-
...effect.bindings !== void 0 ? { bindings: effect.bindings } : {},
|
|
2167
|
-
params,
|
|
2168
|
-
origin,
|
|
2169
|
-
...actor !== void 0 ? { actor } : {},
|
|
2170
|
-
queuedAt: now
|
|
2171
|
-
}, history = {
|
|
2172
|
-
_key: randomKey(),
|
|
2173
|
-
_type: "effectQueued",
|
|
2174
|
-
at: now,
|
|
2175
|
-
effectKey: key,
|
|
2176
|
-
effect: effect.name,
|
|
2177
|
-
origin
|
|
2178
|
-
};
|
|
2179
|
-
return { pending, history };
|
|
2180
|
-
}
|
|
2181
|
-
async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
|
|
2182
|
-
if (!effects || effects.length === 0) return;
|
|
2183
|
-
const now = ctx.now, liveCtx = { ...ctx, instance: materializeInstance(ctx.instance, mutation) }, params = await ctxConditionParams(liveCtx, {
|
|
2184
|
-
...opts?.taskName !== void 0 ? { taskName: opts.taskName } : {},
|
|
2185
|
-
...actor !== void 0 ? { actor } : {},
|
|
2186
|
-
// Caller-supplied action params, readable in bindings as `$params.<name>`.
|
|
2187
|
-
vars: { params: opts?.callerParams ?? {} }
|
|
2188
|
-
});
|
|
2189
|
-
for (const effect of effects) {
|
|
2190
|
-
const resolved = await resolveBindings({
|
|
2191
|
-
bindings: effect.bindings,
|
|
2192
|
-
staticInput: effect.input,
|
|
2193
|
-
snapshot: ctx.snapshot,
|
|
2194
|
-
params
|
|
2195
|
-
}), { pending, history } = buildQueuedEffect(effect, origin, resolved, actor, now);
|
|
2196
|
-
mutation.pendingEffects.push(pending), mutation.history.push(history);
|
|
2339
|
+
...overlay ? { overlay } : {}
|
|
2340
|
+
})).fired) return count;
|
|
2341
|
+
if (count++, count >= CASCADE_LIMIT)
|
|
2342
|
+
throw new CascadeLimitError({ instanceId, limit: CASCADE_LIMIT });
|
|
2197
2343
|
}
|
|
2198
2344
|
}
|
|
2199
|
-
async function
|
|
2200
|
-
const
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2345
|
+
async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
|
|
2346
|
+
const parentRef = child.ancestors.at(-1);
|
|
2347
|
+
if (parentRef === void 0) return;
|
|
2348
|
+
const parent = await client.getDocument(gdrToBareId(parentRef.id));
|
|
2349
|
+
if (!parent || parent._type !== WORKFLOW_INSTANCE_TYPE || typeof parent.definitionSnapshot != "string") return;
|
|
2350
|
+
const definition = parseDefinitionSnapshot(parent), stage = definition.stages.find((s) => s.name === parent.currentStage);
|
|
2351
|
+
if (stage === void 0) return;
|
|
2352
|
+
const parentCurrentTasks = findCurrentTasks(parent), task = (stage.tasks ?? []).find((t) => t.subworkflows === void 0 ? !1 : parentCurrentTasks.find((e) => e.name === t.name)?.spawnedInstances?.some((r) => gdrToBareId(r.id) === child._id) ?? !1);
|
|
2353
|
+
if (task?.subworkflows === void 0) return;
|
|
2354
|
+
const entry = parentCurrentTasks.find((e) => e.name === task.name);
|
|
2355
|
+
if (entry === void 0 || entry.status !== "active") return;
|
|
2356
|
+
const ctx = await buildEngineContext({
|
|
2357
|
+
client,
|
|
2358
|
+
clientForGdr,
|
|
2359
|
+
instance: parent,
|
|
2360
|
+
definition,
|
|
2361
|
+
...clock ? { clock } : {}
|
|
2362
|
+
}), outcome = await evaluateResolutionGate(ctx, task, await taskConditionVars(ctx, entry));
|
|
2363
|
+
if (outcome !== void 0)
|
|
2364
|
+
return { parent, definition, stage, task, outcome };
|
|
2204
2365
|
}
|
|
2205
|
-
async function
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
const
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
)
|
|
2221
|
-
), mutation.pendingEffects = [], mutation.history.push({
|
|
2222
|
-
_key: randomKey(),
|
|
2223
|
-
_type: "aborted",
|
|
2224
|
-
at,
|
|
2366
|
+
async function propagateToAncestors(client, instanceId, actor, clientForGdr, clock) {
|
|
2367
|
+
const instance = await client.getDocument(instanceId);
|
|
2368
|
+
if (!instance) return;
|
|
2369
|
+
const resolvedClientForGdr = clientForGdr ?? (() => client), found = await findResolvedParentSpawn(client, instance, resolvedClientForGdr, clock);
|
|
2370
|
+
if (found === void 0) return;
|
|
2371
|
+
const { parent, definition, stage, task, outcome } = found, ctx = await buildEngineContext({
|
|
2372
|
+
client,
|
|
2373
|
+
clientForGdr: resolvedClientForGdr,
|
|
2374
|
+
instance: parent,
|
|
2375
|
+
definition,
|
|
2376
|
+
...clock ? { clock } : {}
|
|
2377
|
+
}), mutation = startMutation(parent), mutEntry = currentTasks(mutation).find((e) => e.name === task.name);
|
|
2378
|
+
mutEntry !== void 0 && (applyTaskStatusChange({
|
|
2379
|
+
entry: mutEntry,
|
|
2380
|
+
history: mutation.history,
|
|
2225
2381
|
stage: stage.name,
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
clientForGdr: ctx.clientForGdr,
|
|
2231
|
-
instance: ctx.instance,
|
|
2232
|
-
definition: ctx.definition,
|
|
2233
|
-
stageName: stage.name,
|
|
2234
|
-
now: ctx.now
|
|
2235
|
-
}), { fired: !0, stage: stage.name };
|
|
2382
|
+
to: outcome,
|
|
2383
|
+
at: ctx.now,
|
|
2384
|
+
actor: gateActor(outcome)
|
|
2385
|
+
}), await persist(ctx, mutation), await cascadeAutoTransitions(client, parent._id, actor, clientForGdr, clock), await propagateToAncestors(client, parent._id, actor, clientForGdr, clock));
|
|
2236
2386
|
}
|
|
2237
2387
|
const parsedFilters = /* @__PURE__ */ new Map();
|
|
2238
2388
|
async function matchesFilter(args) {
|
|
@@ -2328,105 +2478,13 @@ async function fetchGrantsCached(requestFn, resourcePath) {
|
|
|
2328
2478
|
return;
|
|
2329
2479
|
}
|
|
2330
2480
|
}
|
|
2331
|
-
const resourceKey = (r) => `${r.type}.${r.id}`;
|
|
2332
|
-
function guardsForResource(client) {
|
|
2333
|
-
return client.fetch("*[_type == $t] | order(_id asc)", { t: GUARD_DOC_TYPE });
|
|
2334
|
-
}
|
|
2335
|
-
function instanceGuardQuery(instanceId) {
|
|
2336
|
-
return {
|
|
2337
|
-
query: "*[_type == $guardType && sourceInstanceId == $instanceId]",
|
|
2338
|
-
params: { guardType: GUARD_DOC_TYPE, instanceId }
|
|
2339
|
-
};
|
|
2340
|
-
}
|
|
2341
|
-
function verdictGuardsForInstance(client, instanceId) {
|
|
2342
|
-
const { query, params } = instanceGuardQuery(instanceId);
|
|
2343
|
-
return client.fetch(query, params);
|
|
2344
|
-
}
|
|
2345
|
-
async function guardsForInstance(args) {
|
|
2346
|
-
const { client, clientForGdr, instance } = args, stateUris = [
|
|
2347
|
-
...collectEntryDocUris(instance.state),
|
|
2348
|
-
...instance.stages.flatMap((stage) => collectEntryDocUris(stage.state))
|
|
2349
|
-
], clients = resourceClientMap(
|
|
2350
|
-
instance.workflowResource,
|
|
2351
|
-
client,
|
|
2352
|
-
clientForGdr,
|
|
2353
|
-
stateUris.map((uri) => tryParseGdr(uri))
|
|
2354
|
-
), { query, params } = instanceGuardQuery(instance._id);
|
|
2355
|
-
return queryGuardsAcross(clients.values(), query, params);
|
|
2356
|
-
}
|
|
2357
|
-
async function guardsForDefinition(args) {
|
|
2358
|
-
const { client, clientForGdr, workflowResource, definition, definitions } = args, gdrs = definitions.flatMap(
|
|
2359
|
-
(def) => guardIdRefs(def).map(({ idRef, stage }) => staticIdRefGdr(idRef, stage, def))
|
|
2360
|
-
), clients = resourceClientMap(workflowResource, client, clientForGdr, gdrs);
|
|
2361
|
-
return queryGuardsAcross(clients.values(), "*[_type == $t && sourceDefinition == $definition]", {
|
|
2362
|
-
t: GUARD_DOC_TYPE,
|
|
2363
|
-
definition
|
|
2364
|
-
});
|
|
2365
|
-
}
|
|
2366
|
-
function guardIdRefs(definition) {
|
|
2367
|
-
return definition.stages.flatMap(
|
|
2368
|
-
(stage) => (stage.guards ?? []).flatMap(
|
|
2369
|
-
(guard) => (guard.match.idRefs ?? []).map((idRef) => ({ idRef, stage }))
|
|
2370
|
-
)
|
|
2371
|
-
);
|
|
2372
|
-
}
|
|
2373
|
-
function staticIdRefGdr(idRef, stage, definition) {
|
|
2374
|
-
const read = /^\$state\.([\w-]+)/.exec(idRef);
|
|
2375
|
-
if (read === null) return;
|
|
2376
|
-
const source = entriesInScope(stage, definition).find((e) => e.name === read[1])?.source;
|
|
2377
|
-
return source?.type === "literal" ? gdrFromValue(source.value) : void 0;
|
|
2378
|
-
}
|
|
2379
|
-
function entriesInScope(stage, definition) {
|
|
2380
|
-
return [
|
|
2381
|
-
...definition.state ?? [],
|
|
2382
|
-
...stage.state ?? [],
|
|
2383
|
-
...(stage.tasks ?? []).flatMap((task) => task.state ?? [])
|
|
2384
|
-
];
|
|
2385
|
-
}
|
|
2386
|
-
function gdrFromValue(value) {
|
|
2387
|
-
if (typeof value == "string") return tryParseGdr(value);
|
|
2388
|
-
if (typeof value == "object" && value !== null && "id" in value) {
|
|
2389
|
-
const id = value.id;
|
|
2390
|
-
return typeof id == "string" ? tryParseGdr(id) : void 0;
|
|
2391
|
-
}
|
|
2392
|
-
}
|
|
2393
|
-
function tryParseGdr(uri) {
|
|
2394
|
-
try {
|
|
2395
|
-
return parseGdr(uri);
|
|
2396
|
-
} catch {
|
|
2397
|
-
return;
|
|
2398
|
-
}
|
|
2399
|
-
}
|
|
2400
|
-
function resourceClientMap(base, baseClient, clientForGdr, gdrs) {
|
|
2401
|
-
const map = /* @__PURE__ */ new Map([[resourceKey(base), baseClient]]);
|
|
2402
|
-
for (const parsed of gdrs) {
|
|
2403
|
-
if (parsed === void 0) continue;
|
|
2404
|
-
const key = resourceKey(resourceFromParsed(parsed));
|
|
2405
|
-
map.has(key) || map.set(key, clientForGdr(parsed));
|
|
2406
|
-
}
|
|
2407
|
-
return map;
|
|
2408
|
-
}
|
|
2409
|
-
async function queryGuardsAcross(clients, query, params) {
|
|
2410
|
-
const perResource = await Promise.all(
|
|
2411
|
-
[...clients].map((c) => c.fetch(query, params))
|
|
2412
|
-
);
|
|
2413
|
-
return dedupById(perResource.flat());
|
|
2414
|
-
}
|
|
2415
|
-
function dedupById(guards) {
|
|
2416
|
-
return [...new Map(guards.map((g) => [g._id, g])).values()].sort((a, b) => a._id.localeCompare(b._id));
|
|
2417
|
-
}
|
|
2418
2481
|
async function evaluateInstance(args) {
|
|
2419
|
-
const { client,
|
|
2420
|
-
|
|
2482
|
+
const { client, tag, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
|
|
2483
|
+
validateTag(tag);
|
|
2421
2484
|
const { actor, grants } = await resolveAccess(client, {
|
|
2422
2485
|
...args.access !== void 0 ? { override: args.access } : {},
|
|
2423
2486
|
...args.grantsFromPath !== void 0 ? { grantsFromPath: args.grantsFromPath } : {}
|
|
2424
|
-
}), instance = await client
|
|
2425
|
-
if (!instance)
|
|
2426
|
-
throw new Error(`Workflow instance "${instanceId}" not found`);
|
|
2427
|
-
if (!tags.some((t) => instance.tags?.includes(t)))
|
|
2428
|
-
throw new Error(`Workflow instance "${instanceId}" not visible to this engine (tag mismatch)`);
|
|
2429
|
-
const definition = parseDefinitionSnapshot(instance), snapshot = await hydrateSnapshot({ client, clientForGdr: resourceClients !== void 0 ? (parsed) => resourceClients(parsed) ?? client : () => client, instance }), guards = await verdictGuardsForInstance(client, instance._id);
|
|
2487
|
+
}), instance = await reload(client, instanceId, tag), definition = parseDefinitionSnapshot(instance), snapshot = await hydrateSnapshot({ client, clientForGdr: resourceClients !== void 0 ? (parsed) => resourceClients(parsed) ?? client : () => client, instance }), guards = await verdictGuardsForInstance(client, instance._id);
|
|
2430
2488
|
return evaluateFromSnapshot({
|
|
2431
2489
|
instance,
|
|
2432
2490
|
definition,
|
|
@@ -2567,22 +2625,21 @@ function lifecycleReason(instance, status, stageHasExits) {
|
|
|
2567
2625
|
function disabled(action, reason) {
|
|
2568
2626
|
return { action, allowed: !1, disabledReason: reason };
|
|
2569
2627
|
}
|
|
2570
|
-
function definitionDocId(_workflowResource,
|
|
2571
|
-
return `${
|
|
2628
|
+
function definitionDocId(_workflowResource, tag, definition, version) {
|
|
2629
|
+
return `${tag}.${definition}.v${version}`;
|
|
2572
2630
|
}
|
|
2573
|
-
async function sortByDependencies(client, definitions,
|
|
2574
|
-
const
|
|
2631
|
+
async function sortByDependencies(client, definitions, tag, workflowResource) {
|
|
2632
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2575
2633
|
for (const def of definitions) {
|
|
2576
2634
|
const list = byName.get(def.name) ?? [];
|
|
2577
2635
|
list.push(def), byName.set(def.name, list);
|
|
2578
2636
|
}
|
|
2579
2637
|
const findInBatch = (ref) => findRefInBatch(byName, ref);
|
|
2580
2638
|
return await assertCrossBatchRefsDeployed(client, definitions, {
|
|
2581
|
-
|
|
2639
|
+
tag,
|
|
2582
2640
|
workflowResource,
|
|
2583
|
-
prefix,
|
|
2584
2641
|
findInBatch
|
|
2585
|
-
}), topoSortDefinitions(definitions, { workflowResource,
|
|
2642
|
+
}), topoSortDefinitions(definitions, { workflowResource, tag, findInBatch });
|
|
2586
2643
|
}
|
|
2587
2644
|
function findRefInBatch(byName, ref) {
|
|
2588
2645
|
const candidates = byName.get(ref.name);
|
|
@@ -2595,10 +2652,10 @@ function findRefInBatch(byName, ref) {
|
|
|
2595
2652
|
async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
|
|
2596
2653
|
const missing = [];
|
|
2597
2654
|
for (const def of definitions) {
|
|
2598
|
-
const fromId = definitionDocId(ctx.workflowResource, ctx.
|
|
2655
|
+
const fromId = definitionDocId(ctx.workflowResource, ctx.tag, def.name, def.version);
|
|
2599
2656
|
for (const ref of refsOf(def)) {
|
|
2600
2657
|
if (ctx.findInBatch(ref) !== void 0) continue;
|
|
2601
|
-
const label = await resolveDeployedRefLabel(client, ref, ctx.
|
|
2658
|
+
const label = await resolveDeployedRefLabel(client, ref, ctx.tag);
|
|
2602
2659
|
label !== void 0 && missing.push({ from: fromId, ref: label });
|
|
2603
2660
|
}
|
|
2604
2661
|
}
|
|
@@ -2610,8 +2667,8 @@ async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
|
|
|
2610
2667
|
`)
|
|
2611
2668
|
);
|
|
2612
2669
|
}
|
|
2613
|
-
async function resolveDeployedRefLabel(client, ref,
|
|
2614
|
-
const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name,
|
|
2670
|
+
async function resolveDeployedRefLabel(client, ref, tag) {
|
|
2671
|
+
const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
|
|
2615
2672
|
if (wantsExplicit && (params.version = ref.version), !await client.fetch(
|
|
2616
2673
|
definitionLookupGroq(wantsExplicit),
|
|
2617
2674
|
params
|
|
@@ -2620,7 +2677,7 @@ async function resolveDeployedRefLabel(client, ref, tags) {
|
|
|
2620
2677
|
}
|
|
2621
2678
|
function topoSortDefinitions(definitions, ctx) {
|
|
2622
2679
|
const visited = /* @__PURE__ */ new Set(), visiting = /* @__PURE__ */ new Set(), ordered = [], visit = (def) => {
|
|
2623
|
-
const id = definitionDocId(ctx.workflowResource, ctx.
|
|
2680
|
+
const id = definitionDocId(ctx.workflowResource, ctx.tag, def.name, def.version);
|
|
2624
2681
|
if (!visited.has(id)) {
|
|
2625
2682
|
if (visiting.has(id))
|
|
2626
2683
|
throw new Error(`workflow.deployDefinitions: dependency cycle involving "${id}"`);
|
|
@@ -2661,11 +2718,11 @@ function stableStringify(value) {
|
|
|
2661
2718
|
([a], [b]) => a.localeCompare(b)
|
|
2662
2719
|
).map(([k, v2]) => `${JSON.stringify(k)}:${stableStringify(v2)}`).join(",")}}`;
|
|
2663
2720
|
}
|
|
2664
|
-
async function loadDefinition(client, definition, version,
|
|
2721
|
+
async function loadDefinition(client, definition, version, tag) {
|
|
2665
2722
|
if (version !== void 0) {
|
|
2666
2723
|
const doc = await client.fetch(
|
|
2667
2724
|
definitionLookupGroq(!0),
|
|
2668
|
-
{ definition, version,
|
|
2725
|
+
{ definition, version, tag }
|
|
2669
2726
|
);
|
|
2670
2727
|
if (!doc)
|
|
2671
2728
|
throw new Error(`Workflow definition ${definition} v${version} not deployed`);
|
|
@@ -2673,23 +2730,63 @@ async function loadDefinition(client, definition, version, tags) {
|
|
|
2673
2730
|
}
|
|
2674
2731
|
const latest = await client.fetch(definitionLookupGroq(!1), {
|
|
2675
2732
|
definition,
|
|
2676
|
-
|
|
2733
|
+
tag
|
|
2677
2734
|
});
|
|
2678
2735
|
if (!latest)
|
|
2679
2736
|
throw new Error(`No deployed definition for workflow ${definition}`);
|
|
2680
2737
|
return latest;
|
|
2681
2738
|
}
|
|
2682
|
-
async function loadDefinitionVersions(client, definition,
|
|
2739
|
+
async function loadDefinitionVersions(client, definition, tag) {
|
|
2683
2740
|
return client.fetch(
|
|
2684
|
-
`*[_type == "${WORKFLOW_DEFINITION_TYPE}" && name == $definition &&
|
|
2685
|
-
{ definition,
|
|
2741
|
+
`*[_type == "${WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}] | order(version desc)`,
|
|
2742
|
+
{ definition, tag }
|
|
2686
2743
|
);
|
|
2687
2744
|
}
|
|
2745
|
+
async function abortInstance(args) {
|
|
2746
|
+
const { client, instanceId, reason, options } = args, ctx = await loadContext(client, instanceId, {
|
|
2747
|
+
...options?.clock ? { clock: options.clock } : {},
|
|
2748
|
+
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
2749
|
+
});
|
|
2750
|
+
return commitAbort(ctx, reason, options?.actor);
|
|
2751
|
+
}
|
|
2752
|
+
async function commitAbort(ctx, reason, actor) {
|
|
2753
|
+
if (ctx.instance.completedAt !== void 0)
|
|
2754
|
+
return { fired: !1 };
|
|
2755
|
+
const stage = findStage(ctx.definition, ctx.instance.currentStage), mutation = startMutation(ctx.instance), at = ctx.now, openEntry = findOpenStageEntry(mutation);
|
|
2756
|
+
return openEntry !== void 0 && (openEntry.exitedAt = at), mutation.effectHistory.push(
|
|
2757
|
+
...mutation.pendingEffects.map(
|
|
2758
|
+
(pending) => buildEffectHistoryEntry(pending, {
|
|
2759
|
+
status: "failed",
|
|
2760
|
+
ranAt: at,
|
|
2761
|
+
actor,
|
|
2762
|
+
detail: "instance aborted before dispatch",
|
|
2763
|
+
error: void 0,
|
|
2764
|
+
durationMs: void 0,
|
|
2765
|
+
outputs: void 0
|
|
2766
|
+
})
|
|
2767
|
+
)
|
|
2768
|
+
), mutation.pendingEffects = [], mutation.history.push({
|
|
2769
|
+
_key: randomKey(),
|
|
2770
|
+
_type: "aborted",
|
|
2771
|
+
at,
|
|
2772
|
+
stage: stage.name,
|
|
2773
|
+
...reason !== void 0 ? { reason } : {},
|
|
2774
|
+
...actor !== void 0 ? { actor } : {}
|
|
2775
|
+
}), mutation.completedAt = at, mutation.abortedAt = at, await persist(ctx, mutation), await retractStageGuards({
|
|
2776
|
+
client: ctx.client,
|
|
2777
|
+
clientForGdr: ctx.clientForGdr,
|
|
2778
|
+
instance: ctx.instance,
|
|
2779
|
+
definition: ctx.definition,
|
|
2780
|
+
stageName: stage.name,
|
|
2781
|
+
now: ctx.now
|
|
2782
|
+
}), { fired: !0, stage: stage.name };
|
|
2783
|
+
}
|
|
2688
2784
|
async function fireAction(args) {
|
|
2689
2785
|
const { client, instanceId, task, action, params, options } = args;
|
|
2690
2786
|
for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
|
|
2691
2787
|
const ctx = await loadContext(client, instanceId, {
|
|
2692
|
-
...options?.clock ? { clock: options.clock } : {}
|
|
2788
|
+
...options?.clock ? { clock: options.clock } : {},
|
|
2789
|
+
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
2693
2790
|
});
|
|
2694
2791
|
try {
|
|
2695
2792
|
return await commitAction(ctx, task, action, params, options);
|
|
@@ -2793,7 +2890,7 @@ function bareIdFromSpawnRef(uri) {
|
|
|
2793
2890
|
}
|
|
2794
2891
|
}
|
|
2795
2892
|
async function resolveOperationContext(args) {
|
|
2796
|
-
|
|
2893
|
+
validateTag(args.tag);
|
|
2797
2894
|
const access = await resolveAccess(args.client, {
|
|
2798
2895
|
...args.access !== void 0 ? { override: args.access } : {},
|
|
2799
2896
|
...args.grantsFromPath !== void 0 ? { grantsFromPath: args.grantsFromPath } : {}
|
|
@@ -2815,19 +2912,18 @@ async function cascade(client, instanceId, actor, clientForGdr, clock, overlay)
|
|
|
2815
2912
|
);
|
|
2816
2913
|
return await propagateToAncestors(client, instanceId, actor, clientForGdr, clock), count;
|
|
2817
2914
|
}
|
|
2915
|
+
async function abortAndPropagate(args) {
|
|
2916
|
+
const { client, instanceId, reason, actor, clientForGdr, clock } = args, result = await abortInstance({
|
|
2917
|
+
client,
|
|
2918
|
+
instanceId,
|
|
2919
|
+
...reason !== void 0 ? { reason } : {},
|
|
2920
|
+
options: { ...actor !== void 0 ? { actor } : {}, clock, clientForGdr }
|
|
2921
|
+
});
|
|
2922
|
+
return result.fired && await propagateToAncestors(client, instanceId, actor, clientForGdr, clock), result.fired;
|
|
2923
|
+
}
|
|
2818
2924
|
function buildClientForGdr(defaultClient, resolver) {
|
|
2819
2925
|
return resolver === void 0 ? () => defaultClient : (parsed) => resolver(parsed) ?? defaultClient;
|
|
2820
2926
|
}
|
|
2821
|
-
async function reload(client, instanceId, tags) {
|
|
2822
|
-
const doc = await client.getDocument(instanceId);
|
|
2823
|
-
if (!doc) throw new Error(`Workflow instance ${instanceId} not found`);
|
|
2824
|
-
if (!intersectsTags(doc.tags, tags))
|
|
2825
|
-
throw new Error(`Workflow instance ${instanceId} not visible to this engine (tag mismatch)`);
|
|
2826
|
-
return doc;
|
|
2827
|
-
}
|
|
2828
|
-
function intersectsTags(docTags, engineTags) {
|
|
2829
|
-
return !docTags || docTags.length === 0 ? !1 : engineTags.some((t) => docTags.includes(t));
|
|
2830
|
-
}
|
|
2831
2927
|
function toEffectsContextEntries(ctx) {
|
|
2832
2928
|
return Object.entries(ctx).map(([id, value]) => effectsContextEntry(id, value));
|
|
2833
2929
|
}
|
|
@@ -2837,10 +2933,11 @@ function assertActionAllowed(evaluation, task, action) {
|
|
|
2837
2933
|
throw new ActionDisabledError({ task, action, reason: actionEval.disabledReason });
|
|
2838
2934
|
}
|
|
2839
2935
|
async function applyAction(args) {
|
|
2840
|
-
const { client, instance, task, action, params, actor, grants, clock } = args, options = {
|
|
2936
|
+
const { client, instance, task, action, params, actor, grants, clock, clientForGdr } = args, options = {
|
|
2841
2937
|
actor,
|
|
2842
2938
|
...grants !== void 0 ? { grants } : {},
|
|
2843
|
-
...clock !== void 0 ? { clock } : {}
|
|
2939
|
+
...clock !== void 0 ? { clock } : {},
|
|
2940
|
+
...clientForGdr !== void 0 ? { clientForGdr } : {}
|
|
2844
2941
|
};
|
|
2845
2942
|
return findOpenStageEntry(instance)?.tasks.find((t) => t.name === task)?.status === "pending" && await invokeTask({ client, instanceId: instance._id, task, options }), (await fireAction({
|
|
2846
2943
|
client,
|
|
@@ -2851,6 +2948,78 @@ async function applyAction(args) {
|
|
|
2851
2948
|
options
|
|
2852
2949
|
})).ranOps;
|
|
2853
2950
|
}
|
|
2951
|
+
async function deleteDefinition(args) {
|
|
2952
|
+
const { client, tag, definition, version, cascade: cascade2, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, versions = await loadDefinitionVersions(client, definition, tag);
|
|
2953
|
+
if (versions.length === 0)
|
|
2954
|
+
throw new Error(`No deployed definition for workflow ${definition}`);
|
|
2955
|
+
const targets = version === void 0 ? versions : versions.filter((d) => d.version === version);
|
|
2956
|
+
if (targets.length === 0)
|
|
2957
|
+
throw new Error(`Workflow definition ${definition} v${version} not deployed`);
|
|
2958
|
+
const lastVersionGoes = targets.length === versions.length;
|
|
2959
|
+
await assertNoSpawnReferrers(client, tag, definition, targets, lastVersionGoes);
|
|
2960
|
+
const instanceIds = await nonTerminalInstanceIds(client, tag, definition, version);
|
|
2961
|
+
if (instanceIds.length > 0 && cascade2 !== !0)
|
|
2962
|
+
throw new Error(
|
|
2963
|
+
`Cannot delete ${definition}: ${instanceIds.length} non-terminal instance(s) exist (${previewIds(instanceIds)}). Pass cascade to abort them first \u2014 instances are aborted in place, never deleted.`
|
|
2964
|
+
);
|
|
2965
|
+
const abortedInstanceIds = await abortInstances({
|
|
2966
|
+
client,
|
|
2967
|
+
instanceIds,
|
|
2968
|
+
reason,
|
|
2969
|
+
actor,
|
|
2970
|
+
clientForGdr,
|
|
2971
|
+
clock
|
|
2972
|
+
}), tx = client.transaction();
|
|
2973
|
+
for (const target of targets) tx.delete(target._id);
|
|
2974
|
+
await tx.commit();
|
|
2975
|
+
const deletedGuardCount = lastVersionGoes ? await deleteOrphanedDefinitionGuards({
|
|
2976
|
+
client,
|
|
2977
|
+
clientForGdr,
|
|
2978
|
+
workflowResource: args.workflowResource,
|
|
2979
|
+
definition,
|
|
2980
|
+
definitions: versions,
|
|
2981
|
+
tag
|
|
2982
|
+
}) : 0;
|
|
2983
|
+
return {
|
|
2984
|
+
name: definition,
|
|
2985
|
+
deletedVersions: targets.map((d) => d.version),
|
|
2986
|
+
abortedInstanceIds,
|
|
2987
|
+
deletedGuardCount
|
|
2988
|
+
};
|
|
2989
|
+
}
|
|
2990
|
+
async function assertNoSpawnReferrers(client, tag, definition, targets, lastVersionGoes) {
|
|
2991
|
+
const deployed = await client.fetch(
|
|
2992
|
+
`*[_type == "${WORKFLOW_DEFINITION_TYPE}" && ${tagScopeFilter()}]`,
|
|
2993
|
+
{ tag }
|
|
2994
|
+
), targetIds = new Set(targets.map((d) => d._id)), targetVersions = new Set(targets.map((d) => d.version)), referrers = deployed.filter((d) => !targetIds.has(d._id)).filter(
|
|
2995
|
+
(d) => refsOf(d).some(
|
|
2996
|
+
(ref) => ref.name === definition && (typeof ref.version == "number" ? targetVersions.has(ref.version) : lastVersionGoes)
|
|
2997
|
+
)
|
|
2998
|
+
);
|
|
2999
|
+
if (referrers.length > 0) {
|
|
3000
|
+
const names = referrers.map((d) => `${d.name} v${d.version}`).join(", ");
|
|
3001
|
+
throw new Error(
|
|
3002
|
+
`Cannot delete ${definition}: still spawn-referenced by deployed definition(s) ${names}. Delete or redeploy the referrer(s) first.`
|
|
3003
|
+
);
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
async function nonTerminalInstanceIds(client, tag, definition, version) {
|
|
3007
|
+
const versionFilter = version === void 0 ? "" : " && pinnedVersion == $version";
|
|
3008
|
+
return (await client.fetch(
|
|
3009
|
+
`*[_type == "${WORKFLOW_INSTANCE_TYPE}" && definition == $definition && ${tagScopeFilter()} && !defined(completedAt)${versionFilter}] | order(_id asc){_id}`,
|
|
3010
|
+
{ definition, tag, ...version !== void 0 ? { version } : {} }
|
|
3011
|
+
)).map((doc) => doc._id);
|
|
3012
|
+
}
|
|
3013
|
+
async function abortInstances(args) {
|
|
3014
|
+
const { client, instanceIds, reason, actor, clientForGdr, clock } = args, aborted = [];
|
|
3015
|
+
for (const instanceId of instanceIds)
|
|
3016
|
+
await abortAndPropagate({ client, instanceId, reason, actor, clientForGdr, clock }) && aborted.push(instanceId);
|
|
3017
|
+
return aborted;
|
|
3018
|
+
}
|
|
3019
|
+
function previewIds(ids) {
|
|
3020
|
+
const head = ids.slice(0, 3).join(", ");
|
|
3021
|
+
return ids.length > 3 ? `${head}, \u2026` : head;
|
|
3022
|
+
}
|
|
2854
3023
|
const workflow = {
|
|
2855
3024
|
/**
|
|
2856
3025
|
* Deploy a set of workflow definitions as one call. Each lands as a
|
|
@@ -2869,20 +3038,20 @@ const workflow = {
|
|
|
2869
3038
|
* Cycles in the dependency graph error before any write happens.
|
|
2870
3039
|
*/
|
|
2871
3040
|
deployDefinitions: async (args) => {
|
|
2872
|
-
const { client,
|
|
2873
|
-
|
|
3041
|
+
const { client, tag, workflowResource, definitions } = args;
|
|
3042
|
+
validateTag(tag);
|
|
2874
3043
|
for (const def of definitions)
|
|
2875
3044
|
validateDefinition(def);
|
|
2876
|
-
const ordered = await sortByDependencies(client, definitions,
|
|
3045
|
+
const ordered = await sortByDependencies(client, definitions, tag, workflowResource), results = [], tx = client.transaction();
|
|
2877
3046
|
let hasWrites = !1;
|
|
2878
3047
|
for (const def of ordered) {
|
|
2879
|
-
const id = definitionDocId(workflowResource,
|
|
3048
|
+
const id = definitionDocId(workflowResource, tag, def.name, def.version), expected = {
|
|
2880
3049
|
...def,
|
|
2881
3050
|
_id: id,
|
|
2882
3051
|
_type: WORKFLOW_DEFINITION_TYPE,
|
|
2883
|
-
|
|
3052
|
+
tag
|
|
2884
3053
|
}, existing = await client.getDocument(id);
|
|
2885
|
-
if (existing &&
|
|
3054
|
+
if (existing && existing.tag === tag && isDefinitionUnchanged(existing, expected)) {
|
|
2886
3055
|
results.push({ definition: def.name, version: def.version, status: "unchanged" });
|
|
2887
3056
|
continue;
|
|
2888
3057
|
}
|
|
@@ -2901,6 +3070,14 @@ const workflow = {
|
|
|
2901
3070
|
}
|
|
2902
3071
|
return hasWrites && await tx.commit(), { results };
|
|
2903
3072
|
},
|
|
3073
|
+
/**
|
|
3074
|
+
* Remove a deployed workflow definition (all versions, or one via
|
|
3075
|
+
* `version`). Refuses while non-terminal instances exist unless
|
|
3076
|
+
* `cascade` aborts them first — instances are never deleted, only
|
|
3077
|
+
* aborted in place; see {@link deleteDefinitionInternal} for the
|
|
3078
|
+
* full contract (spawn-referrer check, guard-doc housekeeping).
|
|
3079
|
+
*/
|
|
3080
|
+
deleteDefinition: async (args) => deleteDefinition(args),
|
|
2904
3081
|
/**
|
|
2905
3082
|
* Spawn a new workflow instance from a deployed definition.
|
|
2906
3083
|
*
|
|
@@ -2912,7 +3089,7 @@ const workflow = {
|
|
|
2912
3089
|
startInstance: async (args) => {
|
|
2913
3090
|
const {
|
|
2914
3091
|
client,
|
|
2915
|
-
|
|
3092
|
+
tag,
|
|
2916
3093
|
workflowResource,
|
|
2917
3094
|
definition: definitionName,
|
|
2918
3095
|
version,
|
|
@@ -2921,7 +3098,7 @@ const workflow = {
|
|
|
2921
3098
|
effectsContext,
|
|
2922
3099
|
instanceId,
|
|
2923
3100
|
perspective
|
|
2924
|
-
} = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, definition = await loadDefinition(client, definitionName, version,
|
|
3101
|
+
} = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, definition = await loadDefinition(client, definitionName, version, tag), id = instanceId ?? `${tag}.wf-instance.${randomKey()}`;
|
|
2925
3102
|
if (definition.stages.find((s) => s.name === definition.initialStage) === void 0)
|
|
2926
3103
|
throw new Error(
|
|
2927
3104
|
`Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
|
|
@@ -2933,7 +3110,7 @@ const workflow = {
|
|
|
2933
3110
|
client,
|
|
2934
3111
|
now,
|
|
2935
3112
|
selfId: id,
|
|
2936
|
-
|
|
3113
|
+
tag,
|
|
2937
3114
|
workflowResource,
|
|
2938
3115
|
...perspective !== void 0 ? { perspective } : {}
|
|
2939
3116
|
},
|
|
@@ -2941,7 +3118,7 @@ const workflow = {
|
|
|
2941
3118
|
}), effectivePerspective = perspective ?? derivePerspectiveFromState(resolvedState), base = buildInstanceBase({
|
|
2942
3119
|
id,
|
|
2943
3120
|
now,
|
|
2944
|
-
|
|
3121
|
+
tag,
|
|
2945
3122
|
workflowResource,
|
|
2946
3123
|
definitionName: definition.name,
|
|
2947
3124
|
pinnedVersion: definition.version,
|
|
@@ -2953,7 +3130,7 @@ const workflow = {
|
|
|
2953
3130
|
initialStage: definition.initialStage,
|
|
2954
3131
|
actor
|
|
2955
3132
|
});
|
|
2956
|
-
return await client.create(base), await primeInitialStage(client, id, actor, clientForGdr, clock), await cascade(client, id, actor, clientForGdr, clock), reload(client, id,
|
|
3133
|
+
return await client.create(base), await primeInitialStage(client, id, actor, clientForGdr, clock), await cascade(client, id, actor, clientForGdr, clock), reload(client, id, tag);
|
|
2957
3134
|
},
|
|
2958
3135
|
/**
|
|
2959
3136
|
* Fire an action against a task. If the task is pending, it is
|
|
@@ -2968,19 +3145,19 @@ const workflow = {
|
|
|
2968
3145
|
fireAction: async (args) => {
|
|
2969
3146
|
const {
|
|
2970
3147
|
client,
|
|
2971
|
-
|
|
3148
|
+
tag,
|
|
2972
3149
|
instanceId,
|
|
2973
3150
|
task,
|
|
2974
3151
|
action,
|
|
2975
3152
|
params,
|
|
2976
3153
|
idempotent,
|
|
2977
3154
|
resourceClients
|
|
2978
|
-
} = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId,
|
|
3155
|
+
} = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
|
|
2979
3156
|
if (findOpenStageEntry(before)?.tasks.find((t) => t.name === task) === void 0 && idempotent === !0)
|
|
2980
3157
|
return { instance: before, cascaded: 0, fired: !1 };
|
|
2981
3158
|
const evaluation = await evaluateInstance({
|
|
2982
3159
|
client,
|
|
2983
|
-
|
|
3160
|
+
tag,
|
|
2984
3161
|
instanceId,
|
|
2985
3162
|
access,
|
|
2986
3163
|
clock,
|
|
@@ -2995,10 +3172,11 @@ const workflow = {
|
|
|
2995
3172
|
actor,
|
|
2996
3173
|
...access.grants !== void 0 ? { grants: access.grants } : {},
|
|
2997
3174
|
clock,
|
|
3175
|
+
clientForGdr,
|
|
2998
3176
|
...params !== void 0 ? { params } : {}
|
|
2999
3177
|
}), cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
|
|
3000
3178
|
return {
|
|
3001
|
-
instance: await reload(client, instanceId,
|
|
3179
|
+
instance: await reload(client, instanceId, tag),
|
|
3002
3180
|
cascaded,
|
|
3003
3181
|
fired: !0,
|
|
3004
3182
|
...ranOps !== void 0 ? { ranOps } : {}
|
|
@@ -3012,7 +3190,7 @@ const workflow = {
|
|
|
3012
3190
|
* reference them. Cascades after.
|
|
3013
3191
|
*/
|
|
3014
3192
|
completeEffect: async (args) => {
|
|
3015
|
-
const { client,
|
|
3193
|
+
const { client, tag, instanceId, effectKey, status, outputs, detail, error, durationMs } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
|
|
3016
3194
|
await completeEffect({
|
|
3017
3195
|
client,
|
|
3018
3196
|
instanceId,
|
|
@@ -3022,11 +3200,11 @@ const workflow = {
|
|
|
3022
3200
|
...detail !== void 0 ? { detail } : {},
|
|
3023
3201
|
...error !== void 0 ? { error } : {},
|
|
3024
3202
|
...durationMs !== void 0 ? { durationMs } : {},
|
|
3025
|
-
options: { ...actor !== void 0 ? { actor } : {}, clock }
|
|
3203
|
+
options: { ...actor !== void 0 ? { actor } : {}, clock, clientForGdr }
|
|
3026
3204
|
});
|
|
3027
3205
|
const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
|
|
3028
3206
|
return {
|
|
3029
|
-
instance: await reload(client, instanceId,
|
|
3207
|
+
instance: await reload(client, instanceId, tag),
|
|
3030
3208
|
cascaded,
|
|
3031
3209
|
fired: !0
|
|
3032
3210
|
};
|
|
@@ -3041,11 +3219,11 @@ const workflow = {
|
|
|
3041
3219
|
* affected instances and the engine re-evaluates.
|
|
3042
3220
|
*/
|
|
3043
3221
|
tick: async (args) => {
|
|
3044
|
-
const { client,
|
|
3222
|
+
const { client, tag, instanceId } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, current = await reload(client, instanceId, tag), guards = await verdictGuardsForInstance(client, current._id);
|
|
3045
3223
|
await assertInstanceWriteAllowed({ instance: current, guards, identity: actor.id });
|
|
3046
3224
|
const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
|
|
3047
3225
|
return {
|
|
3048
|
-
instance: await reload(client, instanceId,
|
|
3226
|
+
instance: await reload(client, instanceId, tag),
|
|
3049
3227
|
cascaded,
|
|
3050
3228
|
fired: cascaded > 0
|
|
3051
3229
|
};
|
|
@@ -3056,56 +3234,50 @@ const workflow = {
|
|
|
3056
3234
|
* upstream; this verb performs the mechanical move.
|
|
3057
3235
|
*/
|
|
3058
3236
|
setStage: async (args) => {
|
|
3059
|
-
const { client,
|
|
3060
|
-
await reload(client, instanceId,
|
|
3237
|
+
const { client, tag, instanceId, targetStage, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
|
|
3238
|
+
await reload(client, instanceId, tag);
|
|
3061
3239
|
const result = await setStage({
|
|
3062
3240
|
client,
|
|
3063
3241
|
instanceId,
|
|
3064
3242
|
targetStage,
|
|
3065
3243
|
...reason !== void 0 ? { reason } : {},
|
|
3066
|
-
options: { ...actor !== void 0 ? { actor } : {}, clock }
|
|
3244
|
+
options: { ...actor !== void 0 ? { actor } : {}, clock, clientForGdr }
|
|
3067
3245
|
}), cascaded = result.fired ? await cascade(client, instanceId, actor, clientForGdr, clock) : 0;
|
|
3068
3246
|
return {
|
|
3069
|
-
instance: await reload(client, instanceId,
|
|
3247
|
+
instance: await reload(client, instanceId, tag),
|
|
3070
3248
|
cascaded,
|
|
3071
3249
|
fired: result.fired
|
|
3072
3250
|
};
|
|
3073
3251
|
},
|
|
3074
3252
|
/**
|
|
3075
3253
|
* Admin override — hard-stop an in-flight instance where it stands.
|
|
3076
|
-
* No stage move, no transition effects, pending effects cancelled;
|
|
3077
|
-
*
|
|
3078
|
-
*
|
|
3079
|
-
* so a parent gate waiting on this child re-evaluates.
|
|
3254
|
+
* No stage move, no transition effects, pending effects cancelled;
|
|
3255
|
+
* see {@link abortAndPropagate} for the abort + ancestor-propagation
|
|
3256
|
+
* contract (propagated, not cascaded — the instance is terminal).
|
|
3080
3257
|
*/
|
|
3081
3258
|
abortInstance: async (args) => {
|
|
3082
|
-
const { client,
|
|
3083
|
-
await reload(client, instanceId,
|
|
3084
|
-
const
|
|
3085
|
-
|
|
3086
|
-
instanceId,
|
|
3087
|
-
...reason !== void 0 ? { reason } : {},
|
|
3088
|
-
options: { ...actor !== void 0 ? { actor } : {}, clock }
|
|
3089
|
-
});
|
|
3090
|
-
return result.fired && await propagateToAncestors(client, instanceId, actor, clientForGdr, clock), {
|
|
3091
|
-
instance: await reload(client, instanceId, tags),
|
|
3259
|
+
const { client, tag, instanceId, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
|
|
3260
|
+
await reload(client, instanceId, tag);
|
|
3261
|
+
const fired = await abortAndPropagate({ client, instanceId, reason, actor, clientForGdr, clock });
|
|
3262
|
+
return {
|
|
3263
|
+
instance: await reload(client, instanceId, tag),
|
|
3092
3264
|
cascaded: 0,
|
|
3093
|
-
fired
|
|
3265
|
+
fired
|
|
3094
3266
|
};
|
|
3095
3267
|
},
|
|
3096
3268
|
/**
|
|
3097
|
-
* Fetch a workflow instance by id, scoped to the engine's
|
|
3269
|
+
* Fetch a workflow instance by id, scoped to the engine's tag.
|
|
3098
3270
|
* Throws when the instance doesn't exist or isn't visible to this
|
|
3099
3271
|
* engine.
|
|
3100
3272
|
*/
|
|
3101
3273
|
getInstance: async (args) => {
|
|
3102
|
-
const { client,
|
|
3103
|
-
return
|
|
3274
|
+
const { client, tag, instanceId } = args;
|
|
3275
|
+
return validateTag(tag), reload(client, instanceId, tag);
|
|
3104
3276
|
},
|
|
3105
3277
|
guardsForInstance: async (args) => {
|
|
3106
|
-
const { client,
|
|
3107
|
-
|
|
3108
|
-
const instance = await reload(client, instanceId,
|
|
3278
|
+
const { client, tag, instanceId, resourceClients } = args;
|
|
3279
|
+
validateTag(tag);
|
|
3280
|
+
const instance = await reload(client, instanceId, tag);
|
|
3109
3281
|
return guardsForInstance({
|
|
3110
3282
|
client,
|
|
3111
3283
|
clientForGdr: buildClientForGdr(client, resourceClients),
|
|
@@ -3113,9 +3285,9 @@ const workflow = {
|
|
|
3113
3285
|
});
|
|
3114
3286
|
},
|
|
3115
3287
|
guardsForDefinition: async (args) => {
|
|
3116
|
-
const { client,
|
|
3117
|
-
|
|
3118
|
-
const definitions = await loadDefinitionVersions(client, definition,
|
|
3288
|
+
const { client, tag, workflowResource, definition, resourceClients } = args;
|
|
3289
|
+
validateTag(tag);
|
|
3290
|
+
const definitions = await loadDefinitionVersions(client, definition, tag);
|
|
3119
3291
|
if (definitions.length === 0)
|
|
3120
3292
|
throw new Error(`No deployed definition for workflow ${definition}`);
|
|
3121
3293
|
return guardsForDefinition({
|
|
@@ -3127,22 +3299,21 @@ const workflow = {
|
|
|
3127
3299
|
});
|
|
3128
3300
|
},
|
|
3129
3301
|
/**
|
|
3130
|
-
* Run a caller-supplied GROQ query with the engine's
|
|
3131
|
-
* `$
|
|
3302
|
+
* Run a caller-supplied GROQ query with the engine's tag bound as
|
|
3303
|
+
* `$tag`. This does NOT rewrite the query — arbitrary GROQ
|
|
3132
3304
|
* can't be safely tag-scoped after the fact — so the CALLER MUST
|
|
3133
|
-
* filter on `$
|
|
3134
|
-
*
|
|
3135
|
-
* cross-tenant reads, a query that never references `$engineTags` is
|
|
3305
|
+
* filter on `$tag` (e.g. `tag == $tag`). To guard against accidental
|
|
3306
|
+
* cross-partition reads, a query that never references `$tag` is
|
|
3136
3307
|
* rejected before it reaches the lake. Caller is responsible for type
|
|
3137
3308
|
* narrowing the result.
|
|
3138
3309
|
*/
|
|
3139
3310
|
query: async (args) => {
|
|
3140
|
-
const { client,
|
|
3141
|
-
if (
|
|
3311
|
+
const { client, tag, groq, params } = args;
|
|
3312
|
+
if (validateTag(tag), !/\$tag\b/.test(groq))
|
|
3142
3313
|
throw new Error(
|
|
3143
|
-
`workflow.query: query must filter on $
|
|
3314
|
+
`workflow.query: query must filter on $tag to stay tag-scoped (e.g. "tag == $tag"); got: ${groq}`
|
|
3144
3315
|
);
|
|
3145
|
-
return client.fetch(groq, { ...params,
|
|
3316
|
+
return client.fetch(groq, { ...params, tag });
|
|
3146
3317
|
},
|
|
3147
3318
|
/**
|
|
3148
3319
|
* Snapshot-aware GROQ — runs against the same in-memory view that
|
|
@@ -3161,9 +3332,9 @@ const workflow = {
|
|
|
3161
3332
|
* without re-implementing hydration. Pure read — never writes.
|
|
3162
3333
|
*/
|
|
3163
3334
|
queryInScope: async (args) => {
|
|
3164
|
-
const { client,
|
|
3165
|
-
|
|
3166
|
-
const instance = await reload(client, instanceId,
|
|
3335
|
+
const { client, tag, instanceId, groq, params, resourceClients } = args, clock = args.clock ?? wallClock;
|
|
3336
|
+
validateTag(tag);
|
|
3337
|
+
const instance = await reload(client, instanceId, tag), clientForGdr = buildClientForGdr(client, resourceClients), snapshot = await hydrateSnapshot({ client, clientForGdr, instance }), reserved = buildParams({ instance, now: clock(), snapshot }), tree = parse(groq, { params: { ...reserved, ...params } });
|
|
3167
3338
|
return await (await evaluate(tree, {
|
|
3168
3339
|
dataset: snapshot.docs,
|
|
3169
3340
|
params: { ...reserved, ...params }
|
|
@@ -3173,13 +3344,13 @@ const workflow = {
|
|
|
3173
3344
|
* List every pending effect on the instance. Returns the same entries
|
|
3174
3345
|
* the runtime would see — claimed and unclaimed alike.
|
|
3175
3346
|
*/
|
|
3176
|
-
listPendingEffects: async (args) => (await reload(args.client, args.instanceId, args.
|
|
3347
|
+
listPendingEffects: async (args) => (await reload(args.client, args.instanceId, args.tag)).pendingEffects,
|
|
3177
3348
|
/**
|
|
3178
3349
|
* Filter pending effects on the instance by criteria. `claimed`
|
|
3179
3350
|
* filters on claim presence; `names` restricts to specific effect
|
|
3180
3351
|
* names. Both filters compose (AND).
|
|
3181
3352
|
*/
|
|
3182
|
-
findPendingEffects: async (args) => (await reload(args.client, args.instanceId, args.
|
|
3353
|
+
findPendingEffects: async (args) => (await reload(args.client, args.instanceId, args.tag)).pendingEffects.filter((e) => !(args.claimed === !0 && e.claim === void 0 || args.claimed === !1 && e.claim !== void 0 || args.names !== void 0 && !args.names.includes(e.name))),
|
|
3183
3354
|
/**
|
|
3184
3355
|
* Project the instance from a given actor's perspective. Returns a
|
|
3185
3356
|
* `WorkflowEvaluation` with per-action verdicts (`allowed` + a
|
|
@@ -3189,6 +3360,30 @@ const workflow = {
|
|
|
3189
3360
|
* `fireAction` to gate writes via the same logic.
|
|
3190
3361
|
*/
|
|
3191
3362
|
evaluate: async (args) => evaluateInstance(args),
|
|
3363
|
+
/**
|
|
3364
|
+
* Diagnose why an instance is or isn't progressing. Projects the
|
|
3365
|
+
* instance (the same read as `evaluate`) and classifies it — terminal,
|
|
3366
|
+
* `progressing`, `waiting` (an action is available — healthy), or `stuck`
|
|
3367
|
+
* with a structured cause — returning that verdict as a
|
|
3368
|
+
* {@link DiagnoseResult} alongside the evaluation it came from, so a
|
|
3369
|
+
* consumer can render the supporting evidence without a second projection.
|
|
3370
|
+
* Pure read.
|
|
3371
|
+
*/
|
|
3372
|
+
diagnose: async (args) => {
|
|
3373
|
+
const evaluation = await evaluateInstance(args);
|
|
3374
|
+
return { evaluation, diagnosis: diagnoseInstance(diagnoseInputFromEvaluation(evaluation)) };
|
|
3375
|
+
},
|
|
3376
|
+
/**
|
|
3377
|
+
* List the actions an actor could fire on an instance's current stage,
|
|
3378
|
+
* each flagged `allowed` (with a structured `disabledReason` when not).
|
|
3379
|
+
* Projects the instance from the actor's perspective and flattens its
|
|
3380
|
+
* tasks' actions. Returns the evaluation alongside the actions so a
|
|
3381
|
+
* consumer can read the instance/stage context. Pure read.
|
|
3382
|
+
*/
|
|
3383
|
+
availableActions: async (args) => {
|
|
3384
|
+
const evaluation = await evaluateInstance(args);
|
|
3385
|
+
return { evaluation, actions: availableActions(evaluation.currentStage.tasks) };
|
|
3386
|
+
},
|
|
3192
3387
|
/**
|
|
3193
3388
|
* Materialised spawned children of a parent instance.
|
|
3194
3389
|
*
|
|
@@ -3196,18 +3391,18 @@ const workflow = {
|
|
|
3196
3391
|
* record. (The per-task `spawnedInstances` field on the active stage
|
|
3197
3392
|
* only exists while that stage is current; history survives.) Strips
|
|
3198
3393
|
* the GDR URI on each `instanceRef` to a bare `_id`, fetches the
|
|
3199
|
-
* instances, drops any that aren't visible to this engine's
|
|
3394
|
+
* instances, drops any that aren't visible to this engine's tag, and
|
|
3200
3395
|
* returns them sorted by `startedAt` ascending.
|
|
3201
3396
|
*
|
|
3202
3397
|
* Pass `task` to restrict to a single spawning task on the parent.
|
|
3203
3398
|
*/
|
|
3204
3399
|
children: async (args) => {
|
|
3205
|
-
const { client,
|
|
3206
|
-
|
|
3207
|
-
const parent = await reload(client, instanceId,
|
|
3400
|
+
const { client, tag, instanceId, task } = args;
|
|
3401
|
+
validateTag(tag);
|
|
3402
|
+
const parent = await reload(client, instanceId, tag), isSpawned = (h) => h._type === "spawned", ids = parent.history.filter(isSpawned).filter((h) => task === void 0 || h.task === task).flatMap((h) => bareIdFromSpawnRef(h.instanceRef.id));
|
|
3208
3403
|
return ids.length === 0 ? [] : client.fetch(
|
|
3209
3404
|
`*[_id in $ids && ${tagScopeFilter()}] | order(startedAt asc)`,
|
|
3210
|
-
{ ids,
|
|
3405
|
+
{ ids, tag }
|
|
3211
3406
|
);
|
|
3212
3407
|
},
|
|
3213
3408
|
/**
|
|
@@ -3222,11 +3417,11 @@ const workflow = {
|
|
|
3222
3417
|
}
|
|
3223
3418
|
};
|
|
3224
3419
|
async function verifyDeployedDefinitionsInternal(args) {
|
|
3225
|
-
const { client,
|
|
3226
|
-
|
|
3420
|
+
const { client, tag, effectHandlers, missingHandler, logger } = args;
|
|
3421
|
+
validateTag(tag);
|
|
3227
3422
|
const log = logger("verifyDeployedDefinitions"), definitions = await client.fetch(
|
|
3228
3423
|
`*[_type == "${WORKFLOW_DEFINITION_TYPE}" && ${tagScopeFilter()}] | order(name asc, version asc)`,
|
|
3229
|
-
{
|
|
3424
|
+
{ tag }
|
|
3230
3425
|
), seen = [], missingByName = /* @__PURE__ */ new Map();
|
|
3231
3426
|
for (const def of definitions)
|
|
3232
3427
|
seen.push({ name: def.name, version: def.version, _id: def._id }), walkEffectNames(def, (name, location) => {
|
|
@@ -3275,7 +3470,7 @@ function walkEffectNames(def, visit) {
|
|
|
3275
3470
|
async function drainEffectsInternal(args) {
|
|
3276
3471
|
const {
|
|
3277
3472
|
client,
|
|
3278
|
-
|
|
3473
|
+
tag,
|
|
3279
3474
|
workflowResource,
|
|
3280
3475
|
resourceClients,
|
|
3281
3476
|
instanceId,
|
|
@@ -3286,10 +3481,10 @@ async function drainEffectsInternal(args) {
|
|
|
3286
3481
|
actor: drainerActor,
|
|
3287
3482
|
...args.access?.grants !== void 0 ? { grants: args.access.grants } : {}
|
|
3288
3483
|
};
|
|
3289
|
-
|
|
3484
|
+
validateTag(tag);
|
|
3290
3485
|
const log = logger("drainEffects"), drained = [], failed = [], skipped = [], skippedKeys = /* @__PURE__ */ new Set();
|
|
3291
3486
|
for (; ; ) {
|
|
3292
|
-
const before = await reload(client, instanceId,
|
|
3487
|
+
const before = await reload(client, instanceId, tag), candidate = before.pendingEffects.find(
|
|
3293
3488
|
(e) => e.claim === void 0 && !skippedKeys.has(e._key)
|
|
3294
3489
|
);
|
|
3295
3490
|
if (candidate === void 0) break;
|
|
@@ -3306,7 +3501,7 @@ async function drainEffectsInternal(args) {
|
|
|
3306
3501
|
});
|
|
3307
3502
|
await reportEffectOutcome({
|
|
3308
3503
|
client,
|
|
3309
|
-
|
|
3504
|
+
tag,
|
|
3310
3505
|
workflowResource,
|
|
3311
3506
|
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3312
3507
|
instanceId,
|
|
@@ -3376,7 +3571,7 @@ async function applyMissingHandler(policy, info, log) {
|
|
|
3376
3571
|
}
|
|
3377
3572
|
}
|
|
3378
3573
|
function createInstanceSession(args) {
|
|
3379
|
-
const { client,
|
|
3574
|
+
const { client, tag, clock } = args, clientForGdr = buildClientForGdr(client, args.resourceClients);
|
|
3380
3575
|
let instance = asHeldInstance(args.instance), overlay = /* @__PURE__ */ new Map(), heldGuards = [], committing = !1, buffered;
|
|
3381
3576
|
const selfUri = () => gdrFromResource(instance.workflowResource, instance._id), applyUpdate = (docs) => {
|
|
3382
3577
|
const next = /* @__PURE__ */ new Map();
|
|
@@ -3437,7 +3632,7 @@ function createInstanceSession(args) {
|
|
|
3437
3632
|
return commit(async (actor, held) => {
|
|
3438
3633
|
await assertInstanceWriteAllowed({ instance, guards: heldGuards, identity: actor.id });
|
|
3439
3634
|
const cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
|
|
3440
|
-
return instance = await reload(client, instance._id,
|
|
3635
|
+
return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: cascaded > 0 };
|
|
3441
3636
|
});
|
|
3442
3637
|
},
|
|
3443
3638
|
fireAction({ task, action, params }) {
|
|
@@ -3449,11 +3644,12 @@ function createInstanceSession(args) {
|
|
|
3449
3644
|
task,
|
|
3450
3645
|
action,
|
|
3451
3646
|
actor,
|
|
3647
|
+
clientForGdr,
|
|
3452
3648
|
...grants !== void 0 ? { grants } : {},
|
|
3453
3649
|
...clock !== void 0 ? { clock } : {},
|
|
3454
3650
|
...params !== void 0 ? { params } : {}
|
|
3455
3651
|
}), cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
|
|
3456
|
-
return instance = await reload(client, instance._id,
|
|
3652
|
+
return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: !0, ...ranOps !== void 0 ? { ranOps } : {} };
|
|
3457
3653
|
});
|
|
3458
3654
|
}
|
|
3459
3655
|
};
|
|
@@ -3481,11 +3677,11 @@ const defaultLoggerFactory = (name) => ({
|
|
|
3481
3677
|
}
|
|
3482
3678
|
};
|
|
3483
3679
|
function createEngine(args) {
|
|
3484
|
-
const { client, workflowResource,
|
|
3485
|
-
|
|
3680
|
+
const { client, workflowResource, resourceClients, clock, tag } = args;
|
|
3681
|
+
validateTag(tag);
|
|
3486
3682
|
const effectHandlers = args.effectHandlers ?? {}, missingHandler = args.missingHandler ?? "fail", logger = args.loggerFactory ?? defaultLoggerFactory, bind = (rest) => ({
|
|
3487
3683
|
client,
|
|
3488
|
-
|
|
3684
|
+
tag,
|
|
3489
3685
|
workflowResource,
|
|
3490
3686
|
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3491
3687
|
...clock !== void 0 ? { clock } : {},
|
|
@@ -3493,7 +3689,7 @@ function createEngine(args) {
|
|
|
3493
3689
|
});
|
|
3494
3690
|
return {
|
|
3495
3691
|
client,
|
|
3496
|
-
|
|
3692
|
+
tag,
|
|
3497
3693
|
workflowResource,
|
|
3498
3694
|
effectHandlers,
|
|
3499
3695
|
missingHandler,
|
|
@@ -3504,13 +3700,16 @@ function createEngine(args) {
|
|
|
3504
3700
|
completeEffect: (rest) => workflow.completeEffect(bind(rest)),
|
|
3505
3701
|
tick: (rest) => workflow.tick(bind(rest)),
|
|
3506
3702
|
evaluateInstance: (rest) => evaluateInstance(bind(rest)),
|
|
3703
|
+
diagnose: (rest) => workflow.diagnose(bind(rest)),
|
|
3704
|
+
availableActions: (rest) => workflow.availableActions(bind(rest)),
|
|
3507
3705
|
setStage: (rest) => workflow.setStage(bind(rest)),
|
|
3508
3706
|
abortInstance: (rest) => workflow.abortInstance(bind(rest)),
|
|
3509
|
-
|
|
3510
|
-
|
|
3707
|
+
deleteDefinition: (rest) => workflow.deleteDefinition(bind(rest)),
|
|
3708
|
+
getInstance: ({ instanceId }) => workflow.getInstance({ client, tag, workflowResource, instanceId }),
|
|
3709
|
+
subscriptionDocumentsForInstance: ({ instanceId }) => workflow.getInstance({ client, tag, workflowResource, instanceId }).then(subscriptionDocumentsForInstance),
|
|
3511
3710
|
instance: (instanceDoc, opts) => createInstanceSession({
|
|
3512
3711
|
client,
|
|
3513
|
-
|
|
3712
|
+
tag,
|
|
3514
3713
|
instance: instanceDoc,
|
|
3515
3714
|
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3516
3715
|
...clock !== void 0 ? { clock } : {},
|
|
@@ -3519,45 +3718,45 @@ function createEngine(args) {
|
|
|
3519
3718
|
}),
|
|
3520
3719
|
guardsForInstance: ({ instanceId }) => workflow.guardsForInstance({
|
|
3521
3720
|
client,
|
|
3522
|
-
|
|
3721
|
+
tag,
|
|
3523
3722
|
workflowResource,
|
|
3524
3723
|
instanceId,
|
|
3525
3724
|
...resourceClients !== void 0 ? { resourceClients } : {}
|
|
3526
3725
|
}),
|
|
3527
3726
|
guardsForDefinition: ({ definition }) => workflow.guardsForDefinition({
|
|
3528
3727
|
client,
|
|
3529
|
-
|
|
3728
|
+
tag,
|
|
3530
3729
|
workflowResource,
|
|
3531
3730
|
definition,
|
|
3532
3731
|
...resourceClients !== void 0 ? { resourceClients } : {}
|
|
3533
3732
|
}),
|
|
3534
3733
|
children: ({ instanceId, task }) => workflow.children({
|
|
3535
3734
|
client,
|
|
3536
|
-
|
|
3735
|
+
tag,
|
|
3537
3736
|
workflowResource,
|
|
3538
3737
|
instanceId,
|
|
3539
3738
|
...task !== void 0 ? { task } : {}
|
|
3540
3739
|
}),
|
|
3541
3740
|
query: ({ groq, params }) => workflow.query({
|
|
3542
3741
|
client,
|
|
3543
|
-
|
|
3742
|
+
tag,
|
|
3544
3743
|
workflowResource,
|
|
3545
3744
|
groq,
|
|
3546
3745
|
...params !== void 0 ? { params } : {}
|
|
3547
3746
|
}),
|
|
3548
3747
|
queryInScope: ({ instanceId, groq, params }) => workflow.queryInScope({
|
|
3549
3748
|
client,
|
|
3550
|
-
|
|
3749
|
+
tag,
|
|
3551
3750
|
workflowResource,
|
|
3552
3751
|
instanceId,
|
|
3553
3752
|
groq,
|
|
3554
3753
|
...params !== void 0 ? { params } : {},
|
|
3555
3754
|
...clock !== void 0 ? { clock } : {}
|
|
3556
3755
|
}),
|
|
3557
|
-
listPendingEffects: ({ instanceId }) => workflow.listPendingEffects({ client,
|
|
3756
|
+
listPendingEffects: ({ instanceId }) => workflow.listPendingEffects({ client, tag, workflowResource, instanceId }),
|
|
3558
3757
|
findPendingEffects: ({ instanceId, claimed, names }) => workflow.findPendingEffects({
|
|
3559
3758
|
client,
|
|
3560
|
-
|
|
3759
|
+
tag,
|
|
3561
3760
|
workflowResource,
|
|
3562
3761
|
instanceId,
|
|
3563
3762
|
...claimed !== void 0 ? { claimed } : {},
|
|
@@ -3565,7 +3764,7 @@ function createEngine(args) {
|
|
|
3565
3764
|
}),
|
|
3566
3765
|
drainEffects: ({ instanceId, access }) => drainEffectsInternal({
|
|
3567
3766
|
client,
|
|
3568
|
-
|
|
3767
|
+
tag,
|
|
3569
3768
|
workflowResource,
|
|
3570
3769
|
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3571
3770
|
instanceId,
|
|
@@ -3576,13 +3775,43 @@ function createEngine(args) {
|
|
|
3576
3775
|
}),
|
|
3577
3776
|
verifyDeployedDefinitions: () => verifyDeployedDefinitionsInternal({
|
|
3578
3777
|
client,
|
|
3579
|
-
|
|
3778
|
+
tag,
|
|
3580
3779
|
effectHandlers,
|
|
3581
3780
|
missingHandler,
|
|
3582
3781
|
logger
|
|
3583
3782
|
})
|
|
3584
3783
|
};
|
|
3585
3784
|
}
|
|
3785
|
+
function docIdFor(def, target) {
|
|
3786
|
+
return definitionDocId(target.workflowResource, target.tag, def.name, def.version);
|
|
3787
|
+
}
|
|
3788
|
+
function diffStatus(existing, expected) {
|
|
3789
|
+
return existing === void 0 ? "create" : isDefinitionUnchanged(existing, expected) ? "unchanged" : "update";
|
|
3790
|
+
}
|
|
3791
|
+
function diffEntry(def, existingRaw, target) {
|
|
3792
|
+
const docId = docIdFor(def, target), expected = {
|
|
3793
|
+
...def,
|
|
3794
|
+
_id: docId,
|
|
3795
|
+
_type: WORKFLOW_DEFINITION_TYPE,
|
|
3796
|
+
tag: target.tag
|
|
3797
|
+
}, status = diffStatus(existingRaw, expected), existing = existingRaw ? stripSystemFields(existingRaw) : void 0;
|
|
3798
|
+
return {
|
|
3799
|
+
name: def.name,
|
|
3800
|
+
version: def.version,
|
|
3801
|
+
status,
|
|
3802
|
+
docId,
|
|
3803
|
+
expected,
|
|
3804
|
+
...existing !== void 0 ? { existing } : {}
|
|
3805
|
+
};
|
|
3806
|
+
}
|
|
3807
|
+
async function computeDiffEntries(client, defs, target) {
|
|
3808
|
+
const entries = [];
|
|
3809
|
+
for (const def of defs) {
|
|
3810
|
+
const existingRaw = await client.getDocument(docIdFor(def, target)) ?? void 0;
|
|
3811
|
+
entries.push(diffEntry(def, existingRaw, target));
|
|
3812
|
+
}
|
|
3813
|
+
return entries;
|
|
3814
|
+
}
|
|
3586
3815
|
const HISTORY_DISPLAY = {
|
|
3587
3816
|
stageEntered: {
|
|
3588
3817
|
title: "Stage entered",
|
|
@@ -3776,15 +4005,22 @@ export {
|
|
|
3776
4005
|
WORKFLOW_DEFINITION_TYPE,
|
|
3777
4006
|
WORKFLOW_INSTANCE_TYPE,
|
|
3778
4007
|
WorkflowStateDivergedError,
|
|
4008
|
+
abortReason,
|
|
4009
|
+
actionVerdict,
|
|
4010
|
+
assigneesOf,
|
|
4011
|
+
availableActions,
|
|
3779
4012
|
buildSnapshot,
|
|
3780
|
-
canonicalTag,
|
|
3781
4013
|
compileGuard,
|
|
4014
|
+
computeDiffEntries,
|
|
3782
4015
|
contentReleaseName,
|
|
3783
4016
|
createEngine,
|
|
3784
4017
|
datasetResourceParts,
|
|
3785
4018
|
defaultLoggerFactory,
|
|
3786
4019
|
denyingGuards,
|
|
3787
4020
|
deployStageGuards,
|
|
4021
|
+
diagnoseInputFromEvaluation,
|
|
4022
|
+
diagnoseInstance,
|
|
4023
|
+
diffEntry,
|
|
3788
4024
|
displayDescription,
|
|
3789
4025
|
displayTitle,
|
|
3790
4026
|
effectsContextMap,
|
|
@@ -3803,6 +4039,7 @@ export {
|
|
|
3803
4039
|
isGdr,
|
|
3804
4040
|
isTerminalStage,
|
|
3805
4041
|
lakeGuardId,
|
|
4042
|
+
openStage,
|
|
3806
4043
|
parseGdr,
|
|
3807
4044
|
readsRaw,
|
|
3808
4045
|
refCanvas,
|
|
@@ -3818,7 +4055,7 @@ export {
|
|
|
3818
4055
|
subscriptionDocumentsForInstance,
|
|
3819
4056
|
tagScopeFilter,
|
|
3820
4057
|
validateDefinition,
|
|
3821
|
-
|
|
4058
|
+
validateTag,
|
|
3822
4059
|
verdictGuardsForInstance,
|
|
3823
4060
|
wallClock,
|
|
3824
4061
|
workflow
|