@sanity/workflow-engine 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -79,6 +79,21 @@ function gdrFromResource(res, documentId) {
79
79
  }
80
80
  return gdrUri({ scheme: res.type, resourceId: res.id, documentId });
81
81
  }
82
+ function resourceFromGdrUri(uri) {
83
+ let parsed;
84
+ try {
85
+ parsed = parseGdr(uri);
86
+ } catch {
87
+ return;
88
+ }
89
+ if (parsed.scheme === "dataset")
90
+ return parsed.projectId === void 0 || parsed.dataset === void 0 ? void 0 : { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` };
91
+ if (parsed.resourceId !== void 0)
92
+ return { type: parsed.scheme, id: parsed.resourceId };
93
+ }
94
+ function sameResource(a, b) {
95
+ return a.type === b.type && a.id === b.id;
96
+ }
82
97
  function selfGdr(doc) {
83
98
  return gdrFromResource(doc.workflowResource, doc._id);
84
99
  }
@@ -223,6 +238,8 @@ function validateTask(v2, stageName, task) {
223
238
  for (const entry of task.state ?? [])
224
239
  v2.checkEntry(entry, `${where}.state "${entry.name}"`);
225
240
  task.filter !== void 0 && v2.checkCondition(task.filter, `${where}.filter`), task.completeWhen !== void 0 && v2.checkCondition(task.completeWhen, `${where}.completeWhen`), task.failWhen !== void 0 && v2.checkCondition(task.failWhen, `${where}.failWhen`);
241
+ for (const [name, groq] of Object.entries(task.requirements ?? {}))
242
+ v2.checkCondition(groq, `${where}.requirements "${name}"`);
226
243
  for (const [key, groq] of effectBindings(task.effects))
227
244
  v2.tryParse(groq, `${where} effect binding "${key}"`);
228
245
  validateTaskActions(v2, task), task.subworkflows !== void 0 && validateSubworkflows(v2, where, task.subworkflows);
@@ -460,10 +477,14 @@ function abortReason(instance) {
460
477
  )?.reason;
461
478
  }
462
479
  function diagnoseInputFromEvaluation(evaluation) {
480
+ const stage = openStage(evaluation.instance);
463
481
  return {
464
482
  instance: evaluation.instance,
465
483
  tasks: evaluation.currentStage.tasks,
466
- transitions: evaluation.currentStage.transitions
484
+ transitions: evaluation.currentStage.transitions,
485
+ assignees: Object.fromEntries(
486
+ evaluation.currentStage.tasks.map((t) => [t.task.name, assigneesOf(stage, t.task.name)])
487
+ )
467
488
  };
468
489
  }
469
490
  function openStage(instance) {
@@ -488,18 +509,34 @@ function failedTaskCause(input) {
488
509
  return failed ? { kind: "failed-task", task: failed.task.name } : void 0;
489
510
  }
490
511
  function waitingState(input) {
491
- const active = input.tasks.find((t) => t.status === "active" && (t.task.actions ?? []).length > 0);
512
+ const active = input.tasks.find(
513
+ (t) => t.status === "active" && (t.task.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length === 0
514
+ );
492
515
  if (active !== void 0)
493
516
  return {
494
517
  state: "waiting",
495
518
  task: active.task.name,
496
- assignees: assigneesOf(openStage(input.instance), active.task.name),
519
+ assignees: input.assignees[active.task.name] ?? [],
497
520
  actions: (active.task.actions ?? []).map((a) => a.name)
498
521
  };
499
522
  }
523
+ function blockedState(input) {
524
+ const blocked = input.tasks.find(
525
+ (t) => t.status === "active" && (t.task.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length > 0
526
+ );
527
+ if (blocked !== void 0)
528
+ return {
529
+ state: "blocked",
530
+ task: blocked.task.name,
531
+ requirements: blocked.unmetRequirements ?? [],
532
+ assignees: input.assignees[blocked.task.name] ?? []
533
+ };
534
+ }
500
535
  function noTransitionFiresCause(input) {
501
- if (input.transitions.length !== 0 && !input.transitions.some((t) => t.filterSatisfied) && input.tasks.every((t) => isResolved(t.status)))
502
- return { kind: "no-transition-fires" };
536
+ if (input.transitions.length === 0 || input.transitions.some((t) => t.filterSatisfied) || !input.tasks.every((t) => isResolved(t.status)))
537
+ return;
538
+ const undecidable = input.transitions.filter((t) => t.unevaluable);
539
+ return undecidable.length > 0 ? { kind: "transition-unevaluable", transitions: undecidable.map((t) => t.transition.name) } : { kind: "no-transition-fires" };
503
540
  }
504
541
  function diagnoseInstance(input) {
505
542
  const { instance } = input;
@@ -510,7 +547,51 @@ function diagnoseInstance(input) {
510
547
  if (instance.completedAt !== void 0)
511
548
  return { state: "completed", at: instance.completedAt };
512
549
  const cause = failedEffectCause(input) ?? failedTaskCause(input) ?? hungEffectCause(input) ?? noTransitionFiresCause(input);
513
- return cause !== void 0 ? { state: "stuck", cause } : waitingState(input) ?? { state: "progressing" };
550
+ return cause !== void 0 ? { state: "stuck", cause } : waitingState(input) ?? blockedState(input) ?? { state: "progressing" };
551
+ }
552
+ const RUNNABLE_VERBS = /* @__PURE__ */ new Set(["set-stage", "abort"]);
553
+ function remediationsForCause(cause) {
554
+ switch (cause.kind) {
555
+ case "failed-effect":
556
+ return [
557
+ {
558
+ verb: "retry-effect",
559
+ rationale: "Re-run the failed effect once the upstream system is healthy."
560
+ },
561
+ { verb: "abort", rationale: "Abort the instance if it can no longer recover." }
562
+ ];
563
+ case "hung-effect":
564
+ return [
565
+ {
566
+ verb: "drain-effects",
567
+ rationale: "Re-run the effect drainer to re-pick the claimed effect."
568
+ },
569
+ { verb: "abort", rationale: "Abort the instance if the effect never drains." }
570
+ ];
571
+ case "failed-task":
572
+ return [
573
+ { verb: "reset-task", rationale: "Reset or skip the failed task." },
574
+ {
575
+ verb: "set-stage",
576
+ rationale: "Move the instance past the failed task to the intended next stage."
577
+ }
578
+ ];
579
+ case "no-transition-fires":
580
+ return [
581
+ {
582
+ verb: "set-stage",
583
+ rationale: "Move the instance to the intended next stage to force it forward."
584
+ }
585
+ ];
586
+ case "transition-unevaluable":
587
+ return [];
588
+ }
589
+ }
590
+ function remediationsFor(diagnosis) {
591
+ return diagnosis.state !== "stuck" ? [] : remediationsForCause(diagnosis.cause).map((seed) => ({
592
+ ...seed,
593
+ available: RUNNABLE_VERBS.has(seed.verb)
594
+ }));
514
595
  }
515
596
  function buildSnapshot(args) {
516
597
  const docs = [], knownIds = /* @__PURE__ */ new Set();
@@ -557,6 +638,7 @@ function collectWatchRefs(instance) {
557
638
  function readsRaw(ref) {
558
639
  return ref.type === WORKFLOW_INSTANCE_TYPE || ref.type === "system.release";
559
640
  }
641
+ const DEFAULT_CONTENT_PERSPECTIVE = "drafts";
560
642
  function contentReleaseName(args) {
561
643
  const { ref, perspective } = args;
562
644
  if (!readsRaw(ref) && Array.isArray(perspective))
@@ -582,6 +664,11 @@ function entryReleaseRefs(state) {
582
664
  (entry) => entry._type === "release.ref" && isGdr(entry.value) ? [entry.value] : []
583
665
  );
584
666
  }
667
+ function entrySubjectRefs(state) {
668
+ return stateEntries(state).flatMap(
669
+ (entry) => (entry._type === "doc.ref" || entry._type === "release.ref") && isGdr(entry.value) ? [entry.value] : []
670
+ );
671
+ }
585
672
  function stateEntries(state) {
586
673
  return Array.isArray(state) ? state.filter(
587
674
  (entry) => !!entry && typeof entry == "object"
@@ -606,7 +693,10 @@ async function hydrateSnapshot(args) {
606
693
  };
607
694
  loaded.push({ doc: instance, resource: instance.workflowResource }), visited.add(selfGdr(instance));
608
695
  for (const ref of collectWatchRefs(instance))
609
- await loadInto(ref.id, readsRaw(ref) ? void 0 : instance.perspective);
696
+ await loadInto(
697
+ ref.id,
698
+ readsRaw(ref) ? "raw" : instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
699
+ );
610
700
  return buildSnapshot({ docs: loaded });
611
701
  }
612
702
  async function loadByGdr(defaultClient, clientForGdr, defaultResource, uri, perspective) {
@@ -621,7 +711,7 @@ async function loadByGdr(defaultClient, clientForGdr, defaultResource, uri, pers
621
711
  return doc ? { doc, resource: resourceFromParsed(parsed) } : null;
622
712
  }
623
713
  async function readDoc(client, id, perspective) {
624
- return perspective === void 0 ? await client.getDocument(id) ?? null : await client.fetch("*[_id == $id][0]", { id }, { perspective }) ?? null;
714
+ return perspective === "raw" ? await client.getDocument(id) ?? null : await client.fetch("*[_id == $id][0]", { id }, { perspective }) ?? null;
625
715
  }
626
716
  function resourceFromParsed(parsed) {
627
717
  return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
@@ -722,23 +812,7 @@ async function queryGuardsAcross(clients, query, params) {
722
812
  function dedupById(guards) {
723
813
  return [...new Map(guards.map((g) => [g._id, g])).values()].sort((a, b) => a._id.localeCompare(b._id));
724
814
  }
725
- const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
726
- function validateTags(tags) {
727
- if (tags.length === 0)
728
- throw new Error("tags: must be a non-empty array");
729
- for (const tag of tags)
730
- if (!TAG_RE.test(tag))
731
- throw new Error(
732
- `tags: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
733
- );
734
- }
735
- function canonicalTag(tags) {
736
- return tags[0];
737
- }
738
- function tagScopeFilter(param = "engineTags") {
739
- return `count(tags[@ in $${param}]) > 0`;
740
- }
741
- const GUARD_OWNER = "robot:workflow-engine", GUARD_LIFTED_PREDICATE = "true";
815
+ const SYNC_COMMIT = { visibility: "sync" }, GUARD_OWNER = "robot:workflow-engine", GUARD_LIFTED_PREDICATE = "true";
742
816
  function resolveGuard(guard, instance, stageName, now) {
743
817
  const ctx = { instance, now }, targets = resolveIdRefTargets(guard.match.idRefs, ctx);
744
818
  if (targets === null) return null;
@@ -765,11 +839,11 @@ function resolveGuard(guard, instance, stageName, now) {
765
839
  }
766
840
  async function upsertGuard(client, doc) {
767
841
  if (!await client.getDocument(doc._id)) {
768
- await client.create(doc);
842
+ await client.create(doc, SYNC_COMMIT);
769
843
  return;
770
844
  }
771
845
  const { _id, _type, _rev, _createdAt, _updatedAt, ...body } = doc;
772
- await client.patch(doc._id).set(body).commit();
846
+ await client.patch(doc._id).set(body).commit(SYNC_COMMIT);
773
847
  }
774
848
  function resolvedStageGuards(args) {
775
849
  const stage = args.definition.stages.find((s) => s.name === args.stageName), out = [];
@@ -792,16 +866,16 @@ async function retractStageGuards(args) {
792
866
  const live = await committedInstance(args);
793
867
  if (live !== void 0 && !(live.currentStage === args.stageName && live.abortedAt === void 0))
794
868
  for (const { client, doc } of resolvedStageGuards(args))
795
- await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit();
869
+ await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit(SYNC_COMMIT);
796
870
  }
797
871
  async function deleteOrphanedDefinitionGuards(args) {
798
- const { client, clientForGdr, workflowResource, definition, definitions, tags } = args, perClient = await guardsForDefinitionByClient({
872
+ const { client, clientForGdr, workflowResource, definition, definitions, tag } = args, perClient = await guardsForDefinitionByClient({
799
873
  client,
800
874
  clientForGdr,
801
875
  workflowResource,
802
876
  definition,
803
877
  definitions
804
- }), ownPartitionPrefix = `${canonicalTag(tags)}.`;
878
+ }), ownPartitionPrefix = `${tag}.`;
805
879
  let count = 0;
806
880
  for (const { client: resourceClient, guards } of perClient) {
807
881
  const own = guards.filter((guard) => guard.sourceInstanceId.startsWith(ownPartitionPrefix));
@@ -816,16 +890,32 @@ function randomKey(length = 12) {
816
890
  const bytes = new Uint8Array(length);
817
891
  return globalThis.crypto.getRandomValues(bytes), [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, length);
818
892
  }
819
- async function evaluateCondition(args) {
893
+ function isUnevaluable(result) {
894
+ return result == null;
895
+ }
896
+ async function evaluateConditionOutcome(args) {
820
897
  const { condition, snapshot, params } = args;
821
- return condition === void 0 ? !0 : !!await runGroq(condition, params, snapshot);
898
+ if (condition === void 0) return "satisfied";
899
+ const result = await runGroq(condition, params, snapshot);
900
+ return isUnevaluable(result) ? "unevaluable" : result ? "satisfied" : "unsatisfied";
901
+ }
902
+ async function evaluateCondition(args) {
903
+ return await evaluateConditionOutcome(args) === "satisfied";
822
904
  }
823
905
  async function evaluatePredicates(args) {
824
906
  const out = {};
825
- for (const [name, groq] of Object.entries(args.predicates ?? {}))
826
- out[name] = !!await runGroq(groq, args.params, args.snapshot);
907
+ for (const [name, groq] of Object.entries(args.predicates ?? {})) {
908
+ const result = await runGroq(groq, args.params, args.snapshot);
909
+ out[name] = isUnevaluable(result) ? null : !!result;
910
+ }
827
911
  return out;
828
912
  }
913
+ async function evaluateRequirements(args) {
914
+ const unmet = [];
915
+ for (const [name, condition] of Object.entries(args.requirements ?? {}))
916
+ await evaluateCondition({ condition, snapshot: args.snapshot, params: args.params }) || unmet.push(name);
917
+ return unmet;
918
+ }
829
919
  async function runGroq(groq, params, snapshot) {
830
920
  const tree = parse(groq, { params });
831
921
  return (await evaluate(tree, { dataset: snapshot.docs, params })).get();
@@ -990,10 +1080,10 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
990
1080
  stage: ctx.stageName ?? null,
991
1081
  task: ctx.taskName ?? null,
992
1082
  now: ctx.now,
993
- engineTags: ctx.tags ?? []
1083
+ tag: ctx.tag
994
1084
  });
995
1085
  try {
996
- const fetchOptions = ctx.perspective !== void 0 ? { perspective: ctx.perspective } : void 0, result = await client.fetch(source.query, params, fetchOptions);
1086
+ const fetchOptions = { perspective: ctx.perspective ?? DEFAULT_CONTENT_PERSPECTIVE }, result = await client.fetch(source.query, params, fetchOptions);
997
1087
  return normalizeQueryResult(entry.type, result, ctx.workflowResource) ?? defaultValue;
998
1088
  } catch (err) {
999
1089
  throw new Error(
@@ -1123,13 +1213,16 @@ async function ctxConditionParams(ctx, opts) {
1123
1213
  params
1124
1214
  }), ...params };
1125
1215
  }
1126
- async function ctxEvaluateCondition(ctx, condition, opts) {
1127
- return condition === void 0 ? !0 : evaluateCondition({
1216
+ async function ctxEvaluateConditionOutcome(ctx, condition, opts) {
1217
+ return condition === void 0 ? "satisfied" : evaluateConditionOutcome({
1128
1218
  condition,
1129
1219
  snapshot: ctx.snapshot,
1130
1220
  params: await ctxConditionParams(ctx, opts)
1131
1221
  });
1132
1222
  }
1223
+ async function ctxEvaluateCondition(ctx, condition, opts) {
1224
+ return await ctxEvaluateConditionOutcome(ctx, condition, opts) === "satisfied";
1225
+ }
1133
1226
  async function buildEngineContext(args) {
1134
1227
  const { client, clientForGdr, instance, definition } = args, clock = args.clock ?? wallClock;
1135
1228
  return {
@@ -1157,7 +1250,7 @@ async function resolveStageStateEntries(args) {
1157
1250
  client,
1158
1251
  now,
1159
1252
  selfId: instance._id,
1160
- tags: instance.tags ?? [],
1253
+ tag: instance.tag,
1161
1254
  stageName: stage.name,
1162
1255
  workflowResource: instance.workflowResource,
1163
1256
  // Allow stage-scope entries' `source: { type: "stateRead", scope:
@@ -1177,7 +1270,7 @@ async function resolveTaskStateEntries(args) {
1177
1270
  client,
1178
1271
  now,
1179
1272
  selfId: instance._id,
1180
- tags: instance.tags ?? [],
1273
+ tag: instance.tag,
1181
1274
  stageName: stage.name,
1182
1275
  workflowResource: instance.workflowResource,
1183
1276
  taskName: task.name,
@@ -1249,7 +1342,7 @@ async function deployOrRollback(args) {
1249
1342
  });
1250
1343
  try {
1251
1344
  let patch = args.client.patch(args.instanceId).set(args.restore);
1252
- args.unset && args.unset.length > 0 && (patch = patch.unset(args.unset)), args.committedRev !== void 0 && (patch = patch.ifRevisionId(args.committedRev)), await patch.commit();
1345
+ args.unset && args.unset.length > 0 && (patch = patch.unset(args.unset)), args.committedRev !== void 0 && (patch = patch.ifRevisionId(args.committedRev)), await patch.commit(SYNC_COMMIT);
1253
1346
  } catch (rollbackError) {
1254
1347
  throw new WorkflowStateDivergedError({
1255
1348
  instanceId: args.instanceId,
@@ -1356,7 +1449,7 @@ async function persist(ctx, mutation) {
1356
1449
  lastChangedAt: ctx.now
1357
1450
  }, pendingCreates = mutation.pendingCreates;
1358
1451
  if (pendingCreates.length === 0)
1359
- return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit();
1452
+ return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit(SYNC_COMMIT);
1360
1453
  const tx = ctx.client.transaction();
1361
1454
  for (const body of pendingCreates) tx.create(body);
1362
1455
  tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)), await tx.commit(), mutation.pendingCreates = [];
@@ -1399,6 +1492,16 @@ function findCurrentStageEntry(instance) {
1399
1492
  function findCurrentTasks(instance) {
1400
1493
  return findCurrentStageEntry(instance)?.tasks ?? [];
1401
1494
  }
1495
+ const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
1496
+ function validateTag(tag) {
1497
+ if (!TAG_RE.test(tag))
1498
+ throw new Error(
1499
+ `tag: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
1500
+ );
1501
+ }
1502
+ function tagScopeFilter() {
1503
+ return "tag == $tag";
1504
+ }
1402
1505
  function definitionLookupGroq(explicit) {
1403
1506
  const scoped = `_type == "${WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}`;
1404
1507
  return explicit ? `*[${scoped} && version == $version][0]` : `*[${scoped}] | order(version desc)[0]`;
@@ -1415,6 +1518,14 @@ function clientForDiscovery(defaultClient, clientForGdr, subjectGdrUri) {
1415
1518
  return defaultClient;
1416
1519
  }
1417
1520
  }
1521
+ async function reload(client, instanceId, tag) {
1522
+ const doc = await client.getDocument(instanceId);
1523
+ if (!doc)
1524
+ throw new Error(`Workflow instance ${instanceId} not found`);
1525
+ if (doc.tag !== tag)
1526
+ throw new Error(`Workflow instance ${instanceId} not visible to this engine (tag mismatch)`);
1527
+ return doc;
1528
+ }
1418
1529
  function effectsContextEntry(name, value) {
1419
1530
  const _key = randomKey();
1420
1531
  return typeof value == "string" ? { _key, _type: "effectsContext.string", name, value } : typeof value == "number" ? { _key, _type: "effectsContext.number", name, value } : typeof value == "boolean" ? { _key, _type: "effectsContext.boolean", name, value } : { _key, _type: "effectsContext.ref", name, value };
@@ -1430,7 +1541,7 @@ function buildInstanceBase(args) {
1430
1541
  _rev: "",
1431
1542
  _createdAt: now,
1432
1543
  _updatedAt: now,
1433
- tags: args.tags,
1544
+ tag: args.tag,
1434
1545
  workflowResource: args.workflowResource,
1435
1546
  definition: args.definitionName,
1436
1547
  pinnedVersion: args.pinnedVersion,
@@ -1481,7 +1592,7 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
1481
1592
  `Subworkflow depth limit (${MAX_SPAWN_DEPTH}) exceeded on task "${task.name}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
1482
1593
  );
1483
1594
  }
1484
- const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tags ?? []);
1595
+ const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tag);
1485
1596
  if (definition === null) {
1486
1597
  const versionLabel = sub.definition.version === void 0 || sub.definition.version === "latest" ? "latest" : `v${sub.definition.version}`;
1487
1598
  throw new Error(
@@ -1491,9 +1602,16 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
1491
1602
  const parentScope = await ctxConditionParams(ctx, {
1492
1603
  taskName: task.name,
1493
1604
  ...actor ? { actor } : {}
1494
- }), rows = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
1605
+ }), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
1495
1606
  for (const row of rows) {
1496
- const initialState = await projectRowState(ctx, sub, definition, parentScope, row), { ref, body } = await prepareChildInstance({
1607
+ const initialState = await projectRowState(
1608
+ ctx,
1609
+ sub,
1610
+ definition,
1611
+ parentScope,
1612
+ row,
1613
+ discoveryResource
1614
+ ), { ref, body } = await prepareChildInstance({
1497
1615
  client: ctx.client,
1498
1616
  parent: ctx.instance,
1499
1617
  definition,
@@ -1508,28 +1626,25 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
1508
1626
  }
1509
1627
  async function resolveSpawnRows(ctx, sub) {
1510
1628
  const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
1511
- let value;
1629
+ let value, discoveryResource;
1512
1630
  if (groq.includes("*")) {
1513
- const routingUri = firstDocRefGdrUri(ctx.instance.state), client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
1514
- value = await discoverItems({
1631
+ const routingUri = entrySubjectRefs(ctx.instance.state)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
1632
+ client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
1515
1633
  client,
1516
1634
  groq,
1517
1635
  params,
1518
- ...ctx.instance.perspective !== void 0 ? { perspective: ctx.instance.perspective } : {}
1636
+ // Draft-aware by default (drafts overlay published) when there's no
1637
+ // release, so a `forEach` discovers in-flight items the same way the
1638
+ // engine reads any other content.
1639
+ perspective: ctx.instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
1519
1640
  });
1520
1641
  } else {
1521
1642
  const tree = parse(groq, { params });
1522
1643
  value = await (await evaluate(tree, { dataset: [ctx.instance], params, root: ctx.instance })).get();
1523
1644
  }
1524
- return Array.isArray(value) ? value : value == null ? [] : [value];
1645
+ return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
1525
1646
  }
1526
- function firstDocRefGdrUri(entries) {
1527
- if (entries) {
1528
- for (const s of entries)
1529
- if (s._type === "doc.ref" && s.value !== null) return s.value.id;
1530
- }
1531
- }
1532
- async function projectRowState(ctx, sub, childDefinition, parentScope, row) {
1647
+ async function projectRowState(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
1533
1648
  const out = [];
1534
1649
  for (const [name, groq] of Object.entries(sub.with ?? {})) {
1535
1650
  const declared = (childDefinition.state ?? []).find((e) => e.name === name);
@@ -1537,7 +1652,12 @@ async function projectRowState(ctx, sub, childDefinition, parentScope, row) {
1537
1652
  throw new Error(
1538
1653
  `subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no state entry "${name}"`
1539
1654
  );
1540
- const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, ctx.instance.workflowResource);
1655
+ const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
1656
+ parentResource: ctx.instance.workflowResource,
1657
+ discoveryResource,
1658
+ entryName: name,
1659
+ childName: childDefinition.name
1660
+ });
1541
1661
  value != null && out.push({
1542
1662
  type: declared.type,
1543
1663
  name,
@@ -1554,11 +1674,17 @@ async function resolveContextHandoff(ctx, sub, parentScope) {
1554
1674
  }
1555
1675
  return out;
1556
1676
  }
1557
- function canonicaliseSpawnValue(kind, value, workflowResource) {
1558
- return kind === "doc.ref" ? coerceSpawnGdr(value, workflowResource) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, workflowResource)).filter((v2) => v2 !== null) : value;
1677
+ function canonicaliseSpawnValue(kind, value, cx) {
1678
+ return kind === "doc.ref" ? coerceSpawnGdr(value, cx) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, cx)).filter((v2) => v2 !== null) : value;
1679
+ }
1680
+ function assertBareIdRootsCorrectly(id, cx) {
1681
+ if (!(cx.discoveryResource === void 0 || sameResource(cx.discoveryResource, cx.parentResource)))
1682
+ throw new Error(
1683
+ `subworkflows.with["${cx.entryName}"]: subworkflow "${cx.childName}" projected the bare id "${id}", which roots at the parent resource "${cx.parentResource.id}", but spawn discovery ran against "${cx.discoveryResource.id}". The child would reference a document that doesn't exist in the parent resource. Project an explicit GDR URI in the with-GROQ (e.g. "dataset:<projectId>:<dataset>:" + _id) so the child's subject points at the resource the discovered rows came from.`
1684
+ );
1559
1685
  }
1560
- function coerceSpawnGdr(raw, workflowResource) {
1561
- const toUri = (id) => isGdrUri(id) ? id : gdrFromResource(workflowResource, id);
1686
+ function coerceSpawnGdr(raw, cx) {
1687
+ const toUri = (id) => isGdrUri(id) ? id : (assertBareIdRootsCorrectly(id, cx), gdrFromResource(cx.parentResource, id));
1562
1688
  if (raw == null) return null;
1563
1689
  if (typeof raw == "object" && "id" in raw && "type" in raw) {
1564
1690
  const r = raw;
@@ -1571,15 +1697,15 @@ function coerceSpawnGdr(raw, workflowResource) {
1571
1697
  }
1572
1698
  return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
1573
1699
  }
1574
- async function resolveDefinitionRef(client, ref, tags) {
1575
- const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, engineTags: tags };
1700
+ async function resolveDefinitionRef(client, ref, tag) {
1701
+ const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
1576
1702
  return wantsExplicit && (params.version = ref.version), await client.fetch(
1577
1703
  definitionLookupGroq(wantsExplicit),
1578
1704
  params
1579
1705
  ) ?? null;
1580
1706
  }
1581
1707
  async function prepareChildInstance(args) {
1582
- const { client, parent, definition, initialState, effectsContext, actor, now } = args, childTags = parent.tags ?? [], childPrefix = childTags[0] ?? "wf", workflowResource = parent.workflowResource, childDocId = `${childPrefix}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type: WORKFLOW_INSTANCE_TYPE }, ancestors = [
1708
+ const { client, parent, definition, initialState, effectsContext, actor, now } = args, childTag = parent.tag, workflowResource = parent.workflowResource, childDocId = `${childTag}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type: WORKFLOW_INSTANCE_TYPE }, ancestors = [
1583
1709
  ...parent.ancestors,
1584
1710
  {
1585
1711
  id: selfGdr(parent),
@@ -1592,7 +1718,7 @@ async function prepareChildInstance(args) {
1592
1718
  client,
1593
1719
  now,
1594
1720
  selfId: childDocId,
1595
- tags: childTags,
1721
+ tag: childTag,
1596
1722
  workflowResource,
1597
1723
  ...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
1598
1724
  },
@@ -1611,7 +1737,7 @@ async function prepareChildInstance(args) {
1611
1737
  ), base = buildInstanceBase({
1612
1738
  id: childDocId,
1613
1739
  now,
1614
- tags: childTags,
1740
+ tag: childTag,
1615
1741
  workflowResource,
1616
1742
  definitionName: definition.name,
1617
1743
  pinnedVersion: definition.version,
@@ -2057,21 +2183,24 @@ function resolveMachineStep(mutation, task, entry, now) {
2057
2183
  async function buildStageTasks(ctx, stage) {
2058
2184
  const entries = [];
2059
2185
  for (const task of stage.tasks ?? []) {
2060
- const inScope = await ctxEvaluateCondition(ctx, task.filter, { taskName: task.name });
2186
+ const outcome = await ctxEvaluateConditionOutcome(ctx, task.filter, { taskName: task.name }), inScope = outcome !== "unsatisfied";
2061
2187
  entries.push({
2062
2188
  _key: randomKey(),
2063
2189
  name: task.name,
2064
2190
  status: inScope ? "pending" : "skipped",
2065
- ...task.filter !== void 0 ? {
2066
- filterEvaluation: {
2067
- at: ctx.now,
2068
- truthy: inScope
2069
- }
2070
- } : {}
2191
+ ...task.filter !== void 0 ? { filterEvaluation: buildFilterEvaluation(ctx.now, task.filter, outcome) } : {}
2071
2192
  });
2072
2193
  }
2073
2194
  return entries;
2074
2195
  }
2196
+ function buildFilterEvaluation(at, filter, outcome) {
2197
+ return outcome === "unevaluable" ? {
2198
+ at,
2199
+ truthy: !1,
2200
+ unevaluable: !0,
2201
+ detail: `Filter "${filter}" could not be evaluated (GROQ null \u2014 a referenced operand was missing or incomparable). Task kept in scope so the gate is not silently skipped.`
2202
+ } : { at, truthy: outcome === "satisfied" };
2203
+ }
2075
2204
  function isTerminalStage(stage) {
2076
2205
  return (stage.transitions ?? []).length === 0;
2077
2206
  }
@@ -2221,8 +2350,11 @@ async function commitTransition(ctx, actor) {
2221
2350
  };
2222
2351
  }
2223
2352
  async function pickTransition(ctx, stage) {
2224
- for (const candidate of stage.transitions ?? [])
2225
- if (await ctxEvaluateCondition(ctx, candidate.filter)) return candidate;
2353
+ for (const candidate of stage.transitions ?? []) {
2354
+ const outcome = await ctxEvaluateConditionOutcome(ctx, candidate.filter);
2355
+ if (outcome === "satisfied") return candidate;
2356
+ if (outcome === "unevaluable") return;
2357
+ }
2226
2358
  }
2227
2359
  async function evaluateAutoTransitions(args) {
2228
2360
  const { client, instanceId, options, clientForGdr, clock, overlay } = args, ctx = await loadContext(client, instanceId, {
@@ -2252,7 +2384,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
2252
2384
  }, committed = await client.patch(instance._id).set({
2253
2385
  stages: [initialStageEntry],
2254
2386
  lastChangedAt: now
2255
- }).ifRevisionId(instance._rev).commit();
2387
+ }).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
2256
2388
  await deployOrRollback({
2257
2389
  client,
2258
2390
  instanceId: instance._id,
@@ -2477,17 +2609,12 @@ async function fetchGrantsCached(requestFn, resourcePath) {
2477
2609
  }
2478
2610
  }
2479
2611
  async function evaluateInstance(args) {
2480
- const { client, tags, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
2481
- validateTags(tags);
2612
+ const { client, tag, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
2613
+ validateTag(tag);
2482
2614
  const { actor, grants } = await resolveAccess(client, {
2483
2615
  ...args.access !== void 0 ? { override: args.access } : {},
2484
2616
  ...args.grantsFromPath !== void 0 ? { grantsFromPath: args.grantsFromPath } : {}
2485
- }), instance = await client.getDocument(instanceId);
2486
- if (!instance)
2487
- throw new Error(`Workflow instance "${instanceId}" not found`);
2488
- if (!tags.some((t) => instance.tags?.includes(t)))
2489
- throw new Error(`Workflow instance "${instanceId}" not visible to this engine (tag mismatch)`);
2490
- const definition = parseDefinitionSnapshot(instance), snapshot = await hydrateSnapshot({ client, clientForGdr: resourceClients !== void 0 ? (parsed) => resourceClients(parsed) ?? client : () => client, instance }), guards = await verdictGuardsForInstance(client, instance._id);
2617
+ }), instance = await reload(client, instanceId, tag), definition = parseDefinitionSnapshot(instance), snapshot = await hydrateSnapshot({ client, clientForGdr: resourceClients !== void 0 ? (parsed) => resourceClients(parsed) ?? client : () => client, instance }), guards = await verdictGuardsForInstance(client, instance._id);
2491
2618
  return evaluateFromSnapshot({
2492
2619
  instance,
2493
2620
  definition,
@@ -2519,15 +2646,18 @@ async function evaluateFromSnapshot(args) {
2519
2646
  })
2520
2647
  );
2521
2648
  const transitionEvaluations = [];
2522
- for (const transition of stage.transitions ?? [])
2649
+ for (const transition of stage.transitions ?? []) {
2650
+ const outcome = await evaluateConditionOutcome({
2651
+ condition: transition.filter,
2652
+ snapshot,
2653
+ params: scope
2654
+ });
2523
2655
  transitionEvaluations.push({
2524
2656
  transition,
2525
- filterSatisfied: await evaluateCondition({
2526
- condition: transition.filter,
2527
- snapshot,
2528
- params: scope
2529
- })
2657
+ filterSatisfied: outcome === "satisfied",
2658
+ unevaluable: outcome === "unevaluable"
2530
2659
  });
2660
+ }
2531
2661
  const currentStage = {
2532
2662
  stage,
2533
2663
  tasks: taskEvaluations,
@@ -2575,7 +2705,11 @@ async function evaluateTask(args) {
2575
2705
  ...scopedStateOverlay(instance, snapshot, task.name)
2576
2706
  },
2577
2707
  assigned
2578
- }, actions = [];
2708
+ }, unmetRequirements = await evaluateRequirements({
2709
+ requirements: task.requirements,
2710
+ snapshot,
2711
+ params: taskScope
2712
+ }), requirementsReason = unmetRequirements.length > 0 ? { kind: "requirements-unmet", unmetRequirements } : void 0, actions = [];
2579
2713
  for (const action of task.actions ?? [])
2580
2714
  actions.push(
2581
2715
  await evaluateAction({
@@ -2585,7 +2719,8 @@ async function evaluateTask(args) {
2585
2719
  snapshot,
2586
2720
  taskScope,
2587
2721
  stageHasExits,
2588
- guardDenial
2722
+ guardDenial,
2723
+ requirementsReason
2589
2724
  })
2590
2725
  );
2591
2726
  return {
@@ -2596,12 +2731,22 @@ async function evaluateTask(args) {
2596
2731
  // they're still inbox items. The inbox reads the assignees-kind state
2597
2732
  // entry BY KIND (who-data is state).
2598
2733
  pendingOnActor: (status === "active" || status === "pending") && assigned,
2734
+ ...unmetRequirements.length > 0 ? { unmetRequirements } : {},
2599
2735
  actions
2600
2736
  };
2601
2737
  }
2602
2738
  async function evaluateAction(args) {
2603
- const { action, status, instance, snapshot, taskScope, stageHasExits, guardDenial } = args, lifecycle = lifecycleReason(instance, status, stageHasExits);
2604
- return lifecycle !== void 0 ? disabled(action, lifecycle) : guardDenial !== void 0 ? disabled(action, guardDenial) : action.filter !== void 0 && !await evaluateCondition({
2739
+ const {
2740
+ action,
2741
+ status,
2742
+ instance,
2743
+ snapshot,
2744
+ taskScope,
2745
+ stageHasExits,
2746
+ guardDenial,
2747
+ requirementsReason
2748
+ } = args, lifecycle = lifecycleReason(instance, status, stageHasExits);
2749
+ return lifecycle !== void 0 ? disabled(action, lifecycle) : guardDenial !== void 0 ? disabled(action, guardDenial) : requirementsReason !== void 0 ? disabled(action, requirementsReason) : action.filter !== void 0 && !await evaluateCondition({
2605
2750
  condition: action.filter,
2606
2751
  snapshot,
2607
2752
  params: taskScope
@@ -2628,22 +2773,21 @@ function lifecycleReason(instance, status, stageHasExits) {
2628
2773
  function disabled(action, reason) {
2629
2774
  return { action, allowed: !1, disabledReason: reason };
2630
2775
  }
2631
- function definitionDocId(_workflowResource, prefix, definition, version) {
2632
- return `${prefix}.${definition}.v${version}`;
2776
+ function definitionDocId(_workflowResource, tag, definition, version) {
2777
+ return `${tag}.${definition}.v${version}`;
2633
2778
  }
2634
- async function sortByDependencies(client, definitions, tags, workflowResource) {
2635
- const prefix = canonicalTag(tags), byName = /* @__PURE__ */ new Map();
2779
+ async function sortByDependencies(client, definitions, tag, workflowResource) {
2780
+ const byName = /* @__PURE__ */ new Map();
2636
2781
  for (const def of definitions) {
2637
2782
  const list = byName.get(def.name) ?? [];
2638
2783
  list.push(def), byName.set(def.name, list);
2639
2784
  }
2640
2785
  const findInBatch = (ref) => findRefInBatch(byName, ref);
2641
2786
  return await assertCrossBatchRefsDeployed(client, definitions, {
2642
- tags,
2787
+ tag,
2643
2788
  workflowResource,
2644
- prefix,
2645
2789
  findInBatch
2646
- }), topoSortDefinitions(definitions, { workflowResource, prefix, findInBatch });
2790
+ }), topoSortDefinitions(definitions, { workflowResource, tag, findInBatch });
2647
2791
  }
2648
2792
  function findRefInBatch(byName, ref) {
2649
2793
  const candidates = byName.get(ref.name);
@@ -2656,10 +2800,10 @@ function findRefInBatch(byName, ref) {
2656
2800
  async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
2657
2801
  const missing = [];
2658
2802
  for (const def of definitions) {
2659
- const fromId = definitionDocId(ctx.workflowResource, ctx.prefix, def.name, def.version);
2803
+ const fromId = definitionDocId(ctx.workflowResource, ctx.tag, def.name, def.version);
2660
2804
  for (const ref of refsOf(def)) {
2661
2805
  if (ctx.findInBatch(ref) !== void 0) continue;
2662
- const label = await resolveDeployedRefLabel(client, ref, ctx.tags);
2806
+ const label = await resolveDeployedRefLabel(client, ref, ctx.tag);
2663
2807
  label !== void 0 && missing.push({ from: fromId, ref: label });
2664
2808
  }
2665
2809
  }
@@ -2671,8 +2815,8 @@ async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
2671
2815
  `)
2672
2816
  );
2673
2817
  }
2674
- async function resolveDeployedRefLabel(client, ref, tags) {
2675
- const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, engineTags: tags };
2818
+ async function resolveDeployedRefLabel(client, ref, tag) {
2819
+ const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
2676
2820
  if (wantsExplicit && (params.version = ref.version), !await client.fetch(
2677
2821
  definitionLookupGroq(wantsExplicit),
2678
2822
  params
@@ -2681,7 +2825,7 @@ async function resolveDeployedRefLabel(client, ref, tags) {
2681
2825
  }
2682
2826
  function topoSortDefinitions(definitions, ctx) {
2683
2827
  const visited = /* @__PURE__ */ new Set(), visiting = /* @__PURE__ */ new Set(), ordered = [], visit = (def) => {
2684
- const id = definitionDocId(ctx.workflowResource, ctx.prefix, def.name, def.version);
2828
+ const id = definitionDocId(ctx.workflowResource, ctx.tag, def.name, def.version);
2685
2829
  if (!visited.has(id)) {
2686
2830
  if (visiting.has(id))
2687
2831
  throw new Error(`workflow.deployDefinitions: dependency cycle involving "${id}"`);
@@ -2722,11 +2866,11 @@ function stableStringify(value) {
2722
2866
  ([a], [b]) => a.localeCompare(b)
2723
2867
  ).map(([k, v2]) => `${JSON.stringify(k)}:${stableStringify(v2)}`).join(",")}}`;
2724
2868
  }
2725
- async function loadDefinition(client, definition, version, tags) {
2869
+ async function loadDefinition(client, definition, version, tag) {
2726
2870
  if (version !== void 0) {
2727
2871
  const doc = await client.fetch(
2728
2872
  definitionLookupGroq(!0),
2729
- { definition, version, engineTags: tags }
2873
+ { definition, version, tag }
2730
2874
  );
2731
2875
  if (!doc)
2732
2876
  throw new Error(`Workflow definition ${definition} v${version} not deployed`);
@@ -2734,16 +2878,16 @@ async function loadDefinition(client, definition, version, tags) {
2734
2878
  }
2735
2879
  const latest = await client.fetch(definitionLookupGroq(!1), {
2736
2880
  definition,
2737
- engineTags: tags
2881
+ tag
2738
2882
  });
2739
2883
  if (!latest)
2740
2884
  throw new Error(`No deployed definition for workflow ${definition}`);
2741
2885
  return latest;
2742
2886
  }
2743
- async function loadDefinitionVersions(client, definition, tags) {
2887
+ async function loadDefinitionVersions(client, definition, tag) {
2744
2888
  return client.fetch(
2745
- `*[_type == "${WORKFLOW_DEFINITION_TYPE}" && name == $definition && count(tags[@ in $engineTags]) > 0] | order(version desc)`,
2746
- { definition, engineTags: tags }
2889
+ `*[_type == "${WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}] | order(version desc)`,
2890
+ { definition, tag }
2747
2891
  );
2748
2892
  }
2749
2893
  async function abortInstance(args) {
@@ -2879,7 +3023,8 @@ const disabledReasonDetail = {
2879
3023
  "task-not-active": (r) => `task status is "${r.status}"`,
2880
3024
  "stage-terminal": (r) => `stage "${r.stage}" is terminal`,
2881
3025
  "instance-completed": (r) => `instance completed at ${r.completedAt}`,
2882
- "mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`
3026
+ "mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`,
3027
+ "requirements-unmet": (r) => `unmet requirement(s): ${r.unmetRequirements.join(", ")}`
2883
3028
  };
2884
3029
  function formatDisabledReason(task, action, reason) {
2885
3030
  const detail = disabledReasonDetail[reason.kind](reason);
@@ -2894,7 +3039,7 @@ function bareIdFromSpawnRef(uri) {
2894
3039
  }
2895
3040
  }
2896
3041
  async function resolveOperationContext(args) {
2897
- validateTags(args.tags);
3042
+ validateTag(args.tag);
2898
3043
  const access = await resolveAccess(args.client, {
2899
3044
  ...args.access !== void 0 ? { override: args.access } : {},
2900
3045
  ...args.grantsFromPath !== void 0 ? { grantsFromPath: args.grantsFromPath } : {}
@@ -2928,16 +3073,6 @@ async function abortAndPropagate(args) {
2928
3073
  function buildClientForGdr(defaultClient, resolver) {
2929
3074
  return resolver === void 0 ? () => defaultClient : (parsed) => resolver(parsed) ?? defaultClient;
2930
3075
  }
2931
- async function reload(client, instanceId, tags) {
2932
- const doc = await client.getDocument(instanceId);
2933
- if (!doc) throw new Error(`Workflow instance ${instanceId} not found`);
2934
- if (!intersectsTags(doc.tags, tags))
2935
- throw new Error(`Workflow instance ${instanceId} not visible to this engine (tag mismatch)`);
2936
- return doc;
2937
- }
2938
- function intersectsTags(docTags, engineTags) {
2939
- return !docTags || docTags.length === 0 ? !1 : engineTags.some((t) => docTags.includes(t));
2940
- }
2941
3076
  function toEffectsContextEntries(ctx) {
2942
3077
  return Object.entries(ctx).map(([id, value]) => effectsContextEntry(id, value));
2943
3078
  }
@@ -2963,15 +3098,15 @@ async function applyAction(args) {
2963
3098
  })).ranOps;
2964
3099
  }
2965
3100
  async function deleteDefinition(args) {
2966
- const { client, tags, definition, version, cascade: cascade2, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, versions = await loadDefinitionVersions(client, definition, tags);
3101
+ const { client, tag, definition, version, cascade: cascade2, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, versions = await loadDefinitionVersions(client, definition, tag);
2967
3102
  if (versions.length === 0)
2968
3103
  throw new Error(`No deployed definition for workflow ${definition}`);
2969
3104
  const targets = version === void 0 ? versions : versions.filter((d) => d.version === version);
2970
3105
  if (targets.length === 0)
2971
3106
  throw new Error(`Workflow definition ${definition} v${version} not deployed`);
2972
3107
  const lastVersionGoes = targets.length === versions.length;
2973
- await assertNoSpawnReferrers(client, tags, definition, targets, lastVersionGoes);
2974
- const instanceIds = await nonTerminalInstanceIds(client, tags, definition, version);
3108
+ await assertNoSpawnReferrers(client, tag, definition, targets, lastVersionGoes);
3109
+ const instanceIds = await nonTerminalInstanceIds(client, tag, definition, version);
2975
3110
  if (instanceIds.length > 0 && cascade2 !== !0)
2976
3111
  throw new Error(
2977
3112
  `Cannot delete ${definition}: ${instanceIds.length} non-terminal instance(s) exist (${previewIds(instanceIds)}). Pass cascade to abort them first \u2014 instances are aborted in place, never deleted.`
@@ -2992,7 +3127,7 @@ async function deleteDefinition(args) {
2992
3127
  workflowResource: args.workflowResource,
2993
3128
  definition,
2994
3129
  definitions: versions,
2995
- tags
3130
+ tag
2996
3131
  }) : 0;
2997
3132
  return {
2998
3133
  name: definition,
@@ -3001,10 +3136,10 @@ async function deleteDefinition(args) {
3001
3136
  deletedGuardCount
3002
3137
  };
3003
3138
  }
3004
- async function assertNoSpawnReferrers(client, tags, definition, targets, lastVersionGoes) {
3139
+ async function assertNoSpawnReferrers(client, tag, definition, targets, lastVersionGoes) {
3005
3140
  const deployed = await client.fetch(
3006
3141
  `*[_type == "${WORKFLOW_DEFINITION_TYPE}" && ${tagScopeFilter()}]`,
3007
- { engineTags: tags }
3142
+ { tag }
3008
3143
  ), targetIds = new Set(targets.map((d) => d._id)), targetVersions = new Set(targets.map((d) => d.version)), referrers = deployed.filter((d) => !targetIds.has(d._id)).filter(
3009
3144
  (d) => refsOf(d).some(
3010
3145
  (ref) => ref.name === definition && (typeof ref.version == "number" ? targetVersions.has(ref.version) : lastVersionGoes)
@@ -3017,11 +3152,11 @@ async function assertNoSpawnReferrers(client, tags, definition, targets, lastVer
3017
3152
  );
3018
3153
  }
3019
3154
  }
3020
- async function nonTerminalInstanceIds(client, tags, definition, version) {
3155
+ async function nonTerminalInstanceIds(client, tag, definition, version) {
3021
3156
  const versionFilter = version === void 0 ? "" : " && pinnedVersion == $version";
3022
3157
  return (await client.fetch(
3023
3158
  `*[_type == "${WORKFLOW_INSTANCE_TYPE}" && definition == $definition && ${tagScopeFilter()} && !defined(completedAt)${versionFilter}] | order(_id asc){_id}`,
3024
- { definition, engineTags: tags, ...version !== void 0 ? { version } : {} }
3159
+ { definition, tag, ...version !== void 0 ? { version } : {} }
3025
3160
  )).map((doc) => doc._id);
3026
3161
  }
3027
3162
  async function abortInstances(args) {
@@ -3052,20 +3187,20 @@ const workflow = {
3052
3187
  * Cycles in the dependency graph error before any write happens.
3053
3188
  */
3054
3189
  deployDefinitions: async (args) => {
3055
- const { client, tags, workflowResource, definitions } = args;
3056
- validateTags(tags);
3190
+ const { client, tag, workflowResource, definitions } = args;
3191
+ validateTag(tag);
3057
3192
  for (const def of definitions)
3058
3193
  validateDefinition(def);
3059
- const ordered = await sortByDependencies(client, definitions, tags, workflowResource), results = [], prefix = canonicalTag(tags), tx = client.transaction();
3194
+ const ordered = await sortByDependencies(client, definitions, tag, workflowResource), results = [], tx = client.transaction();
3060
3195
  let hasWrites = !1;
3061
3196
  for (const def of ordered) {
3062
- const id = definitionDocId(workflowResource, prefix, def.name, def.version), expected = {
3197
+ const id = definitionDocId(workflowResource, tag, def.name, def.version), expected = {
3063
3198
  ...def,
3064
3199
  _id: id,
3065
3200
  _type: WORKFLOW_DEFINITION_TYPE,
3066
- tags
3201
+ tag
3067
3202
  }, existing = await client.getDocument(id);
3068
- if (existing && intersectsTags(existing.tags, tags) && isDefinitionUnchanged(existing, expected)) {
3203
+ if (existing && existing.tag === tag && isDefinitionUnchanged(existing, expected)) {
3069
3204
  results.push({ definition: def.name, version: def.version, status: "unchanged" });
3070
3205
  continue;
3071
3206
  }
@@ -3103,7 +3238,7 @@ const workflow = {
3103
3238
  startInstance: async (args) => {
3104
3239
  const {
3105
3240
  client,
3106
- tags,
3241
+ tag,
3107
3242
  workflowResource,
3108
3243
  definition: definitionName,
3109
3244
  version,
@@ -3112,7 +3247,7 @@ const workflow = {
3112
3247
  effectsContext,
3113
3248
  instanceId,
3114
3249
  perspective
3115
- } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, definition = await loadDefinition(client, definitionName, version, tags), id = instanceId ?? `${canonicalTag(tags)}.wf-instance.${randomKey()}`;
3250
+ } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, definition = await loadDefinition(client, definitionName, version, tag), id = instanceId ?? `${tag}.wf-instance.${randomKey()}`;
3116
3251
  if (definition.stages.find((s) => s.name === definition.initialStage) === void 0)
3117
3252
  throw new Error(
3118
3253
  `Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
@@ -3124,7 +3259,7 @@ const workflow = {
3124
3259
  client,
3125
3260
  now,
3126
3261
  selfId: id,
3127
- tags,
3262
+ tag,
3128
3263
  workflowResource,
3129
3264
  ...perspective !== void 0 ? { perspective } : {}
3130
3265
  },
@@ -3132,7 +3267,7 @@ const workflow = {
3132
3267
  }), effectivePerspective = perspective ?? derivePerspectiveFromState(resolvedState), base = buildInstanceBase({
3133
3268
  id,
3134
3269
  now,
3135
- tags,
3270
+ tag,
3136
3271
  workflowResource,
3137
3272
  definitionName: definition.name,
3138
3273
  pinnedVersion: definition.version,
@@ -3144,7 +3279,7 @@ const workflow = {
3144
3279
  initialStage: definition.initialStage,
3145
3280
  actor
3146
3281
  });
3147
- return await client.create(base), await primeInitialStage(client, id, actor, clientForGdr, clock), await cascade(client, id, actor, clientForGdr, clock), reload(client, id, tags);
3282
+ return await client.create(base, SYNC_COMMIT), await primeInitialStage(client, id, actor, clientForGdr, clock), await cascade(client, id, actor, clientForGdr, clock), reload(client, id, tag);
3148
3283
  },
3149
3284
  /**
3150
3285
  * Fire an action against a task. If the task is pending, it is
@@ -3159,19 +3294,19 @@ const workflow = {
3159
3294
  fireAction: async (args) => {
3160
3295
  const {
3161
3296
  client,
3162
- tags,
3297
+ tag,
3163
3298
  instanceId,
3164
3299
  task,
3165
3300
  action,
3166
3301
  params,
3167
3302
  idempotent,
3168
3303
  resourceClients
3169
- } = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tags);
3304
+ } = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
3170
3305
  if (findOpenStageEntry(before)?.tasks.find((t) => t.name === task) === void 0 && idempotent === !0)
3171
3306
  return { instance: before, cascaded: 0, fired: !1 };
3172
3307
  const evaluation = await evaluateInstance({
3173
3308
  client,
3174
- tags,
3309
+ tag,
3175
3310
  instanceId,
3176
3311
  access,
3177
3312
  clock,
@@ -3190,7 +3325,7 @@ const workflow = {
3190
3325
  ...params !== void 0 ? { params } : {}
3191
3326
  }), cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
3192
3327
  return {
3193
- instance: await reload(client, instanceId, tags),
3328
+ instance: await reload(client, instanceId, tag),
3194
3329
  cascaded,
3195
3330
  fired: !0,
3196
3331
  ...ranOps !== void 0 ? { ranOps } : {}
@@ -3204,7 +3339,7 @@ const workflow = {
3204
3339
  * reference them. Cascades after.
3205
3340
  */
3206
3341
  completeEffect: async (args) => {
3207
- const { client, tags, instanceId, effectKey, status, outputs, detail, error, durationMs } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
3342
+ const { client, tag, instanceId, effectKey, status, outputs, detail, error, durationMs } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
3208
3343
  await completeEffect({
3209
3344
  client,
3210
3345
  instanceId,
@@ -3218,7 +3353,7 @@ const workflow = {
3218
3353
  });
3219
3354
  const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
3220
3355
  return {
3221
- instance: await reload(client, instanceId, tags),
3356
+ instance: await reload(client, instanceId, tag),
3222
3357
  cascaded,
3223
3358
  fired: !0
3224
3359
  };
@@ -3233,11 +3368,11 @@ const workflow = {
3233
3368
  * affected instances and the engine re-evaluates.
3234
3369
  */
3235
3370
  tick: async (args) => {
3236
- const { client, tags, instanceId } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, current = await reload(client, instanceId, tags), guards = await verdictGuardsForInstance(client, current._id);
3371
+ const { client, tag, instanceId } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, current = await reload(client, instanceId, tag), guards = await verdictGuardsForInstance(client, current._id);
3237
3372
  await assertInstanceWriteAllowed({ instance: current, guards, identity: actor.id });
3238
3373
  const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
3239
3374
  return {
3240
- instance: await reload(client, instanceId, tags),
3375
+ instance: await reload(client, instanceId, tag),
3241
3376
  cascaded,
3242
3377
  fired: cascaded > 0
3243
3378
  };
@@ -3248,8 +3383,8 @@ const workflow = {
3248
3383
  * upstream; this verb performs the mechanical move.
3249
3384
  */
3250
3385
  setStage: async (args) => {
3251
- const { client, tags, instanceId, targetStage, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
3252
- await reload(client, instanceId, tags);
3386
+ const { client, tag, instanceId, targetStage, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
3387
+ await reload(client, instanceId, tag);
3253
3388
  const result = await setStage({
3254
3389
  client,
3255
3390
  instanceId,
@@ -3258,7 +3393,7 @@ const workflow = {
3258
3393
  options: { ...actor !== void 0 ? { actor } : {}, clock, clientForGdr }
3259
3394
  }), cascaded = result.fired ? await cascade(client, instanceId, actor, clientForGdr, clock) : 0;
3260
3395
  return {
3261
- instance: await reload(client, instanceId, tags),
3396
+ instance: await reload(client, instanceId, tag),
3262
3397
  cascaded,
3263
3398
  fired: result.fired
3264
3399
  };
@@ -3270,28 +3405,28 @@ const workflow = {
3270
3405
  * contract (propagated, not cascaded — the instance is terminal).
3271
3406
  */
3272
3407
  abortInstance: async (args) => {
3273
- const { client, tags, instanceId, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
3274
- await reload(client, instanceId, tags);
3408
+ const { client, tag, instanceId, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
3409
+ await reload(client, instanceId, tag);
3275
3410
  const fired = await abortAndPropagate({ client, instanceId, reason, actor, clientForGdr, clock });
3276
3411
  return {
3277
- instance: await reload(client, instanceId, tags),
3412
+ instance: await reload(client, instanceId, tag),
3278
3413
  cascaded: 0,
3279
3414
  fired
3280
3415
  };
3281
3416
  },
3282
3417
  /**
3283
- * Fetch a workflow instance by id, scoped to the engine's tags.
3418
+ * Fetch a workflow instance by id, scoped to the engine's tag.
3284
3419
  * Throws when the instance doesn't exist or isn't visible to this
3285
3420
  * engine.
3286
3421
  */
3287
3422
  getInstance: async (args) => {
3288
- const { client, tags, instanceId } = args;
3289
- return validateTags(tags), reload(client, instanceId, tags);
3423
+ const { client, tag, instanceId } = args;
3424
+ return validateTag(tag), reload(client, instanceId, tag);
3290
3425
  },
3291
3426
  guardsForInstance: async (args) => {
3292
- const { client, tags, instanceId, resourceClients } = args;
3293
- validateTags(tags);
3294
- const instance = await reload(client, instanceId, tags);
3427
+ const { client, tag, instanceId, resourceClients } = args;
3428
+ validateTag(tag);
3429
+ const instance = await reload(client, instanceId, tag);
3295
3430
  return guardsForInstance({
3296
3431
  client,
3297
3432
  clientForGdr: buildClientForGdr(client, resourceClients),
@@ -3299,9 +3434,9 @@ const workflow = {
3299
3434
  });
3300
3435
  },
3301
3436
  guardsForDefinition: async (args) => {
3302
- const { client, tags, workflowResource, definition, resourceClients } = args;
3303
- validateTags(tags);
3304
- const definitions = await loadDefinitionVersions(client, definition, tags);
3437
+ const { client, tag, workflowResource, definition, resourceClients } = args;
3438
+ validateTag(tag);
3439
+ const definitions = await loadDefinitionVersions(client, definition, tag);
3305
3440
  if (definitions.length === 0)
3306
3441
  throw new Error(`No deployed definition for workflow ${definition}`);
3307
3442
  return guardsForDefinition({
@@ -3313,22 +3448,21 @@ const workflow = {
3313
3448
  });
3314
3449
  },
3315
3450
  /**
3316
- * Run a caller-supplied GROQ query with the engine's tags bound as
3317
- * `$engineTags`. This does NOT rewrite the query — arbitrary GROQ
3451
+ * Run a caller-supplied GROQ query with the engine's tag bound as
3452
+ * `$tag`. This does NOT rewrite the query — arbitrary GROQ
3318
3453
  * can't be safely tag-scoped after the fact — so the CALLER MUST
3319
- * filter on `$engineTags` (e.g.
3320
- * `count(tags[@ in $engineTags]) > 0`). To guard against accidental
3321
- * cross-tenant reads, a query that never references `$engineTags` is
3454
+ * filter on `$tag` (e.g. `tag == $tag`). To guard against accidental
3455
+ * cross-partition reads, a query that never references `$tag` is
3322
3456
  * rejected before it reaches the lake. Caller is responsible for type
3323
3457
  * narrowing the result.
3324
3458
  */
3325
3459
  query: async (args) => {
3326
- const { client, tags, groq, params } = args;
3327
- if (validateTags(tags), !groq.includes("$engineTags"))
3460
+ const { client, tag, groq, params } = args;
3461
+ if (validateTag(tag), !/\$tag\b/.test(groq))
3328
3462
  throw new Error(
3329
- `workflow.query: query must filter on $engineTags to stay tag-scoped (e.g. "count(tags[@ in $engineTags]) > 0"); got: ${groq}`
3463
+ `workflow.query: query must filter on $tag to stay tag-scoped (e.g. "tag == $tag"); got: ${groq}`
3330
3464
  );
3331
- return client.fetch(groq, { ...params, engineTags: tags });
3465
+ return client.fetch(groq, { ...params, tag });
3332
3466
  },
3333
3467
  /**
3334
3468
  * Snapshot-aware GROQ — runs against the same in-memory view that
@@ -3347,9 +3481,9 @@ const workflow = {
3347
3481
  * without re-implementing hydration. Pure read — never writes.
3348
3482
  */
3349
3483
  queryInScope: async (args) => {
3350
- const { client, tags, instanceId, groq, params, resourceClients } = args, clock = args.clock ?? wallClock;
3351
- validateTags(tags);
3352
- const instance = await reload(client, instanceId, tags), clientForGdr = buildClientForGdr(client, resourceClients), snapshot = await hydrateSnapshot({ client, clientForGdr, instance }), reserved = buildParams({ instance, now: clock(), snapshot }), tree = parse(groq, { params: { ...reserved, ...params } });
3484
+ const { client, tag, instanceId, groq, params, resourceClients } = args, clock = args.clock ?? wallClock;
3485
+ validateTag(tag);
3486
+ const instance = await reload(client, instanceId, tag), clientForGdr = buildClientForGdr(client, resourceClients), snapshot = await hydrateSnapshot({ client, clientForGdr, instance }), reserved = buildParams({ instance, now: clock(), snapshot }), tree = parse(groq, { params: { ...reserved, ...params } });
3353
3487
  return await (await evaluate(tree, {
3354
3488
  dataset: snapshot.docs,
3355
3489
  params: { ...reserved, ...params }
@@ -3359,13 +3493,13 @@ const workflow = {
3359
3493
  * List every pending effect on the instance. Returns the same entries
3360
3494
  * the runtime would see — claimed and unclaimed alike.
3361
3495
  */
3362
- listPendingEffects: async (args) => (await reload(args.client, args.instanceId, args.tags)).pendingEffects,
3496
+ listPendingEffects: async (args) => (await reload(args.client, args.instanceId, args.tag)).pendingEffects,
3363
3497
  /**
3364
3498
  * Filter pending effects on the instance by criteria. `claimed`
3365
3499
  * filters on claim presence; `names` restricts to specific effect
3366
3500
  * names. Both filters compose (AND).
3367
3501
  */
3368
- findPendingEffects: async (args) => (await reload(args.client, args.instanceId, args.tags)).pendingEffects.filter((e) => !(args.claimed === !0 && e.claim === void 0 || args.claimed === !1 && e.claim !== void 0 || args.names !== void 0 && !args.names.includes(e.name))),
3502
+ findPendingEffects: async (args) => (await reload(args.client, args.instanceId, args.tag)).pendingEffects.filter((e) => !(args.claimed === !0 && e.claim === void 0 || args.claimed === !1 && e.claim !== void 0 || args.names !== void 0 && !args.names.includes(e.name))),
3369
3503
  /**
3370
3504
  * Project the instance from a given actor's perspective. Returns a
3371
3505
  * `WorkflowEvaluation` with per-action verdicts (`allowed` + a
@@ -3385,8 +3519,8 @@ const workflow = {
3385
3519
  * Pure read.
3386
3520
  */
3387
3521
  diagnose: async (args) => {
3388
- const evaluation = await evaluateInstance(args);
3389
- return { evaluation, diagnosis: diagnoseInstance(diagnoseInputFromEvaluation(evaluation)) };
3522
+ const evaluation = await evaluateInstance(args), diagnosis = diagnoseInstance(diagnoseInputFromEvaluation(evaluation));
3523
+ return { evaluation, diagnosis, remediations: remediationsFor(diagnosis) };
3390
3524
  },
3391
3525
  /**
3392
3526
  * List the actions an actor could fire on an instance's current stage,
@@ -3406,18 +3540,18 @@ const workflow = {
3406
3540
  * record. (The per-task `spawnedInstances` field on the active stage
3407
3541
  * only exists while that stage is current; history survives.) Strips
3408
3542
  * the GDR URI on each `instanceRef` to a bare `_id`, fetches the
3409
- * instances, drops any that aren't visible to this engine's tags, and
3543
+ * instances, drops any that aren't visible to this engine's tag, and
3410
3544
  * returns them sorted by `startedAt` ascending.
3411
3545
  *
3412
3546
  * Pass `task` to restrict to a single spawning task on the parent.
3413
3547
  */
3414
3548
  children: async (args) => {
3415
- const { client, tags, instanceId, task } = args;
3416
- validateTags(tags);
3417
- const parent = await reload(client, instanceId, tags), isSpawned = (h) => h._type === "spawned", ids = parent.history.filter(isSpawned).filter((h) => task === void 0 || h.task === task).flatMap((h) => bareIdFromSpawnRef(h.instanceRef.id));
3549
+ const { client, tag, instanceId, task } = args;
3550
+ validateTag(tag);
3551
+ const parent = await reload(client, instanceId, tag), isSpawned = (h) => h._type === "spawned", ids = parent.history.filter(isSpawned).filter((h) => task === void 0 || h.task === task).flatMap((h) => bareIdFromSpawnRef(h.instanceRef.id));
3418
3552
  return ids.length === 0 ? [] : client.fetch(
3419
3553
  `*[_id in $ids && ${tagScopeFilter()}] | order(startedAt asc)`,
3420
- { ids, engineTags: tags }
3554
+ { ids, tag }
3421
3555
  );
3422
3556
  },
3423
3557
  /**
@@ -3432,11 +3566,11 @@ const workflow = {
3432
3566
  }
3433
3567
  };
3434
3568
  async function verifyDeployedDefinitionsInternal(args) {
3435
- const { client, tags, effectHandlers, missingHandler, logger } = args;
3436
- validateTags(tags);
3569
+ const { client, tag, effectHandlers, missingHandler, logger } = args;
3570
+ validateTag(tag);
3437
3571
  const log = logger("verifyDeployedDefinitions"), definitions = await client.fetch(
3438
3572
  `*[_type == "${WORKFLOW_DEFINITION_TYPE}" && ${tagScopeFilter()}] | order(name asc, version asc)`,
3439
- { engineTags: tags }
3573
+ { tag }
3440
3574
  ), seen = [], missingByName = /* @__PURE__ */ new Map();
3441
3575
  for (const def of definitions)
3442
3576
  seen.push({ name: def.name, version: def.version, _id: def._id }), walkEffectNames(def, (name, location) => {
@@ -3485,7 +3619,7 @@ function walkEffectNames(def, visit) {
3485
3619
  async function drainEffectsInternal(args) {
3486
3620
  const {
3487
3621
  client,
3488
- tags,
3622
+ tag,
3489
3623
  workflowResource,
3490
3624
  resourceClients,
3491
3625
  instanceId,
@@ -3496,10 +3630,10 @@ async function drainEffectsInternal(args) {
3496
3630
  actor: drainerActor,
3497
3631
  ...args.access?.grants !== void 0 ? { grants: args.access.grants } : {}
3498
3632
  };
3499
- validateTags(tags);
3633
+ validateTag(tag);
3500
3634
  const log = logger("drainEffects"), drained = [], failed = [], skipped = [], skippedKeys = /* @__PURE__ */ new Set();
3501
3635
  for (; ; ) {
3502
- const before = await reload(client, instanceId, tags), candidate = before.pendingEffects.find(
3636
+ const before = await reload(client, instanceId, tag), candidate = before.pendingEffects.find(
3503
3637
  (e) => e.claim === void 0 && !skippedKeys.has(e._key)
3504
3638
  );
3505
3639
  if (candidate === void 0) break;
@@ -3516,7 +3650,7 @@ async function drainEffectsInternal(args) {
3516
3650
  });
3517
3651
  await reportEffectOutcome({
3518
3652
  client,
3519
- tags,
3653
+ tag,
3520
3654
  workflowResource,
3521
3655
  ...resourceClients !== void 0 ? { resourceClients } : {},
3522
3656
  instanceId,
@@ -3555,7 +3689,7 @@ async function claimPendingEffect(client, instance, effectKey, drainerActor) {
3555
3689
  claimedBy: drainerActor
3556
3690
  };
3557
3691
  try {
3558
- return await client.patch(instance._id).ifRevisionId(instance._rev).set({ [`pendingEffects[_key=="${effectKey}"].claim`]: claim }).commit(), !0;
3692
+ return await client.patch(instance._id).ifRevisionId(instance._rev).set({ [`pendingEffects[_key=="${effectKey}"].claim`]: claim }).commit(SYNC_COMMIT), !0;
3559
3693
  } catch (error) {
3560
3694
  if (!isRevisionConflict(error)) throw error;
3561
3695
  return !1;
@@ -3586,7 +3720,7 @@ async function applyMissingHandler(policy, info, log) {
3586
3720
  }
3587
3721
  }
3588
3722
  function createInstanceSession(args) {
3589
- const { client, tags, clock } = args, clientForGdr = buildClientForGdr(client, args.resourceClients);
3723
+ const { client, tag, clock } = args, clientForGdr = buildClientForGdr(client, args.resourceClients);
3590
3724
  let instance = asHeldInstance(args.instance), overlay = /* @__PURE__ */ new Map(), heldGuards = [], committing = !1, buffered;
3591
3725
  const selfUri = () => gdrFromResource(instance.workflowResource, instance._id), applyUpdate = (docs) => {
3592
3726
  const next = /* @__PURE__ */ new Map();
@@ -3647,7 +3781,7 @@ function createInstanceSession(args) {
3647
3781
  return commit(async (actor, held) => {
3648
3782
  await assertInstanceWriteAllowed({ instance, guards: heldGuards, identity: actor.id });
3649
3783
  const cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
3650
- return instance = await reload(client, instance._id, tags), { instance, cascaded, fired: cascaded > 0 };
3784
+ return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: cascaded > 0 };
3651
3785
  });
3652
3786
  },
3653
3787
  fireAction({ task, action, params }) {
@@ -3664,7 +3798,7 @@ function createInstanceSession(args) {
3664
3798
  ...clock !== void 0 ? { clock } : {},
3665
3799
  ...params !== void 0 ? { params } : {}
3666
3800
  }), cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
3667
- return instance = await reload(client, instance._id, tags), { instance, cascaded, fired: !0, ...ranOps !== void 0 ? { ranOps } : {} };
3801
+ return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: !0, ...ranOps !== void 0 ? { ranOps } : {} };
3668
3802
  });
3669
3803
  }
3670
3804
  };
@@ -3692,11 +3826,11 @@ const defaultLoggerFactory = (name) => ({
3692
3826
  }
3693
3827
  };
3694
3828
  function createEngine(args) {
3695
- const { client, workflowResource, tags, resourceClients, clock } = args;
3696
- validateTags(tags);
3829
+ const { client, workflowResource, resourceClients, clock, tag } = args;
3830
+ validateTag(tag);
3697
3831
  const effectHandlers = args.effectHandlers ?? {}, missingHandler = args.missingHandler ?? "fail", logger = args.loggerFactory ?? defaultLoggerFactory, bind = (rest) => ({
3698
3832
  client,
3699
- tags,
3833
+ tag,
3700
3834
  workflowResource,
3701
3835
  ...resourceClients !== void 0 ? { resourceClients } : {},
3702
3836
  ...clock !== void 0 ? { clock } : {},
@@ -3704,7 +3838,7 @@ function createEngine(args) {
3704
3838
  });
3705
3839
  return {
3706
3840
  client,
3707
- tags,
3841
+ tag,
3708
3842
  workflowResource,
3709
3843
  effectHandlers,
3710
3844
  missingHandler,
@@ -3720,11 +3854,11 @@ function createEngine(args) {
3720
3854
  setStage: (rest) => workflow.setStage(bind(rest)),
3721
3855
  abortInstance: (rest) => workflow.abortInstance(bind(rest)),
3722
3856
  deleteDefinition: (rest) => workflow.deleteDefinition(bind(rest)),
3723
- getInstance: ({ instanceId }) => workflow.getInstance({ client, tags, workflowResource, instanceId }),
3724
- subscriptionDocumentsForInstance: ({ instanceId }) => workflow.getInstance({ client, tags, workflowResource, instanceId }).then(subscriptionDocumentsForInstance),
3857
+ getInstance: ({ instanceId }) => workflow.getInstance({ client, tag, workflowResource, instanceId }),
3858
+ subscriptionDocumentsForInstance: ({ instanceId }) => workflow.getInstance({ client, tag, workflowResource, instanceId }).then(subscriptionDocumentsForInstance),
3725
3859
  instance: (instanceDoc, opts) => createInstanceSession({
3726
3860
  client,
3727
- tags,
3861
+ tag,
3728
3862
  instance: instanceDoc,
3729
3863
  ...resourceClients !== void 0 ? { resourceClients } : {},
3730
3864
  ...clock !== void 0 ? { clock } : {},
@@ -3733,45 +3867,45 @@ function createEngine(args) {
3733
3867
  }),
3734
3868
  guardsForInstance: ({ instanceId }) => workflow.guardsForInstance({
3735
3869
  client,
3736
- tags,
3870
+ tag,
3737
3871
  workflowResource,
3738
3872
  instanceId,
3739
3873
  ...resourceClients !== void 0 ? { resourceClients } : {}
3740
3874
  }),
3741
3875
  guardsForDefinition: ({ definition }) => workflow.guardsForDefinition({
3742
3876
  client,
3743
- tags,
3877
+ tag,
3744
3878
  workflowResource,
3745
3879
  definition,
3746
3880
  ...resourceClients !== void 0 ? { resourceClients } : {}
3747
3881
  }),
3748
3882
  children: ({ instanceId, task }) => workflow.children({
3749
3883
  client,
3750
- tags,
3884
+ tag,
3751
3885
  workflowResource,
3752
3886
  instanceId,
3753
3887
  ...task !== void 0 ? { task } : {}
3754
3888
  }),
3755
3889
  query: ({ groq, params }) => workflow.query({
3756
3890
  client,
3757
- tags,
3891
+ tag,
3758
3892
  workflowResource,
3759
3893
  groq,
3760
3894
  ...params !== void 0 ? { params } : {}
3761
3895
  }),
3762
3896
  queryInScope: ({ instanceId, groq, params }) => workflow.queryInScope({
3763
3897
  client,
3764
- tags,
3898
+ tag,
3765
3899
  workflowResource,
3766
3900
  instanceId,
3767
3901
  groq,
3768
3902
  ...params !== void 0 ? { params } : {},
3769
3903
  ...clock !== void 0 ? { clock } : {}
3770
3904
  }),
3771
- listPendingEffects: ({ instanceId }) => workflow.listPendingEffects({ client, tags, workflowResource, instanceId }),
3905
+ listPendingEffects: ({ instanceId }) => workflow.listPendingEffects({ client, tag, workflowResource, instanceId }),
3772
3906
  findPendingEffects: ({ instanceId, claimed, names }) => workflow.findPendingEffects({
3773
3907
  client,
3774
- tags,
3908
+ tag,
3775
3909
  workflowResource,
3776
3910
  instanceId,
3777
3911
  ...claimed !== void 0 ? { claimed } : {},
@@ -3779,7 +3913,7 @@ function createEngine(args) {
3779
3913
  }),
3780
3914
  drainEffects: ({ instanceId, access }) => drainEffectsInternal({
3781
3915
  client,
3782
- tags,
3916
+ tag,
3783
3917
  workflowResource,
3784
3918
  ...resourceClients !== void 0 ? { resourceClients } : {},
3785
3919
  instanceId,
@@ -3790,7 +3924,7 @@ function createEngine(args) {
3790
3924
  }),
3791
3925
  verifyDeployedDefinitions: () => verifyDeployedDefinitionsInternal({
3792
3926
  client,
3793
- tags,
3927
+ tag,
3794
3928
  effectHandlers,
3795
3929
  missingHandler,
3796
3930
  logger
@@ -3798,7 +3932,7 @@ function createEngine(args) {
3798
3932
  };
3799
3933
  }
3800
3934
  function docIdFor(def, target) {
3801
- return definitionDocId(target.workflowResource, canonicalTag(target.tags), def.name, def.version);
3935
+ return definitionDocId(target.workflowResource, target.tag, def.name, def.version);
3802
3936
  }
3803
3937
  function diffStatus(existing, expected) {
3804
3938
  return existing === void 0 ? "create" : isDefinitionUnchanged(existing, expected) ? "unchanged" : "update";
@@ -3808,7 +3942,7 @@ function diffEntry(def, existingRaw, target) {
3808
3942
  ...def,
3809
3943
  _id: docId,
3810
3944
  _type: WORKFLOW_DEFINITION_TYPE,
3811
- tags: target.tags
3945
+ tag: target.tag
3812
3946
  }, status = diffStatus(existingRaw, expected), existing = existingRaw ? stripSystemFields(existingRaw) : void 0;
3813
3947
  return {
3814
3948
  name: def.name,
@@ -4008,6 +4142,7 @@ export {
4008
4142
  ActionParamsInvalidError,
4009
4143
  CascadeLimitError,
4010
4144
  ConcurrentFireActionError,
4145
+ DEFAULT_CONTENT_PERSPECTIVE,
4011
4146
  DISPLAY,
4012
4147
  EFFECTS_CONTEXT_DISPLAY,
4013
4148
  GUARD_DOC_TYPE,
@@ -4022,10 +4157,8 @@ export {
4022
4157
  WorkflowStateDivergedError,
4023
4158
  abortReason,
4024
4159
  actionVerdict,
4025
- assigneesOf,
4026
4160
  availableActions,
4027
4161
  buildSnapshot,
4028
- canonicalTag,
4029
4162
  compileGuard,
4030
4163
  computeDiffEntries,
4031
4164
  contentReleaseName,
@@ -4055,13 +4188,13 @@ export {
4055
4188
  isGdr,
4056
4189
  isTerminalStage,
4057
4190
  lakeGuardId,
4058
- openStage,
4059
4191
  parseGdr,
4060
4192
  readsRaw,
4061
4193
  refCanvas,
4062
4194
  refDashboard,
4063
4195
  refDataset,
4064
4196
  refMediaLibrary,
4197
+ remediationsFor,
4065
4198
  resolveAccess,
4066
4199
  resourceFromParsed,
4067
4200
  retractStageGuards,
@@ -4071,7 +4204,7 @@ export {
4071
4204
  subscriptionDocumentsForInstance,
4072
4205
  tagScopeFilter,
4073
4206
  validateDefinition,
4074
- validateTags,
4207
+ validateTag,
4075
4208
  verdictGuardsForInstance,
4076
4209
  wallClock,
4077
4210
  workflow