@sanity/workflow-engine 0.32.0 → 0.33.0

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