@sanity/workflow-engine 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { terminalState, andConditions, deriveActivityKind, parseDefinitionSnapshot, CALLER_BOUND_VARS, START_ALLOWED_VARS, CONDITION_VARS, SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR, isSubjectEntry, isUnevaluable, ContractViolationError, isStartableDefinition, conditionFieldReadNames, rethrowWithContext, conditionParameterNames, isGdr, WORKFLOW_INSTANCE_TYPE, START_FILTER_VARS, gdrFromResource, selfGdr, isSingleDocRefKind, parentRef, actorFulfillsRole, toBareId, WorkflowError, sameResource, resourceFromParsed, tryParseGdr, isTerminalActivityStatus, errorMessage, validateFieldValue, validateFieldAppendItem, evaluateCondition, isSingleDocRefEntry, choiceValueIssues, scalarValidationIssues, StoredFieldOpSchema, formatIssues, conditionSyntaxIssues, checkWorkflowInvariants, formatIssuePath, isGuardReadExpr, conditionEffectReads, EFFECTS_READ, datasetResourceParts, evaluatePredicates, WORKFLOW_DEFINITION_TYPE, tagScopeFilter, isCascadeFired, runGroq, parseGdr, isGdrUri, gdrRef, isInputSourced, checkFieldValue, toPhysicalGdr, isBareSeedId, tolerantEntries, NonEmptyString, FIELD_VALUE_KINDS, tolerantObject, ActorShape, IsoTimestamp, ACTIVITY_STATUSES, GdrShape, DRIVER_KINDS, FIELD_SCOPES, fieldValueSchemas, parsePersistedDoc, assertReadableModel, InstanceNotFoundError, modelStampFor, evaluateConditionOutcome, FIELD_READ, MUTATION_GUARD_ACTIONS, resourceGdr, minReaderModelOf, resourceFromGdrUri, refTypeIssues, rejectedRefTypes, DOCUMENT_VALUE_PERMISSIONS, deriveExecutorClassification, validateTag, driverKind, EffectNotFoundError, extractDocumentId, parseStoredDefinition, validateResourceAliasName, labelFor, DefinitionNotFoundError, definitionDocId, SpawnContractsInvalidError, gdrResourcePrefix, RESOURCE_ALIAS_NAME_SOURCE, isUnprimed, DefinitionInUseError, assertReaderModelAcknowledgement, FieldValueShapeError, isParseableInstant, groupMembershipNames } from "./_chunks-es/invariants.js";
1
+ import { terminalState, andConditions, deriveActivityKind, parseDefinitionSnapshot, CALLER_BOUND_VARS, START_ALLOWED_VARS, CONDITION_VARS, SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR, isSubjectEntry, isUnevaluable, ContractViolationError, isStartableDefinition, conditionFieldReadNames, rethrowWithContext, conditionParameterNames, isGdr, WORKFLOW_INSTANCE_TYPE, START_FILTER_VARS, gdrFromResource, selfGdr, isSingleDocRefKind, parentRef, actorFulfillsRole, toBareId, WorkflowError, sameResource, resourceFromParsed, tryParseGdr, isTerminalActivityStatus, errorMessage, FieldValueShapeError, validateFieldValue, validateFieldAppendItem, evaluateCondition, isSingleDocRefEntry, choiceValueIssues, scalarValidationIssues, StoredFieldOpSchema, formatIssues, conditionSyntaxIssues, checkWorkflowInvariants, formatIssuePath, isGuardReadExpr, conditionEffectReads, EFFECTS_READ, datasetResourceParts, evaluatePredicates, WORKFLOW_DEFINITION_TYPE, tagScopeFilter, isCascadeFired, parseGdr, isGdrUri, gdrRef, isInputSourced, checkFieldValue, toPhysicalGdr, isBareSeedId, tolerantEntries, NonEmptyString, FIELD_VALUE_KINDS, tolerantObject, ActorShape, IsoTimestamp, ACTIVITY_STATUSES, GdrShape, DRIVER_KINDS, FIELD_SCOPES, fieldValueSchemas, parsePersistedDoc, assertReadableModel, InstanceNotFoundError, modelStampFor, evaluateConditionOutcome, runGroq, FIELD_READ, MUTATION_GUARD_ACTIONS, resourceGdr, minReaderModelOf, resourceFromGdrUri, refTypeIssues, rejectedRefTypes, DOCUMENT_VALUE_PERMISSIONS, 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";
2
2
 
3
3
  import { ACTION_SEMANTICS, ACTIVITY_KINDS, ACTOR_KINDS, 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, clientConfigFromResource, fieldTreeShape, gdrUri, isNotesEntry, isTodoListEntry, isTodoListItem, modelVersionOf, parseResourceGdr, readsRootDocument, refCanvas, refDashboard, refDataset, refKindAcceptsTypes, refMediaLibrary, releaseDocId, releaseRef, requiredModelFeatures, requiredReaderModel, resourceAliasesToMap, schemaTreeShape, startKindOf } from "./_chunks-es/invariants.js";
4
4
 
@@ -248,6 +248,11 @@ const HIGH_FREQUENCY_SAMPLE_MS = 6e4, WorkflowDefinitionDeployed = defineWorkflo
248
248
  name: "Editorial Workflows Effects Drained",
249
249
  version: 1,
250
250
  description: "A drain pass ran — drainedCount/drainedEffects report the successful dispatches. Unsampled when non-empty (outcome counts must be complete; volume is bounded by real effect work); empty polls are machine-cadence noise, throttled engine-side to at most one per minute"
251
+ }), WorkflowEffectStateReported = defineWorkflowEvent({
252
+ name: "Editorial Workflows Effect State Reported",
253
+ version: 1,
254
+ description: "A running effect handler committed mid-dispatch field state through commitEffectOps. Sampled (high-frequency by design — a dispatch may report many times): an adoption and usage signal, not an exact per-run report count",
255
+ maxSampleRate: HIGH_FREQUENCY_SAMPLE_MS
251
256
  }), WorkflowEffectCompleted = defineWorkflowEvent({
252
257
  name: "Editorial Workflows Effect Completed",
253
258
  version: 1,
@@ -2219,6 +2224,21 @@ class StartNotSettledError extends WorkflowError {
2219
2224
  }
2220
2225
  }
2221
2226
 
2227
+ class StaleEffectClaimError extends WorkflowError {
2228
+ instanceId;
2229
+ effectKey;
2230
+ reason;
2231
+ constructor(args) {
2232
+ super("stale-effect-claim", `Mid-dispatch report for effect entry "${args.effectKey}" on ${args.instanceId} rejected: ${staleClaimDetail(args.reason)}. Nothing was written. Stop reporting from this dispatch — a newer dispatch (or none) owns the entry now.`),
2233
+ this.name = "StaleEffectClaimError", this.instanceId = args.instanceId, this.effectKey = args.effectKey,
2234
+ this.reason = args.reason;
2235
+ }
2236
+ }
2237
+
2238
+ function staleClaimDetail(reason) {
2239
+ return reason === "unclaimed" ? "the entry holds no claim" : reason === "claim-superseded" ? "the claim was taken over by another dispatch" : "the claim's lease has expired";
2240
+ }
2241
+
2222
2242
  const CONCURRENT_COMMIT_MAX_ATTEMPTS = 3;
2223
2243
 
2224
2244
  function lostRaceMessage(args) {
@@ -2275,6 +2295,20 @@ class ConcurrentCompleteEffectError extends WorkflowError {
2275
2295
  }
2276
2296
  }
2277
2297
 
2298
+ class ConcurrentCommitEffectOpsError extends WorkflowError {
2299
+ instanceId;
2300
+ effectKey;
2301
+ attempts;
2302
+ constructor(args) {
2303
+ super("concurrent-commit-effect-ops", lostRaceMessage({
2304
+ what: `Mid-dispatch report for effect entry "${args.effectKey}"`,
2305
+ instanceId: args.instanceId,
2306
+ attempts: args.attempts
2307
+ })), this.name = "ConcurrentCommitEffectOpsError", this.instanceId = args.instanceId,
2308
+ this.effectKey = args.effectKey, this.attempts = args.attempts;
2309
+ }
2310
+ }
2311
+
2278
2312
  class CascadeLimitError extends WorkflowError {
2279
2313
  instanceId;
2280
2314
  limit;
@@ -2609,8 +2643,17 @@ async function applyFieldUpdateWhere(op, ctx) {
2609
2643
  fields: entry.of
2610
2644
  }
2611
2645
  });
