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