@granular-software/sdk 0.4.63 → 0.4.65

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.
@@ -6534,6 +6534,11 @@ var Session = class {
6534
6534
  }
6535
6535
  }
6536
6536
  buildDirectedInvocationEffectContext(params, feedbackContext) {
6537
+ const commitUnavailable = async () => {
6538
+ throw new Error(
6539
+ "This directed browser tool invocation has no product commit transport"
6540
+ );
6541
+ };
6537
6542
  return {
6538
6543
  effectClientId: this.clientId,
6539
6544
  sandboxId: params.sandboxId || "",
@@ -6546,6 +6551,10 @@ var Session = class {
6546
6551
  userId: "",
6547
6552
  subjectId: ""
6548
6553
  },
6554
+ commit: {
6555
+ effect: commitUnavailable,
6556
+ transition: commitUnavailable
6557
+ },
6549
6558
  ...feedbackContext ? {
6550
6559
  feedback: feedbackContext.feedback,
6551
6560
  transientFeedback: feedbackContext.transientFeedback
@@ -12198,8 +12207,12 @@ external_exports.union([
12198
12207
  external_exports.array(external_exports.string()),
12199
12208
  external_exports.object({
12200
12209
  values: external_exports.array(external_exports.string()),
12210
+ labels: external_exports.array(external_exports.string().min(1)).optional(),
12201
12211
  message: external_exports.string().optional()
12202
- }).strict()
12212
+ }).strict().refine(
12213
+ (rule) => !rule.labels || rule.labels.length === rule.values.length,
12214
+ { message: "Enum labels must match enum values one-for-one" }
12215
+ )
12203
12216
  ]);
12204
12217
  external_exports.union([
12205
12218
  external_exports.boolean(),
@@ -12227,7 +12240,7 @@ var StateMachineStateSchema = external_exports.union([
12227
12240
  external_exports.string(),
12228
12241
  external_exports.object({
12229
12242
  name: external_exports.string().min(1),
12230
- label: external_exports.string().optional(),
12243
+ label: external_exports.string().min(1).optional(),
12231
12244
  description: external_exports.string().optional(),
12232
12245
  isFinal: external_exports.boolean().optional()
12233
12246
  }).strict()
@@ -12313,6 +12326,12 @@ var StateTransitionExpectedOutcomeSchema = external_exports.union([
12313
12326
  summary: external_exports.string().optional()
12314
12327
  }).strict()
12315
12328
  ]);
12329
+ var StateTransitionOutcomeSchema = external_exports.object({
12330
+ label: external_exports.string().min(1).optional(),
12331
+ to: external_exports.string().min(1),
12332
+ primary: external_exports.boolean().optional(),
12333
+ disposition: external_exports.enum(["continue", "error"])
12334
+ }).strict();
12316
12335
  var StateMachineTransitionSchema = external_exports.object({
12317
12336
  name: external_exports.string().min(1),
12318
12337
  from: external_exports.string().min(1),
@@ -12324,7 +12343,8 @@ var StateMachineTransitionSchema = external_exports.object({
12324
12343
  requirements: StateTransitionRequirementsSchema.optional(),
12325
12344
  permission: StateTransitionPermissionSchema.optional(),
12326
12345
  risk: external_exports.enum(["low", "medium", "high"]).optional(),
12327
- expectedOutcome: StateTransitionExpectedOutcomeSchema.optional()
12346
+ expectedOutcome: StateTransitionExpectedOutcomeSchema.optional(),
12347
+ outcomes: external_exports.record(external_exports.string().min(1), StateTransitionOutcomeSchema).optional()
12328
12348
  }).strict();
12329
12349
  external_exports.object({
12330
12350
  name: external_exports.string().min(1),
@@ -13005,6 +13025,238 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
13005
13025
  }
13006
13026
  });
13007
13027
 
13028
+ // src/commit.ts
13029
+ var MAX_PROJECTION_CHANGES = 1e3;
13030
+ var MAX_SAFE_SUMMARY_KEYS = 32;
13031
+ var MAX_SAFE_TEXT_LENGTH = 2e3;
13032
+ var definedTransitionMetadata = /* @__PURE__ */ new WeakMap();
13033
+ function getDefinedTransitionMetadata(transition) {
13034
+ return definedTransitionMetadata.get(transition);
13035
+ }
13036
+ function requireNonEmptyString(value, path2) {
13037
+ if (typeof value !== "string" || value.trim().length === 0) {
13038
+ throw new Error(`${path2} must be a non-empty string`);
13039
+ }
13040
+ return value.trim();
13041
+ }
13042
+ function validateObjectReference(value, path2) {
13043
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
13044
+ throw new Error(`${path2} must be an object reference`);
13045
+ }
13046
+ const reference = value;
13047
+ requireNonEmptyString(reference.className, `${path2}.className`);
13048
+ requireNonEmptyString(reference.id, `${path2}.id`);
13049
+ if (reference.path !== void 0) {
13050
+ requireNonEmptyString(reference.path, `${path2}.path`);
13051
+ }
13052
+ }
13053
+ function validateScalarRecord(value, path2) {
13054
+ if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) {
13055
+ throw new Error(`${path2} must be an object of scalar values`);
13056
+ }
13057
+ for (const [key, item] of Object.entries(value)) {
13058
+ if (item !== null && typeof item !== "string" && typeof item !== "number" && typeof item !== "boolean") {
13059
+ throw new Error(
13060
+ `${path2}.${key} must be a string, finite number, boolean, or null`
13061
+ );
13062
+ }
13063
+ if (typeof item === "number" && !Number.isFinite(item)) {
13064
+ throw new Error(`${path2}.${key} must be finite`);
13065
+ }
13066
+ }
13067
+ }
13068
+ function validateProjectedRecord(value, path2) {
13069
+ validateObjectReference(value, path2);
13070
+ const record = value;
13071
+ if (record.label !== void 0) {
13072
+ if (typeof record.label !== "string") {
13073
+ throw new Error(`${path2}.label must be a string`);
13074
+ }
13075
+ if (record.label.length > MAX_SAFE_TEXT_LENGTH) {
13076
+ throw new Error(
13077
+ `${path2}.label exceeds ${MAX_SAFE_TEXT_LENGTH} characters`
13078
+ );
13079
+ }
13080
+ }
13081
+ validateScalarRecord(record.fields, `${path2}.fields`);
13082
+ }
13083
+ function validateBoundedJson(value, path2, depth = 0) {
13084
+ if (depth > 12) throw new Error(`${path2} is nested too deeply`);
13085
+ if (value === null || typeof value === "boolean") return;
13086
+ if (typeof value === "number") {
13087
+ if (!Number.isFinite(value)) throw new Error(`${path2} must be finite`);
13088
+ return;
13089
+ }
13090
+ if (typeof value === "string") {
13091
+ if (value.length > MAX_SAFE_TEXT_LENGTH) {
13092
+ throw new Error(`${path2} exceeds ${MAX_SAFE_TEXT_LENGTH} characters`);
13093
+ }
13094
+ return;
13095
+ }
13096
+ if (Array.isArray(value)) {
13097
+ if (value.length > MAX_PROJECTION_CHANGES) {
13098
+ throw new Error(`${path2} contains too many values`);
13099
+ }
13100
+ value.forEach(
13101
+ (item, index) => validateBoundedJson(item, `${path2}[${index}]`, depth + 1)
13102
+ );
13103
+ return;
13104
+ }
13105
+ if (!value || typeof value !== "object") {
13106
+ throw new Error(`${path2} contains an unsupported value`);
13107
+ }
13108
+ const entries = Object.entries(value);
13109
+ if (entries.length > 256) throw new Error(`${path2} contains too many keys`);
13110
+ for (const [key, item] of entries) {
13111
+ if (/token|secret|password|authorization|cookie/i.test(key)) {
13112
+ throw new Error(`${path2}.${key} is not allowed in a commit projection`);
13113
+ }
13114
+ validateBoundedJson(item, `${path2}.${key}`, depth + 1);
13115
+ }
13116
+ }
13117
+ function validateProjectionResult(declaration, projection) {
13118
+ if (!projection || typeof projection !== "object") {
13119
+ throw new Error("Projection mapper must return an object");
13120
+ }
13121
+ requireNonEmptyString(
13122
+ projection.source?.reference,
13123
+ "projection.source.reference"
13124
+ );
13125
+ if (projection.source.version !== void 0) {
13126
+ requireNonEmptyString(
13127
+ projection.source.version,
13128
+ "projection.source.version"
13129
+ );
13130
+ }
13131
+ if (!Array.isArray(projection.changes)) {
13132
+ throw new Error("projection.changes must be an array");
13133
+ }
13134
+ if (projection.changes.length > MAX_PROJECTION_CHANGES) {
13135
+ throw new Error(
13136
+ `projection.changes exceeds the ${MAX_PROJECTION_CHANGES} change limit`
13137
+ );
13138
+ }
13139
+ if (projection.primaryTarget) {
13140
+ validateObjectReference(
13141
+ projection.primaryTarget,
13142
+ "projection.primaryTarget"
13143
+ );
13144
+ }
13145
+ if (projection.safeSummary) {
13146
+ const entries = Object.entries(projection.safeSummary);
13147
+ if (entries.length > MAX_SAFE_SUMMARY_KEYS) {
13148
+ throw new Error(
13149
+ `projection.safeSummary exceeds the ${MAX_SAFE_SUMMARY_KEYS} key limit`
13150
+ );
13151
+ }
13152
+ validateBoundedJson(projection.safeSummary, "projection.safeSummary");
13153
+ validateScalarRecord(projection.safeSummary, "projection.safeSummary");
13154
+ }
13155
+ projection.changes.forEach((change, index) => {
13156
+ const changePath = `projection.changes[${index}]`;
13157
+ validateBoundedJson(change, changePath);
13158
+ if (!change || typeof change !== "object" || Array.isArray(change)) {
13159
+ throw new Error(`${changePath} must be an object`);
13160
+ }
13161
+ const rawChange = change;
13162
+ const kind = requireNonEmptyString(rawChange.kind, `${changePath}.kind`);
13163
+ if (kind === "object") {
13164
+ const operation = requireNonEmptyString(
13165
+ rawChange.operation,
13166
+ `${changePath}.operation`
13167
+ );
13168
+ if (operation !== "created" && operation !== "updated" && operation !== "deleted") {
13169
+ throw new Error(
13170
+ `${changePath}.operation must be created, updated, or deleted`
13171
+ );
13172
+ }
13173
+ if (operation === "deleted") {
13174
+ validateObjectReference(rawChange.target, `${changePath}.target`);
13175
+ } else {
13176
+ validateProjectedRecord(rawChange.record, `${changePath}.record`);
13177
+ }
13178
+ } else if (kind === "relationship") {
13179
+ const operation = requireNonEmptyString(
13180
+ rawChange.operation,
13181
+ `${changePath}.operation`
13182
+ );
13183
+ if (operation !== "connected" && operation !== "disconnected") {
13184
+ throw new Error(
13185
+ `${changePath}.operation must be connected or disconnected`
13186
+ );
13187
+ }
13188
+ requireNonEmptyString(
13189
+ rawChange.relationship,
13190
+ `${changePath}.relationship`
13191
+ );
13192
+ validateObjectReference(rawChange.from, `${changePath}.from`);
13193
+ validateObjectReference(rawChange.to, `${changePath}.to`);
13194
+ } else if (kind === "state_observation") {
13195
+ if (rawChange.operation !== void 0) {
13196
+ throw new Error(
13197
+ `${changePath}.operation is not valid for an observation`
13198
+ );
13199
+ }
13200
+ validateObjectReference(rawChange.target, `${changePath}.target`);
13201
+ requireNonEmptyString(rawChange.machine, `${changePath}.machine`);
13202
+ requireNonEmptyString(rawChange.state, `${changePath}.state`);
13203
+ } else {
13204
+ throw new Error(`${changePath}.kind is unsupported: ${kind}`);
13205
+ }
13206
+ });
13207
+ const objectOperations = /* @__PURE__ */ new Map();
13208
+ for (const change of projection.changes) {
13209
+ if (change.kind !== "object") continue;
13210
+ const reference = change.operation === "deleted" ? change.target : change.record;
13211
+ const key = `${reference.className}\0${reference.id}`;
13212
+ const operations = objectOperations.get(key) || {
13213
+ deleted: false,
13214
+ upserted: false
13215
+ };
13216
+ if (change.operation === "deleted") operations.deleted = true;
13217
+ else operations.upserted = true;
13218
+ if (operations.deleted && operations.upserted) {
13219
+ throw new Error(
13220
+ `projection.changes cannot both delete and upsert ${reference.className}/${reference.id}; return only its canonical final state`
13221
+ );
13222
+ }
13223
+ objectOperations.set(key, operations);
13224
+ }
13225
+ const outcome = projection.outcome;
13226
+ if (declaration.kind === "transition") {
13227
+ if (!outcome) {
13228
+ throw new Error("A transition projection must return an outcome");
13229
+ }
13230
+ requireNonEmptyString(outcome.key, "projection.outcome.key");
13231
+ if (outcome.error) {
13232
+ validateBoundedJson(outcome.error, "projection.outcome.error");
13233
+ }
13234
+ } else if (projection.outcome !== void 0) {
13235
+ throw new Error("An effect projection cannot declare a transition outcome");
13236
+ }
13237
+ }
13238
+ function canonicalizeCommitValue(value) {
13239
+ const normalize = (current) => {
13240
+ if (current === null || typeof current === "boolean" || typeof current === "string") {
13241
+ return typeof current === "string" ? current.normalize("NFC") : current;
13242
+ }
13243
+ if (typeof current === "number") {
13244
+ if (!Number.isFinite(current)) {
13245
+ throw new Error("Cannot canonicalize a non-finite number");
13246
+ }
13247
+ return Object.is(current, -0) ? 0 : current;
13248
+ }
13249
+ if (Array.isArray(current)) return current.map(normalize);
13250
+ if (current && typeof current === "object") {
13251
+ return Object.fromEntries(
13252
+ Object.entries(current).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key.normalize("NFC"), normalize(item)])
13253
+ );
13254
+ }
13255
+ throw new Error(`Cannot canonicalize ${typeof current}`);
13256
+ };
13257
+ return JSON.stringify(normalize(value));
13258
+ }
13259
+
13008
13260
  // src/effect-runtime.ts
