@sanity/workflow-engine 0.15.0 → 0.17.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 +79 -0
- package/DATAMODEL.md +177 -33
- package/dist/_chunks-cjs/invariants.cjs +1377 -330
- package/dist/_chunks-es/invariants.js +1319 -326
- package/dist/define.cjs +3 -608
- package/dist/define.d.cts +125 -416
- package/dist/define.d.ts +125 -416
- package/dist/define.js +2 -607
- package/dist/index.cjs +5370 -3906
- package/dist/index.d.cts +1439 -597
- package/dist/index.d.ts +1439 -597
- package/dist/index.js +5191 -3829
- 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
|
}
|
|
@@ -295,17 +315,17 @@ function effectNotFoundMessage(args) {
|
|
|
295
315
|
return args.settled.status === "cancelled" ? `${base} — it was cancelled at ${args.settled.ranAt}${cause}` : `${base} — it already settled "${args.settled.status}" at ${args.settled.ranAt}${cause}`;
|
|
296
316
|
}
|
|
297
317
|
|
|
298
|
-
const
|
|
318
|
+
const LAKE_ID_SEGMENT_RE = /^[a-z0-9][a-z0-9-]*$/, LAKE_ID_SEGMENT_GLOSS = "ASCII lowercase + digits + dashes, no leading dash, no dots";
|
|
299
319
|
|
|
300
320
|
function validateTag(tag) {
|
|
301
|
-
if (!
|
|
321
|
+
if (!LAKE_ID_SEGMENT_RE.test(tag)) throw new ContractViolationError(`tag: invalid tag "${tag}" — must match ${LAKE_ID_SEGMENT_RE.source} (${LAKE_ID_SEGMENT_GLOSS})`);
|
|
302
322
|
}
|
|
303
323
|
|
|
304
324
|
function tagScopeFilter() {
|
|
305
325
|
return "tag == $tag";
|
|
306
326
|
}
|
|
307
327
|
|
|
308
|
-
const NonEmptyString = v__namespace.pipe(v__namespace.string(), v__namespace.nonEmpty("must not be empty"));
|
|
328
|
+
const NonEmptyString$1 = v__namespace.pipe(v__namespace.string(), v__namespace.nonEmpty("must not be empty"));
|
|
309
329
|
|
|
310
330
|
function asPredicate(validate) {
|
|
311
331
|
return value => {
|
|
@@ -319,21 +339,21 @@ function asPredicate(validate) {
|
|
|
319
339
|
|
|
320
340
|
const isValidTag = asPredicate(validateTag), isValidAliasName = asPredicate(validateResourceAliasName), isValidDatasetId = asPredicate(datasetResourceParts), WorkflowResourceSchema = v__namespace.variant("type", [ v__namespace.object({
|
|
321
341
|
type: v__namespace.literal("dataset"),
|
|
322
|
-
id: v__namespace.pipe(NonEmptyString, v__namespace.check(isValidDatasetId, 'invalid dataset resource id — expected "<projectId>.<dataset>"'))
|
|
342
|
+
id: v__namespace.pipe(NonEmptyString$1, v__namespace.check(isValidDatasetId, 'invalid dataset resource id — expected "<projectId>.<dataset>"'))
|
|
323
343
|
}), v__namespace.object({
|
|
324
344
|
type: v__namespace.literal("canvas"),
|
|
325
|
-
id: NonEmptyString
|
|
345
|
+
id: NonEmptyString$1
|
|
326
346
|
}), v__namespace.object({
|
|
327
347
|
type: v__namespace.literal("media-library"),
|
|
328
|
-
id: NonEmptyString
|
|
348
|
+
id: NonEmptyString$1
|
|
329
349
|
}), v__namespace.object({
|
|
330
350
|
type: v__namespace.literal("dashboard"),
|
|
331
|
-
id: NonEmptyString
|
|
351
|
+
id: NonEmptyString$1
|
|
332
352
|
}) ]), ResourceBindingSchema = v__namespace.object({
|
|
333
|
-
name: v__namespace.pipe(NonEmptyString, v__namespace.check(isValidAliasName, "invalid resource handle name — lowercase letters, digits and dashes only, no leading dash")),
|
|
353
|
+
name: v__namespace.pipe(NonEmptyString$1, v__namespace.check(isValidAliasName, "invalid resource handle name — lowercase letters, digits and dashes only, no leading dash")),
|
|
334
354
|
resource: WorkflowResourceSchema
|
|
335
355
|
}), DefinitionSchema = v__namespace.custom(input => typeof input == "object" && input !== null && typeof input.name == "string", "expected a workflow definition (an object with a string `name`)"), DeploymentSchema = v__namespace.object({
|
|
336
|
-
name: NonEmptyString,
|
|
356
|
+
name: NonEmptyString$1,
|
|
337
357
|
tag: v__namespace.pipe(v__namespace.string(), v__namespace.nonEmpty(), v__namespace.check(isValidTag, "invalid tag — lowercase letters, digits and dashes only, no leading dash, no dots")),
|
|
338
358
|
workflowResource: WorkflowResourceSchema,
|
|
339
359
|
resourceAliases: v__namespace.optional(v__namespace.pipe(v__namespace.array(ResourceBindingSchema), v__namespace.check(bindings => new Set(bindings.map(binding => binding.name)).size === bindings.length, "duplicate resource handle name — each binding name must be unique within a deployment"))),
|
|
@@ -397,6 +417,599 @@ 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
|
+
...start.allowed !== void 0 ? {
|
|
523
|
+
allowed: start.allowed
|
|
524
|
+
} : {}
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function checkReservedRoleAliasKeys(aliases, issues) {
|
|
529
|
+
for (const key of Object.keys(aliases ?? {})) key.startsWith("$") && issues.push({
|
|
530
|
+
path: [ "roleAliases", key ],
|
|
531
|
+
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.`
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const TODOLIST_OF = [ {
|
|
536
|
+
type: "string",
|
|
537
|
+
name: "label",
|
|
538
|
+
title: "Label"
|
|
539
|
+
}, {
|
|
540
|
+
type: "string",
|
|
541
|
+
name: "status",
|
|
542
|
+
title: "Status"
|
|
543
|
+
}, {
|
|
544
|
+
type: "assignee",
|
|
545
|
+
name: "assignee",
|
|
546
|
+
title: "Assignee"
|
|
547
|
+
}, {
|
|
548
|
+
type: "date",
|
|
549
|
+
name: "dueDate",
|
|
550
|
+
title: "Due date"
|
|
551
|
+
} ], NOTES_OF = [ {
|
|
552
|
+
type: "text",
|
|
553
|
+
name: "body",
|
|
554
|
+
title: "Body"
|
|
555
|
+
}, {
|
|
556
|
+
type: "actor",
|
|
557
|
+
name: "actor",
|
|
558
|
+
title: "Actor"
|
|
559
|
+
}, {
|
|
560
|
+
type: "datetime",
|
|
561
|
+
name: "at",
|
|
562
|
+
title: "At"
|
|
563
|
+
} ];
|
|
564
|
+
|
|
565
|
+
function desugarFieldEntries({entries: entries, path: path, ctx: ctx}) {
|
|
566
|
+
return !entries || entries.length === 0 ? entries === void 0 ? void 0 : [] : entries.map((entry, i) => desugarFieldEntry({
|
|
567
|
+
entry: entry,
|
|
568
|
+
path: [ ...path, i ],
|
|
569
|
+
ctx: ctx
|
|
570
|
+
}));
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function desugarFieldEntry({entry: entry, path: path, ctx: ctx}) {
|
|
574
|
+
if (entry.type === "claim") return desugarClaimField({
|
|
575
|
+
entry: entry,
|
|
576
|
+
path: path,
|
|
577
|
+
ctx: ctx
|
|
578
|
+
});
|
|
579
|
+
if (entry.type === "todoList" || entry.type === "notes") return desugarListField({
|
|
580
|
+
entry: entry,
|
|
581
|
+
path: path,
|
|
582
|
+
ctx: ctx
|
|
583
|
+
});
|
|
584
|
+
const editable = normalizeEditable({
|
|
585
|
+
editable: entry.editable,
|
|
586
|
+
path: [ ...path, "editable" ],
|
|
587
|
+
ctx: ctx
|
|
588
|
+
});
|
|
589
|
+
return {
|
|
590
|
+
...stripUndefined({
|
|
591
|
+
type: entry.type,
|
|
592
|
+
name: entry.name,
|
|
593
|
+
title: entry.title,
|
|
594
|
+
description: entry.description,
|
|
595
|
+
group: normalizeGroup(entry.group),
|
|
596
|
+
required: entry.required,
|
|
597
|
+
initialValue: entry.initialValue,
|
|
598
|
+
editable: editable,
|
|
599
|
+
types: entry.types,
|
|
600
|
+
fields: entry.fields,
|
|
601
|
+
of: entry.of
|
|
602
|
+
})
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function desugarClaimField({entry: entry, path: path, ctx: ctx}) {
|
|
607
|
+
const desugared = {
|
|
608
|
+
...stripUndefined({
|
|
609
|
+
name: entry.name,
|
|
610
|
+
title: entry.title,
|
|
611
|
+
description: entry.description,
|
|
612
|
+
group: normalizeGroup(entry.group)
|
|
613
|
+
}),
|
|
614
|
+
type: "actor"
|
|
615
|
+
};
|
|
616
|
+
return ctx.claimFields.set(desugared, {
|
|
617
|
+
name: entry.name,
|
|
618
|
+
path: path
|
|
619
|
+
}), desugared;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function desugarListField({entry: entry, path: path, ctx: ctx}) {
|
|
623
|
+
const editable = normalizeEditable({
|
|
624
|
+
editable: entry.editable,
|
|
625
|
+
path: [ ...path, "editable" ],
|
|
626
|
+
ctx: ctx
|
|
627
|
+
});
|
|
628
|
+
return {
|
|
629
|
+
...stripUndefined({
|
|
630
|
+
name: entry.name,
|
|
631
|
+
title: entry.title,
|
|
632
|
+
description: entry.description,
|
|
633
|
+
group: normalizeGroup(entry.group),
|
|
634
|
+
required: entry.required,
|
|
635
|
+
initialValue: entry.initialValue,
|
|
636
|
+
editable: editable
|
|
637
|
+
}),
|
|
638
|
+
type: "array",
|
|
639
|
+
of: entry.type === "todoList" ? TODOLIST_OF : NOTES_OF
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function normalizeEditable({editable: editable, path: path, ctx: ctx}) {
|
|
644
|
+
if (editable === void 0 || editable === !0) return editable;
|
|
645
|
+
if (Array.isArray(editable)) {
|
|
646
|
+
const condition = rolesCondition(editable, ctx.roleAliases);
|
|
647
|
+
if (condition === void 0) {
|
|
648
|
+
ctx.issues.push({
|
|
649
|
+
path: path,
|
|
650
|
+
message: "editable: [] names no roles — use `true` to open the field to anyone in its scope, or list at least one role"
|
|
651
|
+
});
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
return condition;
|
|
655
|
+
}
|
|
656
|
+
return editable;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function editableFieldNames({workflowFields: workflowFields2, stageFields: stageFields2, activities: activities}) {
|
|
660
|
+
const names = /* @__PURE__ */ new Set, collect = entries => {
|
|
661
|
+
for (const entry of entries ?? []) entry.editable !== void 0 && names.add(entry.name);
|
|
662
|
+
};
|
|
663
|
+
collect(workflowFields2), collect(stageFields2);
|
|
664
|
+
for (const activity of activities) collect(activity.fields);
|
|
665
|
+
return names;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function desugarStageEditable({overrides: overrides, inScope: inScope, path: path, ctx: ctx}) {
|
|
669
|
+
if (overrides === void 0) return;
|
|
670
|
+
const out = {};
|
|
671
|
+
for (const [name, value] of Object.entries(overrides)) {
|
|
672
|
+
if (!inScope.has(name)) {
|
|
673
|
+
ctx.issues.push({
|
|
674
|
+
path: [ ...path, name ],
|
|
675
|
+
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\``
|
|
676
|
+
});
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
const normalized = normalizeEditable({
|
|
680
|
+
editable: value,
|
|
681
|
+
path: [ ...path, name ],
|
|
682
|
+
ctx: ctx
|
|
683
|
+
});
|
|
684
|
+
normalized !== void 0 && (out[name] = normalized);
|
|
685
|
+
}
|
|
686
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function layerOf(entries) {
|
|
690
|
+
return new Map((entries ?? []).map(entry => [ entry.name, entry ]));
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function normalizeGroup(group) {
|
|
694
|
+
return group === void 0 || Array.isArray(group) ? group : [ group ];
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function desugarActivity({activity: activity, path: path, stageEnv: stageEnv, ctx: ctx}) {
|
|
698
|
+
const activityFields2 = desugarFieldEntries({
|
|
699
|
+
entries: activity.fields,
|
|
700
|
+
path: [ ...path, "fields" ],
|
|
701
|
+
ctx: ctx
|
|
702
|
+
}), env = {
|
|
703
|
+
layers: [ {
|
|
704
|
+
scope: "activity",
|
|
705
|
+
entries: layerOf(activityFields2)
|
|
706
|
+
}, ...stageEnv.layers ]
|
|
707
|
+
}, actions = (activity.actions ?? []).map((action, a) => desugarAction({
|
|
708
|
+
action: action,
|
|
709
|
+
path: [ ...path, "actions", a ],
|
|
710
|
+
env: env,
|
|
711
|
+
activityName: activity.name,
|
|
712
|
+
ctx: ctx
|
|
713
|
+
})), target = desugarTarget({
|
|
714
|
+
target: activity.target,
|
|
715
|
+
env: env,
|
|
716
|
+
path: [ ...path, "target" ],
|
|
717
|
+
ctx: ctx
|
|
718
|
+
});
|
|
719
|
+
return {
|
|
720
|
+
...stripUndefined({
|
|
721
|
+
name: activity.name,
|
|
722
|
+
title: activity.title,
|
|
723
|
+
description: activity.description,
|
|
724
|
+
groups: activity.groups,
|
|
725
|
+
group: normalizeGroup(activity.group),
|
|
726
|
+
filter: activity.filter,
|
|
727
|
+
requirements: activity.requirements
|
|
728
|
+
}),
|
|
729
|
+
...target ? {
|
|
730
|
+
target: target
|
|
731
|
+
} : {},
|
|
732
|
+
...activityFields2 ? {
|
|
733
|
+
fields: activityFields2
|
|
734
|
+
} : {},
|
|
735
|
+
...actions.length > 0 ? {
|
|
736
|
+
actions: actions
|
|
737
|
+
} : {}
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const TARGET_DOC_KINDS = [ "doc.ref", "doc.refs", "release.ref" ];
|
|
742
|
+
|
|
743
|
+
function desugarTarget({target: target, env: env, path: path, ctx: ctx}) {
|
|
744
|
+
if (target === void 0 || target.type === "url") return target;
|
|
745
|
+
const ref = typeof target.field == "string" ? {
|
|
746
|
+
field: target.field
|
|
747
|
+
} : target.field, field = resolveRef({
|
|
748
|
+
ref: ref,
|
|
749
|
+
env: env,
|
|
750
|
+
path: [ ...path, "field" ],
|
|
751
|
+
ctx: ctx
|
|
752
|
+
}) ?? fallbackRef(ref), entry = entryAt(env, field);
|
|
753
|
+
return entry && !TARGET_DOC_KINDS.includes(entry.type) && ctx.issues.push({
|
|
754
|
+
path: [ ...path, "field" ],
|
|
755
|
+
message: `manual activity target references "${field.field}" of kind "${entry.type}" — a deep-link target needs a document-valued entry (${TARGET_DOC_KINDS.join(", ")})`
|
|
756
|
+
}), {
|
|
757
|
+
type: "field",
|
|
758
|
+
field: field
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function desugarAction({action: action, path: path, env: env, activityName: activityName, ctx: ctx}) {
|
|
763
|
+
if ("type" in action) return desugarClaimAction({
|
|
764
|
+
action: action,
|
|
765
|
+
path: path,
|
|
766
|
+
env: env,
|
|
767
|
+
ctx: ctx
|
|
768
|
+
});
|
|
769
|
+
action.roles !== void 0 && action.roles.length === 0 && ctx.issues.push({
|
|
770
|
+
path: [ ...path, "roles" ],
|
|
771
|
+
message: "roles: [] names no roles — omit it to allow any identity, or list at least one role"
|
|
772
|
+
});
|
|
773
|
+
const cascadeFired = isCascadeFired(action), filter = cascadeFired ? action.filter : andConditions([ rolesCondition(action.roles, ctx.roleAliases), action.filter ]), ops = desugarOps({
|
|
774
|
+
ops: action.ops,
|
|
775
|
+
path: [ ...path, "ops" ],
|
|
776
|
+
env: env,
|
|
777
|
+
firingActivity: activityName,
|
|
778
|
+
ctx: ctx
|
|
779
|
+
}) ?? [];
|
|
780
|
+
return action.status !== void 0 && ops.push({
|
|
781
|
+
type: "status.set",
|
|
782
|
+
activity: activityName,
|
|
783
|
+
status: action.status
|
|
784
|
+
}), {
|
|
785
|
+
...stripUndefined({
|
|
786
|
+
name: action.name,
|
|
787
|
+
title: action.title,
|
|
788
|
+
description: action.description,
|
|
789
|
+
group: normalizeGroup(action.group),
|
|
790
|
+
when: action.when,
|
|
791
|
+
params: action.params,
|
|
792
|
+
effects: action.effects,
|
|
793
|
+
spawn: action.spawn
|
|
794
|
+
}),
|
|
795
|
+
...cascadeFired && action.roles !== void 0 && action.roles.length > 0 ? {
|
|
796
|
+
roles: action.roles
|
|
797
|
+
} : {},
|
|
798
|
+
...filter ? {
|
|
799
|
+
filter: filter
|
|
800
|
+
} : {},
|
|
801
|
+
...ops.length > 0 ? {
|
|
802
|
+
ops: ops
|
|
803
|
+
} : {}
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function desugarClaimAction({action: action, path: path, env: env, ctx: ctx}) {
|
|
808
|
+
const ref = typeof action.field == "string" ? {
|
|
809
|
+
field: action.field
|
|
810
|
+
} : action.field, resolved = resolveRef({
|
|
811
|
+
ref: ref,
|
|
812
|
+
env: env,
|
|
813
|
+
path: [ ...path, "field" ],
|
|
814
|
+
ctx: ctx
|
|
815
|
+
});
|
|
816
|
+
if (resolved) {
|
|
817
|
+
const entry = entryAt(env, resolved);
|
|
818
|
+
entry && entry.type !== "actor" && ctx.issues.push({
|
|
819
|
+
path: [ ...path, "field" ],
|
|
820
|
+
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)`
|
|
821
|
+
}), checkShadowedClaimTarget({
|
|
822
|
+
actionName: action.name,
|
|
823
|
+
field: ref.field,
|
|
824
|
+
resolved: resolved,
|
|
825
|
+
env: env,
|
|
826
|
+
path: [ ...path, "field" ],
|
|
827
|
+
ctx: ctx
|
|
828
|
+
}), entry && ctx.claimedFields.add(entry);
|
|
829
|
+
}
|
|
830
|
+
const noSteal = `!defined($fields.${ref.field})`, filter = andConditions([ noSteal, rolesCondition(action.roles, ctx.roleAliases), action.filter ]), ops = resolved ? [ {
|
|
831
|
+
type: "field.set",
|
|
832
|
+
target: resolved,
|
|
833
|
+
value: {
|
|
834
|
+
type: "actor"
|
|
835
|
+
}
|
|
836
|
+
} ] : [];
|
|
837
|
+
return {
|
|
838
|
+
...stripUndefined({
|
|
839
|
+
name: action.name,
|
|
840
|
+
title: action.title,
|
|
841
|
+
description: action.description,
|
|
842
|
+
group: normalizeGroup(action.group),
|
|
843
|
+
params: action.params,
|
|
844
|
+
effects: action.effects
|
|
845
|
+
}),
|
|
846
|
+
filter: filter,
|
|
847
|
+
...ops.length > 0 ? {
|
|
848
|
+
ops: ops
|
|
849
|
+
} : {}
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
function checkShadowedClaimTarget({actionName: actionName, field: field, resolved: resolved, env: env, path: path, ctx: ctx}) {
|
|
854
|
+
const nearest = env.layers.find(l => l.entries.has(field));
|
|
855
|
+
nearest === void 0 || nearest.scope === resolved.scope || ctx.issues.push({
|
|
856
|
+
path: path,
|
|
857
|
+
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`
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
function checkUnclaimedClaimFields(ctx) {
|
|
862
|
+
for (const [entry, {name: name, path: path}] of ctx.claimFields) ctx.claimedFields.has(entry) || ctx.issues.push({
|
|
863
|
+
path: path,
|
|
864
|
+
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`
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
const DEFAULT_TRANSITION_WHEN = "$allActivitiesDone";
|
|
869
|
+
|
|
870
|
+
function desugarTransition({transition: transition}) {
|
|
871
|
+
return {
|
|
872
|
+
...stripUndefined({
|
|
873
|
+
name: transition.name,
|
|
874
|
+
title: transition.title,
|
|
875
|
+
description: transition.description,
|
|
876
|
+
to: transition.to
|
|
877
|
+
}),
|
|
878
|
+
when: transition.when ?? DEFAULT_TRANSITION_WHEN
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
function desugarGuard(guard) {
|
|
883
|
+
const {match: match, metadata: metadata, ...rest} = guard, {idRefs: idRefs, ...matchRest} = match;
|
|
884
|
+
return {
|
|
885
|
+
...rest,
|
|
886
|
+
match: {
|
|
887
|
+
...matchRest,
|
|
888
|
+
...idRefs !== void 0 ? {
|
|
889
|
+
idRefs: idRefs.map(printGuardRead)
|
|
890
|
+
} : {}
|
|
891
|
+
},
|
|
892
|
+
...metadata !== void 0 ? {
|
|
893
|
+
metadata: Object.fromEntries(Object.entries(metadata).map(([key, read]) => [ key, printGuardRead(read) ]))
|
|
894
|
+
} : {}
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function desugarOps({ops: ops, path: path, env: env, firingActivity: firingActivity, ctx: ctx}) {
|
|
899
|
+
if (ops) return ops.map((op, i) => desugarOp({
|
|
900
|
+
op: op,
|
|
901
|
+
path: [ ...path, i ],
|
|
902
|
+
env: env,
|
|
903
|
+
firingActivity: firingActivity,
|
|
904
|
+
ctx: ctx
|
|
905
|
+
}));
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function desugarOp({op: op, path: path, env: env, firingActivity: firingActivity, ctx: ctx}) {
|
|
909
|
+
if (op.type === "status.set") return {
|
|
910
|
+
type: "status.set",
|
|
911
|
+
activity: op.activity ?? firingActivity,
|
|
912
|
+
status: op.status
|
|
913
|
+
};
|
|
914
|
+
if (op.type === "audit") return desugarAuditOp({
|
|
915
|
+
op: op,
|
|
916
|
+
path: path,
|
|
917
|
+
env: env,
|
|
918
|
+
ctx: ctx
|
|
919
|
+
});
|
|
920
|
+
const target = resolveRef({
|
|
921
|
+
ref: op.target,
|
|
922
|
+
env: env,
|
|
923
|
+
path: [ ...path, "target" ],
|
|
924
|
+
ctx: ctx
|
|
925
|
+
}) ?? fallbackRef(op.target);
|
|
926
|
+
return {
|
|
927
|
+
...op,
|
|
928
|
+
target: target
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
const AUDIT_STAMPS = {
|
|
933
|
+
actor: {
|
|
934
|
+
type: "actor"
|
|
935
|
+
},
|
|
936
|
+
at: {
|
|
937
|
+
type: "now"
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
function desugarAuditOp({op: op, path: path, env: env, ctx: ctx}) {
|
|
942
|
+
const target = resolveRef({
|
|
943
|
+
ref: op.target,
|
|
944
|
+
env: env,
|
|
945
|
+
path: [ ...path, "target" ],
|
|
946
|
+
ctx: ctx
|
|
947
|
+
}) ?? fallbackRef(op.target);
|
|
948
|
+
if (op.value.type !== "object") return ctx.issues.push({
|
|
949
|
+
path: [ ...path, "value" ],
|
|
950
|
+
message: `audit value must be an object source carrying the domain fields (got "${op.value.type}")`
|
|
951
|
+
}), {
|
|
952
|
+
type: "field.append",
|
|
953
|
+
target: target,
|
|
954
|
+
value: op.value
|
|
955
|
+
};
|
|
956
|
+
const actorField = op.stampFields?.actor ?? "actor", atField = op.stampFields?.at ?? "at", fields = {
|
|
957
|
+
...op.value.fields
|
|
958
|
+
};
|
|
959
|
+
for (const [stamp, source] of [ [ actorField, AUDIT_STAMPS.actor ], [ atField, AUDIT_STAMPS.at ] ]) {
|
|
960
|
+
if (stamp in fields) {
|
|
961
|
+
ctx.issues.push({
|
|
962
|
+
path: [ ...path, "value", "fields", stamp ],
|
|
963
|
+
message: `audit stamp field "${stamp}" collides with an authored domain field — rename yours or remap the stamp via stampFields`
|
|
964
|
+
});
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
fields[stamp] = source;
|
|
968
|
+
}
|
|
969
|
+
return {
|
|
970
|
+
type: "field.append",
|
|
971
|
+
target: target,
|
|
972
|
+
value: {
|
|
973
|
+
type: "object",
|
|
974
|
+
fields: fields
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
function resolveRef({ref: ref, env: env, path: path, ctx: ctx}) {
|
|
980
|
+
const layers = ref.scope === void 0 ? env.layers : env.layers.filter(l => l.scope === ref.scope);
|
|
981
|
+
for (const layer of layers) if (layer.entries.has(ref.field)) return {
|
|
982
|
+
scope: layer.scope,
|
|
983
|
+
field: ref.field
|
|
984
|
+
};
|
|
985
|
+
const reachable = env.layers.flatMap(l => [ ...l.entries.keys() ].map(n => `${l.scope}:${n}`));
|
|
986
|
+
ctx.issues.push({
|
|
987
|
+
path: path,
|
|
988
|
+
message: `field reference "${ref.field}"${ref.scope ? ` (scope "${ref.scope}")` : ""} does not resolve to a declared entry. Reachable: ${reachable.join(", ") || "(none)"}`
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
function entryAt(env, ref) {
|
|
993
|
+
return env.layers.find(l => l.scope === ref.scope)?.entries.get(ref.field);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
function fallbackRef(ref) {
|
|
997
|
+
return {
|
|
998
|
+
scope: ref.scope ?? "workflow",
|
|
999
|
+
field: ref.field
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
function rolesCondition(roles, aliases) {
|
|
1004
|
+
if (!(!roles || roles.length === 0)) return groq`count($actor.roles[@ in ${expandRequiredRoles(roles, aliases)}]) > 0`;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
function stripUndefined(obj) {
|
|
1008
|
+
const out = {};
|
|
1009
|
+
for (const [k, value] of Object.entries(obj)) value !== void 0 && (out[k] = value);
|
|
1010
|
+
return out;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
400
1013
|
function isUnevaluable(result) {
|
|
401
1014
|
return result == null;
|
|
402
1015
|
}
|
|
@@ -416,9 +1029,9 @@ async function evaluateCondition(args) {
|
|
|
416
1029
|
|
|
417
1030
|
async function evaluatePredicates(args) {
|
|
418
1031
|
const out = {};
|
|
419
|
-
for (const [name,
|
|
1032
|
+
for (const [name, groq2] of Object.entries(args.predicates ?? {})) {
|
|
420
1033
|
const result = await runGroq({
|
|
421
|
-
groq:
|
|
1034
|
+
groq: groq2,
|
|
422
1035
|
params: args.params,
|
|
423
1036
|
snapshot: args.snapshot
|
|
424
1037
|
}), outcome = groqConditionDescribe.conditionOutcome(result);
|
|
@@ -427,46 +1040,77 @@ async function evaluatePredicates(args) {
|
|
|
427
1040
|
return out;
|
|
428
1041
|
}
|
|
429
1042
|
|
|
430
|
-
async function runGroq({groq:
|
|
1043
|
+
async function runGroq({groq: groq2, params: params, snapshot: snapshot}) {
|
|
431
1044
|
return groqConditionDescribe.runGroq({
|
|
432
|
-
groq:
|
|
1045
|
+
groq: groq2,
|
|
433
1046
|
params: params,
|
|
434
1047
|
dataset: snapshot.docs
|
|
435
1048
|
});
|
|
436
1049
|
}
|
|
437
1050
|
|
|
438
|
-
function conditionSyntaxIssues(
|
|
1051
|
+
function conditionSyntaxIssues(groq2, boundVars) {
|
|
439
1052
|
const issues = [];
|
|
440
1053
|
let tree;
|
|
441
1054
|
try {
|
|
442
|
-
tree = groqJs.parse(
|
|
1055
|
+
tree = groqJs.parse(groq2);
|
|
443
1056
|
} catch (err) {
|
|
444
1057
|
issues.push(errorMessage(err));
|
|
445
1058
|
}
|
|
446
|
-
if (/\*\s*\[\s*_type\b/.test(
|
|
447
|
-
boundVars !== void 0 && tree !== void 0) for (const name of conditionParameterNames(
|
|
1059
|
+
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."),
|
|
1060
|
+
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
1061
|
return issues;
|
|
449
1062
|
}
|
|
450
1063
|
|
|
451
|
-
function conditionParameterNames(
|
|
1064
|
+
function conditionParameterNames(groq2) {
|
|
452
1065
|
const read = /* @__PURE__ */ new Set;
|
|
453
|
-
return walkAstNodes(tryParseGroq(
|
|
1066
|
+
return walkAstNodes(tryParseGroq(groq2), node => {
|
|
454
1067
|
node.type === "Parameter" && typeof node.name == "string" && read.add(node.name);
|
|
455
1068
|
}), read;
|
|
456
1069
|
}
|
|
457
1070
|
|
|
458
|
-
function conditionFieldReadNames(
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
1071
|
+
function conditionFieldReadNames(groq2) {
|
|
1072
|
+
return new Set(conditionFieldReads(groq2).map(read => read.name));
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
function conditionFieldReads(groq2) {
|
|
1076
|
+
const reads = /* @__PURE__ */ new Map;
|
|
1077
|
+
return collectFieldReads(tryParseGroq(groq2), reads), [ ...reads.values() ];
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function collectFieldReads(node, reads) {
|
|
1081
|
+
if (Array.isArray(node)) {
|
|
1082
|
+
for (const item of node) collectFieldReads(item, reads);
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
if (typeof node != "object" || node === null) return;
|
|
1086
|
+
const chain = fieldsAttributeChain(node);
|
|
1087
|
+
if (chain !== void 0) {
|
|
1088
|
+
const [name, ...tail] = chain, path = tail.length > 0 ? tail.join(".") : void 0;
|
|
1089
|
+
reads.set(`${name}.${path ?? ""}`, {
|
|
1090
|
+
name: name,
|
|
1091
|
+
path: path
|
|
1092
|
+
});
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
for (const value of Object.values(node)) collectFieldReads(value, reads);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
function fieldsAttributeChain(node) {
|
|
1099
|
+
const names = [];
|
|
1100
|
+
let current = node;
|
|
1101
|
+
for (;typeof current == "object" && current !== null; ) {
|
|
1102
|
+
const typed = current;
|
|
1103
|
+
if (typed.type !== "AccessAttribute" || typeof typed.name != "string") return;
|
|
1104
|
+
names.unshift(typed.name);
|
|
1105
|
+
const base = typed.base;
|
|
1106
|
+
if (base?.type === "Parameter" && base.name === "fields") return names;
|
|
1107
|
+
current = base;
|
|
1108
|
+
}
|
|
465
1109
|
}
|
|
466
1110
|
|
|
467
|
-
function conditionEffectReads(
|
|
1111
|
+
function conditionEffectReads(groq2) {
|
|
468
1112
|
const reads = [];
|
|
469
|
-
return walkAstNodes(tryParseGroq(
|
|
1113
|
+
return walkAstNodes(tryParseGroq(groq2), node => {
|
|
470
1114
|
if (node.type !== "AccessAttribute" || typeof node.name != "string") return;
|
|
471
1115
|
const base = node.base;
|
|
472
1116
|
base?.type !== "AccessAttribute" || typeof base.name != "string" || base.base?.type === "Parameter" && base.base.name === "effects" && reads.push({
|
|
@@ -476,9 +1120,34 @@ function conditionEffectReads(groq) {
|
|
|
476
1120
|
}), reads;
|
|
477
1121
|
}
|
|
478
1122
|
|
|
479
|
-
function
|
|
1123
|
+
function readsRootDocument(groq2) {
|
|
1124
|
+
return nodeReadsRoot(tryParseGroq(groq2), 0);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
const SCOPED_CHILD = {
|
|
1128
|
+
Filter: "expr",
|
|
1129
|
+
Projection: "expr",
|
|
1130
|
+
Map: "expr",
|
|
1131
|
+
FlatMap: "expr",
|
|
1132
|
+
PipeFuncCall: "args"
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
function nodeReadsRoot(node, depth) {
|
|
1136
|
+
if (Array.isArray(node)) return node.some(item => nodeReadsRoot(item, depth));
|
|
1137
|
+
if (typeof node != "object" || node === null) return !1;
|
|
1138
|
+
const typed = node;
|
|
1139
|
+
if (depth === 0 && typed.type === "This" || depth === 0 && typed.type === "AccessAttribute" && typed.base === void 0) return !0;
|
|
1140
|
+
if (typed.type === "Parent") {
|
|
1141
|
+
const climb = typeof typed.n == "number" ? typed.n : 1;
|
|
1142
|
+
return depth - climb <= 0;
|
|
1143
|
+
}
|
|
1144
|
+
const scopedChild = typeof typed.type == "string" ? SCOPED_CHILD[typed.type] : void 0;
|
|
1145
|
+
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));
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
function tryParseGroq(groq2) {
|
|
480
1149
|
try {
|
|
481
|
-
return groqJs.parse(
|
|
1150
|
+
return groqJs.parse(groq2);
|
|
482
1151
|
} catch {
|
|
483
1152
|
return;
|
|
484
1153
|
}
|
|
@@ -495,7 +1164,13 @@ function walkAstNodes(node, visit) {
|
|
|
495
1164
|
}
|
|
496
1165
|
}
|
|
497
1166
|
|
|
498
|
-
const
|
|
1167
|
+
const ACTIVITY_STATUSES = [ "active", "done", "skipped", "failed" ], TERMINAL_ACTIVITY_STATUSES = [ "done", "skipped", "failed" ];
|
|
1168
|
+
|
|
1169
|
+
function isTerminalActivityStatus(status) {
|
|
1170
|
+
return TERMINAL_ACTIVITY_STATUSES.includes(status);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
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
1174
|
name: "self",
|
|
500
1175
|
binding: "always",
|
|
501
1176
|
label: "this workflow instance",
|
|
@@ -504,7 +1179,7 @@ const CONDITION_VARS = [ {
|
|
|
504
1179
|
name: "fields",
|
|
505
1180
|
binding: "always",
|
|
506
1181
|
label: "the workflow's fields",
|
|
507
|
-
description: "Declared field entries rendered by name (`$fields.<name>` is the value, no
|
|
1182
|
+
description: "Declared field entries rendered by name (`$fields.<name>` is the value, no wrapper). Stage/activity scopes overlay lexically. What a read puts in your hand follows the declared kind: a singular `doc.ref` DEREFERENCES into the hydrated document (lake `_id`/`_type` plus content fields — which may themselves be named `id`/`type`), while `doc.refs` elements and `release.ref` stay REFERENCES — `{id, type[, releaseName]}` with `id` a GDR URI — because identity reads (membership, counting, joins) must stay total without hydrating every target, and targets may live in resources the evaluation cannot fetch from. Conditions evaluate against the in-memory snapshot only, so a dereferencing plural kind would silently hole wherever hydration lagged."
|
|
508
1183
|
}, {
|
|
509
1184
|
name: "parent",
|
|
510
1185
|
binding: "always",
|
|
@@ -525,11 +1200,16 @@ const CONDITION_VARS = [ {
|
|
|
525
1200
|
binding: "always",
|
|
526
1201
|
label: "the current time",
|
|
527
1202
|
description: "The ISO clock reading shared by every condition in one evaluation pass, so they agree on the time."
|
|
1203
|
+
}, {
|
|
1204
|
+
name: "context",
|
|
1205
|
+
binding: "always",
|
|
1206
|
+
label: "start-time context",
|
|
1207
|
+
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
1208
|
}, {
|
|
529
1209
|
name: "effects",
|
|
530
1210
|
binding: "always",
|
|
531
1211
|
label: "automation outputs",
|
|
532
|
-
description: "
|
|
1212
|
+
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
1213
|
}, {
|
|
534
1214
|
name: "effectStatus",
|
|
535
1215
|
binding: "always",
|
|
@@ -564,29 +1244,169 @@ const CONDITION_VARS = [ {
|
|
|
564
1244
|
name: "can",
|
|
565
1245
|
binding: "caller",
|
|
566
1246
|
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
|
|
1247
|
+
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
1248
|
}, {
|
|
569
1249
|
name: "row",
|
|
570
1250
|
binding: "spawn",
|
|
571
1251
|
label: "the spawned row",
|
|
572
|
-
description: "One `
|
|
1252
|
+
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). Deploy rejects a read at every other site (including `spawn.forEach` itself and `spawn.context`), where it is GROQ null."
|
|
573
1253
|
}, {
|
|
574
1254
|
name: "params",
|
|
575
1255
|
binding: "caller",
|
|
576
1256
|
label: "the action's arguments",
|
|
577
|
-
description: "The firing action's args — they hold values only while
|
|
1257
|
+
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
1258
|
}, {
|
|
579
1259
|
name: "subworkflows",
|
|
580
1260
|
binding: "always",
|
|
581
1261
|
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),
|
|
1262
|
+
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`."
|
|
1263
|
+
} ], 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 = [ {
|
|
1264
|
+
name: "tag",
|
|
1265
|
+
description: "The engine's tag partition — scope `*[...]` instance scans with `tag == $tag`."
|
|
1266
|
+
}, {
|
|
1267
|
+
name: "definition",
|
|
1268
|
+
description: "The `name` of the definition under evaluation (its own start block binds it)."
|
|
1269
|
+
}, {
|
|
1270
|
+
name: "now",
|
|
1271
|
+
description: "The ISO clock reading of the evaluating engine."
|
|
1272
|
+
} ], START_ALLOWED_VARS = [ ...START_FILTER_VARS, {
|
|
1273
|
+
name: "fields",
|
|
1274
|
+
description: "The caller's input entries by name (`initialFields` — at `startInstance`, the values the start would seed; at a pre-flight, the values gathered so far, so a read of a not-yet-supplied entry is GROQ null). Document references bind as GDR envelopes — `$fields.<entry>.id` is the GDR URI, never a string authors assemble — a singular `doc.ref` included (nothing hydrates at the gate or the pre-flight). Pathed reads are deploy-checked against these envelope shapes."
|
|
1275
|
+
} ], GUARD_PREDICATE_VARS = [ {
|
|
584
1276
|
name: "guard",
|
|
585
1277
|
description: "The guard document itself (its `metadata` carries deploy-time resolved values)."
|
|
586
1278
|
}, {
|
|
587
1279
|
name: "mutation",
|
|
588
1280
|
description: "The attempted mutation — `mutation.action` is the write kind being gated."
|
|
589
|
-
} ],
|
|
1281
|
+
} ], NonEmptyString = v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1, "must be a non-empty string"));
|
|
1282
|
+
|
|
1283
|
+
function isParseableInstant(value) {
|
|
1284
|
+
return typeof value == "string" && !Number.isNaN(Date.parse(value));
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
const IsoTimestamp = v__namespace.pipe(v__namespace.string(), v__namespace.check(s => isParseableInstant(s), "must be an ISO-8601 datetime string"));
|
|
1288
|
+
|
|
1289
|
+
function tolerantObject() {
|
|
1290
|
+
return (entries, ..._exact) => v__namespace.looseObject(entries);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
function tolerantEntries() {
|
|
1294
|
+
return (entries, ..._exact) => entries;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
function schemaTreeShape(schema) {
|
|
1298
|
+
return walkSchemaShape(schema, /* @__PURE__ */ new Set);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
function walkSchemaShape(node, path) {
|
|
1302
|
+
if (typeof node != "object" || node === null) return "unknown";
|
|
1303
|
+
if (path.has(node)) return "(circular)";
|
|
1304
|
+
path.add(node);
|
|
1305
|
+
try {
|
|
1306
|
+
const schema = node;
|
|
1307
|
+
return containerShape(schema, path) ?? leafShape(schema);
|
|
1308
|
+
} finally {
|
|
1309
|
+
path.delete(node);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
const objectWalker = (schema, path) => objectEntriesShape(schema.entries, path), unionWalker = (schema, path) => ({
|
|
1314
|
+
union: schema.options.map(option => walkSchemaShape(option, path))
|
|
1315
|
+
}), unwrapWalker = (schema, path) => walkSchemaShape(schema.wrapped, path), CONTAINER_WALKERS = {
|
|
1316
|
+
strict_object: objectWalker,
|
|
1317
|
+
loose_object: objectWalker,
|
|
1318
|
+
object: objectWalker,
|
|
1319
|
+
array: (schema, path) => ({
|
|
1320
|
+
array: walkSchemaShape(schema.item, path)
|
|
1321
|
+
}),
|
|
1322
|
+
record: (schema, path) => ({
|
|
1323
|
+
record: walkSchemaShape(schema.value, path)
|
|
1324
|
+
}),
|
|
1325
|
+
union: unionWalker,
|
|
1326
|
+
variant: unionWalker,
|
|
1327
|
+
lazy: (schema, path) => walkSchemaShape(schema.getter(void 0), path),
|
|
1328
|
+
exact_optional: unwrapWalker,
|
|
1329
|
+
optional: unwrapWalker,
|
|
1330
|
+
nullable: unwrapWalker
|
|
1331
|
+
};
|
|
1332
|
+
|
|
1333
|
+
function containerShape(schema, path) {
|
|
1334
|
+
return CONTAINER_WALKERS[String(schema.type)]?.(schema, path);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
function objectEntriesShape(entries, path) {
|
|
1338
|
+
const out = {};
|
|
1339
|
+
for (const [key, entry] of Object.entries(entries)) {
|
|
1340
|
+
const optional = isOptionalEntry(entry), wrapped = optional ? entry.wrapped : entry;
|
|
1341
|
+
out[optional ? `${key}?` : key] = walkSchemaShape(wrapped, path);
|
|
1342
|
+
}
|
|
1343
|
+
return Object.fromEntries(Object.entries(out).sort(([a], [b]) => a < b ? -1 : 1));
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function isOptionalEntry(entry) {
|
|
1347
|
+
if (typeof entry != "object" || entry === null) return !1;
|
|
1348
|
+
const kind = entry.type;
|
|
1349
|
+
return kind === "exact_optional" || kind === "optional";
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
const LEAF_KINDS = /* @__PURE__ */ new Set([ "string", "number", "boolean", "null", "undefined", "unknown", "any" ]);
|
|
1353
|
+
|
|
1354
|
+
function leafShape(schema) {
|
|
1355
|
+
if (schema.type === "picklist") return schema.options.join(" | ");
|
|
1356
|
+
if (schema.type === "literal") return `literal ${String(schema.literal)}`;
|
|
1357
|
+
if (schema.type === "custom") return typeof schema.message == "string" ? `custom(${schema.message})` : "custom";
|
|
1358
|
+
if (!LEAF_KINDS.has(String(schema.type))) throw new Error(`schemaTreeShape: unhandled schema kind "${String(schema.type)}" — extend the walker before regenerating the model ledger`);
|
|
1359
|
+
return String(schema.type);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
function formatValidationError(label, issues) {
|
|
1363
|
+
const lines = issues.map(issue => ` - ${issue.path.length === 0 ? "(root)" : formatIssuePath(issue.path)}: ${issue.message}`);
|
|
1364
|
+
return `${label} failed validation (${issues.length} issue${issues.length === 1 ? "" : "s"}):\n${lines.join(`\n`)}`;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
function issuesFromValibot(issues) {
|
|
1368
|
+
return issues.map(issue => ({
|
|
1369
|
+
path: issue.path ? issue.path.map(item => item.key) : [],
|
|
1370
|
+
message: issue.message
|
|
1371
|
+
}));
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
function formatIssuePath(path) {
|
|
1375
|
+
let out = "";
|
|
1376
|
+
for (const seg of path) typeof seg == "number" ? out += `[${seg}]` : out += out.length === 0 ? String(seg) : `.${String(seg)}`;
|
|
1377
|
+
return out;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
class PersistedDocShapeError extends WorkflowError {
|
|
1381
|
+
documentId;
|
|
1382
|
+
documentType;
|
|
1383
|
+
issues;
|
|
1384
|
+
constructor(args) {
|
|
1385
|
+
super("persisted-doc-shape", formatValidationError(`Persisted ${args.documentType} document "${args.documentId}"`, args.issues)),
|
|
1386
|
+
this.name = "PersistedDocShapeError", this.documentId = args.documentId, this.documentType = args.documentType,
|
|
1387
|
+
this.issues = args.issues;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
function parsePersistedDoc(args) {
|
|
1392
|
+
const result = v__namespace.safeParse(args.schema, args.doc);
|
|
1393
|
+
if (!result.success) throw new PersistedDocShapeError({
|
|
1394
|
+
documentId: documentIdOf(args.doc),
|
|
1395
|
+
documentType: args.docType,
|
|
1396
|
+
issues: issuesFromValibot(result.issues)
|
|
1397
|
+
});
|
|
1398
|
+
return result.output;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
function documentIdOf(doc) {
|
|
1402
|
+
if (typeof doc == "object" && doc !== null) {
|
|
1403
|
+
const id = doc._id;
|
|
1404
|
+
if (typeof id == "string") return id;
|
|
1405
|
+
}
|
|
1406
|
+
return "(unknown id)";
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
const ACTOR_KINDS = [ "person", "agent", "system" ];
|
|
590
1410
|
|
|
591
1411
|
function releaseDocId(releaseName) {
|
|
592
1412
|
return `_.releases.${releaseName}`;
|
|
@@ -633,29 +1453,28 @@ class FieldValueShapeError extends WorkflowError {
|
|
|
633
1453
|
}
|
|
634
1454
|
}
|
|
635
1455
|
|
|
636
|
-
const
|
|
637
|
-
id:
|
|
638
|
-
type:
|
|
639
|
-
}), ReleaseRefShape = v__namespace.pipe(
|
|
640
|
-
id:
|
|
1456
|
+
const GdrUriSchema = v__namespace.custom(s => typeof s == "string" && isGdrUri(s), "must be a GDR URI"), GdrShape = tolerantObject()({
|
|
1457
|
+
id: GdrUriSchema,
|
|
1458
|
+
type: NonEmptyString
|
|
1459
|
+
}), ReleaseRefShape = v__namespace.pipe(tolerantObject()({
|
|
1460
|
+
id: GdrUriSchema,
|
|
641
1461
|
type: v__namespace.literal("system.release"),
|
|
642
|
-
releaseName:
|
|
643
|
-
}), v__namespace.check(ref => !isGdrUri(ref.id) || extractDocumentId(ref.id) === releaseDocId(ref.releaseName), "id must point at the `_.releases.<releaseName>` doc named by releaseName")), ActorShape =
|
|
1462
|
+
releaseName: NonEmptyString
|
|
1463
|
+
}), v__namespace.check(ref => !isGdrUri(ref.id) || extractDocumentId(ref.id) === releaseDocId(ref.releaseName), "id must point at the `_.releases.<releaseName>` doc named by releaseName")), ActorShape = tolerantObject()({
|
|
644
1464
|
kind: v__namespace.picklist(ACTOR_KINDS),
|
|
645
|
-
id:
|
|
646
|
-
roles: v__namespace.
|
|
647
|
-
onBehalfOf: v__namespace.
|
|
648
|
-
}), AssigneeShape = v__namespace.union([
|
|
1465
|
+
id: NonEmptyString,
|
|
1466
|
+
roles: v__namespace.exactOptional(v__namespace.array(v__namespace.string())),
|
|
1467
|
+
onBehalfOf: v__namespace.exactOptional(v__namespace.string())
|
|
1468
|
+
}), AssigneeShape = v__namespace.union([ tolerantObject()({
|
|
649
1469
|
type: v__namespace.literal("user"),
|
|
650
|
-
id:
|
|
651
|
-
}),
|
|
1470
|
+
id: NonEmptyString
|
|
1471
|
+
}), tolerantObject()({
|
|
652
1472
|
type: v__namespace.literal("role"),
|
|
653
|
-
role:
|
|
654
|
-
}) ]), NullableString = v__namespace.union([ v__namespace.null(), v__namespace.string() ]), NullableNumber = v__namespace.union([ v__namespace.null(), v__namespace.number() ]), NullableBoolean = v__namespace.union([ v__namespace.null(), v__namespace.boolean() ]), NullableDateTime = v__namespace.union([ v__namespace.null(),
|
|
1473
|
+
role: NonEmptyString
|
|
1474
|
+
}) ]), NullableString = v__namespace.union([ v__namespace.null(), v__namespace.string() ]), NullableNumber = v__namespace.union([ v__namespace.null(), v__namespace.number() ]), NullableBoolean = v__namespace.union([ v__namespace.null(), v__namespace.boolean() ]), NullableDateTime = v__namespace.union([ v__namespace.null(), IsoTimestamp ]), NullableDate = v__namespace.union([ v__namespace.null(), v__namespace.pipe(v__namespace.string(), v__namespace.regex(/^\d{4}-\d{2}-\d{2}$/, "must be a `YYYY-MM-DD` date")) ]), NullableUrl = NullableString, fieldValueSchemas = {
|
|
655
1475
|
"doc.ref": v__namespace.union([ v__namespace.null(), GdrShape ]),
|
|
656
1476
|
"doc.refs": v__namespace.array(GdrShape),
|
|
657
1477
|
"release.ref": v__namespace.union([ v__namespace.null(), ReleaseRefShape ]),
|
|
658
|
-
query: v__namespace.any(),
|
|
659
1478
|
string: NullableString,
|
|
660
1479
|
text: NullableString,
|
|
661
1480
|
number: NullableNumber,
|
|
@@ -666,6 +1485,9 @@ const GdrShape = v__namespace.looseObject({
|
|
|
666
1485
|
actor: v__namespace.union([ v__namespace.null(), ActorShape ]),
|
|
667
1486
|
assignee: v__namespace.union([ v__namespace.null(), AssigneeShape ]),
|
|
668
1487
|
assignees: v__namespace.array(AssigneeShape)
|
|
1488
|
+
}, valueSchemas = {
|
|
1489
|
+
...fieldValueSchemas,
|
|
1490
|
+
query: v__namespace.any()
|
|
669
1491
|
};
|
|
670
1492
|
|
|
671
1493
|
function shapeValueSchema(shape, leaf) {
|
|
@@ -752,7 +1574,7 @@ function isBareSeedId(id) {
|
|
|
752
1574
|
|
|
753
1575
|
const AuthoringRefId = v__namespace.pipe(v__namespace.string(), v__namespace.check(isAuthoringRefId, "must be a bare document id, a GDR URI, or a portable `@<alias>:<id>` reference")), AuthoringGdrShape = v__namespace.looseObject({
|
|
754
1576
|
id: AuthoringRefId,
|
|
755
|
-
type:
|
|
1577
|
+
type: NonEmptyString
|
|
756
1578
|
}), seedValueSchemas = {
|
|
757
1579
|
...valueSchemas,
|
|
758
1580
|
"doc.ref": v__namespace.union([ v__namespace.null(), AuthoringGdrShape ]),
|
|
@@ -760,7 +1582,7 @@ const AuthoringRefId = v__namespace.pipe(v__namespace.string(), v__namespace.che
|
|
|
760
1582
|
"release.ref": v__namespace.union([ v__namespace.null(), v__namespace.looseObject({
|
|
761
1583
|
id: AuthoringRefId,
|
|
762
1584
|
type: v__namespace.literal("system.release"),
|
|
763
|
-
releaseName:
|
|
1585
|
+
releaseName: NonEmptyString
|
|
764
1586
|
}) ])
|
|
765
1587
|
};
|
|
766
1588
|
|
|
@@ -803,13 +1625,7 @@ function formatIssues(issues) {
|
|
|
803
1625
|
});
|
|
804
1626
|
}
|
|
805
1627
|
|
|
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_]*$/;
|
|
1628
|
+
const NonEmpty = NonEmptyString, PositiveInt = v__namespace.pipe(v__namespace.number(), v__namespace.integer(), v__namespace.minValue(1)), GROQ_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
813
1629
|
|
|
814
1630
|
function groqIdentifier(referencedAs) {
|
|
815
1631
|
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 +1724,7 @@ const StoredFieldOpSchema = v__namespace.variant("type", [ ...opSchemas(StoredFi
|
|
|
908
1724
|
type: v__namespace.literal("status.set"),
|
|
909
1725
|
activity: NonEmpty,
|
|
910
1726
|
status: picklist(ACTIVITY_STATUSES)
|
|
911
|
-
}) ]),
|
|
1727
|
+
}) ]), AuditOpSchema = v__namespace.strictObject({
|
|
912
1728
|
type: v__namespace.literal("audit"),
|
|
913
1729
|
target: AuthoringFieldRefSchema,
|
|
914
1730
|
value: ValueExprSchema,
|
|
@@ -1036,6 +1852,15 @@ const TodoListFieldSchema = pinned()(v__namespace.strictObject(listSugarFields("
|
|
|
1036
1852
|
bindings: v__namespace.optional(v__namespace.record(v__namespace.string(), ConditionSchema)),
|
|
1037
1853
|
input: v__namespace.optional(v__namespace.record(v__namespace.string(), v__namespace.unknown())),
|
|
1038
1854
|
outputs: v__namespace.optional(v__namespace.array(FieldShapeSchema))
|
|
1855
|
+
}), DefinitionRefSchema = v__namespace.strictObject({
|
|
1856
|
+
name: NonEmpty,
|
|
1857
|
+
version: v__namespace.optional(v__namespace.union([ PositiveInt, v__namespace.literal("latest") ]))
|
|
1858
|
+
}), SubworkflowsSchema = v__namespace.strictObject({
|
|
1859
|
+
forEach: NonEmpty,
|
|
1860
|
+
definition: DefinitionRefSchema,
|
|
1861
|
+
with: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
|
|
1862
|
+
context: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
|
|
1863
|
+
onExit: v__namespace.optional(picklist([ "detach", "abort" ]))
|
|
1039
1864
|
}), ActionParamSchema = v__namespace.strictObject({
|
|
1040
1865
|
type: picklist([ "string", "number", "boolean", "url", "dateTime", "actor", "doc.ref", "doc.refs", "json" ]),
|
|
1041
1866
|
name: NonEmpty,
|
|
@@ -1050,14 +1875,19 @@ function actionFields(op, group) {
|
|
|
1050
1875
|
title: v__namespace.optional(v__namespace.string()),
|
|
1051
1876
|
description: v__namespace.optional(v__namespace.string()),
|
|
1052
1877
|
group: v__namespace.optional(group),
|
|
1878
|
+
when: v__namespace.optional(ConditionSchema),
|
|
1053
1879
|
filter: v__namespace.optional(ConditionSchema),
|
|
1054
1880
|
params: v__namespace.optional(v__namespace.array(ActionParamSchema)),
|
|
1055
1881
|
ops: v__namespace.optional(v__namespace.array(op)),
|
|
1056
|
-
effects: v__namespace.optional(v__namespace.array(EffectSchema))
|
|
1882
|
+
effects: v__namespace.optional(v__namespace.array(EffectSchema)),
|
|
1883
|
+
spawn: v__namespace.optional(SubworkflowsSchema)
|
|
1057
1884
|
};
|
|
1058
1885
|
}
|
|
1059
1886
|
|
|
1060
|
-
const StoredActionSchema = pinned()(v__namespace.strictObject(
|
|
1887
|
+
const StoredActionSchema = pinned()(v__namespace.strictObject({
|
|
1888
|
+
...actionFields(StoredOpSchema, StoredGroupMembershipSchema),
|
|
1889
|
+
roles: v__namespace.optional(v__namespace.array(NonEmpty))
|
|
1890
|
+
})), TerminalActivityStatus = picklist(TERMINAL_ACTIVITY_STATUSES), RawAuthoringActionSchema = pinned()(v__namespace.strictObject({
|
|
1061
1891
|
...actionFields(AuthoringOpSchema, AuthoringGroupMembershipSchema),
|
|
1062
1892
|
roles: v__namespace.optional(v__namespace.array(NonEmpty)),
|
|
1063
1893
|
status: v__namespace.optional(TerminalActivityStatus)
|
|
@@ -1072,35 +1902,19 @@ const StoredActionSchema = pinned()(v__namespace.strictObject(actionFields(Store
|
|
|
1072
1902
|
filter: v__namespace.optional(ConditionSchema),
|
|
1073
1903
|
params: v__namespace.optional(v__namespace.array(ActionParamSchema)),
|
|
1074
1904
|
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
|
-
});
|
|
1905
|
+
})), AuthoringActionSchema = pinned()(v__namespace.lazy(input => typeof input == "object" && input !== null && "type" in input ? ClaimActionSchema : RawAuthoringActionSchema));
|
|
1085
1906
|
|
|
1086
|
-
function activityFields({field: field, action: action,
|
|
1907
|
+
function activityFields({field: field, action: action, target: target, group: group}) {
|
|
1087
1908
|
return {
|
|
1088
1909
|
name: NonEmpty,
|
|
1089
1910
|
title: v__namespace.optional(v__namespace.string()),
|
|
1090
1911
|
description: v__namespace.optional(v__namespace.string()),
|
|
1091
1912
|
groups: v__namespace.optional(v__namespace.array(GroupSchema)),
|
|
1092
1913
|
group: v__namespace.optional(group),
|
|
1093
|
-
kind: v__namespace.optional(picklist(ACTIVITY_KINDS)),
|
|
1094
1914
|
target: v__namespace.optional(target),
|
|
1095
|
-
activation: v__namespace.optional(picklist([ "auto", "manual" ])),
|
|
1096
1915
|
filter: v__namespace.optional(ConditionSchema),
|
|
1097
1916
|
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
1917
|
actions: v__namespace.optional(v__namespace.array(action)),
|
|
1103
|
-
subworkflows: v__namespace.optional(SubworkflowsSchema),
|
|
1104
1918
|
fields: v__namespace.optional(v__namespace.array(field))
|
|
1105
1919
|
};
|
|
1106
1920
|
}
|
|
@@ -1108,30 +1922,26 @@ function activityFields({field: field, action: action, op: op, target: target, g
|
|
|
1108
1922
|
const StoredActivitySchema = pinned()(v__namespace.strictObject(activityFields({
|
|
1109
1923
|
field: FieldEntrySchema,
|
|
1110
1924
|
action: StoredActionSchema,
|
|
1111
|
-
op: StoredOpSchema,
|
|
1112
1925
|
target: StoredManualTargetSchema,
|
|
1113
1926
|
group: StoredGroupMembershipSchema
|
|
1114
1927
|
}))), AuthoringActivitySchema = pinned()(v__namespace.strictObject(activityFields({
|
|
1115
1928
|
field: AuthoringFieldEntrySchema,
|
|
1116
1929
|
action: AuthoringActionSchema,
|
|
1117
|
-
op: AuthoringOpSchema,
|
|
1118
1930
|
target: AuthoringManualTargetSchema,
|
|
1119
1931
|
group: AuthoringGroupMembershipSchema
|
|
1120
1932
|
})));
|
|
1121
1933
|
|
|
1122
|
-
function transitionFields(
|
|
1934
|
+
function transitionFields(when) {
|
|
1123
1935
|
return {
|
|
1124
1936
|
name: NonEmpty,
|
|
1125
1937
|
title: v__namespace.optional(v__namespace.string()),
|
|
1126
1938
|
description: v__namespace.optional(v__namespace.string()),
|
|
1127
1939
|
to: NonEmpty,
|
|
1128
|
-
|
|
1129
|
-
ops: v__namespace.optional(v__namespace.array(op)),
|
|
1130
|
-
effects: v__namespace.optional(v__namespace.array(EffectSchema))
|
|
1940
|
+
when: when
|
|
1131
1941
|
};
|
|
1132
1942
|
}
|
|
1133
1943
|
|
|
1134
|
-
const StoredTransitionSchema = pinned()(v__namespace.strictObject(transitionFields(
|
|
1944
|
+
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
1945
|
type: v__namespace.literal("self")
|
|
1136
1946
|
}), v__namespace.strictObject({
|
|
1137
1947
|
type: v__namespace.literal("now")
|
|
@@ -1193,16 +2003,26 @@ const StoredStageSchema = pinned()(v__namespace.strictObject(stageFields({
|
|
|
1193
2003
|
transition: AuthoringTransitionSchema,
|
|
1194
2004
|
guard: AuthoringGuardSchema,
|
|
1195
2005
|
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" ];
|
|
2006
|
+
}))), 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" ];
|
|
2007
|
+
|
|
2008
|
+
function startFields(kind) {
|
|
2009
|
+
return {
|
|
2010
|
+
kind: kind,
|
|
2011
|
+
filter: v__namespace.optional(ConditionSchema),
|
|
2012
|
+
allowed: v__namespace.optional(ConditionSchema)
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
1197
2015
|
|
|
1198
|
-
|
|
2016
|
+
const StoredStartSchema = pinned()(v__namespace.strictObject(startFields(picklist(START_KINDS)))), AuthoringStartSchema = pinned()(v__namespace.strictObject(startFields(v__namespace.optional(picklist(START_KINDS)))));
|
|
2017
|
+
|
|
2018
|
+
function workflowFields({field: field, stage: stage, start: start}) {
|
|
1199
2019
|
return {
|
|
1200
2020
|
name: NonEmpty,
|
|
1201
2021
|
title: NonEmpty,
|
|
1202
2022
|
description: v__namespace.optional(v__namespace.string()),
|
|
1203
2023
|
groups: v__namespace.optional(v__namespace.array(GroupSchema)),
|
|
1204
2024
|
lifecycle: v__namespace.optional(picklist(WORKFLOW_LIFECYCLES)),
|
|
1205
|
-
|
|
2025
|
+
start: v__namespace.optional(start),
|
|
1206
2026
|
initialStage: NonEmpty,
|
|
1207
2027
|
fields: v__namespace.optional(v__namespace.array(field)),
|
|
1208
2028
|
stages: v__namespace.pipe(v__namespace.array(stage), v__namespace.minLength(1, "must declare at least one stage")),
|
|
@@ -1211,7 +2031,11 @@ function workflowFields(field, stage) {
|
|
|
1211
2031
|
};
|
|
1212
2032
|
}
|
|
1213
2033
|
|
|
1214
|
-
const WorkflowDefinitionSchema = pinned()(v__namespace.strictObject(workflowFields(
|
|
2034
|
+
const WorkflowDefinitionSchema = pinned()(v__namespace.strictObject(workflowFields({
|
|
2035
|
+
field: FieldEntrySchema,
|
|
2036
|
+
stage: StoredStageSchema,
|
|
2037
|
+
start: StoredStartSchema
|
|
2038
|
+
})));
|
|
1215
2039
|
|
|
1216
2040
|
function parseStoredDefinition(input, label) {
|
|
1217
2041
|
return parseOrThrow({
|
|
@@ -1221,28 +2045,26 @@ function parseStoredDefinition(input, label) {
|
|
|
1221
2045
|
});
|
|
1222
2046
|
}
|
|
1223
2047
|
|
|
1224
|
-
const AuthoringWorkflowSchema = pinned()(v__namespace.strictObject(workflowFields(
|
|
2048
|
+
const AuthoringWorkflowSchema = pinned()(v__namespace.strictObject(workflowFields({
|
|
2049
|
+
field: AuthoringFieldEntrySchema,
|
|
2050
|
+
stage: AuthoringStageSchema,
|
|
2051
|
+
start: AuthoringStartSchema
|
|
2052
|
+
}))), WORKFLOW_DEFINITION_TYPE = "sanity.workflow.definition";
|
|
1225
2053
|
|
|
1226
2054
|
function isStartableDefinition(definition) {
|
|
1227
2055
|
return definition.lifecycle !== "child";
|
|
1228
2056
|
}
|
|
1229
2057
|
|
|
1230
|
-
function
|
|
1231
|
-
|
|
1232
|
-
return `${label} failed validation (${issues.length} issue${issues.length === 1 ? "" : "s"}):\n${lines.join(`\n`)}`;
|
|
2058
|
+
function startKindOf(definition) {
|
|
2059
|
+
return definition.start?.kind ?? "interactive";
|
|
1233
2060
|
}
|
|
1234
2061
|
|
|
1235
|
-
function
|
|
1236
|
-
return
|
|
1237
|
-
path: issue.path ? issue.path.map(item => item.key) : [],
|
|
1238
|
-
message: issue.message
|
|
1239
|
-
}));
|
|
2062
|
+
function isSubjectEntry(entry) {
|
|
2063
|
+
return entry.required === !0 && (entry.type === "doc.ref" || entry.type === "doc.refs");
|
|
1240
2064
|
}
|
|
1241
2065
|
|
|
1242
|
-
function
|
|
1243
|
-
|
|
1244
|
-
for (const seg of path) typeof seg == "number" ? out += `[${seg}]` : out += out.length === 0 ? String(seg) : `.${String(seg)}`;
|
|
1245
|
-
return out;
|
|
2066
|
+
function isInputSourced(entry) {
|
|
2067
|
+
return entry.initialValue?.type === "input";
|
|
1246
2068
|
}
|
|
1247
2069
|
|
|
1248
2070
|
function parseOrThrow({schema: schema, input: input, label: label}) {
|
|
@@ -1273,6 +2095,13 @@ function checkDuplicates({names: names, what: what, issues: issues}) {
|
|
|
1273
2095
|
return seen;
|
|
1274
2096
|
}
|
|
1275
2097
|
|
|
2098
|
+
function checkDefinitionName(def, issues) {
|
|
2099
|
+
LAKE_ID_SEGMENT_RE.test(def.name) || issues.push({
|
|
2100
|
+
path: [ "name" ],
|
|
2101
|
+
message: `definition name "${def.name}" does not match the definition-name grammar ${LAKE_ID_SEGMENT_RE.source} (${LAKE_ID_SEGMENT_GLOSS}) — the name is interpolated into every deployed document id (\`<tag>.<name>.v<version>\`), which the engine keeps to the same segment grammar as the tag`
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
|
|
1276
2105
|
function checkStages(def, issues) {
|
|
1277
2106
|
const stageNames = checkDuplicates({
|
|
1278
2107
|
names: def.stages.map((stage, i) => ({
|
|
@@ -1329,12 +2158,6 @@ function checkActivities({def: def, i: i, activityNames: activityNames, issues:
|
|
|
1329
2158
|
}
|
|
1330
2159
|
|
|
1331
2160
|
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
2161
|
for (const [a, action] of (activity.actions ?? []).entries()) checkStatusSetOps({
|
|
1339
2162
|
ops: action.ops,
|
|
1340
2163
|
activityNames: activityNames,
|
|
@@ -1382,25 +2205,11 @@ function checkStageReachability({def: def, stageNames: stageNames, issues: issue
|
|
|
1382
2205
|
|
|
1383
2206
|
function effectNameSites(def) {
|
|
1384
2207
|
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
|
-
}
|
|
2208
|
+
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({
|
|
2209
|
+
effects: action.effects,
|
|
2210
|
+
path: [ "stages", i, "activities", j, "actions", a, "effects" ],
|
|
2211
|
+
sites: sites
|
|
2212
|
+
});
|
|
1404
2213
|
return sites;
|
|
1405
2214
|
}
|
|
1406
2215
|
|
|
@@ -1429,6 +2238,10 @@ function checkGuardNames(def, issues) {
|
|
|
1429
2238
|
what: "guard name (the guard lake _id derives from it — unique per definition)",
|
|
1430
2239
|
issues: issues
|
|
1431
2240
|
});
|
|
2241
|
+
for (const {name: name, path: path} of sites) LAKE_ID_SEGMENT_RE.test(name) || issues.push({
|
|
2242
|
+
path: path,
|
|
2243
|
+
message: `guard name "${name}" does not match the guard-name grammar ${LAKE_ID_SEGMENT_RE.source} (${LAKE_ID_SEGMENT_GLOSS}) — the guard's lake _id derives from it at stage entry, so an invalid name would wedge the entering commit with an opaque lake error instead of failing here`
|
|
2244
|
+
});
|
|
1432
2245
|
}
|
|
1433
2246
|
|
|
1434
2247
|
function fieldScopes(def) {
|
|
@@ -1487,19 +2300,23 @@ function checkPredicates(def, issues) {
|
|
|
1487
2300
|
path: [ "predicates", name ],
|
|
1488
2301
|
message: `predicate "${name}" shadows the built-in $${name} — pick another name`
|
|
1489
2302
|
});
|
|
1490
|
-
for (const [name,
|
|
2303
|
+
for (const [name, groq2] of Object.entries(def.predicates ?? {})) checkPredicateBody({
|
|
1491
2304
|
name: name,
|
|
1492
|
-
groq:
|
|
2305
|
+
groq: groq2,
|
|
1493
2306
|
names: names,
|
|
1494
2307
|
issues: issues
|
|
1495
2308
|
});
|
|
1496
2309
|
}
|
|
1497
2310
|
|
|
1498
|
-
function checkPredicateBody({name: name, groq:
|
|
1499
|
-
const reads = conditionParameterNames(
|
|
2311
|
+
function checkPredicateBody({name: name, groq: groq2, names: names, issues: issues}) {
|
|
2312
|
+
const reads = conditionParameterNames(groq2);
|
|
1500
2313
|
for (const caller of CALLER_BOUND_VARS) reads.has(caller) && issues.push({
|
|
1501
2314
|
path: [ "predicates", name ],
|
|
1502
|
-
message: `predicate "${name}" reads $${caller} — predicates
|
|
2315
|
+
message: `predicate "${name}" reads $${caller} — predicates pre-evaluate caller-free; compose caller gates at the condition site instead`
|
|
2316
|
+
});
|
|
2317
|
+
reads.has("row") && issues.push({
|
|
2318
|
+
path: [ "predicates", name ],
|
|
2319
|
+
message: `predicate "${name}" reads $row — ${ROW_BINDING_CLAUSE}; predicates pre-evaluate once per instance, so the read is GROQ null and the predicate can never hold`
|
|
1503
2320
|
});
|
|
1504
2321
|
for (const other of names) other === name || !reads.has(other) || issues.push({
|
|
1505
2322
|
path: [ "predicates", name ],
|
|
@@ -1507,32 +2324,43 @@ function checkPredicateBody({name: name, groq: groq, names: names, issues: issue
|
|
|
1507
2324
|
});
|
|
1508
2325
|
}
|
|
1509
2326
|
|
|
1510
|
-
function
|
|
1511
|
-
return new
|
|
2327
|
+
function entryMap(entries) {
|
|
2328
|
+
return new Map((entries ?? []).map(entry => [ entry.name, entry ]));
|
|
1512
2329
|
}
|
|
1513
2330
|
|
|
1514
|
-
function
|
|
2331
|
+
function checkUnboundConditionVars(def, issues) {
|
|
1515
2332
|
for (const site of conditionSites(def)) {
|
|
1516
2333
|
const reads = conditionParameterNames(site.groq);
|
|
1517
|
-
for (const name of
|
|
2334
|
+
for (const name of unboundVarsAt(site)) reads.has(name) && issues.push({
|
|
1518
2335
|
path: site.path,
|
|
1519
|
-
message: callerVarMessage(site, name)
|
|
2336
|
+
message: name === "row" ? rowVarMessage(site) : callerVarMessage(site, name)
|
|
1520
2337
|
});
|
|
1521
2338
|
}
|
|
1522
2339
|
}
|
|
1523
2340
|
|
|
2341
|
+
function unboundVarsAt(site) {
|
|
2342
|
+
const callerVars = unboundCallerVars(site.policy);
|
|
2343
|
+
return site.bindsRow === !0 ? callerVars : [ ...callerVars, "row" ];
|
|
2344
|
+
}
|
|
2345
|
+
|
|
1524
2346
|
function unboundCallerVars(policy) {
|
|
1525
|
-
return policy === "cascade" ? CALLER_BOUND_VARS : policy === "caller-bound" ? [ "params" ] : [ "can" ];
|
|
2347
|
+
return policy === "cascade" ? CALLER_BOUND_VARS : policy === "caller-bound" ? [ "params" ] : policy === "triggered-payload" ? [ "can", "params" ] : [ "can" ];
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
const ROW_BINDING_CLAUSE = "$row (the discovered row in a spawn projection; the stored row under test in a where-op) is bound only while a spawn `with` projection or a where-op `where` evaluates";
|
|
2351
|
+
|
|
2352
|
+
function rowVarMessage(site) {
|
|
2353
|
+
return `${site.label} reads $row — ${ROW_BINDING_CLAUSE}; this site never binds it, so the read is GROQ null and the condition can never resolve. Move the per-row read into \`spawn.with\` or a where-op, or gate on instance state instead`;
|
|
1526
2354
|
}
|
|
1527
2355
|
|
|
1528
2356
|
function callerVarMessage(site, name) {
|
|
1529
|
-
return site.policy === "cascade" ? `${site.label} reads $${name} —
|
|
2357
|
+
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
2358
|
}
|
|
1531
2359
|
|
|
1532
2360
|
function conditionSites(def) {
|
|
1533
2361
|
const sites = [], workflowLayer = {
|
|
1534
2362
|
scope: "workflow",
|
|
1535
|
-
|
|
2363
|
+
entries: entryMap(def.fields)
|
|
1536
2364
|
};
|
|
1537
2365
|
collectEditableSites({
|
|
1538
2366
|
entries: def.fields,
|
|
@@ -1553,35 +2381,24 @@ function conditionSites(def) {
|
|
|
1553
2381
|
function collectStageConditionSites({stage: stage, i: i, workflowLayer: workflowLayer, sites: sites}) {
|
|
1554
2382
|
const stageLayer = {
|
|
1555
2383
|
scope: "stage",
|
|
1556
|
-
|
|
2384
|
+
entries: entryMap(stage.fields)
|
|
1557
2385
|
}, stageFields2 = [ stageLayer, workflowLayer ], editableWindow = [ stageLayer, workflowLayer, ...(stage.activities ?? []).map(activity => ({
|
|
1558
2386
|
scope: "activity",
|
|
1559
|
-
|
|
2387
|
+
entries: entryMap(activity.fields)
|
|
1560
2388
|
})) ];
|
|
1561
2389
|
for (const [k, t] of (stage.transitions ?? []).entries()) sites.push({
|
|
1562
|
-
groq: t.
|
|
1563
|
-
path: [ "stages", i, "transitions", k, "
|
|
1564
|
-
label: `transition "${t.name}"
|
|
2390
|
+
groq: t.when,
|
|
2391
|
+
path: [ "stages", i, "transitions", k, "when" ],
|
|
2392
|
+
label: `transition "${t.name}" when`,
|
|
1565
2393
|
policy: "cascade",
|
|
1566
2394
|
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
2395
|
});
|
|
1580
2396
|
collectEditableSites({
|
|
1581
2397
|
entries: stage.fields,
|
|
1582
2398
|
path: [ "stages", i, "fields" ],
|
|
1583
2399
|
labelPrefix: `stage "${stage.name}"`,
|
|
1584
2400
|
fields: editableWindow,
|
|
2401
|
+
unionWindow: !0,
|
|
1585
2402
|
sites: sites
|
|
1586
2403
|
});
|
|
1587
2404
|
for (const [name, editable] of Object.entries(stage.editable ?? {})) typeof editable == "string" && sites.push({
|
|
@@ -1589,70 +2406,64 @@ function collectStageConditionSites({stage: stage, i: i, workflowLayer: workflow
|
|
|
1589
2406
|
path: [ "stages", i, "editable", name ],
|
|
1590
2407
|
label: `stage "${stage.name}" editable override "${name}"`,
|
|
1591
2408
|
policy: "caller-bound",
|
|
1592
|
-
fields: editableWindow
|
|
2409
|
+
fields: editableWindow,
|
|
2410
|
+
unionWindow: !0
|
|
1593
2411
|
});
|
|
1594
2412
|
for (const [j, activity] of (stage.activities ?? []).entries()) collectActivityConditionSites({
|
|
1595
2413
|
activity: activity,
|
|
1596
2414
|
path: [ "stages", i, "activities", j ],
|
|
1597
2415
|
stageFields: stageFields2,
|
|
2416
|
+
workflowLayer: workflowLayer,
|
|
1598
2417
|
sites: sites
|
|
1599
2418
|
});
|
|
1600
2419
|
}
|
|
1601
2420
|
|
|
1602
|
-
function collectEditableSites({entries: entries, path: path, labelPrefix: labelPrefix, fields: fields, sites: sites}) {
|
|
2421
|
+
function collectEditableSites({entries: entries, path: path, labelPrefix: labelPrefix, fields: fields, unionWindow: unionWindow, sites: sites}) {
|
|
1603
2422
|
for (const [n, entry] of (entries ?? []).entries()) typeof entry.editable == "string" && sites.push({
|
|
1604
2423
|
groq: entry.editable,
|
|
1605
2424
|
path: [ ...path, n, "editable" ],
|
|
1606
2425
|
label: `${labelPrefix} field "${entry.name}" editable`,
|
|
1607
2426
|
policy: "caller-bound",
|
|
1608
|
-
fields: fields
|
|
2427
|
+
fields: fields,
|
|
2428
|
+
...unionWindow === !0 ? {
|
|
2429
|
+
unionWindow: !0
|
|
2430
|
+
} : {}
|
|
1609
2431
|
});
|
|
1610
2432
|
}
|
|
1611
2433
|
|
|
1612
|
-
function collectOpWhereSites({ops: ops, path: path, label: label, fields: fields, sites: sites}) {
|
|
2434
|
+
function collectOpWhereSites({ops: ops, path: path, label: label, policy: policy, fields: fields, sites: sites}) {
|
|
1613
2435
|
for (const [o, op] of (ops ?? []).entries()) op.where !== void 0 && sites.push({
|
|
1614
2436
|
groq: op.where,
|
|
1615
2437
|
path: [ ...path, o, "where" ],
|
|
1616
2438
|
label: `${label} ${op.type} where`,
|
|
2439
|
+
...policy !== void 0 ? {
|
|
2440
|
+
policy: policy
|
|
2441
|
+
} : {},
|
|
2442
|
+
bindsRow: !0,
|
|
1617
2443
|
fields: fields
|
|
1618
2444
|
});
|
|
1619
2445
|
}
|
|
1620
2446
|
|
|
1621
|
-
function collectActivityConditionSites({activity: activity, path: path, stageFields: stageFields2, sites: sites}) {
|
|
2447
|
+
function collectActivityConditionSites({activity: activity, path: path, stageFields: stageFields2, workflowLayer: workflowLayer, sites: sites}) {
|
|
1622
2448
|
const fields = [ {
|
|
1623
2449
|
scope: "activity",
|
|
1624
|
-
|
|
2450
|
+
entries: entryMap(activity.fields)
|
|
1625
2451
|
}, ...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,
|
|
2452
|
+
activity.filter !== void 0 && sites.push({
|
|
2453
|
+
groq: activity.filter,
|
|
2454
|
+
path: [ ...path, "filter" ],
|
|
2455
|
+
label: `activity "${activity.name}".filter`,
|
|
2456
|
+
policy: "cascade",
|
|
2457
|
+
fields: fields
|
|
2458
|
+
});
|
|
2459
|
+
for (const [name, groq2] of Object.entries(activity.requirements ?? {})) sites.push({
|
|
2460
|
+
groq: groq2,
|
|
1638
2461
|
path: [ ...path, "requirements", name ],
|
|
1639
2462
|
label: `activity "${activity.name}" requirement "${name}"`,
|
|
1640
2463
|
policy: "caller-bound",
|
|
1641
2464
|
fields: fields
|
|
1642
2465
|
});
|
|
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({
|
|
2466
|
+
collectEditableSites({
|
|
1656
2467
|
entries: activity.fields,
|
|
1657
2468
|
path: [ ...path, "fields" ],
|
|
1658
2469
|
labelPrefix: `activity "${activity.name}"`,
|
|
@@ -1662,60 +2473,84 @@ function collectActivityConditionSites({activity: activity, path: path, stageFie
|
|
|
1662
2473
|
activity: activity,
|
|
1663
2474
|
path: path,
|
|
1664
2475
|
fields: fields,
|
|
1665
|
-
|
|
1666
|
-
}), collectSubworkflowSites({
|
|
1667
|
-
activity: activity,
|
|
1668
|
-
path: path,
|
|
1669
|
-
fields: fields,
|
|
2476
|
+
workflowLayer: workflowLayer,
|
|
1670
2477
|
sites: sites
|
|
1671
2478
|
});
|
|
1672
2479
|
}
|
|
1673
2480
|
|
|
1674
|
-
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
|
-
|
|
2481
|
+
function collectActionConditionSites({activity: activity, path: path, fields: fields, workflowLayer: workflowLayer, sites: sites}) {
|
|
2482
|
+
for (const [a, action] of (activity.actions ?? []).entries()) {
|
|
2483
|
+
const actionPath = [ ...path, "actions", a ], cascadeFired = action.when !== void 0;
|
|
2484
|
+
action.when !== void 0 && sites.push({
|
|
2485
|
+
groq: action.when,
|
|
2486
|
+
path: [ ...actionPath, "when" ],
|
|
2487
|
+
label: `action "${action.name}" when`,
|
|
2488
|
+
policy: "cascade",
|
|
2489
|
+
fields: fields
|
|
2490
|
+
}), action.filter !== void 0 && sites.push({
|
|
2491
|
+
groq: action.filter,
|
|
2492
|
+
path: [ ...actionPath, "filter" ],
|
|
2493
|
+
label: `action "${action.name}" filter`,
|
|
2494
|
+
policy: cascadeFired ? "cascade" : "caller-bound",
|
|
2495
|
+
fields: fields
|
|
2496
|
+
});
|
|
2497
|
+
const payloadPolicy = cascadeFired ? "triggered-payload" : void 0;
|
|
2498
|
+
collectBindingSites({
|
|
2499
|
+
effects: action.effects,
|
|
2500
|
+
path: [ ...actionPath, "effects" ],
|
|
2501
|
+
label: `action "${action.name}"`,
|
|
2502
|
+
policy: payloadPolicy,
|
|
2503
|
+
fields: fields,
|
|
2504
|
+
sites: sites
|
|
2505
|
+
}), collectOpWhereSites({
|
|
2506
|
+
ops: action.ops,
|
|
2507
|
+
path: [ ...actionPath, "ops" ],
|
|
2508
|
+
label: `action "${action.name}"`,
|
|
2509
|
+
policy: payloadPolicy,
|
|
2510
|
+
fields: fields,
|
|
2511
|
+
sites: sites
|
|
2512
|
+
}), collectSpawnSites({
|
|
2513
|
+
action: action,
|
|
2514
|
+
path: actionPath,
|
|
2515
|
+
fields: fields,
|
|
2516
|
+
workflowLayer: workflowLayer,
|
|
2517
|
+
sites: sites
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
1694
2520
|
}
|
|
1695
2521
|
|
|
1696
|
-
function
|
|
1697
|
-
const
|
|
1698
|
-
if (
|
|
1699
|
-
const
|
|
2522
|
+
function collectSpawnSites({action: action, path: path, fields: fields, workflowLayer: workflowLayer, sites: sites}) {
|
|
2523
|
+
const spawn = action.spawn;
|
|
2524
|
+
if (spawn === void 0) return;
|
|
2525
|
+
const spawnPath = [ ...path, "spawn" ];
|
|
1700
2526
|
sites.push({
|
|
1701
|
-
groq:
|
|
1702
|
-
path: [ ...
|
|
1703
|
-
label: `
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
2527
|
+
groq: spawn.forEach,
|
|
2528
|
+
path: [ ...spawnPath, "forEach" ],
|
|
2529
|
+
label: `action "${action.name}".spawn.forEach`,
|
|
2530
|
+
policy: "cascade",
|
|
2531
|
+
fields: [ workflowLayer ],
|
|
2532
|
+
windowNote: "spawn.forEach evaluates against workflow-scope $fields only — stage/activity fields are never bound at discovery time"
|
|
2533
|
+
});
|
|
2534
|
+
for (const [group, record, bindsRow] of [ [ "with", spawn.with, !0 ], [ "context", spawn.context, !1 ] ]) for (const [key, groq2] of Object.entries(record ?? {})) sites.push({
|
|
2535
|
+
groq: groq2,
|
|
2536
|
+
path: [ ...spawnPath, group, key ],
|
|
2537
|
+
label: `action "${action.name}".spawn.${group} "${key}"`,
|
|
2538
|
+
policy: "triggered-payload",
|
|
2539
|
+
...bindsRow ? {
|
|
2540
|
+
bindsRow: !0
|
|
2541
|
+
} : {},
|
|
1710
2542
|
fields: fields
|
|
1711
2543
|
});
|
|
1712
2544
|
}
|
|
1713
2545
|
|
|
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:
|
|
2546
|
+
function collectBindingSites({effects: effects, path: path, label: label, policy: policy, fields: fields, sites: sites}) {
|
|
2547
|
+
for (const [e, effect] of (effects ?? []).entries()) for (const [key, groq2] of Object.entries(effect.bindings ?? {})) sites.push({
|
|
2548
|
+
groq: groq2,
|
|
1717
2549
|
path: [ ...path, e, "bindings", key ],
|
|
1718
2550
|
label: `${label} effect "${effect.name}" binding "${key}"`,
|
|
2551
|
+
...policy !== void 0 ? {
|
|
2552
|
+
policy: policy
|
|
2553
|
+
} : {},
|
|
1719
2554
|
fields: fields
|
|
1720
2555
|
});
|
|
1721
2556
|
}
|
|
@@ -1727,34 +2562,102 @@ function checkAssigneesEntries(def, issues) {
|
|
|
1727
2562
|
});
|
|
1728
2563
|
}
|
|
1729
2564
|
|
|
1730
|
-
function
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
2565
|
+
function checkActivityTerminalPaths(def, issues) {
|
|
2566
|
+
for (const [i, stage] of def.stages.entries()) {
|
|
2567
|
+
const resolvable = terminallyResolvableActivities(stage);
|
|
2568
|
+
for (const [j, activity] of (stage.activities ?? []).entries()) resolvable.has(activity.name) || issues.push({
|
|
2569
|
+
path: [ "stages", i, "activities", j ],
|
|
2570
|
+
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`
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
1737
2573
|
}
|
|
1738
2574
|
|
|
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
|
-
};
|
|
2575
|
+
function terminallyResolvableActivities(stage) {
|
|
2576
|
+
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));
|
|
2577
|
+
return new Set(terminalSets.map(op => op.activity));
|
|
2578
|
+
}
|
|
1746
2579
|
|
|
1747
|
-
function
|
|
1748
|
-
for (const [i, stage] of def.stages.entries())
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
2580
|
+
function checkTerminalStageActivities(def, issues) {
|
|
2581
|
+
for (const [i, stage] of def.stages.entries()) (stage.transitions ?? []).length > 0 || (stage.activities ?? []).length === 0 || issues.push({
|
|
2582
|
+
path: [ "stages", i, "activities" ],
|
|
2583
|
+
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`
|
|
2584
|
+
});
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
function storedRolesIssue(action) {
|
|
2588
|
+
if (action.roles !== void 0) {
|
|
2589
|
+
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`;
|
|
2590
|
+
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`;
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
function checkStoredRolesPlacement(def, issues) {
|
|
2595
|
+
for (const [i, stage] of def.stages.entries()) for (const [j, activity] of (stage.activities ?? []).entries()) for (const [a, action] of (activity.actions ?? []).entries()) {
|
|
2596
|
+
const message = storedRolesIssue(action);
|
|
2597
|
+
message !== void 0 && issues.push({
|
|
2598
|
+
path: [ "stages", i, "activities", j, "actions", a, "roles" ],
|
|
2599
|
+
message: message
|
|
2600
|
+
});
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2604
|
+
function checkTriggeredActionParams(def, issues) {
|
|
2605
|
+
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({
|
|
2606
|
+
path: [ "stages", i, "activities", j, "actions", a, "params" ],
|
|
2607
|
+
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`
|
|
2608
|
+
});
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
function checkStart(def, issues) {
|
|
2612
|
+
if (def.start !== void 0 && (def.lifecycle === "child" && issues.push({
|
|
2613
|
+
path: [ "start" ],
|
|
2614
|
+
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'`"
|
|
2615
|
+
}), checkStartFilterReads(def, issues), checkStartAllowedReads(def, issues), def.start.kind === "autonomous")) for (const [n, entry] of (def.fields ?? []).entries()) entry.required !== !0 || isSubjectEntry(entry) || issues.push({
|
|
2616
|
+
path: [ "fields", n, "required" ],
|
|
2617
|
+
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`
|
|
2618
|
+
});
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
function checkStartFilterReads(def, issues) {
|
|
2622
|
+
const filter = def.start?.filter;
|
|
2623
|
+
filter !== void 0 && (conditionParameterNames(filter).has("fields") && issues.push({
|
|
2624
|
+
path: [ "start", "filter" ],
|
|
2625
|
+
message: "start.filter reads $fields, but the filter is browse-time-pure — a start surface evaluates it per document, before any inputs exist, so $fields cannot be bound. Move the input-dependent rule to start.allowed, the start-time permission predicate that binds $fields and is enforced by startInstance"
|
|
2626
|
+
}), readsRootDocument(filter) && !(def.fields ?? []).some(isSubjectEntry) && issues.push({
|
|
2627
|
+
path: [ "start", "filter" ],
|
|
2628
|
+
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 every root read is GROQ null and the filter silently misevaluates. Declare a required subject entry, or gate on the dataset instead"
|
|
2629
|
+
}));
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
function checkStartAllowedReads(def, issues) {
|
|
2633
|
+
const allowed = def.start?.allowed;
|
|
2634
|
+
if (allowed === void 0) return;
|
|
2635
|
+
const bindable = new Map((def.fields ?? []).filter(isInputSourced).map(entry => [ entry.name, entry ])), declared = new Set((def.fields ?? []).map(entry => entry.name)), nullVerdict = `so the read is GROQ null and null semantics decide the verdict (refuse-all or vacuously-allow, by the expression's shape), never the caller's real input. Bindable (input) fields: ${knownList(bindable.keys())}`;
|
|
2636
|
+
for (const read of conditionFieldReads(allowed)) {
|
|
2637
|
+
const entry = bindable.get(read.name);
|
|
2638
|
+
if (entry === void 0) {
|
|
2639
|
+
issues.push({
|
|
2640
|
+
path: [ "start", "allowed" ],
|
|
2641
|
+
message: declared.has(read.name) ? `start.allowed reads $fields.${read.name}, but "${read.name}" is not an \`input\`-sourced entry — $fields binds only the caller's input entries (query/literal/fieldRead entries resolve at materialisation, after the start gate), ${nullVerdict}` : `start.allowed reads $fields.${read.name}, but no workflow-scope field entry named "${read.name}" is declared — $fields binds only the caller's input entries, ${nullVerdict}`
|
|
2642
|
+
});
|
|
2643
|
+
continue;
|
|
2644
|
+
}
|
|
2645
|
+
pushFieldReadPathIssue({
|
|
2646
|
+
where: "start.allowed",
|
|
2647
|
+
read: {
|
|
2648
|
+
field: read.name,
|
|
2649
|
+
path: read.path
|
|
2650
|
+
},
|
|
2651
|
+
target: entry,
|
|
2652
|
+
path: [ "start", "allowed" ],
|
|
2653
|
+
issues: issues,
|
|
2654
|
+
nodes: START_ALLOWED_VALUE_NODES
|
|
1756
2655
|
});
|
|
1757
2656
|
}
|
|
2657
|
+
readsRootDocument(allowed) && issues.push({
|
|
2658
|
+
path: [ "start", "allowed" ],
|
|
2659
|
+
message: "start.allowed reads the candidate document as its root, but no root ever binds in the start-allowed context — startInstance holds inputs, not a loaded document, so the read is GROQ null and null semantics decide the verdict, never the workflow's real state. Read the subject as $fields.<entry> (its GDR URI is $fields.<entry>.id); a per-document visibility rule belongs in start.filter"
|
|
2660
|
+
});
|
|
1758
2661
|
}
|
|
1759
2662
|
|
|
1760
2663
|
function checkGroups(def, issues) {
|
|
@@ -1864,39 +2767,80 @@ function checkGroupMembership({group: group, reachable: reachable, path: path, l
|
|
|
1864
2767
|
}
|
|
1865
2768
|
|
|
1866
2769
|
function checkConditionFieldReads(def, issues) {
|
|
1867
|
-
const everywhere =
|
|
1868
|
-
for (const site of conditionSites(def))
|
|
2770
|
+
const everywhere = allDeclaredFieldEntries(def);
|
|
2771
|
+
for (const site of conditionSites(def)) checkSiteFieldReads({
|
|
1869
2772
|
site: site,
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
}) || issues.push({
|
|
1873
|
-
path: site.path,
|
|
1874
|
-
message: undeclaredFieldReadMessage(site, name)
|
|
2773
|
+
everywhere: everywhere,
|
|
2774
|
+
issues: issues
|
|
1875
2775
|
});
|
|
1876
|
-
for (const [name,
|
|
1877
|
-
|
|
1878
|
-
|
|
2776
|
+
for (const [name, groq2] of Object.entries(def.predicates ?? {})) checkSiteFieldReads({
|
|
2777
|
+
site: {
|
|
2778
|
+
groq: groq2,
|
|
2779
|
+
path: [ "predicates", name ],
|
|
2780
|
+
label: `predicate "${name}"`,
|
|
2781
|
+
fields: "context-dependent"
|
|
2782
|
+
},
|
|
2783
|
+
everywhere: everywhere,
|
|
2784
|
+
issues: issues
|
|
1879
2785
|
});
|
|
1880
2786
|
}
|
|
1881
2787
|
|
|
1882
|
-
function
|
|
1883
|
-
const
|
|
1884
|
-
|
|
1885
|
-
|
|
2788
|
+
function checkSiteFieldReads({site: site, everywhere: everywhere, issues: issues}) {
|
|
2789
|
+
for (const read of conditionFieldReads(site.groq)) {
|
|
2790
|
+
const targets = readTargets({
|
|
2791
|
+
site: site,
|
|
2792
|
+
read: read,
|
|
2793
|
+
everywhere: everywhere
|
|
2794
|
+
});
|
|
2795
|
+
if (targets === "undeclared") {
|
|
2796
|
+
issues.push({
|
|
2797
|
+
path: site.path,
|
|
2798
|
+
message: undeclaredFieldReadMessage(site, read.name)
|
|
2799
|
+
});
|
|
2800
|
+
continue;
|
|
2801
|
+
}
|
|
2802
|
+
const path = read.path;
|
|
2803
|
+
path !== void 0 && (targets.some(target => fieldValuePathIssue({
|
|
2804
|
+
target: target,
|
|
2805
|
+
path: path
|
|
2806
|
+
}) === void 0) || pushFieldReadPathIssue({
|
|
2807
|
+
where: site.label,
|
|
2808
|
+
read: {
|
|
2809
|
+
field: read.name,
|
|
2810
|
+
path: path
|
|
2811
|
+
},
|
|
2812
|
+
target: targets[0],
|
|
2813
|
+
path: site.path,
|
|
2814
|
+
issues: issues
|
|
2815
|
+
}));
|
|
2816
|
+
}
|
|
1886
2817
|
}
|
|
1887
2818
|
|
|
1888
|
-
function
|
|
1889
|
-
|
|
2819
|
+
function readTargets({site: site, read: read, everywhere: everywhere}) {
|
|
2820
|
+
if (site.fields === "context-dependent") return everywhere.get(read.name) ?? "undeclared";
|
|
2821
|
+
const layers = site.fields.filter(layer => layer.entries.has(read.name));
|
|
2822
|
+
if (layers.length === 0) return "undeclared";
|
|
2823
|
+
const entries = layers.map(layer => layer.entries.get(read.name));
|
|
2824
|
+
return site.unionWindow === !0 ? entries : [ entries[0] ];
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
function allDeclaredFieldEntries(def) {
|
|
2828
|
+
const declared = /* @__PURE__ */ new Map;
|
|
2829
|
+
for (const {entries: entries} of fieldScopes(def)) for (const entry of entries ?? []) {
|
|
2830
|
+
const list = declared.get(entry.name);
|
|
2831
|
+
list === void 0 ? declared.set(entry.name, [ entry ]) : list.push(entry);
|
|
2832
|
+
}
|
|
2833
|
+
return declared;
|
|
1890
2834
|
}
|
|
1891
2835
|
|
|
1892
2836
|
function noFieldAnywhereMessage(label, name) {
|
|
1893
|
-
return `${label} reads $fields.${name}, but no field entry named "${name}" is declared anywhere in the definition — the read evaluates to GROQ null, so the condition silently
|
|
2837
|
+
return `${label} reads $fields.${name}, but no field entry named "${name}" is declared anywhere in the definition — the read evaluates to GROQ null, so the condition silently misevaluates`;
|
|
1894
2838
|
}
|
|
1895
2839
|
|
|
1896
2840
|
function undeclaredFieldReadMessage(site, name) {
|
|
1897
2841
|
if (site.fields === "context-dependent") return noFieldAnywhereMessage(site.label, name);
|
|
1898
|
-
const visible = site.fields.flatMap(layer => [ ...layer.
|
|
1899
|
-
return `${site.label} reads $fields.${name}, but
|
|
2842
|
+
const visible = site.fields.flatMap(layer => [ ...layer.entries.keys() ].map(n => `${layer.scope}:${n}`)), window = site.windowNote === void 0 ? `no field entry named "${name}" is lexically visible here` : `"${name}" is not in this site's window (${site.windowNote})`;
|
|
2843
|
+
return `${site.label} reads $fields.${name}, but ${window} — the read evaluates to GROQ null, so the condition silently misevaluates. Visible: ${visible.join(", ") || "(none)"}`;
|
|
1900
2844
|
}
|
|
1901
2845
|
|
|
1902
2846
|
function checkFieldReadSeeds(def, issues) {
|
|
@@ -1977,38 +2921,29 @@ function seedEarlierSiblingTarget(args) {
|
|
|
1977
2921
|
});
|
|
1978
2922
|
}
|
|
1979
2923
|
|
|
2924
|
+
function opSites(def) {
|
|
2925
|
+
const sites = [];
|
|
2926
|
+
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({
|
|
2927
|
+
ops: action.ops,
|
|
2928
|
+
path: [ "stages", i, "activities", j, "actions", a, "ops" ],
|
|
2929
|
+
label: `action "${action.name}"`,
|
|
2930
|
+
stage: stage,
|
|
2931
|
+
activity: activity
|
|
2932
|
+
});
|
|
2933
|
+
return sites;
|
|
2934
|
+
}
|
|
2935
|
+
|
|
1980
2936
|
function checkFieldReadOpValues(def, issues) {
|
|
1981
2937
|
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
|
-
}
|
|
2938
|
+
for (const site of opSites(def)) checkOpsFieldReads({
|
|
2939
|
+
workflowEntries: workflowEntries,
|
|
2940
|
+
stageEntries: site.stage.fields ?? [],
|
|
2941
|
+
issues: issues,
|
|
2942
|
+
ops: site.ops,
|
|
2943
|
+
path: site.path,
|
|
2944
|
+
label: site.label,
|
|
2945
|
+
activityEntries: entryMap(site.activity.fields)
|
|
2946
|
+
});
|
|
2012
2947
|
}
|
|
2013
2948
|
|
|
2014
2949
|
function checkOpsFieldReads({ops: ops, path: path, label: label, ...ctx}) {
|
|
@@ -2027,7 +2962,7 @@ function fieldReadsIn(value, path) {
|
|
|
2027
2962
|
} ] : value.type !== "object" ? [] : Object.entries(value.fields).flatMap(([key, sub]) => fieldReadsIn(sub, [ ...path, "fields", key ]));
|
|
2028
2963
|
}
|
|
2029
2964
|
|
|
2030
|
-
function checkOpFieldRead({read: read, where: where, path: path, workflowEntries: workflowEntries, stageEntries: stageEntries,
|
|
2965
|
+
function checkOpFieldRead({read: read, where: where, path: path, workflowEntries: workflowEntries, stageEntries: stageEntries, activityEntries: activityEntries, issues: issues}) {
|
|
2031
2966
|
const hosts = [ {
|
|
2032
2967
|
scope: "stage",
|
|
2033
2968
|
entries: stageEntries
|
|
@@ -2042,7 +2977,7 @@ function checkOpFieldRead({read: read, where: where, path: path, workflowEntries
|
|
|
2042
2977
|
read: read,
|
|
2043
2978
|
where: where,
|
|
2044
2979
|
hosts: hosts,
|
|
2045
|
-
|
|
2980
|
+
activityEntries: activityEntries
|
|
2046
2981
|
})
|
|
2047
2982
|
});
|
|
2048
2983
|
return;
|
|
@@ -2056,11 +2991,54 @@ function checkOpFieldRead({read: read, where: where, path: path, workflowEntries
|
|
|
2056
2991
|
});
|
|
2057
2992
|
}
|
|
2058
2993
|
|
|
2059
|
-
function opFieldReadMissMessage({read: read, where: where, hosts: hosts,
|
|
2060
|
-
const searched = read.scope === void 0 ? "stage or workflow scope" : `${read.scope} scope`, activityHint =
|
|
2994
|
+
function opFieldReadMissMessage({read: read, where: where, hosts: hosts, activityEntries: activityEntries}) {
|
|
2995
|
+
const searched = read.scope === void 0 ? "stage or workflow scope" : `${read.scope} scope`, activityHint = activityEntries?.has(read.field) ? ` "${read.field}" is an activity-scope field, and op-time field reads resolve against stage and workflow scopes only — declare it at stage scope to read it from an op.` : "", known = hosts.flatMap(host => host.entries.map(entry => `${host.scope}:${entry.name}`));
|
|
2061
2996
|
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
2997
|
}
|
|
2063
2998
|
|
|
2999
|
+
function checkUpdateWhereOps(def, issues) {
|
|
3000
|
+
const workflow = def.fields ?? [];
|
|
3001
|
+
for (const site of opSites(def)) for (const [o, op] of (site.ops ?? []).entries()) {
|
|
3002
|
+
if (op.type !== "field.updateWhere") continue;
|
|
3003
|
+
const scopes = {
|
|
3004
|
+
workflow: workflow,
|
|
3005
|
+
stage: site.stage.fields ?? [],
|
|
3006
|
+
activity: site.activity.fields ?? []
|
|
3007
|
+
};
|
|
3008
|
+
checkUpdateWhereTargetKind({
|
|
3009
|
+
op: op,
|
|
3010
|
+
path: [ ...site.path, o ],
|
|
3011
|
+
label: site.label,
|
|
3012
|
+
scopes: scopes,
|
|
3013
|
+
issues: issues
|
|
3014
|
+
}), checkUpdateWhereMergeKeys({
|
|
3015
|
+
op: op,
|
|
3016
|
+
path: [ ...site.path, o ],
|
|
3017
|
+
label: site.label,
|
|
3018
|
+
issues: issues
|
|
3019
|
+
});
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
|
|
3023
|
+
function checkUpdateWhereTargetKind({op: op, path: path, label: label, scopes: scopes, issues: issues}) {
|
|
3024
|
+
const target = scopes[op.target.scope]?.find(entry => entry.name === op.target.field);
|
|
3025
|
+
target === void 0 || target.type === "array" || issues.push({
|
|
3026
|
+
path: [ ...path, "target" ],
|
|
3027
|
+
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)}`
|
|
3028
|
+
});
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
function rowOpsHint(type) {
|
|
3032
|
+
return type === "doc.refs" || type === "assignees" ? `; to add or drop ${type} rows use field.append / field.removeWhere` : "";
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
function checkUpdateWhereMergeKeys({op: op, path: path, label: label, issues: issues}) {
|
|
3036
|
+
if (op.value.type === "object") for (const key of Object.keys(op.value.fields)) key !== "_key" && key !== "_type" || issues.push({
|
|
3037
|
+
path: [ ...path, "value", "fields", key ],
|
|
3038
|
+
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`
|
|
3039
|
+
});
|
|
3040
|
+
}
|
|
3041
|
+
|
|
2064
3042
|
function checkGuardFieldReads(def, issues) {
|
|
2065
3043
|
const reads = {
|
|
2066
3044
|
workflowEntries: def.fields ?? [],
|
|
@@ -2122,11 +3100,14 @@ function checkGuardFieldRead({name: name, valuePath: valuePath, where: where, pa
|
|
|
2122
3100
|
});
|
|
2123
3101
|
}
|
|
2124
3102
|
|
|
2125
|
-
function pushFieldReadPathIssue({where: where, read: read, target: target, path: path, issues: issues}) {
|
|
3103
|
+
function pushFieldReadPathIssue({where: where, read: read, target: target, path: path, issues: issues, nodes: nodes}) {
|
|
2126
3104
|
if (read.path === void 0) return;
|
|
2127
3105
|
const pathIssue = fieldValuePathIssue({
|
|
2128
3106
|
target: target,
|
|
2129
|
-
path: read.path
|
|
3107
|
+
path: read.path,
|
|
3108
|
+
...nodes ? {
|
|
3109
|
+
nodes: nodes
|
|
3110
|
+
} : {}
|
|
2130
3111
|
});
|
|
2131
3112
|
pathIssue !== void 0 && issues.push({
|
|
2132
3113
|
path: path,
|
|
@@ -2197,22 +3178,26 @@ const SCALAR = {
|
|
|
2197
3178
|
kind: "rows",
|
|
2198
3179
|
of: shape.of ?? []
|
|
2199
3180
|
})
|
|
3181
|
+
}, START_ALLOWED_VALUE_NODES = {
|
|
3182
|
+
...VALUE_NODES,
|
|
3183
|
+
"doc.ref": () => GDR_VALUE
|
|
2200
3184
|
};
|
|
2201
3185
|
|
|
2202
|
-
function valueNodeFor(shape) {
|
|
2203
|
-
return Object.hasOwn(
|
|
3186
|
+
function valueNodeFor(shape, nodes) {
|
|
3187
|
+
return Object.hasOwn(nodes, shape.type) ? nodes[shape.type](shape) : void 0;
|
|
2204
3188
|
}
|
|
2205
3189
|
|
|
2206
3190
|
const INDEX_SEGMENT = /^\d+$/;
|
|
2207
3191
|
|
|
2208
|
-
function stepIntoValueNode(
|
|
3192
|
+
function stepIntoValueNode(args) {
|
|
3193
|
+
const {node: node, segment: segment, nodes: nodes} = args;
|
|
2209
3194
|
switch (node.kind) {
|
|
2210
3195
|
case "scalar":
|
|
2211
3196
|
return "the value is a scalar with no sub-paths";
|
|
2212
3197
|
|
|
2213
3198
|
case "object":
|
|
2214
3199
|
{
|
|
2215
|
-
const sub = node.fields.find(f => f.name === segment), next = sub === void 0 ? void 0 : valueNodeFor(sub);
|
|
3200
|
+
const sub = node.fields.find(f => f.name === segment), next = sub === void 0 ? void 0 : valueNodeFor(sub, nodes);
|
|
2216
3201
|
return next !== void 0 ? next : `an object value has sub-fields ${knownList(node.fields.map(f => f.name))} — "${segment}" is not one`;
|
|
2217
3202
|
}
|
|
2218
3203
|
|
|
@@ -2233,18 +3218,24 @@ function stepIntoValueNode(node, segment) {
|
|
|
2233
3218
|
}
|
|
2234
3219
|
}
|
|
2235
3220
|
|
|
2236
|
-
function fieldValuePathIssue({target: target, path: path}) {
|
|
2237
|
-
let node = valueNodeFor(target);
|
|
3221
|
+
function fieldValuePathIssue({target: target, path: path, nodes: nodes = VALUE_NODES}) {
|
|
3222
|
+
let node = valueNodeFor(target, nodes);
|
|
2238
3223
|
if (node === void 0) return `"${String(target.type)}" is not a known field kind`;
|
|
2239
3224
|
for (const segment of path.split(".")) {
|
|
2240
|
-
const next = stepIntoValueNode(
|
|
3225
|
+
const next = stepIntoValueNode({
|
|
3226
|
+
node: node,
|
|
3227
|
+
segment: segment,
|
|
3228
|
+
nodes: nodes
|
|
3229
|
+
});
|
|
2241
3230
|
if (typeof next == "string") return `at segment "${segment}": ${next}`;
|
|
2242
3231
|
node = next;
|
|
2243
3232
|
}
|
|
2244
3233
|
}
|
|
2245
3234
|
|
|
2246
3235
|
function checkWorkflowInvariants(def) {
|
|
2247
|
-
const issues = []
|
|
3236
|
+
const issues = [];
|
|
3237
|
+
checkDefinitionName(def, issues);
|
|
3238
|
+
const stageNames = checkStages(def, issues);
|
|
2248
3239
|
return checkInitialStage({
|
|
2249
3240
|
def: def,
|
|
2250
3241
|
stageNames: stageNames,
|
|
@@ -2258,16 +3249,22 @@ function checkWorkflowInvariants(def) {
|
|
|
2258
3249
|
stageNames: stageNames,
|
|
2259
3250
|
issues: issues
|
|
2260
3251
|
}), checkEffectNames(def, issues), checkGuardNames(def, issues), checkFieldEntryNames(def, issues),
|
|
2261
|
-
checkRequiredField(def, issues),
|
|
2262
|
-
|
|
2263
|
-
|
|
3252
|
+
checkRequiredField(def, issues), checkStart(def, issues), checkPredicates(def, issues),
|
|
3253
|
+
checkUnboundConditionVars(def, issues), checkConditionFieldReads(def, issues), checkFieldReadSeeds(def, issues),
|
|
3254
|
+
checkFieldReadOpValues(def, issues), checkUpdateWhereOps(def, issues), checkGuardFieldReads(def, issues),
|
|
3255
|
+
checkAssigneesEntries(def, issues), checkActivityTerminalPaths(def, issues), checkTerminalStageActivities(def, issues),
|
|
3256
|
+
checkTriggeredActionParams(def, issues), checkStoredRolesPlacement(def, issues),
|
|
2264
3257
|
checkGroups(def, issues), issues;
|
|
2265
3258
|
}
|
|
2266
3259
|
|
|
2267
3260
|
exports.ACTIVITY_KINDS = ACTIVITY_KINDS;
|
|
2268
3261
|
|
|
3262
|
+
exports.ACTIVITY_STATUSES = ACTIVITY_STATUSES;
|
|
3263
|
+
|
|
2269
3264
|
exports.ACTOR_KINDS = ACTOR_KINDS;
|
|
2270
3265
|
|
|
3266
|
+
exports.ActorShape = ActorShape;
|
|
3267
|
+
|
|
2271
3268
|
exports.AuthoringActionSchema = AuthoringActionSchema;
|
|
2272
3269
|
|
|
2273
3270
|
exports.AuthoringActivitySchema = AuthoringActivitySchema;
|
|
@@ -2290,6 +3287,8 @@ exports.CONDITION_VARS = CONDITION_VARS;
|
|
|
2290
3287
|
|
|
2291
3288
|
exports.ContractViolationError = ContractViolationError;
|
|
2292
3289
|
|
|
3290
|
+
exports.DEFAULT_TRANSITION_WHEN = DEFAULT_TRANSITION_WHEN;
|
|
3291
|
+
|
|
2293
3292
|
exports.DOCUMENT_VALUE_PERMISSIONS = DOCUMENT_VALUE_PERMISSIONS;
|
|
2294
3293
|
|
|
2295
3294
|
exports.DRIVER_KINDS = DRIVER_KINDS;
|
|
@@ -2300,12 +3299,18 @@ exports.DefinitionNotFoundError = DefinitionNotFoundError;
|
|
|
2300
3299
|
|
|
2301
3300
|
exports.EFFECTS_READ = EFFECTS_READ;
|
|
2302
3301
|
|
|
3302
|
+
exports.EXECUTOR_CLASSIFICATIONS = EXECUTOR_CLASSIFICATIONS;
|
|
3303
|
+
|
|
2303
3304
|
exports.EffectNotFoundError = EffectNotFoundError;
|
|
2304
3305
|
|
|
2305
3306
|
exports.EffectSchema = EffectSchema;
|
|
2306
3307
|
|
|
2307
3308
|
exports.FIELD_READ = FIELD_READ;
|
|
2308
3309
|
|
|
3310
|
+
exports.FIELD_SCOPES = FIELD_SCOPES;
|
|
3311
|
+
|
|
3312
|
+
exports.FIELD_VALUE_KINDS = FIELD_VALUE_KINDS;
|
|
3313
|
+
|
|
2309
3314
|
exports.FILTER_SCOPE_VARS = FILTER_SCOPE_VARS;
|
|
2310
3315
|
|
|
2311
3316
|
exports.FieldValueShapeError = FieldValueShapeError;
|
|
@@ -2314,14 +3319,28 @@ exports.GROUP_KINDS = GROUP_KINDS;
|
|
|
2314
3319
|
|
|
2315
3320
|
exports.GUARD_PREDICATE_VARS = GUARD_PREDICATE_VARS;
|
|
2316
3321
|
|
|
3322
|
+
exports.GdrShape = GdrShape;
|
|
3323
|
+
|
|
2317
3324
|
exports.GroupSchema = GroupSchema;
|
|
2318
3325
|
|
|
2319
3326
|
exports.InstanceNotFoundError = InstanceNotFoundError;
|
|
2320
3327
|
|
|
3328
|
+
exports.IsoTimestamp = IsoTimestamp;
|
|
3329
|
+
|
|
3330
|
+
exports.MUTATION_GUARD_ACTIONS = MUTATION_GUARD_ACTIONS;
|
|
3331
|
+
|
|
3332
|
+
exports.NonEmptyString = NonEmptyString;
|
|
3333
|
+
|
|
3334
|
+
exports.PersistedDocShapeError = PersistedDocShapeError;
|
|
3335
|
+
|
|
2321
3336
|
exports.RESERVED_CONDITION_VARS = RESERVED_CONDITION_VARS;
|
|
2322
3337
|
|
|
2323
3338
|
exports.RESOURCE_ALIAS_NAME_SOURCE = RESOURCE_ALIAS_NAME_SOURCE;
|
|
2324
3339
|
|
|
3340
|
+
exports.START_ALLOWED_VARS = START_ALLOWED_VARS;
|
|
3341
|
+
|
|
3342
|
+
exports.START_FILTER_VARS = START_FILTER_VARS;
|
|
3343
|
+
|
|
2325
3344
|
exports.StoredFieldOpSchema = StoredFieldOpSchema;
|
|
2326
3345
|
|
|
2327
3346
|
exports.WORKFLOW_DEFINITION_TYPE = WORKFLOW_DEFINITION_TYPE;
|
|
@@ -2342,6 +3361,8 @@ exports.clientConfigFromResource = clientConfigFromResource;
|
|
|
2342
3361
|
|
|
2343
3362
|
exports.conditionEffectReads = conditionEffectReads;
|
|
2344
3363
|
|
|
3364
|
+
exports.conditionFieldReadNames = conditionFieldReadNames;
|
|
3365
|
+
|
|
2345
3366
|
exports.conditionParameterNames = conditionParameterNames;
|
|
2346
3367
|
|
|
2347
3368
|
exports.conditionSyntaxIssues = conditionSyntaxIssues;
|
|
@@ -2350,6 +3371,14 @@ exports.datasetResourceParts = datasetResourceParts;
|
|
|
2350
3371
|
|
|
2351
3372
|
exports.definitionDocId = definitionDocId;
|
|
2352
3373
|
|
|
3374
|
+
exports.deriveActivityKind = deriveActivityKind;
|
|
3375
|
+
|
|
3376
|
+
exports.deriveExecutorClassification = deriveExecutorClassification;
|
|
3377
|
+
|
|
3378
|
+
exports.desugarWorkflow = desugarWorkflow;
|
|
3379
|
+
|
|
3380
|
+
exports.driverKind = driverKind;
|
|
3381
|
+
|
|
2353
3382
|
exports.errorMessage = errorMessage;
|
|
2354
3383
|
|
|
2355
3384
|
exports.evaluateCondition = evaluateCondition;
|
|
@@ -2358,10 +3387,10 @@ exports.evaluateConditionOutcome = evaluateConditionOutcome;
|
|
|
2358
3387
|
|
|
2359
3388
|
exports.evaluatePredicates = evaluatePredicates;
|
|
2360
3389
|
|
|
2361
|
-
exports.expandRequiredRoles = expandRequiredRoles;
|
|
2362
|
-
|
|
2363
3390
|
exports.extractDocumentId = extractDocumentId;
|
|
2364
3391
|
|
|
3392
|
+
exports.fieldValueSchemas = fieldValueSchemas;
|
|
3393
|
+
|
|
2365
3394
|
exports.formatIssuePath = formatIssuePath;
|
|
2366
3395
|
|
|
2367
3396
|
exports.formatIssues = formatIssues;
|
|
@@ -2376,20 +3405,30 @@ exports.gdrResourcePrefix = gdrResourcePrefix;
|
|
|
2376
3405
|
|
|
2377
3406
|
exports.gdrUri = gdrUri;
|
|
2378
3407
|
|
|
3408
|
+
exports.groq = groq;
|
|
3409
|
+
|
|
2379
3410
|
exports.groupMembershipNames = groupMembershipNames;
|
|
2380
3411
|
|
|
2381
3412
|
exports.isBareSeedId = isBareSeedId;
|
|
2382
3413
|
|
|
3414
|
+
exports.isCascadeFired = isCascadeFired;
|
|
3415
|
+
|
|
2383
3416
|
exports.isGdr = isGdr;
|
|
2384
3417
|
|
|
2385
3418
|
exports.isGdrUri = isGdrUri;
|
|
2386
3419
|
|
|
2387
3420
|
exports.isGuardReadExpr = isGuardReadExpr;
|
|
2388
3421
|
|
|
3422
|
+
exports.isInputSourced = isInputSourced;
|
|
3423
|
+
|
|
2389
3424
|
exports.isNotesEntry = isNotesEntry;
|
|
2390
3425
|
|
|
3426
|
+
exports.isParseableInstant = isParseableInstant;
|
|
3427
|
+
|
|
2391
3428
|
exports.isStartableDefinition = isStartableDefinition;
|
|
2392
3429
|
|
|
3430
|
+
exports.isSubjectEntry = isSubjectEntry;
|
|
3431
|
+
|
|
2393
3432
|
exports.isTerminalActivityStatus = isTerminalActivityStatus;
|
|
2394
3433
|
|
|
2395
3434
|
exports.isTodoListEntry = isTodoListEntry;
|
|
@@ -2400,17 +3439,17 @@ exports.isUnevaluable = isUnevaluable;
|
|
|
2400
3439
|
|
|
2401
3440
|
exports.labelFor = labelFor;
|
|
2402
3441
|
|
|
2403
|
-
exports.normalizeRoleAliases = normalizeRoleAliases;
|
|
2404
|
-
|
|
2405
3442
|
exports.parseGdr = parseGdr;
|
|
2406
3443
|
|
|
2407
3444
|
exports.parseOrThrow = parseOrThrow;
|
|
2408
3445
|
|
|
3446
|
+
exports.parsePersistedDoc = parsePersistedDoc;
|
|
3447
|
+
|
|
2409
3448
|
exports.parseResourceGdr = parseResourceGdr;
|
|
2410
3449
|
|
|
2411
3450
|
exports.parseStoredDefinition = parseStoredDefinition;
|
|
2412
3451
|
|
|
2413
|
-
exports.
|
|
3452
|
+
exports.readsRootDocument = readsRootDocument;
|
|
2414
3453
|
|
|
2415
3454
|
exports.refCanvas = refCanvas;
|
|
2416
3455
|
|
|
@@ -2442,14 +3481,22 @@ exports.runGroq = runGroq;
|
|
|
2442
3481
|
|
|
2443
3482
|
exports.sameResource = sameResource;
|
|
2444
3483
|
|
|
3484
|
+
exports.schemaTreeShape = schemaTreeShape;
|
|
3485
|
+
|
|
2445
3486
|
exports.selfGdr = selfGdr;
|
|
2446
3487
|
|
|
3488
|
+
exports.startKindOf = startKindOf;
|
|
3489
|
+
|
|
2447
3490
|
exports.tagScopeFilter = tagScopeFilter;
|
|
2448
3491
|
|
|
2449
3492
|
exports.toBareId = toBareId;
|
|
2450
3493
|
|
|
2451
3494
|
exports.toPhysicalGdr = toPhysicalGdr;
|
|
2452
3495
|
|
|
3496
|
+
exports.tolerantEntries = tolerantEntries;
|
|
3497
|
+
|
|
3498
|
+
exports.tolerantObject = tolerantObject;
|
|
3499
|
+
|
|
2453
3500
|
exports.tryParseGdr = tryParseGdr;
|
|
2454
3501
|
|
|
2455
3502
|
exports.validateFieldAppendItem = validateFieldAppendItem;
|