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