13009
13261
  function computeEffectKey(effect) {
13010
13262
  const attachedClass = effect.className?.trim();
@@ -13069,6 +13321,45 @@ function resolveInvocationMode(context) {
13069
13321
  }
13070
13322
  return "execute";
13071
13323
  }
13324
+ async function sha256Hex(value) {
13325
+ const digest = await globalThis.crypto.subtle.digest(
13326
+ "SHA-256",
13327
+ new TextEncoder().encode(value)
13328
+ );
13329
+ return Array.from(
13330
+ new Uint8Array(digest),
13331
+ (byte) => byte.toString(16).padStart(2, "0")
13332
+ ).join("");
13333
+ }
13334
+ async function resolveInvocationIdempotencyKey(request) {
13335
+ const supplied = request.context?.idempotencyKey?.trim();
13336
+ if (supplied) return supplied;
13337
+ const invocationId = request.context?.invocationId?.trim();
13338
+ if (!invocationId) {
13339
+ throw new Error(
13340
+ `Committed effect ${request.effectKey} requires an invocation id`
13341
+ );
13342
+ }
13343
+ const digest = await sha256Hex(
13344
+ canonicalizeCommitValue({
13345
+ sandboxId: request.context?.sandboxId || "",
13346
+ environmentId: request.context?.environmentId || "",
13347
+ effectKey: request.effectKey,
13348
+ invocationId,
13349
+ input: request.input
13350
+ })
13351
+ );
13352
+ return `gci_${digest}`;
13353
+ }
13354
+ function createUnavailableCommitContext(message) {
13355
+ const unavailable = async () => {
13356
+ throw new Error(message);
13357
+ };
13358
+ return {
13359
+ effect: unavailable,
13360
+ transition: unavailable
13361
+ };
13362
+ }
13072
13363
  function resolveReverseEffect(effectMap, currentEffect, request, behaviors) {
13073
13364
  const explicitReverseHandler = request.context?.invocation?.reverseHandler?.trim();
13074
13365
  const configuredReverseHandler = behaviors.reverse?.handler?.trim();
@@ -13121,7 +13412,7 @@ function resolveHandlerForMode(effectMap, effect, request) {
13121
13412
  if (mode === "artifactOptions") {
13122
13413
  if (!effect.artifactOptionsHandler) {
13123
13414
  throw new Error(
13124
- `Artifact relationship options are not supported for ${request.effectKey}`
13415
+ `Artifact field options are not supported for ${request.effectKey}`
13125
13416
  );
13126
13417
  }
13127
13418
  return {
@@ -13140,22 +13431,32 @@ function resolveHandlerForMode(effectMap, effect, request) {
13140
13431
  throw new Error(`Dry run is not supported for ${request.effectKey}`);
13141
13432
  }
13142
13433
  if (mode === "reverse") {
13434
+ const sourceEffectKey = request.context?.invocation?.sourceEffectKey?.trim();
13435
+ if (sourceEffectKey && sourceEffectKey !== request.effectKey) {
13436
+ return { effect, mode, handler: effect.handler };
13437
+ }
13438
+ const namedReverseHandler = request.context?.invocation?.reverseHandler?.trim() || behaviors.reverse?.handler?.trim();
13439
+ if (namedReverseHandler) {
13440
+ const reverseEffect = resolveReverseEffect(
13441
+ effectMap,
13442
+ effect,
13443
+ request,
13444
+ behaviors
13445
+ );
13446
+ if (reverseEffect) {
13447
+ return {
13448
+ effect: reverseEffect,
13449
+ mode,
13450
+ handler: reverseEffect.handler
13451
+ };
13452
+ }
13453
+ throw new Error(
13454
+ `Reverse effect ${namedReverseHandler} is not registered for ${request.effectKey}`
13455
+ );
13456
+ }
13143
13457
  if (effect.reverseHandler) {
13144
13458
  return { effect, mode, handler: effect.reverseHandler };
13145
13459
  }
13146
- const reverseEffect = resolveReverseEffect(
13147
- effectMap,
13148
- effect,
13149
- request,
13150
- behaviors
13151
- );
13152
- if (reverseEffect) {
13153
- return {
13154
- effect: reverseEffect,
13155
- mode,
13156
- handler: reverseEffect.reverseHandler || reverseEffect.handler
13157
- };
13158
- }
13159
13460
  throw new Error(
13160
13461
  `Reverse execution is not supported for ${request.effectKey}`
13161
13462
  );
@@ -13256,18 +13557,142 @@ async function invokeRegisteredEffect(effectMap, request, options = {}) {
13256
13557
  throw new Error(`Effect handler not found: ${request.effectKey}`);
13257
13558
  }
13258
13559
  const resolved = resolveHandlerForMode(effectMap, effect, request);
13560
+ const isPreResolvedNamedReverse = resolved.mode === "reverse" && Boolean(request.context?.invocation?.sourceEffectKey?.trim()) && request.context?.invocation?.sourceEffectKey?.trim() !== request.effectKey;
13561
+ const resolvedEffectKey = computeEffectKey(resolved.effect);
13562
+ if (resolved.mode === "reverse" && resolved.effect !== effect && (effect.commit || resolved.effect.commit)) {
13563
+ throw new Error(
13564
+ `Committed named reverse effect ${resolvedEffectKey} must be reserved and dispatched using its own effect key; customer code was not invoked`
13565
+ );
13566
+ }
13567
+ const declaration = resolved.effect.commit;
13568
+ const commitTransport = options.commit;
13569
+ const commitRequired = Boolean(declaration) && (resolved.mode === "execute" || resolved.mode === "reverse");
13570
+ if (commitRequired && !commitTransport) {
13571
+ throw new Error(
13572
+ `Committed effect ${request.effectKey} has no durable commit transport; customer code was not invoked`
13573
+ );
13574
+ }
13575
+ if (commitRequired && declaration?.kind === "transition" && !request.context?.invocation?.transition) {
13576
+ throw new Error(
13577
+ `Transition effect ${request.effectKey} has no resolved transition context; customer code was not invoked`
13578
+ );
13579
+ }
13580
+ const idempotencyKey = commitRequired ? await resolveInvocationIdempotencyKey(request) : request.context?.idempotencyKey;
13581
+ let commitStarted = false;
13582
+ let commitPromise = null;
13583
+ const beginCommit = (requestedKind, productResult) => {
13584
+ if (!declaration || !commitRequired || !commitTransport) {
13585
+ return Promise.reject(
13586
+ new Error(
13587
+ `Effect ${request.effectKey} does not declare an active ${requestedKind} commit`
13588
+ )
13589
+ );
13590
+ }
13591
+ if (declaration.kind !== requestedKind) {
13592
+ return Promise.reject(
13593
+ new Error(
13594
+ `Effect ${request.effectKey} declares ${declaration.kind} commit, not ${requestedKind}`
13595
+ )
13596
+ );
13597
+ }
13598
+ if (commitStarted) {
13599
+ return Promise.reject(
13600
+ new Error(
13601
+ `Effect invocation ${request.context?.invocationId || request.effectKey} already emitted its commit`
13602
+ )
13603
+ );
13604
+ }
13605
+ commitStarted = true;
13606
+ commitPromise = (async () => {
13607
+ let projection;
13608
+ try {
13609
+ projection = declaration.project(productResult);
13610
+ validateProjectionResult(declaration, projection);
13611
+ } catch (error) {
13612
+ const message = error instanceof Error ? error.message : String(error);
13613
+ if (commitTransport.mappingFailed) {
13614
+ await commitTransport.mappingFailed({
13615
+ effectKey: request.effectKey,
13616
+ effectName: request.effectName,
13617
+ invocationId: request.context?.invocationId || "",
13618
+ idempotencyKey: idempotencyKey || "",
13619
+ environmentId: request.context?.environmentId || "",
13620
+ message
13621
+ });
13622
+ }
13623
+ throw new Error(
13624
+ `Product mutation may have succeeded, but its commit projection is invalid: ${message}`
13625
+ );
13626
+ }
13627
+ if (requestedKind === "transition") {
13628
+ const transition = request.context?.invocation?.transition;
13629
+ const outcome = projection.outcome;
13630
+ if (!transition || !outcome?.key) {
13631
+ throw new Error(
13632
+ `Transition effect ${request.effectKey} did not resolve an authored outcome`
13633
+ );
13634
+ }
13635
+ if (!Object.prototype.hasOwnProperty.call(
13636
+ transition.outcomes,
13637
+ outcome.key
13638
+ )) {
13639
+ throw new Error(
13640
+ `Transition ${transition.machine}.${transition.transition} does not declare outcome ${outcome.key}`
13641
+ );
13642
+ }
13643
+ }
13644
+ const invocationId = request.context?.invocationId || "";
13645
+ const environmentId = request.context?.environmentId || "";
13646
+ const sandboxId = request.context?.sandboxId || "";
13647
+ if (!invocationId || !environmentId || !sandboxId || !idempotencyKey) {
13648
+ throw new Error(
13649
+ `Committed effect ${request.effectKey} is missing its trusted invocation scope`
13650
+ );
13651
+ }
13652
+ const commitRequest = {
13653
+ kind: requestedKind,
13654
+ effectKey: request.effectKey,
13655
+ effectName: request.effectName,
13656
+ operationLabel: resolved.effect.label || resolved.effect.name,
13657
+ invocationId,
13658
+ idempotencyKey,
13659
+ sandboxId,
13660
+ environmentId,
13661
+ ...request.context?.sessionId ? { sessionId: request.context.sessionId } : {},
13662
+ ...request.context?.jobId ? { jobId: request.context.jobId } : {},
13663
+ ...request.context?.buildId ? { buildId: request.context.buildId } : {},
13664
+ ...request.context?.invocation?.artifactId ? { artifactId: request.context.invocation.artifactId } : {},
13665
+ projection,
13666
+ ...requestedKind === "transition" && request.context?.invocation?.transition ? { transition: request.context.invocation.transition } : {}
13667
+ };
13668
+ const receipt = await commitTransport.persist(commitRequest);
13669
+ await options.commitAcknowledged?.(receipt);
13670
+ return receipt;
13671
+ })();
13672
+ return commitPromise;
13673
+ };
13674
+ const commitContext = commitRequired ? {
13675
+ effect: (productResult) => beginCommit("effect", productResult),
13676
+ transition: (productResult) => beginCommit("transition", productResult)
13677
+ } : createUnavailableCommitContext(
13678
+ `Effect ${request.effectKey} is not executing a declared product commit`
13679
+ );
13259
13680
  const context = {
13260
13681
  ...request.context || {},
13682
+ ...idempotencyKey ? { idempotencyKey } : {},
13683
+ commit: commitContext,
13261
13684
  behaviors: normalizeEffectBehaviors(
13262
13685
  request.context?.behaviors || effect.metamodels || void 0
13263
13686
  ),
13264
13687
  invocation: {
13265
13688
  mode: resolved.mode,
13266
- sourceEffectKey: request.effectKey,
13267
- sourceEffectName: request.effectName,
13689
+ ...commitRequired && declaration ? { commitKind: declaration.kind } : {},
13690
+ sourceEffectKey: isPreResolvedNamedReverse ? request.context?.invocation?.sourceEffectKey || request.effectKey : request.effectKey,
13691
+ sourceEffectName: isPreResolvedNamedReverse ? request.context?.invocation?.sourceEffectName || request.effectName : request.effectName,
13268
13692
  ...request.context?.invocation?.reverseHandler ? { reverseHandler: request.context.invocation.reverseHandler } : {},
13269
13693
  ...request.context?.invocation?.artifactId ? { artifactId: request.context.invocation.artifactId } : {},
13270
- ...request.context?.invocation?.artifactOptions ? { artifactOptions: request.context.invocation.artifactOptions } : {}
13694
+ ...request.context?.invocation?.artifactOptions ? { artifactOptions: request.context.invocation.artifactOptions } : {},
13695
+ ...request.context?.invocation?.transition ? { transition: request.context.invocation.transition } : {}
13271
13696
  }
13272
13697
  };
13273
13698
  const feedbackContext = options.feedback ? createInvocationFeedbackContext(options.feedback) : null;
@@ -13291,6 +13716,16 @@ async function invokeRegisteredEffect(effectMap, request, options = {}) {
13291
13716
  handlerFailed = true;
13292
13717
  handlerError = error;
13293
13718
  }
13719
+ let commitError;
13720
+ let commitFailed = false;
13721
+ if (commitPromise) {
13722
+ try {
13723
+ await commitPromise;
13724
+ } catch (error) {
13725
+ commitFailed = true;
13726
+ commitError = error;
13727
+ }
13728
+ }
13294
13729
  let feedbackError;
13295
13730
  let feedbackFailed = false;
13296
13731
  if (feedbackContext) {
@@ -13304,9 +13739,17 @@ async function invokeRegisteredEffect(effectMap, request, options = {}) {
13304
13739
  if (handlerFailed) {
13305
13740
  throw handlerError;
13306
13741
  }
13742
+ if (commitFailed) {
13743
+ throw commitError;
13744
+ }
13307
13745
  if (feedbackFailed) {
13308
13746
  throw feedbackError;
13309
13747
  }
13748
+ if (commitRequired && !commitStarted) {
13749
+ throw new Error(
13750
+ `Mutating effect ${request.effectKey} returned without acknowledging its product mutation`
13751
+ );
13752
+ }
13310
13753
  return handlerResult;
13311
13754
  }
13312
13755
 
@@ -13375,12 +13818,7 @@ function toRecordSearchResult(className, node) {
13375
13818
  (field) => normalizeGraphPathSegment(field.name) === "real_id" && typeof field.value === "string" && field.value.trim()
13376
13819
  );
13377
13820
  const id = typeof realIdField?.value === "string" ? realIdField.value.trim() : graphPathId;
13378
- const rawLabel = typeof node.label === "string" && node.label.trim() ? node.label : "";
13379
- if (fields.length === 0 && rawLabel && isPlaceholderRecordLabel(rawLabel, graphPathId, path2)) {
13380
- return null;
13381
- }
13382
- const fallbackLabel = displayLabelFromFields(fields);
13383
- const label = rawLabel && !isPlaceholderRecordLabel(rawLabel, id, path2) ? rawLabel : fallbackLabel || rawLabel || id;
13821
+ const label = typeof node.label === "string" && node.label.trim() ? node.label.trim() : path2 || id;
13384
13822
  return {
13385
13823
  path: path2,
13386
13824
  className,
@@ -13390,30 +13828,6 @@ function toRecordSearchResult(className, node) {
13390
13828
  fields
13391
13829
  };
13392
13830
  }
13393
- function isPlaceholderRecordLabel(label, id, path2) {
13394
- const normalizedLabel = normalizeGraphPathSegment(label);
13395
- return normalizedLabel === normalizeGraphPathSegment(id) || normalizedLabel === normalizeGraphPathSegment(path2);
13396
- }
13397
- function displayLabelFromFields(fields) {
13398
- const preferredFieldNames = [
13399
- "name",
13400
- "title",
13401
- "label",
13402
- "display_name",
13403
- "file_name",
13404
- "number",
13405
- "code"
13406
- ];
13407
- for (const preferred of preferredFieldNames) {
13408
- const match = fields.find(
13409
- (field) => normalizeGraphPathSegment(field.name) === preferred && typeof field.value === "string" && field.value.trim()
13410
- );
13411
- if (typeof match?.value === "string") {
13412
- return match.value.trim();
13413
- }
13414
- }
13415
- return null;
13416
- }
13417
13831
  function normalizeRecordSearchText(value) {
13418
13832
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
13419
13833
  }
@@ -13623,18 +14037,24 @@ function normalizeEnumInput(enumSpec) {
13623
14037
  (value) => typeof value === "string" && value.length > 0
13624
14038
  );
13625
14039
  if (values.length === 0) return null;
13626
- return config.message ? { values, message: config.message } : { values };
14040
+ const labels = Array.isArray(config.labels) && config.labels.length === values.length ? config.labels : values;
14041
+ return {
14042
+ values,
14043
+ labels,
14044
+ ...config.message ? { message: config.message } : {}
14045
+ };
13627
14046
  }
13628
14047
  function buildEnumFieldMutations(fieldPath, enumSpec) {
13629
14048
  const normalized = normalizeEnumInput(enumSpec);
13630
14049
  if (!normalized) return [];
13631
14050
  const messageArg = normalized.message ? `, message: ${JSON.stringify(normalized.message)}` : "";
14051
+ const labelsArg = normalized.labels ? `, labels: ${JSON.stringify(normalized.labels)}` : "";
13632
14052
  return [
13633
14053
  {
13634
14054
  label: `set enum on ${fieldPath}`,
13635
14055
  query: `mutation { at(path: ${JSON.stringify(fieldPath)}) { set_enum(values: ${JSON.stringify(
13636
14056
  normalized.values
13637
- )}${messageArg}) { values } } }`
14057
+ )}${labelsArg}${messageArg}) { values labels } } }`
13638
14058
  }
13639
14059
  ];
13640
14060
  }
@@ -13644,7 +14064,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
13644
14064
  fieldRows: [
13645
14065
  {
13646
14066
  key: "enum",
13647
- description: 'Allowed values. Accepts `["a", "b"]` or `{ "values": [...], "message": "..." }`.'
14067
+ description: 'Allowed values. Use `{ "values": [...], "labels": [...] }` for authored display labels; otherwise each unchanged value is its display fallback.'
13648
14068
  }
13649
14069
  ]
13650
14070
  },
@@ -13654,6 +14074,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
13654
14074
  type EnumMetamodel {
13655
14075
  model: Model!
13656
14076
  values: [String!]!
14077
+ labels: [String!]!
13657
14078
  message: String
13658
14079
  }
13659
14080
 
@@ -13662,7 +14083,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
13662
14083
  }
13663
14084
 
13664
14085
  extend type ModelMutation {
13665
- set_enum(values: [String!]!, message: String): EnumMetamodel
14086
+ set_enum(values: [String!]!, labels: [String!], message: String): EnumMetamodel
13666
14087
  }
13667
14088
  `
13668
14089
  ],
@@ -13671,15 +14092,19 @@ var enumMetamodelPackage = defineMetamodelPackage({
13671
14092
  EnumMetamodel: {
13672
14093
  model: (value) => value.model,
13673
14094
  values: (value) => value.values,
14095
+ labels: (value) => value.labels || [],
13674
14096
  message: (value) => value.message || null
13675
14097
  },
13676
14098
  Model: {
13677
14099
  enum_rule: async (ant) => await run(ant.enum_rule())
13678
14100
  },
13679
14101
  ModelMutation: {
13680
- set_enum: async (ant, { values, message }) => {
13681
- const model = await run(ant.set_enum(values, message));
13682
- return { model, values, message };
14102
+ set_enum: async (ant, { values, labels, message }) => {
14103
+ const resolvedLabels = Array.isArray(labels) && labels.length === values.length ? labels : values;
14104
+ const model = await run(
14105
+ ant.set_enum(values, resolvedLabels, message)
14106
+ );
14107
+ return { model, values, labels: resolvedLabels, message };
13683
14108
  }
13684
14109
  }
13685
14110
  };
@@ -13692,7 +14117,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
13692
14117
  },
13693
14118
  summary: {
13694
14119
  selections: {
13695
- propertyFields: [`enum_rule { values message }`]
14120
+ propertyFields: [`enum_rule { values labels message }`]
13696
14121
  },
13697
14122
  readPropertySummary(rawProperty) {
13698
14123
  const values = Array.isArray(rawProperty.enum_rule?.values) ? rawProperty.enum_rule.values.filter(
@@ -13700,8 +14125,15 @@ var enumMetamodelPackage = defineMetamodelPackage({
13700
14125
  ) : [];
13701
14126
  if (values.length === 0) return { enumRule: null };
13702
14127
  const message = typeof rawProperty.enum_rule?.message === "string" ? rawProperty.enum_rule.message : null;
14128
+ const labels = Array.isArray(rawProperty.enum_rule?.labels) ? rawProperty.enum_rule.labels.filter(
14129
+ (label) => typeof label === "string" && label.length > 0
14130
+ ) : [];
13703
14131
  return {
13704
- enumRule: message ? { values, message } : { values }
14132
+ enumRule: {
14133
+ values,
14134
+ ...labels.length === values.length ? { labels } : {},
14135
+ ...message ? { message } : {}
14136
+ }
13705
14137
  };
13706
14138
  }
13707
14139
  },
@@ -14358,7 +14790,8 @@ function normalizeStateMachines(values) {
14358
14790
  requirements: parseJsonRecord(transition?.requirements) || parseJsonRecord(transition?.requirements_json),
14359
14791
  permission: parseJsonValue(transition?.permission) ?? parseJsonValue(transition?.permission_json),
14360
14792
  risk: transition?.risk === "low" || transition?.risk === "medium" || transition?.risk === "high" ? transition.risk : null,
14361
- expectedOutcome: parseJsonValue(transition?.expectedOutcome) ?? parseJsonValue(transition?.expected_outcome_json)
14793
+ expectedOutcome: parseJsonValue(transition?.expectedOutcome) ?? parseJsonValue(transition?.expected_outcome_json),
14794
+ outcomes: parseJsonRecord(transition?.outcomes) || parseJsonRecord(transition?.outcomes_json)
14362
14795
  })).filter(
14363
14796
  (transition) => transition.name.length > 0 && transition.from.length > 0 && transition.to.length > 0
14364
14797
  );
@@ -14451,6 +14884,11 @@ function transitionMetadataGraphqlArgs(transition) {
14451
14884
  `expected_outcome_json: ${JSON.stringify(JSON.stringify(transition.expectedOutcome))}`
14452
14885
  );
14453
14886
  }
14887
+ if (transition.outcomes) {
14888
+ args.push(
14889
+ `outcomes_json: ${JSON.stringify(JSON.stringify(transition.outcomes))}`
14890
+ );
14891
+ }
14454
14892
  return args.length > 0 ? `, ${args.join(", ")}` : "";
14455
14893
  }
14456
14894
  function buildStateMachineModelMutations(modelPath, machines) {
@@ -14729,7 +15167,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14729
15167
  name: String!
14730
15168
  state_machine: StateMachine!
14731
15169
  add_state(name: String!, is_final: Boolean, label: String, description: String): StateMachineMutation!
14732
- add_transition(name: String!, from: String!, to: String!, label: String, description: String, action_json: String, assignee_json: String, requirements_json: String, permission_json: String, risk: String, expected_outcome_json: String): StateMachineMutation!
15170
+ add_transition(name: String!, from: String!, to: String!, label: String, description: String, action_json: String, assignee_json: String, requirements_json: String, permission_json: String, risk: String, expected_outcome_json: String, outcomes_json: String): StateMachineMutation!
14733
15171
  activate_transition(name: String!): StateMachineMutation!
14734
15172
  }
14735
15173
 
@@ -14746,6 +15184,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14746
15184
  type StateMachineSnapshotMutation {
14747
15185
  snapshot: StateMachineSnapshot!
14748
15186
  activate_transition(name: String!): StateMachineSnapshotMutation!
15187
+ commit_transition(name: String!, outcome: String!, to: String!, commit_id: String!, source_version: String): StateMachineSnapshotMutation!
14749
15188
  observe_state(state: String!, force: Boolean, source: String): StateMachineSnapshotMutation!
14750
15189
  }
14751
15190
 
@@ -14792,6 +15231,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14792
15231
  permission_json: String
14793
15232
  risk: String
14794
15233
  expected_outcome_json: String
15234
+ outcomes_json: String
14795
15235
  }
