@sanity/workflow-engine 0.24.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,65 @@
1
1
  # @sanity/workflow-engine
2
2
 
3
+ ## 0.26.0
4
+
5
+ ## 0.25.0
6
+
7
+ ### Minor Changes
8
+
9
+ - 177d600: Deployed workflow environment tags are now discoverable.
10
+
11
+ The engine gains two cross-partition GROQ builders: `deployedTagsGroq()` lists
12
+ every tag holding a deployed definition in a resource, and
13
+ `definitionTagsGroq()` narrows that to one definition name via `$definition`.
14
+ Both deliberately span tag partitions — every other read is tag-scoped, and
15
+ `workflow.query` refuses GROQ that isn't — so they answer "which environments
16
+ exist here" for a caller holding a resource but no tag. They report what is
17
+ _observed_: a tag with nothing deployed does not appear.
18
+
19
+ The stdio MCP server adds a `list_workflow_tags` tool taking a
20
+ `workflow_resource` and no tag. It is registered by the host rather than exported
21
+ as a tool def, because a def is only ever handed an engine and an engine is
22
+ pinned to one tag. `LIST_WORKFLOW_TAGS_TOOL_NAME` and
23
+ `LIST_WORKFLOW_TAGS_DESCRIPTION` are exported so an embedding host registers the
24
+ same capability under the same name without re-authoring the model-facing
25
+ wording. The `tag` parameter's description now points an agent at that tool where
26
+ a server offers it and at the user otherwise, and requires confirmation either
27
+ way — having the list does not license picking from it.
28
+
29
+ Both registration paths share one outcome path, `withToolTelemetry`, so the
30
+ result envelope, error rendering, and the `Editorial Workflows MCP Tool Called`
31
+ event cannot diverge between a def-backed tool and a host-registered one. Tag
32
+ discovery reports its adoption event like every other tool, and because it names
33
+ a resource it can also initialize the stdio server's deferred telemetry shell —
34
+ which matters when discovery is an agent's first call.
35
+
36
+ The CLI's definition-tags probe runs the engine's builder instead of its own
37
+ copy of the query, so its `params` key is `definition` rather than `name`, and
38
+ the tag partitions it names in an ambiguity error arrive sorted from the lake.
39
+
40
+ ### Patch Changes
41
+
42
+ - fc12989: Fixes actor resolution failing with `principal id "…" is not an account-global
43
+ user id` for a session scoped to a single project, such as a SAML-SSO Studio
44
+ session.
45
+
46
+ `actor.id` is always the account-global user id, and the id's namespace decides
47
+ which read supplies it rather than the host that answered. Whichever `/users/me`
48
+ read returns an already-account-global id supplies it; a project-scoped id is
49
+ bridged to its account-global form first — by string surgery for an
50
+ `e-<globalId>` principal, otherwise through the project's own user directory
51
+ (`/projects/<id>/users/<principal>` → `sanityUserId`). A SAML-SSO session
52
+ resolves through that directory, because the global host holds no
53
+ account-global record for it.
54
+
55
+ A session whose id is already account-global issues no directory request. A
56
+ project-scoped id that no route can bridge is refused, naming both failed
57
+ routes.
58
+
59
+ The project-user directory admits only a row answering the id it asked for,
60
+ matching the batched member join: a row for another principal reports
61
+ inaccessible rather than resolving to that person's identity.
62
+
3
63
  ## 0.24.0
4
64
 
5
65
  ### Minor Changes
@@ -1854,6 +1854,18 @@ function classifyPrincipalId(id) {
1854
1854
  };
1855
1855
  }
1856
1856
 
1857
+ function directoryBridgeId(sanityUserId) {
1858
+ if (typeof sanityUserId == "string") return classifyPrincipalId(sanityUserId).namespace === "global" ? sanityUserId : void 0;
1859
+ }
1860
+
1861
+ function firstCarriedGlobalId(candidates) {
1862
+ for (const candidate of candidates) {
1863
+ if (candidate === void 0) continue;
1864
+ const {globalId: globalId} = classifyPrincipalId(candidate);
1865
+ if (globalId !== void 0) return globalId;
1866
+ }
1867
+ }
1868
+
1857
1869
  function lakePrincipalId(args) {
1858
1870
  return args.localPrincipalId ?? args.actor.id;
1859
1871
  }
@@ -4172,6 +4184,8 @@ exports.deriveExecutorClassification = deriveExecutorClassification;
4172
4184
 
4173
4185
  exports.desugarWorkflow = desugarWorkflow;
4174
4186
 
4187
+ exports.directoryBridgeId = directoryBridgeId;
4188
+
4175
4189
  exports.driverKind = driverKind;
4176
4190
 
4177
4191
  exports.errorMessage = errorMessage;
@@ -4188,6 +4202,8 @@ exports.fieldTreeShape = fieldTreeShape;
4188
4202
 
4189
4203
  exports.fieldValueSchemas = fieldValueSchemas;
4190
4204
 
4205
+ exports.firstCarriedGlobalId = firstCarriedGlobalId;
4206
+
4191
4207
  exports.formatIssuePath = formatIssuePath;
4192
4208
 
4193
4209
  exports.formatIssues = formatIssues;
@@ -1840,6 +1840,18 @@ function classifyPrincipalId(id) {
1840
1840
  };
1841
1841
  }
1842
1842
 
1843
+ function directoryBridgeId(sanityUserId) {
1844
+ if (typeof sanityUserId == "string") return classifyPrincipalId(sanityUserId).namespace === "global" ? sanityUserId : void 0;
1845
+ }
1846
+
1847
+ function firstCarriedGlobalId(candidates) {
1848
+ for (const candidate of candidates) {
1849
+ if (candidate === void 0) continue;
1850
+ const {globalId: globalId} = classifyPrincipalId(candidate);
1851
+ if (globalId !== void 0) return globalId;
1852
+ }
1853
+ }
1854
+
1843
1855
  function lakePrincipalId(args) {
1844
1856
  return args.localPrincipalId ?? args.actor.id;
1845
1857
  }
@@ -4008,4 +4020,4 @@ function checkWorkflowInvariants(def) {
4008
4020
  checkStoredRolesPlacement(def, issues), checkGroups(def, issues), issues;
4009
4021
  }
4010
4022
 
