@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.
@@ -6508,6 +6508,11 @@ var Session = class {
6508
6508
  }
6509
6509
  }
6510
6510
  buildDirectedInvocationEffectContext(params, feedbackContext) {
6511
+ const commitUnavailable = async () => {
6512
+ throw new Error(
6513
+ "This directed browser tool invocation has no product commit transport"
6514
+ );
6515
+ };
6511
6516
  return {
6512
6517
  effectClientId: this.clientId,
6513
6518
  sandboxId: params.sandboxId || "",
@@ -6520,6 +6525,10 @@ var Session = class {
6520
6525
  userId: "",
6521
6526
  subjectId: ""
6522
6527
  },
6528
+ commit: {
6529
+ effect: commitUnavailable,
6530
+ transition: commitUnavailable
6531
+ },
6523
6532
  ...feedbackContext ? {
6524
6533
  feedback: feedbackContext.feedback,
6525
6534
  transientFeedback: feedbackContext.transientFeedback
@@ -12172,8 +12181,12 @@ external_exports.union([
12172
12181
  external_exports.array(external_exports.string()),
12173
12182
  external_exports.object({
12174
12183
  values: external_exports.array(external_exports.string()),
12184
+ labels: external_exports.array(external_exports.string().min(1)).optional(),
12175
12185
  message: external_exports.string().optional()
12176
- }).strict()
12186
+ }).strict().refine(
12187
+ (rule) => !rule.labels || rule.labels.length === rule.values.length,
12188
+ { message: "Enum labels must match enum values one-for-one" }
12189
+ )
12177
12190
  ]);
12178
12191
  external_exports.union([
12179
12192
  external_exports.boolean(),
@@ -12201,7 +12214,7 @@ var StateMachineStateSchema = external_exports.union([
12201
12214
  external_exports.string(),
12202
12215
  external_exports.object({
12203
12216
  name: external_exports.string().min(1),
12204
- label: external_exports.string().optional(),
12217
+ label: external_exports.string().min(1).optional(),
12205
12218
  description: external_exports.string().optional(),
12206
12219
  isFinal: external_exports.boolean().optional()
12207
12220
  }).strict()
@@ -12287,6 +12300,12 @@ var StateTransitionExpectedOutcomeSchema = external_exports.union([
12287
12300
  summary: external_exports.string().optional()
12288
12301
  }).strict()
12289
12302
  ]);
12303
+ var StateTransitionOutcomeSchema = external_exports.object({
12304
+ label: external_exports.string().min(1).optional(),
12305
+ to: external_exports.string().min(1),
12306
+ primary: external_exports.boolean().optional(),
12307
+ disposition: external_exports.enum(["continue", "error"])
12308
+ }).strict();
12290
12309
  var StateMachineTransitionSchema = external_exports.object({
12291
12310
  name: external_exports.string().min(1),
12292
12311
  from: external_exports.string().min(1),
@@ -12298,7 +12317,8 @@ var StateMachineTransitionSchema = external_exports.object({
12298
12317
  requirements: StateTransitionRequirementsSchema.optional(),
12299
12318
  permission: StateTransitionPermissionSchema.optional(),
12300
12319
  risk: external_exports.enum(["low", "medium", "high"]).optional(),
12301
- expectedOutcome: StateTransitionExpectedOutcomeSchema.optional()
12320
+ expectedOutcome: StateTransitionExpectedOutcomeSchema.optional(),
12321
+ outcomes: external_exports.record(external_exports.string().min(1), StateTransitionOutcomeSchema).optional()
12302
12322
  }).strict();
12303
12323
  external_exports.object({
12304
12324
  name: external_exports.string().min(1),
@@ -12979,6 +12999,238 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
12979
12999
  }
12980
13000
  });
12981
13001
 
13002
+ // src/commit.ts
13003
+ var MAX_PROJECTION_CHANGES = 1e3;
13004
+ var MAX_SAFE_SUMMARY_KEYS = 32;
13005
+ var MAX_SAFE_TEXT_LENGTH = 2e3;
13006
+ var definedTransitionMetadata = /* @__PURE__ */ new WeakMap();
13007
+ function getDefinedTransitionMetadata(transition) {
13008
+ return definedTransitionMetadata.get(transition);
13009
+ }
13010
+ function requireNonEmptyString(value, path2) {
13011
+ if (typeof value !== "string" || value.trim().length === 0) {
13012
+ throw new Error(`${path2} must be a non-empty string`);
13013
+ }
13014
+ return value.trim();
13015
+ }
13016
+ function validateObjectReference(value, path2) {
13017
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
13018
+ throw new Error(`${path2} must be an object reference`);
13019
+ }
13020
+ const reference = value;
13021
+ requireNonEmptyString(reference.className, `${path2}.className`);
13022
+ requireNonEmptyString(reference.id, `${path2}.id`);
13023
+ if (reference.path !== void 0) {
13024
+ requireNonEmptyString(reference.path, `${path2}.path`);
13025
+ }
13026
+ }
13027
+ function validateScalarRecord(value, path2) {
13028
+ if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) {
13029
+ throw new Error(`${path2} must be an object of scalar values`);
13030
+ }
13031
+ for (const [key, item] of Object.entries(value)) {
13032
+ if (item !== null && typeof item !== "string" && typeof item !== "number" && typeof item !== "boolean") {
13033
+ throw new Error(
13034
+ `${path2}.${key} must be a string, finite number, boolean, or null`
13035
+ );
13036
+ }
13037
+ if (typeof item === "number" && !Number.isFinite(item)) {
13038
+ throw new Error(`${path2}.${key} must be finite`);
13039
+ }
13040
+ }
13041
+ }
13042
+ function validateProjectedRecord(value, path2) {
13043
+ validateObjectReference(value, path2);
13044
+ const record = value;
13045
+ if (record.label !== void 0) {
13046
+ if (typeof record.label !== "string") {
13047
+ throw new Error(`${path2}.label must be a string`);
13048
+ }
13049
+ if (record.label.length > MAX_SAFE_TEXT_LENGTH) {
13050
+ throw new Error(
13051
+ `${path2}.label exceeds ${MAX_SAFE_TEXT_LENGTH} characters`
13052
+ );
13053
+ }
13054
+ }
13055
+ validateScalarRecord(record.fields, `${path2}.fields`);
13056
+ }
13057
+ function validateBoundedJson(value, path2, depth = 0) {
13058
+ if (depth > 12) throw new Error(`${path2} is nested too deeply`);
13059
+ if (value === null || typeof value === "boolean") return;
13060
+ if (typeof value === "number") {
13061
+ if (!Number.isFinite(value)) throw new Error(`${path2} must be finite`);
13062
+ return;
13063
+ }
13064
+ if (typeof value === "string") {
13065
+ if (value.length > MAX_SAFE_TEXT_LENGTH) {
13066
+ throw new Error(`${path2} exceeds ${MAX_SAFE_TEXT_LENGTH} characters`);
13067
+ }
13068
+ return;
13069
+ }
13070
+ if (Array.isArray(value)) {
13071
+ if (value.length > MAX_PROJECTION_CHANGES) {
13072
+ throw new Error(`${path2} contains too many values`);
13073
+ }
13074
+ value.forEach(
13075
+ (item, index) => validateBoundedJson(item, `${path2}[${index}]`, depth + 1)
13076
+ );
13077
+ return;
13078
+ }
13079
+ if (!value || typeof value !== "object") {
13080
+ throw new Error(`${path2} contains an unsupported value`);
13081
+ }
13082
+ const entries = Object.entries(value);
13083
+ if (entries.length > 256) throw new Error(`${path2} contains too many keys`);
13084
+ for (const [key, item] of entries) {
13085
+ if (/token|secret|password|authorization|cookie/i.test(key)) {
13086
+ throw new Error(`${path2}.${key} is not allowed in a commit projection`);
13087
+ }
13088
+ validateBoundedJson(item, `${path2}.${key}`, depth + 1);
13089
+ }
13090
+ }
13091
+ function validateProjectionResult(declaration, projection) {
13092
+ if (!projection || typeof projection !== "object") {
13093
+ throw new Error("Projection mapper must return an object");
13094
+ }
13095
+ requireNonEmptyString(
13096
+ projection.source?.reference,
13097
+ "projection.source.reference"
13098
+ );
13099
+ if (projection.source.version !== void 0) {
13100
+ requireNonEmptyString(
13101
+ projection.source.version,
13102
+ "projection.source.version"
13103
+ );
13104
+ }
13105
+ if (!Array.isArray(projection.changes)) {
13106
+ throw new Error("projection.changes must be an array");
13107
+ }
13108
+ if (projection.changes.length > MAX_PROJECTION_CHANGES) {
13109
+ throw new Error(
13110
+ `projection.changes exceeds the ${MAX_PROJECTION_CHANGES} change limit`
13111
+ );
13112
+ }
13113
+ if (projection.primaryTarget) {
13114
+ validateObjectReference(
13115
+ projection.primaryTarget,
13116
+ "projection.primaryTarget"
13117
+ );
13118
+ }
13119
+ if (projection.safeSummary) {
13120
+ const entries = Object.entries(projection.safeSummary);
13121
+ if (entries.length > MAX_SAFE_SUMMARY_KEYS) {
13122
+ throw new Error(
13123
+ `projection.safeSummary exceeds the ${MAX_SAFE_SUMMARY_KEYS} key limit`
13124
+ );
13125
+ }
13126
+ validateBoundedJson(projection.safeSummary, "projection.safeSummary");
13127
+ validateScalarRecord(projection.safeSummary, "projection.safeSummary");
13128
+ }
13129
+ projection.changes.forEach((change, index) => {
13130
+ const changePath = `projection.changes[${index}]`;
13131
+ validateBoundedJson(change, changePath);
13132
+ if (!change || typeof change !== "object" || Array.isArray(change)) {
13133
+ throw new Error(`${changePath} must be an object`);
13134
+ }
13135
+ const rawChange = change;
13136
+ const kind = requireNonEmptyString(rawChange.kind, `${changePath}.kind`);
13137
+ if (kind === "object") {
13138
+ const operation = requireNonEmptyString(
13139
+ rawChange.operation,
13140
+ `${changePath}.operation`
13141
+ );
13142
+ if (operation !== "created" && operation !== "updated" && operation !== "deleted") {
13143
+ throw new Error(
13144
+ `${changePath}.operation must be created, updated, or deleted`
13145
+ );
13146
+ }
13147
+ if (operation === "deleted") {
13148
+ validateObjectReference(rawChange.target, `${changePath}.target`);
13149
+ } else {
13150
+ validateProjectedRecord(rawChange.record, `${changePath}.record`);
13151
+ }
13152
+ } else if (kind === "relationship") {
13153
+ const operation = requireNonEmptyString(
13154
+ rawChange.operation,
13155
+ `${changePath}.operation`
13156
+ );
13157
+ if (operation !== "connected" && operation !== "disconnected") {
13158
+ throw new Error(
13159
+ `${changePath}.operation must be connected or disconnected`
13160
+ );
13161
+ }
13162
+ requireNonEmptyString(
13163
+ rawChange.relationship,
13164
+ `${changePath}.relationship`
13165
+ );
13166
+ validateObjectReference(rawChange.from, `${changePath}.from`);
13167
+ validateObjectReference(rawChange.to, `${changePath}.to`);
13168
+ } else if (kind === "state_observation") {
13169
+ if (rawChange.operation !== void 0) {
13170
+ throw new Error(
13171
+ `${changePath}.operation is not valid for an observation`
13172
+ );
13173
+ }
13174
+ validateObjectReference(rawChange.target, `${changePath}.target`);
13175
+ requireNonEmptyString(rawChange.machine, `${changePath}.machine`);
13176
+ requireNonEmptyString(rawChange.state, `${changePath}.state`);
13177
+ } else {
13178
+ throw new Error(`${changePath}.kind is unsupported: ${kind}`);
13179
+ }
13180
+ });
13181
+ const objectOperations = /* @__PURE__ */ new Map();
13182
+ for (const change of projection.changes) {
13183
+ if (change.kind !== "object") continue;
13184
+ const reference = change.operation === "deleted" ? change.target : change.record;
13185
+ const key = `${reference.className}\0${reference.id}`;
13186
+ const operations = objectOperations.get(key) || {
13187
+ deleted: false,
13188
+ upserted: false
13189
+ };
13190
+ if (change.operation === "deleted") operations.deleted = true;
13191
+ else operations.upserted = true;
13192
+ if (operations.deleted && operations.upserted) {
13193
+ throw new Error(
13194
+ `projection.changes cannot both delete and upsert ${reference.className}/${reference.id}; return only its canonical final state`
13195
+ );
13196
+ }
13197
+ objectOperations.set(key, operations);
13198
+ }
13199
+ const outcome = projection.outcome;
13200
+ if (declaration.kind === "transition") {
13201
+ if (!outcome) {
13202
+ throw new Error("A transition projection must return an outcome");
13203
+ }
13204
+ requireNonEmptyString(outcome.key, "projection.outcome.key");
13205
+ if (outcome.error) {
13206
+ validateBoundedJson(outcome.error, "projection.outcome.error");
13207
+ }
13208
+ } else if (projection.outcome !== void 0) {
13209
+ throw new Error("An effect projection cannot declare a transition outcome");
13210
+ }
13211
+ }
13212
+ function canonicalizeCommitValue(value) {
13213
+ const normalize = (current) => {
13214
+ if (current === null || typeof current === "boolean" || typeof current === "string") {
13215
+ return typeof current === "string" ? current.normalize("NFC") : current;
13216
+ }
13217
+ if (typeof current === "number") {
13218
+ if (!Number.isFinite(current)) {
13219
+ throw new Error("Cannot canonicalize a non-finite number");
13220
+ }
13221
+ return Object.is(current, -0) ? 0 : current;
13222
+ }
13223
+ if (Array.isArray(current)) return current.map(normalize);
13224
+ if (current && typeof current === "object") {
13225
+ return Object.fromEntries(
13226
+ Object.entries(current).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key.normalize("NFC"), normalize(item)])
13227
+ );
13228
+ }
13229
+ throw new Error(`Cannot canonicalize ${typeof current}`);
13230
+ };
13231
+ return JSON.stringify(normalize(value));
13232
+ }
13233
+
12982
13234
  // src/effect-runtime.ts
