@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.cjs CHANGED
@@ -95,6 +95,21 @@ function gdrFromResource(res, documentId) {
95
95
  }
96
96
  return gdrUri({ scheme: res.type, resourceId: res.id, documentId });
97
97
  }
98
+ function resourceFromGdrUri(uri) {
99
+ let parsed;
100
+ try {
101
+ parsed = parseGdr(uri);
102
+ } catch {
103
+ return;
104
+ }
105
+ if (parsed.scheme === "dataset")
106
+ return parsed.projectId === void 0 || parsed.dataset === void 0 ? void 0 : { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` };
107
+ if (parsed.resourceId !== void 0)
108
+ return { type: parsed.scheme, id: parsed.resourceId };
109
+ }
110
+ function sameResource(a, b) {
111
+ return a.type === b.type && a.id === b.id;
112
+ }
98
113
  function selfGdr(doc) {
99
114
  return gdrFromResource(doc.workflowResource, doc._id);
100
115
  }
@@ -239,6 +254,8 @@ function validateTask(v2, stageName, task) {
239
254
  for (const entry of task.state ?? [])
240
255
  v2.checkEntry(entry, `${where}.state "${entry.name}"`);
241
256
  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`);
257
+ for (const [name, groq] of Object.entries(task.requirements ?? {}))
258
+ v2.checkCondition(groq, `${where}.requirements "${name}"`);
242
259
  for (const [key, groq] of effectBindings(task.effects))
243
260
  v2.tryParse(groq, `${where} effect binding "${key}"`);
244
261
  validateTaskActions(v2, task), task.subworkflows !== void 0 && validateSubworkflows(v2, where, task.subworkflows);
@@ -476,10 +493,14 @@ function abortReason(instance) {
476
493
  )?.reason;
477
494
  }
478
495
  function diagnoseInputFromEvaluation(evaluation) {
496
+ const stage = openStage(evaluation.instance);
479
497
  return {
480
498
  instance: evaluation.instance,
481
499
  tasks: evaluation.currentStage.tasks,
482
- transitions: evaluation.currentStage.transitions
500
+ transitions: evaluation.currentStage.transitions,
501
+ assignees: Object.fromEntries(
502
+ evaluation.currentStage.tasks.map((t) => [t.task.name, assigneesOf(stage, t.task.name)])
503
+ )
483
504
  };
484
505
  }