4011
- export { ACTION_SEMANTICS, ACTIVITY_KINDS, ACTIVITY_STATUSES, ACTOR_KINDS, ANONYMOUS_IDENTITY, ActorShape, AuthoringActionSchema, AuthoringActivitySchema, AuthoringFieldEntrySchema, AuthoringGuardSchema, AuthoringOpSchema, AuthoringStageSchema, AuthoringTransitionSchema, AuthoringWorkflowSchema, CALLER_BOUND_VARS, CONDITION_VARS, ContractViolationError, DATA_MODEL_CHANGES, DATA_MODEL_MIN_READER, DATA_MODEL_VERSION, DEFAULT_TRANSITION_WHEN, DOCUMENT_VALUE_PERMISSIONS, DRIVER_KINDS, DefinitionInUseError, DefinitionNotFoundError, EFFECTS_READ, EXECUTOR_CLASSIFICATIONS, EffectNotFoundError, EffectSchema, FIELD_READ, FIELD_SCOPES, FIELD_VALUE_KINDS, FILTER_SCOPE_VARS, FieldValueShapeError, GROUP_KINDS, GUARD_PREDICATE_VARS, GdrShape, GroupSchema, InstanceNotFoundError, IsoTimestamp, MUTATION_GUARD_ACTIONS, ModelVersionAheadError, NonEmptyString, PersistedDocShapeError, READER_MODEL_ROLLOUT_URL, RESERVED_CONDITION_VARS, RESOURCE_ALIAS_NAME_SOURCE, ReaderModelAcknowledgementError, START_FILTER_VARS, START_REQUIREMENT_VARS, SYSTEM_IDENTITY, SpawnContractsInvalidError, StoredFieldOpSchema, VersionSpecificDatasetGdrError, WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, WorkflowConfigSchema, WorkflowError, actorFulfillsRole, andConditions, assertReadableModel, assertReaderModelAcknowledgement, checkWorkflowInvariants, choiceValueIssues, classifyPrincipalId, clientConfigFromResource, conditionEffectReads, conditionFieldReadNames, conditionParameterNames, conditionSyntaxIssues, datasetResourceParts, definitionDocId, deriveActivityKind, deriveExecutorClassification, desugarWorkflow, driverKind, errorMessage, evaluateCondition, evaluateConditionOutcome, evaluatePredicates, extractDocumentId, fieldTreeShape, fieldValueSchemas, formatIssuePath, formatIssues, formatValidationError, gdrFromResource, gdrRef, gdrResourcePrefix, gdrUri, groq, groupMembershipNames, isBareSeedId, isCascadeFired, isGdr, isGdrUri, isGuardReadExpr, isInputSourced, isNotesEntry, isParseableInstant, isSingleDocRefEntry, isSingleDocRefKind, isStartableDefinition, isSubjectEntry, isTerminalActivityStatus, isTodoListEntry, isTodoListItem, isUnevaluable, isUnprimed, labelFor, lakePrincipalId, minReaderModelOf, modelStampFor, modelVersionOf, parentRef, parseDefinitionSnapshot, parseDefinitionSnapshotValue, parseFieldValue, parseGdr, parseOrThrow, parsePersistedDoc, parseResourceGdr, parseStoredDefinition, readsRootDocument, refCanvas, refDashboard, refDataset, refKindAcceptsTypes, refMediaLibrary, refTypeIssues, rejectedRefTypes, releaseDocId, releaseRef, requiredModelFeatures, requiredReaderModel, resourceAliasesToMap, resourceFromGdrUri, resourceFromParsed, resourceGdr, rethrowWithContext, runGroq, sameResource, scalarValidationIssues, schemaTreeShape, selfGdr, startKindOf, tagScopeFilter, terminalState, toBareId, toPhysicalGdr, tolerantEntries, tolerantObject, tryParseGdr, validateFieldAppendItem, validateFieldValue, validateResourceAliasName, validateTag };
4023
+ export { ACTION_SEMANTICS, ACTIVITY_KINDS, ACTIVITY_STATUSES, ACTOR_KINDS, ANONYMOUS_IDENTITY, ActorShape, AuthoringActionSchema, AuthoringActivitySchema, AuthoringFieldEntrySchema, AuthoringGuardSchema, AuthoringOpSchema, AuthoringStageSchema, AuthoringTransitionSchema, AuthoringWorkflowSchema, CALLER_BOUND_VARS, CONDITION_VARS, ContractViolationError, DATA_MODEL_CHANGES, DATA_MODEL_MIN_READER, DATA_MODEL_VERSION, DEFAULT_TRANSITION_WHEN, DOCUMENT_VALUE_PERMISSIONS, DRIVER_KINDS, DefinitionInUseError, DefinitionNotFoundError, EFFECTS_READ, EXECUTOR_CLASSIFICATIONS, EffectNotFoundError, EffectSchema, FIELD_READ, FIELD_SCOPES, FIELD_VALUE_KINDS, FILTER_SCOPE_VARS, FieldValueShapeError, GROUP_KINDS, GUARD_PREDICATE_VARS, GdrShape, GroupSchema, InstanceNotFoundError, IsoTimestamp, MUTATION_GUARD_ACTIONS, ModelVersionAheadError, NonEmptyString, PersistedDocShapeError, READER_MODEL_ROLLOUT_URL, RESERVED_CONDITION_VARS, RESOURCE_ALIAS_NAME_SOURCE, ReaderModelAcknowledgementError, START_FILTER_VARS, START_REQUIREMENT_VARS, SYSTEM_IDENTITY, SpawnContractsInvalidError, StoredFieldOpSchema, VersionSpecificDatasetGdrError, WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, WorkflowConfigSchema, WorkflowError, actorFulfillsRole, andConditions, assertReadableModel, assertReaderModelAcknowledgement, checkWorkflowInvariants, choiceValueIssues, classifyPrincipalId, clientConfigFromResource, conditionEffectReads, conditionFieldReadNames, conditionParameterNames, conditionSyntaxIssues, datasetResourceParts, definitionDocId, deriveActivityKind, deriveExecutorClassification, desugarWorkflow, directoryBridgeId, driverKind, errorMessage, evaluateCondition, evaluateConditionOutcome, evaluatePredicates, extractDocumentId, fieldTreeShape, fieldValueSchemas, firstCarriedGlobalId, formatIssuePath, formatIssues, formatValidationError, gdrFromResource, gdrRef, gdrResourcePrefix, gdrUri, groq, groupMembershipNames, isBareSeedId, isCascadeFired, isGdr, isGdrUri, isGuardReadExpr, isInputSourced, isNotesEntry, isParseableInstant, isSingleDocRefEntry, isSingleDocRefKind, isStartableDefinition, isSubjectEntry, isTerminalActivityStatus, isTodoListEntry, isTodoListItem, isUnevaluable, isUnprimed, labelFor, lakePrincipalId, minReaderModelOf, modelStampFor, modelVersionOf, parentRef, parseDefinitionSnapshot, parseDefinitionSnapshotValue, parseFieldValue, parseGdr, parseOrThrow, parsePersistedDoc, parseResourceGdr, parseStoredDefinition, readsRootDocument, refCanvas, refDashboard, refDataset, refKindAcceptsTypes, refMediaLibrary, refTypeIssues, rejectedRefTypes, releaseDocId, releaseRef, requiredModelFeatures, requiredReaderModel, resourceAliasesToMap, resourceFromGdrUri, resourceFromParsed, resourceGdr, rethrowWithContext, runGroq, sameResource, scalarValidationIssues, schemaTreeShape, selfGdr, startKindOf, tagScopeFilter, terminalState, toBareId, toPhysicalGdr, tolerantEntries, tolerantObject, tryParseGdr, validateFieldAppendItem, validateFieldValue, validateResourceAliasName, validateTag };
package/dist/index.cjs CHANGED
@@ -3316,6 +3316,14 @@ function latestDefinitionsGroq() {
3316
3316
  return `*[${scoped} && version == math::max(*[${scoped} && name == ^.name].version)] | order(name asc)`;
3317
3317
  }
3318
3318
 
