@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
|
@@ -4,6 +4,26 @@ import { conditionOutcome, runGroq as runGroq$1, evaluateConditionOutcome as eva
|
|
|
4
4
|
|
|
5
5
|
import { parse } from "groq-js";
|
|
6
6
|
|
|
7
|
+
function isCascadeFired(action) {
|
|
8
|
+
return action.when !== void 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function deriveActivityKind(activity) {
|
|
12
|
+
if (activity.target !== void 0) return "manual";
|
|
13
|
+
const actions = activity.actions ?? [];
|
|
14
|
+
return actions.some(a => !isCascadeFired(a)) ? "user" : actions.some(a => (a.effects ?? []).length > 0 || a.spawn !== void 0) ? "service" : actions.length > 0 ? "receive" : "script";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function deriveExecutorClassification(activity) {
|
|
18
|
+
if (activity.target !== void 0) return "off-system";
|
|
19
|
+
const actions = activity.actions ?? [], cascadeFired = actions.filter(isCascadeFired).length;
|
|
20
|
+
return cascadeFired === actions.length ? "autonomous" : cascadeFired === 0 ? "interactive" : "hybrid";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function driverKind(actor) {
|
|
24
|
+
return actor.kind === "person" || actor.kind === "agent" ? actor.kind : "service";
|
|
25
|
+
}
|
|
26
|
+
|
|
7
27
|
function errorMessage(err) {
|
|
8
28
|
return (err instanceof Error ? err.message : String(err)).replace(/[^\P{Cc}\n\t]/gu, "");
|
|
9
29
|
}
|
|
@@ -381,6 +401,596 @@ function actorFulfillsRole({actorRoles: actorRoles, required: required, aliases:
|
|
|
381
401
|
return actorRoles.some(role => accepted.has(role));
|
|
382
402
|
}
|
|
383
403
|
|
|
404
|
+
function groq(strings, ...values) {
|
|
405
|
+
return strings.reduce((out, part, i) => i === 0 ? part : `${out}${serializeGroqValue(values[i - 1])}${part}`, "");
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function serializeGroqValue(value) {
|
|
409
|
+
const serialized = JSON.stringify(value);
|
|
410
|
+
if (serialized === void 0) throw new Error(`groq tag cannot serialize ${typeof value} — interpolate JSON-representable values only`);
|
|
411
|
+
return serialized;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function desugarWorkflow(authoring) {
|
|
415
|
+
const issues = [];
|
|
416
|
+
checkReservedRoleAliasKeys(authoring.roleAliases, issues);
|
|
417
|
+
const roleAliases = normalizeRoleAliases(authoring.roleAliases), ctx = {
|
|
418
|
+
issues: issues,
|
|
419
|
+
claimFields: /* @__PURE__ */ new Map,
|
|
420
|
+
claimedFields: /* @__PURE__ */ new Set,
|
|
421
|
+
roleAliases: roleAliases
|
|
422
|
+
}, workflowFields2 = desugarFieldEntries({
|
|
423
|
+
entries: authoring.fields,
|
|
424
|
+
path: [ "fields" ],
|
|
425
|
+
ctx: ctx
|
|
426
|
+
}), workflowLayer = layerOf(workflowFields2), stages = authoring.stages.map((stage, i) => {
|
|
427
|
+
const path = [ "stages", i ], stageFields2 = desugarFieldEntries({
|
|
428
|
+
entries: stage.fields,
|
|
429
|
+
path: [ ...path, "fields" ],
|
|
430
|
+
ctx: ctx
|
|
431
|
+
}), stageEnv = {
|
|
432
|
+
layers: [ {
|
|
433
|
+
scope: "stage",
|
|
434
|
+
entries: layerOf(stageFields2)
|
|
435
|
+
}, {
|
|
436
|
+
scope: "workflow",
|
|
437
|
+
entries: workflowLayer
|
|
438
|
+
} ]
|
|
439
|
+
}, activities = (stage.activities ?? []).map((activity, j) => desugarActivity({
|
|
440
|
+
activity: activity,
|
|
441
|
+
path: [ ...path, "activities", j ],
|
|
442
|
+
stageEnv: stageEnv,
|
|
443
|
+
ctx: ctx
|
|
444
|
+
})), transitions = (stage.transitions ?? []).map(transition => desugarTransition({
|
|
445
|
+
transition: transition
|
|
446
|
+
})), editable = desugarStageEditable({
|
|
447
|
+
overrides: stage.editable,
|
|
448
|
+
inScope: editableFieldNames({
|
|
449
|
+
workflowFields: workflowFields2,
|
|
450
|
+
stageFields: stageFields2,
|
|
451
|
+
activities: activities
|
|
452
|
+
}),
|
|
453
|
+
path: [ ...path, "editable" ],
|
|
454
|
+
ctx: ctx
|
|
455
|
+
});
|
|
456
|
+
return {
|
|
457
|
+
...stripUndefined({
|
|
458
|
+
name: stage.name,
|
|
459
|
+
title: stage.title,
|
|
460
|
+
description: stage.description,
|
|
461
|
+
groups: stage.groups,
|
|
462
|
+
guards: stage.guards?.map(desugarGuard)
|
|
463
|
+
}),
|
|
464
|
+
...stageFields2 ? {
|
|
465
|
+
fields: stageFields2
|
|
466
|
+
} : {},
|
|
467
|
+
...activities.length > 0 ? {
|
|
468
|
+
activities: activities
|
|
469
|
+
} : {},
|
|
470
|
+
...transitions.length > 0 ? {
|
|
471
|
+
transitions: transitions
|
|
472
|
+
} : {},
|
|
473
|
+
...editable ? {
|
|
474
|
+
editable: editable
|
|
475
|
+
} : {}
|
|
476
|
+
};
|
|
477
|
+
});
|
|
478
|
+
return checkUnclaimedClaimFields(ctx), {
|
|
479
|
+
definition: {
|
|
480
|
+
...stripUndefined({
|
|
481
|
+
name: authoring.name,
|
|
482
|
+
title: authoring.title,
|
|
483
|
+
description: authoring.description,
|
|
484
|
+
groups: authoring.groups,
|
|
485
|
+
lifecycle: authoring.lifecycle,
|
|
486
|
+
start: desugarStart(authoring.start),
|
|
487
|
+
initialStage: authoring.initialStage,
|
|
488
|
+
predicates: authoring.predicates,
|
|
489
|
+
roleAliases: roleAliases
|
|
490
|
+
}),
|
|
491
|
+
...workflowFields2 ? {
|
|
492
|
+
fields: workflowFields2
|
|
493
|
+
} : {},
|
|
494
|
+
stages: stages
|
|
495
|
+
},
|
|
496
|
+
issues: ctx.issues
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function desugarStart(start) {
|
|
501
|
+
if (start !== void 0) return {
|
|
502
|
+
kind: start.kind ?? "interactive",
|
|
503
|
+
...start.filter !== void 0 ? {
|
|
504
|
+
filter: start.filter
|
|
505
|
+
} : {}
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function checkReservedRoleAliasKeys(aliases, issues) {
|
|
510
|
+
for (const key of Object.keys(aliases ?? {})) key.startsWith("$") && issues.push({
|
|
511
|
+
path: [ "roleAliases", key ],
|
|
512
|
+
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.`
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const TODOLIST_OF = [ {
|
|
517
|
+
type: "string",
|
|
518
|
+
name: "label",
|
|
519
|
+
title: "Label"
|
|
520
|
+
}, {
|
|
521
|
+
type: "string",
|
|
522
|
+
name: "status",
|
|
523
|
+
title: "Status"
|
|
524
|
+
}, {
|
|
525
|
+
type: "assignee",
|
|
526
|
+
name: "assignee",
|
|
527
|
+
title: "Assignee"
|
|
528
|
+
}, {
|
|
529
|
+
type: "date",
|
|
530
|
+
name: "dueDate",
|
|
531
|
+
title: "Due date"
|
|
532
|
+
} ], NOTES_OF = [ {
|
|
533
|
+
type: "text",
|
|
534
|
+
name: "body",
|
|
535
|
+
title: "Body"
|
|
536
|
+
}, {
|
|
537
|
+
type: "actor",
|
|
538
|
+
name: "actor",
|
|
539
|
+
title: "Actor"
|
|
540
|
+
}, {
|
|
541
|
+
type: "datetime",
|
|
542
|
+
name: "at",
|
|
543
|
+
title: "At"
|
|
544
|
+
} ];
|
|
545
|
+
|
|
546
|
+
function desugarFieldEntries({entries: entries, path: path, ctx: ctx}) {
|
|
547
|
+
return !entries || entries.length === 0 ? entries === void 0 ? void 0 : [] : entries.map((entry, i) => desugarFieldEntry({
|
|
548
|
+
entry: entry,
|
|
549
|
+
path: [ ...path, i ],
|
|
550
|
+
ctx: ctx
|
|
551
|
+
}));
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function desugarFieldEntry({entry: entry, path: path, ctx: ctx}) {
|
|
555
|
+
if (entry.type === "claim") return desugarClaimField({
|
|
556
|
+
entry: entry,
|
|
557
|
+
path: path,
|
|
558
|
+
ctx: ctx
|
|
559
|
+
});
|
|
560
|
+
if (entry.type === "todoList" || entry.type === "notes") return desugarListField({
|
|
561
|
+
entry: entry,
|
|
562
|
+
path: path,
|
|
563
|
+
ctx: ctx
|
|
564
|
+
});
|
|
565
|
+
const editable = normalizeEditable({
|
|
566
|
+
editable: entry.editable,
|
|
567
|
+
path: [ ...path, "editable" ],
|
|
568
|
+
ctx: ctx
|
|
569
|
+
});
|
|
570
|
+
return {
|
|
571
|
+
...stripUndefined({
|
|
572
|
+
type: entry.type,
|
|
573
|
+
name: entry.name,
|
|
574
|
+
title: entry.title,
|
|
575
|
+
description: entry.description,
|
|
576
|
+
group: normalizeGroup(entry.group),
|
|
577
|
+
required: entry.required,
|
|
578
|
+
initialValue: entry.initialValue,
|
|
579
|
+
editable: editable,
|
|
580
|
+
types: entry.types,
|
|
581
|
+
fields: entry.fields,
|
|
582
|
+
of: entry.of
|
|
583
|
+
})
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function desugarClaimField({entry: entry, path: path, ctx: ctx}) {
|
|
588
|
+
const desugared = {
|
|
589
|
+
...stripUndefined({
|
|
590
|
+
name: entry.name,
|
|
591
|
+
title: entry.title,
|
|
592
|
+
description: entry.description,
|
|
593
|
+
group: normalizeGroup(entry.group)
|
|
594
|
+
}),
|
|
595
|
+
type: "actor"
|
|
596
|
+
};
|
|
597
|
+
return ctx.claimFields.set(desugared, {
|
|
598
|
+
name: entry.name,
|
|
599
|
+
path: path
|
|
600
|
+
}), desugared;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function desugarListField({entry: entry, path: path, ctx: ctx}) {
|
|
604
|
+
const editable = normalizeEditable({
|
|
605
|
+
editable: entry.editable,
|
|
606
|
+
path: [ ...path, "editable" ],
|
|
607
|
+
ctx: ctx
|
|
608
|
+
});
|
|
609
|
+
return {
|
|
610
|
+
...stripUndefined({
|
|
611
|
+
name: entry.name,
|
|
612
|
+
title: entry.title,
|
|
613
|
+
description: entry.description,
|
|
614
|
+
group: normalizeGroup(entry.group),
|
|
615
|
+
required: entry.required,
|
|
616
|
+
initialValue: entry.initialValue,
|
|
617
|
+
editable: editable
|
|
618
|
+
}),
|
|
619
|
+
type: "array",
|
|
620
|
+
of: entry.type === "todoList" ? TODOLIST_OF : NOTES_OF
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function normalizeEditable({editable: editable, path: path, ctx: ctx}) {
|
|
625
|
+
if (editable === void 0 || editable === !0) return editable;
|
|
626
|
+
if (Array.isArray(editable)) {
|
|
627
|
+
const condition = rolesCondition(editable, ctx.roleAliases);
|
|
628
|
+
if (condition === void 0) {
|
|
629
|
+
ctx.issues.push({
|
|
630
|
+
path: path,
|
|
631
|
+
message: "editable: [] names no roles — use `true` to open the field to anyone in its scope, or list at least one role"
|
|
632
|
+
});
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
return condition;
|
|
636
|
+
}
|
|
637
|
+
return editable;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function editableFieldNames({workflowFields: workflowFields2, stageFields: stageFields2, activities: activities}) {
|
|
641
|
+
const names = /* @__PURE__ */ new Set, collect = entries => {
|
|
642
|
+
for (const entry of entries ?? []) entry.editable !== void 0 && names.add(entry.name);
|
|
643
|
+
};
|
|
644
|
+
collect(workflowFields2), collect(stageFields2);
|
|
645
|
+
for (const activity of activities) collect(activity.fields);
|
|
646
|
+
return names;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function desugarStageEditable({overrides: overrides, inScope: inScope, path: path, ctx: ctx}) {
|
|
650
|
+
if (overrides === void 0) return;
|
|
651
|
+
const out = {};
|
|
652
|
+
for (const [name, value] of Object.entries(overrides)) {
|
|
653
|
+
if (!inScope.has(name)) {
|
|
654
|
+
ctx.issues.push({
|
|
655
|
+
path: [ ...path, name ],
|
|
656
|
+
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\``
|
|
657
|
+
});
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
const normalized = normalizeEditable({
|
|
661
|
+
editable: value,
|
|
662
|
+
path: [ ...path, name ],
|
|
663
|
+
ctx: ctx
|
|
664
|
+
});
|
|
665
|
+
normalized !== void 0 && (out[name] = normalized);
|
|
666
|
+
}
|
|
667
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function layerOf(entries) {
|
|
671
|
+
return new Map((entries ?? []).map(entry => [ entry.name, entry ]));
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function normalizeGroup(group) {
|
|
675
|
+
return group === void 0 || Array.isArray(group) ? group : [ group ];
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function desugarActivity({activity: activity, path: path, stageEnv: stageEnv, ctx: ctx}) {
|
|
679
|
+
const activityFields2 = desugarFieldEntries({
|
|
680
|
+
entries: activity.fields,
|
|
681
|
+
path: [ ...path, "fields" ],
|
|
682
|
+
ctx: ctx
|
|
683
|
+
}), env = {
|
|
684
|
+
layers: [ {
|
|
685
|
+
scope: "activity",
|
|
686
|
+
entries: layerOf(activityFields2)
|
|
687
|
+
}, ...stageEnv.layers ]
|
|
688
|
+
}, actions = (activity.actions ?? []).map((action, a) => desugarAction({
|
|
689
|
+
action: action,
|
|
690
|
+
path: [ ...path, "actions", a ],
|
|
691
|
+
env: env,
|
|
692
|
+
activityName: activity.name,
|
|
693
|
+
ctx: ctx
|
|
694
|
+
})), target = desugarTarget({
|
|
695
|
+
target: activity.target,
|
|
696
|
+
env: env,
|
|
697
|
+
path: [ ...path, "target" ],
|
|
698
|
+
ctx: ctx
|
|
699
|
+
});
|
|
700
|
+
return {
|
|
701
|
+
...stripUndefined({
|
|
702
|
+
name: activity.name,
|
|
703
|
+
title: activity.title,
|
|
704
|
+
description: activity.description,
|
|
705
|
+
groups: activity.groups,
|
|
706
|
+
group: normalizeGroup(activity.group),
|
|
707
|
+
filter: activity.filter,
|
|
708
|
+
requirements: activity.requirements
|
|
709
|
+
}),
|
|
710
|
+
...target ? {
|
|
711
|
+
target: target
|
|
712
|
+
} : {},
|
|
713
|
+
...activityFields2 ? {
|
|
714
|
+
fields: activityFields2
|
|
715
|
+
} : {},
|
|
716
|
+
...actions.length > 0 ? {
|
|
717
|
+
actions: actions
|
|
718
|
+
} : {}
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const TARGET_DOC_KINDS = [ "doc.ref", "doc.refs", "release.ref" ];
|
|
723
|
+
|
|
724
|
+
function desugarTarget({target: target, env: env, path: path, ctx: ctx}) {
|
|
725
|
+
if (target === void 0 || target.type === "url") return target;
|
|
726
|
+
const ref = typeof target.field == "string" ? {
|
|
727
|
+
field: target.field
|
|
728
|
+
} : target.field, field = resolveRef({
|
|
729
|
+
ref: ref,
|
|
730
|
+
env: env,
|
|
731
|
+
path: [ ...path, "field" ],
|
|
732
|
+
ctx: ctx
|
|
733
|
+
}) ?? fallbackRef(ref), entry = entryAt(env, field);
|
|
734
|
+
return entry && !TARGET_DOC_KINDS.includes(entry.type) && ctx.issues.push({
|
|
735
|
+
path: [ ...path, "field" ],
|
|
736
|
+
message: `manual activity target references "${field.field}" of kind "${entry.type}" — a deep-link target needs a document-valued entry (${TARGET_DOC_KINDS.join(", ")})`
|
|
737
|
+
}), {
|
|
738
|
+
type: "field",
|
|
739
|
+
field: field
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function desugarAction({action: action, path: path, env: env, activityName: activityName, ctx: ctx}) {
|
|
744
|
+
if ("type" in action) return desugarClaimAction({
|
|
745
|
+
action: action,
|
|
746
|
+
path: path,
|
|
747
|
+
env: env,
|
|
748
|
+
ctx: ctx
|
|
749
|
+
});
|
|
750
|
+
action.roles !== void 0 && action.roles.length === 0 && ctx.issues.push({
|
|
751
|
+
path: [ ...path, "roles" ],
|
|
752
|
+
message: "roles: [] names no roles — omit it to allow any identity, or list at least one role"
|
|
753
|
+
});
|
|
754
|
+
const cascadeFired = isCascadeFired(action), filter = cascadeFired ? action.filter : andConditions([ rolesCondition(action.roles, ctx.roleAliases), action.filter ]), ops = desugarOps({
|
|
755
|
+
ops: action.ops,
|
|
756
|
+
path: [ ...path, "ops" ],
|
|
757
|
+
env: env,
|
|
758
|
+
firingActivity: activityName,
|
|
759
|
+
ctx: ctx
|
|
760
|
+
}) ?? [];
|
|
761
|
+
return action.status !== void 0 && ops.push({
|
|
762
|
+
type: "status.set",
|
|
763
|
+
activity: activityName,
|
|
764
|
+
status: action.status
|
|
765
|
+
}), {
|
|
766
|
+
...stripUndefined({
|
|
767
|
+
name: action.name,
|
|
768
|
+
title: action.title,
|
|
769
|
+
description: action.description,
|
|
770
|
+
group: normalizeGroup(action.group),
|
|
771
|
+
when: action.when,
|
|
772
|
+
params: action.params,
|
|
773
|
+
effects: action.effects,
|
|
774
|
+
spawn: action.spawn
|
|
775
|
+
}),
|
|
776
|
+
...cascadeFired && action.roles !== void 0 && action.roles.length > 0 ? {
|
|
777
|
+
roles: action.roles
|
|
778
|
+
} : {},
|
|
779
|
+
...filter ? {
|
|
780
|
+
filter: filter
|
|
781
|
+
} : {},
|
|
782
|
+
...ops.length > 0 ? {
|
|
783
|
+
ops: ops
|
|
784
|
+
} : {}
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function desugarClaimAction({action: action, path: path, env: env, ctx: ctx}) {
|
|
789
|
+
const ref = typeof action.field == "string" ? {
|
|
790
|
+
field: action.field
|
|
791
|
+
} : action.field, resolved = resolveRef({
|
|
792
|
+
ref: ref,
|
|
793
|
+
env: env,
|
|
794
|
+
path: [ ...path, "field" ],
|
|
795
|
+
ctx: ctx
|
|
796
|
+
});
|
|
797
|
+
if (resolved) {
|
|
798
|
+
const entry = entryAt(env, resolved);
|
|
799
|
+
entry && entry.type !== "actor" && ctx.issues.push({
|
|
800
|
+
path: [ ...path, "field" ],
|
|
801
|
+
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)`
|
|
802
|
+
}), checkShadowedClaimTarget({
|
|
803
|
+
actionName: action.name,
|
|
804
|
+
field: ref.field,
|
|
805
|
+
resolved: resolved,
|
|
806
|
+
env: env,
|
|
807
|
+
path: [ ...path, "field" ],
|
|
808
|
+
ctx: ctx
|
|
809
|
+
}), entry && ctx.claimedFields.add(entry);
|
|
810
|
+
}
|
|
811
|
+
const noSteal = `!defined($fields.${ref.field})`, filter = andConditions([ noSteal, rolesCondition(action.roles, ctx.roleAliases), action.filter ]), ops = resolved ? [ {
|
|
812
|
+
type: "field.set",
|
|
813
|
+
target: resolved,
|
|
814
|
+
value: {
|
|
815
|
+
type: "actor"
|
|
816
|
+
}
|
|
817
|
+
} ] : [];
|
|
818
|
+
return {
|
|
819
|
+
...stripUndefined({
|
|
820
|
+
name: action.name,
|
|
821
|
+
title: action.title,
|
|
822
|
+
description: action.description,
|
|
823
|
+
group: normalizeGroup(action.group),
|
|
824
|
+
params: action.params,
|
|
825
|
+
effects: action.effects
|
|
826
|
+
}),
|
|
827
|
+
filter: filter,
|
|
828
|
+
...ops.length > 0 ? {
|
|
829
|
+
ops: ops
|
|
830
|
+
} : {}
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
function checkShadowedClaimTarget({actionName: actionName, field: field, resolved: resolved, env: env, path: path, ctx: ctx}) {
|
|
835
|
+
const nearest = env.layers.find(l => l.entries.has(field));
|
|
836
|
+
nearest === void 0 || nearest.scope === resolved.scope || ctx.issues.push({
|
|
837
|
+
path: path,
|
|
838
|
+
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`
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function checkUnclaimedClaimFields(ctx) {
|
|
843
|
+
for (const [entry, {name: name, path: path}] of ctx.claimFields) ctx.claimedFields.has(entry) || ctx.issues.push({
|
|
844
|
+
path: path,
|
|
845
|
+
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`
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const DEFAULT_TRANSITION_WHEN = "$allActivitiesDone";
|
|
850
|
+
|
|
851
|
+
function desugarTransition({transition: transition}) {
|
|
852
|
+
return {
|
|
853
|
+
...stripUndefined({
|
|
854
|
+
name: transition.name,
|
|
855
|
+
title: transition.title,
|
|
856
|
+
description: transition.description,
|
|
857
|
+
to: transition.to
|
|
858
|
+
}),
|
|
859
|
+
when: transition.when ?? DEFAULT_TRANSITION_WHEN
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function desugarGuard(guard) {
|
|
864
|
+
const {match: match, metadata: metadata, ...rest} = guard, {idRefs: idRefs, ...matchRest} = match;
|
|
865
|
+
return {
|
|
866
|
+
...rest,
|
|
867
|
+
match: {
|
|
868
|
+
...matchRest,
|
|
869
|
+
...idRefs !== void 0 ? {
|
|
870
|
+
idRefs: idRefs.map(printGuardRead)
|
|
871
|
+
} : {}
|
|
872
|
+
},
|
|
873
|
+
...metadata !== void 0 ? {
|
|
874
|
+
metadata: Object.fromEntries(Object.entries(metadata).map(([key, read]) => [ key, printGuardRead(read) ]))
|
|
875
|
+
} : {}
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function desugarOps({ops: ops, path: path, env: env, firingActivity: firingActivity, ctx: ctx}) {
|
|
880
|
+
if (ops) return ops.map((op, i) => desugarOp({
|
|
881
|
+
op: op,
|
|
882
|
+
path: [ ...path, i ],
|
|
883
|
+
env: env,
|
|
884
|
+
firingActivity: firingActivity,
|
|
885
|
+
ctx: ctx
|
|
886
|
+
}));
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function desugarOp({op: op, path: path, env: env, firingActivity: firingActivity, ctx: ctx}) {
|
|
890
|
+
if (op.type === "status.set") return {
|
|
891
|
+
type: "status.set",
|
|
892
|
+
activity: op.activity ?? firingActivity,
|
|
893
|
+
status: op.status
|
|
894
|
+
};
|
|
895
|
+
if (op.type === "audit") return desugarAuditOp({
|
|
896
|
+
op: op,
|
|
897
|
+
path: path,
|
|
898
|
+
env: env,
|
|
899
|
+
ctx: ctx
|
|
900
|
+
});
|
|
901
|
+
const target = resolveRef({
|
|
902
|
+
ref: op.target,
|
|
903
|
+
env: env,
|
|
904
|
+
path: [ ...path, "target" ],
|
|
905
|
+
ctx: ctx
|
|
906
|
+
}) ?? fallbackRef(op.target);
|
|
907
|
+
return {
|
|
908
|
+
...op,
|
|
909
|
+
target: target
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
const AUDIT_STAMPS = {
|
|
914
|
+
actor: {
|
|
915
|
+
type: "actor"
|
|
916
|
+
},
|
|
917
|
+
at: {
|
|
918
|
+
type: "now"
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
function desugarAuditOp({op: op, path: path, env: env, ctx: ctx}) {
|
|
923
|
+
const target = resolveRef({
|
|
924
|
+
ref: op.target,
|
|
925
|
+
env: env,
|
|
926
|
+
path: [ ...path, "target" ],
|
|
927
|
+
ctx: ctx
|
|
928
|
+
}) ?? fallbackRef(op.target);
|
|
929
|
+
if (op.value.type !== "object") return ctx.issues.push({
|
|
930
|
+
path: [ ...path, "value" ],
|
|
931
|
+
message: `audit value must be an object source carrying the domain fields (got "${op.value.type}")`
|
|
932
|
+
}), {
|
|
933
|
+
type: "field.append",
|
|
934
|
+
target: target,
|
|
935
|
+
value: op.value
|
|
936
|
+
};
|
|
937
|
+
const actorField = op.stampFields?.actor ?? "actor", atField = op.stampFields?.at ?? "at", fields = {
|
|
938
|
+
...op.value.fields
|
|
939
|
+
};
|
|
940
|
+
for (const [stamp, source] of [ [ actorField, AUDIT_STAMPS.actor ], [ atField, AUDIT_STAMPS.at ] ]) {
|
|
941
|
+
if (stamp in fields) {
|
|
942
|
+
ctx.issues.push({
|
|
943
|
+
path: [ ...path, "value", "fields", stamp ],
|
|
944
|
+
message: `audit stamp field "${stamp}" collides with an authored domain field — rename yours or remap the stamp via stampFields`
|
|
945
|
+
});
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
fields[stamp] = source;
|
|
949
|
+
}
|
|
950
|
+
return {
|
|
951
|
+
type: "field.append",
|
|
952
|
+
target: target,
|
|
953
|
+
value: {
|
|
954
|
+
type: "object",
|
|
955
|
+
fields: fields
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
function resolveRef({ref: ref, env: env, path: path, ctx: ctx}) {
|
|
961
|
+
const layers = ref.scope === void 0 ? env.layers : env.layers.filter(l => l.scope === ref.scope);
|
|
962
|
+
for (const layer of layers) if (layer.entries.has(ref.field)) return {
|
|
963
|
+
scope: layer.scope,
|
|
964
|
+
field: ref.field
|
|
965
|
+
};
|
|
966
|
+
const reachable = env.layers.flatMap(l => [ ...l.entries.keys() ].map(n => `${l.scope}:${n}`));
|
|
967
|
+
ctx.issues.push({
|
|
968
|
+
path: path,
|
|
969
|
+
message: `field reference "${ref.field}"${ref.scope ? ` (scope "${ref.scope}")` : ""} does not resolve to a declared entry. Reachable: ${reachable.join(", ") || "(none)"}`
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
function entryAt(env, ref) {
|
|
974
|
+
return env.layers.find(l => l.scope === ref.scope)?.entries.get(ref.field);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function fallbackRef(ref) {
|
|
978
|
+
return {
|
|
979
|
+
scope: ref.scope ?? "workflow",
|
|
980
|
+
field: ref.field
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
function rolesCondition(roles, aliases) {
|
|
985
|
+
if (!(!roles || roles.length === 0)) return groq`count($actor.roles[@ in ${expandRequiredRoles(roles, aliases)}]) > 0`;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
function stripUndefined(obj) {
|
|
989
|
+
const out = {};
|
|
990
|
+
for (const [k, value] of Object.entries(obj)) value !== void 0 && (out[k] = value);
|
|
991
|
+
return out;
|
|
992
|
+
}
|
|
993
|
+
|
|
384
994
|
function isUnevaluable(result) {
|
|
385
995
|
return result == null;
|
|
386
996
|
}
|
|
@@ -400,9 +1010,9 @@ async function evaluateCondition(args) {
|
|
|
400
1010
|
|
|
401
1011
|
async function evaluatePredicates(args) {
|
|
402
1012
|
const out = {};
|
|
403
|
-
for (const [name,
|
|
1013
|
+
for (const [name, groq2] of Object.entries(args.predicates ?? {})) {
|
|
404
1014
|
const result = await runGroq({
|
|
405
|
-
groq:
|
|
1015
|
+
groq: groq2,
|
|
406
1016
|
params: args.params,
|
|
407
1017
|
snapshot: args.snapshot
|
|
408
1018
|
}), outcome = conditionOutcome(result);
|
|
@@ -411,46 +1021,46 @@ async function evaluatePredicates(args) {
|
|
|
411
1021
|
return out;
|
|
412
1022
|
}
|
|
413
1023
|
|
|
414
|
-
async function runGroq({groq:
|
|
1024
|
+
async function runGroq({groq: groq2, params: params, snapshot: snapshot}) {
|
|
415
1025
|
return runGroq$1({
|
|
416
|
-
groq:
|
|
1026
|
+
groq: groq2,
|
|
417
1027
|
params: params,
|
|
418
1028
|
dataset: snapshot.docs
|
|
419
1029
|
});
|
|
420
1030
|
}
|
|
421
1031
|
|
|
422
|
-
function conditionSyntaxIssues(
|
|
1032
|
+
function conditionSyntaxIssues(groq2, boundVars) {
|
|
423
1033
|
const issues = [];
|
|
424
1034
|
let tree;
|
|
425
1035
|
try {
|
|
426
|
-
tree = parse(
|
|
1036
|
+
tree = parse(groq2);
|
|
427
1037
|
} catch (err) {
|
|
428
1038
|
issues.push(errorMessage(err));
|
|
429
1039
|
}
|
|
430
|
-
if (/\*\s*\[\s*_type\b/.test(
|
|
431
|
-
boundVars !== void 0 && tree !== void 0) for (const name of conditionParameterNames(
|
|
1040
|
+
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."),
|
|
1041
|
+
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(", "));
|
|
432
1042
|
return issues;
|
|
433
1043
|
}
|
|
434
1044
|
|
|
435
|
-
function conditionParameterNames(
|
|
1045
|
+
function conditionParameterNames(groq2) {
|
|
436
1046
|
const read = /* @__PURE__ */ new Set;
|
|
437
|
-
return walkAstNodes(tryParseGroq(
|
|
1047
|
+
return walkAstNodes(tryParseGroq(groq2), node => {
|
|
438
1048
|
node.type === "Parameter" && typeof node.name == "string" && read.add(node.name);
|
|
439
1049
|
}), read;
|
|
440
1050
|
}
|
|
441
1051
|
|
|
442
|
-
function conditionFieldReadNames(
|
|
1052
|
+
function conditionFieldReadNames(groq2) {
|
|
443
1053
|
const read = /* @__PURE__ */ new Set;
|
|
444
|
-
return walkAstNodes(tryParseGroq(
|
|
1054
|
+
return walkAstNodes(tryParseGroq(groq2), node => {
|
|
445
1055
|
if (node.type !== "AccessAttribute" || typeof node.name != "string") return;
|
|
446
1056
|
const base = node.base;
|
|
447
1057
|
base?.type === "Parameter" && base.name === "fields" && read.add(node.name);
|
|
448
1058
|
}), read;
|
|
449
1059
|
}
|
|
450
1060
|
|
|
451
|
-
function conditionEffectReads(
|
|
1061
|
+
function conditionEffectReads(groq2) {
|
|
452
1062
|
const reads = [];
|
|
453
|
-
return walkAstNodes(tryParseGroq(
|
|
1063
|
+
return walkAstNodes(tryParseGroq(groq2), node => {
|
|
454
1064
|
if (node.type !== "AccessAttribute" || typeof node.name != "string") return;
|
|
455
1065
|
const base = node.base;
|
|
456
1066
|
base?.type !== "AccessAttribute" || typeof base.name != "string" || base.base?.type === "Parameter" && base.base.name === "effects" && reads.push({
|
|
@@ -460,9 +1070,34 @@ function conditionEffectReads(groq) {
|
|
|
460
1070
|
}), reads;
|
|
461
1071
|
}
|
|
462
1072
|
|
|
463
|
-
function
|
|
1073
|
+
function readsRootDocument(groq2) {
|
|
1074
|
+
return nodeReadsRoot(tryParseGroq(groq2), 0);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
const SCOPED_CHILD = {
|
|
1078
|
+
Filter: "expr",
|
|
1079
|
+
Projection: "expr",
|
|
1080
|
+
Map: "expr",
|
|
1081
|
+
FlatMap: "expr",
|
|
1082
|
+
PipeFuncCall: "args"
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1085
|
+
function nodeReadsRoot(node, depth) {
|
|
1086
|
+
if (Array.isArray(node)) return node.some(item => nodeReadsRoot(item, depth));
|
|
1087
|
+
if (typeof node != "object" || node === null) return !1;
|
|
1088
|
+
const typed = node;
|
|
1089
|
+
if (depth === 0 && typed.type === "This" || depth === 0 && typed.type === "AccessAttribute" && typed.base === void 0) return !0;
|
|
1090
|
+
if (typed.type === "Parent") {
|
|
1091
|
+
const climb = typeof typed.n == "number" ? typed.n : 1;
|
|
1092
|
+
return depth - climb <= 0;
|
|
1093
|
+
}
|
|
1094
|
+
const scopedChild = typeof typed.type == "string" ? SCOPED_CHILD[typed.type] : void 0;
|
|
1095
|
+
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));
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
function tryParseGroq(groq2) {
|
|
464
1099
|
try {
|
|
465
|
-
return parse(
|
|
1100
|
+
return parse(groq2);
|
|
466
1101
|
} catch {
|
|
467
1102
|
return;
|
|
468
1103
|
}
|
|
@@ -479,7 +1114,13 @@ function walkAstNodes(node, visit) {
|
|
|
479
1114
|
}
|
|
480
1115
|
}
|
|
481
1116
|
|
|
482
|
-
const
|
|
1117
|
+
const ACTIVITY_STATUSES = [ "active", "done", "skipped", "failed" ], TERMINAL_ACTIVITY_STATUSES = [ "done", "skipped", "failed" ];
|
|
1118
|
+
|
|
1119
|
+
function isTerminalActivityStatus(status) {
|
|
1120
|
+
return TERMINAL_ACTIVITY_STATUSES.includes(status);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
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 = [ {
|
|
483
1124
|
name: "self",
|
|
484
1125
|
binding: "always",
|
|
485
1126
|
label: "this workflow instance",
|
|
@@ -509,11 +1150,16 @@ const CONDITION_VARS = [ {
|
|
|
509
1150
|
binding: "always",
|
|
510
1151
|
label: "the current time",
|
|
511
1152
|
description: "The ISO clock reading shared by every condition in one evaluation pass, so they agree on the time."
|
|
1153
|
+
}, {
|
|
1154
|
+
name: "context",
|
|
1155
|
+
binding: "always",
|
|
1156
|
+
label: "start-time context",
|
|
1157
|
+
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."
|
|
512
1158
|
}, {
|
|
513
1159
|
name: "effects",
|
|
514
1160
|
binding: "always",
|
|
515
1161
|
label: "automation outputs",
|
|
516
|
-
description: "
|
|
1162
|
+
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."
|
|
517
1163
|
}, {
|
|
518
1164
|
name: "effectStatus",
|
|
519
1165
|
binding: "always",
|
|
@@ -548,23 +1194,35 @@ const CONDITION_VARS = [ {
|
|
|
548
1194
|
name: "can",
|
|
549
1195
|
binding: "caller",
|
|
550
1196
|
label: "your permissions",
|
|
551
|
-
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
|
|
1197
|
+
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."
|
|
552
1198
|
}, {
|
|
553
1199
|
name: "row",
|
|
554
1200
|
binding: "spawn",
|
|
555
1201
|
label: "the spawned row",
|
|
556
|
-
description: "One `
|
|
1202
|
+
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)."
|
|
557
1203
|
}, {
|
|
558
1204
|
name: "params",
|
|
559
1205
|
binding: "caller",
|
|
560
1206
|
label: "the action's arguments",
|
|
561
|
-
description: "The firing action's args — they hold values only while
|
|
1207
|
+
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."
|
|
562
1208
|
}, {
|
|
563
1209
|
name: "subworkflows",
|
|
564
1210
|
binding: "always",
|
|
565
1211
|
label: "the spawned subworkflows",
|
|
566
|
-
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
|
|
567
|
-
} ], 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),
|
|
1212
|
+
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`."
|
|
1213
|
+
} ], 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 = [ {
|
|
1214
|
+
name: "tag",
|
|
1215
|
+
description: "The engine's tag partition — scope `*[...]` instance scans with `tag == $tag`."
|
|
1216
|
+
}, {
|
|
1217
|
+
name: "definition",
|
|
1218
|
+
description: "The `name` of the definition under evaluation (its own start.filter binds it)."
|
|
1219
|
+
}, {
|
|
1220
|
+
name: "now",
|
|
1221
|
+
description: "The ISO clock reading of the evaluating engine."
|
|
1222
|
+
}, {
|
|
1223
|
+
name: "fields",
|
|
1224
|
+
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."
|
|
1225
|
+
} ], GUARD_PREDICATE_VARS = [ {
|
|
568
1226
|
name: "guard",
|
|
569
1227
|
description: "The guard document itself (its `metadata` carries deploy-time resolved values)."
|
|
570
1228
|
}, {
|
|
@@ -787,13 +1445,7 @@ function formatIssues(issues) {
|
|
|
787
1445
|
});
|
|
788
1446
|
}
|
|
789
1447
|
|
|
790
|
-
const
|
|
791
|
-
|
|
792
|
-
function isTerminalActivityStatus(status) {
|
|
793
|
-
return TERMINAL_ACTIVITY_STATUSES.includes(status);
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
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.pipe(v.string(), v.minLength(1, "must be a non-empty string")), PositiveInt = v.pipe(v.number(), v.integer(), v.minValue(1)), GROQ_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1448
|
+
const NonEmpty = v.pipe(v.string(), v.minLength(1, "must be a non-empty string")), PositiveInt = v.pipe(v.number(), v.integer(), v.minValue(1)), GROQ_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
797
1449
|
|
|
798
1450
|
function groqIdentifier(referencedAs) {
|
|
799
1451
|
return v.pipe(v.string(), v.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`));
|
|
@@ -892,7 +1544,7 @@ const StoredFieldOpSchema = v.variant("type", [ ...opSchemas(StoredFieldRefSchem
|
|
|
892
1544
|
type: v.literal("status.set"),
|
|
893
1545
|
activity: NonEmpty,
|
|
894
1546
|
status: picklist(ACTIVITY_STATUSES)
|
|
895
|
-
}) ]),
|
|
1547
|
+
}) ]), AuditOpSchema = v.strictObject({
|
|
896
1548
|
type: v.literal("audit"),
|
|
897
1549
|
target: AuthoringFieldRefSchema,
|
|
898
1550
|
value: ValueExprSchema,
|
|
@@ -1020,6 +1672,15 @@ const TodoListFieldSchema = pinned()(v.strictObject(listSugarFields("todoList"))
|
|
|
1020
1672
|
bindings: v.optional(v.record(v.string(), ConditionSchema)),
|
|
1021
1673
|
input: v.optional(v.record(v.string(), v.unknown())),
|
|
1022
1674
|
outputs: v.optional(v.array(FieldShapeSchema))
|
|
1675
|
+
}), DefinitionRefSchema = v.strictObject({
|
|
1676
|
+
name: NonEmpty,
|
|
1677
|
+
version: v.optional(v.union([ PositiveInt, v.literal("latest") ]))
|
|
1678
|
+
}), SubworkflowsSchema = v.strictObject({
|
|
1679
|
+
forEach: NonEmpty,
|
|
1680
|
+
definition: DefinitionRefSchema,
|
|
1681
|
+
with: v.optional(v.record(NonEmpty, ConditionSchema)),
|
|
1682
|
+
context: v.optional(v.record(NonEmpty, ConditionSchema)),
|
|
1683
|
+
onExit: v.optional(picklist([ "detach", "abort" ]))
|
|
1023
1684
|
}), ActionParamSchema = v.strictObject({
|
|
1024
1685
|
type: picklist([ "string", "number", "boolean", "url", "dateTime", "actor", "doc.ref", "doc.refs", "json" ]),
|
|
1025
1686
|
name: NonEmpty,
|
|
@@ -1034,14 +1695,19 @@ function actionFields(op, group) {
|
|
|
1034
1695
|
title: v.optional(v.string()),
|
|
1035
1696
|
description: v.optional(v.string()),
|
|
1036
1697
|
group: v.optional(group),
|
|
1698
|
+
when: v.optional(ConditionSchema),
|
|
1037
1699
|
filter: v.optional(ConditionSchema),
|
|
1038
1700
|
params: v.optional(v.array(ActionParamSchema)),
|
|
1039
1701
|
ops: v.optional(v.array(op)),
|
|
1040
|
-
effects: v.optional(v.array(EffectSchema))
|
|
1702
|
+
effects: v.optional(v.array(EffectSchema)),
|
|
1703
|
+
spawn: v.optional(SubworkflowsSchema)
|
|
1041
1704
|
};
|
|
1042
1705
|
}
|
|
1043
1706
|
|
|
1044
|
-
const StoredActionSchema = pinned()(v.strictObject(
|
|
1707
|
+
const StoredActionSchema = pinned()(v.strictObject({
|
|
1708
|
+
...actionFields(StoredOpSchema, StoredGroupMembershipSchema),
|
|
1709
|
+
roles: v.optional(v.array(NonEmpty))
|
|
1710
|
+
})), TerminalActivityStatus = picklist(TERMINAL_ACTIVITY_STATUSES), RawAuthoringActionSchema = pinned()(v.strictObject({
|
|
1045
1711
|
...actionFields(AuthoringOpSchema, AuthoringGroupMembershipSchema),
|
|
1046
1712
|
roles: v.optional(v.array(NonEmpty)),
|
|
1047
1713
|
status: v.optional(TerminalActivityStatus)
|
|
@@ -1056,35 +1722,19 @@ const StoredActionSchema = pinned()(v.strictObject(actionFields(StoredOpSchema,
|
|
|
1056
1722
|
filter: v.optional(ConditionSchema),
|
|
1057
1723
|
params: v.optional(v.array(ActionParamSchema)),
|
|
1058
1724
|
effects: v.optional(v.array(EffectSchema))
|
|
1059
|
-
})), AuthoringActionSchema = pinned()(v.
|
|
1060
|
-
name: NonEmpty,
|
|
1061
|
-
version: v.optional(v.union([ PositiveInt, v.literal("latest") ]))
|
|
1062
|
-
}), SubworkflowsSchema = v.strictObject({
|
|
1063
|
-
forEach: NonEmpty,
|
|
1064
|
-
definition: DefinitionRefSchema,
|
|
1065
|
-
with: v.optional(v.record(NonEmpty, ConditionSchema)),
|
|
1066
|
-
context: v.optional(v.record(NonEmpty, ConditionSchema)),
|
|
1067
|
-
onExit: v.optional(picklist([ "detach", "abort" ]))
|
|
1068
|
-
});
|
|
1725
|
+
})), AuthoringActionSchema = pinned()(v.lazy(input => typeof input == "object" && input !== null && "type" in input ? ClaimActionSchema : RawAuthoringActionSchema));
|
|
1069
1726
|
|
|
1070
|
-
function activityFields({field: field, action: action,
|
|
1727
|
+
function activityFields({field: field, action: action, target: target, group: group}) {
|
|
1071
1728
|
return {
|
|
1072
1729
|
name: NonEmpty,
|
|
1073
1730
|
title: v.optional(v.string()),
|
|
1074
1731
|
description: v.optional(v.string()),
|
|
1075
1732
|
groups: v.optional(v.array(GroupSchema)),
|
|
1076
1733
|
group: v.optional(group),
|
|
1077
|
-
kind: v.optional(picklist(ACTIVITY_KINDS)),
|
|
1078
1734
|
target: v.optional(target),
|
|
1079
|
-
activation: v.optional(picklist([ "auto", "manual" ])),
|
|
1080
1735
|
filter: v.optional(ConditionSchema),
|
|
1081
1736
|
requirements: v.optional(v.record(NonEmpty, ConditionSchema)),
|
|
1082
|
-
completeWhen: v.optional(ConditionSchema),
|
|
1083
|
-
failWhen: v.optional(ConditionSchema),
|
|
1084
|
-
ops: v.optional(v.array(op)),
|
|
1085
|
-
effects: v.optional(v.array(EffectSchema)),
|
|
1086
1737
|
actions: v.optional(v.array(action)),
|
|
1087
|
-
subworkflows: v.optional(SubworkflowsSchema),
|
|
1088
1738
|
fields: v.optional(v.array(field))
|
|
1089
1739
|
};
|
|
1090
1740
|
}
|
|
@@ -1092,30 +1742,26 @@ function activityFields({field: field, action: action, op: op, target: target, g
|
|
|
1092
1742
|
const StoredActivitySchema = pinned()(v.strictObject(activityFields({
|
|
1093
1743
|
field: FieldEntrySchema,
|
|
1094
1744
|
action: StoredActionSchema,
|
|
1095
|
-
op: StoredOpSchema,
|
|
1096
1745
|
target: StoredManualTargetSchema,
|
|
1097
1746
|
group: StoredGroupMembershipSchema
|
|
1098
1747
|
}))), AuthoringActivitySchema = pinned()(v.strictObject(activityFields({
|
|
1099
1748
|
field: AuthoringFieldEntrySchema,
|
|
1100
1749
|
action: AuthoringActionSchema,
|
|
1101
|
-
op: AuthoringOpSchema,
|
|
1102
1750
|
target: AuthoringManualTargetSchema,
|
|
1103
1751
|
group: AuthoringGroupMembershipSchema
|
|
1104
1752
|
})));
|
|
1105
1753
|
|
|
1106
|
-
function transitionFields(
|
|
1754
|
+
function transitionFields(when) {
|
|
1107
1755
|
return {
|
|
1108
1756
|
name: NonEmpty,
|
|
1109
1757
|
title: v.optional(v.string()),
|
|
1110
1758
|
description: v.optional(v.string()),
|
|
1111
1759
|
to: NonEmpty,
|
|
1112
|
-
|
|
1113
|
-
ops: v.optional(v.array(op)),
|
|
1114
|
-
effects: v.optional(v.array(EffectSchema))
|
|
1760
|
+
when: when
|
|
1115
1761
|
};
|
|
1116
1762
|
}
|
|
1117
1763
|
|
|
1118
|
-
const StoredTransitionSchema = pinned()(v.strictObject(transitionFields(
|
|
1764
|
+
const StoredTransitionSchema = pinned()(v.strictObject(transitionFields(ConditionSchema))), AuthoringTransitionSchema = pinned()(v.strictObject(transitionFields(v.optional(ConditionSchema)))), GuardActionSchema = picklist(MUTATION_GUARD_ACTIONS), GuardReadPath = v.pipe(NonEmpty, v.check(path => !/[\r\n\u2028\u2029]/.test(path), "a guard read path cannot contain a line break")), GuardReadSchema = v.variant("type", [ v.strictObject({
|
|
1119
1765
|
type: v.literal("self")
|
|
1120
1766
|
}), v.strictObject({
|
|
1121
1767
|
type: v.literal("now")
|
|
@@ -1177,16 +1823,25 @@ const StoredStageSchema = pinned()(v.strictObject(stageFields({
|
|
|
1177
1823
|
transition: AuthoringTransitionSchema,
|
|
1178
1824
|
guard: AuthoringGuardSchema,
|
|
1179
1825
|
editable: AuthoringEditableSchema
|
|
1180
|
-
}))), RoleAliasesSchema = v.record(NonEmpty, v.pipe(v.array(NonEmpty), v.minLength(1, "a role alias must list at least one fulfilling role"))), WORKFLOW_LIFECYCLES = [ "standalone", "child" ];
|
|
1826
|
+
}))), RoleAliasesSchema = v.record(NonEmpty, v.pipe(v.array(NonEmpty), v.minLength(1, "a role alias must list at least one fulfilling role"))), WORKFLOW_LIFECYCLES = [ "standalone", "child" ], START_KINDS = [ "interactive", "autonomous" ];
|
|
1827
|
+
|
|
1828
|
+
function startFields(kind) {
|
|
1829
|
+
return {
|
|
1830
|
+
kind: kind,
|
|
1831
|
+
filter: v.optional(ConditionSchema)
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1181
1834
|
|
|
1182
|
-
|
|
1835
|
+
const StoredStartSchema = pinned()(v.strictObject(startFields(picklist(START_KINDS)))), AuthoringStartSchema = pinned()(v.strictObject(startFields(v.optional(picklist(START_KINDS)))));
|
|
1836
|
+
|
|
1837
|
+
function workflowFields({field: field, stage: stage, start: start}) {
|
|
1183
1838
|
return {
|
|
1184
1839
|
name: NonEmpty,
|
|
1185
1840
|
title: NonEmpty,
|
|
1186
1841
|
description: v.optional(v.string()),
|
|
1187
1842
|
groups: v.optional(v.array(GroupSchema)),
|
|
1188
1843
|
lifecycle: v.optional(picklist(WORKFLOW_LIFECYCLES)),
|
|
1189
|
-
|
|
1844
|
+
start: v.optional(start),
|
|
1190
1845
|
initialStage: NonEmpty,
|
|
1191
1846
|
fields: v.optional(v.array(field)),
|
|
1192
1847
|
stages: v.pipe(v.array(stage), v.minLength(1, "must declare at least one stage")),
|
|
@@ -1195,7 +1850,11 @@ function workflowFields(field, stage) {
|
|
|
1195
1850
|
};
|
|
1196
1851
|
}
|
|
1197
1852
|
|
|
1198
|
-
const WorkflowDefinitionSchema = pinned()(v.strictObject(workflowFields(
|
|
1853
|
+
const WorkflowDefinitionSchema = pinned()(v.strictObject(workflowFields({
|
|
1854
|
+
field: FieldEntrySchema,
|
|
1855
|
+
stage: StoredStageSchema,
|
|
1856
|
+
start: StoredStartSchema
|
|
1857
|
+
})));
|
|
1199
1858
|
|
|
1200
1859
|
function parseStoredDefinition(input, label) {
|
|
1201
1860
|
return parseOrThrow({
|
|
@@ -1205,12 +1864,28 @@ function parseStoredDefinition(input, label) {
|
|
|
1205
1864
|
});
|
|
1206
1865
|
}
|
|
1207
1866
|
|
|
1208
|
-
const AuthoringWorkflowSchema = pinned()(v.strictObject(workflowFields(
|
|
1867
|
+
const AuthoringWorkflowSchema = pinned()(v.strictObject(workflowFields({
|
|
1868
|
+
field: AuthoringFieldEntrySchema,
|
|
1869
|
+
stage: AuthoringStageSchema,
|
|
1870
|
+
start: AuthoringStartSchema
|
|
1871
|
+
}))), WORKFLOW_DEFINITION_TYPE = "sanity.workflow.definition";
|
|
1209
1872
|
|
|
1210
1873
|
function isStartableDefinition(definition) {
|
|
1211
1874
|
return definition.lifecycle !== "child";
|
|
1212
1875
|
}
|
|
1213
1876
|
|
|
1877
|
+
function startKindOf(definition) {
|
|
1878
|
+
return definition.start?.kind ?? "interactive";
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
function isSubjectEntry(entry) {
|
|
1882
|
+
return entry.required === !0 && (entry.type === "doc.ref" || entry.type === "doc.refs");
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
function isInputSourced(entry) {
|
|
1886
|
+
return entry.initialValue?.type === "input";
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1214
1889
|
function formatValidationError(label, issues) {
|
|
1215
1890
|
const lines = issues.map(issue => ` - ${issue.path.length === 0 ? "(root)" : formatIssuePath(issue.path)}: ${issue.message}`);
|
|
1216
1891
|
return `${label} failed validation (${issues.length} issue${issues.length === 1 ? "" : "s"}):\n${lines.join(`\n`)}`;
|
|
@@ -1313,12 +1988,6 @@ function checkActivities({def: def, i: i, activityNames: activityNames, issues:
|
|
|
1313
1988
|
}
|
|
1314
1989
|
|
|
1315
1990
|
function checkStatusSetTargets({activity: activity, activityNames: activityNames, path: path, issues: issues}) {
|
|
1316
|
-
checkStatusSetOps({
|
|
1317
|
-
ops: activity.ops,
|
|
1318
|
-
activityNames: activityNames,
|
|
1319
|
-
path: [ ...path, "ops" ],
|
|
1320
|
-
issues: issues
|
|
1321
|
-
});
|
|
1322
1991
|
for (const [a, action] of (activity.actions ?? []).entries()) checkStatusSetOps({
|
|
1323
1992
|
ops: action.ops,
|
|
1324
1993
|
activityNames: activityNames,
|
|
@@ -1366,25 +2035,11 @@ function checkStageReachability({def: def, stageNames: stageNames, issues: issue
|
|
|
1366
2035
|
|
|
1367
2036
|
function effectNameSites(def) {
|
|
1368
2037
|
const sites = [];
|
|
1369
|
-
for (const [i, stage] of def.stages.entries()) {
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
sites: sites
|
|
1375
|
-
});
|
|
1376
|
-
for (const [a, action] of (activity.actions ?? []).entries()) collectEffects({
|
|
1377
|
-
effects: action.effects,
|
|
1378
|
-
path: [ "stages", i, "activities", j, "actions", a, "effects" ],
|
|
1379
|
-
sites: sites
|
|
1380
|
-
});
|
|
1381
|
-
}
|
|
1382
|
-
for (const [k, t] of (stage.transitions ?? []).entries()) collectEffects({
|
|
1383
|
-
effects: t.effects,
|
|
1384
|
-
path: [ "stages", i, "transitions", k, "effects" ],
|
|
1385
|
-
sites: sites
|
|
1386
|
-
});
|
|
1387
|
-
}
|
|
2038
|
+
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({
|
|
2039
|
+
effects: action.effects,
|
|
2040
|
+
path: [ "stages", i, "activities", j, "actions", a, "effects" ],
|
|
2041
|
+
sites: sites
|
|
2042
|
+
});
|
|
1388
2043
|
return sites;
|
|
1389
2044
|
}
|
|
1390
2045
|
|
|
@@ -1471,16 +2126,16 @@ function checkPredicates(def, issues) {
|
|
|
1471
2126
|
path: [ "predicates", name ],
|
|
1472
2127
|
message: `predicate "${name}" shadows the built-in $${name} — pick another name`
|
|
1473
2128
|
});
|
|
1474
|
-
for (const [name,
|
|
2129
|
+
for (const [name, groq2] of Object.entries(def.predicates ?? {})) checkPredicateBody({
|
|
1475
2130
|
name: name,
|
|
1476
|
-
groq:
|
|
2131
|
+
groq: groq2,
|
|
1477
2132
|
names: names,
|
|
1478
2133
|
issues: issues
|
|
1479
2134
|
});
|
|
1480
2135
|
}
|
|
1481
2136
|
|
|
1482
|
-
function checkPredicateBody({name: name, groq:
|
|
1483
|
-
const reads = conditionParameterNames(
|
|
2137
|
+
function checkPredicateBody({name: name, groq: groq2, names: names, issues: issues}) {
|
|
2138
|
+
const reads = conditionParameterNames(groq2);
|
|
1484
2139
|
for (const caller of CALLER_BOUND_VARS) reads.has(caller) && issues.push({
|
|
1485
2140
|
path: [ "predicates", name ],
|
|
1486
2141
|
message: `predicate "${name}" reads $${caller} — predicates are instance-level (pre-evaluated once, without a caller); compose caller gates at the condition site instead`
|
|
@@ -1506,11 +2161,11 @@ function checkUnboundCallerVars(def, issues) {
|
|
|
1506
2161
|
}
|
|
1507
2162
|
|
|
1508
2163
|
function unboundCallerVars(policy) {
|
|
1509
|
-
return policy === "cascade" ? CALLER_BOUND_VARS : policy === "caller-bound" ? [ "params" ] : [ "can" ];
|
|
2164
|
+
return policy === "cascade" ? CALLER_BOUND_VARS : policy === "caller-bound" ? [ "params" ] : policy === "triggered-payload" ? [ "can", "params" ] : [ "can" ];
|
|
1510
2165
|
}
|
|
1511
2166
|
|
|
1512
2167
|
function callerVarMessage(site, name) {
|
|
1513
|
-
return site.policy === "cascade" ? `${site.label} reads $${name} —
|
|
2168
|
+
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`;
|
|
1514
2169
|
}
|
|
1515
2170
|
|
|
1516
2171
|
function conditionSites(def) {
|
|
@@ -1543,23 +2198,11 @@ function collectStageConditionSites({stage: stage, i: i, workflowLayer: workflow
|
|
|
1543
2198
|
names: entryNames(activity.fields)
|
|
1544
2199
|
})) ];
|
|
1545
2200
|
for (const [k, t] of (stage.transitions ?? []).entries()) sites.push({
|
|
1546
|
-
groq: t.
|
|
1547
|
-
path: [ "stages", i, "transitions", k, "
|
|
1548
|
-
label: `transition "${t.name}"
|
|
2201
|
+
groq: t.when,
|
|
2202
|
+
path: [ "stages", i, "transitions", k, "when" ],
|
|
2203
|
+
label: `transition "${t.name}" when`,
|
|
1549
2204
|
policy: "cascade",
|
|
1550
2205
|
fields: stageFields2
|
|
1551
|
-
}), collectBindingSites({
|
|
1552
|
-
effects: t.effects,
|
|
1553
|
-
path: [ "stages", i, "transitions", k, "effects" ],
|
|
1554
|
-
label: `transition "${t.name}"`,
|
|
1555
|
-
fields: stageFields2,
|
|
1556
|
-
sites: sites
|
|
1557
|
-
}), collectOpWhereSites({
|
|
1558
|
-
ops: t.ops,
|
|
1559
|
-
path: [ "stages", i, "transitions", k, "ops" ],
|
|
1560
|
-
label: `transition "${t.name}"`,
|
|
1561
|
-
fields: stageFields2,
|
|
1562
|
-
sites: sites
|
|
1563
2206
|
});
|
|
1564
2207
|
collectEditableSites({
|
|
1565
2208
|
entries: stage.fields,
|
|
@@ -1593,11 +2236,14 @@ function collectEditableSites({entries: entries, path: path, labelPrefix: labelP
|
|
|
1593
2236
|
});
|
|
1594
2237
|
}
|
|
1595
2238
|
|
|
1596
|
-
function collectOpWhereSites({ops: ops, path: path, label: label, fields: fields, sites: sites}) {
|
|
2239
|
+
function collectOpWhereSites({ops: ops, path: path, label: label, policy: policy, fields: fields, sites: sites}) {
|
|
1597
2240
|
for (const [o, op] of (ops ?? []).entries()) op.where !== void 0 && sites.push({
|
|
1598
2241
|
groq: op.where,
|
|
1599
2242
|
path: [ ...path, o, "where" ],
|
|
1600
2243
|
label: `${label} ${op.type} where`,
|
|
2244
|
+
...policy !== void 0 ? {
|
|
2245
|
+
policy: policy
|
|
2246
|
+
} : {},
|
|
1601
2247
|
fields: fields
|
|
1602
2248
|
});
|
|
1603
2249
|
}
|
|
@@ -1607,36 +2253,21 @@ function collectActivityConditionSites({activity: activity, path: path, stageFie
|
|
|
1607
2253
|
scope: "activity",
|
|
1608
2254
|
names: entryNames(activity.fields)
|
|
1609
2255
|
}, ...stageFields2 ];
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
}
|
|
1620
|
-
for (const [name, groq] of Object.entries(activity.requirements ?? {})) sites.push({
|
|
1621
|
-
groq: groq,
|
|
2256
|
+
activity.filter !== void 0 && sites.push({
|
|
2257
|
+
groq: activity.filter,
|
|
2258
|
+
path: [ ...path, "filter" ],
|
|
2259
|
+
label: `activity "${activity.name}".filter`,
|
|
2260
|
+
policy: "cascade",
|
|
2261
|
+
fields: fields
|
|
2262
|
+
});
|
|
2263
|
+
for (const [name, groq2] of Object.entries(activity.requirements ?? {})) sites.push({
|
|
2264
|
+
groq: groq2,
|
|
1622
2265
|
path: [ ...path, "requirements", name ],
|
|
1623
2266
|
label: `activity "${activity.name}" requirement "${name}"`,
|
|
1624
2267
|
policy: "caller-bound",
|
|
1625
2268
|
fields: fields
|
|
1626
2269
|
});
|
|
1627
|
-
|
|
1628
|
-
effects: activity.effects,
|
|
1629
|
-
path: [ ...path, "effects" ],
|
|
1630
|
-
label: `activity "${activity.name}"`,
|
|
1631
|
-
fields: fields,
|
|
1632
|
-
sites: sites
|
|
1633
|
-
}), collectOpWhereSites({
|
|
1634
|
-
ops: activity.ops,
|
|
1635
|
-
path: [ ...path, "ops" ],
|
|
1636
|
-
label: `activity "${activity.name}"`,
|
|
1637
|
-
fields: fields,
|
|
1638
|
-
sites: sites
|
|
1639
|
-
}), collectEditableSites({
|
|
2270
|
+
collectEditableSites({
|
|
1640
2271
|
entries: activity.fields,
|
|
1641
2272
|
path: [ ...path, "fields" ],
|
|
1642
2273
|
labelPrefix: `activity "${activity.name}"`,
|
|
@@ -1647,59 +2278,77 @@ function collectActivityConditionSites({activity: activity, path: path, stageFie
|
|
|
1647
2278
|
path: path,
|
|
1648
2279
|
fields: fields,
|
|
1649
2280
|
sites: sites
|
|
1650
|
-
}), collectSubworkflowSites({
|
|
1651
|
-
activity: activity,
|
|
1652
|
-
path: path,
|
|
1653
|
-
fields: fields,
|
|
1654
|
-
sites: sites
|
|
1655
2281
|
});
|
|
1656
2282
|
}
|
|
1657
2283
|
|
|
1658
2284
|
function collectActionConditionSites({activity: activity, path: path, fields: fields, sites: sites}) {
|
|
1659
|
-
for (const [a, action] of (activity.actions ?? []).entries())
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
2285
|
+
for (const [a, action] of (activity.actions ?? []).entries()) {
|
|
2286
|
+
const actionPath = [ ...path, "actions", a ], cascadeFired = action.when !== void 0;
|
|
2287
|
+
action.when !== void 0 && sites.push({
|
|
2288
|
+
groq: action.when,
|
|
2289
|
+
path: [ ...actionPath, "when" ],
|
|
2290
|
+
label: `action "${action.name}" when`,
|
|
2291
|
+
policy: "cascade",
|
|
2292
|
+
fields: fields
|
|
2293
|
+
}), action.filter !== void 0 && sites.push({
|
|
2294
|
+
groq: action.filter,
|
|
2295
|
+
path: [ ...actionPath, "filter" ],
|
|
2296
|
+
label: `action "${action.name}" filter`,
|
|
2297
|
+
policy: cascadeFired ? "cascade" : "caller-bound",
|
|
2298
|
+
fields: fields
|
|
2299
|
+
});
|
|
2300
|
+
const payloadPolicy = cascadeFired ? "triggered-payload" : void 0;
|
|
2301
|
+
collectBindingSites({
|
|
2302
|
+
effects: action.effects,
|
|
2303
|
+
path: [ ...actionPath, "effects" ],
|
|
2304
|
+
label: `action "${action.name}"`,
|
|
2305
|
+
policy: payloadPolicy,
|
|
2306
|
+
fields: fields,
|
|
2307
|
+
sites: sites
|
|
2308
|
+
}), collectOpWhereSites({
|
|
2309
|
+
ops: action.ops,
|
|
2310
|
+
path: [ ...actionPath, "ops" ],
|
|
2311
|
+
label: `action "${action.name}"`,
|
|
2312
|
+
policy: payloadPolicy,
|
|
2313
|
+
fields: fields,
|
|
2314
|
+
sites: sites
|
|
2315
|
+
}), collectSpawnSites({
|
|
2316
|
+
action: action,
|
|
2317
|
+
path: actionPath,
|
|
2318
|
+
fields: fields,
|
|
2319
|
+
sites: sites
|
|
2320
|
+
});
|
|
2321
|
+
}
|
|
1678
2322
|
}
|
|
1679
2323
|
|
|
1680
|
-
function
|
|
1681
|
-
const
|
|
1682
|
-
if (
|
|
1683
|
-
const
|
|
2324
|
+
function collectSpawnSites({action: action, path: path, fields: fields, sites: sites}) {
|
|
2325
|
+
const spawn = action.spawn;
|
|
2326
|
+
if (spawn === void 0) return;
|
|
2327
|
+
const spawnPath = [ ...path, "spawn" ];
|
|
1684
2328
|
sites.push({
|
|
1685
|
-
groq:
|
|
1686
|
-
path: [ ...
|
|
1687
|
-
label: `
|
|
2329
|
+
groq: spawn.forEach,
|
|
2330
|
+
path: [ ...spawnPath, "forEach" ],
|
|
2331
|
+
label: `action "${action.name}".spawn.forEach`,
|
|
2332
|
+
policy: "cascade",
|
|
1688
2333
|
fields: fields
|
|
1689
2334
|
});
|
|
1690
|
-
for (const [group, record] of [ [ "with",
|
|
1691
|
-
groq:
|
|
1692
|
-
path: [ ...
|
|
1693
|
-
label: `
|
|
2335
|
+
for (const [group, record] of [ [ "with", spawn.with ], [ "context", spawn.context ] ]) for (const [key, groq2] of Object.entries(record ?? {})) sites.push({
|
|
2336
|
+
groq: groq2,
|
|
2337
|
+
path: [ ...spawnPath, group, key ],
|
|
2338
|
+
label: `action "${action.name}".spawn.${group} "${key}"`,
|
|
2339
|
+
policy: "triggered-payload",
|
|
1694
2340
|
fields: fields
|
|
1695
2341
|
});
|
|
1696
2342
|
}
|
|
1697
2343
|
|
|
1698
|
-
function collectBindingSites({effects: effects, path: path, label: label, fields: fields, sites: sites}) {
|
|
1699
|
-
for (const [e, effect] of (effects ?? []).entries()) for (const [key,
|
|
1700
|
-
groq:
|
|
2344
|
+
function collectBindingSites({effects: effects, path: path, label: label, policy: policy, fields: fields, sites: sites}) {
|
|
2345
|
+
for (const [e, effect] of (effects ?? []).entries()) for (const [key, groq2] of Object.entries(effect.bindings ?? {})) sites.push({
|
|
2346
|
+
groq: groq2,
|
|
1701
2347
|
path: [ ...path, e, "bindings", key ],
|
|
1702
2348
|
label: `${label} effect "${effect.name}" binding "${key}"`,
|
|
2349
|
+
...policy !== void 0 ? {
|
|
2350
|
+
policy: policy
|
|
2351
|
+
} : {},
|
|
1703
2352
|
fields: fields
|
|
1704
2353
|
});
|
|
1705
2354
|
}
|
|
@@ -1711,36 +2360,76 @@ function checkAssigneesEntries(def, issues) {
|
|
|
1711
2360
|
});
|
|
1712
2361
|
}
|
|
1713
2362
|
|
|
1714
|
-
function
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
2363
|
+
function checkActivityTerminalPaths(def, issues) {
|
|
2364
|
+
for (const [i, stage] of def.stages.entries()) {
|
|
2365
|
+
const resolvable = terminallyResolvableActivities(stage);
|
|
2366
|
+
for (const [j, activity] of (stage.activities ?? []).entries()) resolvable.has(activity.name) || issues.push({
|
|
2367
|
+
path: [ "stages", i, "activities", j ],
|
|
2368
|
+
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`
|
|
2369
|
+
});
|
|
2370
|
+
}
|
|
1721
2371
|
}
|
|
1722
2372
|
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
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),
|
|
1728
|
-
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)
|
|
1729
|
-
};
|
|
2373
|
+
function terminallyResolvableActivities(stage) {
|
|
2374
|
+
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));
|
|
2375
|
+
return new Set(terminalSets.map(op => op.activity));
|
|
2376
|
+
}
|
|
1730
2377
|
|
|
1731
|
-
function
|
|
1732
|
-
for (const [i, stage] of def.stages.entries())
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
2378
|
+
function checkTerminalStageActivities(def, issues) {
|
|
2379
|
+
for (const [i, stage] of def.stages.entries()) (stage.transitions ?? []).length > 0 || (stage.activities ?? []).length === 0 || issues.push({
|
|
2380
|
+
path: [ "stages", i, "activities" ],
|
|
2381
|
+
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`
|
|
2382
|
+
});
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
function storedRolesIssue(action) {
|
|
2386
|
+
if (action.roles !== void 0) {
|
|
2387
|
+
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`;
|
|
2388
|
+
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`;
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
function checkStoredRolesPlacement(def, issues) {
|
|
2393
|
+
for (const [i, stage] of def.stages.entries()) for (const [j, activity] of (stage.activities ?? []).entries()) for (const [a, action] of (activity.actions ?? []).entries()) {
|
|
2394
|
+
const message = storedRolesIssue(action);
|
|
2395
|
+
message !== void 0 && issues.push({
|
|
2396
|
+
path: [ "stages", i, "activities", j, "actions", a, "roles" ],
|
|
2397
|
+
message: message
|
|
1740
2398
|
});
|
|
1741
2399
|
}
|
|
1742
2400
|
}
|
|
1743
2401
|
|
|
2402
|
+
function checkTriggeredActionParams(def, issues) {
|
|
2403
|
+
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({
|
|
2404
|
+
path: [ "stages", i, "activities", j, "actions", a, "params" ],
|
|
2405
|
+
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`
|
|
2406
|
+
});
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
function checkStart(def, issues) {
|
|
2410
|
+
if (def.start !== void 0 && (def.lifecycle === "child" && issues.push({
|
|
2411
|
+
path: [ "start" ],
|
|
2412
|
+
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'`"
|
|
2413
|
+
}), checkStartFilterReads(def, issues), def.start.kind === "autonomous")) for (const [n, entry] of (def.fields ?? []).entries()) entry.required !== !0 || isSubjectEntry(entry) || issues.push({
|
|
2414
|
+
path: [ "fields", n, "required" ],
|
|
2415
|
+
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`
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
function checkStartFilterReads(def, issues) {
|
|
2420
|
+
const filter = def.start?.filter;
|
|
2421
|
+
if (filter === void 0) return;
|
|
2422
|
+
const bindable = new Set((def.fields ?? []).filter(isInputSourced).map(entry => entry.name)), declared = new Set((def.fields ?? []).map(entry => entry.name));
|
|
2423
|
+
for (const name of conditionFieldReadNames(filter)) bindable.has(name) || issues.push({
|
|
2424
|
+
path: [ "start", "filter" ],
|
|
2425
|
+
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)}`
|
|
2426
|
+
});
|
|
2427
|
+
readsRootDocument(filter) && !(def.fields ?? []).some(isSubjectEntry) && issues.push({
|
|
2428
|
+
path: [ "start", "filter" ],
|
|
2429
|
+
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"
|
|
2430
|
+
});
|
|
2431
|
+
}
|
|
2432
|
+
|
|
1744
2433
|
function checkGroups(def, issues) {
|
|
1745
2434
|
const workflowGroups = checkGroupDeclarations({
|
|
1746
2435
|
groups: def.groups,
|
|
@@ -1857,7 +2546,7 @@ function checkConditionFieldReads(def, issues) {
|
|
|
1857
2546
|
path: site.path,
|
|
1858
2547
|
message: undeclaredFieldReadMessage(site, name)
|
|
1859
2548
|
});
|
|
1860
|
-
for (const [name,
|
|
2549
|
+
for (const [name, groq2] of Object.entries(def.predicates ?? {})) for (const read of conditionFieldReadNames(groq2)) everywhere.has(read) || issues.push({
|
|
1861
2550
|
path: [ "predicates", name ],
|
|
1862
2551
|
message: noFieldAnywhereMessage(`predicate "${name}"`, read)
|
|
1863
2552
|
});
|
|
@@ -1961,38 +2650,29 @@ function seedEarlierSiblingTarget(args) {
|
|
|
1961
2650
|
});
|
|
1962
2651
|
}
|
|
1963
2652
|
|
|
2653
|
+
function opSites(def) {
|
|
2654
|
+
const sites = [];
|
|
2655
|
+
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({
|
|
2656
|
+
ops: action.ops,
|
|
2657
|
+
path: [ "stages", i, "activities", j, "actions", a, "ops" ],
|
|
2658
|
+
label: `action "${action.name}"`,
|
|
2659
|
+
stage: stage,
|
|
2660
|
+
activity: activity
|
|
2661
|
+
});
|
|
2662
|
+
return sites;
|
|
2663
|
+
}
|
|
2664
|
+
|
|
1964
2665
|
function checkFieldReadOpValues(def, issues) {
|
|
1965
2666
|
const workflowEntries = def.fields ?? [];
|
|
1966
|
-
for (const
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
path: [ "stages", i, "transitions", k, "ops" ],
|
|
1976
|
-
label: `transition "${t.name}"`
|
|
1977
|
-
});
|
|
1978
|
-
for (const [j, activity] of (stage.activities ?? []).entries()) {
|
|
1979
|
-
const activityNames = entryNames(activity.fields), activityPath = [ "stages", i, "activities", j ];
|
|
1980
|
-
checkOpsFieldReads({
|
|
1981
|
-
...shared,
|
|
1982
|
-
ops: activity.ops,
|
|
1983
|
-
path: [ ...activityPath, "ops" ],
|
|
1984
|
-
label: `activity "${activity.name}"`,
|
|
1985
|
-
activityNames: activityNames
|
|
1986
|
-
});
|
|
1987
|
-
for (const [a, action] of (activity.actions ?? []).entries()) checkOpsFieldReads({
|
|
1988
|
-
...shared,
|
|
1989
|
-
ops: action.ops,
|
|
1990
|
-
path: [ ...activityPath, "actions", a, "ops" ],
|
|
1991
|
-
label: `action "${action.name}"`,
|
|
1992
|
-
activityNames: activityNames
|
|
1993
|
-
});
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
2667
|
+
for (const site of opSites(def)) checkOpsFieldReads({
|
|
2668
|
+
workflowEntries: workflowEntries,
|
|
2669
|
+
stageEntries: site.stage.fields ?? [],
|
|
2670
|
+
issues: issues,
|
|
2671
|
+
ops: site.ops,
|
|
2672
|
+
path: site.path,
|
|
2673
|
+
label: site.label,
|
|
2674
|
+
activityNames: entryNames(site.activity.fields)
|
|
2675
|
+
});
|
|
1996
2676
|
}
|
|
1997
2677
|
|
|
1998
2678
|
function checkOpsFieldReads({ops: ops, path: path, label: label, ...ctx}) {
|
|
@@ -2045,6 +2725,49 @@ function opFieldReadMissMessage({read: read, where: where, hosts: hosts, activit
|
|
|
2045
2725
|
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)"}`;
|
|
2046
2726
|
}
|
|
2047
2727
|
|
|
2728
|
+
function checkUpdateWhereOps(def, issues) {
|
|
2729
|
+
const workflow = def.fields ?? [];
|
|
2730
|
+
for (const site of opSites(def)) for (const [o, op] of (site.ops ?? []).entries()) {
|
|
2731
|
+
if (op.type !== "field.updateWhere") continue;
|
|
2732
|
+
const scopes = {
|
|
2733
|
+
workflow: workflow,
|
|
2734
|
+
stage: site.stage.fields ?? [],
|
|
2735
|
+
activity: site.activity.fields ?? []
|
|
2736
|
+
};
|
|
2737
|
+
checkUpdateWhereTargetKind({
|
|
2738
|
+
op: op,
|
|
2739
|
+
path: [ ...site.path, o ],
|
|
2740
|
+
label: site.label,
|
|
2741
|
+
scopes: scopes,
|
|
2742
|
+
issues: issues
|
|
2743
|
+
}), checkUpdateWhereMergeKeys({
|
|
2744
|
+
op: op,
|
|
2745
|
+
path: [ ...site.path, o ],
|
|
2746
|
+
label: site.label,
|
|
2747
|
+
issues: issues
|
|
2748
|
+
});
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
function checkUpdateWhereTargetKind({op: op, path: path, label: label, scopes: scopes, issues: issues}) {
|
|
2753
|
+
const target = scopes[op.target.scope]?.find(entry => entry.name === op.target.field);
|
|
2754
|
+
target === void 0 || target.type === "array" || issues.push({
|
|
2755
|
+
path: [ ...path, "target" ],
|
|
2756
|
+
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)}`
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
function rowOpsHint(type) {
|
|
2761
|
+
return type === "doc.refs" || type === "assignees" ? `; to add or drop ${type} rows use field.append / field.removeWhere` : "";
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
function checkUpdateWhereMergeKeys({op: op, path: path, label: label, issues: issues}) {
|
|
2765
|
+
if (op.value.type === "object") for (const key of Object.keys(op.value.fields)) key !== "_key" && key !== "_type" || issues.push({
|
|
2766
|
+
path: [ ...path, "value", "fields", key ],
|
|
2767
|
+
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`
|
|
2768
|
+
});
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2048
2771
|
function checkGuardFieldReads(def, issues) {
|
|
2049
2772
|
const reads = {
|
|
2050
2773
|
workflowEntries: def.fields ?? [],
|
|
@@ -2242,10 +2965,12 @@ function checkWorkflowInvariants(def) {
|
|
|
2242
2965
|
stageNames: stageNames,
|
|
2243
2966
|
issues: issues
|
|
2244
2967
|
}), checkEffectNames(def, issues), checkGuardNames(def, issues), checkFieldEntryNames(def, issues),
|
|
2245
|
-
checkRequiredField(def, issues),
|
|
2246
|
-
|
|
2247
|
-
|
|
2968
|
+
checkRequiredField(def, issues), checkStart(def, issues), checkPredicates(def, issues),
|
|
2969
|
+
checkUnboundCallerVars(def, issues), checkConditionFieldReads(def, issues), checkFieldReadSeeds(def, issues),
|
|
2970
|
+
checkFieldReadOpValues(def, issues), checkUpdateWhereOps(def, issues), checkGuardFieldReads(def, issues),
|
|
2971
|
+
checkAssigneesEntries(def, issues), checkActivityTerminalPaths(def, issues), checkTerminalStageActivities(def, issues),
|
|
2972
|
+
checkTriggeredActionParams(def, issues), checkStoredRolesPlacement(def, issues),
|
|
2248
2973
|
checkGroups(def, issues), issues;
|
|
2249
2974
|
}
|
|
2250
2975
|
|
|
2251
|
-
export { ACTIVITY_KINDS, ACTOR_KINDS, AuthoringActionSchema, AuthoringActivitySchema, AuthoringFieldEntrySchema, AuthoringGuardSchema, AuthoringOpSchema, AuthoringStageSchema, AuthoringTransitionSchema, AuthoringWorkflowSchema, CALLER_BOUND_VARS, CONDITION_VARS, ContractViolationError, DOCUMENT_VALUE_PERMISSIONS, DRIVER_KINDS, DefinitionInUseError, DefinitionNotFoundError, EFFECTS_READ, EffectNotFoundError, EffectSchema, FIELD_READ, FILTER_SCOPE_VARS, FieldValueShapeError, GROUP_KINDS, GUARD_PREDICATE_VARS, GroupSchema, InstanceNotFoundError, RESERVED_CONDITION_VARS, RESOURCE_ALIAS_NAME_SOURCE, StoredFieldOpSchema, WORKFLOW_DEFINITION_TYPE, WorkflowConfigSchema, WorkflowError, actorFulfillsRole, andConditions, checkFieldValue, checkWorkflowInvariants, clientConfigFromResource, conditionEffectReads, conditionParameterNames, conditionSyntaxIssues, datasetResourceParts, definitionDocId, errorMessage, evaluateCondition, evaluateConditionOutcome, evaluatePredicates,
|
|
2976
|
+
export { ACTIVITY_KINDS, ACTOR_KINDS, AuthoringActionSchema, AuthoringActivitySchema, AuthoringFieldEntrySchema, AuthoringGuardSchema, AuthoringOpSchema, AuthoringStageSchema, AuthoringTransitionSchema, AuthoringWorkflowSchema, CALLER_BOUND_VARS, CONDITION_VARS, ContractViolationError, DEFAULT_TRANSITION_WHEN, DOCUMENT_VALUE_PERMISSIONS, DRIVER_KINDS, DefinitionInUseError, DefinitionNotFoundError, EFFECTS_READ, EXECUTOR_CLASSIFICATIONS, EffectNotFoundError, EffectSchema, FIELD_READ, FILTER_SCOPE_VARS, FieldValueShapeError, GROUP_KINDS, GUARD_PREDICATE_VARS, GroupSchema, InstanceNotFoundError, RESERVED_CONDITION_VARS, RESOURCE_ALIAS_NAME_SOURCE, START_FILTER_VARS, StoredFieldOpSchema, WORKFLOW_DEFINITION_TYPE, WorkflowConfigSchema, WorkflowError, actorFulfillsRole, andConditions, checkFieldValue, checkWorkflowInvariants, clientConfigFromResource, conditionEffectReads, conditionFieldReadNames, conditionParameterNames, conditionSyntaxIssues, datasetResourceParts, definitionDocId, deriveActivityKind, deriveExecutorClassification, desugarWorkflow, driverKind, errorMessage, evaluateCondition, evaluateConditionOutcome, evaluatePredicates, extractDocumentId, formatIssuePath, formatIssues, formatValidationError, gdrFromResource, gdrRef, gdrResourcePrefix, gdrUri, groq, groupMembershipNames, isBareSeedId, isCascadeFired, isGdr, isGdrUri, isGuardReadExpr, isInputSourced, isNotesEntry, isStartableDefinition, isSubjectEntry, isTerminalActivityStatus, isTodoListEntry, isTodoListItem, isUnevaluable, labelFor, parseGdr, parseOrThrow, parseResourceGdr, parseStoredDefinition, readsRootDocument, refCanvas, refDashboard, refDataset, refMediaLibrary, refTypeIssues, rejectedRefTypes, releaseDocId, releaseRef, resourceAliasesToMap, resourceFromGdrUri, resourceFromParsed, resourceGdr, rethrowWithContext, runGroq, sameResource, selfGdr, startKindOf, tagScopeFilter, toBareId, toPhysicalGdr, tryParseGdr, validateFieldAppendItem, validateFieldValue, validateResourceAliasName, validateTag };
|