@sanity/workflow-engine 0.10.0 → 0.12.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 +189 -74
- package/dist/_chunks-cjs/schema.cjs.map +1 -1
- package/dist/_chunks-es/schema.js +190 -75
- package/dist/_chunks-es/schema.js.map +1 -1
- package/dist/define.cjs +220 -121
- package/dist/define.cjs.map +1 -1
- package/dist/define.d.cts +6473 -3502
- package/dist/define.d.ts +6473 -3502
- package/dist/define.js +221 -122
- package/dist/define.js.map +1 -1
- package/dist/index.cjs +1131 -921
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14866 -10827
- package/dist/index.d.ts +14866 -10827
- package/dist/index.js +1133 -923
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -123,7 +123,7 @@ function isGdr(value) {
|
|
|
123
123
|
return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
|
|
124
124
|
}
|
|
125
125
|
function buildParams(args) {
|
|
126
|
-
const { instance, now, snapshot, extra } = args,
|
|
126
|
+
const { instance, now, snapshot, extra } = args, currentActivities2 = findOpenStageEntry(instance)?.activities ?? [];
|
|
127
127
|
return {
|
|
128
128
|
self: selfGdr(instance),
|
|
129
129
|
fields: renderedFields(instance.fields ?? [], snapshot),
|
|
@@ -134,10 +134,12 @@ function buildParams(args) {
|
|
|
134
134
|
/** `$effects` — parent context handoff by entry name, completed-effect
|
|
135
135
|
* outputs namespaced under the effect's name (`$effects['x.y'].out`). */
|
|
136
136
|
effects: effectsContextMap(instance),
|
|
137
|
-
/** `$
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
/** `$activities` — the current stage's activity rows, statuses included. */
|
|
138
|
+
activities: currentActivities2,
|
|
139
|
+
allActivitiesDone: currentActivities2.every(
|
|
140
|
+
(activity) => activity.status === "done" || activity.status === "skipped"
|
|
141
|
+
),
|
|
142
|
+
anyActivityFailed: currentActivities2.some((activity) => activity.status === "failed"),
|
|
141
143
|
...extra
|
|
142
144
|
};
|
|
143
145
|
}
|
|
@@ -149,15 +151,15 @@ function renderedFields(entries, snapshot) {
|
|
|
149
151
|
function renderedValue(entry, snapshot) {
|
|
150
152
|
return entry._type !== "doc.ref" || entry.value === null || snapshot === void 0 ? entry.value : snapshot.docs.find((d) => d._id === entry.value.id) ?? entry.value;
|
|
151
153
|
}
|
|
152
|
-
function scopedFieldOverlay(instance, snapshot,
|
|
154
|
+
function scopedFieldOverlay(instance, snapshot, activityName) {
|
|
153
155
|
const stageEntry = findOpenStageEntry(instance);
|
|
154
156
|
if (stageEntry === void 0) return {};
|
|
155
|
-
const stageFields = renderedFields(stageEntry.fields ?? [], snapshot),
|
|
156
|
-
return { ...stageFields, ...renderedFields(
|
|
157
|
+
const stageFields = renderedFields(stageEntry.fields ?? [], snapshot), activity = activityName ? stageEntry.activities.find((t) => t.name === activityName) : void 0;
|
|
158
|
+
return { ...stageFields, ...renderedFields(activity?.fields ?? [], snapshot) };
|
|
157
159
|
}
|
|
158
|
-
function assignedFor(instance,
|
|
160
|
+
function assignedFor(instance, activityName, actor, roleAliases) {
|
|
159
161
|
if (actor === void 0) return !1;
|
|
160
|
-
const entry = findOpenStageEntry(instance)?.
|
|
162
|
+
const entry = findOpenStageEntry(instance)?.activities.find((t) => t.name === activityName)?.fields?.find((s) => s._type === "assignees");
|
|
161
163
|
return entry === void 0 ? !1 : entry.value.some(
|
|
162
164
|
(a) => a.type === "user" ? a.id === actor.id : schema.actorFulfillsRole(actor.roles, a.role, roleAliases)
|
|
163
165
|
);
|
|
@@ -289,7 +291,7 @@ function validateDefinition(definition) {
|
|
|
289
291
|
validateStage(v2, stage);
|
|
290
292
|
if (v2.errors.length > 0)
|
|
291
293
|
throw new Error(
|
|
292
|
-
`defineWorkflow("${definition.name}"
|
|
294
|
+
`defineWorkflow("${definition.name}"): ${v2.errors.length} validation error${v2.errors.length === 1 ? "" : "s"}:
|
|
293
295
|
` + v2.errors.join(`
|
|
294
296
|
`)
|
|
295
297
|
);
|
|
@@ -310,7 +312,7 @@ function createDefinitionValidator() {
|
|
|
310
312
|
return { errors, tryParse, checkCondition: (groq, where) => {
|
|
311
313
|
tryParse(groq, where), rejectTypeScan(groq, where);
|
|
312
314
|
}, checkEntry: (entry, label) => {
|
|
313
|
-
entry.
|
|
315
|
+
entry.initialValue?.type === "query" && tryParse(entry.initialValue.query, `${label}.query`);
|
|
314
316
|
}, checkGuardRead: (expr, where) => {
|
|
315
317
|
isGuardReadExpr(expr) || errors.push(
|
|
316
318
|
` \xB7 ${where}: guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$fields.<name>[.path]", or "$effects['<name>'][.path]" (guards store resolved values; the lake cannot see $fields or $effects)`
|
|
@@ -327,8 +329,8 @@ function validateStage(v2, stage) {
|
|
|
327
329
|
for (const [key, groq] of effectBindings(t.effects))
|
|
328
330
|
v2.tryParse(groq, `transition "${t.name}" effect binding "${key}"`);
|
|
329
331
|
}
|
|
330
|
-
for (const
|
|
331
|
-
|
|
332
|
+
for (const activity of stage.activities ?? [])
|
|
333
|
+
validateActivity(v2, stage.name, activity);
|
|
332
334
|
}
|
|
333
335
|
function validateGuardReads(v2, stageName, guard) {
|
|
334
336
|
const where = `stage "${stageName}" guard "${guard.name}"`;
|
|
@@ -337,22 +339,22 @@ function validateGuardReads(v2, stageName, guard) {
|
|
|
337
339
|
for (const [key, expr] of Object.entries(guard.metadata ?? {}))
|
|
338
340
|
v2.checkGuardRead(expr, `${where} metadata "${key}"`);
|
|
339
341
|
}
|
|
340
|
-
function
|
|
341
|
-
const where = `stage "${stageName}"
|
|
342
|
-
for (const entry of
|
|
342
|
+
function validateActivity(v2, stageName, activity) {
|
|
343
|
+
const where = `stage "${stageName}" activity "${activity.name}"`;
|
|
344
|
+
for (const entry of activity.fields ?? [])
|
|
343
345
|
v2.checkEntry(entry, `${where}.fields "${entry.name}"`);
|
|
344
|
-
|
|
345
|
-
for (const [name, groq] of Object.entries(
|
|
346
|
+
activity.filter !== void 0 && v2.checkCondition(activity.filter, `${where}.filter`), activity.completeWhen !== void 0 && v2.checkCondition(activity.completeWhen, `${where}.completeWhen`), activity.failWhen !== void 0 && v2.checkCondition(activity.failWhen, `${where}.failWhen`);
|
|
347
|
+
for (const [name, groq] of Object.entries(activity.requirements ?? {}))
|
|
346
348
|
v2.checkCondition(groq, `${where}.requirements "${name}"`);
|
|
347
|
-
for (const [key, groq] of effectBindings(
|
|
349
|
+
for (const [key, groq] of effectBindings(activity.effects))
|
|
348
350
|
v2.tryParse(groq, `${where} effect binding "${key}"`);
|
|
349
|
-
|
|
351
|
+
validateActivityActions(v2, activity), activity.subworkflows !== void 0 && validateSubworkflows(v2, where, activity.subworkflows);
|
|
350
352
|
}
|
|
351
|
-
function
|
|
352
|
-
for (const a of
|
|
353
|
-
a.filter !== void 0 && v2.checkCondition(a.filter, `
|
|
353
|
+
function validateActivityActions(v2, activity) {
|
|
354
|
+
for (const a of activity.actions ?? []) {
|
|
355
|
+
a.filter !== void 0 && v2.checkCondition(a.filter, `activity "${activity.name}" action "${a.name}".filter`);
|
|
354
356
|
for (const [key, groq] of effectBindings(a.effects))
|
|
355
|
-
v2.tryParse(groq, `
|
|
357
|
+
v2.tryParse(groq, `activity "${activity.name}" action "${a.name}" effect binding "${key}"`);
|
|
356
358
|
}
|
|
357
359
|
}
|
|
358
360
|
function validateSubworkflows(v2, where, sub) {
|
|
@@ -367,10 +369,10 @@ function effectBindings(effects) {
|
|
|
367
369
|
(e) => Object.entries(e.bindings ?? {}).map(([k, groq]) => [`${e.name}.${k}`, groq])
|
|
368
370
|
);
|
|
369
371
|
}
|
|
370
|
-
function actionVerdict(
|
|
372
|
+
function actionVerdict(activity, action) {
|
|
371
373
|
return {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
+
activity: activity.activity.name,
|
|
375
|
+
activityStatus: activity.status,
|
|
374
376
|
action: action.action.name,
|
|
375
377
|
title: action.action.title,
|
|
376
378
|
allowed: action.allowed,
|
|
@@ -378,8 +380,8 @@ function actionVerdict(task, action) {
|
|
|
378
380
|
params: action.action.params ?? []
|
|
379
381
|
};
|
|
380
382
|
}
|
|
381
|
-
function availableActions(
|
|
382
|
-
return
|
|
383
|
+
function availableActions(activities) {
|
|
384
|
+
return activities.flatMap((t) => t.actions.map((a) => actionVerdict(t, a)));
|
|
383
385
|
}
|
|
384
386
|
const wallClock = () => (/* @__PURE__ */ new Date()).toISOString(), GUARD_DOC_TYPE = "temp.system.guard";
|
|
385
387
|
class MutationGuardDeniedError extends Error {
|
|
@@ -501,60 +503,67 @@ function diagnoseInputFromEvaluation(evaluation) {
|
|
|
501
503
|
const stage = openStage(evaluation.instance);
|
|
502
504
|
return {
|
|
503
505
|
instance: evaluation.instance,
|
|
504
|
-
|
|
506
|
+
activities: evaluation.currentStage.activities,
|
|
505
507
|
transitions: evaluation.currentStage.transitions,
|
|
506
508
|
assignees: Object.fromEntries(
|
|
507
|
-
evaluation.currentStage.
|
|
509
|
+
evaluation.currentStage.activities.map((t) => [
|
|
510
|
+
t.activity.name,
|
|
511
|
+
assigneesOf(stage, t.activity.name)
|
|
512
|
+
])
|
|
508
513
|
)
|
|
509
514
|
};
|
|
510
515
|
}
|
|
511
516
|
function openStage(instance) {
|
|
512
517
|
return findOpenStageEntry(instance);
|
|
513
518
|
}
|
|
514
|
-
function assigneesOf(stage,
|
|
515
|
-
return (stage?.
|
|
519
|
+
function assigneesOf(stage, activityName) {
|
|
520
|
+
return (stage?.activities.find((t) => t.name === activityName)?.fields ?? []).filter((s) => s._type === "assignees").flatMap((s) => s.value);
|
|
516
521
|
}
|
|
517
522
|
function isResolved(status) {
|
|
518
523
|
return status === "done" || status === "skipped";
|
|
519
524
|
}
|
|
520
525
|
function failedEffectCause(input) {
|
|
521
|
-
const stalled = new Set(
|
|
526
|
+
const stalled = new Set(
|
|
527
|
+
input.activities.filter((t) => !isResolved(t.status)).map((t) => t.activity.name)
|
|
528
|
+
), effect = [...input.instance.effectHistory].reverse().find(
|
|
529
|
+
(e) => e.status === "failed" && e.origin.kind === "activity" && stalled.has(e.origin.name)
|
|
530
|
+
);
|
|
522
531
|
return effect ? { kind: "failed-effect", effect } : void 0;
|
|
523
532
|
}
|
|
524
533
|
function hungEffectCause(input) {
|
|
525
534
|
const effect = input.instance.pendingEffects.find((e) => e.claim !== void 0);
|
|
526
535
|
return effect ? { kind: "hung-effect", effect } : void 0;
|
|
527
536
|
}
|
|
528
|
-
function
|
|
529
|
-
const failed = input.
|
|
530
|
-
return failed ? { kind: "failed-
|
|
537
|
+
function failedActivityCause(input) {
|
|
538
|
+
const failed = input.activities.find((t) => t.status === "failed");
|
|
539
|
+
return failed ? { kind: "failed-activity", activity: failed.activity.name } : void 0;
|
|
531
540
|
}
|
|
532
541
|
function waitingState(input) {
|
|
533
|
-
const active = input.
|
|
534
|
-
(t) => t.status === "active" && (t.
|
|
542
|
+
const active = input.activities.find(
|
|
543
|
+
(t) => t.status === "active" && (t.activity.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length === 0
|
|
535
544
|
);
|
|
536
545
|
if (active !== void 0)
|
|
537
546
|
return {
|
|
538
547
|
state: "waiting",
|
|
539
|
-
|
|
540
|
-
assignees: input.assignees[active.
|
|
541
|
-
actions: (active.
|
|
548
|
+
activity: active.activity.name,
|
|
549
|
+
assignees: input.assignees[active.activity.name] ?? [],
|
|
550
|
+
actions: (active.activity.actions ?? []).map((a) => a.name)
|
|
542
551
|
};
|
|
543
552
|
}
|
|
544
553
|
function blockedState(input) {
|
|
545
|
-
const blocked = input.
|
|
546
|
-
(t) => t.status === "active" && (t.
|
|
554
|
+
const blocked = input.activities.find(
|
|
555
|
+
(t) => t.status === "active" && (t.activity.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length > 0
|
|
547
556
|
);
|
|
548
557
|
if (blocked !== void 0)
|
|
549
558
|
return {
|
|
550
559
|
state: "blocked",
|
|
551
|
-
|
|
560
|
+
activity: blocked.activity.name,
|
|
552
561
|
requirements: blocked.unmetRequirements ?? [],
|
|
553
|
-
assignees: input.assignees[blocked.
|
|
562
|
+
assignees: input.assignees[blocked.activity.name] ?? []
|
|
554
563
|
};
|
|
555
564
|
}
|
|
556
565
|
function noTransitionFiresCause(input) {
|
|
557
|
-
if (input.transitions.length === 0 || input.transitions.some((t) => t.filterSatisfied) || !input.
|
|
566
|
+
if (input.transitions.length === 0 || input.transitions.some((t) => t.filterSatisfied) || !input.activities.every((t) => isResolved(t.status)))
|
|
558
567
|
return;
|
|
559
568
|
const undecidable = input.transitions.filter((t) => t.unevaluable);
|
|
560
569
|
return undecidable.length > 0 ? { kind: "transition-unevaluable", transitions: undecidable.map((t) => t.transition.name) } : { kind: "no-transition-fires" };
|
|
@@ -567,7 +576,7 @@ function diagnoseInstance(input) {
|
|
|
567
576
|
}
|
|
568
577
|
if (instance.completedAt !== void 0)
|
|
569
578
|
return { state: "completed", at: instance.completedAt };
|
|
570
|
-
const cause = failedEffectCause(input) ??
|
|
579
|
+
const cause = failedEffectCause(input) ?? failedActivityCause(input) ?? hungEffectCause(input) ?? noTransitionFiresCause(input);
|
|
571
580
|
return cause !== void 0 ? { state: "stuck", cause } : waitingState(input) ?? blockedState(input) ?? { state: "progressing" };
|
|
572
581
|
}
|
|
573
582
|
const RUNNABLE_VERBS = /* @__PURE__ */ new Set(["set-stage", "abort"]);
|
|
@@ -589,12 +598,12 @@ function remediationsForCause(cause) {
|
|
|
589
598
|
},
|
|
590
599
|
{ verb: "abort", rationale: "Abort the instance if the effect never drains." }
|
|
591
600
|
];
|
|
592
|
-
case "failed-
|
|
601
|
+
case "failed-activity":
|
|
593
602
|
return [
|
|
594
|
-
{ verb: "reset-
|
|
603
|
+
{ verb: "reset-activity", rationale: "Reset or skip the failed activity." },
|
|
595
604
|
{
|
|
596
605
|
verb: "set-stage",
|
|
597
|
-
rationale: "Move the instance past the failed
|
|
606
|
+
rationale: "Move the instance past the failed activity to the intended next stage."
|
|
598
607
|
}
|
|
599
608
|
];
|
|
600
609
|
case "no-transition-fires":
|
|
@@ -616,13 +625,15 @@ function remediationsFor(diagnosis) {
|
|
|
616
625
|
}
|
|
617
626
|
class ActionParamsInvalidError extends Error {
|
|
618
627
|
action;
|
|
619
|
-
|
|
628
|
+
activity;
|
|
620
629
|
issues;
|
|
621
630
|
constructor(args) {
|
|
622
631
|
const lines = args.issues.map((i) => ` - ${i.param}: ${i.reason}`).join(`
|
|
623
632
|
`);
|
|
624
|
-
super(
|
|
625
|
-
|
|
633
|
+
super(
|
|
634
|
+
`Action "${args.action}" on activity "${args.activity}" rejected: invalid params
|
|
635
|
+
${lines}`
|
|
636
|
+
), this.name = "ActionParamsInvalidError", this.action = args.action, this.activity = args.activity, this.issues = args.issues;
|
|
626
637
|
}
|
|
627
638
|
}
|
|
628
639
|
class RequiredFieldNotProvidedError extends Error {
|
|
@@ -632,7 +643,7 @@ class RequiredFieldNotProvidedError extends Error {
|
|
|
632
643
|
const lines = args.missing.map((m) => ` - ${m.name} (${m.type})`).join(`
|
|
633
644
|
`), where = args.definition !== void 0 ? ` for workflow "${args.definition}"` : "";
|
|
634
645
|
super(
|
|
635
|
-
`Required
|
|
646
|
+
`Required input fields${where} were not provided:
|
|
636
647
|
${lines}
|
|
637
648
|
Provide each via \`initialFields\` when starting standalone, or via the parent's \`subworkflows.with\` when spawned.`
|
|
638
649
|
), this.name = "RequiredFieldNotProvidedError", args.definition !== void 0 && (this.definition = args.definition), this.missing = args.missing;
|
|
@@ -664,13 +675,13 @@ class PartialGuardDeployError extends Error {
|
|
|
664
675
|
const CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS = 3;
|
|
665
676
|
class ConcurrentFireActionError extends Error {
|
|
666
677
|
instanceId;
|
|
667
|
-
|
|
678
|
+
activity;
|
|
668
679
|
action;
|
|
669
680
|
attempts;
|
|
670
681
|
constructor(args) {
|
|
671
682
|
super(
|
|
672
|
-
`Action "${args.action}" on
|
|
673
|
-
), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.
|
|
683
|
+
`Action "${args.action}" on activity "${args.activity}" (instance ${args.instanceId}) lost the optimistic-locking race ${args.attempts} attempts running \u2014 a concurrent writer kept committing first. Re-read and retry, or investigate a write storm on this instance.`
|
|
684
|
+
), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.activity = args.activity, this.action = args.action, this.attempts = args.attempts;
|
|
674
685
|
}
|
|
675
686
|
}
|
|
676
687
|
function isRevisionConflict(error) {
|
|
@@ -683,7 +694,7 @@ class ConcurrentEditFieldError extends Error {
|
|
|
683
694
|
target;
|
|
684
695
|
attempts;
|
|
685
696
|
constructor(args) {
|
|
686
|
-
const where = args.target.
|
|
697
|
+
const where = args.target.activity !== void 0 ? `${args.target.activity}.${args.target.field}` : args.target.field;
|
|
687
698
|
super(
|
|
688
699
|
`Edit of "${args.target.scope}:${where}" (instance ${args.instanceId}) lost the optimistic-locking race ${args.attempts} attempts running \u2014 a concurrent writer kept committing first. Re-read and retry, or investigate a write storm on this instance.`
|
|
689
700
|
), this.name = "ConcurrentEditFieldError", this.instanceId = args.instanceId, this.target = args.target, this.attempts = args.attempts;
|
|
@@ -761,6 +772,9 @@ function subscriptionDocumentsForInstance(instance) {
|
|
|
761
772
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
762
773
|
};
|
|
763
774
|
}
|
|
775
|
+
function instanceWatchesDocument(instance, document) {
|
|
776
|
+
return collectWatchRefs(instance).some((ref) => ref.id === document);
|
|
777
|
+
}
|
|
764
778
|
function entryDocRefs(entries) {
|
|
765
779
|
return fieldEntries(entries).flatMap((entry) => entry._type === "doc.ref" ? isGdr(entry.value) ? [entry.value] : [] : entry._type === "doc.refs" ? Array.isArray(entry.value) ? entry.value.filter(isGdr) : [] : []);
|
|
766
780
|
}
|
|
@@ -875,14 +889,14 @@ function guardIdRefs(definition) {
|
|
|
875
889
|
function staticIdRefGdr(idRef, stage, definition) {
|
|
876
890
|
const read = /^\$fields\.([\w-]+)/.exec(idRef);
|
|
877
891
|
if (read === null) return;
|
|
878
|
-
const
|
|
879
|
-
return
|
|
892
|
+
const seed = entriesInScope(stage, definition).find((e) => e.name === read[1])?.initialValue;
|
|
893
|
+
return seed?.type === "literal" ? gdrFromValue(seed.value) : void 0;
|
|
880
894
|
}
|
|
881
895
|
function entriesInScope(stage, definition) {
|
|
882
896
|
return [
|
|
883
897
|
...definition.fields ?? [],
|
|
884
898
|
...stage.fields ?? [],
|
|
885
|
-
...(stage.
|
|
899
|
+
...(stage.activities ?? []).flatMap((activity) => activity.fields ?? [])
|
|
886
900
|
];
|
|
887
901
|
}
|
|
888
902
|
function gdrFromValue(value) {
|
|
@@ -1064,76 +1078,73 @@ const GdrShape = v__namespace.looseObject({
|
|
|
1064
1078
|
}), AssigneeShape = v__namespace.union([
|
|
1065
1079
|
v__namespace.looseObject({ type: v__namespace.literal("user"), id: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)) }),
|
|
1066
1080
|
v__namespace.looseObject({ type: v__namespace.literal("role"), role: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)) })
|
|
1067
|
-
]),
|
|
1068
|
-
label: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)),
|
|
1069
|
-
done: v__namespace.boolean(),
|
|
1070
|
-
doneBy: v__namespace.optional(v__namespace.string()),
|
|
1071
|
-
doneAt: v__namespace.optional(v__namespace.string()),
|
|
1072
|
-
_key: v__namespace.optional(v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)))
|
|
1073
|
-
}), NoteItemShape = v__namespace.pipe(
|
|
1074
|
-
v__namespace.looseObject({
|
|
1075
|
-
_key: v__namespace.optional(v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)))
|
|
1076
|
-
}),
|
|
1077
|
-
v__namespace.check(
|
|
1078
|
-
(val) => typeof val == "object" && val !== null && !Array.isArray(val),
|
|
1079
|
-
"must be an object"
|
|
1080
|
-
)
|
|
1081
|
-
), NullableString = v__namespace.union([v__namespace.null(), v__namespace.string()]), NullableNumber = v__namespace.union([v__namespace.null(), v__namespace.number()]), NullableBoolean = v__namespace.union([v__namespace.null(), v__namespace.boolean()]), NullableDateTime = v__namespace.union([
|
|
1081
|
+
]), NullableString = v__namespace.union([v__namespace.null(), v__namespace.string()]), NullableNumber = v__namespace.union([v__namespace.null(), v__namespace.number()]), NullableBoolean = v__namespace.union([v__namespace.null(), v__namespace.boolean()]), NullableDateTime = v__namespace.union([
|
|
1082
1082
|
v__namespace.null(),
|
|
1083
1083
|
v__namespace.pipe(
|
|
1084
1084
|
v__namespace.string(),
|
|
1085
1085
|
v__namespace.check((s) => !Number.isNaN(Date.parse(s)), "must be an ISO-8601 datetime string")
|
|
1086
1086
|
)
|
|
1087
|
+
]), NullableDate = v__namespace.union([
|
|
1088
|
+
v__namespace.null(),
|
|
1089
|
+
v__namespace.pipe(v__namespace.string(), v__namespace.regex(/^\d{4}-\d{2}-\d{2}$/, "must be a `YYYY-MM-DD` date"))
|
|
1087
1090
|
]), NullableUrl = NullableString, valueSchemas = {
|
|
1088
1091
|
"doc.ref": v__namespace.union([v__namespace.null(), GdrShape]),
|
|
1089
1092
|
"doc.refs": v__namespace.array(GdrShape),
|
|
1090
1093
|
"release.ref": v__namespace.union([v__namespace.null(), ReleaseRefShape]),
|
|
1091
1094
|
query: v__namespace.any(),
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1095
|
+
string: NullableString,
|
|
1096
|
+
text: NullableString,
|
|
1097
|
+
number: NullableNumber,
|
|
1098
|
+
boolean: NullableBoolean,
|
|
1099
|
+
date: NullableDate,
|
|
1100
|
+
datetime: NullableDateTime,
|
|
1101
|
+
url: NullableUrl,
|
|
1102
|
+
actor: v__namespace.union([v__namespace.null(), ActorShape]),
|
|
1103
|
+
assignee: v__namespace.union([v__namespace.null(), AssigneeShape]),
|
|
1100
1104
|
assignees: v__namespace.array(AssigneeShape)
|
|
1101
|
-
}, itemSchemas = {
|
|
1102
|
-
"doc.refs": GdrShape,
|
|
1103
|
-
checklist: ChecklistItemShape,
|
|
1104
|
-
notes: NoteItemShape,
|
|
1105
|
-
assignees: AssigneeShape
|
|
1106
1105
|
};
|
|
1107
|
-
function
|
|
1108
|
-
return
|
|
1106
|
+
function shapeValueSchema(shape) {
|
|
1107
|
+
return shape.type === "object" ? objectSchema(shape.fields ?? []) : shape.type === "array" ? v__namespace.array(objectSchema(shape.of ?? [])) : valueSchemas[shape.type] ?? v__namespace.any();
|
|
1109
1108
|
}
|
|
1110
|
-
function
|
|
1111
|
-
const
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1109
|
+
function objectSchema(fields) {
|
|
1110
|
+
const entries = /* @__PURE__ */ Object.create(null);
|
|
1111
|
+
for (const f of fields) entries[f.name] = v__namespace.optional(shapeValueSchema(f));
|
|
1112
|
+
return v__namespace.looseObject(entries);
|
|
1113
|
+
}
|
|
1114
|
+
function wholeValueSchema(entryType, shape) {
|
|
1115
|
+
return entryType === "object" ? v__namespace.union([v__namespace.null(), objectSchema(shape.fields ?? [])]) : entryType === "array" ? v__namespace.array(objectSchema(shape.of ?? [])) : valueSchemas[entryType];
|
|
1116
|
+
}
|
|
1117
|
+
function appendItemSchema(entryType, shape) {
|
|
1118
|
+
if (entryType === "array") return objectSchema(shape.of ?? []);
|
|
1119
|
+
if (entryType === "doc.refs") return GdrShape;
|
|
1120
|
+
if (entryType === "assignees") return AssigneeShape;
|
|
1121
|
+
}
|
|
1122
|
+
function checkFieldValue(args) {
|
|
1123
|
+
const schema2 = wholeValueSchema(args.entryType, args);
|
|
1124
|
+
if (schema2 === void 0) return [`unknown field entry type ${args.entryType}`];
|
|
1119
1125
|
const result = v__namespace.safeParse(schema2, args.value);
|
|
1120
|
-
|
|
1126
|
+
return result.success ? void 0 : formatIssues(result.issues);
|
|
1127
|
+
}
|
|
1128
|
+
function validateFieldValue(args) {
|
|
1129
|
+
const issues = checkFieldValue(args);
|
|
1130
|
+
if (issues !== void 0)
|
|
1121
1131
|
throw new FieldValueShapeError({
|
|
1122
1132
|
entryType: args.entryType,
|
|
1123
1133
|
entryName: args.entryName,
|
|
1124
|
-
issues
|
|
1134
|
+
issues,
|
|
1125
1135
|
mode: "value"
|
|
1126
1136
|
});
|
|
1127
1137
|
}
|
|
1128
1138
|
function validateFieldAppendItem(args) {
|
|
1129
|
-
|
|
1139
|
+
const schema2 = appendItemSchema(args.entryType, args);
|
|
1140
|
+
if (schema2 === void 0)
|
|
1130
1141
|
throw new FieldValueShapeError({
|
|
1131
1142
|
entryType: args.entryType,
|
|
1132
1143
|
entryName: args.entryName,
|
|
1133
1144
|
issues: [`field entry type ${args.entryType} does not support append`],
|
|
1134
1145
|
mode: "item"
|
|
1135
1146
|
});
|
|
1136
|
-
const
|
|
1147
|
+
const result = v__namespace.safeParse(schema2, args.item);
|
|
1137
1148
|
if (!result.success)
|
|
1138
1149
|
throw new FieldValueShapeError({
|
|
1139
1150
|
entryType: args.entryType,
|
|
@@ -1161,7 +1172,7 @@ function derivePerspectiveFromFields(entries) {
|
|
|
1161
1172
|
async function resolveDeclaredFields(args) {
|
|
1162
1173
|
const { entryDefs, initialFields, ctx, randomKey: randomKey2 } = args;
|
|
1163
1174
|
if (entryDefs === void 0 || entryDefs.length === 0) return [];
|
|
1164
|
-
|
|
1175
|
+
assertRequiredInputProvided(entryDefs, initialFields, ctx.definitionName);
|
|
1165
1176
|
const out = [];
|
|
1166
1177
|
for (const entry of entryDefs) {
|
|
1167
1178
|
const scopedCtx = { ...ctx, resolvedFields: out };
|
|
@@ -1169,8 +1180,8 @@ async function resolveDeclaredFields(args) {
|
|
|
1169
1180
|
}
|
|
1170
1181
|
return out;
|
|
1171
1182
|
}
|
|
1172
|
-
function
|
|
1173
|
-
const missing = entryDefs.filter((entry) => entry.required === !0 && entry.
|
|
1183
|
+
function assertRequiredInputProvided(entryDefs, initialFields, definitionName) {
|
|
1184
|
+
const missing = entryDefs.filter((entry) => entry.required === !0 && entry.initialValue?.type === "input").filter((entry) => {
|
|
1174
1185
|
const match = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1175
1186
|
return match === void 0 || match.value === void 0 || match.value === null;
|
|
1176
1187
|
}).map((entry) => ({ name: entry.name, type: entry.type }));
|
|
@@ -1180,18 +1191,13 @@ function assertRequiredInitProvided(entryDefs, initialFields, definitionName) {
|
|
|
1180
1191
|
missing
|
|
1181
1192
|
});
|
|
1182
1193
|
}
|
|
1183
|
-
const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set([
|
|
1184
|
-
"doc.refs",
|
|
1185
|
-
"checklist",
|
|
1186
|
-
"notes",
|
|
1187
|
-
"assignees"
|
|
1188
|
-
]);
|
|
1194
|
+
const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set(["doc.refs", "array", "assignees"]);
|
|
1189
1195
|
function defaultEntryValue(entryType) {
|
|
1190
1196
|
return ARRAY_SLOT_TYPES.has(entryType) ? [] : null;
|
|
1191
1197
|
}
|
|
1192
|
-
function
|
|
1198
|
+
function resolveInputValue(entry, initialFields, defaultValue) {
|
|
1193
1199
|
const initMatch = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1194
|
-
return initMatch === void 0 ? defaultValue : (
|
|
1200
|
+
return initMatch === void 0 ? defaultValue : (assertInputValueShape(entry, initMatch.value), initMatch.value);
|
|
1195
1201
|
}
|
|
1196
1202
|
function resolveFieldReadValue(source, ctx, defaultValue) {
|
|
1197
1203
|
const target = (source.scope === "workflow" ? ctx.workflowFields ?? [] : ctx.resolvedFields ?? []).find((s) => s.name === source.field), targetValue = target === void 0 ? void 0 : target.value;
|
|
@@ -1202,7 +1208,7 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
|
1202
1208
|
self: ctx.selfId ?? null,
|
|
1203
1209
|
fields: fieldMapFromResolved(ctx.resolvedFields ?? []),
|
|
1204
1210
|
stage: ctx.stageName ?? null,
|
|
1205
|
-
|
|
1211
|
+
activity: ctx.activityName ?? null,
|
|
1206
1212
|
now: ctx.now,
|
|
1207
1213
|
tag: ctx.tag
|
|
1208
1214
|
});
|
|
@@ -1217,38 +1223,47 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
|
1217
1223
|
}
|
|
1218
1224
|
}
|
|
1219
1225
|
async function resolveEntryValue(entry, initialFields, ctx, defaultValue) {
|
|
1220
|
-
const source = entry.
|
|
1221
|
-
return source.type === "
|
|
1226
|
+
const source = entry.initialValue;
|
|
1227
|
+
return source === void 0 ? defaultValue : source.type === "input" ? resolveInputValue(entry, initialFields, defaultValue) : source.type === "literal" ? source.value ?? defaultValue : source.type === "fieldRead" ? resolveFieldReadValue(source, ctx, defaultValue) : source.type === "query" && ctx.client !== void 0 ? resolveQueryValue(entry, source, ctx, ctx.client, defaultValue) : defaultValue;
|
|
1222
1228
|
}
|
|
1223
1229
|
function buildResolvedEntry(entry, value, _key, now) {
|
|
1224
|
-
|
|
1225
|
-
return entry.type === "query" ? {
|
|
1226
|
-
_key,
|
|
1227
|
-
_type: "query",
|
|
1228
|
-
name: entry.name,
|
|
1229
|
-
...titleProp,
|
|
1230
|
-
...descriptionProp,
|
|
1231
|
-
value,
|
|
1232
|
-
resolvedAt: now
|
|
1233
|
-
} : {
|
|
1230
|
+
return {
|
|
1234
1231
|
_key,
|
|
1235
1232
|
_type: entry.type,
|
|
1236
1233
|
name: entry.name,
|
|
1237
|
-
...
|
|
1238
|
-
...
|
|
1239
|
-
value
|
|
1234
|
+
...entry.title !== void 0 ? { title: entry.title } : {},
|
|
1235
|
+
...entry.description !== void 0 ? { description: entry.description } : {},
|
|
1236
|
+
value,
|
|
1237
|
+
...entry.initialValue?.type === "query" ? { resolvedAt: now } : {},
|
|
1238
|
+
...entry.type === "object" ? { fields: entry.fields ?? [] } : {},
|
|
1239
|
+
...entry.type === "array" ? { of: entry.of ?? [] } : {}
|
|
1240
1240
|
};
|
|
1241
1241
|
}
|
|
1242
1242
|
async function resolveOneEntry(entry, initialFields, ctx, randomKey2) {
|
|
1243
1243
|
const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue(entry, initialFields, ctx, defaultValue);
|
|
1244
|
-
|
|
1244
|
+
if (entry.initialValue?.type === "query") {
|
|
1245
|
+
const issues = checkFieldValue({
|
|
1246
|
+
entryType: entry.type,
|
|
1247
|
+
value,
|
|
1248
|
+
fields: entry.fields,
|
|
1249
|
+
of: entry.of
|
|
1250
|
+
});
|
|
1251
|
+
return issues !== void 0 ? (ctx.recordDiscard?.({ field: entry.name, detail: issues.join("; ") }), buildResolvedEntry(entry, defaultValue, randomKey2(), ctx.now)) : buildResolvedEntry(entry, value, randomKey2(), ctx.now);
|
|
1252
|
+
}
|
|
1253
|
+
return validateFieldValue({
|
|
1254
|
+
entryType: entry.type,
|
|
1255
|
+
entryName: entry.name,
|
|
1256
|
+
value,
|
|
1257
|
+
fields: entry.fields,
|
|
1258
|
+
of: entry.of
|
|
1259
|
+
}), buildResolvedEntry(entry, value, randomKey2(), ctx.now);
|
|
1245
1260
|
}
|
|
1246
1261
|
function fieldMapFromResolved(entries) {
|
|
1247
1262
|
const out = {};
|
|
1248
1263
|
for (const s of entries) out[s.name] = s.value;
|
|
1249
1264
|
return out;
|
|
1250
1265
|
}
|
|
1251
|
-
function
|
|
1266
|
+
function assertInputValueShape(entry, value) {
|
|
1252
1267
|
if (entry.type === "doc.ref") {
|
|
1253
1268
|
assertGdrShape(value, `field entry "${entry.name}" (doc.ref)`);
|
|
1254
1269
|
return;
|
|
@@ -1258,14 +1273,14 @@ function assertInitValueShape(entry, value) {
|
|
|
1258
1273
|
const v2 = value;
|
|
1259
1274
|
if (typeof v2.releaseName != "string" || v2.releaseName.length === 0)
|
|
1260
1275
|
throw new Error(
|
|
1261
|
-
`Invalid
|
|
1276
|
+
`Invalid input value for field entry "${entry.name}" (release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(v2.releaseName)}.`
|
|
1262
1277
|
);
|
|
1263
1278
|
return;
|
|
1264
1279
|
}
|
|
1265
1280
|
if (entry.type === "doc.refs") {
|
|
1266
1281
|
if (!Array.isArray(value))
|
|
1267
1282
|
throw new Error(
|
|
1268
|
-
`Invalid
|
|
1283
|
+
`Invalid input value for field entry "${entry.name}" (doc.refs): expected an array of GDRs, got ${typeof value}.`
|
|
1269
1284
|
);
|
|
1270
1285
|
for (const [i, item] of value.entries())
|
|
1271
1286
|
assertGdrShape(item, `field entry "${entry.name}" (doc.refs) item [${i}]`);
|
|
@@ -1329,12 +1344,12 @@ async function loadContext(client, instanceId, options) {
|
|
|
1329
1344
|
async function ctxConditionParams(ctx, opts) {
|
|
1330
1345
|
const base = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), fields = {
|
|
1331
1346
|
...base.fields,
|
|
1332
|
-
...scopedFieldOverlay(ctx.instance, ctx.snapshot, opts?.
|
|
1347
|
+
...scopedFieldOverlay(ctx.instance, ctx.snapshot, opts?.activityName)
|
|
1333
1348
|
}, params = {
|
|
1334
1349
|
...base,
|
|
1335
1350
|
fields,
|
|
1336
1351
|
actor: opts?.actor,
|
|
1337
|
-
assigned: opts?.
|
|
1352
|
+
assigned: opts?.activityName !== void 0 ? assignedFor(ctx.instance, opts.activityName, opts?.actor, ctx.definition.roleAliases) : !1,
|
|
1338
1353
|
...opts?.vars
|
|
1339
1354
|
};
|
|
1340
1355
|
return { ...await evaluatePredicates({
|
|
@@ -1372,7 +1387,7 @@ function findStage(definition, stageName) {
|
|
|
1372
1387
|
return stage;
|
|
1373
1388
|
}
|
|
1374
1389
|
async function resolveStageFieldEntries(args) {
|
|
1375
|
-
const { client, instance, stage, now, initialFields } = args;
|
|
1390
|
+
const { client, instance, stage, now, initialFields, recordDiscard } = args;
|
|
1376
1391
|
return resolveDeclaredFields({
|
|
1377
1392
|
entryDefs: stage.fields,
|
|
1378
1393
|
initialFields: initialFields ?? [],
|
|
@@ -1383,18 +1398,19 @@ async function resolveStageFieldEntries(args) {
|
|
|
1383
1398
|
tag: instance.tag,
|
|
1384
1399
|
stageName: stage.name,
|
|
1385
1400
|
workflowResource: instance.workflowResource,
|
|
1386
|
-
// Allow stage-scope entries' `
|
|
1401
|
+
// Allow stage-scope entries' `initialValue: { type: "fieldRead", scope:
|
|
1387
1402
|
// "workflow", ... }` to see the already-resolved workflow-scope fields.
|
|
1388
1403
|
workflowFields: instance.fields ?? [],
|
|
1389
|
-
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
1404
|
+
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {},
|
|
1405
|
+
...recordDiscard !== void 0 ? { recordDiscard } : {}
|
|
1390
1406
|
},
|
|
1391
1407
|
randomKey
|
|
1392
1408
|
});
|
|
1393
1409
|
}
|
|
1394
|
-
async function
|
|
1395
|
-
const { client, instance, stage,
|
|
1410
|
+
async function resolveActivityFieldEntries(args) {
|
|
1411
|
+
const { client, instance, stage, activity, now, recordDiscard } = args;
|
|
1396
1412
|
return resolveDeclaredFields({
|
|
1397
|
-
entryDefs:
|
|
1413
|
+
entryDefs: activity.fields,
|
|
1398
1414
|
initialFields: [],
|
|
1399
1415
|
ctx: {
|
|
1400
1416
|
client,
|
|
@@ -1403,85 +1419,70 @@ async function resolveTaskFieldEntries(args) {
|
|
|
1403
1419
|
tag: instance.tag,
|
|
1404
1420
|
stageName: stage.name,
|
|
1405
1421
|
workflowResource: instance.workflowResource,
|
|
1406
|
-
|
|
1422
|
+
activityName: activity.name,
|
|
1407
1423
|
workflowFields: instance.fields ?? [],
|
|
1408
|
-
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
1424
|
+
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {},
|
|
1425
|
+
...recordDiscard !== void 0 ? { recordDiscard } : {}
|
|
1409
1426
|
},
|
|
1410
1427
|
randomKey
|
|
1411
1428
|
});
|
|
1412
1429
|
}
|
|
1413
|
-
async function
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
throw new WorkflowStateDivergedError({
|
|
1419
|
-
instanceId: args.instanceId,
|
|
1420
|
-
guardError,
|
|
1421
|
-
reason: "the move created child instances a parent-only rollback cannot undo"
|
|
1422
|
-
});
|
|
1423
|
-
try {
|
|
1424
|
-
let patch = args.client.patch(args.instanceId).set(args.restore);
|
|
1425
|
-
args.unset && args.unset.length > 0 && (patch = patch.unset(args.unset)), args.committedRev !== void 0 && (patch = patch.ifRevisionId(args.committedRev)), await patch.commit(SYNC_COMMIT);
|
|
1426
|
-
} catch (rollbackError) {
|
|
1427
|
-
throw new WorkflowStateDivergedError({
|
|
1428
|
-
instanceId: args.instanceId,
|
|
1429
|
-
guardError,
|
|
1430
|
-
rollbackError,
|
|
1431
|
-
reason: "rollback of the committed move failed"
|
|
1432
|
-
});
|
|
1433
|
-
}
|
|
1434
|
-
throw guardError instanceof PartialGuardDeployError ? new WorkflowStateDivergedError({
|
|
1435
|
-
instanceId: args.instanceId,
|
|
1436
|
-
guardError,
|
|
1437
|
-
reason: "a multi-guard deploy partially applied; the rolled-back move left orphaned guard locks"
|
|
1438
|
-
}) : guardError;
|
|
1439
|
-
}
|
|
1430
|
+
async function resolveBindings(args) {
|
|
1431
|
+
const resolved = {};
|
|
1432
|
+
for (const [key, groq] of Object.entries(args.bindings ?? {}))
|
|
1433
|
+
resolved[key] = await runGroq(groq, args.params, args.snapshot);
|
|
1434
|
+
return { ...resolved, ...args.staticInput };
|
|
1440
1435
|
}
|
|
1441
|
-
function
|
|
1442
|
-
const
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
task: entry.name,
|
|
1449
|
-
from,
|
|
1450
|
-
to,
|
|
1451
|
-
...actor !== void 0 ? { actor } : {}
|
|
1452
|
-
});
|
|
1436
|
+
async function reload(client, instanceId, tag) {
|
|
1437
|
+
const doc = await client.getDocument(instanceId);
|
|
1438
|
+
if (!doc)
|
|
1439
|
+
throw new Error(`Workflow instance ${instanceId} not found`);
|
|
1440
|
+
if (doc.tag !== tag)
|
|
1441
|
+
throw new Error(`Workflow instance ${instanceId} not visible to this engine (tag mismatch)`);
|
|
1442
|
+
return doc;
|
|
1453
1443
|
}
|
|
1454
|
-
function
|
|
1455
|
-
const
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1444
|
+
function effectsContextEntry(name, value) {
|
|
1445
|
+
const _key = randomKey();
|
|
1446
|
+
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 };
|
|
1447
|
+
}
|
|
1448
|
+
function effectsContextJsonEntry(name, value) {
|
|
1449
|
+
return { _key: randomKey(), _type: "effectsContext.json", name, value: JSON.stringify(value) };
|
|
1450
|
+
}
|
|
1451
|
+
function buildInstanceBase(args) {
|
|
1452
|
+
const { id, now, actor, perspective } = args;
|
|
1453
|
+
return {
|
|
1454
|
+
_id: id,
|
|
1455
|
+
_type: WORKFLOW_INSTANCE_TYPE,
|
|
1456
|
+
_rev: "",
|
|
1457
|
+
_createdAt: now,
|
|
1458
|
+
_updatedAt: now,
|
|
1459
|
+
tag: args.tag,
|
|
1460
|
+
workflowResource: args.workflowResource,
|
|
1461
|
+
definition: args.definitionName,
|
|
1462
|
+
pinnedVersion: args.pinnedVersion,
|
|
1463
|
+
...args.pinnedContentHash !== void 0 ? { pinnedContentHash: args.pinnedContentHash } : {},
|
|
1464
|
+
definitionSnapshot: JSON.stringify(args.definition),
|
|
1465
|
+
fields: args.fields,
|
|
1466
|
+
effectsContext: args.effectsContext,
|
|
1467
|
+
ancestors: args.ancestors,
|
|
1468
|
+
...perspective !== void 0 ? { perspective } : {},
|
|
1469
|
+
currentStage: args.initialStage,
|
|
1470
|
+
stages: [],
|
|
1471
|
+
pendingEffects: [],
|
|
1472
|
+
effectHistory: [],
|
|
1473
|
+
history: [
|
|
1474
|
+
{
|
|
1475
|
+
_key: randomKey(),
|
|
1476
|
+
_type: "stageEntered",
|
|
1477
|
+
at: now,
|
|
1478
|
+
stage: args.initialStage,
|
|
1479
|
+
...actor !== void 0 ? { actor } : {}
|
|
1480
|
+
},
|
|
1481
|
+
...args.extraHistory ?? []
|
|
1482
|
+
],
|
|
1483
|
+
startedAt: now,
|
|
1484
|
+
lastChangedAt: now
|
|
1461
1485
|
};
|
|
1462
|
-
return [
|
|
1463
|
-
{
|
|
1464
|
-
_key: randomKey(),
|
|
1465
|
-
_type: "stageExited",
|
|
1466
|
-
stage: fromStage,
|
|
1467
|
-
toStage,
|
|
1468
|
-
...shared
|
|
1469
|
-
},
|
|
1470
|
-
{
|
|
1471
|
-
_key: randomKey(),
|
|
1472
|
-
_type: "transitionFired",
|
|
1473
|
-
fromStage,
|
|
1474
|
-
toStage,
|
|
1475
|
-
...shared
|
|
1476
|
-
},
|
|
1477
|
-
{
|
|
1478
|
-
_key: randomKey(),
|
|
1479
|
-
_type: "stageEntered",
|
|
1480
|
-
stage: toStage,
|
|
1481
|
-
fromStage,
|
|
1482
|
-
...shared
|
|
1483
|
-
}
|
|
1484
|
-
];
|
|
1485
1486
|
}
|
|
1486
1487
|
function startMutation(instance) {
|
|
1487
1488
|
return {
|
|
@@ -1492,7 +1493,7 @@ function startMutation(instance) {
|
|
|
1492
1493
|
stages: instance.stages.map((s) => ({
|
|
1493
1494
|
...s,
|
|
1494
1495
|
fields: (s.fields ?? []).map((entry) => ({ ...entry })),
|
|
1495
|
-
|
|
1496
|
+
activities: s.activities.map((t) => ({
|
|
1496
1497
|
...t,
|
|
1497
1498
|
...t.fields !== void 0 ? { fields: t.fields.map((entry) => ({ ...entry })) } : {}
|
|
1498
1499
|
}))
|
|
@@ -1568,344 +1569,97 @@ function currentStageEntry(mutation) {
|
|
|
1568
1569
|
);
|
|
1569
1570
|
return entry;
|
|
1570
1571
|
}
|
|
1571
|
-
function
|
|
1572
|
-
return currentStageEntry(mutation).
|
|
1572
|
+
function currentActivities(mutation) {
|
|
1573
|
+
return currentStageEntry(mutation).activities;
|
|
1573
1574
|
}
|
|
1574
|
-
function
|
|
1575
|
-
const stage = findStage(ctx.definition, ctx.instance.currentStage),
|
|
1576
|
-
if (
|
|
1575
|
+
function findActivityInCurrentStage(ctx, activityName) {
|
|
1576
|
+
const stage = findStage(ctx.definition, ctx.instance.currentStage), activity = (stage.activities ?? []).find((t) => t.name === activityName);
|
|
1577
|
+
if (activity === void 0)
|
|
1577
1578
|
throw new Error(
|
|
1578
|
-
`
|
|
1579
|
+
`Activity "${activityName}" not found in current stage "${stage.name}" of ${ctx.definition.name}`
|
|
1579
1580
|
);
|
|
1580
|
-
return { stage,
|
|
1581
|
+
return { stage, activity };
|
|
1581
1582
|
}
|
|
1582
|
-
function
|
|
1583
|
-
const mutEntry =
|
|
1583
|
+
function requireMutationActivityEntry(mutation, activity) {
|
|
1584
|
+
const mutEntry = currentActivities(mutation).find((t) => t.name === activity);
|
|
1584
1585
|
if (mutEntry === void 0)
|
|
1585
|
-
throw new Error(`
|
|
1586
|
+
throw new Error(`Activity "${activity}" disappeared from mutation copy \u2014 invariant broken`);
|
|
1586
1587
|
return mutEntry;
|
|
1587
1588
|
}
|
|
1588
1589
|
function findCurrentStageEntry(instance) {
|
|
1589
1590
|
return findOpenStageEntry(instance);
|
|
1590
1591
|
}
|
|
1591
|
-
function
|
|
1592
|
-
return findCurrentStageEntry(instance)?.
|
|
1593
|
-
}
|
|
1594
|
-
const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
1595
|
-
function validateTag(tag) {
|
|
1596
|
-
if (!TAG_RE.test(tag))
|
|
1597
|
-
throw new Error(
|
|
1598
|
-
`tag: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
|
|
1599
|
-
);
|
|
1600
|
-
}
|
|
1601
|
-
function tagScopeFilter() {
|
|
1602
|
-
return "tag == $tag";
|
|
1603
|
-
}
|
|
1604
|
-
function definitionLookupGroq(explicit) {
|
|
1605
|
-
const scoped = `_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}`;
|
|
1606
|
-
return explicit ? `*[${scoped} && version == $version][0]` : `*[${scoped}] | order(version desc)[0]`;
|
|
1607
|
-
}
|
|
1608
|
-
async function discoverItems(args) {
|
|
1609
|
-
const { client, groq, params, perspective } = args, fetchOptions = perspective !== void 0 ? { perspective } : void 0;
|
|
1610
|
-
return client.fetch(groq, paramsForLake(params), fetchOptions);
|
|
1592
|
+
function findCurrentActivities(instance) {
|
|
1593
|
+
return findCurrentStageEntry(instance)?.activities ?? [];
|
|
1611
1594
|
}
|
|
1612
|
-
function
|
|
1613
|
-
if (!subjectGdrUri || !subjectGdrUri.includes(":")) return defaultClient;
|
|
1595
|
+
async function deployOrRollback(args) {
|
|
1614
1596
|
try {
|
|
1615
|
-
|
|
1616
|
-
} catch {
|
|
1617
|
-
|
|
1597
|
+
await args.deploy();
|
|
1598
|
+
} catch (guardError) {
|
|
1599
|
+
if (!args.reversible)
|
|
1600
|
+
throw new WorkflowStateDivergedError({
|
|
1601
|
+
instanceId: args.instanceId,
|
|
1602
|
+
guardError,
|
|
1603
|
+
reason: "the move created child instances a parent-only rollback cannot undo"
|
|
1604
|
+
});
|
|
1605
|
+
try {
|
|
1606
|
+
let patch = args.client.patch(args.instanceId).set(args.restore);
|
|
1607
|
+
args.unset && args.unset.length > 0 && (patch = patch.unset(args.unset)), args.committedRev !== void 0 && (patch = patch.ifRevisionId(args.committedRev)), await patch.commit(SYNC_COMMIT);
|
|
1608
|
+
} catch (rollbackError) {
|
|
1609
|
+
throw new WorkflowStateDivergedError({
|
|
1610
|
+
instanceId: args.instanceId,
|
|
1611
|
+
guardError,
|
|
1612
|
+
rollbackError,
|
|
1613
|
+
reason: "rollback of the committed move failed"
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
throw guardError instanceof PartialGuardDeployError ? new WorkflowStateDivergedError({
|
|
1617
|
+
instanceId: args.instanceId,
|
|
1618
|
+
guardError,
|
|
1619
|
+
reason: "a multi-guard deploy partially applied; the rolled-back move left orphaned guard locks"
|
|
1620
|
+
}) : guardError;
|
|
1618
1621
|
}
|
|
1619
1622
|
}
|
|
1620
|
-
async function
|
|
1621
|
-
const
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1623
|
+
async function persistThenDeploy(ctx, mutation, deploy) {
|
|
1624
|
+
const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
|
|
1625
|
+
await deployOrRollback({
|
|
1626
|
+
client: ctx.client,
|
|
1627
|
+
instanceId: ctx.instance._id,
|
|
1628
|
+
committedRev: committed._rev,
|
|
1629
|
+
restore: instanceStateFields(ctx.instance),
|
|
1630
|
+
unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
|
|
1631
|
+
reversible: !spawned,
|
|
1632
|
+
deploy
|
|
1633
|
+
});
|
|
1627
1634
|
}
|
|
1628
|
-
function
|
|
1629
|
-
|
|
1630
|
-
|
|
1635
|
+
async function refreshStageGuards(ctx, mutation, stageName) {
|
|
1636
|
+
await deployStageGuards({
|
|
1637
|
+
client: ctx.client,
|
|
1638
|
+
clientForGdr: ctx.clientForGdr,
|
|
1639
|
+
instance: materializeInstance(ctx.instance, mutation),
|
|
1640
|
+
definition: ctx.definition,
|
|
1641
|
+
stageName,
|
|
1642
|
+
now: ctx.now
|
|
1643
|
+
});
|
|
1631
1644
|
}
|
|
1632
|
-
function
|
|
1633
|
-
|
|
1645
|
+
async function completeEffect(args) {
|
|
1646
|
+
const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1647
|
+
...options?.clock ? { clock: options.clock } : {},
|
|
1648
|
+
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
1649
|
+
});
|
|
1650
|
+
return commitCompleteEffect(
|
|
1651
|
+
ctx,
|
|
1652
|
+
effectKey,
|
|
1653
|
+
status,
|
|
1654
|
+
outputs,
|
|
1655
|
+
detail,
|
|
1656
|
+
error,
|
|
1657
|
+
durationMs,
|
|
1658
|
+
options?.actor
|
|
1659
|
+
);
|
|
1634
1660
|
}
|
|
1635
|
-
function
|
|
1636
|
-
const {
|
|
1637
|
-
return {
|
|
1638
|
-
_id: id,
|
|
1639
|
-
_type: WORKFLOW_INSTANCE_TYPE,
|
|
1640
|
-
_rev: "",
|
|
1641
|
-
_createdAt: now,
|
|
1642
|
-
_updatedAt: now,
|
|
1643
|
-
tag: args.tag,
|
|
1644
|
-
workflowResource: args.workflowResource,
|
|
1645
|
-
definition: args.definitionName,
|
|
1646
|
-
pinnedVersion: args.pinnedVersion,
|
|
1647
|
-
definitionSnapshot: JSON.stringify(args.definition),
|
|
1648
|
-
fields: args.fields,
|
|
1649
|
-
effectsContext: args.effectsContext,
|
|
1650
|
-
ancestors: args.ancestors,
|
|
1651
|
-
...perspective !== void 0 ? { perspective } : {},
|
|
1652
|
-
currentStage: args.initialStage,
|
|
1653
|
-
stages: [],
|
|
1654
|
-
pendingEffects: [],
|
|
1655
|
-
effectHistory: [],
|
|
1656
|
-
history: [
|
|
1657
|
-
{
|
|
1658
|
-
_key: randomKey(),
|
|
1659
|
-
_type: "stageEntered",
|
|
1660
|
-
at: now,
|
|
1661
|
-
stage: args.initialStage,
|
|
1662
|
-
...actor !== void 0 ? { actor } : {}
|
|
1663
|
-
}
|
|
1664
|
-
],
|
|
1665
|
-
startedAt: now,
|
|
1666
|
-
lastChangedAt: now
|
|
1667
|
-
};
|
|
1668
|
-
}
|
|
1669
|
-
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";
|
|
1670
|
-
function gateActor(outcome) {
|
|
1671
|
-
return {
|
|
1672
|
-
kind: "system",
|
|
1673
|
-
id: outcome === "failed" ? FAIL_WHEN_SYSTEM_ID : COMPLETE_WHEN_SYSTEM_ID
|
|
1674
|
-
};
|
|
1675
|
-
}
|
|
1676
|
-
function effectiveCompleteWhen(task) {
|
|
1677
|
-
return task.completeWhen ?? (task.subworkflows !== void 0 ? SUBWORKFLOWS_ALL_DONE : void 0);
|
|
1678
|
-
}
|
|
1679
|
-
async function evaluateResolutionGate(ctx, task, vars) {
|
|
1680
|
-
const scope = { taskName: task.name, vars };
|
|
1681
|
-
if (task.failWhen !== void 0 && await ctxEvaluateCondition(ctx, task.failWhen, scope))
|
|
1682
|
-
return "failed";
|
|
1683
|
-
const completeWhen = effectiveCompleteWhen(task);
|
|
1684
|
-
if (completeWhen !== void 0 && await ctxEvaluateCondition(ctx, completeWhen, scope))
|
|
1685
|
-
return "done";
|
|
1686
|
-
}
|
|
1687
|
-
async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
1688
|
-
if (ctx.instance.ancestors.length + 1 > MAX_SPAWN_DEPTH) {
|
|
1689
|
-
const chain = [...ctx.instance.ancestors.map((a) => a.id), selfGdr(ctx.instance)].join(" \u2192 ");
|
|
1690
|
-
throw new Error(
|
|
1691
|
-
`Subworkflow depth limit (${MAX_SPAWN_DEPTH}) exceeded on task "${task.name}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
|
|
1692
|
-
);
|
|
1693
|
-
}
|
|
1694
|
-
const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tag);
|
|
1695
|
-
if (definition === null) {
|
|
1696
|
-
const versionLabel = sub.definition.version === void 0 || sub.definition.version === "latest" ? "latest" : `v${sub.definition.version}`;
|
|
1697
|
-
throw new Error(
|
|
1698
|
-
`Subworkflow definition "${sub.definition.name}" ${versionLabel} not deployed (parent ${ctx.instance._id}, task ${task.name})`
|
|
1699
|
-
);
|
|
1700
|
-
}
|
|
1701
|
-
const parentScope = await ctxConditionParams(ctx, {
|
|
1702
|
-
taskName: task.name,
|
|
1703
|
-
...actor ? { actor } : {}
|
|
1704
|
-
}), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
|
|
1705
|
-
for (const row of rows) {
|
|
1706
|
-
const initialFields = await projectRowFields(
|
|
1707
|
-
ctx,
|
|
1708
|
-
sub,
|
|
1709
|
-
definition,
|
|
1710
|
-
parentScope,
|
|
1711
|
-
row,
|
|
1712
|
-
discoveryResource
|
|
1713
|
-
), { ref, body } = await prepareChildInstance({
|
|
1714
|
-
client: ctx.client,
|
|
1715
|
-
parent: ctx.instance,
|
|
1716
|
-
definition,
|
|
1717
|
-
initialFields,
|
|
1718
|
-
effectsContext,
|
|
1719
|
-
actor,
|
|
1720
|
-
now
|
|
1721
|
-
});
|
|
1722
|
-
refs.push(ref), mutation.pendingCreates.push(body);
|
|
1723
|
-
}
|
|
1724
|
-
return refs;
|
|
1725
|
-
}
|
|
1726
|
-
async function resolveSpawnRows(ctx, sub) {
|
|
1727
|
-
const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
|
|
1728
|
-
let value, discoveryResource;
|
|
1729
|
-
if (groq.includes("*")) {
|
|
1730
|
-
const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
|
|
1731
|
-
client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
|
|
1732
|
-
client,
|
|
1733
|
-
groq,
|
|
1734
|
-
params,
|
|
1735
|
-
// Draft-aware by default (drafts overlay published) when there's no
|
|
1736
|
-
// release, so a `forEach` discovers in-flight items the same way the
|
|
1737
|
-
// engine reads any other content.
|
|
1738
|
-
perspective: ctx.instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
|
|
1739
|
-
});
|
|
1740
|
-
} else {
|
|
1741
|
-
const tree = groqJs.parse(groq, { params });
|
|
1742
|
-
value = await (await groqJs.evaluate(tree, { dataset: [ctx.instance], params, root: ctx.instance })).get();
|
|
1743
|
-
}
|
|
1744
|
-
return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
|
|
1745
|
-
}
|
|
1746
|
-
async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
|
|
1747
|
-
const out = [];
|
|
1748
|
-
for (const [name, groq] of Object.entries(sub.with ?? {})) {
|
|
1749
|
-
const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
|
|
1750
|
-
if (declared === void 0)
|
|
1751
|
-
throw new Error(
|
|
1752
|
-
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
|
|
1753
|
-
);
|
|
1754
|
-
const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
|
|
1755
|
-
parentResource: ctx.instance.workflowResource,
|
|
1756
|
-
discoveryResource,
|
|
1757
|
-
entryName: name,
|
|
1758
|
-
childName: childDefinition.name
|
|
1759
|
-
});
|
|
1760
|
-
value != null && out.push({
|
|
1761
|
-
type: declared.type,
|
|
1762
|
-
name,
|
|
1763
|
-
value
|
|
1764
|
-
});
|
|
1765
|
-
}
|
|
1766
|
-
return out;
|
|
1767
|
-
}
|
|
1768
|
-
async function resolveContextHandoff(ctx, sub, parentScope) {
|
|
1769
|
-
const out = {};
|
|
1770
|
-
for (const [name, groq] of Object.entries(sub.context ?? {})) {
|
|
1771
|
-
const v2 = await runGroq(groq, parentScope, ctx.snapshot);
|
|
1772
|
-
v2 != null && (typeof v2 == "string" || typeof v2 == "number" || typeof v2 == "boolean" || typeof v2 == "object" && "id" in v2 && "type" in v2) && (out[name] = v2);
|
|
1773
|
-
}
|
|
1774
|
-
return out;
|
|
1775
|
-
}
|
|
1776
|
-
function canonicaliseSpawnValue(kind, value, cx) {
|
|
1777
|
-
return kind === "doc.ref" ? coerceSpawnGdr(value, cx) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, cx)).filter((v2) => v2 !== null) : value;
|
|
1778
|
-
}
|
|
1779
|
-
function assertBareIdRootsCorrectly(id, cx) {
|
|
1780
|
-
if (!(cx.discoveryResource === void 0 || sameResource(cx.discoveryResource, cx.parentResource)))
|
|
1781
|
-
throw new Error(
|
|
1782
|
-
`subworkflows.with["${cx.entryName}"]: subworkflow "${cx.childName}" projected the bare id "${id}", which roots at the parent resource "${cx.parentResource.id}", but spawn discovery ran against "${cx.discoveryResource.id}". The child would reference a document that doesn't exist in the parent resource. Project an explicit GDR URI in the with-GROQ (e.g. "dataset:<projectId>:<dataset>:" + _id) so the child's subject points at the resource the discovered rows came from.`
|
|
1783
|
-
);
|
|
1784
|
-
}
|
|
1785
|
-
function coerceSpawnGdr(raw, cx) {
|
|
1786
|
-
const toUri = (id) => isGdrUri(id) ? id : (assertBareIdRootsCorrectly(id, cx), gdrFromResource(cx.parentResource, id));
|
|
1787
|
-
if (raw == null) return null;
|
|
1788
|
-
if (typeof raw == "object" && "id" in raw && "type" in raw) {
|
|
1789
|
-
const r = raw;
|
|
1790
|
-
if (typeof r.id == "string" && typeof r.type == "string")
|
|
1791
|
-
return { id: toUri(r.id), type: r.type };
|
|
1792
|
-
}
|
|
1793
|
-
if (typeof raw == "object" && "_ref" in raw) {
|
|
1794
|
-
const r = raw;
|
|
1795
|
-
if (typeof r._ref == "string") return { id: toUri(r._ref), type: "document" };
|
|
1796
|
-
}
|
|
1797
|
-
return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
|
|
1798
|
-
}
|
|
1799
|
-
async function resolveDefinitionRef(client, ref, tag) {
|
|
1800
|
-
const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
|
|
1801
|
-
return wantsExplicit && (params.version = ref.version), await client.fetch(
|
|
1802
|
-
definitionLookupGroq(wantsExplicit),
|
|
1803
|
-
params
|
|
1804
|
-
) ?? null;
|
|
1805
|
-
}
|
|
1806
|
-
async function prepareChildInstance(args) {
|
|
1807
|
-
const { client, parent, definition, initialFields, 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 = [
|
|
1808
|
-
...parent.ancestors,
|
|
1809
|
-
{
|
|
1810
|
-
id: selfGdr(parent),
|
|
1811
|
-
type: WORKFLOW_INSTANCE_TYPE
|
|
1812
|
-
}
|
|
1813
|
-
], inheritedPerspective = parent.perspective, childFields = await resolveDeclaredFields({
|
|
1814
|
-
entryDefs: definition.fields,
|
|
1815
|
-
initialFields,
|
|
1816
|
-
ctx: {
|
|
1817
|
-
client,
|
|
1818
|
-
now,
|
|
1819
|
-
selfId: childDocId,
|
|
1820
|
-
tag: childTag,
|
|
1821
|
-
workflowResource,
|
|
1822
|
-
definitionName: definition.name,
|
|
1823
|
-
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
|
|
1824
|
-
},
|
|
1825
|
-
randomKey
|
|
1826
|
-
}), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
|
|
1827
|
-
([key, value]) => {
|
|
1828
|
-
if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
|
|
1829
|
-
if (!isGdrUri(value.id))
|
|
1830
|
-
throw new Error(
|
|
1831
|
-
`subworkflows.context["${key}"]: GDR id must be a "<scheme>:..." URI, got ${JSON.stringify(value.id)}.`
|
|
1832
|
-
);
|
|
1833
|
-
return effectsContextEntry(key, { id: value.id, type: value.type });
|
|
1834
|
-
}
|
|
1835
|
-
return effectsContextEntry(key, value);
|
|
1836
|
-
}
|
|
1837
|
-
), base = buildInstanceBase({
|
|
1838
|
-
id: childDocId,
|
|
1839
|
-
now,
|
|
1840
|
-
tag: childTag,
|
|
1841
|
-
workflowResource,
|
|
1842
|
-
definitionName: definition.name,
|
|
1843
|
-
pinnedVersion: definition.version,
|
|
1844
|
-
definition,
|
|
1845
|
-
fields: childFields,
|
|
1846
|
-
effectsContext: effectsContextEntries,
|
|
1847
|
-
ancestors,
|
|
1848
|
-
perspective: childPerspective,
|
|
1849
|
-
initialStage: definition.initialStage,
|
|
1850
|
-
actor
|
|
1851
|
-
});
|
|
1852
|
-
return { ref: childRef, body: base };
|
|
1853
|
-
}
|
|
1854
|
-
async function subworkflowRows(ctx, refs) {
|
|
1855
|
-
if (refs.length === 0) return [];
|
|
1856
|
-
const ids = refs.map((r) => gdrToBareId(r.id));
|
|
1857
|
-
return (await ctx.client.fetch("*[_id in $ids]{_id, currentStage, completedAt}", { ids })).map((c) => ({
|
|
1858
|
-
_id: c._id,
|
|
1859
|
-
stage: c.currentStage,
|
|
1860
|
-
status: c.completedAt !== void 0 && c.completedAt !== null ? "done" : "active"
|
|
1861
|
-
}));
|
|
1862
|
-
}
|
|
1863
|
-
async function resolveBindings(args) {
|
|
1864
|
-
const resolved = {};
|
|
1865
|
-
for (const [key, groq] of Object.entries(args.bindings ?? {}))
|
|
1866
|
-
resolved[key] = await runGroq(groq, args.params, args.snapshot);
|
|
1867
|
-
return { ...resolved, ...args.staticInput };
|
|
1868
|
-
}
|
|
1869
|
-
async function persistThenDeploy(ctx, mutation, deploy) {
|
|
1870
|
-
const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
|
|
1871
|
-
await deployOrRollback({
|
|
1872
|
-
client: ctx.client,
|
|
1873
|
-
instanceId: ctx.instance._id,
|
|
1874
|
-
committedRev: committed._rev,
|
|
1875
|
-
restore: instanceStateFields(ctx.instance),
|
|
1876
|
-
unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
|
|
1877
|
-
reversible: !spawned,
|
|
1878
|
-
deploy
|
|
1879
|
-
});
|
|
1880
|
-
}
|
|
1881
|
-
async function refreshStageGuards(ctx, mutation, stageName) {
|
|
1882
|
-
await deployStageGuards({
|
|
1883
|
-
client: ctx.client,
|
|
1884
|
-
clientForGdr: ctx.clientForGdr,
|
|
1885
|
-
instance: materializeInstance(ctx.instance, mutation),
|
|
1886
|
-
definition: ctx.definition,
|
|
1887
|
-
stageName,
|
|
1888
|
-
now: ctx.now
|
|
1889
|
-
});
|
|
1890
|
-
}
|
|
1891
|
-
async function completeEffect(args) {
|
|
1892
|
-
const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1893
|
-
...options?.clock ? { clock: options.clock } : {},
|
|
1894
|
-
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
1895
|
-
});
|
|
1896
|
-
return commitCompleteEffect(
|
|
1897
|
-
ctx,
|
|
1898
|
-
effectKey,
|
|
1899
|
-
status,
|
|
1900
|
-
outputs,
|
|
1901
|
-
detail,
|
|
1902
|
-
error,
|
|
1903
|
-
durationMs,
|
|
1904
|
-
options?.actor
|
|
1905
|
-
);
|
|
1906
|
-
}
|
|
1907
|
-
function buildEffectHistoryEntry(pending, outcome) {
|
|
1908
|
-
const { status, ranAt, actor, detail, error, durationMs, outputs } = outcome, resolvedActor = actor ?? pending.actor;
|
|
1661
|
+
function buildEffectHistoryEntry(pending, outcome) {
|
|
1662
|
+
const { status, ranAt, actor, detail, error, durationMs, outputs } = outcome, resolvedActor = actor ?? pending.actor;
|
|
1909
1663
|
return {
|
|
1910
1664
|
_key: pending._key,
|
|
1911
1665
|
name: pending.name,
|
|
@@ -1978,7 +1732,7 @@ function buildQueuedEffect(effect, origin, params, actor, now) {
|
|
|
1978
1732
|
async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
|
|
1979
1733
|
if (!effects || effects.length === 0) return;
|
|
1980
1734
|
const now = ctx.now, liveCtx = { ...ctx, instance: materializeInstance(ctx.instance, mutation) }, params = await ctxConditionParams(liveCtx, {
|
|
1981
|
-
...opts?.
|
|
1735
|
+
...opts?.activityName !== void 0 ? { activityName: opts.activityName } : {},
|
|
1982
1736
|
...actor !== void 0 ? { actor } : {},
|
|
1983
1737
|
// Caller-supplied action params, readable in bindings as `$params.<name>`.
|
|
1984
1738
|
vars: { params: opts?.callerParams ?? {} }
|
|
@@ -1993,18 +1747,74 @@ async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
|
|
|
1993
1747
|
mutation.pendingEffects.push(pending), mutation.history.push(history);
|
|
1994
1748
|
}
|
|
1995
1749
|
}
|
|
1996
|
-
function
|
|
1750
|
+
function fieldQueryDiscardedEntry(args) {
|
|
1751
|
+
return {
|
|
1752
|
+
_key: randomKey(),
|
|
1753
|
+
_type: "fieldQueryDiscarded",
|
|
1754
|
+
at: args.at,
|
|
1755
|
+
scope: args.scope,
|
|
1756
|
+
field: args.field,
|
|
1757
|
+
detail: args.detail
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
function recordFieldDiscards(target, scope, at) {
|
|
1761
|
+
return (discard) => target.push(fieldQueryDiscardedEntry({ scope, field: discard.field, detail: discard.detail, at }));
|
|
1762
|
+
}
|
|
1763
|
+
function applyActivityStatusChange(args) {
|
|
1764
|
+
const { entry, history, stage, to, at, actor } = args, from = entry.status;
|
|
1765
|
+
entry.status = to, schema.isTerminalActivityStatus(to) && (entry.completedAt = at, actor !== void 0 && (entry.completedBy = actor.id)), history.push({
|
|
1766
|
+
_key: randomKey(),
|
|
1767
|
+
_type: "activityStatusChanged",
|
|
1768
|
+
at,
|
|
1769
|
+
stage,
|
|
1770
|
+
activity: entry.name,
|
|
1771
|
+
from,
|
|
1772
|
+
to,
|
|
1773
|
+
...actor !== void 0 ? { actor } : {}
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
function stageTransitionHistory(args) {
|
|
1777
|
+
const { fromStage, toStage, at, transition, actor, via, reason } = args, shared = {
|
|
1778
|
+
at,
|
|
1779
|
+
...transition !== void 0 ? { transition } : {},
|
|
1780
|
+
...actor !== void 0 ? { actor } : {},
|
|
1781
|
+
...via !== void 0 ? { via } : {},
|
|
1782
|
+
...reason !== void 0 ? { reason } : {}
|
|
1783
|
+
};
|
|
1784
|
+
return [
|
|
1785
|
+
{
|
|
1786
|
+
_key: randomKey(),
|
|
1787
|
+
_type: "stageExited",
|
|
1788
|
+
stage: fromStage,
|
|
1789
|
+
toStage,
|
|
1790
|
+
...shared
|
|
1791
|
+
},
|
|
1792
|
+
{
|
|
1793
|
+
_key: randomKey(),
|
|
1794
|
+
_type: "transitionFired",
|
|
1795
|
+
fromStage,
|
|
1796
|
+
toStage,
|
|
1797
|
+
...shared
|
|
1798
|
+
},
|
|
1799
|
+
{
|
|
1800
|
+
_key: randomKey(),
|
|
1801
|
+
_type: "stageEntered",
|
|
1802
|
+
stage: toStage,
|
|
1803
|
+
fromStage,
|
|
1804
|
+
...shared
|
|
1805
|
+
}
|
|
1806
|
+
];
|
|
1807
|
+
}
|
|
1808
|
+
function resolveStaticValueExpr(src, ctx) {
|
|
1997
1809
|
switch (src.type) {
|
|
1998
1810
|
case "literal":
|
|
1999
|
-
return
|
|
1811
|
+
return src.value;
|
|
2000
1812
|
case "param":
|
|
2001
|
-
return
|
|
1813
|
+
return ctx.params?.[src.param];
|
|
2002
1814
|
case "actor":
|
|
2003
|
-
return
|
|
1815
|
+
return ctx.actor;
|
|
2004
1816
|
case "now":
|
|
2005
|
-
return
|
|
2006
|
-
default:
|
|
2007
|
-
return { handled: !1 };
|
|
1817
|
+
return ctx.now;
|
|
2008
1818
|
}
|
|
2009
1819
|
}
|
|
2010
1820
|
const FIELD_OP_TYPES = [
|
|
@@ -2017,7 +1827,7 @@ const FIELD_OP_TYPES = [
|
|
|
2017
1827
|
function isFieldOp(summary) {
|
|
2018
1828
|
return FIELD_OP_TYPES.includes(summary.opType);
|
|
2019
1829
|
}
|
|
2020
|
-
function validateActionParams(action,
|
|
1830
|
+
function validateActionParams(action, activityName, callerParams) {
|
|
2021
1831
|
const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
|
|
2022
1832
|
for (const decl of declared) {
|
|
2023
1833
|
const value = params[decl.name], present = decl.name in params && value !== void 0 && value !== null;
|
|
@@ -2031,7 +1841,7 @@ function validateActionParams(action, taskName, callerParams) {
|
|
|
2031
1841
|
}));
|
|
2032
1842
|
}
|
|
2033
1843
|
if (issues.length > 0)
|
|
2034
|
-
throw new ActionParamsInvalidError({ action: action.name,
|
|
1844
|
+
throw new ActionParamsInvalidError({ action: action.name, activity: activityName, issues });
|
|
2035
1845
|
return params;
|
|
2036
1846
|
}
|
|
2037
1847
|
function hasStringField(value, field) {
|
|
@@ -2064,7 +1874,15 @@ function runOps(args) {
|
|
|
2064
1874
|
if (ops === void 0 || ops.length === 0) return [];
|
|
2065
1875
|
const summaries = [];
|
|
2066
1876
|
for (const op of ops) {
|
|
2067
|
-
const summary = applyOp(op, {
|
|
1877
|
+
const summary = applyOp(op, {
|
|
1878
|
+
mutation,
|
|
1879
|
+
stage,
|
|
1880
|
+
activityName: origin.activity,
|
|
1881
|
+
params,
|
|
1882
|
+
actor,
|
|
1883
|
+
self,
|
|
1884
|
+
now
|
|
1885
|
+
});
|
|
2068
1886
|
summaries.push(summary), mutation.history.push(opAppliedEntry({ origin, summary, stage, actor, now }));
|
|
2069
1887
|
}
|
|
2070
1888
|
return summaries;
|
|
@@ -2076,7 +1894,7 @@ function opAppliedEntry(args) {
|
|
|
2076
1894
|
_type: "opApplied",
|
|
2077
1895
|
at: now,
|
|
2078
1896
|
stage,
|
|
2079
|
-
...origin.
|
|
1897
|
+
...origin.activity !== void 0 ? { activity: origin.activity } : {},
|
|
2080
1898
|
...origin.action !== void 0 ? { action: origin.action } : {},
|
|
2081
1899
|
...origin.transition !== void 0 ? { transition: origin.transition } : {},
|
|
2082
1900
|
...origin.edit === !0 ? { edit: !0 } : {},
|
|
@@ -2103,13 +1921,15 @@ function applyOp(op, ctx) {
|
|
|
2103
1921
|
}
|
|
2104
1922
|
}
|
|
2105
1923
|
function applyFieldSet(op, ctx) {
|
|
2106
|
-
const value =
|
|
2107
|
-
return validateFieldValue({ entryType: entry._type, entryName: entry.name, value }), setEntryValue(entry, value), { opType: op.type, target: op.target, resolved: { value } };
|
|
1924
|
+
const value = resolveOpValue(op.value, ctx), entry = locateEntry(ctx, op.target);
|
|
1925
|
+
return validateFieldValue({ entryType: entry._type, entryName: entry.name, value, ...entryShape(entry) }), setEntryValue(entry, value), { opType: op.type, target: op.target, resolved: { value } };
|
|
1926
|
+
}
|
|
1927
|
+
function entryShape(entry) {
|
|
1928
|
+
return entry._type === "object" ? { fields: entry.fields } : entry._type === "array" ? { of: entry.of } : {};
|
|
2108
1929
|
}
|
|
2109
1930
|
const EMPTY_BY_KIND = {
|
|
2110
1931
|
"doc.refs": [],
|
|
2111
|
-
|
|
2112
|
-
notes: [],
|
|
1932
|
+
array: [],
|
|
2113
1933
|
assignees: []
|
|
2114
1934
|
};
|
|
2115
1935
|
function applyFieldUnset(op, ctx) {
|
|
@@ -2117,8 +1937,13 @@ function applyFieldUnset(op, ctx) {
|
|
|
2117
1937
|
return setEntryValue(entry, EMPTY_BY_KIND[entry._type] ?? null), { opType: op.type, target: op.target };
|
|
2118
1938
|
}
|
|
2119
1939
|
function applyFieldAppend(op, ctx) {
|
|
2120
|
-
const item =
|
|
2121
|
-
validateFieldAppendItem({
|
|
1940
|
+
const item = resolveOpValue(op.value, ctx), entry = locateEntry(ctx, op.target);
|
|
1941
|
+
validateFieldAppendItem({
|
|
1942
|
+
entryType: entry._type,
|
|
1943
|
+
entryName: entry.name,
|
|
1944
|
+
item,
|
|
1945
|
+
of: entry._type === "array" ? entry.of : void 0
|
|
1946
|
+
});
|
|
2122
1947
|
const current = Array.isArray(entry.value) ? entry.value : [];
|
|
2123
1948
|
return setEntryValue(entry, [...current, withRowKey(item)]), { opType: op.type, target: op.target, resolved: { item } };
|
|
2124
1949
|
}
|
|
@@ -2126,7 +1951,7 @@ function withRowKey(item) {
|
|
|
2126
1951
|
return typeof item == "object" && item !== null && !Array.isArray(item) && !("_key" in item) ? { _key: randomKey(), ...item } : item;
|
|
2127
1952
|
}
|
|
2128
1953
|
function applyFieldUpdateWhere(op, ctx) {
|
|
2129
|
-
const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op), merge =
|
|
1954
|
+
const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op), merge = resolveOpValue(op.value, ctx);
|
|
2130
1955
|
if (merge === null || typeof merge != "object" || Array.isArray(merge))
|
|
2131
1956
|
throw new Error(
|
|
2132
1957
|
`field.updateWhere value must resolve to an object of fields to merge (target "${op.target.field}")`
|
|
@@ -2152,145 +1977,385 @@ function requireArrayValue(entry, op) {
|
|
|
2152
1977
|
);
|
|
2153
1978
|
return entry.value;
|
|
2154
1979
|
}
|
|
2155
|
-
function applyStatusSet(op, ctx) {
|
|
2156
|
-
const entry = findOpenStageEntry(ctx.mutation)?.
|
|
2157
|
-
if (entry === void 0)
|
|
2158
|
-
throw new Error(
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
1980
|
+
function applyStatusSet(op, ctx) {
|
|
1981
|
+
const entry = findOpenStageEntry(ctx.mutation)?.activities.find((t) => t.name === op.activity);
|
|
1982
|
+
if (entry === void 0)
|
|
1983
|
+
throw new Error(
|
|
1984
|
+
`status.set targets activity "${op.activity}" which has no entry in the open stage`
|
|
1985
|
+
);
|
|
1986
|
+
return applyActivityStatusChange({
|
|
1987
|
+
entry,
|
|
1988
|
+
history: ctx.mutation.history,
|
|
1989
|
+
stage: ctx.stage,
|
|
1990
|
+
to: op.status,
|
|
1991
|
+
at: ctx.now,
|
|
1992
|
+
...ctx.actor !== void 0 ? { actor: ctx.actor } : {}
|
|
1993
|
+
}), { opType: op.type, resolved: { activity: op.activity, status: op.status } };
|
|
1994
|
+
}
|
|
1995
|
+
function setEntryValue(entry, value) {
|
|
1996
|
+
entry.value = value;
|
|
1997
|
+
}
|
|
1998
|
+
function locateEntry(ctx, target) {
|
|
1999
|
+
const entry = fieldHost(ctx, target.scope)?.find((s) => s.name === target.field);
|
|
2000
|
+
if (entry === void 0)
|
|
2001
|
+
throw new Error(
|
|
2002
|
+
`Op target ${target.scope}:"${target.field}" has no resolved field entry on this instance \u2014 the deployed definition and the instance state have diverged`
|
|
2003
|
+
);
|
|
2004
|
+
return entry;
|
|
2005
|
+
}
|
|
2006
|
+
function fieldHost(ctx, scope) {
|
|
2007
|
+
if (scope === "workflow") return ctx.mutation.fields;
|
|
2008
|
+
const stageEntry = findOpenStageEntry(ctx.mutation);
|
|
2009
|
+
if (scope === "stage") return stageEntry?.fields;
|
|
2010
|
+
const activity = stageEntry?.activities.find((t) => t.name === ctx.activityName);
|
|
2011
|
+
return activity !== void 0 && activity.fields === void 0 && (activity.fields = []), activity?.fields;
|
|
2012
|
+
}
|
|
2013
|
+
function resolveOpValue(src, ctx) {
|
|
2014
|
+
switch (src.type) {
|
|
2015
|
+
case "literal":
|
|
2016
|
+
case "param":
|
|
2017
|
+
case "actor":
|
|
2018
|
+
case "now":
|
|
2019
|
+
return resolveStaticValueExpr(src, ctx);
|
|
2020
|
+
case "self":
|
|
2021
|
+
return ctx.self;
|
|
2022
|
+
case "stage":
|
|
2023
|
+
return ctx.stage;
|
|
2024
|
+
case "fieldRead": {
|
|
2025
|
+
const value = readEntryFromMutation(ctx, src);
|
|
2026
|
+
return src.path !== void 0 ? getPath(value, src.path) : value;
|
|
2027
|
+
}
|
|
2028
|
+
case "object": {
|
|
2029
|
+
const out = {};
|
|
2030
|
+
for (const [field, fieldSrc] of Object.entries(src.fields))
|
|
2031
|
+
out[field] = resolveOpValue(fieldSrc, ctx);
|
|
2032
|
+
return out;
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
function entryValue(entries, name) {
|
|
2037
|
+
return entries?.find((s) => s.name === name)?.value;
|
|
2038
|
+
}
|
|
2039
|
+
function readEntryFromMutation(ctx, src) {
|
|
2040
|
+
const scopes = src.scope !== void 0 ? [src.scope] : ["stage", "workflow"], stageEntry = findOpenStageEntry(ctx.mutation);
|
|
2041
|
+
for (const scope of scopes) {
|
|
2042
|
+
const host = scope === "workflow" ? ctx.mutation.fields : stageEntry?.fields, value = entryValue(host, src.field);
|
|
2043
|
+
if (value !== void 0) return value;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
function evalOpPredicate(p, row, ctx) {
|
|
2047
|
+
switch (p.type) {
|
|
2048
|
+
case "all":
|
|
2049
|
+
return p.of.every((sub) => evalOpPredicate(sub, row, ctx));
|
|
2050
|
+
case "any":
|
|
2051
|
+
return p.of.some((sub) => evalOpPredicate(sub, row, ctx));
|
|
2052
|
+
case "field":
|
|
2053
|
+
return row[p.field] === resolveOpValue(p.equals, ctx);
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
2057
|
+
function validateTag(tag) {
|
|
2058
|
+
if (!TAG_RE.test(tag))
|
|
2059
|
+
throw new Error(
|
|
2060
|
+
`tag: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
|
|
2061
|
+
);
|
|
2062
|
+
}
|
|
2063
|
+
function tagScopeFilter() {
|
|
2064
|
+
return "tag == $tag";
|
|
2065
|
+
}
|
|
2066
|
+
function definitionLookupGroq(explicit) {
|
|
2067
|
+
const scoped = `_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}`;
|
|
2068
|
+
return explicit ? `*[${scoped} && version == $version][0]` : `*[${scoped}] | order(version desc)[0]`;
|
|
2069
|
+
}
|
|
2070
|
+
async function discoverItems(args) {
|
|
2071
|
+
const { client, groq, params, perspective } = args, fetchOptions = perspective !== void 0 ? { perspective } : void 0;
|
|
2072
|
+
return client.fetch(groq, paramsForLake(params), fetchOptions);
|
|
2073
|
+
}
|
|
2074
|
+
function clientForDiscovery(defaultClient, clientForGdr, subjectGdrUri) {
|
|
2075
|
+
if (!subjectGdrUri || !subjectGdrUri.includes(":")) return defaultClient;
|
|
2076
|
+
try {
|
|
2077
|
+
return clientForGdr(parseGdr(subjectGdrUri));
|
|
2078
|
+
} catch {
|
|
2079
|
+
return defaultClient;
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
const ENGINE_ACTOR_ID_PREFIX = "engine.";
|
|
2083
|
+
function engineSystemActor(name) {
|
|
2084
|
+
return { kind: "system", id: `${ENGINE_ACTOR_ID_PREFIX}${name}` };
|
|
2085
|
+
}
|
|
2086
|
+
function isEngineActor(actor) {
|
|
2087
|
+
return actor.kind === "system" && actor.id.startsWith(ENGINE_ACTOR_ID_PREFIX);
|
|
2088
|
+
}
|
|
2089
|
+
const MAX_SPAWN_DEPTH = 6, SUBWORKFLOWS_ALL_DONE = "count($subworkflows[status != 'done']) == 0";
|
|
2090
|
+
function gateActor(outcome) {
|
|
2091
|
+
return engineSystemActor(outcome === "failed" ? "failWhen" : "completeWhen");
|
|
2092
|
+
}
|
|
2093
|
+
function effectiveCompleteWhen(activity) {
|
|
2094
|
+
return activity.completeWhen ?? (activity.subworkflows !== void 0 ? SUBWORKFLOWS_ALL_DONE : void 0);
|
|
2095
|
+
}
|
|
2096
|
+
async function evaluateResolutionGate(ctx, activity, vars) {
|
|
2097
|
+
const scope = { activityName: activity.name, vars };
|
|
2098
|
+
if (activity.failWhen !== void 0 && await ctxEvaluateCondition(ctx, activity.failWhen, scope))
|
|
2099
|
+
return "failed";
|
|
2100
|
+
const completeWhen = effectiveCompleteWhen(activity);
|
|
2101
|
+
if (completeWhen !== void 0 && await ctxEvaluateCondition(ctx, completeWhen, scope))
|
|
2102
|
+
return "done";
|
|
2103
|
+
}
|
|
2104
|
+
async function spawnSubworkflows(ctx, mutation, activity, sub, actor) {
|
|
2105
|
+
if (ctx.instance.ancestors.length + 1 > MAX_SPAWN_DEPTH) {
|
|
2106
|
+
const chain = [...ctx.instance.ancestors.map((a) => a.id), selfGdr(ctx.instance)].join(" \u2192 ");
|
|
2107
|
+
throw new Error(
|
|
2108
|
+
`Subworkflow depth limit (${MAX_SPAWN_DEPTH}) exceeded on activity "${activity.name}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
|
|
2109
|
+
);
|
|
2110
|
+
}
|
|
2111
|
+
const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tag);
|
|
2112
|
+
if (definition === null) {
|
|
2113
|
+
const versionLabel = sub.definition.version === void 0 || sub.definition.version === "latest" ? "latest" : `v${sub.definition.version}`;
|
|
2114
|
+
throw new Error(
|
|
2115
|
+
`Subworkflow definition "${sub.definition.name}" ${versionLabel} not deployed (parent ${ctx.instance._id}, activity ${activity.name})`
|
|
2116
|
+
);
|
|
2117
|
+
}
|
|
2118
|
+
const parentScope = await ctxConditionParams(ctx, {
|
|
2119
|
+
activityName: activity.name,
|
|
2120
|
+
...actor ? { actor } : {}
|
|
2121
|
+
}), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
|
|
2122
|
+
for (const row of rows) {
|
|
2123
|
+
const initialFields = await projectRowFields(
|
|
2124
|
+
ctx,
|
|
2125
|
+
sub,
|
|
2126
|
+
definition,
|
|
2127
|
+
parentScope,
|
|
2128
|
+
row,
|
|
2129
|
+
discoveryResource
|
|
2130
|
+
), { ref, body } = await prepareChildInstance({
|
|
2131
|
+
client: ctx.client,
|
|
2132
|
+
parent: ctx.instance,
|
|
2133
|
+
definition,
|
|
2134
|
+
initialFields,
|
|
2135
|
+
effectsContext,
|
|
2136
|
+
actor,
|
|
2137
|
+
now
|
|
2138
|
+
});
|
|
2139
|
+
refs.push(ref), mutation.pendingCreates.push(body);
|
|
2140
|
+
}
|
|
2141
|
+
return refs;
|
|
2142
|
+
}
|
|
2143
|
+
async function resolveSpawnRows(ctx, sub) {
|
|
2144
|
+
const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
|
|
2145
|
+
let value, discoveryResource;
|
|
2146
|
+
if (groq.includes("*")) {
|
|
2147
|
+
const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
|
|
2148
|
+
client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
|
|
2149
|
+
client,
|
|
2150
|
+
groq,
|
|
2151
|
+
params,
|
|
2152
|
+
// Draft-aware by default (drafts overlay published) when there's no
|
|
2153
|
+
// release, so a `forEach` discovers in-flight items the same way the
|
|
2154
|
+
// engine reads any other content.
|
|
2155
|
+
perspective: ctx.instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
|
|
2156
|
+
});
|
|
2157
|
+
} else {
|
|
2158
|
+
const tree = groqJs.parse(groq, { params });
|
|
2159
|
+
value = await (await groqJs.evaluate(tree, { dataset: [ctx.instance], params, root: ctx.instance })).get();
|
|
2160
|
+
}
|
|
2161
|
+
return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
|
|
2162
|
+
}
|
|
2163
|
+
async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
|
|
2164
|
+
const out = [];
|
|
2165
|
+
for (const [name, groq] of Object.entries(sub.with ?? {})) {
|
|
2166
|
+
const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
|
|
2167
|
+
if (declared === void 0)
|
|
2168
|
+
throw new Error(
|
|
2169
|
+
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
|
|
2170
|
+
);
|
|
2171
|
+
const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
|
|
2172
|
+
parentResource: ctx.instance.workflowResource,
|
|
2173
|
+
discoveryResource,
|
|
2174
|
+
entryName: name,
|
|
2175
|
+
childName: childDefinition.name
|
|
2176
|
+
});
|
|
2177
|
+
value != null && out.push({
|
|
2178
|
+
type: declared.type,
|
|
2179
|
+
name,
|
|
2180
|
+
value
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
2183
|
+
return out;
|
|
2184
|
+
}
|
|
2185
|
+
async function resolveContextHandoff(ctx, sub, parentScope) {
|
|
2186
|
+
const out = {};
|
|
2187
|
+
for (const [name, groq] of Object.entries(sub.context ?? {})) {
|
|
2188
|
+
const v2 = await runGroq(groq, parentScope, ctx.snapshot);
|
|
2189
|
+
v2 != null && (typeof v2 == "string" || typeof v2 == "number" || typeof v2 == "boolean" || typeof v2 == "object" && "id" in v2 && "type" in v2) && (out[name] = v2);
|
|
2190
|
+
}
|
|
2191
|
+
return out;
|
|
2167
2192
|
}
|
|
2168
|
-
function
|
|
2169
|
-
|
|
2193
|
+
function canonicaliseSpawnValue(kind, value, cx) {
|
|
2194
|
+
return kind === "doc.ref" ? coerceSpawnGdr(value, cx) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, cx)).filter((v2) => v2 !== null) : value;
|
|
2170
2195
|
}
|
|
2171
|
-
function
|
|
2172
|
-
|
|
2173
|
-
if (entry === void 0)
|
|
2196
|
+
function assertBareIdRootsCorrectly(id, cx) {
|
|
2197
|
+
if (!(cx.discoveryResource === void 0 || sameResource(cx.discoveryResource, cx.parentResource)))
|
|
2174
2198
|
throw new Error(
|
|
2175
|
-
`
|
|
2199
|
+
`subworkflows.with["${cx.entryName}"]: subworkflow "${cx.childName}" projected the bare id "${id}", which roots at the parent resource "${cx.parentResource.id}", but spawn discovery ran against "${cx.discoveryResource.id}". The child would reference a document that doesn't exist in the parent resource. Project an explicit GDR URI in the with-GROQ (e.g. "dataset:<projectId>:<dataset>:" + _id) so the child's subject points at the resource the discovered rows came from.`
|
|
2176
2200
|
);
|
|
2177
|
-
return entry;
|
|
2178
2201
|
}
|
|
2179
|
-
function
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
if (
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
}
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
case "self":
|
|
2191
|
-
return ctx.self;
|
|
2192
|
-
case "stage":
|
|
2193
|
-
return ctx.stage;
|
|
2194
|
-
case "fieldRead": {
|
|
2195
|
-
const value = readEntryFromMutation(ctx, src);
|
|
2196
|
-
return src.path !== void 0 ? getPath(value, src.path) : value;
|
|
2197
|
-
}
|
|
2198
|
-
case "object": {
|
|
2199
|
-
const out = {};
|
|
2200
|
-
for (const [field, fieldSrc] of Object.entries(src.fields))
|
|
2201
|
-
out[field] = resolveOpSource(fieldSrc, ctx);
|
|
2202
|
-
return out;
|
|
2203
|
-
}
|
|
2204
|
-
default:
|
|
2205
|
-
throw new Error(`Source type "${src.type}" is a field-entry origin, not a value`);
|
|
2202
|
+
function coerceSpawnGdr(raw, cx) {
|
|
2203
|
+
const toUri = (id) => isGdrUri(id) ? id : (assertBareIdRootsCorrectly(id, cx), gdrFromResource(cx.parentResource, id));
|
|
2204
|
+
if (raw == null) return null;
|
|
2205
|
+
if (typeof raw == "object" && "id" in raw && "type" in raw) {
|
|
2206
|
+
const r = raw;
|
|
2207
|
+
if (typeof r.id == "string" && typeof r.type == "string")
|
|
2208
|
+
return { id: toUri(r.id), type: r.type };
|
|
2209
|
+
}
|
|
2210
|
+
if (typeof raw == "object" && "_ref" in raw) {
|
|
2211
|
+
const r = raw;
|
|
2212
|
+
if (typeof r._ref == "string") return { id: toUri(r._ref), type: "document" };
|
|
2206
2213
|
}
|
|
2214
|
+
return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
|
|
2207
2215
|
}
|
|
2208
|
-
function
|
|
2209
|
-
|
|
2216
|
+
async function resolveDefinitionRef(client, ref, tag) {
|
|
2217
|
+
const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
|
|
2218
|
+
return wantsExplicit && (params.version = ref.version), await client.fetch(
|
|
2219
|
+
definitionLookupGroq(wantsExplicit),
|
|
2220
|
+
params
|
|
2221
|
+
) ?? null;
|
|
2210
2222
|
}
|
|
2211
|
-
function
|
|
2212
|
-
const
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2223
|
+
async function prepareChildInstance(args) {
|
|
2224
|
+
const { client, parent, definition, initialFields, 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 = [
|
|
2225
|
+
...parent.ancestors,
|
|
2226
|
+
{
|
|
2227
|
+
id: selfGdr(parent),
|
|
2228
|
+
type: WORKFLOW_INSTANCE_TYPE
|
|
2229
|
+
}
|
|
2230
|
+
], inheritedPerspective = parent.perspective, fieldDiscards = [], childFields = await resolveDeclaredFields({
|
|
2231
|
+
entryDefs: definition.fields,
|
|
2232
|
+
initialFields,
|
|
2233
|
+
ctx: {
|
|
2234
|
+
client,
|
|
2235
|
+
now,
|
|
2236
|
+
selfId: childDocId,
|
|
2237
|
+
tag: childTag,
|
|
2238
|
+
workflowResource,
|
|
2239
|
+
definitionName: definition.name,
|
|
2240
|
+
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {},
|
|
2241
|
+
recordDiscard: recordFieldDiscards(fieldDiscards, "workflow", now)
|
|
2242
|
+
},
|
|
2243
|
+
randomKey
|
|
2244
|
+
}), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
|
|
2245
|
+
([key, value]) => {
|
|
2246
|
+
if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
|
|
2247
|
+
if (!isGdrUri(value.id))
|
|
2248
|
+
throw new Error(
|
|
2249
|
+
`subworkflows.context["${key}"]: GDR id must be a "<scheme>:..." URI, got ${JSON.stringify(value.id)}.`
|
|
2250
|
+
);
|
|
2251
|
+
return effectsContextEntry(key, { id: value.id, type: value.type });
|
|
2252
|
+
}
|
|
2253
|
+
return effectsContextEntry(key, value);
|
|
2254
|
+
}
|
|
2255
|
+
), base = buildInstanceBase({
|
|
2256
|
+
id: childDocId,
|
|
2257
|
+
now,
|
|
2258
|
+
tag: childTag,
|
|
2259
|
+
workflowResource,
|
|
2260
|
+
definitionName: definition.name,
|
|
2261
|
+
pinnedVersion: definition.version,
|
|
2262
|
+
pinnedContentHash: definition.contentHash,
|
|
2263
|
+
definition,
|
|
2264
|
+
fields: childFields,
|
|
2265
|
+
effectsContext: effectsContextEntries,
|
|
2266
|
+
ancestors,
|
|
2267
|
+
perspective: childPerspective,
|
|
2268
|
+
initialStage: definition.initialStage,
|
|
2269
|
+
actor,
|
|
2270
|
+
...fieldDiscards.length > 0 ? { extraHistory: fieldDiscards } : {}
|
|
2271
|
+
});
|
|
2272
|
+
return { ref: childRef, body: base };
|
|
2217
2273
|
}
|
|
2218
|
-
function
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
}
|
|
2274
|
+
async function subworkflowRows(ctx, refs) {
|
|
2275
|
+
if (refs.length === 0) return [];
|
|
2276
|
+
const ids = refs.map((r) => gdrToBareId(r.id));
|
|
2277
|
+
return (await ctx.client.fetch("*[_id in $ids]{_id, currentStage, completedAt}", { ids })).map((c) => ({
|
|
2278
|
+
_id: c._id,
|
|
2279
|
+
stage: c.currentStage,
|
|
2280
|
+
status: c.completedAt !== void 0 && c.completedAt !== null ? "done" : "active"
|
|
2281
|
+
}));
|
|
2227
2282
|
}
|
|
2228
|
-
async function
|
|
2229
|
-
const { client, instanceId,
|
|
2283
|
+
async function invokeActivity(args) {
|
|
2284
|
+
const { client, instanceId, activity: activityName, options } = args, ctx = await loadContext(client, instanceId, {
|
|
2230
2285
|
...options?.clock ? { clock: options.clock } : {},
|
|
2231
2286
|
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
2232
2287
|
});
|
|
2233
|
-
return commitInvoke(ctx,
|
|
2288
|
+
return commitInvoke(ctx, activityName, options?.actor);
|
|
2234
2289
|
}
|
|
2235
|
-
async function commitInvoke(ctx,
|
|
2236
|
-
const {
|
|
2290
|
+
async function commitInvoke(ctx, activityName, actor) {
|
|
2291
|
+
const { activity } = findActivityInCurrentStage(ctx, activityName), entry = findCurrentActivities(ctx.instance).find((t) => t.name === activityName);
|
|
2237
2292
|
if (entry === void 0)
|
|
2238
|
-
throw new Error(
|
|
2293
|
+
throw new Error(
|
|
2294
|
+
`Activity "${activityName}" has no status entry on instance ${ctx.instance._id}`
|
|
2295
|
+
);
|
|
2239
2296
|
if (entry.status !== "pending")
|
|
2240
|
-
return { invoked: !1,
|
|
2241
|
-
const mutation = startMutation(ctx.instance), mutEntry =
|
|
2242
|
-
return await
|
|
2297
|
+
return { invoked: !1, activity: activityName };
|
|
2298
|
+
const mutation = startMutation(ctx.instance), mutEntry = requireMutationActivityEntry(mutation, activityName);
|
|
2299
|
+
return await activateActivity(ctx, mutation, activity, mutEntry, actor), await persist(ctx, mutation), { invoked: !0, activity: activityName };
|
|
2243
2300
|
}
|
|
2244
|
-
async function
|
|
2301
|
+
async function activateActivity(ctx, mutation, activity, entry, actor) {
|
|
2245
2302
|
const now = ctx.now;
|
|
2246
|
-
if (entry.status = "active", entry.startedAt = now,
|
|
2303
|
+
if (entry.status = "active", entry.startedAt = now, activity.fields !== void 0 && activity.fields.length > 0) {
|
|
2247
2304
|
const stage = findStage(ctx.definition, mutation.currentStage);
|
|
2248
|
-
entry.fields = await
|
|
2305
|
+
entry.fields = await resolveActivityFieldEntries({
|
|
2249
2306
|
client: ctx.client,
|
|
2250
2307
|
instance: ctx.instance,
|
|
2251
2308
|
stage,
|
|
2252
|
-
|
|
2253
|
-
now
|
|
2309
|
+
activity,
|
|
2310
|
+
now,
|
|
2311
|
+
recordDiscard: recordFieldDiscards(mutation.history, "activity", now)
|
|
2254
2312
|
});
|
|
2255
2313
|
}
|
|
2256
2314
|
runOps({
|
|
2257
|
-
ops:
|
|
2315
|
+
ops: activity.ops,
|
|
2258
2316
|
mutation,
|
|
2259
2317
|
stage: mutation.currentStage,
|
|
2260
|
-
origin: {
|
|
2318
|
+
origin: { activity: activity.name },
|
|
2261
2319
|
params: {},
|
|
2262
2320
|
actor,
|
|
2263
2321
|
self: selfGdr(ctx.instance),
|
|
2264
2322
|
now
|
|
2265
2323
|
}), mutation.history.push({
|
|
2266
2324
|
_key: randomKey(),
|
|
2267
|
-
_type: "
|
|
2325
|
+
_type: "activityActivated",
|
|
2268
2326
|
at: now,
|
|
2269
2327
|
stage: mutation.currentStage,
|
|
2270
|
-
|
|
2328
|
+
activity: activity.name,
|
|
2271
2329
|
...actor !== void 0 ? { actor } : {}
|
|
2272
|
-
}), await queueEffects(
|
|
2273
|
-
|
|
2274
|
-
|
|
2330
|
+
}), await queueEffects(
|
|
2331
|
+
ctx,
|
|
2332
|
+
mutation,
|
|
2333
|
+
activity.effects,
|
|
2334
|
+
{ kind: "activity", name: activity.name },
|
|
2335
|
+
actor,
|
|
2336
|
+
{
|
|
2337
|
+
activityName: activity.name
|
|
2338
|
+
}
|
|
2339
|
+
);
|
|
2275
2340
|
let pendingChildren;
|
|
2276
|
-
if (
|
|
2277
|
-
const createsBefore = mutation.pendingCreates.length, refs = await spawnSubworkflows(ctx, mutation,
|
|
2341
|
+
if (activity.subworkflows !== void 0) {
|
|
2342
|
+
const createsBefore = mutation.pendingCreates.length, refs = await spawnSubworkflows(ctx, mutation, activity, activity.subworkflows, actor);
|
|
2278
2343
|
entry.spawnedInstances = refs, pendingChildren = mutation.pendingCreates.slice(createsBefore).map((c) => ({ _id: c._id, stage: c.currentStage, status: "active" }));
|
|
2279
2344
|
for (const ref of refs)
|
|
2280
2345
|
mutation.history.push({
|
|
2281
2346
|
_key: randomKey(),
|
|
2282
2347
|
_type: "spawned",
|
|
2283
2348
|
at: now,
|
|
2284
|
-
|
|
2349
|
+
activity: activity.name,
|
|
2285
2350
|
instanceRef: ref
|
|
2286
2351
|
});
|
|
2287
2352
|
}
|
|
2288
|
-
await autoResolveOnActivate(ctx, mutation,
|
|
2353
|
+
await autoResolveOnActivate(ctx, mutation, activity, entry, now, pendingChildren) || resolveMachineStep(mutation, activity, entry, now);
|
|
2289
2354
|
}
|
|
2290
|
-
async function autoResolveOnActivate(ctx, mutation,
|
|
2291
|
-
if (
|
|
2292
|
-
const vars = pendingChildren !== void 0 ? { subworkflows: pendingChildren } : await
|
|
2293
|
-
return outcome === void 0 ? !1 : (
|
|
2355
|
+
async function autoResolveOnActivate(ctx, mutation, activity, entry, now, pendingChildren) {
|
|
2356
|
+
if (activity.failWhen === void 0 && effectiveCompleteWhen(activity) === void 0) return !1;
|
|
2357
|
+
const vars = pendingChildren !== void 0 ? { subworkflows: pendingChildren } : await activityConditionVars(ctx, entry), outcome = await evaluateResolutionGate(ctx, activity, vars);
|
|
2358
|
+
return outcome === void 0 ? !1 : (applyActivityStatusChange({
|
|
2294
2359
|
entry,
|
|
2295
2360
|
history: mutation.history,
|
|
2296
2361
|
stage: mutation.currentStage,
|
|
@@ -2299,29 +2364,31 @@ async function autoResolveOnActivate(ctx, mutation, task, entry, now, pendingChi
|
|
|
2299
2364
|
actor: gateActor(outcome)
|
|
2300
2365
|
}), !0);
|
|
2301
2366
|
}
|
|
2302
|
-
async function
|
|
2367
|
+
async function activityConditionVars(ctx, entry) {
|
|
2303
2368
|
return entry.spawnedInstances === void 0 || entry.spawnedInstances.length === 0 ? { subworkflows: [] } : { subworkflows: await subworkflowRows(ctx, entry.spawnedInstances) };
|
|
2304
2369
|
}
|
|
2305
|
-
function resolveMachineStep(mutation,
|
|
2306
|
-
const waitsForActor =
|
|
2307
|
-
waitsForActor || waitsForCondition ||
|
|
2370
|
+
function resolveMachineStep(mutation, activity, entry, now) {
|
|
2371
|
+
const waitsForActor = activity.actions !== void 0 && activity.actions.length > 0, waitsForCondition = activity.completeWhen !== void 0 || activity.subworkflows !== void 0;
|
|
2372
|
+
waitsForActor || waitsForCondition || applyActivityStatusChange({
|
|
2308
2373
|
entry,
|
|
2309
2374
|
history: mutation.history,
|
|
2310
2375
|
stage: mutation.currentStage,
|
|
2311
2376
|
to: "done",
|
|
2312
2377
|
at: now,
|
|
2313
|
-
actor:
|
|
2378
|
+
actor: engineSystemActor("machineStep")
|
|
2314
2379
|
});
|
|
2315
2380
|
}
|
|
2316
|
-
async function
|
|
2381
|
+
async function buildStageActivities(ctx, stage) {
|
|
2317
2382
|
const entries = [];
|
|
2318
|
-
for (const
|
|
2319
|
-
const outcome = await ctxEvaluateConditionOutcome(ctx,
|
|
2383
|
+
for (const activity of stage.activities ?? []) {
|
|
2384
|
+
const outcome = await ctxEvaluateConditionOutcome(ctx, activity.filter, {
|
|
2385
|
+
activityName: activity.name
|
|
2386
|
+
}), inScope = outcome !== "unsatisfied";
|
|
2320
2387
|
entries.push({
|
|
2321
2388
|
_key: randomKey(),
|
|
2322
|
-
name:
|
|
2389
|
+
name: activity.name,
|
|
2323
2390
|
status: inScope ? "pending" : "skipped",
|
|
2324
|
-
...
|
|
2391
|
+
...activity.filter !== void 0 ? { filterEvaluation: buildFilterEvaluation(ctx.now, activity.filter, outcome) } : {}
|
|
2325
2392
|
});
|
|
2326
2393
|
}
|
|
2327
2394
|
return entries;
|
|
@@ -2331,7 +2398,7 @@ function buildFilterEvaluation(at, filter, outcome) {
|
|
|
2331
2398
|
at,
|
|
2332
2399
|
truthy: !1,
|
|
2333
2400
|
unevaluable: !0,
|
|
2334
|
-
detail: `Filter "${filter}" could not be evaluated (GROQ null \u2014 a referenced operand was missing or incomparable).
|
|
2401
|
+
detail: `Filter "${filter}" could not be evaluated (GROQ null \u2014 a referenced operand was missing or incomparable). Activity kept in scope so the gate is not silently skipped.`
|
|
2335
2402
|
} : { at, truthy: outcome === "satisfied" };
|
|
2336
2403
|
}
|
|
2337
2404
|
function isTerminalStage(stage) {
|
|
@@ -2354,15 +2421,16 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
|
|
|
2354
2421
|
client: ctx.client,
|
|
2355
2422
|
instance: ctx.instance,
|
|
2356
2423
|
stage: nextStage,
|
|
2357
|
-
now: at
|
|
2424
|
+
now: at,
|
|
2425
|
+
recordDiscard: recordFieldDiscards(mutation.history, "stage", at)
|
|
2358
2426
|
}),
|
|
2359
|
-
|
|
2427
|
+
activities: await buildStageActivities(ctx, nextStage)
|
|
2360
2428
|
};
|
|
2361
2429
|
mutation.stages.push(nextStageEntry);
|
|
2362
|
-
for (const
|
|
2363
|
-
if (
|
|
2364
|
-
const entry = nextStageEntry.
|
|
2365
|
-
entry && entry.status === "pending" && await
|
|
2430
|
+
for (const activity of nextStage.activities ?? []) {
|
|
2431
|
+
if (activity.activation !== "auto") continue;
|
|
2432
|
+
const entry = nextStageEntry.activities.find((t) => t.name === activity.name);
|
|
2433
|
+
entry && entry.status === "pending" && await activateActivity(ctx, mutation, activity, entry, actor);
|
|
2366
2434
|
}
|
|
2367
2435
|
isTerminalStage(nextStage) && (mutation.completedAt = at);
|
|
2368
2436
|
}
|
|
@@ -2486,15 +2554,22 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
2486
2554
|
instance,
|
|
2487
2555
|
definition,
|
|
2488
2556
|
...clock ? { clock } : {}
|
|
2489
|
-
}), now = ctx.now, initialStageEntry = {
|
|
2557
|
+
}), now = ctx.now, discards = [], initialStageEntry = {
|
|
2490
2558
|
_key: randomKey(),
|
|
2491
2559
|
name: stage.name,
|
|
2492
2560
|
enteredAt: now,
|
|
2493
|
-
fields: await resolveStageFieldEntries({
|
|
2494
|
-
|
|
2561
|
+
fields: await resolveStageFieldEntries({
|
|
2562
|
+
client,
|
|
2563
|
+
instance,
|
|
2564
|
+
stage,
|
|
2565
|
+
now,
|
|
2566
|
+
recordDiscard: recordFieldDiscards(discards, "stage", now)
|
|
2567
|
+
}),
|
|
2568
|
+
activities: await buildStageActivities(ctx, stage)
|
|
2495
2569
|
}, committed = await client.patch(instance._id).set({
|
|
2496
2570
|
stages: [initialStageEntry],
|
|
2497
|
-
lastChangedAt: now
|
|
2571
|
+
lastChangedAt: now,
|
|
2572
|
+
...discards.length > 0 ? { history: [...instance.history, ...discards] } : {}
|
|
2498
2573
|
}).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
|
|
2499
2574
|
await deployOrRollback({
|
|
2500
2575
|
client,
|
|
@@ -2513,12 +2588,20 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
2513
2588
|
stageName: stage.name,
|
|
2514
2589
|
now
|
|
2515
2590
|
})
|
|
2516
|
-
}), await
|
|
2591
|
+
}), await autoActivatePrimedActivities(
|
|
2592
|
+
client,
|
|
2593
|
+
instanceId,
|
|
2594
|
+
definition,
|
|
2595
|
+
stage,
|
|
2596
|
+
actor,
|
|
2597
|
+
clientForGdr,
|
|
2598
|
+
clock
|
|
2599
|
+
);
|
|
2517
2600
|
}
|
|
2518
|
-
async function
|
|
2601
|
+
async function autoActivatePrimedActivities(client, instanceId, definition, stage, actor, clientForGdr, clock) {
|
|
2519
2602
|
const resolvedClientForGdr = clientForGdr ?? (() => client);
|
|
2520
|
-
for (const
|
|
2521
|
-
if (
|
|
2603
|
+
for (const activity of stage.activities ?? []) {
|
|
2604
|
+
if (activity.activation !== "auto") continue;
|
|
2522
2605
|
const updated = await client.getDocument(instanceId);
|
|
2523
2606
|
if (!updated) continue;
|
|
2524
2607
|
const updatedCtx = await buildEngineContext({
|
|
@@ -2527,18 +2610,22 @@ async function autoActivatePrimedTasks(client, instanceId, definition, stage, ac
|
|
|
2527
2610
|
instance: updated,
|
|
2528
2611
|
definition,
|
|
2529
2612
|
...clock ? { clock } : {}
|
|
2530
|
-
}), mutation = startMutation(updated), mutEntry =
|
|
2531
|
-
mutEntry && mutEntry.status === "pending" && (await
|
|
2613
|
+
}), mutation = startMutation(updated), mutEntry = currentActivities(mutation).find((t) => t.name === activity.name);
|
|
2614
|
+
mutEntry && mutEntry.status === "pending" && (await activateActivity(updatedCtx, mutation, activity, mutEntry, actor), await persist(updatedCtx, mutation));
|
|
2532
2615
|
}
|
|
2533
2616
|
}
|
|
2534
2617
|
const CASCADE_LIMIT = 100;
|
|
2535
|
-
async function gatherAutoResolveCandidates(ctx,
|
|
2536
|
-
const
|
|
2537
|
-
for (const
|
|
2538
|
-
const entry =
|
|
2618
|
+
async function gatherAutoResolveCandidates(ctx, activities) {
|
|
2619
|
+
const currentActivitiesList = findCurrentActivities(ctx.instance), candidates = [];
|
|
2620
|
+
for (const activity of activities) {
|
|
2621
|
+
const entry = currentActivitiesList.find((e) => e.name === activity.name);
|
|
2539
2622
|
if (entry?.status !== "active") continue;
|
|
2540
|
-
const outcome = await evaluateResolutionGate(
|
|
2541
|
-
|
|
2623
|
+
const outcome = await evaluateResolutionGate(
|
|
2624
|
+
ctx,
|
|
2625
|
+
activity,
|
|
2626
|
+
await activityConditionVars(ctx, entry)
|
|
2627
|
+
);
|
|
2628
|
+
outcome !== void 0 && candidates.push({ activity, outcome });
|
|
2542
2629
|
}
|
|
2543
2630
|
return candidates;
|
|
2544
2631
|
}
|
|
@@ -2547,17 +2634,17 @@ async function resolveCompleteWhen(client, instanceId, clientForGdr, clock, over
|
|
|
2547
2634
|
...clientForGdr ? { clientForGdr } : {},
|
|
2548
2635
|
...clock ? { clock } : {},
|
|
2549
2636
|
...overlay ? { overlay } : {}
|
|
2550
|
-
}), stage = findStage(ctx.definition, ctx.instance.currentStage),
|
|
2637
|
+
}), stage = findStage(ctx.definition, ctx.instance.currentStage), gatedActivities = (stage.activities ?? []).filter(
|
|
2551
2638
|
(t) => effectiveCompleteWhen(t) !== void 0 || t.failWhen !== void 0
|
|
2552
2639
|
);
|
|
2553
|
-
if (
|
|
2554
|
-
const candidates = await gatherAutoResolveCandidates(ctx,
|
|
2640
|
+
if (gatedActivities.length === 0) return 0;
|
|
2641
|
+
const candidates = await gatherAutoResolveCandidates(ctx, gatedActivities);
|
|
2555
2642
|
if (candidates.length === 0) return 0;
|
|
2556
2643
|
const mutation = startMutation(ctx.instance);
|
|
2557
2644
|
let count = 0;
|
|
2558
|
-
for (const {
|
|
2559
|
-
const mutEntry =
|
|
2560
|
-
mutEntry === void 0 || mutEntry.status !== "active" || (
|
|
2645
|
+
for (const { activity, outcome } of candidates) {
|
|
2646
|
+
const mutEntry = currentActivities(mutation).find((t) => t.name === activity.name);
|
|
2647
|
+
mutEntry === void 0 || mutEntry.status !== "active" || (applyActivityStatusChange({
|
|
2561
2648
|
entry: mutEntry,
|
|
2562
2649
|
history: mutation.history,
|
|
2563
2650
|
stage: stage.name,
|
|
@@ -2590,9 +2677,9 @@ async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
|
|
|
2590
2677
|
if (!parent || parent._type !== WORKFLOW_INSTANCE_TYPE || typeof parent.definitionSnapshot != "string") return;
|
|
2591
2678
|
const definition = parseDefinitionSnapshot(parent), stage = definition.stages.find((s) => s.name === parent.currentStage);
|
|
2592
2679
|
if (stage === void 0) return;
|
|
2593
|
-
const
|
|
2594
|
-
if (
|
|
2595
|
-
const entry =
|
|
2680
|
+
const parentCurrentActivities = findCurrentActivities(parent), activity = (stage.activities ?? []).find((t) => t.subworkflows === void 0 ? !1 : parentCurrentActivities.find((e) => e.name === t.name)?.spawnedInstances?.some((r) => gdrToBareId(r.id) === child._id) ?? !1);
|
|
2681
|
+
if (activity?.subworkflows === void 0) return;
|
|
2682
|
+
const entry = parentCurrentActivities.find((e) => e.name === activity.name);
|
|
2596
2683
|
if (entry === void 0 || entry.status !== "active") return;
|
|
2597
2684
|
const ctx = await buildEngineContext({
|
|
2598
2685
|
client,
|
|
@@ -2600,23 +2687,27 @@ async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
|
|
|
2600
2687
|
instance: parent,
|
|
2601
2688
|
definition,
|
|
2602
2689
|
...clock ? { clock } : {}
|
|
2603
|
-
}), outcome = await evaluateResolutionGate(
|
|
2690
|
+
}), outcome = await evaluateResolutionGate(
|
|
2691
|
+
ctx,
|
|
2692
|
+
activity,
|
|
2693
|
+
await activityConditionVars(ctx, entry)
|
|
2694
|
+
);
|
|
2604
2695
|
if (outcome !== void 0)
|
|
2605
|
-
return { parent, definition, stage,
|
|
2696
|
+
return { parent, definition, stage, activity, outcome };
|
|
2606
2697
|
}
|
|
2607
2698
|
async function propagateToAncestors(client, instanceId, actor, clientForGdr, clock) {
|
|
2608
2699
|
const instance = await client.getDocument(instanceId);
|
|
2609
2700
|
if (!instance) return;
|
|
2610
2701
|
const resolvedClientForGdr = clientForGdr ?? (() => client), found = await findResolvedParentSpawn(client, instance, resolvedClientForGdr, clock);
|
|
2611
2702
|
if (found === void 0) return;
|
|
2612
|
-
const { parent, definition, stage,
|
|
2703
|
+
const { parent, definition, stage, activity, outcome } = found, ctx = await buildEngineContext({
|
|
2613
2704
|
client,
|
|
2614
2705
|
clientForGdr: resolvedClientForGdr,
|
|
2615
2706
|
instance: parent,
|
|
2616
2707
|
definition,
|
|
2617
2708
|
...clock ? { clock } : {}
|
|
2618
|
-
}), mutation = startMutation(parent), mutEntry =
|
|
2619
|
-
mutEntry !== void 0 && (
|
|
2709
|
+
}), mutation = startMutation(parent), mutEntry = currentActivities(mutation).find((e) => e.name === activity.name);
|
|
2710
|
+
mutEntry !== void 0 && (applyActivityStatusChange({
|
|
2620
2711
|
entry: mutEntry,
|
|
2621
2712
|
history: mutation.history,
|
|
2622
2713
|
stage: stage.name,
|
|
@@ -2719,6 +2810,18 @@ async function fetchGrantsCached(requestFn, resourcePath) {
|
|
|
2719
2810
|
return;
|
|
2720
2811
|
}
|
|
2721
2812
|
}
|
|
2813
|
+
function hasItems(arr) {
|
|
2814
|
+
return arr !== void 0 && arr.length > 0;
|
|
2815
|
+
}
|
|
2816
|
+
function deriveActivityKind(activity) {
|
|
2817
|
+
return hasItems(activity.actions) ? "user" : hasItems(activity.effects) ? "service" : activity.completeWhen !== void 0 || activity.subworkflows !== void 0 ? "receive" : "script";
|
|
2818
|
+
}
|
|
2819
|
+
function activityKind(activity) {
|
|
2820
|
+
return activity.kind ?? deriveActivityKind(activity);
|
|
2821
|
+
}
|
|
2822
|
+
function driverKind(actor) {
|
|
2823
|
+
return actor.kind === "user" ? "person" : actor.kind === "ai" ? "agent" : isEngineActor(actor) ? "engine" : "service";
|
|
2824
|
+
}
|
|
2722
2825
|
function effectiveEditable(baseline, override) {
|
|
2723
2826
|
if (baseline === void 0) return;
|
|
2724
2827
|
if (override === void 0) return baseline;
|
|
@@ -2730,14 +2833,15 @@ function slotSites(definition, stage) {
|
|
|
2730
2833
|
const sites = [];
|
|
2731
2834
|
for (const entry of definition.fields ?? []) sites.push({ scope: "workflow", entry });
|
|
2732
2835
|
for (const entry of stage.fields ?? []) sites.push({ scope: "stage", entry });
|
|
2733
|
-
for (const
|
|
2734
|
-
for (const entry of
|
|
2836
|
+
for (const activity of stage.activities ?? [])
|
|
2837
|
+
for (const entry of activity.fields ?? [])
|
|
2838
|
+
sites.push({ scope: "activity", activity: activity.name, entry });
|
|
2735
2839
|
return sites;
|
|
2736
2840
|
}
|
|
2737
2841
|
function toResolvedSlot(site, stage) {
|
|
2738
2842
|
return {
|
|
2739
2843
|
scope: site.scope,
|
|
2740
|
-
...site.
|
|
2844
|
+
...site.activity !== void 0 ? { activity: site.activity } : {},
|
|
2741
2845
|
name: site.entry.name,
|
|
2742
2846
|
type: site.entry.type,
|
|
2743
2847
|
...site.entry.title !== void 0 ? { title: site.entry.title } : {},
|
|
@@ -2749,16 +2853,16 @@ function editableSlotsInStage(definition, stage) {
|
|
|
2749
2853
|
return slotSites(definition, stage).filter((site) => site.entry.editable !== void 0).map((site) => toResolvedSlot(site, stage));
|
|
2750
2854
|
}
|
|
2751
2855
|
function editTargetMatches(slot, target) {
|
|
2752
|
-
return slot.name !== target.field ? !1 : target.
|
|
2856
|
+
return slot.name !== target.field ? !1 : target.activity !== void 0 ? slot.scope === "activity" && slot.activity === target.activity : target.scope !== void 0 ? slot.scope === target.scope : !0;
|
|
2753
2857
|
}
|
|
2754
|
-
const SCOPE_PRECEDENCE = {
|
|
2858
|
+
const SCOPE_PRECEDENCE = { activity: 0, stage: 1, workflow: 2 };
|
|
2755
2859
|
function resolveEditTarget(args) {
|
|
2756
2860
|
const { definition, stage, target } = args, found = slotSites(definition, stage).filter(
|
|
2757
2861
|
(site) => editTargetMatches(
|
|
2758
2862
|
{
|
|
2759
2863
|
scope: site.scope,
|
|
2760
2864
|
name: site.entry.name,
|
|
2761
|
-
...site.
|
|
2865
|
+
...site.activity !== void 0 ? { activity: site.activity } : {}
|
|
2762
2866
|
},
|
|
2763
2867
|
target
|
|
2764
2868
|
)
|
|
@@ -2768,9 +2872,11 @@ function resolveEditTarget(args) {
|
|
|
2768
2872
|
function slotWindowOpen(instance, slot) {
|
|
2769
2873
|
if (instance.completedAt !== void 0) return { open: !1, detail: "instance completed" };
|
|
2770
2874
|
if (instance.abortedAt !== void 0) return { open: !1, detail: "instance aborted" };
|
|
2771
|
-
if (slot.scope !== "
|
|
2772
|
-
const status = findOpenStageEntry(instance)?.
|
|
2773
|
-
|
|
2875
|
+
if (slot.scope !== "activity") return { open: !0 };
|
|
2876
|
+
const status = findOpenStageEntry(instance)?.activities.find(
|
|
2877
|
+
(t) => t.name === slot.activity
|
|
2878
|
+
)?.status;
|
|
2879
|
+
return status === "active" ? { open: !0 } : { open: !1, detail: `activity "${slot.activity}" is ${status ?? "not active"}` };
|
|
2774
2880
|
}
|
|
2775
2881
|
function readSlotValue(instance, slot) {
|
|
2776
2882
|
return slotFieldHost(instance, slot)?.find((e) => e.name === slot.name)?.value;
|
|
@@ -2778,7 +2884,7 @@ function readSlotValue(instance, slot) {
|
|
|
2778
2884
|
function slotFieldHost(instance, slot) {
|
|
2779
2885
|
if (slot.scope === "workflow") return instance.fields;
|
|
2780
2886
|
const stageEntry = findOpenStageEntry(instance);
|
|
2781
|
-
return slot.scope === "stage" ? stageEntry?.fields : stageEntry?.
|
|
2887
|
+
return slot.scope === "stage" ? stageEntry?.fields : stageEntry?.activities.find((t) => t.name === slot.activity)?.fields;
|
|
2782
2888
|
}
|
|
2783
2889
|
function slotProvenance(instance, ref) {
|
|
2784
2890
|
let latest;
|
|
@@ -2821,12 +2927,12 @@ async function evaluateFromSnapshot(args) {
|
|
|
2821
2927
|
throw new Error(
|
|
2822
2928
|
`Instance "${instance._id}" currentStage "${instance.currentStage}" not in definition`
|
|
2823
2929
|
);
|
|
2824
|
-
const scope = await renderScope({ instance, definition, actor, grants, snapshot, now }),
|
|
2825
|
-
for (const
|
|
2826
|
-
|
|
2827
|
-
await
|
|
2828
|
-
|
|
2829
|
-
statusEntry:
|
|
2930
|
+
const scope = await renderScope({ instance, definition, actor, grants, snapshot, now }), currentActivityEntries = findOpenStageEntry(instance)?.activities ?? [], guardDenial = await instanceGuardReason(instance, actor, args.guards), activityEvaluations = [];
|
|
2931
|
+
for (const activity of stage.activities ?? [])
|
|
2932
|
+
activityEvaluations.push(
|
|
2933
|
+
await evaluateActivity({
|
|
2934
|
+
activity,
|
|
2935
|
+
statusEntry: currentActivityEntries.find((t) => t.name === activity.name),
|
|
2830
2936
|
instance,
|
|
2831
2937
|
actor,
|
|
2832
2938
|
snapshot,
|
|
@@ -2851,9 +2957,9 @@ async function evaluateFromSnapshot(args) {
|
|
|
2851
2957
|
}
|
|
2852
2958
|
const currentStage = {
|
|
2853
2959
|
stage,
|
|
2854
|
-
|
|
2960
|
+
activities: activityEvaluations,
|
|
2855
2961
|
transitions: transitionEvaluations
|
|
2856
|
-
}, pendingOnYou =
|
|
2962
|
+
}, pendingOnYou = activityEvaluations.filter((t) => t.pendingOnActor), canInteract = activityEvaluations.some((t) => t.actions.some((a) => a.allowed)), editGuardDenial = guardDenial?.kind === "mutation-guard-denied" ? guardDenial : void 0, editableSlots = await evaluateEditableSlots({
|
|
2857
2963
|
instance,
|
|
2858
2964
|
definition,
|
|
2859
2965
|
stage,
|
|
@@ -2893,7 +2999,7 @@ async function evaluateEditableSlots(args) {
|
|
|
2893
2999
|
}), value = readSlotValue(instance, slot);
|
|
2894
3000
|
slots.push({
|
|
2895
3001
|
scope: slot.scope,
|
|
2896
|
-
...slot.
|
|
3002
|
+
...slot.activity !== void 0 ? { activity: slot.activity } : {},
|
|
2897
3003
|
name: slot.name,
|
|
2898
3004
|
type: slot.type,
|
|
2899
3005
|
...slot.title !== void 0 ? { title: slot.title } : {},
|
|
@@ -2909,7 +3015,14 @@ async function editPredicateSatisfied(args) {
|
|
|
2909
3015
|
const { slot, window, guardDenial, instance, snapshot, scope, actor, roleAliases } = args;
|
|
2910
3016
|
if (!window.open || guardDenial !== void 0) return !1;
|
|
2911
3017
|
if (slot.effective === !0) return !0;
|
|
2912
|
-
const params = slot.scope === "
|
|
3018
|
+
const params = slot.scope === "activity" && slot.activity !== void 0 ? activityScopeFor({
|
|
3019
|
+
scope,
|
|
3020
|
+
instance,
|
|
3021
|
+
snapshot,
|
|
3022
|
+
actor,
|
|
3023
|
+
activityName: slot.activity,
|
|
3024
|
+
roleAliases
|
|
3025
|
+
}) : scope;
|
|
2913
3026
|
return evaluateCondition({ condition: slot.effective, snapshot, params });
|
|
2914
3027
|
}
|
|
2915
3028
|
async function renderScope(args) {
|
|
@@ -2937,20 +3050,20 @@ async function advisoryCan(instance, actor, grants) {
|
|
|
2937
3050
|
});
|
|
2938
3051
|
return can;
|
|
2939
3052
|
}
|
|
2940
|
-
function
|
|
2941
|
-
const { scope, instance, snapshot, actor,
|
|
3053
|
+
function activityScopeFor(args) {
|
|
3054
|
+
const { scope, instance, snapshot, actor, activityName, roleAliases } = args;
|
|
2942
3055
|
return {
|
|
2943
3056
|
...scope,
|
|
2944
3057
|
fields: {
|
|
2945
3058
|
...scope.fields,
|
|
2946
|
-
...scopedFieldOverlay(instance, snapshot,
|
|
3059
|
+
...scopedFieldOverlay(instance, snapshot, activityName)
|
|
2947
3060
|
},
|
|
2948
|
-
assigned: assignedFor(instance,
|
|
3061
|
+
assigned: assignedFor(instance, activityName, actor, roleAliases)
|
|
2949
3062
|
};
|
|
2950
3063
|
}
|
|
2951
|
-
async function
|
|
3064
|
+
async function evaluateActivity(args) {
|
|
2952
3065
|
const {
|
|
2953
|
-
|
|
3066
|
+
activity,
|
|
2954
3067
|
statusEntry,
|
|
2955
3068
|
instance,
|
|
2956
3069
|
actor,
|
|
@@ -2959,36 +3072,37 @@ async function evaluateTask(args) {
|
|
|
2959
3072
|
roleAliases,
|
|
2960
3073
|
stageHasExits,
|
|
2961
3074
|
guardDenial
|
|
2962
|
-
} = args, status = statusEntry?.status ?? "pending",
|
|
3075
|
+
} = args, status = statusEntry?.status ?? "pending", activityScope = activityScopeFor({
|
|
2963
3076
|
scope,
|
|
2964
3077
|
instance,
|
|
2965
3078
|
snapshot,
|
|
2966
3079
|
actor,
|
|
2967
|
-
|
|
3080
|
+
activityName: activity.name,
|
|
2968
3081
|
roleAliases
|
|
2969
|
-
}), assigned =
|
|
2970
|
-
requirements:
|
|
3082
|
+
}), assigned = activityScope.assigned === !0, unmetRequirements = await evaluateRequirements({
|
|
3083
|
+
requirements: activity.requirements,
|
|
2971
3084
|
snapshot,
|
|
2972
|
-
params:
|
|
3085
|
+
params: activityScope
|
|
2973
3086
|
}), requirementsReason = unmetRequirements.length > 0 ? { kind: "requirements-unmet", unmetRequirements } : void 0, actions = [];
|
|
2974
|
-
for (const action of
|
|
3087
|
+
for (const action of activity.actions ?? [])
|
|
2975
3088
|
actions.push(
|
|
2976
3089
|
await evaluateAction({
|
|
2977
3090
|
action,
|
|
2978
3091
|
status,
|
|
2979
3092
|
instance,
|
|
2980
3093
|
snapshot,
|
|
2981
|
-
|
|
3094
|
+
activityScope,
|
|
2982
3095
|
stageHasExits,
|
|
2983
3096
|
guardDenial,
|
|
2984
3097
|
requirementsReason
|
|
2985
3098
|
})
|
|
2986
3099
|
);
|
|
2987
3100
|
return {
|
|
2988
|
-
|
|
3101
|
+
activity,
|
|
2989
3102
|
status,
|
|
3103
|
+
kind: activityKind(activity),
|
|
2990
3104
|
// "pending on actor" treats both `pending` and `active` as waiting on
|
|
2991
|
-
// the assignee — pending
|
|
3105
|
+
// the assignee — pending activities just haven't been activated yet, but
|
|
2992
3106
|
// they're still inbox items. The inbox reads the assignees-kind field
|
|
2993
3107
|
// entry BY KIND (who-data is fields).
|
|
2994
3108
|
pendingOnActor: (status === "active" || status === "pending") && assigned,
|
|
@@ -3002,7 +3116,7 @@ async function evaluateAction(args) {
|
|
|
3002
3116
|
status,
|
|
3003
3117
|
instance,
|
|
3004
3118
|
snapshot,
|
|
3005
|
-
|
|
3119
|
+
activityScope,
|
|
3006
3120
|
stageHasExits,
|
|
3007
3121
|
guardDenial,
|
|
3008
3122
|
requirementsReason
|
|
@@ -3010,7 +3124,7 @@ async function evaluateAction(args) {
|
|
|
3010
3124
|
return lifecycle !== void 0 ? disabled(action, lifecycle) : guardDenial !== void 0 ? disabled(action, guardDenial) : requirementsReason !== void 0 ? disabled(action, requirementsReason) : action.filter !== void 0 && !await evaluateCondition({
|
|
3011
3125
|
condition: action.filter,
|
|
3012
3126
|
snapshot,
|
|
3013
|
-
params:
|
|
3127
|
+
params: activityScope
|
|
3014
3128
|
}) ? disabled(action, { kind: "filter-failed", filter: action.filter }) : { action, allowed: !0 };
|
|
3015
3129
|
}
|
|
3016
3130
|
async function instanceGuardReason(instance, actor, guards) {
|
|
@@ -3028,8 +3142,8 @@ function lifecycleReason(instance, status, stageHasExits) {
|
|
|
3028
3142
|
return { kind: "instance-completed", completedAt: instance.completedAt };
|
|
3029
3143
|
if (!stageHasExits)
|
|
3030
3144
|
return { kind: "stage-terminal", stage: instance.currentStage };
|
|
3031
|
-
if (schema.
|
|
3032
|
-
return { kind: "
|
|
3145
|
+
if (schema.isTerminalActivityStatus(status))
|
|
3146
|
+
return { kind: "activity-not-active", status };
|
|
3033
3147
|
}
|
|
3034
3148
|
function disabled(action, reason) {
|
|
3035
3149
|
return { action, allowed: !1, disabledReason: reason };
|
|
@@ -3037,37 +3151,30 @@ function disabled(action, reason) {
|
|
|
3037
3151
|
function definitionDocId(_workflowResource, tag, definition, version) {
|
|
3038
3152
|
return `${tag}.${definition}.v${version}`;
|
|
3039
3153
|
}
|
|
3040
|
-
async function sortByDependencies(client, definitions, tag
|
|
3154
|
+
async function sortByDependencies(client, definitions, tag) {
|
|
3041
3155
|
const byName = /* @__PURE__ */ new Map();
|
|
3042
3156
|
for (const def of definitions) {
|
|
3043
|
-
|
|
3044
|
-
|
|
3157
|
+
if (byName.has(def.name))
|
|
3158
|
+
throw new Error(
|
|
3159
|
+
`workflow.deployDefinitions: duplicate definition name "${def.name}" in batch \u2014 names identify a batch member (versions are deploy-assigned, not authored)`
|
|
3160
|
+
);
|
|
3161
|
+
byName.set(def.name, def);
|
|
3045
3162
|
}
|
|
3046
3163
|
const findInBatch = (ref) => findRefInBatch(byName, ref);
|
|
3047
|
-
return await assertCrossBatchRefsDeployed(client, definitions, {
|
|
3048
|
-
tag,
|
|
3049
|
-
workflowResource,
|
|
3050
|
-
findInBatch
|
|
3051
|
-
}), topoSortDefinitions(definitions, { workflowResource, tag, findInBatch });
|
|
3164
|
+
return await assertCrossBatchRefsDeployed(client, definitions, { tag, findInBatch }), topoSortDefinitions(definitions, { findInBatch });
|
|
3052
3165
|
}
|
|
3053
3166
|
function findRefInBatch(byName, ref) {
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
return typeof ref.version == "number" ? candidates.find((c) => c.version === ref.version) : candidates.reduce(
|
|
3057
|
-
(best, c) => best === void 0 || c.version > best.version ? c : best,
|
|
3058
|
-
void 0
|
|
3059
|
-
);
|
|
3167
|
+
if (typeof ref.version != "number")
|
|
3168
|
+
return byName.get(ref.name);
|
|
3060
3169
|
}
|
|
3061
3170
|
async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
|
|
3062
3171
|
const missing = [];
|
|
3063
|
-
for (const def of definitions)
|
|
3064
|
-
const fromId = definitionDocId(ctx.workflowResource, ctx.tag, def.name, def.version);
|
|
3172
|
+
for (const def of definitions)
|
|
3065
3173
|
for (const ref of refsOf(def)) {
|
|
3066
3174
|
if (ctx.findInBatch(ref) !== void 0) continue;
|
|
3067
3175
|
const label = await resolveDeployedRefLabel(client, ref, ctx.tag);
|
|
3068
|
-
label !== void 0 && missing.push({ from:
|
|
3176
|
+
label !== void 0 && missing.push({ from: def.name, ref: label });
|
|
3069
3177
|
}
|
|
3070
|
-
}
|
|
3071
3178
|
if (missing.length === 0) return;
|
|
3072
3179
|
const lines = missing.map((m) => ` - ${m.from} \u2192 ${m.ref} (neither in batch nor deployed)`);
|
|
3073
3180
|
throw new Error(
|
|
@@ -3086,16 +3193,15 @@ async function resolveDeployedRefLabel(client, ref, tag) {
|
|
|
3086
3193
|
}
|
|
3087
3194
|
function topoSortDefinitions(definitions, ctx) {
|
|
3088
3195
|
const visited = /* @__PURE__ */ new Set(), visiting = /* @__PURE__ */ new Set(), ordered = [], visit = (def) => {
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
visiting.add(id);
|
|
3196
|
+
if (!visited.has(def.name)) {
|
|
3197
|
+
if (visiting.has(def.name))
|
|
3198
|
+
throw new Error(`workflow.deployDefinitions: dependency cycle involving "${def.name}"`);
|
|
3199
|
+
visiting.add(def.name);
|
|
3094
3200
|
for (const ref of refsOf(def)) {
|
|
3095
3201
|
const child = ctx.findInBatch(ref);
|
|
3096
3202
|
child !== void 0 && visit(child);
|
|
3097
3203
|
}
|
|
3098
|
-
visiting.delete(
|
|
3204
|
+
visiting.delete(def.name), visited.add(def.name), ordered.push(def);
|
|
3099
3205
|
}
|
|
3100
3206
|
};
|
|
3101
3207
|
for (const def of definitions) visit(def);
|
|
@@ -3104,8 +3210,8 @@ function topoSortDefinitions(definitions, ctx) {
|
|
|
3104
3210
|
function refsOf(def) {
|
|
3105
3211
|
const out = [];
|
|
3106
3212
|
for (const stage of def.stages)
|
|
3107
|
-
for (const
|
|
3108
|
-
const ref =
|
|
3213
|
+
for (const activity of stage.activities ?? []) {
|
|
3214
|
+
const ref = activity.subworkflows?.definition;
|
|
3109
3215
|
ref !== void 0 && out.push({
|
|
3110
3216
|
name: ref.name,
|
|
3111
3217
|
...ref.version !== void 0 ? { version: ref.version } : {}
|
|
@@ -3113,8 +3219,23 @@ function refsOf(def) {
|
|
|
3113
3219
|
}
|
|
3114
3220
|
return out;
|
|
3115
3221
|
}
|
|
3116
|
-
function
|
|
3117
|
-
|
|
3222
|
+
function hashDefinitionContent(def) {
|
|
3223
|
+
const canonical = stableStringify(def);
|
|
3224
|
+
let hash = 0xcbf29ce484222325n;
|
|
3225
|
+
for (let i = 0; i < canonical.length; i++)
|
|
3226
|
+
hash ^= BigInt(canonical.charCodeAt(i)), hash = hash * 0x100000001b3n & 0xffffffffffffffffn;
|
|
3227
|
+
return hash.toString(16).padStart(16, "0");
|
|
3228
|
+
}
|
|
3229
|
+
function planDefinitionDeploy(def, latest, target) {
|
|
3230
|
+
const contentHash = hashDefinitionContent(def), unchanged = latest !== void 0 && latest.contentHash === contentHash, version = unchanged ? latest.version : (latest?.version ?? 0) + 1, docId = definitionDocId(target.workflowResource, target.tag, def.name, version), document = {
|
|
3231
|
+
...def,
|
|
3232
|
+
_id: docId,
|
|
3233
|
+
_type: schema.WORKFLOW_DEFINITION_TYPE,
|
|
3234
|
+
tag: target.tag,
|
|
3235
|
+
version,
|
|
3236
|
+
contentHash
|
|
3237
|
+
};
|
|
3238
|
+
return { status: unchanged ? "unchanged" : "create", version, contentHash, docId, document };
|
|
3118
3239
|
}
|
|
3119
3240
|
function stripSystemFields(doc) {
|
|
3120
3241
|
const out = {};
|
|
@@ -3129,22 +3250,26 @@ function stableStringify(value) {
|
|
|
3129
3250
|
}
|
|
3130
3251
|
async function loadDefinition(client, definition, version, tag) {
|
|
3131
3252
|
if (version !== void 0) {
|
|
3132
|
-
const doc = await client.fetch(
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3253
|
+
const doc = await client.fetch(definitionLookupGroq(!0), {
|
|
3254
|
+
definition,
|
|
3255
|
+
version,
|
|
3256
|
+
tag
|
|
3257
|
+
});
|
|
3136
3258
|
if (!doc)
|
|
3137
3259
|
throw new Error(`Workflow definition ${definition} v${version} not deployed`);
|
|
3138
3260
|
return doc;
|
|
3139
3261
|
}
|
|
3140
|
-
const latest = await client
|
|
3141
|
-
definition,
|
|
3142
|
-
tag
|
|
3143
|
-
});
|
|
3262
|
+
const latest = await loadLatestDeployed(client, definition, tag);
|
|
3144
3263
|
if (!latest)
|
|
3145
3264
|
throw new Error(`No deployed definition for workflow ${definition}`);
|
|
3146
3265
|
return latest;
|
|
3147
3266
|
}
|
|
3267
|
+
async function loadLatestDeployed(client, definition, tag) {
|
|
3268
|
+
return await client.fetch(definitionLookupGroq(!1), {
|
|
3269
|
+
definition,
|
|
3270
|
+
tag
|
|
3271
|
+
}) ?? void 0;
|
|
3272
|
+
}
|
|
3148
3273
|
async function loadDefinitionVersions(client, definition, tag) {
|
|
3149
3274
|
return client.fetch(
|
|
3150
3275
|
`*[_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}] | order(version desc)`,
|
|
@@ -3191,63 +3316,63 @@ async function commitAbort(ctx, reason, actor) {
|
|
|
3191
3316
|
}), { fired: !0, stage: stage.name };
|
|
3192
3317
|
}
|
|
3193
3318
|
async function fireAction(args) {
|
|
3194
|
-
const { client, instanceId,
|
|
3319
|
+
const { client, instanceId, activity, action, params, options } = args;
|
|
3195
3320
|
for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
|
|
3196
3321
|
const ctx = await loadCallContext(client, instanceId, options);
|
|
3197
3322
|
try {
|
|
3198
|
-
return await commitAction(ctx,
|
|
3323
|
+
return await commitAction(ctx, activity, action, params, options);
|
|
3199
3324
|
} catch (error) {
|
|
3200
3325
|
if (!isRevisionConflict(error)) throw error;
|
|
3201
3326
|
}
|
|
3202
3327
|
}
|
|
3203
3328
|
throw new ConcurrentFireActionError({
|
|
3204
3329
|
instanceId,
|
|
3205
|
-
|
|
3330
|
+
activity,
|
|
3206
3331
|
action,
|
|
3207
3332
|
attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
|
|
3208
3333
|
});
|
|
3209
3334
|
}
|
|
3210
|
-
async function resolveActionCommit(ctx,
|
|
3211
|
-
const actor = options?.actor, { stage,
|
|
3335
|
+
async function resolveActionCommit(ctx, activityName, actionName, callerParams, options) {
|
|
3336
|
+
const actor = options?.actor, { stage, activity } = findActivityInCurrentStage(ctx, activityName), action = (activity.actions ?? []).find((a) => a.name === actionName);
|
|
3212
3337
|
if (action === void 0)
|
|
3213
|
-
throw new Error(`Action "${actionName}" not declared on
|
|
3338
|
+
throw new Error(`Action "${actionName}" not declared on activity "${activityName}"`);
|
|
3214
3339
|
const can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan(ctx.instance, actor, options.grants) : void 0;
|
|
3215
3340
|
if (!await ctxEvaluateCondition(ctx, action.filter, {
|
|
3216
|
-
|
|
3341
|
+
activityName,
|
|
3217
3342
|
...actor !== void 0 ? { actor } : {},
|
|
3218
3343
|
...can !== void 0 ? { vars: { can } } : {}
|
|
3219
3344
|
}))
|
|
3220
|
-
throw new Error(`Action "${actionName}" filter rejected on
|
|
3221
|
-
const entry =
|
|
3345
|
+
throw new Error(`Action "${actionName}" filter rejected on activity "${activityName}"`);
|
|
3346
|
+
const entry = findCurrentActivities(ctx.instance).find((t) => t.name === activityName);
|
|
3222
3347
|
if (entry === void 0 || entry.status !== "active")
|
|
3223
3348
|
throw new Error(
|
|
3224
|
-
`
|
|
3349
|
+
`Activity "${activityName}" must be active to fire action "${actionName}"; status is ${entry?.status ?? "missing"}`
|
|
3225
3350
|
);
|
|
3226
|
-
const params = validateActionParams(action,
|
|
3227
|
-
return { stage,
|
|
3351
|
+
const params = validateActionParams(action, activityName, callerParams);
|
|
3352
|
+
return { stage, activity, action, params };
|
|
3228
3353
|
}
|
|
3229
|
-
async function commitAction(ctx,
|
|
3354
|
+
async function commitAction(ctx, activityName, actionName, callerParams, options) {
|
|
3230
3355
|
const actor = options?.actor, { stage, action, params } = await resolveActionCommit(
|
|
3231
3356
|
ctx,
|
|
3232
|
-
|
|
3357
|
+
activityName,
|
|
3233
3358
|
actionName,
|
|
3234
3359
|
callerParams,
|
|
3235
3360
|
options
|
|
3236
|
-
), mutation = startMutation(ctx.instance), mutEntry =
|
|
3361
|
+
), mutation = startMutation(ctx.instance), mutEntry = requireMutationActivityEntry(mutation, activityName), now = ctx.now;
|
|
3237
3362
|
mutation.history.push({
|
|
3238
3363
|
_key: randomKey(),
|
|
3239
3364
|
_type: "actionFired",
|
|
3240
3365
|
at: now,
|
|
3241
3366
|
stage: stage.name,
|
|
3242
|
-
|
|
3367
|
+
activity: activityName,
|
|
3243
3368
|
action: actionName,
|
|
3244
|
-
...actor !== void 0 ? { actor } : {}
|
|
3369
|
+
...actor !== void 0 ? { actor, driverKind: driverKind(actor) } : {}
|
|
3245
3370
|
});
|
|
3246
3371
|
const statusBefore = mutEntry.status, ranOps = await runOps({
|
|
3247
3372
|
ops: action.ops,
|
|
3248
3373
|
mutation,
|
|
3249
3374
|
stage: stage.name,
|
|
3250
|
-
origin: {
|
|
3375
|
+
origin: { activity: activityName, action: actionName },
|
|
3251
3376
|
params,
|
|
3252
3377
|
actor,
|
|
3253
3378
|
self: selfGdr(ctx.instance),
|
|
@@ -3255,14 +3380,14 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
|
|
|
3255
3380
|
}), newStatus = mutEntry.status !== statusBefore ? mutEntry.status : void 0;
|
|
3256
3381
|
return await queueEffects(ctx, mutation, action.effects, { kind: "action", name: actionName }, actor, {
|
|
3257
3382
|
callerParams: params,
|
|
3258
|
-
|
|
3383
|
+
activityName
|
|
3259
3384
|
}), await persistThenDeploy(
|
|
3260
3385
|
ctx,
|
|
3261
3386
|
mutation,
|
|
3262
3387
|
() => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
|
|
3263
3388
|
), {
|
|
3264
3389
|
fired: !0,
|
|
3265
|
-
|
|
3390
|
+
activity: activityName,
|
|
3266
3391
|
action: actionName,
|
|
3267
3392
|
...newStatus !== void 0 ? { newStatus } : {},
|
|
3268
3393
|
...ranOps.length > 0 ? { ranOps } : {}
|
|
@@ -3270,23 +3395,23 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
|
|
|
3270
3395
|
}
|
|
3271
3396
|
class ActionDisabledError extends Error {
|
|
3272
3397
|
reason;
|
|
3273
|
-
|
|
3398
|
+
activity;
|
|
3274
3399
|
action;
|
|
3275
3400
|
constructor(args) {
|
|
3276
|
-
super(formatDisabledReason(args.
|
|
3401
|
+
super(formatDisabledReason(args.activity, args.action, args.reason)), this.name = "ActionDisabledError", this.reason = args.reason, this.activity = args.activity, this.action = args.action;
|
|
3277
3402
|
}
|
|
3278
3403
|
}
|
|
3279
3404
|
const disabledReasonDetail = {
|
|
3280
3405
|
"filter-failed": (r) => `action filter returned false${r.detail ? ` (${r.detail})` : ""}`,
|
|
3281
|
-
"
|
|
3406
|
+
"activity-not-active": (r) => `activity status is "${r.status}"`,
|
|
3282
3407
|
"stage-terminal": (r) => `stage "${r.stage}" is terminal`,
|
|
3283
3408
|
"instance-completed": (r) => `instance completed at ${r.completedAt}`,
|
|
3284
3409
|
"mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`,
|
|
3285
3410
|
"requirements-unmet": (r) => `unmet requirement(s): ${r.unmetRequirements.join(", ")}`
|
|
3286
3411
|
};
|
|
3287
|
-
function formatDisabledReason(
|
|
3412
|
+
function formatDisabledReason(activity, action, reason) {
|
|
3288
3413
|
const detail = disabledReasonDetail[reason.kind](reason);
|
|
3289
|
-
return `Action "${
|
|
3414
|
+
return `Action "${activity}:${action}" is not allowed: ${detail}`;
|
|
3290
3415
|
}
|
|
3291
3416
|
class EditFieldDeniedError extends Error {
|
|
3292
3417
|
reason;
|
|
@@ -3305,7 +3430,7 @@ const editDisabledReasonDetail = {
|
|
|
3305
3430
|
function formatEditDisabledReason(target, reason) {
|
|
3306
3431
|
const detail = editDisabledReasonDetail[reason.kind](
|
|
3307
3432
|
reason
|
|
3308
|
-
), where = target.
|
|
3433
|
+
), where = target.activity !== void 0 ? `${target.activity}.${target.field}` : target.field;
|
|
3309
3434
|
return `Field "${target.scope}:${where}" is not editable: ${detail}`;
|
|
3310
3435
|
}
|
|
3311
3436
|
async function editField(args) {
|
|
@@ -3337,7 +3462,7 @@ async function commitEdit(ctx, target, mode, value, options) {
|
|
|
3337
3462
|
stage: stage.name,
|
|
3338
3463
|
origin: {
|
|
3339
3464
|
edit: !0,
|
|
3340
|
-
...slot.scope === "
|
|
3465
|
+
...slot.scope === "activity" && slot.activity !== void 0 ? { activity: slot.activity } : {}
|
|
3341
3466
|
},
|
|
3342
3467
|
params: {},
|
|
3343
3468
|
actor,
|
|
@@ -3356,7 +3481,7 @@ async function commitEdit(ctx, target, mode, value, options) {
|
|
|
3356
3481
|
}
|
|
3357
3482
|
async function assertSlotEditable(ctx, slot, options) {
|
|
3358
3483
|
const actor = options?.actor, window = slotWindowOpen(ctx.instance, slot), can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan(ctx.instance, actor, options.grants) : void 0, predicateSatisfied = window.open && slot.effective !== !0 && slot.effective !== void 0 ? await ctxEvaluateCondition(ctx, slot.effective, {
|
|
3359
|
-
...slot.
|
|
3484
|
+
...slot.activity !== void 0 ? { activityName: slot.activity } : {},
|
|
3360
3485
|
...actor !== void 0 ? { actor } : {},
|
|
3361
3486
|
...can !== void 0 ? { vars: { can } } : {}
|
|
3362
3487
|
}) : slot.effective === !0, reason = editDisabledReason({
|
|
@@ -3382,18 +3507,18 @@ function slotTarget(slot) {
|
|
|
3382
3507
|
return {
|
|
3383
3508
|
scope: slot.scope,
|
|
3384
3509
|
field: slot.name,
|
|
3385
|
-
...slot.
|
|
3510
|
+
...slot.activity !== void 0 ? { activity: slot.activity } : {}
|
|
3386
3511
|
};
|
|
3387
3512
|
}
|
|
3388
3513
|
function labelTarget(target) {
|
|
3389
3514
|
return {
|
|
3390
|
-
scope: target.scope ?? (target.
|
|
3515
|
+
scope: target.scope ?? (target.activity !== void 0 ? "activity" : "workflow"),
|
|
3391
3516
|
field: target.field,
|
|
3392
|
-
...target.
|
|
3517
|
+
...target.activity !== void 0 ? { activity: target.activity } : {}
|
|
3393
3518
|
};
|
|
3394
3519
|
}
|
|
3395
3520
|
function describeTarget(target) {
|
|
3396
|
-
const where = target.
|
|
3521
|
+
const where = target.activity !== void 0 ? `${target.activity}.${target.field}` : target.field;
|
|
3397
3522
|
return target.scope !== void 0 ? `${target.scope}:${where}` : where;
|
|
3398
3523
|
}
|
|
3399
3524
|
function bareIdFromSpawnRef(uri) {
|
|
@@ -3458,10 +3583,10 @@ async function dispatchGatedWrite(args) {
|
|
|
3458
3583
|
...ranOps !== void 0 ? { ranOps } : {}
|
|
3459
3584
|
};
|
|
3460
3585
|
}
|
|
3461
|
-
function assertActionAllowed(evaluation,
|
|
3462
|
-
const actionEval = evaluation.currentStage.
|
|
3586
|
+
function assertActionAllowed(evaluation, activity, action) {
|
|
3587
|
+
const actionEval = evaluation.currentStage.activities.find((t) => t.activity.name === activity)?.actions.find((a) => a.action.name === action);
|
|
3463
3588
|
if (actionEval?.allowed === !1 && actionEval.disabledReason)
|
|
3464
|
-
throw new ActionDisabledError({
|
|
3589
|
+
throw new ActionDisabledError({ activity, action, reason: actionEval.disabledReason });
|
|
3465
3590
|
}
|
|
3466
3591
|
function assertEditAllowed(evaluation, target) {
|
|
3467
3592
|
const slot = evaluation.editableSlots.filter((s) => editTargetMatches(s, target)).sort((a, b) => SCOPE_PRECEDENCE[a.scope] - SCOPE_PRECEDENCE[b.scope])[0];
|
|
@@ -3470,7 +3595,7 @@ function assertEditAllowed(evaluation, target) {
|
|
|
3470
3595
|
target: {
|
|
3471
3596
|
scope: slot.scope,
|
|
3472
3597
|
field: slot.name,
|
|
3473
|
-
...slot.
|
|
3598
|
+
...slot.activity !== void 0 ? { activity: slot.activity } : {}
|
|
3474
3599
|
},
|
|
3475
3600
|
reason: slot.disabledReason
|
|
3476
3601
|
});
|
|
@@ -3495,11 +3620,11 @@ function buildEngineCallOptions(args) {
|
|
|
3495
3620
|
};
|
|
3496
3621
|
}
|
|
3497
3622
|
async function applyAction(args) {
|
|
3498
|
-
const { client, instance,
|
|
3499
|
-
return findOpenStageEntry(instance)?.
|
|
3623
|
+
const { client, instance, activity, action, params, actor, grants, clock, clientForGdr } = args, options = buildEngineCallOptions({ actor, grants, clock, clientForGdr });
|
|
3624
|
+
return findOpenStageEntry(instance)?.activities.find((t) => t.name === activity)?.status === "pending" && await invokeActivity({ client, instanceId: instance._id, activity, options }), (await fireAction({
|
|
3500
3625
|
client,
|
|
3501
3626
|
instanceId: instance._id,
|
|
3502
|
-
|
|
3627
|
+
activity,
|
|
3503
3628
|
action,
|
|
3504
3629
|
...params !== void 0 ? { params } : {},
|
|
3505
3630
|
options
|
|
@@ -3579,14 +3704,16 @@ function previewIds(ids) {
|
|
|
3579
3704
|
}
|
|
3580
3705
|
const workflow = {
|
|
3581
3706
|
/**
|
|
3582
|
-
* Deploy a set of workflow definitions as one call.
|
|
3583
|
-
*
|
|
3584
|
-
*
|
|
3585
|
-
*
|
|
3586
|
-
* (
|
|
3587
|
-
*
|
|
3588
|
-
*
|
|
3589
|
-
*
|
|
3707
|
+
* Deploy a set of workflow definitions as one call. Definitions are
|
|
3708
|
+
* immutable and content-addressed: the author writes no version, and each
|
|
3709
|
+
* deploy compares a definition's content fingerprint to the latest version
|
|
3710
|
+
* already deployed under its name. Identical content is a no-op
|
|
3711
|
+
* (`unchanged`); any change mints the next version (`created`) — deploy
|
|
3712
|
+
* never patches a deployed version, so a definition can't change out from
|
|
3713
|
+
* under the instances pinned to it. `startInstance` picks the highest
|
|
3714
|
+
* version by default. The engine figures out the dependency order itself
|
|
3715
|
+
* (children before parents that spawn them via `subworkflows.definition`)
|
|
3716
|
+
* and reports a per-definition outcome (`created` / `unchanged`).
|
|
3590
3717
|
*
|
|
3591
3718
|
* Refs may point inside the batch OR at already-deployed definitions
|
|
3592
3719
|
* in the lake — both are valid. A ref pointing at neither errors with
|
|
@@ -3599,31 +3726,15 @@ const workflow = {
|
|
|
3599
3726
|
validateTag(tag);
|
|
3600
3727
|
for (const def of definitions)
|
|
3601
3728
|
validateDefinition(def);
|
|
3602
|
-
const ordered = await sortByDependencies(client, definitions, tag
|
|
3729
|
+
const ordered = await sortByDependencies(client, definitions, tag), results = [], tx = client.transaction();
|
|
3603
3730
|
let hasWrites = !1;
|
|
3604
3731
|
for (const def of ordered) {
|
|
3605
|
-
const
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
_type: schema.WORKFLOW_DEFINITION_TYPE,
|
|
3609
|
-
tag
|
|
3610
|
-
}, existing = await client.getDocument(id);
|
|
3611
|
-
if (existing && existing.tag === tag && isDefinitionUnchanged(existing, expected)) {
|
|
3612
|
-
results.push({ definition: def.name, version: def.version, status: "unchanged" });
|
|
3732
|
+
const latest = await loadLatestDeployed(client, def.name, tag), plan = planDefinitionDeploy(def, latest, { tag, workflowResource });
|
|
3733
|
+
if (plan.status === "unchanged") {
|
|
3734
|
+
results.push({ definition: def.name, version: plan.version, status: "unchanged" });
|
|
3613
3735
|
continue;
|
|
3614
3736
|
}
|
|
3615
|
-
|
|
3616
|
-
const expectedKeys = new Set(Object.keys(expected)), toUnset = Object.keys(existing).filter(
|
|
3617
|
-
(k) => !k.startsWith("_") && !expectedKeys.has(k)
|
|
3618
|
-
), patch = client.patch(id).set(expected).ifRevisionId(existing._rev);
|
|
3619
|
-
toUnset.length > 0 && patch.unset(toUnset), tx.patch(patch);
|
|
3620
|
-
} else
|
|
3621
|
-
tx.create(expected);
|
|
3622
|
-
hasWrites = !0, results.push({
|
|
3623
|
-
definition: def.name,
|
|
3624
|
-
version: def.version,
|
|
3625
|
-
status: existing ? "updated" : "created"
|
|
3626
|
-
});
|
|
3737
|
+
tx.create(plan.document), hasWrites = !0, results.push({ definition: def.name, version: plan.version, status: "created" });
|
|
3627
3738
|
}
|
|
3628
3739
|
return hasWrites && await tx.commit(), { results };
|
|
3629
3740
|
},
|
|
@@ -3639,8 +3750,8 @@ const workflow = {
|
|
|
3639
3750
|
* Spawn a new workflow instance from a deployed definition.
|
|
3640
3751
|
*
|
|
3641
3752
|
* Pins the snapshot at start-time, seeds `effectsContext`, enters
|
|
3642
|
-
* the initial stage (which builds
|
|
3643
|
-
* effects, auto-activates
|
|
3753
|
+
* the initial stage (which builds activityStatus, queues onEnter
|
|
3754
|
+
* effects, auto-activates activities). Then cascades auto-transitions
|
|
3644
3755
|
* until stable. Returns the resulting instance.
|
|
3645
3756
|
*/
|
|
3646
3757
|
startInstance: async (args) => {
|
|
@@ -3660,7 +3771,7 @@ const workflow = {
|
|
|
3660
3771
|
throw new Error(
|
|
3661
3772
|
`Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
|
|
3662
3773
|
);
|
|
3663
|
-
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedFields = await resolveDeclaredFields({
|
|
3774
|
+
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], fieldDiscards = [], resolvedFields = await resolveDeclaredFields({
|
|
3664
3775
|
entryDefs: definition.fields ?? [],
|
|
3665
3776
|
initialFields: initialFields ?? [],
|
|
3666
3777
|
ctx: {
|
|
@@ -3670,7 +3781,8 @@ const workflow = {
|
|
|
3670
3781
|
tag,
|
|
3671
3782
|
workflowResource,
|
|
3672
3783
|
definitionName: definition.name,
|
|
3673
|
-
...perspective !== void 0 ? { perspective } : {}
|
|
3784
|
+
...perspective !== void 0 ? { perspective } : {},
|
|
3785
|
+
recordDiscard: recordFieldDiscards(fieldDiscards, "workflow", now)
|
|
3674
3786
|
},
|
|
3675
3787
|
randomKey
|
|
3676
3788
|
}), effectivePerspective = perspective ?? derivePerspectiveFromFields(resolvedFields), base = buildInstanceBase({
|
|
@@ -3680,18 +3792,20 @@ const workflow = {
|
|
|
3680
3792
|
workflowResource,
|
|
3681
3793
|
definitionName: definition.name,
|
|
3682
3794
|
pinnedVersion: definition.version,
|
|
3795
|
+
pinnedContentHash: definition.contentHash,
|
|
3683
3796
|
definition,
|
|
3684
3797
|
fields: resolvedFields,
|
|
3685
3798
|
effectsContext: effectsContextEntries,
|
|
3686
3799
|
ancestors: ancestors ?? [],
|
|
3687
3800
|
perspective: effectivePerspective,
|
|
3688
3801
|
initialStage: definition.initialStage,
|
|
3689
|
-
actor
|
|
3802
|
+
actor,
|
|
3803
|
+
...fieldDiscards.length > 0 ? { extraHistory: fieldDiscards } : {}
|
|
3690
3804
|
});
|
|
3691
3805
|
return await client.create(base, SYNC_COMMIT), await primeInitialStage(client, id, actor, clientForGdr, clock), await cascade(client, id, actor, clientForGdr, clock), reload(client, id, tag);
|
|
3692
3806
|
},
|
|
3693
3807
|
/**
|
|
3694
|
-
* Fire an action against
|
|
3808
|
+
* Fire an action against an activity. If the activity is pending, it is
|
|
3695
3809
|
* auto-invoked first (pending → active) so callers don't have to know
|
|
3696
3810
|
* the lifecycle. Cascades auto-transitions and propagates to ancestors
|
|
3697
3811
|
* after the action commits.
|
|
@@ -3705,13 +3819,13 @@ const workflow = {
|
|
|
3705
3819
|
client,
|
|
3706
3820
|
tag,
|
|
3707
3821
|
instanceId,
|
|
3708
|
-
|
|
3822
|
+
activity,
|
|
3709
3823
|
action,
|
|
3710
3824
|
params,
|
|
3711
3825
|
idempotent,
|
|
3712
3826
|
resourceClients
|
|
3713
3827
|
} = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
|
|
3714
|
-
return findOpenStageEntry(before)?.
|
|
3828
|
+
return findOpenStageEntry(before)?.activities.find((t) => t.name === activity) === void 0 && idempotent === !0 ? { instance: before, cascaded: 0, fired: !1 } : dispatchGatedWrite({
|
|
3715
3829
|
client,
|
|
3716
3830
|
tag,
|
|
3717
3831
|
instanceId,
|
|
@@ -3721,10 +3835,10 @@ const workflow = {
|
|
|
3721
3835
|
clock,
|
|
3722
3836
|
before,
|
|
3723
3837
|
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3724
|
-
apply: (instance, evaluation) => (assertActionAllowed(evaluation,
|
|
3838
|
+
apply: (instance, evaluation) => (assertActionAllowed(evaluation, activity, action), applyAction({
|
|
3725
3839
|
client,
|
|
3726
3840
|
instance,
|
|
3727
|
-
|
|
3841
|
+
activity,
|
|
3728
3842
|
action,
|
|
3729
3843
|
actor,
|
|
3730
3844
|
...access.grants !== void 0 ? { grants: access.grants } : {},
|
|
@@ -3805,7 +3919,7 @@ const workflow = {
|
|
|
3805
3919
|
* Re-evaluate auto-transitions and resolve due waits until stable.
|
|
3806
3920
|
*
|
|
3807
3921
|
* Used by the runtime after any event that might affect the workflow
|
|
3808
|
-
* but isn't itself
|
|
3922
|
+
* but isn't itself an activity action: a subject doc was patched, a sibling
|
|
3809
3923
|
* workflow completed, the clock crossed a `completeWhen` deadline, etc.
|
|
3810
3924
|
* The runtime doesn't need to know what changed — it just nudges
|
|
3811
3925
|
* affected instances and the engine re-evaluates.
|
|
@@ -3915,8 +4029,8 @@ const workflow = {
|
|
|
3915
4029
|
* declared by a `doc.ref` / `doc.refs` entry in scope), then
|
|
3916
4030
|
* evaluates the supplied GROQ in groq-js against that dataset. The
|
|
3917
4031
|
* rendered scope is auto-bound: `$self`, `$fields`, `$parent`,
|
|
3918
|
-
* `$ancestors`, `$stage`, `$now`, `$effects`, `$
|
|
3919
|
-
* `$
|
|
4032
|
+
* `$ancestors`, `$stage`, `$now`, `$effects`, `$activities`,
|
|
4033
|
+
* `$allActivitiesDone`, `$anyActivityFailed` — ids in GDR URI form to match
|
|
3920
4034
|
* the snapshot's keying.
|
|
3921
4035
|
*
|
|
3922
4036
|
* Use when an external consumer (a UI, a debug pane, a test) wants
|
|
@@ -3969,34 +4083,83 @@ const workflow = {
|
|
|
3969
4083
|
* List the actions an actor could fire on an instance's current stage,
|
|
3970
4084
|
* each flagged `allowed` (with a structured `disabledReason` when not).
|
|
3971
4085
|
* Projects the instance from the actor's perspective and flattens its
|
|
3972
|
-
*
|
|
4086
|
+
* activities' actions. Returns the evaluation alongside the actions so a
|
|
3973
4087
|
* consumer can read the instance/stage context. Pure read.
|
|
3974
4088
|
*/
|
|
3975
4089
|
availableActions: async (args) => {
|
|
3976
4090
|
const evaluation = await evaluateInstance(args);
|
|
3977
|
-
return { evaluation, actions: availableActions(evaluation.currentStage.
|
|
4091
|
+
return { evaluation, actions: availableActions(evaluation.currentStage.activities) };
|
|
3978
4092
|
},
|
|
3979
4093
|
/**
|
|
3980
4094
|
* Materialised spawned children of a parent instance.
|
|
3981
4095
|
*
|
|
3982
4096
|
* Walks `history` for `spawned` events — the durable
|
|
3983
|
-
* record. (The per-
|
|
4097
|
+
* record. (The per-activity `spawnedInstances` field on the active stage
|
|
3984
4098
|
* only exists while that stage is current; history survives.) Strips
|
|
3985
4099
|
* the GDR URI on each `instanceRef` to a bare `_id`, fetches the
|
|
3986
4100
|
* instances, drops any that aren't visible to this engine's tag, and
|
|
3987
4101
|
* returns them sorted by `startedAt` ascending.
|
|
3988
4102
|
*
|
|
3989
|
-
* Pass `
|
|
4103
|
+
* Pass `activity` to restrict to a single spawning activity on the parent.
|
|
3990
4104
|
*/
|
|
3991
4105
|
children: async (args) => {
|
|
3992
|
-
const { client, tag, instanceId,
|
|
4106
|
+
const { client, tag, instanceId, activity } = args;
|
|
3993
4107
|
validateTag(tag);
|
|
3994
|
-
const parent = await reload(client, instanceId, tag), isSpawned = (h) => h._type === "spawned", ids = parent.history.filter(isSpawned).filter((h) =>
|
|
4108
|
+
const parent = await reload(client, instanceId, tag), isSpawned = (h) => h._type === "spawned", ids = parent.history.filter(isSpawned).filter((h) => activity === void 0 || h.activity === activity).flatMap((h) => bareIdFromSpawnRef(h.instanceRef.id));
|
|
3995
4109
|
return ids.length === 0 ? [] : client.fetch(
|
|
3996
4110
|
`*[_id in $ids && ${tagScopeFilter()}] | order(startedAt asc)`,
|
|
3997
4111
|
{ ids, tag }
|
|
3998
4112
|
);
|
|
3999
4113
|
},
|
|
4114
|
+
/**
|
|
4115
|
+
* Every in-flight instance whose reactive watch-set includes `document` —
|
|
4116
|
+
* the reverse of {@link subscriptionDocumentsForInstance}.
|
|
4117
|
+
*
|
|
4118
|
+
* For a non-reactive, content-change-driven runtime (a Sanity Function, an
|
|
4119
|
+
* Inngest/durable worker, any server) that holds no instances in memory: a
|
|
4120
|
+
* document changed; which instances should it `tick`? The watch-set covers
|
|
4121
|
+
* the instance itself, its ancestors, and the docs named by
|
|
4122
|
+
* `doc.ref` / `doc.refs` / `release.ref` field entries on the workflow scope
|
|
4123
|
+
* **and the current stage** — so a hand-rolled GROQ over `fields[]` gets it
|
|
4124
|
+
* subtly wrong (misses stage-scope refs, `release.ref`, ancestors).
|
|
4125
|
+
*
|
|
4126
|
+
* A coarse GROQ prefilter narrows candidates server-side (in-flight,
|
|
4127
|
+
* tag-scoped, mentioning the doc anywhere in state); the authoritative match
|
|
4128
|
+
* is {@link instanceWatchesDocument}, derived from `collectWatchRefs`, so the
|
|
4129
|
+
* reverse stays in lockstep with the forward set and honours the
|
|
4130
|
+
* open-stage-only rule the prefilter deliberately over-approximates.
|
|
4131
|
+
*
|
|
4132
|
+
* Single-resource: instances always live in the engine's own resource, so
|
|
4133
|
+
* this reads one client (unlike {@link guardsForInstance}, whose guard docs
|
|
4134
|
+
* are scattered across the watched resources). "Routed by resource" applies
|
|
4135
|
+
* to the **subject**: a cross-dataset doc is matched by its full,
|
|
4136
|
+
* resource-qualified GDR URI, never its bare id, so an instance watching
|
|
4137
|
+
* `dataset:A:ds:doc` is not matched by a change to `dataset:B:ds:doc`.
|
|
4138
|
+
*
|
|
4139
|
+
* `document` must be a resource-qualified GDR URI; a bare id is rejected
|
|
4140
|
+
* (it can't be resource-routed and would silently mismatch). Sorted by
|
|
4141
|
+
* `startedAt` ascending.
|
|
4142
|
+
*/
|
|
4143
|
+
instancesForDocument: async (args) => {
|
|
4144
|
+
const { client, tag, document } = args;
|
|
4145
|
+
if (validateTag(tag), !isGdrUri(document))
|
|
4146
|
+
throw new Error(
|
|
4147
|
+
`workflow.instancesForDocument: "document" must be a resource-qualified GDR URI (e.g. "dataset:project:dataset:doc-id"); got: ${JSON.stringify(document)}`
|
|
4148
|
+
);
|
|
4149
|
+
const fieldRefsDoc = "count(fields[value.id == $document]) > 0 || count(fields[$document in value[].id]) > 0", query = `*[
|
|
4150
|
+
_type == "${WORKFLOW_INSTANCE_TYPE}" && ${tagScopeFilter()} && !defined(completedAt) && (
|
|
4151
|
+
_id == $bareId ||
|
|
4152
|
+
$document in ancestors[].id ||
|
|
4153
|
+
${fieldRefsDoc} ||
|
|
4154
|
+
count(stages[${fieldRefsDoc}]) > 0
|
|
4155
|
+
)
|
|
4156
|
+
] | order(startedAt asc)`;
|
|
4157
|
+
return (await client.fetch(query, {
|
|
4158
|
+
tag,
|
|
4159
|
+
document,
|
|
4160
|
+
bareId: extractDocumentId(document)
|
|
4161
|
+
})).filter((instance) => instanceWatchesDocument(instance, document));
|
|
4162
|
+
},
|
|
4000
4163
|
/**
|
|
4001
4164
|
* Permission helpers — Sanity ACL grants evaluated against documents
|
|
4002
4165
|
* via GROQ. Used by `workflow.evaluate` to soft-gate actions when the
|
|
@@ -4050,10 +4213,13 @@ function walkEffectNames(def, visit) {
|
|
|
4050
4213
|
for (const e of effects ?? []) visit(e.name, location);
|
|
4051
4214
|
};
|
|
4052
4215
|
for (const stage of def.stages ?? []) {
|
|
4053
|
-
for (const t of stage.
|
|
4054
|
-
visitEffects(t.effects, `stage[${stage.name}].
|
|
4216
|
+
for (const t of stage.activities ?? []) {
|
|
4217
|
+
visitEffects(t.effects, `stage[${stage.name}].activity[${t.name}].effects`);
|
|
4055
4218
|
for (const a of t.actions ?? [])
|
|
4056
|
-
visitEffects(
|
|
4219
|
+
visitEffects(
|
|
4220
|
+
a.effects,
|
|
4221
|
+
`stage[${stage.name}].activity[${t.name}].action[${a.name}].effects`
|
|
4222
|
+
);
|
|
4057
4223
|
}
|
|
4058
4224
|
for (const tr of stage.transitions ?? [])
|
|
4059
4225
|
visitEffects(tr.effects, `stage[${stage.name}].transition[${tr.name}].effects`);
|
|
@@ -4069,7 +4235,7 @@ async function drainEffectsInternal(args) {
|
|
|
4069
4235
|
effectHandlers,
|
|
4070
4236
|
missingHandler,
|
|
4071
4237
|
logger
|
|
4072
|
-
} = args, drainerActor = args.access?.actor ??
|
|
4238
|
+
} = args, routeGdr = buildClientForGdr(client, resourceClients), clientFor = (ref) => routeGdr(parseGdr(typeof ref == "string" ? ref : ref.id)), drainerActor = args.access?.actor ?? engineSystemActor("drainEffects"), completionAccess = {
|
|
4073
4239
|
actor: drainerActor,
|
|
4074
4240
|
...args.access?.grants !== void 0 ? { grants: args.access.grants } : {}
|
|
4075
4241
|
};
|
|
@@ -4088,6 +4254,7 @@ async function drainEffectsInternal(args) {
|
|
|
4088
4254
|
if (!await claimPendingEffect(client, before, candidate._key, drainerActor)) continue;
|
|
4089
4255
|
const { outputs, dispatchError } = await dispatchEffect(handler, candidate, {
|
|
4090
4256
|
client,
|
|
4257
|
+
clientFor,
|
|
4091
4258
|
instanceId,
|
|
4092
4259
|
logger
|
|
4093
4260
|
});
|
|
@@ -4142,6 +4309,7 @@ async function dispatchEffect(handler, candidate, ctx) {
|
|
|
4142
4309
|
try {
|
|
4143
4310
|
const result = await handler(candidate.params, {
|
|
4144
4311
|
client: ctx.client,
|
|
4312
|
+
clientFor: ctx.clientFor,
|
|
4145
4313
|
instanceId: ctx.instanceId,
|
|
4146
4314
|
effectKey: candidate._key,
|
|
4147
4315
|
log: (message, extra) => ctx.logger(`effect.${candidate.name}`).info(message, extra)
|
|
@@ -4230,13 +4398,13 @@ function createInstanceSession(args) {
|
|
|
4230
4398
|
return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: cascaded > 0 };
|
|
4231
4399
|
});
|
|
4232
4400
|
},
|
|
4233
|
-
fireAction({
|
|
4401
|
+
fireAction({ activity, action, params }) {
|
|
4234
4402
|
return commit(async (actor, held) => {
|
|
4235
|
-
assertActionAllowed(await evaluateWith(held, heldGuards),
|
|
4403
|
+
assertActionAllowed(await evaluateWith(held, heldGuards), activity, action);
|
|
4236
4404
|
const { grants } = await access(), ranOps = await applyAction({
|
|
4237
4405
|
client,
|
|
4238
4406
|
instance,
|
|
4239
|
-
|
|
4407
|
+
activity,
|
|
4240
4408
|
action,
|
|
4241
4409
|
actor,
|
|
4242
4410
|
clientForGdr,
|
|
@@ -4343,13 +4511,14 @@ function createEngine(args) {
|
|
|
4343
4511
|
definition,
|
|
4344
4512
|
...resourceClients !== void 0 ? { resourceClients } : {}
|
|
4345
4513
|
}),
|
|
4346
|
-
children: ({ instanceId,
|
|
4514
|
+
children: ({ instanceId, activity }) => workflow.children({
|
|
4347
4515
|
client,
|
|
4348
4516
|
tag,
|
|
4349
4517
|
workflowResource,
|
|
4350
4518
|
instanceId,
|
|
4351
|
-
...
|
|
4519
|
+
...activity !== void 0 ? { activity } : {}
|
|
4352
4520
|
}),
|
|
4521
|
+
instancesForDocument: ({ document }) => workflow.instancesForDocument({ client, tag, workflowResource, document }),
|
|
4353
4522
|
query: ({ groq, params }) => workflow.query({
|
|
4354
4523
|
client,
|
|
4355
4524
|
tag,
|
|
@@ -4395,33 +4564,25 @@ function createEngine(args) {
|
|
|
4395
4564
|
})
|
|
4396
4565
|
};
|
|
4397
4566
|
}
|
|
4398
|
-
function
|
|
4399
|
-
|
|
4400
|
-
}
|
|
4401
|
-
function diffStatus(existing, expected) {
|
|
4402
|
-
return existing === void 0 ? "create" : isDefinitionUnchanged(existing, expected) ? "unchanged" : "update";
|
|
4403
|
-
}
|
|
4404
|
-
function diffEntry(def, existingRaw, target) {
|
|
4405
|
-
const docId = docIdFor(def, target), expected = {
|
|
4406
|
-
...def,
|
|
4407
|
-
_id: docId,
|
|
4408
|
-
_type: schema.WORKFLOW_DEFINITION_TYPE,
|
|
4409
|
-
tag: target.tag
|
|
4410
|
-
}, status = diffStatus(existingRaw, expected), existing = existingRaw ? stripSystemFields(existingRaw) : void 0;
|
|
4567
|
+
function diffEntry(def, latestRaw, target) {
|
|
4568
|
+
const plan = planDefinitionDeploy(def, asLatest(latestRaw), target), existing = latestRaw ? stripSystemFields(latestRaw) : void 0;
|
|
4411
4569
|
return {
|
|
4412
4570
|
name: def.name,
|
|
4413
|
-
version:
|
|
4414
|
-
status,
|
|
4415
|
-
docId,
|
|
4416
|
-
expected,
|
|
4571
|
+
version: plan.version,
|
|
4572
|
+
status: plan.status,
|
|
4573
|
+
docId: plan.docId,
|
|
4574
|
+
expected: plan.document,
|
|
4417
4575
|
...existing !== void 0 ? { existing } : {}
|
|
4418
4576
|
};
|
|
4419
4577
|
}
|
|
4578
|
+
function asLatest(raw) {
|
|
4579
|
+
return raw === void 0 ? void 0 : { version: typeof raw.version == "number" ? raw.version : 0, ...typeof raw.contentHash == "string" ? { contentHash: raw.contentHash } : {} };
|
|
4580
|
+
}
|
|
4420
4581
|
async function computeDiffEntries(client, defs, target) {
|
|
4421
4582
|
const entries = [];
|
|
4422
4583
|
for (const def of defs) {
|
|
4423
|
-
const
|
|
4424
|
-
entries.push(diffEntry(def,
|
|
4584
|
+
const latest = await loadLatestDeployed(client, def.name, target.tag);
|
|
4585
|
+
entries.push(diffEntry(def, latest, target));
|
|
4425
4586
|
}
|
|
4426
4587
|
return entries;
|
|
4427
4588
|
}
|
|
@@ -4434,17 +4595,17 @@ const HISTORY_DISPLAY = {
|
|
|
4434
4595
|
title: "Stage exited",
|
|
4435
4596
|
description: "The instance left a stage on the way to the next one."
|
|
4436
4597
|
},
|
|
4437
|
-
|
|
4438
|
-
title: "
|
|
4439
|
-
description: "
|
|
4598
|
+
activityActivated: {
|
|
4599
|
+
title: "Activity activated",
|
|
4600
|
+
description: "An activity flipped from `pending` to `active` and its onEnter effects queued."
|
|
4440
4601
|
},
|
|
4441
|
-
|
|
4442
|
-
title: "
|
|
4443
|
-
description: "An action or op flipped
|
|
4602
|
+
activityStatusChanged: {
|
|
4603
|
+
title: "Activity status changed",
|
|
4604
|
+
description: "An action or op flipped an activity's status (active \u2192 done / skipped / failed)."
|
|
4444
4605
|
},
|
|
4445
4606
|
actionFired: {
|
|
4446
4607
|
title: "Action fired",
|
|
4447
|
-
description: "An action was invoked against
|
|
4608
|
+
description: "An action was invoked against an activity \u2014 ops ran, status may have flipped, effects queued."
|
|
4448
4609
|
},
|
|
4449
4610
|
transitionFired: {
|
|
4450
4611
|
title: "Transition fired",
|
|
@@ -4460,7 +4621,7 @@ const HISTORY_DISPLAY = {
|
|
|
4460
4621
|
},
|
|
4461
4622
|
spawned: {
|
|
4462
4623
|
title: "Spawned child",
|
|
4463
|
-
description: "
|
|
4624
|
+
description: "An activity's `subworkflows` declaration spawned a child workflow instance."
|
|
4464
4625
|
},
|
|
4465
4626
|
aborted: {
|
|
4466
4627
|
title: "Instance aborted",
|
|
@@ -4469,6 +4630,10 @@ const HISTORY_DISPLAY = {
|
|
|
4469
4630
|
opApplied: {
|
|
4470
4631
|
title: "Op applied",
|
|
4471
4632
|
description: "An inline field-mutation op (field.set / field.append / status.set / etc.) ran during an action commit."
|
|
4633
|
+
},
|
|
4634
|
+
fieldQueryDiscarded: {
|
|
4635
|
+
title: "Query result discarded",
|
|
4636
|
+
description: "A query-sourced field's GROQ result didn't fit the field's declared kind, so it was discarded and the field fell back to its default."
|
|
4472
4637
|
}
|
|
4473
4638
|
}, FIELD_SLOT_DISPLAY = {
|
|
4474
4639
|
"doc.ref": {
|
|
@@ -4483,50 +4648,58 @@ const HISTORY_DISPLAY = {
|
|
|
4483
4648
|
title: "Content Release reference",
|
|
4484
4649
|
description: "GDR pointer at a Content Release system doc, carrying the release name the engine derives the instance's read perspective from."
|
|
4485
4650
|
},
|
|
4486
|
-
|
|
4487
|
-
title: "Query result",
|
|
4488
|
-
description: "Snapshot of a GROQ projection captured at entry-resolve time; resolvedAt is stamped on the value."
|
|
4489
|
-
},
|
|
4490
|
-
"value.string": {
|
|
4651
|
+
string: {
|
|
4491
4652
|
title: "String value",
|
|
4492
|
-
description: "Free-form string entry."
|
|
4653
|
+
description: "Free-form single-line string entry."
|
|
4493
4654
|
},
|
|
4494
|
-
|
|
4495
|
-
title: "
|
|
4496
|
-
description: "
|
|
4655
|
+
text: {
|
|
4656
|
+
title: "Text value",
|
|
4657
|
+
description: "Free-form multiline string entry."
|
|
4497
4658
|
},
|
|
4498
|
-
|
|
4659
|
+
number: {
|
|
4499
4660
|
title: "Number value",
|
|
4500
4661
|
description: "Numeric entry."
|
|
4501
4662
|
},
|
|
4502
|
-
|
|
4663
|
+
boolean: {
|
|
4503
4664
|
title: "Boolean value",
|
|
4504
4665
|
description: "True/false entry."
|
|
4505
4666
|
},
|
|
4506
|
-
|
|
4667
|
+
date: {
|
|
4668
|
+
title: "Date value",
|
|
4669
|
+
description: "Date-only (YYYY-MM-DD) entry, no time component."
|
|
4670
|
+
},
|
|
4671
|
+
datetime: {
|
|
4507
4672
|
title: "Date / time value",
|
|
4508
|
-
description: "ISO-timestamp entry."
|
|
4673
|
+
description: "ISO-8601 timestamp entry."
|
|
4509
4674
|
},
|
|
4510
|
-
|
|
4511
|
-
title: "
|
|
4512
|
-
description: "
|
|
4675
|
+
url: {
|
|
4676
|
+
title: "URL value",
|
|
4677
|
+
description: "Validated URL entry."
|
|
4513
4678
|
},
|
|
4514
|
-
|
|
4515
|
-
title: "
|
|
4516
|
-
description: "
|
|
4679
|
+
actor: {
|
|
4680
|
+
title: "Actor value",
|
|
4681
|
+
description: "A typed (user|ai|system) actor identity \u2014 a concrete principal."
|
|
4517
4682
|
},
|
|
4518
|
-
|
|
4519
|
-
title: "
|
|
4520
|
-
description: "
|
|
4683
|
+
assignee: {
|
|
4684
|
+
title: "Assignee",
|
|
4685
|
+
description: "A single typed (user|role) assignee \u2014 who is expected/responsible."
|
|
4521
4686
|
},
|
|
4522
4687
|
assignees: {
|
|
4523
4688
|
title: "Assignees",
|
|
4524
4689
|
description: "Ordered list of typed (user|role) assignees."
|
|
4690
|
+
},
|
|
4691
|
+
object: {
|
|
4692
|
+
title: "Object",
|
|
4693
|
+
description: "An object with named sub-fields; the value is keyed by sub-field name."
|
|
4694
|
+
},
|
|
4695
|
+
array: {
|
|
4696
|
+
title: "Array",
|
|
4697
|
+
description: "An array of objects shaped by the declared `of` sub-fields; ops add/update/remove rows."
|
|
4525
4698
|
}
|
|
4526
4699
|
}, OP_DISPLAY = {
|
|
4527
4700
|
"field.set": {
|
|
4528
4701
|
title: "Set field entry",
|
|
4529
|
-
description: "Overwrite a field entry's value with a resolved
|
|
4702
|
+
description: "Overwrite a field entry's value with a resolved value expression."
|
|
4530
4703
|
},
|
|
4531
4704
|
"field.unset": {
|
|
4532
4705
|
title: "Unset field entry",
|
|
@@ -4534,7 +4707,7 @@ const HISTORY_DISPLAY = {
|
|
|
4534
4707
|
},
|
|
4535
4708
|
"field.append": {
|
|
4536
4709
|
title: "Append to field entry",
|
|
4537
|
-
description: "Push a resolved item onto an array-kind entry (
|
|
4710
|
+
description: "Push a resolved item onto an array-kind entry (array, assignees, doc.refs)."
|
|
4538
4711
|
},
|
|
4539
4712
|
"field.updateWhere": {
|
|
4540
4713
|
title: "Update matching rows",
|
|
@@ -4545,8 +4718,8 @@ const HISTORY_DISPLAY = {
|
|
|
4545
4718
|
description: "Drop rows of an array-kind entry that match the predicate."
|
|
4546
4719
|
},
|
|
4547
4720
|
"status.set": {
|
|
4548
|
-
title: "Set
|
|
4549
|
-
description: "Flip
|
|
4721
|
+
title: "Set activity status",
|
|
4722
|
+
description: "Flip an activity's status explicitly (the action-level `status:` sugar desugars to this)."
|
|
4550
4723
|
}
|
|
4551
4724
|
}, EFFECTS_CONTEXT_DISPLAY = {
|
|
4552
4725
|
"effectsContext.string": {
|
|
@@ -4586,6 +4759,35 @@ const HISTORY_DISPLAY = {
|
|
|
4586
4759
|
title: "Workflow instance",
|
|
4587
4760
|
description: "A running (or finished) workflow against its declared fields."
|
|
4588
4761
|
}
|
|
4762
|
+
}, ACTIVITY_KIND_DISPLAY = {
|
|
4763
|
+
user: {
|
|
4764
|
+
title: "User activity",
|
|
4765
|
+
description: "A person acts via the app \u2014 renders action buttons ranked by outcome."
|
|
4766
|
+
},
|
|
4767
|
+
service: {
|
|
4768
|
+
title: "Service activity",
|
|
4769
|
+
description: "An automated system or effect does the work \u2014 renders effect drain status."
|
|
4770
|
+
},
|
|
4771
|
+
script: {
|
|
4772
|
+
title: "Script activity",
|
|
4773
|
+
description: "The engine runs a step inline and resolves on activation \u2014 an audit row."
|
|
4774
|
+
},
|
|
4775
|
+
manual: {
|
|
4776
|
+
title: "Manual activity",
|
|
4777
|
+
description: "Work done off-system, acknowledged by a person or verified by a predicate \u2014 renders an instruction card with a deep-link."
|
|
4778
|
+
},
|
|
4779
|
+
receive: {
|
|
4780
|
+
title: "Receive activity",
|
|
4781
|
+
description: 'No actor; the engine waits on a condition \u2014 renders "waiting until \u2026", no buttons.'
|
|
4782
|
+
}
|
|
4783
|
+
}, DRIVER_KIND_DISPLAY = {
|
|
4784
|
+
person: { title: "Person", description: "A human fired this action." },
|
|
4785
|
+
agent: { title: "Agent", description: "An AI agent fired this action." },
|
|
4786
|
+
service: { title: "Service", description: "An external system or Function fired this action." },
|
|
4787
|
+
engine: {
|
|
4788
|
+
title: "Engine",
|
|
4789
|
+
description: "The workflow engine itself drove this \u2014 a gate, machine step, or housekeeping."
|
|
4790
|
+
}
|
|
4589
4791
|
}, DISPLAY = {
|
|
4590
4792
|
...HISTORY_DISPLAY,
|
|
4591
4793
|
...FIELD_SLOT_DISPLAY,
|
|
@@ -4600,8 +4802,11 @@ function displayDescription(typeKey) {
|
|
|
4600
4802
|
if (typeKey)
|
|
4601
4803
|
return DISPLAY[typeKey]?.description;
|
|
4602
4804
|
}
|
|
4805
|
+
exports.ACTIVITY_KINDS = schema.ACTIVITY_KINDS;
|
|
4806
|
+
exports.DRIVER_KINDS = schema.DRIVER_KINDS;
|
|
4603
4807
|
exports.WORKFLOW_DEFINITION_TYPE = schema.WORKFLOW_DEFINITION_TYPE;
|
|
4604
4808
|
exports.isStartableDefinition = schema.isStartableDefinition;
|
|
4809
|
+
exports.ACTIVITY_KIND_DISPLAY = ACTIVITY_KIND_DISPLAY;
|
|
4605
4810
|
exports.AUTHORING_DISPLAY = AUTHORING_DISPLAY;
|
|
4606
4811
|
exports.ActionDisabledError = ActionDisabledError;
|
|
4607
4812
|
exports.ActionParamsInvalidError = ActionParamsInvalidError;
|
|
@@ -4610,6 +4815,7 @@ exports.ConcurrentEditFieldError = ConcurrentEditFieldError;
|
|
|
4610
4815
|
exports.ConcurrentFireActionError = ConcurrentFireActionError;
|
|
4611
4816
|
exports.DEFAULT_CONTENT_PERSPECTIVE = DEFAULT_CONTENT_PERSPECTIVE;
|
|
4612
4817
|
exports.DISPLAY = DISPLAY;
|
|
4818
|
+
exports.DRIVER_KIND_DISPLAY = DRIVER_KIND_DISPLAY;
|
|
4613
4819
|
exports.EFFECTS_CONTEXT_DISPLAY = EFFECTS_CONTEXT_DISPLAY;
|
|
4614
4820
|
exports.EditFieldDeniedError = EditFieldDeniedError;
|
|
4615
4821
|
exports.FIELD_SLOT_DISPLAY = FIELD_SLOT_DISPLAY;
|
|
@@ -4625,6 +4831,7 @@ exports.WORKFLOW_INSTANCE_TYPE = WORKFLOW_INSTANCE_TYPE;
|
|
|
4625
4831
|
exports.WorkflowStateDivergedError = WorkflowStateDivergedError;
|
|
4626
4832
|
exports.abortReason = abortReason;
|
|
4627
4833
|
exports.actionVerdict = actionVerdict;
|
|
4834
|
+
exports.activityKind = activityKind;
|
|
4628
4835
|
exports.availableActions = availableActions;
|
|
4629
4836
|
exports.buildSnapshot = buildSnapshot;
|
|
4630
4837
|
exports.compileGuard = compileGuard;
|
|
@@ -4635,11 +4842,13 @@ exports.datasetResourceParts = datasetResourceParts;
|
|
|
4635
4842
|
exports.defaultLoggerFactory = defaultLoggerFactory;
|
|
4636
4843
|
exports.denyingGuards = denyingGuards;
|
|
4637
4844
|
exports.deployStageGuards = deployStageGuards;
|
|
4845
|
+
exports.deriveActivityKind = deriveActivityKind;
|
|
4638
4846
|
exports.diagnoseInputFromEvaluation = diagnoseInputFromEvaluation;
|
|
4639
4847
|
exports.diagnoseInstance = diagnoseInstance;
|
|
4640
4848
|
exports.diffEntry = diffEntry;
|
|
4641
4849
|
exports.displayDescription = displayDescription;
|
|
4642
4850
|
exports.displayTitle = displayTitle;
|
|
4851
|
+
exports.driverKind = driverKind;
|
|
4643
4852
|
exports.effectsContextMap = effectsContextMap;
|
|
4644
4853
|
exports.evaluateFromSnapshot = evaluateFromSnapshot;
|
|
4645
4854
|
exports.evaluateMutationGuard = evaluateMutationGuard;
|
|
@@ -4651,8 +4860,9 @@ exports.guardMatches = guardMatches;
|
|
|
4651
4860
|
exports.guardsForDefinition = guardsForDefinition;
|
|
4652
4861
|
exports.guardsForInstance = guardsForInstance;
|
|
4653
4862
|
exports.guardsForResource = guardsForResource;
|
|
4863
|
+
exports.hashDefinitionContent = hashDefinitionContent;
|
|
4654
4864
|
exports.instanceGuardQuery = instanceGuardQuery;
|
|
4655
|
-
exports.
|
|
4865
|
+
exports.instanceWatchesDocument = instanceWatchesDocument;
|
|
4656
4866
|
exports.isGdr = isGdr;
|
|
4657
4867
|
exports.isTerminalStage = isTerminalStage;
|
|
4658
4868
|
exports.lakeGuardId = lakeGuardId;
|