3319
+ function deployedTagsGroq() {
3320
+ return `array::unique(*[_type == "${invariants.WORKFLOW_DEFINITION_TYPE}"].tag) | order(@ asc)`;
3321
+ }
3322
+
3323
+ function definitionTagsGroq() {
3324
+ return `array::unique(*[_type == "${invariants.WORKFLOW_DEFINITION_TYPE}" && name == $definition].tag) | order(@ asc)`;
3325
+ }
3326
+
3319
3327
  function latestDeployedDefinitions(rows) {
3320
3328
  const byName = /* @__PURE__ */ new Map;
3321
3329
  for (const row of rows) {
@@ -4805,8 +4813,11 @@ function cacheFor(client) {
4805
4813
 
4806
4814
  function ingestDirectoryRows(args) {
4807
4815
  const records = Array.isArray(args.response) ? args.response : [ args.response ];
4808
- for (const row of records) typeof row?.id != "string" || typeof row.sanityUserId != "string" || args.requested.has(row.id) && invariants.classifyPrincipalId(row.sanityUserId).namespace === "global" && (args.cache.set(row.id, row.sanityUserId),
4809
- args.out.set(row.id, row.sanityUserId));
4816
+ for (const row of records) {
4817
+ if (typeof row?.id != "string" || !args.requested.has(row.id)) continue;
4818
+ const globalId = invariants.directoryBridgeId(row.sanityUserId);
4819
+ globalId !== void 0 && (args.cache.set(row.id, globalId), args.out.set(row.id, globalId));
4820
+ }
4810
4821
  }
4811
4822
 
4812
4823
  async function getInstanceDocument(client, instanceId) {
@@ -5452,10 +5463,14 @@ async function resolveGlobalProjectUser(args) {
5452
5463
  async function ingestMemberJoin(args) {
5453
5464
  const {request: request, projectId: projectId, cache: cache} = args, memberIds = await projectMemberIds(request, projectId);
5454
5465
  if (memberIds.length === 0) return;
5455
- const response = await request({
5466
+ const requested = new Set(memberIds), response = await request({
5456
5467
  uri: `/projects/${encodeURIComponent(projectId)}/users/${memberIds.map(encodeURIComponent).join(",")}`
5457
5468
  });
5458
- for (const row of Array.isArray(response) ? response : [ response ]) isClientProjectUser(row) && typeof row.sanityUserId == "string" && cache.set(`${projectId}:${row.sanityUserId}`, row);
5469
+ for (const row of Array.isArray(response) ? response : [ response ]) {
5470
+ if (!isClientProjectUser(row) || !requested.has(row.id)) continue;
5471
+ const globalId = invariants.directoryBridgeId(row.sanityUserId);
5472
+ globalId !== void 0 && cache.set(`${projectId}:${globalId}`, row);
5473
+ }
5459
5474
  }
5460
5475
 
5461
5476
  function clientProjectUserDirectory(client, projectId) {
@@ -5477,7 +5492,10 @@ function clientProjectUserDirectory(client, projectId) {
5477
5492
  }), candidate = Array.isArray(response) ? response[0] : response;
5478
5493
  return candidate == null ? {
5479
5494
  status: "missing"
5480
- } : isClientProjectUser(candidate) ? {
5495
+ } : isClientProjectUser(candidate) ? candidate.id !== id ? {
5496
+ status: "inaccessible",
5497
+ cause: new Error(`Project-user response answered id "${candidate.id}", not the requested "${id}"`)
5498
+ } : {
5481
5499
  status: "resolved",
5482
5500
  user: candidate
5483
5501
  } : {
@@ -8439,14 +8457,20 @@ function cachedGrants({client: client, requestFn: requestFn, resourcePath: resou
8439
8457
  }
8440
8458
 
8441
8459
  async function fetchActor(client, requestFn) {
8442
- const [resourceUser, globalUser] = await Promise.all([ fetchCurrentUser(requestFn, "the workflow resource host"), fetchGlobalUser(client) ]), resourceId = usableId(resourceUser), globalId = usableId(globalUser.user), id = globalId ?? resourceId;
8443
- if (id === void 0) return;
8444
- refuseProjectPrincipalWithoutGlobal({
8445
- resourceId: resourceId,
8446
- globalId: globalId,
8447
- globalUser: globalUser
8448
- });
8449
- const roleNames = (resourceUser?.roles?.length ? resourceUser : globalUser.user)?.roles?.map(r => r.name).filter(n => !!n) ?? [];
8460
+ const [resourceUser, globalUser] = await Promise.all([ fetchCurrentUser(requestFn, "the workflow resource host"), fetchGlobalUser(client) ]), resourceId = usableId(resourceUser), globalHostId = usableId(globalUser.user), carriedId = invariants.firstCarriedGlobalId([ globalHostId, resourceId ]), sessionId = carriedId ?? globalHostId ?? resourceId;
8461
+ if (sessionId === void 0) return;
8462
+ const bridge = carriedId === void 0 ? await bridgeProjectPrincipal({
8463
+ client: client,
8464
+ sessionId: sessionId,
8465
+ resourceId: resourceId
8466
+ }) : NO_BRIDGE, id = bridge.status === "resolved" ? bridge.globalId : sessionId;
8467
+ refuseProjectScopedActor({
8468
+ id: id,
8469
+ globalUser: globalUser,
8470
+ globalHostId: globalHostId,
8471
+ bridgeFailure: bridge.status === "unavailable" ? bridge : void 0
8472
+ });
8473
+ const roleNames = roleNamesFor(resourceUser, globalUser);
8450
8474
  return {
8451
8475
  actor: {
8452
8476
  kind: "person",
@@ -8461,13 +8485,62 @@ async function fetchActor(client, requestFn) {
8461
8485
  };
8462
8486
  }
8463
8487
 
8464
- function refuseProjectPrincipalWithoutGlobal(args) {
8465
- const {resourceId: resourceId, globalId: globalId, globalUser: globalUser} = args;
8466
- if (globalId !== void 0 || resourceId === void 0 || invariants.classifyPrincipalId(resourceId).namespace !== "project") return;
8467
- const reason = "reason" in globalUser ? globalUser.reason : "no record";
8468
- throw new Error(`workflow: the caller's identity on the workflow resource host is project-scoped ("${resourceId}") and the account-global record could not be resolved (${reason}). The engine speaks account-global user ids only and never acts as a project-scoped principal.`, "cause" in globalUser ? {
8469
- cause: globalUser.cause
8470
- } : void 0);
8488
+ function roleNamesFor(resourceUser, globalUser) {
8489
+ return (resourceUser?.roles?.length ? resourceUser : globalUser.user)?.roles?.map(r => r.name).filter(n => !!n) ?? [];
8490
+ }
8491
+
8492
+ const NO_BRIDGE = {
8493
+ status: "not-applicable"
8494
+ };
8495
+
8496
+ function globalRouteReason(globalUser, globalHostId) {
8497
+ return "reason" in globalUser ? globalUser.reason : globalHostId === void 0 ? "no record" : `the global host answered with project-scoped principal "${globalHostId}"`;
8498
+ }
8499
+
8500
+ async function bridgeProjectPrincipal(args) {
8501
+ const {client: client, sessionId: sessionId, resourceId: resourceId} = args;
8502
+ return invariants.classifyPrincipalId(sessionId).namespace !== "project" ? NO_BRIDGE : resourceId === void 0 || invariants.classifyPrincipalId(resourceId).namespace !== "project" ? {
8503
+ status: "unavailable",
8504
+ reason: "no project-scoped principal from the resource host to address the directory with"
8505
+ } : directoryGlobalId({
8506
+ client: client,
8507
+ principalId: resourceId
8508
+ });
8509
+ }
8510
+
8511
+ async function directoryGlobalId(args) {
8512
+ const {client: client, principalId: principalId} = args, projectId = typeof client.config == "function" ? client.config().projectId : void 0;
8513
+ if (projectId === void 0) return {
8514
+ status: "unavailable",
8515
+ reason: "the client reports no projectId, so its user directory has no address"
8516
+ };
8517
+ const lookup = await clientProjectUserDirectory(withRequestTag(client, REQUEST_TAG.accessResolveActor), projectId).findById(principalId);
8518
+ if (lookup.status !== "resolved") return {
8519
+ status: "unavailable",
8520
+ reason: `project "${projectId}"'s user directory reported the principal ${lookup.status}`,
8521
+ ...lookup.status === "inaccessible" && lookup.cause !== void 0 ? {
8522
+ cause: lookup.cause
8523
+ } : {}
8524
+ };
8525
+ const globalId = invariants.directoryBridgeId(lookup.user.sanityUserId);
8526
+ return globalId === void 0 ? {
8527
+ status: "unavailable",
8528
+ reason: `project "${projectId}"'s user directory row for the principal carries no account-global sanityUserId`
8529
+ } : {
8530
+ status: "resolved",
8531
+ globalId: globalId
8532
+ };
8533
+ }
8534
+
8535
+ function refuseProjectScopedActor(args) {
8536
+ const {id: id, globalUser: globalUser, globalHostId: globalHostId, bridgeFailure: bridgeFailure} = args;
8537
+ if (invariants.classifyPrincipalId(id).namespace !== "project") return;
8538
+ const routes = [ `The account-global record: ${globalRouteReason(globalUser, globalHostId)}.` ];
8539
+ bridgeFailure !== void 0 && routes.push(`The project user directory: ${bridgeFailure.reason}.`);
8540
+ const cause = bridgeFailure?.cause ?? ("cause" in globalUser ? globalUser.cause : void 0);
8541
+ throw new Error(`workflow: the caller's identity resolves to the project-scoped principal "${id}" and its account-global id could not be resolved. ${routes.join(" ")} The engine speaks account-global user ids only and never acts as a project-scoped principal.`, cause === void 0 ? void 0 : {
8542
+ cause: cause
8543
+ });
8471
8544
  }
8472
8545
 
8473
8546
  function usableId(user) {
@@ -13381,6 +13454,8 @@ exports.definitionDeployedData = definitionDeployedData;
13381
13454
 
13382
13455
  exports.definitionLookupGroq = definitionLookupGroq;
13383
13456
 
13457
+ exports.definitionTagsGroq = definitionTagsGroq;
13458
+
13384
13459
  exports.definitionsListGroq = definitionsListGroq;
13385
13460
 
13386
13461
  exports.deniedGuardLabels = deniedGuardLabels;
@@ -13391,6 +13466,8 @@ exports.denyingGuards = denyingGuards;
13391
13466
 
13392
13467
  exports.deployStageGuards = deployStageGuards;
13393
13468
 
13469
+ exports.deployedTagsGroq = deployedTagsGroq;
13470
+
13394
13471
  exports.deriveWorkflowAutonomy = deriveWorkflowAutonomy;
13395
13472
 
13396
13473
  exports.describeAtom = describeAtom;
package/dist/index.d.cts CHANGED
@@ -553,14 +553,17 @@ export declare type ActivityStatus = (typeof ACTIVITY_STATUSES)[number];
553
553
  * read time, and only the lake's own token identity is authenticated — hard
554
554
  * enforcement lives there.
555
555
  *
556
- * `id` is the account-global user id (the global API host's `/users/me`)
557
- * the one identity namespace that is stable across projects and org-level
558
- * resources. Robot tokens carry a single universal id, so for them `id`
559
- * equals what every host returns. Instances written before ids were
560
- * namespace-classified carry the workflow resource's local principal id in
561
- * `id` instead; readers interpret those through the prefix classifier in
562
- * `core/identity.ts` with the document's home resource as the implied
563
- * scope.
556
+ * `id` is the account-global user id the one identity namespace that is
557
+ * stable across projects and org-level resources. The engine takes it from the
558
+ * first route that can answer for the token: an id either host's `/users/me`
559
+ * already carries in account-global form (the id itself, or the one embedded in
560
+ * an `e-` principal), and otherwise the project's own user directory
561
+ * (`sanityUserId`) the route a session scoped to a single project depends on,
562
+ * since no global record exists for it. Robot tokens carry a single universal
563
+ * id, so for them `id` equals what every host returns. Instances written before
564
+ * ids were namespace-classified carry the workflow resource's local principal id
565
+ * in `id` instead; readers interpret those through the prefix classifier in
566
+ * `core/identity.ts` with the document's home resource as the implied scope.
564
567
  */
565
568
  export declare interface Actor {
566
569
  kind: ActorKind;
@@ -1682,7 +1685,8 @@ export declare interface ClientProjectUser {
1682
1685
  readonly [key: string]: unknown;
1683
1686
  }
1684
1687
 
1685
- /** Project-user directory for CLI, MCP, and server runtimes using the engine client. */
1688
+ /** Project-user directory served straight from the engine's own client — the
1689
+ * project API's user endpoints, no host adapter. */
1686
1690
  export declare function clientProjectUserDirectory(
1687
1691
  client: WorkflowClient,
1688
1692
  projectId: string,
@@ -2548,10 +2552,13 @@ export declare class DefinitionInUseError extends WorkflowError<"definition-in-u
2548
2552
  }
2549
2553
 
2550
2554
  /**
2551
- * Pure GROQ builder for resolving a deployed workflow definition. Composes the
2552
- * doc-type constant and the tag-scope predicate so the engine's internal
2553
- * lookups and the CLI share one definition of "tag-scoped, latest-or-pinned
2555
+ * Pure GROQ builders for deployed workflow definitions. Composes the doc-type
2556
+ * constant and the tag-scope predicate so the engine's internal lookups and the
2557
+ * CLI share one definition of "tag-scoped, latest-or-pinned
2554
2558
  * `workflow.definition`" instead of hand-writing the query at each call site.
2559
+ *
2560
+ * The tag enumerations are the deliberate exception: they span partitions, for a
2561
+ * caller holding a resource but no tag yet.
2555
2562
  */
2556
2563
  /**
2557
2564
  * GROQ resolving a single deployed {@link WORKFLOW_DEFINITION_TYPE} visible to
@@ -2610,6 +2617,19 @@ export declare function definitionsListGroq(
2610
2617
  versionOrder: "asc" | "desc",
2611
2618
  ): string;
2612
2619
 
2620
+ /**
2621
+ * GROQ listing every tag partition that holds any version of one definition
2622
+ * name — {@link deployedTagsGroq} narrowed from "what exists here" to "where
2623
+ * is this".
2624
+ *
2625
+ * Several hits is ambiguity to refuse, not resolve: version numbers are
2626
+ * per-partition, so the highest one may sit in an environment the caller did
2627
+ * not mean, and the answer would carry no sign of it.
2628
+ *
2629
+ * Params: `$definition`.
2630
+ */
2631
+ export declare function definitionTagsGroq(): string;
2632
+
2613
2633
  export declare interface DeleteDefinitionArgs {
2614
2634
  /** The definition's `name` — delete addresses the workflow, not a doc id. */
2615
2635
  definition: string;
@@ -2759,6 +2779,13 @@ export declare type DeployedDefinition = WorkflowDefinition & {
2759
2779
  minReaderModel?: number;
2760
2780
  };
2761
2781
 
2782
+ /**
2783
+ * GROQ listing every tag partition holding a deployed
2784
+ * {@link WORKFLOW_DEFINITION_TYPE} in the resource. The un-scoped
2785
+ * inverse of {@link tagScopeFilter}, this deliberately spans partitions.
2786
+ */
2787
+ export declare function deployedTagsGroq(): string;
2788
+
2762
2789
  /**
2763
2790
  * Deploy every guard for a stage being entered (idempotent upsert), but only
2764
2791
  * while the instance is still committed to that stage. A deploy that lost the
@@ -4010,7 +4037,7 @@ export declare interface EvaluateArgs {
4010
4037
  instanceId: string;
4011
4038
  /**
4012
4039
  * URL path on the supplied client where the engine should fetch
4013
- * grants. The actor always token-resolves via `/users/me`; without
4040
+ * grants. The actor always token-resolves from the supplied client; without
4014
4041
  * a grants path the rendered `$can` is undefined, so conditions
4015
4042
  * referencing it fail closed — and the real Sanity write boundary
4016
4043
  * still enforces.
@@ -7719,8 +7746,8 @@ export declare interface StartInstanceArgs {
7719
7746
  instanceId?: string;
7720
7747
  /**
7721
7748
  * URL path on the supplied client where the engine fetches the
7722
- * caller's ACL grants. Identity is always token-resolved — `actor`
7723
- * comes from `client.request({ uri: "/users/me" })` — and grants
7749
+ * caller's ACL grants. Identity is always token-resolved — `actor` is
7750
+ * resolved from the supplied client's token, never passed in — and grants
7724
7751
  * feed the advisory `$can.*` params action conditions can read;
7725
7752
  * omitting this leaves the rendered `$can` undefined (conditions
7726
7753
  * referencing it fail closed, nothing else is gated engine-side —
@@ -9365,10 +9392,14 @@ export declare interface WorkflowAutonomy extends AutonomyAnswer {
9365
9392
 
9366
9393
  export declare interface WorkflowClient {
9367
9394
  /**
9368
- * Read the effective client configuration. The engine probes `apiHost`
9369
- * before deriving the global `/users/me` sibling because `@sanity/client`
9370
- * shallow-merges `withConfig` overrides and otherwise preserves an explicit
9371
- * project host even when `useProjectHostname` is set to `false`.
9395
+ * Read the effective client configuration. The engine reads two fields from
9396
+ * it. `apiHost` is probed before deriving the global `/users/me` sibling,
9397
+ * because `@sanity/client` shallow-merges `withConfig` overrides and
9398
+ * otherwise preserves an explicit project host even when
9399
+ * `useProjectHostname` is set to `false`. `projectId` addresses the project's
9400
+ * user directory when a project-scoped principal has to be bridged to its
9401
+ * account-global id — a client reporting none has no directory to ask, and
9402
+ * such a session is refused rather than stamped locally.
9372
9403
  */
9373
9404
  config?: () => WorkflowClientConfig;
9374
9405
  fetch: <T = unknown>(
package/dist/index.d.ts CHANGED
@@ -553,14 +553,17 @@ export declare type ActivityStatus = (typeof ACTIVITY_STATUSES)[number];
553
553
  * read time, and only the lake's own token identity is authenticated — hard
554
554
  * enforcement lives there.
555
555
  *
556
- * `id` is the account-global user id (the global API host's `/users/me`)
557
- * the one identity namespace that is stable across projects and org-level
558
- * resources. Robot tokens carry a single universal id, so for them `id`
559
- * equals what every host returns. Instances written before ids were
560
- * namespace-classified carry the workflow resource's local principal id in
561
- * `id` instead; readers interpret those through the prefix classifier in
562
- * `core/identity.ts` with the document's home resource as the implied
563
- * scope.
556
+ * `id` is the account-global user id the one identity namespace that is
557
+ * stable across projects and org-level resources. The engine takes it from the
558
+ * first route that can answer for the token: an id either host's `/users/me`
559
+ * already carries in account-global form (the id itself, or the one embedded in
560
+ * an `e-` principal), and otherwise the project's own user directory
561
+ * (`sanityUserId`) the route a session scoped to a single project depends on,
562
+ * since no global record exists for it. Robot tokens carry a single universal
563
+ * id, so for them `id` equals what every host returns. Instances written before
564
+ * ids were namespace-classified carry the workflow resource's local principal id
565
+ * in `id` instead; readers interpret those through the prefix classifier in
566
+ * `core/identity.ts` with the document's home resource as the implied scope.
564
567
  */
565
568
  export declare interface Actor {
566
569
  kind: ActorKind;
@@ -1682,7 +1685,8 @@ export declare interface ClientProjectUser {
1682
1685
  readonly [key: string]: unknown;
1683
1686
  }
1684
1687
 
1685
- /** Project-user directory for CLI, MCP, and server runtimes using the engine client. */
1688
+ /** Project-user directory served straight from the engine's own client — the
1689
+ * project API's user endpoints, no host adapter. */
1686
1690
  export declare function clientProjectUserDirectory(
1687
1691
  client: WorkflowClient,
1688
1692
  projectId: string,
@@ -2548,10 +2552,13 @@ export declare class DefinitionInUseError extends WorkflowError<"definition-in-u
2548
2552
  }
2549
2553
 
2550
2554
  /**
2551
- * Pure GROQ builder for resolving a deployed workflow definition. Composes the
2552
- * doc-type constant and the tag-scope predicate so the engine's internal
2553
- * lookups and the CLI share one definition of "tag-scoped, latest-or-pinned
2555
+ * Pure GROQ builders for deployed workflow definitions. Composes the doc-type
2556
+ * constant and the tag-scope predicate so the engine's internal lookups and the
2557
+ * CLI share one definition of "tag-scoped, latest-or-pinned
2554
2558
  * `workflow.definition`" instead of hand-writing the query at each call site.
2559
+ *
2560
+ * The tag enumerations are the deliberate exception: they span partitions, for a
2561
+ * caller holding a resource but no tag yet.
2555
2562
  */
2556
2563
  /**
2557
2564
  * GROQ resolving a single deployed {@link WORKFLOW_DEFINITION_TYPE} visible to
@@ -2610,6 +2617,19 @@ export declare function definitionsListGroq(
2610
2617
  versionOrder: "asc" | "desc",
2611
2618
  ): string;
2612
2619
 
2620
+ /**
2621
+ * GROQ listing every tag partition that holds any version of one definition
2622
+ * name — {@link deployedTagsGroq} narrowed from "what exists here" to "where
2623
+ * is this".
2624
+ *
2625
+ * Several hits is ambiguity to refuse, not resolve: version numbers are
2626
+ * per-partition, so the highest one may sit in an environment the caller did
2627
+ * not mean, and the answer would carry no sign of it.
2628
+ *
2629
+ * Params: `$definition`.
2630
+ */
2631
+ export declare function definitionTagsGroq(): string;
2632
+
2613
2633
  export declare interface DeleteDefinitionArgs {
2614
2634
  /** The definition's `name` — delete addresses the workflow, not a doc id. */
2615
2635
  definition: string;
@@ -2759,6 +2779,13 @@ export declare type DeployedDefinition = WorkflowDefinition & {
2759
2779
  minReaderModel?: number;
2760
2780
  };
2761
2781
 
2782
+ /**
2783
+ * GROQ listing every tag partition holding a deployed
2784
+ * {@link WORKFLOW_DEFINITION_TYPE} in the resource. The un-scoped
2785
+ * inverse of {@link tagScopeFilter}, this deliberately spans partitions.
2786
+ */
2787
+ export declare function deployedTagsGroq(): string;
2788
+
2762
2789
  /**
2763
2790
  * Deploy every guard for a stage being entered (idempotent upsert), but only
2764
2791
  * while the instance is still committed to that stage. A deploy that lost the
@@ -4010,7 +4037,7 @@ export declare interface EvaluateArgs {
4010
4037
  instanceId: string;
4011
4038
  /**
4012
4039
  * URL path on the supplied client where the engine should fetch
4013
- * grants. The actor always token-resolves via `/users/me`; without
4040
+ * grants. The actor always token-resolves from the supplied client; without
4014
4041
  * a grants path the rendered `$can` is undefined, so conditions
4015
4042
  * referencing it fail closed — and the real Sanity write boundary
4016
4043
  * still enforces.
@@ -7719,8 +7746,8 @@ export declare interface StartInstanceArgs {
7719
7746
  instanceId?: string;
7720
7747
  /**
7721
7748
  * URL path on the supplied client where the engine fetches the
7722
- * caller's ACL grants. Identity is always token-resolved — `actor`
7723
- * comes from `client.request({ uri: "/users/me" })` — and grants
7749
+ * caller's ACL grants. Identity is always token-resolved — `actor` is
7750
+ * resolved from the supplied client's token, never passed in — and grants
7724
7751
  * feed the advisory `$can.*` params action conditions can read;
7725
7752
  * omitting this leaves the rendered `$can` undefined (conditions
7726
7753
  * referencing it fail closed, nothing else is gated engine-side —
@@ -9365,10 +9392,14 @@ export declare interface WorkflowAutonomy extends AutonomyAnswer {
9365
9392
 
9366
9393
  export declare interface WorkflowClient {
9367
9394
  /**
9368
- * Read the effective client configuration. The engine probes `apiHost`
9369
- * before deriving the global `/users/me` sibling because `@sanity/client`
9370
- * shallow-merges `withConfig` overrides and otherwise preserves an explicit
9371
- * project host even when `useProjectHostname` is set to `false`.
9395
+ * Read the effective client configuration. The engine reads two fields from
9396
+ * it. `apiHost` is probed before deriving the global `/users/me` sibling,
9397
+ * because `@sanity/client` shallow-merges `withConfig` overrides and
9398
+ * otherwise preserves an explicit project host even when
9399
+ * `useProjectHostname` is set to `false`. `projectId` addresses the project's
9400
+ * user directory when a project-scoped principal has to be bridged to its
9401
+ * account-global id — a client reporting none has no directory to ask, and
9402
+ * such a session is refused rather than stamped locally.
9372
9403
  */
9373
9404
  config?: () => WorkflowClientConfig;
9374
9405
  fetch: <T = unknown>(
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { terminalState, andConditions, deriveActivityKind, parseDefinitionSnapshot, CALLER_BOUND_VARS, START_REQUIREMENT_VARS, CONDITION_VARS, isSubjectEntry, isUnevaluable, ContractViolationError, isStartableDefinition, conditionFieldReadNames, rethrowWithContext, isGdr, errorMessage, conditionParameterNames, START_FILTER_VARS, gdrFromResource, selfGdr, isSingleDocRefKind, parentRef, actorFulfillsRole, toBareId, WorkflowError, sameResource, resourceFromParsed, tryParseGdr, isTerminalActivityStatus, FieldValueShapeError, validateFieldValue, validateFieldAppendItem, evaluateCondition, isSingleDocRefEntry, choiceValueIssues, scalarValidationIssues, StoredFieldOpSchema, formatIssues, conditionSyntaxIssues, checkWorkflowInvariants, formatIssuePath, isGuardReadExpr, conditionEffectReads, EFFECTS_READ, datasetResourceParts, evaluatePredicates, WORKFLOW_DEFINITION_TYPE, tagScopeFilter, isCascadeFired, WORKFLOW_INSTANCE_TYPE, parseGdr, isGdrUri, gdrRef, isInputSourced, parseFieldValue, VersionSpecificDatasetGdrError, toPhysicalGdr, isBareSeedId, tolerantEntries, NonEmptyString, FIELD_VALUE_KINDS, tolerantObject, ActorShape, IsoTimestamp, ACTIVITY_STATUSES, GdrShape, DRIVER_KINDS, FIELD_SCOPES, fieldValueSchemas, parsePersistedDoc, ACTOR_KINDS, classifyPrincipalId, assertReadableModel, InstanceNotFoundError, modelStampFor, evaluateConditionOutcome, runGroq, FIELD_READ, MUTATION_GUARD_ACTIONS, resourceGdr, minReaderModelOf, resourceFromGdrUri, refTypeIssues, rejectedRefTypes, DOCUMENT_VALUE_PERMISSIONS, lakePrincipalId, driverKind, EffectNotFoundError, deriveExecutorClassification, validateTag, extractDocumentId, parseStoredDefinition, validateResourceAliasName, labelFor, DefinitionNotFoundError, definitionDocId, SpawnContractsInvalidError, gdrResourcePrefix, RESOURCE_ALIAS_NAME_SOURCE, isUnprimed, DefinitionInUseError, assertReaderModelAcknowledgement, isParseableInstant, groupMembershipNames } from "./_chunks-es/invariants.js";
1
+ import { terminalState, andConditions, deriveActivityKind, parseDefinitionSnapshot, CALLER_BOUND_VARS, START_REQUIREMENT_VARS, CONDITION_VARS, isSubjectEntry, isUnevaluable, ContractViolationError, isStartableDefinition, conditionFieldReadNames, rethrowWithContext, isGdr, errorMessage, conditionParameterNames, START_FILTER_VARS, gdrFromResource, selfGdr, isSingleDocRefKind, parentRef, actorFulfillsRole, toBareId, WorkflowError, sameResource, resourceFromParsed, tryParseGdr, isTerminalActivityStatus, FieldValueShapeError, validateFieldValue, validateFieldAppendItem, evaluateCondition, isSingleDocRefEntry, choiceValueIssues, scalarValidationIssues, StoredFieldOpSchema, formatIssues, conditionSyntaxIssues, checkWorkflowInvariants, formatIssuePath, isGuardReadExpr, conditionEffectReads, EFFECTS_READ, datasetResourceParts, evaluatePredicates, WORKFLOW_DEFINITION_TYPE, tagScopeFilter, isCascadeFired, WORKFLOW_INSTANCE_TYPE, parseGdr, isGdrUri, gdrRef, isInputSourced, parseFieldValue, VersionSpecificDatasetGdrError, toPhysicalGdr, isBareSeedId, tolerantEntries, NonEmptyString, FIELD_VALUE_KINDS, tolerantObject, ActorShape, IsoTimestamp, ACTIVITY_STATUSES, GdrShape, DRIVER_KINDS, FIELD_SCOPES, fieldValueSchemas, parsePersistedDoc, ACTOR_KINDS, classifyPrincipalId, directoryBridgeId, assertReadableModel, InstanceNotFoundError, modelStampFor, evaluateConditionOutcome, runGroq, FIELD_READ, MUTATION_GUARD_ACTIONS, resourceGdr, minReaderModelOf, resourceFromGdrUri, refTypeIssues, rejectedRefTypes, DOCUMENT_VALUE_PERMISSIONS, lakePrincipalId, driverKind, EffectNotFoundError, firstCarriedGlobalId, deriveExecutorClassification, validateTag, extractDocumentId, parseStoredDefinition, validateResourceAliasName, labelFor, DefinitionNotFoundError, definitionDocId, SpawnContractsInvalidError, gdrResourcePrefix, RESOURCE_ALIAS_NAME_SOURCE, isUnprimed, DefinitionInUseError, assertReaderModelAcknowledgement, isParseableInstant, groupMembershipNames } from "./_chunks-es/invariants.js";
2
2
 
3
3
  import { ACTION_SEMANTICS, ACTIVITY_KINDS, ANONYMOUS_IDENTITY, DATA_MODEL_CHANGES, DATA_MODEL_MIN_READER, DATA_MODEL_VERSION, DEFAULT_TRANSITION_WHEN, EXECUTOR_CLASSIFICATIONS, FILTER_SCOPE_VARS, GROUP_KINDS, GUARD_PREDICATE_VARS, ModelVersionAheadError, PersistedDocShapeError, READER_MODEL_ROLLOUT_URL, RESERVED_CONDITION_VARS, ReaderModelAcknowledgementError, SYSTEM_IDENTITY, clientConfigFromResource, fieldTreeShape, gdrUri, isNotesEntry, isTodoListEntry, isTodoListItem, modelVersionOf, parseDefinitionSnapshotValue, parseResourceGdr, readsRootDocument, refCanvas, refDashboard, refDataset, refKindAcceptsTypes, refMediaLibrary, releaseDocId, releaseRef, requiredModelFeatures, requiredReaderModel, resourceAliasesToMap, schemaTreeShape, startKindOf } from "./_chunks-es/invariants.js";
4
4
 
@@ -3301,6 +3301,14 @@ function latestDefinitionsGroq() {
3301
3301
  return `*[${scoped} && version == math::max(*[${scoped} && name == ^.name].version)] | order(name asc)`;
3302
3302
  }
3303
3303
 
3304
+ function deployedTagsGroq() {
3305
+ return `array::unique(*[_type == "${WORKFLOW_DEFINITION_TYPE}"].tag) | order(@ asc)`;
3306
+ }
3307
+
3308
+ function definitionTagsGroq() {
3309
+ return `array::unique(*[_type == "${WORKFLOW_DEFINITION_TYPE}" && name == $definition].tag) | order(@ asc)`;
3310
+ }
3311
+
3304
3312
  function latestDeployedDefinitions(rows) {
3305
3313
  const byName = /* @__PURE__ */ new Map;
3306
3314
  for (const row of rows) {
@@ -4790,8 +4798,11 @@ function cacheFor(client) {
4790
4798
 
4791
4799
  function ingestDirectoryRows(args) {
4792
4800
  const records = Array.isArray(args.response) ? args.response : [ args.response ];
4793
- for (const row of records) typeof row?.id != "string" || typeof row.sanityUserId != "string" || args.requested.has(row.id) && classifyPrincipalId(row.sanityUserId).namespace === "global" && (args.cache.set(row.id, row.sanityUserId),
4794
- args.out.set(row.id, row.sanityUserId));
4801
+ for (const row of records) {
4802
+ if (typeof row?.id != "string" || !args.requested.has(row.id)) continue;
4803
+ const globalId = directoryBridgeId(row.sanityUserId);
4804
+ globalId !== void 0 && (args.cache.set(row.id, globalId), args.out.set(row.id, globalId));
4805
+ }
4795
4806
  }
4796
4807
 
4797
4808
  async function getInstanceDocument(client, instanceId) {
@@ -5437,10 +5448,14 @@ async function resolveGlobalProjectUser(args) {
5437
5448
  async function ingestMemberJoin(args) {
5438
5449
  const {request: request, projectId: projectId, cache: cache} = args, memberIds = await projectMemberIds(request, projectId);
5439
5450
  if (memberIds.length === 0) return;
5440
- const response = await request({
5451
+ const requested = new Set(memberIds), response = await request({
5441
5452
  uri: `/projects/${encodeURIComponent(projectId)}/users/${memberIds.map(encodeURIComponent).join(",")}`
5442
5453
  });
5443
- for (const row of Array.isArray(response) ? response : [ response ]) isClientProjectUser(row) && typeof row.sanityUserId == "string" && cache.set(`${projectId}:${row.sanityUserId}`, row);
5454
+ for (const row of Array.isArray(response) ? response : [ response ]) {
5455
+ if (!isClientProjectUser(row) || !requested.has(row.id)) continue;
5456
+ const globalId = directoryBridgeId(row.sanityUserId);
5457
+ globalId !== void 0 && cache.set(`${projectId}:${globalId}`, row);
5458
+ }
5444
5459
  }
5445
5460
 
5446
5461
  function clientProjectUserDirectory(client, projectId) {
@@ -5462,7 +5477,10 @@ function clientProjectUserDirectory(client, projectId) {
5462
5477
  }), candidate = Array.isArray(response) ? response[0] : response;
5463
5478
  return candidate == null ? {
5464
5479
  status: "missing"
5465
- } : isClientProjectUser(candidate) ? {
5480
+ } : isClientProjectUser(candidate) ? candidate.id !== id ? {
5481
+ status: "inaccessible",
5482
+ cause: new Error(`Project-user response answered id "${candidate.id}", not the requested "${id}"`)
5483
+ } : {
5466
5484
  status: "resolved",
5467
5485
  user: candidate
5468
5486
  } : {
@@ -8423,14 +8441,20 @@ function cachedGrants({client: client, requestFn: requestFn, resourcePath: resou
8423
8441
  }
8424
8442
 
8425
8443
  async function fetchActor(client, requestFn) {
8426
- const [resourceUser, globalUser] = await Promise.all([ fetchCurrentUser(requestFn, "the workflow resource host"), fetchGlobalUser(client) ]), resourceId = usableId(resourceUser), globalId = usableId(globalUser.user), id = globalId ?? resourceId;
8427
- if (id === void 0) return;
8428
- refuseProjectPrincipalWithoutGlobal({
8429
- resourceId: resourceId,
8430
- globalId: globalId,
8431
- globalUser: globalUser
8432
- });
8433
- const roleNames = (resourceUser?.roles?.length ? resourceUser : globalUser.user)?.roles?.map(r => r.name).filter(n => !!n) ?? [];
8444
+ const [resourceUser, globalUser] = await Promise.all([ fetchCurrentUser(requestFn, "the workflow resource host"), fetchGlobalUser(client) ]), resourceId = usableId(resourceUser), globalHostId = usableId(globalUser.user), carriedId = firstCarriedGlobalId([ globalHostId, resourceId ]), sessionId = carriedId ?? globalHostId ?? resourceId;
8445
+ if (sessionId === void 0) return;
8446
+ const bridge = carriedId === void 0 ? await bridgeProjectPrincipal({
8447
+ client: client,
8448
+ sessionId: sessionId,
8449
+ resourceId: resourceId
8450
+ }) : NO_BRIDGE, id = bridge.status === "resolved" ? bridge.globalId : sessionId;
8451
+ refuseProjectScopedActor({
8452
+ id: id,
8453
+ globalUser: globalUser,
8454
+ globalHostId: globalHostId,
8455
+ bridgeFailure: bridge.status === "unavailable" ? bridge : void 0
8456
+ });
8457
+ const roleNames = roleNamesFor(resourceUser, globalUser);
8434
8458
  return {
8435
8459
  actor: {
8436
8460
  kind: "person",
@@ -8445,13 +8469,62 @@ async function fetchActor(client, requestFn) {
8445
8469
  };
8446
8470
  }
8447
8471
 
8448
- function refuseProjectPrincipalWithoutGlobal(args) {
8449
- const {resourceId: resourceId, globalId: globalId, globalUser: globalUser} = args;
8450
- if (globalId !== void 0 || resourceId === void 0 || classifyPrincipalId(resourceId).namespace !== "project") return;
8451
- const reason = "reason" in globalUser ? globalUser.reason : "no record";
8452
- throw new Error(`workflow: the caller's identity on the workflow resource host is project-scoped ("${resourceId}") and the account-global record could not be resolved (${reason}). The engine speaks account-global user ids only and never acts as a project-scoped principal.`, "cause" in globalUser ? {
8453
- cause: globalUser.cause
8454
- } : void 0);
8472
+ function roleNamesFor(resourceUser, globalUser) {
8473
+ return (resourceUser?.roles?.length ? resourceUser : globalUser.user)?.roles?.map(r => r.name).filter(n => !!n) ?? [];
8474
+ }
8475
+
8476
+ const NO_BRIDGE = {
8477
+ status: "not-applicable"
8478
+ };
8479
+
8480
+ function globalRouteReason(globalUser, globalHostId) {
8481
+ return "reason" in globalUser ? globalUser.reason : globalHostId === void 0 ? "no record" : `the global host answered with project-scoped principal "${globalHostId}"`;
8482
+ }
8483
+
8484
+ async function bridgeProjectPrincipal(args) {
8485
+ const {client: client, sessionId: sessionId, resourceId: resourceId} = args;
8486
+ return classifyPrincipalId(sessionId).namespace !== "project" ? NO_BRIDGE : resourceId === void 0 || classifyPrincipalId(resourceId).namespace !== "project" ? {
8487
+ status: "unavailable",
8488
+ reason: "no project-scoped principal from the resource host to address the directory with"
8489
+ } : directoryGlobalId({
8490
+ client: client,
8491
+ principalId: resourceId
8492
+ });
8493
+ }
8494
+
8495
+ async function directoryGlobalId(args) {
8496
+ const {client: client, principalId: principalId} = args, projectId = typeof client.config == "function" ? client.config().projectId : void 0;
8497
+ if (projectId === void 0) return {
8498
+ status: "unavailable",
8499
+ reason: "the client reports no projectId, so its user directory has no address"
8500
+ };
8501
+ const lookup = await clientProjectUserDirectory(withRequestTag(client, REQUEST_TAG.accessResolveActor), projectId).findById(principalId);
8502
+ if (lookup.status !== "resolved") return {
8503
+ status: "unavailable",
8504
+ reason: `project "${projectId}"'s user directory reported the principal ${lookup.status}`,
8505
+ ...lookup.status === "inaccessible" && lookup.cause !== void 0 ? {
8506
+ cause: lookup.cause
8507
+ } : {}
8508
+ };
8509
+ const globalId = directoryBridgeId(lookup.user.sanityUserId);
8510
+ return globalId === void 0 ? {
8511
+ status: "unavailable",
8512
+ reason: `project "${projectId}"'s user directory row for the principal carries no account-global sanityUserId`
8513
+ } : {
8514
+ status: "resolved",
8515
+ globalId: globalId
8516
+ };
8517
+ }
8518
+
8519
+ function refuseProjectScopedActor(args) {
8520
+ const {id: id, globalUser: globalUser, globalHostId: globalHostId, bridgeFailure: bridgeFailure} = args;
8521
+ if (classifyPrincipalId(id).namespace !== "project") return;
8522
+ const routes = [ `The account-global record: ${globalRouteReason(globalUser, globalHostId)}.` ];
8523
+ bridgeFailure !== void 0 && routes.push(`The project user directory: ${bridgeFailure.reason}.`);
8524
+ const cause = bridgeFailure?.cause ?? ("cause" in globalUser ? globalUser.cause : void 0);
8525
+ throw new Error(`workflow: the caller's identity resolves to the project-scoped principal "${id}" and its account-global id could not be resolved. ${routes.join(" ")} The engine speaks account-global user ids only and never acts as a project-scoped principal.`, cause === void 0 ? void 0 : {
8526
+ cause: cause
8527
+ });
8455
8528
  }
8456
8529
 
8457
8530
  function usableId(user) {
@@ -12921,4 +12994,4 @@ function displayDescription(typeKey) {
12921
12994
  if (typeKey) return DISPLAY[typeKey]?.description;
12922
12995
  }
12923
12996
 
12924
- export { ACTION_SEMANTICS, ACTIVITY_KINDS, ACTIVITY_KIND_DISPLAY, ACTOR_KINDS, ANONYMOUS_IDENTITY, AUTHORING_DISPLAY, ActionDisabledError, ActionParamsInvalidError, CONDITION_VARS, CONTEXT_ENTRY_DISPLAY, CascadeLimitError, ConcurrentCommitEffectOpsError, ConcurrentCompleteEffectError, ConcurrentEditFieldError, ConcurrentFireActionError, ContractViolationError, DATA_MODEL_CHANGES, DATA_MODEL_MIN_READER, DATA_MODEL_VERSION, DEFAULT_CONTENT_PERSPECTIVE, DEFAULT_EFFECT_LEASE_MS, DEFAULT_IDEMPOTENCY_TTL_MS, DEFAULT_TRANSITION_WHEN, DISPLAY, DRIVER_KINDS, DRIVER_KIND_DISPLAY, DefinitionInUseError, DefinitionNotFoundError, EFFECT_COMMIT_DISPATCH_CAP, EFFECT_COMMIT_QUEUE_DEPTH, ENGINE_API_VERSION, EXECUTION_KINDS, EXECUTOR_CLASSIFICATIONS, EXECUTOR_CLASSIFICATION_DISPLAY, EditFieldDeniedError, EffectCommitQueueOverflowError, EffectNotFoundError, EffectOpsInvalidError, EffectOutputsInvalidError, FIELD_KIND_DISPLAY, FILTER_SCOPE_VARS, FieldValueShapeError, GROUP_KINDS, GROUP_KIND_DISPLAY, GUARD_DOC_TYPE, GUARD_OWNER, GUARD_PREDICATE_VARS, HISTORY_DISPLAY, InitialFieldsInvalidError, InstanceNotFoundError, MAX_COUNTERFACTUAL_INDEX2 as MAX_COUNTERFACTUAL_INDEX, MissingHandlerError, ModelVersionAheadError, MutationGuardDeniedError, MutationGuardDocSchema, NEUTRAL_MARK, OP_DISPLAY, OUTCOME_MARKS, PartialGuardDeployError, PersistedDocShapeError, READER_MODEL_ROLLOUT_URL, RESERVED_CONDITION_VARS, ReaderModelAcknowledgementError, RefResourceUndeclaredError, RequiredFieldNotProvidedError, START_FILTER_VARS, START_REQUIREMENT_VARS, SYSTEM_IDENTITY, SpawnContractsInvalidError, StaleEffectClaimError, StartNotAllowedError, StartNotPrimedError, StartNotSettledError, WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, WorkflowActionFired, WorkflowActivityReset, WorkflowDefinitionDeleted, WorkflowDefinitionDeployed, WorkflowEffectCompleted, WorkflowEffectStateReported, WorkflowEffectsDrained, WorkflowError, WorkflowFieldEdited, WorkflowInstanceAborted, WorkflowInstanceSchema, WorkflowInstanceStarted, WorkflowInstanceTicked, WorkflowStageSet, WorkflowStageTransitioned, WorkflowStateDivergedError, abortReason, acceptsDocumentType, aclPathForResource, actionDisabledDetail, actionRendering, actionVerdict, activityAutonomyOf, analyzeCondition2 as analyzeCondition, applicableDefinitions, assertReadableModel, assertReaderModelAcknowledgement, atomReadsDataset2 as atomReadsDataset, autonomySummary, availableActions, buildInitialFields, buildSnapshot, checklistLines, classifyPrincipalId, clientConfigFromResource, clientProjectUserDirectory, compileGuard, computeDiffEntries, conditionFieldReadNames, conditionSitesOf, contentDocQuery, contentDraftFallback, contentReleaseName, contextMap, createEngine, createTelemetryIntake, datasetResourceParts, defaultLoggerFactory, definitionDeployedData, definitionLookupGroq, definitionsListGroq, deniedGuardLabels, deniedGuardRefs, denyingGuards, deployStageGuards, deriveActivityKind, deriveExecutorClassification, deriveWorkflowAutonomy, describeAtom, describeAutonomyWait, describeCondition, describeDefinition, describeFieldInsight, describeNode, describeSite, describeSiteHeading, diagnoseInputFromEvaluation, diagnoseInstance, diffEntry, displayDescription, displayTitle, documentActionDenials, documentPrefilter, driverKind, effectOutputsMap, entryDocRefs, errorMessage, evaluateFromSnapshot, evaluateMutationGuard, evaluateStartFilter, expandResourceAliases, explainCondition2 as explainCondition, explainStartRequirement, extractDocumentId, fieldTreeShape, findCurrentActivityEntry, findOpenStageEntry, formatRead, gdrFromResource, gdrRef, gdrUri, groupMembershipNames, groupSitesOf, guardMatches, guardsForDefinition, guardsForInstance, guardsForResource, guillemets2 as guillemets, hasSingleSubjectRequirement, hashDefinitionContent, humanize2 as humanize, inFlightFilter, initialFieldIssues, instanceDocId, instanceGuardQuery, instanceWatchesDocument, instancesGuardQuery, instancesQuery, isCascadeFired, isClaimExpired, isClientProjectUser, isComparisonOp, isDefinitionApplicable, isFilterScopedOut, isGdr, isInputSourced, isNotesEntry, isProjectUserNotFoundError, isSingleDocRefEntry, isSingleDocRefKind, isStartableDefinition, isSubjectEntry, isTelemetryEnvDenied, isTerminalActivityStatus, isTerminalStage, isTodoListEntry, isTodoListItem, isUnprimed, lakeGuardId, lakePrincipalId, latestDefinitionsGroq, latestDeployedDefinitions, lintEffectOutputs, minReaderModelOf, missingRequiredInputs, modelVersionOf, narrateAutonomyWaits, noopTelemetry, parentRef, parseDefinitionInput, parseDefinitionSnapshot, parseDefinitionSnapshotValue, parseGdr, parseGuardDocument, parseInstanceDocument, parseResourceGdr, processShellUserProperties, projectStartSliceRow, projectToWatchRef, quoted2 as quoted, readInstanceDoc, readsRaw, readsRootDocument, refCanvas, refDashboard, refDataset, refKindAcceptsTypes, refMediaLibrary, refsOf, rejectedRefTypes, releaseDocId, releaseRef, remediationsFor, requiredModelFeatures, requiredReaderModel, resolveAccess, resolveActor, resolveClientActor, resolveFieldEntry$1 as resolveFieldEntry, resourceAliasesToMap, resourceFromParsed, resourceGdr, retractStageGuards, sameResource, scalarValidationIssues, schemaTreeShape, sentenceCase, silentLogger, singleSubjectRequirementRefused, stageAutonomyOf, startFieldsParam, startKindOf, startRefusal, stripSystemFields, subjectDenialLabels, subscriptionDocument, subscriptionDocumentsForInstance, sweepStaleClaims, tagScopeFilter, terminalState, toBareId, tryParseGdr, unboundRequirementReads, unsatisfiedTransitionSummaries, validateDefinition, validateTag, verdictGuardsForInstance, wallClock, whatIfCondition2 as whatIfCondition, withAssignment, workflow };
12997
+ export { ACTION_SEMANTICS, ACTIVITY_KINDS, ACTIVITY_KIND_DISPLAY, ACTOR_KINDS, ANONYMOUS_IDENTITY, AUTHORING_DISPLAY, ActionDisabledError, ActionParamsInvalidError, CONDITION_VARS, CONTEXT_ENTRY_DISPLAY, CascadeLimitError, ConcurrentCommitEffectOpsError, ConcurrentCompleteEffectError, ConcurrentEditFieldError, ConcurrentFireActionError, ContractViolationError, DATA_MODEL_CHANGES, DATA_MODEL_MIN_READER, DATA_MODEL_VERSION, DEFAULT_CONTENT_PERSPECTIVE, DEFAULT_EFFECT_LEASE_MS, DEFAULT_IDEMPOTENCY_TTL_MS, DEFAULT_TRANSITION_WHEN, DISPLAY, DRIVER_KINDS, DRIVER_KIND_DISPLAY, DefinitionInUseError, DefinitionNotFoundError, EFFECT_COMMIT_DISPATCH_CAP, EFFECT_COMMIT_QUEUE_DEPTH, ENGINE_API_VERSION, EXECUTION_KINDS, EXECUTOR_CLASSIFICATIONS, EXECUTOR_CLASSIFICATION_DISPLAY, EditFieldDeniedError, EffectCommitQueueOverflowError, EffectNotFoundError, EffectOpsInvalidError, EffectOutputsInvalidError, FIELD_KIND_DISPLAY, FILTER_SCOPE_VARS, FieldValueShapeError, GROUP_KINDS, GROUP_KIND_DISPLAY, GUARD_DOC_TYPE, GUARD_OWNER, GUARD_PREDICATE_VARS, HISTORY_DISPLAY, InitialFieldsInvalidError, InstanceNotFoundError, MAX_COUNTERFACTUAL_INDEX2 as MAX_COUNTERFACTUAL_INDEX, MissingHandlerError, ModelVersionAheadError, MutationGuardDeniedError, MutationGuardDocSchema, NEUTRAL_MARK, OP_DISPLAY, OUTCOME_MARKS, PartialGuardDeployError, PersistedDocShapeError, READER_MODEL_ROLLOUT_URL, RESERVED_CONDITION_VARS, ReaderModelAcknowledgementError, RefResourceUndeclaredError, RequiredFieldNotProvidedError, START_FILTER_VARS, START_REQUIREMENT_VARS, SYSTEM_IDENTITY, SpawnContractsInvalidError, StaleEffectClaimError, StartNotAllowedError, StartNotPrimedError, StartNotSettledError, WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, WorkflowActionFired, WorkflowActivityReset, WorkflowDefinitionDeleted, WorkflowDefinitionDeployed, WorkflowEffectCompleted, WorkflowEffectStateReported, WorkflowEffectsDrained, WorkflowError, WorkflowFieldEdited, WorkflowInstanceAborted, WorkflowInstanceSchema, WorkflowInstanceStarted, WorkflowInstanceTicked, WorkflowStageSet, WorkflowStageTransitioned, WorkflowStateDivergedError, abortReason, acceptsDocumentType, aclPathForResource, actionDisabledDetail, actionRendering, actionVerdict, activityAutonomyOf, analyzeCondition2 as analyzeCondition, applicableDefinitions, assertReadableModel, assertReaderModelAcknowledgement, atomReadsDataset2 as atomReadsDataset, autonomySummary, availableActions, buildInitialFields, buildSnapshot, checklistLines, classifyPrincipalId, clientConfigFromResource, clientProjectUserDirectory, compileGuard, computeDiffEntries, conditionFieldReadNames, conditionSitesOf, contentDocQuery, contentDraftFallback, contentReleaseName, contextMap, createEngine, createTelemetryIntake, datasetResourceParts, defaultLoggerFactory, definitionDeployedData, definitionLookupGroq, definitionTagsGroq, definitionsListGroq, deniedGuardLabels, deniedGuardRefs, denyingGuards, deployStageGuards, deployedTagsGroq, deriveActivityKind, deriveExecutorClassification, deriveWorkflowAutonomy, describeAtom, describeAutonomyWait, describeCondition, describeDefinition, describeFieldInsight, describeNode, describeSite, describeSiteHeading, diagnoseInputFromEvaluation, diagnoseInstance, diffEntry, displayDescription, displayTitle, documentActionDenials, documentPrefilter, driverKind, effectOutputsMap, entryDocRefs, errorMessage, evaluateFromSnapshot, evaluateMutationGuard, evaluateStartFilter, expandResourceAliases, explainCondition2 as explainCondition, explainStartRequirement, extractDocumentId, fieldTreeShape, findCurrentActivityEntry, findOpenStageEntry, formatRead, gdrFromResource, gdrRef, gdrUri, groupMembershipNames, groupSitesOf, guardMatches, guardsForDefinition, guardsForInstance, guardsForResource, guillemets2 as guillemets, hasSingleSubjectRequirement, hashDefinitionContent, humanize2 as humanize, inFlightFilter, initialFieldIssues, instanceDocId, instanceGuardQuery, instanceWatchesDocument, instancesGuardQuery, instancesQuery, isCascadeFired, isClaimExpired, isClientProjectUser, isComparisonOp, isDefinitionApplicable, isFilterScopedOut, isGdr, isInputSourced, isNotesEntry, isProjectUserNotFoundError, isSingleDocRefEntry, isSingleDocRefKind, isStartableDefinition, isSubjectEntry, isTelemetryEnvDenied, isTerminalActivityStatus, isTerminalStage, isTodoListEntry, isTodoListItem, isUnprimed, lakeGuardId, lakePrincipalId, latestDefinitionsGroq, latestDeployedDefinitions, lintEffectOutputs, minReaderModelOf, missingRequiredInputs, modelVersionOf, narrateAutonomyWaits, noopTelemetry, parentRef, parseDefinitionInput, parseDefinitionSnapshot, parseDefinitionSnapshotValue, parseGdr, parseGuardDocument, parseInstanceDocument, parseResourceGdr, processShellUserProperties, projectStartSliceRow, projectToWatchRef, quoted2 as quoted, readInstanceDoc, readsRaw, readsRootDocument, refCanvas, refDashboard, refDataset, refKindAcceptsTypes, refMediaLibrary, refsOf, rejectedRefTypes, releaseDocId, releaseRef, remediationsFor, requiredModelFeatures, requiredReaderModel, resolveAccess, resolveActor, resolveClientActor, resolveFieldEntry$1 as resolveFieldEntry, resourceAliasesToMap, resourceFromParsed, resourceGdr, retractStageGuards, sameResource, scalarValidationIssues, schemaTreeShape, sentenceCase, silentLogger, singleSubjectRequirementRefused, stageAutonomyOf, startFieldsParam, startKindOf, startRefusal, stripSystemFields, subjectDenialLabels, subscriptionDocument, subscriptionDocumentsForInstance, sweepStaleClaims, tagScopeFilter, terminalState, toBareId, tryParseGdr, unboundRequirementReads, unsatisfiedTransitionSummaries, validateDefinition, validateTag, verdictGuardsForInstance, wallClock, whatIfCondition2 as whatIfCondition, withAssignment, workflow };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/workflow-engine",
3
- "version": "0.24.0",
3
+ "version": "0.26.0",
4
4
  "description": "Workflow / BPM engine for Sanity content — define workflows as data, run them as instances against a Sanity client.",
5
5
  "keywords": [
6
6
  "bpm",