@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.cjs
CHANGED
|
@@ -262,6 +262,20 @@ function effectBindings(effects) {
|
|
|
262
262
|
(e) => Object.entries(e.bindings ?? {}).map(([k, groq]) => [`${e.name}.${k}`, groq])
|
|
263
263
|
);
|
|
264
264
|
}
|
|
265
|
+
function actionVerdict(task, action) {
|
|
266
|
+
return {
|
|
267
|
+
task: task.task.name,
|
|
268
|
+
taskStatus: task.status,
|
|
269
|
+
action: action.action.name,
|
|
270
|
+
title: action.action.title,
|
|
271
|
+
allowed: action.allowed,
|
|
272
|
+
disabledReason: action.disabledReason,
|
|
273
|
+
params: action.action.params ?? []
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function availableActions(tasks) {
|
|
277
|
+
return tasks.flatMap((t) => t.actions.map((a) => actionVerdict(t, a)));
|
|
278
|
+
}
|
|
265
279
|
const wallClock = () => (/* @__PURE__ */ new Date()).toISOString(), GUARD_DOC_TYPE = "temp.system.guard";
|
|
266
280
|
class MutationGuardDeniedError extends Error {
|
|
267
281
|
denied;
|
|
@@ -456,79 +470,63 @@ function stripStateForLake(value) {
|
|
|
456
470
|
function bareId(id) {
|
|
457
471
|
return isGdrUri(id) ? extractDocumentId(id) : id;
|
|
458
472
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
const resource = assertSingleResource(targets), types = resolveMatchTypes(targets, guard.match.types);
|
|
464
|
-
return { doc: compileGuard({
|
|
465
|
-
id: lakeGuardId({ instanceDocId: instance._id, guardName: guard.name }),
|
|
466
|
-
resourceType: resource.type,
|
|
467
|
-
resourceId: resource.id,
|
|
468
|
-
owner: GUARD_OWNER,
|
|
469
|
-
sourceInstanceId: instance._id,
|
|
470
|
-
sourceDefinition: instance.definition,
|
|
471
|
-
sourceStage: stageName,
|
|
472
|
-
name: guard.name,
|
|
473
|
-
...guard.description !== void 0 ? { description: guard.description } : {},
|
|
474
|
-
match: {
|
|
475
|
-
...types !== void 0 ? { types } : {},
|
|
476
|
-
idRefs: bareIdRefs(targets),
|
|
477
|
-
...guard.match.idPatterns !== void 0 ? { idPatterns: guard.match.idPatterns } : {},
|
|
478
|
-
actions: guard.match.actions
|
|
479
|
-
},
|
|
480
|
-
predicate: guard.predicate ?? "",
|
|
481
|
-
metadata: resolveMetadata(guard.metadata, ctx)
|
|
482
|
-
}), routeGdr: targets[0].parsed };
|
|
473
|
+
function abortReason(instance) {
|
|
474
|
+
return instance.history.find(
|
|
475
|
+
(h) => h._type === "aborted"
|
|
476
|
+
)?.reason;
|
|
483
477
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
await client.patch(doc._id).set(body).commit();
|
|
478
|
+
function diagnoseInputFromEvaluation(evaluation) {
|
|
479
|
+
return {
|
|
480
|
+
instance: evaluation.instance,
|
|
481
|
+
tasks: evaluation.currentStage.tasks,
|
|
482
|
+
transitions: evaluation.currentStage.transitions
|
|
483
|
+
};
|
|
491
484
|
}
|
|
492
|
-
function
|
|
493
|
-
|
|
494
|
-
for (const guard of stage?.guards ?? []) {
|
|
495
|
-
const resolved = resolveGuard(guard, args.instance, args.stageName, args.now);
|
|
496
|
-
resolved !== null && out.push({ client: args.clientForGdr(resolved.routeGdr), doc: resolved.doc });
|
|
497
|
-
}
|
|
498
|
-
return out;
|
|
485
|
+
function openStage(instance) {
|
|
486
|
+
return findOpenStageEntry(instance);
|
|
499
487
|
}
|
|
500
|
-
|
|
501
|
-
return
|
|
488
|
+
function assigneesOf(stage, taskName) {
|
|
489
|
+
return (stage?.tasks.find((t) => t.name === taskName)?.state ?? []).filter((s) => s._type === "assignees").flatMap((s) => s.value);
|
|
502
490
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
if (!(live === void 0 || live.currentStage !== args.stageName) && live.abortedAt === void 0)
|
|
506
|
-
for (const { client, doc } of resolvedStageGuards(args))
|
|
507
|
-
await upsertGuard(client, doc);
|
|
491
|
+
function isResolved(status) {
|
|
492
|
+
return status === "done" || status === "skipped";
|
|
508
493
|
}
|
|
509
|
-
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
for (const { client, doc } of resolvedStageGuards(args))
|
|
513
|
-
await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit();
|
|
494
|
+
function failedEffectCause(input) {
|
|
495
|
+
const stalled = new Set(input.tasks.filter((t) => !isResolved(t.status)).map((t) => t.task.name)), effect = [...input.instance.effectHistory].reverse().find((e) => e.status === "failed" && e.origin.kind === "task" && stalled.has(e.origin.name));
|
|
496
|
+
return effect ? { kind: "failed-effect", effect } : void 0;
|
|
514
497
|
}
|
|
515
|
-
function
|
|
516
|
-
const
|
|
517
|
-
return
|
|
498
|
+
function hungEffectCause(input) {
|
|
499
|
+
const effect = input.instance.pendingEffects.find((e) => e.claim !== void 0);
|
|
500
|
+
return effect ? { kind: "hung-effect", effect } : void 0;
|
|
518
501
|
}
|
|
519
|
-
|
|
520
|
-
const
|
|
521
|
-
return
|
|
502
|
+
function failedTaskCause(input) {
|
|
503
|
+
const failed = input.tasks.find((t) => t.status === "failed");
|
|
504
|
+
return failed ? { kind: "failed-task", task: failed.task.name } : void 0;
|
|
522
505
|
}
|
|
523
|
-
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
506
|
+
function waitingState(input) {
|
|
507
|
+
const active = input.tasks.find((t) => t.status === "active" && (t.task.actions ?? []).length > 0);
|
|
508
|
+
if (active !== void 0)
|
|
509
|
+
return {
|
|
510
|
+
state: "waiting",
|
|
511
|
+
task: active.task.name,
|
|
512
|
+
assignees: assigneesOf(openStage(input.instance), active.task.name),
|
|
513
|
+
actions: (active.task.actions ?? []).map((a) => a.name)
|
|
514
|
+
};
|
|
528
515
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
516
|
+
function noTransitionFiresCause(input) {
|
|
517
|
+
if (input.transitions.length !== 0 && !input.transitions.some((t) => t.filterSatisfied) && input.tasks.every((t) => isResolved(t.status)))
|
|
518
|
+
return { kind: "no-transition-fires" };
|
|
519
|
+
}
|
|
520
|
+
function diagnoseInstance(input) {
|
|
521
|
+
const { instance } = input;
|
|
522
|
+
if (instance.abortedAt !== void 0) {
|
|
523
|
+
const reason = abortReason(instance);
|
|
524
|
+
return { state: "aborted", at: instance.abortedAt, ...reason !== void 0 ? { reason } : {} };
|
|
525
|
+
}
|
|
526
|
+
if (instance.completedAt !== void 0)
|
|
527
|
+
return { state: "completed", at: instance.completedAt };
|
|
528
|
+
const cause = failedEffectCause(input) ?? failedTaskCause(input) ?? hungEffectCause(input) ?? noTransitionFiresCause(input);
|
|
529
|
+
return cause !== void 0 ? { state: "stuck", cause } : waitingState(input) ?? { state: "progressing" };
|
|
532
530
|
}
|
|
533
531
|
function buildSnapshot(args) {
|
|
534
532
|
const docs = [], knownIds = /* @__PURE__ */ new Set();
|
|
@@ -647,6 +645,191 @@ function resourceFromParsed(parsed) {
|
|
|
647
645
|
function collectEntryDocUris(resolvedStateEntries) {
|
|
648
646
|
return entryDocRefs(resolvedStateEntries).map((ref) => ref.id);
|
|
649
647
|
}
|
|
648
|
+
const resourceKey = (r) => `${r.type}.${r.id}`;
|
|
649
|
+
function guardsForResource(client) {
|
|
650
|
+
return client.fetch("*[_type == $t] | order(_id asc)", { t: GUARD_DOC_TYPE });
|
|
651
|
+
}
|
|
652
|
+
function instanceGuardQuery(instanceId) {
|
|
653
|
+
return {
|
|
654
|
+
query: "*[_type == $guardType && sourceInstanceId == $instanceId]",
|
|
655
|
+
params: { guardType: GUARD_DOC_TYPE, instanceId }
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
function verdictGuardsForInstance(client, instanceId) {
|
|
659
|
+
const { query, params } = instanceGuardQuery(instanceId);
|
|
660
|
+
return client.fetch(query, params);
|
|
661
|
+
}
|
|
662
|
+
async function guardsForInstance(args) {
|
|
663
|
+
const { client, clientForGdr, instance } = args, stateUris = [
|
|
664
|
+
...collectEntryDocUris(instance.state),
|
|
665
|
+
...instance.stages.flatMap((stage) => collectEntryDocUris(stage.state))
|
|
666
|
+
], clients = resourceClientMap(
|
|
667
|
+
instance.workflowResource,
|
|
668
|
+
client,
|
|
669
|
+
clientForGdr,
|
|
670
|
+
stateUris.map((uri) => tryParseGdr(uri))
|
|
671
|
+
), { query, params } = instanceGuardQuery(instance._id);
|
|
672
|
+
return queryGuardsAcross(clients.values(), query, params);
|
|
673
|
+
}
|
|
674
|
+
async function guardsForDefinition(args) {
|
|
675
|
+
const perClient = await guardsForDefinitionByClient(args);
|
|
676
|
+
return dedupById(perClient.flatMap((entry) => entry.guards));
|
|
677
|
+
}
|
|
678
|
+
async function guardsForDefinitionByClient(args) {
|
|
679
|
+
const { client, clientForGdr, workflowResource, definition, definitions } = args, gdrs = definitions.flatMap(
|
|
680
|
+
(def) => guardIdRefs(def).map(({ idRef, stage }) => staticIdRefGdr(idRef, stage, def))
|
|
681
|
+
), clients = resourceClientMap(workflowResource, client, clientForGdr, gdrs), query = "*[_type == $t && sourceDefinition == $definition]", params = { t: GUARD_DOC_TYPE, definition };
|
|
682
|
+
return Promise.all(
|
|
683
|
+
[...clients.values()].map(async (resourceClient) => ({
|
|
684
|
+
client: resourceClient,
|
|
685
|
+
guards: await resourceClient.fetch(query, params)
|
|
686
|
+
}))
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
function guardIdRefs(definition) {
|
|
690
|
+
return definition.stages.flatMap(
|
|
691
|
+
(stage) => (stage.guards ?? []).flatMap(
|
|
692
|
+
(guard) => (guard.match.idRefs ?? []).map((idRef) => ({ idRef, stage }))
|
|
693
|
+
)
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
function staticIdRefGdr(idRef, stage, definition) {
|
|
697
|
+
const read = /^\$state\.([\w-]+)/.exec(idRef);
|
|
698
|
+
if (read === null) return;
|
|
699
|
+
const source = entriesInScope(stage, definition).find((e) => e.name === read[1])?.source;
|
|
700
|
+
return source?.type === "literal" ? gdrFromValue(source.value) : void 0;
|
|
701
|
+
}
|
|
702
|
+
function entriesInScope(stage, definition) {
|
|
703
|
+
return [
|
|
704
|
+
...definition.state ?? [],
|
|
705
|
+
...stage.state ?? [],
|
|
706
|
+
...(stage.tasks ?? []).flatMap((task) => task.state ?? [])
|
|
707
|
+
];
|
|
708
|
+
}
|
|
709
|
+
function gdrFromValue(value) {
|
|
710
|
+
if (typeof value == "string") return tryParseGdr(value);
|
|
711
|
+
if (typeof value == "object" && value !== null && "id" in value) {
|
|
712
|
+
const id = value.id;
|
|
713
|
+
return typeof id == "string" ? tryParseGdr(id) : void 0;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
function tryParseGdr(uri) {
|
|
717
|
+
try {
|
|
718
|
+
return parseGdr(uri);
|
|
719
|
+
} catch {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
function resourceClientMap(base, baseClient, clientForGdr, gdrs) {
|
|
724
|
+
const map = /* @__PURE__ */ new Map([[resourceKey(base), baseClient]]);
|
|
725
|
+
for (const parsed of gdrs) {
|
|
726
|
+
if (parsed === void 0) continue;
|
|
727
|
+
const key = resourceKey(resourceFromParsed(parsed));
|
|
728
|
+
map.has(key) || map.set(key, clientForGdr(parsed));
|
|
729
|
+
}
|
|
730
|
+
return map;
|
|
731
|
+
}
|
|
732
|
+
async function queryGuardsAcross(clients, query, params) {
|
|
733
|
+
const perResource = await Promise.all(
|
|
734
|
+
[...clients].map((c) => c.fetch(query, params))
|
|
735
|
+
);
|
|
736
|
+
return dedupById(perResource.flat());
|
|
737
|
+
}
|
|
738
|
+
function dedupById(guards) {
|
|
739
|
+
return [...new Map(guards.map((g) => [g._id, g])).values()].sort((a, b) => a._id.localeCompare(b._id));
|
|
740
|
+
}
|
|
741
|
+
const GUARD_OWNER = "robot:workflow-engine", GUARD_LIFTED_PREDICATE = "true";
|
|
742
|
+
function resolveGuard(guard, instance, stageName, now) {
|
|
743
|
+
const ctx = { instance, now }, targets = resolveIdRefTargets(guard.match.idRefs, ctx);
|
|
744
|
+
if (targets === null) return null;
|
|
745
|
+
const resource = assertSingleResource(targets), types = resolveMatchTypes(targets, guard.match.types);
|
|
746
|
+
return { doc: compileGuard({
|
|
747
|
+
id: lakeGuardId({ instanceDocId: instance._id, guardName: guard.name }),
|
|
748
|
+
resourceType: resource.type,
|
|
749
|
+
resourceId: resource.id,
|
|
750
|
+
owner: GUARD_OWNER,
|
|
751
|
+
sourceInstanceId: instance._id,
|
|
752
|
+
sourceDefinition: instance.definition,
|
|
753
|
+
sourceStage: stageName,
|
|
754
|
+
name: guard.name,
|
|
755
|
+
...guard.description !== void 0 ? { description: guard.description } : {},
|
|
756
|
+
match: {
|
|
757
|
+
...types !== void 0 ? { types } : {},
|
|
758
|
+
idRefs: bareIdRefs(targets),
|
|
759
|
+
...guard.match.idPatterns !== void 0 ? { idPatterns: guard.match.idPatterns } : {},
|
|
760
|
+
actions: guard.match.actions
|
|
761
|
+
},
|
|
762
|
+
predicate: guard.predicate ?? "",
|
|
763
|
+
metadata: resolveMetadata(guard.metadata, ctx)
|
|
764
|
+
}), routeGdr: targets[0].parsed };
|
|
765
|
+
}
|
|
766
|
+
async function upsertGuard(client, doc) {
|
|
767
|
+
if (!await client.getDocument(doc._id)) {
|
|
768
|
+
await client.create(doc);
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
const { _id, _type, _rev, _createdAt, _updatedAt, ...body } = doc;
|
|
772
|
+
await client.patch(doc._id).set(body).commit();
|
|
773
|
+
}
|
|
774
|
+
function resolvedStageGuards(args) {
|
|
775
|
+
const stage = args.definition.stages.find((s) => s.name === args.stageName), out = [];
|
|
776
|
+
for (const guard of stage?.guards ?? []) {
|
|
777
|
+
const resolved = resolveGuard(guard, args.instance, args.stageName, args.now);
|
|
778
|
+
resolved !== null && out.push({ client: args.clientForGdr(resolved.routeGdr), doc: resolved.doc });
|
|
779
|
+
}
|
|
780
|
+
return out;
|
|
781
|
+
}
|
|
782
|
+
async function committedInstance(args) {
|
|
783
|
+
return await args.client.getDocument(args.instance._id) ?? void 0;
|
|
784
|
+
}
|
|
785
|
+
async function deployStageGuards(args) {
|
|
786
|
+
const live = await committedInstance(args);
|
|
787
|
+
if (!(live === void 0 || live.currentStage !== args.stageName) && live.abortedAt === void 0)
|
|
788
|
+
for (const { client, doc } of resolvedStageGuards(args))
|
|
789
|
+
await upsertGuard(client, doc);
|
|
790
|
+
}
|
|
791
|
+
async function retractStageGuards(args) {
|
|
792
|
+
const live = await committedInstance(args);
|
|
793
|
+
if (live !== void 0 && !(live.currentStage === args.stageName && live.abortedAt === void 0))
|
|
794
|
+
for (const { client, doc } of resolvedStageGuards(args))
|
|
795
|
+
await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit();
|
|
796
|
+
}
|
|
797
|
+
async function deleteOrphanedDefinitionGuards(args) {
|
|
798
|
+
const { client, clientForGdr, workflowResource, definition, definitions, tag } = args, perClient = await guardsForDefinitionByClient({
|
|
799
|
+
client,
|
|
800
|
+
clientForGdr,
|
|
801
|
+
workflowResource,
|
|
802
|
+
definition,
|
|
803
|
+
definitions
|
|
804
|
+
}), ownPartitionPrefix = `${tag}.`;
|
|
805
|
+
let count = 0;
|
|
806
|
+
for (const { client: resourceClient, guards } of perClient) {
|
|
807
|
+
const own = guards.filter((guard) => guard.sourceInstanceId.startsWith(ownPartitionPrefix));
|
|
808
|
+
if (own.length === 0) continue;
|
|
809
|
+
const tx = resourceClient.transaction();
|
|
810
|
+
for (const guard of own) tx.delete(guard._id);
|
|
811
|
+
await tx.commit(), count += own.length;
|
|
812
|
+
}
|
|
813
|
+
return count;
|
|
814
|
+
}
|
|
815
|
+
function randomKey(length = 12) {
|
|
816
|
+
const bytes = new Uint8Array(length);
|
|
817
|
+
return globalThis.crypto.getRandomValues(bytes), [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, length);
|
|
818
|
+
}
|
|
819
|
+
async function evaluateCondition(args) {
|
|
820
|
+
const { condition, snapshot, params } = args;
|
|
821
|
+
return condition === void 0 ? !0 : !!await runGroq(condition, params, snapshot);
|
|
822
|
+
}
|
|
823
|
+
async function evaluatePredicates(args) {
|
|
824
|
+
const out = {};
|
|
825
|
+
for (const [name, groq] of Object.entries(args.predicates ?? {}))
|
|
826
|
+
out[name] = !!await runGroq(groq, args.params, args.snapshot);
|
|
827
|
+
return out;
|
|
828
|
+
}
|
|
829
|
+
async function runGroq(groq, params, snapshot) {
|
|
830
|
+
const tree = groqJs.parse(groq, { params });
|
|
831
|
+
return (await groqJs.evaluate(tree, { dataset: snapshot.docs, params })).get();
|
|
832
|
+
}
|
|
650
833
|
class StateValueShapeError extends Error {
|
|
651
834
|
entryType;
|
|
652
835
|
entryName;
|
|
@@ -807,7 +990,7 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
|
807
990
|
stage: ctx.stageName ?? null,
|
|
808
991
|
task: ctx.taskName ?? null,
|
|
809
992
|
now: ctx.now,
|
|
810
|
-
|
|
993
|
+
tag: ctx.tag
|
|
811
994
|
});
|
|
812
995
|
try {
|
|
813
996
|
const fetchOptions = ctx.perspective !== void 0 ? { perspective: ctx.perspective } : void 0, result = await client.fetch(source.query, params, fetchOptions);
|
|
@@ -974,7 +1157,7 @@ async function resolveStageStateEntries(args) {
|
|
|
974
1157
|
client,
|
|
975
1158
|
now,
|
|
976
1159
|
selfId: instance._id,
|
|
977
|
-
|
|
1160
|
+
tag: instance.tag,
|
|
978
1161
|
stageName: stage.name,
|
|
979
1162
|
workflowResource: instance.workflowResource,
|
|
980
1163
|
// Allow stage-scope entries' `source: { type: "stateRead", scope:
|
|
@@ -994,7 +1177,7 @@ async function resolveTaskStateEntries(args) {
|
|
|
994
1177
|
client,
|
|
995
1178
|
now,
|
|
996
1179
|
selfId: instance._id,
|
|
997
|
-
|
|
1180
|
+
tag: instance.tag,
|
|
998
1181
|
stageName: stage.name,
|
|
999
1182
|
workflowResource: instance.workflowResource,
|
|
1000
1183
|
taskName: task.name,
|
|
@@ -1004,53 +1187,6 @@ async function resolveTaskStateEntries(args) {
|
|
|
1004
1187
|
randomKey
|
|
1005
1188
|
});
|
|
1006
1189
|
}
|
|
1007
|
-
async function resolveBindings(args) {
|
|
1008
|
-
const resolved = {};
|
|
1009
|
-
for (const [key, groq] of Object.entries(args.bindings ?? {}))
|
|
1010
|
-
resolved[key] = await runGroq(groq, args.params, args.snapshot);
|
|
1011
|
-
return { ...resolved, ...args.staticInput };
|
|
1012
|
-
}
|
|
1013
|
-
function effectsContextEntry(name, value) {
|
|
1014
|
-
const _key = randomKey();
|
|
1015
|
-
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 };
|
|
1016
|
-
}
|
|
1017
|
-
function effectsContextJsonEntry(name, value) {
|
|
1018
|
-
return { _key: randomKey(), _type: "effectsContext.json", name, value: JSON.stringify(value) };
|
|
1019
|
-
}
|
|
1020
|
-
function buildInstanceBase(args) {
|
|
1021
|
-
const { id, now, actor, perspective } = args;
|
|
1022
|
-
return {
|
|
1023
|
-
_id: id,
|
|
1024
|
-
_type: WORKFLOW_INSTANCE_TYPE,
|
|
1025
|
-
_rev: "",
|
|
1026
|
-
_createdAt: now,
|
|
1027
|
-
_updatedAt: now,
|
|
1028
|
-
tags: args.tags,
|
|
1029
|
-
workflowResource: args.workflowResource,
|
|
1030
|
-
definition: args.definitionName,
|
|
1031
|
-
pinnedVersion: args.pinnedVersion,
|
|
1032
|
-
definitionSnapshot: JSON.stringify(args.definition),
|
|
1033
|
-
state: args.state,
|
|
1034
|
-
effectsContext: args.effectsContext,
|
|
1035
|
-
ancestors: args.ancestors,
|
|
1036
|
-
...perspective !== void 0 ? { perspective } : {},
|
|
1037
|
-
currentStage: args.initialStage,
|
|
1038
|
-
stages: [],
|
|
1039
|
-
pendingEffects: [],
|
|
1040
|
-
effectHistory: [],
|
|
1041
|
-
history: [
|
|
1042
|
-
{
|
|
1043
|
-
_key: randomKey(),
|
|
1044
|
-
_type: "stageEntered",
|
|
1045
|
-
at: now,
|
|
1046
|
-
stage: args.initialStage,
|
|
1047
|
-
...actor !== void 0 ? { actor } : {}
|
|
1048
|
-
}
|
|
1049
|
-
],
|
|
1050
|
-
startedAt: now,
|
|
1051
|
-
lastChangedAt: now
|
|
1052
|
-
};
|
|
1053
|
-
}
|
|
1054
1190
|
class ActionParamsInvalidError extends Error {
|
|
1055
1191
|
action;
|
|
1056
1192
|
task;
|
|
@@ -1170,21 +1306,108 @@ function stageTransitionHistory(args) {
|
|
|
1170
1306
|
}
|
|
1171
1307
|
];
|
|
1172
1308
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
)
|
|
1309
|
+
function startMutation(instance) {
|
|
1310
|
+
return {
|
|
1311
|
+
currentStage: instance.currentStage,
|
|
1312
|
+
// Deep-ish copy. Per-scope state entry arrays need their own copies
|
|
1313
|
+
// so ops can mutate without leaking into the source.
|
|
1314
|
+
state: (instance.state ?? []).map((s) => ({ ...s })),
|
|
1315
|
+
stages: instance.stages.map((s) => ({
|
|
1316
|
+
...s,
|
|
1317
|
+
state: (s.state ?? []).map((entry) => ({ ...entry })),
|
|
1318
|
+
tasks: s.tasks.map((t) => ({
|
|
1319
|
+
...t,
|
|
1320
|
+
...t.state !== void 0 ? { state: t.state.map((entry) => ({ ...entry })) } : {}
|
|
1321
|
+
}))
|
|
1322
|
+
})),
|
|
1323
|
+
pendingEffects: [...instance.pendingEffects],
|
|
1324
|
+
effectHistory: [...instance.effectHistory],
|
|
1325
|
+
effectsContext: [...instance.effectsContext],
|
|
1326
|
+
history: [...instance.history],
|
|
1327
|
+
lastChangedAt: instance.lastChangedAt,
|
|
1328
|
+
...instance.completedAt !== void 0 ? { completedAt: instance.completedAt } : {},
|
|
1329
|
+
...instance.abortedAt !== void 0 ? { abortedAt: instance.abortedAt } : {},
|
|
1330
|
+
pendingCreates: []
|
|
1331
|
+
};
|
|
1182
1332
|
}
|
|
1183
|
-
function
|
|
1184
|
-
|
|
1333
|
+
function instanceStateFields(src) {
|
|
1334
|
+
const fields = {
|
|
1335
|
+
currentStage: src.currentStage,
|
|
1336
|
+
state: src.state,
|
|
1337
|
+
stages: src.stages,
|
|
1338
|
+
pendingEffects: src.pendingEffects,
|
|
1339
|
+
effectHistory: src.effectHistory,
|
|
1340
|
+
effectsContext: src.effectsContext,
|
|
1341
|
+
history: src.history
|
|
1342
|
+
};
|
|
1343
|
+
return src.completedAt !== void 0 && (fields.completedAt = src.completedAt), src.abortedAt !== void 0 && (fields.abortedAt = src.abortedAt), fields;
|
|
1185
1344
|
}
|
|
1186
|
-
function
|
|
1187
|
-
return
|
|
1345
|
+
function materializeInstance(base, mutation) {
|
|
1346
|
+
return {
|
|
1347
|
+
...base,
|
|
1348
|
+
currentStage: mutation.currentStage,
|
|
1349
|
+
state: mutation.state,
|
|
1350
|
+
stages: mutation.stages
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
async function persist(ctx, mutation) {
|
|
1354
|
+
const set = {
|
|
1355
|
+
...instanceStateFields(mutation),
|
|
1356
|
+
lastChangedAt: ctx.now
|
|
1357
|
+
}, pendingCreates = mutation.pendingCreates;
|
|
1358
|
+
if (pendingCreates.length === 0)
|
|
1359
|
+
return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit();
|
|
1360
|
+
const tx = ctx.client.transaction();
|
|
1361
|
+
for (const body of pendingCreates) tx.create(body);
|
|
1362
|
+
tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)), await tx.commit(), mutation.pendingCreates = [];
|
|
1363
|
+
const actorForPriming = void 0;
|
|
1364
|
+
for (const body of pendingCreates)
|
|
1365
|
+
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);
|
|
1366
|
+
const reloaded = await ctx.client.getDocument(ctx.instance._id);
|
|
1367
|
+
if (!reloaded)
|
|
1368
|
+
throw new Error(`Instance ${ctx.instance._id} disappeared after transaction commit`);
|
|
1369
|
+
return reloaded;
|
|
1370
|
+
}
|
|
1371
|
+
function currentStageEntry(mutation) {
|
|
1372
|
+
const entry = findOpenStageEntry(mutation);
|
|
1373
|
+
if (entry === void 0)
|
|
1374
|
+
throw new Error(
|
|
1375
|
+
`Mutation invariant broken: no current (un-exited) StageEntry for currentStage "${mutation.currentStage}"`
|
|
1376
|
+
);
|
|
1377
|
+
return entry;
|
|
1378
|
+
}
|
|
1379
|
+
function currentTasks(mutation) {
|
|
1380
|
+
return currentStageEntry(mutation).tasks;
|
|
1381
|
+
}
|
|
1382
|
+
function findTaskInCurrentStage(ctx, taskName) {
|
|
1383
|
+
const stage = findStage(ctx.definition, ctx.instance.currentStage), task = (stage.tasks ?? []).find((t) => t.name === taskName);
|
|
1384
|
+
if (task === void 0)
|
|
1385
|
+
throw new Error(
|
|
1386
|
+
`Task "${taskName}" not found in current stage "${stage.name}" of ${ctx.definition.name}`
|
|
1387
|
+
);
|
|
1388
|
+
return { stage, task };
|
|
1389
|
+
}
|
|
1390
|
+
function requireMutationTaskEntry(mutation, task) {
|
|
1391
|
+
const mutEntry = currentTasks(mutation).find((t) => t.name === task);
|
|
1392
|
+
if (mutEntry === void 0)
|
|
1393
|
+
throw new Error(`Task "${task}" disappeared from mutation copy \u2014 invariant broken`);
|
|
1394
|
+
return mutEntry;
|
|
1395
|
+
}
|
|
1396
|
+
function findCurrentStageEntry(instance) {
|
|
1397
|
+
return findOpenStageEntry(instance);
|
|
1398
|
+
}
|
|
1399
|
+
function findCurrentTasks(instance) {
|
|
1400
|
+
return findCurrentStageEntry(instance)?.tasks ?? [];
|
|
1401
|
+
}
|
|
1402
|
+
const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
1403
|
+
function validateTag(tag) {
|
|
1404
|
+
if (!TAG_RE.test(tag))
|
|
1405
|
+
throw new Error(
|
|
1406
|
+
`tag: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
function tagScopeFilter() {
|
|
1410
|
+
return "tag == $tag";
|
|
1188
1411
|
}
|
|
1189
1412
|
function definitionLookupGroq(explicit) {
|
|
1190
1413
|
const scoped = `_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}`;
|
|
@@ -1202,6 +1425,55 @@ function clientForDiscovery(defaultClient, clientForGdr, subjectGdrUri) {
|
|
|
1202
1425
|
return defaultClient;
|
|
1203
1426
|
}
|
|
1204
1427
|
}
|
|
1428
|
+
async function reload(client, instanceId, tag) {
|
|
1429
|
+
const doc = await client.getDocument(instanceId);
|
|
1430
|
+
if (!doc)
|
|
1431
|
+
throw new Error(`Workflow instance ${instanceId} not found`);
|
|
1432
|
+
if (doc.tag !== tag)
|
|
1433
|
+
throw new Error(`Workflow instance ${instanceId} not visible to this engine (tag mismatch)`);
|
|
1434
|
+
return doc;
|
|
1435
|
+
}
|
|
1436
|
+
function effectsContextEntry(name, value) {
|
|
1437
|
+
const _key = randomKey();
|
|
1438
|
+
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 };
|
|
1439
|
+
}
|
|
1440
|
+
function effectsContextJsonEntry(name, value) {
|
|
1441
|
+
return { _key: randomKey(), _type: "effectsContext.json", name, value: JSON.stringify(value) };
|
|
1442
|
+
}
|
|
1443
|
+
function buildInstanceBase(args) {
|
|
1444
|
+
const { id, now, actor, perspective } = args;
|
|
1445
|
+
return {
|
|
1446
|
+
_id: id,
|
|
1447
|
+
_type: WORKFLOW_INSTANCE_TYPE,
|
|
1448
|
+
_rev: "",
|
|
1449
|
+
_createdAt: now,
|
|
1450
|
+
_updatedAt: now,
|
|
1451
|
+
tag: args.tag,
|
|
1452
|
+
workflowResource: args.workflowResource,
|
|
1453
|
+
definition: args.definitionName,
|
|
1454
|
+
pinnedVersion: args.pinnedVersion,
|
|
1455
|
+
definitionSnapshot: JSON.stringify(args.definition),
|
|
1456
|
+
state: args.state,
|
|
1457
|
+
effectsContext: args.effectsContext,
|
|
1458
|
+
ancestors: args.ancestors,
|
|
1459
|
+
...perspective !== void 0 ? { perspective } : {},
|
|
1460
|
+
currentStage: args.initialStage,
|
|
1461
|
+
stages: [],
|
|
1462
|
+
pendingEffects: [],
|
|
1463
|
+
effectHistory: [],
|
|
1464
|
+
history: [
|
|
1465
|
+
{
|
|
1466
|
+
_key: randomKey(),
|
|
1467
|
+
_type: "stageEntered",
|
|
1468
|
+
at: now,
|
|
1469
|
+
stage: args.initialStage,
|
|
1470
|
+
...actor !== void 0 ? { actor } : {}
|
|
1471
|
+
}
|
|
1472
|
+
],
|
|
1473
|
+
startedAt: now,
|
|
1474
|
+
lastChangedAt: now
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1205
1477
|
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";
|
|
1206
1478
|
function gateActor(outcome) {
|
|
1207
1479
|
return {
|
|
@@ -1227,7 +1499,7 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1227
1499
|
`Subworkflow depth limit (${MAX_SPAWN_DEPTH}) exceeded on task "${task.name}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
|
|
1228
1500
|
);
|
|
1229
1501
|
}
|
|
1230
|
-
const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.
|
|
1502
|
+
const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tag);
|
|
1231
1503
|
if (definition === null) {
|
|
1232
1504
|
const versionLabel = sub.definition.version === void 0 || sub.definition.version === "latest" ? "latest" : `v${sub.definition.version}`;
|
|
1233
1505
|
throw new Error(
|
|
@@ -1317,15 +1589,15 @@ function coerceSpawnGdr(raw, workflowResource) {
|
|
|
1317
1589
|
}
|
|
1318
1590
|
return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
|
|
1319
1591
|
}
|
|
1320
|
-
async function resolveDefinitionRef(client, ref,
|
|
1321
|
-
const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name,
|
|
1592
|
+
async function resolveDefinitionRef(client, ref, tag) {
|
|
1593
|
+
const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
|
|
1322
1594
|
return wantsExplicit && (params.version = ref.version), await client.fetch(
|
|
1323
1595
|
definitionLookupGroq(wantsExplicit),
|
|
1324
1596
|
params
|
|
1325
1597
|
) ?? null;
|
|
1326
1598
|
}
|
|
1327
1599
|
async function prepareChildInstance(args) {
|
|
1328
|
-
const { client, parent, definition, initialState, effectsContext, actor, now } = args,
|
|
1600
|
+
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 = [
|
|
1329
1601
|
...parent.ancestors,
|
|
1330
1602
|
{
|
|
1331
1603
|
id: selfGdr(parent),
|
|
@@ -1338,7 +1610,7 @@ async function prepareChildInstance(args) {
|
|
|
1338
1610
|
client,
|
|
1339
1611
|
now,
|
|
1340
1612
|
selfId: childDocId,
|
|
1341
|
-
|
|
1613
|
+
tag: childTag,
|
|
1342
1614
|
workflowResource,
|
|
1343
1615
|
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
|
|
1344
1616
|
},
|
|
@@ -1357,7 +1629,7 @@ async function prepareChildInstance(args) {
|
|
|
1357
1629
|
), base = buildInstanceBase({
|
|
1358
1630
|
id: childDocId,
|
|
1359
1631
|
now,
|
|
1360
|
-
|
|
1632
|
+
tag: childTag,
|
|
1361
1633
|
workflowResource,
|
|
1362
1634
|
definitionName: definition.name,
|
|
1363
1635
|
pinnedVersion: definition.version,
|
|
@@ -1369,16 +1641,121 @@ async function prepareChildInstance(args) {
|
|
|
1369
1641
|
initialStage: definition.initialStage,
|
|
1370
1642
|
actor
|
|
1371
1643
|
});
|
|
1372
|
-
return { ref: childRef, body: base };
|
|
1373
|
-
}
|
|
1374
|
-
async function subworkflowRows(ctx, refs) {
|
|
1375
|
-
if (refs.length === 0) return [];
|
|
1376
|
-
const ids = refs.map((r) => gdrToBareId(r.id));
|
|
1377
|
-
return (await ctx.client.fetch("*[_id in $ids]{_id, currentStage, completedAt}", { ids })).map((c) => ({
|
|
1378
|
-
_id: c._id,
|
|
1379
|
-
stage: c.currentStage,
|
|
1380
|
-
status: c.completedAt !== void 0 && c.completedAt !== null ? "done" : "active"
|
|
1381
|
-
}));
|
|
1644
|
+
return { ref: childRef, body: base };
|
|
1645
|
+
}
|
|
1646
|
+
async function subworkflowRows(ctx, refs) {
|
|
1647
|
+
if (refs.length === 0) return [];
|
|
1648
|
+
const ids = refs.map((r) => gdrToBareId(r.id));
|
|
1649
|
+
return (await ctx.client.fetch("*[_id in $ids]{_id, currentStage, completedAt}", { ids })).map((c) => ({
|
|
1650
|
+
_id: c._id,
|
|
1651
|
+
stage: c.currentStage,
|
|
1652
|
+
status: c.completedAt !== void 0 && c.completedAt !== null ? "done" : "active"
|
|
1653
|
+
}));
|
|
1654
|
+
}
|
|
1655
|
+
async function resolveBindings(args) {
|
|
1656
|
+
const resolved = {};
|
|
1657
|
+
for (const [key, groq] of Object.entries(args.bindings ?? {}))
|
|
1658
|
+
resolved[key] = await runGroq(groq, args.params, args.snapshot);
|
|
1659
|
+
return { ...resolved, ...args.staticInput };
|
|
1660
|
+
}
|
|
1661
|
+
async function completeEffect(args) {
|
|
1662
|
+
const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1663
|
+
...options?.clock ? { clock: options.clock } : {},
|
|
1664
|
+
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
1665
|
+
});
|
|
1666
|
+
return commitCompleteEffect(
|
|
1667
|
+
ctx,
|
|
1668
|
+
effectKey,
|
|
1669
|
+
status,
|
|
1670
|
+
outputs,
|
|
1671
|
+
detail,
|
|
1672
|
+
error,
|
|
1673
|
+
durationMs,
|
|
1674
|
+
options?.actor
|
|
1675
|
+
);
|
|
1676
|
+
}
|
|
1677
|
+
function buildEffectHistoryEntry(pending, outcome) {
|
|
1678
|
+
const { status, ranAt, actor, detail, error, durationMs, outputs } = outcome, resolvedActor = actor ?? pending.actor;
|
|
1679
|
+
return {
|
|
1680
|
+
_key: pending._key,
|
|
1681
|
+
name: pending.name,
|
|
1682
|
+
...pending.title !== void 0 ? { title: pending.title } : {},
|
|
1683
|
+
...pending.description !== void 0 ? { description: pending.description } : {},
|
|
1684
|
+
params: pending.params,
|
|
1685
|
+
origin: pending.origin,
|
|
1686
|
+
...resolvedActor !== void 0 ? { actor: resolvedActor } : {},
|
|
1687
|
+
ranAt,
|
|
1688
|
+
...durationMs !== void 0 ? { durationMs } : {},
|
|
1689
|
+
status,
|
|
1690
|
+
...detail !== void 0 ? { detail } : {},
|
|
1691
|
+
...error !== void 0 ? { error } : {},
|
|
1692
|
+
...outputs !== void 0 ? { outputs } : {}
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
function upsertEffectsContext(mutation, effectName, outputs) {
|
|
1696
|
+
const existingIndex = mutation.effectsContext.findIndex((e) => e.name === effectName), entry = effectsContextJsonEntry(effectName, outputs);
|
|
1697
|
+
existingIndex >= 0 ? mutation.effectsContext[existingIndex] = entry : mutation.effectsContext.push(entry);
|
|
1698
|
+
}
|
|
1699
|
+
async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, error, durationMs, actor) {
|
|
1700
|
+
const pending = ctx.instance.pendingEffects.find((e) => e._key === effectKey);
|
|
1701
|
+
if (pending === void 0)
|
|
1702
|
+
throw new Error(`Pending effect "${effectKey}" not found on instance ${ctx.instance._id}`);
|
|
1703
|
+
const mutation = startMutation(ctx.instance);
|
|
1704
|
+
mutation.pendingEffects = mutation.pendingEffects.filter((e) => e._key !== effectKey);
|
|
1705
|
+
const ranAt = ctx.now;
|
|
1706
|
+
return mutation.effectHistory.push(
|
|
1707
|
+
buildEffectHistoryEntry(pending, { status, ranAt, actor, detail, error, durationMs, outputs })
|
|
1708
|
+
), status === "done" && outputs !== void 0 && upsertEffectsContext(mutation, pending.name, outputs), mutation.history.push({
|
|
1709
|
+
_key: randomKey(),
|
|
1710
|
+
_type: "effectCompleted",
|
|
1711
|
+
at: ranAt,
|
|
1712
|
+
effectKey,
|
|
1713
|
+
effect: pending.name,
|
|
1714
|
+
status,
|
|
1715
|
+
...outputs !== void 0 ? { outputs } : {},
|
|
1716
|
+
...detail !== void 0 ? { detail } : {},
|
|
1717
|
+
...actor !== void 0 ? { actor } : {}
|
|
1718
|
+
}), await persist(ctx, mutation), { effectKey, status };
|
|
1719
|
+
}
|
|
1720
|
+
function buildQueuedEffect(effect, origin, params, actor, now) {
|
|
1721
|
+
const key = randomKey(), pending = {
|
|
1722
|
+
_key: key,
|
|
1723
|
+
_type: "pendingEffect",
|
|
1724
|
+
name: effect.name,
|
|
1725
|
+
...effect.title !== void 0 ? { title: effect.title } : {},
|
|
1726
|
+
...effect.description !== void 0 ? { description: effect.description } : {},
|
|
1727
|
+
...effect.bindings !== void 0 ? { bindings: effect.bindings } : {},
|
|
1728
|
+
params,
|
|
1729
|
+
origin,
|
|
1730
|
+
...actor !== void 0 ? { actor } : {},
|
|
1731
|
+
queuedAt: now
|
|
1732
|
+
}, history = {
|
|
1733
|
+
_key: randomKey(),
|
|
1734
|
+
_type: "effectQueued",
|
|
1735
|
+
at: now,
|
|
1736
|
+
effectKey: key,
|
|
1737
|
+
effect: effect.name,
|
|
1738
|
+
origin
|
|
1739
|
+
};
|
|
1740
|
+
return { pending, history };
|
|
1741
|
+
}
|
|
1742
|
+
async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
|
|
1743
|
+
if (!effects || effects.length === 0) return;
|
|
1744
|
+
const now = ctx.now, liveCtx = { ...ctx, instance: materializeInstance(ctx.instance, mutation) }, params = await ctxConditionParams(liveCtx, {
|
|
1745
|
+
...opts?.taskName !== void 0 ? { taskName: opts.taskName } : {},
|
|
1746
|
+
...actor !== void 0 ? { actor } : {},
|
|
1747
|
+
// Caller-supplied action params, readable in bindings as `$params.<name>`.
|
|
1748
|
+
vars: { params: opts?.callerParams ?? {} }
|
|
1749
|
+
});
|
|
1750
|
+
for (const effect of effects) {
|
|
1751
|
+
const resolved = await resolveBindings({
|
|
1752
|
+
bindings: effect.bindings,
|
|
1753
|
+
staticInput: effect.input,
|
|
1754
|
+
snapshot: ctx.snapshot,
|
|
1755
|
+
params
|
|
1756
|
+
}), { pending, history } = buildQueuedEffect(effect, origin, resolved, actor, now);
|
|
1757
|
+
mutation.pendingEffects.push(pending), mutation.history.push(history);
|
|
1758
|
+
}
|
|
1382
1759
|
}
|
|
1383
1760
|
function resolveStaticSource(src, ctx) {
|
|
1384
1761
|
switch (src.type) {
|
|
@@ -1609,7 +1986,8 @@ function evalOpPredicate(p, row, ctx) {
|
|
|
1609
1986
|
}
|
|
1610
1987
|
async function invokeTask(args) {
|
|
1611
1988
|
const { client, instanceId, task: taskName, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1612
|
-
...options?.clock ? { clock: options.clock } : {}
|
|
1989
|
+
...options?.clock ? { clock: options.clock } : {},
|
|
1990
|
+
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
1613
1991
|
});
|
|
1614
1992
|
return commitInvoke(ctx, taskName, options?.actor);
|
|
1615
1993
|
}
|
|
@@ -1717,7 +2095,8 @@ function isTerminalStage(stage) {
|
|
|
1717
2095
|
}
|
|
1718
2096
|
async function setStage(args) {
|
|
1719
2097
|
const { client, instanceId, targetStage, reason, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1720
|
-
...options?.clock ? { clock: options.clock } : {}
|
|
2098
|
+
...options?.clock ? { clock: options.clock } : {},
|
|
2099
|
+
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
1721
2100
|
});
|
|
1722
2101
|
return commitSetStage(ctx, targetStage, reason, options?.actor);
|
|
1723
2102
|
}
|
|
@@ -1973,282 +2352,53 @@ async function cascadeAutoTransitions(client, instanceId, actor, clientForGdr, c
|
|
|
1973
2352
|
...actor !== void 0 ? { options: { actor } } : {},
|
|
1974
2353
|
...clientForGdr ? { clientForGdr } : {},
|
|
1975
2354
|
...clock ? { clock } : {},
|
|
1976
|
-
...overlay ? { overlay } : {}
|
|
1977
|
-
})).fired) return count;
|
|
1978
|
-
if (count++, count >= CASCADE_LIMIT)
|
|
1979
|
-
throw new CascadeLimitError({ instanceId, limit: CASCADE_LIMIT });
|
|
1980
|
-
}
|
|
1981
|
-
}
|
|
1982
|
-
async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
|
|
1983
|
-
const parentRef = child.ancestors.at(-1);
|
|
1984
|
-
if (parentRef === void 0) return;
|
|
1985
|
-
const parent = await client.getDocument(gdrToBareId(parentRef.id));
|
|
1986
|
-
if (!parent || parent._type !== WORKFLOW_INSTANCE_TYPE || typeof parent.definitionSnapshot != "string") return;
|
|
1987
|
-
const definition = parseDefinitionSnapshot(parent), stage = definition.stages.find((s) => s.name === parent.currentStage);
|
|
1988
|
-
if (stage === void 0) return;
|
|
1989
|
-
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);
|
|
1990
|
-
if (task?.subworkflows === void 0) return;
|
|
1991
|
-
const entry = parentCurrentTasks.find((e) => e.name === task.name);
|
|
1992
|
-
if (entry === void 0 || entry.status !== "active") return;
|
|
1993
|
-
const ctx = await buildEngineContext({
|
|
1994
|
-
client,
|
|
1995
|
-
clientForGdr,
|
|
1996
|
-
instance: parent,
|
|
1997
|
-
definition,
|
|
1998
|
-
...clock ? { clock } : {}
|
|
1999
|
-
}), outcome = await evaluateResolutionGate(ctx, task, await taskConditionVars(ctx, entry));
|
|
2000
|
-
if (outcome !== void 0)
|
|
2001
|
-
return { parent, definition, stage, task, outcome };
|
|
2002
|
-
}
|
|
2003
|
-
async function propagateToAncestors(client, instanceId, actor, clientForGdr, clock) {
|
|
2004
|
-
const instance = await client.getDocument(instanceId);
|
|
2005
|
-
if (!instance) return;
|
|
2006
|
-
const resolvedClientForGdr = clientForGdr ?? (() => client), found = await findResolvedParentSpawn(client, instance, resolvedClientForGdr, clock);
|
|
2007
|
-
if (found === void 0) return;
|
|
2008
|
-
const { parent, definition, stage, task, outcome } = found, ctx = await buildEngineContext({
|
|
2009
|
-
client,
|
|
2010
|
-
clientForGdr: resolvedClientForGdr,
|
|
2011
|
-
instance: parent,
|
|
2012
|
-
definition,
|
|
2013
|
-
...clock ? { clock } : {}
|
|
2014
|
-
}), mutation = startMutation(parent), mutEntry = currentTasks(mutation).find((e) => e.name === task.name);
|
|
2015
|
-
mutEntry !== void 0 && (applyTaskStatusChange({
|
|
2016
|
-
entry: mutEntry,
|
|
2017
|
-
history: mutation.history,
|
|
2018
|
-
stage: stage.name,
|
|
2019
|
-
to: outcome,
|
|
2020
|
-
at: ctx.now,
|
|
2021
|
-
actor: gateActor(outcome)
|
|
2022
|
-
}), await persist(ctx, mutation), await cascadeAutoTransitions(client, parent._id, actor, clientForGdr, clock), await propagateToAncestors(client, parent._id, actor, clientForGdr, clock));
|
|
2023
|
-
}
|
|
2024
|
-
function startMutation(instance) {
|
|
2025
|
-
return {
|
|
2026
|
-
currentStage: instance.currentStage,
|
|
2027
|
-
// Deep-ish copy. Per-scope state entry arrays need their own copies
|
|
2028
|
-
// so ops can mutate without leaking into the source.
|
|
2029
|
-
state: (instance.state ?? []).map((s) => ({ ...s })),
|
|
2030
|
-
stages: instance.stages.map((s) => ({
|
|
2031
|
-
...s,
|
|
2032
|
-
state: (s.state ?? []).map((entry) => ({ ...entry })),
|
|
2033
|
-
tasks: s.tasks.map((t) => ({
|
|
2034
|
-
...t,
|
|
2035
|
-
...t.state !== void 0 ? { state: t.state.map((entry) => ({ ...entry })) } : {}
|
|
2036
|
-
}))
|
|
2037
|
-
})),
|
|
2038
|
-
pendingEffects: [...instance.pendingEffects],
|
|
2039
|
-
effectHistory: [...instance.effectHistory],
|
|
2040
|
-
effectsContext: [...instance.effectsContext],
|
|
2041
|
-
history: [...instance.history],
|
|
2042
|
-
lastChangedAt: instance.lastChangedAt,
|
|
2043
|
-
...instance.completedAt !== void 0 ? { completedAt: instance.completedAt } : {},
|
|
2044
|
-
...instance.abortedAt !== void 0 ? { abortedAt: instance.abortedAt } : {},
|
|
2045
|
-
pendingCreates: []
|
|
2046
|
-
};
|
|
2047
|
-
}
|
|
2048
|
-
function instanceStateFields(src) {
|
|
2049
|
-
const fields = {
|
|
2050
|
-
currentStage: src.currentStage,
|
|
2051
|
-
state: src.state,
|
|
2052
|
-
stages: src.stages,
|
|
2053
|
-
pendingEffects: src.pendingEffects,
|
|
2054
|
-
effectHistory: src.effectHistory,
|
|
2055
|
-
effectsContext: src.effectsContext,
|
|
2056
|
-
history: src.history
|
|
2057
|
-
};
|
|
2058
|
-
return src.completedAt !== void 0 && (fields.completedAt = src.completedAt), src.abortedAt !== void 0 && (fields.abortedAt = src.abortedAt), fields;
|
|
2059
|
-
}
|
|
2060
|
-
function materializeInstance(base, mutation) {
|
|
2061
|
-
return {
|
|
2062
|
-
...base,
|
|
2063
|
-
currentStage: mutation.currentStage,
|
|
2064
|
-
state: mutation.state,
|
|
2065
|
-
stages: mutation.stages
|
|
2066
|
-
};
|
|
2067
|
-
}
|
|
2068
|
-
async function persist(ctx, mutation) {
|
|
2069
|
-
const set = {
|
|
2070
|
-
...instanceStateFields(mutation),
|
|
2071
|
-
lastChangedAt: ctx.now
|
|
2072
|
-
}, pendingCreates = mutation.pendingCreates;
|
|
2073
|
-
if (pendingCreates.length === 0)
|
|
2074
|
-
return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit();
|
|
2075
|
-
const tx = ctx.client.transaction();
|
|
2076
|
-
for (const body of pendingCreates) tx.create(body);
|
|
2077
|
-
tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)), await tx.commit(), mutation.pendingCreates = [];
|
|
2078
|
-
const actorForPriming = void 0;
|
|
2079
|
-
for (const body of pendingCreates)
|
|
2080
|
-
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);
|
|
2081
|
-
const reloaded = await ctx.client.getDocument(ctx.instance._id);
|
|
2082
|
-
if (!reloaded)
|
|
2083
|
-
throw new Error(`Instance ${ctx.instance._id} disappeared after transaction commit`);
|
|
2084
|
-
return reloaded;
|
|
2085
|
-
}
|
|
2086
|
-
function currentStageEntry(mutation) {
|
|
2087
|
-
const entry = findOpenStageEntry(mutation);
|
|
2088
|
-
if (entry === void 0)
|
|
2089
|
-
throw new Error(
|
|
2090
|
-
`Mutation invariant broken: no current (un-exited) StageEntry for currentStage "${mutation.currentStage}"`
|
|
2091
|
-
);
|
|
2092
|
-
return entry;
|
|
2093
|
-
}
|
|
2094
|
-
function currentTasks(mutation) {
|
|
2095
|
-
return currentStageEntry(mutation).tasks;
|
|
2096
|
-
}
|
|
2097
|
-
function findTaskInCurrentStage(ctx, taskName) {
|
|
2098
|
-
const stage = findStage(ctx.definition, ctx.instance.currentStage), task = (stage.tasks ?? []).find((t) => t.name === taskName);
|
|
2099
|
-
if (task === void 0)
|
|
2100
|
-
throw new Error(
|
|
2101
|
-
`Task "${taskName}" not found in current stage "${stage.name}" of ${ctx.definition.name}`
|
|
2102
|
-
);
|
|
2103
|
-
return { stage, task };
|
|
2104
|
-
}
|
|
2105
|
-
function requireMutationTaskEntry(mutation, task) {
|
|
2106
|
-
const mutEntry = currentTasks(mutation).find((t) => t.name === task);
|
|
2107
|
-
if (mutEntry === void 0)
|
|
2108
|
-
throw new Error(`Task "${task}" disappeared from mutation copy \u2014 invariant broken`);
|
|
2109
|
-
return mutEntry;
|
|
2110
|
-
}
|
|
2111
|
-
function findCurrentStageEntry(instance) {
|
|
2112
|
-
return findOpenStageEntry(instance);
|
|
2113
|
-
}
|
|
2114
|
-
function findCurrentTasks(instance) {
|
|
2115
|
-
return findCurrentStageEntry(instance)?.tasks ?? [];
|
|
2116
|
-
}
|
|
2117
|
-
async function completeEffect(args) {
|
|
2118
|
-
const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
|
|
2119
|
-
...options?.clock ? { clock: options.clock } : {}
|
|
2120
|
-
});
|
|
2121
|
-
return commitCompleteEffect(
|
|
2122
|
-
ctx,
|
|
2123
|
-
effectKey,
|
|
2124
|
-
status,
|
|
2125
|
-
outputs,
|
|
2126
|
-
detail,
|
|
2127
|
-
error,
|
|
2128
|
-
durationMs,
|
|
2129
|
-
options?.actor
|
|
2130
|
-
);
|
|
2131
|
-
}
|
|
2132
|
-
function buildEffectHistoryEntry(pending, outcome) {
|
|
2133
|
-
const { status, ranAt, actor, detail, error, durationMs, outputs } = outcome, resolvedActor = actor ?? pending.actor;
|
|
2134
|
-
return {
|
|
2135
|
-
_key: pending._key,
|
|
2136
|
-
name: pending.name,
|
|
2137
|
-
...pending.title !== void 0 ? { title: pending.title } : {},
|
|
2138
|
-
...pending.description !== void 0 ? { description: pending.description } : {},
|
|
2139
|
-
params: pending.params,
|
|
2140
|
-
origin: pending.origin,
|
|
2141
|
-
...resolvedActor !== void 0 ? { actor: resolvedActor } : {},
|
|
2142
|
-
ranAt,
|
|
2143
|
-
...durationMs !== void 0 ? { durationMs } : {},
|
|
2144
|
-
status,
|
|
2145
|
-
...detail !== void 0 ? { detail } : {},
|
|
2146
|
-
...error !== void 0 ? { error } : {},
|
|
2147
|
-
...outputs !== void 0 ? { outputs } : {}
|
|
2148
|
-
};
|
|
2149
|
-
}
|
|
2150
|
-
function upsertEffectsContext(mutation, effectName, outputs) {
|
|
2151
|
-
const existingIndex = mutation.effectsContext.findIndex((e) => e.name === effectName), entry = effectsContextJsonEntry(effectName, outputs);
|
|
2152
|
-
existingIndex >= 0 ? mutation.effectsContext[existingIndex] = entry : mutation.effectsContext.push(entry);
|
|
2153
|
-
}
|
|
2154
|
-
async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, error, durationMs, actor) {
|
|
2155
|
-
const pending = ctx.instance.pendingEffects.find((e) => e._key === effectKey);
|
|
2156
|
-
if (pending === void 0)
|
|
2157
|
-
throw new Error(`Pending effect "${effectKey}" not found on instance ${ctx.instance._id}`);
|
|
2158
|
-
const mutation = startMutation(ctx.instance);
|
|
2159
|
-
mutation.pendingEffects = mutation.pendingEffects.filter((e) => e._key !== effectKey);
|
|
2160
|
-
const ranAt = ctx.now;
|
|
2161
|
-
return mutation.effectHistory.push(
|
|
2162
|
-
buildEffectHistoryEntry(pending, { status, ranAt, actor, detail, error, durationMs, outputs })
|
|
2163
|
-
), status === "done" && outputs !== void 0 && upsertEffectsContext(mutation, pending.name, outputs), mutation.history.push({
|
|
2164
|
-
_key: randomKey(),
|
|
2165
|
-
_type: "effectCompleted",
|
|
2166
|
-
at: ranAt,
|
|
2167
|
-
effectKey,
|
|
2168
|
-
effect: pending.name,
|
|
2169
|
-
status,
|
|
2170
|
-
...outputs !== void 0 ? { outputs } : {},
|
|
2171
|
-
...detail !== void 0 ? { detail } : {},
|
|
2172
|
-
...actor !== void 0 ? { actor } : {}
|
|
2173
|
-
}), await persist(ctx, mutation), { effectKey, status };
|
|
2174
|
-
}
|
|
2175
|
-
function buildQueuedEffect(effect, origin, params, actor, now) {
|
|
2176
|
-
const key = randomKey(), pending = {
|
|
2177
|
-
_key: key,
|
|
2178
|
-
_type: "pendingEffect",
|
|
2179
|
-
name: effect.name,
|
|
2180
|
-
...effect.title !== void 0 ? { title: effect.title } : {},
|
|
2181
|
-
...effect.description !== void 0 ? { description: effect.description } : {},
|
|
2182
|
-
...effect.bindings !== void 0 ? { bindings: effect.bindings } : {},
|
|
2183
|
-
params,
|
|
2184
|
-
origin,
|
|
2185
|
-
...actor !== void 0 ? { actor } : {},
|
|
2186
|
-
queuedAt: now
|
|
2187
|
-
}, history = {
|
|
2188
|
-
_key: randomKey(),
|
|
2189
|
-
_type: "effectQueued",
|
|
2190
|
-
at: now,
|
|
2191
|
-
effectKey: key,
|
|
2192
|
-
effect: effect.name,
|
|
2193
|
-
origin
|
|
2194
|
-
};
|
|
2195
|
-
return { pending, history };
|
|
2196
|
-
}
|
|
2197
|
-
async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
|
|
2198
|
-
if (!effects || effects.length === 0) return;
|
|
2199
|
-
const now = ctx.now, liveCtx = { ...ctx, instance: materializeInstance(ctx.instance, mutation) }, params = await ctxConditionParams(liveCtx, {
|
|
2200
|
-
...opts?.taskName !== void 0 ? { taskName: opts.taskName } : {},
|
|
2201
|
-
...actor !== void 0 ? { actor } : {},
|
|
2202
|
-
// Caller-supplied action params, readable in bindings as `$params.<name>`.
|
|
2203
|
-
vars: { params: opts?.callerParams ?? {} }
|
|
2204
|
-
});
|
|
2205
|
-
for (const effect of effects) {
|
|
2206
|
-
const resolved = await resolveBindings({
|
|
2207
|
-
bindings: effect.bindings,
|
|
2208
|
-
staticInput: effect.input,
|
|
2209
|
-
snapshot: ctx.snapshot,
|
|
2210
|
-
params
|
|
2211
|
-
}), { pending, history } = buildQueuedEffect(effect, origin, resolved, actor, now);
|
|
2212
|
-
mutation.pendingEffects.push(pending), mutation.history.push(history);
|
|
2355
|
+
...overlay ? { overlay } : {}
|
|
2356
|
+
})).fired) return count;
|
|
2357
|
+
if (count++, count >= CASCADE_LIMIT)
|
|
2358
|
+
throw new CascadeLimitError({ instanceId, limit: CASCADE_LIMIT });
|
|
2213
2359
|
}
|
|
2214
2360
|
}
|
|
2215
|
-
async function
|
|
2216
|
-
const
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2361
|
+
async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
|
|
2362
|
+
const parentRef = child.ancestors.at(-1);
|
|
2363
|
+
if (parentRef === void 0) return;
|
|
2364
|
+
const parent = await client.getDocument(gdrToBareId(parentRef.id));
|
|
2365
|
+
if (!parent || parent._type !== WORKFLOW_INSTANCE_TYPE || typeof parent.definitionSnapshot != "string") return;
|
|
2366
|
+
const definition = parseDefinitionSnapshot(parent), stage = definition.stages.find((s) => s.name === parent.currentStage);
|
|
2367
|
+
if (stage === void 0) return;
|
|
2368
|
+
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);
|
|
2369
|
+
if (task?.subworkflows === void 0) return;
|
|
2370
|
+
const entry = parentCurrentTasks.find((e) => e.name === task.name);
|
|
2371
|
+
if (entry === void 0 || entry.status !== "active") return;
|
|
2372
|
+
const ctx = await buildEngineContext({
|
|
2373
|
+
client,
|
|
2374
|
+
clientForGdr,
|
|
2375
|
+
instance: parent,
|
|
2376
|
+
definition,
|
|
2377
|
+
...clock ? { clock } : {}
|
|
2378
|
+
}), outcome = await evaluateResolutionGate(ctx, task, await taskConditionVars(ctx, entry));
|
|
2379
|
+
if (outcome !== void 0)
|
|
2380
|
+
return { parent, definition, stage, task, outcome };
|
|
2220
2381
|
}
|
|
2221
|
-
async function
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
const
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
)
|
|
2237
|
-
), mutation.pendingEffects = [], mutation.history.push({
|
|
2238
|
-
_key: randomKey(),
|
|
2239
|
-
_type: "aborted",
|
|
2240
|
-
at,
|
|
2382
|
+
async function propagateToAncestors(client, instanceId, actor, clientForGdr, clock) {
|
|
2383
|
+
const instance = await client.getDocument(instanceId);
|
|
2384
|
+
if (!instance) return;
|
|
2385
|
+
const resolvedClientForGdr = clientForGdr ?? (() => client), found = await findResolvedParentSpawn(client, instance, resolvedClientForGdr, clock);
|
|
2386
|
+
if (found === void 0) return;
|
|
2387
|
+
const { parent, definition, stage, task, outcome } = found, ctx = await buildEngineContext({
|
|
2388
|
+
client,
|
|
2389
|
+
clientForGdr: resolvedClientForGdr,
|
|
2390
|
+
instance: parent,
|
|
2391
|
+
definition,
|
|
2392
|
+
...clock ? { clock } : {}
|
|
2393
|
+
}), mutation = startMutation(parent), mutEntry = currentTasks(mutation).find((e) => e.name === task.name);
|
|
2394
|
+
mutEntry !== void 0 && (applyTaskStatusChange({
|
|
2395
|
+
entry: mutEntry,
|
|
2396
|
+
history: mutation.history,
|
|
2241
2397
|
stage: stage.name,
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
clientForGdr: ctx.clientForGdr,
|
|
2247
|
-
instance: ctx.instance,
|
|
2248
|
-
definition: ctx.definition,
|
|
2249
|
-
stageName: stage.name,
|
|
2250
|
-
now: ctx.now
|
|
2251
|
-
}), { fired: !0, stage: stage.name };
|
|
2398
|
+
to: outcome,
|
|
2399
|
+
at: ctx.now,
|
|
2400
|
+
actor: gateActor(outcome)
|
|
2401
|
+
}), await persist(ctx, mutation), await cascadeAutoTransitions(client, parent._id, actor, clientForGdr, clock), await propagateToAncestors(client, parent._id, actor, clientForGdr, clock));
|
|
2252
2402
|
}
|
|
2253
2403
|
const parsedFilters = /* @__PURE__ */ new Map();
|
|
2254
2404
|
async function matchesFilter(args) {
|
|
@@ -2344,105 +2494,13 @@ async function fetchGrantsCached(requestFn, resourcePath) {
|
|
|
2344
2494
|
return;
|
|
2345
2495
|
}
|
|
2346
2496
|
}
|
|
2347
|
-
const resourceKey = (r) => `${r.type}.${r.id}`;
|
|
2348
|
-
function guardsForResource(client) {
|
|
2349
|
-
return client.fetch("*[_type == $t] | order(_id asc)", { t: GUARD_DOC_TYPE });
|
|
2350
|
-
}
|
|
2351
|
-
function instanceGuardQuery(instanceId) {
|
|
2352
|
-
return {
|
|
2353
|
-
query: "*[_type == $guardType && sourceInstanceId == $instanceId]",
|
|
2354
|
-
params: { guardType: GUARD_DOC_TYPE, instanceId }
|
|
2355
|
-
};
|
|
2356
|
-
}
|
|
2357
|
-
function verdictGuardsForInstance(client, instanceId) {
|
|
2358
|
-
const { query, params } = instanceGuardQuery(instanceId);
|
|
2359
|
-
return client.fetch(query, params);
|
|
2360
|
-
}
|
|
2361
|
-
async function guardsForInstance(args) {
|
|
2362
|
-
const { client, clientForGdr, instance } = args, stateUris = [
|
|
2363
|
-
...collectEntryDocUris(instance.state),
|
|
2364
|
-
...instance.stages.flatMap((stage) => collectEntryDocUris(stage.state))
|
|
2365
|
-
], clients = resourceClientMap(
|
|
2366
|
-
instance.workflowResource,
|
|
2367
|
-
client,
|
|
2368
|
-
clientForGdr,
|
|
2369
|
-
stateUris.map((uri) => tryParseGdr(uri))
|
|
2370
|
-
), { query, params } = instanceGuardQuery(instance._id);
|
|
2371
|
-
return queryGuardsAcross(clients.values(), query, params);
|
|
2372
|
-
}
|
|
2373
|
-
async function guardsForDefinition(args) {
|
|
2374
|
-
const { client, clientForGdr, workflowResource, definition, definitions } = args, gdrs = definitions.flatMap(
|
|
2375
|
-
(def) => guardIdRefs(def).map(({ idRef, stage }) => staticIdRefGdr(idRef, stage, def))
|
|
2376
|
-
), clients = resourceClientMap(workflowResource, client, clientForGdr, gdrs);
|
|
2377
|
-
return queryGuardsAcross(clients.values(), "*[_type == $t && sourceDefinition == $definition]", {
|
|
2378
|
-
t: GUARD_DOC_TYPE,
|
|
2379
|
-
definition
|
|
2380
|
-
});
|
|
2381
|
-
}
|
|
2382
|
-
function guardIdRefs(definition) {
|
|
2383
|
-
return definition.stages.flatMap(
|
|
2384
|
-
(stage) => (stage.guards ?? []).flatMap(
|
|
2385
|
-
(guard) => (guard.match.idRefs ?? []).map((idRef) => ({ idRef, stage }))
|
|
2386
|
-
)
|
|
2387
|
-
);
|
|
2388
|
-
}
|
|
2389
|
-
function staticIdRefGdr(idRef, stage, definition) {
|
|
2390
|
-
const read = /^\$state\.([\w-]+)/.exec(idRef);
|
|
2391
|
-
if (read === null) return;
|
|
2392
|
-
const source = entriesInScope(stage, definition).find((e) => e.name === read[1])?.source;
|
|
2393
|
-
return source?.type === "literal" ? gdrFromValue(source.value) : void 0;
|
|
2394
|
-
}
|
|
2395
|
-
function entriesInScope(stage, definition) {
|
|
2396
|
-
return [
|
|
2397
|
-
...definition.state ?? [],
|
|
2398
|
-
...stage.state ?? [],
|
|
2399
|
-
...(stage.tasks ?? []).flatMap((task) => task.state ?? [])
|
|
2400
|
-
];
|
|
2401
|
-
}
|
|
2402
|
-
function gdrFromValue(value) {
|
|
2403
|
-
if (typeof value == "string") return tryParseGdr(value);
|
|
2404
|
-
if (typeof value == "object" && value !== null && "id" in value) {
|
|
2405
|
-
const id = value.id;
|
|
2406
|
-
return typeof id == "string" ? tryParseGdr(id) : void 0;
|
|
2407
|
-
}
|
|
2408
|
-
}
|
|
2409
|
-
function tryParseGdr(uri) {
|
|
2410
|
-
try {
|
|
2411
|
-
return parseGdr(uri);
|
|
2412
|
-
} catch {
|
|
2413
|
-
return;
|
|
2414
|
-
}
|
|
2415
|
-
}
|
|
2416
|
-
function resourceClientMap(base, baseClient, clientForGdr, gdrs) {
|
|
2417
|
-
const map = /* @__PURE__ */ new Map([[resourceKey(base), baseClient]]);
|
|
2418
|
-
for (const parsed of gdrs) {
|
|
2419
|
-
if (parsed === void 0) continue;
|
|
2420
|
-
const key = resourceKey(resourceFromParsed(parsed));
|
|
2421
|
-
map.has(key) || map.set(key, clientForGdr(parsed));
|
|
2422
|
-
}
|
|
2423
|
-
return map;
|
|
2424
|
-
}
|
|
2425
|
-
async function queryGuardsAcross(clients, query, params) {
|
|
2426
|
-
const perResource = await Promise.all(
|
|
2427
|
-
[...clients].map((c) => c.fetch(query, params))
|
|
2428
|
-
);
|
|
2429
|
-
return dedupById(perResource.flat());
|
|
2430
|
-
}
|
|
2431
|
-
function dedupById(guards) {
|
|
2432
|
-
return [...new Map(guards.map((g) => [g._id, g])).values()].sort((a, b) => a._id.localeCompare(b._id));
|
|
2433
|
-
}
|
|
2434
2497
|
async function evaluateInstance(args) {
|
|
2435
|
-
const { client,
|
|
2436
|
-
|
|
2498
|
+
const { client, tag, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
|
|
2499
|
+
validateTag(tag);
|
|
2437
2500
|
const { actor, grants } = await resolveAccess(client, {
|
|
2438
2501
|
...args.access !== void 0 ? { override: args.access } : {},
|
|
2439
2502
|
...args.grantsFromPath !== void 0 ? { grantsFromPath: args.grantsFromPath } : {}
|
|
2440
|
-
}), instance = await client
|
|
2441
|
-
if (!instance)
|
|
2442
|
-
throw new Error(`Workflow instance "${instanceId}" not found`);
|
|
2443
|
-
if (!tags.some((t) => instance.tags?.includes(t)))
|
|
2444
|
-
throw new Error(`Workflow instance "${instanceId}" not visible to this engine (tag mismatch)`);
|
|
2445
|
-
const definition = parseDefinitionSnapshot(instance), snapshot = await hydrateSnapshot({ client, clientForGdr: resourceClients !== void 0 ? (parsed) => resourceClients(parsed) ?? client : () => client, instance }), guards = await verdictGuardsForInstance(client, instance._id);
|
|
2503
|
+
}), 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);
|
|
2446
2504
|
return evaluateFromSnapshot({
|
|
2447
2505
|
instance,
|
|
2448
2506
|
definition,
|
|
@@ -2583,22 +2641,21 @@ function lifecycleReason(instance, status, stageHasExits) {
|
|
|
2583
2641
|
function disabled(action, reason) {
|
|
2584
2642
|
return { action, allowed: !1, disabledReason: reason };
|
|
2585
2643
|
}
|
|
2586
|
-
function definitionDocId(_workflowResource,
|
|
2587
|
-
return `${
|
|
2644
|
+
function definitionDocId(_workflowResource, tag, definition, version) {
|
|
2645
|
+
return `${tag}.${definition}.v${version}`;
|
|
2588
2646
|
}
|
|
2589
|
-
async function sortByDependencies(client, definitions,
|
|
2590
|
-
const
|
|
2647
|
+
async function sortByDependencies(client, definitions, tag, workflowResource) {
|
|
2648
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2591
2649
|
for (const def of definitions) {
|
|
2592
2650
|
const list = byName.get(def.name) ?? [];
|
|
2593
2651
|
list.push(def), byName.set(def.name, list);
|
|
2594
2652
|
}
|
|
2595
2653
|
const findInBatch = (ref) => findRefInBatch(byName, ref);
|
|
2596
2654
|
return await assertCrossBatchRefsDeployed(client, definitions, {
|
|
2597
|
-
|
|
2655
|
+
tag,
|
|
2598
2656
|
workflowResource,
|
|
2599
|
-
prefix,
|
|
2600
2657
|
findInBatch
|
|
2601
|
-
}), topoSortDefinitions(definitions, { workflowResource,
|
|
2658
|
+
}), topoSortDefinitions(definitions, { workflowResource, tag, findInBatch });
|
|
2602
2659
|
}
|
|
2603
2660
|
function findRefInBatch(byName, ref) {
|
|
2604
2661
|
const candidates = byName.get(ref.name);
|
|
@@ -2611,10 +2668,10 @@ function findRefInBatch(byName, ref) {
|
|
|
2611
2668
|
async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
|
|
2612
2669
|
const missing = [];
|
|
2613
2670
|
for (const def of definitions) {
|
|
2614
|
-
const fromId = definitionDocId(ctx.workflowResource, ctx.
|
|
2671
|
+
const fromId = definitionDocId(ctx.workflowResource, ctx.tag, def.name, def.version);
|
|
2615
2672
|
for (const ref of refsOf(def)) {
|
|
2616
2673
|
if (ctx.findInBatch(ref) !== void 0) continue;
|
|
2617
|
-
const label = await resolveDeployedRefLabel(client, ref, ctx.
|
|
2674
|
+
const label = await resolveDeployedRefLabel(client, ref, ctx.tag);
|
|
2618
2675
|
label !== void 0 && missing.push({ from: fromId, ref: label });
|
|
2619
2676
|
}
|
|
2620
2677
|
}
|
|
@@ -2626,8 +2683,8 @@ async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
|
|
|
2626
2683
|
`)
|
|
2627
2684
|
);
|
|
2628
2685
|
}
|
|
2629
|
-
async function resolveDeployedRefLabel(client, ref,
|
|
2630
|
-
const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name,
|
|
2686
|
+
async function resolveDeployedRefLabel(client, ref, tag) {
|
|
2687
|
+
const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
|
|
2631
2688
|
if (wantsExplicit && (params.version = ref.version), !await client.fetch(
|
|
2632
2689
|
definitionLookupGroq(wantsExplicit),
|
|
2633
2690
|
params
|
|
@@ -2636,7 +2693,7 @@ async function resolveDeployedRefLabel(client, ref, tags) {
|
|
|
2636
2693
|
}
|
|
2637
2694
|
function topoSortDefinitions(definitions, ctx) {
|
|
2638
2695
|
const visited = /* @__PURE__ */ new Set(), visiting = /* @__PURE__ */ new Set(), ordered = [], visit = (def) => {
|
|
2639
|
-
const id = definitionDocId(ctx.workflowResource, ctx.
|
|
2696
|
+
const id = definitionDocId(ctx.workflowResource, ctx.tag, def.name, def.version);
|
|
2640
2697
|
if (!visited.has(id)) {
|
|
2641
2698
|
if (visiting.has(id))
|
|
2642
2699
|
throw new Error(`workflow.deployDefinitions: dependency cycle involving "${id}"`);
|
|
@@ -2677,11 +2734,11 @@ function stableStringify(value) {
|
|
|
2677
2734
|
([a], [b]) => a.localeCompare(b)
|
|
2678
2735
|
).map(([k, v2]) => `${JSON.stringify(k)}:${stableStringify(v2)}`).join(",")}}`;
|
|
2679
2736
|
}
|
|
2680
|
-
async function loadDefinition(client, definition, version,
|
|
2737
|
+
async function loadDefinition(client, definition, version, tag) {
|
|
2681
2738
|
if (version !== void 0) {
|
|
2682
2739
|
const doc = await client.fetch(
|
|
2683
2740
|
definitionLookupGroq(!0),
|
|
2684
|
-
{ definition, version,
|
|
2741
|
+
{ definition, version, tag }
|
|
2685
2742
|
);
|
|
2686
2743
|
if (!doc)
|
|
2687
2744
|
throw new Error(`Workflow definition ${definition} v${version} not deployed`);
|
|
@@ -2689,23 +2746,63 @@ async function loadDefinition(client, definition, version, tags) {
|
|
|
2689
2746
|
}
|
|
2690
2747
|
const latest = await client.fetch(definitionLookupGroq(!1), {
|
|
2691
2748
|
definition,
|
|
2692
|
-
|
|
2749
|
+
tag
|
|
2693
2750
|
});
|
|
2694
2751
|
if (!latest)
|
|
2695
2752
|
throw new Error(`No deployed definition for workflow ${definition}`);
|
|
2696
2753
|
return latest;
|
|
2697
2754
|
}
|
|
2698
|
-
async function loadDefinitionVersions(client, definition,
|
|
2755
|
+
async function loadDefinitionVersions(client, definition, tag) {
|
|
2699
2756
|
return client.fetch(
|
|
2700
|
-
`*[_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && name == $definition &&
|
|
2701
|
-
{ definition,
|
|
2757
|
+
`*[_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}] | order(version desc)`,
|
|
2758
|
+
{ definition, tag }
|
|
2702
2759
|
);
|
|
2703
2760
|
}
|
|
2761
|
+
async function abortInstance(args) {
|
|
2762
|
+
const { client, instanceId, reason, options } = args, ctx = await loadContext(client, instanceId, {
|
|
2763
|
+
...options?.clock ? { clock: options.clock } : {},
|
|
2764
|
+
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
2765
|
+
});
|
|
2766
|
+
return commitAbort(ctx, reason, options?.actor);
|
|
2767
|
+
}
|
|
2768
|
+
async function commitAbort(ctx, reason, actor) {
|
|
2769
|
+
if (ctx.instance.completedAt !== void 0)
|
|
2770
|
+
return { fired: !1 };
|
|
2771
|
+
const stage = findStage(ctx.definition, ctx.instance.currentStage), mutation = startMutation(ctx.instance), at = ctx.now, openEntry = findOpenStageEntry(mutation);
|
|
2772
|
+
return openEntry !== void 0 && (openEntry.exitedAt = at), mutation.effectHistory.push(
|
|
2773
|
+
...mutation.pendingEffects.map(
|
|
2774
|
+
(pending) => buildEffectHistoryEntry(pending, {
|
|
2775
|
+
status: "failed",
|
|
2776
|
+
ranAt: at,
|
|
2777
|
+
actor,
|
|
2778
|
+
detail: "instance aborted before dispatch",
|
|
2779
|
+
error: void 0,
|
|
2780
|
+
durationMs: void 0,
|
|
2781
|
+
outputs: void 0
|
|
2782
|
+
})
|
|
2783
|
+
)
|
|
2784
|
+
), mutation.pendingEffects = [], mutation.history.push({
|
|
2785
|
+
_key: randomKey(),
|
|
2786
|
+
_type: "aborted",
|
|
2787
|
+
at,
|
|
2788
|
+
stage: stage.name,
|
|
2789
|
+
...reason !== void 0 ? { reason } : {},
|
|
2790
|
+
...actor !== void 0 ? { actor } : {}
|
|
2791
|
+
}), mutation.completedAt = at, mutation.abortedAt = at, await persist(ctx, mutation), await retractStageGuards({
|
|
2792
|
+
client: ctx.client,
|
|
2793
|
+
clientForGdr: ctx.clientForGdr,
|
|
2794
|
+
instance: ctx.instance,
|
|
2795
|
+
definition: ctx.definition,
|
|
2796
|
+
stageName: stage.name,
|
|
2797
|
+
now: ctx.now
|
|
2798
|
+
}), { fired: !0, stage: stage.name };
|
|
2799
|
+
}
|
|
2704
2800
|
async function fireAction(args) {
|
|
2705
2801
|
const { client, instanceId, task, action, params, options } = args;
|
|
2706
2802
|
for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
|
|
2707
2803
|
const ctx = await loadContext(client, instanceId, {
|
|
2708
|
-
...options?.clock ? { clock: options.clock } : {}
|
|
2804
|
+
...options?.clock ? { clock: options.clock } : {},
|
|
2805
|
+
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
2709
2806
|
});
|
|
2710
2807
|
try {
|
|
2711
2808
|
return await commitAction(ctx, task, action, params, options);
|
|
@@ -2809,7 +2906,7 @@ function bareIdFromSpawnRef(uri) {
|
|
|
2809
2906
|
}
|
|
2810
2907
|
}
|
|
2811
2908
|
async function resolveOperationContext(args) {
|
|
2812
|
-
|
|
2909
|
+
validateTag(args.tag);
|
|
2813
2910
|
const access = await resolveAccess(args.client, {
|
|
2814
2911
|
...args.access !== void 0 ? { override: args.access } : {},
|
|
2815
2912
|
...args.grantsFromPath !== void 0 ? { grantsFromPath: args.grantsFromPath } : {}
|
|
@@ -2831,19 +2928,18 @@ async function cascade(client, instanceId, actor, clientForGdr, clock, overlay)
|
|
|
2831
2928
|
);
|
|
2832
2929
|
return await propagateToAncestors(client, instanceId, actor, clientForGdr, clock), count;
|
|
2833
2930
|
}
|
|
2931
|
+
async function abortAndPropagate(args) {
|
|
2932
|
+
const { client, instanceId, reason, actor, clientForGdr, clock } = args, result = await abortInstance({
|
|
2933
|
+
client,
|
|
2934
|
+
instanceId,
|
|
2935
|
+
...reason !== void 0 ? { reason } : {},
|
|
2936
|
+
options: { ...actor !== void 0 ? { actor } : {}, clock, clientForGdr }
|
|
2937
|
+
});
|
|
2938
|
+
return result.fired && await propagateToAncestors(client, instanceId, actor, clientForGdr, clock), result.fired;
|
|
2939
|
+
}
|
|
2834
2940
|
function buildClientForGdr(defaultClient, resolver) {
|
|
2835
2941
|
return resolver === void 0 ? () => defaultClient : (parsed) => resolver(parsed) ?? defaultClient;
|
|
2836
2942
|
}
|
|
2837
|
-
async function reload(client, instanceId, tags) {
|
|
2838
|
-
const doc = await client.getDocument(instanceId);
|
|
2839
|
-
if (!doc) throw new Error(`Workflow instance ${instanceId} not found`);
|
|
2840
|
-
if (!intersectsTags(doc.tags, tags))
|
|
2841
|
-
throw new Error(`Workflow instance ${instanceId} not visible to this engine (tag mismatch)`);
|
|
2842
|
-
return doc;
|
|
2843
|
-
}
|
|
2844
|
-
function intersectsTags(docTags, engineTags) {
|
|
2845
|
-
return !docTags || docTags.length === 0 ? !1 : engineTags.some((t) => docTags.includes(t));
|
|
2846
|
-
}
|
|
2847
2943
|
function toEffectsContextEntries(ctx) {
|
|
2848
2944
|
return Object.entries(ctx).map(([id, value]) => effectsContextEntry(id, value));
|
|
2849
2945
|
}
|
|
@@ -2853,10 +2949,11 @@ function assertActionAllowed(evaluation, task, action) {
|
|
|
2853
2949
|
throw new ActionDisabledError({ task, action, reason: actionEval.disabledReason });
|
|
2854
2950
|
}
|
|
2855
2951
|
async function applyAction(args) {
|
|
2856
|
-
const { client, instance, task, action, params, actor, grants, clock } = args, options = {
|
|
2952
|
+
const { client, instance, task, action, params, actor, grants, clock, clientForGdr } = args, options = {
|
|
2857
2953
|
actor,
|
|
2858
2954
|
...grants !== void 0 ? { grants } : {},
|
|
2859
|
-
...clock !== void 0 ? { clock } : {}
|
|
2955
|
+
...clock !== void 0 ? { clock } : {},
|
|
2956
|
+
...clientForGdr !== void 0 ? { clientForGdr } : {}
|
|
2860
2957
|
};
|
|
2861
2958
|
return findOpenStageEntry(instance)?.tasks.find((t) => t.name === task)?.status === "pending" && await invokeTask({ client, instanceId: instance._id, task, options }), (await fireAction({
|
|
2862
2959
|
client,
|
|
@@ -2867,6 +2964,78 @@ async function applyAction(args) {
|
|
|
2867
2964
|
options
|
|
2868
2965
|
})).ranOps;
|
|
2869
2966
|
}
|
|
2967
|
+
async function deleteDefinition(args) {
|
|
2968
|
+
const { client, tag, definition, version, cascade: cascade2, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, versions = await loadDefinitionVersions(client, definition, tag);
|
|
2969
|
+
if (versions.length === 0)
|
|
2970
|
+
throw new Error(`No deployed definition for workflow ${definition}`);
|
|
2971
|
+
const targets = version === void 0 ? versions : versions.filter((d) => d.version === version);
|
|
2972
|
+
if (targets.length === 0)
|
|
2973
|
+
throw new Error(`Workflow definition ${definition} v${version} not deployed`);
|
|
2974
|
+
const lastVersionGoes = targets.length === versions.length;
|
|
2975
|
+
await assertNoSpawnReferrers(client, tag, definition, targets, lastVersionGoes);
|
|
2976
|
+
const instanceIds = await nonTerminalInstanceIds(client, tag, definition, version);
|
|
2977
|
+
if (instanceIds.length > 0 && cascade2 !== !0)
|
|
2978
|
+
throw new Error(
|
|
2979
|
+
`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.`
|
|
2980
|
+
);
|
|
2981
|
+
const abortedInstanceIds = await abortInstances({
|
|
2982
|
+
client,
|
|
2983
|
+
instanceIds,
|
|
2984
|
+
reason,
|
|
2985
|
+
actor,
|
|
2986
|
+
clientForGdr,
|
|
2987
|
+
clock
|
|
2988
|
+
}), tx = client.transaction();
|
|
2989
|
+
for (const target of targets) tx.delete(target._id);
|
|
2990
|
+
await tx.commit();
|
|
2991
|
+
const deletedGuardCount = lastVersionGoes ? await deleteOrphanedDefinitionGuards({
|
|
2992
|
+
client,
|
|
2993
|
+
clientForGdr,
|
|
2994
|
+
workflowResource: args.workflowResource,
|
|
2995
|
+
definition,
|
|
2996
|
+
definitions: versions,
|
|
2997
|
+
tag
|
|
2998
|
+
}) : 0;
|
|
2999
|
+
return {
|
|
3000
|
+
name: definition,
|
|
3001
|
+
deletedVersions: targets.map((d) => d.version),
|
|
3002
|
+
abortedInstanceIds,
|
|
3003
|
+
deletedGuardCount
|
|
3004
|
+
};
|
|
3005
|
+
}
|
|
3006
|
+
async function assertNoSpawnReferrers(client, tag, definition, targets, lastVersionGoes) {
|
|
3007
|
+
const deployed = await client.fetch(
|
|
3008
|
+
`*[_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && ${tagScopeFilter()}]`,
|
|
3009
|
+
{ tag }
|
|
3010
|
+
), 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(
|
|
3011
|
+
(d) => refsOf(d).some(
|
|
3012
|
+
(ref) => ref.name === definition && (typeof ref.version == "number" ? targetVersions.has(ref.version) : lastVersionGoes)
|
|
3013
|
+
)
|
|
3014
|
+
);
|
|
3015
|
+
if (referrers.length > 0) {
|
|
3016
|
+
const names = referrers.map((d) => `${d.name} v${d.version}`).join(", ");
|
|
3017
|
+
throw new Error(
|
|
3018
|
+
`Cannot delete ${definition}: still spawn-referenced by deployed definition(s) ${names}. Delete or redeploy the referrer(s) first.`
|
|
3019
|
+
);
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
async function nonTerminalInstanceIds(client, tag, definition, version) {
|
|
3023
|
+
const versionFilter = version === void 0 ? "" : " && pinnedVersion == $version";
|
|
3024
|
+
return (await client.fetch(
|
|
3025
|
+
`*[_type == "${WORKFLOW_INSTANCE_TYPE}" && definition == $definition && ${tagScopeFilter()} && !defined(completedAt)${versionFilter}] | order(_id asc){_id}`,
|
|
3026
|
+
{ definition, tag, ...version !== void 0 ? { version } : {} }
|
|
3027
|
+
)).map((doc) => doc._id);
|
|
3028
|
+
}
|
|
3029
|
+
async function abortInstances(args) {
|
|
3030
|
+
const { client, instanceIds, reason, actor, clientForGdr, clock } = args, aborted = [];
|
|
3031
|
+
for (const instanceId of instanceIds)
|
|
3032
|
+
await abortAndPropagate({ client, instanceId, reason, actor, clientForGdr, clock }) && aborted.push(instanceId);
|
|
3033
|
+
return aborted;
|
|
3034
|
+
}
|
|
3035
|
+
function previewIds(ids) {
|
|
3036
|
+
const head = ids.slice(0, 3).join(", ");
|
|
3037
|
+
return ids.length > 3 ? `${head}, \u2026` : head;
|
|
3038
|
+
}
|
|
2870
3039
|
const workflow = {
|
|
2871
3040
|
/**
|
|
2872
3041
|
* Deploy a set of workflow definitions as one call. Each lands as a
|
|
@@ -2885,20 +3054,20 @@ const workflow = {
|
|
|
2885
3054
|
* Cycles in the dependency graph error before any write happens.
|
|
2886
3055
|
*/
|
|
2887
3056
|
deployDefinitions: async (args) => {
|
|
2888
|
-
const { client,
|
|
2889
|
-
|
|
3057
|
+
const { client, tag, workflowResource, definitions } = args;
|
|
3058
|
+
validateTag(tag);
|
|
2890
3059
|
for (const def of definitions)
|
|
2891
3060
|
validateDefinition(def);
|
|
2892
|
-
const ordered = await sortByDependencies(client, definitions,
|
|
3061
|
+
const ordered = await sortByDependencies(client, definitions, tag, workflowResource), results = [], tx = client.transaction();
|
|
2893
3062
|
let hasWrites = !1;
|
|
2894
3063
|
for (const def of ordered) {
|
|
2895
|
-
const id = definitionDocId(workflowResource,
|
|
3064
|
+
const id = definitionDocId(workflowResource, tag, def.name, def.version), expected = {
|
|
2896
3065
|
...def,
|
|
2897
3066
|
_id: id,
|
|
2898
3067
|
_type: schema.WORKFLOW_DEFINITION_TYPE,
|
|
2899
|
-
|
|
3068
|
+
tag
|
|
2900
3069
|
}, existing = await client.getDocument(id);
|
|
2901
|
-
if (existing &&
|
|
3070
|
+
if (existing && existing.tag === tag && isDefinitionUnchanged(existing, expected)) {
|
|
2902
3071
|
results.push({ definition: def.name, version: def.version, status: "unchanged" });
|
|
2903
3072
|
continue;
|
|
2904
3073
|
}
|
|
@@ -2917,6 +3086,14 @@ const workflow = {
|
|
|
2917
3086
|
}
|
|
2918
3087
|
return hasWrites && await tx.commit(), { results };
|
|
2919
3088
|
},
|
|
3089
|
+
/**
|
|
3090
|
+
* Remove a deployed workflow definition (all versions, or one via
|
|
3091
|
+
* `version`). Refuses while non-terminal instances exist unless
|
|
3092
|
+
* `cascade` aborts them first — instances are never deleted, only
|
|
3093
|
+
* aborted in place; see {@link deleteDefinitionInternal} for the
|
|
3094
|
+
* full contract (spawn-referrer check, guard-doc housekeeping).
|
|
3095
|
+
*/
|
|
3096
|
+
deleteDefinition: async (args) => deleteDefinition(args),
|
|
2920
3097
|
/**
|
|
2921
3098
|
* Spawn a new workflow instance from a deployed definition.
|
|
2922
3099
|
*
|
|
@@ -2928,7 +3105,7 @@ const workflow = {
|
|
|
2928
3105
|
startInstance: async (args) => {
|
|
2929
3106
|
const {
|
|
2930
3107
|
client,
|
|
2931
|
-
|
|
3108
|
+
tag,
|
|
2932
3109
|
workflowResource,
|
|
2933
3110
|
definition: definitionName,
|
|
2934
3111
|
version,
|
|
@@ -2937,7 +3114,7 @@ const workflow = {
|
|
|
2937
3114
|
effectsContext,
|
|
2938
3115
|
instanceId,
|
|
2939
3116
|
perspective
|
|
2940
|
-
} = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, definition = await loadDefinition(client, definitionName, version,
|
|
3117
|
+
} = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, definition = await loadDefinition(client, definitionName, version, tag), id = instanceId ?? `${tag}.wf-instance.${randomKey()}`;
|
|
2941
3118
|
if (definition.stages.find((s) => s.name === definition.initialStage) === void 0)
|
|
2942
3119
|
throw new Error(
|
|
2943
3120
|
`Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
|
|
@@ -2949,7 +3126,7 @@ const workflow = {
|
|
|
2949
3126
|
client,
|
|
2950
3127
|
now,
|
|
2951
3128
|
selfId: id,
|
|
2952
|
-
|
|
3129
|
+
tag,
|
|
2953
3130
|
workflowResource,
|
|
2954
3131
|
...perspective !== void 0 ? { perspective } : {}
|
|
2955
3132
|
},
|
|
@@ -2957,7 +3134,7 @@ const workflow = {
|
|
|
2957
3134
|
}), effectivePerspective = perspective ?? derivePerspectiveFromState(resolvedState), base = buildInstanceBase({
|
|
2958
3135
|
id,
|
|
2959
3136
|
now,
|
|
2960
|
-
|
|
3137
|
+
tag,
|
|
2961
3138
|
workflowResource,
|
|
2962
3139
|
definitionName: definition.name,
|
|
2963
3140
|
pinnedVersion: definition.version,
|
|
@@ -2969,7 +3146,7 @@ const workflow = {
|
|
|
2969
3146
|
initialStage: definition.initialStage,
|
|
2970
3147
|
actor
|
|
2971
3148
|
});
|
|
2972
|
-
return await client.create(base), await primeInitialStage(client, id, actor, clientForGdr, clock), await cascade(client, id, actor, clientForGdr, clock), reload(client, id,
|
|
3149
|
+
return await client.create(base), await primeInitialStage(client, id, actor, clientForGdr, clock), await cascade(client, id, actor, clientForGdr, clock), reload(client, id, tag);
|
|
2973
3150
|
},
|
|
2974
3151
|
/**
|
|
2975
3152
|
* Fire an action against a task. If the task is pending, it is
|
|
@@ -2984,19 +3161,19 @@ const workflow = {
|
|
|
2984
3161
|
fireAction: async (args) => {
|
|
2985
3162
|
const {
|
|
2986
3163
|
client,
|
|
2987
|
-
|
|
3164
|
+
tag,
|
|
2988
3165
|
instanceId,
|
|
2989
3166
|
task,
|
|
2990
3167
|
action,
|
|
2991
3168
|
params,
|
|
2992
3169
|
idempotent,
|
|
2993
3170
|
resourceClients
|
|
2994
|
-
} = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId,
|
|
3171
|
+
} = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
|
|
2995
3172
|
if (findOpenStageEntry(before)?.tasks.find((t) => t.name === task) === void 0 && idempotent === !0)
|
|
2996
3173
|
return { instance: before, cascaded: 0, fired: !1 };
|
|
2997
3174
|
const evaluation = await evaluateInstance({
|
|
2998
3175
|
client,
|
|
2999
|
-
|
|
3176
|
+
tag,
|
|
3000
3177
|
instanceId,
|
|
3001
3178
|
access,
|
|
3002
3179
|
clock,
|
|
@@ -3011,10 +3188,11 @@ const workflow = {
|
|
|
3011
3188
|
actor,
|
|
3012
3189
|
...access.grants !== void 0 ? { grants: access.grants } : {},
|
|
3013
3190
|
clock,
|
|
3191
|
+
clientForGdr,
|
|
3014
3192
|
...params !== void 0 ? { params } : {}
|
|
3015
3193
|
}), cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
|
|
3016
3194
|
return {
|
|
3017
|
-
instance: await reload(client, instanceId,
|
|
3195
|
+
instance: await reload(client, instanceId, tag),
|
|
3018
3196
|
cascaded,
|
|
3019
3197
|
fired: !0,
|
|
3020
3198
|
...ranOps !== void 0 ? { ranOps } : {}
|
|
@@ -3028,7 +3206,7 @@ const workflow = {
|
|
|
3028
3206
|
* reference them. Cascades after.
|
|
3029
3207
|
*/
|
|
3030
3208
|
completeEffect: async (args) => {
|
|
3031
|
-
const { client,
|
|
3209
|
+
const { client, tag, instanceId, effectKey, status, outputs, detail, error, durationMs } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
|
|
3032
3210
|
await completeEffect({
|
|
3033
3211
|
client,
|
|
3034
3212
|
instanceId,
|
|
@@ -3038,11 +3216,11 @@ const workflow = {
|
|
|
3038
3216
|
...detail !== void 0 ? { detail } : {},
|
|
3039
3217
|
...error !== void 0 ? { error } : {},
|
|
3040
3218
|
...durationMs !== void 0 ? { durationMs } : {},
|
|
3041
|
-
options: { ...actor !== void 0 ? { actor } : {}, clock }
|
|
3219
|
+
options: { ...actor !== void 0 ? { actor } : {}, clock, clientForGdr }
|
|
3042
3220
|
});
|
|
3043
3221
|
const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
|
|
3044
3222
|
return {
|
|
3045
|
-
instance: await reload(client, instanceId,
|
|
3223
|
+
instance: await reload(client, instanceId, tag),
|
|
3046
3224
|
cascaded,
|
|
3047
3225
|
fired: !0
|
|
3048
3226
|
};
|
|
@@ -3057,11 +3235,11 @@ const workflow = {
|
|
|
3057
3235
|
* affected instances and the engine re-evaluates.
|
|
3058
3236
|
*/
|
|
3059
3237
|
tick: async (args) => {
|
|
3060
|
-
const { client,
|
|
3238
|
+
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);
|
|
3061
3239
|
await assertInstanceWriteAllowed({ instance: current, guards, identity: actor.id });
|
|
3062
3240
|
const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
|
|
3063
3241
|
return {
|
|
3064
|
-
instance: await reload(client, instanceId,
|
|
3242
|
+
instance: await reload(client, instanceId, tag),
|
|
3065
3243
|
cascaded,
|
|
3066
3244
|
fired: cascaded > 0
|
|
3067
3245
|
};
|
|
@@ -3072,56 +3250,50 @@ const workflow = {
|
|
|
3072
3250
|
* upstream; this verb performs the mechanical move.
|
|
3073
3251
|
*/
|
|
3074
3252
|
setStage: async (args) => {
|
|
3075
|
-
const { client,
|
|
3076
|
-
await reload(client, instanceId,
|
|
3253
|
+
const { client, tag, instanceId, targetStage, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
|
|
3254
|
+
await reload(client, instanceId, tag);
|
|
3077
3255
|
const result = await setStage({
|
|
3078
3256
|
client,
|
|
3079
3257
|
instanceId,
|
|
3080
3258
|
targetStage,
|
|
3081
3259
|
...reason !== void 0 ? { reason } : {},
|
|
3082
|
-
options: { ...actor !== void 0 ? { actor } : {}, clock }
|
|
3260
|
+
options: { ...actor !== void 0 ? { actor } : {}, clock, clientForGdr }
|
|
3083
3261
|
}), cascaded = result.fired ? await cascade(client, instanceId, actor, clientForGdr, clock) : 0;
|
|
3084
3262
|
return {
|
|
3085
|
-
instance: await reload(client, instanceId,
|
|
3263
|
+
instance: await reload(client, instanceId, tag),
|
|
3086
3264
|
cascaded,
|
|
3087
3265
|
fired: result.fired
|
|
3088
3266
|
};
|
|
3089
3267
|
},
|
|
3090
3268
|
/**
|
|
3091
3269
|
* Admin override — hard-stop an in-flight instance where it stands.
|
|
3092
|
-
* No stage move, no transition effects, pending effects cancelled;
|
|
3093
|
-
*
|
|
3094
|
-
*
|
|
3095
|
-
* so a parent gate waiting on this child re-evaluates.
|
|
3270
|
+
* No stage move, no transition effects, pending effects cancelled;
|
|
3271
|
+
* see {@link abortAndPropagate} for the abort + ancestor-propagation
|
|
3272
|
+
* contract (propagated, not cascaded — the instance is terminal).
|
|
3096
3273
|
*/
|
|
3097
3274
|
abortInstance: async (args) => {
|
|
3098
|
-
const { client,
|
|
3099
|
-
await reload(client, instanceId,
|
|
3100
|
-
const
|
|
3101
|
-
|
|
3102
|
-
instanceId,
|
|
3103
|
-
...reason !== void 0 ? { reason } : {},
|
|
3104
|
-
options: { ...actor !== void 0 ? { actor } : {}, clock }
|
|
3105
|
-
});
|
|
3106
|
-
return result.fired && await propagateToAncestors(client, instanceId, actor, clientForGdr, clock), {
|
|
3107
|
-
instance: await reload(client, instanceId, tags),
|
|
3275
|
+
const { client, tag, instanceId, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
|
|
3276
|
+
await reload(client, instanceId, tag);
|
|
3277
|
+
const fired = await abortAndPropagate({ client, instanceId, reason, actor, clientForGdr, clock });
|
|
3278
|
+
return {
|
|
3279
|
+
instance: await reload(client, instanceId, tag),
|
|
3108
3280
|
cascaded: 0,
|
|
3109
|
-
fired
|
|
3281
|
+
fired
|
|
3110
3282
|
};
|
|
3111
3283
|
},
|
|
3112
3284
|
/**
|
|
3113
|
-
* Fetch a workflow instance by id, scoped to the engine's
|
|
3285
|
+
* Fetch a workflow instance by id, scoped to the engine's tag.
|
|
3114
3286
|
* Throws when the instance doesn't exist or isn't visible to this
|
|
3115
3287
|
* engine.
|
|
3116
3288
|
*/
|
|
3117
3289
|
getInstance: async (args) => {
|
|
3118
|
-
const { client,
|
|
3119
|
-
return
|
|
3290
|
+
const { client, tag, instanceId } = args;
|
|
3291
|
+
return validateTag(tag), reload(client, instanceId, tag);
|
|
3120
3292
|
},
|
|
3121
3293
|
guardsForInstance: async (args) => {
|
|
3122
|
-
const { client,
|
|
3123
|
-
|
|
3124
|
-
const instance = await reload(client, instanceId,
|
|
3294
|
+
const { client, tag, instanceId, resourceClients } = args;
|
|
3295
|
+
validateTag(tag);
|
|
3296
|
+
const instance = await reload(client, instanceId, tag);
|
|
3125
3297
|
return guardsForInstance({
|
|
3126
3298
|
client,
|
|
3127
3299
|
clientForGdr: buildClientForGdr(client, resourceClients),
|
|
@@ -3129,9 +3301,9 @@ const workflow = {
|
|
|
3129
3301
|
});
|
|
3130
3302
|
},
|
|
3131
3303
|
guardsForDefinition: async (args) => {
|
|
3132
|
-
const { client,
|
|
3133
|
-
|
|
3134
|
-
const definitions = await loadDefinitionVersions(client, definition,
|
|
3304
|
+
const { client, tag, workflowResource, definition, resourceClients } = args;
|
|
3305
|
+
validateTag(tag);
|
|
3306
|
+
const definitions = await loadDefinitionVersions(client, definition, tag);
|
|
3135
3307
|
if (definitions.length === 0)
|
|
3136
3308
|
throw new Error(`No deployed definition for workflow ${definition}`);
|
|
3137
3309
|
return guardsForDefinition({
|
|
@@ -3143,22 +3315,21 @@ const workflow = {
|
|
|
3143
3315
|
});
|
|
3144
3316
|
},
|
|
3145
3317
|
/**
|
|
3146
|
-
* Run a caller-supplied GROQ query with the engine's
|
|
3147
|
-
* `$
|
|
3318
|
+
* Run a caller-supplied GROQ query with the engine's tag bound as
|
|
3319
|
+
* `$tag`. This does NOT rewrite the query — arbitrary GROQ
|
|
3148
3320
|
* can't be safely tag-scoped after the fact — so the CALLER MUST
|
|
3149
|
-
* filter on `$
|
|
3150
|
-
*
|
|
3151
|
-
* cross-tenant reads, a query that never references `$engineTags` is
|
|
3321
|
+
* filter on `$tag` (e.g. `tag == $tag`). To guard against accidental
|
|
3322
|
+
* cross-partition reads, a query that never references `$tag` is
|
|
3152
3323
|
* rejected before it reaches the lake. Caller is responsible for type
|
|
3153
3324
|
* narrowing the result.
|
|
3154
3325
|
*/
|
|
3155
3326
|
query: async (args) => {
|
|
3156
|
-
const { client,
|
|
3157
|
-
if (
|
|
3327
|
+
const { client, tag, groq, params } = args;
|
|
3328
|
+
if (validateTag(tag), !/\$tag\b/.test(groq))
|
|
3158
3329
|
throw new Error(
|
|
3159
|
-
`workflow.query: query must filter on $
|
|
3330
|
+
`workflow.query: query must filter on $tag to stay tag-scoped (e.g. "tag == $tag"); got: ${groq}`
|
|
3160
3331
|
);
|
|
3161
|
-
return client.fetch(groq, { ...params,
|
|
3332
|
+
return client.fetch(groq, { ...params, tag });
|
|
3162
3333
|
},
|
|
3163
3334
|
/**
|
|
3164
3335
|
* Snapshot-aware GROQ — runs against the same in-memory view that
|
|
@@ -3177,9 +3348,9 @@ const workflow = {
|
|
|
3177
3348
|
* without re-implementing hydration. Pure read — never writes.
|
|
3178
3349
|
*/
|
|
3179
3350
|
queryInScope: async (args) => {
|
|
3180
|
-
const { client,
|
|
3181
|
-
|
|
3182
|
-
const instance = await reload(client, instanceId,
|
|
3351
|
+
const { client, tag, instanceId, groq, params, resourceClients } = args, clock = args.clock ?? wallClock;
|
|
3352
|
+
validateTag(tag);
|
|
3353
|
+
const instance = await reload(client, instanceId, tag), clientForGdr = buildClientForGdr(client, resourceClients), snapshot = await hydrateSnapshot({ client, clientForGdr, instance }), reserved = buildParams({ instance, now: clock(), snapshot }), tree = groqJs.parse(groq, { params: { ...reserved, ...params } });
|
|
3183
3354
|
return await (await groqJs.evaluate(tree, {
|
|
3184
3355
|
dataset: snapshot.docs,
|
|
3185
3356
|
params: { ...reserved, ...params }
|
|
@@ -3189,13 +3360,13 @@ const workflow = {
|
|
|
3189
3360
|
* List every pending effect on the instance. Returns the same entries
|
|
3190
3361
|
* the runtime would see — claimed and unclaimed alike.
|
|
3191
3362
|
*/
|
|
3192
|
-
listPendingEffects: async (args) => (await reload(args.client, args.instanceId, args.
|
|
3363
|
+
listPendingEffects: async (args) => (await reload(args.client, args.instanceId, args.tag)).pendingEffects,
|
|
3193
3364
|
/**
|
|
3194
3365
|
* Filter pending effects on the instance by criteria. `claimed`
|
|
3195
3366
|
* filters on claim presence; `names` restricts to specific effect
|
|
3196
3367
|
* names. Both filters compose (AND).
|
|
3197
3368
|
*/
|
|
3198
|
-
findPendingEffects: async (args) => (await reload(args.client, args.instanceId, args.
|
|
3369
|
+
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))),
|
|
3199
3370
|
/**
|
|
3200
3371
|
* Project the instance from a given actor's perspective. Returns a
|
|
3201
3372
|
* `WorkflowEvaluation` with per-action verdicts (`allowed` + a
|
|
@@ -3205,6 +3376,30 @@ const workflow = {
|
|
|
3205
3376
|
* `fireAction` to gate writes via the same logic.
|
|
3206
3377
|
*/
|
|
3207
3378
|
evaluate: async (args) => evaluateInstance(args),
|
|
3379
|
+
/**
|
|
3380
|
+
* Diagnose why an instance is or isn't progressing. Projects the
|
|
3381
|
+
* instance (the same read as `evaluate`) and classifies it — terminal,
|
|
3382
|
+
* `progressing`, `waiting` (an action is available — healthy), or `stuck`
|
|
3383
|
+
* with a structured cause — returning that verdict as a
|
|
3384
|
+
* {@link DiagnoseResult} alongside the evaluation it came from, so a
|
|
3385
|
+
* consumer can render the supporting evidence without a second projection.
|
|
3386
|
+
* Pure read.
|
|
3387
|
+
*/
|
|
3388
|
+
diagnose: async (args) => {
|
|
3389
|
+
const evaluation = await evaluateInstance(args);
|
|
3390
|
+
return { evaluation, diagnosis: diagnoseInstance(diagnoseInputFromEvaluation(evaluation)) };
|
|
3391
|
+
},
|
|
3392
|
+
/**
|
|
3393
|
+
* List the actions an actor could fire on an instance's current stage,
|
|
3394
|
+
* each flagged `allowed` (with a structured `disabledReason` when not).
|
|
3395
|
+
* Projects the instance from the actor's perspective and flattens its
|
|
3396
|
+
* tasks' actions. Returns the evaluation alongside the actions so a
|
|
3397
|
+
* consumer can read the instance/stage context. Pure read.
|
|
3398
|
+
*/
|
|
3399
|
+
availableActions: async (args) => {
|
|
3400
|
+
const evaluation = await evaluateInstance(args);
|
|
3401
|
+
return { evaluation, actions: availableActions(evaluation.currentStage.tasks) };
|
|
3402
|
+
},
|
|
3208
3403
|
/**
|
|
3209
3404
|
* Materialised spawned children of a parent instance.
|
|
3210
3405
|
*
|
|
@@ -3212,18 +3407,18 @@ const workflow = {
|
|
|
3212
3407
|
* record. (The per-task `spawnedInstances` field on the active stage
|
|
3213
3408
|
* only exists while that stage is current; history survives.) Strips
|
|
3214
3409
|
* the GDR URI on each `instanceRef` to a bare `_id`, fetches the
|
|
3215
|
-
* instances, drops any that aren't visible to this engine's
|
|
3410
|
+
* instances, drops any that aren't visible to this engine's tag, and
|
|
3216
3411
|
* returns them sorted by `startedAt` ascending.
|
|
3217
3412
|
*
|
|
3218
3413
|
* Pass `task` to restrict to a single spawning task on the parent.
|
|
3219
3414
|
*/
|
|
3220
3415
|
children: async (args) => {
|
|
3221
|
-
const { client,
|
|
3222
|
-
|
|
3223
|
-
const parent = await reload(client, instanceId,
|
|
3416
|
+
const { client, tag, instanceId, task } = args;
|
|
3417
|
+
validateTag(tag);
|
|
3418
|
+
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));
|
|
3224
3419
|
return ids.length === 0 ? [] : client.fetch(
|
|
3225
3420
|
`*[_id in $ids && ${tagScopeFilter()}] | order(startedAt asc)`,
|
|
3226
|
-
{ ids,
|
|
3421
|
+
{ ids, tag }
|
|
3227
3422
|
);
|
|
3228
3423
|
},
|
|
3229
3424
|
/**
|
|
@@ -3238,11 +3433,11 @@ const workflow = {
|
|
|
3238
3433
|
}
|
|
3239
3434
|
};
|
|
3240
3435
|
async function verifyDeployedDefinitionsInternal(args) {
|
|
3241
|
-
const { client,
|
|
3242
|
-
|
|
3436
|
+
const { client, tag, effectHandlers, missingHandler, logger } = args;
|
|
3437
|
+
validateTag(tag);
|
|
3243
3438
|
const log = logger("verifyDeployedDefinitions"), definitions = await client.fetch(
|
|
3244
3439
|
`*[_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && ${tagScopeFilter()}] | order(name asc, version asc)`,
|
|
3245
|
-
{
|
|
3440
|
+
{ tag }
|
|
3246
3441
|
), seen = [], missingByName = /* @__PURE__ */ new Map();
|
|
3247
3442
|
for (const def of definitions)
|
|
3248
3443
|
seen.push({ name: def.name, version: def.version, _id: def._id }), walkEffectNames(def, (name, location) => {
|
|
@@ -3291,7 +3486,7 @@ function walkEffectNames(def, visit) {
|
|
|
3291
3486
|
async function drainEffectsInternal(args) {
|
|
3292
3487
|
const {
|
|
3293
3488
|
client,
|
|
3294
|
-
|
|
3489
|
+
tag,
|
|
3295
3490
|
workflowResource,
|
|
3296
3491
|
resourceClients,
|
|
3297
3492
|
instanceId,
|
|
@@ -3302,10 +3497,10 @@ async function drainEffectsInternal(args) {
|
|
|
3302
3497
|
actor: drainerActor,
|
|
3303
3498
|
...args.access?.grants !== void 0 ? { grants: args.access.grants } : {}
|
|
3304
3499
|
};
|
|
3305
|
-
|
|
3500
|
+
validateTag(tag);
|
|
3306
3501
|
const log = logger("drainEffects"), drained = [], failed = [], skipped = [], skippedKeys = /* @__PURE__ */ new Set();
|
|
3307
3502
|
for (; ; ) {
|
|
3308
|
-
const before = await reload(client, instanceId,
|
|
3503
|
+
const before = await reload(client, instanceId, tag), candidate = before.pendingEffects.find(
|
|
3309
3504
|
(e) => e.claim === void 0 && !skippedKeys.has(e._key)
|
|
3310
3505
|
);
|
|
3311
3506
|
if (candidate === void 0) break;
|
|
@@ -3322,7 +3517,7 @@ async function drainEffectsInternal(args) {
|
|
|
3322
3517
|
});
|
|
3323
3518
|
await reportEffectOutcome({
|
|
3324
3519
|
client,
|
|
3325
|
-
|
|
3520
|
+
tag,
|
|
3326
3521
|
workflowResource,
|
|
3327
3522
|
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3328
3523
|
instanceId,
|
|
@@ -3392,7 +3587,7 @@ async function applyMissingHandler(policy, info, log) {
|
|
|
3392
3587
|
}
|
|
3393
3588
|
}
|
|
3394
3589
|
function createInstanceSession(args) {
|
|
3395
|
-
const { client,
|
|
3590
|
+
const { client, tag, clock } = args, clientForGdr = buildClientForGdr(client, args.resourceClients);
|
|
3396
3591
|
let instance = asHeldInstance(args.instance), overlay = /* @__PURE__ */ new Map(), heldGuards = [], committing = !1, buffered;
|
|
3397
3592
|
const selfUri = () => gdrFromResource(instance.workflowResource, instance._id), applyUpdate = (docs) => {
|
|
3398
3593
|
const next = /* @__PURE__ */ new Map();
|
|
@@ -3453,7 +3648,7 @@ function createInstanceSession(args) {
|
|
|
3453
3648
|
return commit(async (actor, held) => {
|
|
3454
3649
|
await assertInstanceWriteAllowed({ instance, guards: heldGuards, identity: actor.id });
|
|
3455
3650
|
const cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
|
|
3456
|
-
return instance = await reload(client, instance._id,
|
|
3651
|
+
return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: cascaded > 0 };
|
|
3457
3652
|
});
|
|
3458
3653
|
},
|
|
3459
3654
|
fireAction({ task, action, params }) {
|
|
@@ -3465,11 +3660,12 @@ function createInstanceSession(args) {
|
|
|
3465
3660
|
task,
|
|
3466
3661
|
action,
|
|
3467
3662
|
actor,
|
|
3663
|
+
clientForGdr,
|
|
3468
3664
|
...grants !== void 0 ? { grants } : {},
|
|
3469
3665
|
...clock !== void 0 ? { clock } : {},
|
|
3470
3666
|
...params !== void 0 ? { params } : {}
|
|
3471
3667
|
}), cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
|
|
3472
|
-
return instance = await reload(client, instance._id,
|
|
3668
|
+
return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: !0, ...ranOps !== void 0 ? { ranOps } : {} };
|
|
3473
3669
|
});
|
|
3474
3670
|
}
|
|
3475
3671
|
};
|
|
@@ -3497,11 +3693,11 @@ const defaultLoggerFactory = (name) => ({
|
|
|
3497
3693
|
}
|
|
3498
3694
|
};
|
|
3499
3695
|
function createEngine(args) {
|
|
3500
|
-
const { client, workflowResource,
|
|
3501
|
-
|
|
3696
|
+
const { client, workflowResource, resourceClients, clock, tag } = args;
|
|
3697
|
+
validateTag(tag);
|
|
3502
3698
|
const effectHandlers = args.effectHandlers ?? {}, missingHandler = args.missingHandler ?? "fail", logger = args.loggerFactory ?? defaultLoggerFactory, bind = (rest) => ({
|
|
3503
3699
|
client,
|
|
3504
|
-
|
|
3700
|
+
tag,
|
|
3505
3701
|
workflowResource,
|
|
3506
3702
|
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3507
3703
|
...clock !== void 0 ? { clock } : {},
|
|
@@ -3509,7 +3705,7 @@ function createEngine(args) {
|
|
|
3509
3705
|
});
|
|
3510
3706
|
return {
|
|
3511
3707
|
client,
|
|
3512
|
-
|
|
3708
|
+
tag,
|
|
3513
3709
|
workflowResource,
|
|
3514
3710
|
effectHandlers,
|
|
3515
3711
|
missingHandler,
|
|
@@ -3520,13 +3716,16 @@ function createEngine(args) {
|
|
|
3520
3716
|
completeEffect: (rest) => workflow.completeEffect(bind(rest)),
|
|
3521
3717
|
tick: (rest) => workflow.tick(bind(rest)),
|
|
3522
3718
|
evaluateInstance: (rest) => evaluateInstance(bind(rest)),
|
|
3719
|
+
diagnose: (rest) => workflow.diagnose(bind(rest)),
|
|
3720
|
+
availableActions: (rest) => workflow.availableActions(bind(rest)),
|
|
3523
3721
|
setStage: (rest) => workflow.setStage(bind(rest)),
|
|
3524
3722
|
abortInstance: (rest) => workflow.abortInstance(bind(rest)),
|
|
3525
|
-
|
|
3526
|
-
|
|
3723
|
+
deleteDefinition: (rest) => workflow.deleteDefinition(bind(rest)),
|
|
3724
|
+
getInstance: ({ instanceId }) => workflow.getInstance({ client, tag, workflowResource, instanceId }),
|
|
3725
|
+
subscriptionDocumentsForInstance: ({ instanceId }) => workflow.getInstance({ client, tag, workflowResource, instanceId }).then(subscriptionDocumentsForInstance),
|
|
3527
3726
|
instance: (instanceDoc, opts) => createInstanceSession({
|
|
3528
3727
|
client,
|
|
3529
|
-
|
|
3728
|
+
tag,
|
|
3530
3729
|
instance: instanceDoc,
|
|
3531
3730
|
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3532
3731
|
...clock !== void 0 ? { clock } : {},
|
|
@@ -3535,45 +3734,45 @@ function createEngine(args) {
|
|
|
3535
3734
|
}),
|
|
3536
3735
|
guardsForInstance: ({ instanceId }) => workflow.guardsForInstance({
|
|
3537
3736
|
client,
|
|
3538
|
-
|
|
3737
|
+
tag,
|
|
3539
3738
|
workflowResource,
|
|
3540
3739
|
instanceId,
|
|
3541
3740
|
...resourceClients !== void 0 ? { resourceClients } : {}
|
|
3542
3741
|
}),
|
|
3543
3742
|
guardsForDefinition: ({ definition }) => workflow.guardsForDefinition({
|
|
3544
3743
|
client,
|
|
3545
|
-
|
|
3744
|
+
tag,
|
|
3546
3745
|
workflowResource,
|
|
3547
3746
|
definition,
|
|
3548
3747
|
...resourceClients !== void 0 ? { resourceClients } : {}
|
|
3549
3748
|
}),
|
|
3550
3749
|
children: ({ instanceId, task }) => workflow.children({
|
|
3551
3750
|
client,
|
|
3552
|
-
|
|
3751
|
+
tag,
|
|
3553
3752
|
workflowResource,
|
|
3554
3753
|
instanceId,
|
|
3555
3754
|
...task !== void 0 ? { task } : {}
|
|
3556
3755
|
}),
|
|
3557
3756
|
query: ({ groq, params }) => workflow.query({
|
|
3558
3757
|
client,
|
|
3559
|
-
|
|
3758
|
+
tag,
|
|
3560
3759
|
workflowResource,
|
|
3561
3760
|
groq,
|
|
3562
3761
|
...params !== void 0 ? { params } : {}
|
|
3563
3762
|
}),
|
|
3564
3763
|
queryInScope: ({ instanceId, groq, params }) => workflow.queryInScope({
|
|
3565
3764
|
client,
|
|
3566
|
-
|
|
3765
|
+
tag,
|
|
3567
3766
|
workflowResource,
|
|
3568
3767
|
instanceId,
|
|
3569
3768
|
groq,
|
|
3570
3769
|
...params !== void 0 ? { params } : {},
|
|
3571
3770
|
...clock !== void 0 ? { clock } : {}
|
|
3572
3771
|
}),
|
|
3573
|
-
listPendingEffects: ({ instanceId }) => workflow.listPendingEffects({ client,
|
|
3772
|
+
listPendingEffects: ({ instanceId }) => workflow.listPendingEffects({ client, tag, workflowResource, instanceId }),
|
|
3574
3773
|
findPendingEffects: ({ instanceId, claimed, names }) => workflow.findPendingEffects({
|
|
3575
3774
|
client,
|
|
3576
|
-
|
|
3775
|
+
tag,
|
|
3577
3776
|
workflowResource,
|
|
3578
3777
|
instanceId,
|
|
3579
3778
|
...claimed !== void 0 ? { claimed } : {},
|
|
@@ -3581,7 +3780,7 @@ function createEngine(args) {
|
|
|
3581
3780
|
}),
|
|
3582
3781
|
drainEffects: ({ instanceId, access }) => drainEffectsInternal({
|
|
3583
3782
|
client,
|
|
3584
|
-
|
|
3783
|
+
tag,
|
|
3585
3784
|
workflowResource,
|
|
3586
3785
|
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3587
3786
|
instanceId,
|
|
@@ -3592,13 +3791,43 @@ function createEngine(args) {
|
|
|
3592
3791
|
}),
|
|
3593
3792
|
verifyDeployedDefinitions: () => verifyDeployedDefinitionsInternal({
|
|
3594
3793
|
client,
|
|
3595
|
-
|
|
3794
|
+
tag,
|
|
3596
3795
|
effectHandlers,
|
|
3597
3796
|
missingHandler,
|
|
3598
3797
|
logger
|
|
3599
3798
|
})
|
|
3600
3799
|
};
|
|
3601
3800
|
}
|
|
3801
|
+
function docIdFor(def, target) {
|
|
3802
|
+
return definitionDocId(target.workflowResource, target.tag, def.name, def.version);
|
|
3803
|
+
}
|
|
3804
|
+
function diffStatus(existing, expected) {
|
|
3805
|
+
return existing === void 0 ? "create" : isDefinitionUnchanged(existing, expected) ? "unchanged" : "update";
|
|
3806
|
+
}
|
|
3807
|
+
function diffEntry(def, existingRaw, target) {
|
|
3808
|
+
const docId = docIdFor(def, target), expected = {
|
|
3809
|
+
...def,
|
|
3810
|
+
_id: docId,
|
|
3811
|
+
_type: schema.WORKFLOW_DEFINITION_TYPE,
|
|
3812
|
+
tag: target.tag
|
|
3813
|
+
}, status = diffStatus(existingRaw, expected), existing = existingRaw ? stripSystemFields(existingRaw) : void 0;
|
|
3814
|
+
return {
|
|
3815
|
+
name: def.name,
|
|
3816
|
+
version: def.version,
|
|
3817
|
+
status,
|
|
3818
|
+
docId,
|
|
3819
|
+
expected,
|
|
3820
|
+
...existing !== void 0 ? { existing } : {}
|
|
3821
|
+
};
|
|
3822
|
+
}
|
|
3823
|
+
async function computeDiffEntries(client, defs, target) {
|
|
3824
|
+
const entries = [];
|
|
3825
|
+
for (const def of defs) {
|
|
3826
|
+
const existingRaw = await client.getDocument(docIdFor(def, target)) ?? void 0;
|
|
3827
|
+
entries.push(diffEntry(def, existingRaw, target));
|
|
3828
|
+
}
|
|
3829
|
+
return entries;
|
|
3830
|
+
}
|
|
3602
3831
|
const HISTORY_DISPLAY = {
|
|
3603
3832
|
stageEntered: {
|
|
3604
3833
|
title: "Stage entered",
|
|
@@ -3791,15 +4020,22 @@ exports.OP_DISPLAY = OP_DISPLAY;
|
|
|
3791
4020
|
exports.STATE_SLOT_DISPLAY = STATE_SLOT_DISPLAY;
|
|
3792
4021
|
exports.WORKFLOW_INSTANCE_TYPE = WORKFLOW_INSTANCE_TYPE;
|
|
3793
4022
|
exports.WorkflowStateDivergedError = WorkflowStateDivergedError;
|
|
4023
|
+
exports.abortReason = abortReason;
|
|
4024
|
+
exports.actionVerdict = actionVerdict;
|
|
4025
|
+
exports.assigneesOf = assigneesOf;
|
|
4026
|
+
exports.availableActions = availableActions;
|
|
3794
4027
|
exports.buildSnapshot = buildSnapshot;
|
|
3795
|
-
exports.canonicalTag = canonicalTag;
|
|
3796
4028
|
exports.compileGuard = compileGuard;
|
|
4029
|
+
exports.computeDiffEntries = computeDiffEntries;
|
|
3797
4030
|
exports.contentReleaseName = contentReleaseName;
|
|
3798
4031
|
exports.createEngine = createEngine;
|
|
3799
4032
|
exports.datasetResourceParts = datasetResourceParts;
|
|
3800
4033
|
exports.defaultLoggerFactory = defaultLoggerFactory;
|
|
3801
4034
|
exports.denyingGuards = denyingGuards;
|
|
3802
4035
|
exports.deployStageGuards = deployStageGuards;
|
|
4036
|
+
exports.diagnoseInputFromEvaluation = diagnoseInputFromEvaluation;
|
|
4037
|
+
exports.diagnoseInstance = diagnoseInstance;
|
|
4038
|
+
exports.diffEntry = diffEntry;
|
|
3803
4039
|
exports.displayDescription = displayDescription;
|
|
3804
4040
|
exports.displayTitle = displayTitle;
|
|
3805
4041
|
exports.effectsContextMap = effectsContextMap;
|
|
@@ -3818,6 +4054,7 @@ exports.isDefinitionUnchanged = isDefinitionUnchanged;
|
|
|
3818
4054
|
exports.isGdr = isGdr;
|
|
3819
4055
|
exports.isTerminalStage = isTerminalStage;
|
|
3820
4056
|
exports.lakeGuardId = lakeGuardId;
|
|
4057
|
+
exports.openStage = openStage;
|
|
3821
4058
|
exports.parseGdr = parseGdr;
|
|
3822
4059
|
exports.readsRaw = readsRaw;
|
|
3823
4060
|
exports.refCanvas = refCanvas;
|
|
@@ -3833,7 +4070,7 @@ exports.subscriptionDocument = subscriptionDocument;
|
|
|
3833
4070
|
exports.subscriptionDocumentsForInstance = subscriptionDocumentsForInstance;
|
|
3834
4071
|
exports.tagScopeFilter = tagScopeFilter;
|
|
3835
4072
|
exports.validateDefinition = validateDefinition;
|
|
3836
|
-
exports.
|
|
4073
|
+
exports.validateTag = validateTag;
|
|
3837
4074
|
exports.verdictGuardsForInstance = verdictGuardsForInstance;
|
|
3838
4075
|
exports.wallClock = wallClock;
|
|
3839
4076
|
exports.workflow = workflow;
|