@sanity/workflow-engine 0.11.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 +158 -79
- package/dist/_chunks-cjs/schema.cjs.map +1 -1
- package/dist/_chunks-es/schema.js +159 -80
- package/dist/_chunks-es/schema.js.map +1 -1
- package/dist/define.cjs +185 -137
- package/dist/define.cjs.map +1 -1
- package/dist/define.d.cts +6180 -3587
- package/dist/define.d.ts +6180 -3587
- package/dist/define.js +186 -138
- package/dist/define.js.map +1 -1
- package/dist/index.cjs +1103 -946
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10238 -6915
- package/dist/index.d.ts +10238 -6915
- package/dist/index.js +1105 -948
- 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,348 +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 ENGINE_ACTOR_ID_PREFIX = "engine.";
|
|
1670
|
-
function engineSystemActor(name) {
|
|
1671
|
-
return { kind: "system", id: `${ENGINE_ACTOR_ID_PREFIX}${name}` };
|
|
1672
|
-
}
|
|
1673
|
-
function isEngineActor(actor) {
|
|
1674
|
-
return actor.kind === "system" && actor.id.startsWith(ENGINE_ACTOR_ID_PREFIX);
|
|
1675
|
-
}
|
|
1676
|
-
const MAX_SPAWN_DEPTH = 6, SUBWORKFLOWS_ALL_DONE = "count($subworkflows[status != 'done']) == 0";
|
|
1677
|
-
function gateActor(outcome) {
|
|
1678
|
-
return engineSystemActor(outcome === "failed" ? "failWhen" : "completeWhen");
|
|
1679
|
-
}
|
|
1680
|
-
function effectiveCompleteWhen(task) {
|
|
1681
|
-
return task.completeWhen ?? (task.subworkflows !== void 0 ? SUBWORKFLOWS_ALL_DONE : void 0);
|
|
1682
|
-
}
|
|
1683
|
-
async function evaluateResolutionGate(ctx, task, vars) {
|
|
1684
|
-
const scope = { taskName: task.name, vars };
|
|
1685
|
-
if (task.failWhen !== void 0 && await ctxEvaluateCondition(ctx, task.failWhen, scope))
|
|
1686
|
-
return "failed";
|
|
1687
|
-
const completeWhen = effectiveCompleteWhen(task);
|
|
1688
|
-
if (completeWhen !== void 0 && await ctxEvaluateCondition(ctx, completeWhen, scope))
|
|
1689
|
-
return "done";
|
|
1690
|
-
}
|
|
1691
|
-
async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
1692
|
-
if (ctx.instance.ancestors.length + 1 > MAX_SPAWN_DEPTH) {
|
|
1693
|
-
const chain = [...ctx.instance.ancestors.map((a) => a.id), selfGdr(ctx.instance)].join(" \u2192 ");
|
|
1694
|
-
throw new Error(
|
|
1695
|
-
`Subworkflow depth limit (${MAX_SPAWN_DEPTH}) exceeded on task "${task.name}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
|
|
1696
|
-
);
|
|
1697
|
-
}
|
|
1698
|
-
const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tag);
|
|
1699
|
-
if (definition === null) {
|
|
1700
|
-
const versionLabel = sub.definition.version === void 0 || sub.definition.version === "latest" ? "latest" : `v${sub.definition.version}`;
|
|
1701
|
-
throw new Error(
|
|
1702
|
-
`Subworkflow definition "${sub.definition.name}" ${versionLabel} not deployed (parent ${ctx.instance._id}, task ${task.name})`
|
|
1703
|
-
);
|
|
1704
|
-
}
|
|
1705
|
-
const parentScope = await ctxConditionParams(ctx, {
|
|
1706
|
-
taskName: task.name,
|
|
1707
|
-
...actor ? { actor } : {}
|
|
1708
|
-
}), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
|
|
1709
|
-
for (const row of rows) {
|
|
1710
|
-
const initialFields = await projectRowFields(
|
|
1711
|
-
ctx,
|
|
1712
|
-
sub,
|
|
1713
|
-
definition,
|
|
1714
|
-
parentScope,
|
|
1715
|
-
row,
|
|
1716
|
-
discoveryResource
|
|
1717
|
-
), { ref, body } = await prepareChildInstance({
|
|
1718
|
-
client: ctx.client,
|
|
1719
|
-
parent: ctx.instance,
|
|
1720
|
-
definition,
|
|
1721
|
-
initialFields,
|
|
1722
|
-
effectsContext,
|
|
1723
|
-
actor,
|
|
1724
|
-
now
|
|
1725
|
-
});
|
|
1726
|
-
refs.push(ref), mutation.pendingCreates.push(body);
|
|
1727
|
-
}
|
|
1728
|
-
return refs;
|
|
1729
|
-
}
|
|
1730
|
-
async function resolveSpawnRows(ctx, sub) {
|
|
1731
|
-
const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
|
|
1732
|
-
let value, discoveryResource;
|
|
1733
|
-
if (groq.includes("*")) {
|
|
1734
|
-
const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
|
|
1735
|
-
client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
|
|
1736
|
-
client,
|
|
1737
|
-
groq,
|
|
1738
|
-
params,
|
|
1739
|
-
// Draft-aware by default (drafts overlay published) when there's no
|
|
1740
|
-
// release, so a `forEach` discovers in-flight items the same way the
|
|
1741
|
-
// engine reads any other content.
|
|
1742
|
-
perspective: ctx.instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
|
|
1743
|
-
});
|
|
1744
|
-
} else {
|
|
1745
|
-
const tree = groqJs.parse(groq, { params });
|
|
1746
|
-
value = await (await groqJs.evaluate(tree, { dataset: [ctx.instance], params, root: ctx.instance })).get();
|
|
1747
|
-
}
|
|
1748
|
-
return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
|
|
1749
|
-
}
|
|
1750
|
-
async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
|
|
1751
|
-
const out = [];
|
|
1752
|
-
for (const [name, groq] of Object.entries(sub.with ?? {})) {
|
|
1753
|
-
const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
|
|
1754
|
-
if (declared === void 0)
|
|
1755
|
-
throw new Error(
|
|
1756
|
-
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
|
|
1757
|
-
);
|
|
1758
|
-
const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
|
|
1759
|
-
parentResource: ctx.instance.workflowResource,
|
|
1760
|
-
discoveryResource,
|
|
1761
|
-
entryName: name,
|
|
1762
|
-
childName: childDefinition.name
|
|
1763
|
-
});
|
|
1764
|
-
value != null && out.push({
|
|
1765
|
-
type: declared.type,
|
|
1766
|
-
name,
|
|
1767
|
-
value
|
|
1768
|
-
});
|
|
1769
|
-
}
|
|
1770
|
-
return out;
|
|
1771
|
-
}
|
|
1772
|
-
async function resolveContextHandoff(ctx, sub, parentScope) {
|
|
1773
|
-
const out = {};
|
|
1774
|
-
for (const [name, groq] of Object.entries(sub.context ?? {})) {
|
|
1775
|
-
const v2 = await runGroq(groq, parentScope, ctx.snapshot);
|
|
1776
|
-
v2 != null && (typeof v2 == "string" || typeof v2 == "number" || typeof v2 == "boolean" || typeof v2 == "object" && "id" in v2 && "type" in v2) && (out[name] = v2);
|
|
1777
|
-
}
|
|
1778
|
-
return out;
|
|
1779
|
-
}
|
|
1780
|
-
function canonicaliseSpawnValue(kind, value, cx) {
|
|
1781
|
-
return kind === "doc.ref" ? coerceSpawnGdr(value, cx) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, cx)).filter((v2) => v2 !== null) : value;
|
|
1782
|
-
}
|
|
1783
|
-
function assertBareIdRootsCorrectly(id, cx) {
|
|
1784
|
-
if (!(cx.discoveryResource === void 0 || sameResource(cx.discoveryResource, cx.parentResource)))
|
|
1785
|
-
throw new Error(
|
|
1786
|
-
`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.`
|
|
1787
|
-
);
|
|
1788
|
-
}
|
|
1789
|
-
function coerceSpawnGdr(raw, cx) {
|
|
1790
|
-
const toUri = (id) => isGdrUri(id) ? id : (assertBareIdRootsCorrectly(id, cx), gdrFromResource(cx.parentResource, id));
|
|
1791
|
-
if (raw == null) return null;
|
|
1792
|
-
if (typeof raw == "object" && "id" in raw && "type" in raw) {
|
|
1793
|
-
const r = raw;
|
|
1794
|
-
if (typeof r.id == "string" && typeof r.type == "string")
|
|
1795
|
-
return { id: toUri(r.id), type: r.type };
|
|
1796
|
-
}
|
|
1797
|
-
if (typeof raw == "object" && "_ref" in raw) {
|
|
1798
|
-
const r = raw;
|
|
1799
|
-
if (typeof r._ref == "string") return { id: toUri(r._ref), type: "document" };
|
|
1800
|
-
}
|
|
1801
|
-
return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
|
|
1802
|
-
}
|
|
1803
|
-
async function resolveDefinitionRef(client, ref, tag) {
|
|
1804
|
-
const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
|
|
1805
|
-
return wantsExplicit && (params.version = ref.version), await client.fetch(
|
|
1806
|
-
definitionLookupGroq(wantsExplicit),
|
|
1807
|
-
params
|
|
1808
|
-
) ?? null;
|
|
1809
|
-
}
|
|
1810
|
-
async function prepareChildInstance(args) {
|
|
1811
|
-
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 = [
|
|
1812
|
-
...parent.ancestors,
|
|
1813
|
-
{
|
|
1814
|
-
id: selfGdr(parent),
|
|
1815
|
-
type: WORKFLOW_INSTANCE_TYPE
|
|
1816
|
-
}
|
|
1817
|
-
], inheritedPerspective = parent.perspective, childFields = await resolveDeclaredFields({
|
|
1818
|
-
entryDefs: definition.fields,
|
|
1819
|
-
initialFields,
|
|
1820
|
-
ctx: {
|
|
1821
|
-
client,
|
|
1822
|
-
now,
|
|
1823
|
-
selfId: childDocId,
|
|
1824
|
-
tag: childTag,
|
|
1825
|
-
workflowResource,
|
|
1826
|
-
definitionName: definition.name,
|
|
1827
|
-
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
|
|
1828
|
-
},
|
|
1829
|
-
randomKey
|
|
1830
|
-
}), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
|
|
1831
|
-
([key, value]) => {
|
|
1832
|
-
if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
|
|
1833
|
-
if (!isGdrUri(value.id))
|
|
1834
|
-
throw new Error(
|
|
1835
|
-
`subworkflows.context["${key}"]: GDR id must be a "<scheme>:..." URI, got ${JSON.stringify(value.id)}.`
|
|
1836
|
-
);
|
|
1837
|
-
return effectsContextEntry(key, { id: value.id, type: value.type });
|
|
1838
|
-
}
|
|
1839
|
-
return effectsContextEntry(key, value);
|
|
1840
|
-
}
|
|
1841
|
-
), base = buildInstanceBase({
|
|
1842
|
-
id: childDocId,
|
|
1843
|
-
now,
|
|
1844
|
-
tag: childTag,
|
|
1845
|
-
workflowResource,
|
|
1846
|
-
definitionName: definition.name,
|
|
1847
|
-
pinnedVersion: definition.version,
|
|
1848
|
-
definition,
|
|
1849
|
-
fields: childFields,
|
|
1850
|
-
effectsContext: effectsContextEntries,
|
|
1851
|
-
ancestors,
|
|
1852
|
-
perspective: childPerspective,
|
|
1853
|
-
initialStage: definition.initialStage,
|
|
1854
|
-
actor
|
|
1855
|
-
});
|
|
1856
|
-
return { ref: childRef, body: base };
|
|
1857
|
-
}
|
|
1858
|
-
async function subworkflowRows(ctx, refs) {
|
|
1859
|
-
if (refs.length === 0) return [];
|
|
1860
|
-
const ids = refs.map((r) => gdrToBareId(r.id));
|
|
1861
|
-
return (await ctx.client.fetch("*[_id in $ids]{_id, currentStage, completedAt}", { ids })).map((c) => ({
|
|
1862
|
-
_id: c._id,
|
|
1863
|
-
stage: c.currentStage,
|
|
1864
|
-
status: c.completedAt !== void 0 && c.completedAt !== null ? "done" : "active"
|
|
1865
|
-
}));
|
|
1866
|
-
}
|
|
1867
|
-
async function resolveBindings(args) {
|
|
1868
|
-
const resolved = {};
|
|
1869
|
-
for (const [key, groq] of Object.entries(args.bindings ?? {}))
|
|
1870
|
-
resolved[key] = await runGroq(groq, args.params, args.snapshot);
|
|
1871
|
-
return { ...resolved, ...args.staticInput };
|
|
1872
|
-
}
|
|
1873
|
-
async function persistThenDeploy(ctx, mutation, deploy) {
|
|
1874
|
-
const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
|
|
1875
|
-
await deployOrRollback({
|
|
1876
|
-
client: ctx.client,
|
|
1877
|
-
instanceId: ctx.instance._id,
|
|
1878
|
-
committedRev: committed._rev,
|
|
1879
|
-
restore: instanceStateFields(ctx.instance),
|
|
1880
|
-
unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
|
|
1881
|
-
reversible: !spawned,
|
|
1882
|
-
deploy
|
|
1883
|
-
});
|
|
1884
|
-
}
|
|
1885
|
-
async function refreshStageGuards(ctx, mutation, stageName) {
|
|
1886
|
-
await deployStageGuards({
|
|
1887
|
-
client: ctx.client,
|
|
1888
|
-
clientForGdr: ctx.clientForGdr,
|
|
1889
|
-
instance: materializeInstance(ctx.instance, mutation),
|
|
1890
|
-
definition: ctx.definition,
|
|
1891
|
-
stageName,
|
|
1892
|
-
now: ctx.now
|
|
1893
|
-
});
|
|
1894
|
-
}
|
|
1895
|
-
async function completeEffect(args) {
|
|
1896
|
-
const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1897
|
-
...options?.clock ? { clock: options.clock } : {},
|
|
1898
|
-
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
1899
|
-
});
|
|
1900
|
-
return commitCompleteEffect(
|
|
1901
|
-
ctx,
|
|
1902
|
-
effectKey,
|
|
1903
|
-
status,
|
|
1904
|
-
outputs,
|
|
1905
|
-
detail,
|
|
1906
|
-
error,
|
|
1907
|
-
durationMs,
|
|
1908
|
-
options?.actor
|
|
1909
|
-
);
|
|
1910
|
-
}
|
|
1911
|
-
function buildEffectHistoryEntry(pending, outcome) {
|
|
1912
|
-
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;
|
|
1913
1663
|
return {
|
|
1914
1664
|
_key: pending._key,
|
|
1915
1665
|
name: pending.name,
|
|
@@ -1982,7 +1732,7 @@ function buildQueuedEffect(effect, origin, params, actor, now) {
|
|
|
1982
1732
|
async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
|
|
1983
1733
|
if (!effects || effects.length === 0) return;
|
|
1984
1734
|
const now = ctx.now, liveCtx = { ...ctx, instance: materializeInstance(ctx.instance, mutation) }, params = await ctxConditionParams(liveCtx, {
|
|
1985
|
-
...opts?.
|
|
1735
|
+
...opts?.activityName !== void 0 ? { activityName: opts.activityName } : {},
|
|
1986
1736
|
...actor !== void 0 ? { actor } : {},
|
|
1987
1737
|
// Caller-supplied action params, readable in bindings as `$params.<name>`.
|
|
1988
1738
|
vars: { params: opts?.callerParams ?? {} }
|
|
@@ -1997,18 +1747,74 @@ async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
|
|
|
1997
1747
|
mutation.pendingEffects.push(pending), mutation.history.push(history);
|
|
1998
1748
|
}
|
|
1999
1749
|
}
|
|
2000
|
-
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) {
|
|
2001
1809
|
switch (src.type) {
|
|
2002
1810
|
case "literal":
|
|
2003
|
-
return
|
|
1811
|
+
return src.value;
|
|
2004
1812
|
case "param":
|
|
2005
|
-
return
|
|
1813
|
+
return ctx.params?.[src.param];
|
|
2006
1814
|
case "actor":
|
|
2007
|
-
return
|
|
1815
|
+
return ctx.actor;
|
|
2008
1816
|
case "now":
|
|
2009
|
-
return
|
|
2010
|
-
default:
|
|
2011
|
-
return { handled: !1 };
|
|
1817
|
+
return ctx.now;
|
|
2012
1818
|
}
|
|
2013
1819
|
}
|
|
2014
1820
|
const FIELD_OP_TYPES = [
|
|
@@ -2021,7 +1827,7 @@ const FIELD_OP_TYPES = [
|
|
|
2021
1827
|
function isFieldOp(summary) {
|
|
2022
1828
|
return FIELD_OP_TYPES.includes(summary.opType);
|
|
2023
1829
|
}
|
|
2024
|
-
function validateActionParams(action,
|
|
1830
|
+
function validateActionParams(action, activityName, callerParams) {
|
|
2025
1831
|
const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
|
|
2026
1832
|
for (const decl of declared) {
|
|
2027
1833
|
const value = params[decl.name], present = decl.name in params && value !== void 0 && value !== null;
|
|
@@ -2035,7 +1841,7 @@ function validateActionParams(action, taskName, callerParams) {
|
|
|
2035
1841
|
}));
|
|
2036
1842
|
}
|
|
2037
1843
|
if (issues.length > 0)
|
|
2038
|
-
throw new ActionParamsInvalidError({ action: action.name,
|
|
1844
|
+
throw new ActionParamsInvalidError({ action: action.name, activity: activityName, issues });
|
|
2039
1845
|
return params;
|
|
2040
1846
|
}
|
|
2041
1847
|
function hasStringField(value, field) {
|
|
@@ -2068,7 +1874,15 @@ function runOps(args) {
|
|
|
2068
1874
|
if (ops === void 0 || ops.length === 0) return [];
|
|
2069
1875
|
const summaries = [];
|
|
2070
1876
|
for (const op of ops) {
|
|
2071
|
-
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
|
+
});
|
|
2072
1886
|
summaries.push(summary), mutation.history.push(opAppliedEntry({ origin, summary, stage, actor, now }));
|
|
2073
1887
|
}
|
|
2074
1888
|
return summaries;
|
|
@@ -2080,7 +1894,7 @@ function opAppliedEntry(args) {
|
|
|
2080
1894
|
_type: "opApplied",
|
|
2081
1895
|
at: now,
|
|
2082
1896
|
stage,
|
|
2083
|
-
...origin.
|
|
1897
|
+
...origin.activity !== void 0 ? { activity: origin.activity } : {},
|
|
2084
1898
|
...origin.action !== void 0 ? { action: origin.action } : {},
|
|
2085
1899
|
...origin.transition !== void 0 ? { transition: origin.transition } : {},
|
|
2086
1900
|
...origin.edit === !0 ? { edit: !0 } : {},
|
|
@@ -2107,13 +1921,15 @@ function applyOp(op, ctx) {
|
|
|
2107
1921
|
}
|
|
2108
1922
|
}
|
|
2109
1923
|
function applyFieldSet(op, ctx) {
|
|
2110
|
-
const value =
|
|
2111
|
-
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 } : {};
|
|
2112
1929
|
}
|
|
2113
1930
|
const EMPTY_BY_KIND = {
|
|
2114
1931
|
"doc.refs": [],
|
|
2115
|
-
|
|
2116
|
-
notes: [],
|
|
1932
|
+
array: [],
|
|
2117
1933
|
assignees: []
|
|
2118
1934
|
};
|
|
2119
1935
|
function applyFieldUnset(op, ctx) {
|
|
@@ -2121,8 +1937,13 @@ function applyFieldUnset(op, ctx) {
|
|
|
2121
1937
|
return setEntryValue(entry, EMPTY_BY_KIND[entry._type] ?? null), { opType: op.type, target: op.target };
|
|
2122
1938
|
}
|
|
2123
1939
|
function applyFieldAppend(op, ctx) {
|
|
2124
|
-
const item =
|
|
2125
|
-
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
|
+
});
|
|
2126
1947
|
const current = Array.isArray(entry.value) ? entry.value : [];
|
|
2127
1948
|
return setEntryValue(entry, [...current, withRowKey(item)]), { opType: op.type, target: op.target, resolved: { item } };
|
|
2128
1949
|
}
|
|
@@ -2130,7 +1951,7 @@ function withRowKey(item) {
|
|
|
2130
1951
|
return typeof item == "object" && item !== null && !Array.isArray(item) && !("_key" in item) ? { _key: randomKey(), ...item } : item;
|
|
2131
1952
|
}
|
|
2132
1953
|
function applyFieldUpdateWhere(op, ctx) {
|
|
2133
|
-
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);
|
|
2134
1955
|
if (merge === null || typeof merge != "object" || Array.isArray(merge))
|
|
2135
1956
|
throw new Error(
|
|
2136
1957
|
`field.updateWhere value must resolve to an object of fields to merge (target "${op.target.field}")`
|
|
@@ -2156,145 +1977,385 @@ function requireArrayValue(entry, op) {
|
|
|
2156
1977
|
);
|
|
2157
1978
|
return entry.value;
|
|
2158
1979
|
}
|
|
2159
|
-
function applyStatusSet(op, ctx) {
|
|
2160
|
-
const entry = findOpenStageEntry(ctx.mutation)?.
|
|
2161
|
-
if (entry === void 0)
|
|
2162
|
-
throw new Error(
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
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;
|
|
2171
2192
|
}
|
|
2172
|
-
function
|
|
2173
|
-
|
|
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;
|
|
2174
2195
|
}
|
|
2175
|
-
function
|
|
2176
|
-
|
|
2177
|
-
if (entry === void 0)
|
|
2196
|
+
function assertBareIdRootsCorrectly(id, cx) {
|
|
2197
|
+
if (!(cx.discoveryResource === void 0 || sameResource(cx.discoveryResource, cx.parentResource)))
|
|
2178
2198
|
throw new Error(
|
|
2179
|
-
`
|
|
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.`
|
|
2180
2200
|
);
|
|
2181
|
-
return entry;
|
|
2182
|
-
}
|
|
2183
|
-
function fieldHost(ctx, scope) {
|
|
2184
|
-
if (scope === "workflow") return ctx.mutation.fields;
|
|
2185
|
-
const stageEntry = findOpenStageEntry(ctx.mutation);
|
|
2186
|
-
if (scope === "stage") return stageEntry?.fields;
|
|
2187
|
-
const task = stageEntry?.tasks.find((t) => t.name === ctx.taskName);
|
|
2188
|
-
return task !== void 0 && task.fields === void 0 && (task.fields = []), task?.fields;
|
|
2189
2201
|
}
|
|
2190
|
-
function
|
|
2191
|
-
const
|
|
2192
|
-
if (
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
}
|
|
2202
|
-
case "object": {
|
|
2203
|
-
const out = {};
|
|
2204
|
-
for (const [field, fieldSrc] of Object.entries(src.fields))
|
|
2205
|
-
out[field] = resolveOpSource(fieldSrc, ctx);
|
|
2206
|
-
return out;
|
|
2207
|
-
}
|
|
2208
|
-
default:
|
|
2209
|
-
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" };
|
|
2210
2213
|
}
|
|
2214
|
+
return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
|
|
2211
2215
|
}
|
|
2212
|
-
function
|
|
2213
|
-
|
|
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;
|
|
2214
2222
|
}
|
|
2215
|
-
function
|
|
2216
|
-
const
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
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 };
|
|
2221
2273
|
}
|
|
2222
|
-
function
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
}
|
|
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
|
+
}));
|
|
2231
2282
|
}
|
|
2232
|
-
async function
|
|
2233
|
-
const { client, instanceId,
|
|
2283
|
+
async function invokeActivity(args) {
|
|
2284
|
+
const { client, instanceId, activity: activityName, options } = args, ctx = await loadContext(client, instanceId, {
|
|
2234
2285
|
...options?.clock ? { clock: options.clock } : {},
|
|
2235
2286
|
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
2236
2287
|
});
|
|
2237
|
-
return commitInvoke(ctx,
|
|
2288
|
+
return commitInvoke(ctx, activityName, options?.actor);
|
|
2238
2289
|
}
|
|
2239
|
-
async function commitInvoke(ctx,
|
|
2240
|
-
const {
|
|
2290
|
+
async function commitInvoke(ctx, activityName, actor) {
|
|
2291
|
+
const { activity } = findActivityInCurrentStage(ctx, activityName), entry = findCurrentActivities(ctx.instance).find((t) => t.name === activityName);
|
|
2241
2292
|
if (entry === void 0)
|
|
2242
|
-
throw new Error(
|
|
2293
|
+
throw new Error(
|
|
2294
|
+
`Activity "${activityName}" has no status entry on instance ${ctx.instance._id}`
|
|
2295
|
+
);
|
|
2243
2296
|
if (entry.status !== "pending")
|
|
2244
|
-
return { invoked: !1,
|
|
2245
|
-
const mutation = startMutation(ctx.instance), mutEntry =
|
|
2246
|
-
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 };
|
|
2247
2300
|
}
|
|
2248
|
-
async function
|
|
2301
|
+
async function activateActivity(ctx, mutation, activity, entry, actor) {
|
|
2249
2302
|
const now = ctx.now;
|
|
2250
|
-
if (entry.status = "active", entry.startedAt = now,
|
|
2303
|
+
if (entry.status = "active", entry.startedAt = now, activity.fields !== void 0 && activity.fields.length > 0) {
|
|
2251
2304
|
const stage = findStage(ctx.definition, mutation.currentStage);
|
|
2252
|
-
entry.fields = await
|
|
2305
|
+
entry.fields = await resolveActivityFieldEntries({
|
|
2253
2306
|
client: ctx.client,
|
|
2254
2307
|
instance: ctx.instance,
|
|
2255
2308
|
stage,
|
|
2256
|
-
|
|
2257
|
-
now
|
|
2309
|
+
activity,
|
|
2310
|
+
now,
|
|
2311
|
+
recordDiscard: recordFieldDiscards(mutation.history, "activity", now)
|
|
2258
2312
|
});
|
|
2259
2313
|
}
|
|
2260
2314
|
runOps({
|
|
2261
|
-
ops:
|
|
2315
|
+
ops: activity.ops,
|
|
2262
2316
|
mutation,
|
|
2263
2317
|
stage: mutation.currentStage,
|
|
2264
|
-
origin: {
|
|
2318
|
+
origin: { activity: activity.name },
|
|
2265
2319
|
params: {},
|
|
2266
2320
|
actor,
|
|
2267
2321
|
self: selfGdr(ctx.instance),
|
|
2268
2322
|
now
|
|
2269
2323
|
}), mutation.history.push({
|
|
2270
2324
|
_key: randomKey(),
|
|
2271
|
-
_type: "
|
|
2325
|
+
_type: "activityActivated",
|
|
2272
2326
|
at: now,
|
|
2273
2327
|
stage: mutation.currentStage,
|
|
2274
|
-
|
|
2328
|
+
activity: activity.name,
|
|
2275
2329
|
...actor !== void 0 ? { actor } : {}
|
|
2276
|
-
}), await queueEffects(
|
|
2277
|
-
|
|
2278
|
-
|
|
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
|
+
);
|
|
2279
2340
|
let pendingChildren;
|
|
2280
|
-
if (
|
|
2281
|
-
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);
|
|
2282
2343
|
entry.spawnedInstances = refs, pendingChildren = mutation.pendingCreates.slice(createsBefore).map((c) => ({ _id: c._id, stage: c.currentStage, status: "active" }));
|
|
2283
2344
|
for (const ref of refs)
|
|
2284
2345
|
mutation.history.push({
|
|
2285
2346
|
_key: randomKey(),
|
|
2286
2347
|
_type: "spawned",
|
|
2287
2348
|
at: now,
|
|
2288
|
-
|
|
2349
|
+
activity: activity.name,
|
|
2289
2350
|
instanceRef: ref
|
|
2290
2351
|
});
|
|
2291
2352
|
}
|
|
2292
|
-
await autoResolveOnActivate(ctx, mutation,
|
|
2353
|
+
await autoResolveOnActivate(ctx, mutation, activity, entry, now, pendingChildren) || resolveMachineStep(mutation, activity, entry, now);
|
|
2293
2354
|
}
|
|
2294
|
-
async function autoResolveOnActivate(ctx, mutation,
|
|
2295
|
-
if (
|
|
2296
|
-
const vars = pendingChildren !== void 0 ? { subworkflows: pendingChildren } : await
|
|
2297
|
-
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({
|
|
2298
2359
|
entry,
|
|
2299
2360
|
history: mutation.history,
|
|
2300
2361
|
stage: mutation.currentStage,
|
|
@@ -2303,12 +2364,12 @@ async function autoResolveOnActivate(ctx, mutation, task, entry, now, pendingChi
|
|
|
2303
2364
|
actor: gateActor(outcome)
|
|
2304
2365
|
}), !0);
|
|
2305
2366
|
}
|
|
2306
|
-
async function
|
|
2367
|
+
async function activityConditionVars(ctx, entry) {
|
|
2307
2368
|
return entry.spawnedInstances === void 0 || entry.spawnedInstances.length === 0 ? { subworkflows: [] } : { subworkflows: await subworkflowRows(ctx, entry.spawnedInstances) };
|
|
2308
2369
|
}
|
|
2309
|
-
function resolveMachineStep(mutation,
|
|
2310
|
-
const waitsForActor =
|
|
2311
|
-
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({
|
|
2312
2373
|
entry,
|
|
2313
2374
|
history: mutation.history,
|
|
2314
2375
|
stage: mutation.currentStage,
|
|
@@ -2317,15 +2378,17 @@ function resolveMachineStep(mutation, task, entry, now) {
|
|
|
2317
2378
|
actor: engineSystemActor("machineStep")
|
|
2318
2379
|
});
|
|
2319
2380
|
}
|
|
2320
|
-
async function
|
|
2381
|
+
async function buildStageActivities(ctx, stage) {
|
|
2321
2382
|
const entries = [];
|
|
2322
|
-
for (const
|
|
2323
|
-
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";
|
|
2324
2387
|
entries.push({
|
|
2325
2388
|
_key: randomKey(),
|
|
2326
|
-
name:
|
|
2389
|
+
name: activity.name,
|
|
2327
2390
|
status: inScope ? "pending" : "skipped",
|
|
2328
|
-
...
|
|
2391
|
+
...activity.filter !== void 0 ? { filterEvaluation: buildFilterEvaluation(ctx.now, activity.filter, outcome) } : {}
|
|
2329
2392
|
});
|
|
2330
2393
|
}
|
|
2331
2394
|
return entries;
|
|
@@ -2335,7 +2398,7 @@ function buildFilterEvaluation(at, filter, outcome) {
|
|
|
2335
2398
|
at,
|
|
2336
2399
|
truthy: !1,
|
|
2337
2400
|
unevaluable: !0,
|
|
2338
|
-
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.`
|
|
2339
2402
|
} : { at, truthy: outcome === "satisfied" };
|
|
2340
2403
|
}
|
|
2341
2404
|
function isTerminalStage(stage) {
|
|
@@ -2358,15 +2421,16 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
|
|
|
2358
2421
|
client: ctx.client,
|
|
2359
2422
|
instance: ctx.instance,
|
|
2360
2423
|
stage: nextStage,
|
|
2361
|
-
now: at
|
|
2424
|
+
now: at,
|
|
2425
|
+
recordDiscard: recordFieldDiscards(mutation.history, "stage", at)
|
|
2362
2426
|
}),
|
|
2363
|
-
|
|
2427
|
+
activities: await buildStageActivities(ctx, nextStage)
|
|
2364
2428
|
};
|
|
2365
2429
|
mutation.stages.push(nextStageEntry);
|
|
2366
|
-
for (const
|
|
2367
|
-
if (
|
|
2368
|
-
const entry = nextStageEntry.
|
|
2369
|
-
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);
|
|
2370
2434
|
}
|
|
2371
2435
|
isTerminalStage(nextStage) && (mutation.completedAt = at);
|
|
2372
2436
|
}
|
|
@@ -2490,15 +2554,22 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
2490
2554
|
instance,
|
|
2491
2555
|
definition,
|
|
2492
2556
|
...clock ? { clock } : {}
|
|
2493
|
-
}), now = ctx.now, initialStageEntry = {
|
|
2557
|
+
}), now = ctx.now, discards = [], initialStageEntry = {
|
|
2494
2558
|
_key: randomKey(),
|
|
2495
2559
|
name: stage.name,
|
|
2496
2560
|
enteredAt: now,
|
|
2497
|
-
fields: await resolveStageFieldEntries({
|
|
2498
|
-
|
|
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)
|
|
2499
2569
|
}, committed = await client.patch(instance._id).set({
|
|
2500
2570
|
stages: [initialStageEntry],
|
|
2501
|
-
lastChangedAt: now
|
|
2571
|
+
lastChangedAt: now,
|
|
2572
|
+
...discards.length > 0 ? { history: [...instance.history, ...discards] } : {}
|
|
2502
2573
|
}).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
|
|
2503
2574
|
await deployOrRollback({
|
|
2504
2575
|
client,
|
|
@@ -2517,12 +2588,20 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
2517
2588
|
stageName: stage.name,
|
|
2518
2589
|
now
|
|
2519
2590
|
})
|
|
2520
|
-
}), await
|
|
2591
|
+
}), await autoActivatePrimedActivities(
|
|
2592
|
+
client,
|
|
2593
|
+
instanceId,
|
|
2594
|
+
definition,
|
|
2595
|
+
stage,
|
|
2596
|
+
actor,
|
|
2597
|
+
clientForGdr,
|
|
2598
|
+
clock
|
|
2599
|
+
);
|
|
2521
2600
|
}
|
|
2522
|
-
async function
|
|
2601
|
+
async function autoActivatePrimedActivities(client, instanceId, definition, stage, actor, clientForGdr, clock) {
|
|
2523
2602
|
const resolvedClientForGdr = clientForGdr ?? (() => client);
|
|
2524
|
-
for (const
|
|
2525
|
-
if (
|
|
2603
|
+
for (const activity of stage.activities ?? []) {
|
|
2604
|
+
if (activity.activation !== "auto") continue;
|
|
2526
2605
|
const updated = await client.getDocument(instanceId);
|
|
2527
2606
|
if (!updated) continue;
|
|
2528
2607
|
const updatedCtx = await buildEngineContext({
|
|
@@ -2531,18 +2610,22 @@ async function autoActivatePrimedTasks(client, instanceId, definition, stage, ac
|
|
|
2531
2610
|
instance: updated,
|
|
2532
2611
|
definition,
|
|
2533
2612
|
...clock ? { clock } : {}
|
|
2534
|
-
}), mutation = startMutation(updated), mutEntry =
|
|
2535
|
-
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));
|
|
2536
2615
|
}
|
|
2537
2616
|
}
|
|
2538
2617
|
const CASCADE_LIMIT = 100;
|
|
2539
|
-
async function gatherAutoResolveCandidates(ctx,
|
|
2540
|
-
const
|
|
2541
|
-
for (const
|
|
2542
|
-
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);
|
|
2543
2622
|
if (entry?.status !== "active") continue;
|
|
2544
|
-
const outcome = await evaluateResolutionGate(
|
|
2545
|
-
|
|
2623
|
+
const outcome = await evaluateResolutionGate(
|
|
2624
|
+
ctx,
|
|
2625
|
+
activity,
|
|
2626
|
+
await activityConditionVars(ctx, entry)
|
|
2627
|
+
);
|
|
2628
|
+
outcome !== void 0 && candidates.push({ activity, outcome });
|
|
2546
2629
|
}
|
|
2547
2630
|
return candidates;
|
|
2548
2631
|
}
|
|
@@ -2551,17 +2634,17 @@ async function resolveCompleteWhen(client, instanceId, clientForGdr, clock, over
|
|
|
2551
2634
|
...clientForGdr ? { clientForGdr } : {},
|
|
2552
2635
|
...clock ? { clock } : {},
|
|
2553
2636
|
...overlay ? { overlay } : {}
|
|
2554
|
-
}), stage = findStage(ctx.definition, ctx.instance.currentStage),
|
|
2637
|
+
}), stage = findStage(ctx.definition, ctx.instance.currentStage), gatedActivities = (stage.activities ?? []).filter(
|
|
2555
2638
|
(t) => effectiveCompleteWhen(t) !== void 0 || t.failWhen !== void 0
|
|
2556
2639
|
);
|
|
2557
|
-
if (
|
|
2558
|
-
const candidates = await gatherAutoResolveCandidates(ctx,
|
|
2640
|
+
if (gatedActivities.length === 0) return 0;
|
|
2641
|
+
const candidates = await gatherAutoResolveCandidates(ctx, gatedActivities);
|
|
2559
2642
|
if (candidates.length === 0) return 0;
|
|
2560
2643
|
const mutation = startMutation(ctx.instance);
|
|
2561
2644
|
let count = 0;
|
|
2562
|
-
for (const {
|
|
2563
|
-
const mutEntry =
|
|
2564
|
-
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({
|
|
2565
2648
|
entry: mutEntry,
|
|
2566
2649
|
history: mutation.history,
|
|
2567
2650
|
stage: stage.name,
|
|
@@ -2594,9 +2677,9 @@ async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
|
|
|
2594
2677
|
if (!parent || parent._type !== WORKFLOW_INSTANCE_TYPE || typeof parent.definitionSnapshot != "string") return;
|
|
2595
2678
|
const definition = parseDefinitionSnapshot(parent), stage = definition.stages.find((s) => s.name === parent.currentStage);
|
|
2596
2679
|
if (stage === void 0) return;
|
|
2597
|
-
const
|
|
2598
|
-
if (
|
|
2599
|
-
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);
|
|
2600
2683
|
if (entry === void 0 || entry.status !== "active") return;
|
|
2601
2684
|
const ctx = await buildEngineContext({
|
|
2602
2685
|
client,
|
|
@@ -2604,23 +2687,27 @@ async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
|
|
|
2604
2687
|
instance: parent,
|
|
2605
2688
|
definition,
|
|
2606
2689
|
...clock ? { clock } : {}
|
|
2607
|
-
}), outcome = await evaluateResolutionGate(
|
|
2690
|
+
}), outcome = await evaluateResolutionGate(
|
|
2691
|
+
ctx,
|
|
2692
|
+
activity,
|
|
2693
|
+
await activityConditionVars(ctx, entry)
|
|
2694
|
+
);
|
|
2608
2695
|
if (outcome !== void 0)
|
|
2609
|
-
return { parent, definition, stage,
|
|
2696
|
+
return { parent, definition, stage, activity, outcome };
|
|
2610
2697
|
}
|
|
2611
2698
|
async function propagateToAncestors(client, instanceId, actor, clientForGdr, clock) {
|
|
2612
2699
|
const instance = await client.getDocument(instanceId);
|
|
2613
2700
|
if (!instance) return;
|
|
2614
2701
|
const resolvedClientForGdr = clientForGdr ?? (() => client), found = await findResolvedParentSpawn(client, instance, resolvedClientForGdr, clock);
|
|
2615
2702
|
if (found === void 0) return;
|
|
2616
|
-
const { parent, definition, stage,
|
|
2703
|
+
const { parent, definition, stage, activity, outcome } = found, ctx = await buildEngineContext({
|
|
2617
2704
|
client,
|
|
2618
2705
|
clientForGdr: resolvedClientForGdr,
|
|
2619
2706
|
instance: parent,
|
|
2620
2707
|
definition,
|
|
2621
2708
|
...clock ? { clock } : {}
|
|
2622
|
-
}), mutation = startMutation(parent), mutEntry =
|
|
2623
|
-
mutEntry !== void 0 && (
|
|
2709
|
+
}), mutation = startMutation(parent), mutEntry = currentActivities(mutation).find((e) => e.name === activity.name);
|
|
2710
|
+
mutEntry !== void 0 && (applyActivityStatusChange({
|
|
2624
2711
|
entry: mutEntry,
|
|
2625
2712
|
history: mutation.history,
|
|
2626
2713
|
stage: stage.name,
|
|
@@ -2723,6 +2810,18 @@ async function fetchGrantsCached(requestFn, resourcePath) {
|
|
|
2723
2810
|
return;
|
|
2724
2811
|
}
|
|
2725
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
|
+
}
|
|
2726
2825
|
function effectiveEditable(baseline, override) {
|
|
2727
2826
|
if (baseline === void 0) return;
|
|
2728
2827
|
if (override === void 0) return baseline;
|
|
@@ -2734,14 +2833,15 @@ function slotSites(definition, stage) {
|
|
|
2734
2833
|
const sites = [];
|
|
2735
2834
|
for (const entry of definition.fields ?? []) sites.push({ scope: "workflow", entry });
|
|
2736
2835
|
for (const entry of stage.fields ?? []) sites.push({ scope: "stage", entry });
|
|
2737
|
-
for (const
|
|
2738
|
-
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 });
|
|
2739
2839
|
return sites;
|
|
2740
2840
|
}
|
|
2741
2841
|
function toResolvedSlot(site, stage) {
|
|
2742
2842
|
return {
|
|
2743
2843
|
scope: site.scope,
|
|
2744
|
-
...site.
|
|
2844
|
+
...site.activity !== void 0 ? { activity: site.activity } : {},
|
|
2745
2845
|
name: site.entry.name,
|
|
2746
2846
|
type: site.entry.type,
|
|
2747
2847
|
...site.entry.title !== void 0 ? { title: site.entry.title } : {},
|
|
@@ -2753,16 +2853,16 @@ function editableSlotsInStage(definition, stage) {
|
|
|
2753
2853
|
return slotSites(definition, stage).filter((site) => site.entry.editable !== void 0).map((site) => toResolvedSlot(site, stage));
|
|
2754
2854
|
}
|
|
2755
2855
|
function editTargetMatches(slot, target) {
|
|
2756
|
-
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;
|
|
2757
2857
|
}
|
|
2758
|
-
const SCOPE_PRECEDENCE = {
|
|
2858
|
+
const SCOPE_PRECEDENCE = { activity: 0, stage: 1, workflow: 2 };
|
|
2759
2859
|
function resolveEditTarget(args) {
|
|
2760
2860
|
const { definition, stage, target } = args, found = slotSites(definition, stage).filter(
|
|
2761
2861
|
(site) => editTargetMatches(
|
|
2762
2862
|
{
|
|
2763
2863
|
scope: site.scope,
|
|
2764
2864
|
name: site.entry.name,
|
|
2765
|
-
...site.
|
|
2865
|
+
...site.activity !== void 0 ? { activity: site.activity } : {}
|
|
2766
2866
|
},
|
|
2767
2867
|
target
|
|
2768
2868
|
)
|
|
@@ -2772,9 +2872,11 @@ function resolveEditTarget(args) {
|
|
|
2772
2872
|
function slotWindowOpen(instance, slot) {
|
|
2773
2873
|
if (instance.completedAt !== void 0) return { open: !1, detail: "instance completed" };
|
|
2774
2874
|
if (instance.abortedAt !== void 0) return { open: !1, detail: "instance aborted" };
|
|
2775
|
-
if (slot.scope !== "
|
|
2776
|
-
const status = findOpenStageEntry(instance)?.
|
|
2777
|
-
|
|
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"}` };
|
|
2778
2880
|
}
|
|
2779
2881
|
function readSlotValue(instance, slot) {
|
|
2780
2882
|
return slotFieldHost(instance, slot)?.find((e) => e.name === slot.name)?.value;
|
|
@@ -2782,7 +2884,7 @@ function readSlotValue(instance, slot) {
|
|
|
2782
2884
|
function slotFieldHost(instance, slot) {
|
|
2783
2885
|
if (slot.scope === "workflow") return instance.fields;
|
|
2784
2886
|
const stageEntry = findOpenStageEntry(instance);
|
|
2785
|
-
return slot.scope === "stage" ? stageEntry?.fields : stageEntry?.
|
|
2887
|
+
return slot.scope === "stage" ? stageEntry?.fields : stageEntry?.activities.find((t) => t.name === slot.activity)?.fields;
|
|
2786
2888
|
}
|
|
2787
2889
|
function slotProvenance(instance, ref) {
|
|
2788
2890
|
let latest;
|
|
@@ -2802,18 +2904,6 @@ function editDisabledReason(args) {
|
|
|
2802
2904
|
if (!predicateSatisfied)
|
|
2803
2905
|
return { kind: "editor-not-permitted", predicate: effective === !0 ? "true" : effective };
|
|
2804
2906
|
}
|
|
2805
|
-
function hasItems(arr) {
|
|
2806
|
-
return arr !== void 0 && arr.length > 0;
|
|
2807
|
-
}
|
|
2808
|
-
function deriveTaskKind(task) {
|
|
2809
|
-
return hasItems(task.actions) ? "user" : hasItems(task.effects) ? "service" : task.completeWhen !== void 0 || task.subworkflows !== void 0 ? "receive" : "script";
|
|
2810
|
-
}
|
|
2811
|
-
function taskKind(task) {
|
|
2812
|
-
return task.kind ?? deriveTaskKind(task);
|
|
2813
|
-
}
|
|
2814
|
-
function driverKind(actor) {
|
|
2815
|
-
return actor.kind === "user" ? "person" : actor.kind === "ai" ? "agent" : isEngineActor(actor) ? "engine" : "service";
|
|
2816
|
-
}
|
|
2817
2907
|
async function evaluateInstance(args) {
|
|
2818
2908
|
const { client, tag, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
|
|
2819
2909
|
validateTag(tag);
|
|
@@ -2837,12 +2927,12 @@ async function evaluateFromSnapshot(args) {
|
|
|
2837
2927
|
throw new Error(
|
|
2838
2928
|
`Instance "${instance._id}" currentStage "${instance.currentStage}" not in definition`
|
|
2839
2929
|
);
|
|
2840
|
-
const scope = await renderScope({ instance, definition, actor, grants, snapshot, now }),
|
|
2841
|
-
for (const
|
|
2842
|
-
|
|
2843
|
-
await
|
|
2844
|
-
|
|
2845
|
-
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),
|
|
2846
2936
|
instance,
|
|
2847
2937
|
actor,
|
|
2848
2938
|
snapshot,
|
|
@@ -2867,9 +2957,9 @@ async function evaluateFromSnapshot(args) {
|
|
|
2867
2957
|
}
|
|
2868
2958
|
const currentStage = {
|
|
2869
2959
|
stage,
|
|
2870
|
-
|
|
2960
|
+
activities: activityEvaluations,
|
|
2871
2961
|
transitions: transitionEvaluations
|
|
2872
|
-
}, 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({
|
|
2873
2963
|
instance,
|
|
2874
2964
|
definition,
|
|
2875
2965
|
stage,
|
|
@@ -2909,7 +2999,7 @@ async function evaluateEditableSlots(args) {
|
|
|
2909
2999
|
}), value = readSlotValue(instance, slot);
|
|
2910
3000
|
slots.push({
|
|
2911
3001
|
scope: slot.scope,
|
|
2912
|
-
...slot.
|
|
3002
|
+
...slot.activity !== void 0 ? { activity: slot.activity } : {},
|
|
2913
3003
|
name: slot.name,
|
|
2914
3004
|
type: slot.type,
|
|
2915
3005
|
...slot.title !== void 0 ? { title: slot.title } : {},
|
|
@@ -2925,7 +3015,14 @@ async function editPredicateSatisfied(args) {
|
|
|
2925
3015
|
const { slot, window, guardDenial, instance, snapshot, scope, actor, roleAliases } = args;
|
|
2926
3016
|
if (!window.open || guardDenial !== void 0) return !1;
|
|
2927
3017
|
if (slot.effective === !0) return !0;
|
|
2928
|
-
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;
|
|
2929
3026
|
return evaluateCondition({ condition: slot.effective, snapshot, params });
|
|
2930
3027
|
}
|
|
2931
3028
|
async function renderScope(args) {
|
|
@@ -2953,20 +3050,20 @@ async function advisoryCan(instance, actor, grants) {
|
|
|
2953
3050
|
});
|
|
2954
3051
|
return can;
|
|
2955
3052
|
}
|
|
2956
|
-
function
|
|
2957
|
-
const { scope, instance, snapshot, actor,
|
|
3053
|
+
function activityScopeFor(args) {
|
|
3054
|
+
const { scope, instance, snapshot, actor, activityName, roleAliases } = args;
|
|
2958
3055
|
return {
|
|
2959
3056
|
...scope,
|
|
2960
3057
|
fields: {
|
|
2961
3058
|
...scope.fields,
|
|
2962
|
-
...scopedFieldOverlay(instance, snapshot,
|
|
3059
|
+
...scopedFieldOverlay(instance, snapshot, activityName)
|
|
2963
3060
|
},
|
|
2964
|
-
assigned: assignedFor(instance,
|
|
3061
|
+
assigned: assignedFor(instance, activityName, actor, roleAliases)
|
|
2965
3062
|
};
|
|
2966
3063
|
}
|
|
2967
|
-
async function
|
|
3064
|
+
async function evaluateActivity(args) {
|
|
2968
3065
|
const {
|
|
2969
|
-
|
|
3066
|
+
activity,
|
|
2970
3067
|
statusEntry,
|
|
2971
3068
|
instance,
|
|
2972
3069
|
actor,
|
|
@@ -2975,37 +3072,37 @@ async function evaluateTask(args) {
|
|
|
2975
3072
|
roleAliases,
|
|
2976
3073
|
stageHasExits,
|
|
2977
3074
|
guardDenial
|
|
2978
|
-
} = args, status = statusEntry?.status ?? "pending",
|
|
3075
|
+
} = args, status = statusEntry?.status ?? "pending", activityScope = activityScopeFor({
|
|
2979
3076
|
scope,
|
|
2980
3077
|
instance,
|
|
2981
3078
|
snapshot,
|
|
2982
3079
|
actor,
|
|
2983
|
-
|
|
3080
|
+
activityName: activity.name,
|
|
2984
3081
|
roleAliases
|
|
2985
|
-
}), assigned =
|
|
2986
|
-
requirements:
|
|
3082
|
+
}), assigned = activityScope.assigned === !0, unmetRequirements = await evaluateRequirements({
|
|
3083
|
+
requirements: activity.requirements,
|
|
2987
3084
|
snapshot,
|
|
2988
|
-
params:
|
|
3085
|
+
params: activityScope
|
|
2989
3086
|
}), requirementsReason = unmetRequirements.length > 0 ? { kind: "requirements-unmet", unmetRequirements } : void 0, actions = [];
|
|
2990
|
-
for (const action of
|
|
3087
|
+
for (const action of activity.actions ?? [])
|
|
2991
3088
|
actions.push(
|
|
2992
3089
|
await evaluateAction({
|
|
2993
3090
|
action,
|
|
2994
3091
|
status,
|
|
2995
3092
|
instance,
|
|
2996
3093
|
snapshot,
|
|
2997
|
-
|
|
3094
|
+
activityScope,
|
|
2998
3095
|
stageHasExits,
|
|
2999
3096
|
guardDenial,
|
|
3000
3097
|
requirementsReason
|
|
3001
3098
|
})
|
|
3002
3099
|
);
|
|
3003
3100
|
return {
|
|
3004
|
-
|
|
3101
|
+
activity,
|
|
3005
3102
|
status,
|
|
3006
|
-
kind:
|
|
3103
|
+
kind: activityKind(activity),
|
|
3007
3104
|
// "pending on actor" treats both `pending` and `active` as waiting on
|
|
3008
|
-
// the assignee — pending
|
|
3105
|
+
// the assignee — pending activities just haven't been activated yet, but
|
|
3009
3106
|
// they're still inbox items. The inbox reads the assignees-kind field
|
|
3010
3107
|
// entry BY KIND (who-data is fields).
|
|
3011
3108
|
pendingOnActor: (status === "active" || status === "pending") && assigned,
|
|
@@ -3019,7 +3116,7 @@ async function evaluateAction(args) {
|
|
|
3019
3116
|
status,
|
|
3020
3117
|
instance,
|
|
3021
3118
|
snapshot,
|
|
3022
|
-
|
|
3119
|
+
activityScope,
|
|
3023
3120
|
stageHasExits,
|
|
3024
3121
|
guardDenial,
|
|
3025
3122
|
requirementsReason
|
|
@@ -3027,7 +3124,7 @@ async function evaluateAction(args) {
|
|
|
3027
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({
|
|
3028
3125
|
condition: action.filter,
|
|
3029
3126
|
snapshot,
|
|
3030
|
-
params:
|
|
3127
|
+
params: activityScope
|
|
3031
3128
|
}) ? disabled(action, { kind: "filter-failed", filter: action.filter }) : { action, allowed: !0 };
|
|
3032
3129
|
}
|
|
3033
3130
|
async function instanceGuardReason(instance, actor, guards) {
|
|
@@ -3045,8 +3142,8 @@ function lifecycleReason(instance, status, stageHasExits) {
|
|
|
3045
3142
|
return { kind: "instance-completed", completedAt: instance.completedAt };
|
|
3046
3143
|
if (!stageHasExits)
|
|
3047
3144
|
return { kind: "stage-terminal", stage: instance.currentStage };
|
|
3048
|
-
if (schema.
|
|
3049
|
-
return { kind: "
|
|
3145
|
+
if (schema.isTerminalActivityStatus(status))
|
|
3146
|
+
return { kind: "activity-not-active", status };
|
|
3050
3147
|
}
|
|
3051
3148
|
function disabled(action, reason) {
|
|
3052
3149
|
return { action, allowed: !1, disabledReason: reason };
|
|
@@ -3054,37 +3151,30 @@ function disabled(action, reason) {
|
|
|
3054
3151
|
function definitionDocId(_workflowResource, tag, definition, version) {
|
|
3055
3152
|
return `${tag}.${definition}.v${version}`;
|
|
3056
3153
|
}
|
|
3057
|
-
async function sortByDependencies(client, definitions, tag
|
|
3154
|
+
async function sortByDependencies(client, definitions, tag) {
|
|
3058
3155
|
const byName = /* @__PURE__ */ new Map();
|
|
3059
3156
|
for (const def of definitions) {
|
|
3060
|
-
|
|
3061
|
-
|
|
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);
|
|
3062
3162
|
}
|
|
3063
3163
|
const findInBatch = (ref) => findRefInBatch(byName, ref);
|
|
3064
|
-
return await assertCrossBatchRefsDeployed(client, definitions, {
|
|
3065
|
-
tag,
|
|
3066
|
-
workflowResource,
|
|
3067
|
-
findInBatch
|
|
3068
|
-
}), topoSortDefinitions(definitions, { workflowResource, tag, findInBatch });
|
|
3164
|
+
return await assertCrossBatchRefsDeployed(client, definitions, { tag, findInBatch }), topoSortDefinitions(definitions, { findInBatch });
|
|
3069
3165
|
}
|
|
3070
3166
|
function findRefInBatch(byName, ref) {
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
return typeof ref.version == "number" ? candidates.find((c) => c.version === ref.version) : candidates.reduce(
|
|
3074
|
-
(best, c) => best === void 0 || c.version > best.version ? c : best,
|
|
3075
|
-
void 0
|
|
3076
|
-
);
|
|
3167
|
+
if (typeof ref.version != "number")
|
|
3168
|
+
return byName.get(ref.name);
|
|
3077
3169
|
}
|
|
3078
3170
|
async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
|
|
3079
3171
|
const missing = [];
|
|
3080
|
-
for (const def of definitions)
|
|
3081
|
-
const fromId = definitionDocId(ctx.workflowResource, ctx.tag, def.name, def.version);
|
|
3172
|
+
for (const def of definitions)
|
|
3082
3173
|
for (const ref of refsOf(def)) {
|
|
3083
3174
|
if (ctx.findInBatch(ref) !== void 0) continue;
|
|
3084
3175
|
const label = await resolveDeployedRefLabel(client, ref, ctx.tag);
|
|
3085
|
-
label !== void 0 && missing.push({ from:
|
|
3176
|
+
label !== void 0 && missing.push({ from: def.name, ref: label });
|
|
3086
3177
|
}
|
|
3087
|
-
}
|
|
3088
3178
|
if (missing.length === 0) return;
|
|
3089
3179
|
const lines = missing.map((m) => ` - ${m.from} \u2192 ${m.ref} (neither in batch nor deployed)`);
|
|
3090
3180
|
throw new Error(
|
|
@@ -3103,16 +3193,15 @@ async function resolveDeployedRefLabel(client, ref, tag) {
|
|
|
3103
3193
|
}
|
|
3104
3194
|
function topoSortDefinitions(definitions, ctx) {
|
|
3105
3195
|
const visited = /* @__PURE__ */ new Set(), visiting = /* @__PURE__ */ new Set(), ordered = [], visit = (def) => {
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
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);
|
|
3111
3200
|
for (const ref of refsOf(def)) {
|
|
3112
3201
|
const child = ctx.findInBatch(ref);
|
|
3113
3202
|
child !== void 0 && visit(child);
|
|
3114
3203
|
}
|
|
3115
|
-
visiting.delete(
|
|
3204
|
+
visiting.delete(def.name), visited.add(def.name), ordered.push(def);
|
|
3116
3205
|
}
|
|
3117
3206
|
};
|
|
3118
3207
|
for (const def of definitions) visit(def);
|
|
@@ -3121,8 +3210,8 @@ function topoSortDefinitions(definitions, ctx) {
|
|
|
3121
3210
|
function refsOf(def) {
|
|
3122
3211
|
const out = [];
|
|
3123
3212
|
for (const stage of def.stages)
|
|
3124
|
-
for (const
|
|
3125
|
-
const ref =
|
|
3213
|
+
for (const activity of stage.activities ?? []) {
|
|
3214
|
+
const ref = activity.subworkflows?.definition;
|
|
3126
3215
|
ref !== void 0 && out.push({
|
|
3127
3216
|
name: ref.name,
|
|
3128
3217
|
...ref.version !== void 0 ? { version: ref.version } : {}
|
|
@@ -3130,8 +3219,23 @@ function refsOf(def) {
|
|
|
3130
3219
|
}
|
|
3131
3220
|
return out;
|
|
3132
3221
|
}
|
|
3133
|
-
function
|
|
3134
|
-
|
|
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 };
|
|
3135
3239
|
}
|
|
3136
3240
|
function stripSystemFields(doc) {
|
|
3137
3241
|
const out = {};
|
|
@@ -3146,22 +3250,26 @@ function stableStringify(value) {
|
|
|
3146
3250
|
}
|
|
3147
3251
|
async function loadDefinition(client, definition, version, tag) {
|
|
3148
3252
|
if (version !== void 0) {
|
|
3149
|
-
const doc = await client.fetch(
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3253
|
+
const doc = await client.fetch(definitionLookupGroq(!0), {
|
|
3254
|
+
definition,
|
|
3255
|
+
version,
|
|
3256
|
+
tag
|
|
3257
|
+
});
|
|
3153
3258
|
if (!doc)
|
|
3154
3259
|
throw new Error(`Workflow definition ${definition} v${version} not deployed`);
|
|
3155
3260
|
return doc;
|
|
3156
3261
|
}
|
|
3157
|
-
const latest = await client
|
|
3158
|
-
definition,
|
|
3159
|
-
tag
|
|
3160
|
-
});
|
|
3262
|
+
const latest = await loadLatestDeployed(client, definition, tag);
|
|
3161
3263
|
if (!latest)
|
|
3162
3264
|
throw new Error(`No deployed definition for workflow ${definition}`);
|
|
3163
3265
|
return latest;
|
|
3164
3266
|
}
|
|
3267
|
+
async function loadLatestDeployed(client, definition, tag) {
|
|
3268
|
+
return await client.fetch(definitionLookupGroq(!1), {
|
|
3269
|
+
definition,
|
|
3270
|
+
tag
|
|
3271
|
+
}) ?? void 0;
|
|
3272
|
+
}
|
|
3165
3273
|
async function loadDefinitionVersions(client, definition, tag) {
|
|
3166
3274
|
return client.fetch(
|
|
3167
3275
|
`*[_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}] | order(version desc)`,
|
|
@@ -3208,55 +3316,55 @@ async function commitAbort(ctx, reason, actor) {
|
|
|
3208
3316
|
}), { fired: !0, stage: stage.name };
|
|
3209
3317
|
}
|
|
3210
3318
|
async function fireAction(args) {
|
|
3211
|
-
const { client, instanceId,
|
|
3319
|
+
const { client, instanceId, activity, action, params, options } = args;
|
|
3212
3320
|
for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
|
|
3213
3321
|
const ctx = await loadCallContext(client, instanceId, options);
|
|
3214
3322
|
try {
|
|
3215
|
-
return await commitAction(ctx,
|
|
3323
|
+
return await commitAction(ctx, activity, action, params, options);
|
|
3216
3324
|
} catch (error) {
|
|
3217
3325
|
if (!isRevisionConflict(error)) throw error;
|
|
3218
3326
|
}
|
|
3219
3327
|
}
|
|
3220
3328
|
throw new ConcurrentFireActionError({
|
|
3221
3329
|
instanceId,
|
|
3222
|
-
|
|
3330
|
+
activity,
|
|
3223
3331
|
action,
|
|
3224
3332
|
attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
|
|
3225
3333
|
});
|
|
3226
3334
|
}
|
|
3227
|
-
async function resolveActionCommit(ctx,
|
|
3228
|
-
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);
|
|
3229
3337
|
if (action === void 0)
|
|
3230
|
-
throw new Error(`Action "${actionName}" not declared on
|
|
3338
|
+
throw new Error(`Action "${actionName}" not declared on activity "${activityName}"`);
|
|
3231
3339
|
const can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan(ctx.instance, actor, options.grants) : void 0;
|
|
3232
3340
|
if (!await ctxEvaluateCondition(ctx, action.filter, {
|
|
3233
|
-
|
|
3341
|
+
activityName,
|
|
3234
3342
|
...actor !== void 0 ? { actor } : {},
|
|
3235
3343
|
...can !== void 0 ? { vars: { can } } : {}
|
|
3236
3344
|
}))
|
|
3237
|
-
throw new Error(`Action "${actionName}" filter rejected on
|
|
3238
|
-
const entry =
|
|
3345
|
+
throw new Error(`Action "${actionName}" filter rejected on activity "${activityName}"`);
|
|
3346
|
+
const entry = findCurrentActivities(ctx.instance).find((t) => t.name === activityName);
|
|
3239
3347
|
if (entry === void 0 || entry.status !== "active")
|
|
3240
3348
|
throw new Error(
|
|
3241
|
-
`
|
|
3349
|
+
`Activity "${activityName}" must be active to fire action "${actionName}"; status is ${entry?.status ?? "missing"}`
|
|
3242
3350
|
);
|
|
3243
|
-
const params = validateActionParams(action,
|
|
3244
|
-
return { stage,
|
|
3351
|
+
const params = validateActionParams(action, activityName, callerParams);
|
|
3352
|
+
return { stage, activity, action, params };
|
|
3245
3353
|
}
|
|
3246
|
-
async function commitAction(ctx,
|
|
3354
|
+
async function commitAction(ctx, activityName, actionName, callerParams, options) {
|
|
3247
3355
|
const actor = options?.actor, { stage, action, params } = await resolveActionCommit(
|
|
3248
3356
|
ctx,
|
|
3249
|
-
|
|
3357
|
+
activityName,
|
|
3250
3358
|
actionName,
|
|
3251
3359
|
callerParams,
|
|
3252
3360
|
options
|
|
3253
|
-
), mutation = startMutation(ctx.instance), mutEntry =
|
|
3361
|
+
), mutation = startMutation(ctx.instance), mutEntry = requireMutationActivityEntry(mutation, activityName), now = ctx.now;
|
|
3254
3362
|
mutation.history.push({
|
|
3255
3363
|
_key: randomKey(),
|
|
3256
3364
|
_type: "actionFired",
|
|
3257
3365
|
at: now,
|
|
3258
3366
|
stage: stage.name,
|
|
3259
|
-
|
|
3367
|
+
activity: activityName,
|
|
3260
3368
|
action: actionName,
|
|
3261
3369
|
...actor !== void 0 ? { actor, driverKind: driverKind(actor) } : {}
|
|
3262
3370
|
});
|
|
@@ -3264,7 +3372,7 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
|
|
|
3264
3372
|
ops: action.ops,
|
|
3265
3373
|
mutation,
|
|
3266
3374
|
stage: stage.name,
|
|
3267
|
-
origin: {
|
|
3375
|
+
origin: { activity: activityName, action: actionName },
|
|
3268
3376
|
params,
|
|
3269
3377
|
actor,
|
|
3270
3378
|
self: selfGdr(ctx.instance),
|
|
@@ -3272,14 +3380,14 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
|
|
|
3272
3380
|
}), newStatus = mutEntry.status !== statusBefore ? mutEntry.status : void 0;
|
|
3273
3381
|
return await queueEffects(ctx, mutation, action.effects, { kind: "action", name: actionName }, actor, {
|
|
3274
3382
|
callerParams: params,
|
|
3275
|
-
|
|
3383
|
+
activityName
|
|
3276
3384
|
}), await persistThenDeploy(
|
|
3277
3385
|
ctx,
|
|
3278
3386
|
mutation,
|
|
3279
3387
|
() => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
|
|
3280
3388
|
), {
|
|
3281
3389
|
fired: !0,
|
|
3282
|
-
|
|
3390
|
+
activity: activityName,
|
|
3283
3391
|
action: actionName,
|
|
3284
3392
|
...newStatus !== void 0 ? { newStatus } : {},
|
|
3285
3393
|
...ranOps.length > 0 ? { ranOps } : {}
|
|
@@ -3287,23 +3395,23 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
|
|
|
3287
3395
|
}
|
|
3288
3396
|
class ActionDisabledError extends Error {
|
|
3289
3397
|
reason;
|
|
3290
|
-
|
|
3398
|
+
activity;
|
|
3291
3399
|
action;
|
|
3292
3400
|
constructor(args) {
|
|
3293
|
-
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;
|
|
3294
3402
|
}
|
|
3295
3403
|
}
|
|
3296
3404
|
const disabledReasonDetail = {
|
|
3297
3405
|
"filter-failed": (r) => `action filter returned false${r.detail ? ` (${r.detail})` : ""}`,
|
|
3298
|
-
"
|
|
3406
|
+
"activity-not-active": (r) => `activity status is "${r.status}"`,
|
|
3299
3407
|
"stage-terminal": (r) => `stage "${r.stage}" is terminal`,
|
|
3300
3408
|
"instance-completed": (r) => `instance completed at ${r.completedAt}`,
|
|
3301
3409
|
"mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`,
|
|
3302
3410
|
"requirements-unmet": (r) => `unmet requirement(s): ${r.unmetRequirements.join(", ")}`
|
|
3303
3411
|
};
|
|
3304
|
-
function formatDisabledReason(
|
|
3412
|
+
function formatDisabledReason(activity, action, reason) {
|
|
3305
3413
|
const detail = disabledReasonDetail[reason.kind](reason);
|
|
3306
|
-
return `Action "${
|
|
3414
|
+
return `Action "${activity}:${action}" is not allowed: ${detail}`;
|
|
3307
3415
|
}
|
|
3308
3416
|
class EditFieldDeniedError extends Error {
|
|
3309
3417
|
reason;
|
|
@@ -3322,7 +3430,7 @@ const editDisabledReasonDetail = {
|
|
|
3322
3430
|
function formatEditDisabledReason(target, reason) {
|
|
3323
3431
|
const detail = editDisabledReasonDetail[reason.kind](
|
|
3324
3432
|
reason
|
|
3325
|
-
), where = target.
|
|
3433
|
+
), where = target.activity !== void 0 ? `${target.activity}.${target.field}` : target.field;
|
|
3326
3434
|
return `Field "${target.scope}:${where}" is not editable: ${detail}`;
|
|
3327
3435
|
}
|
|
3328
3436
|
async function editField(args) {
|
|
@@ -3354,7 +3462,7 @@ async function commitEdit(ctx, target, mode, value, options) {
|
|
|
3354
3462
|
stage: stage.name,
|
|
3355
3463
|
origin: {
|
|
3356
3464
|
edit: !0,
|
|
3357
|
-
...slot.scope === "
|
|
3465
|
+
...slot.scope === "activity" && slot.activity !== void 0 ? { activity: slot.activity } : {}
|
|
3358
3466
|
},
|
|
3359
3467
|
params: {},
|
|
3360
3468
|
actor,
|
|
@@ -3373,7 +3481,7 @@ async function commitEdit(ctx, target, mode, value, options) {
|
|
|
3373
3481
|
}
|
|
3374
3482
|
async function assertSlotEditable(ctx, slot, options) {
|
|
3375
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, {
|
|
3376
|
-
...slot.
|
|
3484
|
+
...slot.activity !== void 0 ? { activityName: slot.activity } : {},
|
|
3377
3485
|
...actor !== void 0 ? { actor } : {},
|
|
3378
3486
|
...can !== void 0 ? { vars: { can } } : {}
|
|
3379
3487
|
}) : slot.effective === !0, reason = editDisabledReason({
|
|
@@ -3399,18 +3507,18 @@ function slotTarget(slot) {
|
|
|
3399
3507
|
return {
|
|
3400
3508
|
scope: slot.scope,
|
|
3401
3509
|
field: slot.name,
|
|
3402
|
-
...slot.
|
|
3510
|
+
...slot.activity !== void 0 ? { activity: slot.activity } : {}
|
|
3403
3511
|
};
|
|
3404
3512
|
}
|
|
3405
3513
|
function labelTarget(target) {
|
|
3406
3514
|
return {
|
|
3407
|
-
scope: target.scope ?? (target.
|
|
3515
|
+
scope: target.scope ?? (target.activity !== void 0 ? "activity" : "workflow"),
|
|
3408
3516
|
field: target.field,
|
|
3409
|
-
...target.
|
|
3517
|
+
...target.activity !== void 0 ? { activity: target.activity } : {}
|
|
3410
3518
|
};
|
|
3411
3519
|
}
|
|
3412
3520
|
function describeTarget(target) {
|
|
3413
|
-
const where = target.
|
|
3521
|
+
const where = target.activity !== void 0 ? `${target.activity}.${target.field}` : target.field;
|
|
3414
3522
|
return target.scope !== void 0 ? `${target.scope}:${where}` : where;
|
|
3415
3523
|
}
|
|
3416
3524
|
function bareIdFromSpawnRef(uri) {
|
|
@@ -3475,10 +3583,10 @@ async function dispatchGatedWrite(args) {
|
|
|
3475
3583
|
...ranOps !== void 0 ? { ranOps } : {}
|
|
3476
3584
|
};
|
|
3477
3585
|
}
|
|
3478
|
-
function assertActionAllowed(evaluation,
|
|
3479
|
-
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);
|
|
3480
3588
|
if (actionEval?.allowed === !1 && actionEval.disabledReason)
|
|
3481
|
-
throw new ActionDisabledError({
|
|
3589
|
+
throw new ActionDisabledError({ activity, action, reason: actionEval.disabledReason });
|
|
3482
3590
|
}
|
|
3483
3591
|
function assertEditAllowed(evaluation, target) {
|
|
3484
3592
|
const slot = evaluation.editableSlots.filter((s) => editTargetMatches(s, target)).sort((a, b) => SCOPE_PRECEDENCE[a.scope] - SCOPE_PRECEDENCE[b.scope])[0];
|
|
@@ -3487,7 +3595,7 @@ function assertEditAllowed(evaluation, target) {
|
|
|
3487
3595
|
target: {
|
|
3488
3596
|
scope: slot.scope,
|
|
3489
3597
|
field: slot.name,
|
|
3490
|
-
...slot.
|
|
3598
|
+
...slot.activity !== void 0 ? { activity: slot.activity } : {}
|
|
3491
3599
|
},
|
|
3492
3600
|
reason: slot.disabledReason
|
|
3493
3601
|
});
|
|
@@ -3512,11 +3620,11 @@ function buildEngineCallOptions(args) {
|
|
|
3512
3620
|
};
|
|
3513
3621
|
}
|
|
3514
3622
|
async function applyAction(args) {
|
|
3515
|
-
const { client, instance,
|
|
3516
|
-
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({
|
|
3517
3625
|
client,
|
|
3518
3626
|
instanceId: instance._id,
|
|
3519
|
-
|
|
3627
|
+
activity,
|
|
3520
3628
|
action,
|
|
3521
3629
|
...params !== void 0 ? { params } : {},
|
|
3522
3630
|
options
|
|
@@ -3596,14 +3704,16 @@ function previewIds(ids) {
|
|
|
3596
3704
|
}
|
|
3597
3705
|
const workflow = {
|
|
3598
3706
|
/**
|
|
3599
|
-
* Deploy a set of workflow definitions as one call.
|
|
3600
|
-
*
|
|
3601
|
-
*
|
|
3602
|
-
*
|
|
3603
|
-
* (
|
|
3604
|
-
*
|
|
3605
|
-
*
|
|
3606
|
-
*
|
|
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`).
|
|
3607
3717
|
*
|
|
3608
3718
|
* Refs may point inside the batch OR at already-deployed definitions
|
|
3609
3719
|
* in the lake — both are valid. A ref pointing at neither errors with
|
|
@@ -3616,31 +3726,15 @@ const workflow = {
|
|
|
3616
3726
|
validateTag(tag);
|
|
3617
3727
|
for (const def of definitions)
|
|
3618
3728
|
validateDefinition(def);
|
|
3619
|
-
const ordered = await sortByDependencies(client, definitions, tag
|
|
3729
|
+
const ordered = await sortByDependencies(client, definitions, tag), results = [], tx = client.transaction();
|
|
3620
3730
|
let hasWrites = !1;
|
|
3621
3731
|
for (const def of ordered) {
|
|
3622
|
-
const
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
_type: schema.WORKFLOW_DEFINITION_TYPE,
|
|
3626
|
-
tag
|
|
3627
|
-
}, existing = await client.getDocument(id);
|
|
3628
|
-
if (existing && existing.tag === tag && isDefinitionUnchanged(existing, expected)) {
|
|
3629
|
-
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" });
|
|
3630
3735
|
continue;
|
|
3631
3736
|
}
|
|
3632
|
-
|
|
3633
|
-
const expectedKeys = new Set(Object.keys(expected)), toUnset = Object.keys(existing).filter(
|
|
3634
|
-
(k) => !k.startsWith("_") && !expectedKeys.has(k)
|
|
3635
|
-
), patch = client.patch(id).set(expected).ifRevisionId(existing._rev);
|
|
3636
|
-
toUnset.length > 0 && patch.unset(toUnset), tx.patch(patch);
|
|
3637
|
-
} else
|
|
3638
|
-
tx.create(expected);
|
|
3639
|
-
hasWrites = !0, results.push({
|
|
3640
|
-
definition: def.name,
|
|
3641
|
-
version: def.version,
|
|
3642
|
-
status: existing ? "updated" : "created"
|
|
3643
|
-
});
|
|
3737
|
+
tx.create(plan.document), hasWrites = !0, results.push({ definition: def.name, version: plan.version, status: "created" });
|
|
3644
3738
|
}
|
|
3645
3739
|
return hasWrites && await tx.commit(), { results };
|
|
3646
3740
|
},
|
|
@@ -3656,8 +3750,8 @@ const workflow = {
|
|
|
3656
3750
|
* Spawn a new workflow instance from a deployed definition.
|
|
3657
3751
|
*
|
|
3658
3752
|
* Pins the snapshot at start-time, seeds `effectsContext`, enters
|
|
3659
|
-
* the initial stage (which builds
|
|
3660
|
-
* effects, auto-activates
|
|
3753
|
+
* the initial stage (which builds activityStatus, queues onEnter
|
|
3754
|
+
* effects, auto-activates activities). Then cascades auto-transitions
|
|
3661
3755
|
* until stable. Returns the resulting instance.
|
|
3662
3756
|
*/
|
|
3663
3757
|
startInstance: async (args) => {
|
|
@@ -3677,7 +3771,7 @@ const workflow = {
|
|
|
3677
3771
|
throw new Error(
|
|
3678
3772
|
`Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
|
|
3679
3773
|
);
|
|
3680
|
-
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedFields = await resolveDeclaredFields({
|
|
3774
|
+
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], fieldDiscards = [], resolvedFields = await resolveDeclaredFields({
|
|
3681
3775
|
entryDefs: definition.fields ?? [],
|
|
3682
3776
|
initialFields: initialFields ?? [],
|
|
3683
3777
|
ctx: {
|
|
@@ -3687,7 +3781,8 @@ const workflow = {
|
|
|
3687
3781
|
tag,
|
|
3688
3782
|
workflowResource,
|
|
3689
3783
|
definitionName: definition.name,
|
|
3690
|
-
...perspective !== void 0 ? { perspective } : {}
|
|
3784
|
+
...perspective !== void 0 ? { perspective } : {},
|
|
3785
|
+
recordDiscard: recordFieldDiscards(fieldDiscards, "workflow", now)
|
|
3691
3786
|
},
|
|
3692
3787
|
randomKey
|
|
3693
3788
|
}), effectivePerspective = perspective ?? derivePerspectiveFromFields(resolvedFields), base = buildInstanceBase({
|
|
@@ -3697,18 +3792,20 @@ const workflow = {
|
|
|
3697
3792
|
workflowResource,
|
|
3698
3793
|
definitionName: definition.name,
|
|
3699
3794
|
pinnedVersion: definition.version,
|
|
3795
|
+
pinnedContentHash: definition.contentHash,
|
|
3700
3796
|
definition,
|
|
3701
3797
|
fields: resolvedFields,
|
|
3702
3798
|
effectsContext: effectsContextEntries,
|
|
3703
3799
|
ancestors: ancestors ?? [],
|
|
3704
3800
|
perspective: effectivePerspective,
|
|
3705
3801
|
initialStage: definition.initialStage,
|
|
3706
|
-
actor
|
|
3802
|
+
actor,
|
|
3803
|
+
...fieldDiscards.length > 0 ? { extraHistory: fieldDiscards } : {}
|
|
3707
3804
|
});
|
|
3708
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);
|
|
3709
3806
|
},
|
|
3710
3807
|
/**
|
|
3711
|
-
* Fire an action against
|
|
3808
|
+
* Fire an action against an activity. If the activity is pending, it is
|
|
3712
3809
|
* auto-invoked first (pending → active) so callers don't have to know
|
|
3713
3810
|
* the lifecycle. Cascades auto-transitions and propagates to ancestors
|
|
3714
3811
|
* after the action commits.
|
|
@@ -3722,13 +3819,13 @@ const workflow = {
|
|
|
3722
3819
|
client,
|
|
3723
3820
|
tag,
|
|
3724
3821
|
instanceId,
|
|
3725
|
-
|
|
3822
|
+
activity,
|
|
3726
3823
|
action,
|
|
3727
3824
|
params,
|
|
3728
3825
|
idempotent,
|
|
3729
3826
|
resourceClients
|
|
3730
3827
|
} = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
|
|
3731
|
-
return findOpenStageEntry(before)?.
|
|
3828
|
+
return findOpenStageEntry(before)?.activities.find((t) => t.name === activity) === void 0 && idempotent === !0 ? { instance: before, cascaded: 0, fired: !1 } : dispatchGatedWrite({
|
|
3732
3829
|
client,
|
|
3733
3830
|
tag,
|
|
3734
3831
|
instanceId,
|
|
@@ -3738,10 +3835,10 @@ const workflow = {
|
|
|
3738
3835
|
clock,
|
|
3739
3836
|
before,
|
|
3740
3837
|
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3741
|
-
apply: (instance, evaluation) => (assertActionAllowed(evaluation,
|
|
3838
|
+
apply: (instance, evaluation) => (assertActionAllowed(evaluation, activity, action), applyAction({
|
|
3742
3839
|
client,
|
|
3743
3840
|
instance,
|
|
3744
|
-
|
|
3841
|
+
activity,
|
|
3745
3842
|
action,
|
|
3746
3843
|
actor,
|
|
3747
3844
|
...access.grants !== void 0 ? { grants: access.grants } : {},
|
|
@@ -3822,7 +3919,7 @@ const workflow = {
|
|
|
3822
3919
|
* Re-evaluate auto-transitions and resolve due waits until stable.
|
|
3823
3920
|
*
|
|
3824
3921
|
* Used by the runtime after any event that might affect the workflow
|
|
3825
|
-
* but isn't itself
|
|
3922
|
+
* but isn't itself an activity action: a subject doc was patched, a sibling
|
|
3826
3923
|
* workflow completed, the clock crossed a `completeWhen` deadline, etc.
|
|
3827
3924
|
* The runtime doesn't need to know what changed — it just nudges
|
|
3828
3925
|
* affected instances and the engine re-evaluates.
|
|
@@ -3932,8 +4029,8 @@ const workflow = {
|
|
|
3932
4029
|
* declared by a `doc.ref` / `doc.refs` entry in scope), then
|
|
3933
4030
|
* evaluates the supplied GROQ in groq-js against that dataset. The
|
|
3934
4031
|
* rendered scope is auto-bound: `$self`, `$fields`, `$parent`,
|
|
3935
|
-
* `$ancestors`, `$stage`, `$now`, `$effects`, `$
|
|
3936
|
-
* `$
|
|
4032
|
+
* `$ancestors`, `$stage`, `$now`, `$effects`, `$activities`,
|
|
4033
|
+
* `$allActivitiesDone`, `$anyActivityFailed` — ids in GDR URI form to match
|
|
3937
4034
|
* the snapshot's keying.
|
|
3938
4035
|
*
|
|
3939
4036
|
* Use when an external consumer (a UI, a debug pane, a test) wants
|
|
@@ -3986,34 +4083,83 @@ const workflow = {
|
|
|
3986
4083
|
* List the actions an actor could fire on an instance's current stage,
|
|
3987
4084
|
* each flagged `allowed` (with a structured `disabledReason` when not).
|
|
3988
4085
|
* Projects the instance from the actor's perspective and flattens its
|
|
3989
|
-
*
|
|
4086
|
+
* activities' actions. Returns the evaluation alongside the actions so a
|
|
3990
4087
|
* consumer can read the instance/stage context. Pure read.
|
|
3991
4088
|
*/
|
|
3992
4089
|
availableActions: async (args) => {
|
|
3993
4090
|
const evaluation = await evaluateInstance(args);
|
|
3994
|
-
return { evaluation, actions: availableActions(evaluation.currentStage.
|
|
4091
|
+
return { evaluation, actions: availableActions(evaluation.currentStage.activities) };
|
|
3995
4092
|
},
|
|
3996
4093
|
/**
|
|
3997
4094
|
* Materialised spawned children of a parent instance.
|
|
3998
4095
|
*
|
|
3999
4096
|
* Walks `history` for `spawned` events — the durable
|
|
4000
|
-
* record. (The per-
|
|
4097
|
+
* record. (The per-activity `spawnedInstances` field on the active stage
|
|
4001
4098
|
* only exists while that stage is current; history survives.) Strips
|
|
4002
4099
|
* the GDR URI on each `instanceRef` to a bare `_id`, fetches the
|
|
4003
4100
|
* instances, drops any that aren't visible to this engine's tag, and
|
|
4004
4101
|
* returns them sorted by `startedAt` ascending.
|
|
4005
4102
|
*
|
|
4006
|
-
* Pass `
|
|
4103
|
+
* Pass `activity` to restrict to a single spawning activity on the parent.
|
|
4007
4104
|
*/
|
|
4008
4105
|
children: async (args) => {
|
|
4009
|
-
const { client, tag, instanceId,
|
|
4106
|
+
const { client, tag, instanceId, activity } = args;
|
|
4010
4107
|
validateTag(tag);
|
|
4011
|
-
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));
|
|
4012
4109
|
return ids.length === 0 ? [] : client.fetch(
|
|
4013
4110
|
`*[_id in $ids && ${tagScopeFilter()}] | order(startedAt asc)`,
|
|
4014
4111
|
{ ids, tag }
|
|
4015
4112
|
);
|
|
4016
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
|
+
},
|
|
4017
4163
|
/**
|
|
4018
4164
|
* Permission helpers — Sanity ACL grants evaluated against documents
|
|
4019
4165
|
* via GROQ. Used by `workflow.evaluate` to soft-gate actions when the
|
|
@@ -4067,10 +4213,13 @@ function walkEffectNames(def, visit) {
|
|
|
4067
4213
|
for (const e of effects ?? []) visit(e.name, location);
|
|
4068
4214
|
};
|
|
4069
4215
|
for (const stage of def.stages ?? []) {
|
|
4070
|
-
for (const t of stage.
|
|
4071
|
-
visitEffects(t.effects, `stage[${stage.name}].
|
|
4216
|
+
for (const t of stage.activities ?? []) {
|
|
4217
|
+
visitEffects(t.effects, `stage[${stage.name}].activity[${t.name}].effects`);
|
|
4072
4218
|
for (const a of t.actions ?? [])
|
|
4073
|
-
visitEffects(
|
|
4219
|
+
visitEffects(
|
|
4220
|
+
a.effects,
|
|
4221
|
+
`stage[${stage.name}].activity[${t.name}].action[${a.name}].effects`
|
|
4222
|
+
);
|
|
4074
4223
|
}
|
|
4075
4224
|
for (const tr of stage.transitions ?? [])
|
|
4076
4225
|
visitEffects(tr.effects, `stage[${stage.name}].transition[${tr.name}].effects`);
|
|
@@ -4086,7 +4235,7 @@ async function drainEffectsInternal(args) {
|
|
|
4086
4235
|
effectHandlers,
|
|
4087
4236
|
missingHandler,
|
|
4088
4237
|
logger
|
|
4089
|
-
} = args, drainerActor = args.access?.actor ?? engineSystemActor("drainEffects"), completionAccess = {
|
|
4238
|
+
} = args, routeGdr = buildClientForGdr(client, resourceClients), clientFor = (ref) => routeGdr(parseGdr(typeof ref == "string" ? ref : ref.id)), drainerActor = args.access?.actor ?? engineSystemActor("drainEffects"), completionAccess = {
|
|
4090
4239
|
actor: drainerActor,
|
|
4091
4240
|
...args.access?.grants !== void 0 ? { grants: args.access.grants } : {}
|
|
4092
4241
|
};
|
|
@@ -4105,6 +4254,7 @@ async function drainEffectsInternal(args) {
|
|
|
4105
4254
|
if (!await claimPendingEffect(client, before, candidate._key, drainerActor)) continue;
|
|
4106
4255
|
const { outputs, dispatchError } = await dispatchEffect(handler, candidate, {
|
|
4107
4256
|
client,
|
|
4257
|
+
clientFor,
|
|
4108
4258
|
instanceId,
|
|
4109
4259
|
logger
|
|
4110
4260
|
});
|
|
@@ -4159,6 +4309,7 @@ async function dispatchEffect(handler, candidate, ctx) {
|
|
|
4159
4309
|
try {
|
|
4160
4310
|
const result = await handler(candidate.params, {
|
|
4161
4311
|
client: ctx.client,
|
|
4312
|
+
clientFor: ctx.clientFor,
|
|
4162
4313
|
instanceId: ctx.instanceId,
|
|
4163
4314
|
effectKey: candidate._key,
|
|
4164
4315
|
log: (message, extra) => ctx.logger(`effect.${candidate.name}`).info(message, extra)
|
|
@@ -4247,13 +4398,13 @@ function createInstanceSession(args) {
|
|
|
4247
4398
|
return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: cascaded > 0 };
|
|
4248
4399
|
});
|
|
4249
4400
|
},
|
|
4250
|
-
fireAction({
|
|
4401
|
+
fireAction({ activity, action, params }) {
|
|
4251
4402
|
return commit(async (actor, held) => {
|
|
4252
|
-
assertActionAllowed(await evaluateWith(held, heldGuards),
|
|
4403
|
+
assertActionAllowed(await evaluateWith(held, heldGuards), activity, action);
|
|
4253
4404
|
const { grants } = await access(), ranOps = await applyAction({
|
|
4254
4405
|
client,
|
|
4255
4406
|
instance,
|
|
4256
|
-
|
|
4407
|
+
activity,
|
|
4257
4408
|
action,
|
|
4258
4409
|
actor,
|
|
4259
4410
|
clientForGdr,
|
|
@@ -4360,13 +4511,14 @@ function createEngine(args) {
|
|
|
4360
4511
|
definition,
|
|
4361
4512
|
...resourceClients !== void 0 ? { resourceClients } : {}
|
|
4362
4513
|
}),
|
|
4363
|
-
children: ({ instanceId,
|
|
4514
|
+
children: ({ instanceId, activity }) => workflow.children({
|
|
4364
4515
|
client,
|
|
4365
4516
|
tag,
|
|
4366
4517
|
workflowResource,
|
|
4367
4518
|
instanceId,
|
|
4368
|
-
...
|
|
4519
|
+
...activity !== void 0 ? { activity } : {}
|
|
4369
4520
|
}),
|
|
4521
|
+
instancesForDocument: ({ document }) => workflow.instancesForDocument({ client, tag, workflowResource, document }),
|
|
4370
4522
|
query: ({ groq, params }) => workflow.query({
|
|
4371
4523
|
client,
|
|
4372
4524
|
tag,
|
|
@@ -4412,33 +4564,25 @@ function createEngine(args) {
|
|
|
4412
4564
|
})
|
|
4413
4565
|
};
|
|
4414
4566
|
}
|
|
4415
|
-
function
|
|
4416
|
-
|
|
4417
|
-
}
|
|
4418
|
-
function diffStatus(existing, expected) {
|
|
4419
|
-
return existing === void 0 ? "create" : isDefinitionUnchanged(existing, expected) ? "unchanged" : "update";
|
|
4420
|
-
}
|
|
4421
|
-
function diffEntry(def, existingRaw, target) {
|
|
4422
|
-
const docId = docIdFor(def, target), expected = {
|
|
4423
|
-
...def,
|
|
4424
|
-
_id: docId,
|
|
4425
|
-
_type: schema.WORKFLOW_DEFINITION_TYPE,
|
|
4426
|
-
tag: target.tag
|
|
4427
|
-
}, 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;
|
|
4428
4569
|
return {
|
|
4429
4570
|
name: def.name,
|
|
4430
|
-
version:
|
|
4431
|
-
status,
|
|
4432
|
-
docId,
|
|
4433
|
-
expected,
|
|
4571
|
+
version: plan.version,
|
|
4572
|
+
status: plan.status,
|
|
4573
|
+
docId: plan.docId,
|
|
4574
|
+
expected: plan.document,
|
|
4434
4575
|
...existing !== void 0 ? { existing } : {}
|
|
4435
4576
|
};
|
|
4436
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
|
+
}
|
|
4437
4581
|
async function computeDiffEntries(client, defs, target) {
|
|
4438
4582
|
const entries = [];
|
|
4439
4583
|
for (const def of defs) {
|
|
4440
|
-
const
|
|
4441
|
-
entries.push(diffEntry(def,
|
|
4584
|
+
const latest = await loadLatestDeployed(client, def.name, target.tag);
|
|
4585
|
+
entries.push(diffEntry(def, latest, target));
|
|
4442
4586
|
}
|
|
4443
4587
|
return entries;
|
|
4444
4588
|
}
|
|
@@ -4451,17 +4595,17 @@ const HISTORY_DISPLAY = {
|
|
|
4451
4595
|
title: "Stage exited",
|
|
4452
4596
|
description: "The instance left a stage on the way to the next one."
|
|
4453
4597
|
},
|
|
4454
|
-
|
|
4455
|
-
title: "
|
|
4456
|
-
description: "
|
|
4598
|
+
activityActivated: {
|
|
4599
|
+
title: "Activity activated",
|
|
4600
|
+
description: "An activity flipped from `pending` to `active` and its onEnter effects queued."
|
|
4457
4601
|
},
|
|
4458
|
-
|
|
4459
|
-
title: "
|
|
4460
|
-
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)."
|
|
4461
4605
|
},
|
|
4462
4606
|
actionFired: {
|
|
4463
4607
|
title: "Action fired",
|
|
4464
|
-
description: "An action was invoked against
|
|
4608
|
+
description: "An action was invoked against an activity \u2014 ops ran, status may have flipped, effects queued."
|
|
4465
4609
|
},
|
|
4466
4610
|
transitionFired: {
|
|
4467
4611
|
title: "Transition fired",
|
|
@@ -4477,7 +4621,7 @@ const HISTORY_DISPLAY = {
|
|
|
4477
4621
|
},
|
|
4478
4622
|
spawned: {
|
|
4479
4623
|
title: "Spawned child",
|
|
4480
|
-
description: "
|
|
4624
|
+
description: "An activity's `subworkflows` declaration spawned a child workflow instance."
|
|
4481
4625
|
},
|
|
4482
4626
|
aborted: {
|
|
4483
4627
|
title: "Instance aborted",
|
|
@@ -4486,6 +4630,10 @@ const HISTORY_DISPLAY = {
|
|
|
4486
4630
|
opApplied: {
|
|
4487
4631
|
title: "Op applied",
|
|
4488
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."
|
|
4489
4637
|
}
|
|
4490
4638
|
}, FIELD_SLOT_DISPLAY = {
|
|
4491
4639
|
"doc.ref": {
|
|
@@ -4500,50 +4648,58 @@ const HISTORY_DISPLAY = {
|
|
|
4500
4648
|
title: "Content Release reference",
|
|
4501
4649
|
description: "GDR pointer at a Content Release system doc, carrying the release name the engine derives the instance's read perspective from."
|
|
4502
4650
|
},
|
|
4503
|
-
|
|
4504
|
-
title: "Query result",
|
|
4505
|
-
description: "Snapshot of a GROQ projection captured at entry-resolve time; resolvedAt is stamped on the value."
|
|
4506
|
-
},
|
|
4507
|
-
"value.string": {
|
|
4651
|
+
string: {
|
|
4508
4652
|
title: "String value",
|
|
4509
|
-
description: "Free-form string entry."
|
|
4653
|
+
description: "Free-form single-line string entry."
|
|
4510
4654
|
},
|
|
4511
|
-
|
|
4512
|
-
title: "
|
|
4513
|
-
description: "
|
|
4655
|
+
text: {
|
|
4656
|
+
title: "Text value",
|
|
4657
|
+
description: "Free-form multiline string entry."
|
|
4514
4658
|
},
|
|
4515
|
-
|
|
4659
|
+
number: {
|
|
4516
4660
|
title: "Number value",
|
|
4517
4661
|
description: "Numeric entry."
|
|
4518
4662
|
},
|
|
4519
|
-
|
|
4663
|
+
boolean: {
|
|
4520
4664
|
title: "Boolean value",
|
|
4521
4665
|
description: "True/false entry."
|
|
4522
4666
|
},
|
|
4523
|
-
|
|
4667
|
+
date: {
|
|
4668
|
+
title: "Date value",
|
|
4669
|
+
description: "Date-only (YYYY-MM-DD) entry, no time component."
|
|
4670
|
+
},
|
|
4671
|
+
datetime: {
|
|
4524
4672
|
title: "Date / time value",
|
|
4525
|
-
description: "ISO-timestamp entry."
|
|
4673
|
+
description: "ISO-8601 timestamp entry."
|
|
4526
4674
|
},
|
|
4527
|
-
|
|
4528
|
-
title: "
|
|
4529
|
-
description: "
|
|
4675
|
+
url: {
|
|
4676
|
+
title: "URL value",
|
|
4677
|
+
description: "Validated URL entry."
|
|
4530
4678
|
},
|
|
4531
|
-
|
|
4532
|
-
title: "
|
|
4533
|
-
description: "
|
|
4679
|
+
actor: {
|
|
4680
|
+
title: "Actor value",
|
|
4681
|
+
description: "A typed (user|ai|system) actor identity \u2014 a concrete principal."
|
|
4534
4682
|
},
|
|
4535
|
-
|
|
4536
|
-
title: "
|
|
4537
|
-
description: "
|
|
4683
|
+
assignee: {
|
|
4684
|
+
title: "Assignee",
|
|
4685
|
+
description: "A single typed (user|role) assignee \u2014 who is expected/responsible."
|
|
4538
4686
|
},
|
|
4539
4687
|
assignees: {
|
|
4540
4688
|
title: "Assignees",
|
|
4541
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."
|
|
4542
4698
|
}
|
|
4543
4699
|
}, OP_DISPLAY = {
|
|
4544
4700
|
"field.set": {
|
|
4545
4701
|
title: "Set field entry",
|
|
4546
|
-
description: "Overwrite a field entry's value with a resolved
|
|
4702
|
+
description: "Overwrite a field entry's value with a resolved value expression."
|
|
4547
4703
|
},
|
|
4548
4704
|
"field.unset": {
|
|
4549
4705
|
title: "Unset field entry",
|
|
@@ -4551,7 +4707,7 @@ const HISTORY_DISPLAY = {
|
|
|
4551
4707
|
},
|
|
4552
4708
|
"field.append": {
|
|
4553
4709
|
title: "Append to field entry",
|
|
4554
|
-
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)."
|
|
4555
4711
|
},
|
|
4556
4712
|
"field.updateWhere": {
|
|
4557
4713
|
title: "Update matching rows",
|
|
@@ -4562,8 +4718,8 @@ const HISTORY_DISPLAY = {
|
|
|
4562
4718
|
description: "Drop rows of an array-kind entry that match the predicate."
|
|
4563
4719
|
},
|
|
4564
4720
|
"status.set": {
|
|
4565
|
-
title: "Set
|
|
4566
|
-
description: "Flip
|
|
4721
|
+
title: "Set activity status",
|
|
4722
|
+
description: "Flip an activity's status explicitly (the action-level `status:` sugar desugars to this)."
|
|
4567
4723
|
}
|
|
4568
4724
|
}, EFFECTS_CONTEXT_DISPLAY = {
|
|
4569
4725
|
"effectsContext.string": {
|
|
@@ -4603,25 +4759,25 @@ const HISTORY_DISPLAY = {
|
|
|
4603
4759
|
title: "Workflow instance",
|
|
4604
4760
|
description: "A running (or finished) workflow against its declared fields."
|
|
4605
4761
|
}
|
|
4606
|
-
},
|
|
4762
|
+
}, ACTIVITY_KIND_DISPLAY = {
|
|
4607
4763
|
user: {
|
|
4608
|
-
title: "User
|
|
4764
|
+
title: "User activity",
|
|
4609
4765
|
description: "A person acts via the app \u2014 renders action buttons ranked by outcome."
|
|
4610
4766
|
},
|
|
4611
4767
|
service: {
|
|
4612
|
-
title: "Service
|
|
4768
|
+
title: "Service activity",
|
|
4613
4769
|
description: "An automated system or effect does the work \u2014 renders effect drain status."
|
|
4614
4770
|
},
|
|
4615
4771
|
script: {
|
|
4616
|
-
title: "Script
|
|
4772
|
+
title: "Script activity",
|
|
4617
4773
|
description: "The engine runs a step inline and resolves on activation \u2014 an audit row."
|
|
4618
4774
|
},
|
|
4619
4775
|
manual: {
|
|
4620
|
-
title: "Manual
|
|
4776
|
+
title: "Manual activity",
|
|
4621
4777
|
description: "Work done off-system, acknowledged by a person or verified by a predicate \u2014 renders an instruction card with a deep-link."
|
|
4622
4778
|
},
|
|
4623
4779
|
receive: {
|
|
4624
|
-
title: "Receive
|
|
4780
|
+
title: "Receive activity",
|
|
4625
4781
|
description: 'No actor; the engine waits on a condition \u2014 renders "waiting until \u2026", no buttons.'
|
|
4626
4782
|
}
|
|
4627
4783
|
}, DRIVER_KIND_DISPLAY = {
|
|
@@ -4646,10 +4802,11 @@ function displayDescription(typeKey) {
|
|
|
4646
4802
|
if (typeKey)
|
|
4647
4803
|
return DISPLAY[typeKey]?.description;
|
|
4648
4804
|
}
|
|
4805
|
+
exports.ACTIVITY_KINDS = schema.ACTIVITY_KINDS;
|
|
4649
4806
|
exports.DRIVER_KINDS = schema.DRIVER_KINDS;
|
|
4650
|
-
exports.TASK_KINDS = schema.TASK_KINDS;
|
|
4651
4807
|
exports.WORKFLOW_DEFINITION_TYPE = schema.WORKFLOW_DEFINITION_TYPE;
|
|
4652
4808
|
exports.isStartableDefinition = schema.isStartableDefinition;
|
|
4809
|
+
exports.ACTIVITY_KIND_DISPLAY = ACTIVITY_KIND_DISPLAY;
|
|
4653
4810
|
exports.AUTHORING_DISPLAY = AUTHORING_DISPLAY;
|
|
4654
4811
|
exports.ActionDisabledError = ActionDisabledError;
|
|
4655
4812
|
exports.ActionParamsInvalidError = ActionParamsInvalidError;
|
|
@@ -4670,11 +4827,11 @@ exports.MutationGuardDeniedError = MutationGuardDeniedError;
|
|
|
4670
4827
|
exports.OP_DISPLAY = OP_DISPLAY;
|
|
4671
4828
|
exports.PartialGuardDeployError = PartialGuardDeployError;
|
|
4672
4829
|
exports.RequiredFieldNotProvidedError = RequiredFieldNotProvidedError;
|
|
4673
|
-
exports.TASK_KIND_DISPLAY = TASK_KIND_DISPLAY;
|
|
4674
4830
|
exports.WORKFLOW_INSTANCE_TYPE = WORKFLOW_INSTANCE_TYPE;
|
|
4675
4831
|
exports.WorkflowStateDivergedError = WorkflowStateDivergedError;
|
|
4676
4832
|
exports.abortReason = abortReason;
|
|
4677
4833
|
exports.actionVerdict = actionVerdict;
|
|
4834
|
+
exports.activityKind = activityKind;
|
|
4678
4835
|
exports.availableActions = availableActions;
|
|
4679
4836
|
exports.buildSnapshot = buildSnapshot;
|
|
4680
4837
|
exports.compileGuard = compileGuard;
|
|
@@ -4685,7 +4842,7 @@ exports.datasetResourceParts = datasetResourceParts;
|
|
|
4685
4842
|
exports.defaultLoggerFactory = defaultLoggerFactory;
|
|
4686
4843
|
exports.denyingGuards = denyingGuards;
|
|
4687
4844
|
exports.deployStageGuards = deployStageGuards;
|
|
4688
|
-
exports.
|
|
4845
|
+
exports.deriveActivityKind = deriveActivityKind;
|
|
4689
4846
|
exports.diagnoseInputFromEvaluation = diagnoseInputFromEvaluation;
|
|
4690
4847
|
exports.diagnoseInstance = diagnoseInstance;
|
|
4691
4848
|
exports.diffEntry = diffEntry;
|
|
@@ -4703,8 +4860,9 @@ exports.guardMatches = guardMatches;
|
|
|
4703
4860
|
exports.guardsForDefinition = guardsForDefinition;
|
|
4704
4861
|
exports.guardsForInstance = guardsForInstance;
|
|
4705
4862
|
exports.guardsForResource = guardsForResource;
|
|
4863
|
+
exports.hashDefinitionContent = hashDefinitionContent;
|
|
4706
4864
|
exports.instanceGuardQuery = instanceGuardQuery;
|
|
4707
|
-
exports.
|
|
4865
|
+
exports.instanceWatchesDocument = instanceWatchesDocument;
|
|
4708
4866
|
exports.isGdr = isGdr;
|
|
4709
4867
|
exports.isTerminalStage = isTerminalStage;
|
|
4710
4868
|
exports.lakeGuardId = lakeGuardId;
|
|
@@ -4723,7 +4881,6 @@ exports.stripSystemFields = stripSystemFields;
|
|
|
4723
4881
|
exports.subscriptionDocument = subscriptionDocument;
|
|
4724
4882
|
exports.subscriptionDocumentsForInstance = subscriptionDocumentsForInstance;
|
|
4725
4883
|
exports.tagScopeFilter = tagScopeFilter;
|
|
4726
|
-
exports.taskKind = taskKind;
|
|
4727
4884
|
exports.validateDefinition = validateDefinition;
|
|
4728
4885
|
exports.validateTag = validateTag;
|
|
4729
4886
|
exports.verdictGuardsForInstance = verdictGuardsForInstance;
|