12983
13235
  function computeEffectKey(effect) {
12984
13236
  const attachedClass = effect.className?.trim();
@@ -13043,6 +13295,45 @@ function resolveInvocationMode(context) {
13043
13295
  }
13044
13296
  return "execute";
13045
13297
  }
13298
+ async function sha256Hex(value) {
13299
+ const digest = await globalThis.crypto.subtle.digest(
13300
+ "SHA-256",
13301
+ new TextEncoder().encode(value)
13302
+ );
13303
+ return Array.from(
13304
+ new Uint8Array(digest),
13305
+ (byte) => byte.toString(16).padStart(2, "0")
13306
+ ).join("");
13307
+ }
13308
+ async function resolveInvocationIdempotencyKey(request) {
13309
+ const supplied = request.context?.idempotencyKey?.trim();
13310
+ if (supplied) return supplied;
13311
+ const invocationId = request.context?.invocationId?.trim();
13312
+ if (!invocationId) {
13313
+ throw new Error(
13314
+ `Committed effect ${request.effectKey} requires an invocation id`
13315
+ );
13316
+ }
13317
+ const digest = await sha256Hex(
13318
+ canonicalizeCommitValue({
13319
+ sandboxId: request.context?.sandboxId || "",
13320
+ environmentId: request.context?.environmentId || "",
13321
+ effectKey: request.effectKey,
13322
+ invocationId,
13323
+ input: request.input
13324
+ })
13325
+ );
13326
+ return `gci_${digest}`;
13327
+ }
13328
+ function createUnavailableCommitContext(message) {
13329
+ const unavailable = async () => {
13330
+ throw new Error(message);
13331
+ };
13332
+ return {
13333
+ effect: unavailable,
13334
+ transition: unavailable
13335
+ };
13336
+ }
13046
13337
  function resolveReverseEffect(effectMap, currentEffect, request, behaviors) {
13047
13338
  const explicitReverseHandler = request.context?.invocation?.reverseHandler?.trim();
13048
13339
  const configuredReverseHandler = behaviors.reverse?.handler?.trim();
@@ -13095,7 +13386,7 @@ function resolveHandlerForMode(effectMap, effect, request) {
13095
13386
  if (mode === "artifactOptions") {
13096
13387
  if (!effect.artifactOptionsHandler) {
13097
13388
  throw new Error(
13098
- `Artifact relationship options are not supported for ${request.effectKey}`
13389
+ `Artifact field options are not supported for ${request.effectKey}`
13099
13390
  );
13100
13391
  }
13101
13392
  return {
@@ -13114,22 +13405,32 @@ function resolveHandlerForMode(effectMap, effect, request) {
13114
13405
  throw new Error(`Dry run is not supported for ${request.effectKey}`);
13115
13406
  }
13116
13407
  if (mode === "reverse") {
13408
+ const sourceEffectKey = request.context?.invocation?.sourceEffectKey?.trim();
13409
+ if (sourceEffectKey && sourceEffectKey !== request.effectKey) {
13410
+ return { effect, mode, handler: effect.handler };
13411
+ }
13412
+ const namedReverseHandler = request.context?.invocation?.reverseHandler?.trim() || behaviors.reverse?.handler?.trim();
13413
+ if (namedReverseHandler) {
13414
+ const reverseEffect = resolveReverseEffect(
13415
+ effectMap,
13416
+ effect,
13417
+ request,
13418
+ behaviors
13419
+ );
13420
+ if (reverseEffect) {
13421
+ return {
13422
+ effect: reverseEffect,
13423
+ mode,
13424
+ handler: reverseEffect.handler
13425
+ };
13426
+ }
13427
+ throw new Error(
13428
+ `Reverse effect ${namedReverseHandler} is not registered for ${request.effectKey}`
13429
+ );
13430
+ }
13117
13431
  if (effect.reverseHandler) {
13118
13432
  return { effect, mode, handler: effect.reverseHandler };
13119
13433
  }
13120
- const reverseEffect = resolveReverseEffect(
13121
- effectMap,
13122
- effect,
13123
- request,
13124
- behaviors
13125
- );
13126
- if (reverseEffect) {
13127
- return {
13128
- effect: reverseEffect,
13129
- mode,
13130
- handler: reverseEffect.reverseHandler || reverseEffect.handler
13131
- };
13132
- }
13133
13434
  throw new Error(
13134
13435
  `Reverse execution is not supported for ${request.effectKey}`
13135
13436
  );
@@ -13230,18 +13531,142 @@ async function invokeRegisteredEffect(effectMap, request, options = {}) {
13230
13531
  throw new Error(`Effect handler not found: ${request.effectKey}`);
13231
13532
  }
13232
13533
  const resolved = resolveHandlerForMode(effectMap, effect, request);
13534
+ const isPreResolvedNamedReverse = resolved.mode === "reverse" && Boolean(request.context?.invocation?.sourceEffectKey?.trim()) && request.context?.invocation?.sourceEffectKey?.trim() !== request.effectKey;
13535
+ const resolvedEffectKey = computeEffectKey(resolved.effect);
13536
+ if (resolved.mode === "reverse" && resolved.effect !== effect && (effect.commit || resolved.effect.commit)) {
13537
+ throw new Error(
13538
+ `Committed named reverse effect ${resolvedEffectKey} must be reserved and dispatched using its own effect key; customer code was not invoked`
13539
+ );
13540
+ }
13541
+ const declaration = resolved.effect.commit;
13542
+ const commitTransport = options.commit;
13543
+ const commitRequired = Boolean(declaration) && (resolved.mode === "execute" || resolved.mode === "reverse");
13544
+ if (commitRequired && !commitTransport) {
13545
+ throw new Error(
13546
+ `Committed effect ${request.effectKey} has no durable commit transport; customer code was not invoked`
13547
+ );
13548
+ }
13549
+ if (commitRequired && declaration?.kind === "transition" && !request.context?.invocation?.transition) {
13550
+ throw new Error(
13551
+ `Transition effect ${request.effectKey} has no resolved transition context; customer code was not invoked`
13552
+ );
13553
+ }
13554
+ const idempotencyKey = commitRequired ? await resolveInvocationIdempotencyKey(request) : request.context?.idempotencyKey;
13555
+ let commitStarted = false;
13556
+ let commitPromise = null;
13557
+ const beginCommit = (requestedKind, productResult) => {
13558
+ if (!declaration || !commitRequired || !commitTransport) {
13559
+ return Promise.reject(
13560
+ new Error(
13561
+ `Effect ${request.effectKey} does not declare an active ${requestedKind} commit`
13562
+ )
13563
+ );
13564
+ }
13565
+ if (declaration.kind !== requestedKind) {
13566
+ return Promise.reject(
13567
+ new Error(
13568
+ `Effect ${request.effectKey} declares ${declaration.kind} commit, not ${requestedKind}`
13569
+ )
13570
+ );
13571
+ }
13572
+ if (commitStarted) {
13573
+ return Promise.reject(
13574
+ new Error(
13575
+ `Effect invocation ${request.context?.invocationId || request.effectKey} already emitted its commit`
13576
+ )
13577
+ );
13578
+ }
13579
+ commitStarted = true;
13580
+ commitPromise = (async () => {
13581
+ let projection;
13582
+ try {
13583
+ projection = declaration.project(productResult);
13584
+ validateProjectionResult(declaration, projection);
13585
+ } catch (error) {
13586
+ const message = error instanceof Error ? error.message : String(error);
13587
+ if (commitTransport.mappingFailed) {
13588
+ await commitTransport.mappingFailed({
13589
+ effectKey: request.effectKey,
13590
+ effectName: request.effectName,
13591
+ invocationId: request.context?.invocationId || "",
13592
+ idempotencyKey: idempotencyKey || "",
13593
+ environmentId: request.context?.environmentId || "",
13594
+ message
13595
+ });
13596
+ }
13597
+ throw new Error(
13598
+ `Product mutation may have succeeded, but its commit projection is invalid: ${message}`
13599
+ );
13600
+ }
13601
+ if (requestedKind === "transition") {
13602
+ const transition = request.context?.invocation?.transition;
13603
+ const outcome = projection.outcome;
13604
+ if (!transition || !outcome?.key) {
13605
+ throw new Error(
13606
+ `Transition effect ${request.effectKey} did not resolve an authored outcome`
13607
+ );
13608
+ }
13609
+ if (!Object.prototype.hasOwnProperty.call(
13610
+ transition.outcomes,
13611
+ outcome.key
13612
+ )) {
13613
+ throw new Error(
13614
+ `Transition ${transition.machine}.${transition.transition} does not declare outcome ${outcome.key}`
13615
+ );
13616
+ }
13617
+ }
13618
+ const invocationId = request.context?.invocationId || "";
13619
+ const environmentId = request.context?.environmentId || "";
13620
+ const sandboxId = request.context?.sandboxId || "";
13621
+ if (!invocationId || !environmentId || !sandboxId || !idempotencyKey) {
13622
+ throw new Error(
13623
+ `Committed effect ${request.effectKey} is missing its trusted invocation scope`
13624
+ );
13625
+ }
13626
+ const commitRequest = {
13627
+ kind: requestedKind,
13628
+ effectKey: request.effectKey,
13629
+ effectName: request.effectName,
13630
+ operationLabel: resolved.effect.label || resolved.effect.name,
13631
+ invocationId,
13632
+ idempotencyKey,
13633
+ sandboxId,
13634
+ environmentId,
13635
+ ...request.context?.sessionId ? { sessionId: request.context.sessionId } : {},
13636
+ ...request.context?.jobId ? { jobId: request.context.jobId } : {},
13637
+ ...request.context?.buildId ? { buildId: request.context.buildId } : {},
13638
+ ...request.context?.invocation?.artifactId ? { artifactId: request.context.invocation.artifactId } : {},
13639
+ projection,
13640
+ ...requestedKind === "transition" && request.context?.invocation?.transition ? { transition: request.context.invocation.transition } : {}
13641
+ };
13642
+ const receipt = await commitTransport.persist(commitRequest);
13643
+ await options.commitAcknowledged?.(receipt);
13644
+ return receipt;
13645
+ })();
13646
+ return commitPromise;
13647
+ };
13648
+ const commitContext = commitRequired ? {
13649
+ effect: (productResult) => beginCommit("effect", productResult),
13650
+ transition: (productResult) => beginCommit("transition", productResult)
13651
+ } : createUnavailableCommitContext(
13652
+ `Effect ${request.effectKey} is not executing a declared product commit`
13653
+ );
13233
13654
  const context = {
13234
13655
  ...request.context || {},
13656
+ ...idempotencyKey ? { idempotencyKey } : {},
13657
+ commit: commitContext,
13235
13658
  behaviors: normalizeEffectBehaviors(
13236
13659
  request.context?.behaviors || effect.metamodels || void 0
13237
13660
  ),
13238
13661
  invocation: {
13239
13662
  mode: resolved.mode,
13240
- sourceEffectKey: request.effectKey,
13241
- sourceEffectName: request.effectName,
13663
+ ...commitRequired && declaration ? { commitKind: declaration.kind } : {},
13664
+ sourceEffectKey: isPreResolvedNamedReverse ? request.context?.invocation?.sourceEffectKey || request.effectKey : request.effectKey,
13665
+ sourceEffectName: isPreResolvedNamedReverse ? request.context?.invocation?.sourceEffectName || request.effectName : request.effectName,
13242
13666
  ...request.context?.invocation?.reverseHandler ? { reverseHandler: request.context.invocation.reverseHandler } : {},
13243
13667
  ...request.context?.invocation?.artifactId ? { artifactId: request.context.invocation.artifactId } : {},
13244
- ...request.context?.invocation?.artifactOptions ? { artifactOptions: request.context.invocation.artifactOptions } : {}
13668
+ ...request.context?.invocation?.artifactOptions ? { artifactOptions: request.context.invocation.artifactOptions } : {},
13669
+ ...request.context?.invocation?.transition ? { transition: request.context.invocation.transition } : {}
13245
13670
  }
13246
13671
  };
13247
13672
  const feedbackContext = options.feedback ? createInvocationFeedbackContext(options.feedback) : null;
@@ -13265,6 +13690,16 @@ async function invokeRegisteredEffect(effectMap, request, options = {}) {
13265
13690
  handlerFailed = true;
13266
13691
  handlerError = error;
13267
13692
  }
13693
+ let commitError;
13694
+ let commitFailed = false;
13695
+ if (commitPromise) {
13696
+ try {
13697
+ await commitPromise;
13698
+ } catch (error) {
13699
+ commitFailed = true;
13700
+ commitError = error;
13701
+ }
13702
+ }
13268
13703
  let feedbackError;
13269
13704
  let feedbackFailed = false;
13270
13705
  if (feedbackContext) {
@@ -13278,9 +13713,17 @@ async function invokeRegisteredEffect(effectMap, request, options = {}) {
13278
13713
  if (handlerFailed) {
13279
13714
  throw handlerError;
13280
13715
  }
13716
+ if (commitFailed) {
13717
+ throw commitError;
13718
+ }
13281
13719
  if (feedbackFailed) {
13282
13720
  throw feedbackError;
13283
13721
  }
13722
+ if (commitRequired && !commitStarted) {
13723
+ throw new Error(
13724
+ `Mutating effect ${request.effectKey} returned without acknowledging its product mutation`
13725
+ );
13726
+ }
13284
13727
  return handlerResult;
13285
13728
  }
13286
13729
 
@@ -13349,12 +13792,7 @@ function toRecordSearchResult(className, node) {
13349
13792
  (field) => normalizeGraphPathSegment(field.name) === "real_id" && typeof field.value === "string" && field.value.trim()
13350
13793
  );
13351
13794
  const id = typeof realIdField?.value === "string" ? realIdField.value.trim() : graphPathId;
13352
- const rawLabel = typeof node.label === "string" && node.label.trim() ? node.label : "";
13353
- if (fields.length === 0 && rawLabel && isPlaceholderRecordLabel(rawLabel, graphPathId, path2)) {
13354
- return null;
13355
- }
13356
- const fallbackLabel = displayLabelFromFields(fields);
13357
- const label = rawLabel && !isPlaceholderRecordLabel(rawLabel, id, path2) ? rawLabel : fallbackLabel || rawLabel || id;
13795
+ const label = typeof node.label === "string" && node.label.trim() ? node.label.trim() : path2 || id;
13358
13796
  return {
13359
13797
  path: path2,
13360
13798
  className,
@@ -13364,30 +13802,6 @@ function toRecordSearchResult(className, node) {
13364
13802
  fields
13365
13803
  };
13366
13804
  }
13367
- function isPlaceholderRecordLabel(label, id, path2) {
13368
- const normalizedLabel = normalizeGraphPathSegment(label);
13369
- return normalizedLabel === normalizeGraphPathSegment(id) || normalizedLabel === normalizeGraphPathSegment(path2);
13370
- }
13371
- function displayLabelFromFields(fields) {
13372
- const preferredFieldNames = [
13373
- "name",
13374
- "title",
13375
- "label",
13376
- "display_name",
13377
- "file_name",
13378
- "number",
13379
- "code"
13380
- ];
13381
- for (const preferred of preferredFieldNames) {
13382
- const match = fields.find(
13383
- (field) => normalizeGraphPathSegment(field.name) === preferred && typeof field.value === "string" && field.value.trim()
13384
- );
13385
- if (typeof match?.value === "string") {
13386
- return match.value.trim();
13387
- }
13388
- }
13389
- return null;
13390
- }
13391
13805
  function normalizeRecordSearchText(value) {
13392
13806
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
13393
13807
  }
@@ -13597,18 +14011,24 @@ function normalizeEnumInput(enumSpec) {
13597
14011
  (value) => typeof value === "string" && value.length > 0
13598
14012
  );
13599
14013
  if (values.length === 0) return null;
13600
- return config.message ? { values, message: config.message } : { values };
14014
+ const labels = Array.isArray(config.labels) && config.labels.length === values.length ? config.labels : values;
14015
+ return {
14016
+ values,
14017
+ labels,
14018
+ ...config.message ? { message: config.message } : {}
14019
+ };
13601
14020
  }
13602
14021
  function buildEnumFieldMutations(fieldPath, enumSpec) {
13603
14022
  const normalized = normalizeEnumInput(enumSpec);
13604
14023
  if (!normalized) return [];
13605
14024
  const messageArg = normalized.message ? `, message: ${JSON.stringify(normalized.message)}` : "";
14025
+ const labelsArg = normalized.labels ? `, labels: ${JSON.stringify(normalized.labels)}` : "";
13606
14026
  return [
13607
14027
  {
13608
14028
  label: `set enum on ${fieldPath}`,
13609
14029
  query: `mutation { at(path: ${JSON.stringify(fieldPath)}) { set_enum(values: ${JSON.stringify(
13610
14030
  normalized.values
13611
- )}${messageArg}) { values } } }`
14031
+ )}${labelsArg}${messageArg}) { values labels } } }`
13612
14032
  }
13613
14033
  ];
13614
14034
  }
@@ -13618,7 +14038,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
13618
14038
  fieldRows: [
13619
14039
  {
13620
14040
  key: "enum",
13621
- description: 'Allowed values. Accepts `["a", "b"]` or `{ "values": [...], "message": "..." }`.'
14041
+ description: 'Allowed values. Use `{ "values": [...], "labels": [...] }` for authored display labels; otherwise each unchanged value is its display fallback.'
13622
14042
  }
13623
14043
  ]
13624
14044
  },
@@ -13628,6 +14048,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
13628
14048
  type EnumMetamodel {
13629
14049
  model: Model!
13630
14050
  values: [String!]!
14051
+ labels: [String!]!
13631
14052
  message: String
13632
14053
  }
13633
14054
 
@@ -13636,7 +14057,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
13636
14057
  }
13637
14058
 
13638
14059
  extend type ModelMutation {
13639
- set_enum(values: [String!]!, message: String): EnumMetamodel
14060
+ set_enum(values: [String!]!, labels: [String!], message: String): EnumMetamodel
13640
14061
  }
13641
14062
  `
13642
14063
  ],
@@ -13645,15 +14066,19 @@ var enumMetamodelPackage = defineMetamodelPackage({
13645
14066
  EnumMetamodel: {
13646
14067
  model: (value) => value.model,
13647
14068
  values: (value) => value.values,
14069
+ labels: (value) => value.labels || [],
13648
14070
  message: (value) => value.message || null
13649
14071
  },
13650
14072
  Model: {
13651
14073
  enum_rule: async (ant) => await run(ant.enum_rule())
13652
14074
  },
13653
14075
  ModelMutation: {
13654
- set_enum: async (ant, { values, message }) => {
13655
- const model = await run(ant.set_enum(values, message));
13656
- return { model, values, message };
14076
+ set_enum: async (ant, { values, labels, message }) => {
14077
+ const resolvedLabels = Array.isArray(labels) && labels.length === values.length ? labels : values;
14078
+ const model = await run(
14079
+ ant.set_enum(values, resolvedLabels, message)
14080
+ );
14081
+ return { model, values, labels: resolvedLabels, message };
13657
14082
  }
13658
14083
  }
13659
14084
  };
@@ -13666,7 +14091,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
13666
14091
  },
13667
14092
  summary: {
13668
14093
  selections: {
13669
- propertyFields: [`enum_rule { values message }`]
14094
+ propertyFields: [`enum_rule { values labels message }`]
13670
14095
  },
13671
14096
  readPropertySummary(rawProperty) {
13672
14097
  const values = Array.isArray(rawProperty.enum_rule?.values) ? rawProperty.enum_rule.values.filter(
@@ -13674,8 +14099,15 @@ var enumMetamodelPackage = defineMetamodelPackage({
13674
14099
  ) : [];
13675
14100
  if (values.length === 0) return { enumRule: null };
13676
14101
  const message = typeof rawProperty.enum_rule?.message === "string" ? rawProperty.enum_rule.message : null;
14102
+ const labels = Array.isArray(rawProperty.enum_rule?.labels) ? rawProperty.enum_rule.labels.filter(
14103
+ (label) => typeof label === "string" && label.length > 0
14104
+ ) : [];
13677
14105
  return {
13678
- enumRule: message ? { values, message } : { values }
14106
+ enumRule: {
14107
+ values,
14108
+ ...labels.length === values.length ? { labels } : {},
14109
+ ...message ? { message } : {}
14110
+ }
13679
14111
  };
13680
14112
  }
13681
14113
  },
@@ -14332,7 +14764,8 @@ function normalizeStateMachines(values) {
14332
14764
  requirements: parseJsonRecord(transition?.requirements) || parseJsonRecord(transition?.requirements_json),
14333
14765
  permission: parseJsonValue(transition?.permission) ?? parseJsonValue(transition?.permission_json),
14334
14766
  risk: transition?.risk === "low" || transition?.risk === "medium" || transition?.risk === "high" ? transition.risk : null,
14335
- expectedOutcome: parseJsonValue(transition?.expectedOutcome) ?? parseJsonValue(transition?.expected_outcome_json)
14767
+ expectedOutcome: parseJsonValue(transition?.expectedOutcome) ?? parseJsonValue(transition?.expected_outcome_json),
14768
+ outcomes: parseJsonRecord(transition?.outcomes) || parseJsonRecord(transition?.outcomes_json)
14336
14769
  })).filter(
14337
14770
  (transition) => transition.name.length > 0 && transition.from.length > 0 && transition.to.length > 0
14338
14771
  );
@@ -14425,6 +14858,11 @@ function transitionMetadataGraphqlArgs(transition) {
14425
14858
  `expected_outcome_json: ${JSON.stringify(JSON.stringify(transition.expectedOutcome))}`
14426
14859
  );
14427
14860
  }
14861
+ if (transition.outcomes) {
14862
+ args.push(
14863
+ `outcomes_json: ${JSON.stringify(JSON.stringify(transition.outcomes))}`
14864
+ );
14865
+ }
14428
14866
  return args.length > 0 ? `, ${args.join(", ")}` : "";
14429
14867
  }
14430
14868
  function buildStateMachineModelMutations(modelPath, machines) {
@@ -14703,7 +15141,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14703
15141
  name: String!
14704
15142
  state_machine: StateMachine!
14705
15143
  add_state(name: String!, is_final: Boolean, label: String, description: String): StateMachineMutation!
14706
- 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!
15144
+ 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!
14707
15145
  activate_transition(name: String!): StateMachineMutation!
14708
15146
  }
14709
15147
 
@@ -14720,6 +15158,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14720
15158
  type StateMachineSnapshotMutation {
14721
15159
  snapshot: StateMachineSnapshot!
14722
15160
  activate_transition(name: String!): StateMachineSnapshotMutation!
15161
+ commit_transition(name: String!, outcome: String!, to: String!, commit_id: String!, source_version: String): StateMachineSnapshotMutation!
14723
15162
  observe_state(state: String!, force: Boolean, source: String): StateMachineSnapshotMutation!
14724
15163
  }
14725
15164
 
@@ -14766,6 +15205,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14766
15205
  permission_json: String
14767
15206
  risk: String
14768
15207
  expected_outcome_json: String
15208
+ outcomes_json: String
14769
15209
  }
