@sanity/workflow-engine 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks-cjs/schema.cjs +189 -74
- package/dist/_chunks-cjs/schema.cjs.map +1 -1
- package/dist/_chunks-es/schema.js +190 -75
- package/dist/_chunks-es/schema.js.map +1 -1
- package/dist/define.cjs +220 -121
- package/dist/define.cjs.map +1 -1
- package/dist/define.d.cts +6473 -3502
- package/dist/define.d.ts +6473 -3502
- package/dist/define.js +221 -122
- package/dist/define.js.map +1 -1
- package/dist/index.cjs +1131 -921
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14866 -10827
- package/dist/index.d.ts +14866 -10827
- package/dist/index.js +1133 -923
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parse, evaluate } from "groq-js";
|
|
2
|
-
import { actorFulfillsRole,
|
|
3
|
-
import { isStartableDefinition } from "./_chunks-es/schema.js";
|
|
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,344 +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 MAX_SPAWN_DEPTH = 6, SUBWORKFLOWS_ALL_DONE = "count($subworkflows[status != 'done']) == 0", FAIL_WHEN_SYSTEM_ID = "engine.failWhen", COMPLETE_WHEN_SYSTEM_ID = "engine.completeWhen";
|
|
1655
|
-
function gateActor(outcome) {
|
|
1656
|
-
return {
|
|
1657
|
-
kind: "system",
|
|
1658
|
-
id: outcome === "failed" ? FAIL_WHEN_SYSTEM_ID : COMPLETE_WHEN_SYSTEM_ID
|
|
1659
|
-
};
|
|
1660
|
-
}
|
|
1661
|
-
function effectiveCompleteWhen(task) {
|
|
1662
|
-
return task.completeWhen ?? (task.subworkflows !== void 0 ? SUBWORKFLOWS_ALL_DONE : void 0);
|
|
1663
|
-
}
|
|
1664
|
-
async function evaluateResolutionGate(ctx, task, vars) {
|
|
1665
|
-
const scope = { taskName: task.name, vars };
|
|
1666
|
-
if (task.failWhen !== void 0 && await ctxEvaluateCondition(ctx, task.failWhen, scope))
|
|
1667
|
-
return "failed";
|
|
1668
|
-
const completeWhen = effectiveCompleteWhen(task);
|
|
1669
|
-
if (completeWhen !== void 0 && await ctxEvaluateCondition(ctx, completeWhen, scope))
|
|
1670
|
-
return "done";
|
|
1671
|
-
}
|
|
1672
|
-
async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
1673
|
-
if (ctx.instance.ancestors.length + 1 > MAX_SPAWN_DEPTH) {
|
|
1674
|
-
const chain = [...ctx.instance.ancestors.map((a) => a.id), selfGdr(ctx.instance)].join(" \u2192 ");
|
|
1675
|
-
throw new Error(
|
|
1676
|
-
`Subworkflow depth limit (${MAX_SPAWN_DEPTH}) exceeded on task "${task.name}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
|
|
1677
|
-
);
|
|
1678
|
-
}
|
|
1679
|
-
const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tag);
|
|
1680
|
-
if (definition === null) {
|
|
1681
|
-
const versionLabel = sub.definition.version === void 0 || sub.definition.version === "latest" ? "latest" : `v${sub.definition.version}`;
|
|
1682
|
-
throw new Error(
|
|
1683
|
-
`Subworkflow definition "${sub.definition.name}" ${versionLabel} not deployed (parent ${ctx.instance._id}, task ${task.name})`
|
|
1684
|
-
);
|
|
1685
|
-
}
|
|
1686
|
-
const parentScope = await ctxConditionParams(ctx, {
|
|
1687
|
-
taskName: task.name,
|
|
1688
|
-
...actor ? { actor } : {}
|
|
1689
|
-
}), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
|
|
1690
|
-
for (const row of rows) {
|
|
1691
|
-
const initialFields = await projectRowFields(
|
|
1692
|
-
ctx,
|
|
1693
|
-
sub,
|
|
1694
|
-
definition,
|
|
1695
|
-
parentScope,
|
|
1696
|
-
row,
|
|
1697
|
-
discoveryResource
|
|
1698
|
-
), { ref, body } = await prepareChildInstance({
|
|
1699
|
-
client: ctx.client,
|
|
1700
|
-
parent: ctx.instance,
|
|
1701
|
-
definition,
|
|
1702
|
-
initialFields,
|
|
1703
|
-
effectsContext,
|
|
1704
|
-
actor,
|
|
1705
|
-
now
|
|
1706
|
-
});
|
|
1707
|
-
refs.push(ref), mutation.pendingCreates.push(body);
|
|
1708
|
-
}
|
|
1709
|
-
return refs;
|
|
1710
|
-
}
|
|
1711
|
-
async function resolveSpawnRows(ctx, sub) {
|
|
1712
|
-
const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
|
|
1713
|
-
let value, discoveryResource;
|
|
1714
|
-
if (groq.includes("*")) {
|
|
1715
|
-
const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
|
|
1716
|
-
client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
|
|
1717
|
-
client,
|
|
1718
|
-
groq,
|
|
1719
|
-
params,
|
|
1720
|
-
// Draft-aware by default (drafts overlay published) when there's no
|
|
1721
|
-
// release, so a `forEach` discovers in-flight items the same way the
|
|
1722
|
-
// engine reads any other content.
|
|
1723
|
-
perspective: ctx.instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
|
|
1724
|
-
});
|
|
1725
|
-
} else {
|
|
1726
|
-
const tree = parse(groq, { params });
|
|
1727
|
-
value = await (await evaluate(tree, { dataset: [ctx.instance], params, root: ctx.instance })).get();
|
|
1728
|
-
}
|
|
1729
|
-
return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
|
|
1730
|
-
}
|
|
1731
|
-
async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
|
|
1732
|
-
const out = [];
|
|
1733
|
-
for (const [name, groq] of Object.entries(sub.with ?? {})) {
|
|
1734
|
-
const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
|
|
1735
|
-
if (declared === void 0)
|
|
1736
|
-
throw new Error(
|
|
1737
|
-
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
|
|
1738
|
-
);
|
|
1739
|
-
const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
|
|
1740
|
-
parentResource: ctx.instance.workflowResource,
|
|
1741
|
-
discoveryResource,
|
|
1742
|
-
entryName: name,
|
|
1743
|
-
childName: childDefinition.name
|
|
1744
|
-
});
|
|
1745
|
-
value != null && out.push({
|
|
1746
|
-
type: declared.type,
|
|
1747
|
-
name,
|
|
1748
|
-
value
|
|
1749
|
-
});
|
|
1750
|
-
}
|
|
1751
|
-
return out;
|
|
1752
|
-
}
|
|
1753
|
-
async function resolveContextHandoff(ctx, sub, parentScope) {
|
|
1754
|
-
const out = {};
|
|
1755
|
-
for (const [name, groq] of Object.entries(sub.context ?? {})) {
|
|
1756
|
-
const v2 = await runGroq(groq, parentScope, ctx.snapshot);
|
|
1757
|
-
v2 != null && (typeof v2 == "string" || typeof v2 == "number" || typeof v2 == "boolean" || typeof v2 == "object" && "id" in v2 && "type" in v2) && (out[name] = v2);
|
|
1758
|
-
}
|
|
1759
|
-
return out;
|
|
1760
|
-
}
|
|
1761
|
-
function canonicaliseSpawnValue(kind, value, cx) {
|
|
1762
|
-
return kind === "doc.ref" ? coerceSpawnGdr(value, cx) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, cx)).filter((v2) => v2 !== null) : value;
|
|
1763
|
-
}
|
|
1764
|
-
function assertBareIdRootsCorrectly(id, cx) {
|
|
1765
|
-
if (!(cx.discoveryResource === void 0 || sameResource(cx.discoveryResource, cx.parentResource)))
|
|
1766
|
-
throw new Error(
|
|
1767
|
-
`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.`
|
|
1768
|
-
);
|
|
1769
|
-
}
|
|
1770
|
-
function coerceSpawnGdr(raw, cx) {
|
|
1771
|
-
const toUri = (id) => isGdrUri(id) ? id : (assertBareIdRootsCorrectly(id, cx), gdrFromResource(cx.parentResource, id));
|
|
1772
|
-
if (raw == null) return null;
|
|
1773
|
-
if (typeof raw == "object" && "id" in raw && "type" in raw) {
|
|
1774
|
-
const r = raw;
|
|
1775
|
-
if (typeof r.id == "string" && typeof r.type == "string")
|
|
1776
|
-
return { id: toUri(r.id), type: r.type };
|
|
1777
|
-
}
|
|
1778
|
-
if (typeof raw == "object" && "_ref" in raw) {
|
|
1779
|
-
const r = raw;
|
|
1780
|
-
if (typeof r._ref == "string") return { id: toUri(r._ref), type: "document" };
|
|
1781
|
-
}
|
|
1782
|
-
return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
|
|
1783
|
-
}
|
|
1784
|
-
async function resolveDefinitionRef(client, ref, tag) {
|
|
1785
|
-
const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
|
|
1786
|
-
return wantsExplicit && (params.version = ref.version), await client.fetch(
|
|
1787
|
-
definitionLookupGroq(wantsExplicit),
|
|
1788
|
-
params
|
|
1789
|
-
) ?? null;
|
|
1790
|
-
}
|
|
1791
|
-
async function prepareChildInstance(args) {
|
|
1792
|
-
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 = [
|
|
1793
|
-
...parent.ancestors,
|
|
1794
|
-
{
|
|
1795
|
-
id: selfGdr(parent),
|
|
1796
|
-
type: WORKFLOW_INSTANCE_TYPE
|
|
1797
|
-
}
|
|
1798
|
-
], inheritedPerspective = parent.perspective, childFields = await resolveDeclaredFields({
|
|
1799
|
-
entryDefs: definition.fields,
|
|
1800
|
-
initialFields,
|
|
1801
|
-
ctx: {
|
|
1802
|
-
client,
|
|
1803
|
-
now,
|
|
1804
|
-
selfId: childDocId,
|
|
1805
|
-
tag: childTag,
|
|
1806
|
-
workflowResource,
|
|
1807
|
-
definitionName: definition.name,
|
|
1808
|
-
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
|
|
1809
|
-
},
|
|
1810
|
-
randomKey
|
|
1811
|
-
}), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
|
|
1812
|
-
([key, value]) => {
|
|
1813
|
-
if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
|
|
1814
|
-
if (!isGdrUri(value.id))
|
|
1815
|
-
throw new Error(
|
|
1816
|
-
`subworkflows.context["${key}"]: GDR id must be a "<scheme>:..." URI, got ${JSON.stringify(value.id)}.`
|
|
1817
|
-
);
|
|
1818
|
-
return effectsContextEntry(key, { id: value.id, type: value.type });
|
|
1819
|
-
}
|
|
1820
|
-
return effectsContextEntry(key, value);
|
|
1821
|
-
}
|
|
1822
|
-
), base = buildInstanceBase({
|
|
1823
|
-
id: childDocId,
|
|
1824
|
-
now,
|
|
1825
|
-
tag: childTag,
|
|
1826
|
-
workflowResource,
|
|
1827
|
-
definitionName: definition.name,
|
|
1828
|
-
pinnedVersion: definition.version,
|
|
1829
|
-
definition,
|
|
1830
|
-
fields: childFields,
|
|
1831
|
-
effectsContext: effectsContextEntries,
|
|
1832
|
-
ancestors,
|
|
1833
|
-
perspective: childPerspective,
|
|
1834
|
-
initialStage: definition.initialStage,
|
|
1835
|
-
actor
|
|
1836
|
-
});
|
|
1837
|
-
return { ref: childRef, body: base };
|
|
1838
|
-
}
|
|
1839
|
-
async function subworkflowRows(ctx, refs) {
|
|
1840
|
-
if (refs.length === 0) return [];
|
|
1841
|
-
const ids = refs.map((r) => gdrToBareId(r.id));
|
|
1842
|
-
return (await ctx.client.fetch("*[_id in $ids]{_id, currentStage, completedAt}", { ids })).map((c) => ({
|
|
1843
|
-
_id: c._id,
|
|
1844
|
-
stage: c.currentStage,
|
|
1845
|
-
status: c.completedAt !== void 0 && c.completedAt !== null ? "done" : "active"
|
|
1846
|
-
}));
|
|
1847
|
-
}
|
|
1848
|
-
async function resolveBindings(args) {
|
|
1849
|
-
const resolved = {};
|
|
1850
|
-
for (const [key, groq] of Object.entries(args.bindings ?? {}))
|
|
1851
|
-
resolved[key] = await runGroq(groq, args.params, args.snapshot);
|
|
1852
|
-
return { ...resolved, ...args.staticInput };
|
|
1853
|
-
}
|
|
1854
|
-
async function persistThenDeploy(ctx, mutation, deploy) {
|
|
1855
|
-
const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
|
|
1856
|
-
await deployOrRollback({
|
|
1857
|
-
client: ctx.client,
|
|
1858
|
-
instanceId: ctx.instance._id,
|
|
1859
|
-
committedRev: committed._rev,
|
|
1860
|
-
restore: instanceStateFields(ctx.instance),
|
|
1861
|
-
unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
|
|
1862
|
-
reversible: !spawned,
|
|
1863
|
-
deploy
|
|
1864
|
-
});
|
|
1865
|
-
}
|
|
1866
|
-
async function refreshStageGuards(ctx, mutation, stageName) {
|
|
1867
|
-
await deployStageGuards({
|
|
1868
|
-
client: ctx.client,
|
|
1869
|
-
clientForGdr: ctx.clientForGdr,
|
|
1870
|
-
instance: materializeInstance(ctx.instance, mutation),
|
|
1871
|
-
definition: ctx.definition,
|
|
1872
|
-
stageName,
|
|
1873
|
-
now: ctx.now
|
|
1874
|
-
});
|
|
1875
|
-
}
|
|
1876
|
-
async function completeEffect(args) {
|
|
1877
|
-
const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1878
|
-
...options?.clock ? { clock: options.clock } : {},
|
|
1879
|
-
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
1880
|
-
});
|
|
1881
|
-
return commitCompleteEffect(
|
|
1882
|
-
ctx,
|
|
1883
|
-
effectKey,
|
|
1884
|
-
status,
|
|
1885
|
-
outputs,
|
|
1886
|
-
detail,
|
|
1887
|
-
error,
|
|
1888
|
-
durationMs,
|
|
1889
|
-
options?.actor
|
|
1890
|
-
);
|
|
1891
|
-
}
|
|
1892
|
-
function buildEffectHistoryEntry(pending, outcome) {
|
|
1893
|
-
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;
|
|
1894
1648
|
return {
|
|
1895
1649
|
_key: pending._key,
|
|
1896
1650
|
name: pending.name,
|
|
@@ -1963,7 +1717,7 @@ function buildQueuedEffect(effect, origin, params, actor, now) {
|
|
|
1963
1717
|
async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
|
|
1964
1718
|
if (!effects || effects.length === 0) return;
|
|
1965
1719
|
const now = ctx.now, liveCtx = { ...ctx, instance: materializeInstance(ctx.instance, mutation) }, params = await ctxConditionParams(liveCtx, {
|
|
1966
|
-
...opts?.
|
|
1720
|
+
...opts?.activityName !== void 0 ? { activityName: opts.activityName } : {},
|
|
1967
1721
|
...actor !== void 0 ? { actor } : {},
|
|
1968
1722
|
// Caller-supplied action params, readable in bindings as `$params.<name>`.
|
|
1969
1723
|
vars: { params: opts?.callerParams ?? {} }
|
|
@@ -1978,18 +1732,74 @@ async function queueEffects(ctx, mutation, effects, origin, actor, opts) {
|
|
|
1978
1732
|
mutation.pendingEffects.push(pending), mutation.history.push(history);
|
|
1979
1733
|
}
|
|
1980
1734
|
}
|
|
1981
|
-
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) {
|
|
1982
1794
|
switch (src.type) {
|
|
1983
1795
|
case "literal":
|
|
1984
|
-
return
|
|
1796
|
+
return src.value;
|
|
1985
1797
|
case "param":
|
|
1986
|
-
return
|
|
1798
|
+
return ctx.params?.[src.param];
|
|
1987
1799
|
case "actor":
|
|
1988
|
-
return
|
|
1800
|
+
return ctx.actor;
|
|
1989
1801
|
case "now":
|
|
1990
|
-
return
|
|
1991
|
-
default:
|
|
1992
|
-
return { handled: !1 };
|
|
1802
|
+
return ctx.now;
|
|
1993
1803
|
}
|
|
1994
1804
|
}
|
|
1995
1805
|
const FIELD_OP_TYPES = [
|
|
@@ -2002,7 +1812,7 @@ const FIELD_OP_TYPES = [
|
|
|
2002
1812
|
function isFieldOp(summary) {
|
|
2003
1813
|
return FIELD_OP_TYPES.includes(summary.opType);
|
|
2004
1814
|
}
|
|
2005
|
-
function validateActionParams(action,
|
|
1815
|
+
function validateActionParams(action, activityName, callerParams) {
|
|
2006
1816
|
const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
|
|
2007
1817
|
for (const decl of declared) {
|
|
2008
1818
|
const value = params[decl.name], present = decl.name in params && value !== void 0 && value !== null;
|
|
@@ -2016,7 +1826,7 @@ function validateActionParams(action, taskName, callerParams) {
|
|
|
2016
1826
|
}));
|
|
2017
1827
|
}
|
|
2018
1828
|
if (issues.length > 0)
|
|
2019
|
-
throw new ActionParamsInvalidError({ action: action.name,
|
|
1829
|
+
throw new ActionParamsInvalidError({ action: action.name, activity: activityName, issues });
|
|
2020
1830
|
return params;
|
|
2021
1831
|
}
|
|
2022
1832
|
function hasStringField(value, field) {
|
|
@@ -2049,7 +1859,15 @@ function runOps(args) {
|
|
|
2049
1859
|
if (ops === void 0 || ops.length === 0) return [];
|
|
2050
1860
|
const summaries = [];
|
|
2051
1861
|
for (const op of ops) {
|
|
2052
|
-
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
|
+
});
|
|
2053
1871
|
summaries.push(summary), mutation.history.push(opAppliedEntry({ origin, summary, stage, actor, now }));
|
|
2054
1872
|
}
|
|
2055
1873
|
return summaries;
|
|
@@ -2061,7 +1879,7 @@ function opAppliedEntry(args) {
|
|
|
2061
1879
|
_type: "opApplied",
|
|
2062
1880
|
at: now,
|
|
2063
1881
|
stage,
|
|
2064
|
-
...origin.
|
|
1882
|
+
...origin.activity !== void 0 ? { activity: origin.activity } : {},
|
|
2065
1883
|
...origin.action !== void 0 ? { action: origin.action } : {},
|
|
2066
1884
|
...origin.transition !== void 0 ? { transition: origin.transition } : {},
|
|
2067
1885
|
...origin.edit === !0 ? { edit: !0 } : {},
|
|
@@ -2088,13 +1906,15 @@ function applyOp(op, ctx) {
|
|
|
2088
1906
|
}
|
|
2089
1907
|
}
|
|
2090
1908
|
function applyFieldSet(op, ctx) {
|
|
2091
|
-
const value =
|
|
2092
|
-
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 } : {};
|
|
2093
1914
|
}
|
|
2094
1915
|
const EMPTY_BY_KIND = {
|
|
2095
1916
|
"doc.refs": [],
|
|
2096
|
-
|
|
2097
|
-
notes: [],
|
|
1917
|
+
array: [],
|
|
2098
1918
|
assignees: []
|
|
2099
1919
|
};
|
|
2100
1920
|
function applyFieldUnset(op, ctx) {
|
|
@@ -2102,8 +1922,13 @@ function applyFieldUnset(op, ctx) {
|
|
|
2102
1922
|
return setEntryValue(entry, EMPTY_BY_KIND[entry._type] ?? null), { opType: op.type, target: op.target };
|
|
2103
1923
|
}
|
|
2104
1924
|
function applyFieldAppend(op, ctx) {
|
|
2105
|
-
const item =
|
|
2106
|
-
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
|
+
});
|
|
2107
1932
|
const current = Array.isArray(entry.value) ? entry.value : [];
|
|
2108
1933
|
return setEntryValue(entry, [...current, withRowKey(item)]), { opType: op.type, target: op.target, resolved: { item } };
|
|
2109
1934
|
}
|
|
@@ -2111,7 +1936,7 @@ function withRowKey(item) {
|
|
|
2111
1936
|
return typeof item == "object" && item !== null && !Array.isArray(item) && !("_key" in item) ? { _key: randomKey(), ...item } : item;
|
|
2112
1937
|
}
|
|
2113
1938
|
function applyFieldUpdateWhere(op, ctx) {
|
|
2114
|
-
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);
|
|
2115
1940
|
if (merge === null || typeof merge != "object" || Array.isArray(merge))
|
|
2116
1941
|
throw new Error(
|
|
2117
1942
|
`field.updateWhere value must resolve to an object of fields to merge (target "${op.target.field}")`
|
|
@@ -2137,145 +1962,385 @@ function requireArrayValue(entry, op) {
|
|
|
2137
1962
|
);
|
|
2138
1963
|
return entry.value;
|
|
2139
1964
|
}
|
|
2140
|
-
function applyStatusSet(op, ctx) {
|
|
2141
|
-
const entry = findOpenStageEntry(ctx.mutation)?.
|
|
2142
|
-
if (entry === void 0)
|
|
2143
|
-
throw new Error(
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
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;
|
|
2152
2177
|
}
|
|
2153
|
-
function
|
|
2154
|
-
|
|
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;
|
|
2155
2180
|
}
|
|
2156
|
-
function
|
|
2157
|
-
|
|
2158
|
-
if (entry === void 0)
|
|
2181
|
+
function assertBareIdRootsCorrectly(id, cx) {
|
|
2182
|
+
if (!(cx.discoveryResource === void 0 || sameResource(cx.discoveryResource, cx.parentResource)))
|
|
2159
2183
|
throw new Error(
|
|
2160
|
-
`
|
|
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.`
|
|
2161
2185
|
);
|
|
2162
|
-
return entry;
|
|
2163
2186
|
}
|
|
2164
|
-
function
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
if (
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
}
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
case "self":
|
|
2176
|
-
return ctx.self;
|
|
2177
|
-
case "stage":
|
|
2178
|
-
return ctx.stage;
|
|
2179
|
-
case "fieldRead": {
|
|
2180
|
-
const value = readEntryFromMutation(ctx, src);
|
|
2181
|
-
return src.path !== void 0 ? getPath(value, src.path) : value;
|
|
2182
|
-
}
|
|
2183
|
-
case "object": {
|
|
2184
|
-
const out = {};
|
|
2185
|
-
for (const [field, fieldSrc] of Object.entries(src.fields))
|
|
2186
|
-
out[field] = resolveOpSource(fieldSrc, ctx);
|
|
2187
|
-
return out;
|
|
2188
|
-
}
|
|
2189
|
-
default:
|
|
2190
|
-
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" };
|
|
2191
2198
|
}
|
|
2199
|
+
return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
|
|
2192
2200
|
}
|
|
2193
|
-
function
|
|
2194
|
-
|
|
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;
|
|
2195
2207
|
}
|
|
2196
|
-
function
|
|
2197
|
-
const
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
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 };
|
|
2202
2258
|
}
|
|
2203
|
-
function
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
}
|
|
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
|
+
}));
|
|
2212
2267
|
}
|
|
2213
|
-
async function
|
|
2214
|
-
const { client, instanceId,
|
|
2268
|
+
async function invokeActivity(args) {
|
|
2269
|
+
const { client, instanceId, activity: activityName, options } = args, ctx = await loadContext(client, instanceId, {
|
|
2215
2270
|
...options?.clock ? { clock: options.clock } : {},
|
|
2216
2271
|
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
2217
2272
|
});
|
|
2218
|
-
return commitInvoke(ctx,
|
|
2273
|
+
return commitInvoke(ctx, activityName, options?.actor);
|
|
2219
2274
|
}
|
|
2220
|
-
async function commitInvoke(ctx,
|
|
2221
|
-
const {
|
|
2275
|
+
async function commitInvoke(ctx, activityName, actor) {
|
|
2276
|
+
const { activity } = findActivityInCurrentStage(ctx, activityName), entry = findCurrentActivities(ctx.instance).find((t) => t.name === activityName);
|
|
2222
2277
|
if (entry === void 0)
|
|
2223
|
-
throw new Error(
|
|
2278
|
+
throw new Error(
|
|
2279
|
+
`Activity "${activityName}" has no status entry on instance ${ctx.instance._id}`
|
|
2280
|
+
);
|
|
2224
2281
|
if (entry.status !== "pending")
|
|
2225
|
-
return { invoked: !1,
|
|
2226
|
-
const mutation = startMutation(ctx.instance), mutEntry =
|
|
2227
|
-
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 };
|
|
2228
2285
|
}
|
|
2229
|
-
async function
|
|
2286
|
+
async function activateActivity(ctx, mutation, activity, entry, actor) {
|
|
2230
2287
|
const now = ctx.now;
|
|
2231
|
-
if (entry.status = "active", entry.startedAt = now,
|
|
2288
|
+
if (entry.status = "active", entry.startedAt = now, activity.fields !== void 0 && activity.fields.length > 0) {
|
|
2232
2289
|
const stage = findStage(ctx.definition, mutation.currentStage);
|
|
2233
|
-
entry.fields = await
|
|
2290
|
+
entry.fields = await resolveActivityFieldEntries({
|
|
2234
2291
|
client: ctx.client,
|
|
2235
2292
|
instance: ctx.instance,
|
|
2236
2293
|
stage,
|
|
2237
|
-
|
|
2238
|
-
now
|
|
2294
|
+
activity,
|
|
2295
|
+
now,
|
|
2296
|
+
recordDiscard: recordFieldDiscards(mutation.history, "activity", now)
|
|
2239
2297
|
});
|
|
2240
2298
|
}
|
|
2241
2299
|
runOps({
|
|
2242
|
-
ops:
|
|
2300
|
+
ops: activity.ops,
|
|
2243
2301
|
mutation,
|
|
2244
2302
|
stage: mutation.currentStage,
|
|
2245
|
-
origin: {
|
|
2303
|
+
origin: { activity: activity.name },
|
|
2246
2304
|
params: {},
|
|
2247
2305
|
actor,
|
|
2248
2306
|
self: selfGdr(ctx.instance),
|
|
2249
2307
|
now
|
|
2250
2308
|
}), mutation.history.push({
|
|
2251
2309
|
_key: randomKey(),
|
|
2252
|
-
_type: "
|
|
2310
|
+
_type: "activityActivated",
|
|
2253
2311
|
at: now,
|
|
2254
2312
|
stage: mutation.currentStage,
|
|
2255
|
-
|
|
2313
|
+
activity: activity.name,
|
|
2256
2314
|
...actor !== void 0 ? { actor } : {}
|
|
2257
|
-
}), await queueEffects(
|
|
2258
|
-
|
|
2259
|
-
|
|
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
|
+
);
|
|
2260
2325
|
let pendingChildren;
|
|
2261
|
-
if (
|
|
2262
|
-
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);
|
|
2263
2328
|
entry.spawnedInstances = refs, pendingChildren = mutation.pendingCreates.slice(createsBefore).map((c) => ({ _id: c._id, stage: c.currentStage, status: "active" }));
|
|
2264
2329
|
for (const ref of refs)
|
|
2265
2330
|
mutation.history.push({
|
|
2266
2331
|
_key: randomKey(),
|
|
2267
2332
|
_type: "spawned",
|
|
2268
2333
|
at: now,
|
|
2269
|
-
|
|
2334
|
+
activity: activity.name,
|
|
2270
2335
|
instanceRef: ref
|
|
2271
2336
|
});
|
|
2272
2337
|
}
|
|
2273
|
-
await autoResolveOnActivate(ctx, mutation,
|
|
2338
|
+
await autoResolveOnActivate(ctx, mutation, activity, entry, now, pendingChildren) || resolveMachineStep(mutation, activity, entry, now);
|
|
2274
2339
|
}
|
|
2275
|
-
async function autoResolveOnActivate(ctx, mutation,
|
|
2276
|
-
if (
|
|
2277
|
-
const vars = pendingChildren !== void 0 ? { subworkflows: pendingChildren } : await
|
|
2278
|
-
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({
|
|
2279
2344
|
entry,
|
|
2280
2345
|
history: mutation.history,
|
|
2281
2346
|
stage: mutation.currentStage,
|
|
@@ -2284,29 +2349,31 @@ async function autoResolveOnActivate(ctx, mutation, task, entry, now, pendingChi
|
|
|
2284
2349
|
actor: gateActor(outcome)
|
|
2285
2350
|
}), !0);
|
|
2286
2351
|
}
|
|
2287
|
-
async function
|
|
2352
|
+
async function activityConditionVars(ctx, entry) {
|
|
2288
2353
|
return entry.spawnedInstances === void 0 || entry.spawnedInstances.length === 0 ? { subworkflows: [] } : { subworkflows: await subworkflowRows(ctx, entry.spawnedInstances) };
|
|
2289
2354
|
}
|
|
2290
|
-
function resolveMachineStep(mutation,
|
|
2291
|
-
const waitsForActor =
|
|
2292
|
-
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({
|
|
2293
2358
|
entry,
|
|
2294
2359
|
history: mutation.history,
|
|
2295
2360
|
stage: mutation.currentStage,
|
|
2296
2361
|
to: "done",
|
|
2297
2362
|
at: now,
|
|
2298
|
-
actor:
|
|
2363
|
+
actor: engineSystemActor("machineStep")
|
|
2299
2364
|
});
|
|
2300
2365
|
}
|
|
2301
|
-
async function
|
|
2366
|
+
async function buildStageActivities(ctx, stage) {
|
|
2302
2367
|
const entries = [];
|
|
2303
|
-
for (const
|
|
2304
|
-
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";
|
|
2305
2372
|
entries.push({
|
|
2306
2373
|
_key: randomKey(),
|
|
2307
|
-
name:
|
|
2374
|
+
name: activity.name,
|
|
2308
2375
|
status: inScope ? "pending" : "skipped",
|
|
2309
|
-
...
|
|
2376
|
+
...activity.filter !== void 0 ? { filterEvaluation: buildFilterEvaluation(ctx.now, activity.filter, outcome) } : {}
|
|
2310
2377
|
});
|
|
2311
2378
|
}
|
|
2312
2379
|
return entries;
|
|
@@ -2316,7 +2383,7 @@ function buildFilterEvaluation(at, filter, outcome) {
|
|
|
2316
2383
|
at,
|
|
2317
2384
|
truthy: !1,
|
|
2318
2385
|
unevaluable: !0,
|
|
2319
|
-
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.`
|
|
2320
2387
|
} : { at, truthy: outcome === "satisfied" };
|
|
2321
2388
|
}
|
|
2322
2389
|
function isTerminalStage(stage) {
|
|
@@ -2339,15 +2406,16 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
|
|
|
2339
2406
|
client: ctx.client,
|
|
2340
2407
|
instance: ctx.instance,
|
|
2341
2408
|
stage: nextStage,
|
|
2342
|
-
now: at
|
|
2409
|
+
now: at,
|
|
2410
|
+
recordDiscard: recordFieldDiscards(mutation.history, "stage", at)
|
|
2343
2411
|
}),
|
|
2344
|
-
|
|
2412
|
+
activities: await buildStageActivities(ctx, nextStage)
|
|
2345
2413
|
};
|
|
2346
2414
|
mutation.stages.push(nextStageEntry);
|
|
2347
|
-
for (const
|
|
2348
|
-
if (
|
|
2349
|
-
const entry = nextStageEntry.
|
|
2350
|
-
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);
|
|
2351
2419
|
}
|
|
2352
2420
|
isTerminalStage(nextStage) && (mutation.completedAt = at);
|
|
2353
2421
|
}
|
|
@@ -2471,15 +2539,22 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
2471
2539
|
instance,
|
|
2472
2540
|
definition,
|
|
2473
2541
|
...clock ? { clock } : {}
|
|
2474
|
-
}), now = ctx.now, initialStageEntry = {
|
|
2542
|
+
}), now = ctx.now, discards = [], initialStageEntry = {
|
|
2475
2543
|
_key: randomKey(),
|
|
2476
2544
|
name: stage.name,
|
|
2477
2545
|
enteredAt: now,
|
|
2478
|
-
fields: await resolveStageFieldEntries({
|
|
2479
|
-
|
|
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)
|
|
2480
2554
|
}, committed = await client.patch(instance._id).set({
|
|
2481
2555
|
stages: [initialStageEntry],
|
|
2482
|
-
lastChangedAt: now
|
|
2556
|
+
lastChangedAt: now,
|
|
2557
|
+
...discards.length > 0 ? { history: [...instance.history, ...discards] } : {}
|
|
2483
2558
|
}).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
|
|
2484
2559
|
await deployOrRollback({
|
|
2485
2560
|
client,
|
|
@@ -2498,12 +2573,20 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
2498
2573
|
stageName: stage.name,
|
|
2499
2574
|
now
|
|
2500
2575
|
})
|
|
2501
|
-
}), await
|
|
2576
|
+
}), await autoActivatePrimedActivities(
|
|
2577
|
+
client,
|
|
2578
|
+
instanceId,
|
|
2579
|
+
definition,
|
|
2580
|
+
stage,
|
|
2581
|
+
actor,
|
|
2582
|
+
clientForGdr,
|
|
2583
|
+
clock
|
|
2584
|
+
);
|
|
2502
2585
|
}
|
|
2503
|
-
async function
|
|
2586
|
+
async function autoActivatePrimedActivities(client, instanceId, definition, stage, actor, clientForGdr, clock) {
|
|
2504
2587
|
const resolvedClientForGdr = clientForGdr ?? (() => client);
|
|
2505
|
-
for (const
|
|
2506
|
-
if (
|
|
2588
|
+
for (const activity of stage.activities ?? []) {
|
|
2589
|
+
if (activity.activation !== "auto") continue;
|
|
2507
2590
|
const updated = await client.getDocument(instanceId);
|
|
2508
2591
|
if (!updated) continue;
|
|
2509
2592
|
const updatedCtx = await buildEngineContext({
|
|
@@ -2512,18 +2595,22 @@ async function autoActivatePrimedTasks(client, instanceId, definition, stage, ac
|
|
|
2512
2595
|
instance: updated,
|
|
2513
2596
|
definition,
|
|
2514
2597
|
...clock ? { clock } : {}
|
|
2515
|
-
}), mutation = startMutation(updated), mutEntry =
|
|
2516
|
-
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));
|
|
2517
2600
|
}
|
|
2518
2601
|
}
|
|
2519
2602
|
const CASCADE_LIMIT = 100;
|
|
2520
|
-
async function gatherAutoResolveCandidates(ctx,
|
|
2521
|
-
const
|
|
2522
|
-
for (const
|
|
2523
|
-
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);
|
|
2524
2607
|
if (entry?.status !== "active") continue;
|
|
2525
|
-
const outcome = await evaluateResolutionGate(
|
|
2526
|
-
|
|
2608
|
+
const outcome = await evaluateResolutionGate(
|
|
2609
|
+
ctx,
|
|
2610
|
+
activity,
|
|
2611
|
+
await activityConditionVars(ctx, entry)
|
|
2612
|
+
);
|
|
2613
|
+
outcome !== void 0 && candidates.push({ activity, outcome });
|
|
2527
2614
|
}
|
|
2528
2615
|
return candidates;
|
|
2529
2616
|
}
|
|
@@ -2532,17 +2619,17 @@ async function resolveCompleteWhen(client, instanceId, clientForGdr, clock, over
|
|
|
2532
2619
|
...clientForGdr ? { clientForGdr } : {},
|
|
2533
2620
|
...clock ? { clock } : {},
|
|
2534
2621
|
...overlay ? { overlay } : {}
|
|
2535
|
-
}), stage = findStage(ctx.definition, ctx.instance.currentStage),
|
|
2622
|
+
}), stage = findStage(ctx.definition, ctx.instance.currentStage), gatedActivities = (stage.activities ?? []).filter(
|
|
2536
2623
|
(t) => effectiveCompleteWhen(t) !== void 0 || t.failWhen !== void 0
|
|
2537
2624
|
);
|
|
2538
|
-
if (
|
|
2539
|
-
const candidates = await gatherAutoResolveCandidates(ctx,
|
|
2625
|
+
if (gatedActivities.length === 0) return 0;
|
|
2626
|
+
const candidates = await gatherAutoResolveCandidates(ctx, gatedActivities);
|
|
2540
2627
|
if (candidates.length === 0) return 0;
|
|
2541
2628
|
const mutation = startMutation(ctx.instance);
|
|
2542
2629
|
let count = 0;
|
|
2543
|
-
for (const {
|
|
2544
|
-
const mutEntry =
|
|
2545
|
-
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({
|
|
2546
2633
|
entry: mutEntry,
|
|
2547
2634
|
history: mutation.history,
|
|
2548
2635
|
stage: stage.name,
|
|
@@ -2575,9 +2662,9 @@ async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
|
|
|
2575
2662
|
if (!parent || parent._type !== WORKFLOW_INSTANCE_TYPE || typeof parent.definitionSnapshot != "string") return;
|
|
2576
2663
|
const definition = parseDefinitionSnapshot(parent), stage = definition.stages.find((s) => s.name === parent.currentStage);
|
|
2577
2664
|
if (stage === void 0) return;
|
|
2578
|
-
const
|
|
2579
|
-
if (
|
|
2580
|
-
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);
|
|
2581
2668
|
if (entry === void 0 || entry.status !== "active") return;
|
|
2582
2669
|
const ctx = await buildEngineContext({
|
|
2583
2670
|
client,
|
|
@@ -2585,23 +2672,27 @@ async function findResolvedParentSpawn(client, child, clientForGdr, clock) {
|
|
|
2585
2672
|
instance: parent,
|
|
2586
2673
|
definition,
|
|
2587
2674
|
...clock ? { clock } : {}
|
|
2588
|
-
}), outcome = await evaluateResolutionGate(
|
|
2675
|
+
}), outcome = await evaluateResolutionGate(
|
|
2676
|
+
ctx,
|
|
2677
|
+
activity,
|
|
2678
|
+
await activityConditionVars(ctx, entry)
|
|
2679
|
+
);
|
|
2589
2680
|
if (outcome !== void 0)
|
|
2590
|
-
return { parent, definition, stage,
|
|
2681
|
+
return { parent, definition, stage, activity, outcome };
|
|
2591
2682
|
}
|
|
2592
2683
|
async function propagateToAncestors(client, instanceId, actor, clientForGdr, clock) {
|
|
2593
2684
|
const instance = await client.getDocument(instanceId);
|
|
2594
2685
|
if (!instance) return;
|
|
2595
2686
|
const resolvedClientForGdr = clientForGdr ?? (() => client), found = await findResolvedParentSpawn(client, instance, resolvedClientForGdr, clock);
|
|
2596
2687
|
if (found === void 0) return;
|
|
2597
|
-
const { parent, definition, stage,
|
|
2688
|
+
const { parent, definition, stage, activity, outcome } = found, ctx = await buildEngineContext({
|
|
2598
2689
|
client,
|
|
2599
2690
|
clientForGdr: resolvedClientForGdr,
|
|
2600
2691
|
instance: parent,
|
|
2601
2692
|
definition,
|
|
2602
2693
|
...clock ? { clock } : {}
|
|
2603
|
-
}), mutation = startMutation(parent), mutEntry =
|
|
2604
|
-
mutEntry !== void 0 && (
|
|
2694
|
+
}), mutation = startMutation(parent), mutEntry = currentActivities(mutation).find((e) => e.name === activity.name);
|
|
2695
|
+
mutEntry !== void 0 && (applyActivityStatusChange({
|
|
2605
2696
|
entry: mutEntry,
|
|
2606
2697
|
history: mutation.history,
|
|
2607
2698
|
stage: stage.name,
|
|
@@ -2704,6 +2795,18 @@ async function fetchGrantsCached(requestFn, resourcePath) {
|
|
|
2704
2795
|
return;
|
|
2705
2796
|
}
|
|
2706
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
|
+
}
|
|
2707
2810
|
function effectiveEditable(baseline, override) {
|
|
2708
2811
|
if (baseline === void 0) return;
|
|
2709
2812
|
if (override === void 0) return baseline;
|
|
@@ -2715,14 +2818,15 @@ function slotSites(definition, stage) {
|
|
|
2715
2818
|
const sites = [];
|
|
2716
2819
|
for (const entry of definition.fields ?? []) sites.push({ scope: "workflow", entry });
|
|
2717
2820
|
for (const entry of stage.fields ?? []) sites.push({ scope: "stage", entry });
|
|
2718
|
-
for (const
|
|
2719
|
-
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 });
|
|
2720
2824
|
return sites;
|
|
2721
2825
|
}
|
|
2722
2826
|
function toResolvedSlot(site, stage) {
|
|
2723
2827
|
return {
|
|
2724
2828
|
scope: site.scope,
|
|
2725
|
-
...site.
|
|
2829
|
+
...site.activity !== void 0 ? { activity: site.activity } : {},
|
|
2726
2830
|
name: site.entry.name,
|
|
2727
2831
|
type: site.entry.type,
|
|
2728
2832
|
...site.entry.title !== void 0 ? { title: site.entry.title } : {},
|
|
@@ -2734,16 +2838,16 @@ function editableSlotsInStage(definition, stage) {
|
|
|
2734
2838
|
return slotSites(definition, stage).filter((site) => site.entry.editable !== void 0).map((site) => toResolvedSlot(site, stage));
|
|
2735
2839
|
}
|
|
2736
2840
|
function editTargetMatches(slot, target) {
|
|
2737
|
-
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;
|
|
2738
2842
|
}
|
|
2739
|
-
const SCOPE_PRECEDENCE = {
|
|
2843
|
+
const SCOPE_PRECEDENCE = { activity: 0, stage: 1, workflow: 2 };
|
|
2740
2844
|
function resolveEditTarget(args) {
|
|
2741
2845
|
const { definition, stage, target } = args, found = slotSites(definition, stage).filter(
|
|
2742
2846
|
(site) => editTargetMatches(
|
|
2743
2847
|
{
|
|
2744
2848
|
scope: site.scope,
|
|
2745
2849
|
name: site.entry.name,
|
|
2746
|
-
...site.
|
|
2850
|
+
...site.activity !== void 0 ? { activity: site.activity } : {}
|
|
2747
2851
|
},
|
|
2748
2852
|
target
|
|
2749
2853
|
)
|
|
@@ -2753,9 +2857,11 @@ function resolveEditTarget(args) {
|
|
|
2753
2857
|
function slotWindowOpen(instance, slot) {
|
|
2754
2858
|
if (instance.completedAt !== void 0) return { open: !1, detail: "instance completed" };
|
|
2755
2859
|
if (instance.abortedAt !== void 0) return { open: !1, detail: "instance aborted" };
|
|
2756
|
-
if (slot.scope !== "
|
|
2757
|
-
const status = findOpenStageEntry(instance)?.
|
|
2758
|
-
|
|
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"}` };
|
|
2759
2865
|
}
|
|
2760
2866
|
function readSlotValue(instance, slot) {
|
|
2761
2867
|
return slotFieldHost(instance, slot)?.find((e) => e.name === slot.name)?.value;
|
|
@@ -2763,7 +2869,7 @@ function readSlotValue(instance, slot) {
|
|
|
2763
2869
|
function slotFieldHost(instance, slot) {
|
|
2764
2870
|
if (slot.scope === "workflow") return instance.fields;
|
|
2765
2871
|
const stageEntry = findOpenStageEntry(instance);
|
|
2766
|
-
return slot.scope === "stage" ? stageEntry?.fields : stageEntry?.
|
|
2872
|
+
return slot.scope === "stage" ? stageEntry?.fields : stageEntry?.activities.find((t) => t.name === slot.activity)?.fields;
|
|
2767
2873
|
}
|
|
2768
2874
|
function slotProvenance(instance, ref) {
|
|
2769
2875
|
let latest;
|
|
@@ -2806,12 +2912,12 @@ async function evaluateFromSnapshot(args) {
|
|
|
2806
2912
|
throw new Error(
|
|
2807
2913
|
`Instance "${instance._id}" currentStage "${instance.currentStage}" not in definition`
|
|
2808
2914
|
);
|
|
2809
|
-
const scope = await renderScope({ instance, definition, actor, grants, snapshot, now }),
|
|
2810
|
-
for (const
|
|
2811
|
-
|
|
2812
|
-
await
|
|
2813
|
-
|
|
2814
|
-
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),
|
|
2815
2921
|
instance,
|
|
2816
2922
|
actor,
|
|
2817
2923
|
snapshot,
|
|
@@ -2836,9 +2942,9 @@ async function evaluateFromSnapshot(args) {
|
|
|
2836
2942
|
}
|
|
2837
2943
|
const currentStage = {
|
|
2838
2944
|
stage,
|
|
2839
|
-
|
|
2945
|
+
activities: activityEvaluations,
|
|
2840
2946
|
transitions: transitionEvaluations
|
|
2841
|
-
}, 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({
|
|
2842
2948
|
instance,
|
|
2843
2949
|
definition,
|
|
2844
2950
|
stage,
|
|
@@ -2878,7 +2984,7 @@ async function evaluateEditableSlots(args) {
|
|
|
2878
2984
|
}), value = readSlotValue(instance, slot);
|
|
2879
2985
|
slots.push({
|
|
2880
2986
|
scope: slot.scope,
|
|
2881
|
-
...slot.
|
|
2987
|
+
...slot.activity !== void 0 ? { activity: slot.activity } : {},
|
|
2882
2988
|
name: slot.name,
|
|
2883
2989
|
type: slot.type,
|
|
2884
2990
|
...slot.title !== void 0 ? { title: slot.title } : {},
|
|
@@ -2894,7 +3000,14 @@ async function editPredicateSatisfied(args) {
|
|
|
2894
3000
|
const { slot, window, guardDenial, instance, snapshot, scope, actor, roleAliases } = args;
|
|
2895
3001
|
if (!window.open || guardDenial !== void 0) return !1;
|
|
2896
3002
|
if (slot.effective === !0) return !0;
|
|
2897
|
-
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;
|
|
2898
3011
|
return evaluateCondition({ condition: slot.effective, snapshot, params });
|
|
2899
3012
|
}
|
|
2900
3013
|
async function renderScope(args) {
|
|
@@ -2922,20 +3035,20 @@ async function advisoryCan(instance, actor, grants) {
|
|
|
2922
3035
|
});
|
|
2923
3036
|
return can;
|
|
2924
3037
|
}
|
|
2925
|
-
function
|
|
2926
|
-
const { scope, instance, snapshot, actor,
|
|
3038
|
+
function activityScopeFor(args) {
|
|
3039
|
+
const { scope, instance, snapshot, actor, activityName, roleAliases } = args;
|
|
2927
3040
|
return {
|
|
2928
3041
|
...scope,
|
|
2929
3042
|
fields: {
|
|
2930
3043
|
...scope.fields,
|
|
2931
|
-
...scopedFieldOverlay(instance, snapshot,
|
|
3044
|
+
...scopedFieldOverlay(instance, snapshot, activityName)
|
|
2932
3045
|
},
|
|
2933
|
-
assigned: assignedFor(instance,
|
|
3046
|
+
assigned: assignedFor(instance, activityName, actor, roleAliases)
|
|
2934
3047
|
};
|
|
2935
3048
|
}
|
|
2936
|
-
async function
|
|
3049
|
+
async function evaluateActivity(args) {
|
|
2937
3050
|
const {
|
|
2938
|
-
|
|
3051
|
+
activity,
|
|
2939
3052
|
statusEntry,
|
|
2940
3053
|
instance,
|
|
2941
3054
|
actor,
|
|
@@ -2944,36 +3057,37 @@ async function evaluateTask(args) {
|
|
|
2944
3057
|
roleAliases,
|
|
2945
3058
|
stageHasExits,
|
|
2946
3059
|
guardDenial
|
|
2947
|
-
} = args, status = statusEntry?.status ?? "pending",
|
|
3060
|
+
} = args, status = statusEntry?.status ?? "pending", activityScope = activityScopeFor({
|
|
2948
3061
|
scope,
|
|
2949
3062
|
instance,
|
|
2950
3063
|
snapshot,
|
|
2951
3064
|
actor,
|
|
2952
|
-
|
|
3065
|
+
activityName: activity.name,
|
|
2953
3066
|
roleAliases
|
|
2954
|
-
}), assigned =
|
|
2955
|
-
requirements:
|
|
3067
|
+
}), assigned = activityScope.assigned === !0, unmetRequirements = await evaluateRequirements({
|
|
3068
|
+
requirements: activity.requirements,
|
|
2956
3069
|
snapshot,
|
|
2957
|
-
params:
|
|
3070
|
+
params: activityScope
|
|
2958
3071
|
}), requirementsReason = unmetRequirements.length > 0 ? { kind: "requirements-unmet", unmetRequirements } : void 0, actions = [];
|
|
2959
|
-
for (const action of
|
|
3072
|
+
for (const action of activity.actions ?? [])
|
|
2960
3073
|
actions.push(
|
|
2961
3074
|
await evaluateAction({
|
|
2962
3075
|
action,
|
|
2963
3076
|
status,
|
|
2964
3077
|
instance,
|
|
2965
3078
|
snapshot,
|
|
2966
|
-
|
|
3079
|
+
activityScope,
|
|
2967
3080
|
stageHasExits,
|
|
2968
3081
|
guardDenial,
|
|
2969
3082
|
requirementsReason
|
|
2970
3083
|
})
|
|
2971
3084
|
);
|
|
2972
3085
|
return {
|
|
2973
|
-
|
|
3086
|
+
activity,
|
|
2974
3087
|
status,
|
|
3088
|
+
kind: activityKind(activity),
|
|
2975
3089
|
// "pending on actor" treats both `pending` and `active` as waiting on
|
|
2976
|
-
// the assignee — pending
|
|
3090
|
+
// the assignee — pending activities just haven't been activated yet, but
|
|
2977
3091
|
// they're still inbox items. The inbox reads the assignees-kind field
|
|
2978
3092
|
// entry BY KIND (who-data is fields).
|
|
2979
3093
|
pendingOnActor: (status === "active" || status === "pending") && assigned,
|
|
@@ -2987,7 +3101,7 @@ async function evaluateAction(args) {
|
|
|
2987
3101
|
status,
|
|
2988
3102
|
instance,
|
|
2989
3103
|
snapshot,
|
|
2990
|
-
|
|
3104
|
+
activityScope,
|
|
2991
3105
|
stageHasExits,
|
|
2992
3106
|
guardDenial,
|
|
2993
3107
|
requirementsReason
|
|
@@ -2995,7 +3109,7 @@ async function evaluateAction(args) {
|
|
|
2995
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({
|
|
2996
3110
|
condition: action.filter,
|
|
2997
3111
|
snapshot,
|
|
2998
|
-
params:
|
|
3112
|
+
params: activityScope
|
|
2999
3113
|
}) ? disabled(action, { kind: "filter-failed", filter: action.filter }) : { action, allowed: !0 };
|
|
3000
3114
|
}
|
|
3001
3115
|
async function instanceGuardReason(instance, actor, guards) {
|
|
@@ -3013,8 +3127,8 @@ function lifecycleReason(instance, status, stageHasExits) {
|
|
|
3013
3127
|
return { kind: "instance-completed", completedAt: instance.completedAt };
|
|
3014
3128
|
if (!stageHasExits)
|
|
3015
3129
|
return { kind: "stage-terminal", stage: instance.currentStage };
|
|
3016
|
-
if (
|
|
3017
|
-
return { kind: "
|
|
3130
|
+
if (isTerminalActivityStatus(status))
|
|
3131
|
+
return { kind: "activity-not-active", status };
|
|
3018
3132
|
}
|
|
3019
3133
|
function disabled(action, reason) {
|
|
3020
3134
|
return { action, allowed: !1, disabledReason: reason };
|
|
@@ -3022,37 +3136,30 @@ function disabled(action, reason) {
|
|
|
3022
3136
|
function definitionDocId(_workflowResource, tag, definition, version) {
|
|
3023
3137
|
return `${tag}.${definition}.v${version}`;
|
|
3024
3138
|
}
|
|
3025
|
-
async function sortByDependencies(client, definitions, tag
|
|
3139
|
+
async function sortByDependencies(client, definitions, tag) {
|
|
3026
3140
|
const byName = /* @__PURE__ */ new Map();
|
|
3027
3141
|
for (const def of definitions) {
|
|
3028
|
-
|
|
3029
|
-
|
|
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);
|
|
3030
3147
|
}
|
|
3031
3148
|
const findInBatch = (ref) => findRefInBatch(byName, ref);
|
|
3032
|
-
return await assertCrossBatchRefsDeployed(client, definitions, {
|
|
3033
|
-
tag,
|
|
3034
|
-
workflowResource,
|
|
3035
|
-
findInBatch
|
|
3036
|
-
}), topoSortDefinitions(definitions, { workflowResource, tag, findInBatch });
|
|
3149
|
+
return await assertCrossBatchRefsDeployed(client, definitions, { tag, findInBatch }), topoSortDefinitions(definitions, { findInBatch });
|
|
3037
3150
|
}
|
|
3038
3151
|
function findRefInBatch(byName, ref) {
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
return typeof ref.version == "number" ? candidates.find((c) => c.version === ref.version) : candidates.reduce(
|
|
3042
|
-
(best, c) => best === void 0 || c.version > best.version ? c : best,
|
|
3043
|
-
void 0
|
|
3044
|
-
);
|
|
3152
|
+
if (typeof ref.version != "number")
|
|
3153
|
+
return byName.get(ref.name);
|
|
3045
3154
|
}
|
|
3046
3155
|
async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
|
|
3047
3156
|
const missing = [];
|
|
3048
|
-
for (const def of definitions)
|
|
3049
|
-
const fromId = definitionDocId(ctx.workflowResource, ctx.tag, def.name, def.version);
|
|
3157
|
+
for (const def of definitions)
|
|
3050
3158
|
for (const ref of refsOf(def)) {
|
|
3051
3159
|
if (ctx.findInBatch(ref) !== void 0) continue;
|
|
3052
3160
|
const label = await resolveDeployedRefLabel(client, ref, ctx.tag);
|
|
3053
|
-
label !== void 0 && missing.push({ from:
|
|
3161
|
+
label !== void 0 && missing.push({ from: def.name, ref: label });
|
|
3054
3162
|
}
|
|
3055
|
-
}
|
|
3056
3163
|
if (missing.length === 0) return;
|
|
3057
3164
|
const lines = missing.map((m) => ` - ${m.from} \u2192 ${m.ref} (neither in batch nor deployed)`);
|
|
3058
3165
|
throw new Error(
|
|
@@ -3071,16 +3178,15 @@ async function resolveDeployedRefLabel(client, ref, tag) {
|
|
|
3071
3178
|
}
|
|
3072
3179
|
function topoSortDefinitions(definitions, ctx) {
|
|
3073
3180
|
const visited = /* @__PURE__ */ new Set(), visiting = /* @__PURE__ */ new Set(), ordered = [], visit = (def) => {
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
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);
|
|
3079
3185
|
for (const ref of refsOf(def)) {
|
|
3080
3186
|
const child = ctx.findInBatch(ref);
|
|
3081
3187
|
child !== void 0 && visit(child);
|
|
3082
3188
|
}
|
|
3083
|
-
visiting.delete(
|
|
3189
|
+
visiting.delete(def.name), visited.add(def.name), ordered.push(def);
|
|
3084
3190
|
}
|
|
3085
3191
|
};
|
|
3086
3192
|
for (const def of definitions) visit(def);
|
|
@@ -3089,8 +3195,8 @@ function topoSortDefinitions(definitions, ctx) {
|
|
|
3089
3195
|
function refsOf(def) {
|
|
3090
3196
|
const out = [];
|
|
3091
3197
|
for (const stage of def.stages)
|
|
3092
|
-
for (const
|
|
3093
|
-
const ref =
|
|
3198
|
+
for (const activity of stage.activities ?? []) {
|
|
3199
|
+
const ref = activity.subworkflows?.definition;
|
|
3094
3200
|
ref !== void 0 && out.push({
|
|
3095
3201
|
name: ref.name,
|
|
3096
3202
|
...ref.version !== void 0 ? { version: ref.version } : {}
|
|
@@ -3098,8 +3204,23 @@ function refsOf(def) {
|
|
|
3098
3204
|
}
|
|
3099
3205
|
return out;
|
|
3100
3206
|
}
|
|
3101
|
-
function
|
|
3102
|
-
|
|
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 };
|
|
3103
3224
|
}
|
|
3104
3225
|
function stripSystemFields(doc) {
|
|
3105
3226
|
const out = {};
|
|
@@ -3114,22 +3235,26 @@ function stableStringify(value) {
|
|
|
3114
3235
|
}
|
|
3115
3236
|
async function loadDefinition(client, definition, version, tag) {
|
|
3116
3237
|
if (version !== void 0) {
|
|
3117
|
-
const doc = await client.fetch(
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3238
|
+
const doc = await client.fetch(definitionLookupGroq(!0), {
|
|
3239
|
+
definition,
|
|
3240
|
+
version,
|
|
3241
|
+
tag
|
|
3242
|
+
});
|
|
3121
3243
|
if (!doc)
|
|
3122
3244
|
throw new Error(`Workflow definition ${definition} v${version} not deployed`);
|
|
3123
3245
|
return doc;
|
|
3124
3246
|
}
|
|
3125
|
-
const latest = await client
|
|
3126
|
-
definition,
|
|
3127
|
-
tag
|
|
3128
|
-
});
|
|
3247
|
+
const latest = await loadLatestDeployed(client, definition, tag);
|
|
3129
3248
|
if (!latest)
|
|
3130
3249
|
throw new Error(`No deployed definition for workflow ${definition}`);
|
|
3131
3250
|
return latest;
|
|
3132
3251
|
}
|
|
3252
|
+
async function loadLatestDeployed(client, definition, tag) {
|
|
3253
|
+
return await client.fetch(definitionLookupGroq(!1), {
|
|
3254
|
+
definition,
|
|
3255
|
+
tag
|
|
3256
|
+
}) ?? void 0;
|
|
3257
|
+
}
|
|
3133
3258
|
async function loadDefinitionVersions(client, definition, tag) {
|
|
3134
3259
|
return client.fetch(
|
|
3135
3260
|
`*[_type == "${WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}] | order(version desc)`,
|
|
@@ -3176,63 +3301,63 @@ async function commitAbort(ctx, reason, actor) {
|
|
|
3176
3301
|
}), { fired: !0, stage: stage.name };
|
|
3177
3302
|
}
|
|
3178
3303
|
async function fireAction(args) {
|
|
3179
|
-
const { client, instanceId,
|
|
3304
|
+
const { client, instanceId, activity, action, params, options } = args;
|
|
3180
3305
|
for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
|
|
3181
3306
|
const ctx = await loadCallContext(client, instanceId, options);
|
|
3182
3307
|
try {
|
|
3183
|
-
return await commitAction(ctx,
|
|
3308
|
+
return await commitAction(ctx, activity, action, params, options);
|
|
3184
3309
|
} catch (error) {
|
|
3185
3310
|
if (!isRevisionConflict(error)) throw error;
|
|
3186
3311
|
}
|
|
3187
3312
|
}
|
|
3188
3313
|
throw new ConcurrentFireActionError({
|
|
3189
3314
|
instanceId,
|
|
3190
|
-
|
|
3315
|
+
activity,
|
|
3191
3316
|
action,
|
|
3192
3317
|
attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
|
|
3193
3318
|
});
|
|
3194
3319
|
}
|
|
3195
|
-
async function resolveActionCommit(ctx,
|
|
3196
|
-
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);
|
|
3197
3322
|
if (action === void 0)
|
|
3198
|
-
throw new Error(`Action "${actionName}" not declared on
|
|
3323
|
+
throw new Error(`Action "${actionName}" not declared on activity "${activityName}"`);
|
|
3199
3324
|
const can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan(ctx.instance, actor, options.grants) : void 0;
|
|
3200
3325
|
if (!await ctxEvaluateCondition(ctx, action.filter, {
|
|
3201
|
-
|
|
3326
|
+
activityName,
|
|
3202
3327
|
...actor !== void 0 ? { actor } : {},
|
|
3203
3328
|
...can !== void 0 ? { vars: { can } } : {}
|
|
3204
3329
|
}))
|
|
3205
|
-
throw new Error(`Action "${actionName}" filter rejected on
|
|
3206
|
-
const entry =
|
|
3330
|
+
throw new Error(`Action "${actionName}" filter rejected on activity "${activityName}"`);
|
|
3331
|
+
const entry = findCurrentActivities(ctx.instance).find((t) => t.name === activityName);
|
|
3207
3332
|
if (entry === void 0 || entry.status !== "active")
|
|
3208
3333
|
throw new Error(
|
|
3209
|
-
`
|
|
3334
|
+
`Activity "${activityName}" must be active to fire action "${actionName}"; status is ${entry?.status ?? "missing"}`
|
|
3210
3335
|
);
|
|
3211
|
-
const params = validateActionParams(action,
|
|
3212
|
-
return { stage,
|
|
3336
|
+
const params = validateActionParams(action, activityName, callerParams);
|
|
3337
|
+
return { stage, activity, action, params };
|
|
3213
3338
|
}
|
|
3214
|
-
async function commitAction(ctx,
|
|
3339
|
+
async function commitAction(ctx, activityName, actionName, callerParams, options) {
|
|
3215
3340
|
const actor = options?.actor, { stage, action, params } = await resolveActionCommit(
|
|
3216
3341
|
ctx,
|
|
3217
|
-
|
|
3342
|
+
activityName,
|
|
3218
3343
|
actionName,
|
|
3219
3344
|
callerParams,
|
|
3220
3345
|
options
|
|
3221
|
-
), mutation = startMutation(ctx.instance), mutEntry =
|
|
3346
|
+
), mutation = startMutation(ctx.instance), mutEntry = requireMutationActivityEntry(mutation, activityName), now = ctx.now;
|
|
3222
3347
|
mutation.history.push({
|
|
3223
3348
|
_key: randomKey(),
|
|
3224
3349
|
_type: "actionFired",
|
|
3225
3350
|
at: now,
|
|
3226
3351
|
stage: stage.name,
|
|
3227
|
-
|
|
3352
|
+
activity: activityName,
|
|
3228
3353
|
action: actionName,
|
|
3229
|
-
...actor !== void 0 ? { actor } : {}
|
|
3354
|
+
...actor !== void 0 ? { actor, driverKind: driverKind(actor) } : {}
|
|
3230
3355
|
});
|
|
3231
3356
|
const statusBefore = mutEntry.status, ranOps = await runOps({
|
|
3232
3357
|
ops: action.ops,
|
|
3233
3358
|
mutation,
|
|
3234
3359
|
stage: stage.name,
|
|
3235
|
-
origin: {
|
|
3360
|
+
origin: { activity: activityName, action: actionName },
|
|
3236
3361
|
params,
|
|
3237
3362
|
actor,
|
|
3238
3363
|
self: selfGdr(ctx.instance),
|
|
@@ -3240,14 +3365,14 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
|
|
|
3240
3365
|
}), newStatus = mutEntry.status !== statusBefore ? mutEntry.status : void 0;
|
|
3241
3366
|
return await queueEffects(ctx, mutation, action.effects, { kind: "action", name: actionName }, actor, {
|
|
3242
3367
|
callerParams: params,
|
|
3243
|
-
|
|
3368
|
+
activityName
|
|
3244
3369
|
}), await persistThenDeploy(
|
|
3245
3370
|
ctx,
|
|
3246
3371
|
mutation,
|
|
3247
3372
|
() => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
|
|
3248
3373
|
), {
|
|
3249
3374
|
fired: !0,
|
|
3250
|
-
|
|
3375
|
+
activity: activityName,
|
|
3251
3376
|
action: actionName,
|
|
3252
3377
|
...newStatus !== void 0 ? { newStatus } : {},
|
|
3253
3378
|
...ranOps.length > 0 ? { ranOps } : {}
|
|
@@ -3255,23 +3380,23 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
|
|
|
3255
3380
|
}
|
|
3256
3381
|
class ActionDisabledError extends Error {
|
|
3257
3382
|
reason;
|
|
3258
|
-
|
|
3383
|
+
activity;
|
|
3259
3384
|
action;
|
|
3260
3385
|
constructor(args) {
|
|
3261
|
-
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;
|
|
3262
3387
|
}
|
|
3263
3388
|
}
|
|
3264
3389
|
const disabledReasonDetail = {
|
|
3265
3390
|
"filter-failed": (r) => `action filter returned false${r.detail ? ` (${r.detail})` : ""}`,
|
|
3266
|
-
"
|
|
3391
|
+
"activity-not-active": (r) => `activity status is "${r.status}"`,
|
|
3267
3392
|
"stage-terminal": (r) => `stage "${r.stage}" is terminal`,
|
|
3268
3393
|
"instance-completed": (r) => `instance completed at ${r.completedAt}`,
|
|
3269
3394
|
"mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`,
|
|
3270
3395
|
"requirements-unmet": (r) => `unmet requirement(s): ${r.unmetRequirements.join(", ")}`
|
|
3271
3396
|
};
|
|
3272
|
-
function formatDisabledReason(
|
|
3397
|
+
function formatDisabledReason(activity, action, reason) {
|
|
3273
3398
|
const detail = disabledReasonDetail[reason.kind](reason);
|
|
3274
|
-
return `Action "${
|
|
3399
|
+
return `Action "${activity}:${action}" is not allowed: ${detail}`;
|
|
3275
3400
|
}
|
|
3276
3401
|
class EditFieldDeniedError extends Error {
|
|
3277
3402
|
reason;
|
|
@@ -3290,7 +3415,7 @@ const editDisabledReasonDetail = {
|
|
|
3290
3415
|
function formatEditDisabledReason(target, reason) {
|
|
3291
3416
|
const detail = editDisabledReasonDetail[reason.kind](
|
|
3292
3417
|
reason
|
|
3293
|
-
), where = target.
|
|
3418
|
+
), where = target.activity !== void 0 ? `${target.activity}.${target.field}` : target.field;
|
|
3294
3419
|
return `Field "${target.scope}:${where}" is not editable: ${detail}`;
|
|
3295
3420
|
}
|
|
3296
3421
|
async function editField(args) {
|
|
@@ -3322,7 +3447,7 @@ async function commitEdit(ctx, target, mode, value, options) {
|
|
|
3322
3447
|
stage: stage.name,
|
|
3323
3448
|
origin: {
|
|
3324
3449
|
edit: !0,
|
|
3325
|
-
...slot.scope === "
|
|
3450
|
+
...slot.scope === "activity" && slot.activity !== void 0 ? { activity: slot.activity } : {}
|
|
3326
3451
|
},
|
|
3327
3452
|
params: {},
|
|
3328
3453
|
actor,
|
|
@@ -3341,7 +3466,7 @@ async function commitEdit(ctx, target, mode, value, options) {
|
|
|
3341
3466
|
}
|
|
3342
3467
|
async function assertSlotEditable(ctx, slot, options) {
|
|
3343
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, {
|
|
3344
|
-
...slot.
|
|
3469
|
+
...slot.activity !== void 0 ? { activityName: slot.activity } : {},
|
|
3345
3470
|
...actor !== void 0 ? { actor } : {},
|
|
3346
3471
|
...can !== void 0 ? { vars: { can } } : {}
|
|
3347
3472
|
}) : slot.effective === !0, reason = editDisabledReason({
|
|
@@ -3367,18 +3492,18 @@ function slotTarget(slot) {
|
|
|
3367
3492
|
return {
|
|
3368
3493
|
scope: slot.scope,
|
|
3369
3494
|
field: slot.name,
|
|
3370
|
-
...slot.
|
|
3495
|
+
...slot.activity !== void 0 ? { activity: slot.activity } : {}
|
|
3371
3496
|
};
|
|
3372
3497
|
}
|
|
3373
3498
|
function labelTarget(target) {
|
|
3374
3499
|
return {
|
|
3375
|
-
scope: target.scope ?? (target.
|
|
3500
|
+
scope: target.scope ?? (target.activity !== void 0 ? "activity" : "workflow"),
|
|
3376
3501
|
field: target.field,
|
|
3377
|
-
...target.
|
|
3502
|
+
...target.activity !== void 0 ? { activity: target.activity } : {}
|
|
3378
3503
|
};
|
|
3379
3504
|
}
|
|
3380
3505
|
function describeTarget(target) {
|
|
3381
|
-
const where = target.
|
|
3506
|
+
const where = target.activity !== void 0 ? `${target.activity}.${target.field}` : target.field;
|
|
3382
3507
|
return target.scope !== void 0 ? `${target.scope}:${where}` : where;
|
|
3383
3508
|
}
|
|
3384
3509
|
function bareIdFromSpawnRef(uri) {
|
|
@@ -3443,10 +3568,10 @@ async function dispatchGatedWrite(args) {
|
|
|
3443
3568
|
...ranOps !== void 0 ? { ranOps } : {}
|
|
3444
3569
|
};
|
|
3445
3570
|
}
|
|
3446
|
-
function assertActionAllowed(evaluation,
|
|
3447
|
-
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);
|
|
3448
3573
|
if (actionEval?.allowed === !1 && actionEval.disabledReason)
|
|
3449
|
-
throw new ActionDisabledError({
|
|
3574
|
+
throw new ActionDisabledError({ activity, action, reason: actionEval.disabledReason });
|
|
3450
3575
|
}
|
|
3451
3576
|
function assertEditAllowed(evaluation, target) {
|
|
3452
3577
|
const slot = evaluation.editableSlots.filter((s) => editTargetMatches(s, target)).sort((a, b) => SCOPE_PRECEDENCE[a.scope] - SCOPE_PRECEDENCE[b.scope])[0];
|
|
@@ -3455,7 +3580,7 @@ function assertEditAllowed(evaluation, target) {
|
|
|
3455
3580
|
target: {
|
|
3456
3581
|
scope: slot.scope,
|
|
3457
3582
|
field: slot.name,
|
|
3458
|
-
...slot.
|
|
3583
|
+
...slot.activity !== void 0 ? { activity: slot.activity } : {}
|
|
3459
3584
|
},
|
|
3460
3585
|
reason: slot.disabledReason
|
|
3461
3586
|
});
|
|
@@ -3480,11 +3605,11 @@ function buildEngineCallOptions(args) {
|
|
|
3480
3605
|
};
|
|
3481
3606
|
}
|
|
3482
3607
|
async function applyAction(args) {
|
|
3483
|
-
const { client, instance,
|
|
3484
|
-
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({
|
|
3485
3610
|
client,
|
|
3486
3611
|
instanceId: instance._id,
|
|
3487
|
-
|
|
3612
|
+
activity,
|
|
3488
3613
|
action,
|
|
3489
3614
|
...params !== void 0 ? { params } : {},
|
|
3490
3615
|
options
|
|
@@ -3564,14 +3689,16 @@ function previewIds(ids) {
|
|
|
3564
3689
|
}
|
|
3565
3690
|
const workflow = {
|
|
3566
3691
|
/**
|
|
3567
|
-
* Deploy a set of workflow definitions as one call.
|
|
3568
|
-
*
|
|
3569
|
-
*
|
|
3570
|
-
*
|
|
3571
|
-
* (
|
|
3572
|
-
*
|
|
3573
|
-
*
|
|
3574
|
-
*
|
|
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`).
|
|
3575
3702
|
*
|
|
3576
3703
|
* Refs may point inside the batch OR at already-deployed definitions
|
|
3577
3704
|
* in the lake — both are valid. A ref pointing at neither errors with
|
|
@@ -3584,31 +3711,15 @@ const workflow = {
|
|
|
3584
3711
|
validateTag(tag);
|
|
3585
3712
|
for (const def of definitions)
|
|
3586
3713
|
validateDefinition(def);
|
|
3587
|
-
const ordered = await sortByDependencies(client, definitions, tag
|
|
3714
|
+
const ordered = await sortByDependencies(client, definitions, tag), results = [], tx = client.transaction();
|
|
3588
3715
|
let hasWrites = !1;
|
|
3589
3716
|
for (const def of ordered) {
|
|
3590
|
-
const
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
_type: WORKFLOW_DEFINITION_TYPE,
|
|
3594
|
-
tag
|
|
3595
|
-
}, existing = await client.getDocument(id);
|
|
3596
|
-
if (existing && existing.tag === tag && isDefinitionUnchanged(existing, expected)) {
|
|
3597
|
-
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" });
|
|
3598
3720
|
continue;
|
|
3599
3721
|
}
|
|
3600
|
-
|
|
3601
|
-
const expectedKeys = new Set(Object.keys(expected)), toUnset = Object.keys(existing).filter(
|
|
3602
|
-
(k) => !k.startsWith("_") && !expectedKeys.has(k)
|
|
3603
|
-
), patch = client.patch(id).set(expected).ifRevisionId(existing._rev);
|
|
3604
|
-
toUnset.length > 0 && patch.unset(toUnset), tx.patch(patch);
|
|
3605
|
-
} else
|
|
3606
|
-
tx.create(expected);
|
|
3607
|
-
hasWrites = !0, results.push({
|
|
3608
|
-
definition: def.name,
|
|
3609
|
-
version: def.version,
|
|
3610
|
-
status: existing ? "updated" : "created"
|
|
3611
|
-
});
|
|
3722
|
+
tx.create(plan.document), hasWrites = !0, results.push({ definition: def.name, version: plan.version, status: "created" });
|
|
3612
3723
|
}
|
|
3613
3724
|
return hasWrites && await tx.commit(), { results };
|
|
3614
3725
|
},
|
|
@@ -3624,8 +3735,8 @@ const workflow = {
|
|
|
3624
3735
|
* Spawn a new workflow instance from a deployed definition.
|
|
3625
3736
|
*
|
|
3626
3737
|
* Pins the snapshot at start-time, seeds `effectsContext`, enters
|
|
3627
|
-
* the initial stage (which builds
|
|
3628
|
-
* effects, auto-activates
|
|
3738
|
+
* the initial stage (which builds activityStatus, queues onEnter
|
|
3739
|
+
* effects, auto-activates activities). Then cascades auto-transitions
|
|
3629
3740
|
* until stable. Returns the resulting instance.
|
|
3630
3741
|
*/
|
|
3631
3742
|
startInstance: async (args) => {
|
|
@@ -3645,7 +3756,7 @@ const workflow = {
|
|
|
3645
3756
|
throw new Error(
|
|
3646
3757
|
`Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
|
|
3647
3758
|
);
|
|
3648
|
-
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedFields = await resolveDeclaredFields({
|
|
3759
|
+
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], fieldDiscards = [], resolvedFields = await resolveDeclaredFields({
|
|
3649
3760
|
entryDefs: definition.fields ?? [],
|
|
3650
3761
|
initialFields: initialFields ?? [],
|
|
3651
3762
|
ctx: {
|
|
@@ -3655,7 +3766,8 @@ const workflow = {
|
|
|
3655
3766
|
tag,
|
|
3656
3767
|
workflowResource,
|
|
3657
3768
|
definitionName: definition.name,
|
|
3658
|
-
...perspective !== void 0 ? { perspective } : {}
|
|
3769
|
+
...perspective !== void 0 ? { perspective } : {},
|
|
3770
|
+
recordDiscard: recordFieldDiscards(fieldDiscards, "workflow", now)
|
|
3659
3771
|
},
|
|
3660
3772
|
randomKey
|
|
3661
3773
|
}), effectivePerspective = perspective ?? derivePerspectiveFromFields(resolvedFields), base = buildInstanceBase({
|
|
@@ -3665,18 +3777,20 @@ const workflow = {
|
|
|
3665
3777
|
workflowResource,
|
|
3666
3778
|
definitionName: definition.name,
|
|
3667
3779
|
pinnedVersion: definition.version,
|
|
3780
|
+
pinnedContentHash: definition.contentHash,
|
|
3668
3781
|
definition,
|
|
3669
3782
|
fields: resolvedFields,
|
|
3670
3783
|
effectsContext: effectsContextEntries,
|
|
3671
3784
|
ancestors: ancestors ?? [],
|
|
3672
3785
|
perspective: effectivePerspective,
|
|
3673
3786
|
initialStage: definition.initialStage,
|
|
3674
|
-
actor
|
|
3787
|
+
actor,
|
|
3788
|
+
...fieldDiscards.length > 0 ? { extraHistory: fieldDiscards } : {}
|
|
3675
3789
|
});
|
|
3676
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);
|
|
3677
3791
|
},
|
|
3678
3792
|
/**
|
|
3679
|
-
* Fire an action against
|
|
3793
|
+
* Fire an action against an activity. If the activity is pending, it is
|
|
3680
3794
|
* auto-invoked first (pending → active) so callers don't have to know
|
|
3681
3795
|
* the lifecycle. Cascades auto-transitions and propagates to ancestors
|
|
3682
3796
|
* after the action commits.
|
|
@@ -3690,13 +3804,13 @@ const workflow = {
|
|
|
3690
3804
|
client,
|
|
3691
3805
|
tag,
|
|
3692
3806
|
instanceId,
|
|
3693
|
-
|
|
3807
|
+
activity,
|
|
3694
3808
|
action,
|
|
3695
3809
|
params,
|
|
3696
3810
|
idempotent,
|
|
3697
3811
|
resourceClients
|
|
3698
3812
|
} = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
|
|
3699
|
-
return findOpenStageEntry(before)?.
|
|
3813
|
+
return findOpenStageEntry(before)?.activities.find((t) => t.name === activity) === void 0 && idempotent === !0 ? { instance: before, cascaded: 0, fired: !1 } : dispatchGatedWrite({
|
|
3700
3814
|
client,
|
|
3701
3815
|
tag,
|
|
3702
3816
|
instanceId,
|
|
@@ -3706,10 +3820,10 @@ const workflow = {
|
|
|
3706
3820
|
clock,
|
|
3707
3821
|
before,
|
|
3708
3822
|
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3709
|
-
apply: (instance, evaluation) => (assertActionAllowed(evaluation,
|
|
3823
|
+
apply: (instance, evaluation) => (assertActionAllowed(evaluation, activity, action), applyAction({
|
|
3710
3824
|
client,
|
|
3711
3825
|
instance,
|
|
3712
|
-
|
|
3826
|
+
activity,
|
|
3713
3827
|
action,
|
|
3714
3828
|
actor,
|
|
3715
3829
|
...access.grants !== void 0 ? { grants: access.grants } : {},
|
|
@@ -3790,7 +3904,7 @@ const workflow = {
|
|
|
3790
3904
|
* Re-evaluate auto-transitions and resolve due waits until stable.
|
|
3791
3905
|
*
|
|
3792
3906
|
* Used by the runtime after any event that might affect the workflow
|
|
3793
|
-
* but isn't itself
|
|
3907
|
+
* but isn't itself an activity action: a subject doc was patched, a sibling
|
|
3794
3908
|
* workflow completed, the clock crossed a `completeWhen` deadline, etc.
|
|
3795
3909
|
* The runtime doesn't need to know what changed — it just nudges
|
|
3796
3910
|
* affected instances and the engine re-evaluates.
|
|
@@ -3900,8 +4014,8 @@ const workflow = {
|
|
|
3900
4014
|
* declared by a `doc.ref` / `doc.refs` entry in scope), then
|
|
3901
4015
|
* evaluates the supplied GROQ in groq-js against that dataset. The
|
|
3902
4016
|
* rendered scope is auto-bound: `$self`, `$fields`, `$parent`,
|
|
3903
|
-
* `$ancestors`, `$stage`, `$now`, `$effects`, `$
|
|
3904
|
-
* `$
|
|
4017
|
+
* `$ancestors`, `$stage`, `$now`, `$effects`, `$activities`,
|
|
4018
|
+
* `$allActivitiesDone`, `$anyActivityFailed` — ids in GDR URI form to match
|
|
3905
4019
|
* the snapshot's keying.
|
|
3906
4020
|
*
|
|
3907
4021
|
* Use when an external consumer (a UI, a debug pane, a test) wants
|
|
@@ -3954,34 +4068,83 @@ const workflow = {
|
|
|
3954
4068
|
* List the actions an actor could fire on an instance's current stage,
|
|
3955
4069
|
* each flagged `allowed` (with a structured `disabledReason` when not).
|
|
3956
4070
|
* Projects the instance from the actor's perspective and flattens its
|
|
3957
|
-
*
|
|
4071
|
+
* activities' actions. Returns the evaluation alongside the actions so a
|
|
3958
4072
|
* consumer can read the instance/stage context. Pure read.
|
|
3959
4073
|
*/
|
|
3960
4074
|
availableActions: async (args) => {
|
|
3961
4075
|
const evaluation = await evaluateInstance(args);
|
|
3962
|
-
return { evaluation, actions: availableActions(evaluation.currentStage.
|
|
4076
|
+
return { evaluation, actions: availableActions(evaluation.currentStage.activities) };
|
|
3963
4077
|
},
|
|
3964
4078
|
/**
|
|
3965
4079
|
* Materialised spawned children of a parent instance.
|
|
3966
4080
|
*
|
|
3967
4081
|
* Walks `history` for `spawned` events — the durable
|
|
3968
|
-
* record. (The per-
|
|
4082
|
+
* record. (The per-activity `spawnedInstances` field on the active stage
|
|
3969
4083
|
* only exists while that stage is current; history survives.) Strips
|
|
3970
4084
|
* the GDR URI on each `instanceRef` to a bare `_id`, fetches the
|
|
3971
4085
|
* instances, drops any that aren't visible to this engine's tag, and
|
|
3972
4086
|
* returns them sorted by `startedAt` ascending.
|
|
3973
4087
|
*
|
|
3974
|
-
* Pass `
|
|
4088
|
+
* Pass `activity` to restrict to a single spawning activity on the parent.
|
|
3975
4089
|
*/
|
|
3976
4090
|
children: async (args) => {
|
|
3977
|
-
const { client, tag, instanceId,
|
|
4091
|
+
const { client, tag, instanceId, activity } = args;
|
|
3978
4092
|
validateTag(tag);
|
|
3979
|
-
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));
|
|
3980
4094
|
return ids.length === 0 ? [] : client.fetch(
|
|
3981
4095
|
`*[_id in $ids && ${tagScopeFilter()}] | order(startedAt asc)`,
|
|
3982
4096
|
{ ids, tag }
|
|
3983
4097
|
);
|
|
3984
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
|
+
},
|
|
3985
4148
|
/**
|
|
3986
4149
|
* Permission helpers — Sanity ACL grants evaluated against documents
|
|
3987
4150
|
* via GROQ. Used by `workflow.evaluate` to soft-gate actions when the
|
|
@@ -4035,10 +4198,13 @@ function walkEffectNames(def, visit) {
|
|
|
4035
4198
|
for (const e of effects ?? []) visit(e.name, location);
|
|
4036
4199
|
};
|
|
4037
4200
|
for (const stage of def.stages ?? []) {
|
|
4038
|
-
for (const t of stage.
|
|
4039
|
-
visitEffects(t.effects, `stage[${stage.name}].
|
|
4201
|
+
for (const t of stage.activities ?? []) {
|
|
4202
|
+
visitEffects(t.effects, `stage[${stage.name}].activity[${t.name}].effects`);
|
|
4040
4203
|
for (const a of t.actions ?? [])
|
|
4041
|
-
visitEffects(
|
|
4204
|
+
visitEffects(
|
|
4205
|
+
a.effects,
|
|
4206
|
+
`stage[${stage.name}].activity[${t.name}].action[${a.name}].effects`
|
|
4207
|
+
);
|
|
4042
4208
|
}
|
|
4043
4209
|
for (const tr of stage.transitions ?? [])
|
|
4044
4210
|
visitEffects(tr.effects, `stage[${stage.name}].transition[${tr.name}].effects`);
|
|
@@ -4054,7 +4220,7 @@ async function drainEffectsInternal(args) {
|
|
|
4054
4220
|
effectHandlers,
|
|
4055
4221
|
missingHandler,
|
|
4056
4222
|
logger
|
|
4057
|
-
} = args, drainerActor = args.access?.actor ??
|
|
4223
|
+
} = args, routeGdr = buildClientForGdr(client, resourceClients), clientFor = (ref) => routeGdr(parseGdr(typeof ref == "string" ? ref : ref.id)), drainerActor = args.access?.actor ?? engineSystemActor("drainEffects"), completionAccess = {
|
|
4058
4224
|
actor: drainerActor,
|
|
4059
4225
|
...args.access?.grants !== void 0 ? { grants: args.access.grants } : {}
|
|
4060
4226
|
};
|
|
@@ -4073,6 +4239,7 @@ async function drainEffectsInternal(args) {
|
|
|
4073
4239
|
if (!await claimPendingEffect(client, before, candidate._key, drainerActor)) continue;
|
|
4074
4240
|
const { outputs, dispatchError } = await dispatchEffect(handler, candidate, {
|
|
4075
4241
|
client,
|
|
4242
|
+
clientFor,
|
|
4076
4243
|
instanceId,
|
|
4077
4244
|
logger
|
|
4078
4245
|
});
|
|
@@ -4127,6 +4294,7 @@ async function dispatchEffect(handler, candidate, ctx) {
|
|
|
4127
4294
|
try {
|
|
4128
4295
|
const result = await handler(candidate.params, {
|
|
4129
4296
|
client: ctx.client,
|
|
4297
|
+
clientFor: ctx.clientFor,
|
|
4130
4298
|
instanceId: ctx.instanceId,
|
|
4131
4299
|
effectKey: candidate._key,
|
|
4132
4300
|
log: (message, extra) => ctx.logger(`effect.${candidate.name}`).info(message, extra)
|
|
@@ -4215,13 +4383,13 @@ function createInstanceSession(args) {
|
|
|
4215
4383
|
return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: cascaded > 0 };
|
|
4216
4384
|
});
|
|
4217
4385
|
},
|
|
4218
|
-
fireAction({
|
|
4386
|
+
fireAction({ activity, action, params }) {
|
|
4219
4387
|
return commit(async (actor, held) => {
|
|
4220
|
-
assertActionAllowed(await evaluateWith(held, heldGuards),
|
|
4388
|
+
assertActionAllowed(await evaluateWith(held, heldGuards), activity, action);
|
|
4221
4389
|
const { grants } = await access(), ranOps = await applyAction({
|
|
4222
4390
|
client,
|
|
4223
4391
|
instance,
|
|
4224
|
-
|
|
4392
|
+
activity,
|
|
4225
4393
|
action,
|
|
4226
4394
|
actor,
|
|
4227
4395
|
clientForGdr,
|
|
@@ -4328,13 +4496,14 @@ function createEngine(args) {
|
|
|
4328
4496
|
definition,
|
|
4329
4497
|
...resourceClients !== void 0 ? { resourceClients } : {}
|
|
4330
4498
|
}),
|
|
4331
|
-
children: ({ instanceId,
|
|
4499
|
+
children: ({ instanceId, activity }) => workflow.children({
|
|
4332
4500
|
client,
|
|
4333
4501
|
tag,
|
|
4334
4502
|
workflowResource,
|
|
4335
4503
|
instanceId,
|
|
4336
|
-
...
|
|
4504
|
+
...activity !== void 0 ? { activity } : {}
|
|
4337
4505
|
}),
|
|
4506
|
+
instancesForDocument: ({ document }) => workflow.instancesForDocument({ client, tag, workflowResource, document }),
|
|
4338
4507
|
query: ({ groq, params }) => workflow.query({
|
|
4339
4508
|
client,
|
|
4340
4509
|
tag,
|
|
@@ -4380,33 +4549,25 @@ function createEngine(args) {
|
|
|
4380
4549
|
})
|
|
4381
4550
|
};
|
|
4382
4551
|
}
|
|
4383
|
-
function
|
|
4384
|
-
|
|
4385
|
-
}
|
|
4386
|
-
function diffStatus(existing, expected) {
|
|
4387
|
-
return existing === void 0 ? "create" : isDefinitionUnchanged(existing, expected) ? "unchanged" : "update";
|
|
4388
|
-
}
|
|
4389
|
-
function diffEntry(def, existingRaw, target) {
|
|
4390
|
-
const docId = docIdFor(def, target), expected = {
|
|
4391
|
-
...def,
|
|
4392
|
-
_id: docId,
|
|
4393
|
-
_type: WORKFLOW_DEFINITION_TYPE,
|
|
4394
|
-
tag: target.tag
|
|
4395
|
-
}, 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;
|
|
4396
4554
|
return {
|
|
4397
4555
|
name: def.name,
|
|
4398
|
-
version:
|
|
4399
|
-
status,
|
|
4400
|
-
docId,
|
|
4401
|
-
expected,
|
|
4556
|
+
version: plan.version,
|
|
4557
|
+
status: plan.status,
|
|
4558
|
+
docId: plan.docId,
|
|
4559
|
+
expected: plan.document,
|
|
4402
4560
|
...existing !== void 0 ? { existing } : {}
|
|
4403
4561
|
};
|
|
4404
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
|
+
}
|
|
4405
4566
|
async function computeDiffEntries(client, defs, target) {
|
|
4406
4567
|
const entries = [];
|
|
4407
4568
|
for (const def of defs) {
|
|
4408
|
-
const
|
|
4409
|
-
entries.push(diffEntry(def,
|
|
4569
|
+
const latest = await loadLatestDeployed(client, def.name, target.tag);
|
|
4570
|
+
entries.push(diffEntry(def, latest, target));
|
|
4410
4571
|
}
|
|
4411
4572
|
return entries;
|
|
4412
4573
|
}
|
|
@@ -4419,17 +4580,17 @@ const HISTORY_DISPLAY = {
|
|
|
4419
4580
|
title: "Stage exited",
|
|
4420
4581
|
description: "The instance left a stage on the way to the next one."
|
|
4421
4582
|
},
|
|
4422
|
-
|
|
4423
|
-
title: "
|
|
4424
|
-
description: "
|
|
4583
|
+
activityActivated: {
|
|
4584
|
+
title: "Activity activated",
|
|
4585
|
+
description: "An activity flipped from `pending` to `active` and its onEnter effects queued."
|
|
4425
4586
|
},
|
|
4426
|
-
|
|
4427
|
-
title: "
|
|
4428
|
-
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)."
|
|
4429
4590
|
},
|
|
4430
4591
|
actionFired: {
|
|
4431
4592
|
title: "Action fired",
|
|
4432
|
-
description: "An action was invoked against
|
|
4593
|
+
description: "An action was invoked against an activity \u2014 ops ran, status may have flipped, effects queued."
|
|
4433
4594
|
},
|
|
4434
4595
|
transitionFired: {
|
|
4435
4596
|
title: "Transition fired",
|
|
@@ -4445,7 +4606,7 @@ const HISTORY_DISPLAY = {
|
|
|
4445
4606
|
},
|
|
4446
4607
|
spawned: {
|
|
4447
4608
|
title: "Spawned child",
|
|
4448
|
-
description: "
|
|
4609
|
+
description: "An activity's `subworkflows` declaration spawned a child workflow instance."
|
|
4449
4610
|
},
|
|
4450
4611
|
aborted: {
|
|
4451
4612
|
title: "Instance aborted",
|
|
@@ -4454,6 +4615,10 @@ const HISTORY_DISPLAY = {
|
|
|
4454
4615
|
opApplied: {
|
|
4455
4616
|
title: "Op applied",
|
|
4456
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."
|
|
4457
4622
|
}
|
|
4458
4623
|
}, FIELD_SLOT_DISPLAY = {
|
|
4459
4624
|
"doc.ref": {
|
|
@@ -4468,50 +4633,58 @@ const HISTORY_DISPLAY = {
|
|
|
4468
4633
|
title: "Content Release reference",
|
|
4469
4634
|
description: "GDR pointer at a Content Release system doc, carrying the release name the engine derives the instance's read perspective from."
|
|
4470
4635
|
},
|
|
4471
|
-
|
|
4472
|
-
title: "Query result",
|
|
4473
|
-
description: "Snapshot of a GROQ projection captured at entry-resolve time; resolvedAt is stamped on the value."
|
|
4474
|
-
},
|
|
4475
|
-
"value.string": {
|
|
4636
|
+
string: {
|
|
4476
4637
|
title: "String value",
|
|
4477
|
-
description: "Free-form string entry."
|
|
4638
|
+
description: "Free-form single-line string entry."
|
|
4478
4639
|
},
|
|
4479
|
-
|
|
4480
|
-
title: "
|
|
4481
|
-
description: "
|
|
4640
|
+
text: {
|
|
4641
|
+
title: "Text value",
|
|
4642
|
+
description: "Free-form multiline string entry."
|
|
4482
4643
|
},
|
|
4483
|
-
|
|
4644
|
+
number: {
|
|
4484
4645
|
title: "Number value",
|
|
4485
4646
|
description: "Numeric entry."
|
|
4486
4647
|
},
|
|
4487
|
-
|
|
4648
|
+
boolean: {
|
|
4488
4649
|
title: "Boolean value",
|
|
4489
4650
|
description: "True/false entry."
|
|
4490
4651
|
},
|
|
4491
|
-
|
|
4652
|
+
date: {
|
|
4653
|
+
title: "Date value",
|
|
4654
|
+
description: "Date-only (YYYY-MM-DD) entry, no time component."
|
|
4655
|
+
},
|
|
4656
|
+
datetime: {
|
|
4492
4657
|
title: "Date / time value",
|
|
4493
|
-
description: "ISO-timestamp entry."
|
|
4658
|
+
description: "ISO-8601 timestamp entry."
|
|
4494
4659
|
},
|
|
4495
|
-
|
|
4496
|
-
title: "
|
|
4497
|
-
description: "
|
|
4660
|
+
url: {
|
|
4661
|
+
title: "URL value",
|
|
4662
|
+
description: "Validated URL entry."
|
|
4498
4663
|
},
|
|
4499
|
-
|
|
4500
|
-
title: "
|
|
4501
|
-
description: "
|
|
4664
|
+
actor: {
|
|
4665
|
+
title: "Actor value",
|
|
4666
|
+
description: "A typed (user|ai|system) actor identity \u2014 a concrete principal."
|
|
4502
4667
|
},
|
|
4503
|
-
|
|
4504
|
-
title: "
|
|
4505
|
-
description: "
|
|
4668
|
+
assignee: {
|
|
4669
|
+
title: "Assignee",
|
|
4670
|
+
description: "A single typed (user|role) assignee \u2014 who is expected/responsible."
|
|
4506
4671
|
},
|
|
4507
4672
|
assignees: {
|
|
4508
4673
|
title: "Assignees",
|
|
4509
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."
|
|
4510
4683
|
}
|
|
4511
4684
|
}, OP_DISPLAY = {
|
|
4512
4685
|
"field.set": {
|
|
4513
4686
|
title: "Set field entry",
|
|
4514
|
-
description: "Overwrite a field entry's value with a resolved
|
|
4687
|
+
description: "Overwrite a field entry's value with a resolved value expression."
|
|
4515
4688
|
},
|
|
4516
4689
|
"field.unset": {
|
|
4517
4690
|
title: "Unset field entry",
|
|
@@ -4519,7 +4692,7 @@ const HISTORY_DISPLAY = {
|
|
|
4519
4692
|
},
|
|
4520
4693
|
"field.append": {
|
|
4521
4694
|
title: "Append to field entry",
|
|
4522
|
-
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)."
|
|
4523
4696
|
},
|
|
4524
4697
|
"field.updateWhere": {
|
|
4525
4698
|
title: "Update matching rows",
|
|
@@ -4530,8 +4703,8 @@ const HISTORY_DISPLAY = {
|
|
|
4530
4703
|
description: "Drop rows of an array-kind entry that match the predicate."
|
|
4531
4704
|
},
|
|
4532
4705
|
"status.set": {
|
|
4533
|
-
title: "Set
|
|
4534
|
-
description: "Flip
|
|
4706
|
+
title: "Set activity status",
|
|
4707
|
+
description: "Flip an activity's status explicitly (the action-level `status:` sugar desugars to this)."
|
|
4535
4708
|
}
|
|
4536
4709
|
}, EFFECTS_CONTEXT_DISPLAY = {
|
|
4537
4710
|
"effectsContext.string": {
|
|
@@ -4571,6 +4744,35 @@ const HISTORY_DISPLAY = {
|
|
|
4571
4744
|
title: "Workflow instance",
|
|
4572
4745
|
description: "A running (or finished) workflow against its declared fields."
|
|
4573
4746
|
}
|
|
4747
|
+
}, ACTIVITY_KIND_DISPLAY = {
|
|
4748
|
+
user: {
|
|
4749
|
+
title: "User activity",
|
|
4750
|
+
description: "A person acts via the app \u2014 renders action buttons ranked by outcome."
|
|
4751
|
+
},
|
|
4752
|
+
service: {
|
|
4753
|
+
title: "Service activity",
|
|
4754
|
+
description: "An automated system or effect does the work \u2014 renders effect drain status."
|
|
4755
|
+
},
|
|
4756
|
+
script: {
|
|
4757
|
+
title: "Script activity",
|
|
4758
|
+
description: "The engine runs a step inline and resolves on activation \u2014 an audit row."
|
|
4759
|
+
},
|
|
4760
|
+
manual: {
|
|
4761
|
+
title: "Manual activity",
|
|
4762
|
+
description: "Work done off-system, acknowledged by a person or verified by a predicate \u2014 renders an instruction card with a deep-link."
|
|
4763
|
+
},
|
|
4764
|
+
receive: {
|
|
4765
|
+
title: "Receive activity",
|
|
4766
|
+
description: 'No actor; the engine waits on a condition \u2014 renders "waiting until \u2026", no buttons.'
|
|
4767
|
+
}
|
|
4768
|
+
}, DRIVER_KIND_DISPLAY = {
|
|
4769
|
+
person: { title: "Person", description: "A human fired this action." },
|
|
4770
|
+
agent: { title: "Agent", description: "An AI agent fired this action." },
|
|
4771
|
+
service: { title: "Service", description: "An external system or Function fired this action." },
|
|
4772
|
+
engine: {
|
|
4773
|
+
title: "Engine",
|
|
4774
|
+
description: "The workflow engine itself drove this \u2014 a gate, machine step, or housekeeping."
|
|
4775
|
+
}
|
|
4574
4776
|
}, DISPLAY = {
|
|
4575
4777
|
...HISTORY_DISPLAY,
|
|
4576
4778
|
...FIELD_SLOT_DISPLAY,
|
|
@@ -4586,6 +4788,8 @@ function displayDescription(typeKey) {
|
|
|
4586
4788
|
return DISPLAY[typeKey]?.description;
|
|
4587
4789
|
}
|
|
4588
4790
|
export {
|
|
4791
|
+
ACTIVITY_KINDS,
|
|
4792
|
+
ACTIVITY_KIND_DISPLAY,
|
|
4589
4793
|
AUTHORING_DISPLAY,
|
|
4590
4794
|
ActionDisabledError,
|
|
4591
4795
|
ActionParamsInvalidError,
|
|
@@ -4594,6 +4798,8 @@ export {
|
|
|
4594
4798
|
ConcurrentFireActionError,
|
|
4595
4799
|
DEFAULT_CONTENT_PERSPECTIVE,
|
|
4596
4800
|
DISPLAY,
|
|
4801
|
+
DRIVER_KINDS,
|
|
4802
|
+
DRIVER_KIND_DISPLAY,
|
|
4597
4803
|
EFFECTS_CONTEXT_DISPLAY,
|
|
4598
4804
|
EditFieldDeniedError,
|
|
4599
4805
|
FIELD_SLOT_DISPLAY,
|
|
@@ -4610,6 +4816,7 @@ export {
|
|
|
4610
4816
|
WorkflowStateDivergedError,
|
|
4611
4817
|
abortReason,
|
|
4612
4818
|
actionVerdict,
|
|
4819
|
+
activityKind,
|
|
4613
4820
|
availableActions,
|
|
4614
4821
|
buildSnapshot,
|
|
4615
4822
|
compileGuard,
|
|
@@ -4620,11 +4827,13 @@ export {
|
|
|
4620
4827
|
defaultLoggerFactory,
|
|
4621
4828
|
denyingGuards,
|
|
4622
4829
|
deployStageGuards,
|
|
4830
|
+
deriveActivityKind,
|
|
4623
4831
|
diagnoseInputFromEvaluation,
|
|
4624
4832
|
diagnoseInstance,
|
|
4625
4833
|
diffEntry,
|
|
4626
4834
|
displayDescription,
|
|
4627
4835
|
displayTitle,
|
|
4836
|
+
driverKind,
|
|
4628
4837
|
effectsContextMap,
|
|
4629
4838
|
evaluateFromSnapshot,
|
|
4630
4839
|
evaluateMutationGuard,
|
|
@@ -4636,8 +4845,9 @@ export {
|
|
|
4636
4845
|
guardsForDefinition,
|
|
4637
4846
|
guardsForInstance,
|
|
4638
4847
|
guardsForResource,
|
|
4848
|
+
hashDefinitionContent,
|
|
4639
4849
|
instanceGuardQuery,
|
|
4640
|
-
|
|
4850
|
+
instanceWatchesDocument,
|
|
4641
4851
|
isGdr,
|
|
4642
4852
|
isStartableDefinition,
|
|
4643
4853
|
isTerminalStage,
|