@sanity/workflow-engine 0.32.0 → 0.33.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 +193 -0
- package/DATAMODEL.md +144 -8
- package/README.md +2 -2
- package/dist/_chunks-cjs/invariants.cjs +2017 -1891
- package/dist/_chunks-es/invariants.js +2047 -1923
- package/dist/define.cjs +6 -3
- package/dist/define.d.cts +431 -255
- package/dist/define.d.ts +431 -255
- package/dist/define.js +7 -4
- package/dist/index.cjs +745 -210
- package/dist/index.d.cts +845 -429
- package/dist/index.d.ts +845 -429
- package/dist/index.js +737 -214
- package/package.json +4 -3
|
@@ -384,107 +384,191 @@ function tagScopeFilter() {
|
|
|
384
384
|
return "tag == $tag";
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
-
const NonEmptyString$1 = v__namespace.pipe(v__namespace.string(), v__namespace.
|
|
387
|
+
const NonEmptyString$1 = v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1, "must be a non-empty string"));
|
|
388
388
|
|
|
389
|
-
function
|
|
390
|
-
return value
|
|
391
|
-
try {
|
|
392
|
-
return validate(value), !0;
|
|
393
|
-
} catch {
|
|
394
|
-
return !1;
|
|
395
|
-
}
|
|
396
|
-
};
|
|
389
|
+
function isParseableInstant(value) {
|
|
390
|
+
return typeof value == "string" && !Number.isNaN(Date.parse(value));
|
|
397
391
|
}
|
|
398
392
|
|
|
399
|
-
const
|
|
393
|
+
const IsoTimestamp = v__namespace.pipe(v__namespace.string(), v__namespace.check(s => isParseableInstant(s), "must be an ISO-8601 datetime string"));
|
|
400
394
|
|
|
401
|
-
function
|
|
402
|
-
return
|
|
395
|
+
function tolerantObject() {
|
|
396
|
+
return (entries, ..._exact) => v__namespace.looseObject(entries);
|
|
403
397
|
}
|
|
404
398
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
}), v__namespace.object({
|
|
409
|
-
type: v__namespace.literal("canvas"),
|
|
410
|
-
id: NonEmptyString$1
|
|
411
|
-
}), v__namespace.object({
|
|
412
|
-
type: v__namespace.literal("media-library"),
|
|
413
|
-
id: NonEmptyString$1
|
|
414
|
-
}), v__namespace.object({
|
|
415
|
-
type: v__namespace.literal("dashboard"),
|
|
416
|
-
id: NonEmptyString$1
|
|
417
|
-
}) ]), ResourceBindingSchema = v__namespace.object({
|
|
418
|
-
name: v__namespace.pipe(NonEmptyString$1, v__namespace.check(isValidAliasName, "invalid resource handle name — lowercase letters, digits and dashes only, no leading dash")),
|
|
419
|
-
resource: WorkflowResourceSchema
|
|
420
|
-
}), 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({
|
|
421
|
-
name: lakeSegment("name"),
|
|
422
|
-
expectedMinReaderModel: v__namespace.optional(v__namespace.custom(() => !0), void 0),
|
|
423
|
-
tag: lakeSegment("tag"),
|
|
424
|
-
workflowResource: WorkflowResourceSchema,
|
|
425
|
-
resourceAliases: v__namespace.optional(v__namespace.pipe(v__namespace.array(ResourceBindingSchema), v__namespace.check(bindings => duplicateHandleMessage(bindings) === void 0, issue => duplicateHandleMessage(issue.input) ?? "duplicate resource handle name"))),
|
|
426
|
-
definitions: v__namespace.pipe(v__namespace.array(DefinitionSchema), v__namespace.minLength(1, "a deployment needs at least one definition"))
|
|
427
|
-
});
|
|
399
|
+
function tolerantEntries() {
|
|
400
|
+
return (entries, ..._exact) => entries;
|
|
401
|
+
}
|
|
428
402
|
|
|
429
|
-
function
|
|
430
|
-
|
|
431
|
-
for (const item of items) {
|
|
432
|
-
const key = keyOf(item), earlier = seen.get(key);
|
|
433
|
-
if (earlier !== void 0) return [ earlier, item ];
|
|
434
|
-
seen.set(key, item);
|
|
435
|
-
}
|
|
403
|
+
function schemaTreeShape(schema) {
|
|
404
|
+
return walkSchemaShape(schema, /* @__PURE__ */ new Set);
|
|
436
405
|
}
|
|
437
406
|
|
|
438
|
-
function
|
|
439
|
-
|
|
440
|
-
if (
|
|
407
|
+
function walkSchemaShape(node, path) {
|
|
408
|
+
if (typeof node != "object" || node === null) return "unknown";
|
|
409
|
+
if (path.has(node)) return "(circular)";
|
|
410
|
+
path.add(node);
|
|
411
|
+
try {
|
|
412
|
+
const schema = node;
|
|
413
|
+
return containerShape(schema, path) ?? leafShape(schema);
|
|
414
|
+
} finally {
|
|
415
|
+
path.delete(node);
|
|
416
|
+
}
|
|
441
417
|
}
|
|
442
418
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
419
|
+
const objectWalker = (schema, path) => objectEntriesShape(schema.entries, path), unionWalker = (schema, path) => ({
|
|
420
|
+
union: schema.options.map(option => walkSchemaShape(option, path))
|
|
421
|
+
}), unwrapWalker = (schema, path) => walkSchemaShape(schema.wrapped, path), CONTAINER_WALKERS = {
|
|
422
|
+
strict_object: objectWalker,
|
|
423
|
+
loose_object: objectWalker,
|
|
424
|
+
object: objectWalker,
|
|
425
|
+
array: (schema, path) => ({
|
|
426
|
+
array: walkSchemaShape(schema.item, path)
|
|
427
|
+
}),
|
|
428
|
+
record: (schema, path) => ({
|
|
429
|
+
record: walkSchemaShape(schema.value, path)
|
|
430
|
+
}),
|
|
431
|
+
union: unionWalker,
|
|
432
|
+
variant: unionWalker,
|
|
433
|
+
lazy: (schema, path) => walkSchemaShape(schema.getter(void 0), path),
|
|
434
|
+
exact_optional: unwrapWalker,
|
|
435
|
+
optional: unwrapWalker,
|
|
436
|
+
nullable: unwrapWalker
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
function containerShape(schema, path) {
|
|
440
|
+
return CONTAINER_WALKERS[String(schema.type)]?.(schema, path);
|
|
446
441
|
}
|
|
447
442
|
|
|
448
|
-
function
|
|
449
|
-
|
|
443
|
+
function objectEntriesShape(entries, path) {
|
|
444
|
+
const out = {};
|
|
445
|
+
for (const [key, entry] of Object.entries(entries)) {
|
|
446
|
+
const optional = isOptionalEntry(entry), wrapped = optional ? entry.wrapped : entry;
|
|
447
|
+
out[optional ? `${key}?` : key] = walkSchemaShape(wrapped, path);
|
|
448
|
+
}
|
|
449
|
+
return Object.fromEntries(Object.entries(out).sort(([a], [b]) => a < b ? -1 : 1));
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
-
function
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
return `deployments "${first.name}" and "${second.name}" share workflow resource + tag "${second.tag}" — both would write into the same partition; change one deployment’s tag or resource`;
|
|
452
|
+
function isOptionalEntry(entry) {
|
|
453
|
+
if (typeof entry != "object" || entry === null) return !1;
|
|
454
|
+
const kind = entry.type;
|
|
455
|
+
return kind === "exact_optional" || kind === "optional";
|
|
457
456
|
}
|
|
458
457
|
|
|
459
|
-
const
|
|
460
|
-
deployments: v__namespace.pipe(v__namespace.array(DeploymentSchema), v__namespace.minLength(1, "a config needs at least one deployment"), v__namespace.check(deployments => duplicateNameMessage(deployments) === void 0, issue => duplicateNameMessage(issue.input) ?? "duplicate deployment name"), v__namespace.check(deployments => partitionCollisionMessage(deployments) === void 0, issue => partitionCollisionMessage(issue.input) ?? "duplicate deployment partition")),
|
|
461
|
-
telemetry: v__namespace.optional(TelemetryLoggerSchema)
|
|
462
|
-
});
|
|
458
|
+
const LEAF_KINDS = /* @__PURE__ */ new Set([ "string", "number", "boolean", "null", "undefined", "unknown", "any" ]);
|
|
463
459
|
|
|
464
|
-
function
|
|
465
|
-
|
|
460
|
+
function leafShape(schema) {
|
|
461
|
+
if (schema.type === "picklist") return schema.options.join(" | ");
|
|
462
|
+
if (schema.type === "literal") return `literal ${String(schema.literal)}`;
|
|
463
|
+
if (schema.type === "custom") return typeof schema.message == "string" ? `custom(${schema.message})` : "custom";
|
|
464
|
+
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`);
|
|
465
|
+
return String(schema.type);
|
|
466
466
|
}
|
|
467
467
|
|
|
468
|
-
|
|
468
|
+
function formatValidationError(label, issues) {
|
|
469
|
+
const lines = issues.map(issue => ` - ${issue.path.length === 0 ? "(root)" : formatIssuePath(issue.path)}: ${issue.message}`);
|
|
470
|
+
return `${label} failed validation (${issues.length} issue${issues.length === 1 ? "" : "s"}):\n${lines.join(`\n`)}`;
|
|
471
|
+
}
|
|
469
472
|
|
|
470
|
-
function
|
|
471
|
-
return
|
|
473
|
+
function issuesFromValibot(issues) {
|
|
474
|
+
return issues.map(issue => ({
|
|
475
|
+
path: issue.path ? issue.path.map(item => item.key) : [],
|
|
476
|
+
message: issue.message
|
|
477
|
+
}));
|
|
472
478
|
}
|
|
473
479
|
|
|
474
|
-
function
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
480
|
+
function formatIssuePath(path) {
|
|
481
|
+
let out = "";
|
|
482
|
+
for (const seg of path) typeof seg == "number" ? out += `[${seg}]` : out += out.length === 0 ? String(seg) : `.${String(seg)}`;
|
|
483
|
+
return out;
|
|
484
|
+
}
|
|
478
485
|
|
|
479
|
-
|
|
480
|
-
|
|
486
|
+
class PersistedDocShapeError extends WorkflowError {
|
|
487
|
+
documentId;
|
|
488
|
+
documentType;
|
|
489
|
+
issues;
|
|
490
|
+
constructor(args) {
|
|
491
|
+
super("persisted-doc-shape", formatValidationError(`Persisted ${args.documentType} document "${args.documentId}"`, args.issues)),
|
|
492
|
+
this.name = "PersistedDocShapeError", this.documentId = args.documentId, this.documentType = args.documentType,
|
|
493
|
+
this.issues = args.issues;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
481
496
|
|
|
482
|
-
|
|
483
|
-
|
|
497
|
+
function parsePersistedDoc(args) {
|
|
498
|
+
const result = v__namespace.safeParse(args.schema, args.doc);
|
|
499
|
+
if (!result.success) throw new PersistedDocShapeError({
|
|
500
|
+
documentId: documentIdOf(args.doc),
|
|
501
|
+
documentType: args.docType,
|
|
502
|
+
issues: issuesFromValibot(result.issues)
|
|
503
|
+
});
|
|
504
|
+
return result.output;
|
|
505
|
+
}
|
|
484
506
|
|
|
485
|
-
|
|
486
|
-
|
|
507
|
+
function documentIdOf(doc) {
|
|
508
|
+
if (typeof doc == "object" && doc !== null) {
|
|
509
|
+
const id = doc._id;
|
|
510
|
+
if (typeof id == "string") return id;
|
|
487
511
|
}
|
|
512
|
+
return "(unknown id)";
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const ACTOR_KINDS = [ "person", "agent", "system" ];
|
|
516
|
+
|
|
517
|
+
function releaseDocId(releaseName) {
|
|
518
|
+
return `_.releases.${releaseName}`;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function releaseRef({res: res, releaseName: releaseName}) {
|
|
522
|
+
if (releaseName.length === 0) throw new ContractViolationError("releaseRef: releaseName must be a non-empty release name");
|
|
523
|
+
return {
|
|
524
|
+
id: gdrFromResource(res, releaseDocId(releaseName)),
|
|
525
|
+
type: "system.release",
|
|
526
|
+
releaseName: releaseName
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function isAlwaysArrayFieldKind(kind) {
|
|
531
|
+
return kind === "doc.refs" || kind === "assignee" || kind === "assignees" || kind === "array";
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function isSingleDocRefKind(kind) {
|
|
535
|
+
return kind === "doc.ref" || kind === "subject";
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function refKindAcceptsTypes(kind) {
|
|
539
|
+
return isSingleDocRefKind(kind) || kind === "doc.refs";
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function assignmentKindAcceptsRoles(kind) {
|
|
543
|
+
return kind === "assignee" || kind === "assignees";
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function isSingleDocRefEntry(entry) {
|
|
547
|
+
return isSingleDocRefKind(entry._type);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function isAssignmentFieldEntry(entry) {
|
|
551
|
+
return assignmentKindAcceptsRoles(entry._type);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function isTodoListItem(row) {
|
|
555
|
+
if (typeof row != "object" || row === null) return !1;
|
|
556
|
+
const candidate = row, status = candidate.status;
|
|
557
|
+
return typeof candidate._key == "string" && typeof candidate.label == "string" && (status == null || typeof status == "string");
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function declaredRowColumns(entry) {
|
|
561
|
+
if (("_type" in entry ? entry._type : entry.type) === "array") return new Set((("of" in entry ? entry.of : void 0) ?? []).map(shape => shape.name));
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function isTodoListEntry(entry) {
|
|
565
|
+
const columns = declaredRowColumns(entry);
|
|
566
|
+
return columns !== void 0 && columns.has("label") && columns.has("status");
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function isNotesEntry(entry) {
|
|
570
|
+
const columns = declaredRowColumns(entry);
|
|
571
|
+
return columns !== void 0 && columns.has("body") && columns.has("actor") && columns.has("at");
|
|
488
572
|
}
|
|
489
573
|
|
|
490
574
|
const UNIVERSAL_ROLE_ALIAS_KEY = "$all";
|
|
@@ -515,2151 +599,2157 @@ function actorFulfillsRole({actorRoles: actorRoles, required: required, aliases:
|
|
|
515
599
|
return actorRoles.some(role => accepted.has(role));
|
|
516
600
|
}
|
|
517
601
|
|
|
518
|
-
function
|
|
519
|
-
return
|
|
602
|
+
function normalizeAssignmentMembers(value) {
|
|
603
|
+
return value === null ? [] : Array.isArray(value) ? value : [ value ];
|
|
520
604
|
}
|
|
521
605
|
|
|
522
|
-
function
|
|
523
|
-
|
|
524
|
-
if (serialized === void 0) throw new Error(`groq tag cannot serialize ${typeof value} — interpolate JSON-representable values only`);
|
|
525
|
-
return serialized;
|
|
606
|
+
function assignmentMembers(entries) {
|
|
607
|
+
return entries.flatMap(entry => isAssignmentFieldEntry(entry) ? normalizeAssignmentMembers(entry.value) : []);
|
|
526
608
|
}
|
|
527
609
|
|
|
528
|
-
function
|
|
529
|
-
|
|
530
|
-
checkReservedRoleAliasKeys(authoring.roleAliases, issues);
|
|
531
|
-
const roleAliases = normalizeRoleAliases(authoring.roleAliases), ctx = {
|
|
532
|
-
issues: issues,
|
|
533
|
-
roleAliases: roleAliases
|
|
534
|
-
}, workflowFields2 = desugarFieldEntries({
|
|
535
|
-
entries: authoring.fields,
|
|
536
|
-
path: [ "fields" ],
|
|
537
|
-
ctx: ctx
|
|
538
|
-
}), workflowLayer = layerOf(workflowFields2), stages = authoring.stages.map((stage, i) => {
|
|
539
|
-
const path = [ "stages", i ], stageFields2 = desugarFieldEntries({
|
|
540
|
-
entries: stage.fields,
|
|
541
|
-
path: [ ...path, "fields" ],
|
|
542
|
-
ctx: ctx
|
|
543
|
-
}), stageEnv = {
|
|
544
|
-
layers: [ {
|
|
545
|
-
scope: "stage",
|
|
546
|
-
entries: layerOf(stageFields2)
|
|
547
|
-
}, {
|
|
548
|
-
scope: "workflow",
|
|
549
|
-
entries: workflowLayer
|
|
550
|
-
} ]
|
|
551
|
-
}, activities = (stage.activities ?? []).map((activity, j) => desugarActivity({
|
|
552
|
-
activity: activity,
|
|
553
|
-
path: [ ...path, "activities", j ],
|
|
554
|
-
stageEnv: stageEnv,
|
|
555
|
-
ctx: ctx
|
|
556
|
-
})), transitions = (stage.transitions ?? []).map(transition => desugarTransition({
|
|
557
|
-
transition: transition
|
|
558
|
-
})), editable = desugarStageEditable({
|
|
559
|
-
overrides: stage.editable,
|
|
560
|
-
inScope: editableFieldNames({
|
|
561
|
-
workflowFields: workflowFields2,
|
|
562
|
-
stageFields: stageFields2,
|
|
563
|
-
activities: activities
|
|
564
|
-
}),
|
|
565
|
-
path: [ ...path, "editable" ],
|
|
566
|
-
ctx: ctx
|
|
567
|
-
});
|
|
568
|
-
return {
|
|
569
|
-
...stripUndefined({
|
|
570
|
-
name: stage.name,
|
|
571
|
-
semantics: stage.semantics,
|
|
572
|
-
title: stage.title,
|
|
573
|
-
description: stage.description,
|
|
574
|
-
groups: stage.groups,
|
|
575
|
-
guards: stage.guards?.map(desugarGuard)
|
|
576
|
-
}),
|
|
577
|
-
...stageFields2 ? {
|
|
578
|
-
fields: stageFields2
|
|
579
|
-
} : {},
|
|
580
|
-
...activities.length > 0 ? {
|
|
581
|
-
activities: activities
|
|
582
|
-
} : {},
|
|
583
|
-
...transitions.length > 0 ? {
|
|
584
|
-
transitions: transitions
|
|
585
|
-
} : {},
|
|
586
|
-
...editable ? {
|
|
587
|
-
editable: editable
|
|
588
|
-
} : {}
|
|
589
|
-
};
|
|
590
|
-
});
|
|
591
|
-
return {
|
|
592
|
-
definition: {
|
|
593
|
-
...stripUndefined({
|
|
594
|
-
name: authoring.name,
|
|
595
|
-
semantics: authoring.semantics,
|
|
596
|
-
title: authoring.title,
|
|
597
|
-
description: authoring.description,
|
|
598
|
-
groups: authoring.groups,
|
|
599
|
-
lifecycle: authoring.lifecycle,
|
|
600
|
-
start: desugarStart(authoring.start),
|
|
601
|
-
initialStage: authoring.initialStage,
|
|
602
|
-
predicates: authoring.predicates,
|
|
603
|
-
roleAliases: roleAliases
|
|
604
|
-
}),
|
|
605
|
-
...workflowFields2 ? {
|
|
606
|
-
fields: workflowFields2
|
|
607
|
-
} : {},
|
|
608
|
-
stages: stages
|
|
609
|
-
},
|
|
610
|
-
issues: ctx.issues
|
|
611
|
-
};
|
|
610
|
+
function assignmentState(members) {
|
|
611
|
+
return members.length === 0 ? "unrouted" : members.some(member => member.type === "user") ? "held" : "routed";
|
|
612
612
|
}
|
|
613
613
|
|
|
614
|
-
function
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
614
|
+
function activeAssignmentMembers(members) {
|
|
615
|
+
const users = members.filter(member => member.type === "user");
|
|
616
|
+
return users.length > 0 ? users : members;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function assignmentMatch(members, identity) {
|
|
620
|
+
const active = activeAssignmentMembers(members);
|
|
621
|
+
return active.some(member => member.type === "user" && member.id === identity.userId) ? "user" : active.some(member => member.type === "role" && identity.roles.includes(member.role)) ? "role" : void 0;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function identityMatchesAssignment(members, identity) {
|
|
625
|
+
return assignmentMatch(members, identity) !== void 0;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function assignmentStateCounts(assignments, identity) {
|
|
629
|
+
const counts = {
|
|
630
|
+
unrouted: 0,
|
|
631
|
+
routed: 0,
|
|
632
|
+
held: 0
|
|
623
633
|
};
|
|
634
|
+
for (const members of assignments) {
|
|
635
|
+
const state = assignmentState(members);
|
|
636
|
+
state === "unrouted" ? counts.unrouted += 1 : identityMatchesAssignment(members, identity) && (counts[state] += 1);
|
|
637
|
+
}
|
|
638
|
+
return counts;
|
|
624
639
|
}
|
|
625
640
|
|
|
626
|
-
function
|
|
627
|
-
|
|
628
|
-
path: [ "roleAliases", key ],
|
|
629
|
-
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.`
|
|
630
|
-
});
|
|
641
|
+
function openActivityAssignments(instance) {
|
|
642
|
+
return (findOpenStageEntry(instance)?.activities ?? []).filter(activity => activity.status === "active").map(activity => assignmentMembers(activity.fields ?? []));
|
|
631
643
|
}
|
|
632
644
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
title: "Label"
|
|
637
|
-
}, {
|
|
638
|
-
type: "string",
|
|
639
|
-
name: "status",
|
|
640
|
-
title: "Status"
|
|
641
|
-
}, {
|
|
642
|
-
type: "assignee",
|
|
643
|
-
name: "assignee",
|
|
644
|
-
title: "Assignee"
|
|
645
|
-
}, {
|
|
646
|
-
type: "date",
|
|
647
|
-
name: "dueDate",
|
|
648
|
-
title: "Due date"
|
|
649
|
-
} ], NOTES_OF = [ {
|
|
650
|
-
type: "text",
|
|
651
|
-
name: "body",
|
|
652
|
-
title: "Body"
|
|
653
|
-
}, {
|
|
654
|
-
type: "actor",
|
|
655
|
-
name: "actor",
|
|
656
|
-
title: "Actor"
|
|
657
|
-
}, {
|
|
658
|
-
type: "datetime",
|
|
659
|
-
name: "at",
|
|
660
|
-
title: "At"
|
|
661
|
-
} ];
|
|
645
|
+
function instanceAssignmentStateCounts(instance, identity) {
|
|
646
|
+
return assignmentStateCounts(openActivityAssignments(instance), identity);
|
|
647
|
+
}
|
|
662
648
|
|
|
663
|
-
function
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
649
|
+
function actorMatchesAssignment(args) {
|
|
650
|
+
const actor = args.actor;
|
|
651
|
+
return actor === void 0 ? !1 : activeAssignmentMembers(args.members).some(member => member.type === "user" ? member.id === actor.id : actorFulfillsRole({
|
|
652
|
+
actorRoles: actor.roles,
|
|
653
|
+
required: member.role,
|
|
654
|
+
aliases: args.roleAliases
|
|
668
655
|
}));
|
|
669
656
|
}
|
|
670
657
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
roles: entry.roles,
|
|
696
|
-
fields: entry.fields,
|
|
697
|
-
of: entry.of
|
|
698
|
-
})
|
|
658
|
+
const ANONYMOUS_IDENTITY = "<anonymous>", SYSTEM_IDENTITY = "<system>", E_PREFIXED_PROJECT_ID = /^e-(.+)$/;
|
|
659
|
+
|
|
660
|
+
function classifyPrincipalId(id) {
|
|
661
|
+
if (id === ANONYMOUS_IDENTITY || id === SYSTEM_IDENTITY) return {
|
|
662
|
+
namespace: "sentinel"
|
|
663
|
+
};
|
|
664
|
+
if (id.startsWith("g")) return {
|
|
665
|
+
namespace: "global",
|
|
666
|
+
globalId: id
|
|
667
|
+
};
|
|
668
|
+
if (id.startsWith("p-")) return {
|
|
669
|
+
namespace: "robot",
|
|
670
|
+
globalId: id
|
|
671
|
+
};
|
|
672
|
+
const embeddedGlobal = E_PREFIXED_PROJECT_ID.exec(id)?.[1];
|
|
673
|
+
return embeddedGlobal !== void 0 ? embeddedGlobal.startsWith("g") ? {
|
|
674
|
+
namespace: "project",
|
|
675
|
+
globalId: embeddedGlobal
|
|
676
|
+
} : {
|
|
677
|
+
namespace: "unknown"
|
|
678
|
+
} : id.startsWith("p") ? {
|
|
679
|
+
namespace: "project"
|
|
680
|
+
} : {
|
|
681
|
+
namespace: "unknown"
|
|
699
682
|
};
|
|
700
683
|
}
|
|
701
684
|
|
|
702
|
-
function
|
|
703
|
-
|
|
704
|
-
editable: entry.editable,
|
|
705
|
-
path: [ ...path, "editable" ],
|
|
706
|
-
ctx: ctx
|
|
707
|
-
});
|
|
708
|
-
return {
|
|
709
|
-
...stripUndefined({
|
|
710
|
-
name: entry.name,
|
|
711
|
-
title: entry.title,
|
|
712
|
-
description: entry.description,
|
|
713
|
-
group: normalizeGroup(entry.group),
|
|
714
|
-
required: entry.required,
|
|
715
|
-
initialValue: entry.initialValue,
|
|
716
|
-
editable: editable
|
|
717
|
-
}),
|
|
718
|
-
type: "array",
|
|
719
|
-
of: entry.type === "todoList" ? TODOLIST_OF : NOTES_OF
|
|
720
|
-
};
|
|
685
|
+
function directoryBridgeId(sanityUserId) {
|
|
686
|
+
if (typeof sanityUserId == "string") return classifyPrincipalId(sanityUserId).namespace === "global" ? sanityUserId : void 0;
|
|
721
687
|
}
|
|
722
688
|
|
|
723
|
-
function
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
const
|
|
727
|
-
if (
|
|
728
|
-
ctx.issues.push({
|
|
729
|
-
path: path,
|
|
730
|
-
message: "editable: [] names no roles — use `true` to open the field to anyone in its scope, or list at least one role"
|
|
731
|
-
});
|
|
732
|
-
return;
|
|
733
|
-
}
|
|
734
|
-
return condition;
|
|
689
|
+
function firstCarriedGlobalId(candidates) {
|
|
690
|
+
for (const candidate of candidates) {
|
|
691
|
+
if (candidate === void 0) continue;
|
|
692
|
+
const {globalId: globalId} = classifyPrincipalId(candidate);
|
|
693
|
+
if (globalId !== void 0) return globalId;
|
|
735
694
|
}
|
|
736
|
-
return editable;
|
|
737
695
|
}
|
|
738
696
|
|
|
739
|
-
function
|
|
740
|
-
|
|
741
|
-
for (const entry of entries ?? []) entry.editable !== void 0 && names.add(entry.name);
|
|
742
|
-
};
|
|
743
|
-
collect(workflowFields2), collect(stageFields2);
|
|
744
|
-
for (const activity of activities) collect(activity.fields);
|
|
745
|
-
return names;
|
|
697
|
+
function lakePrincipalId(args) {
|
|
698
|
+
return args.localPrincipalId ?? args.actor.id;
|
|
746
699
|
}
|
|
747
700
|
|
|
748
|
-
function
|
|
749
|
-
|
|
750
|
-
const out = {};
|
|
751
|
-
for (const [name, value] of Object.entries(overrides)) {
|
|
752
|
-
if (!inScope.has(name)) {
|
|
753
|
-
ctx.issues.push({
|
|
754
|
-
path: [ ...path, name ],
|
|
755
|
-
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\``
|
|
756
|
-
});
|
|
757
|
-
continue;
|
|
758
|
-
}
|
|
759
|
-
const normalized = normalizeEditable({
|
|
760
|
-
editable: value,
|
|
761
|
-
path: [ ...path, name ],
|
|
762
|
-
ctx: ctx
|
|
763
|
-
});
|
|
764
|
-
normalized !== void 0 && (out[name] = normalized);
|
|
765
|
-
}
|
|
766
|
-
return Object.keys(out).length > 0 ? out : void 0;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
function layerOf(entries) {
|
|
770
|
-
return new Map((entries ?? []).map(entry => [ entry.name, entry ]));
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
function normalizeGroup(group) {
|
|
774
|
-
return group === void 0 || Array.isArray(group) ? group : [ group ];
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
function desugarActivity({activity: activity, path: path, stageEnv: stageEnv, ctx: ctx}) {
|
|
778
|
-
const activityFields2 = desugarFieldEntries({
|
|
779
|
-
entries: activity.fields,
|
|
780
|
-
path: [ ...path, "fields" ],
|
|
781
|
-
ctx: ctx
|
|
782
|
-
}), env = {
|
|
783
|
-
layers: [ {
|
|
784
|
-
scope: "activity",
|
|
785
|
-
entries: layerOf(activityFields2)
|
|
786
|
-
}, ...stageEnv.layers ]
|
|
787
|
-
}, actions = (activity.actions ?? []).map((action, a) => desugarAction({
|
|
788
|
-
action: action,
|
|
789
|
-
path: [ ...path, "actions", a ],
|
|
790
|
-
env: env,
|
|
791
|
-
activityName: activity.name,
|
|
792
|
-
ctx: ctx
|
|
793
|
-
})), target = desugarTarget({
|
|
794
|
-
target: activity.target,
|
|
795
|
-
env: env,
|
|
796
|
-
path: [ ...path, "target" ],
|
|
797
|
-
ctx: ctx
|
|
798
|
-
});
|
|
799
|
-
return {
|
|
800
|
-
...stripUndefined({
|
|
801
|
-
name: activity.name,
|
|
802
|
-
semantics: activity.semantics,
|
|
803
|
-
title: activity.title,
|
|
804
|
-
description: activity.description,
|
|
805
|
-
groups: activity.groups,
|
|
806
|
-
group: normalizeGroup(activity.group),
|
|
807
|
-
filter: activity.filter,
|
|
808
|
-
requirements: activity.requirements
|
|
809
|
-
}),
|
|
810
|
-
...target ? {
|
|
811
|
-
target: target
|
|
812
|
-
} : {},
|
|
813
|
-
...activityFields2 ? {
|
|
814
|
-
fields: activityFields2
|
|
815
|
-
} : {},
|
|
816
|
-
...actions.length > 0 ? {
|
|
817
|
-
actions: actions
|
|
818
|
-
} : {}
|
|
819
|
-
};
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
const TARGET_DOC_KINDS = [ "doc.ref", "doc.refs", "subject", "release.ref" ];
|
|
823
|
-
|
|
824
|
-
function desugarTarget({target: target, env: env, path: path, ctx: ctx}) {
|
|
825
|
-
if (target === void 0 || target.type === "url") return target;
|
|
826
|
-
const ref = typeof target.field == "string" ? {
|
|
827
|
-
field: target.field
|
|
828
|
-
} : target.field, field = resolveRef({
|
|
829
|
-
ref: ref,
|
|
830
|
-
env: env,
|
|
831
|
-
path: [ ...path, "field" ],
|
|
832
|
-
ctx: ctx
|
|
833
|
-
}) ?? fallbackRef(ref), entry = entryAt(env, field);
|
|
834
|
-
return entry && !TARGET_DOC_KINDS.includes(entry.type) && ctx.issues.push({
|
|
835
|
-
path: [ ...path, "field" ],
|
|
836
|
-
message: `manual activity target references "${field.field}" of kind "${entry.type}" — a deep-link target needs a document-valued entry (${TARGET_DOC_KINDS.join(", ")})`
|
|
837
|
-
}), {
|
|
838
|
-
type: "field",
|
|
839
|
-
field: field
|
|
840
|
-
};
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
function reportEmptyActionRoles({action: action, path: path, ctx: ctx}) {
|
|
844
|
-
action.roles === void 0 || action.roles.length > 0 || ctx.issues.push({
|
|
845
|
-
path: [ ...path, "roles" ],
|
|
846
|
-
message: "roles: [] names no roles — omit it to allow any identity, or list at least one role"
|
|
847
|
-
});
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
function desugarActionOps(args) {
|
|
851
|
-
const {action: action, path: path, env: env, activityName: activityName, ctx: ctx} = args, ops = desugarOps({
|
|
852
|
-
ops: action.ops,
|
|
853
|
-
path: [ ...path, "ops" ],
|
|
854
|
-
env: env,
|
|
855
|
-
firingActivity: activityName,
|
|
856
|
-
ctx: ctx
|
|
857
|
-
}) ?? [];
|
|
858
|
-
return action.status !== void 0 && ops.push({
|
|
859
|
-
type: "status.set",
|
|
860
|
-
activity: activityName,
|
|
861
|
-
status: action.status
|
|
862
|
-
}), ops;
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
function desugarAction({action: action, path: path, env: env, activityName: activityName, ctx: ctx}) {
|
|
866
|
-
reportEmptyActionRoles({
|
|
867
|
-
action: action,
|
|
868
|
-
path: path,
|
|
869
|
-
ctx: ctx
|
|
870
|
-
});
|
|
871
|
-
const cascadeFired = isCascadeFired(action), filter = cascadeFired ? action.filter : andConditions([ rolesCondition(action.roles, ctx.roleAliases), action.filter ]), ops = desugarActionOps({
|
|
872
|
-
action: action,
|
|
873
|
-
path: path,
|
|
874
|
-
env: env,
|
|
875
|
-
activityName: activityName,
|
|
876
|
-
ctx: ctx
|
|
877
|
-
});
|
|
878
|
-
return {
|
|
879
|
-
...stripUndefined({
|
|
880
|
-
name: action.name,
|
|
881
|
-
semantics: action.semantics,
|
|
882
|
-
title: action.title,
|
|
883
|
-
description: action.description,
|
|
884
|
-
group: normalizeGroup(action.group),
|
|
885
|
-
when: action.when,
|
|
886
|
-
params: action.params,
|
|
887
|
-
effects: action.effects,
|
|
888
|
-
spawn: action.spawn
|
|
889
|
-
}),
|
|
890
|
-
...cascadeFired && action.roles !== void 0 && action.roles.length > 0 ? {
|
|
891
|
-
roles: action.roles
|
|
892
|
-
} : {},
|
|
893
|
-
...filter ? {
|
|
894
|
-
filter: filter
|
|
895
|
-
} : {},
|
|
896
|
-
...ops.length > 0 ? {
|
|
897
|
-
ops: ops
|
|
898
|
-
} : {}
|
|
899
|
-
};
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
const DEFAULT_TRANSITION_WHEN = "$allActivitiesDone";
|
|
903
|
-
|
|
904
|
-
function desugarTransition({transition: transition}) {
|
|
905
|
-
return {
|
|
906
|
-
...stripUndefined({
|
|
907
|
-
name: transition.name,
|
|
908
|
-
title: transition.title,
|
|
909
|
-
description: transition.description,
|
|
910
|
-
to: transition.to
|
|
911
|
-
}),
|
|
912
|
-
when: transition.when ?? DEFAULT_TRANSITION_WHEN
|
|
913
|
-
};
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
function desugarGuard(guard) {
|
|
917
|
-
const {match: match, metadata: metadata, ...rest} = guard, {idRefs: idRefs, ...matchRest} = match;
|
|
918
|
-
return {
|
|
919
|
-
...rest,
|
|
920
|
-
match: {
|
|
921
|
-
...matchRest,
|
|
922
|
-
...idRefs !== void 0 ? {
|
|
923
|
-
idRefs: idRefs.map(printGuardRead)
|
|
924
|
-
} : {}
|
|
925
|
-
},
|
|
926
|
-
...metadata !== void 0 ? {
|
|
927
|
-
metadata: Object.fromEntries(Object.entries(metadata).map(([key, read]) => [ key, printGuardRead(read) ]))
|
|
928
|
-
} : {}
|
|
929
|
-
};
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
function desugarOps({ops: ops, path: path, env: env, firingActivity: firingActivity, ctx: ctx}) {
|
|
933
|
-
if (ops) return ops.map((op, i) => desugarOp({
|
|
934
|
-
op: op,
|
|
935
|
-
path: [ ...path, i ],
|
|
936
|
-
env: env,
|
|
937
|
-
firingActivity: firingActivity,
|
|
938
|
-
ctx: ctx
|
|
939
|
-
}));
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
function desugarOp({op: op, path: path, env: env, firingActivity: firingActivity, ctx: ctx}) {
|
|
943
|
-
if (op.type === "status.set") return {
|
|
944
|
-
type: "status.set",
|
|
945
|
-
activity: op.activity ?? firingActivity,
|
|
946
|
-
status: op.status
|
|
947
|
-
};
|
|
948
|
-
if (op.type === "audit") return desugarAuditOp({
|
|
949
|
-
op: op,
|
|
950
|
-
path: path,
|
|
951
|
-
env: env,
|
|
952
|
-
ctx: ctx
|
|
953
|
-
});
|
|
954
|
-
const target = resolveRef({
|
|
955
|
-
ref: op.target,
|
|
956
|
-
env: env,
|
|
957
|
-
path: [ ...path, "target" ],
|
|
958
|
-
ctx: ctx
|
|
959
|
-
}) ?? fallbackRef(op.target);
|
|
960
|
-
return {
|
|
961
|
-
...op,
|
|
962
|
-
target: target
|
|
963
|
-
};
|
|
701
|
+
function isRecord(value) {
|
|
702
|
+
return typeof value == "object" && value !== null && !Array.isArray(value);
|
|
964
703
|
}
|
|
965
704
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
function desugarAuditOp({op: op, path: path, env: env, ctx: ctx}) {
|
|
976
|
-
const target = resolveRef({
|
|
977
|
-
ref: op.target,
|
|
978
|
-
env: env,
|
|
979
|
-
path: [ ...path, "target" ],
|
|
980
|
-
ctx: ctx
|
|
981
|
-
}) ?? fallbackRef(op.target);
|
|
982
|
-
if (op.value.type !== "object") return ctx.issues.push({
|
|
983
|
-
path: [ ...path, "value" ],
|
|
984
|
-
message: `audit value must be an object source carrying the domain fields (got "${op.value.type}")`
|
|
985
|
-
}), {
|
|
986
|
-
type: "field.append",
|
|
987
|
-
target: target,
|
|
988
|
-
value: op.value
|
|
989
|
-
};
|
|
990
|
-
const actorField = op.stampFields?.actor ?? "actor", atField = op.stampFields?.at ?? "at", fields = {
|
|
991
|
-
...op.value.fields
|
|
992
|
-
};
|
|
993
|
-
for (const [stamp, source] of [ [ actorField, AUDIT_STAMPS.actor ], [ atField, AUDIT_STAMPS.at ] ]) {
|
|
994
|
-
if (stamp in fields) {
|
|
995
|
-
ctx.issues.push({
|
|
996
|
-
path: [ ...path, "value", "fields", stamp ],
|
|
997
|
-
message: `audit stamp field "${stamp}" collides with an authored domain field — rename yours or remap the stamp via stampFields`
|
|
998
|
-
});
|
|
999
|
-
continue;
|
|
1000
|
-
}
|
|
1001
|
-
fields[stamp] = source;
|
|
705
|
+
class FieldValueShapeError extends WorkflowError {
|
|
706
|
+
entryType;
|
|
707
|
+
entryName;
|
|
708
|
+
issues;
|
|
709
|
+
constructor(args) {
|
|
710
|
+
const issueText = args.issues.join("; ");
|
|
711
|
+
super("field-value-shape", `Field entry ${args.mode} shape invalid for "${args.entryName}" (${args.entryType}): ${issueText}`),
|
|
712
|
+
this.name = "FieldValueShapeError", this.entryType = args.entryType, this.entryName = args.entryName,
|
|
713
|
+
this.issues = args.issues;
|
|
1002
714
|
}
|
|
1003
|
-
return {
|
|
1004
|
-
type: "field.append",
|
|
1005
|
-
target: target,
|
|
1006
|
-
value: {
|
|
1007
|
-
type: "object",
|
|
1008
|
-
fields: fields
|
|
1009
|
-
}
|
|
1010
|
-
};
|
|
1011
715
|
}
|
|
1012
716
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
}
|
|
717
|
+
const GdrUriSchema = v__namespace.custom(s => typeof s == "string" && isGdrUri(s), "must be a GDR URI"), GdrShape = tolerantObject()({
|
|
718
|
+
id: GdrUriSchema,
|
|
719
|
+
type: NonEmptyString$1
|
|
720
|
+
}), ReleaseRefShape = v__namespace.pipe(tolerantObject()({
|
|
721
|
+
id: GdrUriSchema,
|
|
722
|
+
type: v__namespace.literal("system.release"),
|
|
723
|
+
releaseName: NonEmptyString$1
|
|
724
|
+
}), 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()({
|
|
725
|
+
kind: v__namespace.picklist(ACTOR_KINDS),
|
|
726
|
+
id: NonEmptyString$1,
|
|
727
|
+
roles: v__namespace.exactOptional(v__namespace.array(v__namespace.string())),
|
|
728
|
+
onBehalfOf: v__namespace.exactOptional(v__namespace.string())
|
|
729
|
+
}), AssigneeShape = v__namespace.union([ tolerantObject()({
|
|
730
|
+
type: v__namespace.literal("user"),
|
|
731
|
+
id: NonEmptyString$1
|
|
732
|
+
}), tolerantObject()({
|
|
733
|
+
type: v__namespace.literal("role"),
|
|
734
|
+
role: NonEmptyString$1
|
|
735
|
+
}) ]), AssigneeListShape = v__namespace.pipe(v__namespace.union([ v__namespace.null(), AssigneeShape, v__namespace.array(AssigneeShape) ]), v__namespace.transform(normalizeAssignmentMembers)), 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() ]), NullableProgress = v__namespace.union([ v__namespace.null(), v__namespace.pipe(v__namespace.number(), v__namespace.finite("progress must be a finite number"), v__namespace.minValue(0, "progress must be at least 0"), v__namespace.maxValue(100, "progress must be at most 100")) ]), 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, CHOICE_KINDS = /* @__PURE__ */ new Set([ "string", "text", "number", "url", "date", "dueDate", "datetime", "dueDatetime", "dateTime" ]);
|
|
1029
736
|
|
|
1030
|
-
function
|
|
1031
|
-
return
|
|
1032
|
-
scope: ref.scope ?? "workflow",
|
|
1033
|
-
field: ref.field
|
|
1034
|
-
};
|
|
737
|
+
function normalizedChoiceKind(kind) {
|
|
738
|
+
return kind === "dateTime" ? "datetime" : kind;
|
|
1035
739
|
}
|
|
1036
740
|
|
|
1037
|
-
function
|
|
1038
|
-
|
|
741
|
+
function checkChoiceList(args) {
|
|
742
|
+
const {entryType: entryType, options: options, validation: validation} = args;
|
|
743
|
+
if (options === void 0) return;
|
|
744
|
+
if (!CHOICE_KINDS.has(entryType)) return [ `\`options\` is not valid on "${entryType}" values` ];
|
|
745
|
+
const kind = normalizedChoiceKind(entryType), issues = options.list.flatMap((option, index) => issuesOf(checkValueAgainst({
|
|
746
|
+
entryType: kind,
|
|
747
|
+
value: option.value,
|
|
748
|
+
validation: validation
|
|
749
|
+
}, valueSchemas))?.map(issue => `at options.list.${index}.value: ${issue}`) ?? []), seen = /* @__PURE__ */ new Set;
|
|
750
|
+
for (const option of options.list) seen.has(option.value) && issues.push(`duplicate option value ${JSON.stringify(option.value)}`),
|
|
751
|
+
seen.add(option.value);
|
|
752
|
+
return issues.length === 0 ? void 0 : issues;
|
|
1039
753
|
}
|
|
1040
754
|
|
|
1041
|
-
function
|
|
1042
|
-
|
|
1043
|
-
for (const [k, value] of Object.entries(obj)) value !== void 0 && (out[k] = value);
|
|
1044
|
-
return out;
|
|
755
|
+
function choiceValueIssues(options, value) {
|
|
756
|
+
if (!(options === void 0 || value === null || value === void 0)) return options.list.some(option => Object.is(option.value, value)) ? void 0 : [ `value ${JSON.stringify(value)} is not declared in \`options.list\`; expected one of ${options.list.map(option => JSON.stringify(option.value)).join(", ")}` ];
|
|
1045
757
|
}
|
|
1046
758
|
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
759
|
+
const fieldValueSchemas = {
|
|
760
|
+
"doc.ref": v__namespace.union([ v__namespace.null(), GdrShape ]),
|
|
761
|
+
"doc.refs": v__namespace.array(GdrShape),
|
|
762
|
+
subject: v__namespace.union([ v__namespace.null(), GdrShape ]),
|
|
763
|
+
"release.ref": v__namespace.union([ v__namespace.null(), ReleaseRefShape ]),
|
|
764
|
+
string: NullableString,
|
|
765
|
+
text: NullableString,
|
|
766
|
+
number: NullableNumber,
|
|
767
|
+
progress: NullableProgress,
|
|
768
|
+
boolean: NullableBoolean,
|
|
769
|
+
date: NullableDate,
|
|
770
|
+
dueDate: NullableDate,
|
|
771
|
+
datetime: NullableDateTime,
|
|
772
|
+
dueDatetime: NullableDateTime,
|
|
773
|
+
url: NullableUrl,
|
|
774
|
+
actor: v__namespace.union([ v__namespace.null(), ActorShape ]),
|
|
775
|
+
assignee: AssigneeListShape,
|
|
776
|
+
assignees: v__namespace.array(AssigneeShape)
|
|
777
|
+
}, WritePrincipalId = v__namespace.pipe(NonEmptyString$1, v__namespace.rawTransform(({dataset: dataset, addIssue: addIssue}) => {
|
|
778
|
+
const classified = classifyPrincipalId(dataset.value);
|
|
779
|
+
return classified.namespace === "global" || classified.namespace === "robot" ? dataset.value : classified.namespace === "project" && classified.globalId !== void 0 ? classified.globalId : (addIssue({
|
|
780
|
+
message: `principal id "${dataset.value}" is not an account-global user id. Workflow user ids are the global \`sanityUserId\` (or a robot token's id). Resolve project members through your surface's member hook, or /projects/<projectId>/users/<id> → sanityUserId.`
|
|
781
|
+
}), dataset.value);
|
|
782
|
+
})), ActorWriteShape = tolerantObject()({
|
|
783
|
+
kind: v__namespace.picklist(ACTOR_KINDS),
|
|
784
|
+
id: WritePrincipalId,
|
|
785
|
+
roles: v__namespace.exactOptional(v__namespace.array(v__namespace.string())),
|
|
786
|
+
onBehalfOf: v__namespace.exactOptional(v__namespace.string())
|
|
787
|
+
}), AssigneePrincipalId = v__namespace.pipe(WritePrincipalId, v__namespace.check(id => classifyPrincipalId(id).namespace !== "robot", "robot principals cannot be assignment members")), AssigneeWriteShape = v__namespace.union([ tolerantObject()({
|
|
788
|
+
type: v__namespace.literal("user"),
|
|
789
|
+
id: AssigneePrincipalId
|
|
790
|
+
}), tolerantObject()({
|
|
791
|
+
type: v__namespace.literal("role"),
|
|
792
|
+
role: NonEmptyString$1
|
|
793
|
+
}) ]), SingularAssigneeWriteShape = v__namespace.pipe(v__namespace.union([ AssigneeWriteShape, v__namespace.array(AssigneeWriteShape) ]), v__namespace.transform(normalizeAssignmentMembers), v__namespace.check(members => members.filter(member => member.type === "user").length <= 1, "must contain at most one user member")), valueSchemas = {
|
|
794
|
+
...fieldValueSchemas,
|
|
795
|
+
actor: v__namespace.nullable(ActorWriteShape),
|
|
796
|
+
assignee: SingularAssigneeWriteShape,
|
|
797
|
+
assignees: v__namespace.array(AssigneeWriteShape),
|
|
798
|
+
query: v__namespace.any()
|
|
799
|
+
};
|
|
1050
800
|
|
|
1051
|
-
|
|
1052
|
-
const {
|
|
1053
|
-
return
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
dataset: snapshot.docs
|
|
801
|
+
function shapeValueSchema(args) {
|
|
802
|
+
const {shape: shape, leaf: leaf} = args;
|
|
803
|
+
if (shape.type === "object") return objectSchema({
|
|
804
|
+
fields: shape.fields ?? [],
|
|
805
|
+
leaf: leaf
|
|
1057
806
|
});
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
const result = await runGroq({
|
|
1068
|
-
groq: groq2,
|
|
1069
|
-
params: args.params,
|
|
1070
|
-
snapshot: args.snapshot
|
|
1071
|
-
}), outcome = groqConditionDescribe.conditionOutcome(result);
|
|
1072
|
-
out[name] = outcome === "unevaluable" ? null : outcome === "satisfied";
|
|
1073
|
-
}
|
|
1074
|
-
return out;
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
async function runGroq({groq: groq2, params: params, snapshot: snapshot}) {
|
|
1078
|
-
return groqConditionDescribe.runGroq({
|
|
1079
|
-
groq: groq2,
|
|
1080
|
-
params: params,
|
|
1081
|
-
dataset: snapshot.docs
|
|
807
|
+
if (shape.type === "array") return v__namespace.array(objectSchema({
|
|
808
|
+
fields: shape.of ?? [],
|
|
809
|
+
leaf: leaf
|
|
810
|
+
}));
|
|
811
|
+
const schema = leaf[shape.type] ?? v__namespace.any();
|
|
812
|
+
return constrainedScalarSchema({
|
|
813
|
+
schema: schema,
|
|
814
|
+
entryType: shape.type,
|
|
815
|
+
...shape
|
|
1082
816
|
});
|
|
1083
817
|
}
|
|
1084
818
|
|
|
1085
|
-
function
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
try {
|
|
1089
|
-
tree = groqJs.parse(groq2);
|
|
1090
|
-
} catch (err) {
|
|
1091
|
-
issues.push(errorMessage(err));
|
|
1092
|
-
}
|
|
1093
|
-
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."),
|
|
1094
|
-
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(", "));
|
|
1095
|
-
return issues;
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
function conditionParameterNames(groq2) {
|
|
1099
|
-
const read = /* @__PURE__ */ new Set;
|
|
1100
|
-
return walkAstNodes(tryParseGroq(groq2), node => {
|
|
1101
|
-
node.type === "Parameter" && typeof node.name == "string" && read.add(node.name);
|
|
1102
|
-
}), read;
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
function conditionFieldReadNames(groq2) {
|
|
1106
|
-
return new Set(conditionFieldReads(groq2).map(read => read.name));
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
function conditionFieldReads(groq2) {
|
|
1110
|
-
const reads = /* @__PURE__ */ new Map;
|
|
1111
|
-
return collectFieldReads(tryParseGroq(groq2), reads), [ ...reads.values() ];
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
function collectFieldReads(node, reads) {
|
|
1115
|
-
if (Array.isArray(node)) {
|
|
1116
|
-
for (const item of node) collectFieldReads(item, reads);
|
|
1117
|
-
return;
|
|
1118
|
-
}
|
|
1119
|
-
if (typeof node != "object" || node === null) return;
|
|
1120
|
-
const chain = fieldsAttributeChain(node);
|
|
1121
|
-
if (chain !== void 0) {
|
|
1122
|
-
const [name, ...tail] = chain, path = tail.length > 0 ? tail.join(".") : void 0;
|
|
1123
|
-
reads.set(`${name}.${path ?? ""}`, {
|
|
1124
|
-
name: name,
|
|
1125
|
-
path: path
|
|
1126
|
-
});
|
|
1127
|
-
return;
|
|
1128
|
-
}
|
|
1129
|
-
for (const value of Object.values(node)) collectFieldReads(value, reads);
|
|
819
|
+
function scalarMeasurement(entryType, value) {
|
|
820
|
+
if ((entryType === "number" || entryType === "progress") && typeof value == "number") return value;
|
|
821
|
+
if ((entryType === "string" || entryType === "text") && typeof value == "string") return value.length;
|
|
1130
822
|
}
|
|
1131
823
|
|
|
1132
|
-
function
|
|
1133
|
-
const
|
|
1134
|
-
|
|
1135
|
-
for (;typeof current == "object" && current !== null; ) {
|
|
1136
|
-
const typed = current;
|
|
1137
|
-
if (typed.type !== "AccessAttribute" || typeof typed.name != "string") return;
|
|
1138
|
-
names.unshift(typed.name);
|
|
1139
|
-
const base = typed.base;
|
|
1140
|
-
if (base?.type === "Parameter" && base.name === "fields") return names;
|
|
1141
|
-
current = base;
|
|
1142
|
-
}
|
|
824
|
+
function scalarBoundIssue(args) {
|
|
825
|
+
const {entryType: entryType, measured: measured, bound: bound, limit: limit} = args;
|
|
826
|
+
return bound === void 0 || limit === "min" && measured >= bound || limit === "max" && measured <= bound ? void 0 : `${entryType === "number" || entryType === "progress" ? "" : "length "}must be ${limit === "min" ? "greater than or equal to" : "less than or equal to"} ${bound}`;
|
|
1143
827
|
}
|
|
1144
828
|
|
|
1145
|
-
function
|
|
1146
|
-
const
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
829
|
+
function scalarValidationIssues(args) {
|
|
830
|
+
const {entryType: entryType, validation: validation, value: value} = args;
|
|
831
|
+
if (validation === void 0 || value === null || value === void 0) return;
|
|
832
|
+
const measured = scalarMeasurement(entryType, value);
|
|
833
|
+
if (measured === void 0) return;
|
|
834
|
+
const issues = [ scalarBoundIssue({
|
|
835
|
+
entryType: entryType,
|
|
836
|
+
measured: measured,
|
|
837
|
+
bound: validation.min,
|
|
838
|
+
limit: "min"
|
|
839
|
+
}), scalarBoundIssue({
|
|
840
|
+
entryType: entryType,
|
|
841
|
+
measured: measured,
|
|
842
|
+
bound: validation.max,
|
|
843
|
+
limit: "max"
|
|
844
|
+
}) ].filter(issue => issue !== void 0);
|
|
845
|
+
return issues.length === 0 ? void 0 : issues;
|
|
1155
846
|
}
|
|
1156
847
|
|
|
1157
|
-
function
|
|
1158
|
-
|
|
848
|
+
function constrainedScalarSchema(args) {
|
|
849
|
+
const {schema: schema, entryType: entryType, options: options, validation: validation} = args;
|
|
850
|
+
return options === void 0 && validation === void 0 ? schema : v__namespace.pipe(schema, v__namespace.check(value => choiceValueIssues(options, value) === void 0 && scalarValidationIssues({
|
|
851
|
+
entryType: entryType,
|
|
852
|
+
validation: validation,
|
|
853
|
+
value: value
|
|
854
|
+
}) === void 0, issue => [ ...choiceValueIssues(options, issue.input) ?? [], ...scalarValidationIssues({
|
|
855
|
+
entryType: entryType,
|
|
856
|
+
validation: validation,
|
|
857
|
+
value: issue.input
|
|
858
|
+
}) ?? [] ].join("; ")));
|
|
1159
859
|
}
|
|
1160
860
|
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
function nodeReadsRoot(node, depth) {
|
|
1170
|
-
if (Array.isArray(node)) return node.some(item => nodeReadsRoot(item, depth));
|
|
1171
|
-
if (typeof node != "object" || node === null) return !1;
|
|
1172
|
-
const typed = node;
|
|
1173
|
-
if (depth === 0 && typed.type === "This" || depth === 0 && typed.type === "AccessAttribute" && typed.base === void 0) return !0;
|
|
1174
|
-
if (typed.type === "Parent") {
|
|
1175
|
-
const climb = typeof typed.n == "number" ? typed.n : 1;
|
|
1176
|
-
return depth - climb <= 0;
|
|
1177
|
-
}
|
|
1178
|
-
const scopedChild = typeof typed.type == "string" ? SCOPED_CHILD[typed.type] : void 0;
|
|
1179
|
-
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));
|
|
861
|
+
function objectSchema(args) {
|
|
862
|
+
const {fields: fields, leaf: leaf} = args, entries = /* @__PURE__ */ Object.create(null);
|
|
863
|
+
for (const f of fields) entries[f.name] = v__namespace.optional(shapeValueSchema({
|
|
864
|
+
shape: f,
|
|
865
|
+
leaf: leaf
|
|
866
|
+
}));
|
|
867
|
+
return v__namespace.looseObject(entries);
|
|
1180
868
|
}
|
|
1181
869
|
|
|
1182
|
-
function
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
}
|
|
870
|
+
function wholeValueSchema(args) {
|
|
871
|
+
const {entryType: entryType, shape: shape, leaf: leaf} = args;
|
|
872
|
+
return entryType === "object" ? v__namespace.union([ v__namespace.null(), objectSchema({
|
|
873
|
+
fields: shape.fields ?? [],
|
|
874
|
+
leaf: leaf
|
|
875
|
+
}) ]) : entryType === "array" ? v__namespace.array(objectSchema({
|
|
876
|
+
fields: shape.of ?? [],
|
|
877
|
+
leaf: leaf
|
|
878
|
+
})) : leaf[entryType];
|
|
1188
879
|
}
|
|
1189
880
|
|
|
1190
|
-
function
|
|
1191
|
-
if (
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
}
|
|
1195
|
-
if (
|
|
1196
|
-
|
|
1197
|
-
for (const value of Object.values(node)) walkAstNodes(value, visit);
|
|
1198
|
-
}
|
|
881
|
+
function appendItemSchema(entryType, shape) {
|
|
882
|
+
if (entryType === "array") return objectSchema({
|
|
883
|
+
fields: shape.of ?? [],
|
|
884
|
+
leaf: valueSchemas
|
|
885
|
+
});
|
|
886
|
+
if (entryType === "doc.refs") return GdrShape;
|
|
887
|
+
if (entryType === "assignee" || entryType === "assignees") return AssigneeWriteShape;
|
|
1199
888
|
}
|
|
1200
889
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
890
|
+
function rejectedRefTypes(args) {
|
|
891
|
+
const {entryType: entryType, types: types, value: value} = args;
|
|
892
|
+
if (types === void 0 || value === null || value === void 0) return [];
|
|
893
|
+
if (!refKindAcceptsTypes(entryType)) return [];
|
|
894
|
+
let items = [ value ];
|
|
895
|
+
return entryType === "doc.refs" && (items = Array.isArray(value) ? value : []),
|
|
896
|
+
[ ...new Set(items.map(gdrTypeOf).filter(t => t !== void 0 && !types.includes(t))) ];
|
|
1205
897
|
}
|
|
1206
898
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
899
|
+
function refTypeIssues(args) {
|
|
900
|
+
const rejected = rejectedRefTypes(args);
|
|
901
|
+
if (rejected.length === 0) return;
|
|
902
|
+
const accepts = (args.types ?? []).map(t => `"${t}"`).join(", ");
|
|
903
|
+
return rejected.map(t => `document type "${t}" is not accepted — this entry accepts ${accepts} (a GDR's \`type\` names the target document's schema type)`);
|
|
1211
904
|
}
|
|
1212
905
|
|
|
1213
|
-
function
|
|
1214
|
-
|
|
906
|
+
function gdrTypeOf(item) {
|
|
907
|
+
if (typeof item != "object" || item === null) return;
|
|
908
|
+
const t = item.type;
|
|
909
|
+
return typeof t == "string" ? t : void 0;
|
|
1215
910
|
}
|
|
1216
911
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
function releaseDocId(releaseName) {
|
|
1220
|
-
return `_.releases.${releaseName}`;
|
|
912
|
+
function parseFieldValue(args) {
|
|
913
|
+
return checkValueAgainst(args, valueSchemas);
|
|
1221
914
|
}
|
|
1222
915
|
|
|
1223
|
-
function
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
916
|
+
function checkValueAgainst(args, leaf) {
|
|
917
|
+
const schema = wholeValueSchema({
|
|
918
|
+
entryType: args.entryType,
|
|
919
|
+
shape: args,
|
|
920
|
+
leaf: leaf
|
|
921
|
+
});
|
|
922
|
+
if (schema === void 0) return {
|
|
923
|
+
issues: [ `unknown field entry type ${args.entryType}` ]
|
|
924
|
+
};
|
|
925
|
+
const result = v__namespace.safeParse(schema, args.value);
|
|
926
|
+
if (!result.success) return {
|
|
927
|
+
issues: formatIssues(result.issues)
|
|
928
|
+
};
|
|
929
|
+
const postIssues = refTypeIssues({
|
|
930
|
+
entryType: args.entryType,
|
|
931
|
+
types: args.types,
|
|
932
|
+
value: args.value
|
|
933
|
+
}) ?? choiceValueIssues(args.options, args.value) ?? scalarValidationIssues({
|
|
934
|
+
entryType: args.entryType,
|
|
935
|
+
validation: args.validation,
|
|
936
|
+
value: args.value
|
|
937
|
+
}) ?? assignmentValueIssues(args, result.output);
|
|
938
|
+
return postIssues !== void 0 ? {
|
|
939
|
+
issues: postIssues
|
|
940
|
+
} : {
|
|
941
|
+
output: result.output
|
|
1229
942
|
};
|
|
1230
943
|
}
|
|
1231
944
|
|
|
1232
|
-
function
|
|
1233
|
-
return
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
function isSingleDocRefKind(kind) {
|
|
1237
|
-
return kind === "doc.ref" || kind === "subject";
|
|
945
|
+
function assignmentIdentity(assignee) {
|
|
946
|
+
return assignee.type === "user" ? `user:${assignee.id}` : `role:${assignee.role}`;
|
|
1238
947
|
}
|
|
1239
948
|
|
|
1240
|
-
function
|
|
1241
|
-
|
|
949
|
+
function assignmentCandidate(value) {
|
|
950
|
+
const parsed = v__namespace.safeParse(AssigneeShape, value);
|
|
951
|
+
return parsed.success ? parsed.output : void 0;
|
|
1242
952
|
}
|
|
1243
953
|
|
|
1244
|
-
function
|
|
1245
|
-
|
|
954
|
+
function assignmentValueIssues(args, value) {
|
|
955
|
+
const retained = previousAssignmentIdentities(args), issues = [];
|
|
956
|
+
return visitAssignmentEntries({
|
|
957
|
+
entryType: args.entryType,
|
|
958
|
+
value: value,
|
|
959
|
+
roles: args.roles,
|
|
960
|
+
fields: args.fields,
|
|
961
|
+
of: args.of,
|
|
962
|
+
path: "$"
|
|
963
|
+
}, ({assignee: assignee, roles: roles, path: path}) => {
|
|
964
|
+
consumeRetainedAssignment({
|
|
965
|
+
retained: retained,
|
|
966
|
+
path: path,
|
|
967
|
+
identity: assignmentIdentity(assignee)
|
|
968
|
+
}) || issues.push(...assignmentCandidateIssues({
|
|
969
|
+
roles: roles,
|
|
970
|
+
memberRoles: args.memberRoles,
|
|
971
|
+
roleAliases: args.roleAliases
|
|
972
|
+
}, assignee));
|
|
973
|
+
}), issues.length === 0 ? void 0 : issues;
|
|
1246
974
|
}
|
|
1247
975
|
|
|
1248
|
-
function
|
|
1249
|
-
|
|
976
|
+
function consumeRetainedAssignment(args) {
|
|
977
|
+
const identities = args.retained.get(args.path), remaining = identities?.get(args.identity) ?? 0;
|
|
978
|
+
return remaining === 0 || identities === void 0 ? !1 : (remaining === 1 ? identities.delete(args.identity) : identities.set(args.identity, remaining - 1),
|
|
979
|
+
!0);
|
|
1250
980
|
}
|
|
1251
981
|
|
|
1252
|
-
function
|
|
1253
|
-
|
|
982
|
+
function previousAssignmentIdentities(args) {
|
|
983
|
+
const retained = /* @__PURE__ */ new Map;
|
|
984
|
+
if (args.previousValue === void 0) return retained;
|
|
985
|
+
const schema = wholeValueSchema({
|
|
986
|
+
entryType: args.entryType,
|
|
987
|
+
shape: args,
|
|
988
|
+
leaf: valueSchemas
|
|
989
|
+
});
|
|
990
|
+
if (schema === void 0) return retained;
|
|
991
|
+
const previous = v__namespace.safeParse(schema, args.previousValue);
|
|
992
|
+
return previous.success && visitAssignmentEntries({
|
|
993
|
+
entryType: args.entryType,
|
|
994
|
+
value: previous.output,
|
|
995
|
+
roles: args.roles,
|
|
996
|
+
fields: args.fields,
|
|
997
|
+
of: args.of,
|
|
998
|
+
path: "$"
|
|
999
|
+
}, ({assignee: assignee, path: path}) => {
|
|
1000
|
+
const identities = retained.get(path) ?? /* @__PURE__ */ new Map, identity = assignmentIdentity(assignee);
|
|
1001
|
+
identities.set(identity, (identities.get(identity) ?? 0) + 1), retained.set(path, identities);
|
|
1002
|
+
}), retained;
|
|
1254
1003
|
}
|
|
1255
1004
|
|
|
1256
|
-
function
|
|
1257
|
-
if (
|
|
1258
|
-
|
|
1259
|
-
|
|
1005
|
+
function visitAssignmentEntries(args, visit) {
|
|
1006
|
+
if (assignmentKindAcceptsRoles(args.entryType)) {
|
|
1007
|
+
visitAssignmentCandidates(args, visit);
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
if (args.entryType === "object") {
|
|
1011
|
+
visitAssignmentFields({
|
|
1012
|
+
fields: args.fields,
|
|
1013
|
+
value: args.value,
|
|
1014
|
+
path: args.path
|
|
1015
|
+
}, visit);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
if (!(args.entryType !== "array" || !Array.isArray(args.value))) for (const row of args.value) visitAssignmentFields({
|
|
1019
|
+
fields: args.of,
|
|
1020
|
+
value: row,
|
|
1021
|
+
path: `${args.path}[]`
|
|
1022
|
+
}, visit);
|
|
1260
1023
|
}
|
|
1261
1024
|
|
|
1262
|
-
function
|
|
1263
|
-
if (
|
|
1025
|
+
function visitAssignmentCandidates(args, visit) {
|
|
1026
|
+
if (args.roles === void 0) return;
|
|
1027
|
+
const indexedValues = Array.isArray(args.value) ? args.value.map(value => ({
|
|
1028
|
+
value: value,
|
|
1029
|
+
path: args.path
|
|
1030
|
+
})) : [ {
|
|
1031
|
+
value: args.value,
|
|
1032
|
+
path: args.path
|
|
1033
|
+
} ];
|
|
1034
|
+
for (const {value: value, path: path} of indexedValues) {
|
|
1035
|
+
const assignee = assignmentCandidate(value);
|
|
1036
|
+
assignee !== void 0 && visit({
|
|
1037
|
+
assignee: assignee,
|
|
1038
|
+
roles: args.roles,
|
|
1039
|
+
path: path
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1264
1042
|
}
|
|
1265
1043
|
|
|
1266
|
-
function
|
|
1267
|
-
const
|
|
1268
|
-
|
|
1044
|
+
function visitAssignmentFields(args, visit) {
|
|
1045
|
+
if (!(args.fields === void 0 || !isRecord(args.value))) for (const field of args.fields) visitAssignmentShape({
|
|
1046
|
+
shape: field,
|
|
1047
|
+
value: args.value[field.name],
|
|
1048
|
+
path: `${args.path}.${field.name}`
|
|
1049
|
+
}, visit);
|
|
1269
1050
|
}
|
|
1270
1051
|
|
|
1271
|
-
function
|
|
1272
|
-
|
|
1273
|
-
|
|
1052
|
+
function visitAssignmentShape(args, visit) {
|
|
1053
|
+
visitAssignmentEntries({
|
|
1054
|
+
entryType: args.shape.type,
|
|
1055
|
+
value: args.value,
|
|
1056
|
+
roles: args.shape.roles,
|
|
1057
|
+
fields: args.shape.fields,
|
|
1058
|
+
of: args.shape.of,
|
|
1059
|
+
path: args.path
|
|
1060
|
+
}, visit);
|
|
1274
1061
|
}
|
|
1275
1062
|
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
label: "the workflow's fields",
|
|
1285
|
-
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` (or `subject`) 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."
|
|
1286
|
-
}, {
|
|
1287
|
-
name: "parent",
|
|
1288
|
-
binding: "always",
|
|
1289
|
-
label: "the parent workflow",
|
|
1290
|
-
description: "The parent instance's GDR URI, `null` on a root instance."
|
|
1291
|
-
}, {
|
|
1292
|
-
name: "ancestors",
|
|
1293
|
-
binding: "always",
|
|
1294
|
-
label: "the workflow's ancestors",
|
|
1295
|
-
description: "GDR URIs of the ancestor chain, root first."
|
|
1296
|
-
}, {
|
|
1297
|
-
name: "stage",
|
|
1298
|
-
binding: "always",
|
|
1299
|
-
label: "the current stage",
|
|
1300
|
-
description: "The current stage's name."
|
|
1301
|
-
}, {
|
|
1302
|
-
name: "now",
|
|
1303
|
-
binding: "always",
|
|
1304
|
-
label: "the current time",
|
|
1305
|
-
description: "The ISO clock reading shared by every condition in one evaluation pass, so they agree on the time."
|
|
1306
|
-
}, {
|
|
1307
|
-
name: "context",
|
|
1308
|
-
binding: "always",
|
|
1309
|
-
label: "start-time context",
|
|
1310
|
-
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."
|
|
1311
|
-
}, {
|
|
1312
|
-
name: "effects",
|
|
1313
|
-
binding: "always",
|
|
1314
|
-
label: "automation outputs",
|
|
1315
|
-
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."
|
|
1316
|
-
}, {
|
|
1317
|
-
name: "effectStatus",
|
|
1318
|
-
binding: "always",
|
|
1319
|
-
label: "automation status",
|
|
1320
|
-
description: "Effect name → `'done'` | `'failed'` of the latest completed run queued during the current stage entry; absent until the effect drains for this entry. Re-entry-safe: runs queued under a prior entry never count, so `$effectStatus['<effect>'] == 'done'` (or `defined($effectStatus['<effect>'])` for settled-either-way) waits for the fresh run."
|
|
1321
|
-
}, {
|
|
1322
|
-
name: "activities",
|
|
1323
|
-
binding: "always",
|
|
1324
|
-
label: "this stage's activities",
|
|
1325
|
-
description: "The current stage's activity rows, statuses included."
|
|
1326
|
-
}, {
|
|
1327
|
-
name: "allActivitiesDone",
|
|
1328
|
-
binding: "always",
|
|
1329
|
-
label: "all activities in this stage are finished",
|
|
1330
|
-
description: "Every current-stage activity is `done` or `skipped` — the default transition gate."
|
|
1331
|
-
}, {
|
|
1332
|
-
name: "anyActivityFailed",
|
|
1333
|
-
binding: "always",
|
|
1334
|
-
label: "an activity in this stage has failed",
|
|
1335
|
-
description: "Some current-stage activity is `failed`."
|
|
1336
|
-
}, {
|
|
1337
|
-
name: "actor",
|
|
1338
|
-
binding: "caller",
|
|
1339
|
-
label: "you",
|
|
1340
|
-
description: "The acting identity (id + roles); `undefined` when no caller rides the evaluation. Never holds a value in the cascade gates (deploy-rejected there)."
|
|
1341
|
-
}, {
|
|
1342
|
-
name: "assigned",
|
|
1343
|
-
binding: "caller",
|
|
1344
|
-
label: "you are assigned to this activity",
|
|
1345
|
-
description: "Whether the caller matches the activity's assignees-kind field entry (by user id, or by a role under the definition's `roleAliases`); `false` outside an activity context. Constant `false` in the cascade gates (deploy-rejected there)."
|
|
1346
|
-
}, {
|
|
1347
|
-
name: "can",
|
|
1348
|
-
binding: "caller",
|
|
1349
|
-
label: "your permissions",
|
|
1350
|
-
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."
|
|
1351
|
-
}, {
|
|
1352
|
-
name: "attributes",
|
|
1353
|
-
binding: "caller",
|
|
1354
|
-
label: "your attributes",
|
|
1355
|
-
description: "Advisory org-level User Attributes for the caller (Enterprise — same values as lake `user::attributes()`), keyed by attribute name with each active scalar or array value. `undefined` on expected HTTP absence; unexpected fetch failures throw; empty page binds `{}`. Bound wherever grants ride the evaluation (same sites as `$can`). Soft-gate paths fetch at most 100 attributes (no further pages) and warn when the envelope reports `hasMore: true` (partial bag still binds). Not a security boundary — the Content Lake remains the only enforcement point."
|
|
1356
|
-
}, {
|
|
1357
|
-
name: "row",
|
|
1358
|
-
binding: "spawn",
|
|
1359
|
-
label: "the spawned row",
|
|
1360
|
-
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."
|
|
1361
|
-
}, {
|
|
1362
|
-
name: "params",
|
|
1363
|
-
binding: "caller",
|
|
1364
|
-
label: "the action's arguments",
|
|
1365
|
-
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."
|
|
1366
|
-
}, {
|
|
1367
|
-
name: "subworkflows",
|
|
1368
|
-
binding: "always",
|
|
1369
|
-
label: "the spawned subworkflows",
|
|
1370
|
-
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`."
|
|
1371
|
-
} ], 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 = [ {
|
|
1372
|
-
name: "tag",
|
|
1373
|
-
label: "this engine tag",
|
|
1374
|
-
description: "The engine's tag partition — scope `*[...]` instance scans with `tag == $tag`."
|
|
1375
|
-
}, {
|
|
1376
|
-
name: "definition",
|
|
1377
|
-
label: "this workflow definition",
|
|
1378
|
-
description: "The `name` of the definition under evaluation (its own start block binds it)."
|
|
1379
|
-
}, {
|
|
1380
|
-
name: "now",
|
|
1381
|
-
label: "the current time",
|
|
1382
|
-
description: "The ISO clock reading of the evaluating engine."
|
|
1383
|
-
} ], START_REQUIREMENT_VARS = [ ...START_FILTER_VARS, {
|
|
1384
|
-
name: "fields",
|
|
1385
|
-
label: "the start's input fields",
|
|
1386
|
-
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` or `subject` included (nothing hydrates at the gate or the pre-flight). Pathed reads are deploy-checked against these envelope shapes."
|
|
1387
|
-
} ], GUARD_PREDICATE_VARS = [ {
|
|
1388
|
-
name: "document",
|
|
1389
|
-
description: "The attempted mutation images, exposed as `document.before` and `document.after`."
|
|
1390
|
-
}, {
|
|
1391
|
-
name: "guard",
|
|
1392
|
-
description: "The guard document itself (its `metadata` carries deploy-time resolved values)."
|
|
1393
|
-
}, {
|
|
1394
|
-
name: "mutation",
|
|
1395
|
-
description: "The attempted mutation — `mutation.action` is the write kind being gated."
|
|
1396
|
-
} ], NonEmptyString = v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1, "must be a non-empty string"));
|
|
1397
|
-
|
|
1398
|
-
function isParseableInstant(value) {
|
|
1399
|
-
return typeof value == "string" && !Number.isNaN(Date.parse(value));
|
|
1063
|
+
function assignmentCandidateIssues(args, assignee) {
|
|
1064
|
+
if (assignee.type === "role") return collectiveRoleIssues(args, assignee.role);
|
|
1065
|
+
const held = args.memberRoles?.[assignee.id];
|
|
1066
|
+
return held === void 0 ? args.memberRoles === void 0 ? [] : [ `user "${assignee.id}" has no available project membership for role eligibility` ] : args.roles.some(required => actorFulfillsRole({
|
|
1067
|
+
actorRoles: held,
|
|
1068
|
+
required: required,
|
|
1069
|
+
aliases: args.roleAliases
|
|
1070
|
+
})) ? [] : [ `user "${assignee.id}" does not fulfill any eligible role: ${formatEligibleRoles(args.roles)}` ];
|
|
1400
1071
|
}
|
|
1401
1072
|
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
function tolerantObject() {
|
|
1405
|
-
return (entries, ..._exact) => v__namespace.looseObject(entries);
|
|
1073
|
+
function collectiveRoleIssues(args, role) {
|
|
1074
|
+
return args.roles.includes(role) ? [] : [ `collective role "${role}" is not eligible — this entry accepts ${formatEligibleRoles(args.roles)}` ];
|
|
1406
1075
|
}
|
|
1407
1076
|
|
|
1408
|
-
function
|
|
1409
|
-
return (
|
|
1077
|
+
function formatEligibleRoles(roles) {
|
|
1078
|
+
return roles.map(role => `"${role}"`).join(", ");
|
|
1410
1079
|
}
|
|
1411
1080
|
|
|
1412
|
-
function
|
|
1413
|
-
return
|
|
1081
|
+
function issuesOf(check) {
|
|
1082
|
+
return "issues" in check ? check.issues : void 0;
|
|
1414
1083
|
}
|
|
1415
1084
|
|
|
1416
|
-
function
|
|
1417
|
-
|
|
1418
|
-
if (
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
}
|
|
1085
|
+
function validateFieldValue(args) {
|
|
1086
|
+
const check = checkValueAgainst(args, valueSchemas);
|
|
1087
|
+
if ("issues" in check) throw new FieldValueShapeError({
|
|
1088
|
+
entryType: args.entryType,
|
|
1089
|
+
entryName: args.entryName,
|
|
1090
|
+
issues: check.issues,
|
|
1091
|
+
mode: "value"
|
|
1092
|
+
});
|
|
1093
|
+
return check.output;
|
|
1426
1094
|
}
|
|
1427
1095
|
|
|
1428
|
-
const
|
|
1429
|
-
union: schema.options.map(option => walkSchemaShape(option, path))
|
|
1430
|
-
}), unwrapWalker = (schema, path) => walkSchemaShape(schema.wrapped, path), CONTAINER_WALKERS = {
|
|
1431
|
-
strict_object: objectWalker,
|
|
1432
|
-
loose_object: objectWalker,
|
|
1433
|
-
object: objectWalker,
|
|
1434
|
-
array: (schema, path) => ({
|
|
1435
|
-
array: walkSchemaShape(schema.item, path)
|
|
1436
|
-
}),
|
|
1437
|
-
record: (schema, path) => ({
|
|
1438
|
-
record: walkSchemaShape(schema.value, path)
|
|
1439
|
-
}),
|
|
1440
|
-
union: unionWalker,
|
|
1441
|
-
variant: unionWalker,
|
|
1442
|
-
lazy: (schema, path) => walkSchemaShape(schema.getter(void 0), path),
|
|
1443
|
-
exact_optional: unwrapWalker,
|
|
1444
|
-
optional: unwrapWalker,
|
|
1445
|
-
nullable: unwrapWalker
|
|
1446
|
-
};
|
|
1096
|
+
const BARE_ID_SOURCE = "[A-Za-z0-9_][A-Za-z0-9._-]{0,127}", BARE_ID_RE = new RegExp(`^${BARE_ID_SOURCE}$`), ALIAS_REF_RE = new RegExp(`^@${RESOURCE_ALIAS_NAME_SOURCE}:${BARE_ID_SOURCE}$`);
|
|
1447
1097
|
|
|
1448
|
-
function
|
|
1449
|
-
return
|
|
1098
|
+
function isBareDocumentId(id) {
|
|
1099
|
+
return BARE_ID_RE.test(id) && !id.includes("..");
|
|
1450
1100
|
}
|
|
1451
1101
|
|
|
1452
|
-
function
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
out[optional ? `${key}?` : key] = walkSchemaShape(wrapped, path);
|
|
1102
|
+
function isAuthoringRefId(id) {
|
|
1103
|
+
if (id.startsWith("@")) {
|
|
1104
|
+
const separator = id.indexOf(":");
|
|
1105
|
+
return ALIAS_REF_RE.test(id) && isBareDocumentId(id.slice(separator + 1));
|
|
1457
1106
|
}
|
|
1458
|
-
return
|
|
1107
|
+
return id.includes(":") ? isGdrUri(id) : isBareDocumentId(id);
|
|
1459
1108
|
}
|
|
1460
1109
|
|
|
1461
|
-
function
|
|
1462
|
-
|
|
1463
|
-
const kind = entry.type;
|
|
1464
|
-
return kind === "exact_optional" || kind === "optional";
|
|
1110
|
+
function isBareSeedId(id) {
|
|
1111
|
+
return isBareDocumentId(id);
|
|
1465
1112
|
}
|
|
1466
1113
|
|
|
1467
|
-
const
|
|
1114
|
+
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({
|
|
1115
|
+
id: AuthoringRefId,
|
|
1116
|
+
type: NonEmptyString$1
|
|
1117
|
+
}), seedValueSchemas = {
|
|
1118
|
+
...valueSchemas,
|
|
1119
|
+
"doc.ref": v__namespace.union([ v__namespace.null(), AuthoringGdrShape ]),
|
|
1120
|
+
"doc.refs": v__namespace.array(AuthoringGdrShape),
|
|
1121
|
+
subject: v__namespace.union([ v__namespace.null(), AuthoringGdrShape ]),
|
|
1122
|
+
"release.ref": v__namespace.union([ v__namespace.null(), v__namespace.looseObject({
|
|
1123
|
+
id: AuthoringRefId,
|
|
1124
|
+
type: v__namespace.literal("system.release"),
|
|
1125
|
+
releaseName: NonEmptyString$1
|
|
1126
|
+
}) ])
|
|
1127
|
+
};
|
|
1468
1128
|
|
|
1469
|
-
function
|
|
1470
|
-
|
|
1471
|
-
if (schema.type === "literal") return `literal ${String(schema.literal)}`;
|
|
1472
|
-
if (schema.type === "custom") return typeof schema.message == "string" ? `custom(${schema.message})` : "custom";
|
|
1473
|
-
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`);
|
|
1474
|
-
return String(schema.type);
|
|
1129
|
+
function checkLiteralSeed(args) {
|
|
1130
|
+
return args.value === null ? [ "a literal seed cannot be null — omit `initialValue` to start the field empty" ] : issuesOf(checkValueAgainst(args, seedValueSchemas));
|
|
1475
1131
|
}
|
|
1476
1132
|
|
|
1477
|
-
function
|
|
1478
|
-
const
|
|
1479
|
-
|
|
1133
|
+
function validateFieldAppendItem(args) {
|
|
1134
|
+
const schema = appendItemSchema(args.entryType, args);
|
|
1135
|
+
if (schema === void 0) throw new FieldValueShapeError({
|
|
1136
|
+
entryType: args.entryType,
|
|
1137
|
+
entryName: args.entryName,
|
|
1138
|
+
issues: [ `field entry type ${args.entryType} does not support append` ],
|
|
1139
|
+
mode: "item"
|
|
1140
|
+
});
|
|
1141
|
+
const result = v__namespace.safeParse(schema, args.item);
|
|
1142
|
+
if (!result.success) throw new FieldValueShapeError({
|
|
1143
|
+
entryType: args.entryType,
|
|
1144
|
+
entryName: args.entryName,
|
|
1145
|
+
issues: formatIssues(result.issues),
|
|
1146
|
+
mode: "item"
|
|
1147
|
+
});
|
|
1148
|
+
const assignmentArgs = {
|
|
1149
|
+
entryType: appendAssignmentEntryType(args.entryType),
|
|
1150
|
+
value: result.output,
|
|
1151
|
+
roles: args.roles,
|
|
1152
|
+
fields: args.entryType === "array" ? args.of : void 0,
|
|
1153
|
+
memberRoles: args.memberRoles,
|
|
1154
|
+
roleAliases: args.roleAliases
|
|
1155
|
+
}, assignmentIssues = assignmentValueIssues(assignmentArgs, result.output);
|
|
1156
|
+
if (assignmentIssues !== void 0) throw new FieldValueShapeError({
|
|
1157
|
+
entryType: args.entryType,
|
|
1158
|
+
entryName: args.entryName,
|
|
1159
|
+
issues: assignmentIssues,
|
|
1160
|
+
mode: "item"
|
|
1161
|
+
});
|
|
1162
|
+
const typeIssues = refTypeIssues({
|
|
1163
|
+
entryType: args.entryType,
|
|
1164
|
+
types: args.types,
|
|
1165
|
+
value: [ args.item ]
|
|
1166
|
+
});
|
|
1167
|
+
if (typeIssues !== void 0) throw new FieldValueShapeError({
|
|
1168
|
+
entryType: args.entryType,
|
|
1169
|
+
entryName: args.entryName,
|
|
1170
|
+
issues: typeIssues,
|
|
1171
|
+
mode: "item"
|
|
1172
|
+
});
|
|
1173
|
+
return result.output;
|
|
1480
1174
|
}
|
|
1481
1175
|
|
|
1482
|
-
function
|
|
1483
|
-
return
|
|
1484
|
-
path: issue.path ? issue.path.map(item => item.key) : [],
|
|
1485
|
-
message: issue.message
|
|
1486
|
-
}));
|
|
1176
|
+
function appendAssignmentEntryType(entryType) {
|
|
1177
|
+
return entryType === "assignee" || entryType === "assignees" ? "assignee" : entryType === "array" ? "object" : entryType;
|
|
1487
1178
|
}
|
|
1488
1179
|
|
|
1489
|
-
function
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1180
|
+
function formatIssues(issues, formatMessage = issue => issue.message) {
|
|
1181
|
+
const formatOne = (issue, prefix) => {
|
|
1182
|
+
const keys = [ ...prefix, ...issue.path?.map(p => p.key) ?? [] ], sub = issue.issues?.filter(candidate => candidate.expected !== "null");
|
|
1183
|
+
return sub !== void 0 && sub.length > 0 ? sub.flatMap(candidate => formatOne(candidate, keys)) : [ `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${formatMessage(issue)}` ];
|
|
1184
|
+
};
|
|
1185
|
+
return issues.flatMap(issue => formatOne(issue, []));
|
|
1493
1186
|
}
|
|
1494
1187
|
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
constructor(args) {
|
|
1500
|
-
super("persisted-doc-shape", formatValidationError(`Persisted ${args.documentType} document "${args.documentId}"`, args.issues)),
|
|
1501
|
-
this.name = "PersistedDocShapeError", this.documentId = args.documentId, this.documentType = args.documentType,
|
|
1502
|
-
this.issues = args.issues;
|
|
1503
|
-
}
|
|
1188
|
+
const ACTIVITY_STATUSES = groqConditionDescribe._exhaustiveOptions()([ "active", "done", "skipped", "failed" ]), TERMINAL_ACTIVITY_STATUSES = groqConditionDescribe._exhaustiveOptions()([ "done", "skipped", "failed" ]);
|
|
1189
|
+
|
|
1190
|
+
function isTerminalActivityStatus(status) {
|
|
1191
|
+
return TERMINAL_ACTIVITY_STATUSES.includes(status);
|
|
1504
1192
|
}
|
|
1505
1193
|
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
documentType: args.docType,
|
|
1511
|
-
issues: issuesFromValibot(result.issues)
|
|
1512
|
-
});
|
|
1513
|
-
return result.output;
|
|
1194
|
+
const SIGNAL_SEMANTICS = [ "signal.positive", "signal.caution", "signal.critical" ], DECISION_SEMANTICS = [ "decision.accept", "decision.decline" ], ACTION_SEMANTICS = [ ...DECISION_SEMANTICS, ...SIGNAL_SEMANTICS ], FIELD_SCOPES = groqConditionDescribe._exhaustiveOptions()([ "workflow", "stage", "activity" ]), DOCUMENT_VALUE_PERMISSIONS = [ "create", "manage", "read", "update" ], LAKE_MUTATION_GUARD_ACTIONS = groqConditionDescribe._exhaustiveOptions()([ "create", "update", "delete" ]), MUTATION_GUARD_ACTIONS = groqConditionDescribe._exhaustiveOptions()([ ...LAKE_MUTATION_GUARD_ACTIONS, "publish", "unpublish" ]), GUARD_ACTIONS_REQUIRED_MESSAGE = "a guard must match at least one action", MUTATION_GUARD_ID_SPACES = [ "authored", "edit", "published" ];
|
|
1195
|
+
|
|
1196
|
+
function mutationGuardActionIdSpace(action) {
|
|
1197
|
+
return action === "update" ? "edit" : action === "publish" || action === "unpublish" ? "published" : "authored";
|
|
1514
1198
|
}
|
|
1515
1199
|
|
|
1516
|
-
function
|
|
1517
|
-
|
|
1518
|
-
const id = doc._id;
|
|
1519
|
-
if (typeof id == "string") return id;
|
|
1520
|
-
}
|
|
1521
|
-
return "(unknown id)";
|
|
1200
|
+
function mutationGuardRequiresSplitEmission(actions) {
|
|
1201
|
+
return new Set(actions.map(mutationGuardActionIdSpace)).size > 1;
|
|
1522
1202
|
}
|
|
1523
1203
|
|
|
1524
|
-
const
|
|
1204
|
+
const ACTIVITY_KINDS = [ "user", "service", "script", "manual", "receive" ], EXECUTOR_CLASSIFICATIONS = [ "autonomous", "interactive", "off-system", "hybrid" ], GROUP_KINDS = [ "core", "details" ], DRIVER_KINDS = [ "person", "agent", "service", "engine" ], NonEmpty = NonEmptyString$1, PositiveInt = v__namespace.pipe(v__namespace.number(), v__namespace.integer(), v__namespace.minValue(1)), GROQ_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1525
1205
|
|
|
1526
|
-
function
|
|
1527
|
-
return
|
|
1206
|
+
function groqIdentifier(referencedAs) {
|
|
1207
|
+
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`));
|
|
1528
1208
|
}
|
|
1529
1209
|
|
|
1530
|
-
function
|
|
1531
|
-
return
|
|
1210
|
+
function picklist(options) {
|
|
1211
|
+
return v__namespace.picklist(options, invalidOptionMessage(options));
|
|
1532
1212
|
}
|
|
1533
1213
|
|
|
1534
|
-
function
|
|
1535
|
-
return
|
|
1214
|
+
function invalidOptionMessage(options) {
|
|
1215
|
+
return `Invalid option: expected one of ${options.map(option => `"${option}"`).join("|")}`;
|
|
1536
1216
|
}
|
|
1537
1217
|
|
|
1538
|
-
function
|
|
1539
|
-
|
|
1540
|
-
return users.length > 0 ? users : members;
|
|
1218
|
+
function pinned() {
|
|
1219
|
+
return (schema, ..._exact) => schema;
|
|
1541
1220
|
}
|
|
1542
1221
|
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1222
|
+
const LiteralSchema = v__namespace.strictObject({
|
|
1223
|
+
type: v__namespace.literal("literal"),
|
|
1224
|
+
value: v__namespace.unknown()
|
|
1225
|
+
}), FieldReadSchema = v__namespace.strictObject({
|
|
1226
|
+
type: v__namespace.literal("fieldRead"),
|
|
1227
|
+
scope: v__namespace.optional(v__namespace.union([ v__namespace.literal("workflow"), v__namespace.literal("stage") ])),
|
|
1228
|
+
field: NonEmpty,
|
|
1229
|
+
path: v__namespace.optional(v__namespace.string())
|
|
1230
|
+
}), FieldSourceSchema = v__namespace.variant("type", [ v__namespace.strictObject({
|
|
1231
|
+
type: v__namespace.literal("input")
|
|
1232
|
+
}), v__namespace.strictObject({
|
|
1233
|
+
type: v__namespace.literal("query"),
|
|
1234
|
+
query: NonEmpty
|
|
1235
|
+
}), LiteralSchema, FieldReadSchema ]), ValueExprSchema = v__namespace.lazy(() => v__namespace.variant("type", [ LiteralSchema, FieldReadSchema, v__namespace.strictObject({
|
|
1236
|
+
type: v__namespace.literal("param"),
|
|
1237
|
+
param: NonEmpty
|
|
1238
|
+
}), v__namespace.strictObject({
|
|
1239
|
+
type: v__namespace.literal("actor")
|
|
1240
|
+
}), v__namespace.strictObject({
|
|
1241
|
+
type: v__namespace.literal("now")
|
|
1242
|
+
}), v__namespace.strictObject({
|
|
1243
|
+
type: v__namespace.literal("self")
|
|
1244
|
+
}), v__namespace.strictObject({
|
|
1245
|
+
type: v__namespace.literal("stage")
|
|
1246
|
+
}), v__namespace.strictObject({
|
|
1247
|
+
type: v__namespace.literal("object"),
|
|
1248
|
+
fields: v__namespace.record(NonEmpty, ValueExprSchema)
|
|
1249
|
+
}) ])), StoredFieldRefSchema = v__namespace.strictObject({
|
|
1250
|
+
scope: picklist(FIELD_SCOPES),
|
|
1251
|
+
field: NonEmpty
|
|
1252
|
+
}), AuthoringFieldRefSchema = v__namespace.strictObject({
|
|
1253
|
+
scope: v__namespace.optional(picklist(FIELD_SCOPES)),
|
|
1254
|
+
field: NonEmpty
|
|
1255
|
+
}), HREF_SCHEMES = [ "http:", "https:" ];
|
|
1256
|
+
|
|
1257
|
+
function isHttpUrl(value) {
|
|
1258
|
+
try {
|
|
1259
|
+
return HREF_SCHEMES.includes(new URL(value).protocol);
|
|
1260
|
+
} catch {
|
|
1261
|
+
return !1;
|
|
1262
|
+
}
|
|
1546
1263
|
}
|
|
1547
1264
|
|
|
1548
|
-
|
|
1549
|
-
|
|
1265
|
+
const UrlString = v__namespace.pipe(v__namespace.string(), v__namespace.url("must be a valid URL"), v__namespace.check(isHttpUrl, "must be an http(s) URL"));
|
|
1266
|
+
|
|
1267
|
+
function manualTargetSchema(ref) {
|
|
1268
|
+
return v__namespace.variant("type", [ v__namespace.strictObject({
|
|
1269
|
+
type: v__namespace.literal("url"),
|
|
1270
|
+
url: UrlString
|
|
1271
|
+
}), v__namespace.strictObject({
|
|
1272
|
+
type: v__namespace.literal("field"),
|
|
1273
|
+
field: ref
|
|
1274
|
+
}) ]);
|
|
1550
1275
|
}
|
|
1551
1276
|
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1277
|
+
const StoredManualTargetSchema = pinned()(manualTargetSchema(StoredFieldRefSchema)), AuthoringManualTargetSchema = pinned()(manualTargetSchema(v__namespace.union([ NonEmpty, AuthoringFieldRefSchema ]))), ConditionSchema = NonEmpty;
|
|
1278
|
+
|
|
1279
|
+
function opSchemas(targetSchema) {
|
|
1280
|
+
return [ v__namespace.strictObject({
|
|
1281
|
+
type: v__namespace.literal("field.set"),
|
|
1282
|
+
target: targetSchema,
|
|
1283
|
+
value: ValueExprSchema
|
|
1284
|
+
}), v__namespace.strictObject({
|
|
1285
|
+
type: v__namespace.literal("field.setIfMissing"),
|
|
1286
|
+
target: targetSchema,
|
|
1287
|
+
value: ValueExprSchema
|
|
1288
|
+
}), v__namespace.strictObject({
|
|
1289
|
+
type: v__namespace.literal("field.unset"),
|
|
1290
|
+
target: targetSchema
|
|
1291
|
+
}), v__namespace.strictObject({
|
|
1292
|
+
type: v__namespace.literal("field.append"),
|
|
1293
|
+
target: targetSchema,
|
|
1294
|
+
value: ValueExprSchema
|
|
1295
|
+
}), v__namespace.strictObject({
|
|
1296
|
+
type: v__namespace.literal("field.inc"),
|
|
1297
|
+
target: targetSchema,
|
|
1298
|
+
value: v__namespace.optional(ValueExprSchema)
|
|
1299
|
+
}), v__namespace.strictObject({
|
|
1300
|
+
type: v__namespace.literal("field.dec"),
|
|
1301
|
+
target: targetSchema,
|
|
1302
|
+
value: v__namespace.optional(ValueExprSchema)
|
|
1303
|
+
}), v__namespace.strictObject({
|
|
1304
|
+
type: v__namespace.literal("field.updateWhere"),
|
|
1305
|
+
target: targetSchema,
|
|
1306
|
+
where: ConditionSchema,
|
|
1307
|
+
value: ValueExprSchema
|
|
1308
|
+
}), v__namespace.strictObject({
|
|
1309
|
+
type: v__namespace.literal("field.removeWhere"),
|
|
1310
|
+
target: targetSchema,
|
|
1311
|
+
where: ConditionSchema
|
|
1312
|
+
}) ];
|
|
1563
1313
|
}
|
|
1564
1314
|
|
|
1565
|
-
|
|
1566
|
-
|
|
1315
|
+
const StoredFieldOpSchema = pinned()(v__namespace.variant("type", [ ...opSchemas(StoredFieldRefSchema) ])), StoredOpSchema = pinned()(v__namespace.variant("type", [ ...opSchemas(StoredFieldRefSchema), v__namespace.strictObject({
|
|
1316
|
+
type: v__namespace.literal("status.set"),
|
|
1317
|
+
activity: NonEmpty,
|
|
1318
|
+
status: picklist(ACTIVITY_STATUSES)
|
|
1319
|
+
}) ])), AuditOpSchema = v__namespace.strictObject({
|
|
1320
|
+
type: v__namespace.literal("audit"),
|
|
1321
|
+
target: AuthoringFieldRefSchema,
|
|
1322
|
+
value: ValueExprSchema,
|
|
1323
|
+
stampFields: v__namespace.optional(v__namespace.strictObject({
|
|
1324
|
+
actor: v__namespace.optional(NonEmpty),
|
|
1325
|
+
at: v__namespace.optional(NonEmpty)
|
|
1326
|
+
}))
|
|
1327
|
+
}), AuthoringOpSchema = pinned()(v__namespace.variant("type", [ ...opSchemas(AuthoringFieldRefSchema), v__namespace.strictObject({
|
|
1328
|
+
type: v__namespace.literal("status.set"),
|
|
1329
|
+
activity: v__namespace.optional(NonEmpty),
|
|
1330
|
+
status: picklist(ACTIVITY_STATUSES)
|
|
1331
|
+
}), AuditOpSchema ])), GroupName = v__namespace.pipe(v__namespace.string(), v__namespace.regex(GROQ_IDENTIFIER, "must be an identifier (letters, digits, underscore; not starting with a digit)")), GroupSchema = pinned()(v__namespace.strictObject({
|
|
1332
|
+
name: GroupName,
|
|
1333
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1334
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1335
|
+
kind: v__namespace.optional(picklist(GROUP_KINDS))
|
|
1336
|
+
})), GroupNameList = v__namespace.pipe(v__namespace.array(GroupName), v__namespace.minLength(1, "name at least one group, or omit `group`"), v__namespace.check(names => new Set(names).size === names.length, "a group is listed more than once — list each group once")), StoredGroupMembershipSchema = GroupNameList, AuthoringGroupMembershipSchema = v__namespace.union([ GroupName, GroupNameList ]);
|
|
1337
|
+
|
|
1338
|
+
function groupMembershipNames(group) {
|
|
1339
|
+
return group === void 0 ? [] : typeof group == "string" ? [ group ] : [ ...group ];
|
|
1567
1340
|
}
|
|
1568
1341
|
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1342
|
+
const FIELD_VALUE_KINDS = [ "doc.ref", "doc.refs", "subject", "release.ref", "string", "text", "number", "progress", "boolean", "date", "dueDate", "datetime", "dueDatetime", "url", "actor", "assignee", "assignees", "object", "array" ], FieldValueKindSchema = picklist(FIELD_VALUE_KINDS), FieldKindSchema = picklist(FIELD_VALUE_KINDS), AUTHORING_FIELD_SUGAR_KINDS = groqConditionDescribe._exhaustiveOptions()([ "todoList", "notes" ]), AUTHORING_FIELD_KINDS = [ ...FIELD_VALUE_KINDS, ...AUTHORING_FIELD_SUGAR_KINDS ], AuthoringRawFieldKindSchema = v__namespace.picklist(FIELD_VALUE_KINDS, issue => `${invalidOptionMessage(AUTHORING_FIELD_KINDS)} but received ${JSON.stringify(issue.input)}`), FieldEntryName = groqIdentifier("`$fields.<name>`"), FiniteNumber = v__namespace.pipe(v__namespace.number(), v__namespace.finite("must be finite")), ScalarValidationSchema = v__namespace.pipe(v__namespace.strictObject({
|
|
1343
|
+
min: v__namespace.optional(FiniteNumber),
|
|
1344
|
+
max: v__namespace.optional(FiniteNumber)
|
|
1345
|
+
}), v__namespace.check(validation => validation.min !== void 0 || validation.max !== void 0, "declare at least one bound, or omit `validation`"), v__namespace.check(validation => validation.min === void 0 || validation.max === void 0 || validation.min <= validation.max, "`min` must be less than or equal to `max`")), ChoiceOptionsSchema = v__namespace.strictObject({
|
|
1346
|
+
list: v__namespace.pipe(v__namespace.array(v__namespace.strictObject({
|
|
1347
|
+
title: NonEmpty,
|
|
1348
|
+
value: v__namespace.union([ v__namespace.string(), v__namespace.number() ])
|
|
1349
|
+
})), v__namespace.minLength(1, "declare at least one choice, or omit `options`"))
|
|
1350
|
+
});
|
|
1572
1351
|
|
|
1573
|
-
function
|
|
1574
|
-
|
|
1575
|
-
return actor === void 0 ? !1 : activeAssignmentMembers(args.members).some(member => member.type === "user" ? member.id === actor.id : actorFulfillsRole({
|
|
1576
|
-
actorRoles: actor.roles,
|
|
1577
|
-
required: member.role,
|
|
1578
|
-
aliases: args.roleAliases
|
|
1579
|
-
}));
|
|
1352
|
+
function asShape(input) {
|
|
1353
|
+
return typeof input == "object" && input !== null ? input : {};
|
|
1580
1354
|
}
|
|
1581
1355
|
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
if (id === ANONYMOUS_IDENTITY || id === SYSTEM_IDENTITY) return {
|
|
1586
|
-
namespace: "sentinel"
|
|
1587
|
-
};
|
|
1588
|
-
if (id.startsWith("g")) return {
|
|
1589
|
-
namespace: "global",
|
|
1590
|
-
globalId: id
|
|
1591
|
-
};
|
|
1592
|
-
if (id.startsWith("p-")) return {
|
|
1593
|
-
namespace: "robot",
|
|
1594
|
-
globalId: id
|
|
1595
|
-
};
|
|
1596
|
-
const embeddedGlobal = E_PREFIXED_PROJECT_ID.exec(id)?.[1];
|
|
1597
|
-
return embeddedGlobal !== void 0 ? embeddedGlobal.startsWith("g") ? {
|
|
1598
|
-
namespace: "project",
|
|
1599
|
-
globalId: embeddedGlobal
|
|
1600
|
-
} : {
|
|
1601
|
-
namespace: "unknown"
|
|
1602
|
-
} : id.startsWith("p") ? {
|
|
1603
|
-
namespace: "project"
|
|
1604
|
-
} : {
|
|
1605
|
-
namespace: "unknown"
|
|
1606
|
-
};
|
|
1356
|
+
function compositeShapeOk(input) {
|
|
1357
|
+
const shape = asShape(input);
|
|
1358
|
+
return shape.type === "object" ? Array.isArray(shape.fields) && shape.fields.length > 0 && shape.of === void 0 : shape.type === "array" ? Array.isArray(shape.of) && shape.of.length > 0 && shape.fields === void 0 : shape.fields === void 0 && shape.of === void 0;
|
|
1607
1359
|
}
|
|
1608
1360
|
|
|
1609
|
-
function
|
|
1610
|
-
|
|
1361
|
+
function compositeShapeMessage(input) {
|
|
1362
|
+
const shape = asShape(input);
|
|
1363
|
+
return shape.type === "object" ? shape.of !== void 0 ? "an `object` kind declares its sub-fields with `fields`, not `of`" : "an `object` kind needs a non-empty `fields` list of sub-field shapes" : shape.type === "array" ? shape.fields !== void 0 ? "an `array` kind declares its item shape with `of`, not `fields`" : "an `array` kind needs a non-empty `of` list of sub-field shapes" : `\`fields\` / \`of\` are only valid on the \`object\` / \`array\` kinds, not "${String(shape.type)}"`;
|
|
1611
1364
|
}
|
|
1612
1365
|
|
|
1613
|
-
function
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1366
|
+
function duplicateSubfieldName(input) {
|
|
1367
|
+
const shape = asShape(input);
|
|
1368
|
+
let list = [];
|
|
1369
|
+
Array.isArray(shape.fields) ? list = shape.fields : Array.isArray(shape.of) && (list = shape.of);
|
|
1370
|
+
const seen = /* @__PURE__ */ new Set;
|
|
1371
|
+
for (const item of list) {
|
|
1372
|
+
const name = asShape(item).name;
|
|
1373
|
+
if (typeof name == "string") {
|
|
1374
|
+
if (seen.has(name)) return name;
|
|
1375
|
+
seen.add(name);
|
|
1376
|
+
}
|
|
1618
1377
|
}
|
|
1619
1378
|
}
|
|
1620
1379
|
|
|
1621
|
-
function
|
|
1622
|
-
return
|
|
1380
|
+
function compositeChecked(entries) {
|
|
1381
|
+
return v__namespace.pipe(v__namespace.strictObject(entries), v__namespace.check(input => compositeShapeOk(input), issue => compositeShapeMessage(issue.input)), v__namespace.check(input => duplicateSubfieldName(input) === void 0, issue => `duplicate sub-field name "${duplicateSubfieldName(issue.input)}" — sub-field names must be unique within \`fields\` / \`of\``));
|
|
1623
1382
|
}
|
|
1624
1383
|
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1384
|
+
const AssignmentRolesSchema = v__namespace.pipe(v__namespace.array(NonEmpty), v__namespace.minLength(1, "declare at least one eligible role, or omit `roles`")), FieldShapeSchema = v__namespace.lazy(() => v__namespace.pipe(compositeChecked({
|
|
1385
|
+
type: FieldValueKindSchema,
|
|
1386
|
+
name: FieldEntryName,
|
|
1387
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1388
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1389
|
+
options: v__namespace.optional(ChoiceOptionsSchema),
|
|
1390
|
+
validation: v__namespace.optional(ScalarValidationSchema),
|
|
1391
|
+
roles: v__namespace.optional(AssignmentRolesSchema),
|
|
1392
|
+
fields: v__namespace.optional(v__namespace.array(FieldShapeSchema)),
|
|
1393
|
+
of: v__namespace.optional(v__namespace.array(FieldShapeSchema))
|
|
1394
|
+
}), choiceOptionsCheck(), scalarValidationCheck(), assignmentRolesCheck())), StoredEditableSchema = pinned()(v__namespace.union([ v__namespace.literal(!0), NonEmpty ])), AuthoringEditableSchema = pinned()(v__namespace.union([ v__namespace.literal(!0), v__namespace.array(NonEmpty), NonEmpty ]));
|
|
1628
1395
|
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
}
|
|
1396
|
+
function fieldBase(editable, group) {
|
|
1397
|
+
return {
|
|
1398
|
+
name: FieldEntryName,
|
|
1399
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1400
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1401
|
+
group: v__namespace.optional(group),
|
|
1402
|
+
required: v__namespace.optional(v__namespace.boolean()),
|
|
1403
|
+
initialValue: v__namespace.optional(FieldSourceSchema),
|
|
1404
|
+
editable: v__namespace.optional(editable)
|
|
1405
|
+
};
|
|
1639
1406
|
}
|
|
1640
1407
|
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
onBehalfOf: v__namespace.exactOptional(v__namespace.string())
|
|
1653
|
-
}), AssigneeShape = v__namespace.union([ tolerantObject()({
|
|
1654
|
-
type: v__namespace.literal("user"),
|
|
1655
|
-
id: NonEmptyString
|
|
1656
|
-
}), tolerantObject()({
|
|
1657
|
-
type: v__namespace.literal("role"),
|
|
1658
|
-
role: NonEmptyString
|
|
1659
|
-
}) ]), AssigneeListShape = v__namespace.pipe(v__namespace.union([ v__namespace.null(), AssigneeShape, v__namespace.array(AssigneeShape) ]), v__namespace.transform(normalizeAssignmentMembers)), 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() ]), NullableProgress = v__namespace.union([ v__namespace.null(), v__namespace.pipe(v__namespace.number(), v__namespace.finite("progress must be a finite number"), v__namespace.minValue(0, "progress must be at least 0"), v__namespace.maxValue(100, "progress must be at most 100")) ]), 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, CHOICE_KINDS = /* @__PURE__ */ new Set([ "string", "text", "number", "url", "date", "dueDate", "datetime", "dueDatetime", "dateTime" ]);
|
|
1660
|
-
|
|
1661
|
-
function normalizedChoiceKind(kind) {
|
|
1662
|
-
return kind === "dateTime" ? "datetime" : kind;
|
|
1408
|
+
function fieldEntryFields({editable: editable, group: group, kind: kind = FieldKindSchema}) {
|
|
1409
|
+
return {
|
|
1410
|
+
type: kind,
|
|
1411
|
+
...fieldBase(editable, group),
|
|
1412
|
+
options: v__namespace.optional(ChoiceOptionsSchema),
|
|
1413
|
+
validation: v__namespace.optional(ScalarValidationSchema),
|
|
1414
|
+
types: v__namespace.optional(v__namespace.pipe(v__namespace.array(NonEmpty), v__namespace.minLength(1, "declare at least one accepted type, or omit `types` to accept any"))),
|
|
1415
|
+
roles: v__namespace.optional(AssignmentRolesSchema),
|
|
1416
|
+
fields: v__namespace.optional(v__namespace.array(FieldShapeSchema)),
|
|
1417
|
+
of: v__namespace.optional(v__namespace.array(FieldShapeSchema))
|
|
1418
|
+
};
|
|
1663
1419
|
}
|
|
1664
1420
|
|
|
1665
|
-
function
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
return issues.length === 0 ? void 0 : issues;
|
|
1421
|
+
function literalSeedIssues(entry) {
|
|
1422
|
+
if (entry.initialValue?.type === "literal") return checkLiteralSeed({
|
|
1423
|
+
entryType: entry.type,
|
|
1424
|
+
value: entry.initialValue.value,
|
|
1425
|
+
types: entry.types,
|
|
1426
|
+
fields: entry.fields,
|
|
1427
|
+
of: entry.of,
|
|
1428
|
+
options: entry.options,
|
|
1429
|
+
validation: entry.validation,
|
|
1430
|
+
roles: entry.roles
|
|
1431
|
+
});
|
|
1677
1432
|
}
|
|
1678
1433
|
|
|
1679
|
-
function
|
|
1680
|
-
|
|
1434
|
+
function literalSeedCheck() {
|
|
1435
|
+
return v__namespace.check(entry => literalSeedIssues(entry) === void 0, issue => `initialValue literal does not fit the declared kind: ${(literalSeedIssues(issue.input) ?? []).join("; ")}`);
|
|
1681
1436
|
}
|
|
1682
1437
|
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
subject: v__namespace.union([ v__namespace.null(), GdrShape ]),
|
|
1687
|
-
"release.ref": v__namespace.union([ v__namespace.null(), ReleaseRefShape ]),
|
|
1688
|
-
string: NullableString,
|
|
1689
|
-
text: NullableString,
|
|
1690
|
-
number: NullableNumber,
|
|
1691
|
-
progress: NullableProgress,
|
|
1692
|
-
boolean: NullableBoolean,
|
|
1693
|
-
date: NullableDate,
|
|
1694
|
-
dueDate: NullableDate,
|
|
1695
|
-
datetime: NullableDateTime,
|
|
1696
|
-
dueDatetime: NullableDateTime,
|
|
1697
|
-
url: NullableUrl,
|
|
1698
|
-
actor: v__namespace.union([ v__namespace.null(), ActorShape ]),
|
|
1699
|
-
assignee: AssigneeListShape,
|
|
1700
|
-
assignees: v__namespace.array(AssigneeShape)
|
|
1701
|
-
}, WritePrincipalId = v__namespace.pipe(NonEmptyString, v__namespace.rawTransform(({dataset: dataset, addIssue: addIssue}) => {
|
|
1702
|
-
const classified = classifyPrincipalId(dataset.value);
|
|
1703
|
-
return classified.namespace === "global" || classified.namespace === "robot" ? dataset.value : classified.namespace === "project" && classified.globalId !== void 0 ? classified.globalId : (addIssue({
|
|
1704
|
-
message: `principal id "${dataset.value}" is not an account-global user id. Workflow user ids are the global \`sanityUserId\` (or a robot token's id). Resolve project members through your surface's member hook, or /projects/<projectId>/users/<id> → sanityUserId.`
|
|
1705
|
-
}), dataset.value);
|
|
1706
|
-
})), ActorWriteShape = tolerantObject()({
|
|
1707
|
-
kind: v__namespace.picklist(ACTOR_KINDS),
|
|
1708
|
-
id: WritePrincipalId,
|
|
1709
|
-
roles: v__namespace.exactOptional(v__namespace.array(v__namespace.string())),
|
|
1710
|
-
onBehalfOf: v__namespace.exactOptional(v__namespace.string())
|
|
1711
|
-
}), AssigneePrincipalId = v__namespace.pipe(WritePrincipalId, v__namespace.check(id => classifyPrincipalId(id).namespace !== "robot", "robot principals cannot be assignment members")), AssigneeWriteShape = v__namespace.union([ tolerantObject()({
|
|
1712
|
-
type: v__namespace.literal("user"),
|
|
1713
|
-
id: AssigneePrincipalId
|
|
1714
|
-
}), tolerantObject()({
|
|
1715
|
-
type: v__namespace.literal("role"),
|
|
1716
|
-
role: NonEmptyString
|
|
1717
|
-
}) ]), SingularAssigneeWriteShape = v__namespace.pipe(v__namespace.union([ AssigneeWriteShape, v__namespace.array(AssigneeWriteShape) ]), v__namespace.transform(normalizeAssignmentMembers), v__namespace.check(members => members.filter(member => member.type === "user").length <= 1, "must contain at most one user member")), valueSchemas = {
|
|
1718
|
-
...fieldValueSchemas,
|
|
1719
|
-
actor: v__namespace.nullable(ActorWriteShape),
|
|
1720
|
-
assignee: SingularAssigneeWriteShape,
|
|
1721
|
-
assignees: v__namespace.array(AssigneeWriteShape),
|
|
1722
|
-
query: v__namespace.any()
|
|
1723
|
-
};
|
|
1438
|
+
function refTypesCheck() {
|
|
1439
|
+
return v__namespace.check(entry => entry.types === void 0 || refKindAcceptsTypes(entry.type), issue => `\`types\` is only valid on \`doc.ref\` / \`doc.refs\` / \`subject\` entries, not "${issue.input.type}"`);
|
|
1440
|
+
}
|
|
1724
1441
|
|
|
1725
|
-
function
|
|
1726
|
-
|
|
1727
|
-
if (shape.type === "object") return objectSchema({
|
|
1728
|
-
fields: shape.fields ?? [],
|
|
1729
|
-
leaf: leaf
|
|
1730
|
-
});
|
|
1731
|
-
if (shape.type === "array") return v__namespace.array(objectSchema({
|
|
1732
|
-
fields: shape.of ?? [],
|
|
1733
|
-
leaf: leaf
|
|
1734
|
-
}));
|
|
1735
|
-
const schema = leaf[shape.type] ?? v__namespace.any();
|
|
1736
|
-
return constrainedScalarSchema({
|
|
1737
|
-
schema: schema,
|
|
1738
|
-
entryType: shape.type,
|
|
1739
|
-
...shape
|
|
1740
|
-
});
|
|
1442
|
+
function assignmentRolesCheck() {
|
|
1443
|
+
return v__namespace.check(entry => entry.roles === void 0 || assignmentKindAcceptsRoles(entry.type), issue => `\`roles\` is only valid on \`assignee\` / \`assignees\` entries, not "${issue.input.type}"`);
|
|
1741
1444
|
}
|
|
1742
1445
|
|
|
1743
|
-
function
|
|
1744
|
-
|
|
1745
|
-
|
|
1446
|
+
function choiceOptionsCheck() {
|
|
1447
|
+
return v__namespace.check(entry => checkChoiceList({
|
|
1448
|
+
entryType: entry.type,
|
|
1449
|
+
options: entry.options,
|
|
1450
|
+
validation: entry.validation
|
|
1451
|
+
}) === void 0, issue => (checkChoiceList({
|
|
1452
|
+
entryType: issue.input.type,
|
|
1453
|
+
options: issue.input.options,
|
|
1454
|
+
validation: issue.input.validation
|
|
1455
|
+
}) ?? []).join("; "));
|
|
1746
1456
|
}
|
|
1747
1457
|
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1458
|
+
const SCALAR_VALIDATION_KINDS = /* @__PURE__ */ new Set([ "string", "text", "number", "progress" ]);
|
|
1459
|
+
|
|
1460
|
+
function scalarValidationCheck() {
|
|
1461
|
+
return v__namespace.check(entry => scalarValidationDeclarationIssues(entry) === void 0, issue => (scalarValidationDeclarationIssues(issue.input) ?? []).join("; "));
|
|
1751
1462
|
}
|
|
1752
1463
|
|
|
1753
|
-
function
|
|
1754
|
-
const {
|
|
1755
|
-
if (validation === void 0
|
|
1756
|
-
|
|
1757
|
-
if (
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
}), scalarBoundIssue({
|
|
1764
|
-
entryType: entryType,
|
|
1765
|
-
measured: measured,
|
|
1766
|
-
bound: validation.max,
|
|
1767
|
-
limit: "max"
|
|
1768
|
-
}) ].filter(issue => issue !== void 0);
|
|
1464
|
+
function scalarValidationDeclarationIssues(entry) {
|
|
1465
|
+
const {type: type, validation: validation} = entry;
|
|
1466
|
+
if (validation === void 0) return;
|
|
1467
|
+
if (!SCALAR_VALIDATION_KINDS.has(type)) return [ `\`validation\` is only valid on \`string\` / \`text\` / \`number\` / \`progress\` values, not "${type}"` ];
|
|
1468
|
+
if (type === "progress") {
|
|
1469
|
+
const issues2 = Object.entries(validation).flatMap(([bound, value]) => typeof value == "number" && value >= 0 && value <= 100 ? [] : [ `\`validation.${bound}\` must stay within the progress kind's 0–100 contract` ]);
|
|
1470
|
+
return issues2.length === 0 ? void 0 : issues2;
|
|
1471
|
+
}
|
|
1472
|
+
if (type === "number") return;
|
|
1473
|
+
const issues = Object.entries(validation).flatMap(([bound, value]) => Number.isInteger(value) && value >= 0 ? [] : [ `\`validation.${bound}\` must be a non-negative integer for ${type} length` ]);
|
|
1769
1474
|
return issues.length === 0 ? void 0 : issues;
|
|
1770
1475
|
}
|
|
1771
1476
|
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1477
|
+
const FieldEntrySchema = pinned()(v__namespace.pipe(compositeChecked(fieldEntryFields({
|
|
1478
|
+
editable: StoredEditableSchema,
|
|
1479
|
+
group: StoredGroupMembershipSchema
|
|
1480
|
+
})), refTypesCheck(), assignmentRolesCheck(), choiceOptionsCheck(), scalarValidationCheck(), literalSeedCheck())), RawAuthoringFieldEntrySchema = pinned()(v__namespace.pipe(compositeChecked(fieldEntryFields({
|
|
1481
|
+
editable: AuthoringEditableSchema,
|
|
1482
|
+
group: AuthoringGroupMembershipSchema,
|
|
1483
|
+
kind: AuthoringRawFieldKindSchema
|
|
1484
|
+
})), refTypesCheck(), assignmentRolesCheck(), choiceOptionsCheck(), scalarValidationCheck(), literalSeedCheck()));
|
|
1485
|
+
|
|
1486
|
+
function listSugarFields(type) {
|
|
1487
|
+
return {
|
|
1488
|
+
type: v__namespace.literal(type),
|
|
1489
|
+
...fieldBase(AuthoringEditableSchema, AuthoringGroupMembershipSchema)
|
|
1490
|
+
};
|
|
1783
1491
|
}
|
|
1784
1492
|
|
|
1785
|
-
|
|
1786
|
-
const
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1493
|
+
const TodoListFieldSchema = pinned()(v__namespace.strictObject(listSugarFields("todoList"))), NotesFieldSchema = pinned()(v__namespace.strictObject(listSugarFields("notes"))), AuthoringFieldEntrySchema = pinned()(v__namespace.lazy(input => {
|
|
1494
|
+
const type = asShape(input).type;
|
|
1495
|
+
return type === "todoList" ? TodoListFieldSchema : type === "notes" ? NotesFieldSchema : RawAuthoringFieldEntrySchema;
|
|
1496
|
+
})), RUNTIME_KINDS = groqConditionDescribe._exhaustiveOptions()([ "function", "durableFunction", "selfHosted" ]), RuntimeBlockSchema = pinned()(v__namespace.strictObject({
|
|
1497
|
+
kind: picklist(RUNTIME_KINDS)
|
|
1498
|
+
})), EffectRuntimeBlockSchema = pinned()(v__namespace.variant("kind", [ v__namespace.strictObject({
|
|
1499
|
+
kind: v__namespace.literal("function"),
|
|
1500
|
+
timeout: v__namespace.optional(PositiveInt),
|
|
1501
|
+
memory: v__namespace.optional(PositiveInt)
|
|
1502
|
+
}), v__namespace.strictObject({
|
|
1503
|
+
kind: v__namespace.literal("durableFunction")
|
|
1504
|
+
}), v__namespace.strictObject({
|
|
1505
|
+
kind: v__namespace.literal("selfHosted")
|
|
1506
|
+
}) ], invalidOptionMessage(RUNTIME_KINDS))), EFFECT_RETRY_KINDS = groqConditionDescribe._exhaustiveOptions()([ "engine" ]), EffectRetryBackoffSchema = pinned()(v__namespace.strictObject({
|
|
1507
|
+
kind: picklist([ "fixed", "exponential" ]),
|
|
1508
|
+
delayMs: PositiveInt
|
|
1509
|
+
}));
|
|
1510
|
+
|
|
1511
|
+
function effectRetryFields(kind) {
|
|
1512
|
+
return {
|
|
1513
|
+
kind: kind,
|
|
1514
|
+
attempts: PositiveInt,
|
|
1515
|
+
backoff: v__namespace.optional(EffectRetryBackoffSchema),
|
|
1516
|
+
expiryMs: v__namespace.optional(PositiveInt)
|
|
1517
|
+
};
|
|
1792
1518
|
}
|
|
1793
1519
|
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1520
|
+
const StoredEffectRetrySchema = pinned()(v__namespace.strictObject(effectRetryFields(picklist(EFFECT_RETRY_KINDS)))), AuthoringEffectRetrySchema = pinned()(v__namespace.strictObject(effectRetryFields(v__namespace.optional(picklist(EFFECT_RETRY_KINDS)))));
|
|
1521
|
+
|
|
1522
|
+
function effectFields(retry) {
|
|
1523
|
+
return {
|
|
1524
|
+
name: NonEmpty,
|
|
1525
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1526
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1527
|
+
bindings: v__namespace.optional(v__namespace.record(v__namespace.string(), ConditionSchema)),
|
|
1528
|
+
input: v__namespace.optional(v__namespace.record(v__namespace.string(), v__namespace.unknown())),
|
|
1529
|
+
outputs: v__namespace.optional(v__namespace.array(FieldShapeSchema)),
|
|
1530
|
+
retry: v__namespace.optional(retry)
|
|
1531
|
+
};
|
|
1803
1532
|
}
|
|
1804
1533
|
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1534
|
+
const EffectSchema = pinned()(v__namespace.strictObject(effectFields(StoredEffectRetrySchema))), AuthoringEffectSchema = pinned()(v__namespace.strictObject({
|
|
1535
|
+
...effectFields(AuthoringEffectRetrySchema),
|
|
1536
|
+
runtime: v__namespace.optional(EffectRuntimeBlockSchema)
|
|
1537
|
+
})), DefinitionRefSchema = v__namespace.strictObject({
|
|
1538
|
+
name: NonEmpty,
|
|
1539
|
+
version: v__namespace.optional(v__namespace.union([ PositiveInt, v__namespace.literal("latest") ]))
|
|
1540
|
+
}), SubworkflowsSchema = v__namespace.strictObject({
|
|
1541
|
+
forEach: NonEmpty,
|
|
1542
|
+
definition: DefinitionRefSchema,
|
|
1543
|
+
with: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
|
|
1544
|
+
context: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
|
|
1545
|
+
onExit: v__namespace.optional(picklist([ "detach", "abort" ]))
|
|
1546
|
+
}), ActionParamSchema = v__namespace.pipe(v__namespace.strictObject({
|
|
1547
|
+
type: picklist([ "string", "number", "boolean", "url", "dateTime", "actor", "doc.ref", "doc.refs", "json" ]),
|
|
1548
|
+
name: NonEmpty,
|
|
1549
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1550
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1551
|
+
required: v__namespace.optional(v__namespace.boolean()),
|
|
1552
|
+
options: v__namespace.optional(ChoiceOptionsSchema),
|
|
1553
|
+
validation: v__namespace.optional(ScalarValidationSchema)
|
|
1554
|
+
}), choiceOptionsCheck(), scalarValidationCheck()), CUSTOM_SEMANTIC_HINT = "`custom.<camelCaseMeaning>`", CustomSemanticSchema = v__namespace.custom(input => typeof input == "string" && /^custom\.[a-z][a-zA-Z0-9]*$/.test(input)), SemanticSchema = v__namespace.union([ picklist(SIGNAL_SEMANTICS), CustomSemanticSchema ], `expected ${SIGNAL_SEMANTICS.join(", ")}, or ${CUSTOM_SEMANTIC_HINT}`), ActionSemanticSchema = v__namespace.union([ picklist(ACTION_SEMANTICS), CustomSemanticSchema ], `expected ${ACTION_SEMANTICS.join(", ")}, or ${CUSTOM_SEMANTIC_HINT}`);
|
|
1555
|
+
|
|
1556
|
+
function semanticNamespace(semantic) {
|
|
1557
|
+
return semantic.startsWith("custom.") ? semantic : semantic.split(".", 1)[0] ?? semantic;
|
|
1812
1558
|
}
|
|
1813
1559
|
|
|
1814
|
-
function
|
|
1815
|
-
const
|
|
1816
|
-
|
|
1817
|
-
if (!refKindAcceptsTypes(entryType)) return [];
|
|
1818
|
-
let items = [ value ];
|
|
1819
|
-
return entryType === "doc.refs" && (items = Array.isArray(value) ? value : []),
|
|
1820
|
-
[ ...new Set(items.map(gdrTypeOf).filter(t => t !== void 0 && !types.includes(t))) ];
|
|
1560
|
+
function hasUniqueSemanticNamespaces(semantics) {
|
|
1561
|
+
const namespaces = semantics.map(semanticNamespace);
|
|
1562
|
+
return new Set(namespaces).size === namespaces.length;
|
|
1821
1563
|
}
|
|
1822
1564
|
|
|
1823
|
-
function
|
|
1824
|
-
|
|
1825
|
-
if (rejected.length === 0) return;
|
|
1826
|
-
const accepts = (args.types ?? []).map(t => `"${t}"`).join(", ");
|
|
1827
|
-
return rejected.map(t => `document type "${t}" is not accepted — this entry accepts ${accepts} (a GDR's \`type\` names the target document's schema type)`);
|
|
1565
|
+
function semanticsFieldSchema(semantic) {
|
|
1566
|
+
return v__namespace.optional(v__namespace.pipe(v__namespace.array(semantic), v__namespace.minLength(1, "declare at least one semantic, or omit `semantics`"), v__namespace.check(hasUniqueSemanticNamespaces, "declare at most one semantic from each namespace")));
|
|
1828
1567
|
}
|
|
1829
1568
|
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1569
|
+
const SemanticsFieldSchema = semanticsFieldSchema(SemanticSchema), ActionSemanticsFieldSchema = semanticsFieldSchema(ActionSemanticSchema);
|
|
1570
|
+
|
|
1571
|
+
function actionFields(schemas) {
|
|
1572
|
+
const {op: op, group: group, effect: effect} = schemas;
|
|
1573
|
+
return {
|
|
1574
|
+
name: NonEmpty,
|
|
1575
|
+
semantics: ActionSemanticsFieldSchema,
|
|
1576
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1577
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1578
|
+
group: v__namespace.optional(group),
|
|
1579
|
+
when: v__namespace.optional(ConditionSchema),
|
|
1580
|
+
filter: v__namespace.optional(ConditionSchema),
|
|
1581
|
+
params: v__namespace.optional(v__namespace.array(ActionParamSchema)),
|
|
1582
|
+
ops: v__namespace.optional(v__namespace.array(op)),
|
|
1583
|
+
effects: v__namespace.optional(v__namespace.array(effect)),
|
|
1584
|
+
spawn: v__namespace.optional(SubworkflowsSchema)
|
|
1585
|
+
};
|
|
1834
1586
|
}
|
|
1835
1587
|
|
|
1836
|
-
|
|
1837
|
-
|
|
1588
|
+
const StoredActionSchema = pinned()(v__namespace.strictObject({
|
|
1589
|
+
...actionFields({
|
|
1590
|
+
op: StoredOpSchema,
|
|
1591
|
+
group: StoredGroupMembershipSchema,
|
|
1592
|
+
effect: EffectSchema
|
|
1593
|
+
}),
|
|
1594
|
+
roles: v__namespace.optional(v__namespace.array(NonEmpty))
|
|
1595
|
+
})), TerminalActivityStatus = picklist(TERMINAL_ACTIVITY_STATUSES), RawAuthoringActionSchema = pinned()(v__namespace.strictObject({
|
|
1596
|
+
...actionFields({
|
|
1597
|
+
op: AuthoringOpSchema,
|
|
1598
|
+
group: AuthoringGroupMembershipSchema,
|
|
1599
|
+
effect: AuthoringEffectSchema
|
|
1600
|
+
}),
|
|
1601
|
+
roles: v__namespace.optional(v__namespace.array(NonEmpty)),
|
|
1602
|
+
status: v__namespace.optional(TerminalActivityStatus)
|
|
1603
|
+
})), AuthoringActionSchema = pinned()(RawAuthoringActionSchema), requirementBase = {
|
|
1604
|
+
name: NonEmpty,
|
|
1605
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1606
|
+
description: v__namespace.optional(v__namespace.string())
|
|
1607
|
+
}, GroqRequirementSchemaRaw = v__namespace.strictObject({
|
|
1608
|
+
...requirementBase,
|
|
1609
|
+
type: v__namespace.literal("groq"),
|
|
1610
|
+
query: ConditionSchema
|
|
1611
|
+
}), SingleSubjectRequirementSchemaRaw = v__namespace.strictObject({
|
|
1612
|
+
...requirementBase,
|
|
1613
|
+
type: v__namespace.literal("singleSubject")
|
|
1614
|
+
}), GroqRequirementSchema = pinned()(GroqRequirementSchemaRaw), StartRequirementSchema = pinned()(v__namespace.variant("type", [ GroqRequirementSchemaRaw, SingleSubjectRequirementSchemaRaw ]));
|
|
1615
|
+
|
|
1616
|
+
function activityFields({field: field, action: action, target: target, group: group}) {
|
|
1617
|
+
return {
|
|
1618
|
+
name: NonEmpty,
|
|
1619
|
+
semantics: SemanticsFieldSchema,
|
|
1620
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1621
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1622
|
+
groups: v__namespace.optional(v__namespace.array(GroupSchema)),
|
|
1623
|
+
group: v__namespace.optional(group),
|
|
1624
|
+
target: v__namespace.optional(target),
|
|
1625
|
+
filter: v__namespace.optional(ConditionSchema),
|
|
1626
|
+
requirements: v__namespace.optional(v__namespace.array(GroqRequirementSchema)),
|
|
1627
|
+
actions: v__namespace.optional(v__namespace.array(action)),
|
|
1628
|
+
fields: v__namespace.optional(v__namespace.array(field))
|
|
1629
|
+
};
|
|
1838
1630
|
}
|
|
1839
1631
|
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1632
|
+
const StoredActivitySchema = pinned()(v__namespace.strictObject(activityFields({
|
|
1633
|
+
field: FieldEntrySchema,
|
|
1634
|
+
action: StoredActionSchema,
|
|
1635
|
+
target: StoredManualTargetSchema,
|
|
1636
|
+
group: StoredGroupMembershipSchema
|
|
1637
|
+
}))), AuthoringActivitySchema = pinned()(v__namespace.strictObject(activityFields({
|
|
1638
|
+
field: AuthoringFieldEntrySchema,
|
|
1639
|
+
action: AuthoringActionSchema,
|
|
1640
|
+
target: AuthoringManualTargetSchema,
|
|
1641
|
+
group: AuthoringGroupMembershipSchema
|
|
1642
|
+
})));
|
|
1643
|
+
|
|
1644
|
+
function transitionFields(when) {
|
|
1645
|
+
return {
|
|
1646
|
+
name: NonEmpty,
|
|
1647
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1648
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1649
|
+
to: NonEmpty,
|
|
1650
|
+
when: when
|
|
1848
1651
|
};
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
const StoredTransitionSchema = pinned()(v__namespace.strictObject(transitionFields(ConditionSchema))), AuthoringTransitionSchema = pinned()(v__namespace.strictObject(transitionFields(v__namespace.optional(ConditionSchema)))), GuardActionSchema = picklist(MUTATION_GUARD_ACTIONS), GuardActionsSchema = v__namespace.array(GuardActionSchema), NonEmptyGuardActionsSchema = v__namespace.pipe(GuardActionsSchema, v__namespace.minLength(1, GUARD_ACTIONS_REQUIRED_MESSAGE)), 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 = pinned()(v__namespace.variant("type", [ v__namespace.strictObject({
|
|
1655
|
+
type: v__namespace.literal("self")
|
|
1656
|
+
}), v__namespace.strictObject({
|
|
1657
|
+
type: v__namespace.literal("now")
|
|
1658
|
+
}), v__namespace.strictObject({
|
|
1659
|
+
type: v__namespace.literal("fieldRead"),
|
|
1660
|
+
field: FieldEntryName,
|
|
1661
|
+
path: v__namespace.optional(GuardReadPath)
|
|
1662
|
+
}), v__namespace.strictObject({
|
|
1663
|
+
type: v__namespace.literal("effectsRead"),
|
|
1664
|
+
effect: v__namespace.pipe(NonEmpty, v__namespace.check(name => !name.includes("'"), "an effect name cannot contain `'`")),
|
|
1665
|
+
path: v__namespace.optional(GuardReadPath)
|
|
1666
|
+
}) ]));
|
|
1667
|
+
|
|
1668
|
+
function guardMatchFields(read, actions) {
|
|
1669
|
+
return {
|
|
1670
|
+
types: v__namespace.optional(v__namespace.array(NonEmpty)),
|
|
1671
|
+
idRefs: v__namespace.optional(v__namespace.array(read)),
|
|
1672
|
+
idPatterns: v__namespace.optional(v__namespace.array(NonEmpty)),
|
|
1673
|
+
actions: actions
|
|
1852
1674
|
};
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
issues: postIssues
|
|
1864
|
-
} : {
|
|
1865
|
-
output: result.output
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
function guardFields(read, actions) {
|
|
1678
|
+
return {
|
|
1679
|
+
name: NonEmpty,
|
|
1680
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1681
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1682
|
+
match: v__namespace.strictObject(guardMatchFields(read, actions)),
|
|
1683
|
+
predicate: v__namespace.optional(v__namespace.string()),
|
|
1684
|
+
metadata: v__namespace.optional(v__namespace.record(NonEmpty, read))
|
|
1866
1685
|
};
|
|
1867
1686
|
}
|
|
1868
1687
|
|
|
1869
|
-
|
|
1870
|
-
return assignee.type === "user" ? `user:${assignee.id}` : `role:${assignee.role}`;
|
|
1871
|
-
}
|
|
1688
|
+
const GuardSchema = v__namespace.strictObject(guardFields(NonEmpty, GuardActionsSchema)), AuthoringGuardSchema = v__namespace.strictObject(guardFields(GuardReadSchema, NonEmptyGuardActionsSchema));
|
|
1872
1689
|
|
|
1873
|
-
function
|
|
1874
|
-
|
|
1875
|
-
|
|
1690
|
+
function stageFields({field: field, activity: activity, transition: transition, guard: guard, editable: editable}) {
|
|
1691
|
+
return {
|
|
1692
|
+
name: NonEmpty,
|
|
1693
|
+
semantics: SemanticsFieldSchema,
|
|
1694
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1695
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1696
|
+
groups: v__namespace.optional(v__namespace.array(GroupSchema)),
|
|
1697
|
+
activities: v__namespace.optional(v__namespace.array(activity)),
|
|
1698
|
+
transitions: v__namespace.optional(v__namespace.array(transition)),
|
|
1699
|
+
guards: v__namespace.optional(v__namespace.array(guard)),
|
|
1700
|
+
fields: v__namespace.optional(v__namespace.array(field)),
|
|
1701
|
+
editable: v__namespace.optional(v__namespace.record(FieldEntryName, editable))
|
|
1702
|
+
};
|
|
1876
1703
|
}
|
|
1877
1704
|
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
}
|
|
1705
|
+
const StoredStageSchema = pinned()(v__namespace.strictObject(stageFields({
|
|
1706
|
+
field: FieldEntrySchema,
|
|
1707
|
+
activity: StoredActivitySchema,
|
|
1708
|
+
transition: StoredTransitionSchema,
|
|
1709
|
+
guard: GuardSchema,
|
|
1710
|
+
editable: StoredEditableSchema
|
|
1711
|
+
}))), AuthoringStageSchema = pinned()(v__namespace.strictObject(stageFields({
|
|
1712
|
+
field: AuthoringFieldEntrySchema,
|
|
1713
|
+
activity: AuthoringActivitySchema,
|
|
1714
|
+
transition: AuthoringTransitionSchema,
|
|
1715
|
+
guard: AuthoringGuardSchema,
|
|
1716
|
+
editable: AuthoringEditableSchema
|
|
1717
|
+
}))), RoleAliasesSchema = pinned()(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 = groqConditionDescribe._exhaustiveOptions()([ "standalone", "child" ]), START_KINDS = groqConditionDescribe._exhaustiveOptions()([ "interactive", "autonomous" ]);
|
|
1718
|
+
|
|
1719
|
+
function startFields(kind) {
|
|
1720
|
+
return {
|
|
1721
|
+
kind: kind,
|
|
1722
|
+
filter: v__namespace.optional(ConditionSchema),
|
|
1723
|
+
requirements: v__namespace.optional(v__namespace.array(StartRequirementSchema))
|
|
1724
|
+
};
|
|
1898
1725
|
}
|
|
1899
1726
|
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1727
|
+
const StoredStartSchema = pinned()(v__namespace.strictObject(startFields(picklist(START_KINDS)))), AuthoringStartSchema = pinned()(v__namespace.strictObject(startFields(v__namespace.optional(picklist(START_KINDS)))));
|
|
1728
|
+
|
|
1729
|
+
function workflowFields({field: field, stage: stage, start: start}) {
|
|
1730
|
+
return {
|
|
1731
|
+
name: NonEmpty,
|
|
1732
|
+
semantics: SemanticsFieldSchema,
|
|
1733
|
+
title: NonEmpty,
|
|
1734
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1735
|
+
groups: v__namespace.optional(v__namespace.array(GroupSchema)),
|
|
1736
|
+
lifecycle: v__namespace.optional(picklist(WORKFLOW_LIFECYCLES)),
|
|
1737
|
+
start: v__namespace.optional(start),
|
|
1738
|
+
initialStage: NonEmpty,
|
|
1739
|
+
fields: v__namespace.optional(v__namespace.array(field)),
|
|
1740
|
+
stages: v__namespace.pipe(v__namespace.array(stage), v__namespace.minLength(1, "must declare at least one stage")),
|
|
1741
|
+
predicates: v__namespace.optional(v__namespace.record(groqIdentifier("`$<name>`"), ConditionSchema)),
|
|
1742
|
+
roleAliases: v__namespace.optional(RoleAliasesSchema)
|
|
1743
|
+
};
|
|
1904
1744
|
}
|
|
1905
1745
|
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1746
|
+
const WorkflowDefinitionSchema = pinned()(v__namespace.strictObject(workflowFields({
|
|
1747
|
+
field: FieldEntrySchema,
|
|
1748
|
+
stage: StoredStageSchema,
|
|
1749
|
+
start: StoredStartSchema
|
|
1750
|
+
})));
|
|
1751
|
+
|
|
1752
|
+
function parseStoredDefinition(input, label) {
|
|
1753
|
+
return parseOrThrow({
|
|
1754
|
+
schema: WorkflowDefinitionSchema,
|
|
1755
|
+
input: input,
|
|
1756
|
+
label: label
|
|
1913
1757
|
});
|
|
1914
|
-
if (schema === void 0) return retained;
|
|
1915
|
-
const previous = v__namespace.safeParse(schema, args.previousValue);
|
|
1916
|
-
return previous.success && visitAssignmentEntries({
|
|
1917
|
-
entryType: args.entryType,
|
|
1918
|
-
value: previous.output,
|
|
1919
|
-
roles: args.roles,
|
|
1920
|
-
fields: args.fields,
|
|
1921
|
-
of: args.of,
|
|
1922
|
-
path: "$"
|
|
1923
|
-
}, ({assignee: assignee, path: path}) => {
|
|
1924
|
-
const identities = retained.get(path) ?? /* @__PURE__ */ new Map, identity = assignmentIdentity(assignee);
|
|
1925
|
-
identities.set(identity, (identities.get(identity) ?? 0) + 1), retained.set(path, identities);
|
|
1926
|
-
}), retained;
|
|
1927
1758
|
}
|
|
1928
1759
|
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
value: args.value,
|
|
1938
|
-
path: args.path
|
|
1939
|
-
}, visit);
|
|
1940
|
-
return;
|
|
1941
|
-
}
|
|
1942
|
-
if (!(args.entryType !== "array" || !Array.isArray(args.value))) for (const row of args.value) visitAssignmentFields({
|
|
1943
|
-
fields: args.of,
|
|
1944
|
-
value: row,
|
|
1945
|
-
path: `${args.path}[]`
|
|
1946
|
-
}, visit);
|
|
1947
|
-
}
|
|
1760
|
+
const AuthoringWorkflowSchema = pinned()(v__namespace.strictObject({
|
|
1761
|
+
...workflowFields({
|
|
1762
|
+
field: AuthoringFieldEntrySchema,
|
|
1763
|
+
stage: AuthoringStageSchema,
|
|
1764
|
+
start: AuthoringStartSchema
|
|
1765
|
+
}),
|
|
1766
|
+
runtime: v__namespace.optional(RuntimeBlockSchema)
|
|
1767
|
+
})), WORKFLOW_DEFINITION_TYPE = "sanity.workflow.definition";
|
|
1948
1768
|
|
|
1949
|
-
function
|
|
1950
|
-
|
|
1951
|
-
const indexedValues = Array.isArray(args.value) ? args.value.map(value => ({
|
|
1952
|
-
value: value,
|
|
1953
|
-
path: args.path
|
|
1954
|
-
})) : [ {
|
|
1955
|
-
value: args.value,
|
|
1956
|
-
path: args.path
|
|
1957
|
-
} ];
|
|
1958
|
-
for (const {value: value, path: path} of indexedValues) {
|
|
1959
|
-
const assignee = assignmentCandidate(value);
|
|
1960
|
-
assignee !== void 0 && visit({
|
|
1961
|
-
assignee: assignee,
|
|
1962
|
-
roles: args.roles,
|
|
1963
|
-
path: path
|
|
1964
|
-
});
|
|
1965
|
-
}
|
|
1769
|
+
function isStartableDefinition(definition) {
|
|
1770
|
+
return definition.lifecycle !== "child";
|
|
1966
1771
|
}
|
|
1967
1772
|
|
|
1968
|
-
function
|
|
1969
|
-
|
|
1970
|
-
shape: field,
|
|
1971
|
-
value: args.value[field.name],
|
|
1972
|
-
path: `${args.path}.${field.name}`
|
|
1973
|
-
}, visit);
|
|
1773
|
+
function startKindOf(definition) {
|
|
1774
|
+
return definition.start?.kind ?? "interactive";
|
|
1974
1775
|
}
|
|
1975
1776
|
|
|
1976
|
-
function
|
|
1977
|
-
|
|
1978
|
-
entryType: args.shape.type,
|
|
1979
|
-
value: args.value,
|
|
1980
|
-
roles: args.shape.roles,
|
|
1981
|
-
fields: args.shape.fields,
|
|
1982
|
-
of: args.shape.of,
|
|
1983
|
-
path: args.path
|
|
1984
|
-
}, visit);
|
|
1777
|
+
function isSubjectEntry(entry) {
|
|
1778
|
+
return entry.type === "subject";
|
|
1985
1779
|
}
|
|
1986
1780
|
|
|
1987
|
-
function
|
|
1988
|
-
|
|
1989
|
-
const held = args.memberRoles?.[assignee.id];
|
|
1990
|
-
return held === void 0 ? args.memberRoles === void 0 ? [] : [ `user "${assignee.id}" has no available project membership for role eligibility` ] : args.roles.some(required => actorFulfillsRole({
|
|
1991
|
-
actorRoles: held,
|
|
1992
|
-
required: required,
|
|
1993
|
-
aliases: args.roleAliases
|
|
1994
|
-
})) ? [] : [ `user "${assignee.id}" does not fulfill any eligible role: ${formatEligibleRoles(args.roles)}` ];
|
|
1781
|
+
function isDueDateEntry(entry) {
|
|
1782
|
+
return entry.type === "dueDate" || entry.type === "dueDatetime";
|
|
1995
1783
|
}
|
|
1996
1784
|
|
|
1997
|
-
function
|
|
1998
|
-
return
|
|
1785
|
+
function isInputSourced(entry) {
|
|
1786
|
+
return entry.initialValue?.type === "input";
|
|
1999
1787
|
}
|
|
2000
1788
|
|
|
2001
|
-
function
|
|
2002
|
-
|
|
1789
|
+
function parseOrThrow({schema: schema, input: input, label: label}) {
|
|
1790
|
+
const result = v__namespace.safeParse(schema, input);
|
|
1791
|
+
if (!result.success) throw new Error(formatValidationError(label, issuesFromValibot(result.issues)));
|
|
1792
|
+
return result.output;
|
|
2003
1793
|
}
|
|
2004
1794
|
|
|
2005
|
-
function
|
|
2006
|
-
|
|
1795
|
+
function labelFor(fn, value) {
|
|
1796
|
+
if (value && typeof value == "object" && "name" in value) {
|
|
1797
|
+
const raw = value.name;
|
|
1798
|
+
if (typeof raw == "string") return `${fn}("${raw}")`;
|
|
1799
|
+
}
|
|
1800
|
+
return fn;
|
|
2007
1801
|
}
|
|
2008
1802
|
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
1803
|
+
const NonEmptyString = v__namespace.pipe(v__namespace.string(), v__namespace.nonEmpty("must not be empty"));
|
|
1804
|
+
|
|
1805
|
+
function asPredicate(validate) {
|
|
1806
|
+
return value => {
|
|
1807
|
+
try {
|
|
1808
|
+
return validate(value), !0;
|
|
1809
|
+
} catch {
|
|
1810
|
+
return !1;
|
|
1811
|
+
}
|
|
1812
|
+
};
|
|
2018
1813
|
}
|
|
2019
1814
|
|
|
2020
|
-
const
|
|
1815
|
+
const isValidTag = asPredicate(validateTag), isValidAliasName = asPredicate(validateResourceAliasName), isValidDatasetId = asPredicate(datasetResourceParts);
|
|
2021
1816
|
|
|
2022
|
-
function
|
|
2023
|
-
return
|
|
1817
|
+
function lakeSegment(label) {
|
|
1818
|
+
return v__namespace.pipe(v__namespace.string(), v__namespace.nonEmpty(), v__namespace.check(isValidTag, `invalid ${label} — ${LAKE_ID_SEGMENT_GLOSS}`));
|
|
2024
1819
|
}
|
|
2025
1820
|
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
1821
|
+
const WorkflowResourceSchema = v__namespace.variant("type", [ v__namespace.object({
|
|
1822
|
+
type: v__namespace.literal("dataset"),
|
|
1823
|
+
id: v__namespace.pipe(NonEmptyString, v__namespace.check(isValidDatasetId, 'invalid dataset resource id — expected "<projectId>.<dataset>"'))
|
|
1824
|
+
}), v__namespace.object({
|
|
1825
|
+
type: v__namespace.literal("canvas"),
|
|
1826
|
+
id: NonEmptyString
|
|
1827
|
+
}), v__namespace.object({
|
|
1828
|
+
type: v__namespace.literal("media-library"),
|
|
1829
|
+
id: NonEmptyString
|
|
1830
|
+
}), v__namespace.object({
|
|
1831
|
+
type: v__namespace.literal("dashboard"),
|
|
1832
|
+
id: NonEmptyString
|
|
1833
|
+
}) ]), ResourceBindingSchema = v__namespace.object({
|
|
1834
|
+
name: v__namespace.pipe(NonEmptyString, v__namespace.check(isValidAliasName, "invalid resource handle name — lowercase letters, digits and dashes only, no leading dash")),
|
|
1835
|
+
resource: WorkflowResourceSchema
|
|
1836
|
+
}), 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({
|
|
1837
|
+
name: lakeSegment("name"),
|
|
1838
|
+
expectedMinReaderModel: v__namespace.optional(v__namespace.custom(() => !0), void 0),
|
|
1839
|
+
tag: lakeSegment("tag"),
|
|
1840
|
+
workflowResource: WorkflowResourceSchema,
|
|
1841
|
+
resourceAliases: v__namespace.optional(v__namespace.pipe(v__namespace.array(ResourceBindingSchema), v__namespace.check(bindings => duplicateHandleMessage(bindings) === void 0, issue => duplicateHandleMessage(issue.input) ?? "duplicate resource handle name"))),
|
|
1842
|
+
definitions: v__namespace.pipe(v__namespace.array(DefinitionSchema), v__namespace.minLength(1, "a deployment needs at least one definition")),
|
|
1843
|
+
runtime: v__namespace.optional(RuntimeBlockSchema)
|
|
1844
|
+
});
|
|
1845
|
+
|
|
1846
|
+
function firstDuplicatePair(items, keyOf) {
|
|
1847
|
+
const seen = /* @__PURE__ */ new Map;
|
|
1848
|
+
for (const item of items) {
|
|
1849
|
+
const key = keyOf(item), earlier = seen.get(key);
|
|
1850
|
+
if (earlier !== void 0) return [ earlier, item ];
|
|
1851
|
+
seen.set(key, item);
|
|
2030
1852
|
}
|
|
2031
|
-
return id.includes(":") ? isGdrUri(id) : isBareDocumentId(id);
|
|
2032
|
-
}
|
|
2033
|
-
|
|
2034
|
-
function isBareSeedId(id) {
|
|
2035
|
-
return isBareDocumentId(id);
|
|
2036
1853
|
}
|
|
2037
1854
|
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
}), seedValueSchemas = {
|
|
2042
|
-
...valueSchemas,
|
|
2043
|
-
"doc.ref": v__namespace.union([ v__namespace.null(), AuthoringGdrShape ]),
|
|
2044
|
-
"doc.refs": v__namespace.array(AuthoringGdrShape),
|
|
2045
|
-
subject: v__namespace.union([ v__namespace.null(), AuthoringGdrShape ]),
|
|
2046
|
-
"release.ref": v__namespace.union([ v__namespace.null(), v__namespace.looseObject({
|
|
2047
|
-
id: AuthoringRefId,
|
|
2048
|
-
type: v__namespace.literal("system.release"),
|
|
2049
|
-
releaseName: NonEmptyString
|
|
2050
|
-
}) ])
|
|
2051
|
-
};
|
|
2052
|
-
|
|
2053
|
-
function checkLiteralSeed(args) {
|
|
2054
|
-
return args.value === null ? [ "a literal seed cannot be null — omit `initialValue` to start the field empty" ] : issuesOf(checkValueAgainst(args, seedValueSchemas));
|
|
1855
|
+
function duplicateHandleMessage(bindings) {
|
|
1856
|
+
const pair = firstDuplicatePair(bindings, binding => binding.name);
|
|
1857
|
+
if (pair !== void 0) return `duplicate resource handle name "${pair[1].name}" — each binding name must be unique within a deployment`;
|
|
2055
1858
|
}
|
|
2056
1859
|
|
|
2057
|
-
function
|
|
2058
|
-
const
|
|
2059
|
-
if (
|
|
2060
|
-
entryType: args.entryType,
|
|
2061
|
-
entryName: args.entryName,
|
|
2062
|
-
issues: [ `field entry type ${args.entryType} does not support append` ],
|
|
2063
|
-
mode: "item"
|
|
2064
|
-
});
|
|
2065
|
-
const result = v__namespace.safeParse(schema, args.item);
|
|
2066
|
-
if (!result.success) throw new FieldValueShapeError({
|
|
2067
|
-
entryType: args.entryType,
|
|
2068
|
-
entryName: args.entryName,
|
|
2069
|
-
issues: formatIssues(result.issues),
|
|
2070
|
-
mode: "item"
|
|
2071
|
-
});
|
|
2072
|
-
const assignmentArgs = {
|
|
2073
|
-
entryType: appendAssignmentEntryType(args.entryType),
|
|
2074
|
-
value: result.output,
|
|
2075
|
-
roles: args.roles,
|
|
2076
|
-
fields: args.entryType === "array" ? args.of : void 0,
|
|
2077
|
-
memberRoles: args.memberRoles,
|
|
2078
|
-
roleAliases: args.roleAliases
|
|
2079
|
-
}, assignmentIssues = assignmentValueIssues(assignmentArgs, result.output);
|
|
2080
|
-
if (assignmentIssues !== void 0) throw new FieldValueShapeError({
|
|
2081
|
-
entryType: args.entryType,
|
|
2082
|
-
entryName: args.entryName,
|
|
2083
|
-
issues: assignmentIssues,
|
|
2084
|
-
mode: "item"
|
|
2085
|
-
});
|
|
2086
|
-
const typeIssues = refTypeIssues({
|
|
2087
|
-
entryType: args.entryType,
|
|
2088
|
-
types: args.types,
|
|
2089
|
-
value: [ args.item ]
|
|
2090
|
-
});
|
|
2091
|
-
if (typeIssues !== void 0) throw new FieldValueShapeError({
|
|
2092
|
-
entryType: args.entryType,
|
|
2093
|
-
entryName: args.entryName,
|
|
2094
|
-
issues: typeIssues,
|
|
2095
|
-
mode: "item"
|
|
2096
|
-
});
|
|
2097
|
-
return result.output;
|
|
1860
|
+
function duplicateNameMessage(deployments) {
|
|
1861
|
+
const pair = firstDuplicatePair(deployments, deployment => deployment.name);
|
|
1862
|
+
if (pair !== void 0) return `duplicate deployment name "${pair[1].name}" — each deployment must use a unique name`;
|
|
2098
1863
|
}
|
|
2099
1864
|
|
|
2100
|
-
function
|
|
2101
|
-
return
|
|
1865
|
+
function partitionKey(deployment) {
|
|
1866
|
+
return `${resourceGdr(deployment.workflowResource)} ${deployment.tag}`;
|
|
2102
1867
|
}
|
|
2103
1868
|
|
|
2104
|
-
function
|
|
2105
|
-
const
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
};
|
|
2109
|
-
return issues.flatMap(issue => formatOne(issue, []));
|
|
1869
|
+
function partitionCollisionMessage(deployments) {
|
|
1870
|
+
const pair = firstDuplicatePair(deployments, partitionKey);
|
|
1871
|
+
if (pair === void 0) return;
|
|
1872
|
+
const [first, second] = pair;
|
|
1873
|
+
return `deployments "${first.name}" and "${second.name}" share workflow resource + tag "${second.tag}" — both would write into the same partition; change one deployment’s tag or resource`;
|
|
2110
1874
|
}
|
|
2111
1875
|
|
|
2112
|
-
const
|
|
1876
|
+
const TelemetryLoggerSchema = v__namespace.custom(input => typeof input == "object" && input !== null && typeof input.log == "function", "expected a telemetry logger (an object with a `log` function)"), WorkflowConfigSchema = v__namespace.object({
|
|
1877
|
+
deployments: v__namespace.pipe(v__namespace.array(DeploymentSchema), v__namespace.minLength(1, "a config needs at least one deployment"), v__namespace.check(deployments => duplicateNameMessage(deployments) === void 0, issue => duplicateNameMessage(issue.input) ?? "duplicate deployment name"), v__namespace.check(deployments => partitionCollisionMessage(deployments) === void 0, issue => partitionCollisionMessage(issue.input) ?? "duplicate deployment partition")),
|
|
1878
|
+
telemetry: v__namespace.optional(TelemetryLoggerSchema)
|
|
1879
|
+
});
|
|
2113
1880
|
|
|
2114
|
-
function
|
|
2115
|
-
return
|
|
1881
|
+
function resourceAliasesToMap(resourceAliases) {
|
|
1882
|
+
return Object.fromEntries((resourceAliases ?? []).map(binding => [ binding.name, binding.resource ]));
|
|
2116
1883
|
}
|
|
2117
1884
|
|
|
2118
|
-
|
|
2119
|
-
return v__namespace.picklist(options, invalidOptionMessage(options));
|
|
2120
|
-
}
|
|
1885
|
+
const FIELD_READ = /^\$fields\.(\w+)(?:\.(.+))?$/, EFFECTS_READ = /^\$effects\['([^']+)'\](?:\.(.+))?$/;
|
|
2121
1886
|
|
|
2122
|
-
function
|
|
2123
|
-
return
|
|
1887
|
+
function isGuardReadExpr(expr) {
|
|
1888
|
+
return expr === "$self" || expr === "$now" || FIELD_READ.test(expr) || EFFECTS_READ.test(expr);
|
|
2124
1889
|
}
|
|
2125
1890
|
|
|
2126
|
-
function
|
|
2127
|
-
|
|
2128
|
-
|
|
1891
|
+
function printGuardRead(read) {
|
|
1892
|
+
switch (read.type) {
|
|
1893
|
+
case "self":
|
|
1894
|
+
return "$self";
|
|
2129
1895
|
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
value: v__namespace.unknown()
|
|
2133
|
-
}), FieldReadSchema = v__namespace.strictObject({
|
|
2134
|
-
type: v__namespace.literal("fieldRead"),
|
|
2135
|
-
scope: v__namespace.optional(v__namespace.union([ v__namespace.literal("workflow"), v__namespace.literal("stage") ])),
|
|
2136
|
-
field: NonEmpty,
|
|
2137
|
-
path: v__namespace.optional(v__namespace.string())
|
|
2138
|
-
}), FieldSourceSchema = v__namespace.variant("type", [ v__namespace.strictObject({
|
|
2139
|
-
type: v__namespace.literal("input")
|
|
2140
|
-
}), v__namespace.strictObject({
|
|
2141
|
-
type: v__namespace.literal("query"),
|
|
2142
|
-
query: NonEmpty
|
|
2143
|
-
}), LiteralSchema, FieldReadSchema ]), ValueExprSchema = v__namespace.lazy(() => v__namespace.variant("type", [ LiteralSchema, FieldReadSchema, v__namespace.strictObject({
|
|
2144
|
-
type: v__namespace.literal("param"),
|
|
2145
|
-
param: NonEmpty
|
|
2146
|
-
}), v__namespace.strictObject({
|
|
2147
|
-
type: v__namespace.literal("actor")
|
|
2148
|
-
}), v__namespace.strictObject({
|
|
2149
|
-
type: v__namespace.literal("now")
|
|
2150
|
-
}), v__namespace.strictObject({
|
|
2151
|
-
type: v__namespace.literal("self")
|
|
2152
|
-
}), v__namespace.strictObject({
|
|
2153
|
-
type: v__namespace.literal("stage")
|
|
2154
|
-
}), v__namespace.strictObject({
|
|
2155
|
-
type: v__namespace.literal("object"),
|
|
2156
|
-
fields: v__namespace.record(NonEmpty, ValueExprSchema)
|
|
2157
|
-
}) ])), StoredFieldRefSchema = v__namespace.strictObject({
|
|
2158
|
-
scope: picklist(FIELD_SCOPES),
|
|
2159
|
-
field: NonEmpty
|
|
2160
|
-
}), AuthoringFieldRefSchema = v__namespace.strictObject({
|
|
2161
|
-
scope: v__namespace.optional(picklist(FIELD_SCOPES)),
|
|
2162
|
-
field: NonEmpty
|
|
2163
|
-
}), HREF_SCHEMES = [ "http:", "https:" ];
|
|
1896
|
+
case "now":
|
|
1897
|
+
return "$now";
|
|
2164
1898
|
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
return
|
|
1899
|
+
case "fieldRead":
|
|
1900
|
+
return read.path === void 0 ? `$fields.${read.field}` : `$fields.${read.field}.${read.path}`;
|
|
1901
|
+
|
|
1902
|
+
case "effectsRead":
|
|
1903
|
+
return read.path === void 0 ? `$effects['${read.effect}']` : `$effects['${read.effect}'].${read.path}`;
|
|
2170
1904
|
}
|
|
2171
1905
|
}
|
|
2172
1906
|
|
|
2173
|
-
|
|
1907
|
+
function groq(strings, ...values) {
|
|
1908
|
+
return strings.reduce((out, part, i) => i === 0 ? part : `${out}${serializeGroqValue(values[i - 1])}${part}`, "");
|
|
1909
|
+
}
|
|
2174
1910
|
|
|
2175
|
-
function
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
}), v__namespace.strictObject({
|
|
2180
|
-
type: v__namespace.literal("field"),
|
|
2181
|
-
field: ref
|
|
2182
|
-
}) ]);
|
|
1911
|
+
function serializeGroqValue(value) {
|
|
1912
|
+
const serialized = JSON.stringify(value);
|
|
1913
|
+
if (serialized === void 0) throw new Error(`groq tag cannot serialize ${typeof value} — interpolate JSON-representable values only`);
|
|
1914
|
+
return serialized;
|
|
2183
1915
|
}
|
|
2184
1916
|
|
|
2185
|
-
|
|
1917
|
+
function desugarWorkflow(authoring) {
|
|
1918
|
+
const issues = [];
|
|
1919
|
+
checkReservedRoleAliasKeys(authoring.roleAliases, issues);
|
|
1920
|
+
const roleAliases = normalizeRoleAliases(authoring.roleAliases), ctx = {
|
|
1921
|
+
issues: issues,
|
|
1922
|
+
roleAliases: roleAliases,
|
|
1923
|
+
effectRuntime: /* @__PURE__ */ new Map
|
|
1924
|
+
}, workflowFields2 = desugarFieldEntries({
|
|
1925
|
+
entries: authoring.fields,
|
|
1926
|
+
path: [ "fields" ],
|
|
1927
|
+
ctx: ctx
|
|
1928
|
+
}), workflowLayer = layerOf(workflowFields2), stages = authoring.stages.map((stage, i) => {
|
|
1929
|
+
const path = [ "stages", i ], stageFields2 = desugarFieldEntries({
|
|
1930
|
+
entries: stage.fields,
|
|
1931
|
+
path: [ ...path, "fields" ],
|
|
1932
|
+
ctx: ctx
|
|
1933
|
+
}), stageEnv = {
|
|
1934
|
+
layers: [ {
|
|
1935
|
+
scope: "stage",
|
|
1936
|
+
entries: layerOf(stageFields2)
|
|
1937
|
+
}, {
|
|
1938
|
+
scope: "workflow",
|
|
1939
|
+
entries: workflowLayer
|
|
1940
|
+
} ]
|
|
1941
|
+
}, activities = (stage.activities ?? []).map((activity, j) => desugarActivity({
|
|
1942
|
+
activity: activity,
|
|
1943
|
+
path: [ ...path, "activities", j ],
|
|
1944
|
+
stageEnv: stageEnv,
|
|
1945
|
+
ctx: ctx
|
|
1946
|
+
})), transitions = (stage.transitions ?? []).map(transition => desugarTransition({
|
|
1947
|
+
transition: transition
|
|
1948
|
+
})), editable = desugarStageEditable({
|
|
1949
|
+
overrides: stage.editable,
|
|
1950
|
+
inScope: editableFieldNames({
|
|
1951
|
+
workflowFields: workflowFields2,
|
|
1952
|
+
stageFields: stageFields2,
|
|
1953
|
+
activities: activities
|
|
1954
|
+
}),
|
|
1955
|
+
path: [ ...path, "editable" ],
|
|
1956
|
+
ctx: ctx
|
|
1957
|
+
});
|
|
1958
|
+
return {
|
|
1959
|
+
...stripUndefined({
|
|
1960
|
+
name: stage.name,
|
|
1961
|
+
semantics: stage.semantics,
|
|
1962
|
+
title: stage.title,
|
|
1963
|
+
description: stage.description,
|
|
1964
|
+
groups: stage.groups,
|
|
1965
|
+
guards: stage.guards?.map(desugarGuard)
|
|
1966
|
+
}),
|
|
1967
|
+
...stageFields2 ? {
|
|
1968
|
+
fields: stageFields2
|
|
1969
|
+
} : {},
|
|
1970
|
+
...activities.length > 0 ? {
|
|
1971
|
+
activities: activities
|
|
1972
|
+
} : {},
|
|
1973
|
+
...transitions.length > 0 ? {
|
|
1974
|
+
transitions: transitions
|
|
1975
|
+
} : {},
|
|
1976
|
+
...editable ? {
|
|
1977
|
+
editable: editable
|
|
1978
|
+
} : {}
|
|
1979
|
+
};
|
|
1980
|
+
});
|
|
1981
|
+
return {
|
|
1982
|
+
definition: {
|
|
1983
|
+
...stripUndefined({
|
|
1984
|
+
name: authoring.name,
|
|
1985
|
+
semantics: authoring.semantics,
|
|
1986
|
+
title: authoring.title,
|
|
1987
|
+
description: authoring.description,
|
|
1988
|
+
groups: authoring.groups,
|
|
1989
|
+
lifecycle: authoring.lifecycle,
|
|
1990
|
+
start: desugarStart(authoring.start),
|
|
1991
|
+
initialStage: authoring.initialStage,
|
|
1992
|
+
predicates: authoring.predicates,
|
|
1993
|
+
roleAliases: roleAliases
|
|
1994
|
+
}),
|
|
1995
|
+
...workflowFields2 ? {
|
|
1996
|
+
fields: workflowFields2
|
|
1997
|
+
} : {},
|
|
1998
|
+
stages: stages
|
|
1999
|
+
},
|
|
2000
|
+
runtime: collectRuntime({
|
|
2001
|
+
authoring: authoring,
|
|
2002
|
+
ctx: ctx
|
|
2003
|
+
}),
|
|
2004
|
+
issues: ctx.issues
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2186
2007
|
|
|
2187
|
-
function
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
value: v__namespace.optional(ValueExprSchema)
|
|
2211
|
-
}), v__namespace.strictObject({
|
|
2212
|
-
type: v__namespace.literal("field.updateWhere"),
|
|
2213
|
-
target: targetSchema,
|
|
2214
|
-
where: ConditionSchema,
|
|
2215
|
-
value: ValueExprSchema
|
|
2216
|
-
}), v__namespace.strictObject({
|
|
2217
|
-
type: v__namespace.literal("field.removeWhere"),
|
|
2218
|
-
target: targetSchema,
|
|
2219
|
-
where: ConditionSchema
|
|
2220
|
-
}) ];
|
|
2008
|
+
function collectRuntime(args) {
|
|
2009
|
+
const {kind: kind} = args.authoring.runtime ?? {}, effects = Object.fromEntries(args.ctx.effectRuntime), declared = {
|
|
2010
|
+
...kind === void 0 ? {} : {
|
|
2011
|
+
kind: kind
|
|
2012
|
+
},
|
|
2013
|
+
...Object.keys(effects).length === 0 ? {} : {
|
|
2014
|
+
effects: effects
|
|
2015
|
+
}
|
|
2016
|
+
};
|
|
2017
|
+
return Object.keys(declared).length === 0 ? void 0 : declared;
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
function desugarEffect(args) {
|
|
2021
|
+
const {runtime: runtime, ...effect} = args.effect;
|
|
2022
|
+
runtime !== void 0 && args.ctx.effectRuntime.set(effect.name, runtime);
|
|
2023
|
+
const {retry: retry} = effect;
|
|
2024
|
+
return retry === void 0 ? effect : {
|
|
2025
|
+
...effect,
|
|
2026
|
+
retry: {
|
|
2027
|
+
...retry,
|
|
2028
|
+
kind: retry.kind ?? "engine"
|
|
2029
|
+
}
|
|
2030
|
+
};
|
|
2221
2031
|
}
|
|
2222
2032
|
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
}))
|
|
2235
|
-
}), AuthoringOpSchema = pinned()(v__namespace.variant("type", [ ...opSchemas(AuthoringFieldRefSchema), v__namespace.strictObject({
|
|
2236
|
-
type: v__namespace.literal("status.set"),
|
|
2237
|
-
activity: v__namespace.optional(NonEmpty),
|
|
2238
|
-
status: picklist(ACTIVITY_STATUSES)
|
|
2239
|
-
}), AuditOpSchema ])), GroupName = v__namespace.pipe(v__namespace.string(), v__namespace.regex(GROQ_IDENTIFIER, "must be an identifier (letters, digits, underscore; not starting with a digit)")), GroupSchema = pinned()(v__namespace.strictObject({
|
|
2240
|
-
name: GroupName,
|
|
2241
|
-
title: v__namespace.optional(v__namespace.string()),
|
|
2242
|
-
description: v__namespace.optional(v__namespace.string()),
|
|
2243
|
-
kind: v__namespace.optional(picklist(GROUP_KINDS))
|
|
2244
|
-
})), GroupNameList = v__namespace.pipe(v__namespace.array(GroupName), v__namespace.minLength(1, "name at least one group, or omit `group`"), v__namespace.check(names => new Set(names).size === names.length, "a group is listed more than once — list each group once")), StoredGroupMembershipSchema = GroupNameList, AuthoringGroupMembershipSchema = v__namespace.union([ GroupName, GroupNameList ]);
|
|
2033
|
+
function desugarStart(start) {
|
|
2034
|
+
if (start !== void 0) return {
|
|
2035
|
+
kind: start.kind ?? "interactive",
|
|
2036
|
+
...start.filter !== void 0 ? {
|
|
2037
|
+
filter: start.filter
|
|
2038
|
+
} : {},
|
|
2039
|
+
...start.requirements !== void 0 ? {
|
|
2040
|
+
requirements: start.requirements
|
|
2041
|
+
} : {}
|
|
2042
|
+
};
|
|
2043
|
+
}
|
|
2245
2044
|
|
|
2246
|
-
function
|
|
2247
|
-
|
|
2045
|
+
function checkReservedRoleAliasKeys(aliases, issues) {
|
|
2046
|
+
for (const key of Object.keys(aliases ?? {})) key.startsWith("$") && issues.push({
|
|
2047
|
+
path: [ "roleAliases", key ],
|
|
2048
|
+
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.`
|
|
2049
|
+
});
|
|
2248
2050
|
}
|
|
2249
2051
|
|
|
2250
|
-
const
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
}
|
|
2052
|
+
const TODOLIST_OF = [ {
|
|
2053
|
+
type: "string",
|
|
2054
|
+
name: "label",
|
|
2055
|
+
title: "Label"
|
|
2056
|
+
}, {
|
|
2057
|
+
type: "string",
|
|
2058
|
+
name: "status",
|
|
2059
|
+
title: "Status"
|
|
2060
|
+
}, {
|
|
2061
|
+
type: "assignee",
|
|
2062
|
+
name: "assignee",
|
|
2063
|
+
title: "Assignee"
|
|
2064
|
+
}, {
|
|
2065
|
+
type: "date",
|
|
2066
|
+
name: "dueDate",
|
|
2067
|
+
title: "Due date"
|
|
2068
|
+
} ], NOTES_OF = [ {
|
|
2069
|
+
type: "text",
|
|
2070
|
+
name: "body",
|
|
2071
|
+
title: "Body"
|
|
2072
|
+
}, {
|
|
2073
|
+
type: "actor",
|
|
2074
|
+
name: "actor",
|
|
2075
|
+
title: "Actor"
|
|
2076
|
+
}, {
|
|
2077
|
+
type: "datetime",
|
|
2078
|
+
name: "at",
|
|
2079
|
+
title: "At"
|
|
2080
|
+
} ];
|
|
2259
2081
|
|
|
2260
|
-
function
|
|
2261
|
-
return
|
|
2082
|
+
function desugarFieldEntries({entries: entries, path: path, ctx: ctx}) {
|
|
2083
|
+
return !entries || entries.length === 0 ? entries === void 0 ? void 0 : [] : entries.map((entry, i) => desugarFieldEntry({
|
|
2084
|
+
entry: entry,
|
|
2085
|
+
path: [ ...path, i ],
|
|
2086
|
+
ctx: ctx
|
|
2087
|
+
}));
|
|
2262
2088
|
}
|
|
2263
2089
|
|
|
2264
|
-
function
|
|
2265
|
-
|
|
2266
|
-
|
|
2090
|
+
function desugarFieldEntry({entry: entry, path: path, ctx: ctx}) {
|
|
2091
|
+
if (entry.type === "todoList" || entry.type === "notes") return desugarListField({
|
|
2092
|
+
entry: entry,
|
|
2093
|
+
path: path,
|
|
2094
|
+
ctx: ctx
|
|
2095
|
+
});
|
|
2096
|
+
const editable = normalizeEditable({
|
|
2097
|
+
editable: entry.editable,
|
|
2098
|
+
path: [ ...path, "editable" ],
|
|
2099
|
+
ctx: ctx
|
|
2100
|
+
});
|
|
2101
|
+
return {
|
|
2102
|
+
...stripUndefined({
|
|
2103
|
+
type: entry.type,
|
|
2104
|
+
name: entry.name,
|
|
2105
|
+
title: entry.title,
|
|
2106
|
+
description: entry.description,
|
|
2107
|
+
group: normalizeGroup(entry.group),
|
|
2108
|
+
required: entry.required,
|
|
2109
|
+
initialValue: entry.initialValue,
|
|
2110
|
+
editable: editable,
|
|
2111
|
+
options: entry.options,
|
|
2112
|
+
validation: entry.validation,
|
|
2113
|
+
types: entry.types,
|
|
2114
|
+
roles: entry.roles,
|
|
2115
|
+
fields: entry.fields,
|
|
2116
|
+
of: entry.of
|
|
2117
|
+
})
|
|
2118
|
+
};
|
|
2267
2119
|
}
|
|
2268
2120
|
|
|
2269
|
-
function
|
|
2270
|
-
const
|
|
2271
|
-
|
|
2121
|
+
function desugarListField({entry: entry, path: path, ctx: ctx}) {
|
|
2122
|
+
const editable = normalizeEditable({
|
|
2123
|
+
editable: entry.editable,
|
|
2124
|
+
path: [ ...path, "editable" ],
|
|
2125
|
+
ctx: ctx
|
|
2126
|
+
});
|
|
2127
|
+
return {
|
|
2128
|
+
...stripUndefined({
|
|
2129
|
+
name: entry.name,
|
|
2130
|
+
title: entry.title,
|
|
2131
|
+
description: entry.description,
|
|
2132
|
+
group: normalizeGroup(entry.group),
|
|
2133
|
+
required: entry.required,
|
|
2134
|
+
initialValue: entry.initialValue,
|
|
2135
|
+
editable: editable
|
|
2136
|
+
}),
|
|
2137
|
+
type: "array",
|
|
2138
|
+
of: entry.type === "todoList" ? TODOLIST_OF : NOTES_OF
|
|
2139
|
+
};
|
|
2272
2140
|
}
|
|
2273
2141
|
|
|
2274
|
-
function
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2142
|
+
function normalizeEditable({editable: editable, path: path, ctx: ctx}) {
|
|
2143
|
+
if (editable === void 0 || editable === !0) return editable;
|
|
2144
|
+
if (Array.isArray(editable)) {
|
|
2145
|
+
const condition = rolesCondition(editable, ctx.roleAliases);
|
|
2146
|
+
if (condition === void 0) {
|
|
2147
|
+
ctx.issues.push({
|
|
2148
|
+
path: path,
|
|
2149
|
+
message: "editable: [] names no roles — use `true` to open the field to anyone in its scope, or list at least one role"
|
|
2150
|
+
});
|
|
2151
|
+
return;
|
|
2284
2152
|
}
|
|
2153
|
+
return condition;
|
|
2285
2154
|
}
|
|
2155
|
+
return editable;
|
|
2286
2156
|
}
|
|
2287
2157
|
|
|
2288
|
-
function
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
const AssignmentRolesSchema = v__namespace.pipe(v__namespace.array(NonEmpty), v__namespace.minLength(1, "declare at least one eligible role, or omit `roles`")), FieldShapeSchema = v__namespace.lazy(() => v__namespace.pipe(compositeChecked({
|
|
2293
|
-
type: FieldValueKindSchema,
|
|
2294
|
-
name: FieldEntryName,
|
|
2295
|
-
title: v__namespace.optional(v__namespace.string()),
|
|
2296
|
-
description: v__namespace.optional(v__namespace.string()),
|
|
2297
|
-
options: v__namespace.optional(ChoiceOptionsSchema),
|
|
2298
|
-
validation: v__namespace.optional(ScalarValidationSchema),
|
|
2299
|
-
roles: v__namespace.optional(AssignmentRolesSchema),
|
|
2300
|
-
fields: v__namespace.optional(v__namespace.array(FieldShapeSchema)),
|
|
2301
|
-
of: v__namespace.optional(v__namespace.array(FieldShapeSchema))
|
|
2302
|
-
}), choiceOptionsCheck(), scalarValidationCheck(), assignmentRolesCheck())), StoredEditableSchema = pinned()(v__namespace.union([ v__namespace.literal(!0), NonEmpty ])), AuthoringEditableSchema = pinned()(v__namespace.union([ v__namespace.literal(!0), v__namespace.array(NonEmpty), NonEmpty ]));
|
|
2303
|
-
|
|
2304
|
-
function fieldBase(editable, group) {
|
|
2305
|
-
return {
|
|
2306
|
-
name: FieldEntryName,
|
|
2307
|
-
title: v__namespace.optional(v__namespace.string()),
|
|
2308
|
-
description: v__namespace.optional(v__namespace.string()),
|
|
2309
|
-
group: v__namespace.optional(group),
|
|
2310
|
-
required: v__namespace.optional(v__namespace.boolean()),
|
|
2311
|
-
initialValue: v__namespace.optional(FieldSourceSchema),
|
|
2312
|
-
editable: v__namespace.optional(editable)
|
|
2158
|
+
function editableFieldNames({workflowFields: workflowFields2, stageFields: stageFields2, activities: activities}) {
|
|
2159
|
+
const names = /* @__PURE__ */ new Set, collect = entries => {
|
|
2160
|
+
for (const entry of entries ?? []) entry.editable !== void 0 && names.add(entry.name);
|
|
2313
2161
|
};
|
|
2162
|
+
collect(workflowFields2), collect(stageFields2);
|
|
2163
|
+
for (const activity of activities) collect(activity.fields);
|
|
2164
|
+
return names;
|
|
2314
2165
|
}
|
|
2315
2166
|
|
|
2316
|
-
function
|
|
2317
|
-
return
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2167
|
+
function desugarStageEditable({overrides: overrides, inScope: inScope, path: path, ctx: ctx}) {
|
|
2168
|
+
if (overrides === void 0) return;
|
|
2169
|
+
const out = {};
|
|
2170
|
+
for (const [name, value] of Object.entries(overrides)) {
|
|
2171
|
+
if (!inScope.has(name)) {
|
|
2172
|
+
ctx.issues.push({
|
|
2173
|
+
path: [ ...path, name ],
|
|
2174
|
+
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\``
|
|
2175
|
+
});
|
|
2176
|
+
continue;
|
|
2177
|
+
}
|
|
2178
|
+
const normalized = normalizeEditable({
|
|
2179
|
+
editable: value,
|
|
2180
|
+
path: [ ...path, name ],
|
|
2181
|
+
ctx: ctx
|
|
2182
|
+
});
|
|
2183
|
+
normalized !== void 0 && (out[name] = normalized);
|
|
2184
|
+
}
|
|
2185
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
2327
2186
|
}
|
|
2328
2187
|
|
|
2329
|
-
function
|
|
2330
|
-
|
|
2331
|
-
entryType: entry.type,
|
|
2332
|
-
value: entry.initialValue.value,
|
|
2333
|
-
types: entry.types,
|
|
2334
|
-
fields: entry.fields,
|
|
2335
|
-
of: entry.of,
|
|
2336
|
-
options: entry.options,
|
|
2337
|
-
validation: entry.validation,
|
|
2338
|
-
roles: entry.roles
|
|
2339
|
-
});
|
|
2188
|
+
function layerOf(entries) {
|
|
2189
|
+
return new Map((entries ?? []).map(entry => [ entry.name, entry ]));
|
|
2340
2190
|
}
|
|
2341
2191
|
|
|
2342
|
-
function
|
|
2343
|
-
return
|
|
2192
|
+
function normalizeGroup(group) {
|
|
2193
|
+
return group === void 0 || Array.isArray(group) ? group : [ group ];
|
|
2344
2194
|
}
|
|
2345
2195
|
|
|
2346
|
-
function
|
|
2347
|
-
|
|
2196
|
+
function desugarActivity({activity: activity, path: path, stageEnv: stageEnv, ctx: ctx}) {
|
|
2197
|
+
const activityFields2 = desugarFieldEntries({
|
|
2198
|
+
entries: activity.fields,
|
|
2199
|
+
path: [ ...path, "fields" ],
|
|
2200
|
+
ctx: ctx
|
|
2201
|
+
}), env = {
|
|
2202
|
+
layers: [ {
|
|
2203
|
+
scope: "activity",
|
|
2204
|
+
entries: layerOf(activityFields2)
|
|
2205
|
+
}, ...stageEnv.layers ]
|
|
2206
|
+
}, actions = (activity.actions ?? []).map((action, a) => desugarAction({
|
|
2207
|
+
action: action,
|
|
2208
|
+
path: [ ...path, "actions", a ],
|
|
2209
|
+
env: env,
|
|
2210
|
+
activityName: activity.name,
|
|
2211
|
+
ctx: ctx
|
|
2212
|
+
})), target = desugarTarget({
|
|
2213
|
+
target: activity.target,
|
|
2214
|
+
env: env,
|
|
2215
|
+
path: [ ...path, "target" ],
|
|
2216
|
+
ctx: ctx
|
|
2217
|
+
});
|
|
2218
|
+
return {
|
|
2219
|
+
...stripUndefined({
|
|
2220
|
+
name: activity.name,
|
|
2221
|
+
semantics: activity.semantics,
|
|
2222
|
+
title: activity.title,
|
|
2223
|
+
description: activity.description,
|
|
2224
|
+
groups: activity.groups,
|
|
2225
|
+
group: normalizeGroup(activity.group),
|
|
2226
|
+
filter: activity.filter,
|
|
2227
|
+
requirements: activity.requirements
|
|
2228
|
+
}),
|
|
2229
|
+
...target ? {
|
|
2230
|
+
target: target
|
|
2231
|
+
} : {},
|
|
2232
|
+
...activityFields2 ? {
|
|
2233
|
+
fields: activityFields2
|
|
2234
|
+
} : {},
|
|
2235
|
+
...actions.length > 0 ? {
|
|
2236
|
+
actions: actions
|
|
2237
|
+
} : {}
|
|
2238
|
+
};
|
|
2348
2239
|
}
|
|
2349
2240
|
|
|
2350
|
-
|
|
2351
|
-
return v__namespace.check(entry => entry.roles === void 0 || assignmentKindAcceptsRoles(entry.type), issue => `\`roles\` is only valid on \`assignee\` / \`assignees\` entries, not "${issue.input.type}"`);
|
|
2352
|
-
}
|
|
2241
|
+
const TARGET_DOC_KINDS = [ "doc.ref", "doc.refs", "subject", "release.ref" ];
|
|
2353
2242
|
|
|
2354
|
-
function
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
}) ??
|
|
2243
|
+
function desugarTarget({target: target, env: env, path: path, ctx: ctx}) {
|
|
2244
|
+
if (target === void 0 || target.type === "url") return target;
|
|
2245
|
+
const ref = typeof target.field == "string" ? {
|
|
2246
|
+
field: target.field
|
|
2247
|
+
} : target.field, field = resolveRef({
|
|
2248
|
+
ref: ref,
|
|
2249
|
+
env: env,
|
|
2250
|
+
path: [ ...path, "field" ],
|
|
2251
|
+
ctx: ctx
|
|
2252
|
+
}) ?? fallbackRef(ref), entry = entryAt(env, field);
|
|
2253
|
+
return entry && !TARGET_DOC_KINDS.includes(entry.type) && ctx.issues.push({
|
|
2254
|
+
path: [ ...path, "field" ],
|
|
2255
|
+
message: `manual activity target references "${field.field}" of kind "${entry.type}" — a deep-link target needs a document-valued entry (${TARGET_DOC_KINDS.join(", ")})`
|
|
2256
|
+
}), {
|
|
2257
|
+
type: "field",
|
|
2258
|
+
field: field
|
|
2259
|
+
};
|
|
2364
2260
|
}
|
|
2365
2261
|
|
|
2366
|
-
|
|
2262
|
+
function reportEmptyActionRoles({action: action, path: path, ctx: ctx}) {
|
|
2263
|
+
action.roles === void 0 || action.roles.length > 0 || ctx.issues.push({
|
|
2264
|
+
path: [ ...path, "roles" ],
|
|
2265
|
+
message: "roles: [] names no roles — omit it to allow any identity, or list at least one role"
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2367
2268
|
|
|
2368
|
-
function
|
|
2369
|
-
|
|
2269
|
+
function desugarActionOps(args) {
|
|
2270
|
+
const {action: action, path: path, env: env, activityName: activityName, ctx: ctx} = args, ops = desugarOps({
|
|
2271
|
+
ops: action.ops,
|
|
2272
|
+
path: [ ...path, "ops" ],
|
|
2273
|
+
env: env,
|
|
2274
|
+
firingActivity: activityName,
|
|
2275
|
+
ctx: ctx
|
|
2276
|
+
}) ?? [];
|
|
2277
|
+
return action.status !== void 0 && ops.push({
|
|
2278
|
+
type: "status.set",
|
|
2279
|
+
activity: activityName,
|
|
2280
|
+
status: action.status
|
|
2281
|
+
}), ops;
|
|
2370
2282
|
}
|
|
2371
2283
|
|
|
2372
|
-
function
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2284
|
+
function desugarAction({action: action, path: path, env: env, activityName: activityName, ctx: ctx}) {
|
|
2285
|
+
reportEmptyActionRoles({
|
|
2286
|
+
action: action,
|
|
2287
|
+
path: path,
|
|
2288
|
+
ctx: ctx
|
|
2289
|
+
});
|
|
2290
|
+
const cascadeFired = isCascadeFired(action), filter = cascadeFired ? action.filter : andConditions([ rolesCondition(action.roles, ctx.roleAliases), action.filter ]), ops = desugarActionOps({
|
|
2291
|
+
action: action,
|
|
2292
|
+
path: path,
|
|
2293
|
+
env: env,
|
|
2294
|
+
activityName: activityName,
|
|
2295
|
+
ctx: ctx
|
|
2296
|
+
});
|
|
2297
|
+
return {
|
|
2298
|
+
...stripUndefined({
|
|
2299
|
+
name: action.name,
|
|
2300
|
+
semantics: action.semantics,
|
|
2301
|
+
title: action.title,
|
|
2302
|
+
description: action.description,
|
|
2303
|
+
group: normalizeGroup(action.group),
|
|
2304
|
+
when: action.when,
|
|
2305
|
+
params: action.params,
|
|
2306
|
+
effects: action.effects?.map(effect => desugarEffect({
|
|
2307
|
+
effect: effect,
|
|
2308
|
+
ctx: ctx
|
|
2309
|
+
})),
|
|
2310
|
+
spawn: action.spawn
|
|
2311
|
+
}),
|
|
2312
|
+
...cascadeFired && action.roles !== void 0 && action.roles.length > 0 ? {
|
|
2313
|
+
roles: action.roles
|
|
2314
|
+
} : {},
|
|
2315
|
+
...filter ? {
|
|
2316
|
+
filter: filter
|
|
2317
|
+
} : {},
|
|
2318
|
+
...ops.length > 0 ? {
|
|
2319
|
+
ops: ops
|
|
2320
|
+
} : {}
|
|
2321
|
+
};
|
|
2383
2322
|
}
|
|
2384
2323
|
|
|
2385
|
-
const
|
|
2386
|
-
editable: StoredEditableSchema,
|
|
2387
|
-
group: StoredGroupMembershipSchema
|
|
2388
|
-
})), refTypesCheck(), assignmentRolesCheck(), choiceOptionsCheck(), scalarValidationCheck(), literalSeedCheck())), RawAuthoringFieldEntrySchema = pinned()(v__namespace.pipe(compositeChecked(fieldEntryFields({
|
|
2389
|
-
editable: AuthoringEditableSchema,
|
|
2390
|
-
group: AuthoringGroupMembershipSchema,
|
|
2391
|
-
kind: AuthoringRawFieldKindSchema
|
|
2392
|
-
})), refTypesCheck(), assignmentRolesCheck(), choiceOptionsCheck(), scalarValidationCheck(), literalSeedCheck()));
|
|
2324
|
+
const DEFAULT_TRANSITION_WHEN = "$allActivitiesDone";
|
|
2393
2325
|
|
|
2394
|
-
function
|
|
2326
|
+
function desugarTransition({transition: transition}) {
|
|
2395
2327
|
return {
|
|
2396
|
-
|
|
2397
|
-
|
|
2328
|
+
...stripUndefined({
|
|
2329
|
+
name: transition.name,
|
|
2330
|
+
title: transition.title,
|
|
2331
|
+
description: transition.description,
|
|
2332
|
+
to: transition.to
|
|
2333
|
+
}),
|
|
2334
|
+
when: transition.when ?? DEFAULT_TRANSITION_WHEN
|
|
2398
2335
|
};
|
|
2399
2336
|
}
|
|
2400
2337
|
|
|
2401
|
-
|
|
2402
|
-
const
|
|
2403
|
-
return
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
}
|
|
2415
|
-
forEach: NonEmpty,
|
|
2416
|
-
definition: DefinitionRefSchema,
|
|
2417
|
-
with: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
|
|
2418
|
-
context: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
|
|
2419
|
-
onExit: v__namespace.optional(picklist([ "detach", "abort" ]))
|
|
2420
|
-
}), ActionParamSchema = v__namespace.pipe(v__namespace.strictObject({
|
|
2421
|
-
type: picklist([ "string", "number", "boolean", "url", "dateTime", "actor", "doc.ref", "doc.refs", "json" ]),
|
|
2422
|
-
name: NonEmpty,
|
|
2423
|
-
title: v__namespace.optional(v__namespace.string()),
|
|
2424
|
-
description: v__namespace.optional(v__namespace.string()),
|
|
2425
|
-
required: v__namespace.optional(v__namespace.boolean()),
|
|
2426
|
-
options: v__namespace.optional(ChoiceOptionsSchema),
|
|
2427
|
-
validation: v__namespace.optional(ScalarValidationSchema)
|
|
2428
|
-
}), choiceOptionsCheck(), scalarValidationCheck()), CUSTOM_SEMANTIC_HINT = "`custom.<camelCaseMeaning>`", CustomSemanticSchema = v__namespace.custom(input => typeof input == "string" && /^custom\.[a-z][a-zA-Z0-9]*$/.test(input)), SemanticSchema = v__namespace.union([ picklist(SIGNAL_SEMANTICS), CustomSemanticSchema ], `expected ${SIGNAL_SEMANTICS.join(", ")}, or ${CUSTOM_SEMANTIC_HINT}`), ActionSemanticSchema = v__namespace.union([ picklist(ACTION_SEMANTICS), CustomSemanticSchema ], `expected ${ACTION_SEMANTICS.join(", ")}, or ${CUSTOM_SEMANTIC_HINT}`);
|
|
2429
|
-
|
|
2430
|
-
function semanticNamespace(semantic) {
|
|
2431
|
-
return semantic.startsWith("custom.") ? semantic : semantic.split(".", 1)[0] ?? semantic;
|
|
2338
|
+
function desugarGuard(guard) {
|
|
2339
|
+
const {match: match, metadata: metadata, ...rest} = guard, {idRefs: idRefs, ...matchRest} = match;
|
|
2340
|
+
return {
|
|
2341
|
+
...rest,
|
|
2342
|
+
match: {
|
|
2343
|
+
...matchRest,
|
|
2344
|
+
...idRefs !== void 0 ? {
|
|
2345
|
+
idRefs: idRefs.map(printGuardRead)
|
|
2346
|
+
} : {}
|
|
2347
|
+
},
|
|
2348
|
+
...metadata !== void 0 ? {
|
|
2349
|
+
metadata: Object.fromEntries(Object.entries(metadata).map(([key, read]) => [ key, printGuardRead(read) ]))
|
|
2350
|
+
} : {}
|
|
2351
|
+
};
|
|
2432
2352
|
}
|
|
2433
2353
|
|
|
2434
|
-
function
|
|
2435
|
-
|
|
2436
|
-
|
|
2354
|
+
function desugarOps({ops: ops, path: path, env: env, firingActivity: firingActivity, ctx: ctx}) {
|
|
2355
|
+
if (ops) return ops.map((op, i) => desugarOp({
|
|
2356
|
+
op: op,
|
|
2357
|
+
path: [ ...path, i ],
|
|
2358
|
+
env: env,
|
|
2359
|
+
firingActivity: firingActivity,
|
|
2360
|
+
ctx: ctx
|
|
2361
|
+
}));
|
|
2437
2362
|
}
|
|
2438
2363
|
|
|
2439
|
-
function
|
|
2440
|
-
|
|
2364
|
+
function desugarOp({op: op, path: path, env: env, firingActivity: firingActivity, ctx: ctx}) {
|
|
2365
|
+
if (op.type === "status.set") return {
|
|
2366
|
+
type: "status.set",
|
|
2367
|
+
activity: op.activity ?? firingActivity,
|
|
2368
|
+
status: op.status
|
|
2369
|
+
};
|
|
2370
|
+
if (op.type === "audit") return desugarAuditOp({
|
|
2371
|
+
op: op,
|
|
2372
|
+
path: path,
|
|
2373
|
+
env: env,
|
|
2374
|
+
ctx: ctx
|
|
2375
|
+
});
|
|
2376
|
+
const target = resolveRef({
|
|
2377
|
+
ref: op.target,
|
|
2378
|
+
env: env,
|
|
2379
|
+
path: [ ...path, "target" ],
|
|
2380
|
+
ctx: ctx
|
|
2381
|
+
}) ?? fallbackRef(op.target);
|
|
2382
|
+
return {
|
|
2383
|
+
...op,
|
|
2384
|
+
target: target
|
|
2385
|
+
};
|
|
2441
2386
|
}
|
|
2442
2387
|
|
|
2443
|
-
const
|
|
2388
|
+
const AUDIT_STAMPS = {
|
|
2389
|
+
actor: {
|
|
2390
|
+
type: "actor"
|
|
2391
|
+
},
|
|
2392
|
+
at: {
|
|
2393
|
+
type: "now"
|
|
2394
|
+
}
|
|
2395
|
+
};
|
|
2444
2396
|
|
|
2445
|
-
function
|
|
2397
|
+
function desugarAuditOp({op: op, path: path, env: env, ctx: ctx}) {
|
|
2398
|
+
const target = resolveRef({
|
|
2399
|
+
ref: op.target,
|
|
2400
|
+
env: env,
|
|
2401
|
+
path: [ ...path, "target" ],
|
|
2402
|
+
ctx: ctx
|
|
2403
|
+
}) ?? fallbackRef(op.target);
|
|
2404
|
+
if (op.value.type !== "object") return ctx.issues.push({
|
|
2405
|
+
path: [ ...path, "value" ],
|
|
2406
|
+
message: `audit value must be an object source carrying the domain fields (got "${op.value.type}")`
|
|
2407
|
+
}), {
|
|
2408
|
+
type: "field.append",
|
|
2409
|
+
target: target,
|
|
2410
|
+
value: op.value
|
|
2411
|
+
};
|
|
2412
|
+
const actorField = op.stampFields?.actor ?? "actor", atField = op.stampFields?.at ?? "at", fields = {
|
|
2413
|
+
...op.value.fields
|
|
2414
|
+
};
|
|
2415
|
+
for (const [stamp, source] of [ [ actorField, AUDIT_STAMPS.actor ], [ atField, AUDIT_STAMPS.at ] ]) {
|
|
2416
|
+
if (stamp in fields) {
|
|
2417
|
+
ctx.issues.push({
|
|
2418
|
+
path: [ ...path, "value", "fields", stamp ],
|
|
2419
|
+
message: `audit stamp field "${stamp}" collides with an authored domain field — rename yours or remap the stamp via stampFields`
|
|
2420
|
+
});
|
|
2421
|
+
continue;
|
|
2422
|
+
}
|
|
2423
|
+
fields[stamp] = source;
|
|
2424
|
+
}
|
|
2446
2425
|
return {
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
filter: v__namespace.optional(ConditionSchema),
|
|
2454
|
-
params: v__namespace.optional(v__namespace.array(ActionParamSchema)),
|
|
2455
|
-
ops: v__namespace.optional(v__namespace.array(op)),
|
|
2456
|
-
effects: v__namespace.optional(v__namespace.array(EffectSchema)),
|
|
2457
|
-
spawn: v__namespace.optional(SubworkflowsSchema)
|
|
2426
|
+
type: "field.append",
|
|
2427
|
+
target: target,
|
|
2428
|
+
value: {
|
|
2429
|
+
type: "object",
|
|
2430
|
+
fields: fields
|
|
2431
|
+
}
|
|
2458
2432
|
};
|
|
2459
2433
|
}
|
|
2460
2434
|
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
roles: v__namespace.optional(v__namespace.array(NonEmpty)),
|
|
2467
|
-
status: v__namespace.optional(TerminalActivityStatus)
|
|
2468
|
-
})), AuthoringActionSchema = pinned()(RawAuthoringActionSchema), requirementBase = {
|
|
2469
|
-
name: NonEmpty,
|
|
2470
|
-
title: v__namespace.optional(v__namespace.string()),
|
|
2471
|
-
description: v__namespace.optional(v__namespace.string())
|
|
2472
|
-
}, GroqRequirementSchemaRaw = v__namespace.strictObject({
|
|
2473
|
-
...requirementBase,
|
|
2474
|
-
type: v__namespace.literal("groq"),
|
|
2475
|
-
query: ConditionSchema
|
|
2476
|
-
}), SingleSubjectRequirementSchemaRaw = v__namespace.strictObject({
|
|
2477
|
-
...requirementBase,
|
|
2478
|
-
type: v__namespace.literal("singleSubject")
|
|
2479
|
-
}), GroqRequirementSchema = pinned()(GroqRequirementSchemaRaw), StartRequirementSchema = pinned()(v__namespace.variant("type", [ GroqRequirementSchemaRaw, SingleSubjectRequirementSchemaRaw ]));
|
|
2480
|
-
|
|
2481
|
-
function activityFields({field: field, action: action, target: target, group: group}) {
|
|
2482
|
-
return {
|
|
2483
|
-
name: NonEmpty,
|
|
2484
|
-
semantics: SemanticsFieldSchema,
|
|
2485
|
-
title: v__namespace.optional(v__namespace.string()),
|
|
2486
|
-
description: v__namespace.optional(v__namespace.string()),
|
|
2487
|
-
groups: v__namespace.optional(v__namespace.array(GroupSchema)),
|
|
2488
|
-
group: v__namespace.optional(group),
|
|
2489
|
-
target: v__namespace.optional(target),
|
|
2490
|
-
filter: v__namespace.optional(ConditionSchema),
|
|
2491
|
-
requirements: v__namespace.optional(v__namespace.array(GroqRequirementSchema)),
|
|
2492
|
-
actions: v__namespace.optional(v__namespace.array(action)),
|
|
2493
|
-
fields: v__namespace.optional(v__namespace.array(field))
|
|
2435
|
+
function resolveRef({ref: ref, env: env, path: path, ctx: ctx}) {
|
|
2436
|
+
const layers = ref.scope === void 0 ? env.layers : env.layers.filter(l => l.scope === ref.scope);
|
|
2437
|
+
for (const layer of layers) if (layer.entries.has(ref.field)) return {
|
|
2438
|
+
scope: layer.scope,
|
|
2439
|
+
field: ref.field
|
|
2494
2440
|
};
|
|
2441
|
+
const reachable = env.layers.flatMap(l => [ ...l.entries.keys() ].map(n => `${l.scope}:${n}`));
|
|
2442
|
+
ctx.issues.push({
|
|
2443
|
+
path: path,
|
|
2444
|
+
message: `field reference "${ref.field}"${ref.scope ? ` (scope "${ref.scope}")` : ""} does not resolve to a declared entry. Reachable: ${reachable.join(", ") || "(none)"}`
|
|
2445
|
+
});
|
|
2495
2446
|
}
|
|
2496
2447
|
|
|
2497
|
-
|
|
2498
|
-
field
|
|
2499
|
-
|
|
2500
|
-
target: StoredManualTargetSchema,
|
|
2501
|
-
group: StoredGroupMembershipSchema
|
|
2502
|
-
}))), AuthoringActivitySchema = pinned()(v__namespace.strictObject(activityFields({
|
|
2503
|
-
field: AuthoringFieldEntrySchema,
|
|
2504
|
-
action: AuthoringActionSchema,
|
|
2505
|
-
target: AuthoringManualTargetSchema,
|
|
2506
|
-
group: AuthoringGroupMembershipSchema
|
|
2507
|
-
})));
|
|
2448
|
+
function entryAt(env, ref) {
|
|
2449
|
+
return env.layers.find(l => l.scope === ref.scope)?.entries.get(ref.field);
|
|
2450
|
+
}
|
|
2508
2451
|
|
|
2509
|
-
function
|
|
2452
|
+
function fallbackRef(ref) {
|
|
2510
2453
|
return {
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
description: v__namespace.optional(v__namespace.string()),
|
|
2514
|
-
to: NonEmpty,
|
|
2515
|
-
when: when
|
|
2454
|
+
scope: ref.scope ?? "workflow",
|
|
2455
|
+
field: ref.field
|
|
2516
2456
|
};
|
|
2517
2457
|
}
|
|
2518
2458
|
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
}
|
|
2522
|
-
type: v__namespace.literal("now")
|
|
2523
|
-
}), v__namespace.strictObject({
|
|
2524
|
-
type: v__namespace.literal("fieldRead"),
|
|
2525
|
-
field: FieldEntryName,
|
|
2526
|
-
path: v__namespace.optional(GuardReadPath)
|
|
2527
|
-
}), v__namespace.strictObject({
|
|
2528
|
-
type: v__namespace.literal("effectsRead"),
|
|
2529
|
-
effect: v__namespace.pipe(NonEmpty, v__namespace.check(name => !name.includes("'"), "an effect name cannot contain `'`")),
|
|
2530
|
-
path: v__namespace.optional(GuardReadPath)
|
|
2531
|
-
}) ]));
|
|
2459
|
+
function rolesCondition(roles, aliases) {
|
|
2460
|
+
if (!(!roles || roles.length === 0)) return groq`count($actor.roles[@ in ${expandRequiredRoles(roles, aliases)}]) > 0`;
|
|
2461
|
+
}
|
|
2532
2462
|
|
|
2533
|
-
function
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
idPatterns: v__namespace.optional(v__namespace.array(NonEmpty)),
|
|
2538
|
-
actions: actions
|
|
2539
|
-
};
|
|
2463
|
+
function stripUndefined(obj) {
|
|
2464
|
+
const out = {};
|
|
2465
|
+
for (const [k, value] of Object.entries(obj)) value !== void 0 && (out[k] = value);
|
|
2466
|
+
return out;
|
|
2540
2467
|
}
|
|
2541
2468
|
|
|
2542
|
-
function
|
|
2543
|
-
return
|
|
2544
|
-
name: NonEmpty,
|
|
2545
|
-
title: v__namespace.optional(v__namespace.string()),
|
|
2546
|
-
description: v__namespace.optional(v__namespace.string()),
|
|
2547
|
-
match: v__namespace.strictObject(guardMatchFields(read, actions)),
|
|
2548
|
-
predicate: v__namespace.optional(v__namespace.string()),
|
|
2549
|
-
metadata: v__namespace.optional(v__namespace.record(NonEmpty, read))
|
|
2550
|
-
};
|
|
2469
|
+
function isUnevaluable(result) {
|
|
2470
|
+
return result == null;
|
|
2551
2471
|
}
|
|
2552
2472
|
|
|
2553
|
-
|
|
2473
|
+
async function evaluateConditionOutcome(args) {
|
|
2474
|
+
const {condition: condition, snapshot: snapshot, params: params} = args;
|
|
2475
|
+
return groqConditionDescribe.evaluateConditionOutcome({
|
|
2476
|
+
condition: condition,
|
|
2477
|
+
params: params,
|
|
2478
|
+
dataset: snapshot.docs
|
|
2479
|
+
});
|
|
2480
|
+
}
|
|
2554
2481
|
|
|
2555
|
-
function
|
|
2556
|
-
return
|
|
2557
|
-
name: NonEmpty,
|
|
2558
|
-
semantics: SemanticsFieldSchema,
|
|
2559
|
-
title: v__namespace.optional(v__namespace.string()),
|
|
2560
|
-
description: v__namespace.optional(v__namespace.string()),
|
|
2561
|
-
groups: v__namespace.optional(v__namespace.array(GroupSchema)),
|
|
2562
|
-
activities: v__namespace.optional(v__namespace.array(activity)),
|
|
2563
|
-
transitions: v__namespace.optional(v__namespace.array(transition)),
|
|
2564
|
-
guards: v__namespace.optional(v__namespace.array(guard)),
|
|
2565
|
-
fields: v__namespace.optional(v__namespace.array(field)),
|
|
2566
|
-
editable: v__namespace.optional(v__namespace.record(FieldEntryName, editable))
|
|
2567
|
-
};
|
|
2482
|
+
async function evaluateCondition(args) {
|
|
2483
|
+
return await evaluateConditionOutcome(args) === "satisfied";
|
|
2568
2484
|
}
|
|
2569
2485
|
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
}))), RoleAliasesSchema = pinned()(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 = groqConditionDescribe._exhaustiveOptions()([ "standalone", "child" ]), START_KINDS = groqConditionDescribe._exhaustiveOptions()([ "interactive", "autonomous" ]);
|
|
2486
|
+
async function evaluatePredicates(args) {
|
|
2487
|
+
const out = {};
|
|
2488
|
+
for (const [name, groq2] of Object.entries(args.predicates ?? {})) {
|
|
2489
|
+
const result = await runGroq({
|
|
2490
|
+
groq: groq2,
|
|
2491
|
+
params: args.params,
|
|
2492
|
+
snapshot: args.snapshot
|
|
2493
|
+
}), outcome = groqConditionDescribe.conditionOutcome(result);
|
|
2494
|
+
out[name] = outcome === "unevaluable" ? null : outcome === "satisfied";
|
|
2495
|
+
}
|
|
2496
|
+
return out;
|
|
2497
|
+
}
|
|
2583
2498
|
|
|
2584
|
-
function
|
|
2585
|
-
return {
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
};
|
|
2499
|
+
async function runGroq({groq: groq2, params: params, snapshot: snapshot}) {
|
|
2500
|
+
return groqConditionDescribe.runGroq({
|
|
2501
|
+
groq: groq2,
|
|
2502
|
+
params: params,
|
|
2503
|
+
dataset: snapshot.docs
|
|
2504
|
+
});
|
|
2590
2505
|
}
|
|
2591
2506
|
|
|
2592
|
-
|
|
2507
|
+
function conditionSyntaxIssues(groq2, boundVars) {
|
|
2508
|
+
const issues = [];
|
|
2509
|
+
let tree;
|
|
2510
|
+
try {
|
|
2511
|
+
tree = groqJs.parse(groq2);
|
|
2512
|
+
} catch (err) {
|
|
2513
|
+
issues.push(errorMessage(err));
|
|
2514
|
+
}
|
|
2515
|
+
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."),
|
|
2516
|
+
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(", "));
|
|
2517
|
+
return issues;
|
|
2518
|
+
}
|
|
2593
2519
|
|
|
2594
|
-
function
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
description: v__namespace.optional(v__namespace.string()),
|
|
2600
|
-
groups: v__namespace.optional(v__namespace.array(GroupSchema)),
|
|
2601
|
-
lifecycle: v__namespace.optional(picklist(WORKFLOW_LIFECYCLES)),
|
|
2602
|
-
start: v__namespace.optional(start),
|
|
2603
|
-
initialStage: NonEmpty,
|
|
2604
|
-
fields: v__namespace.optional(v__namespace.array(field)),
|
|
2605
|
-
stages: v__namespace.pipe(v__namespace.array(stage), v__namespace.minLength(1, "must declare at least one stage")),
|
|
2606
|
-
predicates: v__namespace.optional(v__namespace.record(groqIdentifier("`$<name>`"), ConditionSchema)),
|
|
2607
|
-
roleAliases: v__namespace.optional(RoleAliasesSchema)
|
|
2608
|
-
};
|
|
2520
|
+
function conditionParameterNames(groq2) {
|
|
2521
|
+
const read = /* @__PURE__ */ new Set;
|
|
2522
|
+
return walkAstNodes(tryParseGroq(groq2), node => {
|
|
2523
|
+
node.type === "Parameter" && typeof node.name == "string" && read.add(node.name);
|
|
2524
|
+
}), read;
|
|
2609
2525
|
}
|
|
2610
2526
|
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
stage: StoredStageSchema,
|
|
2614
|
-
start: StoredStartSchema
|
|
2615
|
-
})));
|
|
2616
|
-
|
|
2617
|
-
function parseStoredDefinition(input, label) {
|
|
2618
|
-
return parseOrThrow({
|
|
2619
|
-
schema: WorkflowDefinitionSchema,
|
|
2620
|
-
input: input,
|
|
2621
|
-
label: label
|
|
2622
|
-
});
|
|
2527
|
+
function conditionFieldReadNames(groq2) {
|
|
2528
|
+
return new Set(conditionFieldReads(groq2).map(read => read.name));
|
|
2623
2529
|
}
|
|
2624
2530
|
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
}))), WORKFLOW_DEFINITION_TYPE = "sanity.workflow.definition";
|
|
2531
|
+
function conditionFieldReads(groq2) {
|
|
2532
|
+
const reads = /* @__PURE__ */ new Map;
|
|
2533
|
+
return collectFieldReads(tryParseGroq(groq2), reads), [ ...reads.values() ];
|
|
2534
|
+
}
|
|
2630
2535
|
|
|
2631
|
-
function
|
|
2632
|
-
|
|
2536
|
+
function collectFieldReads(node, reads) {
|
|
2537
|
+
if (Array.isArray(node)) {
|
|
2538
|
+
for (const item of node) collectFieldReads(item, reads);
|
|
2539
|
+
return;
|
|
2540
|
+
}
|
|
2541
|
+
if (typeof node != "object" || node === null) return;
|
|
2542
|
+
const chain = fieldsAttributeChain(node);
|
|
2543
|
+
if (chain !== void 0) {
|
|
2544
|
+
const [name, ...tail] = chain, path = tail.length > 0 ? tail.join(".") : void 0;
|
|
2545
|
+
reads.set(`${name}.${path ?? ""}`, {
|
|
2546
|
+
name: name,
|
|
2547
|
+
path: path
|
|
2548
|
+
});
|
|
2549
|
+
return;
|
|
2550
|
+
}
|
|
2551
|
+
for (const value of Object.values(node)) collectFieldReads(value, reads);
|
|
2633
2552
|
}
|
|
2634
2553
|
|
|
2635
|
-
function
|
|
2636
|
-
|
|
2554
|
+
function fieldsAttributeChain(node) {
|
|
2555
|
+
const names = [];
|
|
2556
|
+
let current = node;
|
|
2557
|
+
for (;typeof current == "object" && current !== null; ) {
|
|
2558
|
+
const typed = current;
|
|
2559
|
+
if (typed.type !== "AccessAttribute" || typeof typed.name != "string") return;
|
|
2560
|
+
names.unshift(typed.name);
|
|
2561
|
+
const base = typed.base;
|
|
2562
|
+
if (base?.type === "Parameter" && base.name === "fields") return names;
|
|
2563
|
+
current = base;
|
|
2564
|
+
}
|
|
2637
2565
|
}
|
|
2638
2566
|
|
|
2639
|
-
function
|
|
2640
|
-
|
|
2567
|
+
function conditionEffectReads(groq2) {
|
|
2568
|
+
const reads = [];
|
|
2569
|
+
return walkAstNodes(tryParseGroq(groq2), node => {
|
|
2570
|
+
if (node.type !== "AccessAttribute" || typeof node.name != "string") return;
|
|
2571
|
+
const base = node.base;
|
|
2572
|
+
base?.type !== "AccessAttribute" || typeof base.name != "string" || base.base?.type === "Parameter" && base.base.name === "effects" && reads.push({
|
|
2573
|
+
effect: base.name,
|
|
2574
|
+
key: node.name
|
|
2575
|
+
});
|
|
2576
|
+
}), reads;
|
|
2641
2577
|
}
|
|
2642
2578
|
|
|
2643
|
-
function
|
|
2644
|
-
return
|
|
2579
|
+
function readsRootDocument(groq2) {
|
|
2580
|
+
return nodeReadsRoot(tryParseGroq(groq2), 0);
|
|
2645
2581
|
}
|
|
2646
2582
|
|
|
2647
|
-
|
|
2648
|
-
|
|
2583
|
+
const SCOPED_CHILD = {
|
|
2584
|
+
Filter: "expr",
|
|
2585
|
+
Projection: "expr",
|
|
2586
|
+
Map: "expr",
|
|
2587
|
+
FlatMap: "expr",
|
|
2588
|
+
PipeFuncCall: "args"
|
|
2589
|
+
};
|
|
2590
|
+
|
|
2591
|
+
function nodeReadsRoot(node, depth) {
|
|
2592
|
+
if (Array.isArray(node)) return node.some(item => nodeReadsRoot(item, depth));
|
|
2593
|
+
if (typeof node != "object" || node === null) return !1;
|
|
2594
|
+
const typed = node;
|
|
2595
|
+
if (depth === 0 && typed.type === "This" || depth === 0 && typed.type === "AccessAttribute" && typed.base === void 0) return !0;
|
|
2596
|
+
if (typed.type === "Parent") {
|
|
2597
|
+
const climb = typeof typed.n == "number" ? typed.n : 1;
|
|
2598
|
+
return depth - climb <= 0;
|
|
2599
|
+
}
|
|
2600
|
+
const scopedChild = typeof typed.type == "string" ? SCOPED_CHILD[typed.type] : void 0;
|
|
2601
|
+
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));
|
|
2649
2602
|
}
|
|
2650
2603
|
|
|
2651
|
-
function
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2604
|
+
function tryParseGroq(groq2) {
|
|
2605
|
+
try {
|
|
2606
|
+
return groqJs.parse(groq2);
|
|
2607
|
+
} catch {
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2655
2610
|
}
|
|
2656
2611
|
|
|
2657
|
-
function
|
|
2658
|
-
if (
|
|
2659
|
-
const
|
|
2660
|
-
|
|
2612
|
+
function walkAstNodes(node, visit) {
|
|
2613
|
+
if (Array.isArray(node)) {
|
|
2614
|
+
for (const item of node) walkAstNodes(item, visit);
|
|
2615
|
+
return;
|
|
2661
2616
|
}
|
|
2662
|
-
|
|
2617
|
+
if (!(typeof node != "object" || node === null)) {
|
|
2618
|
+
visit(node);
|
|
2619
|
+
for (const value of Object.values(node)) walkAstNodes(value, visit);
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
const CONDITION_VARS = [ {
|
|
2624
|
+
name: "self",
|
|
2625
|
+
binding: "always",
|
|
2626
|
+
label: "this workflow instance",
|
|
2627
|
+
description: "GDR URI of the instance document itself — `*[_id == $self][0]` reads the instance in the snapshot."
|
|
2628
|
+
}, {
|
|
2629
|
+
name: "fields",
|
|
2630
|
+
binding: "always",
|
|
2631
|
+
label: "the workflow's fields",
|
|
2632
|
+
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` (or `subject`) 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."
|
|
2633
|
+
}, {
|
|
2634
|
+
name: "parent",
|
|
2635
|
+
binding: "always",
|
|
2636
|
+
label: "the parent workflow",
|
|
2637
|
+
description: "The parent instance's GDR URI, `null` on a root instance."
|
|
2638
|
+
}, {
|
|
2639
|
+
name: "ancestors",
|
|
2640
|
+
binding: "always",
|
|
2641
|
+
label: "the workflow's ancestors",
|
|
2642
|
+
description: "GDR URIs of the ancestor chain, root first."
|
|
2643
|
+
}, {
|
|
2644
|
+
name: "stage",
|
|
2645
|
+
binding: "always",
|
|
2646
|
+
label: "the current stage",
|
|
2647
|
+
description: "The current stage's name."
|
|
2648
|
+
}, {
|
|
2649
|
+
name: "now",
|
|
2650
|
+
binding: "always",
|
|
2651
|
+
label: "the current time",
|
|
2652
|
+
description: "The ISO clock reading shared by every condition in one evaluation pass, so they agree on the time."
|
|
2653
|
+
}, {
|
|
2654
|
+
name: "context",
|
|
2655
|
+
binding: "always",
|
|
2656
|
+
label: "start-time context",
|
|
2657
|
+
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."
|
|
2658
|
+
}, {
|
|
2659
|
+
name: "effects",
|
|
2660
|
+
binding: "always",
|
|
2661
|
+
label: "automation outputs",
|
|
2662
|
+
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."
|
|
2663
|
+
}, {
|
|
2664
|
+
name: "effectStatus",
|
|
2665
|
+
binding: "always",
|
|
2666
|
+
label: "automation status",
|
|
2667
|
+
description: "Effect name → `'done'` | `'failed'` of the latest completed run queued during the current stage entry; absent until the effect drains for this entry. Re-entry-safe: runs queued under a prior entry never count, so `$effectStatus['<effect>'] == 'done'` (or `defined($effectStatus['<effect>'])` for settled-either-way) waits for the fresh run."
|
|
2668
|
+
}, {
|
|
2669
|
+
name: "activities",
|
|
2670
|
+
binding: "always",
|
|
2671
|
+
label: "this stage's activities",
|
|
2672
|
+
description: "The current stage's activity rows, statuses included."
|
|
2673
|
+
}, {
|
|
2674
|
+
name: "allActivitiesDone",
|
|
2675
|
+
binding: "always",
|
|
2676
|
+
label: "all activities in this stage are finished",
|
|
2677
|
+
description: "Every current-stage activity is `done` or `skipped` — the default transition gate."
|
|
2678
|
+
}, {
|
|
2679
|
+
name: "anyActivityFailed",
|
|
2680
|
+
binding: "always",
|
|
2681
|
+
label: "an activity in this stage has failed",
|
|
2682
|
+
description: "Some current-stage activity is `failed`."
|
|
2683
|
+
}, {
|
|
2684
|
+
name: "actor",
|
|
2685
|
+
binding: "caller",
|
|
2686
|
+
label: "you",
|
|
2687
|
+
description: "The acting identity (id + roles); `undefined` when no caller rides the evaluation. Never holds a value in the cascade gates (deploy-rejected there)."
|
|
2688
|
+
}, {
|
|
2689
|
+
name: "assigned",
|
|
2690
|
+
binding: "caller",
|
|
2691
|
+
label: "you are assigned to this activity",
|
|
2692
|
+
description: "Whether the caller matches the activity's assignees-kind field entry (by user id, or by a role under the definition's `roleAliases`); `false` outside an activity context. Constant `false` in the cascade gates (deploy-rejected there)."
|
|
2693
|
+
}, {
|
|
2694
|
+
name: "can",
|
|
2695
|
+
binding: "caller",
|
|
2696
|
+
label: "your permissions",
|
|
2697
|
+
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."
|
|
2698
|
+
}, {
|
|
2699
|
+
name: "attributes",
|
|
2700
|
+
binding: "caller",
|
|
2701
|
+
label: "your attributes",
|
|
2702
|
+
description: "Advisory org-level User Attributes for the caller (Enterprise — same values as lake `user::attributes()`), keyed by attribute name with each active scalar or array value. `undefined` on expected HTTP absence; unexpected fetch failures throw; empty page binds `{}`. Bound wherever grants ride the evaluation (same sites as `$can`). Soft-gate paths fetch at most 100 attributes (no further pages) and warn when the envelope reports `hasMore: true` (partial bag still binds). Not a security boundary — the Content Lake remains the only enforcement point."
|
|
2703
|
+
}, {
|
|
2704
|
+
name: "row",
|
|
2705
|
+
binding: "spawn",
|
|
2706
|
+
label: "the spawned row",
|
|
2707
|
+
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."
|
|
2708
|
+
}, {
|
|
2709
|
+
name: "params",
|
|
2710
|
+
binding: "caller",
|
|
2711
|
+
label: "the action's arguments",
|
|
2712
|
+
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."
|
|
2713
|
+
}, {
|
|
2714
|
+
name: "subworkflows",
|
|
2715
|
+
binding: "always",
|
|
2716
|
+
label: "the spawned subworkflows",
|
|
2717
|
+
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`."
|
|
2718
|
+
} ], 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 = [ {
|
|
2719
|
+
name: "tag",
|
|
2720
|
+
label: "this engine tag",
|
|
2721
|
+
description: "The engine's tag partition — scope `*[...]` instance scans with `tag == $tag`."
|
|
2722
|
+
}, {
|
|
2723
|
+
name: "definition",
|
|
2724
|
+
label: "this workflow definition",
|
|
2725
|
+
description: "The `name` of the definition under evaluation (its own start block binds it)."
|
|
2726
|
+
}, {
|
|
2727
|
+
name: "now",
|
|
2728
|
+
label: "the current time",
|
|
2729
|
+
description: "The ISO clock reading of the evaluating engine."
|
|
2730
|
+
} ], START_REQUIREMENT_VARS = [ ...START_FILTER_VARS, {
|
|
2731
|
+
name: "fields",
|
|
2732
|
+
label: "the start's input fields",
|
|
2733
|
+
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` or `subject` included (nothing hydrates at the gate or the pre-flight). Pathed reads are deploy-checked against these envelope shapes."
|
|
2734
|
+
} ], GUARD_PREDICATE_VARS = [ {
|
|
2735
|
+
name: "document",
|
|
2736
|
+
description: "The attempted mutation images, exposed as `document.before` and `document.after`."
|
|
2737
|
+
}, {
|
|
2738
|
+
name: "guard",
|
|
2739
|
+
description: "The guard document itself (its `metadata` carries deploy-time resolved values)."
|
|
2740
|
+
}, {
|
|
2741
|
+
name: "mutation",
|
|
2742
|
+
description: "The attempted mutation — `mutation.action` is the write kind being gated."
|
|
2743
|
+
} ];
|
|
2744
|
+
|
|
2745
|
+
function effectBackoffMs(retry, endedAttempts) {
|
|
2746
|
+
const {backoff: backoff} = retry;
|
|
2747
|
+
return backoff === void 0 || endedAttempts < 1 ? 0 : backoff.kind === "fixed" ? backoff.delayMs : backoff.delayMs * 2 ** (endedAttempts - 1);
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2750
|
+
function totalEffectBackoffMs(retry) {
|
|
2751
|
+
const waits = retry.attempts - 1;
|
|
2752
|
+
return retry.backoff === void 0 || waits < 1 ? 0 : retry.backoff.kind === "fixed" ? retry.backoff.delayMs * waits : retry.backoff.delayMs * (2 ** waits - 1);
|
|
2663
2753
|
}
|
|
2664
2754
|
|
|
2665
2755
|
function knownList(ids) {
|
|
@@ -3250,6 +3340,40 @@ function pushOutputIssues(args) {
|
|
|
3250
3340
|
}
|
|
3251
3341
|
}
|
|
3252
3342
|
|
|
3343
|
+
function checkEffectRetry(def, issues) {
|
|
3344
|
+
for (const {action: action, path: path} of actionSites(def)) for (const [e, effect] of (action.effects ?? []).entries()) effect.retry !== void 0 && pushRetryIssue({
|
|
3345
|
+
retry: effect.retry,
|
|
3346
|
+
name: effect.name,
|
|
3347
|
+
path: [ ...path, "effects", e, "retry" ],
|
|
3348
|
+
issues: issues
|
|
3349
|
+
});
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
function pushRetryIssue(args) {
|
|
3353
|
+
const {retry: retry, name: name, path: path, issues: issues} = args;
|
|
3354
|
+
if (retry.expiryMs !== void 0 && retry.expiryMs > MAX_RETRY_SPAN_MS) {
|
|
3355
|
+
issues.push({
|
|
3356
|
+
path: [ ...path, "expiryMs" ],
|
|
3357
|
+
message: `effect "${name}" declares a ${retry.expiryMs}ms \`expiryMs\`, past the ${MAX_RETRY_SPAN_MS}ms ceiling — a policy runs inside one \`drainEffects\` invocation, and no host offers a single execution longer than a year. Lower \`expiryMs\``
|
|
3358
|
+
});
|
|
3359
|
+
return;
|
|
3360
|
+
}
|
|
3361
|
+
const wait = totalEffectBackoffMs(retry);
|
|
3362
|
+
if (wait > MAX_RETRY_SPAN_MS) {
|
|
3363
|
+
issues.push({
|
|
3364
|
+
path: [ ...path, "attempts" ],
|
|
3365
|
+
message: `effect "${name}" accumulates ${spanned(wait)} of backoff before attempt ${retry.attempts}, past the ${MAX_RETRY_SPAN_MS}ms ceiling — a policy runs inside one \`drainEffects\` invocation, and no host offers a single execution longer than a year. Lower \`attempts\` or \`backoff.delayMs\``
|
|
3366
|
+
});
|
|
3367
|
+
return;
|
|
3368
|
+
}
|
|
3369
|
+
retry.expiryMs === void 0 || wait < retry.expiryMs || issues.push({
|
|
3370
|
+
path: [ ...path, "expiryMs" ],
|
|
3371
|
+
message: `effect "${name}" waits ${wait}ms of backoff before attempt ${retry.attempts}, which its ${retry.expiryMs}ms \`expiryMs\` closes first — the effect fails before the declared attempts run. Raise \`expiryMs\`, or lower \`attempts\`/\`backoff.delayMs\``
|
|
3372
|
+
});
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
const spanned = ms => Number.isFinite(ms) ? `${ms}ms` : "more milliseconds than a number can count", MAX_RETRY_SPAN_MS = 316224e5;
|
|
3376
|
+
|
|
3253
3377
|
function checkSubjectEntries(def, issues) {
|
|
3254
3378
|
for (const {entries: entries, scope: scope, path: path, label: label} of fieldScopes(def)) {
|
|
3255
3379
|
const subjects = (entries ?? []).flatMap((entry, n) => isSubjectEntry(entry) ? [ {
|
|
@@ -4025,7 +4149,7 @@ function checkWorkflowInvariants(def) {
|
|
|
4025
4149
|
checkUnboundConditionVars(def, issues), checkConditionFieldReads(def, issues), checkFieldReadSeeds(def, issues),
|
|
4026
4150
|
checkFieldReadOpValues(def, issues), checkFieldTargetOps(def, issues), checkGuardFieldReads(def, issues),
|
|
4027
4151
|
checkAssigneesEntries(def, issues), checkDueDateEntries(def, issues), checkSubjectEntries(def, issues),
|
|
4028
|
-
checkLevelKindEffectOutputs(def, issues), checkActivityTerminalPaths(def, issues),
|
|
4152
|
+
checkLevelKindEffectOutputs(def, issues), checkEffectRetry(def, issues), checkActivityTerminalPaths(def, issues),
|
|
4029
4153
|
checkTerminalStageActivities(def, issues), checkTriggeredActionParams(def, issues),
|
|
4030
4154
|
checkStoredRolesPlacement(def, issues), checkGroups(def, issues), issues;
|
|
4031
4155
|
}
|
|
@@ -4046,6 +4170,8 @@ exports.AuthoringActionSchema = AuthoringActionSchema;
|
|
|
4046
4170
|
|
|
4047
4171
|
exports.AuthoringActivitySchema = AuthoringActivitySchema;
|
|
4048
4172
|
|
|
4173
|
+
exports.AuthoringEffectSchema = AuthoringEffectSchema;
|
|
4174
|
+
|
|
4049
4175
|
exports.AuthoringFieldEntrySchema = AuthoringFieldEntrySchema;
|
|
4050
4176
|
|
|
4051
4177
|
exports.AuthoringGuardSchema = AuthoringGuardSchema;
|
|
@@ -4082,8 +4208,6 @@ exports.EXECUTOR_CLASSIFICATIONS = EXECUTOR_CLASSIFICATIONS;
|
|
|
4082
4208
|
|
|
4083
4209
|
exports.EffectNotFoundError = EffectNotFoundError;
|
|
4084
4210
|
|
|
4085
|
-
exports.EffectSchema = EffectSchema;
|
|
4086
|
-
|
|
4087
4211
|
exports.FIELD_READ = FIELD_READ;
|
|
4088
4212
|
|
|
4089
4213
|
exports.FIELD_SCOPES = FIELD_SCOPES;
|
|
@@ -4114,7 +4238,7 @@ exports.MUTATION_GUARD_ACTIONS = MUTATION_GUARD_ACTIONS;
|
|
|
4114
4238
|
|
|
4115
4239
|
exports.MUTATION_GUARD_ID_SPACES = MUTATION_GUARD_ID_SPACES;
|
|
4116
4240
|
|
|
4117
|
-
exports.NonEmptyString = NonEmptyString;
|
|
4241
|
+
exports.NonEmptyString = NonEmptyString$1;
|
|
4118
4242
|
|
|
4119
4243
|
exports.PersistedDocShapeError = PersistedDocShapeError;
|
|
4120
4244
|
|
|
@@ -4196,6 +4320,8 @@ exports.directoryBridgeId = directoryBridgeId;
|
|
|
4196
4320
|
|
|
4197
4321
|
exports.driverKind = driverKind;
|
|
4198
4322
|
|
|
4323
|
+
exports.effectBackoffMs = effectBackoffMs;
|
|
4324
|
+
|
|
4199
4325
|
exports.errorMessage = errorMessage;
|
|
4200
4326
|
|
|
4201
4327
|
exports.evaluateCondition = evaluateCondition;
|