2612
- if (merge === null || typeof merge != "object" || Array.isArray(merge)) throw new Error(`field.updateWhere value must resolve to an object of fields to merge (target "${op.target.field}")`);
2613
- requireNoReservedMergeKeys(merge, op);
2646
+ if (merge === null || typeof merge != "object" || Array.isArray(merge)) throw new FieldValueShapeError({
2647
+ entryType: entry._type,
2648
+ entryName: entry.name,
2649
+ mode: "value",
2650
+ issues: [ `field.updateWhere value must resolve to an object of fields to merge (target "${op.target.field}")` ]
2651
+ });
2652
+ requireNoReservedMergeKeys({
2653
+ merge: merge,
2654
+ entry: entry,
2655
+ op: op
2656
+ });
2614
2657
  const matches = await rowMatches({
2615
2658
  where: op.where,
2616
2659
  rows: rows,
@@ -2656,9 +2699,14 @@ function requireMergeableRows(entry, op) {
2656
2699
  if (entry._type !== "array") throw new Error(`${op.type} target ${op.target.scope}:"${op.target.field}" is a ${entry._type} entry — updateWhere merges declared row sub-fields, so it targets \`array\` entries only`);
2657
2700
  }
2658
2701
 
2659
- function requireNoReservedMergeKeys(merge, op) {
2660
- const reserved = [ "_key", "_type" ].filter(key => key in merge);
2661
- if (reserved.length !== 0) throw new Error(`field.updateWhere merge writes the reserved row key${reserved.length === 1 ? "" : "s"} ${reserved.map(k => `"${k}"`).join(", ")} (target "${op.target.field}") — row identity and bookkeeping are engine-stamped, never merged`);
2702
+ function requireNoReservedMergeKeys(args) {
2703
+ const {merge: merge, entry: entry, op: op} = args, reserved = [ "_key", "_type" ].filter(key => key in merge);
2704
+ if (reserved.length !== 0) throw new FieldValueShapeError({
2705
+ entryType: entry._type,
2706
+ entryName: entry.name,
2707
+ mode: "value",
2708
+ issues: [ `field.updateWhere merge writes the reserved row key${reserved.length === 1 ? "" : "s"} ${reserved.map(k => `"${k}"`).join(", ")} (target "${op.target.field}") — row identity and bookkeeping are engine-stamped, never merged` ]
2709
+ });
2662
2710
  }
2663
2711
 
2664
2712
  function requireArrayValue(entry, op) {
@@ -3426,20 +3474,11 @@ function remediationsFor(diagnosis) {
3426
3474
  }));
3427
3475
  }
3428
3476
 
3429
- async function resolveBindings(args) {
3430
- const resolved = {};
3431
- for (const [key, groq] of Object.entries(args.bindings ?? {})) resolved[key] = await runGroq({
3432
- groq: groq,
3433
- params: args.params,
3434
- snapshot: args.snapshot
3435
- });
3436
- return {
3437
- ...resolved,
3438
- ...args.staticInput
3439
- };
3477
+ function isClaimExpired(claim, now) {
3478
+ return claim.leaseExpiresAt === void 0 || hasPassed(claim.leaseExpiresAt, now);
3440
3479
  }
3441
3480
 
3442
- const EXECUTION_KINDS = {
3481
+ const EFFECT_RUN_STATUSES = [ "done", "failed", "cancelled" ], EXECUTION_KINDS = {
3443
3482
  interactive: "interactive",
3444
3483
  server: "server",
3445
3484
  cli: "cli",
@@ -3998,7 +4037,7 @@ function coerceToGdr(raw, workflowResource) {
3998
4037
  } : null;
3999
4038
  }
4000
4039
 
4001
- const EFFECT_RUN_STATUSES = [ "done", "failed", "cancelled" ], NonEmpty = NonEmptyString, UnknownRecord = v.record(v.string(), v.unknown()), PersistedChoiceOptionsSchema = v.looseObject({
4040
+ const NonEmpty = NonEmptyString, UnknownRecord = v.record(v.string(), v.unknown()), PersistedChoiceOptionsSchema = v.looseObject({
4002
4041
  list: v.array(v.looseObject({
4003
4042
  title: v.string(),
4004
4043
  value: v.union([ v.string(), v.number() ])
@@ -4058,7 +4097,7 @@ const OptionalRefTypes = v.exactOptional(v.array(v.string())), ResolvedFieldEntr
4058
4097
  })), v.looseObject(tolerantEntries()({
4059
4098
  ...fieldArm("subject", fieldValueSchemas.subject),
4060
4099
  types: OptionalRefTypes
4061
- })), v.looseObject(tolerantEntries()(fieldArm("release.ref", fieldValueSchemas["release.ref"]))), v.looseObject(tolerantEntries()(fieldArm("string", fieldValueSchemas.string))), v.looseObject(tolerantEntries()(fieldArm("text", fieldValueSchemas.text))), v.looseObject(tolerantEntries()(fieldArm("number", fieldValueSchemas.number))), v.looseObject(tolerantEntries()(fieldArm("boolean", fieldValueSchemas.boolean))), v.looseObject(tolerantEntries()(fieldArm("date", fieldValueSchemas.date))), v.looseObject(tolerantEntries()(fieldArm("datetime", fieldValueSchemas.datetime))), v.looseObject(tolerantEntries()(fieldArm("url", fieldValueSchemas.url))), v.looseObject(tolerantEntries()(fieldArm("actor", fieldValueSchemas.actor))), v.looseObject(tolerantEntries()(fieldArm("assignee", fieldValueSchemas.assignee))), v.looseObject(tolerantEntries()(fieldArm("assignees", fieldValueSchemas.assignees))), v.looseObject(tolerantEntries()({
4100
+ })), v.looseObject(tolerantEntries()(fieldArm("release.ref", fieldValueSchemas["release.ref"]))), v.looseObject(tolerantEntries()(fieldArm("string", fieldValueSchemas.string))), v.looseObject(tolerantEntries()(fieldArm("text", fieldValueSchemas.text))), v.looseObject(tolerantEntries()(fieldArm("number", fieldValueSchemas.number))), v.looseObject(tolerantEntries()(fieldArm("progress", fieldValueSchemas.progress))), v.looseObject(tolerantEntries()(fieldArm("boolean", fieldValueSchemas.boolean))), v.looseObject(tolerantEntries()(fieldArm("date", fieldValueSchemas.date))), v.looseObject(tolerantEntries()(fieldArm("datetime", fieldValueSchemas.datetime))), v.looseObject(tolerantEntries()(fieldArm("url", fieldValueSchemas.url))), v.looseObject(tolerantEntries()(fieldArm("actor", fieldValueSchemas.actor))), v.looseObject(tolerantEntries()(fieldArm("assignee", fieldValueSchemas.assignee))), v.looseObject(tolerantEntries()(fieldArm("assignees", fieldValueSchemas.assignees))), v.looseObject(tolerantEntries()({
4062
4101
  ...fieldArm("object", v.union([ v.null(), UnknownRecord ])),
4063
4102
  fields: v.array(PersistedFieldShapeSchema)
4064
4103
  })), v.looseObject(tolerantEntries()({
@@ -4071,7 +4110,8 @@ const OptionalRefTypes = v.exactOptional(v.array(v.string())), ResolvedFieldEntr
4071
4110
  _type: v.literal("pendingEffect.claim"),
4072
4111
  claimedAt: IsoTimestamp,
4073
4112
  claimedBy: ActorShape,
4074
- leaseExpiresAt: v.exactOptional(IsoTimestamp)
4113
+ leaseExpiresAt: v.exactOptional(IsoTimestamp),
4114
+ claimToken: v.exactOptional(NonEmpty)
4075
4115
  }), PendingEffectSchema = tolerantObject()({
4076
4116
  _key: NonEmpty,
4077
4117
  _type: v.exactOptional(v.literal("pendingEffect")),
@@ -4487,6 +4527,10 @@ function collectEntryDocUris(resolvedFieldEntries) {
4487
4527
  return entryDocRefs(resolvedFieldEntries).map(ref => ref.id);
4488
4528
  }
4489
4529
 
4530
+ function isEngineContext(ctx) {
4531
+ return "client" in ctx;
4532
+ }
4533
+
4490
4534
  function loadCallContext({client: client, instanceId: instanceId, options: options}) {
4491
4535
  return loadContext({
4492
4536
  client: client,
@@ -4662,6 +4706,19 @@ async function resolveActivityFieldEntries(args) {
4662
4706
  });
4663
4707
  }
4664
4708
 
4709
+ async function resolveBindings(args) {
4710
+ const resolved = {};
4711
+ for (const [key, groq] of Object.entries(args.bindings ?? {})) resolved[key] = await runGroq({
4712
+ groq: groq,
4713
+ params: args.params,
4714
+ snapshot: args.snapshot
4715
+ });
4716
+ return {
4717
+ ...resolved,
4718
+ ...args.staticInput
4719
+ };
4720
+ }
4721
+
4665
4722
  const DEFAULT_IDEMPOTENCY_TTL_MS = 1440 * 60 * 1e3;
4666
4723
 
4667
4724
  class RequestAlreadyProcessedError extends Error {
@@ -4836,6 +4893,7 @@ const REQUEST_TAG = {
4836
4893
  fireAction: "workflow.fire-action",
4837
4894
  editField: "workflow.edit-field",
4838
4895
  completeEffect: "workflow.complete-effect",
4896
+ commitEffectOps: "workflow.commit-effect-ops",
4839
4897
  tick: "workflow.tick",
4840
4898
  evaluate: "workflow.evaluate",
4841
4899
  evaluateStart: "workflow.evaluate-start",
@@ -6104,1965 +6162,2134 @@ async function fetchGrants(args) {
6104
6162
  });
6105
6163
  }
6106
6164
 
6107
- const actorCache = /* @__PURE__ */ new WeakMap, grantsCache = /* @__PURE__ */ new WeakMap;
6108
-
6109
- async function resolveAccess(taggedClient, args = {}) {
6110
- const client = unwrapRequestTag(taggedClient), requestFn = lazyRequest(client);
6111
- if (requestFn === void 0) throw new ContractViolationError("workflow: no actor available. The engine resolves the actor from the client's token via `client.request({ uri: '/users/me' })`. Supply a real `@sanity/client` configured with a token (the test bench serves these endpoints per registered token).");
6112
- const grantsPromise = args.grantsFromPath !== void 0 ? cachedGrants({
6113
- client: client,
6114
- requestFn: requestFn,
6115
- resourcePath: args.grantsFromPath
6116
- }) : Promise.resolve(void 0), [actor, grants] = await Promise.all([ cachedActor(client, requestFn), grantsPromise ]);
6117
- if (actor === void 0) throw new ContractViolationError("workflow: failed to resolve actor from `/users/me`. The client is configured but the endpoint returned no usable identity — check the token.");
6118
- return {
6119
- actor: actor,
6120
- ...grants !== void 0 ? {
6121
- grants: grants
6122
- } : {}
6123
- };
6124
- }
6125
-
6126
- function cachedActor(client, requestFn) {
6127
- const cached = actorCache.get(client);
6128
- if (cached !== void 0) return cached;
6129
- const pending = fetchActor(requestFn).catch(err => {
6130
- throw actorCache.get(client) === pending && actorCache.delete(client), err;
6131
- });
6132
- return actorCache.set(client, pending), pending;
6133
- }
6134
-
6135
- function grantsForClientPath(taggedClient, resourcePath) {
6136
- const client = unwrapRequestTag(taggedClient), requestFn = lazyRequest(client);
6137
- return requestFn === void 0 ? Promise.resolve(void 0) : cachedGrants({
6138
- client: client,
6139
- requestFn: requestFn,
6140
- resourcePath: resourcePath
6165
+ async function advisoryCan({instance: instance, actor: actor, grants: grants}) {
6166
+ if (grants === void 0) return;
6167
+ const can = {};
6168
+ for (const permission of DOCUMENT_VALUE_PERMISSIONS) can[permission] = await grantsPermissionOn({
6169
+ document: instance,
6170
+ grants: grants,
6171
+ permission: permission,
6172
+ userId: actor.id
6141
6173
  });
6174
+ return can;
6142
6175
  }
6143
6176
 
6144
- function lazyRequest(client) {
6145
- return client.request === void 0 ? void 0 : opts => client.request(opts);
6146
- }
6147
-
6148
- function cachedGrants({client: client, requestFn: requestFn, resourcePath: resourcePath}) {
6149
- let byPath = grantsCache.get(client);
6150
- byPath === void 0 && (byPath = /* @__PURE__ */ new Map, grantsCache.set(client, byPath));
6151
- let cached = byPath.get(resourcePath);
6152
- return cached === void 0 && (cached = fetchGrantsCached(requestFn, resourcePath),
6153
- byPath.set(resourcePath, cached)), cached;
6177
+ function subjectDenialLabels(denied) {
6178
+ return denied.map(d => `${d.permission} on ${d.subject} (${d.resource})`);
6154
6179
  }
6155
6180
 
6156
- async function fetchActor(requestFn) {
6157
- let user;
6158
- try {
6159
- user = await requestFn({
6160
- uri: "/users/me",
6161
- tag: REQUEST_TAG.accessResolveActor
6162
- });
6163
- } catch (err) {
6164
- throw new Error('workflow: /users/me request failed. The engine resolves the actor from the client\'s token via `client.request({ uri: "/users/me" })`. Check the token/connectivity.', {
6165
- cause: err
6166
- });
6181
+ class ActionDisabledError extends WorkflowError {
6182
+ reason;
6183
+ activity;
6184
+ action;
6185
+ constructor(args) {
6186
+ super("action-disabled", formatDisabledReason({
6187
+ activity: args.activity,
6188
+ action: args.action,
6189
+ reason: args.reason
6190
+ })), this.name = "ActionDisabledError", this.reason = args.reason, this.activity = args.activity,
6191
+ this.action = args.action;
6167
6192
  }
6168
- if (!user || typeof user.id != "string" || user.id.length === 0) return;
6169
- const roleNames = user.roles?.map(r => r.name).filter(n => !!n) ?? [];
6170
- return {
6171
- kind: "person",
6172
- id: user.id,
6173
- ...roleNames.length > 0 ? {
6174
- roles: roleNames
6175
- } : {}
6176
- };
6177
6193
  }
6178
6194
 
6179
- async function fetchGrantsCached(requestFn, resourcePath) {
6180
- try {
6181
- return await fetchGrants({
6182
- client: {
6183
- request: requestFn
6184
- },
6185
- resourcePath: resourcePath
6186
- });
6187
- } catch (err) {
6188
- console.warn(`workflow: failed to fetch grants from "${resourcePath}"; advisory permission reads that depend on them are skipped — a rendered $can stays undefined (conditions referencing it fail closed) and the subject-write forecast omits this resource. The lake still enforces writes. Original error: ${errorMessage(err)}`);
6189
- return;
6195
+ class StartNotAllowedError extends WorkflowError {
6196
+ definition;
6197
+ insight;
6198
+ constructor(args) {
6199
+ super("start-not-allowed", `startInstance refused: start.allowed on definition "${args.definition}" evaluated ` + (args.insight.outcome === "unevaluable" ? `GROQ null ("can't decide" — fail-closed)` : "false") + " for the supplied initialFields. Pre-flight the verdict with evaluateStart."),
6200
+ this.name = "StartNotAllowedError", this.definition = args.definition, this.insight = args.insight;
6190
6201
  }
6191
6202
  }
6192
6203
 
6193
- async function buildFieldInsights({sites: sites, snapshot: snapshot}) {
6194
- const insights = [];
6195
- for (const field of fieldsReadAcross(sites)) {
6196
- const involved = sites.filter(entry => readsField(entry.insight, field));
6197
- insights.push({
6198
- field: field,
6199
- reads: dedupeReads(involved.flatMap(entry => fieldReads(entry.insight, field))),
6200
- involvedIn: involved.map(entry => entry.site),
6201
- proposals: await verifyProposals({
6202
- field: field,
6203
- involved: involved,
6204
- snapshot: snapshot
6205
- })
6206
- });
6207
- }
6208
- return insights;
6204
+ function actionRendering(action) {
6205
+ const kind = action.disabledReason?.kind;
6206
+ return kind === "filter-failed" ? "absent" : action.triggered === !0 || kind === "cascade-fired" ? "automation" : "button";
6209
6207
  }
6210
6208
 
6211
- function fieldsReadAcross(sites) {
6212
- const names = sites.flatMap(entry => entry.insight.analysis.reads).filter(read => read.variable === "fields").map(read => read.path[0]).filter(head => typeof head == "string");
6213
- return [ ...new Set(names) ];
6214
- }
6209
+ const disabledReasonDetail = {
6210
+ "filter-failed": r => `action filter returned false${r.detail ? ` (${r.detail})` : ""}`,
6211
+ "cascade-fired": r => `the action is cascade-fired (when: ${JSON.stringify(r.when)}) — the engine fires it on truth; it cannot be invoked via fireAction`,
6212
+ "activity-not-active": r => `activity status is "${r.status}"`,
6213
+ "stage-terminal": r => `stage "${r.stage}" is terminal`,
6214
+ "instance-completed": r => `instance completed at ${r.completedAt}`,
6215
+ "instance-aborted": r => `instance aborted at ${r.abortedAt}`,
6216
+ "requirements-unmet": r => `unmet requirement(s): ${r.unmetRequirements.join(", ")}`,
6217
+ "subject-permission-denied": r => `missing subject permission(s): ${subjectDenialLabels(r.denied).join(", ")}`
6218
+ };
6215
6219
 
6216
- function fieldReads(insight, field) {
6217
- return insight.analysis.reads.filter(read => read.variable === "fields" && read.path[0] === field);
6220
+ function actionDisabledDetail(reason) {
6221
+ return disabledReasonDetail[reason.kind](reason);
6218
6222
  }
6219
6223
 
6220
- function readsField(insight, field) {
6221
- return fieldReads(insight, field).length > 0;
6224
+ function formatDisabledReason({activity: activity, action: action, reason: reason}) {
6225
+ return `Action "${activity}:${action}" is not allowed: ${actionDisabledDetail(reason)}`;
6222
6226
  }
6223
6227
 
6224
- async function verifyProposals({field: field, involved: involved, snapshot: snapshot}) {
6225
- const proposals = [];
6226
- for (const assign of candidateAssignments(involved, field)) {
6227
- const consequences = [];
6228
- for (const entry of involved) {
6229
- const result = await whatIfCondition({
6230
- condition: entry.condition,
6231
- dataset: snapshot.docs,
6232
- params: entry.params,
6233
- assign: assign
6234
- });
6235
- result.changed && consequences.push({
6236
- site: entry.site,
6237
- before: result.before,
6238
- after: result.after
6239
- });
6240
- }
6241
- consequences.length > 0 && proposals.push({
6242
- assign: assign,
6243
- consequences: consequences
6244
- });
6228
+ class EditFieldDeniedError extends WorkflowError {
6229
+ reason;
6230
+ target;
6231
+ constructor(args) {
6232
+ super("edit-field-denied", formatEditDisabledReason(args.target, args.reason)),
6233
+ this.name = "EditFieldDeniedError", this.reason = args.reason, this.target = args.target;
6245
6234
  }
6246
- return proposals;
6247
6235
  }
6248
6236
 
6249
- function candidateAssignments(involved, field) {
6250
- const candidates = involved.flatMap(entry => entry.insight.blockedBy).flatMap(atom => atom.requirement !== void 0 ? [ atom.requirement ] : []).filter(requirement => requirement.target.variable === "fields").filter(requirement => requirement.target.path[0] === field).flatMap(assignmentFor).filter(fabricatable);
6251
- return dedupeBy(candidates, assignment => JSON.stringify([ assignment.target.path, assignment.value ]));
6252
- }
6237
+ const editDisabledReasonDetail = {
6238
+ "not-editable": () => "field is not declared editable",
6239
+ "instance-completed": r => `instance completed at ${r.completedAt}`,
6240
+ "instance-aborted": r => `instance aborted at ${r.abortedAt}`,
6241
+ "edit-window-closed": r => `edit window closed (${r.detail})`,
6242
+ "editor-not-permitted": r => `editor not permitted (${r.predicate})`
6243
+ };
6253
6244
 
6254
- function assignmentFor(requirement) {
6255
- switch (requirement.kind) {
6256
- case "equals":
6257
- return [ {
6258
- target: requirement.target,
6259
- value: requirement.value
6260
- } ];
6245
+ function formatEditDisabledReason(target, reason) {
6246
+ const detail = editDisabledReasonDetail[reason.kind](reason), where = target.activity !== void 0 ? `${target.activity}.${target.field}` : target.field;
6247
+ return `Field "${target.scope}:${where}" is not editable: ${detail}`;
6248
+ }
6261
6249
 
6262
- case "truthy":
6263
- return [ {
6264
- target: requirement.target,
6265
- value: !0
6266
- } ];
6267
-
6268
- case "falsy":
6269
- return [ {
6270
- target: requirement.target,
6271
- value: !1
6272
- } ];
6273
-
6274
- case "differs":
6275
- case "defined":
6276
- case "undefined":
6277
- case "compares":
6278
- return [];
6279
- }
6280
- }
6281
-
6282
- function fabricatable(assignment) {
6283
- return assignment.target.path.every(segment => typeof segment != "number" || segment <= MAX_COUNTERFACTUAL_INDEX);
6250
+ async function fireAction(args) {
6251
+ const {client: client, instanceId: instanceId, activity: activity, action: action, params: params, requestRecord: requestRecord, options: options} = args;
6252
+ return retryOnRevisionConflict({
6253
+ client: client,
6254
+ instanceId: instanceId,
6255
+ options: options,
6256
+ commit: ctx => commitAction({
6257
+ ctx: ctx,
6258
+ activityName: activity,
6259
+ actionName: action,
6260
+ callerParams: params,
6261
+ requestRecord: requestRecord,
6262
+ options: options
6263
+ }),
6264
+ onExhausted: () => new ConcurrentFireActionError({
6265
+ instanceId: instanceId,
6266
+ activity: activity,
6267
+ action: action,
6268
+ attempts: CONCURRENT_COMMIT_MAX_ATTEMPTS
6269
+ })
6270
+ });
6284
6271
  }
6285
6272
 
6286
- async function subjectResourceGrants(args) {
6287
- const {clientForGdr: clientForGdr, instance: instance} = args, clients = /* @__PURE__ */ new Map;
6288
- for (const {parsed: parsed, resource: resource} of foreignSubjectRefs(instance)) {
6289
- const key = resourceGdr(resource);
6290
- clients.has(key) || clients.set(key, {
6291
- resource: resource,
6292
- client: clientForGdr(parsed)
6273
+ async function resolveActionCommit({ctx: ctx, activityName: activityName, actionName: actionName, callerParams: callerParams, options: options}) {
6274
+ const actor = options?.actor, {stage: stage, activity: activity} = findActivityInCurrentStage(ctx, activityName), action = (activity.actions ?? []).find(a => a.name === actionName);
6275
+ if (action === void 0) throw new ContractViolationError(`Action "${actionName}" not declared on activity "${activityName}"`);
6276
+ if (action.when !== void 0) throw new ActionDisabledError({
6277
+ activity: activityName,
6278
+ action: actionName,
6279
+ reason: {
6280
+ kind: "cascade-fired",
6281
+ when: action.when
6282
+ }
6283
+ });
6284
+ if (action.filter !== void 0) {
6285
+ const can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan({
6286
+ instance: ctx.instance,
6287
+ actor: actor,
6288
+ grants: options.grants
6289
+ }) : void 0;
6290
+ if (!await ctxEvaluateCondition({
6291
+ ctx: ctx,
6292
+ condition: action.filter,
6293
+ opts: {
6294
+ activityName: activityName,
6295
+ ...actor !== void 0 ? {
6296
+ actor: actor
6297
+ } : {},
6298
+ ...can !== void 0 ? {
6299
+ vars: {
6300
+ can: can
6301
+ }
6302
+ } : {}
6303
+ }
6304
+ })) throw new ActionDisabledError({
6305
+ activity: activityName,
6306
+ action: actionName,
6307
+ reason: {
6308
+ kind: "filter-failed",
6309
+ filter: action.filter,
6310
+ detail: "commit re-check"
6311
+ }
6293
6312
  });
6294
6313
  }
6295
- const resolved = await Promise.all([ ...clients.entries() ].map(async ([key, entry]) => {
6296
- const path = aclPathForResource(entry.resource);
6297
- if (path === void 0) return;
6298
- const grants = await grantsForClientPath(entry.client, path);
6299
- return grants === void 0 ? void 0 : [ key, grants ];
6300
- }));
6301
- return new Map(resolved.filter(entry => entry !== void 0));
6302
- }
6303
-
6304
- async function evaluateInstance(args) {
6305
- const {client: client, tag: tag, workflowResource: workflowResource, instanceId: instanceId, resourceClients: resourceClients} = args, now = (args.clock ?? wallClock)();
6306
- validateTag(tag);
6307
- const {actor: actor, grants: grants} = await resolveAccess(client, {
6308
- ...args.grantsFromPath !== void 0 ? {
6309
- grantsFromPath: args.grantsFromPath
6310
- } : {}
6311
- }), instance = await reload({
6312
- client: client,
6313
- instanceId: instanceId,
6314
- tag: tag
6315
- }), definition = parseDefinitionSnapshot(instance), clientForGdr = buildClientForGdr({
6316
- client: client,
6317
- workflowResource: workflowResource,
6318
- resourceClients: resourceClients
6319
- }), snapshot = await hydrateSnapshot({
6320
- client: client,
6321
- clientForGdr: clientForGdr,
6322
- instance: instance
6323
- }), guards = await verdictGuardsForInstance(client, instance._id), resourceGrants = await subjectResourceGrants({
6324
- clientForGdr: clientForGdr,
6325
- instance: instance
6314
+ const entry = findCurrentActivities(ctx.instance).find(t => t.name === activityName);
6315
+ if (entry === void 0 || entry.status !== "active") throw notActiveAtCommit({
6316
+ activityName: activityName,
6317
+ actionName: actionName,
6318
+ status: entry?.status
6326
6319
  });
6327
- return evaluateFromSnapshot({
6328
- instance: instance,
6329
- definition: definition,
6330
- actor: actor,
6331
- snapshot: snapshot,
6332
- guards: guards,
6333
- now: now,
6334
- resourceGrants: resourceGrants,
6335
- ...grants !== void 0 ? {
6336
- grants: grants
6337
- } : {}
6320
+ const params = validateActionParams({
6321
+ action: action,
6322
+ activityName: activityName,
6323
+ callerParams: callerParams
6338
6324
  });
6339
- }
6340
-
6341
- function memoizedByName(render) {
6342
- const rendered = /* @__PURE__ */ new Map;
6343
- return activityName => {
6344
- const hit = rendered.get(activityName);
6345
- if (hit !== void 0) return hit;
6346
- const scope = render(activityName);
6347
- return rendered.set(activityName, scope), scope;
6325
+ return {
6326
+ stage: stage,
6327
+ activity: activity,
6328
+ action: action,
6329
+ params: params
6348
6330
  };
6349
6331
  }
6350
6332
 
6351
- function currentStageOf(instance, definition) {
6352
- try {
6353
- return findStage(definition, instance.currentStage);
6354
- } catch (err) {
6355
- throw new Error(`Instance "${instance._id}" currentStage "${instance.currentStage}" not in definition`, {
6356
- cause: err
6357
- });
6358
- }
6333
+ function notActiveAtCommit(args) {
6334
+ const {activityName: activityName, actionName: actionName, status: status} = args;
6335
+ return status !== void 0 && isTerminalActivityStatus(status) ? new ActionDisabledError({
6336
+ activity: activityName,
6337
+ action: actionName,
6338
+ reason: {
6339
+ kind: "activity-not-active",
6340
+ status: status
6341
+ }
6342
+ }) : new Error(`Activity "${activityName}" must be active to fire action "${actionName}"; status is ${status ?? "missing"}`);
6359
6343
  }
6360
6344
 
6361
- async function explainSite(args) {
6362
- const {site: site, condition: condition, params: params, snapshot: snapshot, sites: sites} = args, insight = await explainCondition({
6363
- condition: condition,
6364
- dataset: snapshot.docs,
6365
- params: params
6345
+ async function applyActionFire({ctx: ctx, mutation: mutation, activity: activity, action: action, params: params, actor: actor, triggered: triggered}) {
6346
+ mutation.history.push({
6347
+ _key: randomKey(),
6348
+ _type: "actionFired",
6349
+ at: ctx.now,
6350
+ stage: mutation.currentStage,
6351
+ activity: activity.name,
6352
+ action: action.name,
6353
+ ...actor !== void 0 ? {
6354
+ actor: actor,
6355
+ driverKind: driverKind(actor)
6356
+ } : {},
6357
+ ...triggered ? {
6358
+ triggered: !0
6359
+ } : {}
6366
6360
  });
6367
- return sites.push({
6368
- site: site,
6369
- condition: condition,
6361
+ const ranOps = await runOps({
6362
+ ops: action.ops,
6363
+ mutation: mutation,
6364
+ stage: mutation.currentStage,
6365
+ origin: {
6366
+ activity: activity.name,
6367
+ action: action.name
6368
+ },
6370
6369
  params: params,
6371
- insight: insight
6372
- }), insight;
6373
- }
6374
-
6375
- async function evaluateFromSnapshot(args) {
6376
- const {instance: instance, definition: definition, actor: actor, grants: grants, snapshot: snapshot} = args, now = args.now ?? wallClock(), stage = currentStageOf(instance, definition), autonomy = autonomyOf(definition), stageAutonomy2 = stageAutonomyOf(autonomy, stage.name), scopeSource = {
6377
- instance: instance,
6378
- definition: definition,
6379
- snapshot: snapshot,
6380
- now: now
6381
- }, can = await advisoryCan({
6382
- instance: instance,
6383
6370
  actor: actor,
6384
- grants: grants
6385
- }), scope = await renderConditionScope(scopeSource, {
6371
+ self: selfGdr(ctx.instance),
6372
+ now: ctx.now,
6373
+ snapshot: ctx.snapshot,
6374
+ refSurface: ctx.refSurface
6375
+ });
6376
+ if (await queueEffects({
6377
+ ctx: ctx,
6378
+ mutation: mutation,
6379
+ effects: action.effects,
6380
+ origin: {
6381
+ kind: "action",
6382
+ name: action.name
6383
+ },
6386
6384
  actor: actor,
6387
- vars: {
6388
- can: can
6385
+ opts: {
6386
+ callerParams: params,
6387
+ activityName: activity.name
6389
6388
  }
6390
- }), scopeForActivity = memoizedByName(activityName => renderConditionScope(scopeSource, {
6389
+ }), action.spawn !== void 0) {
6390
+ if (!isEngineContext(ctx)) throw new ContractViolationError(`Action "${action.name}" declares spawn, which requires a lake-capable engine context`);
6391
+ await spawnSubworkflows({
6392
+ ctx: ctx,
6393
+ mutation: mutation,
6394
+ activity: activity,
6395
+ action: action,
6396
+ sub: action.spawn,
6397
+ actor: actor
6398
+ });
6399
+ }
6400
+ return {
6401
+ ranOps: ranOps
6402
+ };
6403
+ }
6404
+
6405
+ async function commitAction({ctx: ctx, activityName: activityName, actionName: actionName, callerParams: callerParams, requestRecord: requestRecord, options: options}) {
6406
+ assertRequestUnprocessed({
6407
+ instance: ctx.instance,
6408
+ record: requestRecord,
6409
+ now: ctx.now
6410
+ });
6411
+ const actor = options?.actor, {stage: stage, activity: activity, action: action, params: params} = await resolveActionCommit({
6412
+ ctx: ctx,
6391
6413
  activityName: activityName,
6392
- actor: actor,
6393
- vars: {
6394
- can: can
6395
- }
6396
- })), cascadeScopeForActivity = memoizedByName(activityName => renderConditionScope(scopeSource, {
6397
- activityName: activityName
6398
- })), currentActivityEntries = findOpenStageEntry(instance)?.activities ?? [], guardDenial = await instanceGuardReason({
6399
- instance: instance,
6400
- actor: actor,
6401
- guards: args.guards
6402
- }), subjectDenials = await forecastSubjectDenials({
6403
- instance: instance,
6404
- actor: actor,
6405
- snapshot: snapshot,
6406
- resourceGrants: args.resourceGrants
6407
- }), subjectPermissionReason = subjectDenials.length > 0 ? {
6408
- kind: "subject-permission-denied",
6409
- denied: subjectDenials
6410
- } : void 0, sites = [], activityEvaluations = [];
6411
- for (const activity of stage.activities ?? []) activityEvaluations.push(await evaluateActivity({
6412
- activity: activity,
6413
- statusEntry: currentActivityEntries.find(t => t.name === activity.name),
6414
- instance: instance,
6415
- snapshot: snapshot,
6416
- activityScope: await scopeForActivity(activity.name),
6417
- cascadeActivityScope: () => cascadeScopeForActivity(activity.name),
6418
- stageHasExits: !isTerminalStage(stage),
6419
- guardDenial: guardDenial,
6420
- subjectPermissionReason: subjectPermissionReason,
6421
- sites: sites,
6422
- autonomy: activityAutonomyOf(stageAutonomy2, activity.name)
6423
- }));
6424
- const cascadeParams = await renderConditionScope(scopeSource), transitionEvaluations = [];
6425
- for (const transition of stage.transitions ?? []) {
6426
- const insight = await explainSite({
6427
- site: {
6428
- kind: "transition",
6429
- transition: transition.name
6430
- },
6431
- condition: transition.when,
6432
- params: cascadeParams,
6433
- snapshot: snapshot,
6434
- sites: sites
6435
- });
6436
- transitionEvaluations.push({
6437
- transition: transition,
6438
- whenSatisfied: insight.outcome === "satisfied",
6439
- unevaluable: insight.outcome === "unevaluable",
6440
- insight: insight
6441
- });
6442
- }
6443
- const currentStage = {
6444
- stage: stage,
6445
- activities: activityEvaluations,
6446
- transitions: transitionEvaluations,
6447
- autonomy: stageAutonomy2
6448
- }, pendingOnYou = activityEvaluations.filter(t => t.pendingOnActor), canInteract = activityEvaluations.some(t => t.actions.some(a => a.allowed)), editGuardDenial = guardDenial?.kind === "mutation-guard-denied" ? guardDenial : void 0, editableFields = await evaluateEditableFields({
6449
- instance: instance,
6450
- definition: definition,
6451
- stage: stage,
6452
- snapshot: snapshot,
6453
- scope: scope,
6454
- scopeForActivity: scopeForActivity,
6455
- guardDenial: editGuardDenial,
6456
- sites: sites
6414
+ actionName: actionName,
6415
+ callerParams: callerParams,
6416
+ options: options
6417
+ }), mutation = startMutation(ctx.instance);
6418
+ recordProcessedRequest({
6419
+ mutation: mutation,
6420
+ record: requestRecord,
6421
+ now: ctx.now
6457
6422
  });
6458
- return {
6459
- instance: instance,
6460
- definition: definition,
6423
+ const mutEntry = requireMutationActivityEntry(mutation, activityName), statusBefore = mutEntry.status, {ranOps: ranOps} = await applyActionFire({
6424
+ ctx: ctx,
6425
+ mutation: mutation,
6426
+ activity: activity,
6427
+ action: action,
6428
+ params: params,
6461
6429
  actor: actor,
6462
- currentStage: currentStage,
6463
- pendingOnYou: pendingOnYou,
6464
- canInteract: canInteract,
6465
- editableFields: editableFields,
6466
- fieldInsights: await buildFieldInsights({
6467
- sites: sites,
6468
- snapshot: snapshot
6469
- }),
6470
- autonomy: autonomy
6430
+ triggered: !1
6431
+ }), newStatus = mutEntry.status !== statusBefore ? mutEntry.status : void 0;
6432
+ return await persistThenMaybeRefresh({
6433
+ ctx: ctx,
6434
+ mutation: mutation,
6435
+ stageName: stage.name,
6436
+ didChangeState: ranOps.some(isFieldOp)
6437
+ }), {
6438
+ fired: !0,
6439
+ activity: activityName,
6440
+ action: actionName,
6441
+ ...newStatus !== void 0 ? {
6442
+ newStatus: newStatus
6443
+ } : {},
6444
+ ...ranOps.length > 0 ? {
6445
+ ranOps: ranOps
6446
+ } : {}
6471
6447
  };
6472
6448
  }
6473
6449
 
6474
- const AUTONOMY_CACHE = /* @__PURE__ */ new WeakMap;
6475
-
6476
- function autonomyOf(definition) {
6477
- const hit = AUTONOMY_CACHE.get(definition);
6478
- if (hit !== void 0) return hit;
6479
- const derived = deriveWorkflowAutonomy(definition);
6480
- return AUTONOMY_CACHE.set(definition, derived), derived;
6450
+ async function runTriggeredActions({ctx: ctx, mutation: mutation, stage: stage}) {
6451
+ let fires = 0, ranFieldOps = !1;
6452
+ const liveCtx = liveViewContext(ctx, mutation);
6453
+ let firedThisPass = !0;
6454
+ for (;firedThisPass; ) {
6455
+ firedThisPass = !1;
6456
+ for (const activity of stage.activities ?? []) {
6457
+ const fired = await runActivityTriggers({
6458
+ ctx: liveCtx,
6459
+ mutation: mutation,
6460
+ activity: activity
6461
+ });
6462
+ fires += fired.fires, ranFieldOps = ranFieldOps || fired.ranFieldOps, fired.fires > 0 && (firedThisPass = !0);
6463
+ }
6464
+ }
6465
+ return {
6466
+ fires: fires,
6467
+ ranFieldOps: ranFieldOps
6468
+ };
6481
6469
  }
6482
6470
 
6483
- async function evaluateEditableFields(args) {
6484
- const {instance: instance, definition: definition, stage: stage, snapshot: snapshot, scope: scope, scopeForActivity: scopeForActivity, guardDenial: guardDenial, sites: sites} = args, fields = [];
6485
- for (const site of editableFieldsInStage(definition, stage)) {
6486
- const window = fieldWindowOpen(instance, site), insight = await editPredicateInsight({
6487
- site: site,
6488
- snapshot: snapshot,
6489
- scope: scope,
6490
- scopeForActivity: scopeForActivity,
6491
- sites: sites
6492
- }), predicateSatisfied = insight === void 0 || insight.outcome === "satisfied", reason = editDisabledReason({
6493
- effective: site.effective,
6494
- instance: instance,
6495
- window: window,
6496
- guardDenial: guardDenial,
6497
- predicateSatisfied: predicateSatisfied
6498
- }), value = readFieldValue(instance, site);
6499
- fields.push({
6500
- scope: site.scope,
6501
- ...site.activity !== void 0 ? {
6502
- activity: site.activity
6503
- } : {},
6504
- name: site.name,
6505
- type: site.type,
6506
- ...site.title !== void 0 ? {
6507
- title: site.title
6508
- } : {},
6509
- ...site.validation !== void 0 ? {
6510
- validation: site.validation
6511
- } : {},
6512
- value: value,
6513
- editable: reason === void 0,
6514
- ...reason !== void 0 ? {
6515
- disabledReason: reason
6516
- } : {},
6517
- ...fieldProvenance(instance, site.ref),
6518
- ...insight !== void 0 ? {
6519
- insight: insight
6520
- } : {}
6471
+ async function runActivityTriggers({ctx: ctx, mutation: mutation, activity: activity}) {
6472
+ let fires = 0, ranFieldOps = !1;
6473
+ for (const action of activity.actions ?? []) {
6474
+ if (!isCascadeFired(action)) continue;
6475
+ const entry = currentActivities(mutation).find(e => e.name === activity.name);
6476
+ if (entry === void 0 || entry.status !== "active") break;
6477
+ if ((entry.firedActions ?? []).includes(action.name) || !await triggerIsLive({
6478
+ ctx: ctx,
6479
+ activity: activity,
6480
+ action: action
6481
+ })) continue;
6482
+ const result = await fireTriggeredAction({
6483
+ ctx: ctx,
6484
+ mutation: mutation,
6485
+ activity: activity,
6486
+ action: action,
6487
+ entry: entry
6521
6488
  });
6489
+ if (fires++, ranFieldOps = ranFieldOps || result.ranFieldOps, isTerminalActivityStatus(entry.status)) break;
6522
6490
  }
6523
- return fields;
6491
+ return {
6492
+ fires: fires,
6493
+ ranFieldOps: ranFieldOps
6494
+ };
6524
6495
  }
6525
6496
 
6526
- async function editPredicateInsight(args) {
6527
- const {site: site, snapshot: snapshot, scope: scope, scopeForActivity: scopeForActivity, sites: sites} = args;
6528
- if (typeof site.effective != "string") return;
6529
- const params = site.scope === "activity" && site.activity !== void 0 ? await scopeForActivity(site.activity) : scope;
6530
- return explainSite({
6531
- site: {
6532
- kind: "editable-field",
6533
- scope: site.scope,
6534
- name: site.name,
6535
- ...site.activity !== void 0 ? {
6536
- activity: site.activity
6537
- } : {}
6538
- },
6539
- condition: site.effective,
6540
- params: params,
6541
- snapshot: snapshot,
6542
- sites: sites
6543
- });
6497
+ async function triggerIsLive({ctx: ctx, activity: activity, action: action}) {
6498
+ if (!tokenMayExecute({
6499
+ actor: ctx.actor,
6500
+ action: action,
6501
+ ctx: ctx
6502
+ })) return !1;
6503
+ const scope = {
6504
+ activityName: activity.name
6505
+ };
6506
+ return action.filter !== void 0 && await ctxEvaluateConditionOutcome({
6507
+ ctx: ctx,
6508
+ condition: action.filter,
6509
+ opts: scope
6510
+ }) !== "satisfied" ? !1 : await ctxEvaluateConditionOutcome({
6511
+ ctx: ctx,
6512
+ condition: action.when,
6513
+ opts: scope
6514
+ }) === "satisfied";
6544
6515
  }
6545
6516
 
6546
- async function advisoryCan({instance: instance, actor: actor, grants: grants}) {
6547
- if (grants === void 0) return;
6548
- const can = {};
6549
- for (const permission of DOCUMENT_VALUE_PERMISSIONS) can[permission] = await grantsPermissionOn({
6550
- document: instance,
6551
- grants: grants,
6552
- permission: permission,
6553
- userId: actor.id
6517
+ function tokenMayExecute({actor: actor, action: action, ctx: ctx}) {
6518
+ const roles = action.roles;
6519
+ return roles === void 0 || roles.length === 0 ? !0 : actor === void 0 ? !1 : roles.some(required => actorFulfillsRole({
6520
+ actorRoles: actor.roles,
6521
+ required: required,
6522
+ aliases: ctx.definition.roleAliases
6523
+ }));
6524
+ }
6525
+
6526
+ async function fireTriggeredAction({ctx: ctx, mutation: mutation, activity: activity, action: action, entry: entry}) {
6527
+ entry.firedActions = [ ...entry.firedActions ?? [], action.name ];
6528
+ const {ranOps: ranOps} = await applyActionFire({
6529
+ ctx: ctx,
6530
+ mutation: mutation,
6531
+ activity: activity,
6532
+ action: action,
6533
+ params: {},
6534
+ actor: ctx.actor,
6535
+ triggered: !0
6554
6536
  });
6555
- return can;
6537
+ return {
6538
+ ranFieldOps: ranOps.some(isFieldOp)
6539
+ };
6556
6540
  }
6557
6541
 
6558
- async function forecastSubjectDenials(args) {
6559
- const {instance: instance, actor: actor, snapshot: snapshot, resourceGrants: resourceGrants} = args;
6560
- if (resourceGrants === void 0 || resourceGrants.size === 0) return [];
6561
- const denials = [], seen = /* @__PURE__ */ new Set;
6562
- for (const {ref: ref, parsed: parsed, resource: resource} of foreignSubjectRefs(instance)) {
6563
- if (seen.has(ref.id)) continue;
6564
- seen.add(ref.id);
6565
- const grants = resourceGrants.get(resourceGdr(resource));
6566
- if (grants === void 0) continue;
6567
- const doc = snapshot.docs.find(d => d._id === ref.id);
6568
- doc !== void 0 && await subjectUpdateAllowed({
6569
- doc: doc,
6570
- parsed: parsed,
6571
- grants: grants,
6572
- actorId: actor.id
6573
- }) === !1 && denials.push({
6574
- subject: ref.id,
6575
- resource: resourceGdr(resource),
6576
- permission: "update"
6542
+ async function primeInitialStage({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, clock: clock, executionContext: executionContext, telemetry: telemetry}) {
6543
+ const instance = await getInstanceDocument(client, instanceId);
6544
+ if (!instance || instance.stages.length > 0) return;
6545
+ const definition = parseDefinitionSnapshot(instance), stage = definition.stages.find(s => s.name === instance.currentStage);
6546
+ if (stage === void 0) return;
6547
+ const ctx = await buildEngineContext({
6548
+ client: client,
6549
+ clientForGdr: clientForGdr,
6550
+ refSurface: refSurface,
6551
+ instance: instance,
6552
+ definition: definition,
6553
+ ...clock ? {
6554
+ clock: clock
6555
+ } : {},
6556
+ ...actor ? {
6557
+ actor: actor
6558
+ } : {},
6559
+ ...executionContext ? {
6560
+ executionContext: executionContext
6561
+ } : {},
6562
+ ...telemetry ? {
6563
+ telemetry: telemetry
6564
+ } : {}
6565
+ }), now = ctx.now, discards = [], initialStageEntry = {
6566
+ _key: randomKey(),
6567
+ name: stage.name,
6568
+ enteredAt: now,
6569
+ fields: await resolveStageFieldEntries({
6570
+ client: client,
6571
+ instance: instance,
6572
+ stage: stage,
6573
+ now: now,
6574
+ refSurface: refSurface,
6575
+ recordDiscard: recordFieldDiscards({
6576
+ target: discards,
6577
+ scope: "stage",
6578
+ at: now
6579
+ })
6580
+ }),
6581
+ activities: []
6582
+ }, primedCtx = {
6583
+ ...ctx,
6584
+ instance: {
6585
+ ...instance,
6586
+ stages: [ initialStageEntry ]
6587
+ }
6588
+ };
6589
+ initialStageEntry.activities = await buildStageActivities({
6590
+ ctx: primedCtx,
6591
+ stage: stage,
6592
+ recordDiscard: recordFieldDiscards({
6593
+ target: discards,
6594
+ scope: "activity",
6595
+ at: now
6596
+ })
6597
+ });
6598
+ const terminal = isTerminalStage(stage), committed = await client.patch(instance._id).set({
6599
+ stages: [ initialStageEntry ],
6600
+ lastChangedAt: now,
6601
+ ...terminal ? {
6602
+ completedAt: now
6603
+ } : {},
6604
+ ...discards.length > 0 ? {
6605
+ history: [ ...instance.history, ...stampHistoryEntries(discards, ctx.executionContext) ]
6606
+ } : {}
6607
+ }).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
6608
+ await deployOrRollback({
6609
+ client: client,
6610
+ instanceId: instance._id,
6611
+ committedRev: committed._rev,
6612
+ restore: {
6613
+ stages: instance.stages,
6614
+ history: instance.history
6615
+ },
6616
+ ...terminal ? {
6617
+ unset: [ "completedAt" ]
6618
+ } : {},
6619
+ reversible: !0,
6620
+ deploy: () => deployStageGuards({
6621
+ client: client,
6622
+ clientForGdr: clientForGdr,
6623
+ instance: instance,
6624
+ definition: definition,
6625
+ stageName: stage.name,
6626
+ now: now,
6627
+ snapshot: ctx.snapshot
6628
+ })
6629
+ });
6630
+ }
6631
+
6632
+ const CASCADE_LIMIT = 100;
6633
+
6634
+ async function runCascadeHop({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, clock: clock, executionContext: executionContext, telemetry: telemetry, overlay: overlay}) {
6635
+ const ctx = await loadContext({
6636
+ client: client,
6637
+ instanceId: instanceId,
6638
+ options: {
6639
+ clientForGdr: clientForGdr,
6640
+ refSurface: refSurface,
6641
+ ...actor ? {
6642
+ actor: actor
6643
+ } : {},
6644
+ ...clock ? {
6645
+ clock: clock
6646
+ } : {},
6647
+ ...executionContext ? {
6648
+ executionContext: executionContext
6649
+ } : {},
6650
+ ...telemetry ? {
6651
+ telemetry: telemetry
6652
+ } : {},
6653
+ ...overlay ? {
6654
+ overlay: overlay
6655
+ } : {}
6656
+ }
6657
+ });
6658
+ if (isTerminal(ctx)) return {
6659
+ moved: !1
6660
+ };
6661
+ const stage = findStage(ctx.definition, ctx.instance.currentStage), mutation = startMutation(ctx.instance), fired = await runTriggeredActions({
6662
+ ctx: ctx,
6663
+ mutation: mutation,
6664
+ stage: stage
6665
+ }), hopCtx = liveViewContext(ctx, mutation), transition = await pickTransition(hopCtx, stage);
6666
+ return transition === void 0 ? (fired.fires > 0 && await persistThenMaybeRefresh({
6667
+ ctx: ctx,
6668
+ mutation: mutation,
6669
+ stageName: stage.name,
6670
+ didChangeState: fired.ranFieldOps
6671
+ }), {
6672
+ moved: !1
6673
+ }) : (await commitStageMove({
6674
+ ctx: ctx,
6675
+ mutation: mutation,
6676
+ fromStage: stage,
6677
+ toStage: findStage(ctx.definition, transition.to),
6678
+ transition: transition.name,
6679
+ via: "transition",
6680
+ actor: actor
6681
+ }), {
6682
+ moved: !0
6683
+ });
6684
+ }
6685
+
6686
+ async function cascadeAutoTransitions({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, clock: clock, executionContext: executionContext, telemetry: telemetry, overlay: overlay}) {
6687
+ let count = 0;
6688
+ for (;;) {
6689
+ if (await drainCondemnedChildren({
6690
+ client: client,
6691
+ instanceId: instanceId,
6692
+ actor: actor,
6693
+ clientForGdr: clientForGdr,
6694
+ refSurface: refSurface,
6695
+ clock: clock,
6696
+ executionContext: executionContext,
6697
+ telemetry: telemetry
6698
+ }), !(await runCascadeHop({
6699
+ client: client,
6700
+ instanceId: instanceId,
6701
+ actor: actor,
6702
+ clientForGdr: clientForGdr,
6703
+ refSurface: refSurface,
6704
+ clock: clock,
6705
+ executionContext: executionContext,
6706
+ telemetry: telemetry,
6707
+ overlay: overlay
6708
+ })).moved) return count;
6709
+ if (count++, count >= CASCADE_LIMIT) throw new CascadeLimitError({
6710
+ instanceId: instanceId,
6711
+ limit: CASCADE_LIMIT
6577
6712
  });
6578
6713
  }
6579
- return denials;
6580
6714
  }
6581
6715
 
6582
- async function subjectUpdateAllowed(args) {
6583
- const {doc: doc, parsed: parsed, grants: grants, actorId: actorId} = args;
6716
+ async function drainCondemnedChildren({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, clock: clock, executionContext: executionContext, telemetry: telemetry, draining: draining = /* @__PURE__ */ new Set}) {
6717
+ if (draining.has(instanceId)) return;
6718
+ draining.add(instanceId);
6719
+ const instance = await getInstanceDocument(client, instanceId);
6720
+ if (!instance) return;
6721
+ const condemned = condemnedSubworkflows(instance);
6722
+ if (condemned.length !== 0) {
6723
+ for (const row of condemned) await settleCondemnedRow({
6724
+ client: client,
6725
+ ownerId: instanceId,
6726
+ row: row,
6727
+ actor: actor,
6728
+ clientForGdr: clientForGdr,
6729
+ refSurface: refSurface,
6730
+ clock: clock,
6731
+ executionContext: executionContext,
6732
+ telemetry: telemetry,
6733
+ draining: draining
6734
+ });
6735
+ await stampDrainedRows({
6736
+ client: client,
6737
+ instance: instance,
6738
+ condemned: condemned,
6739
+ clock: clock,
6740
+ executionContext: executionContext
6741
+ });
6742
+ }
6743
+ }
6744
+
6745
+ async function settleCondemnedRow({client: client, ownerId: ownerId, row: row, actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, clock: clock, executionContext: executionContext, telemetry: telemetry, draining: draining}) {
6746
+ const childId = toBareId(row.ref.id);
6584
6747
  try {
6585
- return await grantsPermissionOn({
6586
- document: {
6587
- ...doc,
6588
- _id: parsed.documentId
6589
- },
6590
- grants: grants,
6591
- permission: "update",
6592
- userId: actorId
6748
+ await abortInstance({
6749
+ client: client,
6750
+ instanceId: childId,
6751
+ reason: row.abortPending?.reason ?? "condemned by parent",
6752
+ options: {
6753
+ ...actor ? {
6754
+ actor: actor
6755
+ } : {},
6756
+ clientForGdr: clientForGdr,
6757
+ refSurface: refSurface,
6758
+ ...clock ? {
6759
+ clock: clock
6760
+ } : {},
6761
+ ...executionContext ? {
6762
+ executionContext: executionContext
6763
+ } : {},
6764
+ ...telemetry ? {
6765
+ telemetry: telemetry
6766
+ } : {}
6767
+ }
6768
+ }), await drainCondemnedChildren({
6769
+ client: client,
6770
+ instanceId: childId,
6771
+ actor: actor,
6772
+ clientForGdr: clientForGdr,
6773
+ refSurface: refSurface,
6774
+ clock: clock,
6775
+ executionContext: executionContext,
6776
+ telemetry: telemetry,
6777
+ draining: draining
6778
+ });
6779
+ } catch (cause) {
6780
+ if (cause instanceof InstanceNotFoundError) return;
6781
+ throw cause instanceof WorkflowStateDivergedError ? cause : new WorkflowStateDivergedError({
6782
+ instanceId: ownerId,
6783
+ guardError: cause,
6784
+ reason: `owed child abort of "${childId}" failed while draining condemned rows`
6593
6785
  });
6594
- } catch (err) {
6595
- console.warn(`workflow: subject-write forecast skipped for "${doc._id}" — evaluating that resource's grants failed (the lake still enforces the write). Original error: ${errorMessage(err)}`);
6596
- return;
6597
6786
  }
6598
6787
  }
6599
6788
 
6600
- async function evaluateActivity(args) {
6601
- const {activity: activity, statusEntry: statusEntry, instance: instance, snapshot: snapshot, activityScope: activityScope, cascadeActivityScope: cascadeActivityScope, stageHasExits: stageHasExits, guardDenial: guardDenial, subjectPermissionReason: subjectPermissionReason, sites: sites, autonomy: autonomy} = args, status = statusEntry?.status ?? "skipped", assigned = activityScope.assigned === !0, {unmetRequirements: unmetRequirements, ...conditionInsights} = await explainActivityConditions({
6602
- activity: activity,
6603
- activityScope: activityScope,
6604
- cascadeActivityScope: cascadeActivityScope,
6605
- snapshot: snapshot,
6606
- sites: sites
6607
- }), requirementsReason = unmetRequirements.length > 0 ? {
6608
- kind: "requirements-unmet",
6609
- unmetRequirements: unmetRequirements
6610
- } : void 0, actions = [];
6611
- for (const action of activity.actions ?? []) actions.push(await evaluateAction({
6612
- action: action,
6613
- activityName: activity.name,
6614
- status: status,
6615
- instance: instance,
6616
- snapshot: snapshot,
6617
- activityScope: activityScope,
6618
- cascadeActivityScope: cascadeActivityScope,
6619
- stageHasExits: stageHasExits,
6620
- guardDenial: guardDenial,
6621
- subjectPermissionReason: subjectPermissionReason,
6622
- requirementsReason: requirementsReason,
6623
- sites: sites
6624
- }));
6625
- return {
6626
- activity: activity,
6627
- status: status,
6628
- kind: deriveActivityKind(activity),
6629
- classification: deriveExecutorClassification(activity),
6630
- autonomy: autonomy,
6631
- pendingOnActor: status === "active" && assigned,
6632
- scopedOut: isFilterScopedOut({
6633
- status: status,
6634
- startedAt: statusEntry?.startedAt
6635
- }),
6636
- ...unmetRequirements.length > 0 ? {
6637
- unmetRequirements: unmetRequirements
6638
- } : {},
6639
- ...conditionInsights,
6640
- actions: actions
6641
- };
6789
+ async function stampDrainedRows({client: client, instance: instance, condemned: condemned, clock: clock, executionContext: executionContext}) {
6790
+ const now = (clock ?? wallClock)(), stamp = resolveExecutionContext(executionContext), ids = condemned.map(row => toBareId(row.ref.id)), children = await client.fetch("*[_id in $ids]{_id, currentStage, completedAt, abortedAt, modelVersion, minReaderModel}", {
6791
+ ids: ids
6792
+ }), byId = new Map(children.map(c => [ assertReadableModel(c)._id, c ])), condemnedKeys = new Set(condemned.map(row => row._key)), history = [ ...instance.history ], subworkflows = (instance.subworkflows ?? []).map(row => {
6793
+ if (row.resolved !== void 0 || !condemnedKeys.has(row._key)) return row;
6794
+ const child = byId.get(toBareId(row.ref.id)), resolved = child !== void 0 ? terminalResolution(child) : {
6795
+ at: now,
6796
+ aborted: !0
6797
+ };
6798
+ return resolved === void 0 ? row : (history.push(...stampHistoryEntries([ subworkflowResolvedEntry({
6799
+ row: row,
6800
+ at: now,
6801
+ status: resolved.aborted === !0 ? "aborted" : "done"
6802
+ }) ], stamp)), {
6803
+ ...row,
6804
+ resolved: resolved
6805
+ });
6806
+ });
6807
+ await client.patch(instance._id).set({
6808
+ subworkflows: subworkflows,
6809
+ history: history,
6810
+ lastChangedAt: now
6811
+ }).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
6642
6812
  }
6643
6813
 
6644
- async function explainActivityConditions({activity: activity, activityScope: activityScope, cascadeActivityScope: cascadeActivityScope, snapshot: snapshot, sites: sites}) {
6645
- const explainAt = (site, condition) => explainSite({
6646
- site: site,
6647
- condition: condition,
6648
- params: activityScope,
6649
- snapshot: snapshot,
6650
- sites: sites
6651
- }), requirementEntries = [];
6652
- for (const [name, condition] of Object.entries(activity.requirements ?? {})) requirementEntries.push([ name, await explainAt({
6653
- kind: "requirement",
6654
- activity: activity.name,
6655
- requirement: name
6656
- }, condition) ]);
6657
- const requirementInsights = Object.fromEntries(requirementEntries), unmetRequirements = requirementEntries.filter(([, insight]) => insight.outcome !== "satisfied").map(([name]) => name), filterInsight = activity.filter !== void 0 ? await explainSite({
6658
- site: {
6659
- kind: "activity-filter",
6660
- activity: activity.name
6661
- },
6662
- condition: activity.filter,
6663
- params: await cascadeActivityScope(),
6664
- snapshot: snapshot,
6665
- sites: sites
6666
- }) : void 0;
6667
- return {
6668
- unmetRequirements: unmetRequirements,
6669
- ...activity.requirements !== void 0 ? {
6670
- requirementInsights: requirementInsights
6671
- } : {},
6672
- ...filterInsight !== void 0 ? {
6673
- filterInsight: filterInsight
6814
+ function terminalResolution(child) {
6815
+ const status = resolvedChildStatus(child);
6816
+ if (!(status === void 0 || child.completedAt === void 0 || child.completedAt === null)) return {
6817
+ at: child.completedAt,
6818
+ stage: child.currentStage,
6819
+ ...status === "aborted" ? {
6820
+ aborted: !0
6674
6821
  } : {}
6675
6822
  };
6676
6823
  }
6677
6824
 
6678
- async function evaluateAction(args) {
6679
- const {action: action, activityName: activityName, snapshot: snapshot, activityScope: activityScope, sites: sites} = args, conditionScope = isCascadeFired(action) ? await args.cascadeActivityScope() : activityScope, insights = await explainActionGates({
6680
- action: action,
6681
- activityName: activityName,
6682
- conditionScope: conditionScope,
6683
- snapshot: snapshot,
6684
- sites: sites
6685
- });
6686
- return action.when !== void 0 ? triggeredActionVerdict({
6687
- action: action,
6688
- when: action.when,
6689
- insights: insights
6690
- }) : fireableActionVerdict({
6691
- args: args,
6692
- insights: insights
6693
- });
6825
+ function subworkflowResolvedEntry({row: row, at: at, status: status}) {
6826
+ return {
6827
+ _key: randomKey(),
6828
+ _type: "subworkflowResolved",
6829
+ at: at,
6830
+ activity: row.activity,
6831
+ instanceRef: row.ref,
6832
+ status: status
6833
+ };
6694
6834
  }
6695
6835
 
6696
- async function explainActionGates({action: action, activityName: activityName, conditionScope: conditionScope, snapshot: snapshot, sites: sites}) {
6697
- const insight = action.filter !== void 0 ? await explainSite({
6698
- site: {
6699
- kind: "action",
6700
- activity: activityName,
6701
- action: action.name
6702
- },
6703
- condition: action.filter,
6704
- params: conditionScope,
6705
- snapshot: snapshot,
6706
- sites: sites
6707
- }) : void 0, whenInsight = action.when !== void 0 ? await explainSite({
6708
- site: {
6709
- kind: "action-when",
6710
- activity: activityName,
6711
- action: action.name
6712
- },
6713
- condition: action.when,
6714
- params: conditionScope,
6715
- snapshot: snapshot,
6716
- sites: sites
6717
- }) : void 0;
6718
- return {
6719
- ...insight !== void 0 ? {
6720
- insight: insight
6836
+ async function propagateToAncestors({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, clock: clock, executionContext: executionContext, telemetry: telemetry}) {
6837
+ const loaded = await loadPropagationPair(client, instanceId);
6838
+ if (loaded === void 0) return;
6839
+ const {child: child, parent: parent} = loaded, definition = parseDefinitionSnapshot(parent), ctx = await buildEngineContext({
6840
+ client: client,
6841
+ clientForGdr: clientForGdr,
6842
+ refSurface: refSurface,
6843
+ instance: parent,
6844
+ definition: definition,
6845
+ ...clock ? {
6846
+ clock: clock
6721
6847
  } : {},
6722
- ...whenInsight !== void 0 ? {
6723
- whenInsight: whenInsight
6848
+ ...actor ? {
6849
+ actor: actor
6850
+ } : {},
6851
+ ...executionContext ? {
6852
+ executionContext: executionContext
6853
+ } : {},
6854
+ ...telemetry ? {
6855
+ telemetry: telemetry
6724
6856
  } : {}
6725
- };
6857
+ }), mutation = startMutation(parent), row = mutation.subworkflows.find(r => toBareId(r.ref.id) === child._id);
6858
+ if (row === void 0) {
6859
+ await recordOrphanedPropagation({
6860
+ ctx: ctx,
6861
+ mutation: mutation,
6862
+ child: child
6863
+ });
6864
+ return;
6865
+ }
6866
+ const changed = stampResolvedChild({
6867
+ mutation: mutation,
6868
+ row: row,
6869
+ child: child,
6870
+ now: ctx.now
6871
+ }), parentTerminal = parent.completedAt !== void 0;
6872
+ changed && await persist(ctx, mutation), !parentTerminal && (await cascadeAutoTransitions({
6873
+ client: client,
6874
+ instanceId: parent._id,
6875
+ actor: actor,
6876
+ clientForGdr: clientForGdr,
6877
+ refSurface: refSurface,
6878
+ clock: clock,
6879
+ executionContext: executionContext,
6880
+ telemetry: telemetry
6881
+ }), await propagateToAncestors({
6882
+ client: client,
6883
+ instanceId: parent._id,
6884
+ actor: actor,
6885
+ clientForGdr: clientForGdr,
6886
+ refSurface: refSurface,
6887
+ clock: clock,
6888
+ executionContext: executionContext,
6889
+ telemetry: telemetry
6890
+ }));
6726
6891
  }
6727
6892
 
6728
- function triggeredActionVerdict({action: action, when: when, insights: insights}) {
6729
- return insights.insight !== void 0 && insights.insight.outcome === "unsatisfied" ? disabled({
6730
- action: action,
6731
- reason: {
6732
- kind: "filter-failed",
6733
- filter: action.filter ?? ""
6734
- },
6735
- ...insights
6736
- }) : {
6737
- ...actionEvaluationIdentity(action),
6738
- allowed: !1,
6739
- triggered: !0,
6740
- disabledReason: {
6741
- kind: "cascade-fired",
6742
- when: when
6743
- },
6744
- ...insights
6893
+ async function loadPropagationPair(client, instanceId) {
6894
+ const child = await getInstanceDocument(client, instanceId);
6895
+ if (!child) return;
6896
+ const parentGdr = parentRef(child);
6897
+ if (parentGdr === void 0) return;
6898
+ const parentDoc = await client.getDocument(toBareId(parentGdr.id));
6899
+ if (!(!parentDoc || parentDoc._type !== WORKFLOW_INSTANCE_TYPE)) return {
6900
+ child: child,
6901
+ parent: readInstanceDoc(parentDoc)
6745
6902
  };
6746
6903
  }
6747
6904
 
6748
- function fireableActionVerdict({args: args, insights: insights}) {
6749
- const {action: action, status: status, instance: instance, stageHasExits: stageHasExits, guardDenial: guardDenial} = args, lifecycle = lifecycleReason({
6750
- instance: instance,
6751
- status: status,
6752
- stageHasExits: stageHasExits
6753
- });
6754
- if (lifecycle !== void 0) return disabled({
6755
- action: action,
6756
- reason: lifecycle,
6757
- ...insights
6758
- });
6759
- if (guardDenial !== void 0) return disabled({
6760
- action: action,
6761
- reason: guardDenial,
6762
- ...insights
6763
- });
6764
- if (args.subjectPermissionReason !== void 0 && (action.effects?.length ?? 0) > 0) return disabled({
6765
- action: action,
6766
- reason: args.subjectPermissionReason,
6767
- ...insights
6768
- });
6769
- if (args.requirementsReason !== void 0) return disabled({
6770
- action: action,
6771
- reason: args.requirementsReason,
6772
- ...insights
6773
- });
6774
- const {insight: insight} = insights;
6775
- return action.filter !== void 0 && insight !== void 0 && insight.outcome !== "satisfied" ? disabled({
6776
- action: action,
6777
- reason: {
6778
- kind: "filter-failed",
6779
- filter: action.filter
6905
+ function stampResolvedChild({mutation: mutation, row: row, child: child, now: now}) {
6906
+ if (row.resolved !== void 0) return !1;
6907
+ const resolved = terminalResolution(child);
6908
+ return resolved === void 0 ? !1 : (row.resolved = resolved, mutation.history.push(subworkflowResolvedEntry({
6909
+ row: row,
6910
+ at: now,
6911
+ status: resolved.aborted === !0 ? "aborted" : "done"
6912
+ })), !0);
6913
+ }
6914
+
6915
+ async function recordOrphanedPropagation({ctx: ctx, mutation: mutation, child: child}) {
6916
+ const childUri = selfGdr(child);
6917
+ mutation.history.some(h => h._type === "subworkflowOrphaned" && h.instanceRef.id === childUri) || (mutation.history.push({
6918
+ _key: randomKey(),
6919
+ _type: "subworkflowOrphaned",
6920
+ at: ctx.now,
6921
+ instanceRef: {
6922
+ id: childUri,
6923
+ type: WORKFLOW_INSTANCE_TYPE
6780
6924
  },
6781
- ...insights
6782
- }) : {
6783
- ...actionEvaluationIdentity(action),
6784
- allowed: !0,
6785
- ...insights
6786
- };
6925
+ detail: `Instance "${child._id}" names this instance as its parent but no subworkflow-registry row matches it, so its state cannot drive any gate here.`
6926
+ }), await persist(ctx, mutation));
6787
6927
  }
6788
6928
 
6789
- function actionEvaluationIdentity(action) {
6929
+ function startMutation(instance) {
6790
6930
  return {
6791
- action: action,
6792
- ...action.semantics !== void 0 ? {
6793
- semantics: action.semantics
6794
- } : {}
6931
+ minReaderModel: minReaderModelOf(instance),
6932
+ currentStage: instance.currentStage,
6933
+ fields: (instance.fields ?? []).map(s => ({
6934
+ ...s
6935
+ })),
6936
+ stages: instance.stages.map(s => ({
6937
+ ...s,
6938
+ fields: (s.fields ?? []).map(entry => ({
6939
+ ...entry
6940
+ })),
6941
+ activities: s.activities.map(t => ({
6942
+ ...t,
6943
+ ...t.fields !== void 0 ? {
6944
+ fields: t.fields.map(entry => ({
6945
+ ...entry
6946
+ }))
6947
+ } : {}
6948
+ }))
6949
+ })),
6950
+ subworkflows: (instance.subworkflows ?? []).map(row => ({
6951
+ ...row,
6952
+ ...row.abortPending !== void 0 ? {
6953
+ abortPending: {
6954
+ ...row.abortPending
6955
+ }
6956
+ } : {},
6957
+ ...row.resolved !== void 0 ? {
6958
+ resolved: {
6959
+ ...row.resolved
6960
+ }
6961
+ } : {}
6962
+ })),
6963
+ pendingEffects: [ ...instance.pendingEffects ],
6964
+ effectHistory: [ ...instance.effectHistory ],
6965
+ context: [ ...instance.context ],
6966
+ history: [ ...instance.history ],
6967
+ processedRequests: [ ...instance.processedRequests ?? [] ],
6968
+ lastChangedAt: instance.lastChangedAt,
6969
+ ...instance.completedAt !== void 0 ? {
6970
+ completedAt: instance.completedAt
6971
+ } : {},
6972
+ ...instance.abortedAt !== void 0 ? {
6973
+ abortedAt: instance.abortedAt
6974
+ } : {},
6975
+ pendingCreates: []
6795
6976
  };
6796
6977
  }
6797
6978
 
6798
- async function instanceGuardReason({instance: instance, actor: actor, guards: guards}) {
6799
- if (guards === void 0 || guards.length === 0) return;
6800
- const denied = await instanceWriteDenials({
6801
- instance: instance,
6802
- guards: guards,
6803
- identity: actor.id
6804
- });
6805
- if (denied.length !== 0) return {
6806
- kind: "mutation-guard-denied",
6807
- denied: deniedGuardRefs(denied)
6979
+ function instanceStateFields(src) {
6980
+ const state = {
6981
+ currentStage: src.currentStage,
6982
+ fields: src.fields,
6983
+ stages: src.stages,
6984
+ subworkflows: src.subworkflows ?? [],
6985
+ pendingEffects: src.pendingEffects,
6986
+ effectHistory: src.effectHistory,
6987
+ context: src.context,
6988
+ history: src.history,
6989
+ processedRequests: src.processedRequests ?? [],
6990
+ ...src.completedAt !== void 0 ? {
6991
+ completedAt: src.completedAt
6992
+ } : {},
6993
+ ...src.abortedAt !== void 0 ? {
6994
+ abortedAt: src.abortedAt
6995
+ } : {}
6996
+ };
6997
+ return {
6998
+ ...modelStampFor({
6999
+ documentType: "instance",
7000
+ document: state,
7001
+ storedMinReaderModel: src.minReaderModel
7002
+ }),
7003
+ ...state
6808
7004
  };
6809
7005
  }
6810
7006
 
6811
- function lifecycleReason({instance: instance, status: status, stageHasExits: stageHasExits}) {
6812
- const terminalReason = instanceTerminalReason(instance);
6813
- if (terminalReason !== void 0) return terminalReason;
6814
- if (!stageHasExits) return {
6815
- kind: "stage-terminal",
6816
- stage: instance.currentStage
6817
- };
6818
- if (isTerminalActivityStatus(status)) return {
6819
- kind: "activity-not-active",
6820
- status: status
7007
+ function liveViewContext(ctx, mutation) {
7008
+ const materialized = materializeInstance(ctx.instance, mutation);
7009
+ return {
7010
+ ...ctx,
7011
+ instance: materialized,
7012
+ snapshot: overlayInstanceInSnapshot(ctx.snapshot, materialized)
6821
7013
  };
6822
7014
  }
6823
7015
 
6824
- function disabled(args) {
7016
+ function materializeInstance(base, mutation) {
6825
7017
  return {
6826
- ...actionEvaluationIdentity(args.action),
6827
- allowed: !1,
6828
- disabledReason: args.reason,
6829
- ...args.insight !== void 0 ? {
6830
- insight: args.insight
6831
- } : {},
6832
- ...args.whenInsight !== void 0 ? {
6833
- whenInsight: args.whenInsight
6834
- } : {}
7018
+ ...base,
7019
+ currentStage: mutation.currentStage,
7020
+ fields: mutation.fields,
7021
+ stages: mutation.stages,
7022
+ subworkflows: mutation.subworkflows,
7023
+ context: mutation.context,
7024
+ pendingEffects: mutation.pendingEffects,
7025
+ effectHistory: mutation.effectHistory
6835
7026
  };
6836
7027
  }
6837
7028
 
6838
- function subjectDenialLabels(denied) {
6839
- return denied.map(d => `${d.permission} on ${d.subject} (${d.resource})`);
7029
+ async function persist(ctx, mutation) {
7030
+ stampExecutionContext(ctx, mutation);
7031
+ const set = {
7032
+ ...instanceStateFields(mutation),
7033
+ lastChangedAt: ctx.now
7034
+ }, pendingCreates = mutation.pendingCreates;
7035
+ if (pendingCreates.length === 0) return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit(SYNC_COMMIT);
7036
+ const tx = ctx.client.transaction();
7037
+ for (const {body: body} of pendingCreates) tx.create(body);
7038
+ tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)),
7039
+ await tx.commit(), mutation.pendingCreates = [];
7040
+ const actorForPriming = ctx.actor;
7041
+ for (const {body: body, started: started} of pendingCreates) try {
7042
+ await primeInitialStage({
7043
+ client: ctx.client,
7044
+ instanceId: body._id,
7045
+ actor: actorForPriming,
7046
+ clientForGdr: ctx.clientForGdr,
7047
+ refSurface: ctx.refSurface,
7048
+ clock: ctx.clock,
7049
+ executionContext: ctx.executionContext,
7050
+ telemetry: ctx.telemetry
7051
+ }), await cascadeAutoTransitions({
7052
+ client: ctx.client,
7053
+ instanceId: body._id,
7054
+ actor: actorForPriming,
7055
+ clientForGdr: ctx.clientForGdr,
7056
+ refSurface: ctx.refSurface,
7057
+ clock: ctx.clock,
7058
+ executionContext: ctx.executionContext,
7059
+ telemetry: ctx.telemetry
7060
+ }), await propagateToAncestors({
7061
+ client: ctx.client,
7062
+ instanceId: body._id,
7063
+ actor: actorForPriming,
7064
+ clientForGdr: ctx.clientForGdr,
7065
+ refSurface: ctx.refSurface,
7066
+ clock: ctx.clock,
7067
+ executionContext: ctx.executionContext,
7068
+ telemetry: ctx.telemetry
7069
+ }), ctx.telemetry.log(WorkflowInstanceStarted, started);
7070
+ } catch (cause) {
7071
+ throw cause instanceof WorkflowStateDivergedError ? cause : new WorkflowStateDivergedError({
7072
+ instanceId: ctx.instance._id,
7073
+ guardError: cause,
7074
+ reason: `spawned child "${body._id}" failed to settle after the spawn transaction committed`
7075
+ });
7076
+ }
7077
+ const reloaded = await getInstanceDocument(ctx.client, ctx.instance._id);
7078
+ if (!reloaded) throw new Error(`Instance ${ctx.instance._id} disappeared after transaction commit`);
7079
+ return reloaded;
6840
7080
  }
6841
7081
 
6842
- class ActionDisabledError extends WorkflowError {
6843
- reason;
6844
- activity;
6845
- action;
6846
- constructor(args) {
6847
- super("action-disabled", formatDisabledReason({
6848
- activity: args.activity,
6849
- action: args.action,
6850
- reason: args.reason
6851
- })), this.name = "ActionDisabledError", this.reason = args.reason, this.activity = args.activity,
6852
- this.action = args.action;
6853
- }
7082
+ function stampExecutionContext(ctx, mutation) {
7083
+ const stamp = ctx.executionContext, appended = mutation.history.slice(ctx.instance.history.length);
7084
+ mutation.history = [ ...mutation.history.slice(0, ctx.instance.history.length), ...stampHistoryEntries(appended, stamp) ];
7085
+ for (const {body: body} of mutation.pendingCreates) body.history = stampHistoryEntries(body.history, stamp);
6854
7086
  }
6855
7087
 
6856
- class StartNotAllowedError extends WorkflowError {
6857
- definition;
6858
- insight;
6859
- constructor(args) {
6860
- super("start-not-allowed", `startInstance refused: start.allowed on definition "${args.definition}" evaluated ` + (args.insight.outcome === "unevaluable" ? `GROQ null ("can't decide" — fail-closed)` : "false") + " for the supplied initialFields. Pre-flight the verdict with evaluateStart."),
6861
- this.name = "StartNotAllowedError", this.definition = args.definition, this.insight = args.insight;
6862
- }
7088
+ function currentStageEntry(mutation) {
7089
+ const entry = findOpenStageEntry(mutation);
7090
+ if (entry === void 0) throw new Error(`Mutation invariant broken: no current (un-exited) StageEntry for currentStage "${mutation.currentStage}"`);
7091
+ return entry;
6863
7092
  }
6864
7093
 
6865
- function actionRendering(action) {
6866
- const kind = action.disabledReason?.kind;
6867
- return kind === "filter-failed" ? "absent" : action.triggered === !0 || kind === "cascade-fired" ? "automation" : "button";
7094
+ function currentActivities(mutation) {
7095
+ return currentStageEntry(mutation).activities;
6868
7096
  }
6869
7097
 
6870
- const disabledReasonDetail = {
6871
- "filter-failed": r => `action filter returned false${r.detail ? ` (${r.detail})` : ""}`,
6872
- "cascade-fired": r => `the action is cascade-fired (when: ${JSON.stringify(r.when)}) the engine fires it on truth; it cannot be invoked via fireAction`,
6873
- "activity-not-active": r => `activity status is "${r.status}"`,
6874
- "stage-terminal": r => `stage "${r.stage}" is terminal`,
6875
- "instance-completed": r => `instance completed at ${r.completedAt}`,
6876
- "instance-aborted": r => `instance aborted at ${r.abortedAt}`,
6877
- "requirements-unmet": r => `unmet requirement(s): ${r.unmetRequirements.join(", ")}`,
6878
- "subject-permission-denied": r => `missing subject permission(s): ${subjectDenialLabels(r.denied).join(", ")}`
6879
- };
6880
-
6881
- function actionDisabledDetail(reason) {
6882
- return disabledReasonDetail[reason.kind](reason);
7098
+ function findActivityInCurrentStage(ctx, activityName) {
7099
+ const stage = findStage(ctx.definition, ctx.instance.currentStage), activity = (stage.activities ?? []).find(t => t.name === activityName);
7100
+ if (activity === void 0) throw new ContractViolationError(`Activity "${activityName}" not found in current stage "${stage.name}" of ${ctx.definition.name}`);
7101
+ return {
7102
+ stage: stage,
7103
+ activity: activity
7104
+ };
6883
7105
  }
6884
7106
 
6885
- function formatDisabledReason({activity: activity, action: action, reason: reason}) {
6886
- return `Action "${activity}:${action}" is not allowed: ${actionDisabledDetail(reason)}`;
7107
+ function requireMutationActivityEntry(mutation, activity) {
7108
+ const mutEntry = currentActivities(mutation).find(t => t.name === activity);
7109
+ if (mutEntry === void 0) throw new Error(`Activity "${activity}" disappeared from mutation copy — invariant broken`);
7110
+ return mutEntry;
6887
7111
  }
6888
7112
 
6889
- class EditFieldDeniedError extends WorkflowError {
6890
- reason;
6891
- target;
6892
- constructor(args) {
6893
- super("edit-field-denied", formatEditDisabledReason(args.target, args.reason)),
6894
- this.name = "EditFieldDeniedError", this.reason = args.reason, this.target = args.target;
6895
- }
7113
+ function findCurrentStageEntry(instance) {
7114
+ return findOpenStageEntry(instance);
6896
7115
  }
6897
7116
 
6898
- const editDisabledReasonDetail = {
6899
- "not-editable": () => "field is not declared editable",
6900
- "instance-completed": r => `instance completed at ${r.completedAt}`,
6901
- "instance-aborted": r => `instance aborted at ${r.abortedAt}`,
6902
- "edit-window-closed": r => `edit window closed (${r.detail})`,
6903
- "editor-not-permitted": r => `editor not permitted (${r.predicate})`
6904
- };
6905
-
6906
- function formatEditDisabledReason(target, reason) {
6907
- const detail = editDisabledReasonDetail[reason.kind](reason), where = target.activity !== void 0 ? `${target.activity}.${target.field}` : target.field;
6908
- return `Field "${target.scope}:${where}" is not editable: ${detail}`;
7117
+ function findCurrentActivities(instance) {
7118
+ return findCurrentStageEntry(instance)?.activities ?? [];
6909
7119
  }
6910
7120
 
6911
- async function fireAction(args) {
6912
- const {client: client, instanceId: instanceId, activity: activity, action: action, params: params, requestRecord: requestRecord, options: options} = args;
7121
+ async function completeEffect(args) {
7122
+ const {client: client, instanceId: instanceId, effectKey: effectKey, status: status, outputs: outputs, ops: ops, detail: detail, error: error, durationMs: durationMs, requestRecord: requestRecord, options: options} = args;
7123
+ if (status !== "done" && status !== "failed") throw new ContractViolationError(`completeEffect: status must be "done" or "failed", got ${JSON.stringify(status)} — "cancelled" is engine-stamped by abort, never reportable`);
6913
7124
  return retryOnRevisionConflict({
6914
7125
  client: client,
6915
7126
  instanceId: instanceId,
6916
7127
  options: options,
6917
- commit: ctx => commitAction({
7128
+ commit: ctx => commitCompleteEffect({
6918
7129
  ctx: ctx,
6919
- activityName: activity,
6920
- actionName: action,
6921
- callerParams: params,
7130
+ effectKey: effectKey,
7131
+ status: status,
7132
+ outputs: outputs,
7133
+ ops: ops,
7134
+ detail: detail,
7135
+ error: error,
7136
+ durationMs: durationMs,
6922
7137
  requestRecord: requestRecord,
6923
- options: options
7138
+ actor: options.actor
6924
7139
  }),
6925
- onExhausted: () => new ConcurrentFireActionError({
7140
+ onExhausted: () => new ConcurrentCompleteEffectError({
6926
7141
  instanceId: instanceId,
6927
- activity: activity,
6928
- action: action,
7142
+ effectKey: effectKey,
6929
7143
  attempts: CONCURRENT_COMMIT_MAX_ATTEMPTS
6930
7144
  })
6931
7145
  });
6932
7146
  }
6933
7147
 
6934
- async function resolveActionCommit({ctx: ctx, activityName: activityName, actionName: actionName, callerParams: callerParams, options: options}) {
6935
- const actor = options?.actor, {stage: stage, activity: activity} = findActivityInCurrentStage(ctx, activityName), action = (activity.actions ?? []).find(a => a.name === actionName);
6936
- if (action === void 0) throw new ContractViolationError(`Action "${actionName}" not declared on activity "${activityName}"`);
6937
- if (action.when !== void 0) throw new ActionDisabledError({
6938
- activity: activityName,
6939
- action: actionName,
6940
- reason: {
6941
- kind: "cascade-fired",
6942
- when: action.when
6943
- }
6944
- });
6945
- if (action.filter !== void 0) {
6946
- const can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan({
6947
- instance: ctx.instance,
6948
- actor: actor,
6949
- grants: options.grants
6950
- }) : void 0;
6951
- if (!await ctxEvaluateCondition({
6952
- ctx: ctx,
6953
- condition: action.filter,
6954
- opts: {
6955
- activityName: activityName,
6956
- ...actor !== void 0 ? {
6957
- actor: actor
6958
- } : {},
6959
- ...can !== void 0 ? {
6960
- vars: {
6961
- can: can
6962
- }
6963
- } : {}
6964
- }
6965
- })) throw new ActionDisabledError({
6966
- activity: activityName,
6967
- action: actionName,
6968
- reason: {
6969
- kind: "filter-failed",
6970
- filter: action.filter,
6971
- detail: "commit re-check"
6972
- }
6973
- });
6974
- }
6975
- const entry = findCurrentActivities(ctx.instance).find(t => t.name === activityName);
6976
- if (entry === void 0 || entry.status !== "active") throw notActiveAtCommit({
6977
- activityName: activityName,
6978
- actionName: actionName,
6979
- status: entry?.status
6980
- });
6981
- const params = validateActionParams({
6982
- action: action,
6983
- activityName: activityName,
6984
- callerParams: callerParams
6985
- });
7148
+ function buildEffectHistoryEntry(pending, outcome) {
7149
+ const {status: status, ranAt: ranAt, actor: actor, detail: detail, error: error, durationMs: durationMs, outputs: outputs} = outcome, resolvedActor = actor ?? pending.actor;
6986
7150
  return {
6987
- stage: stage,
6988
- activity: activity,
6989
- action: action,
6990
- params: params
7151
+ _key: pending._key,
7152
+ name: pending.name,
7153
+ ...pending.title !== void 0 ? {
7154
+ title: pending.title
7155
+ } : {},
7156
+ ...pending.description !== void 0 ? {
7157
+ description: pending.description
7158
+ } : {},
7159
+ params: pending.params,
7160
+ origin: pending.origin,
7161
+ ...resolvedActor !== void 0 ? {
7162
+ actor: resolvedActor
7163
+ } : {},
7164
+ ...pending.stageEntryKey !== void 0 ? {
7165
+ stageEntryKey: pending.stageEntryKey
7166
+ } : {},
7167
+ ranAt: ranAt,
7168
+ ...durationMs !== void 0 ? {
7169
+ durationMs: durationMs
7170
+ } : {},
7171
+ status: status,
7172
+ ...detail !== void 0 ? {
7173
+ detail: detail
7174
+ } : {},
7175
+ ...error !== void 0 ? {
7176
+ error: error
7177
+ } : {},
7178
+ ...outputs !== void 0 ? {
7179
+ outputs: outputs
7180
+ } : {}
6991
7181
  };
6992
7182
  }
6993
7183
 
6994
- function notActiveAtCommit(args) {
6995
- const {activityName: activityName, actionName: actionName, status: status} = args;
6996
- return status !== void 0 && isTerminalActivityStatus(status) ? new ActionDisabledError({
6997
- activity: activityName,
6998
- action: actionName,
6999
- reason: {
7000
- kind: "activity-not-active",
7001
- status: status
7184
+ function buildEffectSettlement(pending, outcome) {
7185
+ const {status: status, ranAt: ranAt, actor: actor, detail: detail, outputs: outputs} = outcome;
7186
+ return {
7187
+ run: buildEffectHistoryEntry(pending, outcome),
7188
+ event: {
7189
+ _key: randomKey(),
7190
+ _type: "effectCompleted",
7191
+ at: ranAt,
7192
+ effectKey: pending._key,
7193
+ effect: pending.name,
7194
+ status: status,
7195
+ ...outputs !== void 0 ? {
7196
+ outputs: outputs
7197
+ } : {},
7198
+ ...detail !== void 0 ? {
7199
+ detail: detail
7200
+ } : {},
7201
+ ...actor !== void 0 ? {
7202
+ actor: actor
7203
+ } : {}
7002
7204
  }
7003
- }) : new Error(`Activity "${activityName}" must be active to fire action "${actionName}"; status is ${status ?? "missing"}`);
7205
+ };
7004
7206
  }
7005
7207
 
7006
- async function applyActionFire({ctx: ctx, mutation: mutation, activity: activity, action: action, params: params, actor: actor, triggered: triggered}) {
7007
- mutation.history.push({
7008
- _key: randomKey(),
7009
- _type: "actionFired",
7010
- at: ctx.now,
7011
- stage: mutation.currentStage,
7012
- activity: activity.name,
7013
- action: action.name,
7014
- ...actor !== void 0 ? {
7015
- actor: actor,
7016
- driverKind: driverKind(actor)
7208
+ function validateEffectOutputs(args) {
7209
+ const {outputs: outputs, declared: declared, effectName: effectName} = args, byName = new Map(declared.map(shape => [ shape.name, shape ])), issues = [];
7210
+ for (const [key, value] of Object.entries(outputs)) {
7211
+ const shape = byName.get(key);
7212
+ if (shape === void 0) {
7213
+ issues.push(`"${key}" is not a declared output`);
7214
+ continue;
7215
+ }
7216
+ const shapeIssues = effectOutputIssues(shape, value);
7217
+ shapeIssues !== void 0 && issues.push(...shapeIssues.map(i => `"${key}": ${i}`));
7218
+ }
7219
+ if (issues.length > 0) throw new EffectOutputsInvalidError({
7220
+ effect: effectName,
7221
+ issues: issues
7222
+ });
7223
+ }
7224
+
7225
+ function effectOutputIssues(shape, value) {
7226
+ return checkFieldValue({
7227
+ entryType: shape.type,
7228
+ value: value,
7229
+ ...shape.fields !== void 0 ? {
7230
+ fields: shape.fields
7017
7231
  } : {},
7018
- ...triggered ? {
7019
- triggered: !0
7232
+ ...shape.of !== void 0 ? {
7233
+ of: shape.of
7234
+ } : {},
7235
+ ...shape.options !== void 0 ? {
7236
+ options: shape.options
7237
+ } : {},
7238
+ ...shape.validation !== void 0 ? {
7239
+ validation: shape.validation
7240
+ } : {}
7241
+ });
7242
+ }
7243
+
7244
+ function requirePendingEffect(instance, effectKey) {
7245
+ const pending = instance.pendingEffects.find(e => e._key === effectKey);
7246
+ if (pending !== void 0) return pending;
7247
+ const run = instance.effectHistory.find(e => e._key === effectKey), settled = run !== void 0 ? {
7248
+ status: run.status,
7249
+ ranAt: run.ranAt,
7250
+ ...run.detail !== void 0 ? {
7251
+ detail: run.detail
7252
+ } : {}
7253
+ } : void 0;
7254
+ throw new EffectNotFoundError({
7255
+ instanceId: instance._id,
7256
+ effectKey: effectKey,
7257
+ ...settled !== void 0 ? {
7258
+ settled: settled
7020
7259
  } : {}
7021
7260
  });
7022
- const ranOps = await runOps({
7023
- ops: action.ops,
7024
- mutation: mutation,
7025
- stage: mutation.currentStage,
7026
- origin: {
7027
- activity: activity.name,
7028
- action: action.name
7029
- },
7030
- params: params,
7031
- actor: actor,
7032
- self: selfGdr(ctx.instance),
7033
- now: ctx.now,
7034
- snapshot: ctx.snapshot,
7035
- refSurface: ctx.refSurface
7261
+ }
7262
+
7263
+ function validateCompletionInput({pending: pending, definition: definition, status: status, ops: ops, outputs: outputs}) {
7264
+ if (status === "failed" && ops !== void 0 && ops.length > 0) throw new EffectOpsInvalidError({
7265
+ effect: pending.name,
7266
+ issues: [ "ops cannot accompany a failed completion — field.set the outcome on a done completion instead" ]
7036
7267
  });
7037
- return await queueEffects({
7038
- ctx: ctx,
7039
- mutation: mutation,
7040
- effects: action.effects,
7041
- origin: {
7042
- kind: "action",
7043
- name: action.name
7044
- },
7045
- actor: actor,
7046
- opts: {
7047
- callerParams: params,
7048
- activityName: activity.name
7049
- }
7050
- }), action.spawn !== void 0 && await spawnSubworkflows({
7051
- ctx: ctx,
7052
- mutation: mutation,
7053
- activity: activity,
7054
- action: action,
7055
- sub: action.spawn,
7056
- actor: actor
7057
- }), {
7058
- ranOps: ranOps
7059
- };
7268
+ if (status === "failed" && outputs !== void 0) throw new EffectOutputsInvalidError({
7269
+ effect: pending.name,
7270
+ issues: [ "outputs cannot accompany a failed completion — report outputs on a done completion instead" ]
7271
+ });
7272
+ if (outputs !== void 0) {
7273
+ const declared = findEffect(definition, pending.name)?.outputs ?? [];
7274
+ validateEffectOutputs({
7275
+ outputs: outputs,
7276
+ declared: declared,
7277
+ effectName: pending.name
7278
+ });
7279
+ }
7280
+ return ops !== void 0 ? validateEffectOps(ops, pending.name) : [];
7060
7281
  }
7061
7282
 
7062
- async function commitAction({ctx: ctx, activityName: activityName, actionName: actionName, callerParams: callerParams, requestRecord: requestRecord, options: options}) {
7283
+ async function commitCompleteEffect({ctx: ctx, effectKey: effectKey, status: status, outputs: outputs, ops: ops, detail: detail, error: error, durationMs: durationMs, requestRecord: requestRecord, actor: actor}) {
7063
7284
  assertRequestUnprocessed({
7064
7285
  instance: ctx.instance,
7065
7286
  record: requestRecord,
7066
7287
  now: ctx.now
7067
7288
  });
7068
- const actor = options?.actor, {stage: stage, activity: activity, action: action, params: params} = await resolveActionCommit({
7069
- ctx: ctx,
7070
- activityName: activityName,
7071
- actionName: actionName,
7072
- callerParams: callerParams,
7073
- options: options
7289
+ const pending = requirePendingEffect(ctx.instance, effectKey), validatedOps = validateCompletionInput({
7290
+ pending: pending,
7291
+ definition: ctx.definition,
7292
+ status: status,
7293
+ ops: ops,
7294
+ outputs: outputs
7074
7295
  }), mutation = startMutation(ctx.instance);
7075
7296
  recordProcessedRequest({
7076
7297
  mutation: mutation,
7077
7298
  record: requestRecord,
7078
7299
  now: ctx.now
7300
+ }), mutation.pendingEffects = mutation.pendingEffects.filter(e => e._key !== effectKey);
7301
+ const ranAt = ctx.now, settlement = buildEffectSettlement(pending, {
7302
+ status: status,
7303
+ ranAt: ranAt,
7304
+ actor: actor,
7305
+ detail: detail,
7306
+ error: error,
7307
+ durationMs: durationMs,
7308
+ outputs: outputs
7079
7309
  });
7080
- const mutEntry = requireMutationActivityEntry(mutation, activityName), statusBefore = mutEntry.status, {ranOps: ranOps} = await applyActionFire({
7081
- ctx: ctx,
7310
+ mutation.effectHistory.push(settlement.run);
7311
+ const wroteEffectOutputs = status === "done" && outputs !== void 0;
7312
+ mutation.history.push(settlement.event);
7313
+ const ranOps = await runOps({
7314
+ ops: validatedOps,
7082
7315
  mutation: mutation,
7083
- activity: activity,
7084
- action: action,
7085
- params: params,
7316
+ stage: ctx.instance.currentStage,
7317
+ origin: {
7318
+ effect: pending.name
7319
+ },
7320
+ params: pending.params,
7086
7321
  actor: actor,
7087
- triggered: !1
7088
- }), newStatus = mutEntry.status !== statusBefore ? mutEntry.status : void 0;
7322
+ self: selfGdr(ctx.instance),
7323
+ now: ranAt,
7324
+ snapshot: ctx.snapshot,
7325
+ refSurface: ctx.refSurface
7326
+ }), needsGuardRefresh = wroteEffectOutputs || ranOps.some(isFieldOp);
7089
7327
  return await persistThenMaybeRefresh({
7090
7328
  ctx: ctx,
7091
7329
  mutation: mutation,
7092
- stageName: stage.name,
7093
- didChangeState: ranOps.some(isFieldOp)
7330
+ stageName: ctx.instance.currentStage,
7331
+ didChangeState: needsGuardRefresh
7094
7332
  }), {
7095
- fired: !0,
7096
- activity: activityName,
7097
- action: actionName,
7098
- ...newStatus !== void 0 ? {
7099
- newStatus: newStatus
7100
- } : {},
7101
- ...ranOps.length > 0 ? {
7102
- ranOps: ranOps
7103
- } : {}
7104
- };
7105
- }
7106
-
7107
- async function runTriggeredActions({ctx: ctx, mutation: mutation, stage: stage}) {
7108
- let fires = 0, ranFieldOps = !1;
7109
- const liveCtx = liveViewContext(ctx, mutation);
7110
- let firedThisPass = !0;
7111
- for (;firedThisPass; ) {
7112
- firedThisPass = !1;
7113
- for (const activity of stage.activities ?? []) {
7114
- const fired = await runActivityTriggers({
7115
- ctx: liveCtx,
7116
- mutation: mutation,
7117
- activity: activity
7118
- });
7119
- fires += fired.fires, ranFieldOps = ranFieldOps || fired.ranFieldOps, fired.fires > 0 && (firedThisPass = !0);
7120
- }
7121
- }
7122
- return {
7123
- fires: fires,
7124
- ranFieldOps: ranFieldOps
7125
- };
7126
- }
7127
-
7128
- async function runActivityTriggers({ctx: ctx, mutation: mutation, activity: activity}) {
7129
- let fires = 0, ranFieldOps = !1;
7130
- for (const action of activity.actions ?? []) {
7131
- if (!isCascadeFired(action)) continue;
7132
- const entry = currentActivities(mutation).find(e => e.name === activity.name);
7133
- if (entry === void 0 || entry.status !== "active") break;
7134
- if ((entry.firedActions ?? []).includes(action.name) || !await triggerIsLive({
7135
- ctx: ctx,
7136
- activity: activity,
7137
- action: action
7138
- })) continue;
7139
- const result = await fireTriggeredAction({
7140
- ctx: ctx,
7141
- mutation: mutation,
7142
- activity: activity,
7143
- action: action,
7144
- entry: entry
7145
- });
7146
- if (fires++, ranFieldOps = ranFieldOps || result.ranFieldOps, isTerminalActivityStatus(entry.status)) break;
7147
- }
7148
- return {
7149
- fires: fires,
7150
- ranFieldOps: ranFieldOps
7333
+ effectKey: effectKey,
7334
+ effect: pending.name,
7335
+ status: status,
7336
+ origin: pending.origin.kind
7151
7337
  };
7152
7338
  }
7153
7339
 
7154
- async function triggerIsLive({ctx: ctx, activity: activity, action: action}) {
7155
- if (!tokenMayExecute({
7156
- actor: ctx.actor,
7157
- action: action,
7158
- ctx: ctx
7159
- })) return !1;
7160
- const scope = {
7161
- activityName: activity.name
7340
+ function buildQueuedEffect({effect: effect, origin: origin, params: params, actor: actor, now: now, stageEntryKey: stageEntryKey}) {
7341
+ const key = randomKey(), pending = {
7342
+ _key: key,
7343
+ _type: "pendingEffect",
7344
+ name: effect.name,
7345
+ ...effect.title !== void 0 ? {
7346
+ title: effect.title
7347
+ } : {},
7348
+ ...effect.description !== void 0 ? {
7349
+ description: effect.description
7350
+ } : {},
7351
+ ...effect.bindings !== void 0 ? {
7352
+ bindings: effect.bindings
7353
+ } : {},
7354
+ params: params,
7355
+ origin: origin,
7356
+ ...actor !== void 0 ? {
7357
+ actor: actor
7358
+ } : {},
7359
+ queuedAt: now,
7360
+ ...stageEntryKey !== void 0 ? {
7361
+ stageEntryKey: stageEntryKey
7362
+ } : {}
7363
+ }, history = {
7364
+ _key: randomKey(),
7365
+ _type: "effectQueued",
7366
+ at: now,
7367
+ effectKey: key,
7368
+ effect: effect.name,
7369
+ origin: origin
7162
7370
  };
7163
- return action.filter !== void 0 && await ctxEvaluateConditionOutcome({
7164
- ctx: ctx,
7165
- condition: action.filter,
7166
- opts: scope
7167
- }) !== "satisfied" ? !1 : await ctxEvaluateConditionOutcome({
7168
- ctx: ctx,
7169
- condition: action.when,
7170
- opts: scope
7171
- }) === "satisfied";
7172
- }
7173
-
7174
- function tokenMayExecute({actor: actor, action: action, ctx: ctx}) {
7175
- const roles = action.roles;
7176
- return roles === void 0 || roles.length === 0 ? !0 : actor === void 0 ? !1 : roles.some(required => actorFulfillsRole({
7177
- actorRoles: actor.roles,
7178
- required: required,
7179
- aliases: ctx.definition.roleAliases
7180
- }));
7181
- }
7182
-
7183
- async function fireTriggeredAction({ctx: ctx, mutation: mutation, activity: activity, action: action, entry: entry}) {
7184
- entry.firedActions = [ ...entry.firedActions ?? [], action.name ];
7185
- const {ranOps: ranOps} = await applyActionFire({
7186
- ctx: ctx,
7187
- mutation: mutation,
7188
- activity: activity,
7189
- action: action,
7190
- params: {},
7191
- actor: ctx.actor,
7192
- triggered: !0
7193
- });
7194
7371
  return {
7195
- ranFieldOps: ranOps.some(isFieldOp)
7372
+ pending: pending,
7373
+ history: history
7196
7374
  };
7197
7375
  }
7198
7376
 
7199
- async function primeInitialStage({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, clock: clock, executionContext: executionContext, telemetry: telemetry}) {
7200
- const instance = await getInstanceDocument(client, instanceId);
7201
- if (!instance || instance.stages.length > 0) return;
7202
- const definition = parseDefinitionSnapshot(instance), stage = definition.stages.find(s => s.name === instance.currentStage);
7203
- if (stage === void 0) return;
7204
- const ctx = await buildEngineContext({
7205
- client: client,
7206
- clientForGdr: clientForGdr,
7207
- refSurface: refSurface,
7208
- instance: instance,
7209
- definition: definition,
7210
- ...clock ? {
7211
- clock: clock
7377
+ async function queueEffects({ctx: ctx, mutation: mutation, effects: effects, origin: origin, actor: actor, opts: opts}) {
7378
+ if (!effects || effects.length === 0) return;
7379
+ const now = ctx.now, liveCtx = {
7380
+ ...ctx,
7381
+ instance: materializeInstance(ctx.instance, mutation)
7382
+ }, stageEntryKey = opts?.stageEntryKey ?? findOpenStageEntry(liveCtx.instance)?._key, params = await ctxConditionParams(liveCtx, {
7383
+ ...opts?.activityName !== void 0 ? {
7384
+ activityName: opts.activityName
7212
7385
  } : {},
7213
- ...actor ? {
7386
+ ...actor !== void 0 ? {
7214
7387
  actor: actor
7215
7388
  } : {},
7216
- ...executionContext ? {
7217
- executionContext: executionContext
7218
- } : {},
7219
- ...telemetry ? {
7220
- telemetry: telemetry
7221
- } : {}
7222
- }), now = ctx.now, discards = [], initialStageEntry = {
7223
- _key: randomKey(),
7224
- name: stage.name,
7225
- enteredAt: now,
7226
- fields: await resolveStageFieldEntries({
7227
- client: client,
7228
- instance: instance,
7229
- stage: stage,
7230
- now: now,
7231
- refSurface: refSurface,
7232
- recordDiscard: recordFieldDiscards({
7233
- target: discards,
7234
- scope: "stage",
7235
- at: now
7236
- })
7237
- }),
7238
- activities: []
7239
- }, primedCtx = {
7240
- ...ctx,
7241
- instance: {
7242
- ...instance,
7243
- stages: [ initialStageEntry ]
7389
+ vars: {
7390
+ params: opts?.callerParams ?? {}
7244
7391
  }
7245
- };
7246
- initialStageEntry.activities = await buildStageActivities({
7247
- ctx: primedCtx,
7248
- stage: stage,
7249
- recordDiscard: recordFieldDiscards({
7250
- target: discards,
7251
- scope: "activity",
7252
- at: now
7253
- })
7254
7392
  });
7255
- const terminal = isTerminalStage(stage), committed = await client.patch(instance._id).set({
7256
- stages: [ initialStageEntry ],
7257
- lastChangedAt: now,
7258
- ...terminal ? {
7259
- completedAt: now
7260
- } : {},
7261
- ...discards.length > 0 ? {
7262
- history: [ ...instance.history, ...stampHistoryEntries(discards, ctx.executionContext) ]
7263
- } : {}
7264
- }).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
7265
- await deployOrRollback({
7266
- client: client,
7267
- instanceId: instance._id,
7268
- committedRev: committed._rev,
7269
- restore: {
7270
- stages: instance.stages,
7271
- history: instance.history
7272
- },
7273
- ...terminal ? {
7274
- unset: [ "completedAt" ]
7275
- } : {},
7276
- reversible: !0,
7277
- deploy: () => deployStageGuards({
7278
- client: client,
7279
- clientForGdr: clientForGdr,
7280
- instance: instance,
7281
- definition: definition,
7282
- stageName: stage.name,
7393
+ for (const effect of effects) {
7394
+ const resolved = await resolveBindings({
7395
+ bindings: effect.bindings,
7396
+ staticInput: effect.input,
7397
+ snapshot: ctx.snapshot,
7398
+ params: params
7399
+ }), {pending: pending, history: history} = buildQueuedEffect({
7400
+ effect: effect,
7401
+ origin: origin,
7402
+ params: resolved,
7403
+ actor: actor,
7283
7404
  now: now,
7284
- snapshot: ctx.snapshot
7405
+ stageEntryKey: stageEntryKey
7406
+ });
7407
+ mutation.pendingEffects.push(pending), mutation.history.push(history);
7408
+ }
7409
+ }
7410
+
7411
+ async function commitEffectOps(args) {
7412
+ const {client: client, instanceId: instanceId, effectKey: effectKey, claimToken: claimToken, ops: ops, requestRecord: requestRecord, leaseMs: leaseMs, options: options} = args;
7413
+ return retryOnRevisionConflict({
7414
+ client: client,
7415
+ instanceId: instanceId,
7416
+ options: options,
7417
+ commit: ctx => commitReport({
7418
+ ctx: ctx,
7419
+ effectKey: effectKey,
7420
+ claimToken: claimToken,
7421
+ ops: ops,
7422
+ requestRecord: requestRecord,
7423
+ leaseMs: leaseMs
7424
+ }),
7425
+ onExhausted: () => new ConcurrentCommitEffectOpsError({
7426
+ instanceId: instanceId,
7427
+ effectKey: effectKey,
7428
+ attempts: CONCURRENT_COMMIT_MAX_ATTEMPTS
7285
7429
  })
7286
7430
  });
7287
7431
  }
7288
7432
 
7289
- const CASCADE_LIMIT = 100;
7433
+ function staleClaimReason(args) {
7434
+ const claim = args.pending.claim;
7435
+ if (claim === void 0) return "unclaimed";
7436
+ if (claim.claimToken !== args.claimToken) return "claim-superseded";
7437
+ if (isClaimExpired(claim, args.now)) return "lease-expired";
7438
+ }
7290
7439
 
7291
- async function runCascadeHop({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, clock: clock, executionContext: executionContext, telemetry: telemetry, overlay: overlay}) {
7292
- const ctx = await loadContext({
7293
- client: client,
7294
- instanceId: instanceId,
7295
- options: {
7296
- clientForGdr: clientForGdr,
7297
- refSurface: refSurface,
7298
- ...actor ? {
7299
- actor: actor
7300
- } : {},
7301
- ...clock ? {
7302
- clock: clock
7303
- } : {},
7304
- ...executionContext ? {
7305
- executionContext: executionContext
7306
- } : {},
7307
- ...telemetry ? {
7308
- telemetry: telemetry
7309
- } : {},
7310
- ...overlay ? {
7311
- overlay: overlay
7312
- } : {}
7440
+ function renewClaimLease(args) {
7441
+ const {mutation: mutation, effectKey: effectKey, now: now, leaseMs: leaseMs} = args;
7442
+ mutation.pendingEffects = mutation.pendingEffects.map(entry => entry._key === effectKey && entry.claim !== void 0 ? {
7443
+ ...entry,
7444
+ claim: {
7445
+ ...entry.claim,
7446
+ leaseExpiresAt: addMs(now, leaseMs)
7313
7447
  }
7448
+ } : entry);
7449
+ }
7450
+
7451
+ async function commitReport({ctx: ctx, effectKey: effectKey, claimToken: claimToken, ops: ops, requestRecord: requestRecord, leaseMs: leaseMs}) {
7452
+ assertRequestUnprocessed({
7453
+ instance: ctx.instance,
7454
+ record: requestRecord,
7455
+ now: ctx.now
7314
7456
  });
7315
- if (isTerminal(ctx)) return {
7316
- moved: !1
7317
- };
7318
- const stage = findStage(ctx.definition, ctx.instance.currentStage), mutation = startMutation(ctx.instance), fired = await runTriggeredActions({
7319
- ctx: ctx,
7457
+ const pending = requirePendingEffect(ctx.instance, effectKey), reason = staleClaimReason({
7458
+ pending: pending,
7459
+ claimToken: claimToken,
7460
+ now: ctx.now
7461
+ });
7462
+ if (reason !== void 0) throw new StaleEffectClaimError({
7463
+ instanceId: ctx.instance._id,
7464
+ effectKey: effectKey,
7465
+ reason: reason
7466
+ });
7467
+ const validatedOps = validateEffectOps(ops, pending.name);
7468
+ if (validatedOps.length === 0) throw new EffectOpsInvalidError({
7469
+ effect: pending.name,
7470
+ issues: [ "a mid-dispatch report must carry at least one field op — there is nothing to commit" ]
7471
+ });
7472
+ const mutation = startMutation(ctx.instance);
7473
+ return recordProcessedRequest({
7320
7474
  mutation: mutation,
7321
- stage: stage
7322
- }), hopCtx = liveViewContext(ctx, mutation), transition = await pickTransition(hopCtx, stage);
7323
- return transition === void 0 ? (fired.fires > 0 && await persistThenMaybeRefresh({
7324
- ctx: ctx,
7475
+ record: requestRecord,
7476
+ now: ctx.now
7477
+ }), renewClaimLease({
7325
7478
  mutation: mutation,
7326
- stageName: stage.name,
7327
- didChangeState: fired.ranFieldOps
7328
- }), {
7329
- moved: !1
7330
- }) : (await commitStageMove({
7479
+ effectKey: effectKey,
7480
+ now: ctx.now,
7481
+ leaseMs: leaseMs
7482
+ }), await runOps({
7483
+ ops: validatedOps,
7484
+ mutation: mutation,
7485
+ stage: ctx.instance.currentStage,
7486
+ origin: {
7487
+ effect: pending.name
7488
+ },
7489
+ params: pending.params,
7490
+ actor: ctx.actor,
7491
+ self: selfGdr(ctx.instance),
7492
+ now: ctx.now,
7493
+ snapshot: ctx.snapshot,
7494
+ refSurface: ctx.refSurface
7495
+ }), await persistThenMaybeRefresh({
7331
7496
  ctx: ctx,
7332
7497
  mutation: mutation,
7333
- fromStage: stage,
7334
- toStage: findStage(ctx.definition, transition.to),
7335
- transition: transition.name,
7336
- via: "transition",
7337
- actor: actor
7498
+ stageName: ctx.instance.currentStage,
7499
+ didChangeState: !0
7338
7500
  }), {
7339
- moved: !0
7501
+ effectKey: effectKey,
7502
+ effect: pending.name
7503
+ };
7504
+ }
7505
+
7506
+ const actorCache = /* @__PURE__ */ new WeakMap, grantsCache = /* @__PURE__ */ new WeakMap;
7507
+
7508
+ async function resolveAccess(taggedClient, args = {}) {
7509
+ const client = unwrapRequestTag(taggedClient), requestFn = lazyRequest(client);
7510
+ if (requestFn === void 0) throw new ContractViolationError("workflow: no actor available. The engine resolves the actor from the client's token via `client.request({ uri: '/users/me' })`. Supply a real `@sanity/client` configured with a token (the test bench serves these endpoints per registered token).");
7511
+ const grantsPromise = args.grantsFromPath !== void 0 ? cachedGrants({
7512
+ client: client,
7513
+ requestFn: requestFn,
7514
+ resourcePath: args.grantsFromPath
7515
+ }) : Promise.resolve(void 0), [actor, grants] = await Promise.all([ cachedActor(client, requestFn), grantsPromise ]);
7516
+ if (actor === void 0) throw new ContractViolationError("workflow: failed to resolve actor from `/users/me`. The client is configured but the endpoint returned no usable identity — check the token.");
7517
+ return {
7518
+ actor: actor,
7519
+ ...grants !== void 0 ? {
7520
+ grants: grants
7521
+ } : {}
7522
+ };
7523
+ }
7524
+
7525
+ function cachedActor(client, requestFn) {
7526
+ const cached = actorCache.get(client);
7527
+ if (cached !== void 0) return cached;
7528
+ const pending = fetchActor(requestFn).catch(err => {
7529
+ throw actorCache.get(client) === pending && actorCache.delete(client), err;
7340
7530
  });
7531
+ return actorCache.set(client, pending), pending;
7341
7532
  }
7342
7533
 
7343
- async function cascadeAutoTransitions({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, clock: clock, executionContext: executionContext, telemetry: telemetry, overlay: overlay}) {
7344
- let count = 0;
7345
- for (;;) {
7346
- if (await drainCondemnedChildren({
7347
- client: client,
7348
- instanceId: instanceId,
7349
- actor: actor,
7350
- clientForGdr: clientForGdr,
7351
- refSurface: refSurface,
7352
- clock: clock,
7353
- executionContext: executionContext,
7354
- telemetry: telemetry
7355
- }), !(await runCascadeHop({
7356
- client: client,
7357
- instanceId: instanceId,
7358
- actor: actor,
7359
- clientForGdr: clientForGdr,
7360
- refSurface: refSurface,
7361
- clock: clock,
7362
- executionContext: executionContext,
7363
- telemetry: telemetry,
7364
- overlay: overlay
7365
- })).moved) return count;
7366
- if (count++, count >= CASCADE_LIMIT) throw new CascadeLimitError({
7367
- instanceId: instanceId,
7368
- limit: CASCADE_LIMIT
7534
+ function grantsForClientPath(taggedClient, resourcePath) {
7535
+ const client = unwrapRequestTag(taggedClient), requestFn = lazyRequest(client);
7536
+ return requestFn === void 0 ? Promise.resolve(void 0) : cachedGrants({
7537
+ client: client,
7538
+ requestFn: requestFn,
7539
+ resourcePath: resourcePath
7540
+ });
7541
+ }
7542
+
7543
+ function lazyRequest(client) {
7544
+ return client.request === void 0 ? void 0 : opts => client.request(opts);
7545
+ }
7546
+
7547
+ function cachedGrants({client: client, requestFn: requestFn, resourcePath: resourcePath}) {
7548
+ let byPath = grantsCache.get(client);
7549
+ byPath === void 0 && (byPath = /* @__PURE__ */ new Map, grantsCache.set(client, byPath));
7550
+ let cached = byPath.get(resourcePath);
7551
+ return cached === void 0 && (cached = fetchGrantsCached(requestFn, resourcePath),
7552
+ byPath.set(resourcePath, cached)), cached;
7553
+ }
7554
+
7555
+ async function fetchActor(requestFn) {
7556
+ let user;
7557
+ try {
7558
+ user = await requestFn({
7559
+ uri: "/users/me",
7560
+ tag: REQUEST_TAG.accessResolveActor
7561
+ });
7562
+ } catch (err) {
7563
+ throw new Error('workflow: /users/me request failed. The engine resolves the actor from the client\'s token via `client.request({ uri: "/users/me" })`. Check the token/connectivity.', {
7564
+ cause: err
7369
7565
  });
7370
7566
  }
7567
+ if (!user || typeof user.id != "string" || user.id.length === 0) return;
7568
+ const roleNames = user.roles?.map(r => r.name).filter(n => !!n) ?? [];
7569
+ return {
7570
+ kind: "person",
7571
+ id: user.id,
7572
+ ...roleNames.length > 0 ? {
7573
+ roles: roleNames
7574
+ } : {}
7575
+ };
7371
7576
  }
7372
7577
 
7373
- async function drainCondemnedChildren({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, clock: clock, executionContext: executionContext, telemetry: telemetry, draining: draining = /* @__PURE__ */ new Set}) {
7374
- if (draining.has(instanceId)) return;
7375
- draining.add(instanceId);
7376
- const instance = await getInstanceDocument(client, instanceId);
7377
- if (!instance) return;
7378
- const condemned = condemnedSubworkflows(instance);
7379
- if (condemned.length !== 0) {
7380
- for (const row of condemned) await settleCondemnedRow({
7381
- client: client,
7382
- ownerId: instanceId,
7383
- row: row,
7384
- actor: actor,
7385
- clientForGdr: clientForGdr,
7386
- refSurface: refSurface,
7387
- clock: clock,
7388
- executionContext: executionContext,
7389
- telemetry: telemetry,
7390
- draining: draining
7391
- });
7392
- await stampDrainedRows({
7393
- client: client,
7394
- instance: instance,
7395
- condemned: condemned,
7396
- clock: clock,
7397
- executionContext: executionContext
7578
+ async function fetchGrantsCached(requestFn, resourcePath) {
7579
+ try {
7580
+ return await fetchGrants({
7581
+ client: {
7582
+ request: requestFn
7583
+ },
7584
+ resourcePath: resourcePath
7398
7585
  });
7586
+ } catch (err) {
7587
+ console.warn(`workflow: failed to fetch grants from "${resourcePath}"; advisory permission reads that depend on them are skipped — a rendered $can stays undefined (conditions referencing it fail closed) and the subject-write forecast omits this resource. The lake still enforces writes. Original error: ${errorMessage(err)}`);
7588
+ return;
7399
7589
  }
7400
7590
  }
7401
7591
 
7402
- async function settleCondemnedRow({client: client, ownerId: ownerId, row: row, actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, clock: clock, executionContext: executionContext, telemetry: telemetry, draining: draining}) {
7403
- const childId = toBareId(row.ref.id);
7592
+ const REPLAY_SURFACE = {
7593
+ allows: () => !0,
7594
+ description: "any resource (in-memory replay; the committing surface enforces)"
7595
+ };
7596
+
7597
+ async function whatIfFireAction(args) {
7598
+ const {instance: instance, definition: definition, snapshot: snapshot, now: now, actor: actor, stage: stage, activity: activity, action: action} = args;
7599
+ if (!replayable({
7600
+ instance: instance,
7601
+ stage: stage,
7602
+ action: action
7603
+ })) return;
7604
+ const ctx = {
7605
+ now: now,
7606
+ instance: instance,
7607
+ definition: definition,
7608
+ snapshot: snapshot,
7609
+ refSurface: REPLAY_SURFACE,
7610
+ actor: actor
7611
+ }, mutation = startMutation(instance);
7404
7612
  try {
7405
- await abortInstance({
7406
- client: client,
7407
- instanceId: childId,
7408
- reason: row.abortPending?.reason ?? "condemned by parent",
7409
- options: {
7410
- ...actor ? {
7411
- actor: actor
7412
- } : {},
7413
- clientForGdr: clientForGdr,
7414
- refSurface: refSurface,
7415
- ...clock ? {
7416
- clock: clock
7417
- } : {},
7418
- ...executionContext ? {
7419
- executionContext: executionContext
7420
- } : {},
7421
- ...telemetry ? {
7422
- telemetry: telemetry
7423
- } : {}
7424
- }
7425
- }), await drainCondemnedChildren({
7426
- client: client,
7427
- instanceId: childId,
7613
+ await applyActionFire({
7614
+ ctx: ctx,
7615
+ mutation: mutation,
7616
+ activity: activity,
7617
+ action: action,
7618
+ params: {},
7428
7619
  actor: actor,
7429
- clientForGdr: clientForGdr,
7430
- refSurface: refSurface,
7431
- clock: clock,
7432
- executionContext: executionContext,
7433
- telemetry: telemetry,
7434
- draining: draining
7435
- });
7436
- } catch (cause) {
7437
- if (cause instanceof InstanceNotFoundError) return;
7438
- throw cause instanceof WorkflowStateDivergedError ? cause : new WorkflowStateDivergedError({
7439
- instanceId: ownerId,
7440
- guardError: cause,
7441
- reason: `owed child abort of "${childId}" failed while draining condemned rows`
7620
+ triggered: !1
7621
+ }), await runTriggeredActions({
7622
+ ctx: ctx,
7623
+ mutation: mutation,
7624
+ stage: stage
7442
7625
  });
7626
+ } catch (err) {
7627
+ if (err instanceof FieldValueShapeError) return;
7628
+ throw err;
7443
7629
  }
7630
+ const transition = await pickTransition(liveViewContext(ctx, mutation), stage);
7631
+ return transition === void 0 ? {
7632
+ exitsStage: !1
7633
+ } : {
7634
+ exitsStage: !0,
7635
+ transition: transition.name
7636
+ };
7444
7637
  }
7445
7638
 
7446
- async function stampDrainedRows({client: client, instance: instance, condemned: condemned, clock: clock, executionContext: executionContext}) {
7447
- const now = (clock ?? wallClock)(), stamp = resolveExecutionContext(executionContext), ids = condemned.map(row => toBareId(row.ref.id)), children = await client.fetch("*[_id in $ids]{_id, currentStage, completedAt, abortedAt, modelVersion, minReaderModel}", {
7448
- ids: ids
7449
- }), byId = new Map(children.map(c => [ assertReadableModel(c)._id, c ])), condemnedKeys = new Set(condemned.map(row => row._key)), history = [ ...instance.history ], subworkflows = (instance.subworkflows ?? []).map(row => {
7450
- if (row.resolved !== void 0 || !condemnedKeys.has(row._key)) return row;
7451
- const child = byId.get(toBareId(row.ref.id)), resolved = child !== void 0 ? terminalResolution(child) : {
7452
- at: now,
7453
- aborted: !0
7454
- };
7455
- return resolved === void 0 ? row : (history.push(...stampHistoryEntries([ subworkflowResolvedEntry({
7456
- row: row,
7457
- at: now,
7458
- status: resolved.aborted === !0 ? "aborted" : "done"
7459
- }) ], stamp)), {
7460
- ...row,
7461
- resolved: resolved
7639
+ function replayable(args) {
7640
+ const {instance: instance, stage: stage, action: action} = args;
7641
+ if (terminalState(instance) !== "in-flight" || (action.params ?? []).length > 0 || action.spawn !== void 0) return !1;
7642
+ const primed = new Set((findOpenStageEntry(instance)?.activities ?? []).map(e => e.name));
7643
+ return (stage.activities ?? []).every(declared => primed.has(declared.name)) ? !(stage.activities ?? []).flatMap(declared => declared.actions ?? []).some(sibling => isCascadeFired(sibling) && sibling.spawn !== void 0) : !1;
7644
+ }
7645
+
7646
+ async function buildFieldInsights({sites: sites, snapshot: snapshot}) {
7647
+ const insights = [];
7648
+ for (const field of fieldsReadAcross(sites)) {
7649
+ const involved = sites.filter(entry => readsField(entry.insight, field));
7650
+ insights.push({
7651
+ field: field,
7652
+ reads: dedupeReads(involved.flatMap(entry => fieldReads(entry.insight, field))),
7653
+ involvedIn: involved.map(entry => entry.site),
7654
+ proposals: await verifyProposals({
7655
+ field: field,
7656
+ involved: involved,
7657
+ snapshot: snapshot
7658
+ })
7462
7659
  });
7463
- });
7464
- await client.patch(instance._id).set({
7465
- subworkflows: subworkflows,
7466
- history: history,
7467
- lastChangedAt: now
7468
- }).ifRevisionId(instance._rev).commit(SYNC_COMMIT);
7660
+ }
7661
+ return insights;
7469
7662
  }
7470
7663
 
7471
- function terminalResolution(child) {
7472
- const status = resolvedChildStatus(child);
7473
- if (!(status === void 0 || child.completedAt === void 0 || child.completedAt === null)) return {
7474
- at: child.completedAt,
7475
- stage: child.currentStage,
7476
- ...status === "aborted" ? {
7477
- aborted: !0
7478
- } : {}
7479
- };
7664
+ function fieldsReadAcross(sites) {
7665
+ const names = sites.flatMap(entry => entry.insight.analysis.reads).filter(read => read.variable === "fields").map(read => read.path[0]).filter(head => typeof head == "string");
7666
+ return [ ...new Set(names) ];
7480
7667
  }
7481
7668
 
7482
- function subworkflowResolvedEntry({row: row, at: at, status: status}) {
7483
- return {
7484
- _key: randomKey(),
7485
- _type: "subworkflowResolved",
7486
- at: at,
7487
- activity: row.activity,
7488
- instanceRef: row.ref,
7489
- status: status
7490
- };
7669
+ function fieldReads(insight, field) {
7670
+ return insight.analysis.reads.filter(read => read.variable === "fields" && read.path[0] === field);
7491
7671
  }
7492
7672
 
7493
- async function propagateToAncestors({client: client, instanceId: instanceId, actor: actor, clientForGdr: clientForGdr, refSurface: refSurface, clock: clock, executionContext: executionContext, telemetry: telemetry}) {
7494
- const loaded = await loadPropagationPair(client, instanceId);
7495
- if (loaded === void 0) return;
7496
- const {child: child, parent: parent} = loaded, definition = parseDefinitionSnapshot(parent), ctx = await buildEngineContext({
7497
- client: client,
7498
- clientForGdr: clientForGdr,
7499
- refSurface: refSurface,
7500
- instance: parent,
7501
- definition: definition,
7502
- ...clock ? {
7503
- clock: clock
7504
- } : {},
7505
- ...actor ? {
7506
- actor: actor
7507
- } : {},
7508
- ...executionContext ? {
7509
- executionContext: executionContext
7510
- } : {},
7511
- ...telemetry ? {
7512
- telemetry: telemetry
7513
- } : {}
7514
- }), mutation = startMutation(parent), row = mutation.subworkflows.find(r => toBareId(r.ref.id) === child._id);
7515
- if (row === void 0) {
7516
- await recordOrphanedPropagation({
7517
- ctx: ctx,
7518
- mutation: mutation,
7519
- child: child
7520
- });
7521
- return;
7522
- }
7523
- const changed = stampResolvedChild({
7524
- mutation: mutation,
7525
- row: row,
7526
- child: child,
7527
- now: ctx.now
7528
- }), parentTerminal = parent.completedAt !== void 0;
7529
- changed && await persist(ctx, mutation), !parentTerminal && (await cascadeAutoTransitions({
7530
- client: client,
7531
- instanceId: parent._id,
7532
- actor: actor,
7533
- clientForGdr: clientForGdr,
7534
- refSurface: refSurface,
7535
- clock: clock,
7536
- executionContext: executionContext,
7537
- telemetry: telemetry
7538
- }), await propagateToAncestors({
7539
- client: client,
7540
- instanceId: parent._id,
7541
- actor: actor,
7542
- clientForGdr: clientForGdr,
7543
- refSurface: refSurface,
7544
- clock: clock,
7545
- executionContext: executionContext,
7546
- telemetry: telemetry
7547
- }));
7673
+ function readsField(insight, field) {
7674
+ return fieldReads(insight, field).length > 0;
7548
7675
  }
7549
7676
 
7550
- async function loadPropagationPair(client, instanceId) {
7551
- const child = await getInstanceDocument(client, instanceId);
7552
- if (!child) return;
7553
- const parentGdr = parentRef(child);
7554
- if (parentGdr === void 0) return;
7555
- const parentDoc = await client.getDocument(toBareId(parentGdr.id));
7556
- if (!(!parentDoc || parentDoc._type !== WORKFLOW_INSTANCE_TYPE)) return {
7557
- child: child,
7558
- parent: readInstanceDoc(parentDoc)
7559
- };
7677
+ async function verifyProposals({field: field, involved: involved, snapshot: snapshot}) {
7678
+ const proposals = [];
7679
+ for (const assign of candidateAssignments(involved, field)) {
7680
+ const consequences = [];
7681
+ for (const entry of involved) {
7682
+ const result = await whatIfCondition({
7683
+ condition: entry.condition,
7684
+ dataset: snapshot.docs,
7685
+ params: entry.params,
7686
+ assign: assign
7687
+ });
7688
+ result.changed && consequences.push({
7689
+ site: entry.site,
7690
+ before: result.before,
7691
+ after: result.after
7692
+ });
7693
+ }
7694
+ consequences.length > 0 && proposals.push({
7695
+ assign: assign,
7696
+ consequences: consequences
7697
+ });
7698
+ }
7699
+ return proposals;
7560
7700
  }
7561
7701
 
7562
- function stampResolvedChild({mutation: mutation, row: row, child: child, now: now}) {
7563
- if (row.resolved !== void 0) return !1;
7564
- const resolved = terminalResolution(child);
7565
- return resolved === void 0 ? !1 : (row.resolved = resolved, mutation.history.push(subworkflowResolvedEntry({
7566
- row: row,
7567
- at: now,
7568
- status: resolved.aborted === !0 ? "aborted" : "done"
7569
- })), !0);
7702
+ function candidateAssignments(involved, field) {
7703
+ const candidates = involved.flatMap(entry => entry.insight.blockedBy).flatMap(atom => atom.requirement !== void 0 ? [ atom.requirement ] : []).filter(requirement => requirement.target.variable === "fields").filter(requirement => requirement.target.path[0] === field).flatMap(assignmentFor).filter(fabricatable);
7704
+ return dedupeBy(candidates, assignment => JSON.stringify([ assignment.target.path, assignment.value ]));
7570
7705
  }
7571
7706
 
7572
- async function recordOrphanedPropagation({ctx: ctx, mutation: mutation, child: child}) {
7573
- const childUri = selfGdr(child);
7574
- mutation.history.some(h => h._type === "subworkflowOrphaned" && h.instanceRef.id === childUri) || (mutation.history.push({
7575
- _key: randomKey(),
7576
- _type: "subworkflowOrphaned",
7577
- at: ctx.now,
7578
- instanceRef: {
7579
- id: childUri,
7580
- type: WORKFLOW_INSTANCE_TYPE
7581
- },
7582
- detail: `Instance "${child._id}" names this instance as its parent but no subworkflow-registry row matches it, so its state cannot drive any gate here.`
7583
- }), await persist(ctx, mutation));
7584
- }
7707
+ function assignmentFor(requirement) {
7708
+ switch (requirement.kind) {
7709
+ case "equals":
7710
+ return [ {
7711
+ target: requirement.target,
7712
+ value: requirement.value
7713
+ } ];
7585
7714
 
7586
- function startMutation(instance) {
7587
- return {
7588
- minReaderModel: minReaderModelOf(instance),
7589
- currentStage: instance.currentStage,
7590
- fields: (instance.fields ?? []).map(s => ({
7591
- ...s
7592
- })),
7593
- stages: instance.stages.map(s => ({
7594
- ...s,
7595
- fields: (s.fields ?? []).map(entry => ({
7596
- ...entry
7597
- })),
7598
- activities: s.activities.map(t => ({
7599
- ...t,
7600
- ...t.fields !== void 0 ? {
7601
- fields: t.fields.map(entry => ({
7602
- ...entry
7603
- }))
7604
- } : {}
7605
- }))
7606
- })),
7607
- subworkflows: (instance.subworkflows ?? []).map(row => ({
7608
- ...row,
7609
- ...row.abortPending !== void 0 ? {
7610
- abortPending: {
7611
- ...row.abortPending
7612
- }
7613
- } : {},
7614
- ...row.resolved !== void 0 ? {
7615
- resolved: {
7616
- ...row.resolved
7617
- }
7618
- } : {}
7619
- })),
7620
- pendingEffects: [ ...instance.pendingEffects ],
7621
- effectHistory: [ ...instance.effectHistory ],
7622
- context: [ ...instance.context ],
7623
- history: [ ...instance.history ],
7624
- processedRequests: [ ...instance.processedRequests ?? [] ],
7625
- lastChangedAt: instance.lastChangedAt,
7626
- ...instance.completedAt !== void 0 ? {
7627
- completedAt: instance.completedAt
7628
- } : {},
7629
- ...instance.abortedAt !== void 0 ? {
7630
- abortedAt: instance.abortedAt
7631
- } : {},
7632
- pendingCreates: []
7633
- };
7634
- }
7715
+ case "truthy":
7716
+ return [ {
7717
+ target: requirement.target,
7718
+ value: !0
7719
+ } ];
7635
7720
 
7636
- function instanceStateFields(src) {
7637
- const state = {
7638
- currentStage: src.currentStage,
7639
- fields: src.fields,
7640
- stages: src.stages,
7641
- subworkflows: src.subworkflows ?? [],
7642
- pendingEffects: src.pendingEffects,
7643
- effectHistory: src.effectHistory,
7644
- context: src.context,
7645
- history: src.history,
7646
- processedRequests: src.processedRequests ?? [],
7647
- ...src.completedAt !== void 0 ? {
7648
- completedAt: src.completedAt
7649
- } : {},
7650
- ...src.abortedAt !== void 0 ? {
7651
- abortedAt: src.abortedAt
7652
- } : {}
7653
- };
7654
- return {
7655
- ...modelStampFor({
7656
- documentType: "instance",
7657
- document: state,
7658
- storedMinReaderModel: src.minReaderModel
7659
- }),
7660
- ...state
7661
- };
7662
- }
7721
+ case "falsy":
7722
+ return [ {
7723
+ target: requirement.target,
7724
+ value: !1
7725
+ } ];
7663
7726
 
7664
- function liveViewContext(ctx, mutation) {
7665
- const materialized = materializeInstance(ctx.instance, mutation);
7666
- return {
7667
- ...ctx,
7668
- instance: materialized,
7669
- snapshot: overlayInstanceInSnapshot(ctx.snapshot, materialized)
7670
- };
7727
+ case "differs":
7728
+ case "defined":
7729
+ case "undefined":
7730
+ case "compares":
7731
+ return [];
7732
+ }
7671
7733
  }
7672
7734
 
7673
- function materializeInstance(base, mutation) {
7674
- return {
7675
- ...base,
7676
- currentStage: mutation.currentStage,
7677
- fields: mutation.fields,
7678
- stages: mutation.stages,
7679
- subworkflows: mutation.subworkflows,
7680
- context: mutation.context,
7681
- pendingEffects: mutation.pendingEffects,
7682
- effectHistory: mutation.effectHistory
7683
- };
7735
+ function fabricatable(assignment) {
7736
+ return assignment.target.path.every(segment => typeof segment != "number" || segment <= MAX_COUNTERFACTUAL_INDEX);
7684
7737
  }
7685
7738
 
7686
- async function persist(ctx, mutation) {
7687
- stampExecutionContext(ctx, mutation);
7688
- const set = {
7689
- ...instanceStateFields(mutation),
7690
- lastChangedAt: ctx.now
7691
- }, pendingCreates = mutation.pendingCreates;
7692
- if (pendingCreates.length === 0) return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit(SYNC_COMMIT);
7693
- const tx = ctx.client.transaction();
7694
- for (const {body: body} of pendingCreates) tx.create(body);
7695
- tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)),
7696
- await tx.commit(), mutation.pendingCreates = [];
7697
- const actorForPriming = ctx.actor;
7698
- for (const {body: body, started: started} of pendingCreates) try {
7699
- await primeInitialStage({
7700
- client: ctx.client,
7701
- instanceId: body._id,
7702
- actor: actorForPriming,
7703
- clientForGdr: ctx.clientForGdr,
7704
- refSurface: ctx.refSurface,
7705
- clock: ctx.clock,
7706
- executionContext: ctx.executionContext,
7707
- telemetry: ctx.telemetry
7708
- }), await cascadeAutoTransitions({
7709
- client: ctx.client,
7710
- instanceId: body._id,
7711
- actor: actorForPriming,
7712
- clientForGdr: ctx.clientForGdr,
7713
- refSurface: ctx.refSurface,
7714
- clock: ctx.clock,
7715
- executionContext: ctx.executionContext,
7716
- telemetry: ctx.telemetry
7717
- }), await propagateToAncestors({
7718
- client: ctx.client,
7719
- instanceId: body._id,
7720
- actor: actorForPriming,
7721
- clientForGdr: ctx.clientForGdr,
7722
- refSurface: ctx.refSurface,
7723
- clock: ctx.clock,
7724
- executionContext: ctx.executionContext,
7725
- telemetry: ctx.telemetry
7726
- }), ctx.telemetry.log(WorkflowInstanceStarted, started);
7727
- } catch (cause) {
7728
- throw cause instanceof WorkflowStateDivergedError ? cause : new WorkflowStateDivergedError({
7729
- instanceId: ctx.instance._id,
7730
- guardError: cause,
7731
- reason: `spawned child "${body._id}" failed to settle after the spawn transaction committed`
7739
+ async function subjectResourceGrants(args) {
7740
+ const {clientForGdr: clientForGdr, instance: instance} = args, clients = /* @__PURE__ */ new Map;
7741
+ for (const {parsed: parsed, resource: resource} of foreignSubjectRefs(instance)) {
7742
+ const key = resourceGdr(resource);
7743
+ clients.has(key) || clients.set(key, {
7744
+ resource: resource,
7745
+ client: clientForGdr(parsed)
7732
7746
  });
7733
7747
  }
7734
- const reloaded = await getInstanceDocument(ctx.client, ctx.instance._id);
7735
- if (!reloaded) throw new Error(`Instance ${ctx.instance._id} disappeared after transaction commit`);
7736
- return reloaded;
7748
+ const resolved = await Promise.all([ ...clients.entries() ].map(async ([key, entry]) => {
7749
+ const path = aclPathForResource(entry.resource);
7750
+ if (path === void 0) return;
7751
+ const grants = await grantsForClientPath(entry.client, path);
7752
+ return grants === void 0 ? void 0 : [ key, grants ];
7753
+ }));
7754
+ return new Map(resolved.filter(entry => entry !== void 0));
7737
7755
  }
7738
7756
 
7739
- function stampExecutionContext(ctx, mutation) {
7740
- const stamp = ctx.executionContext, appended = mutation.history.slice(ctx.instance.history.length);
7741
- mutation.history = [ ...mutation.history.slice(0, ctx.instance.history.length), ...stampHistoryEntries(appended, stamp) ];
7742
- for (const {body: body} of mutation.pendingCreates) body.history = stampHistoryEntries(body.history, stamp);
7757
+ async function evaluateInstance(args) {
7758
+ const {client: client, tag: tag, workflowResource: workflowResource, instanceId: instanceId, resourceClients: resourceClients} = args, now = (args.clock ?? wallClock)();
7759
+ validateTag(tag);
7760
+ const {actor: actor, grants: grants} = await resolveAccess(client, {
7761
+ ...args.grantsFromPath !== void 0 ? {
7762
+ grantsFromPath: args.grantsFromPath
7763
+ } : {}
7764
+ }), instance = await reload({
7765
+ client: client,
7766
+ instanceId: instanceId,
7767
+ tag: tag
7768
+ }), definition = parseDefinitionSnapshot(instance), clientForGdr = buildClientForGdr({
7769
+ client: client,
7770
+ workflowResource: workflowResource,
7771
+ resourceClients: resourceClients
7772
+ }), snapshot = await hydrateSnapshot({
7773
+ client: client,
7774
+ clientForGdr: clientForGdr,
7775
+ instance: instance
7776
+ }), guards = await verdictGuardsForInstance(client, instance._id), resourceGrants = await subjectResourceGrants({
7777
+ clientForGdr: clientForGdr,
7778
+ instance: instance
7779
+ });
7780
+ return evaluateFromSnapshot({
7781
+ instance: instance,
7782
+ definition: definition,
7783
+ actor: actor,
7784
+ snapshot: snapshot,
7785
+ guards: guards,
7786
+ now: now,
7787
+ resourceGrants: resourceGrants,
7788
+ ...grants !== void 0 ? {
7789
+ grants: grants
7790
+ } : {}
7791
+ });
7792
+ }
7793
+
7794
+ function memoizedByName(render) {
7795
+ const rendered = /* @__PURE__ */ new Map;
7796
+ return activityName => {
7797
+ const hit = rendered.get(activityName);
7798
+ if (hit !== void 0) return hit;
7799
+ const scope = render(activityName);
7800
+ return rendered.set(activityName, scope), scope;
7801
+ };
7743
7802
  }
7744
7803
 
7745
- function currentStageEntry(mutation) {
7746
- const entry = findOpenStageEntry(mutation);
7747
- if (entry === void 0) throw new Error(`Mutation invariant broken: no current (un-exited) StageEntry for currentStage "${mutation.currentStage}"`);
7748
- return entry;
7804
+ function currentStageOf(instance, definition) {
7805
+ try {
7806
+ return findStage(definition, instance.currentStage);
7807
+ } catch (err) {
7808
+ throw new Error(`Instance "${instance._id}" currentStage "${instance.currentStage}" not in definition`, {
7809
+ cause: err
7810
+ });
7811
+ }
7749
7812
  }
7750
7813
 
7751
- function currentActivities(mutation) {
7752
- return currentStageEntry(mutation).activities;
7814
+ async function explainSite(args) {
7815
+ const {site: site, condition: condition, params: params, snapshot: snapshot, sites: sites} = args, insight = await explainCondition({
7816
+ condition: condition,
7817
+ dataset: snapshot.docs,
7818
+ params: params
7819
+ });
7820
+ return sites.push({
7821
+ site: site,
7822
+ condition: condition,
7823
+ params: params,
7824
+ insight: insight
7825
+ }), insight;
7753
7826
  }
7754
7827
 
7755
- function findActivityInCurrentStage(ctx, activityName) {
7756
- const stage = findStage(ctx.definition, ctx.instance.currentStage), activity = (stage.activities ?? []).find(t => t.name === activityName);
7757
- if (activity === void 0) throw new ContractViolationError(`Activity "${activityName}" not found in current stage "${stage.name}" of ${ctx.definition.name}`);
7758
- return {
7828
+ async function evaluateFromSnapshot(args) {
7829
+ const {instance: instance, definition: definition, actor: actor, grants: grants, snapshot: snapshot} = args, now = args.now ?? wallClock(), stage = currentStageOf(instance, definition), autonomy = autonomyOf(definition), stageAutonomy2 = stageAutonomyOf(autonomy, stage.name), fireConsequence = (activity, action) => whatIfFireAction({
7830
+ instance: instance,
7831
+ definition: definition,
7832
+ snapshot: snapshot,
7833
+ now: now,
7834
+ actor: actor,
7759
7835
  stage: stage,
7760
- activity: activity
7836
+ activity: activity,
7837
+ action: action
7838
+ }), scopeSource = {
7839
+ instance: instance,
7840
+ definition: definition,
7841
+ snapshot: snapshot,
7842
+ now: now
7843
+ }, can = await advisoryCan({
7844
+ instance: instance,
7845
+ actor: actor,
7846
+ grants: grants
7847
+ }), scope = await renderConditionScope(scopeSource, {
7848
+ actor: actor,
7849
+ vars: {
7850
+ can: can
7851
+ }
7852
+ }), scopeForActivity = memoizedByName(activityName => renderConditionScope(scopeSource, {
7853
+ activityName: activityName,
7854
+ actor: actor,
7855
+ vars: {
7856
+ can: can
7857
+ }
7858
+ })), cascadeScopeForActivity = memoizedByName(activityName => renderConditionScope(scopeSource, {
7859
+ activityName: activityName
7860
+ })), currentActivityEntries = findOpenStageEntry(instance)?.activities ?? [], guardDenial = await instanceGuardReason({
7861
+ instance: instance,
7862
+ actor: actor,
7863
+ guards: args.guards
7864
+ }), subjectDenials = await forecastSubjectDenials({
7865
+ instance: instance,
7866
+ actor: actor,
7867
+ snapshot: snapshot,
7868
+ resourceGrants: args.resourceGrants
7869
+ }), subjectPermissionReason = subjectDenials.length > 0 ? {
7870
+ kind: "subject-permission-denied",
7871
+ denied: subjectDenials
7872
+ } : void 0, sites = [], activityEvaluations = [];
7873
+ for (const activity of stage.activities ?? []) activityEvaluations.push(await evaluateActivity({
7874
+ activity: activity,
7875
+ statusEntry: currentActivityEntries.find(t => t.name === activity.name),
7876
+ instance: instance,
7877
+ snapshot: snapshot,
7878
+ activityScope: await scopeForActivity(activity.name),
7879
+ cascadeActivityScope: () => cascadeScopeForActivity(activity.name),
7880
+ stageHasExits: !isTerminalStage(stage),
7881
+ guardDenial: guardDenial,
7882
+ subjectPermissionReason: subjectPermissionReason,
7883
+ sites: sites,
7884
+ autonomy: activityAutonomyOf(stageAutonomy2, activity.name),
7885
+ fireConsequence: fireConsequence
7886
+ }));
7887
+ const cascadeParams = await renderConditionScope(scopeSource), transitionEvaluations = [];
7888
+ for (const transition of stage.transitions ?? []) {
7889
+ const insight = await explainSite({
7890
+ site: {
7891
+ kind: "transition",
7892
+ transition: transition.name
7893
+ },
7894
+ condition: transition.when,
7895
+ params: cascadeParams,
7896
+ snapshot: snapshot,
7897
+ sites: sites
7898
+ });
7899
+ transitionEvaluations.push({
7900
+ transition: transition,
7901
+ whenSatisfied: insight.outcome === "satisfied",
7902
+ unevaluable: insight.outcome === "unevaluable",
7903
+ insight: insight
7904
+ });
7905
+ }
7906
+ const currentStage = {
7907
+ stage: stage,
7908
+ activities: activityEvaluations,
7909
+ transitions: transitionEvaluations,
7910
+ autonomy: stageAutonomy2
7911
+ }, pendingOnYou = activityEvaluations.filter(t => t.pendingOnActor), canInteract = activityEvaluations.some(t => t.actions.some(a => a.allowed)), editGuardDenial = guardDenial?.kind === "mutation-guard-denied" ? guardDenial : void 0, editableFields = await evaluateEditableFields({
7912
+ instance: instance,
7913
+ definition: definition,
7914
+ stage: stage,
7915
+ snapshot: snapshot,
7916
+ scope: scope,
7917
+ scopeForActivity: scopeForActivity,
7918
+ guardDenial: editGuardDenial,
7919
+ sites: sites
7920
+ });
7921
+ return {
7922
+ instance: instance,
7923
+ definition: definition,
7924
+ actor: actor,
7925
+ currentStage: currentStage,
7926
+ pendingOnYou: pendingOnYou,
7927
+ canInteract: canInteract,
7928
+ editableFields: editableFields,
7929
+ fieldInsights: await buildFieldInsights({
7930
+ sites: sites,
7931
+ snapshot: snapshot
7932
+ }),
7933
+ autonomy: autonomy
7761
7934
  };
7762
7935
  }
7763
7936
 
7764
- function requireMutationActivityEntry(mutation, activity) {
7765
- const mutEntry = currentActivities(mutation).find(t => t.name === activity);
7766
- if (mutEntry === void 0) throw new Error(`Activity "${activity}" disappeared from mutation copy — invariant broken`);
7767
- return mutEntry;
7768
- }
7937
+ const AUTONOMY_CACHE = /* @__PURE__ */ new WeakMap;
7769
7938
 
7770
- function findCurrentStageEntry(instance) {
7771
- return findOpenStageEntry(instance);
7939
+ function autonomyOf(definition) {
7940
+ const hit = AUTONOMY_CACHE.get(definition);
7941
+ if (hit !== void 0) return hit;
7942
+ const derived = deriveWorkflowAutonomy(definition);
7943
+ return AUTONOMY_CACHE.set(definition, derived), derived;
7772
7944
  }
7773
7945
 
7774
- function findCurrentActivities(instance) {
7775
- return findCurrentStageEntry(instance)?.activities ?? [];
7946
+ async function evaluateEditableFields(args) {
7947
+ const {instance: instance, definition: definition, stage: stage, snapshot: snapshot, scope: scope, scopeForActivity: scopeForActivity, guardDenial: guardDenial, sites: sites} = args, fields = [];
7948
+ for (const site of editableFieldsInStage(definition, stage)) {
7949
+ const window = fieldWindowOpen(instance, site), insight = await editPredicateInsight({
7950
+ site: site,
7951
+ snapshot: snapshot,
7952
+ scope: scope,
7953
+ scopeForActivity: scopeForActivity,
7954
+ sites: sites
7955
+ }), predicateSatisfied = insight === void 0 || insight.outcome === "satisfied", reason = editDisabledReason({
7956
+ effective: site.effective,
7957
+ instance: instance,
7958
+ window: window,
7959
+ guardDenial: guardDenial,
7960
+ predicateSatisfied: predicateSatisfied
7961
+ }), value = readFieldValue(instance, site);
7962
+ fields.push({
7963
+ scope: site.scope,
7964
+ ...site.activity !== void 0 ? {
7965
+ activity: site.activity
7966
+ } : {},
7967
+ name: site.name,
7968
+ type: site.type,
7969
+ ...site.title !== void 0 ? {
7970
+ title: site.title
7971
+ } : {},
7972
+ ...site.validation !== void 0 ? {
7973
+ validation: site.validation
7974
+ } : {},
7975
+ value: value,
7976
+ editable: reason === void 0,
7977
+ ...reason !== void 0 ? {
7978
+ disabledReason: reason
7979
+ } : {},
7980
+ ...fieldProvenance(instance, site.ref),
7981
+ ...insight !== void 0 ? {
7982
+ insight: insight
7983
+ } : {}
7984
+ });
7985
+ }
7986
+ return fields;
7776
7987
  }
7777
7988
 
7778
- async function completeEffect(args) {
7779
- const {client: client, instanceId: instanceId, effectKey: effectKey, status: status, outputs: outputs, ops: ops, detail: detail, error: error, durationMs: durationMs, requestRecord: requestRecord, options: options} = args;
7780
- if (status !== "done" && status !== "failed") throw new ContractViolationError(`completeEffect: status must be "done" or "failed", got ${JSON.stringify(status)} — "cancelled" is engine-stamped by abort, never reportable`);
7781
- return retryOnRevisionConflict({
7782
- client: client,
7783
- instanceId: instanceId,
7784
- options: options,
7785
- commit: ctx => commitCompleteEffect({
7786
- ctx: ctx,
7787
- effectKey: effectKey,
7788
- status: status,
7789
- outputs: outputs,
7790
- ops: ops,
7791
- detail: detail,
7792
- error: error,
7793
- durationMs: durationMs,
7794
- requestRecord: requestRecord,
7795
- actor: options.actor
7796
- }),
7797
- onExhausted: () => new ConcurrentCompleteEffectError({
7798
- instanceId: instanceId,
7799
- effectKey: effectKey,
7800
- attempts: CONCURRENT_COMMIT_MAX_ATTEMPTS
7801
- })
7989
+ async function editPredicateInsight(args) {
7990
+ const {site: site, snapshot: snapshot, scope: scope, scopeForActivity: scopeForActivity, sites: sites} = args;
7991
+ if (typeof site.effective != "string") return;
7992
+ const params = site.scope === "activity" && site.activity !== void 0 ? await scopeForActivity(site.activity) : scope;
7993
+ return explainSite({
7994
+ site: {
7995
+ kind: "editable-field",
7996
+ scope: site.scope,
7997
+ name: site.name,
7998
+ ...site.activity !== void 0 ? {
7999
+ activity: site.activity
8000
+ } : {}
8001
+ },
8002
+ condition: site.effective,
8003
+ params: params,
8004
+ snapshot: snapshot,
8005
+ sites: sites
7802
8006
  });
7803
8007
  }
7804
8008
 
7805
- function buildEffectHistoryEntry(pending, outcome) {
7806
- const {status: status, ranAt: ranAt, actor: actor, detail: detail, error: error, durationMs: durationMs, outputs: outputs} = outcome, resolvedActor = actor ?? pending.actor;
8009
+ async function forecastSubjectDenials(args) {
8010
+ const {instance: instance, actor: actor, snapshot: snapshot, resourceGrants: resourceGrants} = args;
8011
+ if (resourceGrants === void 0 || resourceGrants.size === 0) return [];
8012
+ const denials = [], seen = /* @__PURE__ */ new Set;
8013
+ for (const {ref: ref, parsed: parsed, resource: resource} of foreignSubjectRefs(instance)) {
8014
+ if (seen.has(ref.id)) continue;
8015
+ seen.add(ref.id);
8016
+ const grants = resourceGrants.get(resourceGdr(resource));
8017
+ if (grants === void 0) continue;
8018
+ const doc = snapshot.docs.find(d => d._id === ref.id);
8019
+ doc !== void 0 && await subjectUpdateAllowed({
8020
+ doc: doc,
8021
+ parsed: parsed,
8022
+ grants: grants,
8023
+ actorId: actor.id
8024
+ }) === !1 && denials.push({
8025
+ subject: ref.id,
8026
+ resource: resourceGdr(resource),
8027
+ permission: "update"
8028
+ });
8029
+ }
8030
+ return denials;
8031
+ }
8032
+
8033
+ async function subjectUpdateAllowed(args) {
8034
+ const {doc: doc, parsed: parsed, grants: grants, actorId: actorId} = args;
8035
+ try {
8036
+ return await grantsPermissionOn({
8037
+ document: {
8038
+ ...doc,
8039
+ _id: parsed.documentId
8040
+ },
8041
+ grants: grants,
8042
+ permission: "update",
8043
+ userId: actorId
8044
+ });
8045
+ } catch (err) {
8046
+ console.warn(`workflow: subject-write forecast skipped for "${doc._id}" — evaluating that resource's grants failed (the lake still enforces the write). Original error: ${errorMessage(err)}`);
8047
+ return;
8048
+ }
8049
+ }
8050
+
8051
+ async function evaluateActivity(args) {
8052
+ const {activity: activity, statusEntry: statusEntry, instance: instance, snapshot: snapshot, activityScope: activityScope, cascadeActivityScope: cascadeActivityScope, stageHasExits: stageHasExits, guardDenial: guardDenial, subjectPermissionReason: subjectPermissionReason, sites: sites, autonomy: autonomy, fireConsequence: fireConsequence} = args, status = statusEntry?.status ?? "skipped", assigned = activityScope.assigned === !0, {unmetRequirements: unmetRequirements, ...conditionInsights} = await explainActivityConditions({
8053
+ activity: activity,
8054
+ activityScope: activityScope,
8055
+ cascadeActivityScope: cascadeActivityScope,
8056
+ snapshot: snapshot,
8057
+ sites: sites
8058
+ }), requirementsReason = unmetRequirements.length > 0 ? {
8059
+ kind: "requirements-unmet",
8060
+ unmetRequirements: unmetRequirements
8061
+ } : void 0, actions = [];
8062
+ for (const action of activity.actions ?? []) actions.push(await evaluateAction({
8063
+ action: action,
8064
+ activityName: activity.name,
8065
+ status: status,
8066
+ instance: instance,
8067
+ snapshot: snapshot,
8068
+ activityScope: activityScope,
8069
+ cascadeActivityScope: cascadeActivityScope,
8070
+ stageHasExits: stageHasExits,
8071
+ guardDenial: guardDenial,
8072
+ subjectPermissionReason: subjectPermissionReason,
8073
+ requirementsReason: requirementsReason,
8074
+ sites: sites,
8075
+ fireConsequence: a => fireConsequence(activity, a)
8076
+ }));
7807
8077
  return {
7808
- _key: pending._key,
7809
- name: pending.name,
7810
- ...pending.title !== void 0 ? {
7811
- title: pending.title
7812
- } : {},
7813
- ...pending.description !== void 0 ? {
7814
- description: pending.description
7815
- } : {},
7816
- params: pending.params,
7817
- origin: pending.origin,
7818
- ...resolvedActor !== void 0 ? {
7819
- actor: resolvedActor
7820
- } : {},
7821
- ...pending.stageEntryKey !== void 0 ? {
7822
- stageEntryKey: pending.stageEntryKey
7823
- } : {},
7824
- ranAt: ranAt,
7825
- ...durationMs !== void 0 ? {
7826
- durationMs: durationMs
7827
- } : {},
8078
+ activity: activity,
7828
8079
  status: status,
7829
- ...detail !== void 0 ? {
7830
- detail: detail
7831
- } : {},
7832
- ...error !== void 0 ? {
7833
- error: error
8080
+ kind: deriveActivityKind(activity),
8081
+ classification: deriveExecutorClassification(activity),
8082
+ autonomy: autonomy,
8083
+ pendingOnActor: status === "active" && assigned,
8084
+ scopedOut: isFilterScopedOut({
8085
+ status: status,
8086
+ startedAt: statusEntry?.startedAt
8087
+ }),
8088
+ ...unmetRequirements.length > 0 ? {
8089
+ unmetRequirements: unmetRequirements
7834
8090
  } : {},
7835
- ...outputs !== void 0 ? {
7836
- outputs: outputs
7837
- } : {}
8091
+ ...conditionInsights,
8092
+ actions: actions
7838
8093
  };
7839
8094
  }
7840
8095
 
7841
- function buildEffectSettlement(pending, outcome) {
7842
- const {status: status, ranAt: ranAt, actor: actor, detail: detail, outputs: outputs} = outcome;
8096
+ async function explainActivityConditions({activity: activity, activityScope: activityScope, cascadeActivityScope: cascadeActivityScope, snapshot: snapshot, sites: sites}) {
8097
+ const explainAt = (site, condition) => explainSite({
8098
+ site: site,
8099
+ condition: condition,
8100
+ params: activityScope,
8101
+ snapshot: snapshot,
8102
+ sites: sites
8103
+ }), requirementEntries = [];
8104
+ for (const [name, condition] of Object.entries(activity.requirements ?? {})) requirementEntries.push([ name, await explainAt({
8105
+ kind: "requirement",
8106
+ activity: activity.name,
8107
+ requirement: name
8108
+ }, condition) ]);
8109
+ const requirementInsights = Object.fromEntries(requirementEntries), unmetRequirements = requirementEntries.filter(([, insight]) => insight.outcome !== "satisfied").map(([name]) => name), filterInsight = activity.filter !== void 0 ? await explainSite({
8110
+ site: {
8111
+ kind: "activity-filter",
8112
+ activity: activity.name
8113
+ },
8114
+ condition: activity.filter,
8115
+ params: await cascadeActivityScope(),
8116
+ snapshot: snapshot,
8117
+ sites: sites
8118
+ }) : void 0;
7843
8119
  return {
7844
- run: buildEffectHistoryEntry(pending, outcome),
7845
- event: {
7846
- _key: randomKey(),
7847
- _type: "effectCompleted",
7848
- at: ranAt,
7849
- effectKey: pending._key,
7850
- effect: pending.name,
7851
- status: status,
7852
- ...outputs !== void 0 ? {
7853
- outputs: outputs
7854
- } : {},
7855
- ...detail !== void 0 ? {
7856
- detail: detail
7857
- } : {},
7858
- ...actor !== void 0 ? {
7859
- actor: actor
7860
- } : {}
7861
- }
8120
+ unmetRequirements: unmetRequirements,
8121
+ ...activity.requirements !== void 0 ? {
8122
+ requirementInsights: requirementInsights
8123
+ } : {},
8124
+ ...filterInsight !== void 0 ? {
8125
+ filterInsight: filterInsight
8126
+ } : {}
7862
8127
  };
7863
8128
  }
7864
8129
 
7865
- function validateEffectOutputs(args) {
7866
- const {outputs: outputs, declared: declared, effectName: effectName} = args, byName = new Map(declared.map(shape => [ shape.name, shape ])), issues = [];
7867
- for (const [key, value] of Object.entries(outputs)) {
7868
- const shape = byName.get(key);
7869
- if (shape === void 0) {
7870
- issues.push(`"${key}" is not a declared output`);
7871
- continue;
7872
- }
7873
- const shapeIssues = effectOutputIssues(shape, value);
7874
- shapeIssues !== void 0 && issues.push(...shapeIssues.map(i => `"${key}": ${i}`));
7875
- }
7876
- if (issues.length > 0) throw new EffectOutputsInvalidError({
7877
- effect: effectName,
7878
- issues: issues
8130
+ async function evaluateAction(args) {
8131
+ const {action: action, activityName: activityName, snapshot: snapshot, activityScope: activityScope, sites: sites} = args, conditionScope = isCascadeFired(action) ? await args.cascadeActivityScope() : activityScope, insights = await explainActionGates({
8132
+ action: action,
8133
+ activityName: activityName,
8134
+ conditionScope: conditionScope,
8135
+ snapshot: snapshot,
8136
+ sites: sites
7879
8137
  });
7880
- }
7881
-
7882
- function effectOutputIssues(shape, value) {
7883
- return checkFieldValue({
7884
- entryType: shape.type,
7885
- value: value,
7886
- ...shape.fields !== void 0 ? {
7887
- fields: shape.fields
7888
- } : {},
7889
- ...shape.of !== void 0 ? {
7890
- of: shape.of
7891
- } : {},
7892
- ...shape.options !== void 0 ? {
7893
- options: shape.options
7894
- } : {},
7895
- ...shape.validation !== void 0 ? {
7896
- validation: shape.validation
7897
- } : {}
8138
+ if (action.when !== void 0) return triggeredActionVerdict({
8139
+ action: action,
8140
+ when: action.when,
8141
+ insights: insights
7898
8142
  });
8143
+ const verdict = fireableActionVerdict({
8144
+ args: args,
8145
+ insights: insights
8146
+ }), firing = await args.fireConsequence(action);
8147
+ return firing !== void 0 ? {
8148
+ ...verdict,
8149
+ firing: firing
8150
+ } : verdict;
7899
8151
  }
7900
8152
 
7901
- function requirePendingEffect(instance, effectKey) {
7902
- const pending = instance.pendingEffects.find(e => e._key === effectKey);
7903
- if (pending !== void 0) return pending;
7904
- const run = instance.effectHistory.find(e => e._key === effectKey), settled = run !== void 0 ? {
7905
- status: run.status,
7906
- ranAt: run.ranAt,
7907
- ...run.detail !== void 0 ? {
7908
- detail: run.detail
7909
- } : {}
7910
- } : void 0;
7911
- throw new EffectNotFoundError({
7912
- instanceId: instance._id,
7913
- effectKey: effectKey,
7914
- ...settled !== void 0 ? {
7915
- settled: settled
8153
+ async function explainActionGates({action: action, activityName: activityName, conditionScope: conditionScope, snapshot: snapshot, sites: sites}) {
8154
+ const insight = action.filter !== void 0 ? await explainSite({
8155
+ site: {
8156
+ kind: "action",
8157
+ activity: activityName,
8158
+ action: action.name
8159
+ },
8160
+ condition: action.filter,
8161
+ params: conditionScope,
8162
+ snapshot: snapshot,
8163
+ sites: sites
8164
+ }) : void 0, whenInsight = action.when !== void 0 ? await explainSite({
8165
+ site: {
8166
+ kind: "action-when",
8167
+ activity: activityName,
8168
+ action: action.name
8169
+ },
8170
+ condition: action.when,
8171
+ params: conditionScope,
8172
+ snapshot: snapshot,
8173
+ sites: sites
8174
+ }) : void 0;
8175
+ return {
8176
+ ...insight !== void 0 ? {
8177
+ insight: insight
8178
+ } : {},
8179
+ ...whenInsight !== void 0 ? {
8180
+ whenInsight: whenInsight
7916
8181
  } : {}
7917
- });
8182
+ };
7918
8183
  }
7919
8184
 
7920
- function validateCompletionInput({pending: pending, definition: definition, status: status, ops: ops, outputs: outputs}) {
7921
- if (status === "failed" && ops !== void 0 && ops.length > 0) throw new EffectOpsInvalidError({
7922
- effect: pending.name,
7923
- issues: [ "ops cannot accompany a failed completion — field.set the outcome on a done completion instead" ]
7924
- });
7925
- if (status === "failed" && outputs !== void 0) throw new EffectOutputsInvalidError({
7926
- effect: pending.name,
7927
- issues: [ "outputs cannot accompany a failed completion — report outputs on a done completion instead" ]
7928
- });
7929
- if (outputs !== void 0) {
7930
- const declared = findEffect(definition, pending.name)?.outputs ?? [];
7931
- validateEffectOutputs({
7932
- outputs: outputs,
7933
- declared: declared,
7934
- effectName: pending.name
7935
- });
7936
- }
7937
- return ops !== void 0 ? validateEffectOps(ops, pending.name) : [];
8185
+ function triggeredActionVerdict({action: action, when: when, insights: insights}) {
8186
+ return insights.insight !== void 0 && insights.insight.outcome === "unsatisfied" ? disabled({
8187
+ action: action,
8188
+ reason: {
8189
+ kind: "filter-failed",
8190
+ filter: action.filter ?? ""
8191
+ },
8192
+ ...insights
8193
+ }) : {
8194
+ ...actionEvaluationIdentity(action),
8195
+ allowed: !1,
8196
+ triggered: !0,
8197
+ disabledReason: {
8198
+ kind: "cascade-fired",
8199
+ when: when
8200
+ },
8201
+ ...insights
8202
+ };
7938
8203
  }
7939
8204
 
7940
- async function commitCompleteEffect({ctx: ctx, effectKey: effectKey, status: status, outputs: outputs, ops: ops, detail: detail, error: error, durationMs: durationMs, requestRecord: requestRecord, actor: actor}) {
7941
- assertRequestUnprocessed({
7942
- instance: ctx.instance,
7943
- record: requestRecord,
7944
- now: ctx.now
7945
- });
7946
- const pending = requirePendingEffect(ctx.instance, effectKey), validatedOps = validateCompletionInput({
7947
- pending: pending,
7948
- definition: ctx.definition,
7949
- status: status,
7950
- ops: ops,
7951
- outputs: outputs
7952
- }), mutation = startMutation(ctx.instance);
7953
- recordProcessedRequest({
7954
- mutation: mutation,
7955
- record: requestRecord,
7956
- now: ctx.now
7957
- }), mutation.pendingEffects = mutation.pendingEffects.filter(e => e._key !== effectKey);
7958
- const ranAt = ctx.now, settlement = buildEffectSettlement(pending, {
8205
+ function fireableActionVerdict({args: args, insights: insights}) {
8206
+ const {action: action, status: status, instance: instance, stageHasExits: stageHasExits, guardDenial: guardDenial} = args, lifecycle = lifecycleReason({
8207
+ instance: instance,
7959
8208
  status: status,
7960
- ranAt: ranAt,
7961
- actor: actor,
7962
- detail: detail,
7963
- error: error,
7964
- durationMs: durationMs,
7965
- outputs: outputs
8209
+ stageHasExits: stageHasExits
7966
8210
  });
7967
- mutation.effectHistory.push(settlement.run);
7968
- const wroteEffectOutputs = status === "done" && outputs !== void 0;
7969
- mutation.history.push(settlement.event);
7970
- const ranOps = await runOps({
7971
- ops: validatedOps,
7972
- mutation: mutation,
7973
- stage: ctx.instance.currentStage,
7974
- origin: {
7975
- effect: pending.name
8211
+ if (lifecycle !== void 0) return disabled({
8212
+ action: action,
8213
+ reason: lifecycle,
8214
+ ...insights
8215
+ });
8216
+ if (guardDenial !== void 0) return disabled({
8217
+ action: action,
8218
+ reason: guardDenial,
8219
+ ...insights
8220
+ });
8221
+ if (args.subjectPermissionReason !== void 0 && (action.effects?.length ?? 0) > 0) return disabled({
8222
+ action: action,
8223
+ reason: args.subjectPermissionReason,
8224
+ ...insights
8225
+ });
8226
+ if (args.requirementsReason !== void 0) return disabled({
8227
+ action: action,
8228
+ reason: args.requirementsReason,
8229
+ ...insights
8230
+ });
8231
+ const {insight: insight} = insights;
8232
+ return action.filter !== void 0 && insight !== void 0 && insight.outcome !== "satisfied" ? disabled({
8233
+ action: action,
8234
+ reason: {
8235
+ kind: "filter-failed",
8236
+ filter: action.filter
7976
8237
  },
7977
- params: pending.params,
7978
- actor: actor,
7979
- self: selfGdr(ctx.instance),
7980
- now: ranAt,
7981
- snapshot: ctx.snapshot,
7982
- refSurface: ctx.refSurface
7983
- }), needsGuardRefresh = wroteEffectOutputs || ranOps.some(isFieldOp);
7984
- return await persistThenMaybeRefresh({
7985
- ctx: ctx,
7986
- mutation: mutation,
7987
- stageName: ctx.instance.currentStage,
7988
- didChangeState: needsGuardRefresh
7989
- }), {
7990
- effectKey: effectKey,
7991
- effect: pending.name,
7992
- status: status,
7993
- origin: pending.origin.kind
8238
+ ...insights
8239
+ }) : {
8240
+ ...actionEvaluationIdentity(action),
8241
+ allowed: !0,
8242
+ ...insights
7994
8243
  };
7995
8244
  }
7996
8245
 
7997
- function buildQueuedEffect({effect: effect, origin: origin, params: params, actor: actor, now: now, stageEntryKey: stageEntryKey}) {
7998
- const key = randomKey(), pending = {
7999
- _key: key,
8000
- _type: "pendingEffect",
8001
- name: effect.name,
8002
- ...effect.title !== void 0 ? {
8003
- title: effect.title
8004
- } : {},
8005
- ...effect.description !== void 0 ? {
8006
- description: effect.description
8007
- } : {},
8008
- ...effect.bindings !== void 0 ? {
8009
- bindings: effect.bindings
8010
- } : {},
8011
- params: params,
8012
- origin: origin,
8013
- ...actor !== void 0 ? {
8014
- actor: actor
8015
- } : {},
8016
- queuedAt: now,
8017
- ...stageEntryKey !== void 0 ? {
8018
- stageEntryKey: stageEntryKey
8246
+ function actionEvaluationIdentity(action) {
8247
+ return {
8248
+ action: action,
8249
+ ...action.semantics !== void 0 ? {
8250
+ semantics: action.semantics
8019
8251
  } : {}
8020
- }, history = {
8021
- _key: randomKey(),
8022
- _type: "effectQueued",
8023
- at: now,
8024
- effectKey: key,
8025
- effect: effect.name,
8026
- origin: origin
8027
8252
  };
8028
- return {
8029
- pending: pending,
8030
- history: history
8253
+ }
8254
+
8255
+ async function instanceGuardReason({instance: instance, actor: actor, guards: guards}) {
8256
+ if (guards === void 0 || guards.length === 0) return;
8257
+ const denied = await instanceWriteDenials({
8258
+ instance: instance,
8259
+ guards: guards,
8260
+ identity: actor.id
8261
+ });
8262
+ if (denied.length !== 0) return {
8263
+ kind: "mutation-guard-denied",
8264
+ denied: deniedGuardRefs(denied)
8031
8265
  };
8032
8266
  }
8033
8267
 
8034
- async function queueEffects({ctx: ctx, mutation: mutation, effects: effects, origin: origin, actor: actor, opts: opts}) {
8035
- if (!effects || effects.length === 0) return;
8036
- const now = ctx.now, liveCtx = {
8037
- ...ctx,
8038
- instance: materializeInstance(ctx.instance, mutation)
8039
- }, stageEntryKey = opts?.stageEntryKey ?? findOpenStageEntry(liveCtx.instance)?._key, params = await ctxConditionParams(liveCtx, {
8040
- ...opts?.activityName !== void 0 ? {
8041
- activityName: opts.activityName
8042
- } : {},
8043
- ...actor !== void 0 ? {
8044
- actor: actor
8268
+ function lifecycleReason({instance: instance, status: status, stageHasExits: stageHasExits}) {
8269
+ const terminalReason = instanceTerminalReason(instance);
8270
+ if (terminalReason !== void 0) return terminalReason;
8271
+ if (!stageHasExits) return {
8272
+ kind: "stage-terminal",
8273
+ stage: instance.currentStage
8274
+ };
8275
+ if (isTerminalActivityStatus(status)) return {
8276
+ kind: "activity-not-active",
8277
+ status: status
8278
+ };
8279
+ }
8280
+
8281
+ function disabled(args) {
8282
+ return {
8283
+ ...actionEvaluationIdentity(args.action),
8284
+ allowed: !1,
8285
+ disabledReason: args.reason,
8286
+ ...args.insight !== void 0 ? {
8287
+ insight: args.insight
8045
8288
  } : {},
8046
- vars: {
8047
- params: opts?.callerParams ?? {}
8048
- }
8049
- });
8050
- for (const effect of effects) {
8051
- const resolved = await resolveBindings({
8052
- bindings: effect.bindings,
8053
- staticInput: effect.input,
8054
- snapshot: ctx.snapshot,
8055
- params: params
8056
- }), {pending: pending, history: history} = buildQueuedEffect({
8057
- effect: effect,
8058
- origin: origin,
8059
- params: resolved,
8060
- actor: actor,
8061
- now: now,
8062
- stageEntryKey: stageEntryKey
8063
- });
8064
- mutation.pendingEffects.push(pending), mutation.history.push(history);
8065
- }
8289
+ ...args.whenInsight !== void 0 ? {
8290
+ whenInsight: args.whenInsight
8291
+ } : {}
8292
+ };
8066
8293
  }
8067
8294
 
8068
8295
  function inFlightFilter() {
@@ -8090,25 +8317,81 @@ function idsArm(filter, params) {
8090
8317
  }
8091
8318
  }
8092
8319
 
8093
- function instancesQuery(args) {
8094
- const {tag: tag, filter: filter = {}} = args;
8095
- if (validateTag(tag), filter.limit !== void 0 && (!Number.isInteger(filter.limit) || filter.limit <= 0)) throw new ContractViolationError(`instancesQuery: limit must be a positive integer; got ${JSON.stringify(filter.limit)}`);
8096
- const conditions = [ `_type == "${WORKFLOW_INSTANCE_TYPE}"`, tagScopeFilter() ], params = {
8097
- tag: tag
8098
- };
8099
- filter.includeCompleted !== !0 && conditions.push(inFlightFilter()), filter.definition !== void 0 && (conditions.push("definition == $definition"),
8100
- params.definition = filter.definition), filter.stage !== void 0 && (conditions.push("currentStage == $stage"),
8101
- params.stage = filter.stage);
8102
- const arms = [ documentArm(filter, params), idsArm(filter, params) ].filter(arm => arm !== void 0);
8103
- arms.length > 0 && conditions.push(`(${arms.join(" || ")})`);
8104
- const body = `*[${conditions.join(" && ")}]`;
8105
- return filter.limit !== void 0 ? {
8106
- query: `${body} | order(startedAt desc) [0...${filter.limit}]`,
8107
- params: params
8108
- } : {
8109
- query: `${body} | order(startedAt asc)`,
8110
- params: params
8111
- };
8320
+ function instancesQuery(args) {
8321
+ const {tag: tag, filter: filter = {}} = args;
8322
+ if (validateTag(tag), filter.limit !== void 0 && (!Number.isInteger(filter.limit) || filter.limit <= 0)) throw new ContractViolationError(`instancesQuery: limit must be a positive integer; got ${JSON.stringify(filter.limit)}`);
8323
+ const conditions = [ `_type == "${WORKFLOW_INSTANCE_TYPE}"`, tagScopeFilter() ], params = {
8324
+ tag: tag
8325
+ };
8326
+ filter.includeCompleted !== !0 && conditions.push(inFlightFilter()), filter.definition !== void 0 && (conditions.push("definition == $definition"),
8327
+ params.definition = filter.definition), filter.stage !== void 0 && (conditions.push("currentStage == $stage"),
8328
+ params.stage = filter.stage);
8329
+ const arms = [ documentArm(filter, params), idsArm(filter, params) ].filter(arm => arm !== void 0);
8330
+ arms.length > 0 && conditions.push(`(${arms.join(" || ")})`);
8331
+ const body = `*[${conditions.join(" && ")}]`;
8332
+ return filter.limit !== void 0 ? {
8333
+ query: `${body} | order(startedAt desc) [0...${filter.limit}]`,
8334
+ params: params
8335
+ } : {
8336
+ query: `${body} | order(startedAt asc)`,
8337
+ params: params
8338
+ };
8339
+ }
8340
+
8341
+ const DEFAULT_EFFECT_LEASE_MS = 300 * 1e3;
8342
+
8343
+ function claimReleasedEntry(args) {
8344
+ return {
8345
+ _key: randomKey(),
8346
+ _type: "effectClaimReleased",
8347
+ at: args.at,
8348
+ effectKey: args.pending._key,
8349
+ effect: args.pending.name,
8350
+ claim: args.claim,
8351
+ via: args.via,
8352
+ actor: args.actor
8353
+ };
8354
+ }
8355
+
8356
+ const SWEEP_MAX_ATTEMPTS = 5;
8357
+
8358
+ async function sweepStaleClaims(args) {
8359
+ const {tag: tag, instanceId: instanceId} = args, client = pinApiVersion(args.client);
8360
+ validateTag(tag);
8361
+ const clock = args.clock ?? wallClock, {actor: actor} = await resolveAccess(client), stamp = resolveExecutionContext(args.executionContext);
8362
+ for (let attempt = 1; attempt <= SWEEP_MAX_ATTEMPTS; attempt++) {
8363
+ const now = clock(), instance = await reload({
8364
+ client: client,
8365
+ instanceId: instanceId,
8366
+ tag: tag
8367
+ }), expired = instance.pendingEffects.filter(e => e.claim !== void 0 && isClaimExpired(e.claim, now));
8368
+ if (expired.length === 0) return {
8369
+ released: []
8370
+ };
8371
+ const expiredKeys = new Set(expired.map(e => e._key)), releasedQueue = instance.pendingEffects.map(e => expiredKeys.has(e._key) ? stripClaim(e) : e), auditRows = stampHistoryEntries(expired.map(pending => claimReleasedEntry({
8372
+ pending: pending,
8373
+ claim: pending.claim,
8374
+ at: now,
8375
+ via: "sweep",
8376
+ actor: actor
8377
+ })), stamp);
8378
+ try {
8379
+ return await client.patch(instance._id).ifRevisionId(instance._rev).set({
8380
+ pendingEffects: releasedQueue,
8381
+ history: [ ...instance.history, ...auditRows ]
8382
+ }).commit(SYNC_COMMIT), {
8383
+ released: expired
8384
+ };
8385
+ } catch (error) {
8386
+ if (!isRevisionConflict(error)) throw error;
8387
+ }
8388
+ }
8389
+ throw new Error(`sweepStaleClaims lost the optimistic-locking race ${SWEEP_MAX_ATTEMPTS} attempts running on ${instanceId} — a concurrent writer kept committing first. Retry later, or investigate a write storm on this instance.`);
8390
+ }
8391
+
8392
+ function stripClaim(entry) {
8393
+ const {claim: _claim, ...rest} = entry;
8394
+ return rest;
8112
8395
  }
8113
8396
 
8114
8397
  async function sortByDependencies({client: client, definitions: definitions, tag: tag}) {
@@ -9402,6 +9685,66 @@ const workflow = {
9402
9685
  }
9403
9686
  });
9404
9687
  },
9688
+ commitEffectOps: async rawArgs => {
9689
+ const args = taggedScope(rawArgs, REQUEST_TAG.commitEffectOps), {client: client, tag: tag, instanceId: instanceId, effectKey: effectKey, claimToken: claimToken, ops: ops, executionContext: executionContext} = args, {actor: actor, clientForGdr: clientForGdr, refSurface: refSurface} = await resolveOperationContext(args), clock = args.clock ?? wallClock, record = requestRecordFor({
9690
+ idempotencyKey: args.idempotencyKey,
9691
+ op: "commitEffectOps",
9692
+ idempotencyTtlMs: args.idempotencyTtlMs
9693
+ });
9694
+ if (record === void 0) throw new ContractViolationError("commitEffectOps: idempotencyKey is required — the engine cannot assume a supplied field op is idempotent, so every mid-dispatch report must be retry-safe");
9695
+ return runDeduped({
9696
+ client: client,
9697
+ tag: tag,
9698
+ instanceId: instanceId,
9699
+ record: record,
9700
+ now: clock(),
9701
+ actor: actor,
9702
+ clientForGdr: clientForGdr,
9703
+ refSurface: refSurface,
9704
+ clock: clock,
9705
+ executionContext: executionContext,
9706
+ telemetry: args.telemetry,
9707
+ run: async () => {
9708
+ const report = await commitEffectOps({
9709
+ client: client,
9710
+ instanceId: instanceId,
9711
+ effectKey: effectKey,
9712
+ claimToken: claimToken,
9713
+ ops: ops,
9714
+ requestRecord: record,
9715
+ leaseMs: args.leaseMs ?? DEFAULT_EFFECT_LEASE_MS,
9716
+ options: engineOptionsForActor({
9717
+ actor: actor,
9718
+ clock: clock,
9719
+ clientForGdr: clientForGdr,
9720
+ refSurface: refSurface,
9721
+ executionContext: executionContext,
9722
+ telemetry: args.telemetry
9723
+ })
9724
+ }), {cascaded: cascaded, instance: instance} = await cascadeAndReload({
9725
+ client: client,
9726
+ tag: tag,
9727
+ instanceId: instanceId,
9728
+ actor: actor,
9729
+ clientForGdr: clientForGdr,
9730
+ refSurface: refSurface,
9731
+ clock: clock,
9732
+ executionContext: executionContext,
9733
+ telemetry: args.telemetry
9734
+ });
9735
+ return resolveTelemetry(args.telemetry).log(WorkflowEffectStateReported, {
9736
+ ...definitionHashFragment(instance.pinnedContentHash),
9737
+ instanceId: instanceId,
9738
+ effect: report.effect,
9739
+ cascaded: cascaded
9740
+ }), {
9741
+ instance: instance,
9742
+ cascaded: cascaded,
9743
+ changed: !0
9744
+ };
9745
+ }
9746
+ });
9747
+ },
9405
9748
  tick: async rawArgs => {
9406
9749
  const args = taggedScope(rawArgs, REQUEST_TAG.tick), {client: client, tag: tag, instanceId: instanceId, executionContext: executionContext} = args, {actor: actor, clientForGdr: clientForGdr, refSurface: refSurface} = await resolveOperationContext(args), clock = args.clock ?? wallClock, current = await reload({
9407
9750
  client: client,
@@ -10003,64 +10346,100 @@ async function resolveActor(directory, actor) {
10003
10346
  };
10004
10347
  }
10005
10348
 
10006
- const DEFAULT_EFFECT_LEASE_MS = 300 * 1e3;
10349
+ const EFFECT_COMMIT_QUEUE_DEPTH = 32, EFFECT_COMMIT_DISPATCH_CAP = 200;
10007
10350
 
10008
- function isClaimExpired(claim, now) {
10009
- return claim.leaseExpiresAt === void 0 || hasPassed(claim.leaseExpiresAt, now);
10351
+ class EffectCommitQueueOverflowError extends WorkflowError {
10352
+ effectKey;
10353
+ bound;
10354
+ limit;
10355
+ constructor(args) {
10356
+ super("effect-commit-queue-overflow", args.bound === "queue-depth" ? `Effect dispatch "${args.effectKey}" has ${args.limit} mid-dispatch commits queued — await your ctx.commitOps/ctx.setProgress calls (or report less often); the queue serializes real engine commits and cannot grow without bound` : `Effect dispatch "${args.effectKey}" already made ${args.limit} mid-dispatch commits — the per-dispatch cap bounds instance history growth; report more coarsely`),
10357
+ this.name = "EffectCommitQueueOverflowError", this.effectKey = args.effectKey, this.bound = args.bound,
10358
+ this.limit = args.limit;
10359
+ }
10010
10360
  }
10011
10361
 
10012
- function claimReleasedEntry(args) {
10013
- return {
10014
- _key: randomKey(),
10015
- _type: "effectClaimReleased",
10016
- at: args.at,
10017
- effectKey: args.pending._key,
10018
- effect: args.pending.name,
10019
- claim: args.claim,
10020
- via: args.via,
10021
- actor: args.actor
10022
- };
10362
+ function isClaimLoss(err) {
10363
+ return err instanceof StaleEffectClaimError || err instanceof EffectNotFoundError;
10023
10364
  }
10024
10365
 
10025
- const SWEEP_MAX_ATTEMPTS = 5;
10366
+ function pendingEntry(args) {
10367
+ let resolve, reject;
10368
+ const promise = new Promise((res, rej) => {
10369
+ resolve = res, reject = rej;
10370
+ });
10371
+ return promise.catch(() => {}), {
10372
+ ...args,
10373
+ resolve: resolve,
10374
+ reject: reject,
10375
+ promise: promise
10376
+ };
10377
+ }
10026
10378
 
10027
- async function sweepStaleClaims(args) {
10028
- const {tag: tag, instanceId: instanceId} = args, client = pinApiVersion(args.client);
10029
- validateTag(tag);
10030
- const clock = args.clock ?? wallClock, {actor: actor} = await resolveAccess(client), stamp = resolveExecutionContext(args.executionContext);
10031
- for (let attempt = 1; attempt <= SWEEP_MAX_ATTEMPTS; attempt++) {
10032
- const now = clock(), instance = await reload({
10033
- client: client,
10034
- instanceId: instanceId,
10035
- tag: tag
10036
- }), expired = instance.pendingEffects.filter(e => e.claim !== void 0 && isClaimExpired(e.claim, now));
10037
- if (expired.length === 0) return {
10038
- released: []
10039
- };
10040
- const expiredKeys = new Set(expired.map(e => e._key)), releasedQueue = instance.pendingEffects.map(e => expiredKeys.has(e._key) ? stripClaim(e) : e), auditRows = stampHistoryEntries(expired.map(pending => claimReleasedEntry({
10041
- pending: pending,
10042
- claim: pending.claim,
10043
- at: now,
10044
- via: "sweep",
10045
- actor: actor
10046
- })), stamp);
10047
- try {
10048
- return await client.patch(instance._id).ifRevisionId(instance._rev).set({
10049
- pendingEffects: releasedQueue,
10050
- history: [ ...instance.history, ...auditRows ]
10051
- }).commit(SYNC_COMMIT), {
10052
- released: expired
10053
- };
10054
- } catch (error) {
10055
- if (!isRevisionConflict(error)) throw error;
10379
+ function createEffectCommitQueue(args) {
10380
+ const {effectKey: effectKey, claimToken: claimToken, commit: commit} = args, pending = [];
10381
+ let admitted = 0, dead, closed = !1, tail = Promise.resolve();
10382
+ function assertOpen() {
10383
+ if (dead !== void 0) throw dead;
10384
+ if (closed) throw new Error(`Effect dispatch "${effectKey}" already settled — a mid-dispatch report after the handler returned cannot commit; await your reports inside the handler`);
10385
+ }
10386
+ function admit(entry) {
10387
+ if (assertOpen(), pending.length >= EFFECT_COMMIT_QUEUE_DEPTH) throw new EffectCommitQueueOverflowError({
10388
+ effectKey: effectKey,
10389
+ bound: "queue-depth",
10390
+ limit: EFFECT_COMMIT_QUEUE_DEPTH
10391
+ });
10392
+ if (admitted >= EFFECT_COMMIT_DISPATCH_CAP) throw new EffectCommitQueueOverflowError({
10393
+ effectKey: effectKey,
10394
+ bound: "dispatch-cap",
10395
+ limit: EFFECT_COMMIT_DISPATCH_CAP
10396
+ });
10397
+ return admitted += 1, pending.push(entry), tail = tail.then(runNext), entry.promise;
10398
+ }
10399
+ function die(err) {
10400
+ dead = err;
10401
+ for (const entry of pending.splice(0)) entry.reject(err);
10402
+ }
10403
+ async function runNext() {
10404
+ const entry = pending.shift();
10405
+ if (entry !== void 0) try {
10406
+ await commit({
10407
+ ops: entry.buildOps(),
10408
+ idempotencyKey: entry.idempotencyKey
10409
+ }), entry.resolve();
10410
+ } catch (err) {
10411
+ entry.reject(err), die(err);
10056
10412
  }
10057
10413
  }
10058
- throw new Error(`sweepStaleClaims lost the optimistic-locking race ${SWEEP_MAX_ATTEMPTS} attempts running on ${instanceId} — a concurrent writer kept committing first. Retry later, or investigate a write storm on this instance.`);
10059
- }
10060
-
10061
- function stripClaim(entry) {
10062
- const {claim: _claim, ...rest} = entry;
10063
- return rest;
10414
+ return {
10415
+ commitOps: req => admit(pendingEntry({
10416
+ buildOps: () => req.ops,
10417
+ idempotencyKey: req.idempotencyKey
10418
+ })),
10419
+ setProgress: (target, value) => {
10420
+ assertOpen();
10421
+ const ref = typeof target == "string" ? {
10422
+ scope: "workflow",
10423
+ field: target
10424
+ } : target, coalesceKey = `${ref.scope}:${ref.field}`, queued = pending.find(entry2 => entry2.coalesceKey === coalesceKey);
10425
+ if (queued !== void 0) return queued.latestValue = value, queued.promise;
10426
+ const entry = pendingEntry({
10427
+ buildOps: () => [ {
10428
+ type: "field.set",
10429
+ target: ref,
10430
+ value: {
10431
+ type: "literal",
10432
+ value: entry.latestValue
10433
+ }
10434
+ } ],
10435
+ idempotencyKey: `effect:${effectKey}:${claimToken}:progress:${coalesceKey}:${admitted}`,
10436
+ coalesceKey: coalesceKey,
10437
+ latestValue: value
10438
+ });
10439
+ return admit(entry);
10440
+ },
10441
+ settle: async () => (closed = !0, await tail, dead !== void 0 && !isClaimLoss(dead) ? dead : void 0)
10442
+ };
10064
10443
  }
10065
10444
 
10066
10445
  class MissingHandlerError extends WorkflowError {
@@ -10156,74 +10535,126 @@ async function drainEffectsInternal(args) {
10156
10535
  failed: [],
10157
10536
  skipped: [],
10158
10537
  lost: []
10159
- }, skippedKeys = /* @__PURE__ */ new Set;
10160
- for (;;) {
10161
- const now = clock(), before = await reload({
10162
- client: client,
10163
- instanceId: instanceId,
10164
- tag: tag
10165
- });
10166
- noteDrainedInstanceHash(buckets, before);
10167
- const candidate = findClaimableCandidate({
10168
- instance: before,
10169
- now: now,
10170
- skippedKeys: skippedKeys
10171
- });
10172
- if (candidate === void 0) break;
10173
- const handler = effectHandlers[candidate.name];
10174
- if (handler === void 0) {
10175
- await assertSkippableOrThrow({
10176
- missingHandler: missingHandler,
10177
- candidate: candidate,
10178
- instanceId: instanceId,
10179
- log: log
10180
- }), buckets.skipped.push(candidate), skippedKeys.add(candidate._key);
10181
- continue;
10182
- }
10183
- if (!await claimPendingEffect({
10184
- client: client,
10185
- instance: before,
10186
- candidate: candidate,
10187
- drainerActor: drainerActor,
10188
- now: now,
10189
- leaseMs: leaseMs,
10190
- ...executionContext !== void 0 ? {
10191
- executionContext: executionContext
10192
- } : {}
10193
- })) continue;
10194
- const outcome = await dispatchAndReport({
10195
- handler: handler,
10196
- candidate: candidate,
10197
- client: client,
10198
- handlerClient: handlerClient,
10199
- tag: tag,
10200
- workflowResource: workflowResource,
10201
- ...resourceClients !== void 0 ? {
10202
- resourceClients: resourceClients
10203
- } : {},
10204
- instanceId: instanceId,
10205
- clientFor: clientFor,
10206
- logger: logger,
10207
- log: log,
10208
- ...executionContext !== void 0 ? {
10209
- executionContext: executionContext
10210
- } : {},
10211
- telemetry: cascadeTelemetry,
10212
- clock: clock
10213
- });
10214
- buckets[outcome].push(candidate);
10215
- }
10538
+ }, pass = {
10539
+ client: client,
10540
+ handlerClient: handlerClient,
10541
+ tag: tag,
10542
+ workflowResource: workflowResource,
10543
+ ...resourceClients !== void 0 ? {
10544
+ resourceClients: resourceClients
10545
+ } : {},
10546
+ instanceId: instanceId,
10547
+ effectHandlers: effectHandlers,
10548
+ missingHandler: missingHandler,
10549
+ logger: logger,
10550
+ log: log,
10551
+ leaseMs: leaseMs,
10552
+ clock: clock,
10553
+ ...executionContext !== void 0 ? {
10554
+ executionContext: executionContext
10555
+ } : {},
10556
+ telemetry: cascadeTelemetry,
10557
+ drainerActor: drainerActor,
10558
+ clientFor: clientFor,
10559
+ buckets: buckets,
10560
+ skippedKeys: /* @__PURE__ */ new Set
10561
+ };
10562
+ for (;await drainOneCandidate(pass); ) ;
10216
10563
  return buckets;
10217
10564
  }
10218
10565
 
10566
+ async function drainOneCandidate(pass) {
10567
+ const {client: client, tag: tag, instanceId: instanceId, buckets: buckets, skippedKeys: skippedKeys} = pass, now = pass.clock(), before = await reload({
10568
+ client: client,
10569
+ instanceId: instanceId,
10570
+ tag: tag
10571
+ });
10572
+ noteDrainedInstanceHash(buckets, before);
10573
+ const candidate = findClaimableCandidate({
10574
+ instance: before,
10575
+ now: now,
10576
+ skippedKeys: skippedKeys
10577
+ });
10578
+ if (candidate === void 0) return !1;
10579
+ const handler = pass.effectHandlers[candidate.name];
10580
+ if (handler === void 0) return await assertSkippableOrThrow({
10581
+ missingHandler: pass.missingHandler,
10582
+ candidate: candidate,
10583
+ instanceId: instanceId,
10584
+ log: pass.log
10585
+ }), buckets.skipped.push(candidate), skippedKeys.add(candidate._key), !0;
10586
+ const claimed = await claimPendingEffect({
10587
+ client: client,
10588
+ instance: before,
10589
+ candidate: candidate,
10590
+ drainerActor: pass.drainerActor,
10591
+ now: now,
10592
+ leaseMs: pass.leaseMs,
10593
+ ...pass.executionContext !== void 0 ? {
10594
+ executionContext: pass.executionContext
10595
+ } : {}
10596
+ });
10597
+ if (claimed === void 0) return !0;
10598
+ const outcome = await dispatchAndReport({
10599
+ handler: handler,
10600
+ candidate: candidate,
10601
+ claimToken: claimed.claimToken,
10602
+ leaseMs: pass.leaseMs,
10603
+ client: client,
10604
+ handlerClient: pass.handlerClient,
10605
+ tag: tag,
10606
+ workflowResource: pass.workflowResource,
10607
+ ...pass.resourceClients !== void 0 ? {
10608
+ resourceClients: pass.resourceClients
10609
+ } : {},
10610
+ instanceId: instanceId,
10611
+ clientFor: pass.clientFor,
10612
+ logger: pass.logger,
10613
+ log: pass.log,
10614
+ ...pass.executionContext !== void 0 ? {
10615
+ executionContext: pass.executionContext
10616
+ } : {},
10617
+ telemetry: pass.telemetry,
10618
+ clock: pass.clock
10619
+ });
10620
+ return buckets[outcome].push(candidate), !0;
10621
+ }
10622
+
10219
10623
  function findClaimableCandidate({instance: instance, now: now, skippedKeys: skippedKeys}) {
10220
10624
  return instance.pendingEffects.find(e => (e.claim === void 0 || isClaimExpired(e.claim, now)) && !skippedKeys.has(e._key));
10221
10625
  }
10222
10626
 
10223
10627
  async function dispatchAndReport(args) {
10224
- const {handler: handler, candidate: candidate, client: client, handlerClient: handlerClient, tag: tag, workflowResource: workflowResource, resourceClients: resourceClients, instanceId: instanceId, clientFor: clientFor, logger: logger, log: log, executionContext: executionContext, telemetry: telemetry, clock: clock} = args, {outputs: outputs, ops: ops, dispatchError: dispatchError} = await dispatchEffect({
10628
+ const {handler: handler, candidate: candidate, claimToken: claimToken, leaseMs: leaseMs, client: client, handlerClient: handlerClient, tag: tag, workflowResource: workflowResource, resourceClients: resourceClients, instanceId: instanceId, clientFor: clientFor, logger: logger, log: log, executionContext: executionContext, telemetry: telemetry, clock: clock} = args, commitQueue = createEffectCommitQueue({
10629
+ effectKey: candidate._key,
10630
+ claimToken: claimToken,
10631
+ commit: async req => {
10632
+ await workflow.commitEffectOps({
10633
+ client: client,
10634
+ tag: tag,
10635
+ workflowResource: workflowResource,
10636
+ ...resourceClients !== void 0 ? {
10637
+ resourceClients: resourceClients
10638
+ } : {},
10639
+ ...executionContext !== void 0 ? {
10640
+ executionContext: executionContext
10641
+ } : {},
10642
+ ...telemetry !== void 0 ? {
10643
+ telemetry: telemetry
10644
+ } : {},
10645
+ instanceId: instanceId,
10646
+ effectKey: candidate._key,
10647
+ claimToken: claimToken,
10648
+ ops: req.ops,
10649
+ idempotencyKey: req.idempotencyKey,
10650
+ leaseMs: leaseMs,
10651
+ clock: clock
10652
+ });
10653
+ }
10654
+ }), {outputs: outputs, ops: ops, dispatchError: dispatchError} = await dispatchEffect({
10225
10655
  handler: handler,
10226
10656
  candidate: candidate,
10657
+ queue: commitQueue,
10227
10658
  ctx: {
10228
10659
  client: handlerClient,
10229
10660
  clientFor: clientFor,
@@ -10303,11 +10734,12 @@ async function assertSkippableOrThrow({missingHandler: missingHandler, candidate
10303
10734
  }
10304
10735
 
10305
10736
  async function claimPendingEffect({client: client, instance: instance, candidate: candidate, drainerActor: drainerActor, now: now, leaseMs: leaseMs, executionContext: executionContext}) {
10306
- const priorClaim = candidate.claim, claim = {
10737
+ const priorClaim = candidate.claim, claimToken = randomKey(), claim = {
10307
10738
  _type: "pendingEffect.claim",
10308
10739
  claimedAt: now,
10309
10740
  claimedBy: drainerActor,
10310
- leaseExpiresAt: addMs(now, leaseMs)
10741
+ leaseExpiresAt: addMs(now, leaseMs),
10742
+ claimToken: claimToken
10311
10743
  }, takeoverAudit = priorClaim !== void 0 ? stampHistoryEntries([ claimReleasedEntry({
10312
10744
  pending: candidate,
10313
10745
  claim: priorClaim,
@@ -10321,43 +10753,64 @@ async function claimPendingEffect({client: client, instance: instance, candidate
10321
10753
  ...takeoverAudit !== void 0 ? {
10322
10754
  history: [ ...instance.history, takeoverAudit ]
10323
10755
  } : {}
10324
- }).commit(SYNC_COMMIT), !0;
10756
+ }).commit(SYNC_COMMIT), {
10757
+ claimToken: claimToken
10758
+ };
10325
10759
  } catch (error) {
10326
10760
  if (!isRevisionConflict(error)) throw error;
10327
- return !1;
10761
+ return;
10328
10762
  }
10329
10763
  }
10330
10764
 
10331
- async function dispatchEffect({handler: handler, candidate: candidate, ctx: ctx}) {
10765
+ async function invokeHandler({handler: handler, candidate: candidate, queue: queue, ctx: ctx}) {
10332
10766
  try {
10333
10767
  const result = await handler(candidate.params, {
10334
10768
  client: ctx.client,
10335
10769
  clientFor: ctx.clientFor,
10336
10770
  instanceId: ctx.instanceId,
10337
10771
  effectKey: candidate._key,
10338
- log: (message, extra) => ctx.logger(`effect.${candidate.name}`).info(message, extra)
10772
+ log: (message, extra) => ctx.logger(`effect.${candidate.name}`).info(message, extra),
10773
+ commitOps: queue.commitOps,
10774
+ setProgress: queue.setProgress
10339
10775
  });
10340
- return {
10341
- ...result?.outputs !== void 0 ? {
10342
- outputs: result.outputs
10343
- } : {},
10344
- ...result?.ops !== void 0 ? {
10345
- ops: result.ops
10346
- } : {}
10776
+ return result === void 0 ? {} : {
10777
+ result: result
10347
10778
  };
10348
10779
  } catch (err) {
10349
- const message = errorMessage(err), stack = err instanceof Error && err.stack !== void 0 ? err.stack : void 0;
10350
10780
  return {
10351
- dispatchError: stack !== void 0 ? {
10352
- message: message,
10353
- stack: stack
10354
- } : {
10355
- message: message
10781
+ thrown: {
10782
+ err: err
10356
10783
  }
10357
10784
  };
10358
10785
  }
10359
10786
  }
10360
10787
 
10788
+ function shapeDispatchError(err) {
10789
+ const message = errorMessage(err), stack = err instanceof Error && err.stack !== void 0 ? err.stack : void 0;
10790
+ return stack !== void 0 ? {
10791
+ message: message,
10792
+ stack: stack
10793
+ } : {
10794
+ message: message
10795
+ };
10796
+ }
10797
+
10798
+ async function dispatchEffect(args) {
10799
+ const {result: result, thrown: thrown} = await invokeHandler(args), settleFailure = await args.queue.settle(), failed = thrown ?? (settleFailure !== void 0 ? {
10800
+ err: settleFailure
10801
+ } : void 0);
10802
+ return failed !== void 0 ? {
10803
+ dispatchError: shapeDispatchError(failed.err)
10804
+ } : {
10805
+ ...result?.outputs !== void 0 ? {
10806
+ outputs: result.outputs
10807
+ } : {},
10808
+ ...result?.ops !== void 0 ? {
10809
+ ops: result.ops
10810
+ } : {}
10811
+ };
10812
+ }
10813
+
10361
10814
  async function applyMissingHandler({policy: policy, info: info, log: log}) {
10362
10815
  if (policy === "fail") return "fail";
10363
10816
  if (policy === "skip") return log.warn(`Missing effect handler "${info.name}" — skipping`, {
@@ -10834,6 +11287,7 @@ function createEngine(args) {
10834
11287
  fireAction: rest => workflow.fireAction(withScope(rest)),
10835
11288
  editField: rest => workflow.editField(withScope(rest)),
10836
11289
  completeEffect: rest => workflow.completeEffect(withScope(rest)),
11290
+ commitEffectOps: rest => workflow.commitEffectOps(withScope(rest)),
10837
11291
  tick: rest => workflow.tick(withScope(rest)),
10838
11292
  evaluate: rest => workflow.evaluate(withScope(rest)),
10839
11293
  diagnose: rest => workflow.diagnose(withScope(rest)),
@@ -11240,6 +11694,10 @@ const HISTORY_DISPLAY = {
11240
11694
  title: "Number value",
11241
11695
  description: "Numeric entry."
11242
11696
  },
11697
+ progress: {
11698
+ title: "Progress",
11699
+ description: "Application-defined completion, 0–100 inclusive (fractions allowed)."
11700
+ },
11243
11701
  boolean: {
11244
11702
  title: "Boolean value",
11245
11703
  description: "True/false entry."
@@ -11402,4 +11860,4 @@ function displayDescription(typeKey) {
11402
11860
  if (typeKey) return DISPLAY[typeKey]?.description;
11403
11861
  }
11404
11862
 
11405
- export { ACTION_SEMANTICS, ACTIVITY_KINDS, ACTIVITY_KIND_DISPLAY, ACTOR_KINDS, AUTHORING_DISPLAY, ActionDisabledError, ActionParamsInvalidError, CONDITION_VARS, CONTEXT_ENTRY_DISPLAY, CascadeLimitError, 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, ENGINE_API_VERSION, EXECUTION_KINDS, EXECUTOR_CLASSIFICATIONS, EXECUTOR_CLASSIFICATION_DISPLAY, EditFieldDeniedError, 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, OP_DISPLAY, PartialGuardDeployError, PersistedDocShapeError, READER_MODEL_ROLLOUT_URL, RESERVED_CONDITION_VARS, ReaderModelAcknowledgementError, RefResourceUndeclaredError, RequiredFieldNotProvidedError, START_ALLOWED_VARS, START_FILTER_VARS, SpawnContractsInvalidError, StartNotAllowedError, StartNotPrimedError, StartNotSettledError, WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, WorkflowActionFired, WorkflowDefinitionDeleted, WorkflowDefinitionDeployed, WorkflowEffectCompleted, 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, 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, explainStartAllowed, extractDocumentId, fieldTreeShape, findCurrentActivityEntry, findOpenStageEntry, formatRead, gdrFromResource, gdrRef, gdrUri, groupMembershipNames, groupSitesOf, guardMatches, guardsForDefinition, guardsForInstance, guardsForResource, guillemets2 as guillemets, hashDefinitionContent, humanize2 as humanize, inFlightFilter, initialFieldIssues, instanceDocId, instanceGuardQuery, instanceWatchesDocument, instancesQuery, isCascadeFired, isClaimExpired, isComparisonOp, isDefinitionApplicable, isFilterScopedOut, isGdr, isInputSourced, isNotesEntry, isProjectUserNotFoundError, isSingleDocRefEntry, isSingleDocRefKind, isStartableDefinition, isSubjectEntry, isTelemetryEnvDenied, isTerminalActivityStatus, isTerminalStage, isTodoListEntry, isTodoListItem, isUnprimed, lakeGuardId, latestDeployedDefinitions, lintEffectOutputs, minReaderModelOf, missingRequiredInputs, modelVersionOf, narrateAutonomyWaits, noopTelemetry, parentRef, parseDefinitionInput, parseDefinitionSnapshot, parseGdr, parseGuardDocument, parseInstanceDocument, parseResourceGdr, processShellUserProperties, 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, stageAutonomyOf, startFieldsParam, startKindOf, startRefusal, stripSystemFields, subjectDenialLabels, subscriptionDocument, subscriptionDocumentsForInstance, sweepStaleClaims, tagScopeFilter, terminalState, toBareId, tryParseGdr, unboundAllowedReads, unsatisfiedTransitionSummaries, validateDefinition, validateTag, verdictGuardsForInstance, wallClock, whatIfCondition2 as whatIfCondition, withAssignment, workflow };
11863
+ export { ACTION_SEMANTICS, ACTIVITY_KINDS, ACTIVITY_KIND_DISPLAY, ACTOR_KINDS, 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, OP_DISPLAY, PartialGuardDeployError, PersistedDocShapeError, READER_MODEL_ROLLOUT_URL, RESERVED_CONDITION_VARS, ReaderModelAcknowledgementError, RefResourceUndeclaredError, RequiredFieldNotProvidedError, START_ALLOWED_VARS, START_FILTER_VARS, SpawnContractsInvalidError, StaleEffectClaimError, StartNotAllowedError, StartNotPrimedError, StartNotSettledError, WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, WorkflowActionFired, 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, 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, explainStartAllowed, extractDocumentId, fieldTreeShape, findCurrentActivityEntry, findOpenStageEntry, formatRead, gdrFromResource, gdrRef, gdrUri, groupMembershipNames, groupSitesOf, guardMatches, guardsForDefinition, guardsForInstance, guardsForResource, guillemets2 as guillemets, hashDefinitionContent, humanize2 as humanize, inFlightFilter, initialFieldIssues, instanceDocId, instanceGuardQuery, instanceWatchesDocument, instancesQuery, isCascadeFired, isClaimExpired, isComparisonOp, isDefinitionApplicable, isFilterScopedOut, isGdr, isInputSourced, isNotesEntry, isProjectUserNotFoundError, isSingleDocRefEntry, isSingleDocRefKind, isStartableDefinition, isSubjectEntry, isTelemetryEnvDenied, isTerminalActivityStatus, isTerminalStage, isTodoListEntry, isTodoListItem, isUnprimed, lakeGuardId, latestDeployedDefinitions, lintEffectOutputs, minReaderModelOf, missingRequiredInputs, modelVersionOf, narrateAutonomyWaits, noopTelemetry, parentRef, parseDefinitionInput, parseDefinitionSnapshot, parseGdr, parseGuardDocument, parseInstanceDocument, parseResourceGdr, processShellUserProperties, 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, stageAutonomyOf, startFieldsParam, startKindOf, startRefusal, stripSystemFields, subjectDenialLabels, subscriptionDocument, subscriptionDocumentsForInstance, sweepStaleClaims, tagScopeFilter, terminalState, toBareId, tryParseGdr, unboundAllowedReads, unsatisfiedTransitionSummaries, validateDefinition, validateTag, verdictGuardsForInstance, wallClock, whatIfCondition2 as whatIfCondition, withAssignment, workflow };