@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.
package/dist/cli/index.js CHANGED
@@ -14048,8 +14048,12 @@ external_exports.union([
14048
14048
  external_exports.array(external_exports.string()),
14049
14049
  external_exports.object({
14050
14050
  values: external_exports.array(external_exports.string()),
14051
+ labels: external_exports.array(external_exports.string().min(1)).optional(),
14051
14052
  message: external_exports.string().optional()
14052
- }).strict()
14053
+ }).strict().refine(
14054
+ (rule) => !rule.labels || rule.labels.length === rule.values.length,
14055
+ { message: "Enum labels must match enum values one-for-one" }
14056
+ )
14053
14057
  ]);
14054
14058
  external_exports.union([
14055
14059
  external_exports.boolean(),
@@ -14077,7 +14081,7 @@ var StateMachineStateSchema = external_exports.union([
14077
14081
  external_exports.string(),
14078
14082
  external_exports.object({
14079
14083
  name: external_exports.string().min(1),
14080
- label: external_exports.string().optional(),
14084
+ label: external_exports.string().min(1).optional(),
14081
14085
  description: external_exports.string().optional(),
14082
14086
  isFinal: external_exports.boolean().optional()
14083
14087
  }).strict()
@@ -14163,6 +14167,12 @@ var StateTransitionExpectedOutcomeSchema = external_exports.union([
14163
14167
  summary: external_exports.string().optional()
14164
14168
  }).strict()
14165
14169
  ]);
14170
+ var StateTransitionOutcomeSchema = external_exports.object({
14171
+ label: external_exports.string().min(1).optional(),
14172
+ to: external_exports.string().min(1),
14173
+ primary: external_exports.boolean().optional(),
14174
+ disposition: external_exports.enum(["continue", "error"])
14175
+ }).strict();
14166
14176
  var StateMachineTransitionSchema = external_exports.object({
14167
14177
  name: external_exports.string().min(1),
14168
14178
  from: external_exports.string().min(1),
@@ -14174,7 +14184,8 @@ var StateMachineTransitionSchema = external_exports.object({
14174
14184
  requirements: StateTransitionRequirementsSchema.optional(),
14175
14185
  permission: StateTransitionPermissionSchema.optional(),
14176
14186
  risk: external_exports.enum(["low", "medium", "high"]).optional(),
14177
- expectedOutcome: StateTransitionExpectedOutcomeSchema.optional()
14187
+ expectedOutcome: StateTransitionExpectedOutcomeSchema.optional(),
14188
+ outcomes: external_exports.record(external_exports.string().min(1), StateTransitionOutcomeSchema).optional()
14178
14189
  }).strict();
14179
14190
  external_exports.object({
14180
14191
  name: external_exports.string().min(1),
@@ -14866,18 +14877,24 @@ function normalizeEnumInput(enumSpec) {
14866
14877
  (value) => typeof value === "string" && value.length > 0
14867
14878
  );
14868
14879
  if (values.length === 0) return null;
14869
- return config.message ? { values, message: config.message } : { values };
14880
+ const labels = Array.isArray(config.labels) && config.labels.length === values.length ? config.labels : values;
14881
+ return {
14882
+ values,
14883
+ labels,
14884
+ ...config.message ? { message: config.message } : {}
14885
+ };
14870
14886
  }
14871
14887
  function buildEnumFieldMutations(fieldPath, enumSpec) {
14872
14888
  const normalized = normalizeEnumInput(enumSpec);
14873
14889
  if (!normalized) return [];
14874
14890
  const messageArg = normalized.message ? `, message: ${JSON.stringify(normalized.message)}` : "";
14891
+ const labelsArg = normalized.labels ? `, labels: ${JSON.stringify(normalized.labels)}` : "";
14875
14892
  return [
14876
14893
  {
14877
14894
  label: `set enum on ${fieldPath}`,
14878
14895
  query: `mutation { at(path: ${JSON.stringify(fieldPath)}) { set_enum(values: ${JSON.stringify(
14879
14896
  normalized.values
14880
- )}${messageArg}) { values } } }`
14897
+ )}${labelsArg}${messageArg}) { values labels } } }`
14881
14898
  }
14882
14899
  ];
14883
14900
  }
@@ -14887,7 +14904,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
14887
14904
  fieldRows: [
14888
14905
  {
14889
14906
  key: "enum",
14890
- description: 'Allowed values. Accepts `["a", "b"]` or `{ "values": [...], "message": "..." }`.'
14907
+ description: 'Allowed values. Use `{ "values": [...], "labels": [...] }` for authored display labels; otherwise each unchanged value is its display fallback.'
14891
14908
  }
14892
14909
  ]
14893
14910
  },
@@ -14897,6 +14914,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
14897
14914
  type EnumMetamodel {
14898
14915
  model: Model!
14899
14916
  values: [String!]!
14917
+ labels: [String!]!
14900
14918
  message: String
14901
14919
  }
14902
14920
 
@@ -14905,7 +14923,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
14905
14923
  }
14906
14924
 
14907
14925
  extend type ModelMutation {
14908
- set_enum(values: [String!]!, message: String): EnumMetamodel
14926
+ set_enum(values: [String!]!, labels: [String!], message: String): EnumMetamodel
14909
14927
  }
14910
14928
  `
14911
14929
  ],
@@ -14914,15 +14932,19 @@ var enumMetamodelPackage = defineMetamodelPackage({
14914
14932
  EnumMetamodel: {
14915
14933
  model: (value) => value.model,
14916
14934
  values: (value) => value.values,
14935
+ labels: (value) => value.labels || [],
14917
14936
  message: (value) => value.message || null
14918
14937
  },
14919
14938
  Model: {
14920
14939
  enum_rule: async (ant) => await run(ant.enum_rule())
14921
14940
  },
14922
14941
  ModelMutation: {
14923
- set_enum: async (ant, { values, message }) => {
14924
- const model = await run(ant.set_enum(values, message));
14925
- return { model, values, message };
14942
+ set_enum: async (ant, { values, labels, message }) => {
14943
+ const resolvedLabels = Array.isArray(labels) && labels.length === values.length ? labels : values;
14944
+ const model = await run(
14945
+ ant.set_enum(values, resolvedLabels, message)
14946
+ );
14947
+ return { model, values, labels: resolvedLabels, message };
14926
14948
  }
14927
14949
  }
14928
14950
  };
@@ -14935,7 +14957,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
14935
14957
  },
14936
14958
  summary: {
14937
14959
  selections: {
14938
- propertyFields: [`enum_rule { values message }`]
14960
+ propertyFields: [`enum_rule { values labels message }`]
14939
14961
  },
14940
14962
  readPropertySummary(rawProperty) {
14941
14963
  const values = Array.isArray(rawProperty.enum_rule?.values) ? rawProperty.enum_rule.values.filter(
@@ -14943,8 +14965,15 @@ var enumMetamodelPackage = defineMetamodelPackage({
14943
14965
  ) : [];
14944
14966
  if (values.length === 0) return { enumRule: null };
14945
14967
  const message = typeof rawProperty.enum_rule?.message === "string" ? rawProperty.enum_rule.message : null;
14968
+ const labels = Array.isArray(rawProperty.enum_rule?.labels) ? rawProperty.enum_rule.labels.filter(
14969
+ (label) => typeof label === "string" && label.length > 0
14970
+ ) : [];
14946
14971
  return {
14947
- enumRule: message ? { values, message } : { values }
14972
+ enumRule: {
14973
+ values,
14974
+ ...labels.length === values.length ? { labels } : {},
14975
+ ...message ? { message } : {}
14976
+ }
14948
14977
  };
14949
14978
  }
14950
14979
  },
@@ -15507,7 +15536,8 @@ function normalizeStateMachines(values) {
15507
15536
  requirements: parseJsonRecord(transition?.requirements) || parseJsonRecord(transition?.requirements_json),
15508
15537
  permission: parseJsonValue(transition?.permission) ?? parseJsonValue(transition?.permission_json),
15509
15538
  risk: transition?.risk === "low" || transition?.risk === "medium" || transition?.risk === "high" ? transition.risk : null,
15510
- expectedOutcome: parseJsonValue(transition?.expectedOutcome) ?? parseJsonValue(transition?.expected_outcome_json)
15539
+ expectedOutcome: parseJsonValue(transition?.expectedOutcome) ?? parseJsonValue(transition?.expected_outcome_json),
15540
+ outcomes: parseJsonRecord(transition?.outcomes) || parseJsonRecord(transition?.outcomes_json)
15511
15541
  })).filter(
15512
15542
  (transition) => transition.name.length > 0 && transition.from.length > 0 && transition.to.length > 0
15513
15543
  );
@@ -15600,6 +15630,11 @@ function transitionMetadataGraphqlArgs(transition) {
15600
15630
  `expected_outcome_json: ${JSON.stringify(JSON.stringify(transition.expectedOutcome))}`
15601
15631
  );
15602
15632
  }
15633
+ if (transition.outcomes) {
15634
+ args.push(
15635
+ `outcomes_json: ${JSON.stringify(JSON.stringify(transition.outcomes))}`
15636
+ );
15637
+ }
15603
15638
  return args.length > 0 ? `, ${args.join(", ")}` : "";
15604
15639
  }
15605
15640
  function buildStateMachineModelMutations(modelPath, machines) {
@@ -15878,7 +15913,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15878
15913
  name: String!
15879
15914
  state_machine: StateMachine!
15880
15915
  add_state(name: String!, is_final: Boolean, label: String, description: String): StateMachineMutation!
15881
- 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!
15916
+ 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!
15882
15917
  activate_transition(name: String!): StateMachineMutation!
15883
15918
  }
15884
15919
 
@@ -15895,6 +15930,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15895
15930
  type StateMachineSnapshotMutation {
15896
15931
  snapshot: StateMachineSnapshot!
15897
15932
  activate_transition(name: String!): StateMachineSnapshotMutation!
15933
+ commit_transition(name: String!, outcome: String!, to: String!, commit_id: String!, source_version: String): StateMachineSnapshotMutation!
15898
15934
  observe_state(state: String!, force: Boolean, source: String): StateMachineSnapshotMutation!
15899
15935
  }
15900
15936
 
@@ -15941,6 +15977,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15941
15977
  permission_json: String
15942
15978
  risk: String
15943
15979
  expected_outcome_json: String
15980
+ outcomes_json: String
15944
15981
  }