14796
15236
 
14797
15237
  type StateMachinePath {
@@ -14802,6 +15242,10 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14802
15242
  type StateMachineTransitionEvent {
14803
15243
  sequence: Int!
14804
15244
  occurred_at: Float!
15245
+ commit_id: String
15246
+ outcome: String
15247
+ source_version: String
15248
+ projected_from_mismatch: String
14805
15249
  transition: StateMachineTransition!
14806
15250
  from: StateMachineState!
14807
15251
  to: StateMachineState!
@@ -14867,7 +15311,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14867
15311
  requirements_json,
14868
15312
  permission_json,
14869
15313
  risk,
14870
- expected_outcome_json
15314
+ expected_outcome_json,
15315
+ outcomes_json
14871
15316
  }) => {
14872
15317
  await run(
14873
15318
  value.target.add_state_machine_transition(
@@ -14883,7 +15328,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14883
15328
  requirementsJson: requirements_json,
14884
15329
  permissionJson: permission_json,
14885
15330
  risk,
14886
- expectedOutcomeJson: expected_outcome_json
15331
+ expectedOutcomeJson: expected_outcome_json,
15332
+ outcomesJson: outcomes_json
14887
15333
  }
14888
15334
  )
14889
15335
  );
@@ -14904,6 +15350,19 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14904
15350
  );
