@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.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parse, evaluate } from "groq-js";
|
|
2
|
-
import { actorFulfillsRole,
|
|
3
|
-
import {
|
|
2
|
+
import { actorFulfillsRole, isTerminalActivityStatus, WORKFLOW_DEFINITION_TYPE, andConditions, DOCUMENT_VALUE_PERMISSIONS } from "./_chunks-es/schema.js";
|
|
3
|
+
import { ACTIVITY_KINDS, DRIVER_KINDS, isStartableDefinition } from "./_chunks-es/schema.js";
|
|
4
4
|
import * as v from "valibot";
|
|
5
5
|
function findOpenStageEntry(host) {
|
|
6
6
|
return host.stages.find((s) => s.name === host.currentStage && s.exitedAt === void 0);
|
|
@@ -108,7 +108,7 @@ function isGdr(value) {
|
|
|
108
108
|
return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
|
|
109
109
|
}
|
|
110
110
|
function buildParams(args) {
|
|
111
|
-
const { instance, now, snapshot, extra } = args,
|
|
111
|
+
const { instance, now, snapshot, extra } = args, currentActivities2 = findOpenStageEntry(instance)?.activities ?? [];
|
|
112
112
|
return {
|
|
113
113
|
self: selfGdr(instance),
|
|
114
114
|
fields: renderedFields(instance.fields ?? [], snapshot),
|
|
@@ -119,10 +119,12 @@ function buildParams(args) {
|
|
|
119
119
|
/** `$effects` — parent context handoff by entry name, completed-effect
|
|
120
120
|
* outputs namespaced under the effect's name (`$effects['x.y'].out`). */
|
|
121
121
|
effects: effectsContextMap(instance),
|
|
122
|
-
/** `$
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
/** `$activities` — the current stage's activity rows, statuses included. */
|
|
123
|
+
activities: currentActivities2,
|
|
124
|
+
allActivitiesDone: currentActivities2.every(
|
|
125
|
+
(activity) => activity.status === "done" || activity.status === "skipped"
|
|
126
|
+
),
|
|
127
|
+
anyActivityFailed: currentActivities2.some((activity) => activity.status === "failed"),
|
|
126
128
|
...extra
|
|
127
129
|
};
|
|
128
130
|
}
|
|
@@ -134,15 +136,15 @@ function renderedFields(entries, snapshot) {
|
|
|
134
136
|
function renderedValue(entry, snapshot) {
|
|
135
137
|
return entry._type !== "doc.ref" || entry.value === null || snapshot === void 0 ? entry.value : snapshot.docs.find((d) => d._id === entry.value.id) ?? entry.value;
|
|
136
138
|
}
|
|
137
|
-
function scopedFieldOverlay(instance, snapshot,
|
|
139
|
+
function scopedFieldOverlay(instance, snapshot, activityName) {
|
|
138
140
|
const stageEntry = findOpenStageEntry(instance);
|
|
139
141
|
if (stageEntry === void 0) return {};
|
|
140
|
-
const stageFields = renderedFields(stageEntry.fields ?? [], snapshot),
|
|
141
|
-
return { ...stageFields, ...renderedFields(
|
|
142
|
+
const stageFields = renderedFields(stageEntry.fields ?? [], snapshot), activity = activityName ? stageEntry.activities.find((t) => t.name === activityName) : void 0;
|
|
143
|
+
return { ...stageFields, ...renderedFields(activity?.fields ?? [], snapshot) };
|
|
142
144
|
}
|
|
143
|
-
function assignedFor(instance,
|
|
145
|
+
function assignedFor(instance, activityName, actor, roleAliases) {
|
|
144
146
|
if (actor === void 0) return !1;
|
|
145
|
-
const entry = findOpenStageEntry(instance)?.
|
|
147
|
+
const entry = findOpenStageEntry(instance)?.activities.find((t) => t.name === activityName)?.fields?.find((s) => s._type === "assignees");
|
|
146
148
|
return entry === void 0 ? !1 : entry.value.some(
|
|
147
149
|
(a) => a.type === "user" ? a.id === actor.id : actorFulfillsRole(actor.roles, a.role, roleAliases)
|
|
148
150
|
);
|
|
@@ -274,7 +276,7 @@ function validateDefinition(definition) {
|
|
|
274
276
|
validateStage(v2, stage);
|
|
275
277
|
if (v2.errors.length > 0)
|
|
276
278
|
throw new Error(
|
|
277
|
-
`defineWorkflow("${definition.name}"
|
|
279
|
+
`defineWorkflow("${definition.name}"): ${v2.errors.length} validation error${v2.errors.length === 1 ? "" : "s"}:
|
|
278
280
|
` + v2.errors.join(`
|
|
279
281
|
`)
|
|
280
282
|
);
|
|
@@ -295,7 +297,7 @@ function createDefinitionValidator() {
|
|
|
295
297
|
return { errors, tryParse, checkCondition: (groq, where) => {
|
|
296
298
|
tryParse(groq, where), rejectTypeScan(groq, where);
|
|
297
299
|
}, checkEntry: (entry, label) => {
|
|
298
|
-
entry.
|
|
300
|
+
entry.initialValue?.type === "query" && tryParse(entry.initialValue.query, `${label}.query`);
|
|
299
301
|
}, checkGuardRead: (expr, where) => {
|
|
300
302
|
isGuardReadExpr(expr) || errors.push(
|
|
301
303
|
` \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)`
|
|
@@ -312,8 +314,8 @@ function validateStage(v2, stage) {
|
|
|
312
314
|
for (const [key, groq] of effectBindings(t.effects))
|
|
313
315
|
v2.tryParse(groq, `transition "${t.name}" effect binding "${key}"`);
|
|
314
316
|
}
|
|
315
|
-
for (const
|
|
316
|
-
|
|
317
|
+
for (const activity of stage.activities ?? [])
|
|
318
|
+
validateActivity(v2, stage.name, activity);
|
|
317
319
|
}
|
|
318
320
|
function validateGuardReads(v2, stageName, guard) {
|
|
319
321
|
const where = `stage "${stageName}" guard "${guard.name}"`;
|
|
@@ -322,22 +324,22 @@ function validateGuardReads(v2, stageName, guard) {
|
|
|
322
324
|
for (const [key, expr] of Object.entries(guard.metadata ?? {}))
|
|
323
325
|
v2.checkGuardRead(expr, `${where} metadata "${key}"`);
|
|
324
326
|
}
|
|
325
|
-
function
|
|
326
|
-
const where = `stage "${stageName}"
|
|
327
|
-
for (const entry of
|
|
327
|
+
function validateActivity(v2, stageName, activity) {
|
|
328
|
+
const where = `stage "${stageName}" activity "${activity.name}"`;
|
|
329
|
+
for (const entry of activity.fields ?? [])
|
|
328
330
|
v2.checkEntry(entry, `${where}.fields "${entry.name}"`);
|
|
329
|
-
|
|
330
|
-
for (const [name, groq] of Object.entries(
|
|
331
|
+
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`);
|
|
332
|
+
for (const [name, groq] of Object.entries(activity.requirements ?? {}))
|
|
331
333
|
v2.checkCondition(groq, `${where}.requirements "${name}"`);
|
|
332
|
-
for (const [key, groq] of effectBindings(
|
|
334
|
+
for (const [key, groq] of effectBindings(activity.effects))
|
|
333
335
|
v2.tryParse(groq, `${where} effect binding "${key}"`);
|
|
334
|
-
|
|
336
|
+
validateActivityActions(v2, activity), activity.subworkflows !== void 0 && validateSubworkflows(v2, where, activity.subworkflows);
|
|
335
337
|
}
|
|
336
|
-
function
|
|
337
|
-
for (const a of
|
|
338
|
-
a.filter !== void 0 && v2.checkCondition(a.filter, `
|
|
338
|
+
function validateActivityActions(v2, activity) {
|
|
339
|
+
for (const a of activity.actions ?? []) {
|
|
340
|
+
a.filter !== void 0 && v2.checkCondition(a.filter, `activity "${activity.name}" action "${a.name}".filter`);
|
|
339
341
|
for (const [key, groq] of effectBindings(a.effects))
|
|
340
|
-
v2.tryParse(groq, `
|
|
342
|
+
v2.tryParse(groq, `activity "${activity.name}" action "${a.name}" effect binding "${key}"`);
|
|
341
343
|
}
|
|
342
344
|
}
|
|
343
345
|
function validateSubworkflows(v2, where, sub) {
|
|
@@ -352,10 +354,10 @@ function effectBindings(effects) {
|
|
|
352
354
|
(e) => Object.entries(e.bindings ?? {}).map(([k, groq]) => [`${e.name}.${k}`, groq])
|
|
353
355
|
);
|
|
354
356
|
}
|
|
355
|
-
function actionVerdict(
|
|
357
|
+
function actionVerdict(activity, action) {
|
|
356
358
|
return {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
+
activity: activity.activity.name,
|
|
360
|
+
activityStatus: activity.status,
|
|
359
361
|
action: action.action.name,
|
|
360
362
|
title: action.action.title,
|
|
361
363
|
allowed: action.allowed,
|
|
@@ -363,8 +365,8 @@ function actionVerdict(task, action) {
|
|
|
363
365
|
params: action.action.params ?? []
|
|
364
366
|
};
|
|
365
367
|
}
|
|
366
|
-
function availableActions(
|
|
367
|
-
return
|
|
368
|
+
function availableActions(activities) {
|
|
369
|
+
return activities.flatMap((t) => t.actions.map((a) => actionVerdict(t, a)));
|
|
368
370
|
}
|
|
369
371
|
const wallClock = () => (/* @__PURE__ */ new Date()).toISOString(), GUARD_DOC_TYPE = "temp.system.guard";
|
|
370
372
|
class MutationGuardDeniedError extends Error {
|
|
@@ -486,60 +488,67 @@ function diagnoseInputFromEvaluation(evaluation) {
|
|
|
486
488
|
const stage = openStage(evaluation.instance);
|
|
487
489
|
return {
|
|
488
490
|
instance: evaluation.instance,
|
|
489
|
-
|
|
491
|
+
activities: evaluation.currentStage.activities,
|
|
490
492
|
transitions: evaluation.currentStage.transitions,
|
|
491
493
|
assignees: Object.fromEntries(
|
|
492
|
-
evaluation.currentStage.
|
|
494
|
+
evaluation.currentStage.activities.map((t) => [
|
|
495
|
+
t.activity.name,
|
|
496
|
+
assigneesOf(stage, t.activity.name)
|
|
497
|
+
])
|
|
493
498
|
)
|
|
494
499
|
};
|
|
495
500
|
}
|
|
496
501
|
function openStage(instance) {
|
|
497
502
|
return findOpenStageEntry(instance);
|
|
498
503
|
}
|
|
499
|
-
function assigneesOf(stage,
|
|
500
|
-
return (stage?.
|
|
504
|
+
function assigneesOf(stage, activityName) {
|
|
505
|
+
return (stage?.activities.find((t) => t.name === activityName)?.fields ?? []).filter((s) => s._type === "assignees").flatMap((s) => s.value);
|
|
501
506
|
}
|
|
502
507
|
function isResolved(status) {
|
|
503
508
|
return status === "done" || status === "skipped";
|
|
504
509
|
}
|
|
505
510
|
function failedEffectCause(input) {
|
|
506
|
-
const stalled = new Set(
|
|
511
|
+
const stalled = new Set(
|
|
512
|
+
input.activities.filter((t) => !isResolved(t.status)).map((t) => t.activity.name)
|
|
513
|
+
), effect = [...input.instance.effectHistory].reverse().find(
|
|
514
|
+
(e) => e.status === "failed" && e.origin.kind === "activity" && stalled.has(e.origin.name)
|
|
515
|
+
);
|
|
507
516
|
return effect ? { kind: "failed-effect", effect } : void 0;
|
|
508
517
|
}
|
|
509
518
|
function hungEffectCause(input) {
|
|
510
519
|
const effect = input.instance.pendingEffects.find((e) => e.claim !== void 0);
|
|
511
520
|
return effect ? { kind: "hung-effect", effect } : void 0;
|
|
512
521
|
}
|
|
513
|
-
function
|
|
514
|
-
const failed = input.
|
|
515
|
-
return failed ? { kind: "failed-
|
|
522
|
+
function failedActivityCause(input) {
|
|
523
|
+
const failed = input.activities.find((t) => t.status === "failed");
|
|
524
|
+
return failed ? { kind: "failed-activity", activity: failed.activity.name } : void 0;
|
|
516
525
|
}
|
|
517
526
|
function waitingState(input) {
|
|
518
|
-
const active = input.
|
|
519
|
-
(t) => t.status === "active" && (t.
|
|
527
|
+
const active = input.activities.find(
|
|
528
|
+
(t) => t.status === "active" && (t.activity.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length === 0
|
|
520
529
|
);
|
|
521
530
|
if (active !== void 0)
|
|
522
531
|
return {
|
|
523
532
|
state: "waiting",
|
|
524
|
-
|
|
525
|
-
assignees: input.assignees[active.
|
|
526
|
-
actions: (active.
|
|
533
|
+
activity: active.activity.name,
|
|
534
|
+
assignees: input.assignees[active.activity.name] ?? [],
|
|
535
|
+
actions: (active.activity.actions ?? []).map((a) => a.name)
|
|
527
536
|
};
|
|
528
537
|
}
|
|
529
538
|
function blockedState(input) {
|
|
530
|
-
const blocked = input.
|
|
531
|
-
(t) => t.status === "active" && (t.
|
|
539
|
+
const blocked = input.activities.find(
|
|
540
|
+
(t) => t.status === "active" && (t.activity.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length > 0
|
|
532
541
|
);
|
|
533
542
|
if (blocked !== void 0)
|
|
534
543
|
return {
|
|
535
544
|
state: "blocked",
|
|
536
|
-
|
|
545
|
+
activity: blocked.activity.name,
|
|
537
546
|
requirements: blocked.unmetRequirements ?? [],
|
|
538
|
-
assignees: input.assignees[blocked.
|
|
547
|
+
assignees: input.assignees[blocked.activity.name] ?? []
|
|
539
548
|
};
|
|
540
549
|
}
|
|
541
550
|
function noTransitionFiresCause(input) {
|
|
542
|
-
if (input.transitions.length === 0 || input.transitions.some((t) => t.filterSatisfied) || !input.
|
|
551
|
+
if (input.transitions.length === 0 || input.transitions.some((t) => t.filterSatisfied) || !input.activities.every((t) => isResolved(t.status)))
|
|
543
552
|
return;
|
|
544
553
|
const undecidable = input.transitions.filter((t) => t.unevaluable);
|
|
545
554
|
return undecidable.length > 0 ? { kind: "transition-unevaluable", transitions: undecidable.map((t) => t.transition.name) } : { kind: "no-transition-fires" };
|
|
@@ -552,7 +561,7 @@ function diagnoseInstance(input) {
|
|
|
552
561
|
}
|
|
553
562
|
if (instance.completedAt !== void 0)
|
|
554
563
|
return { state: "completed", at: instance.completedAt };
|
|
555
|
-
const cause = failedEffectCause(input) ??
|
|
564
|
+
const cause = failedEffectCause(input) ?? failedActivityCause(input) ?? hungEffectCause(input) ?? noTransitionFiresCause(input);
|
|
556
565
|
return cause !== void 0 ? { state: "stuck", cause } : waitingState(input) ?? blockedState(input) ?? { state: "progressing" };
|
|
557
566
|
}
|
|
558
567
|
const RUNNABLE_VERBS = /* @__PURE__ */ new Set(["set-stage", "abort"]);
|
|
@@ -574,12 +583,12 @@ function remediationsForCause(cause) {
|
|
|
574
583
|
},
|
|
575
584
|
{ verb: "abort", rationale: "Abort the instance if the effect never drains." }
|
|
576
585
|
];
|
|
577
|
-
case "failed-
|
|
586
|
+
case "failed-activity":
|
|
578
587
|
return [
|
|
579
|
-
{ verb: "reset-
|
|
588
|
+
{ verb: "reset-activity", rationale: "Reset or skip the failed activity." },
|
|
580
589
|
{
|
|
581
590
|
verb: "set-stage",
|
|
582
|
-
rationale: "Move the instance past the failed
|
|
591
|
+
rationale: "Move the instance past the failed activity to the intended next stage."
|
|
583
592
|
}
|
|
584
593
|
];
|
|
585
594
|
case "no-transition-fires":
|
|
@@ -601,13 +610,15 @@ function remediationsFor(diagnosis) {
|
|
|
601
610
|
}
|
|
602
611
|
class ActionParamsInvalidError extends Error {
|
|
603
612
|
action;
|
|
604
|
-
|
|
613
|
+
activity;
|
|
605
614
|
issues;
|
|
606
615
|
constructor(args) {
|
|
607
616
|
const lines = args.issues.map((i) => ` - ${i.param}: ${i.reason}`).join(`
|
|
608
617
|
`);
|
|
609
|
-
super(
|
|
610
|
-
|
|
618
|
+
super(
|
|
619
|
+
`Action "${args.action}" on activity "${args.activity}" rejected: invalid params
|
|
620
|
+
${lines}`
|
|
621
|
+
), this.name = "ActionParamsInvalidError", this.action = args.action, this.activity = args.activity, this.issues = args.issues;
|
|
611
622
|
}
|
|
612
623
|
}
|
|
613
624
|
class RequiredFieldNotProvidedError extends Error {
|
|
@@ -617,7 +628,7 @@ class RequiredFieldNotProvidedError extends Error {
|
|
|
617
628
|
const lines = args.missing.map((m) => ` - ${m.name} (${m.type})`).join(`
|
|
618
629
|
`), where = args.definition !== void 0 ? ` for workflow "${args.definition}"` : "";
|
|
619
630
|
super(
|
|
620
|
-
`Required
|
|
631
|
+
`Required input fields${where} were not provided:
|
|
621
632
|
${lines}
|
|
622
633
|
Provide each via \`initialFields\` when starting standalone, or via the parent's \`subworkflows.with\` when spawned.`
|
|
623
634
|
), this.name = "RequiredFieldNotProvidedError", args.definition !== void 0 && (this.definition = args.definition), this.missing = args.missing;
|
|
@@ -649,13 +660,13 @@ class PartialGuardDeployError extends Error {
|
|
|
649
660
|
const CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS = 3;
|
|
650
661
|
class ConcurrentFireActionError extends Error {
|
|
651
662
|
instanceId;
|
|
652
|
-
|
|
663
|
+
activity;
|
|
653
664
|
action;
|
|
654
665
|
attempts;
|
|
655
666
|
constructor(args) {
|
|
656
667
|
super(
|
|
657
|
-
`Action "${args.action}" on
|
|
658
|
-
), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.
|
|
668
|
+
`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.`
|
|
669
|
+
), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.activity = args.activity, this.action = args.action, this.attempts = args.attempts;
|
|
659
670
|
}
|
|
660
671
|
}
|
|
661
672
|
function isRevisionConflict(error) {
|
|
@@ -668,7 +679,7 @@ class ConcurrentEditFieldError extends Error {
|
|
|
668
679
|
target;
|
|
669
680
|
attempts;
|
|
670
681
|
constructor(args) {
|
|
671
|
-
const where = args.target.
|
|
682
|
+
const where = args.target.activity !== void 0 ? `${args.target.activity}.${args.target.field}` : args.target.field;
|
|
672
683
|
super(
|
|
673
684
|
`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.`
|
|
674
685
|
), this.name = "ConcurrentEditFieldError", this.instanceId = args.instanceId, this.target = args.target, this.attempts = args.attempts;
|
|
@@ -746,6 +757,9 @@ function subscriptionDocumentsForInstance(instance) {
|
|
|
746
757
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
747
758
|
};
|
|
748
759
|
}
|
|
760
|
+
function instanceWatchesDocument(instance, document) {
|
|
761
|
+
return collectWatchRefs(instance).some((ref) => ref.id === document);
|
|
762
|
+
}
|
|
749
763
|
function entryDocRefs(entries) {
|
|
750
764
|
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) : [] : []);
|
|
751
765
|
}
|
|
@@ -860,14 +874,14 @@ function guardIdRefs(definition) {
|
|
|
860
874
|
function staticIdRefGdr(idRef, stage, definition) {
|
|
861
875
|
const read = /^\$fields\.([\w-]+)/.exec(idRef);
|
|
862
876
|
if (read === null) return;
|
|
863
|
-
const
|
|
864
|
-
return
|
|
877
|
+
const seed = entriesInScope(stage, definition).find((e) => e.name === read[1])?.initialValue;
|
|
878
|
+
return seed?.type === "literal" ? gdrFromValue(seed.value) : void 0;
|
|
865
879
|
}
|
|
866
880
|
function entriesInScope(stage, definition) {
|
|
867
881
|
return [
|
|
868
882
|
...definition.fields ?? [],
|
|
869
883
|
...stage.fields ?? [],
|
|
870
|
-
...(stage.
|
|
884
|
+
...(stage.activities ?? []).flatMap((activity) => activity.fields ?? [])
|
|
871
885
|
];
|
|
872
886
|
}
|
|
873
887
|
function gdrFromValue(value) {
|
|
@@ -1049,76 +1063,73 @@ const GdrShape = v.looseObject({
|
|
|
1049
1063
|
}), AssigneeShape = v.union([
|
|
1050
1064
|
v.looseObject({ type: v.literal("user"), id: v.pipe(v.string(), v.minLength(1)) }),
|
|
1051
1065
|
v.looseObject({ type: v.literal("role"), role: v.pipe(v.string(), v.minLength(1)) })
|
|
1052
|
-
]),
|
|
1053
|
-
label: v.pipe(v.string(), v.minLength(1)),
|
|
1054
|
-
done: v.boolean(),
|
|
1055
|
-
doneBy: v.optional(v.string()),
|
|
1056
|
-
doneAt: v.optional(v.string()),
|
|
1057
|
-
_key: v.optional(v.pipe(v.string(), v.minLength(1)))
|
|
1058
|
-
}), NoteItemShape = v.pipe(
|
|
1059
|
-
v.looseObject({
|
|
1060
|
-
_key: v.optional(v.pipe(v.string(), v.minLength(1)))
|
|
1061
|
-
}),
|
|
1062
|
-
v.check(
|
|
1063
|
-
(val) => typeof val == "object" && val !== null && !Array.isArray(val),
|
|
1064
|
-
"must be an object"
|
|
1065
|
-
)
|
|
1066
|
-
), NullableString = v.union([v.null(), v.string()]), NullableNumber = v.union([v.null(), v.number()]), NullableBoolean = v.union([v.null(), v.boolean()]), NullableDateTime = v.union([
|
|
1066
|
+
]), NullableString = v.union([v.null(), v.string()]), NullableNumber = v.union([v.null(), v.number()]), NullableBoolean = v.union([v.null(), v.boolean()]), NullableDateTime = v.union([
|
|
1067
1067
|
v.null(),
|
|
1068
1068
|
v.pipe(
|
|
1069
1069
|
v.string(),
|
|
1070
1070
|
v.check((s) => !Number.isNaN(Date.parse(s)), "must be an ISO-8601 datetime string")
|
|
1071
1071
|
)
|
|
1072
|
+
]), NullableDate = v.union([
|
|
1073
|
+
v.null(),
|
|
1074
|
+
v.pipe(v.string(), v.regex(/^\d{4}-\d{2}-\d{2}$/, "must be a `YYYY-MM-DD` date"))
|
|
1072
1075
|
]), NullableUrl = NullableString, valueSchemas = {
|
|
1073
1076
|
"doc.ref": v.union([v.null(), GdrShape]),
|
|
1074
1077
|
"doc.refs": v.array(GdrShape),
|
|
1075
1078
|
"release.ref": v.union([v.null(), ReleaseRefShape]),
|
|
1076
1079
|
query: v.any(),
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1080
|
+
string: NullableString,
|
|
1081
|
+
text: NullableString,
|
|
1082
|
+
number: NullableNumber,
|
|
1083
|
+
boolean: NullableBoolean,
|
|
1084
|
+
date: NullableDate,
|
|
1085
|
+
datetime: NullableDateTime,
|
|
1086
|
+
url: NullableUrl,
|
|
1087
|
+
actor: v.union([v.null(), ActorShape]),
|
|
1088
|
+
assignee: v.union([v.null(), AssigneeShape]),
|
|
1085
1089
|
assignees: v.array(AssigneeShape)
|
|
1086
|
-
}, itemSchemas = {
|
|
1087
|
-
"doc.refs": GdrShape,
|
|
1088
|
-
checklist: ChecklistItemShape,
|
|
1089
|
-
notes: NoteItemShape,
|
|
1090
|
-
assignees: AssigneeShape
|
|
1091
1090
|
};
|
|
1092
|
-
function
|
|
1093
|
-
return
|
|
1091
|
+
function shapeValueSchema(shape) {
|
|
1092
|
+
return shape.type === "object" ? objectSchema(shape.fields ?? []) : shape.type === "array" ? v.array(objectSchema(shape.of ?? [])) : valueSchemas[shape.type] ?? v.any();
|
|
1094
1093
|
}
|
|
1095
|
-
function
|
|
1096
|
-
const
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1094
|
+
function objectSchema(fields) {
|
|
1095
|
+
const entries = /* @__PURE__ */ Object.create(null);
|
|
1096
|
+
for (const f of fields) entries[f.name] = v.optional(shapeValueSchema(f));
|
|
1097
|
+
return v.looseObject(entries);
|
|
1098
|
+
}
|
|
1099
|
+
function wholeValueSchema(entryType, shape) {
|
|
1100
|
+
return entryType === "object" ? v.union([v.null(), objectSchema(shape.fields ?? [])]) : entryType === "array" ? v.array(objectSchema(shape.of ?? [])) : valueSchemas[entryType];
|
|
1101
|
+
}
|
|
1102
|
+
function appendItemSchema(entryType, shape) {
|
|
1103
|
+
if (entryType === "array") return objectSchema(shape.of ?? []);
|
|
1104
|
+
if (entryType === "doc.refs") return GdrShape;
|
|
1105
|
+
if (entryType === "assignees") return AssigneeShape;
|
|
1106
|
+
}
|
|
1107
|
+
function checkFieldValue(args) {
|
|
1108
|
+
const schema = wholeValueSchema(args.entryType, args);
|
|
1109
|
+
if (schema === void 0) return [`unknown field entry type ${args.entryType}`];
|
|
1104
1110
|
const result = v.safeParse(schema, args.value);
|
|
1105
|
-
|
|
1111
|
+
return result.success ? void 0 : formatIssues(result.issues);
|
|
1112
|
+
}
|
|
1113
|
+
function validateFieldValue(args) {
|
|
1114
|
+
const issues = checkFieldValue(args);
|
|
1115
|
+
if (issues !== void 0)
|
|
1106
1116
|
throw new FieldValueShapeError({
|
|
1107
1117
|
entryType: args.entryType,
|
|
1108
1118
|
entryName: args.entryName,
|
|
1109
|
-
issues
|
|
1119
|
+
issues,
|
|
1110
1120
|
mode: "value"
|
|
1111
1121
|
});
|
|
1112
1122
|
}
|
|
1113
1123
|
function validateFieldAppendItem(args) {
|
|
1114
|
-
|
|
1124
|
+
const schema = appendItemSchema(args.entryType, args);
|
|
1125
|
+
if (schema === void 0)
|
|
1115
1126
|
throw new FieldValueShapeError({
|
|
1116
1127
|
entryType: args.entryType,
|
|
1117
1128
|
entryName: args.entryName,
|
|
1118
1129
|
issues: [`field entry type ${args.entryType} does not support append`],
|
|
1119
1130
|
mode: "item"
|
|
1120
1131
|
});
|
|
1121
|
-
const
|
|
1132
|
+
const result = v.safeParse(schema, args.item);
|
|
1122
1133
|
if (!result.success)
|
|
1123
1134
|
throw new FieldValueShapeError({
|
|
1124
1135
|
entryType: args.entryType,
|
|
@@ -1146,7 +1157,7 @@ function derivePerspectiveFromFields(entries) {
|
|
|
1146
1157
|
async function resolveDeclaredFields(args) {
|
|
1147
1158
|
const { entryDefs, initialFields, ctx, randomKey: randomKey2 } = args;
|
|
1148
1159
|
if (entryDefs === void 0 || entryDefs.length === 0) return [];
|
|
1149
|
-
|
|
1160
|
+
assertRequiredInputProvided(entryDefs, initialFields, ctx.definitionName);
|
|
1150
1161
|
const out = [];
|
|
1151
1162
|
for (const entry of entryDefs) {
|
|
1152
1163
|
const scopedCtx = { ...ctx, resolvedFields: out };
|
|
@@ -1154,8 +1165,8 @@ async function resolveDeclaredFields(args) {
|
|
|
1154
1165
|
}
|
|
1155
1166
|
return out;
|
|
1156
1167
|
}
|
|
1157
|
-
function
|
|
1158
|
-
const missing = entryDefs.filter((entry) => entry.required === !0 && entry.
|
|
1168
|
+
function assertRequiredInputProvided(entryDefs, initialFields, definitionName) {
|
|
1169
|
+
const missing = entryDefs.filter((entry) => entry.required === !0 && entry.initialValue?.type === "input").filter((entry) => {
|
|
1159
1170
|
const match = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1160
1171
|
return match === void 0 || match.value === void 0 || match.value === null;
|
|
1161
1172
|
}).map((entry) => ({ name: entry.name, type: entry.type }));
|
|
@@ -1165,18 +1176,13 @@ function assertRequiredInitProvided(entryDefs, initialFields, definitionName) {
|
|
|
1165
1176
|
missing
|
|
1166
1177
|
});
|
|
1167
1178
|
}
|
|
1168
|
-
const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set([
|
|
1169
|
-
"doc.refs",
|
|
1170
|
-
"checklist",
|
|
1171
|
-
"notes",
|
|
1172
|
-
"assignees"
|
|
1173
|
-
]);
|
|
1179
|
+
const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set(["doc.refs", "array", "assignees"]);
|
|
1174
1180
|
function defaultEntryValue(entryType) {
|
|
1175
1181
|
return ARRAY_SLOT_TYPES.has(entryType) ? [] : null;
|
|
1176
1182
|
}
|
|
1177
|
-
function
|
|
1183
|
+
function resolveInputValue(entry, initialFields, defaultValue) {
|
|
1178
1184
|
const initMatch = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1179
|
-
return initMatch === void 0 ? defaultValue : (
|
|
1185
|
+
return initMatch === void 0 ? defaultValue : (assertInputValueShape(entry, initMatch.value), initMatch.value);
|
|
1180
1186
|
}
|
|
1181
1187
|
function resolveFieldReadValue(source, ctx, defaultValue) {
|
|
1182
1188
|
const target = (source.scope === "workflow" ? ctx.workflowFields ?? [] : ctx.resolvedFields ?? []).find((s) => s.name === source.field), targetValue = target === void 0 ? void 0 : target.value;
|
|
@@ -1187,7 +1193,7 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
|
1187
1193
|
self: ctx.selfId ?? null,
|
|
1188
1194
|
fields: fieldMapFromResolved(ctx.resolvedFields ?? []),
|
|
1189
1195
|
stage: ctx.stageName ?? null,
|
|
1190
|
-
|
|
1196
|
+
activity: ctx.activityName ?? null,
|
|
1191
1197
|
now: ctx.now,
|
|
1192
1198
|
tag: ctx.tag
|
|
1193
1199
|
});
|
|
@@ -1202,38 +1208,47 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
|
1202
1208
|
}
|
|
1203
1209
|
}
|
|
1204
1210
|
async function resolveEntryValue(entry, initialFields, ctx, defaultValue) {
|
|
1205
|
-
const source = entry.
|
|
1206
|
-
return source.type === "
|
|
1211
|
+
const source = entry.initialValue;
|
|
1212
|
+
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;
|
|
1207
1213
|
}
|
|
1208
1214
|
function buildResolvedEntry(entry, value, _key, now) {
|
|
1209
|
-
|
|
1210
|
-
return entry.type === "query" ? {
|
|
1211
|
-
_key,
|
|
1212
|
-
_type: "query",
|
|
1213
|
-
name: entry.name,
|
|
1214
|
-
...titleProp,
|
|
1215
|
-
...descriptionProp,
|
|
1216
|
-
value,
|
|
1217
|
-
resolvedAt: now
|
|
1218
|
-
} : {
|
|
1215
|
+
return {
|
|
1219
1216
|
_key,
|
|
1220
1217
|
_type: entry.type,
|
|
1221
1218
|
name: entry.name,
|
|
1222
|
-
...
|
|
1223
|
-
...
|
|
1224
|
-
value
|
|
1219
|
+
...entry.title !== void 0 ? { title: entry.title } : {},
|
|
1220
|
+
...entry.description !== void 0 ? { description: entry.description } : {},
|
|
1221
|
+
value,
|
|
1222
|
+
...entry.initialValue?.type === "query" ? { resolvedAt: now } : {},
|
|
1223
|
+
...entry.type === "object" ? { fields: entry.fields ?? [] } : {},
|
|
1224
|
+
...entry.type === "array" ? { of: entry.of ?? [] } : {}
|
|
1225
1225
|
};
|
|
1226
1226
|
}
|
|
1227
1227
|
async function resolveOneEntry(entry, initialFields, ctx, randomKey2) {
|
|
1228
1228
|
const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue(entry, initialFields, ctx, defaultValue);
|
|
1229
|
-
|
|
1229
|
+
if (entry.initialValue?.type === "query") {
|
|
1230
|
+
const issues = checkFieldValue({
|
|
1231
|
+
entryType: entry.type,
|
|
1232
|
+
value,
|
|
1233
|
+
fields: entry.fields,
|
|
1234
|
+
of: entry.of
|
|
1235
|
+
});
|
|
1236
|
+
return issues !== void 0 ? (ctx.recordDiscard?.({ field: entry.name, detail: issues.join("; ") }), buildResolvedEntry(entry, defaultValue, randomKey2(), ctx.now)) : buildResolvedEntry(entry, value, randomKey2(), ctx.now);
|
|
1237
|
+
}
|
|
1238
|
+
return validateFieldValue({
|
|
1239
|
+
entryType: entry.type,
|
|
1240
|
+
entryName: entry.name,
|
|
1241
|
+
value,
|
|
1242
|
+
fields: entry.fields,
|
|
1243
|
+
of: entry.of
|
|
1244
|
+
}), buildResolvedEntry(entry, value, randomKey2(), ctx.now);
|
|
1230
1245
|
}
|
|
1231
1246
|
function fieldMapFromResolved(entries) {
|
|
1232
1247
|
const out = {};
|
|
1233
1248
|
for (const s of entries) out[s.name] = s.value;
|
|
1234
1249
|
return out;
|
|
1235
1250
|
}
|
|
1236
|
-
function
|
|
1251
|
+
function assertInputValueShape(entry, value) {
|
|
1237
1252
|
if (entry.type === "doc.ref") {
|
|
1238
1253
|
assertGdrShape(value, `field entry "${entry.name}" (doc.ref)`);
|
|
1239
1254
|
return;
|
|
@@ -1243,14 +1258,14 @@ function assertInitValueShape(entry, value) {
|
|
|
1243
1258
|
const v2 = value;
|
|
1244
1259
|
if (typeof v2.releaseName != "string" || v2.releaseName.length === 0)
|
|
1245
1260
|
throw new Error(
|
|
1246
|
-
`Invalid
|
|
1261
|
+
`Invalid input value for field entry "${entry.name}" (release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(v2.releaseName)}.`
|
|
1247
1262
|
);
|
|
1248
1263
|
return;
|
|
1249
1264
|
}
|
|
1250
1265
|
if (entry.type === "doc.refs") {
|
|
1251
1266
|
if (!Array.isArray(value))
|
|
1252
1267
|
throw new Error(
|
|
1253
|
-
`Invalid
|
|
1268
|
+
`Invalid input value for field entry "${entry.name}" (doc.refs): expected an array of GDRs, got ${typeof value}.`
|
|
1254
1269
|
);
|
|
1255
1270
|
for (const [i, item] of value.entries())
|
|
1256
1271
|
assertGdrShape(item, `field entry "${entry.name}" (doc.refs) item [${i}]`);
|
|
@@ -1314,12 +1329,12 @@ async function loadContext(client, instanceId, options) {
|
|
|
1314
1329
|
async function ctxConditionParams(ctx, opts) {
|
|
1315
1330
|
const base = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), fields = {
|
|
1316
1331
|
...base.fields,
|
|
1317
|
-
...scopedFieldOverlay(ctx.instance, ctx.snapshot, opts?.
|
|
1332
|
+
...scopedFieldOverlay(ctx.instance, ctx.snapshot, opts?.activityName)
|
|
1318
1333
|
}, params = {
|
|
1319
1334
|
...base,
|
|
1320
1335
|
fields,
|
|
1321
1336
|
actor: opts?.actor,
|
|
1322
|
-
assigned: opts?.
|
|
1337
|
+
assigned: opts?.activityName !== void 0 ? assignedFor(ctx.instance, opts.activityName, opts?.actor, ctx.definition.roleAliases) : !1,
|
|
1323
1338
|
...opts?.vars
|
|
1324
1339
|
};
|
|
1325
1340
|
return { ...await evaluatePredicates({
|
|
@@ -1357,7 +1372,7 @@ function findStage(definition, stageName) {
|
|
|
1357
1372
|
return stage;
|
|
1358
1373
|
}
|
|
1359
1374
|
async function resolveStageFieldEntries(args) {
|
|
1360
|
-
const { client, instance, stage, now, initialFields } = args;
|
|
1375
|
+
const { client, instance, stage, now, initialFields, recordDiscard } = args;
|
|
1361
1376
|
return resolveDeclaredFields({
|
|
1362
1377
|
entryDefs: stage.fields,
|
|
1363
1378
|
initialFields: initialFields ?? [],
|
|
@@ -1368,18 +1383,19 @@ async function resolveStageFieldEntries(args) {
|
|
|
1368
1383
|
tag: instance.tag,
|
|
1369
1384
|
stageName: stage.name,
|
|
1370
1385
|
workflowResource: instance.workflowResource,
|
|
1371
|
-
// Allow stage-scope entries' `
|
|
1386
|
+
// Allow stage-scope entries' `initialValue: { type: "fieldRead", scope:
|
|
1372
1387
|
// "workflow", ... }` to see the already-resolved workflow-scope fields.
|
|
1373
1388
|
workflowFields: instance.fields ?? [],
|
|
1374
|
-
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
1389
|
+
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {},
|
|
1390
|
+
...recordDiscard !== void 0 ? { recordDiscard } : {}
|
|
1375
1391
|
},
|
|
1376
1392
|
randomKey
|
|
1377
1393
|
});
|
|
1378
1394
|
}
|
|
1379
|
-
async function
|
|
1380
|
-
const { client, instance, stage,
|
|
1395
|
+
async function resolveActivityFieldEntries(args) {
|
|
1396
|
+
const { client, instance, stage, activity, now, recordDiscard } = args;
|
|
1381
1397
|
return resolveDeclaredFields({
|
|
1382
|
-
entryDefs:
|
|
1398
|
+
entryDefs: activity.fields,
|
|
1383
1399
|
initialFields: [],
|
|
1384
1400
|
ctx: {
|
|
1385
1401
|
client,
|
|
@@ -1388,85 +1404,70 @@ async function resolveTaskFieldEntries(args) {
|
|
|
1388
1404
|
tag: instance.tag,
|
|
1389
1405
|
stageName: stage.name,
|
|
1390
1406
|
workflowResource: instance.workflowResource,
|
|
1391
|
-
|
|
1407
|
+
activityName: activity.name,
|
|
1392
1408
|
workflowFields: instance.fields ?? [],
|
|
1393
|
-
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
1409
|
+
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {},
|
|
1410
|
+
...recordDiscard !== void 0 ? { recordDiscard } : {}
|
|
1394
1411
|
},
|
|
1395
1412
|
randomKey
|
|
1396
1413
|
});
|
|
1397
1414
|
}
|
|
1398
|
-
async function
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
throw new WorkflowStateDivergedError({
|
|
1404
|
-
instanceId: args.instanceId,
|
|
1405
|
-
guardError,
|
|
1406
|
-
reason: "the move created child instances a parent-only rollback cannot undo"
|
|
1407
|
-
});
|
|
1408
|
-
try {
|
|
1409
|
-
let patch = args.client.patch(args.instanceId).set(args.restore);
|
|
1410
|
-
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);
|
|
1411
|
-
} catch (rollbackError) {
|
|
1412
|
-
throw new WorkflowStateDivergedError({
|
|
1413
|
-
instanceId: args.instanceId,
|
|
1414
|
-
guardError,
|
|
1415
|
-
rollbackError,
|
|
1416
|
-
reason: "rollback of the committed move failed"
|
|
1417
|
-
});
|
|
1418
|
-
}
|
|
1419
|
-
throw guardError instanceof PartialGuardDeployError ? new WorkflowStateDivergedError({
|
|
1420
|
-
instanceId: args.instanceId,
|
|
1421
|
-
guardError,
|
|
1422
|
-
reason: "a multi-guard deploy partially applied; the rolled-back move left orphaned guard locks"
|
|
1423
|
-
}) : guardError;
|
|
1424
|
-
}
|
|
1415
|
+
async function resolveBindings(args) {
|
|
1416
|
+
const resolved = {};
|
|
1417
|
+
for (const [key, groq] of Object.entries(args.bindings ?? {}))
|
|
1418
|
+
resolved[key] = await runGroq(groq, args.params, args.snapshot);
|
|
1419
|
+
return { ...resolved, ...args.staticInput };
|
|
1425
1420
|
}
|
|
1426
|
-
function
|
|
1427
|
-
const
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
task: entry.name,
|
|
1434
|
-
from,
|
|
1435
|
-
to,
|
|
1436
|
-
...actor !== void 0 ? { actor } : {}
|
|
1437
|
-
});
|
|
1421
|
+
async function reload(client, instanceId, tag) {
|
|
1422
|
+
const doc = await client.getDocument(instanceId);
|
|
1423
|
+
if (!doc)
|
|
1424
|
+
throw new Error(`Workflow instance ${instanceId} not found`);
|
|
1425
|
+
if (doc.tag !== tag)
|
|
1426
|
+
throw new Error(`Workflow instance ${instanceId} not visible to this engine (tag mismatch)`);
|
|
1427
|
+
return doc;
|
|
1438
1428
|
}
|
|
1439
|
-
function
|
|
1440
|
-
const
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1429
|
+
function effectsContextEntry(name, value) {
|
|
1430
|
+
const _key = randomKey();
|
|
1431
|
+
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 };
|
|
1432
|
+
}
|
|
1433
|
+
function effectsContextJsonEntry(name, value) {
|
|
1434
|
+
return { _key: randomKey(), _type: "effectsContext.json", name, value: JSON.stringify(value) };
|
|
1435
|
+
}
|
|
1436
|
+
function buildInstanceBase(args) {
|
|
1437
|
+
const { id, now, actor, perspective } = args;
|
|
1438
|
+
return {
|
|
1439
|
+
_id: id,
|
|
1440
|
+
_type: WORKFLOW_INSTANCE_TYPE,
|
|
1441
|
+
_rev: "",
|
|
1442
|
+
_createdAt: now,
|
|
1443
|
+
_updatedAt: now,
|
|
1444
|
+
tag: args.tag,
|
|
1445
|
+
workflowResource: args.workflowResource,
|
|
1446
|
+
definition: args.definitionName,
|
|
1447
|
+
pinnedVersion: args.pinnedVersion,
|
|
1448
|
+
...args.pinnedContentHash !== void 0 ? { pinnedContentHash: args.pinnedContentHash } : {},
|
|
1449
|
+
definitionSnapshot: JSON.stringify(args.definition),
|
|
1450
|
+
fields: args.fields,
|
|
1451
|
+
effectsContext: args.effectsContext,
|
|
1452
|
+
ancestors: args.ancestors,
|
|
1453
|
+
...perspective !== void 0 ? { perspective } : {},
|
|
1454
|
+
currentStage: args.initialStage,
|
|
1455
|
+
stages: [],
|
|
1456
|
+
pendingEffects: [],
|
|
1457
|
+
effectHistory: [],
|
|
1458
|
+
history: [
|
|
1459
|
+
{
|
|
1460
|
+
_key: randomKey(),
|
|
1461
|
+
_type: "stageEntered",
|
|
1462
|
+
at: now,
|
|
1463
|
+
stage: args.initialStage,
|
|
1464
|
+
...actor !== void 0 ? { actor } : {}
|
|
1465
|
+
},
|
|
1466
|
+
...args.extraHistory ?? []
|
|
1467
|
+
],
|
|
1468
|
+
startedAt: now,
|
|
1469
|
+
lastChangedAt: now
|
|
1446
1470
|
};
|
|
1447
|
-
return [
|
|
1448
|
-
{
|
|
1449
|
-
_key: randomKey(),
|
|
1450
|
-
_type: "stageExited",
|
|
1451
|
-
stage: fromStage,
|
|
1452
|
-
toStage,
|
|
1453
|
-
...shared
|
|
1454
|
-
},
|
|
1455
|
-
{
|
|
1456
|
-
_key: randomKey(),
|
|
1457
|
-
_type: "transitionFired",
|
|
1458
|
-
fromStage,
|
|
1459
|
-
toStage,
|
|
1460
|
-
...shared
|
|
1461
|
-
},
|
|
1462
|
-
{
|
|
1463
|
-
_key: randomKey(),
|
|
1464
|
-
_type: "stageEntered",
|
|
1465
|
-
stage: toStage,
|
|
1466
|
-
fromStage,
|
|
1467
|
-
...shared
|
|
1468
|
-
}
|
|
1469
|
-
];
|
|
1470
1471
|
}
|
|
1471
1472
|
function startMutation(instance) {
|
|
1472
1473
|
return {
|
|
@@ -1477,7 +1478,7 @@ function startMutation(instance) {
|
|
|
1477
1478
|
stages: instance.stages.map((s) => ({
|
|
1478
1479
|
...s,
|
|
1479
1480
|
fields: (s.fields ?? []).map((entry) => ({ ...entry })),
|
|
1480
|
-
|
|
1481
|
+
activities: s.activities.map((t) => ({
|
|
1481
1482
|
...t,
|
|
1482
1483
|
...t.fields !== void 0 ? { fields: t.fields.map((entry) => ({ ...entry })) } : {}
|
|
1483
1484
|
}))
|
|
@@ -1553,348 +1554,97 @@ function currentStageEntry(mutation) {
|
|
|
1553
1554
|
);
|
|
1554
1555
|
return entry;
|
|
1555
1556
|
}
|
|
1556
|
-
function
|
|
1557
|
-
return currentStageEntry(mutation).
|
|
1557
|
+
function currentActivities(mutation) {
|
|
1558
|
+
return currentStageEntry(mutation).activities;
|
|
1558
1559
|
}
|
|
1559
|
-
function
|
|
1560
|
-
const stage = findStage(ctx.definition, ctx.instance.currentStage),
|
|
1561
|
-
if (
|
|
1560
|
+
function findActivityInCurrentStage(ctx, activityName) {
|
|
1561
|
+
const stage = findStage(ctx.definition, ctx.instance.currentStage), activity = (stage.activities ?? []).find((t) => t.name === activityName);
|
|
1562
|
+
if (activity === void 0)
|
|
1562
1563
|
throw new Error(
|
|
1563
|
-
`
|
|
1564
|
+
`Activity "${activityName}" not found in current stage "${stage.name}" of ${ctx.definition.name}`
|
|
1564
1565
|
);
|
|
1565
|
-
return { stage,
|
|
1566
|
+
return { stage, activity };
|
|
1566
1567
|
}
|
|
1567
|
-
function
|
|
1568
|
-
const mutEntry =
|
|
1568
|
+
function requireMutationActivityEntry(mutation, activity) {
|
|
1569
|
+
const mutEntry = currentActivities(mutation).find((t) => t.name === activity);
|
|
1569
1570
|
if (mutEntry === void 0)
|
|
1570
|
-
throw new Error(`
|
|
1571
|
+
throw new Error(`Activity "${activity}" disappeared from mutation copy \u2014 invariant broken`);
|
|
1571
1572
|
return mutEntry;
|
|
1572
1573
|
}
|
|
1573
1574
|
function findCurrentStageEntry(instance) {
|
|
1574
1575
|
return findOpenStageEntry(instance);
|
|
1575
1576
|
}
|
|
1576
|
-
function
|
|
1577
|
-
return findCurrentStageEntry(instance)?.
|
|
1578
|
-
}
|
|
1579
|
-
const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
1580
|
-
function validateTag(tag) {
|
|
1581
|
-
if (!TAG_RE.test(tag))
|
|
1582
|
-
throw new Error(
|
|
1583
|
-
`tag: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
|
|
1584
|
-
);
|
|
1585
|
-
}
|
|
1586
|
-
function tagScopeFilter() {
|
|
1587
|
-
return "tag == $tag";
|
|
1588
|
-
}
|
|
1589
|
-
function definitionLookupGroq(explicit) {
|
|
1590
|
-
const scoped = `_type == "${WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}`;
|
|
1591
|
-
return explicit ? `*[${scoped} && version == $version][0]` : `*[${scoped}] | order(version desc)[0]`;
|
|
1592
|
-
}
|
|
1593
|
-
async function discoverItems(args) {
|
|
1594
|
-
const { client, groq, params, perspective } = args, fetchOptions = perspective !== void 0 ? { perspective } : void 0;
|
|
1595
|
-
return client.fetch(groq, paramsForLake(params), fetchOptions);
|
|
1577
|
+
function findCurrentActivities(instance) {
|
|
1578
|
+
return findCurrentStageEntry(instance)?.activities ?? [];
|
|
1596
1579
|
}
|
|
1597
|
-
function
|
|
1598
|
-
if (!subjectGdrUri || !subjectGdrUri.includes(":")) return defaultClient;
|
|
1580
|
+
async function deployOrRollback(args) {
|
|
1599
1581
|
try {
|
|
1600
|
-
|
|
1601
|
-
} catch {
|
|
1602
|
-
|
|
1582
|
+
await args.deploy();
|
|
1583
|
+
} catch (guardError) {
|
|
1584
|
+
if (!args.reversible)
|
|
1585
|
+
throw new WorkflowStateDivergedError({
|
|
1586
|
+
instanceId: args.instanceId,
|
|
1587
|
+
guardError,
|
|
1588
|
+
reason: "the move created child instances a parent-only rollback cannot undo"
|
|
1589
|
+
});
|
|
1590
|
+
try {
|
|
1591
|
+
let patch = args.client.patch(args.instanceId).set(args.restore);
|
|
1592
|
+
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);
|
|
1593
|
+
} catch (rollbackError) {
|
|
1594
|
+
throw new WorkflowStateDivergedError({
|
|
1595
|
+
instanceId: args.instanceId,
|
|
1596
|
+
guardError,
|
|
1597
|
+
rollbackError,
|
|
1598
|
+
reason: "rollback of the committed move failed"
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
throw guardError instanceof PartialGuardDeployError ? new WorkflowStateDivergedError({
|
|
1602
|
+
instanceId: args.instanceId,
|
|
1603
|
+
guardError,
|
|
1604
|
+
reason: "a multi-guard deploy partially applied; the rolled-back move left orphaned guard locks"
|
|
1605
|
+
}) : guardError;
|
|
1603
1606
|
}
|
|
1604
1607
|
}
|
|
1605
|
-
async function
|
|
1606
|
-
const
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1608
|
+
async function persistThenDeploy(ctx, mutation, deploy) {
|
|
1609
|
+
const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
|
|
1610
|
+
await deployOrRollback({
|
|
1611
|
+
client: ctx.client,
|
|
1612
|
+
instanceId: ctx.instance._id,
|
|
1613
|
+
committedRev: committed._rev,
|
|
1614
|
+
restore: instanceStateFields(ctx.instance),
|
|
1615
|
+
unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
|
|
1616
|
+
reversible: !spawned,
|
|
1617
|
+
deploy
|
|
1618
|
+
});
|
|
1612
1619
|
}
|
|
1613
|
-
function
|
|
1614
|
-
|
|
1615
|
-
|
|
1620
|
+
async function refreshStageGuards(ctx, mutation, stageName) {
|
|
1621
|
+
await deployStageGuards({
|
|
1622
|
+
client: ctx.client,
|
|
1623
|
+
clientForGdr: ctx.clientForGdr,
|
|
1624
|
+
instance: materializeInstance(ctx.instance, mutation),
|
|
1625
|
+
definition: ctx.definition,
|
|
1626
|
+
stageName,
|
|
1627
|
+
now: ctx.now
|
|
1628
|
+
});
|
|
1616
1629
|
}
|
|
1617
|
-
function
|
|
1618
|
-
|
|
1630
|
+
async function completeEffect(args) {
|
|
1631
|
+
const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1632
|
+
...options?.clock ? { clock: options.clock } : {},
|
|
1633
|
+
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
1634
|
+
});
|
|
1635
|
+
return commitCompleteEffect(
|
|
1636
|
+
ctx,
|
|
1637
|
+
effectKey,
|
|
1638
|
+
status,
|
|
1639
|
+
outputs,
|
|
1640
|
+
detail,
|
|
1641
|
+
error,
|
|
1642
|
+
durationMs,
|
|
1643
|
+
options?.actor
|
|
1644
|
+
);
|
|
1619
1645
|
}
|
|
1620
|
-
function
|
|
1621
|
-
const {
|
|
1622
|
-
return {
|
|
1623
|
-
_id: id,
|
|
1624
|
-
_type: WORKFLOW_INSTANCE_TYPE,
|
|
1625
|
-
_rev: "",
|
|
1626
|
-
_createdAt: now,
|
|
1627
|
-
_updatedAt: now,
|
|
1628
|
-
tag: args.tag,
|
|
1629
|
-
workflowResource: args.workflowResource,
|
|
1630
|
-
definition: args.definitionName,
|
|
1631
|
-
pinnedVersion: args.pinnedVersion,
|
|
1632
|
-
definitionSnapshot: JSON.stringify(args.definition),
|
|
1633
|
-
fields: args.fields,
|
|
1634
|
-
effectsContext: args.effectsContext,
|
|
1635
|
-
ancestors: args.ancestors,
|
|
1636
|
-
...perspective !== void 0 ? { perspective } : {},
|
|
1637
|
-
currentStage: args.initialStage,
|
|
1638
|
-
stages: [],
|
|
1639
|
-
pendingEffects: [],
|
|
1640
|
-
effectHistory: [],
|
|
1641
|
-
history: [
|
|
1642
|
-
{
|
|
1643
|
-
_key: randomKey(),
|
|
1644
|
-
_type: "stageEntered",
|
|
1645
|
-
at: now,
|
|
1646
|
-
stage: args.initialStage,
|
|
1647
|
-
...actor !== void 0 ? { actor } : {}
|
|
1648
|
-
}
|
|
1649
|
-
],
|
|
1650
|
-
startedAt: now,
|
|
1651
|
-
lastChangedAt: now
|
|
1652
|
-
};
|
|
1653
|
-
}
|
|
1654
|
-
const ENGINE_ACTOR_ID_PREFIX = "engine.";
|
|
1655
|
-
function engineSystemActor(name) {
|
|
1656
|
-
return { kind: "system", id: `${ENGINE_ACTOR_ID_PREFIX}${name}` };
|
|
1657
|
-
}
|
|
1658
|
-
function isEngineActor(actor) {
|
|
1659
|
-
return actor.kind === "system" && actor.id.startsWith(ENGINE_ACTOR_ID_PREFIX);
|
|
1660
|
-
}
|
|
1661
|
-
const MAX_SPAWN_DEPTH = 6, SUBWORKFLOWS_ALL_DONE = "count($subworkflows[status != 'done']) == 0";
|
|
1662
|
-
function gateActor(outcome) {
|
|
1663
|
-
return engineSystemActor(outcome === "failed" ? "failWhen" : "completeWhen");
|
|
1664
|
-
}
|
|
1665
|
-
function effectiveCompleteWhen(task) {
|
|
1666
|
-
return task.completeWhen ?? (task.subworkflows !== void 0 ? SUBWORKFLOWS_ALL_DONE : void 0);
|
|
1667
|
-
}
|
|
1668
|
-
async function evaluateResolutionGate(ctx, task, vars) {
|
|
1669
|
-
const scope = { taskName: task.name, vars };
|
|
1670
|
-
if (task.failWhen !== void 0 && await ctxEvaluateCondition(ctx, task.failWhen, scope))
|
|
1671
|
-
return "failed";
|
|
1672
|
-
const completeWhen = effectiveCompleteWhen(task);
|
|
1673
|
-
if (completeWhen !== void 0 && await ctxEvaluateCondition(ctx, completeWhen, scope))
|
|
1674
|
-
return "done";
|
|
1675
|
-
}
|
|
1676
|
-
async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
1677
|
-
if (ctx.instance.ancestors.length + 1 > MAX_SPAWN_DEPTH) {
|
|
1678
|
-
const chain = [...ctx.instance.ancestors.map((a) => a.id), selfGdr(ctx.instance)].join(" \u2192 ");
|
|
1679
|
-
throw new Error(
|
|
1680
|
-
`Subworkflow depth limit (${MAX_SPAWN_DEPTH}) exceeded on task "${task.name}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
|
|
1681
|
-
);
|
|
1682
|
-
}
|
|
1683
|
-
const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tag);
|
|
1684
|
-
if (definition === null) {
|
|
1685
|
-
const versionLabel = sub.definition.version === void 0 || sub.definition.version === "latest" ? "latest" : `v${sub.definition.version}`;
|
|
1686
|
-
throw new Error(
|
|
1687
|
-
`Subworkflow definition "${sub.definition.name}" ${versionLabel} not deployed (parent ${ctx.instance._id}, task ${task.name})`
|
|
1688
|
-
);
|
|
1689
|
-
}
|
|
1690
|
-
const parentScope = await ctxConditionParams(ctx, {
|
|
1691
|
-
taskName: task.name,
|
|
1692
|
-
...actor ? { actor } : {}
|
|
1693
|
-
}), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
|
|
1694
|
-
for (const row of rows) {
|
|
1695
|
-
const initialFields = await projectRowFields(
|
|
1696
|
-
ctx,
|
|
1697
|
-
sub,
|
|
1698
|
-
definition,
|
|
1699
|
-
parentScope,
|
|
1700
|
-
row,
|
|
1701
|
-
discoveryResource
|
|
1702
|
-
), { ref, body } = await prepareChildInstance({
|
|
1703
|
-
client: ctx.client,
|
|
1704
|
-
parent: ctx.instance,
|
|
1705
|
-
definition,
|
|
1706
|
-
initialFields,
|
|
1707
|
-
effectsContext,
|
|
1708
|
-
actor,
|
|
1709
|
-
now
|
|
1710
|
-
});
|
|
1711
|
-
refs.push(ref), mutation.pendingCreates.push(body);
|
|
1712
|
-
}
|
|
1713
|
-
return refs;
|
|
1714
|
-
}
|
|
1715
|
-
async function resolveSpawnRows(ctx, sub) {
|
|
1716
|
-
const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
|
|
1717
|
-
let value, discoveryResource;
|
|
1718
|
-
if (groq.includes("*")) {
|
|
1719
|
-
const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
|
|
1720
|
-
client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
|
|
1721
|
-
client,
|
|
1722
|
-
groq,
|
|
1723
|
-
params,
|
|
1724
|
-
// Draft-aware by default (drafts overlay published) when there's no
|
|
1725
|
-
// release, so a `forEach` discovers in-flight items the same way the
|
|
1726
|
-
// engine reads any other content.
|
|
1727
|
-
perspective: ctx.instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
|
|
1728
|
-
});
|
|
1729
|
-
} else {
|
|
1730
|
-
const tree = parse(groq, { params });
|
|
1731
|
-
value = await (await evaluate(tree, { dataset: [ctx.instance], params, root: ctx.instance })).get();
|
|
1732
|
-
}
|
|
1733
|
-
return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
|
|
1734
|
-
}
|
|
1735
|
-
async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
|
|
1736
|
-
const out = [];
|
|
1737
|
-
for (const [name, groq] of Object.entries(sub.with ?? {})) {
|
|
1738
|
-
const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
|
|
1739
|
-
if (declared === void 0)
|
|
1740
|
-
throw new Error(
|
|
1741
|
-
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
|
|
1742
|
-
);
|
|
1743
|
-
const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
|
|
1744
|
-
parentResource: ctx.instance.workflowResource,
|
|
1745
|
-
discoveryResource,
|
|
1746
|
-
entryName: name,
|
|
1747
|
-
childName: childDefinition.name
|
|
1748
|
-
});
|
|
1749
|
-
value != null && out.push({
|
|
1750
|
-
type: declared.type,
|
|
1751
|
-
name,
|
|
1752
|
-
value
|
|
1753
|
-
});
|
|
1754
|
-
}
|
|
1755
|
-
return out;
|
|
1756
|
-
}
|
|
1757
|
-
async function resolveContextHandoff(ctx, sub, parentScope) {
|
|
1758
|
-
const out = {};
|
|
1759
|
-
for (const [name, groq] of Object.entries(sub.context ?? {})) {
|
|
1760
|
-
const v2 = await runGroq(groq, parentScope, ctx.snapshot);
|
|
1761
|
-
v2 != null && (typeof v2 == "string" || typeof v2 == "number" || typeof v2 == "boolean" || typeof v2 == "object" && "id" in v2 && "type" in v2) && (out[name] = v2);
|
|
1762
|
-
}
|
|
1763
|
-
return out;
|
|
1764
|
-
}
|
|
1765
|
-
function canonicaliseSpawnValue(kind, value, cx) {
|
|
1766
|
-
return kind === "doc.ref" ? coerceSpawnGdr(value, cx) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, cx)).filter((v2) => v2 !== null) : value;
|
|
1767
|
-
}
|
|
1768
|
-
function assertBareIdRootsCorrectly(id, cx) {
|
|
1769
|
-
if (!(cx.discoveryResource === void 0 || sameResource(cx.discoveryResource, cx.parentResource)))
|
|
1770
|
-
throw new Error(
|
|
1771
|
-
`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.`
|
|
1772
|
-
);
|
|
1773
|
-
}
|
|
1774
|
-
function coerceSpawnGdr(raw, cx) {
|
|
1775
|
-
const toUri = (id) => isGdrUri(id) ? id : (assertBareIdRootsCorrectly(id, cx), gdrFromResource(cx.parentResource, id));
|
|
1776
|
-
if (raw == null) return null;
|
|
1777
|
-
if (typeof raw == "object" && "id" in raw && "type" in raw) {
|
|
1778
|
-
const r = raw;
|
|
1779
|
-
if (typeof r.id == "string" && typeof r.type == "string")
|
|
1780
|
-
return { id: toUri(r.id), type: r.type };
|
|
1781
|
-
}
|
|
1782
|
-
if (typeof raw == "object" && "_ref" in raw) {
|
|
1783
|
-
const r = raw;
|
|
1784
|
-
if (typeof r._ref == "string") return { id: toUri(r._ref), type: "document" };
|
|
1785
|
-
}
|
|
1786
|
-
return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
|
|
1787
|
-
}
|
|
1788
|
-
async function resolveDefinitionRef(client, ref, tag) {
|
|
1789
|
-
const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
|
|
1790
|
-
return wantsExplicit && (params.version = ref.version), await client.fetch(
|
|
1791
|
-
definitionLookupGroq(wantsExplicit),
|
|
1792
|
-
params
|
|
1793
|
-
) ?? null;
|
|
1794
|
-
}
|
|
1795
|
-
async function prepareChildInstance(args) {
|
|
1796
|
-
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 = [
|
|
1797
|
-
...parent.ancestors,
|
|
1798
|
-
{
|
|
1799
|
-
id: selfGdr(parent),
|
|
1800
|
-
type: WORKFLOW_INSTANCE_TYPE
|
|
1801
|
-
}
|
|
1802
|
-
], inheritedPerspective = parent.perspective, childFields = await resolveDeclaredFields({
|
|
1803
|
-
entryDefs: definition.fields,
|
|
1804
|
-
initialFields,
|
|
1805
|
-
ctx: {
|
|
1806
|
-
client,
|
|
1807
|
-
now,
|
|
1808
|
-
selfId: childDocId,
|
|
1809
|
-
tag: childTag,
|
|
1810
|
-
workflowResource,
|
|
1811
|
-
definitionName: definition.name,
|
|
1812
|
-
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
|
|
1813
|
-
},
|
|
1814
|
-
randomKey
|
|
1815
|
-
}), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
|
|
1816
|
-
([key, value]) => {
|
|
1817
|
-
if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
|
|
1818
|
-
if (!isGdrUri(value.id))
|
|
1819
|
-
throw new Error(
|
|
1820
|
-
`subworkflows.context["${key}"]: GDR id must be a "<scheme>:..." URI, got ${JSON.stringify(value.id)}.`
|
|
1821
|
-
);
|
|
1822
|
-
return effectsContextEntry(key, { id: value.id, type: value.type });
|
|
1823
|
-
}
|
|
1824
|
-
return effectsContextEntry(key, value);
|
|
1825
|
-
}
|
|
1826
|
-
), base = buildInstanceBase({
|
|
1827
|
-
id: childDocId,
|
|
1828
|
-
now,
|
|
1829
|
-
tag: childTag,
|
|
1830
|
-
workflowResource,
|
|
1831
|
-
definitionName: definition.name,
|
|
1832
|
-
pinnedVersion: definition.version,
|
|
1833
|
-
definition,
|
|
1834
|
-
fields: childFields,
|
|
1835
|
-
effectsContext: effectsContextEntries,
|
|
1836
|
-
ancestors,
|
|
1837
|
-
perspective: childPerspective,
|
|
1838
|
-
initialStage: definition.initialStage,
|
|
1839
|
-
actor
|
|
1840
|
-
});
|
|
1841
|
-
return { ref: childRef, body: base };
|
|
1842
|
-
}
|
|
1843
|
-
async function subworkflowRows(ctx, refs) {
|
|
1844
|
-
if (refs.length === 0) return [];
|
|
1845
|
-
const ids = refs.map((r) => gdrToBareId(r.id));
|
|
1846
|
-
return (await ctx.client.fetch("*[_id in $ids]{_id, currentStage, completedAt}", { ids })).map((c) => ({
|
|
1847
|
-
_id: c._id,
|
|
1848
|
-
stage: c.currentStage,
|
|
1849
|
-
status: c.completedAt !== void 0 && c.completedAt !== null ? "done" : "active"
|
|
1850
|
-
}));
|
|
1851
|
-
}
|
|
1852
|
-
async function resolveBindings(args) {
|
|
1853
|
-
const resolved = {};
|
|
1854
|
-
for (const [key, groq] of Object.entries(args.bindings ?? {}))
|
|
1855
|
-
resolved[key] = await runGroq(groq, args.params, args.snapshot);
|
|
1856
|
-
return { ...resolved, ...args.staticInput };
|
|
1857
|
-
}
|
|
1858
|
-
async function persistThenDeploy(ctx, mutation, deploy) {
|
|
1859
|
-
const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
|
|
1860
|
-
await deployOrRollback({
|
|
1861
|
-
client: ctx.client,
|
|
1862
|
-
instanceId: ctx.instance._id,
|
|
1863
|
-
committedRev: committed._rev,
|
|
1864
|
-
restore: instanceStateFields(ctx.instance),
|
|
1865
|
-
unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
|
|
1866
|
-
reversible: !spawned,
|
|
1867
|
-
deploy
|
|
1868
|
-
});
|
|
1869
|
-
}
|
|
1870
|
-
async function refreshStageGuards(ctx, mutation, stageName) {
|
|
1871
|
-
await deployStageGuards({
|
|
1872
|
-
client: ctx.client,
|
|
1873
|
-
clientForGdr: ctx.clientForGdr,
|
|
1874
|
-
instance: materializeInstance(ctx.instance, mutation),
|
|
1875
|
-
definition: ctx.definition,
|
|
1876
|
-
stageName,
|
|
1877
|
-
now: ctx.now
|
|
1878
|
-
});
|
|
1879
|
-
}
|
|
1880
|
-
async function completeEffect(args) {
|
|
1881
|
-
const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1882
|
-
...options?.clock ? { clock: options.clock } : {},
|
|
1883
|
-
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
1884
|
-
});
|
|
1885
|
-
return commitCompleteEffect(
|
|
1886
|
-
ctx,
|
|
1887
|
-
effectKey,
|
|
1888
|
-
status,
|
|
1889
|
-
outputs,
|
|
1890
|
-
detail,
|
|
1891
|
-
error,
|
|
1892
|
-
durationMs,
|
|
1893
|
-
options?.actor
|
|
1894
|
-
);
|
|
1895
|
-
}
|
|
1896
|
-
function buildEffectHistoryEntry(pending, outcome) {
|
|
1897
|
-
const { status, ranAt, actor, detail, error, durationMs, outputs } = outcome, resolvedActor = actor ?? pending.actor;
|
|
1646
|
+
function buildEffectHistoryEntry(pending, outcome) {
|
|
1647
|
+
const { status, ranAt, actor, detail, error, durationMs, outputs } = outcome, resolvedActor = actor ?? pending.actor;
|
|
1898
1648
|
return {
|
|
1899
1649
|
_key: pending._key,
|
|
1900
1650
|
name: pending.name,
|
|
@@ -1967,7 +1717,7 @@ function buildQueuedEffect(effect, origin, params, actor, now) {
|
|
|
1967
1717
|
async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
|
|
1968
1718
|
if (!effects || effects.length === 0) return;
|
|
1969
1719
|
const now = ctx.now, liveCtx = { ...ctx, instance: materializeInstance(ctx.instance, mutation) }, params = await ctxConditionParams(liveCtx, {
|
|
1970
|
-
...opts?.
|
|
1720
|
+
...opts?.activityName !== void 0 ? { activityName: opts.activityName } : {},
|
|
1971
1721
|
...actor !== void 0 ? { actor } : {},
|
|
1972
1722
|
// Caller-supplied action params, readable in bindings as `$params.<name>`.
|
|
1973
1723
|
vars: { params: opts?.callerParams ?? {} }
|
|
@@ -1982,18 +1732,74 @@ async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
|
|
|
1982
1732
|
mutation.pendingEffects.push(pending), mutation.history.push(history);
|
|
1983
1733
|
}
|
|
1984
1734
|
}
|
|
1985
|
-
function
|
|
1735
|
+
function fieldQueryDiscardedEntry(args) {
|
|
1736
|
+
return {
|
|
1737
|
+
_key: randomKey(),
|
|
1738
|
+
_type: "fieldQueryDiscarded",
|
|
1739
|
+
at: args.at,
|
|
1740
|
+
scope: args.scope,
|
|
1741
|
+
field: args.field,
|
|
1742
|
+
detail: args.detail
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
function recordFieldDiscards(target, scope, at) {
|
|
1746
|
+
return (discard) => target.push(fieldQueryDiscardedEntry({ scope, field: discard.field, detail: discard.detail, at }));
|
|
1747
|
+
}
|
|
1748
|
+
function applyActivityStatusChange(args) {
|
|
1749
|
+
const { entry, history, stage, to, at, actor } = args, from = entry.status;
|
|
1750
|
+
entry.status = to, isTerminalActivityStatus(to) && (entry.completedAt = at, actor !== void 0 && (entry.completedBy = actor.id)), history.push({
|
|
1751
|
+
_key: randomKey(),
|
|
1752
|
+
_type: "activityStatusChanged",
|
|
1753
|
+
at,
|
|
1754
|
+
stage,
|
|
1755
|
+
activity: entry.name,
|
|
1756
|
+
from,
|
|
1757
|
+
to,
|
|
1758
|
+
...actor !== void 0 ? { actor } : {}
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
function stageTransitionHistory(args) {
|
|
1762
|
+
const { fromStage, toStage, at, transition, actor, via, reason } = args, shared = {
|
|
1763
|
+
at,
|
|
1764
|
+
...transition !== void 0 ? { transition } : {},
|
|
1765
|
+
...actor !== void 0 ? { actor } : {},
|
|
1766
|
+
...via !== void 0 ? { via } : {},
|
|
1767
|
+
...reason !== void 0 ? { reason } : {}
|
|
1768
|
+
};
|
|
1769
|
+
return [
|
|
1770
|
+
{
|
|
1771
|
+
_key: randomKey(),
|
|
1772
|
+
_type: "stageExited",
|
|
1773
|
+
stage: fromStage,
|
|
1774
|
+
toStage,
|
|
1775
|
+
...shared
|
|
1776
|
+
},
|
|
1777
|
+
{
|
|
1778
|
+
_key: randomKey(),
|
|
1779
|
+
_type: "transitionFired",
|
|
1780
|
+
fromStage,
|
|
1781
|
+
toStage,
|
|
1782
|
+
...shared
|
|
1783
|
+
},
|
|
1784
|
+
{
|
|
1785
|
+
_key: randomKey(),
|
|
1786
|
+
_type: "stageEntered",
|
|
1787
|
+
stage: toStage,
|
|
1788
|
+
fromStage,
|
|
1789
|
+
...shared
|
|
1790
|
+
}
|
|
1791
|
+
];
|
|
1792
|
+
}
|
|
1793
|
+
function resolveStaticValueExpr(src, ctx) {
|
|
1986
1794
|
switch (src.type) {
|
|
1987
1795
|
case "literal":
|
|
1988
|
-
return
|
|
1796
|
+
return src.value;
|
|
1989
1797
|
case "param":
|
|
1990
|
-
return
|
|
1798
|
+
return ctx.params?.[src.param];
|
|
1991
1799
|
case "actor":
|
|
1992
|
-
return
|
|
1800
|
+
return ctx.actor;
|
|
1993
1801
|
case "now":
|
|
1994
|
-
return
|
|
1995
|
-
default:
|
|
1996
|
-
return { handled: !1 };
|
|
1802
|
+
return ctx.now;
|
|
1997
1803
|
}
|
|
1998
1804
|
}
|
|
1999
1805
|
const FIELD_OP_TYPES = [
|
|
@@ -2006,7 +1812,7 @@ const FIELD_OP_TYPES = [
|
|
|
2006
1812
|
function isFieldOp(summary) {
|
|
2007
1813
|
return FIELD_OP_TYPES.includes(summary.opType);
|
|
2008
1814
|
}
|
|
2009
|
-
function validateActionParams(action,
|
|
1815
|
+
function validateActionParams(action, activityName, callerParams) {
|
|
2010
1816
|
const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
|
|
2011
1817
|
for (const decl of declared) {
|
|
2012
1818
|
const value = params[decl.name], present = decl.name in params && value !== void 0 && value !== null;
|
|
@@ -2020,7 +1826,7 @@ function validateActionParams(action, taskName, callerParams) {
|
|
|
2020
1826
|
}));
|
|
2021
1827
|
}
|
|
2022
1828
|
if (issues.length > 0)
|
|
2023
|
-
throw new ActionParamsInvalidError({ action: action.name,
|
|
1829
|
+
throw new ActionParamsInvalidError({ action: action.name, activity: activityName, issues });
|
|
2024
1830
|
return params;
|
|
2025
1831
|
}
|
|
2026
1832
|
function hasStringField(value, field) {
|
|
@@ -2053,7 +1859,15 @@ function runOps(args) {
|
|
|
2053
1859
|
if (ops === void 0 || ops.length === 0) return [];
|
|
2054
1860
|
const summaries = [];
|
|
2055
1861
|
for (const op of ops) {
|
|
2056
|
-
const summary = applyOp(op, {
|
|
1862
|
+
const summary = applyOp(op, {
|
|
1863
|
+
mutation,
|
|
1864
|
+
stage,
|
|
1865
|
+
activityName: origin.activity,
|
|
1866
|
+
params,
|
|
1867
|
+
actor,
|
|
1868
|
+
self,
|
|
1869
|
+
now
|
|
1870
|
+
});
|
|
2057
1871
|
summaries.push(summary), mutation.history.push(opAppliedEntry({ origin, summary, stage, actor, now }));
|
|
2058
1872
|
}
|
|
2059
1873
|
return summaries;
|
|
@@ -2065,7 +1879,7 @@ function opAppliedEntry(args) {
|
|
|
2065
1879
|
_type: "opApplied",
|
|
2066
1880
|
at: now,
|
|
2067
1881
|
stage,
|
|
2068
|
-
...origin.
|
|
1882
|
+
...origin.activity !== void 0 ? { activity: origin.activity } : {},
|
|
2069
1883
|
...origin.action !== void 0 ? { action: origin.action } : {},
|
|
2070
1884
|
...origin.transition !== void 0 ? { transition: origin.transition } : {},
|
|
2071
1885
|
...origin.edit === !0 ? { edit: !0 } : {},
|
|
@@ -2092,13 +1906,15 @@ function applyOp(op, ctx) {
|
|
|
2092
1906
|
}
|
|
2093
1907
|
}
|
|
2094
1908
|
function applyFieldSet(op, ctx) {
|
|
2095
|
-
const value =
|
|
2096
|
-
return validateFieldValue({ entryType: entry._type, entryName: entry.name, value }), setEntryValue(entry, value), { opType: op.type, target: op.target, resolved: { value } };
|
|
1909
|
+
const value = resolveOpValue(op.value, ctx), entry = locateEntry(ctx, op.target);
|
|
1910
|
+
return validateFieldValue({ entryType: entry._type, entryName: entry.name, value, ...entryShape(entry) }), setEntryValue(entry, value), { opType: op.type, target: op.target, resolved: { value } };
|
|
1911
|
+
}
|
|
1912
|
+
function entryShape(entry) {
|
|
1913
|
+
return entry._type === "object" ? { fields: entry.fields } : entry._type === "array" ? { of: entry.of } : {};
|
|
2097
1914
|
}
|
|
2098
1915
|
const EMPTY_BY_KIND = {
|
|
2099
1916
|
"doc.refs": [],
|
|
2100
|
-
|
|
2101
|
-
notes: [],
|
|
1917
|
+
array: [],
|
|
2102
1918
|
assignees: []
|
|
2103
1919
|
};
|
|
2104
1920
|
function applyFieldUnset(op, ctx) {
|
|
@@ -2106,8 +1922,13 @@ function applyFieldUnset(op, ctx) {
|
|
|
2106
1922
|
return setEntryValue(entry, EMPTY_BY_KIND[entry._type] ?? null), { opType: op.type, target: op.target };
|
|
2107
1923
|
}
|
|
2108
1924
|
function applyFieldAppend(op, ctx) {
|
|
2109
|
-
const item =
|
|
2110
|
-
validateFieldAppendItem({
|
|
1925
|
+
const item = resolveOpValue(op.value, ctx), entry = locateEntry(ctx, op.target);
|
|
1926
|
+
validateFieldAppendItem({
|
|
1927
|
+
entryType: entry._type,
|
|
1928
|
+
entryName: entry.name,
|
|
1929
|
+
item,
|
|
1930
|
+
of: entry._type === "array" ? entry.of : void 0
|
|
1931
|
+
});
|
|
2111
1932
|
const current = Array.isArray(entry.value) ? entry.value : [];
|
|
2112
1933
|
return setEntryValue(entry, [...current, withRowKey(item)]), { opType: op.type, target: op.target, resolved: { item } };
|
|
2113
1934
|
}
|
|
@@ -2115,7 +1936,7 @@ function withRowKey(item) {
|
|
|
2115
1936
|
return typeof item == "object" && item !== null && !Array.isArray(item) && !("_key" in item) ? { _key: randomKey(), ...item } : item;
|
|
2116
1937
|
}
|
|
2117
1938
|
function applyFieldUpdateWhere(op, ctx) {
|
|
2118
|
-
const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op), merge =
|
|
1939
|
+
const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op), merge = resolveOpValue(op.value, ctx);
|
|
2119
1940
|
if (merge === null || typeof merge != "object" || Array.isArray(merge))
|
|
2120
1941
|
throw new Error(
|
|
2121
1942
|
`field.updateWhere value must resolve to an object of fields to merge (target "${op.target.field}")`
|
|
@@ -2141,145 +1962,385 @@ function requireArrayValue(entry, op) {
|
|
|
2141
1962
|
);
|
|
2142
1963
|
return entry.value;
|
|
2143
1964
|
}
|
|
2144
|
-
function applyStatusSet(op, ctx) {
|
|
2145
|
-
const entry = findOpenStageEntry(ctx.mutation)?.
|
|
2146
|
-
if (entry === void 0)
|
|
2147
|
-
throw new Error(
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
1965
|
+
function applyStatusSet(op, ctx) {
|
|
1966
|
+
const entry = findOpenStageEntry(ctx.mutation)?.activities.find((t) => t.name === op.activity);
|
|
1967
|
+
if (entry === void 0)
|
|
1968
|
+
throw new Error(
|
|
1969
|
+
`status.set targets activity "${op.activity}" which has no entry in the open stage`
|
|
1970
|
+
);
|
|
1971
|
+
return applyActivityStatusChange({
|
|
1972
|
+
entry,
|
|
1973
|
+
history: ctx.mutation.history,
|
|
1974
|
+
stage: ctx.stage,
|
|
1975
|
+
to: op.status,
|
|
1976
|
+
at: ctx.now,
|
|
1977
|
+
...ctx.actor !== void 0 ? { actor: ctx.actor } : {}
|
|
1978
|
+
}), { opType: op.type, resolved: { activity: op.activity, status: op.status } };
|
|
1979
|
+
}
|
|
1980
|
+
function setEntryValue(entry, value) {
|
|
1981
|
+
entry.value = value;
|
|
1982
|
+
}
|
|
1983
|
+
function locateEntry(ctx, target) {
|
|
1984
|
+
const entry = fieldHost(ctx, target.scope)?.find((s) => s.name === target.field);
|
|
1985
|
+
if (entry === void 0)
|
|
1986
|
+
throw new Error(
|
|
1987
|
+
`Op target ${target.scope}:"${target.field}" has no resolved field entry on this instance \u2014 the deployed definition and the instance state have diverged`
|
|
1988
|
+
);
|
|
1989
|
+
return entry;
|
|
1990
|
+
}
|
|
1991
|
+
function fieldHost(ctx, scope) {
|
|
1992
|
+
if (scope === "workflow") return ctx.mutation.fields;
|
|
1993
|
+
const stageEntry = findOpenStageEntry(ctx.mutation);
|
|
1994
|
+
if (scope === "stage") return stageEntry?.fields;
|
|
1995
|
+
const activity = stageEntry?.activities.find((t) => t.name === ctx.activityName);
|
|
1996
|
+
return activity !== void 0 && activity.fields === void 0 && (activity.fields = []), activity?.fields;
|
|
1997
|
+
}
|
|
1998
|
+
function resolveOpValue(src, ctx) {
|
|
1999
|
+
switch (src.type) {
|
|
2000
|
+
case "literal":
|
|
2001
|
+
case "param":
|
|
2002
|
+
case "actor":
|
|
2003
|
+
case "now":
|
|
2004
|
+
return resolveStaticValueExpr(src, ctx);
|
|
2005
|
+
case "self":
|
|
2006
|
+
return ctx.self;
|
|
2007
|
+
case "stage":
|
|
2008
|
+
return ctx.stage;
|
|
2009
|
+
case "fieldRead": {
|
|
2010
|
+
const value = readEntryFromMutation(ctx, src);
|
|
2011
|
+
return src.path !== void 0 ? getPath(value, src.path) : value;
|
|
2012
|
+
}
|
|
2013
|
+
case "object": {
|
|
2014
|
+
const out = {};
|
|
2015
|
+
for (const [field, fieldSrc] of Object.entries(src.fields))
|
|
2016
|
+
out[field] = resolveOpValue(fieldSrc, ctx);
|
|
2017
|
+
return out;
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
function entryValue(entries, name) {
|
|
2022
|
+
return entries?.find((s) => s.name === name)?.value;
|
|
2023
|
+
}
|
|
2024
|
+
function readEntryFromMutation(ctx, src) {
|
|
2025
|
+
const scopes = src.scope !== void 0 ? [src.scope] : ["stage", "workflow"], stageEntry = findOpenStageEntry(ctx.mutation);
|
|
2026
|
+
for (const scope of scopes) {
|
|
2027
|
+
const host = scope === "workflow" ? ctx.mutation.fields : stageEntry?.fields, value = entryValue(host, src.field);
|
|
2028
|
+
if (value !== void 0) return value;
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
function evalOpPredicate(p, row, ctx) {
|
|
2032
|
+
switch (p.type) {
|
|
2033
|
+
case "all":
|
|
2034
|
+
return p.of.every((sub) => evalOpPredicate(sub, row, ctx));
|
|
2035
|
+
case "any":
|
|
2036
|
+
return p.of.some((sub) => evalOpPredicate(sub, row, ctx));
|
|
2037
|
+
case "field":
|
|
2038
|
+
return row[p.field] === resolveOpValue(p.equals, ctx);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
2042
|
+
function validateTag(tag) {
|
|
2043
|
+
if (!TAG_RE.test(tag))
|
|
2044
|
+
throw new Error(
|
|
2045
|
+
`tag: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
|
|
2046
|
+
);
|
|
2047
|
+
}
|
|
2048
|
+
function tagScopeFilter() {
|
|
2049
|
+
return "tag == $tag";
|
|
2050
|
+
}
|
|
2051
|
+
function definitionLookupGroq(explicit) {
|
|
2052
|
+
const scoped = `_type == "${WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}`;
|
|
2053
|
+
return explicit ? `*[${scoped} && version == $version][0]` : `*[${scoped}] | order(version desc)[0]`;
|
|
2054
|
+
}
|
|
2055
|
+
async function discoverItems(args) {
|
|
2056
|
+
const { client, groq, params, perspective } = args, fetchOptions = perspective !== void 0 ? { perspective } : void 0;
|
|
2057
|
+
return client.fetch(groq, paramsForLake(params), fetchOptions);
|
|
2058
|
+
}
|
|
2059
|
+
function clientForDiscovery(defaultClient, clientForGdr, subjectGdrUri) {
|
|
2060
|
+
if (!subjectGdrUri || !subjectGdrUri.includes(":")) return defaultClient;
|
|
2061
|
+
try {
|
|
2062
|
+
return clientForGdr(parseGdr(subjectGdrUri));
|
|
2063
|
+
} catch {
|
|
2064
|
+
return defaultClient;
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
const ENGINE_ACTOR_ID_PREFIX = "engine.";
|
|
2068
|
+
function engineSystemActor(name) {
|
|
2069
|
+
return { kind: "system", id: `${ENGINE_ACTOR_ID_PREFIX}${name}` };
|
|
2070
|
+
}
|
|
2071
|
+
function isEngineActor(actor) {
|
|
2072
|
+
return actor.kind === "system" && actor.id.startsWith(ENGINE_ACTOR_ID_PREFIX);
|
|
2073
|
+
}
|
|
2074
|
+
const MAX_SPAWN_DEPTH = 6, SUBWORKFLOWS_ALL_DONE = "count($subworkflows[status != 'done']) == 0";
|
|
2075
|
+
function gateActor(outcome) {
|
|
2076
|
+
return engineSystemActor(outcome === "failed" ? "failWhen" : "completeWhen");
|
|
2077
|
+
}
|
|
2078
|
+
function effectiveCompleteWhen(activity) {
|
|
2079
|
+
return activity.completeWhen ?? (activity.subworkflows !== void 0 ? SUBWORKFLOWS_ALL_DONE : void 0);
|
|
2080
|
+
}
|
|
2081
|
+
async function evaluateResolutionGate(ctx, activity, vars) {
|
|
2082
|
+
const scope = { activityName: activity.name, vars };
|
|
2083
|
+
if (activity.failWhen !== void 0 && await ctxEvaluateCondition(ctx, activity.failWhen, scope))
|
|
2084
|
+
return "failed";
|
|
2085
|
+
const completeWhen = effectiveCompleteWhen(activity);
|
|
2086
|
+
if (completeWhen !== void 0 && await ctxEvaluateCondition(ctx, completeWhen, scope))
|
|
2087
|
+
return "done";
|
|
2088
|
+
}
|
|
2089
|
+
async function spawnSubworkflows(ctx, mutation, activity, sub, actor) {
|
|
2090
|
+
if (ctx.instance.ancestors.length + 1 > MAX_SPAWN_DEPTH) {
|
|
2091
|
+
const chain = [...ctx.instance.ancestors.map((a) => a.id), selfGdr(ctx.instance)].join(" \u2192 ");
|
|
2092
|
+
throw new Error(
|
|
2093
|
+
`Subworkflow depth limit (${MAX_SPAWN_DEPTH}) exceeded on activity "${activity.name}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
|
|
2094
|
+
);
|
|
2095
|
+
}
|
|
2096
|
+
const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tag);
|
|
2097
|
+
if (definition === null) {
|
|
2098
|
+
const versionLabel = sub.definition.version === void 0 || sub.definition.version === "latest" ? "latest" : `v${sub.definition.version}`;
|
|
2099
|
+
throw new Error(
|
|
2100
|
+
`Subworkflow definition "${sub.definition.name}" ${versionLabel} not deployed (parent ${ctx.instance._id}, activity ${activity.name})`
|
|
2101
|
+
);
|
|
2102
|
+
}
|
|
2103
|
+
const parentScope = await ctxConditionParams(ctx, {
|
|
2104
|
+
activityName: activity.name,
|
|
2105
|
+
...actor ? { actor } : {}
|
|
2106
|
+
}), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
|
|
2107
|
+
for (const row of rows) {
|
|
2108
|
+
const initialFields = await projectRowFields(
|
|
2109
|
+
ctx,
|
|
2110
|
+
sub,
|
|
2111
|
+
definition,
|
|
2112
|
+
parentScope,
|
|
2113
|
+
row,
|
|
2114
|
+
discoveryResource
|
|
2115
|
+
), { ref, body } = await prepareChildInstance({
|
|
2116
|
+
client: ctx.client,
|
|
2117
|
+
parent: ctx.instance,
|
|
2118
|
+
definition,
|
|
2119
|
+
initialFields,
|
|
2120
|
+
effectsContext,
|
|
2121
|
+
actor,
|
|
2122
|
+
now
|
|
2123
|
+
});
|
|
2124
|
+
refs.push(ref), mutation.pendingCreates.push(body);
|
|
2125
|
+
}
|
|
2126
|
+
return refs;
|
|
2127
|
+
}
|
|
2128
|
+
async function resolveSpawnRows(ctx, sub) {
|
|
2129
|
+
const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
|
|
2130
|
+
let value, discoveryResource;
|
|
2131
|
+
if (groq.includes("*")) {
|
|
2132
|
+
const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
|
|
2133
|
+
client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
|
|
2134
|
+
client,
|
|
2135
|
+
groq,
|
|
2136
|
+
params,
|
|
2137
|
+
// Draft-aware by default (drafts overlay published) when there's no
|
|
2138
|
+
// release, so a `forEach` discovers in-flight items the same way the
|
|
2139
|
+
// engine reads any other content.
|
|
2140
|
+
perspective: ctx.instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
|
|
2141
|
+
});
|
|
2142
|
+
} else {
|
|
2143
|
+
const tree = parse(groq, { params });
|
|
2144
|
+
value = await (await evaluate(tree, { dataset: [ctx.instance], params, root: ctx.instance })).get();
|
|
2145
|
+
}
|
|
2146
|
+
return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
|
|
2147
|
+
}
|
|
2148
|
+
async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
|
|
2149
|
+
const out = [];
|
|
2150
|
+
for (const [name, groq] of Object.entries(sub.with ?? {})) {
|
|
2151
|
+
const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
|
|
2152
|
+
if (declared === void 0)
|
|
2153
|
+
throw new Error(
|
|
2154
|
+
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
|
|
2155
|
+
);
|
|
2156
|
+
const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
|
|
2157
|
+
parentResource: ctx.instance.workflowResource,
|
|
2158
|
+
discoveryResource,
|
|
2159
|
+
entryName: name,
|
|
2160
|
+
childName: childDefinition.name
|
|
2161
|
+
});
|
|
2162
|
+
value != null && out.push({
|
|
2163
|
+
type: declared.type,
|
|
2164
|
+
name,
|
|
2165
|
+
value
|
|
2166
|
+
});
|
|
2167
|
+
}
|
|
2168
|
+
return out;
|
|
2169
|
+
}
|
|
2170
|
+
async function resolveContextHandoff(ctx, sub, parentScope) {
|
|
2171
|
+
const out = {};
|
|
2172
|
+
for (const [name, groq] of Object.entries(sub.context ?? {})) {
|
|
2173
|
+
const v2 = await runGroq(groq, parentScope, ctx.snapshot);
|
|
2174
|
+
v2 != null && (typeof v2 == "string" || typeof v2 == "number" || typeof v2 == "boolean" || typeof v2 == "object" && "id" in v2 && "type" in v2) && (out[name] = v2);
|
|
2175
|
+
}
|
|
2176
|
+
return out;
|
|
2156
2177
|
}
|
|
2157
|
-
function
|
|
2158
|
-
|
|
2178
|
+
function canonicaliseSpawnValue(kind, value, cx) {
|
|
2179
|
+
return kind === "doc.ref" ? coerceSpawnGdr(value, cx) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, cx)).filter((v2) => v2 !== null) : value;
|
|
2159
2180
|
}
|
|
2160
|
-
function
|
|
2161
|
-
|
|
2162
|
-
if (entry === void 0)
|
|
2181
|
+
function assertBareIdRootsCorrectly(id, cx) {
|
|
2182
|
+
if (!(cx.discoveryResource === void 0 || sameResource(cx.discoveryResource, cx.parentResource)))
|
|
2163
2183
|
throw new Error(
|
|
2164
|
-
`
|
|
2184
|
+
`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.`
|
|
2165
2185
|
);
|
|
2166
|
-
return entry;
|
|
2167
|
-
}
|
|
2168
|
-
function fieldHost(ctx, scope) {
|
|
2169
|
-
if (scope === "workflow") return ctx.mutation.fields;
|
|
2170
|
-
const stageEntry = findOpenStageEntry(ctx.mutation);
|
|
2171
|
-
if (scope === "stage") return stageEntry?.fields;
|
|
2172
|
-
const task = stageEntry?.tasks.find((t) => t.name === ctx.taskName);
|
|
2173
|
-
return task !== void 0 && task.fields === void 0 && (task.fields = []), task?.fields;
|
|
2174
2186
|
}
|
|
2175
|
-
function
|
|
2176
|
-
const
|
|
2177
|
-
if (
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
}
|
|
2187
|
-
case "object": {
|
|
2188
|
-
const out = {};
|
|
2189
|
-
for (const [field, fieldSrc] of Object.entries(src.fields))
|
|
2190
|
-
out[field] = resolveOpSource(fieldSrc, ctx);
|
|
2191
|
-
return out;
|
|
2192
|
-
}
|
|
2193
|
-
default:
|
|
2194
|
-
throw new Error(`Source type "${src.type}" is a field-entry origin, not a value`);
|
|
2187
|
+
function coerceSpawnGdr(raw, cx) {
|
|
2188
|
+
const toUri = (id) => isGdrUri(id) ? id : (assertBareIdRootsCorrectly(id, cx), gdrFromResource(cx.parentResource, id));
|
|
2189
|
+
if (raw == null) return null;
|
|
2190
|
+
if (typeof raw == "object" && "id" in raw && "type" in raw) {
|
|
2191
|
+
const r = raw;
|
|
2192
|
+
if (typeof r.id == "string" && typeof r.type == "string")
|
|
2193
|
+
return { id: toUri(r.id), type: r.type };
|
|
2194
|
+
}
|
|
2195
|
+
if (typeof raw == "object" && "_ref" in raw) {
|
|
2196
|
+
const r = raw;
|
|
2197
|
+
if (typeof r._ref == "string") return { id: toUri(r._ref), type: "document" };
|
|
2195
2198
|
}
|
|
2199
|
+
return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
|
|
2196
2200
|
}
|
|
2197
|
-
function
|
|
2198
|
-
|
|
2201
|
+
async function resolveDefinitionRef(client, ref, tag) {
|
|
2202
|
+
const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
|
|
2203
|
+
return wantsExplicit && (params.version = ref.version), await client.fetch(
|
|
2204
|
+
definitionLookupGroq(wantsExplicit),
|
|
2205
|
+
params
|
|
2206
|
+
) ?? null;
|
|
2199
2207
|
}
|
|
2200
|
-
function
|
|
2201
|
-
const
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2208
|
+
async function prepareChildInstance(args) {
|
|
2209
|
+
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 = [
|
|
2210
|
+
...parent.ancestors,
|
|
2211
|
+
{
|
|
2212
|
+
id: selfGdr(parent),
|
|
2213
|
+
type: WORKFLOW_INSTANCE_TYPE
|
|
2214
|
+
}
|
|
2215
|
+
], inheritedPerspective = parent.perspective, fieldDiscards = [], childFields = await resolveDeclaredFields({
|
|
2216
|
+
entryDefs: definition.fields,
|
|
2217
|
+
initialFields,
|
|
2218
|
+
ctx: {
|
|
2219
|
+
client,
|
|
2220
|
+
now,
|
|
2221
|
+
selfId: childDocId,
|
|
2222
|
+
tag: childTag,
|
|
2223
|
+
workflowResource,
|
|
2224
|
+
definitionName: definition.name,
|
|
2225
|
+
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {},
|
|
2226
|
+
recordDiscard: recordFieldDiscards(fieldDiscards, "workflow", now)
|
|
2227
|
+
},
|
|
2228
|
+
randomKey
|
|
2229
|
+
}), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
|
|
2230
|
+
([key, value]) => {
|
|
2231
|
+
if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
|
|
2232
|
+
if (!isGdrUri(value.id))
|
|
2233
|
+
throw new Error(
|
|
2234
|
+
`subworkflows.context["${key}"]: GDR id must be a "<scheme>:..." URI, got ${JSON.stringify(value.id)}.`
|
|
2235
|
+
);
|
|
2236
|
+
return effectsContextEntry(key, { id: value.id, type: value.type });
|
|
2237
|
+
}
|
|
2238
|
+
return effectsContextEntry(key, value);
|
|
2239
|
+
}
|
|
2240
|
+
), base = buildInstanceBase({
|
|
2241
|
+
id: childDocId,
|
|
2242
|
+
now,
|
|
2243
|
+
tag: childTag,
|
|
2244
|
+
workflowResource,
|
|
2245
|
+
definitionName: definition.name,
|
|
2246
|
+
pinnedVersion: definition.version,
|
|
2247
|
+
pinnedContentHash: definition.contentHash,
|
|
2248
|
+
definition,
|
|
2249
|
+
fields: childFields,
|
|
2250
|
+
effectsContext: effectsContextEntries,
|
|
2251
|
+
ancestors,
|
|
2252
|
+
perspective: childPerspective,
|
|
2253
|
+
initialStage: definition.initialStage,
|
|
2254
|
+
actor,
|
|
2255
|
+
...fieldDiscards.length > 0 ? { extraHistory: fieldDiscards } : {}
|
|
2256
|
+
});
|
|
2257
|
+
return { ref: childRef, body: base };
|
|
2206
2258
|
}
|
|
2207
|
-
function
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
}
|
|
2259
|
+
async function subworkflowRows(ctx, refs) {
|
|
2260
|
+
if (refs.length === 0) return [];
|
|
2261
|
+
const ids = refs.map((r) => gdrToBareId(r.id));
|
|
2262
|
+
return (await ctx.client.fetch("*[_id in $ids]{_id, currentStage, completedAt}", { ids })).map((c) => ({
|
|
2263
|
+
_id: c._id,
|
|
2264
|
+
stage: c.currentStage,
|
|
2265
|
+
status: c.completedAt !== void 0 && c.completedAt !== null ? "done" : "active"
|
|
2266
|
+
}));
|
|
2216
2267
|
}
|
|
2217
|
-
async function
|
|
2218
|
-
const { client, instanceId,
|
|
2268
|
+
async function invokeActivity(args) {
|
|
2269
|
+
const { client, instanceId, activity: activityName, options } = args, ctx = await loadContext(client, instanceId, {
|
|
2219
2270
|
...options?.clock ? { clock: options.clock } : {},
|
|
2220
2271
|
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
2221
2272
|
});
|
|
2222
|
-
return commitInvoke(ctx,
|
|
2273
|
+
return commitInvoke(ctx, activityName, options?.actor);
|
|
2223
2274
|
}
|
|
2224
|
-
async function commitInvoke(ctx,
|
|
2225
|
-
const {
|
|
2275
|
+
async function commitInvoke(ctx, activityName, actor) {
|
|
2276
|
+
const { activity } = findActivityInCurrentStage(ctx, activityName), entry = findCurrentActivities(ctx.instance).find((t) => t.name === activityName);
|
|
2226
2277
|
if (entry === void 0)
|
|
2227
|
-
throw new Error(
|
|
2278
|
+
throw new Error(
|
|
2279
|
+
`Activity "${activityName}" has no status entry on instance ${ctx.instance._id}`
|
|
2280
|
+
);
|
|
2228
2281
|
if (entry.status !== "pending")
|
|
2229
|
-
return { invoked: !1,
|
|
2230
|
-
const mutation = startMutation(ctx.instance), mutEntry =
|
|
2231
|
-
return await
|
|
2282
|
+
return { invoked: !1, activity: activityName };
|
|
2283
|
+
const mutation = startMutation(ctx.instance), mutEntry = requireMutationActivityEntry(mutation, activityName);
|
|
2284
|
+
return await activateActivity(ctx, mutation, activity, mutEntry, actor), await persist(ctx, mutation), { invoked: !0, activity: activityName };
|
|
2232
2285
|
}
|
|
2233
|
-
async function
|
|
2286
|
+
async function activateActivity(ctx, mutation, activity, entry, actor) {
|
|
2234
2287
|
const now = ctx.now;
|
|
2235
|
-
if (entry.status = "active", entry.startedAt = now,
|
|
2288
|
+
if (entry.status = "active", entry.startedAt = now, activity.fields !== void 0 && activity.fields.length > 0) {
|
|
2236
2289
|
const stage = findStage(ctx.definition, mutation.currentStage);
|
|
2237
|
-
entry.fields = await
|
|
2290
|
+
entry.fields = await resolveActivityFieldEntries({
|
|
2238
2291
|
client: ctx.client,
|
|
2239
2292
|
instance: ctx.instance,
|
|
2240
2293
|
stage,
|
|
2241
|
-
|
|
2242
|
-
now
|
|
2294
|
+
activity,
|
|
2295
|
+
now,
|
|
2296
|
+
recordDiscard: recordFieldDiscards(mutation.history, "activity", now)
|
|
2243
2297
|
});
|
|
2244
2298
|
}
|
|
2245
2299
|
runOps({
|
|
2246
|
-
ops:
|
|
2300
|
+
ops: activity.ops,
|
|
2247
2301
|
mutation,
|
|
2248
2302
|
stage: mutation.currentStage,
|
|
2249
|
-
origin: {
|
|
2303
|
+
origin: { activity: activity.name },
|
|
2250
2304
|
params: {},
|
|
2251
2305
|
actor,
|
|
2252
2306
|
self: selfGdr(ctx.instance),
|
|
2253
2307
|
now
|
|
2254
2308
|
}), mutation.history.push({
|
|
2255
2309
|
_key: randomKey(),
|
|
2256
|
-
_type: "
|
|
2310
|
+
_type: "activityActivated",
|
|
2257
2311
|
at: now,
|
|
2258
2312
|
stage: mutation.currentStage,
|
|
2259
|
-
|
|
2313
|
+
activity: activity.name,
|
|
2260
2314
|
...actor !== void 0 ? { actor } : {}
|
|
2261
|
-
}), await queueEffects(
|
|
2262
|
-
|
|
2263
|
-
|
|
2315
|
+
}), await queueEffects(
|
|
2316
|
+
ctx,
|
|
2317
|
+
mutation,
|
|
2318
|
+
activity.effects,
|
|
2319
|
+
{ kind: "activity", name: activity.name },
|
|
2320
|
+
actor,
|
|
2321
|
+
{
|
|
2322
|
+
activityName: activity.name
|
|
2323
|
+
}
|
|
2324
|
+
);
|
|
2264
2325
|
let pendingChildren;
|
|
2265
|
-
if (
|
|
2266
|
-
const createsBefore = mutation.pendingCreates.length, refs = await spawnSubworkflows(ctx, mutation,
|
|
2326
|
+
if (activity.subworkflows !== void 0) {
|
|
2327
|
+
const createsBefore = mutation.pendingCreates.length, refs = await spawnSubworkflows(ctx, mutation, activity, activity.subworkflows, actor);
|
|
2267
2328
|
entry.spawnedInstances = refs, pendingChildren = mutation.pendingCreates.slice(createsBefore).map((c) => ({ _id: c._id, stage: c.currentStage, status: "active" }));
|
|
2268
2329
|
for (const ref of refs)
|
|
2269
2330
|
mutation.history.push({
|
|
2270
2331
|
_key: randomKey(),
|
|
2271
2332
|
_type: "spawned",
|
|
2272
2333
|
at: now,
|
|
2273
|
-
|
|
2334
|
+
activity: activity.name,
|
|
2274
2335
|
instanceRef: ref
|
|
2275
2336
|
});
|
|
2276
2337
|
}
|
|
2277
|
-
await autoResolveOnActivate(ctx, mutation,
|
|
2338
|
+
await autoResolveOnActivate(ctx, mutation, activity, entry, now, pendingChildren) || resolveMachineStep(mutation, activity, entry, now);
|
|
2278
2339
|
}
|
|
2279
|
-
async function autoResolveOnActivate(ctx, mutation,
|
|
2280
|
-
if (
|
|
2281
|
-
const vars = pendingChildren !== void 0 ? { subworkflows: pendingChildren } : await
|
|
2282
|
-
return outcome === void 0 ? !1 : (
|
|
2340
|
+
async function autoResolveOnActivate(ctx, mutation, activity, entry, now, pendingChildren) {
|
|
2341
|
+
if (activity.failWhen === void 0 && effectiveCompleteWhen(activity) === void 0) return !1;
|
|
2342
|
+
const vars = pendingChildren !== void 0 ? { subworkflows: pendingChildren } : await activityConditionVars(ctx, entry), outcome = await evaluateResolutionGate(ctx, activity, vars);
|
|
2343
|
+
return outcome === void 0 ? !1 : (applyActivityStatusChange({
|
|
2283
2344
|
entry,
|
|
2284
2345
|
history: mutation.history,
|
|
2285
2346
|
stage: mutation.currentStage,
|
|
@@ -2288,12 +2349,12 @@ async function autoResolveOnActivate(ctx, mutation, task, entry, now, pendingChi
|
|
|
2288
2349
|
actor: gateActor(outcome)
|
|
2289
2350
|
}), !0);
|
|
2290
2351
|
}
|
|
2291
|
-
async function
|
|
2352
|
+
async function activityConditionVars(ctx, entry) {
|
|
2292
2353
|
return entry.spawnedInstances === void 0 || entry.spawnedInstances.length === 0 ? { subworkflows: [] } : { subworkflows: await subworkflowRows(ctx, entry.spawnedInstances) };
|
|
2293
2354
|
}
|
|
2294
|
-
function resolveMachineStep(mutation,
|
|
2295
|
-
const waitsForActor =
|
|
2296
|
-
waitsForActor || waitsForCondition ||
|
|
2355
|
+
function resolveMachineStep(mutation, activity, entry, now) {
|
|
2356
|
+
const waitsForActor = activity.actions !== void 0 && activity.actions.length > 0, waitsForCondition = activity.completeWhen !== void 0 || activity.subworkflows !== void 0;
|
|
2357
|
+
waitsForActor || waitsForCondition || applyActivityStatusChange({
|
|
2297
2358
|
entry,
|
|
2298
2359
|
history: mutation.history,
|
|
2299
2360
|
stage: mutation.currentStage,
|
|
@@ -2302,15 +2363,17 @@ function resolveMachineStep(mutation, task, entry, now) {
|
|
|
2302
2363
|
actor: engineSystemActor("machineStep")
|
|
2303
2364
|
});
|
|
2304
2365
|
}
|
|
2305
|
-
async function
|
|
2366
|
+
async function buildStageActivities(ctx, stage) {
|
|
2306
2367
|
const entries = [];
|
|
2307
|
-
for (const
|
|
2308
|
-
const outcome = await ctxEvaluateConditionOutcome(ctx,
|
|
2368
|
+
for (const activity of stage.activities ?? []) {
|
|
2369
|
+
const outcome = await ctxEvaluateConditionOutcome(ctx, activity.filter, {
|
|
2370
|
+
activityName: activity.name
|
|
2371
|
+
}), inScope = outcome !== "unsatisfied";
|
|
2309
2372
|
entries.push({
|
|
2310
2373
|
_key: randomKey(),
|
|
2311
|
-
name:
|
|
2374
|
+
name: activity.name,
|
|
2312
2375
|
status: inScope ? "pending" : "skipped",
|
|
2313
|
-
...
|
|
2376
|
+
...activity.filter !== void 0 ? { filterEvaluation: buildFilterEvaluation(ctx.now, activity.filter, outcome) } : {}
|
|
2314
2377
|
});
|
|
2315
2378
|
}
|
|
2316
2379
|
return entries;
|
|
@@ -2320,7 +2383,7 @@ function buildFilterEvaluation(at, filter, outcome) {
|
|
|
2320
2383
|
at,
|
|
2321
2384
|
truthy: !1,
|
|
2322
2385
|
unevaluable: !0,
|
|
2323
|
-
detail: `Filter "${filter}" could not be evaluated (GROQ null \u2014 a referenced operand was missing or incomparable).
|
|
2386
|
+
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.`
|
|
2324
2387
|
} : { at, truthy: outcome === "satisfied" };
|
|
2325
2388
|
}
|
|
2326
2389
|
function isTerminalStage(stage) {
|
|
@@ -2343,15 +2406,16 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
|
|
|
2343
2406
|
client: ctx.client,
|
|
2344
2407
|
instance: ctx.instance,
|
|
2345
2408
|
stage: nextStage,
|
|
2346
|
-
now: at
|
|
2409
|
+
now: at,
|
|
2410
|
+
recordDiscard: recordFieldDiscards(mutation.history, "stage", at)
|
|
2347
2411
|
}),
|
|
2348
|
-
|
|
2412
|
+
activities: await buildStageActivities(ctx, nextStage)
|
|
2349
2413
|
};
|
|
2350
2414
|
mutation.stages.push(nextStageEntry);
|
|
2351
|
-
for (const
|
|
2352
|
-
if (
|
|
2353
|
-
const entry = nextStageEntry.
|
|
2354
|
-
entry && entry.status === "pending" && await
|
|
2415
|
+
for (const activity of nextStage.activities ?? []) {
|
|
2416
|
+
if (activity.activation !== "auto") continue;
|
|
2417
|
+
const entry = nextStageEntry.activities.find((t) => t.name === activity.name);
|
|
2418
|
+
entry && entry.status === "pending" && await activateActivity(ctx, mutation, activity, entry, actor);
|
|
2355
2419
|
}
|
|
2356
2420
|
isTerminalStage(nextStage) && (mutation.completedAt = at);
|
|
2357
2421
|
}
|
|
@@ -2475,15 +2539,22 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
2475
2539
|
instance,
|
|
2476
2540
|
definition,
|
|
2477
2541
|
...clock ? { clock } : {}
|
|
2478
|
-
}), now = ctx.now, initialStageEntry = {
|
|
2542
|
+
}), now = ctx.now, discards = [], initialStageEntry = {
|
|
2479
2543
|
_key: randomKey(),
|
|
2480
2544
|
name: stage.name,
|
|
2481
2545
|
enteredAt: now,
|
|
2482
|
-
fields: await resolveStageFieldEntries({
|
|
2483
|
-
|
|
2546
|
+
fields: await resolveStageFieldEntries({
|
|
2547
|
+
client,
|
|
2548
|
+
instance,
|
|
2549
|
+
stage,
|
|
2550
|
+
now,
|
|
2551
|
+
recordDiscard: recordFieldDiscards(discards, "stage", now)
|
|
2552
|
+
}),
|
|
2553
|
+
activities: await buildStageActivities(ctx, stage)
|
|
2484
2554
|
}, committed = await client.patch(instance._id).set({
|
|
2485
2555
|
stages: [initialStageEntry],
|
|
2486
|
-
lastChangedAt: now
|
|
2556
|
+
lastChangedAt: now,
|
|
2557
|
+
...discards.length > 0 ? { history: [...instance.history, ...discards] } : {}
|
|
2487
2558
|
}).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
|
|
2488
2559
|
await deployOrRollback({
|
|
2489
2560
|
client,
|
|
@@ -2502,12 +2573,20 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
2502
2573
|
stageName: stage.name,
|
|
2503
2574
|
now
|
|
2504
2575
|
})
|
|
2505
|
-
}), await
|
|
2576
|
+
}), await autoActivatePrimedActivities(
|
|
2577
|
+
client,
|
|
2578
|
+
instanceId,
|
|
2579
|
+
definition,
|
|
2580
|
+
stage,
|
|
2581
|
+
actor,
|
|
2582
|
+
clientForGdr,
|
|
2583
|
+
clock
|
|
2584
|
+
);
|
|
2506
2585
|
}
|
|
2507
|
-
async function
|
|
2586
|
+
async function autoActivatePrimedActivities(client, instanceId, definition, stage, actor, clientForGdr, clock) {
|
|
2508
2587
|
const resolvedClientForGdr = clientForGdr ?? (() => client);
|
|
2509
|
-
for (const
|
|
2510
|
-
if (
|
|
2588
|
+
for (const activity of stage.activities ?? []) {
|
|
2589
|
+
if (activity.activation !== "auto") continue;
|
|
2511
2590
|
const updated = await client.getDocument(instanceId);
|
|
2512
2591
|
if (!updated) continue;
|
|
2513
2592
|
const updatedCtx = await buildEngineContext({
|
|
@@ -2516,18 +2595,22 @@ async function autoActivatePrimedTasks(client, instanceId, definition, stage, ac
|
|
|
2516
2595
|
instance: updated,
|
|
2517
2596
|
definition,
|
|
2518
2597
|
...clock ? { clock } : {}
|
|
2519
|
-
}), mutation = startMutation(updated), mutEntry =
|
|
2520
|
-
mutEntry && mutEntry.status === "pending" && (await
|
|
2598
|
+
}), mutation = startMutation(updated), mutEntry = currentActivities(mutation).find((t) => t.name === activity.name);
|
|
2599
|
+
mutEntry && mutEntry.status === "pending" && (await activateActivity(updatedCtx, mutation, activity, mutEntry, actor), await persist(updatedCtx, mutation));
|
|
2521
2600
|
}
|
|
2522
2601
|
}
|
|
2523
2602
|
const CASCADE_LIMIT = 100;
|
|
2524
|
-
async function gatherAutoResolveCandidates(ctx,
|
|
2525
|
-
const
|
|
2526
|
-
for (const
|
|
2527
|
-
const entry =
|
|
2603
|
+
async function gatherAutoResolveCandidates(ctx, activities) {
|
|
2604
|
+
const currentActivitiesList = findCurrentActivities(ctx.instance), candidates = [];
|
|
2605
|
+
for (const activity of activities) {
|
|
2606
|
+
const entry = currentActivitiesList.find((e) => e.name === activity.name);
|
|
2528
2607
|
if (entry?.status !== "active") continue;
|
|
2529
|
-
const outcome = await evaluateResolutionGate(
|
|
2530
|
-
|
|
2608
|
+
const outcome = await evaluateResolutionGate(
|
|
2609
|
+
ctx,
|
|
2610
|
+
activity,
|
|
2611
|
+
await activityConditionVars(ctx, entry)
|
|
2612
|
+
);
|
|
2613
|
+
outcome !== void 0 && candidates.push({ activity, outcome });
|
|
2531
2614
|
}
|
|
2532
2615
|
return candidates;
|
|
2533
2616
|
}
|
|
@@ -2536,17 +2619,17 @@ async function resolveCompleteWhen(client, instanceId, clientForGdr, clock, over
|
|
|
2536
2619
|
...clientForGdr ? { clientForGdr } : {},
|
|
2537
2620
|
...clock ? { clock } : {},
|
|
2538
2621
|
...overlay ? { overlay } : {}
|
|
2539
|
-
}), stage = findStage(ctx.definition, ctx.instance.currentStage),
|
|
2622
|
+
}), stage = findStage(ctx.definition, ctx.instance.currentStage), gatedActivities = (stage.activities ?? []).filter(
|
|
2540
2623
|
(t) => effectiveCompleteWhen(t) !== void 0 || t.failWhen !== void 0
|
|
2541
2624
|
);
|
|
2542
|
-
if (
|
|
2543
|
-
const candidates = await gatherAutoResolveCandidates(ctx,
|
|
2625
|
+
if (gatedActivities.length === 0) return 0;
|
|
2626
|
+
const candidates = await gatherAutoResolveCandidates(ctx, gatedActivities);
|
|
2544
2627
|
if (candidates.length === 0) return 0;
|
|
2545
2628
|
const mutation = startMutation(ctx.instance);
|
|
2546
2629
|
let count = 0;
|
|
2547
|
-
for (const {
|
|
2548
|
-
const mutEntry =
|
|
2549
|
-
mutEntry === void 0 || mutEntry.status !== "active" || (
|
|
2630
|
+
for (const { activity, outcome } of candidates) {
|
|
2631
|
+
const mutEntry = currentActivities(mutation).find((t) => t.name === activity.name);
|
|
2632
|
+
mutEntry === void 0 || mutEntry.status !== "active" || (applyActivityStatusChange({
|
|
2550
2633
|
entry: mutEntry,
|
|
2551
2634
|
history: mutation.history,
|
|
2552
2635
|
stage: stage.name,
|
|
@@ -2579,9 +2662,9 @@ async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
|
|
|
2579
2662
|
if (!parent || parent._type !== WORKFLOW_INSTANCE_TYPE || typeof parent.definitionSnapshot != "string") return;
|
|
2580
2663
|
const definition = parseDefinitionSnapshot(parent), stage = definition.stages.find((s) => s.name === parent.currentStage);
|
|
2581
2664
|
if (stage === void 0) return;
|
|
2582
|
-
const
|
|
2583
|
-
if (
|
|
2584
|
-
const entry =
|
|
2665
|
+
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);
|
|
2666
|
+
if (activity?.subworkflows === void 0) return;
|
|
2667
|
+
const entry = parentCurrentActivities.find((e) => e.name === activity.name);
|
|
2585
2668
|
if (entry === void 0 || entry.status !== "active") return;
|
|
2586
2669
|
const ctx = await buildEngineContext({
|
|
2587
2670
|
client,
|
|
@@ -2589,23 +2672,27 @@ async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
|
|
|
2589
2672
|
instance: parent,
|
|
2590
2673
|
definition,
|
|
2591
2674
|
...clock ? { clock } : {}
|
|
2592
|
-
}), outcome = await evaluateResolutionGate(
|
|
2675
|
+
}), outcome = await evaluateResolutionGate(
|
|
2676
|
+
ctx,
|
|
2677
|
+
activity,
|
|
2678
|
+
await activityConditionVars(ctx, entry)
|
|
2679
|
+
);
|
|
2593
2680
|
if (outcome !== void 0)
|
|
2594
|
-
return { parent, definition, stage,
|
|
2681
|
+
return { parent, definition, stage, activity, outcome };
|
|
2595
2682
|
}
|
|
2596
2683
|
async function propagateToAncestors(client, instanceId, actor, clientForGdr, clock) {
|
|
2597
2684
|
const instance = await client.getDocument(instanceId);
|
|
2598
2685
|
if (!instance) return;
|
|
2599
2686
|
const resolvedClientForGdr = clientForGdr ?? (() => client), found = await findResolvedParentSpawn(client, instance, resolvedClientForGdr, clock);
|
|
2600
2687
|
if (found === void 0) return;
|
|
2601
|
-
const { parent, definition, stage,
|
|
2688
|
+
const { parent, definition, stage, activity, outcome } = found, ctx = await buildEngineContext({
|
|
2602
2689
|
client,
|
|
2603
2690
|
clientForGdr: resolvedClientForGdr,
|
|
2604
2691
|
instance: parent,
|
|
2605
2692
|
definition,
|
|
2606
2693
|
...clock ? { clock } : {}
|
|
2607
|
-
}), mutation = startMutation(parent), mutEntry =
|
|
2608
|
-
mutEntry !== void 0 && (
|
|
2694
|
+
}), mutation = startMutation(parent), mutEntry = currentActivities(mutation).find((e) => e.name === activity.name);
|
|
2695
|
+
mutEntry !== void 0 && (applyActivityStatusChange({
|
|
2609
2696
|
entry: mutEntry,
|
|
2610
2697
|
history: mutation.history,
|
|
2611
2698
|
stage: stage.name,
|
|
@@ -2708,6 +2795,18 @@ async function fetchGrantsCached(requestFn, resourcePath) {
|
|
|
2708
2795
|
return;
|
|
2709
2796
|
}
|
|
2710
2797
|
}
|
|
2798
|
+
function hasItems(arr) {
|
|
2799
|
+
return arr !== void 0 && arr.length > 0;
|
|
2800
|
+
}
|
|
2801
|
+
function deriveActivityKind(activity) {
|
|
2802
|
+
return hasItems(activity.actions) ? "user" : hasItems(activity.effects) ? "service" : activity.completeWhen !== void 0 || activity.subworkflows !== void 0 ? "receive" : "script";
|
|
2803
|
+
}
|
|
2804
|
+
function activityKind(activity) {
|
|
2805
|
+
return activity.kind ?? deriveActivityKind(activity);
|
|
2806
|
+
}
|
|
2807
|
+
function driverKind(actor) {
|
|
2808
|
+
return actor.kind === "user" ? "person" : actor.kind === "ai" ? "agent" : isEngineActor(actor) ? "engine" : "service";
|
|
2809
|
+
}
|
|
2711
2810
|
function effectiveEditable(baseline, override) {
|
|
2712
2811
|
if (baseline === void 0) return;
|
|
2713
2812
|
if (override === void 0) return baseline;
|
|
@@ -2719,14 +2818,15 @@ function slotSites(definition, stage) {
|
|
|
2719
2818
|
const sites = [];
|
|
2720
2819
|
for (const entry of definition.fields ?? []) sites.push({ scope: "workflow", entry });
|
|
2721
2820
|
for (const entry of stage.fields ?? []) sites.push({ scope: "stage", entry });
|
|
2722
|
-
for (const
|
|
2723
|
-
for (const entry of
|
|
2821
|
+
for (const activity of stage.activities ?? [])
|
|
2822
|
+
for (const entry of activity.fields ?? [])
|
|
2823
|
+
sites.push({ scope: "activity", activity: activity.name, entry });
|
|
2724
2824
|
return sites;
|
|
2725
2825
|
}
|
|
2726
2826
|
function toResolvedSlot(site, stage) {
|
|
2727
2827
|
return {
|
|
2728
2828
|
scope: site.scope,
|
|
2729
|
-
...site.
|
|
2829
|
+
...site.activity !== void 0 ? { activity: site.activity } : {},
|
|
2730
2830
|
name: site.entry.name,
|
|
2731
2831
|
type: site.entry.type,
|
|
2732
2832
|
...site.entry.title !== void 0 ? { title: site.entry.title } : {},
|
|
@@ -2738,16 +2838,16 @@ function editableSlotsInStage(definition, stage) {
|
|
|
2738
2838
|
return slotSites(definition, stage).filter((site) => site.entry.editable !== void 0).map((site) => toResolvedSlot(site, stage));
|
|
2739
2839
|
}
|
|
2740
2840
|
function editTargetMatches(slot, target) {
|
|
2741
|
-
return slot.name !== target.field ? !1 : target.
|
|
2841
|
+
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;
|
|
2742
2842
|
}
|
|
2743
|
-
const SCOPE_PRECEDENCE = {
|
|
2843
|
+
const SCOPE_PRECEDENCE = { activity: 0, stage: 1, workflow: 2 };
|
|
2744
2844
|
function resolveEditTarget(args) {
|
|
2745
2845
|
const { definition, stage, target } = args, found = slotSites(definition, stage).filter(
|
|
2746
2846
|
(site) => editTargetMatches(
|
|
2747
2847
|
{
|
|
2748
2848
|
scope: site.scope,
|
|
2749
2849
|
name: site.entry.name,
|
|
2750
|
-
...site.
|
|
2850
|
+
...site.activity !== void 0 ? { activity: site.activity } : {}
|
|
2751
2851
|
},
|
|
2752
2852
|
target
|
|
2753
2853
|
)
|
|
@@ -2757,9 +2857,11 @@ function resolveEditTarget(args) {
|
|
|
2757
2857
|
function slotWindowOpen(instance, slot) {
|
|
2758
2858
|
if (instance.completedAt !== void 0) return { open: !1, detail: "instance completed" };
|
|
2759
2859
|
if (instance.abortedAt !== void 0) return { open: !1, detail: "instance aborted" };
|
|
2760
|
-
if (slot.scope !== "
|
|
2761
|
-
const status = findOpenStageEntry(instance)?.
|
|
2762
|
-
|
|
2860
|
+
if (slot.scope !== "activity") return { open: !0 };
|
|
2861
|
+
const status = findOpenStageEntry(instance)?.activities.find(
|
|
2862
|
+
(t) => t.name === slot.activity
|
|
2863
|
+
)?.status;
|
|
2864
|
+
return status === "active" ? { open: !0 } : { open: !1, detail: `activity "${slot.activity}" is ${status ?? "not active"}` };
|
|
2763
2865
|
}
|
|
2764
2866
|
function readSlotValue(instance, slot) {
|
|
2765
2867
|
return slotFieldHost(instance, slot)?.find((e) => e.name === slot.name)?.value;
|
|
@@ -2767,7 +2869,7 @@ function readSlotValue(instance, slot) {
|
|
|
2767
2869
|
function slotFieldHost(instance, slot) {
|
|
2768
2870
|
if (slot.scope === "workflow") return instance.fields;
|
|
2769
2871
|
const stageEntry = findOpenStageEntry(instance);
|
|
2770
|
-
return slot.scope === "stage" ? stageEntry?.fields : stageEntry?.
|
|
2872
|
+
return slot.scope === "stage" ? stageEntry?.fields : stageEntry?.activities.find((t) => t.name === slot.activity)?.fields;
|
|
2771
2873
|
}
|
|
2772
2874
|
function slotProvenance(instance, ref) {
|
|
2773
2875
|
let latest;
|
|
@@ -2787,18 +2889,6 @@ function editDisabledReason(args) {
|
|
|
2787
2889
|
if (!predicateSatisfied)
|
|
2788
2890
|
return { kind: "editor-not-permitted", predicate: effective === !0 ? "true" : effective };
|
|
2789
2891
|
}
|
|
2790
|
-
function hasItems(arr) {
|
|
2791
|
-
return arr !== void 0 && arr.length > 0;
|
|
2792
|
-
}
|
|
2793
|
-
function deriveTaskKind(task) {
|
|
2794
|
-
return hasItems(task.actions) ? "user" : hasItems(task.effects) ? "service" : task.completeWhen !== void 0 || task.subworkflows !== void 0 ? "receive" : "script";
|
|
2795
|
-
}
|
|
2796
|
-
function taskKind(task) {
|
|
2797
|
-
return task.kind ?? deriveTaskKind(task);
|
|
2798
|
-
}
|
|
2799
|
-
function driverKind(actor) {
|
|
2800
|
-
return actor.kind === "user" ? "person" : actor.kind === "ai" ? "agent" : isEngineActor(actor) ? "engine" : "service";
|
|
2801
|
-
}
|
|
2802
2892
|
async function evaluateInstance(args) {
|
|
2803
2893
|
const { client, tag, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
|
|
2804
2894
|
validateTag(tag);
|
|
@@ -2822,12 +2912,12 @@ async function evaluateFromSnapshot(args) {
|
|
|
2822
2912
|
throw new Error(
|
|
2823
2913
|
`Instance "${instance._id}" currentStage "${instance.currentStage}" not in definition`
|
|
2824
2914
|
);
|
|
2825
|
-
const scope = await renderScope({ instance, definition, actor, grants, snapshot, now }),
|
|
2826
|
-
for (const
|
|
2827
|
-
|
|
2828
|
-
await
|
|
2829
|
-
|
|
2830
|
-
statusEntry:
|
|
2915
|
+
const scope = await renderScope({ instance, definition, actor, grants, snapshot, now }), currentActivityEntries = findOpenStageEntry(instance)?.activities ?? [], guardDenial = await instanceGuardReason(instance, actor, args.guards), activityEvaluations = [];
|
|
2916
|
+
for (const activity of stage.activities ?? [])
|
|
2917
|
+
activityEvaluations.push(
|
|
2918
|
+
await evaluateActivity({
|
|
2919
|
+
activity,
|
|
2920
|
+
statusEntry: currentActivityEntries.find((t) => t.name === activity.name),
|
|
2831
2921
|
instance,
|
|
2832
2922
|
actor,
|
|
2833
2923
|
snapshot,
|
|
@@ -2852,9 +2942,9 @@ async function evaluateFromSnapshot(args) {
|
|
|
2852
2942
|
}
|
|
2853
2943
|
const currentStage = {
|
|
2854
2944
|
stage,
|
|
2855
|
-
|
|
2945
|
+
activities: activityEvaluations,
|
|
2856
2946
|
transitions: transitionEvaluations
|
|
2857
|
-
}, pendingOnYou =
|
|
2947
|
+
}, 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({
|
|
2858
2948
|
instance,
|
|
2859
2949
|
definition,
|
|
2860
2950
|
stage,
|
|
@@ -2894,7 +2984,7 @@ async function evaluateEditableSlots(args) {
|
|
|
2894
2984
|
}), value = readSlotValue(instance, slot);
|
|
2895
2985
|
slots.push({
|
|
2896
2986
|
scope: slot.scope,
|
|
2897
|
-
...slot.
|
|
2987
|
+
...slot.activity !== void 0 ? { activity: slot.activity } : {},
|
|
2898
2988
|
name: slot.name,
|
|
2899
2989
|
type: slot.type,
|
|
2900
2990
|
...slot.title !== void 0 ? { title: slot.title } : {},
|
|
@@ -2910,7 +3000,14 @@ async function editPredicateSatisfied(args) {
|
|
|
2910
3000
|
const { slot, window, guardDenial, instance, snapshot, scope, actor, roleAliases } = args;
|
|
2911
3001
|
if (!window.open || guardDenial !== void 0) return !1;
|
|
2912
3002
|
if (slot.effective === !0) return !0;
|
|
2913
|
-
const params = slot.scope === "
|
|
3003
|
+
const params = slot.scope === "activity" && slot.activity !== void 0 ? activityScopeFor({
|
|
3004
|
+
scope,
|
|
3005
|
+
instance,
|
|
3006
|
+
snapshot,
|
|
3007
|
+
actor,
|
|
3008
|
+
activityName: slot.activity,
|
|
3009
|
+
roleAliases
|
|
3010
|
+
}) : scope;
|
|
2914
3011
|
return evaluateCondition({ condition: slot.effective, snapshot, params });
|
|
2915
3012
|
}
|
|
2916
3013
|
async function renderScope(args) {
|
|
@@ -2938,20 +3035,20 @@ async function advisoryCan(instance, actor, grants) {
|
|
|
2938
3035
|
});
|
|
2939
3036
|
return can;
|
|
2940
3037
|
}
|
|
2941
|
-
function
|
|
2942
|
-
const { scope, instance, snapshot, actor,
|
|
3038
|
+
function activityScopeFor(args) {
|
|
3039
|
+
const { scope, instance, snapshot, actor, activityName, roleAliases } = args;
|
|
2943
3040
|
return {
|
|
2944
3041
|
...scope,
|
|
2945
3042
|
fields: {
|
|
2946
3043
|
...scope.fields,
|
|
2947
|
-
...scopedFieldOverlay(instance, snapshot,
|
|
3044
|
+
...scopedFieldOverlay(instance, snapshot, activityName)
|
|
2948
3045
|
},
|
|
2949
|
-
assigned: assignedFor(instance,
|
|
3046
|
+
assigned: assignedFor(instance, activityName, actor, roleAliases)
|
|
2950
3047
|
};
|
|
2951
3048
|
}
|
|
2952
|
-
async function
|
|
3049
|
+
async function evaluateActivity(args) {
|
|
2953
3050
|
const {
|
|
2954
|
-
|
|
3051
|
+
activity,
|
|
2955
3052
|
statusEntry,
|
|
2956
3053
|
instance,
|
|
2957
3054
|
actor,
|
|
@@ -2960,37 +3057,37 @@ async function evaluateTask(args) {
|
|
|
2960
3057
|
roleAliases,
|
|
2961
3058
|
stageHasExits,
|
|
2962
3059
|
guardDenial
|
|
2963
|
-
} = args, status = statusEntry?.status ?? "pending",
|
|
3060
|
+
} = args, status = statusEntry?.status ?? "pending", activityScope = activityScopeFor({
|
|
2964
3061
|
scope,
|
|
2965
3062
|
instance,
|
|
2966
3063
|
snapshot,
|
|
2967
3064
|
actor,
|
|
2968
|
-
|
|
3065
|
+
activityName: activity.name,
|
|
2969
3066
|
roleAliases
|
|
2970
|
-
}), assigned =
|
|
2971
|
-
requirements:
|
|
3067
|
+
}), assigned = activityScope.assigned === !0, unmetRequirements = await evaluateRequirements({
|
|
3068
|
+
requirements: activity.requirements,
|
|
2972
3069
|
snapshot,
|
|
2973
|
-
params:
|
|
3070
|
+
params: activityScope
|
|
2974
3071
|
}), requirementsReason = unmetRequirements.length > 0 ? { kind: "requirements-unmet", unmetRequirements } : void 0, actions = [];
|
|
2975
|
-
for (const action of
|
|
3072
|
+
for (const action of activity.actions ?? [])
|
|
2976
3073
|
actions.push(
|
|
2977
3074
|
await evaluateAction({
|
|
2978
3075
|
action,
|
|
2979
3076
|
status,
|
|
2980
3077
|
instance,
|
|
2981
3078
|
snapshot,
|
|
2982
|
-
|
|
3079
|
+
activityScope,
|
|
2983
3080
|
stageHasExits,
|
|
2984
3081
|
guardDenial,
|
|
2985
3082
|
requirementsReason
|
|
2986
3083
|
})
|
|
2987
3084
|
);
|
|
2988
3085
|
return {
|
|
2989
|
-
|
|
3086
|
+
activity,
|
|
2990
3087
|
status,
|
|
2991
|
-
kind:
|
|
3088
|
+
kind: activityKind(activity),
|
|
2992
3089
|
// "pending on actor" treats both `pending` and `active` as waiting on
|
|
2993
|
-
// the assignee — pending
|
|
3090
|
+
// the assignee — pending activities just haven't been activated yet, but
|
|
2994
3091
|
// they're still inbox items. The inbox reads the assignees-kind field
|
|
2995
3092
|
// entry BY KIND (who-data is fields).
|
|
2996
3093
|
pendingOnActor: (status === "active" || status === "pending") && assigned,
|
|
@@ -3004,7 +3101,7 @@ async function evaluateAction(args) {
|
|
|
3004
3101
|
status,
|
|
3005
3102
|
instance,
|
|
3006
3103
|
snapshot,
|
|
3007
|
-
|
|
3104
|
+
activityScope,
|
|
3008
3105
|
stageHasExits,
|
|
3009
3106
|
guardDenial,
|
|
3010
3107
|
requirementsReason
|
|
@@ -3012,7 +3109,7 @@ async function evaluateAction(args) {
|
|
|
3012
3109
|
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({
|
|
3013
3110
|
condition: action.filter,
|
|
3014
3111
|
snapshot,
|
|
3015
|
-
params:
|
|
3112
|
+
params: activityScope
|
|
3016
3113
|
}) ? disabled(action, { kind: "filter-failed", filter: action.filter }) : { action, allowed: !0 };
|
|
3017
3114
|
}
|
|
3018
3115
|
async function instanceGuardReason(instance, actor, guards) {
|
|
@@ -3030,8 +3127,8 @@ function lifecycleReason(instance, status, stageHasExits) {
|
|
|
3030
3127
|
return { kind: "instance-completed", completedAt: instance.completedAt };
|
|
3031
3128
|
if (!stageHasExits)
|
|
3032
3129
|
return { kind: "stage-terminal", stage: instance.currentStage };
|
|
3033
|
-
if (
|
|
3034
|
-
return { kind: "
|
|
3130
|
+
if (isTerminalActivityStatus(status))
|
|
3131
|
+
return { kind: "activity-not-active", status };
|
|
3035
3132
|
}
|
|
3036
3133
|
function disabled(action, reason) {
|
|
3037
3134
|
return { action, allowed: !1, disabledReason: reason };
|
|
@@ -3039,37 +3136,30 @@ function disabled(action, reason) {
|
|
|
3039
3136
|
function definitionDocId(_workflowResource, tag, definition, version) {
|
|
3040
3137
|
return `${tag}.${definition}.v${version}`;
|
|
3041
3138
|
}
|
|
3042
|
-
async function sortByDependencies(client, definitions, tag
|
|
3139
|
+
async function sortByDependencies(client, definitions, tag) {
|
|
3043
3140
|
const byName = /* @__PURE__ */ new Map();
|
|
3044
3141
|
for (const def of definitions) {
|
|
3045
|
-
|
|
3046
|
-
|
|
3142
|
+
if (byName.has(def.name))
|
|
3143
|
+
throw new Error(
|
|
3144
|
+
`workflow.deployDefinitions: duplicate definition name "${def.name}" in batch \u2014 names identify a batch member (versions are deploy-assigned, not authored)`
|
|
3145
|
+
);
|
|
3146
|
+
byName.set(def.name, def);
|
|
3047
3147
|
}
|
|
3048
3148
|
const findInBatch = (ref) => findRefInBatch(byName, ref);
|
|
3049
|
-
return await assertCrossBatchRefsDeployed(client, definitions, {
|
|
3050
|
-
tag,
|
|
3051
|
-
workflowResource,
|
|
3052
|
-
findInBatch
|
|
3053
|
-
}), topoSortDefinitions(definitions, { workflowResource, tag, findInBatch });
|
|
3149
|
+
return await assertCrossBatchRefsDeployed(client, definitions, { tag, findInBatch }), topoSortDefinitions(definitions, { findInBatch });
|
|
3054
3150
|
}
|
|
3055
3151
|
function findRefInBatch(byName, ref) {
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
return typeof ref.version == "number" ? candidates.find((c) => c.version === ref.version) : candidates.reduce(
|
|
3059
|
-
(best, c) => best === void 0 || c.version > best.version ? c : best,
|
|
3060
|
-
void 0
|
|
3061
|
-
);
|
|
3152
|
+
if (typeof ref.version != "number")
|
|
3153
|
+
return byName.get(ref.name);
|
|
3062
3154
|
}
|
|
3063
3155
|
async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
|
|
3064
3156
|
const missing = [];
|
|
3065
|
-
for (const def of definitions)
|
|
3066
|
-
const fromId = definitionDocId(ctx.workflowResource, ctx.tag, def.name, def.version);
|
|
3157
|
+
for (const def of definitions)
|
|
3067
3158
|
for (const ref of refsOf(def)) {
|
|
3068
3159
|
if (ctx.findInBatch(ref) !== void 0) continue;
|
|
3069
3160
|
const label = await resolveDeployedRefLabel(client, ref, ctx.tag);
|
|
3070
|
-
label !== void 0 && missing.push({ from:
|
|
3161
|
+
label !== void 0 && missing.push({ from: def.name, ref: label });
|
|
3071
3162
|
}
|
|
3072
|
-
}
|
|
3073
3163
|
if (missing.length === 0) return;
|
|
3074
3164
|
const lines = missing.map((m) => ` - ${m.from} \u2192 ${m.ref} (neither in batch nor deployed)`);
|
|
3075
3165
|
throw new Error(
|
|
@@ -3088,16 +3178,15 @@ async function resolveDeployedRefLabel(client, ref, tag) {
|
|
|
3088
3178
|
}
|
|
3089
3179
|
function topoSortDefinitions(definitions, ctx) {
|
|
3090
3180
|
const visited = /* @__PURE__ */ new Set(), visiting = /* @__PURE__ */ new Set(), ordered = [], visit = (def) => {
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
visiting.add(id);
|
|
3181
|
+
if (!visited.has(def.name)) {
|
|
3182
|
+
if (visiting.has(def.name))
|
|
3183
|
+
throw new Error(`workflow.deployDefinitions: dependency cycle involving "${def.name}"`);
|
|
3184
|
+
visiting.add(def.name);
|
|
3096
3185
|
for (const ref of refsOf(def)) {
|
|
3097
3186
|
const child = ctx.findInBatch(ref);
|
|
3098
3187
|
child !== void 0 && visit(child);
|
|
3099
3188
|
}
|
|
3100
|
-
visiting.delete(
|
|
3189
|
+
visiting.delete(def.name), visited.add(def.name), ordered.push(def);
|
|
3101
3190
|
}
|
|
3102
3191
|
};
|
|
3103
3192
|
for (const def of definitions) visit(def);
|
|
@@ -3106,8 +3195,8 @@ function topoSortDefinitions(definitions, ctx) {
|
|
|
3106
3195
|
function refsOf(def) {
|
|
3107
3196
|
const out = [];
|
|
3108
3197
|
for (const stage of def.stages)
|
|
3109
|
-
for (const
|
|
3110
|
-
const ref =
|
|
3198
|
+
for (const activity of stage.activities ?? []) {
|
|
3199
|
+
const ref = activity.subworkflows?.definition;
|
|
3111
3200
|
ref !== void 0 && out.push({
|
|
3112
3201
|
name: ref.name,
|
|
3113
3202
|
...ref.version !== void 0 ? { version: ref.version } : {}
|
|
@@ -3115,8 +3204,23 @@ function refsOf(def) {
|
|
|
3115
3204
|
}
|
|
3116
3205
|
return out;
|
|
3117
3206
|
}
|
|
3118
|
-
function
|
|
3119
|
-
|
|
3207
|
+
function hashDefinitionContent(def) {
|
|
3208
|
+
const canonical = stableStringify(def);
|
|
3209
|
+
let hash = 0xcbf29ce484222325n;
|
|
3210
|
+
for (let i = 0; i < canonical.length; i++)
|
|
3211
|
+
hash ^= BigInt(canonical.charCodeAt(i)), hash = hash * 0x100000001b3n & 0xffffffffffffffffn;
|
|
3212
|
+
return hash.toString(16).padStart(16, "0");
|
|
3213
|
+
}
|
|
3214
|
+
function planDefinitionDeploy(def, latest, target) {
|
|
3215
|
+
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 = {
|
|
3216
|
+
...def,
|
|
3217
|
+
_id: docId,
|
|
3218
|
+
_type: WORKFLOW_DEFINITION_TYPE,
|
|
3219
|
+
tag: target.tag,
|
|
3220
|
+
version,
|
|
3221
|
+
contentHash
|
|
3222
|
+
};
|
|
3223
|
+
return { status: unchanged ? "unchanged" : "create", version, contentHash, docId, document };
|
|
3120
3224
|
}
|
|
3121
3225
|
function stripSystemFields(doc) {
|
|
3122
3226
|
const out = {};
|
|
@@ -3131,22 +3235,26 @@ function stableStringify(value) {
|
|
|
3131
3235
|
}
|
|
3132
3236
|
async function loadDefinition(client, definition, version, tag) {
|
|
3133
3237
|
if (version !== void 0) {
|
|
3134
|
-
const doc = await client.fetch(
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3238
|
+
const doc = await client.fetch(definitionLookupGroq(!0), {
|
|
3239
|
+
definition,
|
|
3240
|
+
version,
|
|
3241
|
+
tag
|
|
3242
|
+
});
|
|
3138
3243
|
if (!doc)
|
|
3139
3244
|
throw new Error(`Workflow definition ${definition} v${version} not deployed`);
|
|
3140
3245
|
return doc;
|
|
3141
3246
|
}
|
|
3142
|
-
const latest = await client
|
|
3143
|
-
definition,
|
|
3144
|
-
tag
|
|
3145
|
-
});
|
|
3247
|
+
const latest = await loadLatestDeployed(client, definition, tag);
|
|
3146
3248
|
if (!latest)
|
|
3147
3249
|
throw new Error(`No deployed definition for workflow ${definition}`);
|
|
3148
3250
|
return latest;
|
|
3149
3251
|
}
|
|
3252
|
+
async function loadLatestDeployed(client, definition, tag) {
|
|
3253
|
+
return await client.fetch(definitionLookupGroq(!1), {
|
|
3254
|
+
definition,
|
|
3255
|
+
tag
|
|
3256
|
+
}) ?? void 0;
|
|
3257
|
+
}
|
|
3150
3258
|
async function loadDefinitionVersions(client, definition, tag) {
|
|
3151
3259
|
return client.fetch(
|
|
3152
3260
|
`*[_type == "${WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}] | order(version desc)`,
|
|
@@ -3193,55 +3301,55 @@ async function commitAbort(ctx, reason, actor) {
|
|
|
3193
3301
|
}), { fired: !0, stage: stage.name };
|
|
3194
3302
|
}
|
|
3195
3303
|
async function fireAction(args) {
|
|
3196
|
-
const { client, instanceId,
|
|
3304
|
+
const { client, instanceId, activity, action, params, options } = args;
|
|
3197
3305
|
for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
|
|
3198
3306
|
const ctx = await loadCallContext(client, instanceId, options);
|
|
3199
3307
|
try {
|
|
3200
|
-
return await commitAction(ctx,
|
|
3308
|
+
return await commitAction(ctx, activity, action, params, options);
|
|
3201
3309
|
} catch (error) {
|
|
3202
3310
|
if (!isRevisionConflict(error)) throw error;
|
|
3203
3311
|
}
|
|
3204
3312
|
}
|
|
3205
3313
|
throw new ConcurrentFireActionError({
|
|
3206
3314
|
instanceId,
|
|
3207
|
-
|
|
3315
|
+
activity,
|
|
3208
3316
|
action,
|
|
3209
3317
|
attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
|
|
3210
3318
|
});
|
|
3211
3319
|
}
|
|
3212
|
-
async function resolveActionCommit(ctx,
|
|
3213
|
-
const actor = options?.actor, { stage,
|
|
3320
|
+
async function resolveActionCommit(ctx, activityName, actionName, callerParams, options) {
|
|
3321
|
+
const actor = options?.actor, { stage, activity } = findActivityInCurrentStage(ctx, activityName), action = (activity.actions ?? []).find((a) => a.name === actionName);
|
|
3214
3322
|
if (action === void 0)
|
|
3215
|
-
throw new Error(`Action "${actionName}" not declared on
|
|
3323
|
+
throw new Error(`Action "${actionName}" not declared on activity "${activityName}"`);
|
|
3216
3324
|
const can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan(ctx.instance, actor, options.grants) : void 0;
|
|
3217
3325
|
if (!await ctxEvaluateCondition(ctx, action.filter, {
|
|
3218
|
-
|
|
3326
|
+
activityName,
|
|
3219
3327
|
...actor !== void 0 ? { actor } : {},
|
|
3220
3328
|
...can !== void 0 ? { vars: { can } } : {}
|
|
3221
3329
|
}))
|
|
3222
|
-
throw new Error(`Action "${actionName}" filter rejected on
|
|
3223
|
-
const entry =
|
|
3330
|
+
throw new Error(`Action "${actionName}" filter rejected on activity "${activityName}"`);
|
|
3331
|
+
const entry = findCurrentActivities(ctx.instance).find((t) => t.name === activityName);
|
|
3224
3332
|
if (entry === void 0 || entry.status !== "active")
|
|
3225
3333
|
throw new Error(
|
|
3226
|
-
`
|
|
3334
|
+
`Activity "${activityName}" must be active to fire action "${actionName}"; status is ${entry?.status ?? "missing"}`
|
|
3227
3335
|
);
|
|
3228
|
-
const params = validateActionParams(action,
|
|
3229
|
-
return { stage,
|
|
3336
|
+
const params = validateActionParams(action, activityName, callerParams);
|
|
3337
|
+
return { stage, activity, action, params };
|
|
3230
3338
|
}
|
|
3231
|
-
async function commitAction(ctx,
|
|
3339
|
+
async function commitAction(ctx, activityName, actionName, callerParams, options) {
|
|
3232
3340
|
const actor = options?.actor, { stage, action, params } = await resolveActionCommit(
|
|
3233
3341
|
ctx,
|
|
3234
|
-
|
|
3342
|
+
activityName,
|
|
3235
3343
|
actionName,
|
|
3236
3344
|
callerParams,
|
|
3237
3345
|
options
|
|
3238
|
-
), mutation = startMutation(ctx.instance), mutEntry =
|
|
3346
|
+
), mutation = startMutation(ctx.instance), mutEntry = requireMutationActivityEntry(mutation, activityName), now = ctx.now;
|
|
3239
3347
|
mutation.history.push({
|
|
3240
3348
|
_key: randomKey(),
|
|
3241
3349
|
_type: "actionFired",
|
|
3242
3350
|
at: now,
|
|
3243
3351
|
stage: stage.name,
|
|
3244
|
-
|
|
3352
|
+
activity: activityName,
|
|
3245
3353
|
action: actionName,
|
|
3246
3354
|
...actor !== void 0 ? { actor, driverKind: driverKind(actor) } : {}
|
|
3247
3355
|
});
|
|
@@ -3249,7 +3357,7 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
|
|
|
3249
3357
|
ops: action.ops,
|
|
3250
3358
|
mutation,
|
|
3251
3359
|
stage: stage.name,
|
|
3252
|
-
origin: {
|
|
3360
|
+
origin: { activity: activityName, action: actionName },
|
|
3253
3361
|
params,
|
|
3254
3362
|
actor,
|
|
3255
3363
|
self: selfGdr(ctx.instance),
|
|
@@ -3257,14 +3365,14 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
|
|
|
3257
3365
|
}), newStatus = mutEntry.status !== statusBefore ? mutEntry.status : void 0;
|
|
3258
3366
|
return await queueEffects(ctx, mutation, action.effects, { kind: "action", name: actionName }, actor, {
|
|
3259
3367
|
callerParams: params,
|
|
3260
|
-
|
|
3368
|
+
activityName
|
|
3261
3369
|
}), await persistThenDeploy(
|
|
3262
3370
|
ctx,
|
|
3263
3371
|
mutation,
|
|
3264
3372
|
() => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
|
|
3265
3373
|
), {
|
|
3266
3374
|
fired: !0,
|
|
3267
|
-
|
|
3375
|
+
activity: activityName,
|
|
3268
3376
|
action: actionName,
|
|
3269
3377
|
...newStatus !== void 0 ? { newStatus } : {},
|
|
3270
3378
|
...ranOps.length > 0 ? { ranOps } : {}
|
|
@@ -3272,23 +3380,23 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
|
|
|
3272
3380
|
}
|
|
3273
3381
|
class ActionDisabledError extends Error {
|
|
3274
3382
|
reason;
|
|
3275
|
-
|
|
3383
|
+
activity;
|
|
3276
3384
|
action;
|
|
3277
3385
|
constructor(args) {
|
|
3278
|
-
super(formatDisabledReason(args.
|
|
3386
|
+
super(formatDisabledReason(args.activity, args.action, args.reason)), this.name = "ActionDisabledError", this.reason = args.reason, this.activity = args.activity, this.action = args.action;
|
|
3279
3387
|
}
|
|
3280
3388
|
}
|
|
3281
3389
|
const disabledReasonDetail = {
|
|
3282
3390
|
"filter-failed": (r) => `action filter returned false${r.detail ? ` (${r.detail})` : ""}`,
|
|
3283
|
-
"
|
|
3391
|
+
"activity-not-active": (r) => `activity status is "${r.status}"`,
|
|
3284
3392
|
"stage-terminal": (r) => `stage "${r.stage}" is terminal`,
|
|
3285
3393
|
"instance-completed": (r) => `instance completed at ${r.completedAt}`,
|
|
3286
3394
|
"mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`,
|
|
3287
3395
|
"requirements-unmet": (r) => `unmet requirement(s): ${r.unmetRequirements.join(", ")}`
|
|
3288
3396
|
};
|
|
3289
|
-
function formatDisabledReason(
|
|
3397
|
+
function formatDisabledReason(activity, action, reason) {
|
|
3290
3398
|
const detail = disabledReasonDetail[reason.kind](reason);
|
|
3291
|
-
return `Action "${
|
|
3399
|
+
return `Action "${activity}:${action}" is not allowed: ${detail}`;
|
|
3292
3400
|
}
|
|
3293
3401
|
class EditFieldDeniedError extends Error {
|
|
3294
3402
|
reason;
|
|
@@ -3307,7 +3415,7 @@ const editDisabledReasonDetail = {
|
|
|
3307
3415
|
function formatEditDisabledReason(target, reason) {
|
|
3308
3416
|
const detail = editDisabledReasonDetail[reason.kind](
|
|
3309
3417
|
reason
|
|
3310
|
-
), where = target.
|
|
3418
|
+
), where = target.activity !== void 0 ? `${target.activity}.${target.field}` : target.field;
|
|
3311
3419
|
return `Field "${target.scope}:${where}" is not editable: ${detail}`;
|
|
3312
3420
|
}
|
|
3313
3421
|
async function editField(args) {
|
|
@@ -3339,7 +3447,7 @@ async function commitEdit(ctx, target, mode, value, options) {
|
|
|
3339
3447
|
stage: stage.name,
|
|
3340
3448
|
origin: {
|
|
3341
3449
|
edit: !0,
|
|
3342
|
-
...slot.scope === "
|
|
3450
|
+
...slot.scope === "activity" && slot.activity !== void 0 ? { activity: slot.activity } : {}
|
|
3343
3451
|
},
|
|
3344
3452
|
params: {},
|
|
3345
3453
|
actor,
|
|
@@ -3358,7 +3466,7 @@ async function commitEdit(ctx, target, mode, value, options) {
|
|
|
3358
3466
|
}
|
|
3359
3467
|
async function assertSlotEditable(ctx, slot, options) {
|
|
3360
3468
|
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, {
|
|
3361
|
-
...slot.
|
|
3469
|
+
...slot.activity !== void 0 ? { activityName: slot.activity } : {},
|
|
3362
3470
|
...actor !== void 0 ? { actor } : {},
|
|
3363
3471
|
...can !== void 0 ? { vars: { can } } : {}
|
|
3364
3472
|
}) : slot.effective === !0, reason = editDisabledReason({
|
|
@@ -3384,18 +3492,18 @@ function slotTarget(slot) {
|
|
|
3384
3492
|
return {
|
|
3385
3493
|
scope: slot.scope,
|
|
3386
3494
|
field: slot.name,
|
|
3387
|
-
...slot.
|
|
3495
|
+
...slot.activity !== void 0 ? { activity: slot.activity } : {}
|
|
3388
3496
|
};
|
|
3389
3497
|
}
|
|
3390
3498
|
function labelTarget(target) {
|
|
3391
3499
|
return {
|
|
3392
|
-
scope: target.scope ?? (target.
|
|
3500
|
+
scope: target.scope ?? (target.activity !== void 0 ? "activity" : "workflow"),
|
|
3393
3501
|
field: target.field,
|
|
3394
|
-
...target.
|
|
3502
|
+
...target.activity !== void 0 ? { activity: target.activity } : {}
|
|
3395
3503
|
};
|
|
3396
3504
|
}
|
|
3397
3505
|
function describeTarget(target) {
|
|
3398
|
-
const where = target.
|
|
3506
|
+
const where = target.activity !== void 0 ? `${target.activity}.${target.field}` : target.field;
|
|
3399
3507
|
return target.scope !== void 0 ? `${target.scope}:${where}` : where;
|
|
3400
3508
|
}
|
|
3401
3509
|
function bareIdFromSpawnRef(uri) {
|
|
@@ -3460,10 +3568,10 @@ async function dispatchGatedWrite(args) {
|
|
|
3460
3568
|
...ranOps !== void 0 ? { ranOps } : {}
|
|
3461
3569
|
};
|
|
3462
3570
|
}
|
|
3463
|
-
function assertActionAllowed(evaluation,
|
|
3464
|
-
const actionEval = evaluation.currentStage.
|
|
3571
|
+
function assertActionAllowed(evaluation, activity, action) {
|
|
3572
|
+
const actionEval = evaluation.currentStage.activities.find((t) => t.activity.name === activity)?.actions.find((a) => a.action.name === action);
|
|
3465
3573
|
if (actionEval?.allowed === !1 && actionEval.disabledReason)
|
|
3466
|
-
throw new ActionDisabledError({
|
|
3574
|
+
throw new ActionDisabledError({ activity, action, reason: actionEval.disabledReason });
|
|
3467
3575
|
}
|
|
3468
3576
|
function assertEditAllowed(evaluation, target) {
|
|
3469
3577
|
const slot = evaluation.editableSlots.filter((s) => editTargetMatches(s, target)).sort((a, b) => SCOPE_PRECEDENCE[a.scope] - SCOPE_PRECEDENCE[b.scope])[0];
|
|
@@ -3472,7 +3580,7 @@ function assertEditAllowed(evaluation, target) {
|
|
|
3472
3580
|
target: {
|
|
3473
3581
|
scope: slot.scope,
|
|
3474
3582
|
field: slot.name,
|
|
3475
|
-
...slot.
|
|
3583
|
+
...slot.activity !== void 0 ? { activity: slot.activity } : {}
|
|
3476
3584
|
},
|
|
3477
3585
|
reason: slot.disabledReason
|
|
3478
3586
|
});
|
|
@@ -3497,11 +3605,11 @@ function buildEngineCallOptions(args) {
|
|
|
3497
3605
|
};
|
|
3498
3606
|
}
|
|
3499
3607
|
async function applyAction(args) {
|
|
3500
|
-
const { client, instance,
|
|
3501
|
-
return findOpenStageEntry(instance)?.
|
|
3608
|
+
const { client, instance, activity, action, params, actor, grants, clock, clientForGdr } = args, options = buildEngineCallOptions({ actor, grants, clock, clientForGdr });
|
|
3609
|
+
return findOpenStageEntry(instance)?.activities.find((t) => t.name === activity)?.status === "pending" && await invokeActivity({ client, instanceId: instance._id, activity, options }), (await fireAction({
|
|
3502
3610
|
client,
|
|
3503
3611
|
instanceId: instance._id,
|
|
3504
|
-
|
|
3612
|
+
activity,
|
|
3505
3613
|
action,
|
|
3506
3614
|
...params !== void 0 ? { params } : {},
|
|
3507
3615
|
options
|
|
@@ -3581,14 +3689,16 @@ function previewIds(ids) {
|
|
|
3581
3689
|
}
|
|
3582
3690
|
const workflow = {
|
|
3583
3691
|
/**
|
|
3584
|
-
* Deploy a set of workflow definitions as one call.
|
|
3585
|
-
*
|
|
3586
|
-
*
|
|
3587
|
-
*
|
|
3588
|
-
* (
|
|
3589
|
-
*
|
|
3590
|
-
*
|
|
3591
|
-
*
|
|
3692
|
+
* Deploy a set of workflow definitions as one call. Definitions are
|
|
3693
|
+
* immutable and content-addressed: the author writes no version, and each
|
|
3694
|
+
* deploy compares a definition's content fingerprint to the latest version
|
|
3695
|
+
* already deployed under its name. Identical content is a no-op
|
|
3696
|
+
* (`unchanged`); any change mints the next version (`created`) — deploy
|
|
3697
|
+
* never patches a deployed version, so a definition can't change out from
|
|
3698
|
+
* under the instances pinned to it. `startInstance` picks the highest
|
|
3699
|
+
* version by default. The engine figures out the dependency order itself
|
|
3700
|
+
* (children before parents that spawn them via `subworkflows.definition`)
|
|
3701
|
+
* and reports a per-definition outcome (`created` / `unchanged`).
|
|
3592
3702
|
*
|
|
3593
3703
|
* Refs may point inside the batch OR at already-deployed definitions
|
|
3594
3704
|
* in the lake — both are valid. A ref pointing at neither errors with
|
|
@@ -3601,31 +3711,15 @@ const workflow = {
|
|
|
3601
3711
|
validateTag(tag);
|
|
3602
3712
|
for (const def of definitions)
|
|
3603
3713
|
validateDefinition(def);
|
|
3604
|
-
const ordered = await sortByDependencies(client, definitions, tag
|
|
3714
|
+
const ordered = await sortByDependencies(client, definitions, tag), results = [], tx = client.transaction();
|
|
3605
3715
|
let hasWrites = !1;
|
|
3606
3716
|
for (const def of ordered) {
|
|
3607
|
-
const
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
_type: WORKFLOW_DEFINITION_TYPE,
|
|
3611
|
-
tag
|
|
3612
|
-
}, existing = await client.getDocument(id);
|
|
3613
|
-
if (existing && existing.tag === tag && isDefinitionUnchanged(existing, expected)) {
|
|
3614
|
-
results.push({ definition: def.name, version: def.version, status: "unchanged" });
|
|
3717
|
+
const latest = await loadLatestDeployed(client, def.name, tag), plan = planDefinitionDeploy(def, latest, { tag, workflowResource });
|
|
3718
|
+
if (plan.status === "unchanged") {
|
|
3719
|
+
results.push({ definition: def.name, version: plan.version, status: "unchanged" });
|
|
3615
3720
|
continue;
|
|
3616
3721
|
}
|
|
3617
|
-
|
|
3618
|
-
const expectedKeys = new Set(Object.keys(expected)), toUnset = Object.keys(existing).filter(
|
|
3619
|
-
(k) => !k.startsWith("_") && !expectedKeys.has(k)
|
|
3620
|
-
), patch = client.patch(id).set(expected).ifRevisionId(existing._rev);
|
|
3621
|
-
toUnset.length > 0 && patch.unset(toUnset), tx.patch(patch);
|
|
3622
|
-
} else
|
|
3623
|
-
tx.create(expected);
|
|
3624
|
-
hasWrites = !0, results.push({
|
|
3625
|
-
definition: def.name,
|
|
3626
|
-
version: def.version,
|
|
3627
|
-
status: existing ? "updated" : "created"
|
|
3628
|
-
});
|
|
3722
|
+
tx.create(plan.document), hasWrites = !0, results.push({ definition: def.name, version: plan.version, status: "created" });
|
|
3629
3723
|
}
|
|
3630
3724
|
return hasWrites && await tx.commit(), { results };
|
|
3631
3725
|
},
|
|
@@ -3641,8 +3735,8 @@ const workflow = {
|
|
|
3641
3735
|
* Spawn a new workflow instance from a deployed definition.
|
|
3642
3736
|
*
|
|
3643
3737
|
* Pins the snapshot at start-time, seeds `effectsContext`, enters
|
|
3644
|
-
* the initial stage (which builds
|
|
3645
|
-
* effects, auto-activates
|
|
3738
|
+
* the initial stage (which builds activityStatus, queues onEnter
|
|
3739
|
+
* effects, auto-activates activities). Then cascades auto-transitions
|
|
3646
3740
|
* until stable. Returns the resulting instance.
|
|
3647
3741
|
*/
|
|
3648
3742
|
startInstance: async (args) => {
|
|
@@ -3662,7 +3756,7 @@ const workflow = {
|
|
|
3662
3756
|
throw new Error(
|
|
3663
3757
|
`Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
|
|
3664
3758
|
);
|
|
3665
|
-
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedFields = await resolveDeclaredFields({
|
|
3759
|
+
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], fieldDiscards = [], resolvedFields = await resolveDeclaredFields({
|
|
3666
3760
|
entryDefs: definition.fields ?? [],
|
|
3667
3761
|
initialFields: initialFields ?? [],
|
|
3668
3762
|
ctx: {
|
|
@@ -3672,7 +3766,8 @@ const workflow = {
|
|
|
3672
3766
|
tag,
|
|
3673
3767
|
workflowResource,
|
|
3674
3768
|
definitionName: definition.name,
|
|
3675
|
-
...perspective !== void 0 ? { perspective } : {}
|
|
3769
|
+
...perspective !== void 0 ? { perspective } : {},
|
|
3770
|
+
recordDiscard: recordFieldDiscards(fieldDiscards, "workflow", now)
|
|
3676
3771
|
},
|
|
3677
3772
|
randomKey
|
|
3678
3773
|
}), effectivePerspective = perspective ?? derivePerspectiveFromFields(resolvedFields), base = buildInstanceBase({
|
|
@@ -3682,18 +3777,20 @@ const workflow = {
|
|
|
3682
3777
|
workflowResource,
|
|
3683
3778
|
definitionName: definition.name,
|
|
3684
3779
|
pinnedVersion: definition.version,
|
|
3780
|
+
pinnedContentHash: definition.contentHash,
|
|
3685
3781
|
definition,
|
|
3686
3782
|
fields: resolvedFields,
|
|
3687
3783
|
effectsContext: effectsContextEntries,
|
|
3688
3784
|
ancestors: ancestors ?? [],
|
|
3689
3785
|
perspective: effectivePerspective,
|
|
3690
3786
|
initialStage: definition.initialStage,
|
|
3691
|
-
actor
|
|
3787
|
+
actor,
|
|
3788
|
+
...fieldDiscards.length > 0 ? { extraHistory: fieldDiscards } : {}
|
|
3692
3789
|
});
|
|
3693
3790
|
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);
|
|
3694
3791
|
},
|
|
3695
3792
|
/**
|
|
3696
|
-
* Fire an action against
|
|
3793
|
+
* Fire an action against an activity. If the activity is pending, it is
|
|
3697
3794
|
* auto-invoked first (pending → active) so callers don't have to know
|
|
3698
3795
|
* the lifecycle. Cascades auto-transitions and propagates to ancestors
|
|
3699
3796
|
* after the action commits.
|
|
@@ -3707,13 +3804,13 @@ const workflow = {
|
|
|
3707
3804
|
client,
|
|
3708
3805
|
tag,
|
|
3709
3806
|
instanceId,
|
|
3710
|
-
|
|
3807
|
+
activity,
|
|
3711
3808
|
action,
|
|
3712
3809
|
params,
|
|
3713
3810
|
idempotent,
|
|
3714
3811
|
resourceClients
|
|
3715
3812
|
} = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
|
|
3716
|
-
return findOpenStageEntry(before)?.
|
|
3813
|
+
return findOpenStageEntry(before)?.activities.find((t) => t.name === activity) === void 0 && idempotent === !0 ? { instance: before, cascaded: 0, fired: !1 } : dispatchGatedWrite({
|
|
3717
3814
|
client,
|
|
3718
3815
|
tag,
|
|
3719
3816
|
instanceId,
|
|
@@ -3723,10 +3820,10 @@ const workflow = {
|
|
|
3723
3820
|
clock,
|
|
3724
3821
|
before,
|
|
3725
3822
|
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3726
|
-
apply: (instance, evaluation) => (assertActionAllowed(evaluation,
|
|
3823
|
+
apply: (instance, evaluation) => (assertActionAllowed(evaluation, activity, action), applyAction({
|
|
3727
3824
|
client,
|
|
3728
3825
|
instance,
|
|
3729
|
-
|
|
3826
|
+
activity,
|
|
3730
3827
|
action,
|
|
3731
3828
|
actor,
|
|
3732
3829
|
...access.grants !== void 0 ? { grants: access.grants } : {},
|
|
@@ -3807,7 +3904,7 @@ const workflow = {
|
|
|
3807
3904
|
* Re-evaluate auto-transitions and resolve due waits until stable.
|
|
3808
3905
|
*
|
|
3809
3906
|
* Used by the runtime after any event that might affect the workflow
|
|
3810
|
-
* but isn't itself
|
|
3907
|
+
* but isn't itself an activity action: a subject doc was patched, a sibling
|
|
3811
3908
|
* workflow completed, the clock crossed a `completeWhen` deadline, etc.
|
|
3812
3909
|
* The runtime doesn't need to know what changed — it just nudges
|
|
3813
3910
|
* affected instances and the engine re-evaluates.
|
|
@@ -3917,8 +4014,8 @@ const workflow = {
|
|
|
3917
4014
|
* declared by a `doc.ref` / `doc.refs` entry in scope), then
|
|
3918
4015
|
* evaluates the supplied GROQ in groq-js against that dataset. The
|
|
3919
4016
|
* rendered scope is auto-bound: `$self`, `$fields`, `$parent`,
|
|
3920
|
-
* `$ancestors`, `$stage`, `$now`, `$effects`, `$
|
|
3921
|
-
* `$
|
|
4017
|
+
* `$ancestors`, `$stage`, `$now`, `$effects`, `$activities`,
|
|
4018
|
+
* `$allActivitiesDone`, `$anyActivityFailed` — ids in GDR URI form to match
|
|
3922
4019
|
* the snapshot's keying.
|
|
3923
4020
|
*
|
|
3924
4021
|
* Use when an external consumer (a UI, a debug pane, a test) wants
|
|
@@ -3971,34 +4068,83 @@ const workflow = {
|
|
|
3971
4068
|
* List the actions an actor could fire on an instance's current stage,
|
|
3972
4069
|
* each flagged `allowed` (with a structured `disabledReason` when not).
|
|
3973
4070
|
* Projects the instance from the actor's perspective and flattens its
|
|
3974
|
-
*
|
|
4071
|
+
* activities' actions. Returns the evaluation alongside the actions so a
|
|
3975
4072
|
* consumer can read the instance/stage context. Pure read.
|
|
3976
4073
|
*/
|
|
3977
4074
|
availableActions: async (args) => {
|
|
3978
4075
|
const evaluation = await evaluateInstance(args);
|
|
3979
|
-
return { evaluation, actions: availableActions(evaluation.currentStage.
|
|
4076
|
+
return { evaluation, actions: availableActions(evaluation.currentStage.activities) };
|
|
3980
4077
|
},
|
|
3981
4078
|
/**
|
|
3982
4079
|
* Materialised spawned children of a parent instance.
|
|
3983
4080
|
*
|
|
3984
4081
|
* Walks `history` for `spawned` events — the durable
|
|
3985
|
-
* record. (The per-
|
|
4082
|
+
* record. (The per-activity `spawnedInstances` field on the active stage
|
|
3986
4083
|
* only exists while that stage is current; history survives.) Strips
|
|
3987
4084
|
* the GDR URI on each `instanceRef` to a bare `_id`, fetches the
|
|
3988
4085
|
* instances, drops any that aren't visible to this engine's tag, and
|
|
3989
4086
|
* returns them sorted by `startedAt` ascending.
|
|
3990
4087
|
*
|
|
3991
|
-
* Pass `
|
|
4088
|
+
* Pass `activity` to restrict to a single spawning activity on the parent.
|
|
3992
4089
|
*/
|
|
3993
4090
|
children: async (args) => {
|
|
3994
|
-
const { client, tag, instanceId,
|
|
4091
|
+
const { client, tag, instanceId, activity } = args;
|
|
3995
4092
|
validateTag(tag);
|
|
3996
|
-
const parent = await reload(client, instanceId, tag), isSpawned = (h) => h._type === "spawned", ids = parent.history.filter(isSpawned).filter((h) =>
|
|
4093
|
+
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));
|
|
3997
4094
|
return ids.length === 0 ? [] : client.fetch(
|
|
3998
4095
|
`*[_id in $ids && ${tagScopeFilter()}] | order(startedAt asc)`,
|
|
3999
4096
|
{ ids, tag }
|
|
4000
4097
|
);
|
|
4001
4098
|
},
|
|
4099
|
+
/**
|
|
4100
|
+
* Every in-flight instance whose reactive watch-set includes `document` —
|
|
4101
|
+
* the reverse of {@link subscriptionDocumentsForInstance}.
|
|
4102
|
+
*
|
|
4103
|
+
* For a non-reactive, content-change-driven runtime (a Sanity Function, an
|
|
4104
|
+
* Inngest/durable worker, any server) that holds no instances in memory: a
|
|
4105
|
+
* document changed; which instances should it `tick`? The watch-set covers
|
|
4106
|
+
* the instance itself, its ancestors, and the docs named by
|
|
4107
|
+
* `doc.ref` / `doc.refs` / `release.ref` field entries on the workflow scope
|
|
4108
|
+
* **and the current stage** — so a hand-rolled GROQ over `fields[]` gets it
|
|
4109
|
+
* subtly wrong (misses stage-scope refs, `release.ref`, ancestors).
|
|
4110
|
+
*
|
|
4111
|
+
* A coarse GROQ prefilter narrows candidates server-side (in-flight,
|
|
4112
|
+
* tag-scoped, mentioning the doc anywhere in state); the authoritative match
|
|
4113
|
+
* is {@link instanceWatchesDocument}, derived from `collectWatchRefs`, so the
|
|
4114
|
+
* reverse stays in lockstep with the forward set and honours the
|
|
4115
|
+
* open-stage-only rule the prefilter deliberately over-approximates.
|
|
4116
|
+
*
|
|
4117
|
+
* Single-resource: instances always live in the engine's own resource, so
|
|
4118
|
+
* this reads one client (unlike {@link guardsForInstance}, whose guard docs
|
|
4119
|
+
* are scattered across the watched resources). "Routed by resource" applies
|
|
4120
|
+
* to the **subject**: a cross-dataset doc is matched by its full,
|
|
4121
|
+
* resource-qualified GDR URI, never its bare id, so an instance watching
|
|
4122
|
+
* `dataset:A:ds:doc` is not matched by a change to `dataset:B:ds:doc`.
|
|
4123
|
+
*
|
|
4124
|
+
* `document` must be a resource-qualified GDR URI; a bare id is rejected
|
|
4125
|
+
* (it can't be resource-routed and would silently mismatch). Sorted by
|
|
4126
|
+
* `startedAt` ascending.
|
|
4127
|
+
*/
|
|
4128
|
+
instancesForDocument: async (args) => {
|
|
4129
|
+
const { client, tag, document } = args;
|
|
4130
|
+
if (validateTag(tag), !isGdrUri(document))
|
|
4131
|
+
throw new Error(
|
|
4132
|
+
`workflow.instancesForDocument: "document" must be a resource-qualified GDR URI (e.g. "dataset:project:dataset:doc-id"); got: ${JSON.stringify(document)}`
|
|
4133
|
+
);
|
|
4134
|
+
const fieldRefsDoc = "count(fields[value.id == $document]) > 0 || count(fields[$document in value[].id]) > 0", query = `*[
|
|
4135
|
+
_type == "${WORKFLOW_INSTANCE_TYPE}" && ${tagScopeFilter()} && !defined(completedAt) && (
|
|
4136
|
+
_id == $bareId ||
|
|
4137
|
+
$document in ancestors[].id ||
|
|
4138
|
+
${fieldRefsDoc} ||
|
|
4139
|
+
count(stages[${fieldRefsDoc}]) > 0
|
|
4140
|
+
)
|
|
4141
|
+
] | order(startedAt asc)`;
|
|
4142
|
+
return (await client.fetch(query, {
|
|
4143
|
+
tag,
|
|
4144
|
+
document,
|
|
4145
|
+
bareId: extractDocumentId(document)
|
|
4146
|
+
})).filter((instance) => instanceWatchesDocument(instance, document));
|
|
4147
|
+
},
|
|
4002
4148
|
/**
|
|
4003
4149
|
* Permission helpers — Sanity ACL grants evaluated against documents
|
|
4004
4150
|
* via GROQ. Used by `workflow.evaluate` to soft-gate actions when the
|
|
@@ -4052,10 +4198,13 @@ function walkEffectNames(def, visit) {
|
|
|
4052
4198
|
for (const e of effects ?? []) visit(e.name, location);
|
|
4053
4199
|
};
|
|
4054
4200
|
for (const stage of def.stages ?? []) {
|
|
4055
|
-
for (const t of stage.
|
|
4056
|
-
visitEffects(t.effects, `stage[${stage.name}].
|
|
4201
|
+
for (const t of stage.activities ?? []) {
|
|
4202
|
+
visitEffects(t.effects, `stage[${stage.name}].activity[${t.name}].effects`);
|
|
4057
4203
|
for (const a of t.actions ?? [])
|
|
4058
|
-
visitEffects(
|
|
4204
|
+
visitEffects(
|
|
4205
|
+
a.effects,
|
|
4206
|
+
`stage[${stage.name}].activity[${t.name}].action[${a.name}].effects`
|
|
4207
|
+
);
|
|
4059
4208
|
}
|
|
4060
4209
|
for (const tr of stage.transitions ?? [])
|
|
4061
4210
|
visitEffects(tr.effects, `stage[${stage.name}].transition[${tr.name}].effects`);
|
|
@@ -4071,7 +4220,7 @@ async function drainEffectsInternal(args) {
|
|
|
4071
4220
|
effectHandlers,
|
|
4072
4221
|
missingHandler,
|
|
4073
4222
|
logger
|
|
4074
|
-
} = args, drainerActor = args.access?.actor ?? engineSystemActor("drainEffects"), completionAccess = {
|
|
4223
|
+
} = args, routeGdr = buildClientForGdr(client, resourceClients), clientFor = (ref) => routeGdr(parseGdr(typeof ref == "string" ? ref : ref.id)), drainerActor = args.access?.actor ?? engineSystemActor("drainEffects"), completionAccess = {
|
|
4075
4224
|
actor: drainerActor,
|
|
4076
4225
|
...args.access?.grants !== void 0 ? { grants: args.access.grants } : {}
|
|
4077
4226
|
};
|
|
@@ -4090,6 +4239,7 @@ async function drainEffectsInternal(args) {
|
|
|
4090
4239
|
if (!await claimPendingEffect(client, before, candidate._key, drainerActor)) continue;
|
|
4091
4240
|
const { outputs, dispatchError } = await dispatchEffect(handler, candidate, {
|
|
4092
4241
|
client,
|
|
4242
|
+
clientFor,
|
|
4093
4243
|
instanceId,
|
|
4094
4244
|
logger
|
|
4095
4245
|
});
|
|
@@ -4144,6 +4294,7 @@ async function dispatchEffect(handler, candidate, ctx) {
|
|
|
4144
4294
|
try {
|
|
4145
4295
|
const result = await handler(candidate.params, {
|
|
4146
4296
|
client: ctx.client,
|
|
4297
|
+
clientFor: ctx.clientFor,
|
|
4147
4298
|
instanceId: ctx.instanceId,
|
|
4148
4299
|
effectKey: candidate._key,
|
|
4149
4300
|
log: (message, extra) => ctx.logger(`effect.${candidate.name}`).info(message, extra)
|
|
@@ -4232,13 +4383,13 @@ function createInstanceSession(args) {
|
|
|
4232
4383
|
return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: cascaded > 0 };
|
|
4233
4384
|
});
|
|
4234
4385
|
},
|
|
4235
|
-
fireAction({
|
|
4386
|
+
fireAction({ activity, action, params }) {
|
|
4236
4387
|
return commit(async (actor, held) => {
|
|
4237
|
-
assertActionAllowed(await evaluateWith(held, heldGuards),
|
|
4388
|
+
assertActionAllowed(await evaluateWith(held, heldGuards), activity, action);
|
|
4238
4389
|
const { grants } = await access(), ranOps = await applyAction({
|
|
4239
4390
|
client,
|
|
4240
4391
|
instance,
|
|
4241
|
-
|
|
4392
|
+
activity,
|
|
4242
4393
|
action,
|
|
4243
4394
|
actor,
|
|
4244
4395
|
clientForGdr,
|
|
@@ -4345,13 +4496,14 @@ function createEngine(args) {
|
|
|
4345
4496
|
definition,
|
|
4346
4497
|
...resourceClients !== void 0 ? { resourceClients } : {}
|
|
4347
4498
|
}),
|
|
4348
|
-
children: ({ instanceId,
|
|
4499
|
+
children: ({ instanceId, activity }) => workflow.children({
|
|
4349
4500
|
client,
|
|
4350
4501
|
tag,
|
|
4351
4502
|
workflowResource,
|
|
4352
4503
|
instanceId,
|
|
4353
|
-
...
|
|
4504
|
+
...activity !== void 0 ? { activity } : {}
|
|
4354
4505
|
}),
|
|
4506
|
+
instancesForDocument: ({ document }) => workflow.instancesForDocument({ client, tag, workflowResource, document }),
|
|
4355
4507
|
query: ({ groq, params }) => workflow.query({
|
|
4356
4508
|
client,
|
|
4357
4509
|
tag,
|
|
@@ -4397,33 +4549,25 @@ function createEngine(args) {
|
|
|
4397
4549
|
})
|
|
4398
4550
|
};
|
|
4399
4551
|
}
|
|
4400
|
-
function
|
|
4401
|
-
|
|
4402
|
-
}
|
|
4403
|
-
function diffStatus(existing, expected) {
|
|
4404
|
-
return existing === void 0 ? "create" : isDefinitionUnchanged(existing, expected) ? "unchanged" : "update";
|
|
4405
|
-
}
|
|
4406
|
-
function diffEntry(def, existingRaw, target) {
|
|
4407
|
-
const docId = docIdFor(def, target), expected = {
|
|
4408
|
-
...def,
|
|
4409
|
-
_id: docId,
|
|
4410
|
-
_type: WORKFLOW_DEFINITION_TYPE,
|
|
4411
|
-
tag: target.tag
|
|
4412
|
-
}, status = diffStatus(existingRaw, expected), existing = existingRaw ? stripSystemFields(existingRaw) : void 0;
|
|
4552
|
+
function diffEntry(def, latestRaw, target) {
|
|
4553
|
+
const plan = planDefinitionDeploy(def, asLatest(latestRaw), target), existing = latestRaw ? stripSystemFields(latestRaw) : void 0;
|
|
4413
4554
|
return {
|
|
4414
4555
|
name: def.name,
|
|
4415
|
-
version:
|
|
4416
|
-
status,
|
|
4417
|
-
docId,
|
|
4418
|
-
expected,
|
|
4556
|
+
version: plan.version,
|
|
4557
|
+
status: plan.status,
|
|
4558
|
+
docId: plan.docId,
|
|
4559
|
+
expected: plan.document,
|
|
4419
4560
|
...existing !== void 0 ? { existing } : {}
|
|
4420
4561
|
};
|
|
4421
4562
|
}
|
|
4563
|
+
function asLatest(raw) {
|
|
4564
|
+
return raw === void 0 ? void 0 : { version: typeof raw.version == "number" ? raw.version : 0, ...typeof raw.contentHash == "string" ? { contentHash: raw.contentHash } : {} };
|
|
4565
|
+
}
|
|
4422
4566
|
async function computeDiffEntries(client, defs, target) {
|
|
4423
4567
|
const entries = [];
|
|
4424
4568
|
for (const def of defs) {
|
|
4425
|
-
const
|
|
4426
|
-
entries.push(diffEntry(def,
|
|
4569
|
+
const latest = await loadLatestDeployed(client, def.name, target.tag);
|
|
4570
|
+
entries.push(diffEntry(def, latest, target));
|
|
4427
4571
|
}
|
|
4428
4572
|
return entries;
|
|
4429
4573
|
}
|
|
@@ -4436,17 +4580,17 @@ const HISTORY_DISPLAY = {
|
|
|
4436
4580
|
title: "Stage exited",
|
|
4437
4581
|
description: "The instance left a stage on the way to the next one."
|
|
4438
4582
|
},
|
|
4439
|
-
|
|
4440
|
-
title: "
|
|
4441
|
-
description: "
|
|
4583
|
+
activityActivated: {
|
|
4584
|
+
title: "Activity activated",
|
|
4585
|
+
description: "An activity flipped from `pending` to `active` and its onEnter effects queued."
|
|
4442
4586
|
},
|
|
4443
|
-
|
|
4444
|
-
title: "
|
|
4445
|
-
description: "An action or op flipped
|
|
4587
|
+
activityStatusChanged: {
|
|
4588
|
+
title: "Activity status changed",
|
|
4589
|
+
description: "An action or op flipped an activity's status (active \u2192 done / skipped / failed)."
|
|
4446
4590
|
},
|
|
4447
4591
|
actionFired: {
|
|
4448
4592
|
title: "Action fired",
|
|
4449
|
-
description: "An action was invoked against
|
|
4593
|
+
description: "An action was invoked against an activity \u2014 ops ran, status may have flipped, effects queued."
|
|
4450
4594
|
},
|
|
4451
4595
|
transitionFired: {
|
|
4452
4596
|
title: "Transition fired",
|
|
@@ -4462,7 +4606,7 @@ const HISTORY_DISPLAY = {
|
|
|
4462
4606
|
},
|
|
4463
4607
|
spawned: {
|
|
4464
4608
|
title: "Spawned child",
|
|
4465
|
-
description: "
|
|
4609
|
+
description: "An activity's `subworkflows` declaration spawned a child workflow instance."
|
|
4466
4610
|
},
|
|
4467
4611
|
aborted: {
|
|
4468
4612
|
title: "Instance aborted",
|
|
@@ -4471,6 +4615,10 @@ const HISTORY_DISPLAY = {
|
|
|
4471
4615
|
opApplied: {
|
|
4472
4616
|
title: "Op applied",
|
|
4473
4617
|
description: "An inline field-mutation op (field.set / field.append / status.set / etc.) ran during an action commit."
|
|
4618
|
+
},
|
|
4619
|
+
fieldQueryDiscarded: {
|
|
4620
|
+
title: "Query result discarded",
|
|
4621
|
+
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."
|
|
4474
4622
|
}
|
|
4475
4623
|
}, FIELD_SLOT_DISPLAY = {
|
|
4476
4624
|
"doc.ref": {
|
|
@@ -4485,50 +4633,58 @@ const HISTORY_DISPLAY = {
|
|
|
4485
4633
|
title: "Content Release reference",
|
|
4486
4634
|
description: "GDR pointer at a Content Release system doc, carrying the release name the engine derives the instance's read perspective from."
|
|
4487
4635
|
},
|
|
4488
|
-
|
|
4489
|
-
title: "Query result",
|
|
4490
|
-
description: "Snapshot of a GROQ projection captured at entry-resolve time; resolvedAt is stamped on the value."
|
|
4491
|
-
},
|
|
4492
|
-
"value.string": {
|
|
4636
|
+
string: {
|
|
4493
4637
|
title: "String value",
|
|
4494
|
-
description: "Free-form string entry."
|
|
4638
|
+
description: "Free-form single-line string entry."
|
|
4495
4639
|
},
|
|
4496
|
-
|
|
4497
|
-
title: "
|
|
4498
|
-
description: "
|
|
4640
|
+
text: {
|
|
4641
|
+
title: "Text value",
|
|
4642
|
+
description: "Free-form multiline string entry."
|
|
4499
4643
|
},
|
|
4500
|
-
|
|
4644
|
+
number: {
|
|
4501
4645
|
title: "Number value",
|
|
4502
4646
|
description: "Numeric entry."
|
|
4503
4647
|
},
|
|
4504
|
-
|
|
4648
|
+
boolean: {
|
|
4505
4649
|
title: "Boolean value",
|
|
4506
4650
|
description: "True/false entry."
|
|
4507
4651
|
},
|
|
4508
|
-
|
|
4652
|
+
date: {
|
|
4653
|
+
title: "Date value",
|
|
4654
|
+
description: "Date-only (YYYY-MM-DD) entry, no time component."
|
|
4655
|
+
},
|
|
4656
|
+
datetime: {
|
|
4509
4657
|
title: "Date / time value",
|
|
4510
|
-
description: "ISO-timestamp entry."
|
|
4658
|
+
description: "ISO-8601 timestamp entry."
|
|
4511
4659
|
},
|
|
4512
|
-
|
|
4513
|
-
title: "
|
|
4514
|
-
description: "
|
|
4660
|
+
url: {
|
|
4661
|
+
title: "URL value",
|
|
4662
|
+
description: "Validated URL entry."
|
|
4515
4663
|
},
|
|
4516
|
-
|
|
4517
|
-
title: "
|
|
4518
|
-
description: "
|
|
4664
|
+
actor: {
|
|
4665
|
+
title: "Actor value",
|
|
4666
|
+
description: "A typed (user|ai|system) actor identity \u2014 a concrete principal."
|
|
4519
4667
|
},
|
|
4520
|
-
|
|
4521
|
-
title: "
|
|
4522
|
-
description: "
|
|
4668
|
+
assignee: {
|
|
4669
|
+
title: "Assignee",
|
|
4670
|
+
description: "A single typed (user|role) assignee \u2014 who is expected/responsible."
|
|
4523
4671
|
},
|
|
4524
4672
|
assignees: {
|
|
4525
4673
|
title: "Assignees",
|
|
4526
4674
|
description: "Ordered list of typed (user|role) assignees."
|
|
4675
|
+
},
|
|
4676
|
+
object: {
|
|
4677
|
+
title: "Object",
|
|
4678
|
+
description: "An object with named sub-fields; the value is keyed by sub-field name."
|
|
4679
|
+
},
|
|
4680
|
+
array: {
|
|
4681
|
+
title: "Array",
|
|
4682
|
+
description: "An array of objects shaped by the declared `of` sub-fields; ops add/update/remove rows."
|
|
4527
4683
|
}
|
|
4528
4684
|
}, OP_DISPLAY = {
|
|
4529
4685
|
"field.set": {
|
|
4530
4686
|
title: "Set field entry",
|
|
4531
|
-
description: "Overwrite a field entry's value with a resolved
|
|
4687
|
+
description: "Overwrite a field entry's value with a resolved value expression."
|
|
4532
4688
|
},
|
|
4533
4689
|
"field.unset": {
|
|
4534
4690
|
title: "Unset field entry",
|
|
@@ -4536,7 +4692,7 @@ const HISTORY_DISPLAY = {
|
|
|
4536
4692
|
},
|
|
4537
4693
|
"field.append": {
|
|
4538
4694
|
title: "Append to field entry",
|
|
4539
|
-
description: "Push a resolved item onto an array-kind entry (
|
|
4695
|
+
description: "Push a resolved item onto an array-kind entry (array, assignees, doc.refs)."
|
|
4540
4696
|
},
|
|
4541
4697
|
"field.updateWhere": {
|
|
4542
4698
|
title: "Update matching rows",
|
|
@@ -4547,8 +4703,8 @@ const HISTORY_DISPLAY = {
|
|
|
4547
4703
|
description: "Drop rows of an array-kind entry that match the predicate."
|
|
4548
4704
|
},
|
|
4549
4705
|
"status.set": {
|
|
4550
|
-
title: "Set
|
|
4551
|
-
description: "Flip
|
|
4706
|
+
title: "Set activity status",
|
|
4707
|
+
description: "Flip an activity's status explicitly (the action-level `status:` sugar desugars to this)."
|
|
4552
4708
|
}
|
|
4553
4709
|
}, EFFECTS_CONTEXT_DISPLAY = {
|
|
4554
4710
|
"effectsContext.string": {
|
|
@@ -4588,25 +4744,25 @@ const HISTORY_DISPLAY = {
|
|
|
4588
4744
|
title: "Workflow instance",
|
|
4589
4745
|
description: "A running (or finished) workflow against its declared fields."
|
|
4590
4746
|
}
|
|
4591
|
-
},
|
|
4747
|
+
}, ACTIVITY_KIND_DISPLAY = {
|
|
4592
4748
|
user: {
|
|
4593
|
-
title: "User
|
|
4749
|
+
title: "User activity",
|
|
4594
4750
|
description: "A person acts via the app \u2014 renders action buttons ranked by outcome."
|
|
4595
4751
|
},
|
|
4596
4752
|
service: {
|
|
4597
|
-
title: "Service
|
|
4753
|
+
title: "Service activity",
|
|
4598
4754
|
description: "An automated system or effect does the work \u2014 renders effect drain status."
|
|
4599
4755
|
},
|
|
4600
4756
|
script: {
|
|
4601
|
-
title: "Script
|
|
4757
|
+
title: "Script activity",
|
|
4602
4758
|
description: "The engine runs a step inline and resolves on activation \u2014 an audit row."
|
|
4603
4759
|
},
|
|
4604
4760
|
manual: {
|
|
4605
|
-
title: "Manual
|
|
4761
|
+
title: "Manual activity",
|
|
4606
4762
|
description: "Work done off-system, acknowledged by a person or verified by a predicate \u2014 renders an instruction card with a deep-link."
|
|
4607
4763
|
},
|
|
4608
4764
|
receive: {
|
|
4609
|
-
title: "Receive
|
|
4765
|
+
title: "Receive activity",
|
|
4610
4766
|
description: 'No actor; the engine waits on a condition \u2014 renders "waiting until \u2026", no buttons.'
|
|
4611
4767
|
}
|
|
4612
4768
|
}, DRIVER_KIND_DISPLAY = {
|
|
@@ -4632,6 +4788,8 @@ function displayDescription(typeKey) {
|
|
|
4632
4788
|
return DISPLAY[typeKey]?.description;
|
|
4633
4789
|
}
|
|
4634
4790
|
export {
|
|
4791
|
+
ACTIVITY_KINDS,
|
|
4792
|
+
ACTIVITY_KIND_DISPLAY,
|
|
4635
4793
|
AUTHORING_DISPLAY,
|
|
4636
4794
|
ActionDisabledError,
|
|
4637
4795
|
ActionParamsInvalidError,
|
|
@@ -4653,13 +4811,12 @@ export {
|
|
|
4653
4811
|
OP_DISPLAY,
|
|
4654
4812
|
PartialGuardDeployError,
|
|
4655
4813
|
RequiredFieldNotProvidedError,
|
|
4656
|
-
TASK_KINDS,
|
|
4657
|
-
TASK_KIND_DISPLAY,
|
|
4658
4814
|
WORKFLOW_DEFINITION_TYPE,
|
|
4659
4815
|
WORKFLOW_INSTANCE_TYPE,
|
|
4660
4816
|
WorkflowStateDivergedError,
|
|
4661
4817
|
abortReason,
|
|
4662
4818
|
actionVerdict,
|
|
4819
|
+
activityKind,
|
|
4663
4820
|
availableActions,
|
|
4664
4821
|
buildSnapshot,
|
|
4665
4822
|
compileGuard,
|
|
@@ -4670,7 +4827,7 @@ export {
|
|
|
4670
4827
|
defaultLoggerFactory,
|
|
4671
4828
|
denyingGuards,
|
|
4672
4829
|
deployStageGuards,
|
|
4673
|
-
|
|
4830
|
+
deriveActivityKind,
|
|
4674
4831
|
diagnoseInputFromEvaluation,
|
|
4675
4832
|
diagnoseInstance,
|
|
4676
4833
|
diffEntry,
|
|
@@ -4688,8 +4845,9 @@ export {
|
|
|
4688
4845
|
guardsForDefinition,
|
|
4689
4846
|
guardsForInstance,
|
|
4690
4847
|
guardsForResource,
|
|
4848
|
+
hashDefinitionContent,
|
|
4691
4849
|
instanceGuardQuery,
|
|
4692
|
-
|
|
4850
|
+
instanceWatchesDocument,
|
|
4693
4851
|
isGdr,
|
|
4694
4852
|
isStartableDefinition,
|
|
4695
4853
|
isTerminalStage,
|
|
@@ -4709,7 +4867,6 @@ export {
|
|
|
4709
4867
|
subscriptionDocument,
|
|
4710
4868
|
subscriptionDocumentsForInstance,
|
|
4711
4869
|
tagScopeFilter,
|
|
4712
|
-
taskKind,
|
|
4713
4870
|
validateDefinition,
|
|
4714
4871
|
validateTag,
|
|
4715
4872
|
verdictGuardsForInstance,
|