15945
15982
 
15946
15983
  type StateMachinePath {
@@ -15951,6 +15988,10 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15951
15988
  type StateMachineTransitionEvent {
15952
15989
  sequence: Int!
15953
15990
  occurred_at: Float!
15991
+ commit_id: String
15992
+ outcome: String
15993
+ source_version: String
15994
+ projected_from_mismatch: String
15954
15995
  transition: StateMachineTransition!
15955
15996
  from: StateMachineState!
15956
15997
  to: StateMachineState!
@@ -16016,7 +16057,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
16016
16057
  requirements_json,
16017
16058
  permission_json,
16018
16059
  risk,
16019
- expected_outcome_json
16060
+ expected_outcome_json,
16061
+ outcomes_json
16020
16062
  }) => {
16021
16063
  await run(
16022
16064
  value.target.add_state_machine_transition(
@@ -16032,7 +16074,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
16032
16074
  requirementsJson: requirements_json,
16033
16075
  permissionJson: permission_json,
16034
16076
  risk,
16035
- expectedOutcomeJson: expected_outcome_json
16077
+ expectedOutcomeJson: expected_outcome_json,
16078
+ outcomesJson: outcomes_json
16036
16079
  }
16037
16080
  )
16038
16081
  );
@@ -16053,6 +16096,19 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
16053
16096
  );
16054
16097
  return value;
16055
16098
  },
