@sanity/workflow-engine 0.15.0 → 0.16.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/CHANGELOG.md +36 -0
- package/DATAMODEL.md +73 -4
- package/dist/_chunks-cjs/invariants.cjs +982 -235
- package/dist/_chunks-es/invariants.js +956 -231
- package/dist/define.cjs +3 -608
- package/dist/define.d.cts +95 -409
- package/dist/define.d.ts +95 -409
- package/dist/define.js +2 -607
- package/dist/index.cjs +1877 -1733
- package/dist/index.d.cts +762 -526
- package/dist/index.d.ts +762 -526
- package/dist/index.js +2078 -1979
- package/package.json +3 -3
|
@@ -20,6 +20,26 @@ function _interopNamespaceCompat(e) {
|
|
|
20
20
|
|
|
21
21
|
var v__namespace = /* @__PURE__ */ _interopNamespaceCompat(v);
|
|
22
22
|
|
|
23
|
+
function isCascadeFired(action) {
|
|
24
|
+
return action.when !== void 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function deriveActivityKind(activity) {
|
|
28
|
+
if (activity.target !== void 0) return "manual";
|
|
29
|
+
const actions = activity.actions ?? [];
|
|
30
|
+
return actions.some(a => !isCascadeFired(a)) ? "user" : actions.some(a => (a.effects ?? []).length > 0 || a.spawn !== void 0) ? "service" : actions.length > 0 ? "receive" : "script";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function deriveExecutorClassification(activity) {
|
|
34
|
+
if (activity.target !== void 0) return "off-system";
|
|
35
|
+
const actions = activity.actions ?? [], cascadeFired = actions.filter(isCascadeFired).length;
|
|
36
|
+
return cascadeFired === actions.length ? "autonomous" : cascadeFired === 0 ? "interactive" : "hybrid";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function driverKind(actor) {
|
|
40
|
+
return actor.kind === "person" || actor.kind === "agent" ? actor.kind : "service";
|
|
41
|
+
}
|
|
42
|
+
|
|
23
43
|
function errorMessage(err) {
|
|
24
44
|
return (err instanceof Error ? err.message : String(err)).replace(/[^\P{Cc}\n\t]/gu, "");
|
|
25
45
|
}
|
|
@@ -397,6 +417,596 @@ function actorFulfillsRole({actorRoles: actorRoles, required: required, aliases:
|
|
|
397
417
|
return actorRoles.some(role => accepted.has(role));
|
|
398
418
|
}
|
|
399
419
|
|
|
420
|
+
function groq(strings, ...values) {
|
|
421
|
+
return strings.reduce((out, part, i) => i === 0 ? part : `${out}${serializeGroqValue(values[i - 1])}${part}`, "");
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function serializeGroqValue(value) {
|
|
425
|
+
const serialized = JSON.stringify(value);
|
|
426
|
+
if (serialized === void 0) throw new Error(`groq tag cannot serialize ${typeof value} — interpolate JSON-representable values only`);
|
|
427
|
+
return serialized;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function desugarWorkflow(authoring) {
|
|
431
|
+
const issues = [];
|
|
432
|
+
checkReservedRoleAliasKeys(authoring.roleAliases, issues);
|
|
433
|
+
const roleAliases = normalizeRoleAliases(authoring.roleAliases), ctx = {
|
|
434
|
+
issues: issues,
|
|
435
|
+
claimFields: /* @__PURE__ */ new Map,
|
|
436
|
+
claimedFields: /* @__PURE__ */ new Set,
|
|
437
|
+
roleAliases: roleAliases
|
|
438
|
+
}, workflowFields2 = desugarFieldEntries({
|
|
439
|
+
entries: authoring.fields,
|
|
440
|
+
path: [ "fields" ],
|
|
441
|
+
ctx: ctx
|
|
442
|
+
}), workflowLayer = layerOf(workflowFields2), stages = authoring.stages.map((stage, i) => {
|
|
443
|
+
const path = [ "stages", i ], stageFields2 = desugarFieldEntries({
|
|
444
|
+
entries: stage.fields,
|
|
445
|
+
path: [ ...path, "fields" ],
|
|
446
|
+
ctx: ctx
|
|
447
|
+
}), stageEnv = {
|
|
448
|
+
layers: [ {
|
|
449
|
+
scope: "stage",
|
|
450
|
+
entries: layerOf(stageFields2)
|
|
451
|
+
}, {
|
|
452
|
+
scope: "workflow",
|
|
453
|
+
entries: workflowLayer
|
|
454
|
+
} ]
|
|
455
|
+
}, activities = (stage.activities ?? []).map((activity, j) => desugarActivity({
|
|
456
|
+
activity: activity,
|
|
457
|
+
path: [ ...path, "activities", j ],
|
|
458
|
+
stageEnv: stageEnv,
|
|
459
|
+
ctx: ctx
|
|
460
|
+
})), transitions = (stage.transitions ?? []).map(transition => desugarTransition({
|
|
461
|
+
transition: transition
|
|
462
|
+
})), editable = desugarStageEditable({
|
|
463
|
+
overrides: stage.editable,
|
|
464
|
+
inScope: editableFieldNames({
|
|
465
|
+
workflowFields: workflowFields2,
|
|
466
|
+
stageFields: stageFields2,
|
|
467
|
+
activities: activities
|
|
468
|
+
}),
|
|
469
|
+
path: [ ...path, "editable" ],
|
|
470
|
+
ctx: ctx
|
|
471
|
+
});
|
|
472
|
+
return {
|
|
473
|
+
...stripUndefined({
|
|
474
|
+
name: stage.name,
|
|
475
|
+
title: stage.title,
|
|
476
|
+
description: stage.description,
|
|
477
|
+
groups: stage.groups,
|
|
478
|
+
guards: stage.guards?.map(desugarGuard)
|
|
479
|
+
}),
|
|
480
|
+
...stageFields2 ? {
|
|
481
|
+
fields: stageFields2
|
|
482
|
+
} : {},
|
|
483
|
+
...activities.length > 0 ? {
|
|
484
|
+
activities: activities
|
|
485
|
+
} : {},
|
|
486
|
+
...transitions.length > 0 ? {
|
|
487
|
+
transitions: transitions
|
|
488
|
+
} : {},
|
|
489
|
+
...editable ? {
|
|
490
|
+
editable: editable
|
|
491
|
+
} : {}
|
|
492
|
+
};
|
|
493
|
+
});
|
|
494
|
+
return checkUnclaimedClaimFields(ctx), {
|
|
495
|
+
definition: {
|
|
496
|
+
...stripUndefined({
|
|
497
|
+
name: authoring.name,
|
|
498
|
+
title: authoring.title,
|
|
499
|
+
description: authoring.description,
|
|
500
|
+
groups: authoring.groups,
|
|
501
|
+
lifecycle: authoring.lifecycle,
|
|
502
|
+
start: desugarStart(authoring.start),
|
|
503
|
+
initialStage: authoring.initialStage,
|
|
504
|
+
predicates: authoring.predicates,
|
|
505
|
+
roleAliases: roleAliases
|
|
506
|
+
}),
|
|
507
|
+
...workflowFields2 ? {
|
|
508
|
+
fields: workflowFields2
|
|
509
|
+
} : {},
|
|
510
|
+
stages: stages
|
|
511
|
+
},
|
|
512
|
+
issues: ctx.issues
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function desugarStart(start) {
|
|
517
|
+
if (start !== void 0) return {
|
|
518
|
+
kind: start.kind ?? "interactive",
|
|
519
|
+
...start.filter !== void 0 ? {
|
|
520
|
+
filter: start.filter
|
|
521
|
+
} : {}
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function checkReservedRoleAliasKeys(aliases, issues) {
|
|
526
|
+
for (const key of Object.keys(aliases ?? {})) key.startsWith("$") && issues.push({
|
|
527
|
+
path: [ "roleAliases", key ],
|
|
528
|
+
message: `role alias key "${key}" uses the reserved "$" prefix — that namespace is the engine's stored spelling for the universal fulfiller. Use "*" to mean "fulfills any gate", or rename the role.`
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const TODOLIST_OF = [ {
|
|
533
|
+
type: "string",
|
|
534
|
+
name: "label",
|
|
535
|
+
title: "Label"
|
|
536
|
+
}, {
|
|
537
|
+
type: "string",
|
|
538
|
+
name: "status",
|
|
539
|
+
title: "Status"
|
|
540
|
+
}, {
|
|
541
|
+
type: "assignee",
|
|
542
|
+
name: "assignee",
|
|
543
|
+
title: "Assignee"
|
|
544
|
+
}, {
|
|
545
|
+
type: "date",
|
|
546
|
+
name: "dueDate",
|
|
547
|
+
title: "Due date"
|
|
548
|
+
} ], NOTES_OF = [ {
|
|
549
|
+
type: "text",
|
|
550
|
+
name: "body",
|
|
551
|
+
title: "Body"
|
|
552
|
+
}, {
|
|
553
|
+
type: "actor",
|
|
554
|
+
name: "actor",
|
|
555
|
+
title: "Actor"
|
|
556
|
+
}, {
|
|
557
|
+
type: "datetime",
|
|
558
|
+
name: "at",
|
|
559
|
+
title: "At"
|
|
560
|
+
} ];
|
|
561
|
+
|
|
562
|
+
function desugarFieldEntries({entries: entries, path: path, ctx: ctx}) {
|
|
563
|
+
return !entries || entries.length === 0 ? entries === void 0 ? void 0 : [] : entries.map((entry, i) => desugarFieldEntry({
|
|
564
|
+
entry: entry,
|
|
565
|
+
path: [ ...path, i ],
|
|
566
|
+
ctx: ctx
|
|
567
|
+
}));
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function desugarFieldEntry({entry: entry, path: path, ctx: ctx}) {
|
|
571
|
+
if (entry.type === "claim") return desugarClaimField({
|
|
572
|
+
entry: entry,
|
|
573
|
+
path: path,
|
|
574
|
+
ctx: ctx
|
|
575
|
+
});
|
|
576
|
+
if (entry.type === "todoList" || entry.type === "notes") return desugarListField({
|
|
577
|
+
entry: entry,
|
|
578
|
+
path: path,
|
|
579
|
+
ctx: ctx
|
|
580
|
+
});
|
|
581
|
+
const editable = normalizeEditable({
|
|
582
|
+
editable: entry.editable,
|
|
583
|
+
path: [ ...path, "editable" ],
|
|
584
|
+
ctx: ctx
|
|
585
|
+
});
|
|
586
|
+
return {
|
|
587
|
+
...stripUndefined({
|
|
588
|
+
type: entry.type,
|
|
589
|
+
name: entry.name,
|
|
590
|
+
title: entry.title,
|
|
591
|
+
description: entry.description,
|
|
592
|
+
group: normalizeGroup(entry.group),
|
|
593
|
+
required: entry.required,
|
|
594
|
+
initialValue: entry.initialValue,
|
|
595
|
+
editable: editable,
|
|
596
|
+
types: entry.types,
|
|
597
|
+
fields: entry.fields,
|
|
598
|
+
of: entry.of
|
|
599
|
+
})
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function desugarClaimField({entry: entry, path: path, ctx: ctx}) {
|
|
604
|
+
const desugared = {
|
|
605
|
+
...stripUndefined({
|
|
606
|
+
name: entry.name,
|
|
607
|
+
title: entry.title,
|
|
608
|
+
description: entry.description,
|
|
609
|
+
group: normalizeGroup(entry.group)
|
|
610
|
+
}),
|
|
611
|
+
type: "actor"
|
|
612
|
+
};
|
|
613
|
+
return ctx.claimFields.set(desugared, {
|
|
614
|
+
name: entry.name,
|
|
615
|
+
path: path
|
|
616
|
+
}), desugared;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function desugarListField({entry: entry, path: path, ctx: ctx}) {
|
|
620
|
+
const editable = normalizeEditable({
|
|
621
|
+
editable: entry.editable,
|
|
622
|
+
path: [ ...path, "editable" ],
|
|
623
|
+
ctx: ctx
|
|
624
|
+
});
|
|
625
|
+
return {
|
|
626
|
+
...stripUndefined({
|
|
627
|
+
name: entry.name,
|
|
628
|
+
title: entry.title,
|
|
629
|
+
description: entry.description,
|
|
630
|
+
group: normalizeGroup(entry.group),
|
|
631
|
+
required: entry.required,
|
|
632
|
+
initialValue: entry.initialValue,
|
|
633
|
+
editable: editable
|
|
634
|
+
}),
|
|
635
|
+
type: "array",
|
|
636
|
+
of: entry.type === "todoList" ? TODOLIST_OF : NOTES_OF
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function normalizeEditable({editable: editable, path: path, ctx: ctx}) {
|
|
641
|
+
if (editable === void 0 || editable === !0) return editable;
|
|
642
|
+
if (Array.isArray(editable)) {
|
|
643
|
+
const condition = rolesCondition(editable, ctx.roleAliases);
|
|
644
|
+
if (condition === void 0) {
|
|
645
|
+
ctx.issues.push({
|
|
646
|
+
path: path,
|
|
647
|
+
message: "editable: [] names no roles — use `true` to open the field to anyone in its scope, or list at least one role"
|
|
648
|
+
});
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
return condition;
|
|
652
|
+
}
|
|
653
|
+
return editable;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function editableFieldNames({workflowFields: workflowFields2, stageFields: stageFields2, activities: activities}) {
|
|
657
|
+
const names = /* @__PURE__ */ new Set, collect = entries => {
|
|
658
|
+
for (const entry of entries ?? []) entry.editable !== void 0 && names.add(entry.name);
|
|
659
|
+
};
|
|
660
|
+
collect(workflowFields2), collect(stageFields2);
|
|
661
|
+
for (const activity of activities) collect(activity.fields);
|
|
662
|
+
return names;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function desugarStageEditable({overrides: overrides, inScope: inScope, path: path, ctx: ctx}) {
|
|
666
|
+
if (overrides === void 0) return;
|
|
667
|
+
const out = {};
|
|
668
|
+
for (const [name, value] of Object.entries(overrides)) {
|
|
669
|
+
if (!inScope.has(name)) {
|
|
670
|
+
ctx.issues.push({
|
|
671
|
+
path: [ ...path, name ],
|
|
672
|
+
message: `stage editable override "${name}" does not narrow an editable field in scope — name a workflow/stage/activity field of this stage that declares \`editable\``
|
|
673
|
+
});
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
const normalized = normalizeEditable({
|
|
677
|
+
editable: value,
|
|
678
|
+
path: [ ...path, name ],
|
|
679
|
+
ctx: ctx
|
|
680
|
+
});
|
|
681
|
+
normalized !== void 0 && (out[name] = normalized);
|
|
682
|
+
}
|
|
683
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function layerOf(entries) {
|
|
687
|
+
return new Map((entries ?? []).map(entry => [ entry.name, entry ]));
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function normalizeGroup(group) {
|
|
691
|
+
return group === void 0 || Array.isArray(group) ? group : [ group ];
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function desugarActivity({activity: activity, path: path, stageEnv: stageEnv, ctx: ctx}) {
|
|
695
|
+
const activityFields2 = desugarFieldEntries({
|
|
696
|
+
entries: activity.fields,
|
|
697
|
+
path: [ ...path, "fields" ],
|
|
698
|
+
ctx: ctx
|
|
699
|
+
}), env = {
|
|
700
|
+
layers: [ {
|
|
701
|
+
scope: "activity",
|
|
702
|
+
entries: layerOf(activityFields2)
|
|
703
|
+
}, ...stageEnv.layers ]
|
|
704
|
+
}, actions = (activity.actions ?? []).map((action, a) => desugarAction({
|
|
705
|
+
action: action,
|
|
706
|
+
path: [ ...path, "actions", a ],
|
|
707
|
+
env: env,
|
|
708
|
+
activityName: activity.name,
|
|
709
|
+
ctx: ctx
|
|
710
|
+
})), target = desugarTarget({
|
|
711
|
+
target: activity.target,
|
|
712
|
+
env: env,
|
|
713
|
+
path: [ ...path, "target" ],
|
|
714
|
+
ctx: ctx
|
|
715
|
+
});
|
|
716
|
+
return {
|
|
717
|
+
...stripUndefined({
|
|
718
|
+
name: activity.name,
|
|
719
|
+
title: activity.title,
|
|
720
|
+
description: activity.description,
|
|
721
|
+
groups: activity.groups,
|
|
722
|
+
group: normalizeGroup(activity.group),
|
|
723
|
+
filter: activity.filter,
|
|
724
|
+
requirements: activity.requirements
|
|
725
|
+
}),
|
|
726
|
+
...target ? {
|
|
727
|
+
target: target
|
|
728
|
+
} : {},
|
|
729
|
+
...activityFields2 ? {
|
|
730
|
+
fields: activityFields2
|
|
731
|
+
} : {},
|
|
732
|
+
...actions.length > 0 ? {
|
|
733
|
+
actions: actions
|
|
734
|
+
} : {}
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const TARGET_DOC_KINDS = [ "doc.ref", "doc.refs", "release.ref" ];
|
|
739
|
+
|
|
740
|
+
function desugarTarget({target: target, env: env, path: path, ctx: ctx}) {
|
|
741
|
+
if (target === void 0 || target.type === "url") return target;
|
|
742
|
+
const ref = typeof target.field == "string" ? {
|
|
743
|
+
field: target.field
|
|
744
|
+
} : target.field, field = resolveRef({
|
|
745
|
+
ref: ref,
|
|
746
|
+
env: env,
|
|
747
|
+
path: [ ...path, "field" ],
|
|
748
|
+
ctx: ctx
|
|
749
|
+
}) ?? fallbackRef(ref), entry = entryAt(env, field);
|
|
750
|
+
return entry && !TARGET_DOC_KINDS.includes(entry.type) && ctx.issues.push({
|
|
751
|
+
path: [ ...path, "field" ],
|
|
752
|
+
message: `manual activity target references "${field.field}" of kind "${entry.type}" — a deep-link target needs a document-valued entry (${TARGET_DOC_KINDS.join(", ")})`
|
|
753
|
+
}), {
|
|
754
|
+
type: "field",
|
|
755
|
+
field: field
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function desugarAction({action: action, path: path, env: env, activityName: activityName, ctx: ctx}) {
|
|
760
|
+
if ("type" in action) return desugarClaimAction({
|
|
761
|
+
action: action,
|
|
762
|
+
path: path,
|
|
763
|
+
env: env,
|
|
764
|
+
ctx: ctx
|
|
765
|
+
});
|
|
766
|
+
action.roles !== void 0 && action.roles.length === 0 && ctx.issues.push({
|
|
767
|
+
path: [ ...path, "roles" ],
|
|
768
|
+
message: "roles: [] names no roles — omit it to allow any identity, or list at least one role"
|
|
769
|
+
});
|
|
770
|
+
const cascadeFired = isCascadeFired(action), filter = cascadeFired ? action.filter : andConditions([ rolesCondition(action.roles, ctx.roleAliases), action.filter ]), ops = desugarOps({
|
|
771
|
+
ops: action.ops,
|
|
772
|
+
path: [ ...path, "ops" ],
|
|
773
|
+
env: env,
|
|
774
|
+
firingActivity: activityName,
|
|
775
|
+
ctx: ctx
|
|
776
|
+
}) ?? [];
|
|
777
|
+
return action.status !== void 0 && ops.push({
|
|
778
|
+
type: "status.set",
|
|
779
|
+
activity: activityName,
|
|
780
|
+
status: action.status
|
|
781
|
+
}), {
|
|
782
|
+
...stripUndefined({
|
|
783
|
+
name: action.name,
|
|
784
|
+
title: action.title,
|
|
785
|
+
description: action.description,
|
|
786
|
+
group: normalizeGroup(action.group),
|
|
787
|
+
when: action.when,
|
|
788
|
+
params: action.params,
|
|
789
|
+
effects: action.effects,
|
|
790
|
+
spawn: action.spawn
|
|
791
|
+
}),
|
|
792
|
+
...cascadeFired && action.roles !== void 0 && action.roles.length > 0 ? {
|
|
793
|
+
roles: action.roles
|
|
794
|
+
} : {},
|
|
795
|
+
...filter ? {
|
|
796
|
+
filter: filter
|
|
797
|
+
} : {},
|
|
798
|
+
...ops.length > 0 ? {
|
|
799
|
+
ops: ops
|
|
800
|
+
} : {}
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function desugarClaimAction({action: action, path: path, env: env, ctx: ctx}) {
|
|
805
|
+
const ref = typeof action.field == "string" ? {
|
|
806
|
+
field: action.field
|
|
807
|
+
} : action.field, resolved = resolveRef({
|
|
808
|
+
ref: ref,
|
|
809
|
+
env: env,
|
|
810
|
+
path: [ ...path, "field" ],
|
|
811
|
+
ctx: ctx
|
|
812
|
+
});
|
|
813
|
+
if (resolved) {
|
|
814
|
+
const entry = entryAt(env, resolved);
|
|
815
|
+
entry && entry.type !== "actor" && ctx.issues.push({
|
|
816
|
+
path: [ ...path, "field" ],
|
|
817
|
+
message: `claim action "${action.name}" references "${resolved.field}" of kind "${entry.type}" — a claim pair needs an actor-valued entry (a "claim" or "actor" field declaration)`
|
|
818
|
+
}), checkShadowedClaimTarget({
|
|
819
|
+
actionName: action.name,
|
|
820
|
+
field: ref.field,
|
|
821
|
+
resolved: resolved,
|
|
822
|
+
env: env,
|
|
823
|
+
path: [ ...path, "field" ],
|
|
824
|
+
ctx: ctx
|
|
825
|
+
}), entry && ctx.claimedFields.add(entry);
|
|
826
|
+
}
|
|
827
|
+
const noSteal = `!defined($fields.${ref.field})`, filter = andConditions([ noSteal, rolesCondition(action.roles, ctx.roleAliases), action.filter ]), ops = resolved ? [ {
|
|
828
|
+
type: "field.set",
|
|
829
|
+
target: resolved,
|
|
830
|
+
value: {
|
|
831
|
+
type: "actor"
|
|
832
|
+
}
|
|
833
|
+
} ] : [];
|
|
834
|
+
return {
|
|
835
|
+
...stripUndefined({
|
|
836
|
+
name: action.name,
|
|
837
|
+
title: action.title,
|
|
838
|
+
description: action.description,
|
|
839
|
+
group: normalizeGroup(action.group),
|
|
840
|
+
params: action.params,
|
|
841
|
+
effects: action.effects
|
|
842
|
+
}),
|
|
843
|
+
filter: filter,
|
|
844
|
+
...ops.length > 0 ? {
|
|
845
|
+
ops: ops
|
|
846
|
+
} : {}
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function checkShadowedClaimTarget({actionName: actionName, field: field, resolved: resolved, env: env, path: path, ctx: ctx}) {
|
|
851
|
+
const nearest = env.layers.find(l => l.entries.has(field));
|
|
852
|
+
nearest === void 0 || nearest.scope === resolved.scope || ctx.issues.push({
|
|
853
|
+
path: path,
|
|
854
|
+
message: `claim action "${actionName}" targets "${field}" at scope "${resolved.scope}", but a nearer ${nearest.scope}-scope entry of the same name shadows it in $fields — the no-steal filter would read the shadowing entry while the claim writes the "${resolved.scope}" one. Rename one of the entries or claim the nearer one`
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function checkUnclaimedClaimFields(ctx) {
|
|
859
|
+
for (const [entry, {name: name, path: path}] of ctx.claimFields) ctx.claimedFields.has(entry) || ctx.issues.push({
|
|
860
|
+
path: path,
|
|
861
|
+
message: `claim field "${name}" is never referenced by a claim action — you announced the pattern and wrote half of it. Declare a defineAction({ type: "claim", field: "${name}" }) or use a raw actor entry`
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const DEFAULT_TRANSITION_WHEN = "$allActivitiesDone";
|
|
866
|
+
|
|
867
|
+
function desugarTransition({transition: transition}) {
|
|
868
|
+
return {
|
|
869
|
+
...stripUndefined({
|
|
870
|
+
name: transition.name,
|
|
871
|
+
title: transition.title,
|
|
872
|
+
description: transition.description,
|
|
873
|
+
to: transition.to
|
|
874
|
+
}),
|
|
875
|
+
when: transition.when ?? DEFAULT_TRANSITION_WHEN
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function desugarGuard(guard) {
|
|
880
|
+
const {match: match, metadata: metadata, ...rest} = guard, {idRefs: idRefs, ...matchRest} = match;
|
|
881
|
+
return {
|
|
882
|
+
...rest,
|
|
883
|
+
match: {
|
|
884
|
+
...matchRest,
|
|
885
|
+
...idRefs !== void 0 ? {
|
|
886
|
+
idRefs: idRefs.map(printGuardRead)
|
|
887
|
+
} : {}
|
|
888
|
+
},
|
|
889
|
+
...metadata !== void 0 ? {
|
|
890
|
+
metadata: Object.fromEntries(Object.entries(metadata).map(([key, read]) => [ key, printGuardRead(read) ]))
|
|
891
|
+
} : {}
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function desugarOps({ops: ops, path: path, env: env, firingActivity: firingActivity, ctx: ctx}) {
|
|
896
|
+
if (ops) return ops.map((op, i) => desugarOp({
|
|
897
|
+
op: op,
|
|
898
|
+
path: [ ...path, i ],
|
|
899
|
+
env: env,
|
|
900
|
+
firingActivity: firingActivity,
|
|
901
|
+
ctx: ctx
|
|
902
|
+
}));
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function desugarOp({op: op, path: path, env: env, firingActivity: firingActivity, ctx: ctx}) {
|
|
906
|
+
if (op.type === "status.set") return {
|
|
907
|
+
type: "status.set",
|
|
908
|
+
activity: op.activity ?? firingActivity,
|
|
909
|
+
status: op.status
|
|
910
|
+
};
|
|
911
|
+
if (op.type === "audit") return desugarAuditOp({
|
|
912
|
+
op: op,
|
|
913
|
+
path: path,
|
|
914
|
+
env: env,
|
|
915
|
+
ctx: ctx
|
|
916
|
+
});
|
|
917
|
+
const target = resolveRef({
|
|
918
|
+
ref: op.target,
|
|
919
|
+
env: env,
|
|
920
|
+
path: [ ...path, "target" ],
|
|
921
|
+
ctx: ctx
|
|
922
|
+
}) ?? fallbackRef(op.target);
|
|
923
|
+
return {
|
|
924
|
+
...op,
|
|
925
|
+
target: target
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const AUDIT_STAMPS = {
|
|
930
|
+
actor: {
|
|
931
|
+
type: "actor"
|
|
932
|
+
},
|
|
933
|
+
at: {
|
|
934
|
+
type: "now"
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
function desugarAuditOp({op: op, path: path, env: env, ctx: ctx}) {
|
|
939
|
+
const target = resolveRef({
|
|
940
|
+
ref: op.target,
|
|
941
|
+
env: env,
|
|
942
|
+
path: [ ...path, "target" ],
|
|
943
|
+
ctx: ctx
|
|
944
|
+
}) ?? fallbackRef(op.target);
|
|
945
|
+
if (op.value.type !== "object") return ctx.issues.push({
|
|
946
|
+
path: [ ...path, "value" ],
|
|
947
|
+
message: `audit value must be an object source carrying the domain fields (got "${op.value.type}")`
|
|
948
|
+
}), {
|
|
949
|
+
type: "field.append",
|
|
950
|
+
target: target,
|
|
951
|
+
value: op.value
|
|
952
|
+
};
|
|
953
|
+
const actorField = op.stampFields?.actor ?? "actor", atField = op.stampFields?.at ?? "at", fields = {
|
|
954
|
+
...op.value.fields
|
|
955
|
+
};
|
|
956
|
+
for (const [stamp, source] of [ [ actorField, AUDIT_STAMPS.actor ], [ atField, AUDIT_STAMPS.at ] ]) {
|
|
957
|
+
if (stamp in fields) {
|
|
958
|
+
ctx.issues.push({
|
|
959
|
+
path: [ ...path, "value", "fields", stamp ],
|
|
960
|
+
message: `audit stamp field "${stamp}" collides with an authored domain field — rename yours or remap the stamp via stampFields`
|
|
961
|
+
});
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
fields[stamp] = source;
|
|
965
|
+
}
|
|
966
|
+
return {
|
|
967
|
+
type: "field.append",
|
|
968
|
+
target: target,
|
|
969
|
+
value: {
|
|
970
|
+
type: "object",
|
|
971
|
+
fields: fields
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function resolveRef({ref: ref, env: env, path: path, ctx: ctx}) {
|
|
977
|
+
const layers = ref.scope === void 0 ? env.layers : env.layers.filter(l => l.scope === ref.scope);
|
|
978
|
+
for (const layer of layers) if (layer.entries.has(ref.field)) return {
|
|
979
|
+
scope: layer.scope,
|
|
980
|
+
field: ref.field
|
|
981
|
+
};
|
|
982
|
+
const reachable = env.layers.flatMap(l => [ ...l.entries.keys() ].map(n => `${l.scope}:${n}`));
|
|
983
|
+
ctx.issues.push({
|
|
984
|
+
path: path,
|
|
985
|
+
message: `field reference "${ref.field}"${ref.scope ? ` (scope "${ref.scope}")` : ""} does not resolve to a declared entry. Reachable: ${reachable.join(", ") || "(none)"}`
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
function entryAt(env, ref) {
|
|
990
|
+
return env.layers.find(l => l.scope === ref.scope)?.entries.get(ref.field);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
function fallbackRef(ref) {
|
|
994
|
+
return {
|
|
995
|
+
scope: ref.scope ?? "workflow",
|
|
996
|
+
field: ref.field
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
function rolesCondition(roles, aliases) {
|
|
1001
|
+
if (!(!roles || roles.length === 0)) return groq`count($actor.roles[@ in ${expandRequiredRoles(roles, aliases)}]) > 0`;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
function stripUndefined(obj) {
|
|
1005
|
+
const out = {};
|
|
1006
|
+
for (const [k, value] of Object.entries(obj)) value !== void 0 && (out[k] = value);
|
|
1007
|
+
return out;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
400
1010
|
function isUnevaluable(result) {
|
|
401
1011
|
return result == null;
|
|
402
1012
|
}
|
|
@@ -416,9 +1026,9 @@ async function evaluateCondition(args) {
|
|
|
416
1026
|
|
|
417
1027
|
async function evaluatePredicates(args) {
|
|
418
1028
|
const out = {};
|
|
419
|
-
for (const [name,
|
|
1029
|
+
for (const [name, groq2] of Object.entries(args.predicates ?? {})) {
|
|
420
1030
|
const result = await runGroq({
|
|
421
|
-
groq:
|
|
1031
|
+
groq: groq2,
|
|
422
1032
|
params: args.params,
|
|
423
1033
|
snapshot: args.snapshot
|
|
424
1034
|
}), outcome = groqConditionDescribe.conditionOutcome(result);
|
|
@@ -427,46 +1037,46 @@ async function evaluatePredicates(args) {
|
|
|
427
1037
|
return out;
|
|
428
1038
|
}
|
|
429
1039
|
|
|
430
|
-
async function runGroq({groq:
|
|
1040
|
+
async function runGroq({groq: groq2, params: params, snapshot: snapshot}) {
|
|
431
1041
|
return groqConditionDescribe.runGroq({
|
|
432
|
-
groq:
|
|
1042
|
+
groq: groq2,
|
|
433
1043
|
params: params,
|
|
434
1044
|
dataset: snapshot.docs
|
|
435
1045
|
});
|
|
436
1046
|
}
|
|
437
1047
|
|
|
438
|
-
function conditionSyntaxIssues(
|
|
1048
|
+
function conditionSyntaxIssues(groq2, boundVars) {
|
|
439
1049
|
const issues = [];
|
|
440
1050
|
let tree;
|
|
441
1051
|
try {
|
|
442
|
-
tree = groqJs.parse(
|
|
1052
|
+
tree = groqJs.parse(groq2);
|
|
443
1053
|
} catch (err) {
|
|
444
1054
|
issues.push(errorMessage(err));
|
|
445
1055
|
}
|
|
446
|
-
if (/\*\s*\[\s*_type\b/.test(
|
|
447
|
-
boundVars !== void 0 && tree !== void 0) for (const name of conditionParameterNames(
|
|
1056
|
+
if (/\*\s*\[\s*_type\b/.test(groq2) && issues.push("condition scans by `_type` — that's a discovery query, not a predicate. Conditions evaluate against the in-memory snapshot (instance + ancestors + field-declared docs). To bring extra docs in scope, declare a `doc.ref` (or `doc.refs`) field entry on the workflow or this stage. For lake scans like \"all articles in this release\", use a spawn action's `forEach` instead."),
|
|
1057
|
+
boundVars !== void 0 && tree !== void 0) for (const name of conditionParameterNames(groq2)) boundVars.includes(name) || issues.push(`reads $${name}, which this scope does not bind — an unbound variable evaluates to GROQ null, so the condition silently never matches. Bound here: ` + boundVars.map(n => `$${n}`).join(", "));
|
|
448
1058
|
return issues;
|
|
449
1059
|
}
|
|
450
1060
|
|
|
451
|
-
function conditionParameterNames(
|
|
1061
|
+
function conditionParameterNames(groq2) {
|
|
452
1062
|
const read = /* @__PURE__ */ new Set;
|
|
453
|
-
return walkAstNodes(tryParseGroq(
|
|
1063
|
+
return walkAstNodes(tryParseGroq(groq2), node => {
|
|
454
1064
|
node.type === "Parameter" && typeof node.name == "string" && read.add(node.name);
|
|
455
1065
|
}), read;
|
|
456
1066
|
}
|
|
457
1067
|
|
|
458
|
-
function conditionFieldReadNames(
|
|
1068
|
+
function conditionFieldReadNames(groq2) {
|
|
459
1069
|
const read = /* @__PURE__ */ new Set;
|
|
460
|
-
return walkAstNodes(tryParseGroq(
|
|
1070
|
+
return walkAstNodes(tryParseGroq(groq2), node => {
|
|
461
1071
|
if (node.type !== "AccessAttribute" || typeof node.name != "string") return;
|
|
462
1072
|
const base = node.base;
|
|
463
1073
|
base?.type === "Parameter" && base.name === "fields" && read.add(node.name);
|
|
464
1074
|
}), read;
|
|
465
1075
|
}
|
|
466
1076
|
|
|
467
|
-
function conditionEffectReads(
|
|
1077
|
+
function conditionEffectReads(groq2) {
|
|
468
1078
|
const reads = [];
|
|
469
|
-
return walkAstNodes(tryParseGroq(
|
|
1079
|
+
return walkAstNodes(tryParseGroq(groq2), node => {
|
|
470
1080
|
if (node.type !== "AccessAttribute" || typeof node.name != "string") return;
|
|
471
1081
|
const base = node.base;
|
|
472
1082
|
base?.type !== "AccessAttribute" || typeof base.name != "string" || base.base?.type === "Parameter" && base.base.name === "effects" && reads.push({
|
|
@@ -476,9 +1086,34 @@ function conditionEffectReads(groq) {
|
|
|
476
1086
|
}), reads;
|
|
477
1087
|
}
|
|
478
1088
|
|
|
479
|
-
function
|
|
1089
|
+
function readsRootDocument(groq2) {
|
|
1090
|
+
return nodeReadsRoot(tryParseGroq(groq2), 0);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
const SCOPED_CHILD = {
|
|
1094
|
+
Filter: "expr",
|
|
1095
|
+
Projection: "expr",
|
|
1096
|
+
Map: "expr",
|
|
1097
|
+
FlatMap: "expr",
|
|
1098
|
+
PipeFuncCall: "args"
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
function nodeReadsRoot(node, depth) {
|
|
1102
|
+
if (Array.isArray(node)) return node.some(item => nodeReadsRoot(item, depth));
|
|
1103
|
+
if (typeof node != "object" || node === null) return !1;
|
|
1104
|
+
const typed = node;
|
|
1105
|
+
if (depth === 0 && typed.type === "This" || depth === 0 && typed.type === "AccessAttribute" && typed.base === void 0) return !0;
|
|
1106
|
+
if (typed.type === "Parent") {
|
|
1107
|
+
const climb = typeof typed.n == "number" ? typed.n : 1;
|
|
1108
|
+
return depth - climb <= 0;
|
|
1109
|
+
}
|
|
1110
|
+
const scopedChild = typeof typed.type == "string" ? SCOPED_CHILD[typed.type] : void 0;
|
|
1111
|
+
return scopedChild !== void 0 ? Object.entries(node).some(([key, value]) => nodeReadsRoot(value, key === scopedChild ? depth + 1 : depth)) : Object.values(node).some(value => nodeReadsRoot(value, depth));
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
function tryParseGroq(groq2) {
|
|
480
1115
|
try {
|
|
481
|
-
return groqJs.parse(
|
|
1116
|
+
return groqJs.parse(groq2);
|
|
482
1117
|
} catch {
|
|
483
1118
|
return;
|
|
484
1119
|
}
|
|
@@ -495,7 +1130,13 @@ function walkAstNodes(node, visit) {
|
|
|
495
1130
|
}
|
|
496
1131
|
}
|
|
497
1132
|
|
|
498
|
-
const
|
|
1133
|
+
const ACTIVITY_STATUSES = [ "active", "done", "skipped", "failed" ], TERMINAL_ACTIVITY_STATUSES = [ "done", "skipped", "failed" ];
|
|
1134
|
+
|
|
1135
|
+
function isTerminalActivityStatus(status) {
|
|
1136
|
+
return TERMINAL_ACTIVITY_STATUSES.includes(status);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
const FIELD_SCOPES = [ "workflow", "stage", "activity" ], DOCUMENT_VALUE_PERMISSIONS = [ "create", "read", "update" ], MUTATION_GUARD_ACTIONS = [ "create", "update", "delete", "publish", "unpublish" ], ACTIVITY_KINDS = [ "user", "service", "script", "manual", "receive" ], EXECUTOR_CLASSIFICATIONS = [ "autonomous", "interactive", "off-system", "hybrid" ], GROUP_KINDS = [ "core", "details" ], DRIVER_KINDS = [ "person", "agent", "service", "engine" ], CONDITION_VARS = [ {
|
|
499
1140
|
name: "self",
|
|
500
1141
|
binding: "always",
|
|
501
1142
|
label: "this workflow instance",
|
|
@@ -525,11 +1166,16 @@ const CONDITION_VARS = [ {
|
|
|
525
1166
|
binding: "always",
|
|
526
1167
|
label: "the current time",
|
|
527
1168
|
description: "The ISO clock reading shared by every condition in one evaluation pass, so they agree on the time."
|
|
1169
|
+
}, {
|
|
1170
|
+
name: "context",
|
|
1171
|
+
binding: "always",
|
|
1172
|
+
label: "start-time context",
|
|
1173
|
+
description: "The instance's `context` bag: values seeded at `startInstance` plus a parent's `spawn.context` handoff, by entry name. Written only at start — never mutated afterwards."
|
|
528
1174
|
}, {
|
|
529
1175
|
name: "effects",
|
|
530
1176
|
binding: "always",
|
|
531
1177
|
label: "automation outputs",
|
|
532
|
-
description: "
|
|
1178
|
+
description: "Completed effects' outputs, namespaced by effect name (`$effects['<effect>'].<output>`) — each effect's latest completed run wins. Handler fuel — transition triggers should read instance/lake state (or $effectStatus) instead."
|
|
533
1179
|
}, {
|
|
534
1180
|
name: "effectStatus",
|
|
535
1181
|
binding: "always",
|
|
@@ -564,23 +1210,35 @@ const CONDITION_VARS = [ {
|
|
|
564
1210
|
name: "can",
|
|
565
1211
|
binding: "caller",
|
|
566
1212
|
label: "your permissions",
|
|
567
|
-
description: "Advisory per-permission booleans computed from the caller's grants; `undefined` without grants. Bound wherever grants ride the evaluation: the projection's rendered scope (action filters, activity requirements, editability predicates) and the fireAction/editField commit gates. Deploy rejects it at every site that evaluates without grants: transition
|
|
1213
|
+
description: "Advisory per-permission booleans computed from the caller's grants; `undefined` without grants. Bound wherever grants ride the evaluation: the projection's rendered scope (fireAction-action filters, activity requirements, editability predicates) and the fireAction/editField commit gates. Deploy rejects it at every site that evaluates without grants: transition `when`s, activity filters, cascade-fired actions' `when`/`filter`, effect bindings, where-op `where`s, and the spawn `forEach`/`with`/`context` sites."
|
|
568
1214
|
}, {
|
|
569
1215
|
name: "row",
|
|
570
1216
|
binding: "spawn",
|
|
571
1217
|
label: "the spawned row",
|
|
572
|
-
description: "One `
|
|
1218
|
+
description: "One `spawn.forEach` result row, bound while its `with` map evaluates — and the row under test while a where-op `where` evaluates (per row)."
|
|
573
1219
|
}, {
|
|
574
1220
|
name: "params",
|
|
575
1221
|
binding: "caller",
|
|
576
1222
|
label: "the action's arguments",
|
|
577
|
-
description: "The firing action's args — they hold values only while
|
|
1223
|
+
description: "The firing action's args — they hold values only while a fireAction-fired action's effect bindings and where-op `where`s evaluate (spawn sites don't bind it at all, and a cascade-fired action has no caller to supply args). Deploy rejects a read in the cascade gates, in a cascade-fired action's payload, and in the caller-bound projection (action filters, requirements, editable predicates): args exist only once the caller fires the action, after those sites have evaluated."
|
|
578
1224
|
}, {
|
|
579
1225
|
name: "subworkflows",
|
|
580
1226
|
binding: "always",
|
|
581
1227
|
label: "the spawned subworkflows",
|
|
582
|
-
description: "Every row of the instance's subworkflow registry, faceted by `activity`/`definition`/`rowKey`/`status` (`'active'|'done'|'aborted'`) with `current` marking the open stage entry's cohort and `stage` the child's current stage. Usable anywhere — transition
|
|
583
|
-
} ], RESERVED_CONDITION_VARS = CONDITION_VARS.map(v2 => v2.name), FILTER_SCOPE_VARS = CONDITION_VARS.filter(v2 => v2.binding === "always").map(v2 => v2.name), CALLER_BOUND_VARS = CONDITION_VARS.filter(v2 => v2.binding === "caller").map(v2 => v2.name),
|
|
1228
|
+
description: "Every row of the instance's subworkflow registry, faceted by `activity`/`action`/`definition`/`rowKey`/`status` (`'active'|'done'|'aborted'`) with `current` marking the open stage entry's cohort and `stage` the child's current stage. Usable anywhere — transition `when`s, requirements, any stage's gates; the settled gate is `count($subworkflows[activity == <name> && current && status == 'active']) == 0`."
|
|
1229
|
+
} ], RESERVED_CONDITION_VARS = CONDITION_VARS.map(v2 => v2.name), FILTER_SCOPE_VARS = CONDITION_VARS.filter(v2 => v2.binding === "always").map(v2 => v2.name), CALLER_BOUND_VARS = CONDITION_VARS.filter(v2 => v2.binding === "caller").map(v2 => v2.name), START_FILTER_VARS = [ {
|
|
1230
|
+
name: "tag",
|
|
1231
|
+
description: "The engine's tag partition — scope `*[...]` instance scans with `tag == $tag`."
|
|
1232
|
+
}, {
|
|
1233
|
+
name: "definition",
|
|
1234
|
+
description: "The `name` of the definition under evaluation (its own start.filter binds it)."
|
|
1235
|
+
}, {
|
|
1236
|
+
name: "now",
|
|
1237
|
+
description: "The ISO clock reading of the evaluating engine."
|
|
1238
|
+
}, {
|
|
1239
|
+
name: "fields",
|
|
1240
|
+
description: "The candidate initialFields by entry name, when the read surface has them (the Studio start control: yes; a per-doc picker: no). Document references bind as GDR envelopes — `$fields.<entry>.id` is the GDR URI, never a string authors assemble."
|
|
1241
|
+
} ], GUARD_PREDICATE_VARS = [ {
|
|
584
1242
|
name: "guard",
|
|
585
1243
|
description: "The guard document itself (its `metadata` carries deploy-time resolved values)."
|
|
586
1244
|
}, {
|
|
@@ -803,13 +1461,7 @@ function formatIssues(issues) {
|
|
|
803
1461
|
});
|
|
804
1462
|
}
|
|
805
1463
|
|
|
806
|
-
const
|
|
807
|
-
|
|
808
|
-
function isTerminalActivityStatus(status) {
|
|
809
|
-
return TERMINAL_ACTIVITY_STATUSES.includes(status);
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
const FIELD_SCOPES = [ "workflow", "stage", "activity" ], DOCUMENT_VALUE_PERMISSIONS = [ "create", "read", "update" ], MUTATION_GUARD_ACTIONS = [ "create", "update", "delete", "publish", "unpublish" ], ACTIVITY_KINDS = [ "user", "service", "script", "manual", "receive" ], GROUP_KINDS = [ "core", "details" ], DRIVER_KINDS = [ "person", "agent", "service", "engine" ], NonEmpty = v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1, "must be a non-empty string")), PositiveInt = v__namespace.pipe(v__namespace.number(), v__namespace.integer(), v__namespace.minValue(1)), GROQ_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1464
|
+
const NonEmpty = v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1, "must be a non-empty string")), PositiveInt = v__namespace.pipe(v__namespace.number(), v__namespace.integer(), v__namespace.minValue(1)), GROQ_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
813
1465
|
|
|
814
1466
|
function groqIdentifier(referencedAs) {
|
|
815
1467
|
return v__namespace.pipe(v__namespace.string(), v__namespace.regex(GROQ_IDENTIFIER, `must be a GROQ-safe identifier (letters, digits, underscore; not starting with a digit) because it is referenced as ${referencedAs} in GROQ conditions`));
|
|
@@ -908,7 +1560,7 @@ const StoredFieldOpSchema = v__namespace.variant("type", [ ...opSchemas(StoredFi
|
|
|
908
1560
|
type: v__namespace.literal("status.set"),
|
|
909
1561
|
activity: NonEmpty,
|
|
910
1562
|
status: picklist(ACTIVITY_STATUSES)
|
|
911
|
-
}) ]),
|
|
1563
|
+
}) ]), AuditOpSchema = v__namespace.strictObject({
|
|
912
1564
|
type: v__namespace.literal("audit"),
|
|
913
1565
|
target: AuthoringFieldRefSchema,
|
|
914
1566
|
value: ValueExprSchema,
|
|
@@ -1036,6 +1688,15 @@ const TodoListFieldSchema = pinned()(v__namespace.strictObject(listSugarFields("
|
|
|
1036
1688
|
bindings: v__namespace.optional(v__namespace.record(v__namespace.string(), ConditionSchema)),
|
|
1037
1689
|
input: v__namespace.optional(v__namespace.record(v__namespace.string(), v__namespace.unknown())),
|
|
1038
1690
|
outputs: v__namespace.optional(v__namespace.array(FieldShapeSchema))
|
|
1691
|
+
}), DefinitionRefSchema = v__namespace.strictObject({
|
|
1692
|
+
name: NonEmpty,
|
|
1693
|
+
version: v__namespace.optional(v__namespace.union([ PositiveInt, v__namespace.literal("latest") ]))
|
|
1694
|
+
}), SubworkflowsSchema = v__namespace.strictObject({
|
|
1695
|
+
forEach: NonEmpty,
|
|
1696
|
+
definition: DefinitionRefSchema,
|
|
1697
|
+
with: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
|
|
1698
|
+
context: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
|
|
1699
|
+
onExit: v__namespace.optional(picklist([ "detach", "abort" ]))
|
|
1039
1700
|
}), ActionParamSchema = v__namespace.strictObject({
|
|
1040
1701
|
type: picklist([ "string", "number", "boolean", "url", "dateTime", "actor", "doc.ref", "doc.refs", "json" ]),
|
|
1041
1702
|
name: NonEmpty,
|
|
@@ -1050,14 +1711,19 @@ function actionFields(op, group) {
|
|
|
1050
1711
|
title: v__namespace.optional(v__namespace.string()),
|
|
1051
1712
|
description: v__namespace.optional(v__namespace.string()),
|
|
1052
1713
|
group: v__namespace.optional(group),
|
|
1714
|
+
when: v__namespace.optional(ConditionSchema),
|
|
1053
1715
|
filter: v__namespace.optional(ConditionSchema),
|
|
1054
1716
|
params: v__namespace.optional(v__namespace.array(ActionParamSchema)),
|
|
1055
1717
|
ops: v__namespace.optional(v__namespace.array(op)),
|
|
1056
|
-
effects: v__namespace.optional(v__namespace.array(EffectSchema))
|
|
1718
|
+
effects: v__namespace.optional(v__namespace.array(EffectSchema)),
|
|
1719
|
+
spawn: v__namespace.optional(SubworkflowsSchema)
|
|
1057
1720
|
};
|
|
1058
1721
|
}
|
|
1059
1722
|
|
|
1060
|
-
const StoredActionSchema = pinned()(v__namespace.strictObject(
|
|
1723
|
+
const StoredActionSchema = pinned()(v__namespace.strictObject({
|
|
1724
|
+
...actionFields(StoredOpSchema, StoredGroupMembershipSchema),
|
|
1725
|
+
roles: v__namespace.optional(v__namespace.array(NonEmpty))
|
|
1726
|
+
})), TerminalActivityStatus = picklist(TERMINAL_ACTIVITY_STATUSES), RawAuthoringActionSchema = pinned()(v__namespace.strictObject({
|
|
1061
1727
|
...actionFields(AuthoringOpSchema, AuthoringGroupMembershipSchema),
|
|
1062
1728
|
roles: v__namespace.optional(v__namespace.array(NonEmpty)),
|
|
1063
1729
|
status: v__namespace.optional(TerminalActivityStatus)
|
|
@@ -1072,35 +1738,19 @@ const StoredActionSchema = pinned()(v__namespace.strictObject(actionFields(Store
|
|
|
1072
1738
|
filter: v__namespace.optional(ConditionSchema),
|
|
1073
1739
|
params: v__namespace.optional(v__namespace.array(ActionParamSchema)),
|
|
1074
1740
|
effects: v__namespace.optional(v__namespace.array(EffectSchema))
|
|
1075
|
-
})), AuthoringActionSchema = pinned()(v__namespace.
|
|
1076
|
-
name: NonEmpty,
|
|
1077
|
-
version: v__namespace.optional(v__namespace.union([ PositiveInt, v__namespace.literal("latest") ]))
|
|
1078
|
-
}), SubworkflowsSchema = v__namespace.strictObject({
|
|
1079
|
-
forEach: NonEmpty,
|
|
1080
|
-
definition: DefinitionRefSchema,
|
|
1081
|
-
with: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
|
|
1082
|
-
context: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
|
|
1083
|
-
onExit: v__namespace.optional(picklist([ "detach", "abort" ]))
|
|
1084
|
-
});
|
|
1741
|
+
})), AuthoringActionSchema = pinned()(v__namespace.lazy(input => typeof input == "object" && input !== null && "type" in input ? ClaimActionSchema : RawAuthoringActionSchema));
|
|
1085
1742
|
|
|
1086
|
-
function activityFields({field: field, action: action,
|
|
1743
|
+
function activityFields({field: field, action: action, target: target, group: group}) {
|
|
1087
1744
|
return {
|
|
1088
1745
|
name: NonEmpty,
|
|
1089
1746
|
title: v__namespace.optional(v__namespace.string()),
|
|
1090
1747
|
description: v__namespace.optional(v__namespace.string()),
|
|
1091
1748
|
groups: v__namespace.optional(v__namespace.array(GroupSchema)),
|
|
1092
1749
|
group: v__namespace.optional(group),
|
|
1093
|
-
kind: v__namespace.optional(picklist(ACTIVITY_KINDS)),
|
|
1094
1750
|
target: v__namespace.optional(target),
|
|
1095
|
-
activation: v__namespace.optional(picklist([ "auto", "manual" ])),
|
|
1096
1751
|
filter: v__namespace.optional(ConditionSchema),
|
|
1097
1752
|
requirements: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
|
|
1098
|
-
completeWhen: v__namespace.optional(ConditionSchema),
|
|
1099
|
-
failWhen: v__namespace.optional(ConditionSchema),
|
|
1100
|
-
ops: v__namespace.optional(v__namespace.array(op)),
|
|
1101
|
-
effects: v__namespace.optional(v__namespace.array(EffectSchema)),
|
|
1102
1753
|
actions: v__namespace.optional(v__namespace.array(action)),
|
|
1103
|
-
subworkflows: v__namespace.optional(SubworkflowsSchema),
|
|
1104
1754
|
fields: v__namespace.optional(v__namespace.array(field))
|
|
1105
1755
|
};
|
|
1106
1756
|
}
|
|
@@ -1108,30 +1758,26 @@ function activityFields({field: field, action: action, op: op, target: target, g
|
|
|
1108
1758
|
const StoredActivitySchema = pinned()(v__namespace.strictObject(activityFields({
|
|
1109
1759
|
field: FieldEntrySchema,
|
|
1110
1760
|
action: StoredActionSchema,
|
|
1111
|
-
op: StoredOpSchema,
|
|
1112
1761
|
target: StoredManualTargetSchema,
|
|
1113
1762
|
group: StoredGroupMembershipSchema
|
|
1114
1763
|
}))), AuthoringActivitySchema = pinned()(v__namespace.strictObject(activityFields({
|
|
1115
1764
|
field: AuthoringFieldEntrySchema,
|
|
1116
1765
|
action: AuthoringActionSchema,
|
|
1117
|
-
op: AuthoringOpSchema,
|
|
1118
1766
|
target: AuthoringManualTargetSchema,
|
|
1119
1767
|
group: AuthoringGroupMembershipSchema
|
|
1120
1768
|
})));
|
|
1121
1769
|
|
|
1122
|
-
function transitionFields(
|
|
1770
|
+
function transitionFields(when) {
|
|
1123
1771
|
return {
|
|
1124
1772
|
name: NonEmpty,
|
|
1125
1773
|
title: v__namespace.optional(v__namespace.string()),
|
|
1126
1774
|
description: v__namespace.optional(v__namespace.string()),
|
|
1127
1775
|
to: NonEmpty,
|
|
1128
|
-
|
|
1129
|
-
ops: v__namespace.optional(v__namespace.array(op)),
|
|
1130
|
-
effects: v__namespace.optional(v__namespace.array(EffectSchema))
|
|
1776
|
+
when: when
|
|
1131
1777
|
};
|
|
1132
1778
|
}
|
|
1133
1779
|
|
|
1134
|
-
const StoredTransitionSchema = pinned()(v__namespace.strictObject(transitionFields(
|
|
1780
|
+
const StoredTransitionSchema = pinned()(v__namespace.strictObject(transitionFields(ConditionSchema))), AuthoringTransitionSchema = pinned()(v__namespace.strictObject(transitionFields(v__namespace.optional(ConditionSchema)))), GuardActionSchema = picklist(MUTATION_GUARD_ACTIONS), GuardReadPath = v__namespace.pipe(NonEmpty, v__namespace.check(path => !/[\r\n\u2028\u2029]/.test(path), "a guard read path cannot contain a line break")), GuardReadSchema = v__namespace.variant("type", [ v__namespace.strictObject({
|
|
1135
1781
|
type: v__namespace.literal("self")
|
|
1136
1782
|
}), v__namespace.strictObject({
|
|
1137
1783
|
type: v__namespace.literal("now")
|
|
@@ -1193,16 +1839,25 @@ const StoredStageSchema = pinned()(v__namespace.strictObject(stageFields({
|
|
|
1193
1839
|
transition: AuthoringTransitionSchema,
|
|
1194
1840
|
guard: AuthoringGuardSchema,
|
|
1195
1841
|
editable: AuthoringEditableSchema
|
|
1196
|
-
}))), RoleAliasesSchema = v__namespace.record(NonEmpty, v__namespace.pipe(v__namespace.array(NonEmpty), v__namespace.minLength(1, "a role alias must list at least one fulfilling role"))), WORKFLOW_LIFECYCLES = [ "standalone", "child" ];
|
|
1842
|
+
}))), RoleAliasesSchema = v__namespace.record(NonEmpty, v__namespace.pipe(v__namespace.array(NonEmpty), v__namespace.minLength(1, "a role alias must list at least one fulfilling role"))), WORKFLOW_LIFECYCLES = [ "standalone", "child" ], START_KINDS = [ "interactive", "autonomous" ];
|
|
1843
|
+
|
|
1844
|
+
function startFields(kind) {
|
|
1845
|
+
return {
|
|
1846
|
+
kind: kind,
|
|
1847
|
+
filter: v__namespace.optional(ConditionSchema)
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
const StoredStartSchema = pinned()(v__namespace.strictObject(startFields(picklist(START_KINDS)))), AuthoringStartSchema = pinned()(v__namespace.strictObject(startFields(v__namespace.optional(picklist(START_KINDS)))));
|
|
1197
1852
|
|
|
1198
|
-
function workflowFields(field, stage) {
|
|
1853
|
+
function workflowFields({field: field, stage: stage, start: start}) {
|
|
1199
1854
|
return {
|
|
1200
1855
|
name: NonEmpty,
|
|
1201
1856
|
title: NonEmpty,
|
|
1202
1857
|
description: v__namespace.optional(v__namespace.string()),
|
|
1203
1858
|
groups: v__namespace.optional(v__namespace.array(GroupSchema)),
|
|
1204
1859
|
lifecycle: v__namespace.optional(picklist(WORKFLOW_LIFECYCLES)),
|
|
1205
|
-
|
|
1860
|
+
start: v__namespace.optional(start),
|
|
1206
1861
|
initialStage: NonEmpty,
|
|
1207
1862
|
fields: v__namespace.optional(v__namespace.array(field)),
|
|
1208
1863
|
stages: v__namespace.pipe(v__namespace.array(stage), v__namespace.minLength(1, "must declare at least one stage")),
|
|
@@ -1211,7 +1866,11 @@ function workflowFields(field, stage) {
|
|
|
1211
1866
|
};
|
|
1212
1867
|
}
|
|
1213
1868
|
|
|
1214
|
-
const WorkflowDefinitionSchema = pinned()(v__namespace.strictObject(workflowFields(
|
|
1869
|
+
const WorkflowDefinitionSchema = pinned()(v__namespace.strictObject(workflowFields({
|
|
1870
|
+
field: FieldEntrySchema,
|
|
1871
|
+
stage: StoredStageSchema,
|
|
1872
|
+
start: StoredStartSchema
|
|
1873
|
+
})));
|
|
1215
1874
|
|
|
1216
1875
|
function parseStoredDefinition(input, label) {
|
|
1217
1876
|
return parseOrThrow({
|
|
@@ -1221,12 +1880,28 @@ function parseStoredDefinition(input, label) {
|
|
|
1221
1880
|
});
|
|
1222
1881
|
}
|
|
1223
1882
|
|
|
1224
|
-
const AuthoringWorkflowSchema = pinned()(v__namespace.strictObject(workflowFields(
|
|
1883
|
+
const AuthoringWorkflowSchema = pinned()(v__namespace.strictObject(workflowFields({
|
|
1884
|
+
field: AuthoringFieldEntrySchema,
|
|
1885
|
+
stage: AuthoringStageSchema,
|
|
1886
|
+
start: AuthoringStartSchema
|
|
1887
|
+
}))), WORKFLOW_DEFINITION_TYPE = "sanity.workflow.definition";
|
|
1225
1888
|
|
|
1226
1889
|
function isStartableDefinition(definition) {
|
|
1227
1890
|
return definition.lifecycle !== "child";
|
|
1228
1891
|
}
|
|
1229
1892
|
|
|
1893
|
+
function startKindOf(definition) {
|
|
1894
|
+
return definition.start?.kind ?? "interactive";
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
function isSubjectEntry(entry) {
|
|
1898
|
+
return entry.required === !0 && (entry.type === "doc.ref" || entry.type === "doc.refs");
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
function isInputSourced(entry) {
|
|
1902
|
+
return entry.initialValue?.type === "input";
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1230
1905
|
function formatValidationError(label, issues) {
|
|
1231
1906
|
const lines = issues.map(issue => ` - ${issue.path.length === 0 ? "(root)" : formatIssuePath(issue.path)}: ${issue.message}`);
|
|
1232
1907
|
return `${label} failed validation (${issues.length} issue${issues.length === 1 ? "" : "s"}):\n${lines.join(`\n`)}`;
|
|
@@ -1329,12 +2004,6 @@ function checkActivities({def: def, i: i, activityNames: activityNames, issues:
|
|
|
1329
2004
|
}
|
|
1330
2005
|
|
|
1331
2006
|
function checkStatusSetTargets({activity: activity, activityNames: activityNames, path: path, issues: issues}) {
|
|
1332
|
-
checkStatusSetOps({
|
|
1333
|
-
ops: activity.ops,
|
|
1334
|
-
activityNames: activityNames,
|
|
1335
|
-
path: [ ...path, "ops" ],
|
|
1336
|
-
issues: issues
|
|
1337
|
-
});
|
|
1338
2007
|
for (const [a, action] of (activity.actions ?? []).entries()) checkStatusSetOps({
|
|
1339
2008
|
ops: action.ops,
|
|
1340
2009
|
activityNames: activityNames,
|
|
@@ -1382,25 +2051,11 @@ function checkStageReachability({def: def, stageNames: stageNames, issues: issue
|
|
|
1382
2051
|
|
|
1383
2052
|
function effectNameSites(def) {
|
|
1384
2053
|
const sites = [];
|
|
1385
|
-
for (const [i, stage] of def.stages.entries()) {
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
sites: sites
|
|
1391
|
-
});
|
|
1392
|
-
for (const [a, action] of (activity.actions ?? []).entries()) collectEffects({
|
|
1393
|
-
effects: action.effects,
|
|
1394
|
-
path: [ "stages", i, "activities", j, "actions", a, "effects" ],
|
|
1395
|
-
sites: sites
|
|
1396
|
-
});
|
|
1397
|
-
}
|
|
1398
|
-
for (const [k, t] of (stage.transitions ?? []).entries()) collectEffects({
|
|
1399
|
-
effects: t.effects,
|
|
1400
|
-
path: [ "stages", i, "transitions", k, "effects" ],
|
|
1401
|
-
sites: sites
|
|
1402
|
-
});
|
|
1403
|
-
}
|
|
2054
|
+
for (const [i, stage] of def.stages.entries()) for (const [j, activity] of (stage.activities ?? []).entries()) for (const [a, action] of (activity.actions ?? []).entries()) collectEffects({
|
|
2055
|
+
effects: action.effects,
|
|
2056
|
+
path: [ "stages", i, "activities", j, "actions", a, "effects" ],
|
|
2057
|
+
sites: sites
|
|
2058
|
+
});
|
|
1404
2059
|
return sites;
|
|
1405
2060
|
}
|
|
1406
2061
|
|
|
@@ -1487,16 +2142,16 @@ function checkPredicates(def, issues) {
|
|
|
1487
2142
|
path: [ "predicates", name ],
|
|
1488
2143
|
message: `predicate "${name}" shadows the built-in $${name} — pick another name`
|
|
1489
2144
|
});
|
|
1490
|
-
for (const [name,
|
|
2145
|
+
for (const [name, groq2] of Object.entries(def.predicates ?? {})) checkPredicateBody({
|
|
1491
2146
|
name: name,
|
|
1492
|
-
groq:
|
|
2147
|
+
groq: groq2,
|
|
1493
2148
|
names: names,
|
|
1494
2149
|
issues: issues
|
|
1495
2150
|
});
|
|
1496
2151
|
}
|
|
1497
2152
|
|
|
1498
|
-
function checkPredicateBody({name: name, groq:
|
|
1499
|
-
const reads = conditionParameterNames(
|
|
2153
|
+
function checkPredicateBody({name: name, groq: groq2, names: names, issues: issues}) {
|
|
2154
|
+
const reads = conditionParameterNames(groq2);
|
|
1500
2155
|
for (const caller of CALLER_BOUND_VARS) reads.has(caller) && issues.push({
|
|
1501
2156
|
path: [ "predicates", name ],
|
|
1502
2157
|
message: `predicate "${name}" reads $${caller} — predicates are instance-level (pre-evaluated once, without a caller); compose caller gates at the condition site instead`
|
|
@@ -1522,11 +2177,11 @@ function checkUnboundCallerVars(def, issues) {
|
|
|
1522
2177
|
}
|
|
1523
2178
|
|
|
1524
2179
|
function unboundCallerVars(policy) {
|
|
1525
|
-
return policy === "cascade" ? CALLER_BOUND_VARS : policy === "caller-bound" ? [ "params" ] : [ "can" ];
|
|
2180
|
+
return policy === "cascade" ? CALLER_BOUND_VARS : policy === "caller-bound" ? [ "params" ] : policy === "triggered-payload" ? [ "can", "params" ] : [ "can" ];
|
|
1526
2181
|
}
|
|
1527
2182
|
|
|
1528
2183
|
function callerVarMessage(site, name) {
|
|
1529
|
-
return site.policy === "cascade" ? `${site.label} reads $${name} —
|
|
2184
|
+
return site.policy === "cascade" ? `${site.label} reads $${name} — cascade gates (transition \`when\`s, activity \`filter\`s, a cascade-fired action's \`when\`/\`filter\`) must resolve identically no matter whose token drives the cascade ($assigned is constant false, the other caller vars hold no value); gate on instance state (e.g. a field an action wrote), or pin executing identities with \`roles\`` : site.policy === "caller-bound" ? `${site.label} reads $${name} — $params (the firing action's args) is bound only while the action's effect bindings and where-op \`where\`s evaluate; this site never binds it, so the condition could never pass. Bind $params in an effect binding or a where-op instead, or gate on a field an action wrote` : site.policy === "triggered-payload" && name === "params" ? `${site.label} reads $params — a cascade-fired action has no caller to supply args, so $params never holds a value in its payload; read fields or effect outputs instead` : `${site.label} reads $can — $can (the caller's grants) is bound only in the caller-bound projection (action filters, requirements, editable predicates) and never holds a value in cascade conditions; move the check to one of those sites or drop $can`;
|
|
1530
2185
|
}
|
|
1531
2186
|
|
|
1532
2187
|
function conditionSites(def) {
|
|
@@ -1559,23 +2214,11 @@ function collectStageConditionSites({stage: stage, i: i, workflowLayer: workflow
|
|
|
1559
2214
|
names: entryNames(activity.fields)
|
|
1560
2215
|
})) ];
|
|
1561
2216
|
for (const [k, t] of (stage.transitions ?? []).entries()) sites.push({
|
|
1562
|
-
groq: t.
|
|
1563
|
-
path: [ "stages", i, "transitions", k, "
|
|
1564
|
-
label: `transition "${t.name}"
|
|
2217
|
+
groq: t.when,
|
|
2218
|
+
path: [ "stages", i, "transitions", k, "when" ],
|
|
2219
|
+
label: `transition "${t.name}" when`,
|
|
1565
2220
|
policy: "cascade",
|
|
1566
2221
|
fields: stageFields2
|
|
1567
|
-
}), collectBindingSites({
|
|
1568
|
-
effects: t.effects,
|
|
1569
|
-
path: [ "stages", i, "transitions", k, "effects" ],
|
|
1570
|
-
label: `transition "${t.name}"`,
|
|
1571
|
-
fields: stageFields2,
|
|
1572
|
-
sites: sites
|
|
1573
|
-
}), collectOpWhereSites({
|
|
1574
|
-
ops: t.ops,
|
|
1575
|
-
path: [ "stages", i, "transitions", k, "ops" ],
|
|
1576
|
-
label: `transition "${t.name}"`,
|
|
1577
|
-
fields: stageFields2,
|
|
1578
|
-
sites: sites
|
|
1579
2222
|
});
|
|
1580
2223
|
collectEditableSites({
|
|
1581
2224
|
entries: stage.fields,
|
|
@@ -1609,11 +2252,14 @@ function collectEditableSites({entries: entries, path: path, labelPrefix: labelP
|
|
|
1609
2252
|
});
|
|
1610
2253
|
}
|
|
1611
2254
|
|
|
1612
|
-
function collectOpWhereSites({ops: ops, path: path, label: label, fields: fields, sites: sites}) {
|
|
2255
|
+
function collectOpWhereSites({ops: ops, path: path, label: label, policy: policy, fields: fields, sites: sites}) {
|
|
1613
2256
|
for (const [o, op] of (ops ?? []).entries()) op.where !== void 0 && sites.push({
|
|
1614
2257
|
groq: op.where,
|
|
1615
2258
|
path: [ ...path, o, "where" ],
|
|
1616
2259
|
label: `${label} ${op.type} where`,
|
|
2260
|
+
...policy !== void 0 ? {
|
|
2261
|
+
policy: policy
|
|
2262
|
+
} : {},
|
|
1617
2263
|
fields: fields
|
|
1618
2264
|
});
|
|
1619
2265
|
}
|
|
@@ -1623,36 +2269,21 @@ function collectActivityConditionSites({activity: activity, path: path, stageFie
|
|
|
1623
2269
|
scope: "activity",
|
|
1624
2270
|
names: entryNames(activity.fields)
|
|
1625
2271
|
}, ...stageFields2 ];
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
}
|
|
1636
|
-
for (const [name, groq] of Object.entries(activity.requirements ?? {})) sites.push({
|
|
1637
|
-
groq: groq,
|
|
2272
|
+
activity.filter !== void 0 && sites.push({
|
|
2273
|
+
groq: activity.filter,
|
|
2274
|
+
path: [ ...path, "filter" ],
|
|
2275
|
+
label: `activity "${activity.name}".filter`,
|
|
2276
|
+
policy: "cascade",
|
|
2277
|
+
fields: fields
|
|
2278
|
+
});
|
|
2279
|
+
for (const [name, groq2] of Object.entries(activity.requirements ?? {})) sites.push({
|
|
2280
|
+
groq: groq2,
|
|
1638
2281
|
path: [ ...path, "requirements", name ],
|
|
1639
2282
|
label: `activity "${activity.name}" requirement "${name}"`,
|
|
1640
2283
|
policy: "caller-bound",
|
|
1641
2284
|
fields: fields
|
|
1642
2285
|
});
|
|
1643
|
-
|
|
1644
|
-
effects: activity.effects,
|
|
1645
|
-
path: [ ...path, "effects" ],
|
|
1646
|
-
label: `activity "${activity.name}"`,
|
|
1647
|
-
fields: fields,
|
|
1648
|
-
sites: sites
|
|
1649
|
-
}), collectOpWhereSites({
|
|
1650
|
-
ops: activity.ops,
|
|
1651
|
-
path: [ ...path, "ops" ],
|
|
1652
|
-
label: `activity "${activity.name}"`,
|
|
1653
|
-
fields: fields,
|
|
1654
|
-
sites: sites
|
|
1655
|
-
}), collectEditableSites({
|
|
2286
|
+
collectEditableSites({
|
|
1656
2287
|
entries: activity.fields,
|
|
1657
2288
|
path: [ ...path, "fields" ],
|
|
1658
2289
|
labelPrefix: `activity "${activity.name}"`,
|
|
@@ -1663,59 +2294,77 @@ function collectActivityConditionSites({activity: activity, path: path, stageFie
|
|
|
1663
2294
|
path: path,
|
|
1664
2295
|
fields: fields,
|
|
1665
2296
|
sites: sites
|
|
1666
|
-
}), collectSubworkflowSites({
|
|
1667
|
-
activity: activity,
|
|
1668
|
-
path: path,
|
|
1669
|
-
fields: fields,
|
|
1670
|
-
sites: sites
|
|
1671
2297
|
});
|
|
1672
2298
|
}
|
|
1673
2299
|
|
|
1674
2300
|
function collectActionConditionSites({activity: activity, path: path, fields: fields, sites: sites}) {
|
|
1675
|
-
for (const [a, action] of (activity.actions ?? []).entries())
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
2301
|
+
for (const [a, action] of (activity.actions ?? []).entries()) {
|
|
2302
|
+
const actionPath = [ ...path, "actions", a ], cascadeFired = action.when !== void 0;
|
|
2303
|
+
action.when !== void 0 && sites.push({
|
|
2304
|
+
groq: action.when,
|
|
2305
|
+
path: [ ...actionPath, "when" ],
|
|
2306
|
+
label: `action "${action.name}" when`,
|
|
2307
|
+
policy: "cascade",
|
|
2308
|
+
fields: fields
|
|
2309
|
+
}), action.filter !== void 0 && sites.push({
|
|
2310
|
+
groq: action.filter,
|
|
2311
|
+
path: [ ...actionPath, "filter" ],
|
|
2312
|
+
label: `action "${action.name}" filter`,
|
|
2313
|
+
policy: cascadeFired ? "cascade" : "caller-bound",
|
|
2314
|
+
fields: fields
|
|
2315
|
+
});
|
|
2316
|
+
const payloadPolicy = cascadeFired ? "triggered-payload" : void 0;
|
|
2317
|
+
collectBindingSites({
|
|
2318
|
+
effects: action.effects,
|
|
2319
|
+
path: [ ...actionPath, "effects" ],
|
|
2320
|
+
label: `action "${action.name}"`,
|
|
2321
|
+
policy: payloadPolicy,
|
|
2322
|
+
fields: fields,
|
|
2323
|
+
sites: sites
|
|
2324
|
+
}), collectOpWhereSites({
|
|
2325
|
+
ops: action.ops,
|
|
2326
|
+
path: [ ...actionPath, "ops" ],
|
|
2327
|
+
label: `action "${action.name}"`,
|
|
2328
|
+
policy: payloadPolicy,
|
|
2329
|
+
fields: fields,
|
|
2330
|
+
sites: sites
|
|
2331
|
+
}), collectSpawnSites({
|
|
2332
|
+
action: action,
|
|
2333
|
+
path: actionPath,
|
|
2334
|
+
fields: fields,
|
|
2335
|
+
sites: sites
|
|
2336
|
+
});
|
|
2337
|
+
}
|
|
1694
2338
|
}
|
|
1695
2339
|
|
|
1696
|
-
function
|
|
1697
|
-
const
|
|
1698
|
-
if (
|
|
1699
|
-
const
|
|
2340
|
+
function collectSpawnSites({action: action, path: path, fields: fields, sites: sites}) {
|
|
2341
|
+
const spawn = action.spawn;
|
|
2342
|
+
if (spawn === void 0) return;
|
|
2343
|
+
const spawnPath = [ ...path, "spawn" ];
|
|
1700
2344
|
sites.push({
|
|
1701
|
-
groq:
|
|
1702
|
-
path: [ ...
|
|
1703
|
-
label: `
|
|
2345
|
+
groq: spawn.forEach,
|
|
2346
|
+
path: [ ...spawnPath, "forEach" ],
|
|
2347
|
+
label: `action "${action.name}".spawn.forEach`,
|
|
2348
|
+
policy: "cascade",
|
|
1704
2349
|
fields: fields
|
|
1705
2350
|
});
|
|
1706
|
-
for (const [group, record] of [ [ "with",
|
|
1707
|
-
groq:
|
|
1708
|
-
path: [ ...
|
|
1709
|
-
label: `
|
|
2351
|
+
for (const [group, record] of [ [ "with", spawn.with ], [ "context", spawn.context ] ]) for (const [key, groq2] of Object.entries(record ?? {})) sites.push({
|
|
2352
|
+
groq: groq2,
|
|
2353
|
+
path: [ ...spawnPath, group, key ],
|
|
2354
|
+
label: `action "${action.name}".spawn.${group} "${key}"`,
|
|
2355
|
+
policy: "triggered-payload",
|
|
1710
2356
|
fields: fields
|
|
1711
2357
|
});
|
|
1712
2358
|
}
|
|
1713
2359
|
|
|
1714
|
-
function collectBindingSites({effects: effects, path: path, label: label, fields: fields, sites: sites}) {
|
|
1715
|
-
for (const [e, effect] of (effects ?? []).entries()) for (const [key,
|
|
1716
|
-
groq:
|
|
2360
|
+
function collectBindingSites({effects: effects, path: path, label: label, policy: policy, fields: fields, sites: sites}) {
|
|
2361
|
+
for (const [e, effect] of (effects ?? []).entries()) for (const [key, groq2] of Object.entries(effect.bindings ?? {})) sites.push({
|
|
2362
|
+
groq: groq2,
|
|
1717
2363
|
path: [ ...path, e, "bindings", key ],
|
|
1718
2364
|
label: `${label} effect "${effect.name}" binding "${key}"`,
|
|
2365
|
+
...policy !== void 0 ? {
|
|
2366
|
+
policy: policy
|
|
2367
|
+
} : {},
|
|
1719
2368
|
fields: fields
|
|
1720
2369
|
});
|
|
1721
2370
|
}
|
|
@@ -1727,36 +2376,76 @@ function checkAssigneesEntries(def, issues) {
|
|
|
1727
2376
|
});
|
|
1728
2377
|
}
|
|
1729
2378
|
|
|
1730
|
-
function
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
2379
|
+
function checkActivityTerminalPaths(def, issues) {
|
|
2380
|
+
for (const [i, stage] of def.stages.entries()) {
|
|
2381
|
+
const resolvable = terminallyResolvableActivities(stage);
|
|
2382
|
+
for (const [j, activity] of (stage.activities ?? []).entries()) resolvable.has(activity.name) || issues.push({
|
|
2383
|
+
path: [ "stages", i, "activities", j ],
|
|
2384
|
+
message: `activity "${activity.name}" has no path to a terminal status — no action in stage "${stage.name}" resolves it (\`status: 'done'\` on one of its actions, a \`{when: <condition>, status: 'done'}\` trigger, or a sibling's \`status.set\`), so it can never finish and the stage's \`$allActivitiesDone\` gate would wedge`
|
|
2385
|
+
});
|
|
2386
|
+
}
|
|
1737
2387
|
}
|
|
1738
2388
|
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
receive: s => [ s.hasCompleteWhen || s.hasSubworkflows ? void 0 : "needs a `completeWhen` (or `subworkflows`) condition to wait on", s.hasActions ? "has no actor, so it cannot carry actions" : void 0 ].filter(m => m !== void 0),
|
|
1744
|
-
script: s => [ s.hasActions ? "runs inline with no actor, so it cannot carry actions" : void 0, s.hasCompleteWhen ? "resolves on activation, so it cannot carry a `completeWhen` (that is a `receive`/`service` wait)" : void 0, s.hasSubworkflows ? "resolves on activation, so it cannot fan out with `subworkflows`" : void 0 ].filter(m => m !== void 0)
|
|
1745
|
-
};
|
|
2389
|
+
function terminallyResolvableActivities(stage) {
|
|
2390
|
+
const terminalSets = (stage.activities ?? []).flatMap(activity => activity.actions ?? []).flatMap(action => action.ops ?? []).filter(op => op.type === "status.set").filter(op => TERMINAL_ACTIVITY_STATUSES.includes(op.status));
|
|
2391
|
+
return new Set(terminalSets.map(op => op.activity));
|
|
2392
|
+
}
|
|
1746
2393
|
|
|
1747
|
-
function
|
|
1748
|
-
for (const [i, stage] of def.stages.entries())
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
2394
|
+
function checkTerminalStageActivities(def, issues) {
|
|
2395
|
+
for (const [i, stage] of def.stages.entries()) (stage.transitions ?? []).length > 0 || (stage.activities ?? []).length === 0 || issues.push({
|
|
2396
|
+
path: [ "stages", i, "activities" ],
|
|
2397
|
+
message: `stage "${stage.name}" is terminal (no transitions) but declares activities — entering a terminal stage completes the instance, so they can never run. Move the work to a \`when\` action in the source stage (it commits in the hop that moves here), or give the stage a transition out`
|
|
2398
|
+
});
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
function storedRolesIssue(action) {
|
|
2402
|
+
if (action.roles !== void 0) {
|
|
2403
|
+
if (action.roles.length === 0) return `action "${action.name}" stores \`roles: []\`, which names no roles — the runtime reads an empty pin as "anyone may execute", the opposite of a pin. Omit \`roles\` to allow any identity, or list at least one role`;
|
|
2404
|
+
if (action.when === void 0) return `action "${action.name}" stores \`roles\` but is fireAction-fired (no \`when\`) — the stored pin is only read on cascade fires. Author \`roles\` normally (it desugars into the action's \`filter\`), or add \`when\` to make it a trigger`;
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
function checkStoredRolesPlacement(def, issues) {
|
|
2409
|
+
for (const [i, stage] of def.stages.entries()) for (const [j, activity] of (stage.activities ?? []).entries()) for (const [a, action] of (activity.actions ?? []).entries()) {
|
|
2410
|
+
const message = storedRolesIssue(action);
|
|
2411
|
+
message !== void 0 && issues.push({
|
|
2412
|
+
path: [ "stages", i, "activities", j, "actions", a, "roles" ],
|
|
2413
|
+
message: message
|
|
1756
2414
|
});
|
|
1757
2415
|
}
|
|
1758
2416
|
}
|
|
1759
2417
|
|
|
2418
|
+
function checkTriggeredActionParams(def, issues) {
|
|
2419
|
+
for (const [i, stage] of def.stages.entries()) for (const [j, activity] of (stage.activities ?? []).entries()) for (const [a, action] of (activity.actions ?? []).entries()) action.when === void 0 || (action.params ?? []).length === 0 || issues.push({
|
|
2420
|
+
path: [ "stages", i, "activities", j, "actions", a, "params" ],
|
|
2421
|
+
message: `action "${action.name}" declares params but is cascade-fired (\`when\`) — no caller ever supplies args to a trigger. Drop the params, or drop \`when\` to make it a fireAction-fired action`
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
function checkStart(def, issues) {
|
|
2426
|
+
if (def.start !== void 0 && (def.lifecycle === "child" && issues.push({
|
|
2427
|
+
path: [ "start" ],
|
|
2428
|
+
message: "a spawn-only (lifecycle 'child') definition declares `start` — children are instantiated by a parent's `spawn`, never started standalone, so the block would never apply. Remove `start`, or drop `lifecycle: 'child'`"
|
|
2429
|
+
}), checkStartFilterReads(def, issues), def.start.kind === "autonomous")) for (const [n, entry] of (def.fields ?? []).entries()) entry.required !== !0 || isSubjectEntry(entry) || issues.push({
|
|
2430
|
+
path: [ "fields", n, "required" ],
|
|
2431
|
+
message: `start.kind 'autonomous' means runs are initiated by a system reacting to a document, so every required input must be derivable from that triggering document — required entry "${entry.name}" (kind "${entry.type}") is not a document reference. Make it optional, seed it another way (query/literal), or declare it doc.ref / doc.refs`
|
|
2432
|
+
});
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
function checkStartFilterReads(def, issues) {
|
|
2436
|
+
const filter = def.start?.filter;
|
|
2437
|
+
if (filter === void 0) return;
|
|
2438
|
+
const bindable = new Set((def.fields ?? []).filter(isInputSourced).map(entry => entry.name)), declared = new Set((def.fields ?? []).map(entry => entry.name));
|
|
2439
|
+
for (const name of conditionFieldReadNames(filter)) bindable.has(name) || issues.push({
|
|
2440
|
+
path: [ "start", "filter" ],
|
|
2441
|
+
message: declared.has(name) ? `start.filter reads $fields.${name}, but "${name}" is not an \`input\`-sourced entry — $fields binds only the caller's input entries (query/literal/fieldRead entries resolve at materialisation, after any read-side evaluation), so the read is GROQ null and the filter silently never passes. Bindable (input) fields: ${knownList(bindable)}` : `start.filter reads $fields.${name}, but no workflow-scope field entry named "${name}" is declared — $fields binds only the caller's input entries, so the read is GROQ null and the filter silently never passes. Bindable (input) fields: ${knownList(bindable)}`
|
|
2442
|
+
});
|
|
2443
|
+
readsRootDocument(filter) && !(def.fields ?? []).some(isSubjectEntry) && issues.push({
|
|
2444
|
+
path: [ "start", "filter" ],
|
|
2445
|
+
message: "start.filter reads the candidate document (its root), but the definition declares no required subject entry (a required doc.ref / doc.refs input) — a read surface would never have a document to bind as root, so the filter could never pass. Declare a required subject entry, or gate on $fields / the dataset instead"
|
|
2446
|
+
});
|
|
2447
|
+
}
|
|
2448
|
+
|
|
1760
2449
|
function checkGroups(def, issues) {
|
|
1761
2450
|
const workflowGroups = checkGroupDeclarations({
|
|
1762
2451
|
groups: def.groups,
|
|
@@ -1873,7 +2562,7 @@ function checkConditionFieldReads(def, issues) {
|
|
|
1873
2562
|
path: site.path,
|
|
1874
2563
|
message: undeclaredFieldReadMessage(site, name)
|
|
1875
2564
|
});
|
|
1876
|
-
for (const [name,
|
|
2565
|
+
for (const [name, groq2] of Object.entries(def.predicates ?? {})) for (const read of conditionFieldReadNames(groq2)) everywhere.has(read) || issues.push({
|
|
1877
2566
|
path: [ "predicates", name ],
|
|
1878
2567
|
message: noFieldAnywhereMessage(`predicate "${name}"`, read)
|
|
1879
2568
|
});
|
|
@@ -1977,38 +2666,29 @@ function seedEarlierSiblingTarget(args) {
|
|
|
1977
2666
|
});
|
|
1978
2667
|
}
|
|
1979
2668
|
|
|
2669
|
+
function opSites(def) {
|
|
2670
|
+
const sites = [];
|
|
2671
|
+
for (const [i, stage] of def.stages.entries()) for (const [j, activity] of (stage.activities ?? []).entries()) for (const [a, action] of (activity.actions ?? []).entries()) sites.push({
|
|
2672
|
+
ops: action.ops,
|
|
2673
|
+
path: [ "stages", i, "activities", j, "actions", a, "ops" ],
|
|
2674
|
+
label: `action "${action.name}"`,
|
|
2675
|
+
stage: stage,
|
|
2676
|
+
activity: activity
|
|
2677
|
+
});
|
|
2678
|
+
return sites;
|
|
2679
|
+
}
|
|
2680
|
+
|
|
1980
2681
|
function checkFieldReadOpValues(def, issues) {
|
|
1981
2682
|
const workflowEntries = def.fields ?? [];
|
|
1982
|
-
for (const
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
path: [ "stages", i, "transitions", k, "ops" ],
|
|
1992
|
-
label: `transition "${t.name}"`
|
|
1993
|
-
});
|
|
1994
|
-
for (const [j, activity] of (stage.activities ?? []).entries()) {
|
|
1995
|
-
const activityNames = entryNames(activity.fields), activityPath = [ "stages", i, "activities", j ];
|
|
1996
|
-
checkOpsFieldReads({
|
|
1997
|
-
...shared,
|
|
1998
|
-
ops: activity.ops,
|
|
1999
|
-
path: [ ...activityPath, "ops" ],
|
|
2000
|
-
label: `activity "${activity.name}"`,
|
|
2001
|
-
activityNames: activityNames
|
|
2002
|
-
});
|
|
2003
|
-
for (const [a, action] of (activity.actions ?? []).entries()) checkOpsFieldReads({
|
|
2004
|
-
...shared,
|
|
2005
|
-
ops: action.ops,
|
|
2006
|
-
path: [ ...activityPath, "actions", a, "ops" ],
|
|
2007
|
-
label: `action "${action.name}"`,
|
|
2008
|
-
activityNames: activityNames
|
|
2009
|
-
});
|
|
2010
|
-
}
|
|
2011
|
-
}
|
|
2683
|
+
for (const site of opSites(def)) checkOpsFieldReads({
|
|
2684
|
+
workflowEntries: workflowEntries,
|
|
2685
|
+
stageEntries: site.stage.fields ?? [],
|
|
2686
|
+
issues: issues,
|
|
2687
|
+
ops: site.ops,
|
|
2688
|
+
path: site.path,
|
|
2689
|
+
label: site.label,
|
|
2690
|
+
activityNames: entryNames(site.activity.fields)
|
|
2691
|
+
});
|
|
2012
2692
|
}
|
|
2013
2693
|
|
|
2014
2694
|
function checkOpsFieldReads({ops: ops, path: path, label: label, ...ctx}) {
|
|
@@ -2061,6 +2741,49 @@ function opFieldReadMissMessage({read: read, where: where, hosts: hosts, activit
|
|
|
2061
2741
|
return `${where} reads field "${read.field}", which is not declared at ${searched} — the read resolves to undefined at op time, so the write silently lands empty.${activityHint} Known: ${known.join(", ") || "(none)"}`;
|
|
2062
2742
|
}
|
|
2063
2743
|
|
|
2744
|
+
function checkUpdateWhereOps(def, issues) {
|
|
2745
|
+
const workflow = def.fields ?? [];
|
|
2746
|
+
for (const site of opSites(def)) for (const [o, op] of (site.ops ?? []).entries()) {
|
|
2747
|
+
if (op.type !== "field.updateWhere") continue;
|
|
2748
|
+
const scopes = {
|
|
2749
|
+
workflow: workflow,
|
|
2750
|
+
stage: site.stage.fields ?? [],
|
|
2751
|
+
activity: site.activity.fields ?? []
|
|
2752
|
+
};
|
|
2753
|
+
checkUpdateWhereTargetKind({
|
|
2754
|
+
op: op,
|
|
2755
|
+
path: [ ...site.path, o ],
|
|
2756
|
+
label: site.label,
|
|
2757
|
+
scopes: scopes,
|
|
2758
|
+
issues: issues
|
|
2759
|
+
}), checkUpdateWhereMergeKeys({
|
|
2760
|
+
op: op,
|
|
2761
|
+
path: [ ...site.path, o ],
|
|
2762
|
+
label: site.label,
|
|
2763
|
+
issues: issues
|
|
2764
|
+
});
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2768
|
+
function checkUpdateWhereTargetKind({op: op, path: path, label: label, scopes: scopes, issues: issues}) {
|
|
2769
|
+
const target = scopes[op.target.scope]?.find(entry => entry.name === op.target.field);
|
|
2770
|
+
target === void 0 || target.type === "array" || issues.push({
|
|
2771
|
+
path: [ ...path, "target" ],
|
|
2772
|
+
message: `${label} field.updateWhere targets ${op.target.scope}-scope "${op.target.field}" (${target.type}) — updateWhere merges declared row sub-fields, so its target must be an \`array\` entry${rowOpsHint(target.type)}`
|
|
2773
|
+
});
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
function rowOpsHint(type) {
|
|
2777
|
+
return type === "doc.refs" || type === "assignees" ? `; to add or drop ${type} rows use field.append / field.removeWhere` : "";
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
function checkUpdateWhereMergeKeys({op: op, path: path, label: label, issues: issues}) {
|
|
2781
|
+
if (op.value.type === "object") for (const key of Object.keys(op.value.fields)) key !== "_key" && key !== "_type" || issues.push({
|
|
2782
|
+
path: [ ...path, "value", "fields", key ],
|
|
2783
|
+
message: `${label} field.updateWhere merge writes the reserved row key "${key}" — row identity and bookkeeping are engine-stamped, and a merge would restamp every matched row with the same literal`
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2064
2787
|
function checkGuardFieldReads(def, issues) {
|
|
2065
2788
|
const reads = {
|
|
2066
2789
|
workflowEntries: def.fields ?? [],
|
|
@@ -2258,9 +2981,11 @@ function checkWorkflowInvariants(def) {
|
|
|
2258
2981
|
stageNames: stageNames,
|
|
2259
2982
|
issues: issues
|
|
2260
2983
|
}), checkEffectNames(def, issues), checkGuardNames(def, issues), checkFieldEntryNames(def, issues),
|
|
2261
|
-
checkRequiredField(def, issues),
|
|
2262
|
-
|
|
2263
|
-
|
|
2984
|
+
checkRequiredField(def, issues), checkStart(def, issues), checkPredicates(def, issues),
|
|
2985
|
+
checkUnboundCallerVars(def, issues), checkConditionFieldReads(def, issues), checkFieldReadSeeds(def, issues),
|
|
2986
|
+
checkFieldReadOpValues(def, issues), checkUpdateWhereOps(def, issues), checkGuardFieldReads(def, issues),
|
|
2987
|
+
checkAssigneesEntries(def, issues), checkActivityTerminalPaths(def, issues), checkTerminalStageActivities(def, issues),
|
|
2988
|
+
checkTriggeredActionParams(def, issues), checkStoredRolesPlacement(def, issues),
|
|
2264
2989
|
checkGroups(def, issues), issues;
|
|
2265
2990
|
}
|
|
2266
2991
|
|
|
@@ -2290,6 +3015,8 @@ exports.CONDITION_VARS = CONDITION_VARS;
|
|
|
2290
3015
|
|
|
2291
3016
|
exports.ContractViolationError = ContractViolationError;
|
|
2292
3017
|
|
|
3018
|
+
exports.DEFAULT_TRANSITION_WHEN = DEFAULT_TRANSITION_WHEN;
|
|
3019
|
+
|
|
2293
3020
|
exports.DOCUMENT_VALUE_PERMISSIONS = DOCUMENT_VALUE_PERMISSIONS;
|
|
2294
3021
|
|
|
2295
3022
|
exports.DRIVER_KINDS = DRIVER_KINDS;
|
|
@@ -2300,6 +3027,8 @@ exports.DefinitionNotFoundError = DefinitionNotFoundError;
|
|
|
2300
3027
|
|
|
2301
3028
|
exports.EFFECTS_READ = EFFECTS_READ;
|
|
2302
3029
|
|
|
3030
|
+
exports.EXECUTOR_CLASSIFICATIONS = EXECUTOR_CLASSIFICATIONS;
|
|
3031
|
+
|
|
2303
3032
|
exports.EffectNotFoundError = EffectNotFoundError;
|
|
2304
3033
|
|
|
2305
3034
|
exports.EffectSchema = EffectSchema;
|
|
@@ -2322,6 +3051,8 @@ exports.RESERVED_CONDITION_VARS = RESERVED_CONDITION_VARS;
|
|
|
2322
3051
|
|
|
2323
3052
|
exports.RESOURCE_ALIAS_NAME_SOURCE = RESOURCE_ALIAS_NAME_SOURCE;
|
|
2324
3053
|
|
|
3054
|
+
exports.START_FILTER_VARS = START_FILTER_VARS;
|
|
3055
|
+
|
|
2325
3056
|
exports.StoredFieldOpSchema = StoredFieldOpSchema;
|
|
2326
3057
|
|
|
2327
3058
|
exports.WORKFLOW_DEFINITION_TYPE = WORKFLOW_DEFINITION_TYPE;
|
|
@@ -2342,6 +3073,8 @@ exports.clientConfigFromResource = clientConfigFromResource;
|
|
|
2342
3073
|
|
|
2343
3074
|
exports.conditionEffectReads = conditionEffectReads;
|
|
2344
3075
|
|
|
3076
|
+
exports.conditionFieldReadNames = conditionFieldReadNames;
|
|
3077
|
+
|
|
2345
3078
|
exports.conditionParameterNames = conditionParameterNames;
|
|
2346
3079
|
|
|
2347
3080
|
exports.conditionSyntaxIssues = conditionSyntaxIssues;
|
|
@@ -2350,6 +3083,14 @@ exports.datasetResourceParts = datasetResourceParts;
|
|
|
2350
3083
|
|
|
2351
3084
|
exports.definitionDocId = definitionDocId;
|
|
2352
3085
|
|
|
3086
|
+
exports.deriveActivityKind = deriveActivityKind;
|
|
3087
|
+
|
|
3088
|
+
exports.deriveExecutorClassification = deriveExecutorClassification;
|
|
3089
|
+
|
|
3090
|
+
exports.desugarWorkflow = desugarWorkflow;
|
|
3091
|
+
|
|
3092
|
+
exports.driverKind = driverKind;
|
|
3093
|
+
|
|
2353
3094
|
exports.errorMessage = errorMessage;
|
|
2354
3095
|
|
|
2355
3096
|
exports.evaluateCondition = evaluateCondition;
|
|
@@ -2358,8 +3099,6 @@ exports.evaluateConditionOutcome = evaluateConditionOutcome;
|
|
|
2358
3099
|
|
|
2359
3100
|
exports.evaluatePredicates = evaluatePredicates;
|
|
2360
3101
|
|
|
2361
|
-
exports.expandRequiredRoles = expandRequiredRoles;
|
|
2362
|
-
|
|
2363
3102
|
exports.extractDocumentId = extractDocumentId;
|
|
2364
3103
|
|
|
2365
3104
|
exports.formatIssuePath = formatIssuePath;
|
|
@@ -2376,20 +3115,28 @@ exports.gdrResourcePrefix = gdrResourcePrefix;
|
|
|
2376
3115
|
|
|
2377
3116
|
exports.gdrUri = gdrUri;
|
|
2378
3117
|
|
|
3118
|
+
exports.groq = groq;
|
|
3119
|
+
|
|
2379
3120
|
exports.groupMembershipNames = groupMembershipNames;
|
|
2380
3121
|
|
|
2381
3122
|
exports.isBareSeedId = isBareSeedId;
|
|
2382
3123
|
|
|
3124
|
+
exports.isCascadeFired = isCascadeFired;
|
|
3125
|
+
|
|
2383
3126
|
exports.isGdr = isGdr;
|
|
2384
3127
|
|
|
2385
3128
|
exports.isGdrUri = isGdrUri;
|
|
2386
3129
|
|
|
2387
3130
|
exports.isGuardReadExpr = isGuardReadExpr;
|
|
2388
3131
|
|
|
3132
|
+
exports.isInputSourced = isInputSourced;
|
|
3133
|
+
|
|
2389
3134
|
exports.isNotesEntry = isNotesEntry;
|
|
2390
3135
|
|
|
2391
3136
|
exports.isStartableDefinition = isStartableDefinition;
|
|
2392
3137
|
|
|
3138
|
+
exports.isSubjectEntry = isSubjectEntry;
|
|
3139
|
+
|
|
2393
3140
|
exports.isTerminalActivityStatus = isTerminalActivityStatus;
|
|
2394
3141
|
|
|
2395
3142
|
exports.isTodoListEntry = isTodoListEntry;
|
|
@@ -2400,8 +3147,6 @@ exports.isUnevaluable = isUnevaluable;
|
|
|
2400
3147
|
|
|
2401
3148
|
exports.labelFor = labelFor;
|
|
2402
3149
|
|
|
2403
|
-
exports.normalizeRoleAliases = normalizeRoleAliases;
|
|
2404
|
-
|
|
2405
3150
|
exports.parseGdr = parseGdr;
|
|
2406
3151
|
|
|
2407
3152
|
exports.parseOrThrow = parseOrThrow;
|
|
@@ -2410,7 +3155,7 @@ exports.parseResourceGdr = parseResourceGdr;
|
|
|
2410
3155
|
|
|
2411
3156
|
exports.parseStoredDefinition = parseStoredDefinition;
|
|
2412
3157
|
|
|
2413
|
-
exports.
|
|
3158
|
+
exports.readsRootDocument = readsRootDocument;
|
|
2414
3159
|
|
|
2415
3160
|
exports.refCanvas = refCanvas;
|
|
2416
3161
|
|
|
@@ -2444,6 +3189,8 @@ exports.sameResource = sameResource;
|
|
|
2444
3189
|
|
|
2445
3190
|
exports.selfGdr = selfGdr;
|
|
2446
3191
|
|
|
3192
|
+
exports.startKindOf = startKindOf;
|
|
3193
|
+
|
|
2447
3194
|
exports.tagScopeFilter = tagScopeFilter;
|
|
2448
3195
|
|
|
2449
3196
|
exports.toBareId = toBareId;
|