485
506
  function openStage(instance) {
@@ -504,18 +525,34 @@ function failedTaskCause(input) {
504
525
  return failed ? { kind: "failed-task", task: failed.task.name } : void 0;
505
526
  }
506
527
  function waitingState(input) {
507
- const active = input.tasks.find((t) => t.status === "active" && (t.task.actions ?? []).length > 0);
528
+ const active = input.tasks.find(
529
+ (t) => t.status === "active" && (t.task.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length === 0
530
+ );
508
531
  if (active !== void 0)
509
532
  return {
510
533
  state: "waiting",
511
534
  task: active.task.name,
512
- assignees: assigneesOf(openStage(input.instance), active.task.name),
535
+ assignees: input.assignees[active.task.name] ?? [],
513
536
  actions: (active.task.actions ?? []).map((a) => a.name)
514
537
  };
515
538
  }
539
+ function blockedState(input) {
540
+ const blocked = input.tasks.find(
541
+ (t) => t.status === "active" && (t.task.actions ?? []).length > 0 && (t.unmetRequirements ?? []).length > 0
542
+ );
543
+ if (blocked !== void 0)
544
+ return {
545
+ state: "blocked",
546
+ task: blocked.task.name,
547
+ requirements: blocked.unmetRequirements ?? [],
548
+ assignees: input.assignees[blocked.task.name] ?? []
549
+ };
550
+ }
516
551
  function noTransitionFiresCause(input) {
517
- if (input.transitions.length !== 0 && !input.transitions.some((t) => t.filterSatisfied) && input.tasks.every((t) => isResolved(t.status)))
518
- return { kind: "no-transition-fires" };
552
+ if (input.transitions.length === 0 || input.transitions.some((t) => t.filterSatisfied) || !input.tasks.every((t) => isResolved(t.status)))
553
+ return;
554
+ const undecidable = input.transitions.filter((t) => t.unevaluable);
555
+ return undecidable.length > 0 ? { kind: "transition-unevaluable", transitions: undecidable.map((t) => t.transition.name) } : { kind: "no-transition-fires" };
519
556
  }
520
557
  function diagnoseInstance(input) {
521
558
  const { instance } = input;
@@ -526,7 +563,51 @@ function diagnoseInstance(input) {
526
563
  if (instance.completedAt !== void 0)
527
564
  return { state: "completed", at: instance.completedAt };
528
565
  const cause = failedEffectCause(input) ?? failedTaskCause(input) ?? hungEffectCause(input) ?? noTransitionFiresCause(input);
529
- return cause !== void 0 ? { state: "stuck", cause } : waitingState(input) ?? { state: "progressing" };
566
+ return cause !== void 0 ? { state: "stuck", cause } : waitingState(input) ?? blockedState(input) ?? { state: "progressing" };
567
+ }
568
+ const RUNNABLE_VERBS = /* @__PURE__ */ new Set(["set-stage", "abort"]);
569
+ function remediationsForCause(cause) {
570
+ switch (cause.kind) {
571
+ case "failed-effect":
572
+ return [
573
+ {
574
+ verb: "retry-effect",
575
+ rationale: "Re-run the failed effect once the upstream system is healthy."
576
+ },
577
+ { verb: "abort", rationale: "Abort the instance if it can no longer recover." }
578
+ ];
579
+ case "hung-effect":
580
+ return [
581
+ {
582
+ verb: "drain-effects",
583
+ rationale: "Re-run the effect drainer to re-pick the claimed effect."
584
+ },
585
+ { verb: "abort", rationale: "Abort the instance if the effect never drains." }
586
+ ];
587
+ case "failed-task":
588
+ return [
589
+ { verb: "reset-task", rationale: "Reset or skip the failed task." },
590
+ {
591
+ verb: "set-stage",
592
+ rationale: "Move the instance past the failed task to the intended next stage."
593
+ }
594
+ ];
595
+ case "no-transition-fires":
596
+ return [
597
+ {
598
+ verb: "set-stage",
599
+ rationale: "Move the instance to the intended next stage to force it forward."
600
+ }
601
+ ];
602
+ case "transition-unevaluable":
603
+ return [];
604
+ }
605
+ }
606
+ function remediationsFor(diagnosis) {
607
+ return diagnosis.state !== "stuck" ? [] : remediationsForCause(diagnosis.cause).map((seed) => ({
608
+ ...seed,
609
+ available: RUNNABLE_VERBS.has(seed.verb)
610
+ }));
530
611
  }
531
612
  function buildSnapshot(args) {
532
613
  const docs = [], knownIds = /* @__PURE__ */ new Set();
@@ -573,6 +654,7 @@ function collectWatchRefs(instance) {
573
654
  function readsRaw(ref) {
574
655
  return ref.type === WORKFLOW_INSTANCE_TYPE || ref.type === "system.release";
575
656
  }
657
+ const DEFAULT_CONTENT_PERSPECTIVE = "drafts";
576
658
  function contentReleaseName(args) {
577
659
  const { ref, perspective } = args;
578
660
  if (!readsRaw(ref) && Array.isArray(perspective))
@@ -598,6 +680,11 @@ function entryReleaseRefs(state) {
598
680
  (entry) => entry._type === "release.ref" && isGdr(entry.value) ? [entry.value] : []
599
681
  );
600
682
  }
683
+ function entrySubjectRefs(state) {
684
+ return stateEntries(state).flatMap(
685
+ (entry) => (entry._type === "doc.ref" || entry._type === "release.ref") && isGdr(entry.value) ? [entry.value] : []
686
+ );
687
+ }
601
688
  function stateEntries(state) {
602
689
  return Array.isArray(state) ? state.filter(
603
690
  (entry) => !!entry && typeof entry == "object"
@@ -622,7 +709,10 @@ async function hydrateSnapshot(args) {
622
709
  };
623
710
  loaded.push({ doc: instance, resource: instance.workflowResource }), visited.add(selfGdr(instance));
624
711
  for (const ref of collectWatchRefs(instance))
625
- await loadInto(ref.id, readsRaw(ref) ? void 0 : instance.perspective);
712
+ await loadInto(
713
+ ref.id,
714
+ readsRaw(ref) ? "raw" : instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
715
+ );
626
716
  return buildSnapshot({ docs: loaded });
627
717
  }
628
718
  async function loadByGdr(defaultClient, clientForGdr, defaultResource, uri, perspective) {
@@ -637,7 +727,7 @@ async function loadByGdr(defaultClient, clientForGdr, defaultResource, uri, pers
637
727
  return doc ? { doc, resource: resourceFromParsed(parsed) } : null;
638
728
  }
639
729
  async function readDoc(client, id, perspective) {
640
- return perspective === void 0 ? await client.getDocument(id) ?? null : await client.fetch("*[_id == $id][0]", { id }, { perspective }) ?? null;
730
+ return perspective === "raw" ? await client.getDocument(id) ?? null : await client.fetch("*[_id == $id][0]", { id }, { perspective }) ?? null;
641
731
  }
642
732
  function resourceFromParsed(parsed) {
643
733
  return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
@@ -738,23 +828,7 @@ async function queryGuardsAcross(clients, query, params) {
738
828
  function dedupById(guards) {
739
829
  return [...new Map(guards.map((g) => [g._id, g])).values()].sort((a, b) => a._id.localeCompare(b._id));
740
830
  }
741
- const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
742
- function validateTags(tags) {
743
- if (tags.length === 0)
744
- throw new Error("tags: must be a non-empty array");
745
- for (const tag of tags)
746
- if (!TAG_RE.test(tag))
747
- throw new Error(
748
- `tags: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
749
- );
750
- }
751
- function canonicalTag(tags) {
752
- return tags[0];
753
- }
754
- function tagScopeFilter(param = "engineTags") {
755
- return `count(tags[@ in $${param}]) > 0`;
756
- }
757
- const GUARD_OWNER = "robot:workflow-engine", GUARD_LIFTED_PREDICATE = "true";
831
+ const SYNC_COMMIT = { visibility: "sync" }, GUARD_OWNER = "robot:workflow-engine", GUARD_LIFTED_PREDICATE = "true";
758
832
  function resolveGuard(guard, instance, stageName, now) {
759
833
  const ctx = { instance, now }, targets = resolveIdRefTargets(guard.match.idRefs, ctx);
760
834
  if (targets === null) return null;
@@ -781,11 +855,11 @@ function resolveGuard(guard, instance, stageName, now) {
781
855
  }
782
856
  async function upsertGuard(client, doc) {
783
857
  if (!await client.getDocument(doc._id)) {
784
- await client.create(doc);
858
+ await client.create(doc, SYNC_COMMIT);
785
859
  return;
786
860
  }
787
861
  const { _id, _type, _rev, _createdAt, _updatedAt, ...body } = doc;
788
- await client.patch(doc._id).set(body).commit();
862
+ await client.patch(doc._id).set(body).commit(SYNC_COMMIT);
789
863
  }
790
864
  function resolvedStageGuards(args) {
791
865
  const stage = args.definition.stages.find((s) => s.name === args.stageName), out = [];
@@ -808,16 +882,16 @@ async function retractStageGuards(args) {
808
882
  const live = await committedInstance(args);
809
883
  if (live !== void 0 && !(live.currentStage === args.stageName && live.abortedAt === void 0))
810
884
  for (const { client, doc } of resolvedStageGuards(args))
811
- await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit();
885
+ await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit(SYNC_COMMIT);
812
886
  }
813
887
  async function deleteOrphanedDefinitionGuards(args) {
814
- const { client, clientForGdr, workflowResource, definition, definitions, tags } = args, perClient = await guardsForDefinitionByClient({
888
+ const { client, clientForGdr, workflowResource, definition, definitions, tag } = args, perClient = await guardsForDefinitionByClient({
815
889
  client,
816
890
  clientForGdr,
817
891
  workflowResource,
818
892
  definition,
819
893
  definitions
820
- }), ownPartitionPrefix = `${canonicalTag(tags)}.`;
894
+ }), ownPartitionPrefix = `${tag}.`;
821
895
  let count = 0;
822
896
  for (const { client: resourceClient, guards } of perClient) {
823
897
  const own = guards.filter((guard) => guard.sourceInstanceId.startsWith(ownPartitionPrefix));
@@ -832,16 +906,32 @@ function randomKey(length = 12) {
832
906
  const bytes = new Uint8Array(length);
833
907
  return globalThis.crypto.getRandomValues(bytes), [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, length);
834
908
  }
835
- async function evaluateCondition(args) {
909
+ function isUnevaluable(result) {
910
+ return result == null;
911
+ }
912
+ async function evaluateConditionOutcome(args) {
836
913
  const { condition, snapshot, params } = args;
837
- return condition === void 0 ? !0 : !!await runGroq(condition, params, snapshot);
914
+ if (condition === void 0) return "satisfied";
915
+ const result = await runGroq(condition, params, snapshot);
916
+ return isUnevaluable(result) ? "unevaluable" : result ? "satisfied" : "unsatisfied";
917
+ }
918
+ async function evaluateCondition(args) {
919
+ return await evaluateConditionOutcome(args) === "satisfied";
838
920
  }
839
921
  async function evaluatePredicates(args) {
840
922
  const out = {};
841
- for (const [name, groq] of Object.entries(args.predicates ?? {}))
842
- out[name] = !!await runGroq(groq, args.params, args.snapshot);
923
+ for (const [name, groq] of Object.entries(args.predicates ?? {})) {
924
+ const result = await runGroq(groq, args.params, args.snapshot);
925
+ out[name] = isUnevaluable(result) ? null : !!result;
926
+ }
843
927
  return out;
844
928
  }
929
+ async function evaluateRequirements(args) {
930
+ const unmet = [];
931
+ for (const [name, condition] of Object.entries(args.requirements ?? {}))
932
+ await evaluateCondition({ condition, snapshot: args.snapshot, params: args.params }) || unmet.push(name);
933
+ return unmet;
934
+ }
845
935
  async function runGroq(groq, params, snapshot) {
846
936
  const tree = groqJs.parse(groq, { params });
847
937
  return (await groqJs.evaluate(tree, { dataset: snapshot.docs, params })).get();
@@ -1006,10 +1096,10 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
1006
1096
  stage: ctx.stageName ?? null,
1007
1097
  task: ctx.taskName ?? null,
1008
1098
  now: ctx.now,
1009
- engineTags: ctx.tags ?? []
1099
+ tag: ctx.tag
1010
1100
  });
1011
1101
  try {
1012
- const fetchOptions = ctx.perspective !== void 0 ? { perspective: ctx.perspective } : void 0, result = await client.fetch(source.query, params, fetchOptions);
1102
+ const fetchOptions = { perspective: ctx.perspective ?? DEFAULT_CONTENT_PERSPECTIVE }, result = await client.fetch(source.query, params, fetchOptions);
1013
1103
  return normalizeQueryResult(entry.type, result, ctx.workflowResource) ?? defaultValue;
1014
1104
  } catch (err) {
1015
1105
  throw new Error(
@@ -1139,13 +1229,16 @@ async function ctxConditionParams(ctx, opts) {
1139
1229
  params
1140
1230
  }), ...params };
1141
1231
  }
1142
- async function ctxEvaluateCondition(ctx, condition, opts) {
1143
- return condition === void 0 ? !0 : evaluateCondition({
1232
+ async function ctxEvaluateConditionOutcome(ctx, condition, opts) {
1233
+ return condition === void 0 ? "satisfied" : evaluateConditionOutcome({
1144
1234
  condition,
1145
1235
  snapshot: ctx.snapshot,
1146
1236
  params: await ctxConditionParams(ctx, opts)
1147
1237
  });
1148
1238
  }
1239
+ async function ctxEvaluateCondition(ctx, condition, opts) {
1240
+ return await ctxEvaluateConditionOutcome(ctx, condition, opts) === "satisfied";
1241
+ }
1149
1242
  async function buildEngineContext(args) {
1150
1243
  const { client, clientForGdr, instance, definition } = args, clock = args.clock ?? wallClock;
1151
1244
  return {
@@ -1173,7 +1266,7 @@ async function resolveStageStateEntries(args) {
1173
1266
  client,
1174
1267
  now,
1175
1268
  selfId: instance._id,
1176
- tags: instance.tags ?? [],
1269
+ tag: instance.tag,
1177
1270
  stageName: stage.name,
1178
1271
  workflowResource: instance.workflowResource,
1179
1272
  // Allow stage-scope entries' `source: { type: "stateRead", scope:
@@ -1193,7 +1286,7 @@ async function resolveTaskStateEntries(args) {
1193
1286
  client,
1194
1287
  now,
1195
1288
  selfId: instance._id,
1196
- tags: instance.tags ?? [],
1289
+ tag: instance.tag,
1197
1290
  stageName: stage.name,
1198
1291
  workflowResource: instance.workflowResource,
1199
1292
  taskName: task.name,
@@ -1265,7 +1358,7 @@ async function deployOrRollback(args) {
1265
1358
  });
1266
1359
  try {
1267
1360
  let patch = args.client.patch(args.instanceId).set(args.restore);
1268
- args.unset && args.unset.length > 0 && (patch = patch.unset(args.unset)), args.committedRev !== void 0 && (patch = patch.ifRevisionId(args.committedRev)), await patch.commit();
1361
+ 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);
1269
1362
  } catch (rollbackError) {
1270
1363
  throw new WorkflowStateDivergedError({
1271
1364
  instanceId: args.instanceId,
@@ -1372,7 +1465,7 @@ async function persist(ctx, mutation) {
1372
1465
  lastChangedAt: ctx.now
1373
1466
  }, pendingCreates = mutation.pendingCreates;
1374
1467
  if (pendingCreates.length === 0)
1375
- return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit();
1468
+ return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit(SYNC_COMMIT);
1376
1469
  const tx = ctx.client.transaction();
1377
1470
  for (const body of pendingCreates) tx.create(body);
1378
1471
  tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)), await tx.commit(), mutation.pendingCreates = [];
@@ -1415,6 +1508,16 @@ function findCurrentStageEntry(instance) {
1415
1508
  function findCurrentTasks(instance) {
1416
1509
  return findCurrentStageEntry(instance)?.tasks ?? [];
1417
1510
  }
1511
+ const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
1512
+ function validateTag(tag) {
1513
+ if (!TAG_RE.test(tag))
1514
+ throw new Error(
1515
+ `tag: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
1516
+ );
1517
+ }
1518
+ function tagScopeFilter() {
1519
+ return "tag == $tag";
1520
+ }
1418
1521
  function definitionLookupGroq(explicit) {
1419
1522
  const scoped = `_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}`;
1420
1523
  return explicit ? `*[${scoped} && version == $version][0]` : `*[${scoped}] | order(version desc)[0]`;
@@ -1431,6 +1534,14 @@ function clientForDiscovery(defaultClient, clientForGdr, subjectGdrUri) {
1431
1534
  return defaultClient;
1432
1535
  }
1433
1536
  }
1537
+ async function reload(client, instanceId, tag) {
1538
+ const doc = await client.getDocument(instanceId);
1539
+ if (!doc)
1540
+ throw new Error(`Workflow instance ${instanceId} not found`);
1541
+ if (doc.tag !== tag)
1542
+ throw new Error(`Workflow instance ${instanceId} not visible to this engine (tag mismatch)`);
1543
+ return doc;
1544
+ }
1434
1545
  function effectsContextEntry(name, value) {
1435
1546
  const _key = randomKey();
1436
1547
  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 };
@@ -1446,7 +1557,7 @@ function buildInstanceBase(args) {
1446
1557
  _rev: "",
1447
1558
  _createdAt: now,
1448
1559
  _updatedAt: now,
1449
- tags: args.tags,
1560
+ tag: args.tag,
1450
1561
  workflowResource: args.workflowResource,
1451
1562
  definition: args.definitionName,
1452
1563
  pinnedVersion: args.pinnedVersion,
@@ -1497,7 +1608,7 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
1497
1608
  `Subworkflow depth limit (${MAX_SPAWN_DEPTH}) exceeded on task "${task.name}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
1498
1609
  );
1499
1610
  }
1500
- const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tags ?? []);
1611
+ const now = ctx.now, definition = await resolveDefinitionRef(ctx.client, sub.definition, ctx.instance.tag);
1501
1612
  if (definition === null) {
1502
1613
  const versionLabel = sub.definition.version === void 0 || sub.definition.version === "latest" ? "latest" : `v${sub.definition.version}`;
1503
1614
  throw new Error(
@@ -1507,9 +1618,16 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
1507
1618
  const parentScope = await ctxConditionParams(ctx, {
1508
1619
  taskName: task.name,
1509
1620
  ...actor ? { actor } : {}
1510
- }), rows = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
1621
+ }), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
1511
1622
  for (const row of rows) {
1512
- const initialState = await projectRowState(ctx, sub, definition, parentScope, row), { ref, body } = await prepareChildInstance({
1623
+ const initialState = await projectRowState(
1624
+ ctx,
1625
+ sub,
1626
+ definition,
1627
+ parentScope,
1628
+ row,
1629
+ discoveryResource
1630
+ ), { ref, body } = await prepareChildInstance({
1513
1631
  client: ctx.client,
1514
1632
  parent: ctx.instance,
1515
1633
  definition,
@@ -1524,28 +1642,25 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
1524
1642
  }
1525
1643
  async function resolveSpawnRows(ctx, sub) {
1526
1644
  const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
1527
- let value;
1645
+ let value, discoveryResource;
1528
1646
  if (groq.includes("*")) {
1529
- const routingUri = firstDocRefGdrUri(ctx.instance.state), client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
1530
- value = await discoverItems({
1647
+ const routingUri = entrySubjectRefs(ctx.instance.state)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
1648
+ client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
1531
1649
  client,
1532
1650
  groq,
1533
1651
  params,
1534
- ...ctx.instance.perspective !== void 0 ? { perspective: ctx.instance.perspective } : {}
1652
+ // Draft-aware by default (drafts overlay published) when there's no
1653
+ // release, so a `forEach` discovers in-flight items the same way the
1654
+ // engine reads any other content.
1655
+ perspective: ctx.instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
1535
1656
  });
1536
1657
  } else {
1537
1658
  const tree = groqJs.parse(groq, { params });
1538
1659
  value = await (await groqJs.evaluate(tree, { dataset: [ctx.instance], params, root: ctx.instance })).get();
1539
1660
  }
1540
- return Array.isArray(value) ? value : value == null ? [] : [value];
1661
+ return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
1541
1662
  }
1542
- function firstDocRefGdrUri(entries) {
1543
- if (entries) {
1544
- for (const s of entries)
1545
- if (s._type === "doc.ref" && s.value !== null) return s.value.id;
1546
- }
1547
- }
1548
- async function projectRowState(ctx, sub, childDefinition, parentScope, row) {
1663
+ async function projectRowState(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
1549
1664
  const out = [];
1550
1665
  for (const [name, groq] of Object.entries(sub.with ?? {})) {
1551
1666
  const declared = (childDefinition.state ?? []).find((e) => e.name === name);
@@ -1553,7 +1668,12 @@ async function projectRowState(ctx, sub, childDefinition, parentScope, row) {
1553
1668
  throw new Error(
1554
1669
  `subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no state entry "${name}"`
1555
1670
  );
1556
- const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, ctx.instance.workflowResource);
1671
+ const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
1672
+ parentResource: ctx.instance.workflowResource,
1673
+ discoveryResource,
1674
+ entryName: name,
1675
+ childName: childDefinition.name
1676
+ });
1557
1677
  value != null && out.push({
1558
1678
  type: declared.type,
1559
1679
  name,
@@ -1570,11 +1690,17 @@ async function resolveContextHandoff(ctx, sub, parentScope) {
1570
1690
  }
1571
1691
  return out;
1572
1692
  }
1573
- function canonicaliseSpawnValue(kind, value, workflowResource) {
1574
- return kind === "doc.ref" ? coerceSpawnGdr(value, workflowResource) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, workflowResource)).filter((v2) => v2 !== null) : value;
1693
+ function canonicaliseSpawnValue(kind, value, cx) {
1694
+ return kind === "doc.ref" ? coerceSpawnGdr(value, cx) : kind === "doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, cx)).filter((v2) => v2 !== null) : value;
1695
+ }
1696
+ function assertBareIdRootsCorrectly(id, cx) {
1697
+ if (!(cx.discoveryResource === void 0 || sameResource(cx.discoveryResource, cx.parentResource)))
1698
+ throw new Error(
1699
+ `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.`
1700
+ );
1575
1701
  }
1576
- function coerceSpawnGdr(raw, workflowResource) {
1577
- const toUri = (id) => isGdrUri(id) ? id : gdrFromResource(workflowResource, id);
1702
+ function coerceSpawnGdr(raw, cx) {
1703
+ const toUri = (id) => isGdrUri(id) ? id : (assertBareIdRootsCorrectly(id, cx), gdrFromResource(cx.parentResource, id));
1578
1704
  if (raw == null) return null;
1579
1705
  if (typeof raw == "object" && "id" in raw && "type" in raw) {
1580
1706
  const r = raw;
@@ -1587,15 +1713,15 @@ function coerceSpawnGdr(raw, workflowResource) {
1587
1713
  }
1588
1714
  return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
1589
1715
  }
1590
- async function resolveDefinitionRef(client, ref, tags) {
1591
- const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, engineTags: tags };
1716
+ async function resolveDefinitionRef(client, ref, tag) {
1717
+ const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
1592
1718
  return wantsExplicit && (params.version = ref.version), await client.fetch(
1593
1719
  definitionLookupGroq(wantsExplicit),
1594
1720
  params
1595
1721
  ) ?? null;
1596
1722
  }
1597
1723
  async function prepareChildInstance(args) {
1598
- 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 = [
1724
+ 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 = [
1599
1725
  ...parent.ancestors,
1600
1726
  {
1601
1727
  id: selfGdr(parent),
@@ -1608,7 +1734,7 @@ async function prepareChildInstance(args) {
1608
1734
  client,
1609
1735
  now,
1610
1736
  selfId: childDocId,
1611
- tags: childTags,
1737
+ tag: childTag,
1612
1738
  workflowResource,
1613
1739
  ...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
1614
1740
  },
@@ -1627,7 +1753,7 @@ async function prepareChildInstance(args) {
1627
1753
  ), base = buildInstanceBase({
1628
1754
  id: childDocId,
1629
1755
  now,
1630
- tags: childTags,
1756
+ tag: childTag,
1631
1757
  workflowResource,
1632
1758
  definitionName: definition.name,
1633
1759
  pinnedVersion: definition.version,
@@ -2073,21 +2199,24 @@ function resolveMachineStep(mutation, task, entry, now) {
2073
2199
  async function buildStageTasks(ctx, stage) {
2074
2200
  const entries = [];
2075
2201
  for (const task of stage.tasks ?? []) {
2076
- const inScope = await ctxEvaluateCondition(ctx, task.filter, { taskName: task.name });
2202
+ const outcome = await ctxEvaluateConditionOutcome(ctx, task.filter, { taskName: task.name }), inScope = outcome !== "unsatisfied";
2077
2203
  entries.push({
2078
2204
  _key: randomKey(),
2079
2205
  name: task.name,
2080
2206
  status: inScope ? "pending" : "skipped",
2081
- ...task.filter !== void 0 ? {
2082
- filterEvaluation: {
2083
- at: ctx.now,
2084
- truthy: inScope
2085
- }
2086
- } : {}
2207
+ ...task.filter !== void 0 ? { filterEvaluation: buildFilterEvaluation(ctx.now, task.filter, outcome) } : {}
2087
2208
  });
2088
2209
  }
2089
2210
  return entries;
2090
2211
  }
2212
+ function buildFilterEvaluation(at, filter, outcome) {
2213
+ return outcome === "unevaluable" ? {
2214
+ at,
2215
+ truthy: !1,
2216
+ unevaluable: !0,
2217
+ 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.`
2218
+ } : { at, truthy: outcome === "satisfied" };
2219
+ }
2091
2220
  function isTerminalStage(stage) {
2092
2221
  return (stage.transitions ?? []).length === 0;
2093
2222
  }
@@ -2237,8 +2366,11 @@ async function commitTransition(ctx, actor) {
2237
2366
  };
2238
2367
  }
2239
2368
  async function pickTransition(ctx, stage) {
2240
- for (const candidate of stage.transitions ?? [])
2241
- if (await ctxEvaluateCondition(ctx, candidate.filter)) return candidate;
2369
+ for (const candidate of stage.transitions ?? []) {
2370
+ const outcome = await ctxEvaluateConditionOutcome(ctx, candidate.filter);
2371
+ if (outcome === "satisfied") return candidate;
2372
+ if (outcome === "unevaluable") return;
2373
+ }
2242
2374
  }
2243
2375
  async function evaluateAutoTransitions(args) {
2244
2376
  const { client, instanceId, options, clientForGdr, clock, overlay } = args, ctx = await loadContext(client, instanceId, {
@@ -2268,7 +2400,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
2268
2400
  }, committed = await client.patch(instance._id).set({
2269
2401
  stages: [initialStageEntry],
2270
2402
  lastChangedAt: now
2271
- }).ifRevisionId(instance._rev).commit();
2403
+ }).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
2272
2404
  await deployOrRollback({
2273
2405
  client,
2274
2406
  instanceId: instance._id,
@@ -2493,17 +2625,12 @@ async function fetchGrantsCached(requestFn, resourcePath) {
2493
2625
  }
2494
2626
  }
2495
2627
  async function evaluateInstance(args) {
2496
- const { client, tags, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
2497
- validateTags(tags);
2628
+ const { client, tag, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
2629
+ validateTag(tag);
2498
2630
  const { actor, grants } = await resolveAccess(client, {
2499
2631
  ...args.access !== void 0 ? { override: args.access } : {},
2500
2632
  ...args.grantsFromPath !== void 0 ? { grantsFromPath: args.grantsFromPath } : {}
2501
- }), instance = await client.getDocument(instanceId);
2502
- if (!instance)
2503
- throw new Error(`Workflow instance "${instanceId}" not found`);
2504
- if (!tags.some((t) => instance.tags?.includes(t)))
2505
- throw new Error(`Workflow instance "${instanceId}" not visible to this engine (tag mismatch)`);
2506
- const definition = parseDefinitionSnapshot(instance), snapshot = await hydrateSnapshot({ client, clientForGdr: resourceClients !== void 0 ? (parsed) => resourceClients(parsed) ?? client : () => client, instance }), guards = await verdictGuardsForInstance(client, instance._id);
2633
+ }), 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);
2507
2634
  return evaluateFromSnapshot({
2508
2635
  instance,
2509
2636
  definition,
@@ -2535,15 +2662,18 @@ async function evaluateFromSnapshot(args) {
2535
2662
  })
2536
2663
  );
2537
2664
  const transitionEvaluations = [];
2538
- for (const transition of stage.transitions ?? [])
2665
+ for (const transition of stage.transitions ?? []) {
2666
+ const outcome = await evaluateConditionOutcome({
2667
+ condition: transition.filter,
2668
+ snapshot,
2669
+ params: scope
2670
+ });
2539
2671
  transitionEvaluations.push({
2540
2672
  transition,
2541
- filterSatisfied: await evaluateCondition({
2542
- condition: transition.filter,
2543
- snapshot,
2544
- params: scope
2545
- })
2673
+ filterSatisfied: outcome === "satisfied",
2674
+ unevaluable: outcome === "unevaluable"
2546
2675
  });
2676
+ }
2547
2677
  const currentStage = {
2548
2678
  stage,
2549
2679
  tasks: taskEvaluations,
@@ -2591,7 +2721,11 @@ async function evaluateTask(args) {
2591
2721
  ...scopedStateOverlay(instance, snapshot, task.name)
2592
2722
  },
2593
2723
  assigned
2594
- }, actions = [];
2724
+ }, unmetRequirements = await evaluateRequirements({
2725
+ requirements: task.requirements,
2726
+ snapshot,
2727
+ params: taskScope
2728
+ }), requirementsReason = unmetRequirements.length > 0 ? { kind: "requirements-unmet", unmetRequirements } : void 0, actions = [];
2595
2729
  for (const action of task.actions ?? [])
2596
2730
  actions.push(
2597
2731
  await evaluateAction({
@@ -2601,7 +2735,8 @@ async function evaluateTask(args) {
2601
2735
  snapshot,
2602
2736
  taskScope,
2603
2737
  stageHasExits,
2604
- guardDenial
2738
+ guardDenial,
2739
+ requirementsReason
2605
2740
  })
2606
2741
  );
2607
2742
  return {
@@ -2612,12 +2747,22 @@ async function evaluateTask(args) {
2612
2747
  // they're still inbox items. The inbox reads the assignees-kind state
2613
2748
  // entry BY KIND (who-data is state).
2614
2749
  pendingOnActor: (status === "active" || status === "pending") && assigned,
2750
+ ...unmetRequirements.length > 0 ? { unmetRequirements } : {},
2615
2751
  actions
2616
2752
  };
2617
2753
  }
2618
2754
  async function evaluateAction(args) {
2619
- const { action, status, instance, snapshot, taskScope, stageHasExits, guardDenial } = args, lifecycle = lifecycleReason(instance, status, stageHasExits);
2620
- return lifecycle !== void 0 ? disabled(action, lifecycle) : guardDenial !== void 0 ? disabled(action, guardDenial) : action.filter !== void 0 && !await evaluateCondition({
2755
+ const {
2756
+ action,
2757
+ status,
2758
+ instance,
2759
+ snapshot,
2760
+ taskScope,
2761
+ stageHasExits,
2762
+ guardDenial,
2763
+ requirementsReason
2764
+ } = args, lifecycle = lifecycleReason(instance, status, stageHasExits);
2765
+ 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({
2621
2766
  condition: action.filter,
2622
2767
  snapshot,
2623
2768
  params: taskScope
@@ -2644,22 +2789,21 @@ function lifecycleReason(instance, status, stageHasExits) {
2644
2789
  function disabled(action, reason) {
2645
2790
  return { action, allowed: !1, disabledReason: reason };
2646
2791
  }
2647
- function definitionDocId(_workflowResource, prefix, definition, version) {
2648
- return `${prefix}.${definition}.v${version}`;
2792
+ function definitionDocId(_workflowResource, tag, definition, version) {
2793
+ return `${tag}.${definition}.v${version}`;
2649
2794
  }
2650
- async function sortByDependencies(client, definitions, tags, workflowResource) {
2651
- const prefix = canonicalTag(tags), byName = /* @__PURE__ */ new Map();
2795
+ async function sortByDependencies(client, definitions, tag, workflowResource) {
2796
+ const byName = /* @__PURE__ */ new Map();
2652
2797
  for (const def of definitions) {
2653
2798
  const list = byName.get(def.name) ?? [];
2654
2799
  list.push(def), byName.set(def.name, list);
2655
2800
  }
2656
2801
  const findInBatch = (ref) => findRefInBatch(byName, ref);
2657
2802
  return await assertCrossBatchRefsDeployed(client, definitions, {
2658
- tags,
2803
+ tag,
2659
2804
  workflowResource,
2660
- prefix,
2661
2805
  findInBatch
2662
- }), topoSortDefinitions(definitions, { workflowResource, prefix, findInBatch });
2806
+ }), topoSortDefinitions(definitions, { workflowResource, tag, findInBatch });
2663
2807
  }
2664
2808
  function findRefInBatch(byName, ref) {
2665
2809
  const candidates = byName.get(ref.name);
@@ -2672,10 +2816,10 @@ function findRefInBatch(byName, ref) {
2672
2816
  async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
2673
2817
  const missing = [];
2674
2818
  for (const def of definitions) {
2675
- const fromId = definitionDocId(ctx.workflowResource, ctx.prefix, def.name, def.version);
2819
+ const fromId = definitionDocId(ctx.workflowResource, ctx.tag, def.name, def.version);
2676
2820
  for (const ref of refsOf(def)) {
2677
2821
  if (ctx.findInBatch(ref) !== void 0) continue;
2678
- const label = await resolveDeployedRefLabel(client, ref, ctx.tags);
2822
+ const label = await resolveDeployedRefLabel(client, ref, ctx.tag);
2679
2823
  label !== void 0 && missing.push({ from: fromId, ref: label });
2680
2824
  }
2681
2825
  }
@@ -2687,8 +2831,8 @@ async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
2687
2831
  `)
2688
2832
  );
2689
2833
  }
2690
- async function resolveDeployedRefLabel(client, ref, tags) {
2691
- const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, engineTags: tags };
2834
+ async function resolveDeployedRefLabel(client, ref, tag) {
2835
+ const wantsExplicit = typeof ref.version == "number", params = { definition: ref.name, tag };
2692
2836
  if (wantsExplicit && (params.version = ref.version), !await client.fetch(
2693
2837
  definitionLookupGroq(wantsExplicit),
2694
2838
  params
@@ -2697,7 +2841,7 @@ async function resolveDeployedRefLabel(client, ref, tags) {
2697
2841
  }
2698
2842
  function topoSortDefinitions(definitions, ctx) {
2699
2843
  const visited = /* @__PURE__ */ new Set(), visiting = /* @__PURE__ */ new Set(), ordered = [], visit = (def) => {
2700
- const id = definitionDocId(ctx.workflowResource, ctx.prefix, def.name, def.version);
2844
+ const id = definitionDocId(ctx.workflowResource, ctx.tag, def.name, def.version);
2701
2845
  if (!visited.has(id)) {
2702
2846
  if (visiting.has(id))
2703
2847
  throw new Error(`workflow.deployDefinitions: dependency cycle involving "${id}"`);
@@ -2738,11 +2882,11 @@ function stableStringify(value) {
2738
2882
  ([a], [b]) => a.localeCompare(b)
2739
2883
  ).map(([k, v2]) => `${JSON.stringify(k)}:${stableStringify(v2)}`).join(",")}}`;
2740
2884
  }
2741
- async function loadDefinition(client, definition, version, tags) {
2885
+ async function loadDefinition(client, definition, version, tag) {
2742
2886
  if (version !== void 0) {
2743
2887
  const doc = await client.fetch(
2744
2888
  definitionLookupGroq(!0),
2745
- { definition, version, engineTags: tags }
2889
+ { definition, version, tag }
2746
2890
  );
2747
2891
  if (!doc)
2748
2892
  throw new Error(`Workflow definition ${definition} v${version} not deployed`);
@@ -2750,16 +2894,16 @@ async function loadDefinition(client, definition, version, tags) {
2750
2894
  }
2751
2895
  const latest = await client.fetch(definitionLookupGroq(!1), {
2752
2896
  definition,
2753
- engineTags: tags
2897
+ tag
2754
2898
  });
2755
2899
  if (!latest)
2756
2900
  throw new Error(`No deployed definition for workflow ${definition}`);
2757
2901
  return latest;
2758
2902
  }
2759
- async function loadDefinitionVersions(client, definition, tags) {
2903
+ async function loadDefinitionVersions(client, definition, tag) {
2760
2904
  return client.fetch(
2761
- `*[_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && name == $definition && count(tags[@ in $engineTags]) > 0] | order(version desc)`,
2762
- { definition, engineTags: tags }
2905
+ `*[_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && name == $definition && ${tagScopeFilter()}] | order(version desc)`,
2906
+ { definition, tag }
2763
2907
  );
2764
2908
  }
2765
2909
  async function abortInstance(args) {
@@ -2895,7 +3039,8 @@ const disabledReasonDetail = {
2895
3039
  "task-not-active": (r) => `task status is "${r.status}"`,
2896
3040
  "stage-terminal": (r) => `stage "${r.stage}" is terminal`,
2897
3041
  "instance-completed": (r) => `instance completed at ${r.completedAt}`,
2898
- "mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`
3042
+ "mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`,
3043
+ "requirements-unmet": (r) => `unmet requirement(s): ${r.unmetRequirements.join(", ")}`
2899
3044
  };
2900
3045
  function formatDisabledReason(task, action, reason) {
2901
3046
  const detail = disabledReasonDetail[reason.kind](reason);
@@ -2910,7 +3055,7 @@ function bareIdFromSpawnRef(uri) {
2910
3055
  }
2911
3056
  }
2912
3057
  async function resolveOperationContext(args) {
2913
- validateTags(args.tags);
3058
+ validateTag(args.tag);
2914
3059
  const access = await resolveAccess(args.client, {
2915
3060
  ...args.access !== void 0 ? { override: args.access } : {},
2916
3061
  ...args.grantsFromPath !== void 0 ? { grantsFromPath: args.grantsFromPath } : {}
@@ -2944,16 +3089,6 @@ async function abortAndPropagate(args) {
2944
3089
  function buildClientForGdr(defaultClient, resolver) {
2945
3090
  return resolver === void 0 ? () => defaultClient : (parsed) => resolver(parsed) ?? defaultClient;
2946
3091
  }
2947
- async function reload(client, instanceId, tags) {
2948
- const doc = await client.getDocument(instanceId);
2949
- if (!doc) throw new Error(`Workflow instance ${instanceId} not found`);
2950
- if (!intersectsTags(doc.tags, tags))
2951
- throw new Error(`Workflow instance ${instanceId} not visible to this engine (tag mismatch)`);
2952
- return doc;
2953
- }
2954
- function intersectsTags(docTags, engineTags) {
2955
- return !docTags || docTags.length === 0 ? !1 : engineTags.some((t) => docTags.includes(t));
2956
- }
2957
3092
  function toEffectsContextEntries(ctx) {
2958
3093
  return Object.entries(ctx).map(([id, value]) => effectsContextEntry(id, value));
2959
3094
  }
@@ -2979,15 +3114,15 @@ async function applyAction(args) {
2979
3114
  })).ranOps;
2980
3115
  }
2981
3116
  async function deleteDefinition(args) {
2982
- const { client, tags, definition, version, cascade: cascade2, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, versions = await loadDefinitionVersions(client, definition, tags);
3117
+ const { client, tag, definition, version, cascade: cascade2, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, versions = await loadDefinitionVersions(client, definition, tag);
2983
3118
  if (versions.length === 0)
2984
3119
  throw new Error(`No deployed definition for workflow ${definition}`);
2985
3120
  const targets = version === void 0 ? versions : versions.filter((d) => d.version === version);
2986
3121
  if (targets.length === 0)
2987
3122
  throw new Error(`Workflow definition ${definition} v${version} not deployed`);
2988
3123
  const lastVersionGoes = targets.length === versions.length;
2989
- await assertNoSpawnReferrers(client, tags, definition, targets, lastVersionGoes);
2990
- const instanceIds = await nonTerminalInstanceIds(client, tags, definition, version);
3124
+ await assertNoSpawnReferrers(client, tag, definition, targets, lastVersionGoes);
3125
+ const instanceIds = await nonTerminalInstanceIds(client, tag, definition, version);
2991
3126
  if (instanceIds.length > 0 && cascade2 !== !0)
2992
3127
  throw new Error(
2993
3128
  `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.`
@@ -3008,7 +3143,7 @@ async function deleteDefinition(args) {
3008
3143
  workflowResource: args.workflowResource,
3009
3144
  definition,
3010
3145
  definitions: versions,
3011
- tags
3146
+ tag
3012
3147
  }) : 0;
3013
3148
  return {
3014
3149
  name: definition,
@@ -3017,10 +3152,10 @@ async function deleteDefinition(args) {
3017
3152
  deletedGuardCount
3018
3153
  };
3019
3154
  }
3020
- async function assertNoSpawnReferrers(client, tags, definition, targets, lastVersionGoes) {
3155
+ async function assertNoSpawnReferrers(client, tag, definition, targets, lastVersionGoes) {
3021
3156
  const deployed = await client.fetch(
3022
3157
  `*[_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && ${tagScopeFilter()}]`,
3023
- { engineTags: tags }
3158
+ { tag }
3024
3159
  ), 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(
3025
3160
  (d) => refsOf(d).some(
3026
3161
  (ref) => ref.name === definition && (typeof ref.version == "number" ? targetVersions.has(ref.version) : lastVersionGoes)
@@ -3033,11 +3168,11 @@ async function assertNoSpawnReferrers(client, tags, definition, targets, lastVer
3033
3168
  );
3034
3169
  }
3035
3170
  }
3036
- async function nonTerminalInstanceIds(client, tags, definition, version) {
3171
+ async function nonTerminalInstanceIds(client, tag, definition, version) {
3037
3172
  const versionFilter = version === void 0 ? "" : " && pinnedVersion == $version";
3038
3173
  return (await client.fetch(
3039
3174
  `*[_type == "${WORKFLOW_INSTANCE_TYPE}" && definition == $definition && ${tagScopeFilter()} && !defined(completedAt)${versionFilter}] | order(_id asc){_id}`,
3040
- { definition, engineTags: tags, ...version !== void 0 ? { version } : {} }
3175
+ { definition, tag, ...version !== void 0 ? { version } : {} }
3041
3176
  )).map((doc) => doc._id);
3042
3177
  }
3043
3178
  async function abortInstances(args) {
@@ -3068,20 +3203,20 @@ const workflow = {
3068
3203
  * Cycles in the dependency graph error before any write happens.
3069
3204
  */
3070
3205
  deployDefinitions: async (args) => {
3071
- const { client, tags, workflowResource, definitions } = args;
3072
- validateTags(tags);
3206
+ const { client, tag, workflowResource, definitions } = args;
3207
+ validateTag(tag);
3073
3208
  for (const def of definitions)
3074
3209
  validateDefinition(def);
3075
- const ordered = await sortByDependencies(client, definitions, tags, workflowResource), results = [], prefix = canonicalTag(tags), tx = client.transaction();
3210
+ const ordered = await sortByDependencies(client, definitions, tag, workflowResource), results = [], tx = client.transaction();
3076
3211
  let hasWrites = !1;
3077
3212
  for (const def of ordered) {
3078
- const id = definitionDocId(workflowResource, prefix, def.name, def.version), expected = {
3213
+ const id = definitionDocId(workflowResource, tag, def.name, def.version), expected = {
3079
3214
  ...def,
3080
3215
  _id: id,
3081
3216
  _type: schema.WORKFLOW_DEFINITION_TYPE,
3082
- tags
3217
+ tag
3083
3218
  }, existing = await client.getDocument(id);
3084
- if (existing && intersectsTags(existing.tags, tags) && isDefinitionUnchanged(existing, expected)) {
3219
+ if (existing && existing.tag === tag && isDefinitionUnchanged(existing, expected)) {
3085
3220
  results.push({ definition: def.name, version: def.version, status: "unchanged" });
3086
3221
  continue;
3087
3222
  }
@@ -3119,7 +3254,7 @@ const workflow = {
3119
3254
  startInstance: async (args) => {
3120
3255
  const {
3121
3256
  client,
3122
- tags,
3257
+ tag,
3123
3258
  workflowResource,
3124
3259
  definition: definitionName,
3125
3260
  version,
@@ -3128,7 +3263,7 @@ const workflow = {
3128
3263
  effectsContext,
3129
3264
  instanceId,
3130
3265
  perspective
3131
- } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, definition = await loadDefinition(client, definitionName, version, tags), id = instanceId ?? `${canonicalTag(tags)}.wf-instance.${randomKey()}`;
3266
+ } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, definition = await loadDefinition(client, definitionName, version, tag), id = instanceId ?? `${tag}.wf-instance.${randomKey()}`;
3132
3267
  if (definition.stages.find((s) => s.name === definition.initialStage) === void 0)
3133
3268
  throw new Error(
3134
3269
  `Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
@@ -3140,7 +3275,7 @@ const workflow = {
3140
3275
  client,
3141
3276
  now,
3142
3277
  selfId: id,
3143
- tags,
3278
+ tag,
3144
3279
  workflowResource,
3145
3280
  ...perspective !== void 0 ? { perspective } : {}
3146
3281
  },
@@ -3148,7 +3283,7 @@ const workflow = {
3148
3283
  }), effectivePerspective = perspective ?? derivePerspectiveFromState(resolvedState), base = buildInstanceBase({
3149
3284
  id,
3150
3285
  now,
3151
- tags,
3286
+ tag,
3152
3287
  workflowResource,
3153
3288
  definitionName: definition.name,
3154
3289
  pinnedVersion: definition.version,
@@ -3160,7 +3295,7 @@ const workflow = {
3160
3295
  initialStage: definition.initialStage,
3161
3296
  actor
3162
3297
  });
3163
- return await client.create(base), await primeInitialStage(client, id, actor, clientForGdr, clock), await cascade(client, id, actor, clientForGdr, clock), reload(client, id, tags);
3298
+ 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);
3164
3299
  },
3165
3300
  /**
3166
3301
  * Fire an action against a task. If the task is pending, it is
@@ -3175,19 +3310,19 @@ const workflow = {
3175
3310
  fireAction: async (args) => {
3176
3311
  const {
3177
3312
  client,
3178
- tags,
3313
+ tag,
3179
3314
  instanceId,
3180
3315
  task,
3181
3316
  action,
3182
3317
  params,
3183
3318
  idempotent,
3184
3319
  resourceClients
3185
- } = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tags);
3320
+ } = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
3186
3321
  if (findOpenStageEntry(before)?.tasks.find((t) => t.name === task) === void 0 && idempotent === !0)
3187
3322
  return { instance: before, cascaded: 0, fired: !1 };
3188
3323
  const evaluation = await evaluateInstance({
3189
3324
  client,
3190
- tags,
3325
+ tag,
3191
3326
  instanceId,
3192
3327
  access,
3193
3328
  clock,
@@ -3206,7 +3341,7 @@ const workflow = {
3206
3341
  ...params !== void 0 ? { params } : {}
3207
3342
  }), cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
3208
3343
  return {
3209
- instance: await reload(client, instanceId, tags),
3344
+ instance: await reload(client, instanceId, tag),
3210
3345
  cascaded,
3211
3346
  fired: !0,
3212
3347
  ...ranOps !== void 0 ? { ranOps } : {}
@@ -3220,7 +3355,7 @@ const workflow = {
3220
3355
  * reference them. Cascades after.
3221
3356
  */
3222
3357
  completeEffect: async (args) => {
3223
- const { client, tags, instanceId, effectKey, status, outputs, detail, error, durationMs } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
3358
+ const { client, tag, instanceId, effectKey, status, outputs, detail, error, durationMs } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
3224
3359
  await completeEffect({
3225
3360
  client,
3226
3361
  instanceId,
@@ -3234,7 +3369,7 @@ const workflow = {
3234
3369
  });
3235
3370
  const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
3236
3371
  return {
3237
- instance: await reload(client, instanceId, tags),
3372
+ instance: await reload(client, instanceId, tag),
3238
3373
  cascaded,
3239
3374
  fired: !0
3240
3375
  };
@@ -3249,11 +3384,11 @@ const workflow = {
3249
3384
  * affected instances and the engine re-evaluates.
3250
3385
  */
3251
3386
  tick: async (args) => {
3252
- 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);
3387
+ 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);
3253
3388
  await assertInstanceWriteAllowed({ instance: current, guards, identity: actor.id });
3254
3389
  const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
3255
3390
  return {
3256
- instance: await reload(client, instanceId, tags),
3391
+ instance: await reload(client, instanceId, tag),
3257
3392
  cascaded,
3258
3393
  fired: cascaded > 0
3259
3394
  };
@@ -3264,8 +3399,8 @@ const workflow = {
3264
3399
  * upstream; this verb performs the mechanical move.
3265
3400
  */
3266
3401
  setStage: async (args) => {
3267
- const { client, tags, instanceId, targetStage, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
3268
- await reload(client, instanceId, tags);
3402
+ const { client, tag, instanceId, targetStage, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
3403
+ await reload(client, instanceId, tag);
3269
3404
  const result = await setStage({
3270
3405
  client,
3271
3406
  instanceId,
@@ -3274,7 +3409,7 @@ const workflow = {
3274
3409
  options: { ...actor !== void 0 ? { actor } : {}, clock, clientForGdr }
3275
3410
  }), cascaded = result.fired ? await cascade(client, instanceId, actor, clientForGdr, clock) : 0;
3276
3411
  return {
3277
- instance: await reload(client, instanceId, tags),
3412
+ instance: await reload(client, instanceId, tag),
3278
3413
  cascaded,
3279
3414
  fired: result.fired
3280
3415
  };
@@ -3286,28 +3421,28 @@ const workflow = {
3286
3421
  * contract (propagated, not cascaded — the instance is terminal).
3287
3422
  */
3288
3423
  abortInstance: async (args) => {
3289
- const { client, tags, instanceId, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
3290
- await reload(client, instanceId, tags);
3424
+ const { client, tag, instanceId, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
3425
+ await reload(client, instanceId, tag);
3291
3426
  const fired = await abortAndPropagate({ client, instanceId, reason, actor, clientForGdr, clock });
3292
3427
  return {
3293
- instance: await reload(client, instanceId, tags),
3428
+ instance: await reload(client, instanceId, tag),
3294
3429
  cascaded: 0,
3295
3430
  fired
3296
3431
  };
3297
3432
  },
3298
3433
  /**
3299
- * Fetch a workflow instance by id, scoped to the engine's tags.
3434
+ * Fetch a workflow instance by id, scoped to the engine's tag.
3300
3435
  * Throws when the instance doesn't exist or isn't visible to this
3301
3436
  * engine.
3302
3437
  */
3303
3438
  getInstance: async (args) => {
3304
- const { client, tags, instanceId } = args;
3305
- return validateTags(tags), reload(client, instanceId, tags);
3439
+ const { client, tag, instanceId } = args;
3440
+ return validateTag(tag), reload(client, instanceId, tag);
3306
3441
  },
3307
3442
  guardsForInstance: async (args) => {
3308
- const { client, tags, instanceId, resourceClients } = args;
3309
- validateTags(tags);
3310
- const instance = await reload(client, instanceId, tags);
3443
+ const { client, tag, instanceId, resourceClients } = args;
3444
+ validateTag(tag);
3445
+ const instance = await reload(client, instanceId, tag);
3311
3446
  return guardsForInstance({
3312
3447
  client,
3313
3448
  clientForGdr: buildClientForGdr(client, resourceClients),
@@ -3315,9 +3450,9 @@ const workflow = {
3315
3450
  });
3316
3451
  },
3317
3452
  guardsForDefinition: async (args) => {
3318
- const { client, tags, workflowResource, definition, resourceClients } = args;
3319
- validateTags(tags);
3320
- const definitions = await loadDefinitionVersions(client, definition, tags);
3453
+ const { client, tag, workflowResource, definition, resourceClients } = args;
3454
+ validateTag(tag);
3455
+ const definitions = await loadDefinitionVersions(client, definition, tag);
3321
3456
  if (definitions.length === 0)
3322
3457
  throw new Error(`No deployed definition for workflow ${definition}`);
3323
3458
  return guardsForDefinition({
@@ -3329,22 +3464,21 @@ const workflow = {
3329
3464
  });
3330
3465
  },
3331
3466
  /**
3332
- * Run a caller-supplied GROQ query with the engine's tags bound as
3333
- * `$engineTags`. This does NOT rewrite the query — arbitrary GROQ
3467
+ * Run a caller-supplied GROQ query with the engine's tag bound as
3468
+ * `$tag`. This does NOT rewrite the query — arbitrary GROQ
3334
3469
  * can't be safely tag-scoped after the fact — so the CALLER MUST
3335
- * filter on `$engineTags` (e.g.
3336
- * `count(tags[@ in $engineTags]) > 0`). To guard against accidental
3337
- * cross-tenant reads, a query that never references `$engineTags` is
3470
+ * filter on `$tag` (e.g. `tag == $tag`). To guard against accidental
3471
+ * cross-partition reads, a query that never references `$tag` is
3338
3472
  * rejected before it reaches the lake. Caller is responsible for type
3339
3473
  * narrowing the result.
3340
3474
  */
3341
3475
  query: async (args) => {
3342
- const { client, tags, groq, params } = args;
3343
- if (validateTags(tags), !groq.includes("$engineTags"))
3476
+ const { client, tag, groq, params } = args;
3477
+ if (validateTag(tag), !/\$tag\b/.test(groq))
3344
3478
  throw new Error(
3345
- `workflow.query: query must filter on $engineTags to stay tag-scoped (e.g. "count(tags[@ in $engineTags]) > 0"); got: ${groq}`
3479
+ `workflow.query: query must filter on $tag to stay tag-scoped (e.g. "tag == $tag"); got: ${groq}`
3346
3480
  );
3347
- return client.fetch(groq, { ...params, engineTags: tags });
3481
+ return client.fetch(groq, { ...params, tag });
3348
3482
  },
3349
3483
  /**
3350
3484
  * Snapshot-aware GROQ — runs against the same in-memory view that
@@ -3363,9 +3497,9 @@ const workflow = {
3363
3497
  * without re-implementing hydration. Pure read — never writes.
3364
3498
  */
3365
3499
  queryInScope: async (args) => {
3366
- const { client, tags, instanceId, groq, params, resourceClients } = args, clock = args.clock ?? wallClock;
3367
- validateTags(tags);
3368
- const instance = await reload(client, instanceId, tags), clientForGdr = buildClientForGdr(client, resourceClients), snapshot = await hydrateSnapshot({ client, clientForGdr, instance }), reserved = buildParams({ instance, now: clock(), snapshot }), tree = groqJs.parse(groq, { params: { ...reserved, ...params } });
3500
+ const { client, tag, instanceId, groq, params, resourceClients } = args, clock = args.clock ?? wallClock;
3501
+ validateTag(tag);
3502
+ const instance = await reload(client, instanceId, tag), clientForGdr = buildClientForGdr(client, resourceClients), snapshot = await hydrateSnapshot({ client, clientForGdr, instance }), reserved = buildParams({ instance, now: clock(), snapshot }), tree = groqJs.parse(groq, { params: { ...reserved, ...params } });
3369
3503
  return await (await groqJs.evaluate(tree, {
3370
3504
  dataset: snapshot.docs,
3371
3505
  params: { ...reserved, ...params }
@@ -3375,13 +3509,13 @@ const workflow = {
3375
3509
  * List every pending effect on the instance. Returns the same entries
3376
3510
  * the runtime would see — claimed and unclaimed alike.
3377
3511
  */
3378
- listPendingEffects: async (args) => (await reload(args.client, args.instanceId, args.tags)).pendingEffects,
3512
+ listPendingEffects: async (args) => (await reload(args.client, args.instanceId, args.tag)).pendingEffects,
3379
3513
  /**
3380
3514
  * Filter pending effects on the instance by criteria. `claimed`
3381
3515
  * filters on claim presence; `names` restricts to specific effect
3382
3516
  * names. Both filters compose (AND).
3383
3517
  */
3384
- 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))),
3518
+ 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))),
3385
3519
  /**
3386
3520
  * Project the instance from a given actor's perspective. Returns a
3387
3521
  * `WorkflowEvaluation` with per-action verdicts (`allowed` + a
@@ -3401,8 +3535,8 @@ const workflow = {
3401
3535
  * Pure read.
3402
3536
  */
3403
3537
  diagnose: async (args) => {
3404
- const evaluation = await evaluateInstance(args);
3405
- return { evaluation, diagnosis: diagnoseInstance(diagnoseInputFromEvaluation(evaluation)) };
3538
+ const evaluation = await evaluateInstance(args), diagnosis = diagnoseInstance(diagnoseInputFromEvaluation(evaluation));
3539
+ return { evaluation, diagnosis, remediations: remediationsFor(diagnosis) };
3406
3540
  },
3407
3541
  /**
3408
3542
  * List the actions an actor could fire on an instance's current stage,
@@ -3422,18 +3556,18 @@ const workflow = {
3422
3556
  * record. (The per-task `spawnedInstances` field on the active stage
3423
3557
  * only exists while that stage is current; history survives.) Strips
3424
3558
  * the GDR URI on each `instanceRef` to a bare `_id`, fetches the
3425
- * instances, drops any that aren't visible to this engine's tags, and
3559
+ * instances, drops any that aren't visible to this engine's tag, and
3426
3560
  * returns them sorted by `startedAt` ascending.
3427
3561
  *
3428
3562
  * Pass `task` to restrict to a single spawning task on the parent.
3429
3563
  */
3430
3564
  children: async (args) => {
3431
- const { client, tags, instanceId, task } = args;
3432
- validateTags(tags);
3433
- 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));
3565
+ const { client, tag, instanceId, task } = args;
3566
+ validateTag(tag);
3567
+ 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));
3434
3568
  return ids.length === 0 ? [] : client.fetch(
3435
3569
  `*[_id in $ids && ${tagScopeFilter()}] | order(startedAt asc)`,
3436
- { ids, engineTags: tags }
3570
+ { ids, tag }
3437
3571
  );
3438
3572
  },
3439
3573
  /**
@@ -3448,11 +3582,11 @@ const workflow = {
3448
3582
  }
3449
3583
  };
3450
3584
  async function verifyDeployedDefinitionsInternal(args) {
3451
- const { client, tags, effectHandlers, missingHandler, logger } = args;
3452
- validateTags(tags);
3585
+ const { client, tag, effectHandlers, missingHandler, logger } = args;
3586
+ validateTag(tag);
3453
3587
  const log = logger("verifyDeployedDefinitions"), definitions = await client.fetch(
3454
3588
  `*[_type == "${schema.WORKFLOW_DEFINITION_TYPE}" && ${tagScopeFilter()}] | order(name asc, version asc)`,
3455
- { engineTags: tags }
3589
+ { tag }
3456
3590
  ), seen = [], missingByName = /* @__PURE__ */ new Map();
3457
3591
  for (const def of definitions)
3458
3592
  seen.push({ name: def.name, version: def.version, _id: def._id }), walkEffectNames(def, (name, location) => {
@@ -3501,7 +3635,7 @@ function walkEffectNames(def, visit) {
3501
3635
  async function drainEffectsInternal(args) {
3502
3636
  const {
3503
3637
  client,
3504
- tags,
3638
+ tag,
3505
3639
  workflowResource,
3506
3640
  resourceClients,
3507
3641
  instanceId,
@@ -3512,10 +3646,10 @@ async function drainEffectsInternal(args) {
3512
3646
  actor: drainerActor,
3513
3647
  ...args.access?.grants !== void 0 ? { grants: args.access.grants } : {}
3514
3648
  };
3515
- validateTags(tags);
3649
+ validateTag(tag);
3516
3650
  const log = logger("drainEffects"), drained = [], failed = [], skipped = [], skippedKeys = /* @__PURE__ */ new Set();
3517
3651
  for (; ; ) {
3518
- const before = await reload(client, instanceId, tags), candidate = before.pendingEffects.find(
3652
+ const before = await reload(client, instanceId, tag), candidate = before.pendingEffects.find(
3519
3653
  (e) => e.claim === void 0 && !skippedKeys.has(e._key)
3520
3654
  );
3521
3655
  if (candidate === void 0) break;
@@ -3532,7 +3666,7 @@ async function drainEffectsInternal(args) {
3532
3666
  });
3533
3667
  await reportEffectOutcome({
3534
3668
  client,
3535
- tags,
3669
+ tag,
3536
3670
  workflowResource,
3537
3671
  ...resourceClients !== void 0 ? { resourceClients } : {},
3538
3672
  instanceId,
@@ -3571,7 +3705,7 @@ async function claimPendingEffect(client, instance, effectKey, drainerActor) {
3571
3705
  claimedBy: drainerActor
3572
3706
  };
3573
3707
  try {
3574
- return await client.patch(instance._id).ifRevisionId(instance._rev).set({ [`pendingEffects[_key=="${effectKey}"].claim`]: claim }).commit(), !0;
3708
+ return await client.patch(instance._id).ifRevisionId(instance._rev).set({ [`pendingEffects[_key=="${effectKey}"].claim`]: claim }).commit(SYNC_COMMIT), !0;
3575
3709
  } catch (error) {
3576
3710
  if (!isRevisionConflict(error)) throw error;
3577
3711
  return !1;
@@ -3602,7 +3736,7 @@ async function applyMissingHandler(policy, info, log) {
3602
3736
  }
3603
3737
  }
3604
3738
  function createInstanceSession(args) {
3605
- const { client, tags, clock } = args, clientForGdr = buildClientForGdr(client, args.resourceClients);
3739
+ const { client, tag, clock } = args, clientForGdr = buildClientForGdr(client, args.resourceClients);
3606
3740
  let instance = asHeldInstance(args.instance), overlay = /* @__PURE__ */ new Map(), heldGuards = [], committing = !1, buffered;
3607
3741
  const selfUri = () => gdrFromResource(instance.workflowResource, instance._id), applyUpdate = (docs) => {
3608
3742
  const next = /* @__PURE__ */ new Map();
@@ -3663,7 +3797,7 @@ function createInstanceSession(args) {
3663
3797
  return commit(async (actor, held) => {
3664
3798
  await assertInstanceWriteAllowed({ instance, guards: heldGuards, identity: actor.id });
3665
3799
  const cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
3666
- return instance = await reload(client, instance._id, tags), { instance, cascaded, fired: cascaded > 0 };
3800
+ return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: cascaded > 0 };
3667
3801
  });
3668
3802
  },
3669
3803
  fireAction({ task, action, params }) {
@@ -3680,7 +3814,7 @@ function createInstanceSession(args) {
3680
3814
  ...clock !== void 0 ? { clock } : {},
3681
3815
  ...params !== void 0 ? { params } : {}
3682
3816
  }), cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
3683
- return instance = await reload(client, instance._id, tags), { instance, cascaded, fired: !0, ...ranOps !== void 0 ? { ranOps } : {} };
3817
+ return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: !0, ...ranOps !== void 0 ? { ranOps } : {} };
3684
3818
  });
3685
3819
  }
3686
3820
  };
@@ -3708,11 +3842,11 @@ const defaultLoggerFactory = (name) => ({
3708
3842
  }
3709
3843
  };
3710
3844
  function createEngine(args) {
3711
- const { client, workflowResource, tags, resourceClients, clock } = args;
3712
- validateTags(tags);
3845
+ const { client, workflowResource, resourceClients, clock, tag } = args;
3846
+ validateTag(tag);
3713
3847
  const effectHandlers = args.effectHandlers ?? {}, missingHandler = args.missingHandler ?? "fail", logger = args.loggerFactory ?? defaultLoggerFactory, bind = (rest) => ({
3714
3848
  client,
3715
- tags,
3849
+ tag,
3716
3850
  workflowResource,
3717
3851
  ...resourceClients !== void 0 ? { resourceClients } : {},
3718
3852
  ...clock !== void 0 ? { clock } : {},
@@ -3720,7 +3854,7 @@ function createEngine(args) {
3720
3854
  });
3721
3855
  return {
3722
3856
  client,
3723
- tags,
3857
+ tag,
3724
3858
  workflowResource,
3725
3859
  effectHandlers,
3726
3860
  missingHandler,
@@ -3736,11 +3870,11 @@ function createEngine(args) {
3736
3870
  setStage: (rest) => workflow.setStage(bind(rest)),
3737
3871
  abortInstance: (rest) => workflow.abortInstance(bind(rest)),
3738
3872
  deleteDefinition: (rest) => workflow.deleteDefinition(bind(rest)),
3739
- getInstance: ({ instanceId }) => workflow.getInstance({ client, tags, workflowResource, instanceId }),
3740
- subscriptionDocumentsForInstance: ({ instanceId }) => workflow.getInstance({ client, tags, workflowResource, instanceId }).then(subscriptionDocumentsForInstance),
3873
+ getInstance: ({ instanceId }) => workflow.getInstance({ client, tag, workflowResource, instanceId }),
3874
+ subscriptionDocumentsForInstance: ({ instanceId }) => workflow.getInstance({ client, tag, workflowResource, instanceId }).then(subscriptionDocumentsForInstance),
3741
3875
  instance: (instanceDoc, opts) => createInstanceSession({
3742
3876
  client,
3743
- tags,
3877
+ tag,
3744
3878
  instance: instanceDoc,
3745
3879
  ...resourceClients !== void 0 ? { resourceClients } : {},
3746
3880
  ...clock !== void 0 ? { clock } : {},
@@ -3749,45 +3883,45 @@ function createEngine(args) {
3749
3883
  }),
3750
3884
  guardsForInstance: ({ instanceId }) => workflow.guardsForInstance({
3751
3885
  client,
3752
- tags,
3886
+ tag,
3753
3887
  workflowResource,
3754
3888
  instanceId,
3755
3889
  ...resourceClients !== void 0 ? { resourceClients } : {}
3756
3890
  }),
3757
3891
  guardsForDefinition: ({ definition }) => workflow.guardsForDefinition({
3758
3892
  client,
3759
- tags,
3893
+ tag,
3760
3894
  workflowResource,
3761
3895
  definition,
3762
3896
  ...resourceClients !== void 0 ? { resourceClients } : {}
3763
3897
  }),
3764
3898
  children: ({ instanceId, task }) => workflow.children({
3765
3899
  client,
3766
- tags,
3900
+ tag,
3767
3901
  workflowResource,
3768
3902
  instanceId,
3769
3903
  ...task !== void 0 ? { task } : {}
3770
3904
  }),
3771
3905
  query: ({ groq, params }) => workflow.query({
3772
3906
  client,
3773
- tags,
3907
+ tag,
3774
3908
  workflowResource,
3775
3909
  groq,
3776
3910
  ...params !== void 0 ? { params } : {}
3777
3911
  }),
3778
3912
  queryInScope: ({ instanceId, groq, params }) => workflow.queryInScope({
3779
3913
  client,
3780
- tags,
3914
+ tag,
3781
3915
  workflowResource,
3782
3916
  instanceId,
3783
3917
  groq,
3784
3918
  ...params !== void 0 ? { params } : {},
3785
3919
  ...clock !== void 0 ? { clock } : {}
3786
3920
  }),
3787
- listPendingEffects: ({ instanceId }) => workflow.listPendingEffects({ client, tags, workflowResource, instanceId }),
3921
+ listPendingEffects: ({ instanceId }) => workflow.listPendingEffects({ client, tag, workflowResource, instanceId }),
3788
3922
  findPendingEffects: ({ instanceId, claimed, names }) => workflow.findPendingEffects({
3789
3923
  client,
3790
- tags,
3924
+ tag,
3791
3925
  workflowResource,
3792
3926
  instanceId,
3793
3927
  ...claimed !== void 0 ? { claimed } : {},
@@ -3795,7 +3929,7 @@ function createEngine(args) {
3795
3929
  }),
3796
3930
  drainEffects: ({ instanceId, access }) => drainEffectsInternal({
3797
3931
  client,
3798
- tags,
3932
+ tag,
3799
3933
  workflowResource,
3800
3934
  ...resourceClients !== void 0 ? { resourceClients } : {},
3801
3935
  instanceId,
@@ -3806,7 +3940,7 @@ function createEngine(args) {
3806
3940
  }),
3807
3941
  verifyDeployedDefinitions: () => verifyDeployedDefinitionsInternal({
3808
3942
  client,
3809
- tags,
3943
+ tag,
3810
3944
  effectHandlers,
3811
3945
  missingHandler,
3812
3946
  logger
@@ -3814,7 +3948,7 @@ function createEngine(args) {
3814
3948
  };
3815
3949
  }
3816
3950
  function docIdFor(def, target) {
3817
- return definitionDocId(target.workflowResource, canonicalTag(target.tags), def.name, def.version);
3951
+ return definitionDocId(target.workflowResource, target.tag, def.name, def.version);
3818
3952
  }
3819
3953
  function diffStatus(existing, expected) {
3820
3954
  return existing === void 0 ? "create" : isDefinitionUnchanged(existing, expected) ? "unchanged" : "update";
@@ -3824,7 +3958,7 @@ function diffEntry(def, existingRaw, target) {
3824
3958
  ...def,
3825
3959
  _id: docId,
3826
3960
  _type: schema.WORKFLOW_DEFINITION_TYPE,
3827
- tags: target.tags
3961
+ tag: target.tag
3828
3962
  }, status = diffStatus(existingRaw, expected), existing = existingRaw ? stripSystemFields(existingRaw) : void 0;
3829
3963
  return {
3830
3964
  name: def.name,
@@ -4024,6 +4158,7 @@ exports.ActionDisabledError = ActionDisabledError;
4024
4158
  exports.ActionParamsInvalidError = ActionParamsInvalidError;
4025
4159
  exports.CascadeLimitError = CascadeLimitError;
4026
4160
  exports.ConcurrentFireActionError = ConcurrentFireActionError;
4161
+ exports.DEFAULT_CONTENT_PERSPECTIVE = DEFAULT_CONTENT_PERSPECTIVE;
4027
4162
  exports.DISPLAY = DISPLAY;
4028
4163
  exports.EFFECTS_CONTEXT_DISPLAY = EFFECTS_CONTEXT_DISPLAY;
4029
4164
  exports.GUARD_DOC_TYPE = GUARD_DOC_TYPE;
@@ -4037,10 +4172,8 @@ exports.WORKFLOW_INSTANCE_TYPE = WORKFLOW_INSTANCE_TYPE;
4037
4172
  exports.WorkflowStateDivergedError = WorkflowStateDivergedError;
4038
4173
  exports.abortReason = abortReason;
4039
4174
  exports.actionVerdict = actionVerdict;
4040
- exports.assigneesOf = assigneesOf;
4041
4175
  exports.availableActions = availableActions;
4042
4176
  exports.buildSnapshot = buildSnapshot;
4043
- exports.canonicalTag = canonicalTag;
4044
4177
  exports.compileGuard = compileGuard;
4045
4178
  exports.computeDiffEntries = computeDiffEntries;
4046
4179
  exports.contentReleaseName = contentReleaseName;
@@ -4070,13 +4203,13 @@ exports.isDefinitionUnchanged = isDefinitionUnchanged;
4070
4203
  exports.isGdr = isGdr;
4071
4204
  exports.isTerminalStage = isTerminalStage;
4072
4205
  exports.lakeGuardId = lakeGuardId;
4073
- exports.openStage = openStage;
4074
4206
  exports.parseGdr = parseGdr;
4075
4207
  exports.readsRaw = readsRaw;
4076
4208
  exports.refCanvas = refCanvas;
4077
4209
  exports.refDashboard = refDashboard;
4078
4210
  exports.refDataset = refDataset;
4079
4211
  exports.refMediaLibrary = refMediaLibrary;
4212
+ exports.remediationsFor = remediationsFor;
4080
4213
  exports.resolveAccess = resolveAccess;
4081
4214
  exports.resourceFromParsed = resourceFromParsed;
4082
4215
  exports.retractStageGuards = retractStageGuards;
@@ -4086,7 +4219,7 @@ exports.subscriptionDocument = subscriptionDocument;
4086
4219
  exports.subscriptionDocumentsForInstance = subscriptionDocumentsForInstance;
4087
4220
  exports.tagScopeFilter = tagScopeFilter;
4088
4221
  exports.validateDefinition = validateDefinition;
4089
- exports.validateTags = validateTags;
4222
+ exports.validateTag = validateTag;
4090
4223
  exports.verdictGuardsForInstance = verdictGuardsForInstance;
4091
4224
  exports.wallClock = wallClock;
4092
4225
  exports.workflow = workflow;