14770
15210
 
14771
15211
  type StateMachinePath {
@@ -14776,6 +15216,10 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14776
15216
  type StateMachineTransitionEvent {
14777
15217
  sequence: Int!
14778
15218
  occurred_at: Float!
15219
+ commit_id: String
15220
+ outcome: String
15221
+ source_version: String
15222
+ projected_from_mismatch: String
14779
15223
  transition: StateMachineTransition!
14780
15224
  from: StateMachineState!
14781
15225
  to: StateMachineState!
@@ -14841,7 +15285,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14841
15285
  requirements_json,
14842
15286
  permission_json,
14843
15287
  risk,
14844
- expected_outcome_json
15288
+ expected_outcome_json,
15289
+ outcomes_json
14845
15290
  }) => {
14846
15291
  await run(
14847
15292
  value.target.add_state_machine_transition(
@@ -14857,7 +15302,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14857
15302
  requirementsJson: requirements_json,
14858
15303
  permissionJson: permission_json,
14859
15304
  risk,
14860
- expectedOutcomeJson: expected_outcome_json
15305
+ expectedOutcomeJson: expected_outcome_json,
15306
+ outcomesJson: outcomes_json
14861
15307
  }
14862
15308
  )
14863
15309
  );
@@ -14878,6 +15324,19 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14878
15324
  );