16099
+ commit_transition: async (value, { name, outcome, to, commit_id, source_version }) => {
16100
+ await run(
16101
+ value.target.commit_state_machine_transition(
16102
+ value.name,
16103
+ name,
16104
+ outcome,
16105
+ to,
16106
+ commit_id,
16107
+ source_version
16108
+ )
16109
+ );
16110
+ return value;
16111
+ },
16056
16112
  observe_state: async (value, { state, force, source }) => {
16057
16113
  await run(
16058
16114
  value.target.observe_state_machine_state(
@@ -16082,7 +16138,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
16082
16138
  requirements_json: (value) => value.requirements_json || null,
16083
16139
  permission_json: (value) => value.permission_json || null,
16084
16140
  risk: (value) => value.risk || null,
16085
- expected_outcome_json: (value) => value.expected_outcome_json || null
16141
+ expected_outcome_json: (value) => value.expected_outcome_json || null,
16142
+ outcomes_json: (value) => value.outcomes_json || null
16086
16143
  },
16087
16144
  StateMachinePath: {
16088
16145
  states: (value) => value.states,
@@ -16091,6 +16148,10 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
16091
16148
  StateMachineTransitionEvent: {
16092
16149
  sequence: (value) => value.sequence,
16093
16150
  occurred_at: (value) => value.occurred_at,
16151
+ commit_id: (value) => value.commit_id || null,
16152
+ outcome: (value) => value.outcome || null,
16153
+ source_version: (value) => value.source_version || null,
16154
+ projected_from_mismatch: (value) => value.projected_from_mismatch || null,
16094
16155
  transition: (value) => value.transition,
16095
16156
  from: (value) => value.from,
16096
16157
  to: (value) => value.to
@@ -16164,6 +16225,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
16164
16225
  permission_json
16165
16226
  risk
16166
16227
  expected_outcome_json
16228
+ outcomes_json
16167
16229
  }
16168
16230
  }`
16169
16231
  ]
@@ -22073,6 +22135,11 @@ var Session = class {
22073
22135
  }
22074
22136
  }
22075
22137
  buildDirectedInvocationEffectContext(params, feedbackContext) {
22138
+ const commitUnavailable = async () => {
22139
+ throw new Error(
22140
+ "This directed browser tool invocation has no product commit transport"
22141
+ );
22142
+ };
22076
22143
  return {
22077
22144
  effectClientId: this.clientId,
22078
22145
  sandboxId: params.sandboxId || "",
@@ -22085,6 +22152,10 @@ var Session = class {
22085
22152
  userId: "",
22086
22153
  subjectId: ""
22087
22154
  },
22155
+ commit: {
22156
+ effect: commitUnavailable,
22157
+ transition: commitUnavailable
22158
+ },
22088
22159
  ...feedbackContext ? {
22089
22160
  feedback: feedbackContext.feedback,
22090
22161
  transientFeedback: feedbackContext.transientFeedback
@@ -23614,6 +23685,238 @@ function buildSessionTranscript(input) {
23614
23685
  });
23615
23686
  }
23616
23687
 
23688
+ // src/commit.ts
23689
+ var MAX_PROJECTION_CHANGES = 1e3;
23690
+ var MAX_SAFE_SUMMARY_KEYS = 32;
23691
+ var MAX_SAFE_TEXT_LENGTH = 2e3;
23692
+ var definedTransitionMetadata = /* @__PURE__ */ new WeakMap();
23693
+ function getDefinedTransitionMetadata(transition) {
23694
+ return definedTransitionMetadata.get(transition);
23695
+ }
23696
+ function requireNonEmptyString(value, path7) {
23697
+ if (typeof value !== "string" || value.trim().length === 0) {
23698
+ throw new Error(`${path7} must be a non-empty string`);
23699
+ }
23700
+ return value.trim();
23701
+ }
23702
+ function validateObjectReference(value, path7) {
23703
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
23704
+ throw new Error(`${path7} must be an object reference`);
23705
+ }
23706
+ const reference = value;
23707
+ requireNonEmptyString(reference.className, `${path7}.className`);
23708
+ requireNonEmptyString(reference.id, `${path7}.id`);
23709
+ if (reference.path !== void 0) {
23710
+ requireNonEmptyString(reference.path, `${path7}.path`);
23711
+ }
23712
+ }
23713
+ function validateScalarRecord(value, path7) {
23714
+ if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) {
23715
+ throw new Error(`${path7} must be an object of scalar values`);
23716
+ }
23717
+ for (const [key, item] of Object.entries(value)) {
23718
+ if (item !== null && typeof item !== "string" && typeof item !== "number" && typeof item !== "boolean") {
23719
+ throw new Error(
23720
+ `${path7}.${key} must be a string, finite number, boolean, or null`
23721
+ );
23722
+ }
23723
+ if (typeof item === "number" && !Number.isFinite(item)) {
23724
+ throw new Error(`${path7}.${key} must be finite`);
23725
+ }
23726
+ }
23727
+ }
23728
+ function validateProjectedRecord(value, path7) {
23729
+ validateObjectReference(value, path7);
23730
+ const record = value;
23731
+ if (record.label !== void 0) {
23732
+ if (typeof record.label !== "string") {
23733
+ throw new Error(`${path7}.label must be a string`);
23734
+ }
23735
+ if (record.label.length > MAX_SAFE_TEXT_LENGTH) {
23736
+ throw new Error(
23737
+ `${path7}.label exceeds ${MAX_SAFE_TEXT_LENGTH} characters`
23738
+ );
23739
+ }
23740
+ }
23741
+ validateScalarRecord(record.fields, `${path7}.fields`);
23742
+ }
23743
+ function validateBoundedJson(value, path7, depth = 0) {
23744
+ if (depth > 12) throw new Error(`${path7} is nested too deeply`);
23745
+ if (value === null || typeof value === "boolean") return;
23746
+ if (typeof value === "number") {
23747
+ if (!Number.isFinite(value)) throw new Error(`${path7} must be finite`);
23748
+ return;
23749
+ }
23750
+ if (typeof value === "string") {
23751
+ if (value.length > MAX_SAFE_TEXT_LENGTH) {
23752
+ throw new Error(`${path7} exceeds ${MAX_SAFE_TEXT_LENGTH} characters`);
23753
+ }
23754
+ return;
23755
+ }
23756
+ if (Array.isArray(value)) {
23757
+ if (value.length > MAX_PROJECTION_CHANGES) {
23758
+ throw new Error(`${path7} contains too many values`);
23759
+ }
23760
+ value.forEach(
23761
+ (item, index) => validateBoundedJson(item, `${path7}[${index}]`, depth + 1)
23762
+ );
23763
+ return;
23764
+ }
23765
+ if (!value || typeof value !== "object") {
23766
+ throw new Error(`${path7} contains an unsupported value`);
23767
+ }
23768
+ const entries = Object.entries(value);
23769
+ if (entries.length > 256) throw new Error(`${path7} contains too many keys`);
23770
+ for (const [key, item] of entries) {
23771
+ if (/token|secret|password|authorization|cookie/i.test(key)) {
23772
+ throw new Error(`${path7}.${key} is not allowed in a commit projection`);
23773
+ }
23774
+ validateBoundedJson(item, `${path7}.${key}`, depth + 1);
23775
+ }
23776
+ }
23777
+ function validateProjectionResult(declaration, projection) {
23778
+ if (!projection || typeof projection !== "object") {
23779
+ throw new Error("Projection mapper must return an object");
23780
+ }
23781
+ requireNonEmptyString(
23782
+ projection.source?.reference,
23783
+ "projection.source.reference"
23784
+ );
23785
+ if (projection.source.version !== void 0) {
23786
+ requireNonEmptyString(
23787
+ projection.source.version,
23788
+ "projection.source.version"
23789
+ );
23790
+ }
23791
+ if (!Array.isArray(projection.changes)) {
23792
+ throw new Error("projection.changes must be an array");
23793
+ }
23794
+ if (projection.changes.length > MAX_PROJECTION_CHANGES) {
23795
+ throw new Error(
23796
+ `projection.changes exceeds the ${MAX_PROJECTION_CHANGES} change limit`
23797
+ );
23798
+ }
23799
+ if (projection.primaryTarget) {
23800
+ validateObjectReference(
23801
+ projection.primaryTarget,
23802
+ "projection.primaryTarget"
23803
+ );
23804
+ }
23805
+ if (projection.safeSummary) {
23806
+ const entries = Object.entries(projection.safeSummary);
23807
+ if (entries.length > MAX_SAFE_SUMMARY_KEYS) {
23808
+ throw new Error(
23809
+ `projection.safeSummary exceeds the ${MAX_SAFE_SUMMARY_KEYS} key limit`
23810
+ );
23811
+ }
23812
+ validateBoundedJson(projection.safeSummary, "projection.safeSummary");
23813
+ validateScalarRecord(projection.safeSummary, "projection.safeSummary");
23814
+ }
23815
+ projection.changes.forEach((change, index) => {
23816
+ const changePath = `projection.changes[${index}]`;
23817
+ validateBoundedJson(change, changePath);
23818
+ if (!change || typeof change !== "object" || Array.isArray(change)) {
23819
+ throw new Error(`${changePath} must be an object`);
23820
+ }
23821
+ const rawChange = change;
23822
+ const kind = requireNonEmptyString(rawChange.kind, `${changePath}.kind`);
23823
+ if (kind === "object") {
23824
+ const operation = requireNonEmptyString(
23825
+ rawChange.operation,
23826
+ `${changePath}.operation`
23827
+ );
23828
+ if (operation !== "created" && operation !== "updated" && operation !== "deleted") {
23829
+ throw new Error(
23830
+ `${changePath}.operation must be created, updated, or deleted`
23831
+ );
23832
+ }
23833
+ if (operation === "deleted") {
23834
+ validateObjectReference(rawChange.target, `${changePath}.target`);
23835
+ } else {
23836
+ validateProjectedRecord(rawChange.record, `${changePath}.record`);
23837
+ }
23838
+ } else if (kind === "relationship") {
23839
+ const operation = requireNonEmptyString(
23840
+ rawChange.operation,
23841
+ `${changePath}.operation`
23842
+ );
23843
+ if (operation !== "connected" && operation !== "disconnected") {
23844
+ throw new Error(
23845
+ `${changePath}.operation must be connected or disconnected`
23846
+ );
23847
+ }
23848
+ requireNonEmptyString(
23849
+ rawChange.relationship,
23850
+ `${changePath}.relationship`
23851
+ );
23852
+ validateObjectReference(rawChange.from, `${changePath}.from`);
23853
+ validateObjectReference(rawChange.to, `${changePath}.to`);
23854
+ } else if (kind === "state_observation") {
23855
+ if (rawChange.operation !== void 0) {
23856
+ throw new Error(
23857
+ `${changePath}.operation is not valid for an observation`
23858
+ );
23859
+ }
23860
+ validateObjectReference(rawChange.target, `${changePath}.target`);
23861
+ requireNonEmptyString(rawChange.machine, `${changePath}.machine`);
23862
+ requireNonEmptyString(rawChange.state, `${changePath}.state`);
23863
+ } else {
23864
+ throw new Error(`${changePath}.kind is unsupported: ${kind}`);
23865
+ }
23866
+ });
23867
+ const objectOperations = /* @__PURE__ */ new Map();
23868
+ for (const change of projection.changes) {
23869
+ if (change.kind !== "object") continue;
23870
+ const reference = change.operation === "deleted" ? change.target : change.record;
23871
+ const key = `${reference.className}\0${reference.id}`;
23872
+ const operations = objectOperations.get(key) || {
23873
+ deleted: false,
23874
+ upserted: false
23875
+ };
23876
+ if (change.operation === "deleted") operations.deleted = true;
23877
+ else operations.upserted = true;
23878
+ if (operations.deleted && operations.upserted) {
23879
+ throw new Error(
23880
+ `projection.changes cannot both delete and upsert ${reference.className}/${reference.id}; return only its canonical final state`
23881
+ );
23882
+ }
23883
+ objectOperations.set(key, operations);
23884
+ }
23885
+ const outcome = projection.outcome;
23886
+ if (declaration.kind === "transition") {
23887
+ if (!outcome) {
23888
+ throw new Error("A transition projection must return an outcome");
23889
+ }
23890
+ requireNonEmptyString(outcome.key, "projection.outcome.key");
23891
+ if (outcome.error) {
23892
+ validateBoundedJson(outcome.error, "projection.outcome.error");
23893
+ }
23894
+ } else if (projection.outcome !== void 0) {
23895
+ throw new Error("An effect projection cannot declare a transition outcome");
23896
+ }
23897
+ }
23898
+ function canonicalizeCommitValue(value) {
23899
+ const normalize = (current) => {
23900
+ if (current === null || typeof current === "boolean" || typeof current === "string") {
23901
+ return typeof current === "string" ? current.normalize("NFC") : current;
23902
+ }
23903
+ if (typeof current === "number") {
23904
+ if (!Number.isFinite(current)) {
23905
+ throw new Error("Cannot canonicalize a non-finite number");
23906
+ }
23907
+ return Object.is(current, -0) ? 0 : current;
23908
+ }
23909
+ if (Array.isArray(current)) return current.map(normalize);
23910
+ if (current && typeof current === "object") {
23911
+ return Object.fromEntries(
23912
+ Object.entries(current).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key.normalize("NFC"), normalize(item)])
23913
+ );
23914
+ }
23915
+ throw new Error(`Cannot canonicalize ${typeof current}`);
23916
+ };
23917
+ return JSON.stringify(normalize(value));
23918
+ }
23919
+
23617
23920
  // src/effect-runtime.ts
23618
23921
  function computeEffectKey(effect) {
23619
23922
  const attachedClass = effect.className?.trim();
@@ -23678,6 +23981,45 @@ function resolveInvocationMode(context) {
23678
23981
  }
23679
23982
  return "execute";
23680
23983
  }
23984
+ async function sha256Hex(value) {
23985
+ const digest = await globalThis.crypto.subtle.digest(
23986
+ "SHA-256",
23987
+ new TextEncoder().encode(value)
23988
+ );
23989
+ return Array.from(
23990
+ new Uint8Array(digest),
23991
+ (byte) => byte.toString(16).padStart(2, "0")
23992
+ ).join("");
23993
+ }
23994
+ async function resolveInvocationIdempotencyKey(request) {
23995
+ const supplied = request.context?.idempotencyKey?.trim();
23996
+ if (supplied) return supplied;
23997
+ const invocationId = request.context?.invocationId?.trim();
23998
+ if (!invocationId) {
23999
+ throw new Error(
24000
+ `Committed effect ${request.effectKey} requires an invocation id`
24001
+ );
24002
+ }
24003
+ const digest = await sha256Hex(
24004
+ canonicalizeCommitValue({
24005
+ sandboxId: request.context?.sandboxId || "",
24006
+ environmentId: request.context?.environmentId || "",
24007
+ effectKey: request.effectKey,
24008
+ invocationId,
24009
+ input: request.input
24010
+ })
24011
+ );
24012
+ return `gci_${digest}`;
24013
+ }
24014
+ function createUnavailableCommitContext(message) {
24015
+ const unavailable = async () => {
24016
+ throw new Error(message);
24017
+ };
24018
+ return {
24019
+ effect: unavailable,
24020
+ transition: unavailable
24021
+ };
24022
+ }
23681
24023
  function resolveReverseEffect(effectMap, currentEffect, request, behaviors) {
23682
24024
  const explicitReverseHandler = request.context?.invocation?.reverseHandler?.trim();
23683
24025
  const configuredReverseHandler = behaviors.reverse?.handler?.trim();
@@ -23730,7 +24072,7 @@ function resolveHandlerForMode(effectMap, effect, request) {
23730
24072
  if (mode === "artifactOptions") {
23731
24073
  if (!effect.artifactOptionsHandler) {
23732
24074
  throw new Error(
23733
- `Artifact relationship options are not supported for ${request.effectKey}`
24075
+ `Artifact field options are not supported for ${request.effectKey}`
23734
24076
  );
23735
24077
  }
23736
24078
  return {
@@ -23749,22 +24091,32 @@ function resolveHandlerForMode(effectMap, effect, request) {
23749
24091
  throw new Error(`Dry run is not supported for ${request.effectKey}`);
23750
24092
  }
23751
24093
  if (mode === "reverse") {
24094
+ const sourceEffectKey = request.context?.invocation?.sourceEffectKey?.trim();
24095
+ if (sourceEffectKey && sourceEffectKey !== request.effectKey) {
24096
+ return { effect, mode, handler: effect.handler };
24097
+ }
24098
+ const namedReverseHandler = request.context?.invocation?.reverseHandler?.trim() || behaviors.reverse?.handler?.trim();
24099
+ if (namedReverseHandler) {
24100
+ const reverseEffect = resolveReverseEffect(
24101
+ effectMap,
24102
+ effect,
24103
+ request,
24104
+ behaviors
24105
+ );
24106
+ if (reverseEffect) {
24107
+ return {
24108
+ effect: reverseEffect,
24109
+ mode,
24110
+ handler: reverseEffect.handler
24111
+ };
24112
+ }
24113
+ throw new Error(
24114
+ `Reverse effect ${namedReverseHandler} is not registered for ${request.effectKey}`
24115
+ );
24116
+ }
23752
24117
  if (effect.reverseHandler) {
23753
24118
  return { effect, mode, handler: effect.reverseHandler };
23754
24119
  }
23755
- const reverseEffect = resolveReverseEffect(
23756
- effectMap,
23757
- effect,
23758
- request,
23759
- behaviors
23760
- );
23761
- if (reverseEffect) {
23762
- return {
23763
- effect: reverseEffect,
23764
- mode,
23765
- handler: reverseEffect.reverseHandler || reverseEffect.handler
23766
- };
23767
- }
23768
24120
  throw new Error(
23769
24121
  `Reverse execution is not supported for ${request.effectKey}`
23770
24122
  );
@@ -23865,18 +24217,142 @@ async function invokeRegisteredEffect(effectMap, request, options = {}) {
23865
24217
  throw new Error(`Effect handler not found: ${request.effectKey}`);
23866
24218
  }
23867
24219
  const resolved = resolveHandlerForMode(effectMap, effect, request);
24220
+ const isPreResolvedNamedReverse = resolved.mode === "reverse" && Boolean(request.context?.invocation?.sourceEffectKey?.trim()) && request.context?.invocation?.sourceEffectKey?.trim() !== request.effectKey;
24221
+ const resolvedEffectKey = computeEffectKey(resolved.effect);
24222
+ if (resolved.mode === "reverse" && resolved.effect !== effect && (effect.commit || resolved.effect.commit)) {
24223
+ throw new Error(
24224
+ `Committed named reverse effect ${resolvedEffectKey} must be reserved and dispatched using its own effect key; customer code was not invoked`
24225
+ );
24226
+ }
24227
+ const declaration = resolved.effect.commit;
24228
+ const commitTransport = options.commit;
24229
+ const commitRequired = Boolean(declaration) && (resolved.mode === "execute" || resolved.mode === "reverse");
24230
+ if (commitRequired && !commitTransport) {
24231
+ throw new Error(
24232
+ `Committed effect ${request.effectKey} has no durable commit transport; customer code was not invoked`
24233
+ );
24234
+ }
24235
+ if (commitRequired && declaration?.kind === "transition" && !request.context?.invocation?.transition) {
24236
+ throw new Error(
24237
+ `Transition effect ${request.effectKey} has no resolved transition context; customer code was not invoked`
24238
+ );
24239
+ }
24240
+ const idempotencyKey = commitRequired ? await resolveInvocationIdempotencyKey(request) : request.context?.idempotencyKey;
24241
+ let commitStarted = false;
24242
+ let commitPromise = null;
24243
+ const beginCommit = (requestedKind, productResult) => {
24244
+ if (!declaration || !commitRequired || !commitTransport) {
24245
+ return Promise.reject(
24246
+ new Error(
24247
+ `Effect ${request.effectKey} does not declare an active ${requestedKind} commit`
24248
+ )
24249
+ );
24250
+ }
24251
+ if (declaration.kind !== requestedKind) {
24252
+ return Promise.reject(
24253
+ new Error(
24254
+ `Effect ${request.effectKey} declares ${declaration.kind} commit, not ${requestedKind}`
24255
+ )
24256
+ );
24257
+ }
24258
+ if (commitStarted) {
24259
+ return Promise.reject(
24260
+ new Error(
24261
+ `Effect invocation ${request.context?.invocationId || request.effectKey} already emitted its commit`
24262
+ )
24263
+ );
24264
+ }
24265
+ commitStarted = true;
24266
+ commitPromise = (async () => {
24267
+ let projection;
24268
+ try {
24269
+ projection = declaration.project(productResult);
24270
+ validateProjectionResult(declaration, projection);
24271
+ } catch (error2) {
24272
+ const message = error2 instanceof Error ? error2.message : String(error2);
24273
+ if (commitTransport.mappingFailed) {
24274
+ await commitTransport.mappingFailed({
24275
+ effectKey: request.effectKey,
24276
+ effectName: request.effectName,
24277
+ invocationId: request.context?.invocationId || "",
24278
+ idempotencyKey: idempotencyKey || "",
24279
+ environmentId: request.context?.environmentId || "",
24280
+ message
24281
+ });
24282
+ }
24283
+ throw new Error(
24284
+ `Product mutation may have succeeded, but its commit projection is invalid: ${message}`
24285
+ );
24286
+ }
24287
+ if (requestedKind === "transition") {
24288
+ const transition = request.context?.invocation?.transition;
24289
+ const outcome = projection.outcome;
24290
+ if (!transition || !outcome?.key) {
24291
+ throw new Error(
24292
+ `Transition effect ${request.effectKey} did not resolve an authored outcome`
24293
+ );
24294
+ }
24295
+ if (!Object.prototype.hasOwnProperty.call(
24296
+ transition.outcomes,
24297
+ outcome.key
24298
+ )) {
24299
+ throw new Error(
24300
+ `Transition ${transition.machine}.${transition.transition} does not declare outcome ${outcome.key}`
24301
+ );
24302
+ }
24303
+ }
24304
+ const invocationId = request.context?.invocationId || "";
24305
+ const environmentId = request.context?.environmentId || "";
24306
+ const sandboxId = request.context?.sandboxId || "";
24307
+ if (!invocationId || !environmentId || !sandboxId || !idempotencyKey) {
24308
+ throw new Error(
24309
+ `Committed effect ${request.effectKey} is missing its trusted invocation scope`
24310
+ );
24311
+ }
24312
+ const commitRequest = {
24313
+ kind: requestedKind,
24314
+ effectKey: request.effectKey,
24315
+ effectName: request.effectName,
24316
+ operationLabel: resolved.effect.label || resolved.effect.name,
24317
+ invocationId,
24318
+ idempotencyKey,
24319
+ sandboxId,
24320
+ environmentId,
24321
+ ...request.context?.sessionId ? { sessionId: request.context.sessionId } : {},
24322
+ ...request.context?.jobId ? { jobId: request.context.jobId } : {},
24323
+ ...request.context?.buildId ? { buildId: request.context.buildId } : {},
24324
+ ...request.context?.invocation?.artifactId ? { artifactId: request.context.invocation.artifactId } : {},
24325
+ projection,
24326
+ ...requestedKind === "transition" && request.context?.invocation?.transition ? { transition: request.context.invocation.transition } : {}
24327
+ };
24328
+ const receipt = await commitTransport.persist(commitRequest);
24329
+ await options.commitAcknowledged?.(receipt);
24330
+ return receipt;
24331
+ })();
24332
+ return commitPromise;
24333
+ };
24334
+ const commitContext = commitRequired ? {
24335
+ effect: (productResult) => beginCommit("effect", productResult),
24336
+ transition: (productResult) => beginCommit("transition", productResult)
24337
+ } : createUnavailableCommitContext(
24338
+ `Effect ${request.effectKey} is not executing a declared product commit`
24339
+ );
23868
24340
  const context = {
23869
24341
  ...request.context || {},
24342
+ ...idempotencyKey ? { idempotencyKey } : {},
24343
+ commit: commitContext,
23870
24344
  behaviors: normalizeEffectBehaviors(
23871
24345
  request.context?.behaviors || effect.metamodels || void 0
23872
24346
  ),
23873
24347
  invocation: {
23874
24348
  mode: resolved.mode,
23875
- sourceEffectKey: request.effectKey,
23876
- sourceEffectName: request.effectName,
24349
+ ...commitRequired && declaration ? { commitKind: declaration.kind } : {},
24350
+ sourceEffectKey: isPreResolvedNamedReverse ? request.context?.invocation?.sourceEffectKey || request.effectKey : request.effectKey,
24351
+ sourceEffectName: isPreResolvedNamedReverse ? request.context?.invocation?.sourceEffectName || request.effectName : request.effectName,
23877
24352
  ...request.context?.invocation?.reverseHandler ? { reverseHandler: request.context.invocation.reverseHandler } : {},
23878
24353
  ...request.context?.invocation?.artifactId ? { artifactId: request.context.invocation.artifactId } : {},
23879
- ...request.context?.invocation?.artifactOptions ? { artifactOptions: request.context.invocation.artifactOptions } : {}
24354
+ ...request.context?.invocation?.artifactOptions ? { artifactOptions: request.context.invocation.artifactOptions } : {},
24355
+ ...request.context?.invocation?.transition ? { transition: request.context.invocation.transition } : {}
23880
24356
  }
23881
24357
  };
23882
24358
  const feedbackContext = options.feedback ? createInvocationFeedbackContext(options.feedback) : null;
@@ -23900,6 +24376,16 @@ async function invokeRegisteredEffect(effectMap, request, options = {}) {
23900
24376
  handlerFailed = true;
23901
24377
  handlerError = error2;
23902
24378
  }
24379
+ let commitError;
24380
+ let commitFailed = false;
24381
+ if (commitPromise) {
24382
+ try {
24383
+ await commitPromise;
24384
+ } catch (error2) {
24385
+ commitFailed = true;
24386
+ commitError = error2;
24387
+ }
24388
+ }
23903
24389
  let feedbackError;
23904
24390
  let feedbackFailed = false;
23905
24391
  if (feedbackContext) {
@@ -23913,9 +24399,17 @@ async function invokeRegisteredEffect(effectMap, request, options = {}) {
23913
24399
  if (handlerFailed) {
23914
24400
  throw handlerError;
23915
24401
  }
24402
+ if (commitFailed) {
24403
+ throw commitError;
24404
+ }
23916
24405
  if (feedbackFailed) {
23917
24406
  throw feedbackError;
23918
24407
  }
24408
+ if (commitRequired && !commitStarted) {
24409
+ throw new Error(
24410
+ `Mutating effect ${request.effectKey} returned without acknowledging its product mutation`
24411
+ );
24412
+ }
23919
24413
  return handlerResult;
23920
24414
  }
23921
24415
 
@@ -23984,12 +24478,7 @@ function toRecordSearchResult(className, node) {
23984
24478
  (field) => normalizeGraphPathSegment(field.name) === "real_id" && typeof field.value === "string" && field.value.trim()
23985
24479
  );
23986
24480
  const id = typeof realIdField?.value === "string" ? realIdField.value.trim() : graphPathId;
23987
- const rawLabel = typeof node.label === "string" && node.label.trim() ? node.label : "";
23988
- if (fields.length === 0 && rawLabel && isPlaceholderRecordLabel(rawLabel, graphPathId, path7)) {
23989
- return null;
23990
- }
23991
- const fallbackLabel = displayLabelFromFields(fields);
23992
- const label = rawLabel && !isPlaceholderRecordLabel(rawLabel, id, path7) ? rawLabel : fallbackLabel || rawLabel || id;
24481
+ const label = typeof node.label === "string" && node.label.trim() ? node.label.trim() : path7 || id;
23993
24482
  return {
23994
24483
  path: path7,
23995
24484
  className,
@@ -23999,30 +24488,6 @@ function toRecordSearchResult(className, node) {
23999
24488
  fields
24000
24489
  };
24001
24490
  }
24002
- function isPlaceholderRecordLabel(label, id, path7) {
24003
- const normalizedLabel = normalizeGraphPathSegment(label);
24004
- return normalizedLabel === normalizeGraphPathSegment(id) || normalizedLabel === normalizeGraphPathSegment(path7);
24005
- }
24006
- function displayLabelFromFields(fields) {
24007
- const preferredFieldNames = [
24008
- "name",
24009
- "title",
24010
- "label",
24011
- "display_name",
24012
- "file_name",
24013
- "number",
24014
- "code"
24015
- ];
24016
- for (const preferred of preferredFieldNames) {
24017
- const match = fields.find(
24018
- (field) => normalizeGraphPathSegment(field.name) === preferred && typeof field.value === "string" && field.value.trim()
24019
- );
24020
- if (typeof match?.value === "string") {
24021
- return match.value.trim();
24022
- }
24023
- }
24024
- return null;
24025
- }
24026
24491
  function normalizeRecordSearchText(value) {
24027
24492
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
24028
24493
  }
@@ -24652,6 +25117,133 @@ var Environment = class _Environment {
24652
25117
  getAwaitingCount: async () => this.getAwaitingRecordCount()
24653
25118
  };
24654
25119
  }
25120
+ /**
25121
+ * Acknowledge a declared product mutation that already happened outside a
25122
+ * Granular-run effect (for example, in a webhook consumer). These methods
25123
+ * run the declaration's pure projection mapper; they never call its handler.
25124
+ */
25125
+ get commit() {
25126
+ return {
25127
+ effect: async (effect, productResult, options = {}) => this.persistExternalEffect(effect, productResult, options),
25128
+ transition: async (transition, productResult, options = {}) => this.persistExternalTransition(transition, productResult, options),
25129
+ get: async (commitId) => this.controlPlaneRequest(
25130
+ `/control/environments/${this.environmentId}/commits/${encodeURIComponent(
25131
+ commitId
25132
+ )}`
25133
+ ),
25134
+ retry: async (commitId) => this.controlPlaneRequest(
25135
+ `/control/environments/${this.environmentId}/commits/${encodeURIComponent(
25136
+ commitId
25137
+ )}/retry`,
25138
+ { method: "POST" }
25139
+ )
25140
+ };
25141
+ }
25142
+ /** Inspect or retry the agent synchronization of a product snapshot. */
25143
+ get observation() {
25144
+ return {
25145
+ get: async (observationId) => this.controlPlaneRequest(
25146
+ `/control/environments/${this.environmentId}/observations/${encodeURIComponent(
25147
+ observationId
25148
+ )}`
25149
+ ),
25150
+ retry: async (observationId) => this.controlPlaneRequest(
25151
+ `/control/environments/${this.environmentId}/observations/${encodeURIComponent(
25152
+ observationId
25153
+ )}/retry`,
25154
+ { method: "POST" }
25155
+ )
25156
+ };
25157
+ }
25158
+ async persistExternalEffect(effect, productResult, options) {
25159
+ const projection = effect.commit.project(productResult);
25160
+ validateProjectionResult(effect.commit, projection);
25161
+ this.requireExternalIdentity(projection.source.version, options);
25162
+ return this.controlPlaneRequest(
25163
+ `/control/environments/${this.environmentId}/external-commits`,
25164
+ {
25165
+ method: "POST",
25166
+ body: JSON.stringify({
25167
+ kind: "effect",
25168
+ effectKey: computeEffectKey2(effect),
25169
+ ...options.sourceEventId?.trim() ? { sourceEventId: options.sourceEventId.trim() } : {},
25170
+ projection
25171
+ })
25172
+ }
25173
+ );
25174
+ }
25175
+ async persistExternalTransition(transition, productResult, options) {
25176
+ const metadata = getDefinedTransitionMetadata(transition);
25177
+ if (!metadata) {
25178
+ throw new Error(
25179
+ "environment.commit.transition requires a transition returned by defineStateMachine"
25180
+ );
25181
+ }
25182
+ const projection = transition.effect.commit.project(productResult);
25183
+ validateProjectionResult(transition.effect.commit, projection);
25184
+ this.requireExternalIdentity(projection.source.version, options);
25185
+ if (!Object.prototype.hasOwnProperty.call(
25186
+ transition.outcomes,
25187
+ projection.outcome.key
25188
+ )) {
25189
+ throw new Error(
25190
+ `Transition ${metadata.machine}.${metadata.transition} does not declare outcome ${projection.outcome.key}`
25191
+ );
25192
+ }
25193
+ if (!projection.primaryTarget) {
25194
+ throw new Error(
25195
+ "An external transition projection requires primaryTarget to identify the transitioned product record"
25196
+ );
25197
+ }
25198
+ return this.controlPlaneRequest(
25199
+ `/control/environments/${this.environmentId}/external-commits`,
25200
+ {
25201
+ method: "POST",
25202
+ body: JSON.stringify({
25203
+ kind: "transition",
25204
+ effectKey: computeEffectKey2(transition.effect),
25205
+ ...options.sourceEventId?.trim() ? { sourceEventId: options.sourceEventId.trim() } : {},
25206
+ projection,
25207
+ transition: {
25208
+ className: projection.primaryTarget.className,
25209
+ objectId: projection.primaryTarget.id,
25210
+ ...projection.primaryTarget.path ? { objectPath: projection.primaryTarget.path } : {},
25211
+ machine: metadata.machine,
25212
+ transition: metadata.transition,
25213
+ from: transition.from
25214
+ }
25215
+ })
25216
+ }
25217
+ );
25218
+ }
25219
+ requireExternalIdentity(sourceVersion, options) {
25220
+ if (!options.sourceEventId?.trim() && !sourceVersion?.trim()) {
25221
+ throw new Error(
25222
+ "An external commit requires sourceEventId or a source version from its projection mapper"
25223
+ );
25224
+ }
25225
+ }
25226
+ /**
25227
+ * Synchronize a versioned product snapshot without claiming an effect or
25228
+ * lifecycle transition. This records no transition history.
25229
+ */
25230
+ async observe(mapper, productResult) {
25231
+ const declaration = { kind: "effect"};
25232
+ const projection = mapper(productResult);
25233
+ validateProjectionResult(declaration, projection);
25234
+ if (!projection.source.version?.trim()) {
25235
+ throw new Error(
25236
+ "environment.observe requires a monotonic source version or serialized adapter sequence"
25237
+ );
25238
+ }
25239
+ return this.controlPlaneRequest(
25240
+ `/control/environments/${this.environmentId}/observations`,
25241
+ {
25242
+ method: "POST",
25243
+ body: JSON.stringify({ projection })
25244
+ }
25245
+ );
25246
+ }
24655
25247
  /**
24656
25248
  * Mirror product-owned workflow state into Granular without making Granular
24657
25249
  * own the customer application's state machine.
@@ -26058,6 +26650,14 @@ var EnvironmentSession = class extends Session {
26058
26650
  }
26059
26651
  };
26060
26652
  }
26653
+ get mutations() {
26654
+ return {
26655
+ list: (options = {}) => this.sessionDataRequest(
26656
+ "/mutations",
26657
+ options
26658
+ )
26659
+ };
26660
+ }
26061
26661
  get artifacts() {
26062
26662
  return {
26063
26663
  list: (options = {}) => {
@@ -27557,6 +28157,7 @@ var Granular = class _Granular {
27557
28157
  const serialized = {
27558
28158
  effectKey: computeEffectKey2(effect),
27559
28159
  name: effect.name,
28160
+ ...effect.label ? { label: effect.label } : {},
27560
28161
  description: effect.description,
27561
28162
  inputSchema: effect.inputSchema,
27562
28163
  stability: effect.stability || "stable",
@@ -27580,6 +28181,9 @@ var Granular = class _Granular {
27580
28181
  if (effect.metamodels !== void 0) {
27581
28182
  serialized.metamodels = effect.metamodels;
27582
28183
  }
28184
+ if (effect.commit !== void 0) {
28185
+ serialized.commit = { kind: effect.commit.kind };
28186
+ }
27583
28187
  return serialized;
27584
28188
  }
27585
28189
  async publishSandboxEffectCatalog(host) {
@@ -27800,16 +28404,60 @@ var Granular = class _Granular {
27800
28404
  };
27801
28405
  wsClient.registerRpcHandler("effect.invoke", async (params) => {
27802
28406
  const request = params;
27803
- return invokeRegisteredEffect(
28407
+ let commitReceipt;
28408
+ const result = await invokeRegisteredEffect(
27804
28409
  this.getSandboxEffectMap(sandboxId),
27805
28410
  request,
27806
28411
  {
28412
+ commitAcknowledged: (receipt) => {
28413
+ commitReceipt = receipt;
28414
+ },
28415
+ commit: {
28416
+ persist: (commitRequest) => this.request(
28417
+ `/control/environments/${encodeURIComponent(
28418
+ commitRequest.environmentId
28419
+ )}/commits`,
28420
+ {
28421
+ method: "POST",
28422
+ body: JSON.stringify({
28423
+ kind: commitRequest.kind,
28424
+ invocationId: commitRequest.invocationId,
28425
+ idempotencyKey: commitRequest.idempotencyKey,
28426
+ effectKey: commitRequest.effectKey,
28427
+ projection: commitRequest.projection
28428
+ })
28429
+ }
28430
+ ),
28431
+ mappingFailed: (failure) => this.request(
28432
+ `/control/environments/${encodeURIComponent(
28433
+ failure.environmentId
28434
+ )}/commit-invocations/${encodeURIComponent(
28435
+ failure.invocationId
28436
+ )}`,
28437
+ {
28438
+ method: "PATCH",
28439
+ body: JSON.stringify({
28440
+ status: "mapping_failed",
28441
+ error: {
28442
+ code: "commit_projection_mapping_failed",
28443
+ message: failure.message,
28444
+ retryable: false
28445
+ }
28446
+ })
28447
+ }
28448
+ )
28449
+ },
27807
28450
  feedback: {
27808
28451
  invocationId: request.callId,
27809
28452
  publish: (method, publishParams) => wsClient.call(method, publishParams)
27810
28453
  }
27811
28454
  }
27812
28455
  );
28456
+ return {
28457
+ __granularEffectInvocationResult: true,
28458
+ result,
28459
+ ...commitReceipt ? { commit: commitReceipt } : {}
28460
+ };
27813
28461
  });
27814
28462
  wsClient.on("open", () => {
27815
28463
  void this.synchronizeEffectHost(host).catch((error2) => {