14905
15351
  return value;
14906
15352
  },
15353
+ commit_transition: async (value, { name, outcome, to, commit_id, source_version }) => {
15354
+ await run(
15355
+ value.target.commit_state_machine_transition(
15356
+ value.name,
15357
+ name,
15358
+ outcome,
15359
+ to,
15360
+ commit_id,
15361
+ source_version
15362
+ )
15363
+ );
15364
+ return value;
15365
+ },
14907
15366
  observe_state: async (value, { state, force, source }) => {
14908
15367
  await run(
14909
15368
  value.target.observe_state_machine_state(
@@ -14933,7 +15392,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14933
15392
  requirements_json: (value) => value.requirements_json || null,
14934
15393
  permission_json: (value) => value.permission_json || null,
14935
15394
  risk: (value) => value.risk || null,
14936
- expected_outcome_json: (value) => value.expected_outcome_json || null
15395
+ expected_outcome_json: (value) => value.expected_outcome_json || null,
15396
+ outcomes_json: (value) => value.outcomes_json || null
14937
15397
  },
14938
15398
  StateMachinePath: {
14939
15399
  states: (value) => value.states,
@@ -14942,6 +15402,10 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14942
15402
  StateMachineTransitionEvent: {
14943
15403
  sequence: (value) => value.sequence,
14944
15404
  occurred_at: (value) => value.occurred_at,
15405
+ commit_id: (value) => value.commit_id || null,
15406
+ outcome: (value) => value.outcome || null,
15407
+ source_version: (value) => value.source_version || null,
15408
+ projected_from_mismatch: (value) => value.projected_from_mismatch || null,
14945
15409
  transition: (value) => value.transition,
14946
15410
  from: (value) => value.from,
14947
15411
  to: (value) => value.to
@@ -15015,6 +15479,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15015
15479
  permission_json
15016
15480
  risk
15017
15481
  expected_outcome_json
15482
+ outcomes_json
15018
15483
  }
15019
15484
  }`
15020
15485
  ]
@@ -15644,6 +16109,133 @@ var Environment = class _Environment {
15644
16109
  getAwaitingCount: async () => this.getAwaitingRecordCount()
15645
16110
  };
15646
16111
  }
16112
+ /**
16113
+ * Acknowledge a declared product mutation that already happened outside a
16114
+ * Granular-run effect (for example, in a webhook consumer). These methods
16115
+ * run the declaration's pure projection mapper; they never call its handler.
16116
+ */
16117
+ get commit() {
16118
+ return {
16119
+ effect: async (effect, productResult, options = {}) => this.persistExternalEffect(effect, productResult, options),
16120
+ transition: async (transition, productResult, options = {}) => this.persistExternalTransition(transition, productResult, options),
16121
+ get: async (commitId) => this.controlPlaneRequest(
16122
+ `/control/environments/${this.environmentId}/commits/${encodeURIComponent(
16123
+ commitId
16124
+ )}`
16125
+ ),
16126
+ retry: async (commitId) => this.controlPlaneRequest(
16127
+ `/control/environments/${this.environmentId}/commits/${encodeURIComponent(
16128
+ commitId
16129
+ )}/retry`,
16130
+ { method: "POST" }
16131
+ )
16132
+ };
16133
+ }
16134
+ /** Inspect or retry the agent synchronization of a product snapshot. */
16135
+ get observation() {
16136
+ return {
16137
+ get: async (observationId) => this.controlPlaneRequest(
16138
+ `/control/environments/${this.environmentId}/observations/${encodeURIComponent(
16139
+ observationId
16140
+ )}`
16141
+ ),
16142
+ retry: async (observationId) => this.controlPlaneRequest(
16143
+ `/control/environments/${this.environmentId}/observations/${encodeURIComponent(
16144
+ observationId
16145
+ )}/retry`,
16146
+ { method: "POST" }
16147
+ )
16148
+ };
16149
+ }
16150
+ async persistExternalEffect(effect, productResult, options) {
16151
+ const projection = effect.commit.project(productResult);
16152
+ validateProjectionResult(effect.commit, projection);
16153
+ this.requireExternalIdentity(projection.source.version, options);
16154
+ return this.controlPlaneRequest(
16155
+ `/control/environments/${this.environmentId}/external-commits`,
16156
+ {
16157
+ method: "POST",
16158
+ body: JSON.stringify({
16159
+ kind: "effect",
16160
+ effectKey: computeEffectKey2(effect),
16161
+ ...options.sourceEventId?.trim() ? { sourceEventId: options.sourceEventId.trim() } : {},
16162
+ projection
16163
+ })
16164
+ }
16165
+ );
16166
+ }
16167
+ async persistExternalTransition(transition, productResult, options) {
16168
+ const metadata = getDefinedTransitionMetadata(transition);
16169
+ if (!metadata) {
16170
+ throw new Error(
16171
+ "environment.commit.transition requires a transition returned by defineStateMachine"
16172
+ );
16173
+ }
16174
+ const projection = transition.effect.commit.project(productResult);
16175
+ validateProjectionResult(transition.effect.commit, projection);
16176
+ this.requireExternalIdentity(projection.source.version, options);
16177
+ if (!Object.prototype.hasOwnProperty.call(
16178
+ transition.outcomes,
16179
+ projection.outcome.key
16180
+ )) {
16181
+ throw new Error(
16182
+ `Transition ${metadata.machine}.${metadata.transition} does not declare outcome ${projection.outcome.key}`
16183
+ );
16184
+ }
16185
+ if (!projection.primaryTarget) {
16186
+ throw new Error(
16187
+ "An external transition projection requires primaryTarget to identify the transitioned product record"
16188
+ );
16189
+ }
16190
+ return this.controlPlaneRequest(
16191
+ `/control/environments/${this.environmentId}/external-commits`,
16192
+ {
16193
+ method: "POST",
16194
+ body: JSON.stringify({
16195
+ kind: "transition",
16196
+ effectKey: computeEffectKey2(transition.effect),
16197
+ ...options.sourceEventId?.trim() ? { sourceEventId: options.sourceEventId.trim() } : {},
16198
+ projection,
16199
+ transition: {
16200
+ className: projection.primaryTarget.className,
16201
+ objectId: projection.primaryTarget.id,
16202
+ ...projection.primaryTarget.path ? { objectPath: projection.primaryTarget.path } : {},
16203
+ machine: metadata.machine,
16204
+ transition: metadata.transition,
16205
+ from: transition.from
16206
+ }
16207
+ })
16208
+ }
16209
+ );
16210
+ }
16211
+ requireExternalIdentity(sourceVersion, options) {
16212
+ if (!options.sourceEventId?.trim() && !sourceVersion?.trim()) {
16213
+ throw new Error(
16214
+ "An external commit requires sourceEventId or a source version from its projection mapper"
16215
+ );
16216
+ }
16217
+ }
16218
+ /**
16219
+ * Synchronize a versioned product snapshot without claiming an effect or
16220
+ * lifecycle transition. This records no transition history.
16221
+ */
16222
+ async observe(mapper, productResult) {
16223
+ const declaration = { kind: "effect"};
16224
+ const projection = mapper(productResult);
16225
+ validateProjectionResult(declaration, projection);
16226
+ if (!projection.source.version?.trim()) {
16227
+ throw new Error(
16228
+ "environment.observe requires a monotonic source version or serialized adapter sequence"
16229
+ );
16230
+ }
16231
+ return this.controlPlaneRequest(
16232
+ `/control/environments/${this.environmentId}/observations`,
16233
+ {
16234
+ method: "POST",
16235
+ body: JSON.stringify({ projection })
16236
+ }
16237
+ );
16238
+ }
15647
16239
  /**
15648
16240
  * Mirror product-owned workflow state into Granular without making Granular
15649
16241
  * own the customer application's state machine.
@@ -17050,6 +17642,14 @@ var EnvironmentSession = class extends Session {
17050
17642
  }
17051
17643
  };
17052
17644
  }
17645
+ get mutations() {
17646
+ return {
17647
+ list: (options = {}) => this.sessionDataRequest(
17648
+ "/mutations",
17649
+ options
17650
+ )
17651
+ };
17652
+ }
17053
17653
  get artifacts() {
17054
17654
  return {
17055
17655
  list: (options = {}) => {
@@ -18549,6 +19149,7 @@ var Granular = class _Granular {
18549
19149
  const serialized = {
18550
19150
  effectKey: computeEffectKey2(effect),
18551
19151
  name: effect.name,
19152
+ ...effect.label ? { label: effect.label } : {},
18552
19153
  description: effect.description,
18553
19154
  inputSchema: effect.inputSchema,
18554
19155
  stability: effect.stability || "stable",
@@ -18572,6 +19173,9 @@ var Granular = class _Granular {
18572
19173
  if (effect.metamodels !== void 0) {
18573
19174
  serialized.metamodels = effect.metamodels;
18574
19175
  }
19176
+ if (effect.commit !== void 0) {
19177
+ serialized.commit = { kind: effect.commit.kind };
19178
+ }
18575
19179
  return serialized;
18576
19180
  }
18577
19181
  async publishSandboxEffectCatalog(host) {
@@ -18792,16 +19396,60 @@ var Granular = class _Granular {
18792
19396
  };
18793
19397
  wsClient.registerRpcHandler("effect.invoke", async (params) => {
18794
19398
  const request = params;
18795
- return invokeRegisteredEffect(
19399
+ let commitReceipt;
19400
+ const result = await invokeRegisteredEffect(
18796
19401
  this.getSandboxEffectMap(sandboxId),
18797
19402
  request,
18798
19403
  {
19404
+ commitAcknowledged: (receipt) => {
19405
+ commitReceipt = receipt;
19406
+ },
19407
+ commit: {
19408
+ persist: (commitRequest) => this.request(
19409
+ `/control/environments/${encodeURIComponent(
19410
+ commitRequest.environmentId
19411
+ )}/commits`,
19412
+ {
19413
+ method: "POST",
19414
+ body: JSON.stringify({
19415
+ kind: commitRequest.kind,
19416
+ invocationId: commitRequest.invocationId,
19417
+ idempotencyKey: commitRequest.idempotencyKey,
19418
+ effectKey: commitRequest.effectKey,
19419
+ projection: commitRequest.projection
19420
+ })
19421
+ }
19422
+ ),
19423
+ mappingFailed: (failure) => this.request(
19424
+ `/control/environments/${encodeURIComponent(
19425
+ failure.environmentId
19426
+ )}/commit-invocations/${encodeURIComponent(
19427
+ failure.invocationId
19428
+ )}`,
19429
+ {
19430
+ method: "PATCH",
19431
+ body: JSON.stringify({
19432
+ status: "mapping_failed",
19433
+ error: {
19434
+ code: "commit_projection_mapping_failed",
19435
+ message: failure.message,
19436
+ retryable: false
19437
+ }
19438
+ })
19439
+ }
19440
+ )
19441
+ },
18799
19442
  feedback: {
18800
19443
  invocationId: request.callId,
18801
19444
  publish: (method, publishParams) => wsClient.call(method, publishParams)
18802
19445
  }
18803
19446
  }
18804
19447
  );
19448
+ return {
19449
+ __granularEffectInvocationResult: true,
19450
+ result,
19451
+ ...commitReceipt ? { commit: commitReceipt } : {}
19452
+ };
18805
19453
  });
18806
19454
  wsClient.on("open", () => {
18807
19455
  void this.synchronizeEffectHost(host).catch((error) => {