14879
15325
  return value;
14880
15326
  },
15327
+ commit_transition: async (value, { name, outcome, to, commit_id, source_version }) => {
15328
+ await run(
15329
+ value.target.commit_state_machine_transition(
15330
+ value.name,
15331
+ name,
15332
+ outcome,
15333
+ to,
15334
+ commit_id,
15335
+ source_version
15336
+ )
15337
+ );
15338
+ return value;
15339
+ },
14881
15340
  observe_state: async (value, { state, force, source }) => {
14882
15341
  await run(
14883
15342
  value.target.observe_state_machine_state(
@@ -14907,7 +15366,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14907
15366
  requirements_json: (value) => value.requirements_json || null,
14908
15367
  permission_json: (value) => value.permission_json || null,
14909
15368
  risk: (value) => value.risk || null,
14910
- expected_outcome_json: (value) => value.expected_outcome_json || null
15369
+ expected_outcome_json: (value) => value.expected_outcome_json || null,
15370
+ outcomes_json: (value) => value.outcomes_json || null
14911
15371
  },
14912
15372
  StateMachinePath: {
14913
15373
  states: (value) => value.states,
@@ -14916,6 +15376,10 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14916
15376
  StateMachineTransitionEvent: {
14917
15377
  sequence: (value) => value.sequence,
14918
15378
  occurred_at: (value) => value.occurred_at,
15379
+ commit_id: (value) => value.commit_id || null,
15380
+ outcome: (value) => value.outcome || null,
15381
+ source_version: (value) => value.source_version || null,
15382
+ projected_from_mismatch: (value) => value.projected_from_mismatch || null,
14919
15383
  transition: (value) => value.transition,
14920
15384
  from: (value) => value.from,
14921
15385
  to: (value) => value.to
@@ -14989,6 +15453,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14989
15453
  permission_json
14990
15454
  risk
14991
15455
  expected_outcome_json
15456
+ outcomes_json
14992
15457
  }
14993
15458
  }`
14994
15459
  ]
@@ -15618,6 +16083,133 @@ var Environment = class _Environment {
15618
16083
  getAwaitingCount: async () => this.getAwaitingRecordCount()
15619
16084
  };
15620
16085
  }
16086
+ /**
16087
+ * Acknowledge a declared product mutation that already happened outside a
16088
+ * Granular-run effect (for example, in a webhook consumer). These methods
16089
+ * run the declaration's pure projection mapper; they never call its handler.
16090
+ */
16091
+ get commit() {
16092
+ return {
16093
+ effect: async (effect, productResult, options = {}) => this.persistExternalEffect(effect, productResult, options),
16094
+ transition: async (transition, productResult, options = {}) => this.persistExternalTransition(transition, productResult, options),
16095
+ get: async (commitId) => this.controlPlaneRequest(
16096
+ `/control/environments/${this.environmentId}/commits/${encodeURIComponent(
16097
+ commitId
16098
+ )}`
16099
+ ),
16100
+ retry: async (commitId) => this.controlPlaneRequest(
16101
+ `/control/environments/${this.environmentId}/commits/${encodeURIComponent(
16102
+ commitId
16103
+ )}/retry`,
16104
+ { method: "POST" }
16105
+ )
16106
+ };
16107
+ }
16108
+ /** Inspect or retry the agent synchronization of a product snapshot. */
16109
+ get observation() {
16110
+ return {
16111
+ get: async (observationId) => this.controlPlaneRequest(
16112
+ `/control/environments/${this.environmentId}/observations/${encodeURIComponent(
16113
+ observationId
16114
+ )}`
16115
+ ),
16116
+ retry: async (observationId) => this.controlPlaneRequest(
16117
+ `/control/environments/${this.environmentId}/observations/${encodeURIComponent(
16118
+ observationId
16119
+ )}/retry`,
16120
+ { method: "POST" }
16121
+ )
16122
+ };
16123
+ }
16124
+ async persistExternalEffect(effect, productResult, options) {
16125
+ const projection = effect.commit.project(productResult);
16126
+ validateProjectionResult(effect.commit, projection);
16127
+ this.requireExternalIdentity(projection.source.version, options);
16128
+ return this.controlPlaneRequest(
16129
+ `/control/environments/${this.environmentId}/external-commits`,
16130
+ {
16131
+ method: "POST",
16132
+ body: JSON.stringify({
16133
+ kind: "effect",
16134
+ effectKey: computeEffectKey2(effect),
16135
+ ...options.sourceEventId?.trim() ? { sourceEventId: options.sourceEventId.trim() } : {},
16136
+ projection
16137
+ })
16138
+ }
16139
+ );
16140
+ }
16141
+ async persistExternalTransition(transition, productResult, options) {
16142
+ const metadata = getDefinedTransitionMetadata(transition);
16143
+ if (!metadata) {
16144
+ throw new Error(
16145
+ "environment.commit.transition requires a transition returned by defineStateMachine"
16146
+ );
16147
+ }
16148
+ const projection = transition.effect.commit.project(productResult);
16149
+ validateProjectionResult(transition.effect.commit, projection);
16150
+ this.requireExternalIdentity(projection.source.version, options);
16151
+ if (!Object.prototype.hasOwnProperty.call(
16152
+ transition.outcomes,
16153
+ projection.outcome.key
16154
+ )) {
16155
+ throw new Error(
16156
+ `Transition ${metadata.machine}.${metadata.transition} does not declare outcome ${projection.outcome.key}`
16157
+ );
16158
+ }
16159
+ if (!projection.primaryTarget) {
16160
+ throw new Error(
16161
+ "An external transition projection requires primaryTarget to identify the transitioned product record"
16162
+ );
16163
+ }
16164
+ return this.controlPlaneRequest(
16165
+ `/control/environments/${this.environmentId}/external-commits`,
16166
+ {
16167
+ method: "POST",
16168
+ body: JSON.stringify({
16169
+ kind: "transition",
16170
+ effectKey: computeEffectKey2(transition.effect),
16171
+ ...options.sourceEventId?.trim() ? { sourceEventId: options.sourceEventId.trim() } : {},
16172
+ projection,
16173
+ transition: {
16174
+ className: projection.primaryTarget.className,
16175
+ objectId: projection.primaryTarget.id,
16176
+ ...projection.primaryTarget.path ? { objectPath: projection.primaryTarget.path } : {},
16177
+ machine: metadata.machine,
16178
+ transition: metadata.transition,
16179
+ from: transition.from
16180
+ }
16181
+ })
16182
+ }
16183
+ );
16184
+ }
16185
+ requireExternalIdentity(sourceVersion, options) {
16186
+ if (!options.sourceEventId?.trim() && !sourceVersion?.trim()) {
16187
+ throw new Error(
16188
+ "An external commit requires sourceEventId or a source version from its projection mapper"
16189
+ );
16190
+ }
16191
+ }
16192
+ /**
16193
+ * Synchronize a versioned product snapshot without claiming an effect or
16194
+ * lifecycle transition. This records no transition history.
16195
+ */
16196
+ async observe(mapper, productResult) {
16197
+ const declaration = { kind: "effect"};
16198
+ const projection = mapper(productResult);
16199
+ validateProjectionResult(declaration, projection);
16200
+ if (!projection.source.version?.trim()) {
16201
+ throw new Error(
16202
+ "environment.observe requires a monotonic source version or serialized adapter sequence"
16203
+ );
16204
+ }
16205
+ return this.controlPlaneRequest(
16206
+ `/control/environments/${this.environmentId}/observations`,
16207
+ {
16208
+ method: "POST",
16209
+ body: JSON.stringify({ projection })
16210
+ }
16211
+ );
16212
+ }
15621
16213
  /**
15622
16214
  * Mirror product-owned workflow state into Granular without making Granular
15623
16215
  * own the customer application's state machine.
@@ -17024,6 +17616,14 @@ var EnvironmentSession = class extends Session {
17024
17616
  }
17025
17617
  };
17026
17618
  }
17619
+ get mutations() {
17620
+ return {
17621
+ list: (options = {}) => this.sessionDataRequest(
17622
+ "/mutations",
17623
+ options
17624
+ )
17625
+ };
17626
+ }
17027
17627
  get artifacts() {
17028
17628
  return {
17029
17629
  list: (options = {}) => {
@@ -18523,6 +19123,7 @@ var Granular = class _Granular {
18523
19123
  const serialized = {
18524
19124
  effectKey: computeEffectKey2(effect),
18525
19125
  name: effect.name,
19126
+ ...effect.label ? { label: effect.label } : {},
18526
19127
  description: effect.description,
18527
19128
  inputSchema: effect.inputSchema,
18528
19129
  stability: effect.stability || "stable",
@@ -18546,6 +19147,9 @@ var Granular = class _Granular {
18546
19147
  if (effect.metamodels !== void 0) {
18547
19148
  serialized.metamodels = effect.metamodels;
18548
19149
  }
19150
+ if (effect.commit !== void 0) {
19151
+ serialized.commit = { kind: effect.commit.kind };
19152
+ }
18549
19153
  return serialized;
18550
19154
  }
18551
19155
  async publishSandboxEffectCatalog(host) {
@@ -18766,16 +19370,60 @@ var Granular = class _Granular {
18766
19370
  };
18767
19371
  wsClient.registerRpcHandler("effect.invoke", async (params) => {
18768
19372
  const request = params;
18769
- return invokeRegisteredEffect(
19373
+ let commitReceipt;
19374
+ const result = await invokeRegisteredEffect(
18770
19375
  this.getSandboxEffectMap(sandboxId),
18771
19376
  request,
18772
19377
  {
19378
+ commitAcknowledged: (receipt) => {
19379
+ commitReceipt = receipt;
19380
+ },
19381
+ commit: {
19382
+ persist: (commitRequest) => this.request(
19383
+ `/control/environments/${encodeURIComponent(
19384
+ commitRequest.environmentId
19385
+ )}/commits`,
19386
+ {
19387
+ method: "POST",
19388
+ body: JSON.stringify({
19389
+ kind: commitRequest.kind,
19390
+ invocationId: commitRequest.invocationId,
19391
+ idempotencyKey: commitRequest.idempotencyKey,
19392
+ effectKey: commitRequest.effectKey,
19393
+ projection: commitRequest.projection
19394
+ })
19395
+ }
19396
+ ),
19397
+ mappingFailed: (failure) => this.request(
19398
+ `/control/environments/${encodeURIComponent(
19399
+ failure.environmentId
19400
+ )}/commit-invocations/${encodeURIComponent(
19401
+ failure.invocationId
19402
+ )}`,
19403
+ {
19404
+ method: "PATCH",
19405
+ body: JSON.stringify({
19406
+ status: "mapping_failed",
19407
+ error: {
19408
+ code: "commit_projection_mapping_failed",
19409
+ message: failure.message,
19410
+ retryable: false
19411
+ }
19412
+ })
19413
+ }
19414
+ )
19415
+ },
18773
19416
  feedback: {
18774
19417
  invocationId: request.callId,
18775
19418
  publish: (method, publishParams) => wsClient.call(method, publishParams)
18776
19419
  }
18777
19420
  }
18778
19421
  );
19422
+ return {
19423
+ __granularEffectInvocationResult: true,
19424
+ result,
19425
+ ...commitReceipt ? { commit: commitReceipt } : {}
19426
+ };
18779
19427
  });
18780
19428
  wsClient.on("open", () => {
18781
19429
  void this.synchronizeEffectHost(host).catch((error) => {