@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/index.js CHANGED
@@ -6555,6 +6555,11 @@ var Session = class {
6555
6555
  }
6556
6556
  }
6557
6557
  buildDirectedInvocationEffectContext(params, feedbackContext) {
6558
+ const commitUnavailable = async () => {
6559
+ throw new Error(
6560
+ "This directed browser tool invocation has no product commit transport"
6561
+ );
6562
+ };
6558
6563
  return {
6559
6564
  effectClientId: this.clientId,
6560
6565
  sandboxId: params.sandboxId || "",
@@ -6567,6 +6572,10 @@ var Session = class {
6567
6572
  userId: "",
6568
6573
  subjectId: ""
6569
6574
  },
6575
+ commit: {
6576
+ effect: commitUnavailable,
6577
+ transition: commitUnavailable
6578
+ },
6570
6579
  ...feedbackContext ? {
6571
6580
  feedback: feedbackContext.feedback,
6572
6581
  transientFeedback: feedbackContext.transientFeedback
@@ -12219,8 +12228,12 @@ external_exports.union([
12219
12228
  external_exports.array(external_exports.string()),
12220
12229
  external_exports.object({
12221
12230
  values: external_exports.array(external_exports.string()),
12231
+ labels: external_exports.array(external_exports.string().min(1)).optional(),
12222
12232
  message: external_exports.string().optional()
12223
- }).strict()
12233
+ }).strict().refine(
12234
+ (rule) => !rule.labels || rule.labels.length === rule.values.length,
12235
+ { message: "Enum labels must match enum values one-for-one" }
12236
+ )
12224
12237
  ]);
12225
12238
  external_exports.union([
12226
12239
  external_exports.boolean(),
@@ -12248,7 +12261,7 @@ var StateMachineStateSchema = external_exports.union([
12248
12261
  external_exports.string(),
12249
12262
  external_exports.object({
12250
12263
  name: external_exports.string().min(1),
12251
- label: external_exports.string().optional(),
12264
+ label: external_exports.string().min(1).optional(),
12252
12265
  description: external_exports.string().optional(),
12253
12266
  isFinal: external_exports.boolean().optional()
12254
12267
  }).strict()
@@ -12334,6 +12347,12 @@ var StateTransitionExpectedOutcomeSchema = external_exports.union([
12334
12347
  summary: external_exports.string().optional()
12335
12348
  }).strict()
12336
12349
  ]);
12350
+ var StateTransitionOutcomeSchema = external_exports.object({
12351
+ label: external_exports.string().min(1).optional(),
12352
+ to: external_exports.string().min(1),
12353
+ primary: external_exports.boolean().optional(),
12354
+ disposition: external_exports.enum(["continue", "error"])
12355
+ }).strict();
12337
12356
  var StateMachineTransitionSchema = external_exports.object({
12338
12357
  name: external_exports.string().min(1),
12339
12358
  from: external_exports.string().min(1),
@@ -12345,7 +12364,8 @@ var StateMachineTransitionSchema = external_exports.object({
12345
12364
  requirements: StateTransitionRequirementsSchema.optional(),
12346
12365
  permission: StateTransitionPermissionSchema.optional(),
12347
12366
  risk: external_exports.enum(["low", "medium", "high"]).optional(),
12348
- expectedOutcome: StateTransitionExpectedOutcomeSchema.optional()
12367
+ expectedOutcome: StateTransitionExpectedOutcomeSchema.optional(),
12368
+ outcomes: external_exports.record(external_exports.string().min(1), StateTransitionOutcomeSchema).optional()
12349
12369
  }).strict();
12350
12370
  external_exports.object({
12351
12371
  name: external_exports.string().min(1),
@@ -13026,6 +13046,289 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
13026
13046
  }
13027
13047
  });
13028
13048
 
13049
+ // src/commit.ts
13050
+ var MAX_PROJECTION_CHANGES = 1e3;
13051
+ var MAX_SAFE_SUMMARY_KEYS = 32;
13052
+ var MAX_SAFE_TEXT_LENGTH = 2e3;
13053
+ var definedTransitionMetadata = /* @__PURE__ */ new WeakMap();
13054
+ function getDefinedTransitionMetadata(transition) {
13055
+ return definedTransitionMetadata.get(transition);
13056
+ }
13057
+ function defineProjection(mapper) {
13058
+ return mapper;
13059
+ }
13060
+ function defineEffect(effect) {
13061
+ return effect;
13062
+ }
13063
+ function defineStateMachine(definition) {
13064
+ const stateNames = new Set(Object.keys(definition.states));
13065
+ for (const [transitionName, transition] of Object.entries(
13066
+ definition.transitions
13067
+ )) {
13068
+ if (!stateNames.has(transition.from)) {
13069
+ throw new Error(
13070
+ `Transition ${transitionName} starts at undeclared state ${transition.from}`
13071
+ );
13072
+ }
13073
+ if (transition.effect.commit?.kind !== "transition") {
13074
+ throw new Error(
13075
+ `Transition ${transitionName} must use a transition-commit effect`
13076
+ );
13077
+ }
13078
+ const outcomes = Object.entries(transition.outcomes);
13079
+ const primary = outcomes.filter(([, outcome]) => outcome.primary === true);
13080
+ if (primary.length !== 1) {
13081
+ throw new Error(
13082
+ `Transition ${transitionName} must declare exactly one primary outcome`
13083
+ );
13084
+ }
13085
+ for (const [outcomeKey, outcome] of outcomes) {
13086
+ if (outcome.to !== "$current" && !stateNames.has(outcome.to)) {
13087
+ throw new Error(
13088
+ `Transition ${transitionName} outcome ${outcomeKey} targets undeclared state ${outcome.to}`
13089
+ );
13090
+ }
13091
+ if (outcome.to === "$current" && outcome.disposition !== "error") {
13092
+ throw new Error(
13093
+ `Transition ${transitionName} outcome ${outcomeKey} may use $current only with error disposition`
13094
+ );
13095
+ }
13096
+ }
13097
+ }
13098
+ for (const [transitionName, transition] of Object.entries(
13099
+ definition.transitions
13100
+ )) {
13101
+ definedTransitionMetadata.set(transition, {
13102
+ machine: definition.name,
13103
+ transition: transitionName
13104
+ });
13105
+ }
13106
+ return definition;
13107
+ }
13108
+ function requireNonEmptyString(value, path) {
13109
+ if (typeof value !== "string" || value.trim().length === 0) {
13110
+ throw new Error(`${path} must be a non-empty string`);
13111
+ }
13112
+ return value.trim();
13113
+ }
13114
+ function validateObjectReference(value, path) {
13115
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
13116
+ throw new Error(`${path} must be an object reference`);
13117
+ }
13118
+ const reference = value;
13119
+ requireNonEmptyString(reference.className, `${path}.className`);
13120
+ requireNonEmptyString(reference.id, `${path}.id`);
13121
+ if (reference.path !== void 0) {
13122
+ requireNonEmptyString(reference.path, `${path}.path`);
13123
+ }
13124
+ }
13125
+ function validateScalarRecord(value, path) {
13126
+ if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) {
13127
+ throw new Error(`${path} must be an object of scalar values`);
13128
+ }
13129
+ for (const [key, item] of Object.entries(value)) {
13130
+ if (item !== null && typeof item !== "string" && typeof item !== "number" && typeof item !== "boolean") {
13131
+ throw new Error(
13132
+ `${path}.${key} must be a string, finite number, boolean, or null`
13133
+ );
13134
+ }
13135
+ if (typeof item === "number" && !Number.isFinite(item)) {
13136
+ throw new Error(`${path}.${key} must be finite`);
13137
+ }
13138
+ }
13139
+ }
13140
+ function validateProjectedRecord(value, path) {
13141
+ validateObjectReference(value, path);
13142
+ const record = value;
13143
+ if (record.label !== void 0) {
13144
+ if (typeof record.label !== "string") {
13145
+ throw new Error(`${path}.label must be a string`);
13146
+ }
13147
+ if (record.label.length > MAX_SAFE_TEXT_LENGTH) {
13148
+ throw new Error(
13149
+ `${path}.label exceeds ${MAX_SAFE_TEXT_LENGTH} characters`
13150
+ );
13151
+ }
13152
+ }
13153
+ validateScalarRecord(record.fields, `${path}.fields`);
13154
+ }
13155
+ function validateBoundedJson(value, path, depth = 0) {
13156
+ if (depth > 12) throw new Error(`${path} is nested too deeply`);
13157
+ if (value === null || typeof value === "boolean") return;
13158
+ if (typeof value === "number") {
13159
+ if (!Number.isFinite(value)) throw new Error(`${path} must be finite`);
13160
+ return;
13161
+ }
13162
+ if (typeof value === "string") {
13163
+ if (value.length > MAX_SAFE_TEXT_LENGTH) {
13164
+ throw new Error(`${path} exceeds ${MAX_SAFE_TEXT_LENGTH} characters`);
13165
+ }
13166
+ return;
13167
+ }
13168
+ if (Array.isArray(value)) {
13169
+ if (value.length > MAX_PROJECTION_CHANGES) {
13170
+ throw new Error(`${path} contains too many values`);
13171
+ }
13172
+ value.forEach(
13173
+ (item, index) => validateBoundedJson(item, `${path}[${index}]`, depth + 1)
13174
+ );
13175
+ return;
13176
+ }
13177
+ if (!value || typeof value !== "object") {
13178
+ throw new Error(`${path} contains an unsupported value`);
13179
+ }
13180
+ const entries = Object.entries(value);
13181
+ if (entries.length > 256) throw new Error(`${path} contains too many keys`);
13182
+ for (const [key, item] of entries) {
13183
+ if (/token|secret|password|authorization|cookie/i.test(key)) {
13184
+ throw new Error(`${path}.${key} is not allowed in a commit projection`);
13185
+ }
13186
+ validateBoundedJson(item, `${path}.${key}`, depth + 1);
13187
+ }
13188
+ }
13189
+ function validateProjectionResult(declaration, projection) {
13190
+ if (!projection || typeof projection !== "object") {
13191
+ throw new Error("Projection mapper must return an object");
13192
+ }
13193
+ requireNonEmptyString(
13194
+ projection.source?.reference,
13195
+ "projection.source.reference"
13196
+ );
13197
+ if (projection.source.version !== void 0) {
13198
+ requireNonEmptyString(
13199
+ projection.source.version,
13200
+ "projection.source.version"
13201
+ );
13202
+ }
13203
+ if (!Array.isArray(projection.changes)) {
13204
+ throw new Error("projection.changes must be an array");
13205
+ }
13206
+ if (projection.changes.length > MAX_PROJECTION_CHANGES) {
13207
+ throw new Error(
13208
+ `projection.changes exceeds the ${MAX_PROJECTION_CHANGES} change limit`
13209
+ );
13210
+ }
13211
+ if (projection.primaryTarget) {
13212
+ validateObjectReference(
13213
+ projection.primaryTarget,
13214
+ "projection.primaryTarget"
13215
+ );
13216
+ }
13217
+ if (projection.safeSummary) {
13218
+ const entries = Object.entries(projection.safeSummary);
13219
+ if (entries.length > MAX_SAFE_SUMMARY_KEYS) {
13220
+ throw new Error(
13221
+ `projection.safeSummary exceeds the ${MAX_SAFE_SUMMARY_KEYS} key limit`
13222
+ );
13223
+ }
13224
+ validateBoundedJson(projection.safeSummary, "projection.safeSummary");
13225
+ validateScalarRecord(projection.safeSummary, "projection.safeSummary");
13226
+ }
13227
+ projection.changes.forEach((change, index) => {
13228
+ const changePath = `projection.changes[${index}]`;
13229
+ validateBoundedJson(change, changePath);
13230
+ if (!change || typeof change !== "object" || Array.isArray(change)) {
13231
+ throw new Error(`${changePath} must be an object`);
13232
+ }
13233
+ const rawChange = change;
13234
+ const kind = requireNonEmptyString(rawChange.kind, `${changePath}.kind`);
13235
+ if (kind === "object") {
13236
+ const operation = requireNonEmptyString(
13237
+ rawChange.operation,
13238
+ `${changePath}.operation`
13239
+ );
13240
+ if (operation !== "created" && operation !== "updated" && operation !== "deleted") {
13241
+ throw new Error(
13242
+ `${changePath}.operation must be created, updated, or deleted`
13243
+ );
13244
+ }
13245
+ if (operation === "deleted") {
13246
+ validateObjectReference(rawChange.target, `${changePath}.target`);
13247
+ } else {
13248
+ validateProjectedRecord(rawChange.record, `${changePath}.record`);
13249
+ }
13250
+ } else if (kind === "relationship") {
13251
+ const operation = requireNonEmptyString(
13252
+ rawChange.operation,
13253
+ `${changePath}.operation`
13254
+ );
13255
+ if (operation !== "connected" && operation !== "disconnected") {
13256
+ throw new Error(
13257
+ `${changePath}.operation must be connected or disconnected`
13258
+ );
13259
+ }
13260
+ requireNonEmptyString(
13261
+ rawChange.relationship,
13262
+ `${changePath}.relationship`
13263
+ );
13264
+ validateObjectReference(rawChange.from, `${changePath}.from`);
13265
+ validateObjectReference(rawChange.to, `${changePath}.to`);
13266
+ } else if (kind === "state_observation") {
13267
+ if (rawChange.operation !== void 0) {
13268
+ throw new Error(
13269
+ `${changePath}.operation is not valid for an observation`
13270
+ );
13271
+ }
13272
+ validateObjectReference(rawChange.target, `${changePath}.target`);
13273
+ requireNonEmptyString(rawChange.machine, `${changePath}.machine`);
13274
+ requireNonEmptyString(rawChange.state, `${changePath}.state`);
13275
+ } else {
13276
+ throw new Error(`${changePath}.kind is unsupported: ${kind}`);
13277
+ }
13278
+ });
13279
+ const objectOperations = /* @__PURE__ */ new Map();
13280
+ for (const change of projection.changes) {
13281
+ if (change.kind !== "object") continue;
13282
+ const reference = change.operation === "deleted" ? change.target : change.record;
13283
+ const key = `${reference.className}\0${reference.id}`;
13284
+ const operations = objectOperations.get(key) || {
13285
+ deleted: false,
13286
+ upserted: false
13287
+ };
13288
+ if (change.operation === "deleted") operations.deleted = true;
13289
+ else operations.upserted = true;
13290
+ if (operations.deleted && operations.upserted) {
13291
+ throw new Error(
13292
+ `projection.changes cannot both delete and upsert ${reference.className}/${reference.id}; return only its canonical final state`
13293
+ );
13294
+ }
13295
+ objectOperations.set(key, operations);
13296
+ }
13297
+ const outcome = projection.outcome;
13298
+ if (declaration.kind === "transition") {
13299
+ if (!outcome) {
13300
+ throw new Error("A transition projection must return an outcome");
13301
+ }
13302
+ requireNonEmptyString(outcome.key, "projection.outcome.key");
13303
+ if (outcome.error) {
13304
+ validateBoundedJson(outcome.error, "projection.outcome.error");
13305
+ }
13306
+ } else if (projection.outcome !== void 0) {
13307
+ throw new Error("An effect projection cannot declare a transition outcome");
13308
+ }
13309
+ }
13310
+ function canonicalizeCommitValue(value) {
13311
+ const normalize = (current) => {
13312
+ if (current === null || typeof current === "boolean" || typeof current === "string") {
13313
+ return typeof current === "string" ? current.normalize("NFC") : current;
13314
+ }
13315
+ if (typeof current === "number") {
13316
+ if (!Number.isFinite(current)) {
13317
+ throw new Error("Cannot canonicalize a non-finite number");
13318
+ }
13319
+ return Object.is(current, -0) ? 0 : current;
13320
+ }
13321
+ if (Array.isArray(current)) return current.map(normalize);
13322
+ if (current && typeof current === "object") {
13323
+ return Object.fromEntries(
13324
+ Object.entries(current).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key.normalize("NFC"), normalize(item)])
13325
+ );
13326
+ }
13327
+ throw new Error(`Cannot canonicalize ${typeof current}`);
13328
+ };
13329
+ return JSON.stringify(normalize(value));
13330
+ }
13331
+
13029
13332
  // src/effect-runtime.ts
13030
13333
  function computeEffectKey(effect) {
13031
13334
  const attachedClass = effect.className?.trim();
@@ -13090,6 +13393,45 @@ function resolveInvocationMode(context) {
13090
13393
  }
13091
13394
  return "execute";
13092
13395
  }
13396
+ async function sha256Hex(value) {
13397
+ const digest = await globalThis.crypto.subtle.digest(
13398
+ "SHA-256",
13399
+ new TextEncoder().encode(value)
13400
+ );
13401
+ return Array.from(
13402
+ new Uint8Array(digest),
13403
+ (byte) => byte.toString(16).padStart(2, "0")
13404
+ ).join("");
13405
+ }
13406
+ async function resolveInvocationIdempotencyKey(request) {
13407
+ const supplied = request.context?.idempotencyKey?.trim();
13408
+ if (supplied) return supplied;
13409
+ const invocationId = request.context?.invocationId?.trim();
13410
+ if (!invocationId) {
13411
+ throw new Error(
13412
+ `Committed effect ${request.effectKey} requires an invocation id`
13413
+ );
13414
+ }
13415
+ const digest = await sha256Hex(
13416
+ canonicalizeCommitValue({
13417
+ sandboxId: request.context?.sandboxId || "",
13418
+ environmentId: request.context?.environmentId || "",
13419
+ effectKey: request.effectKey,
13420
+ invocationId,
13421
+ input: request.input
13422
+ })
13423
+ );
13424
+ return `gci_${digest}`;
13425
+ }
13426
+ function createUnavailableCommitContext(message) {
13427
+ const unavailable = async () => {
13428
+ throw new Error(message);
13429
+ };
13430
+ return {
13431
+ effect: unavailable,
13432
+ transition: unavailable
13433
+ };
13434
+ }
13093
13435
  function resolveReverseEffect(effectMap, currentEffect, request, behaviors) {
13094
13436
  const explicitReverseHandler = request.context?.invocation?.reverseHandler?.trim();
13095
13437
  const configuredReverseHandler = behaviors.reverse?.handler?.trim();
@@ -13142,7 +13484,7 @@ function resolveHandlerForMode(effectMap, effect, request) {
13142
13484
  if (mode === "artifactOptions") {
13143
13485
  if (!effect.artifactOptionsHandler) {
13144
13486
  throw new Error(
13145
- `Artifact relationship options are not supported for ${request.effectKey}`
13487
+ `Artifact field options are not supported for ${request.effectKey}`
13146
13488
  );
13147
13489
  }
13148
13490
  return {
@@ -13161,22 +13503,32 @@ function resolveHandlerForMode(effectMap, effect, request) {
13161
13503
  throw new Error(`Dry run is not supported for ${request.effectKey}`);
13162
13504
  }
13163
13505
  if (mode === "reverse") {
13506
+ const sourceEffectKey = request.context?.invocation?.sourceEffectKey?.trim();
13507
+ if (sourceEffectKey && sourceEffectKey !== request.effectKey) {
13508
+ return { effect, mode, handler: effect.handler };
13509
+ }
13510
+ const namedReverseHandler = request.context?.invocation?.reverseHandler?.trim() || behaviors.reverse?.handler?.trim();
13511
+ if (namedReverseHandler) {
13512
+ const reverseEffect = resolveReverseEffect(
13513
+ effectMap,
13514
+ effect,
13515
+ request,
13516
+ behaviors
13517
+ );
13518
+ if (reverseEffect) {
13519
+ return {
13520
+ effect: reverseEffect,
13521
+ mode,
13522
+ handler: reverseEffect.handler
13523
+ };
13524
+ }
13525
+ throw new Error(
13526
+ `Reverse effect ${namedReverseHandler} is not registered for ${request.effectKey}`
13527
+ );
13528
+ }
13164
13529
  if (effect.reverseHandler) {
13165
13530
  return { effect, mode, handler: effect.reverseHandler };
13166
13531
  }
13167
- const reverseEffect = resolveReverseEffect(
13168
- effectMap,
13169
- effect,
13170
- request,
13171
- behaviors
13172
- );
13173
- if (reverseEffect) {
13174
- return {
13175
- effect: reverseEffect,
13176
- mode,
13177
- handler: reverseEffect.reverseHandler || reverseEffect.handler
13178
- };
13179
- }
13180
13532
  throw new Error(
13181
13533
  `Reverse execution is not supported for ${request.effectKey}`
13182
13534
  );
@@ -13277,18 +13629,142 @@ async function invokeRegisteredEffect(effectMap, request, options = {}) {
13277
13629
  throw new Error(`Effect handler not found: ${request.effectKey}`);
13278
13630
  }
13279
13631
  const resolved = resolveHandlerForMode(effectMap, effect, request);
13632
+ const isPreResolvedNamedReverse = resolved.mode === "reverse" && Boolean(request.context?.invocation?.sourceEffectKey?.trim()) && request.context?.invocation?.sourceEffectKey?.trim() !== request.effectKey;
13633
+ const resolvedEffectKey = computeEffectKey(resolved.effect);
13634
+ if (resolved.mode === "reverse" && resolved.effect !== effect && (effect.commit || resolved.effect.commit)) {
13635
+ throw new Error(
13636
+ `Committed named reverse effect ${resolvedEffectKey} must be reserved and dispatched using its own effect key; customer code was not invoked`
13637
+ );
13638
+ }
13639
+ const declaration = resolved.effect.commit;
13640
+ const commitTransport = options.commit;
13641
+ const commitRequired = Boolean(declaration) && (resolved.mode === "execute" || resolved.mode === "reverse");
13642
+ if (commitRequired && !commitTransport) {
13643
+ throw new Error(
13644
+ `Committed effect ${request.effectKey} has no durable commit transport; customer code was not invoked`
13645
+ );
13646
+ }
13647
+ if (commitRequired && declaration?.kind === "transition" && !request.context?.invocation?.transition) {
13648
+ throw new Error(
13649
+ `Transition effect ${request.effectKey} has no resolved transition context; customer code was not invoked`
13650
+ );
13651
+ }
13652
+ const idempotencyKey = commitRequired ? await resolveInvocationIdempotencyKey(request) : request.context?.idempotencyKey;
13653
+ let commitStarted = false;
13654
+ let commitPromise = null;
13655
+ const beginCommit = (requestedKind, productResult) => {
13656
+ if (!declaration || !commitRequired || !commitTransport) {
13657
+ return Promise.reject(
13658
+ new Error(
13659
+ `Effect ${request.effectKey} does not declare an active ${requestedKind} commit`
13660
+ )
13661
+ );
13662
+ }
13663
+ if (declaration.kind !== requestedKind) {
13664
+ return Promise.reject(
13665
+ new Error(
13666
+ `Effect ${request.effectKey} declares ${declaration.kind} commit, not ${requestedKind}`
13667
+ )
13668
+ );
13669
+ }
13670
+ if (commitStarted) {
13671
+ return Promise.reject(
13672
+ new Error(
13673
+ `Effect invocation ${request.context?.invocationId || request.effectKey} already emitted its commit`
13674
+ )
13675
+ );
13676
+ }
13677
+ commitStarted = true;
13678
+ commitPromise = (async () => {
13679
+ let projection;
13680
+ try {
13681
+ projection = declaration.project(productResult);
13682
+ validateProjectionResult(declaration, projection);
13683
+ } catch (error) {
13684
+ const message = error instanceof Error ? error.message : String(error);
13685
+ if (commitTransport.mappingFailed) {
13686
+ await commitTransport.mappingFailed({
13687
+ effectKey: request.effectKey,
13688
+ effectName: request.effectName,
13689
+ invocationId: request.context?.invocationId || "",
13690
+ idempotencyKey: idempotencyKey || "",
13691
+ environmentId: request.context?.environmentId || "",
13692
+ message
13693
+ });
13694
+ }
13695
+ throw new Error(
13696
+ `Product mutation may have succeeded, but its commit projection is invalid: ${message}`
13697
+ );
13698
+ }
13699
+ if (requestedKind === "transition") {
13700
+ const transition = request.context?.invocation?.transition;
13701
+ const outcome = projection.outcome;
13702
+ if (!transition || !outcome?.key) {
13703
+ throw new Error(
13704
+ `Transition effect ${request.effectKey} did not resolve an authored outcome`
13705
+ );
13706
+ }
13707
+ if (!Object.prototype.hasOwnProperty.call(
13708
+ transition.outcomes,
13709
+ outcome.key
13710
+ )) {
13711
+ throw new Error(
13712
+ `Transition ${transition.machine}.${transition.transition} does not declare outcome ${outcome.key}`
13713
+ );
13714
+ }
13715
+ }
13716
+ const invocationId = request.context?.invocationId || "";
13717
+ const environmentId = request.context?.environmentId || "";
13718
+ const sandboxId = request.context?.sandboxId || "";
13719
+ if (!invocationId || !environmentId || !sandboxId || !idempotencyKey) {
13720
+ throw new Error(
13721
+ `Committed effect ${request.effectKey} is missing its trusted invocation scope`
13722
+ );
13723
+ }
13724
+ const commitRequest = {
13725
+ kind: requestedKind,
13726
+ effectKey: request.effectKey,
13727
+ effectName: request.effectName,
13728
+ operationLabel: resolved.effect.label || resolved.effect.name,
13729
+ invocationId,
13730
+ idempotencyKey,
13731
+ sandboxId,
13732
+ environmentId,
13733
+ ...request.context?.sessionId ? { sessionId: request.context.sessionId } : {},
13734
+ ...request.context?.jobId ? { jobId: request.context.jobId } : {},
13735
+ ...request.context?.buildId ? { buildId: request.context.buildId } : {},
13736
+ ...request.context?.invocation?.artifactId ? { artifactId: request.context.invocation.artifactId } : {},
13737
+ projection,
13738
+ ...requestedKind === "transition" && request.context?.invocation?.transition ? { transition: request.context.invocation.transition } : {}
13739
+ };
13740
+ const receipt = await commitTransport.persist(commitRequest);
13741
+ await options.commitAcknowledged?.(receipt);
13742
+ return receipt;
13743
+ })();
13744
+ return commitPromise;
13745
+ };
13746
+ const commitContext = commitRequired ? {
13747
+ effect: (productResult) => beginCommit("effect", productResult),
13748
+ transition: (productResult) => beginCommit("transition", productResult)
13749
+ } : createUnavailableCommitContext(
13750
+ `Effect ${request.effectKey} is not executing a declared product commit`
13751
+ );
13280
13752
  const context = {
13281
13753
  ...request.context || {},
13754
+ ...idempotencyKey ? { idempotencyKey } : {},
13755
+ commit: commitContext,
13282
13756
  behaviors: normalizeEffectBehaviors(
13283
13757
  request.context?.behaviors || effect.metamodels || void 0
13284
13758
  ),
13285
13759
  invocation: {
13286
13760
  mode: resolved.mode,
13287
- sourceEffectKey: request.effectKey,
13288
- sourceEffectName: request.effectName,
13761
+ ...commitRequired && declaration ? { commitKind: declaration.kind } : {},
13762
+ sourceEffectKey: isPreResolvedNamedReverse ? request.context?.invocation?.sourceEffectKey || request.effectKey : request.effectKey,
13763
+ sourceEffectName: isPreResolvedNamedReverse ? request.context?.invocation?.sourceEffectName || request.effectName : request.effectName,
13289
13764
  ...request.context?.invocation?.reverseHandler ? { reverseHandler: request.context.invocation.reverseHandler } : {},
13290
13765
  ...request.context?.invocation?.artifactId ? { artifactId: request.context.invocation.artifactId } : {},
13291
- ...request.context?.invocation?.artifactOptions ? { artifactOptions: request.context.invocation.artifactOptions } : {}
13766
+ ...request.context?.invocation?.artifactOptions ? { artifactOptions: request.context.invocation.artifactOptions } : {},
13767
+ ...request.context?.invocation?.transition ? { transition: request.context.invocation.transition } : {}
13292
13768
  }
13293
13769
  };
13294
13770
  const feedbackContext = options.feedback ? createInvocationFeedbackContext(options.feedback) : null;
@@ -13312,6 +13788,16 @@ async function invokeRegisteredEffect(effectMap, request, options = {}) {
13312
13788
  handlerFailed = true;
13313
13789
  handlerError = error;
13314
13790
  }
13791
+ let commitError;
13792
+ let commitFailed = false;
13793
+ if (commitPromise) {
13794
+ try {
13795
+ await commitPromise;
13796
+ } catch (error) {
13797
+ commitFailed = true;
13798
+ commitError = error;
13799
+ }
13800
+ }
13315
13801
  let feedbackError;
13316
13802
  let feedbackFailed = false;
13317
13803
  if (feedbackContext) {
@@ -13325,9 +13811,17 @@ async function invokeRegisteredEffect(effectMap, request, options = {}) {
13325
13811
  if (handlerFailed) {
13326
13812
  throw handlerError;
13327
13813
  }
13814
+ if (commitFailed) {
13815
+ throw commitError;
13816
+ }
13328
13817
  if (feedbackFailed) {
13329
13818
  throw feedbackError;
13330
13819
  }
13820
+ if (commitRequired && !commitStarted) {
13821
+ throw new Error(
13822
+ `Mutating effect ${request.effectKey} returned without acknowledging its product mutation`
13823
+ );
13824
+ }
13331
13825
  return handlerResult;
13332
13826
  }
13333
13827
 
@@ -13396,12 +13890,7 @@ function toRecordSearchResult(className, node) {
13396
13890
  (field) => normalizeGraphPathSegment(field.name) === "real_id" && typeof field.value === "string" && field.value.trim()
13397
13891
  );
13398
13892
  const id = typeof realIdField?.value === "string" ? realIdField.value.trim() : graphPathId;
13399
- const rawLabel = typeof node.label === "string" && node.label.trim() ? node.label : "";
13400
- if (fields.length === 0 && rawLabel && isPlaceholderRecordLabel(rawLabel, graphPathId, path)) {
13401
- return null;
13402
- }
13403
- const fallbackLabel = displayLabelFromFields(fields);
13404
- const label = rawLabel && !isPlaceholderRecordLabel(rawLabel, id, path) ? rawLabel : fallbackLabel || rawLabel || id;
13893
+ const label = typeof node.label === "string" && node.label.trim() ? node.label.trim() : path || id;
13405
13894
  return {
13406
13895
  path,
13407
13896
  className,
@@ -13411,30 +13900,6 @@ function toRecordSearchResult(className, node) {
13411
13900
  fields
13412
13901
  };
13413
13902
  }
13414
- function isPlaceholderRecordLabel(label, id, path) {
13415
- const normalizedLabel = normalizeGraphPathSegment(label);
13416
- return normalizedLabel === normalizeGraphPathSegment(id) || normalizedLabel === normalizeGraphPathSegment(path);
13417
- }
13418
- function displayLabelFromFields(fields) {
13419
- const preferredFieldNames = [
13420
- "name",
13421
- "title",
13422
- "label",
13423
- "display_name",
13424
- "file_name",
13425
- "number",
13426
- "code"
13427
- ];
13428
- for (const preferred of preferredFieldNames) {
13429
- const match = fields.find(
13430
- (field) => normalizeGraphPathSegment(field.name) === preferred && typeof field.value === "string" && field.value.trim()
13431
- );
13432
- if (typeof match?.value === "string") {
13433
- return match.value.trim();
13434
- }
13435
- }
13436
- return null;
13437
- }
13438
13903
  function normalizeRecordSearchText(value) {
13439
13904
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
13440
13905
  }
@@ -13644,18 +14109,24 @@ function normalizeEnumInput(enumSpec) {
13644
14109
  (value) => typeof value === "string" && value.length > 0
13645
14110
  );
13646
14111
  if (values.length === 0) return null;
13647
- return config.message ? { values, message: config.message } : { values };
14112
+ const labels = Array.isArray(config.labels) && config.labels.length === values.length ? config.labels : values;
14113
+ return {
14114
+ values,
14115
+ labels,
14116
+ ...config.message ? { message: config.message } : {}
14117
+ };
13648
14118
  }
13649
14119
  function buildEnumFieldMutations(fieldPath, enumSpec) {
13650
14120
  const normalized = normalizeEnumInput(enumSpec);
13651
14121
  if (!normalized) return [];
13652
14122
  const messageArg = normalized.message ? `, message: ${JSON.stringify(normalized.message)}` : "";
14123
+ const labelsArg = normalized.labels ? `, labels: ${JSON.stringify(normalized.labels)}` : "";
13653
14124
  return [
13654
14125
  {
13655
14126
  label: `set enum on ${fieldPath}`,
13656
14127
  query: `mutation { at(path: ${JSON.stringify(fieldPath)}) { set_enum(values: ${JSON.stringify(
13657
14128
  normalized.values
13658
- )}${messageArg}) { values } } }`
14129
+ )}${labelsArg}${messageArg}) { values labels } } }`
13659
14130
  }
13660
14131
  ];
13661
14132
  }
@@ -13665,7 +14136,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
13665
14136
  fieldRows: [
13666
14137
  {
13667
14138
  key: "enum",
13668
- description: 'Allowed values. Accepts `["a", "b"]` or `{ "values": [...], "message": "..." }`.'
14139
+ description: 'Allowed values. Use `{ "values": [...], "labels": [...] }` for authored display labels; otherwise each unchanged value is its display fallback.'
13669
14140
  }
13670
14141
  ]
13671
14142
  },
@@ -13675,6 +14146,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
13675
14146
  type EnumMetamodel {
13676
14147
  model: Model!
13677
14148
  values: [String!]!
14149
+ labels: [String!]!
13678
14150
  message: String
13679
14151
  }
13680
14152
 
@@ -13683,7 +14155,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
13683
14155
  }
13684
14156
 
13685
14157
  extend type ModelMutation {
13686
- set_enum(values: [String!]!, message: String): EnumMetamodel
14158
+ set_enum(values: [String!]!, labels: [String!], message: String): EnumMetamodel
13687
14159
  }
13688
14160
  `
13689
14161
  ],
@@ -13692,15 +14164,19 @@ var enumMetamodelPackage = defineMetamodelPackage({
13692
14164
  EnumMetamodel: {
13693
14165
  model: (value) => value.model,
13694
14166
  values: (value) => value.values,
14167
+ labels: (value) => value.labels || [],
13695
14168
  message: (value) => value.message || null
13696
14169
  },
13697
14170
  Model: {
13698
14171
  enum_rule: async (ant) => await run(ant.enum_rule())
13699
14172
  },
13700
14173
  ModelMutation: {
13701
- set_enum: async (ant, { values, message }) => {
13702
- const model = await run(ant.set_enum(values, message));
13703
- return { model, values, message };
14174
+ set_enum: async (ant, { values, labels, message }) => {
14175
+ const resolvedLabels = Array.isArray(labels) && labels.length === values.length ? labels : values;
14176
+ const model = await run(
14177
+ ant.set_enum(values, resolvedLabels, message)
14178
+ );
14179
+ return { model, values, labels: resolvedLabels, message };
13704
14180
  }
13705
14181
  }
13706
14182
  };
@@ -13713,7 +14189,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
13713
14189
  },
13714
14190
  summary: {
13715
14191
  selections: {
13716
- propertyFields: [`enum_rule { values message }`]
14192
+ propertyFields: [`enum_rule { values labels message }`]
13717
14193
  },
13718
14194
  readPropertySummary(rawProperty) {
13719
14195
  const values = Array.isArray(rawProperty.enum_rule?.values) ? rawProperty.enum_rule.values.filter(
@@ -13721,8 +14197,15 @@ var enumMetamodelPackage = defineMetamodelPackage({
13721
14197
  ) : [];
13722
14198
  if (values.length === 0) return { enumRule: null };
13723
14199
  const message = typeof rawProperty.enum_rule?.message === "string" ? rawProperty.enum_rule.message : null;
14200
+ const labels = Array.isArray(rawProperty.enum_rule?.labels) ? rawProperty.enum_rule.labels.filter(
14201
+ (label) => typeof label === "string" && label.length > 0
14202
+ ) : [];
13724
14203
  return {
13725
- enumRule: message ? { values, message } : { values }
14204
+ enumRule: {
14205
+ values,
14206
+ ...labels.length === values.length ? { labels } : {},
14207
+ ...message ? { message } : {}
14208
+ }
13726
14209
  };
13727
14210
  }
13728
14211
  },
@@ -14379,7 +14862,8 @@ function normalizeStateMachines(values) {
14379
14862
  requirements: parseJsonRecord(transition?.requirements) || parseJsonRecord(transition?.requirements_json),
14380
14863
  permission: parseJsonValue(transition?.permission) ?? parseJsonValue(transition?.permission_json),
14381
14864
  risk: transition?.risk === "low" || transition?.risk === "medium" || transition?.risk === "high" ? transition.risk : null,
14382
- expectedOutcome: parseJsonValue(transition?.expectedOutcome) ?? parseJsonValue(transition?.expected_outcome_json)
14865
+ expectedOutcome: parseJsonValue(transition?.expectedOutcome) ?? parseJsonValue(transition?.expected_outcome_json),
14866
+ outcomes: parseJsonRecord(transition?.outcomes) || parseJsonRecord(transition?.outcomes_json)
14383
14867
  })).filter(
14384
14868
  (transition) => transition.name.length > 0 && transition.from.length > 0 && transition.to.length > 0
14385
14869
  );
@@ -14472,6 +14956,11 @@ function transitionMetadataGraphqlArgs(transition) {
14472
14956
  `expected_outcome_json: ${JSON.stringify(JSON.stringify(transition.expectedOutcome))}`
14473
14957
  );
14474
14958
  }
14959
+ if (transition.outcomes) {
14960
+ args.push(
14961
+ `outcomes_json: ${JSON.stringify(JSON.stringify(transition.outcomes))}`
14962
+ );
14963
+ }
14475
14964
  return args.length > 0 ? `, ${args.join(", ")}` : "";
14476
14965
  }
14477
14966
  function buildStateMachineModelMutations(modelPath, machines) {
@@ -14534,7 +15023,7 @@ function buildMachineTypes(classSummary, machine) {
14534
15023
  ];
14535
15024
  }
14536
15025
  function buildMachineMethods(classSummary, machine) {
14537
- const stateName = stateTypeName(classSummary.name, machine.name);
15026
+ const stateName2 = stateTypeName(classSummary.name, machine.name);
14538
15027
  const transitionName = transitionTypeName(classSummary.name, machine.name);
14539
15028
  pathTypeName(classSummary.name, machine.name);
14540
15029
  const docsPrefix = `${classSummary.name}.${machine.name}`;
@@ -14544,12 +15033,12 @@ function buildMachineMethods(classSummary, machine) {
14544
15033
  docs: [`Get the current ${docsPrefix} state.`],
14545
15034
  static: false,
14546
15035
  params: [],
14547
- returnType: `Promise<${stateName} | null>`,
15036
+ returnType: `Promise<${stateName2} | null>`,
14548
15037
  runtime: {
14549
15038
  kind: "state_machine",
14550
15039
  machineName: machine.name,
14551
15040
  className: classSummary.name,
14552
- stateTypeName: stateName,
15041
+ stateTypeName: stateName2,
14553
15042
  transitionTypeName: transitionName,
14554
15043
  operation: "get_current"
14555
15044
  }
@@ -14560,13 +15049,13 @@ function buildMachineMethods(classSummary, machine) {
14560
15049
  `Reach a ${docsPrefix} state through the shortest allowed transition path.`
14561
15050
  ],
14562
15051
  static: false,
14563
- params: [{ name: "target", type: stateName }],
15052
+ params: [{ name: "target", type: stateName2 }],
14564
15053
  returnType: `Promise<${toPascalCase2(classSummary.name)}>`,
14565
15054
  runtime: {
14566
15055
  kind: "state_machine",
14567
15056
  machineName: machine.name,
14568
15057
  className: classSummary.name,
14569
- stateTypeName: stateName,
15058
+ stateTypeName: stateName2,
14570
15059
  transitionTypeName: transitionName,
14571
15060
  operation: "reach"
14572
15061
  }
@@ -14581,7 +15070,7 @@ function buildMachineMethods(classSummary, machine) {
14581
15070
  kind: "state_machine",
14582
15071
  machineName: machine.name,
14583
15072
  className: classSummary.name,
14584
- stateTypeName: stateName,
15073
+ stateTypeName: stateName2,
14585
15074
  transitionTypeName: transitionName,
14586
15075
  operation: "list_transitions"
14587
15076
  }
@@ -14591,12 +15080,12 @@ function buildMachineMethods(classSummary, machine) {
14591
15080
  docs: [`List reachable states for ${docsPrefix} from the current state.`],
14592
15081
  static: false,
14593
15082
  params: [],
14594
- returnType: `Promise<${stateName}[]>`,
15083
+ returnType: `Promise<${stateName2}[]>`,
14595
15084
  runtime: {
14596
15085
  kind: "state_machine",
14597
15086
  machineName: machine.name,
14598
15087
  className: classSummary.name,
14599
- stateTypeName: stateName,
15088
+ stateTypeName: stateName2,
14600
15089
  transitionTypeName: transitionName,
14601
15090
  operation: "list_reachable_states"
14602
15091
  }
@@ -14611,7 +15100,7 @@ function buildMachineMethods(classSummary, machine) {
14611
15100
  kind: "state_machine",
14612
15101
  machineName: machine.name,
14613
15102
  className: classSummary.name,
14614
- stateTypeName: stateName,
15103
+ stateTypeName: stateName2,
14615
15104
  transitionTypeName: transitionName,
14616
15105
  operation: "is_final"
14617
15106
  }
@@ -14622,13 +15111,13 @@ function buildMachineMethods(classSummary, machine) {
14622
15111
  `List shortest transition paths from the current ${docsPrefix} state to a target state.`
14623
15112
  ],
14624
15113
  static: false,
14625
- params: [{ name: "target", type: stateName }],
14626
- returnType: `Promise<Array<{ states: ${stateName}[]; transitions: ${transitionName}[] }>>`,
15114
+ params: [{ name: "target", type: stateName2 }],
15115
+ returnType: `Promise<Array<{ states: ${stateName2}[]; transitions: ${transitionName}[] }>>`,
14627
15116
  runtime: {
14628
15117
  kind: "state_machine",
14629
15118
  machineName: machine.name,
14630
15119
  className: classSummary.name,
14631
- stateTypeName: stateName,
15120
+ stateTypeName: stateName2,
14632
15121
  transitionTypeName: transitionName,
14633
15122
  operation: "paths_to"
14634
15123
  }
@@ -14652,7 +15141,7 @@ function buildMachineMethods(classSummary, machine) {
14652
15141
  kind: "state_machine",
14653
15142
  machineName: machine.name,
14654
15143
  className: classSummary.name,
14655
- stateTypeName: stateName,
15144
+ stateTypeName: stateName2,
14656
15145
  transitionTypeName: transitionName,
14657
15146
  operation: "reach",
14658
15147
  targetState: stateNameValue,
@@ -14671,7 +15160,7 @@ function buildMachineMethods(classSummary, machine) {
14671
15160
  kind: "state_machine",
14672
15161
  machineName: machine.name,
14673
15162
  className: classSummary.name,
14674
- stateTypeName: stateName,
15163
+ stateTypeName: stateName2,
14675
15164
  transitionTypeName: transitionName,
14676
15165
  operation: "prepare_reach",
14677
15166
  targetState: stateNameValue,
@@ -14684,7 +15173,7 @@ function buildMachineMethods(classSummary, machine) {
14684
15173
  kind: "state_machine",
14685
15174
  machineName: machine.name,
14686
15175
  className: classSummary.name,
14687
- stateTypeName: stateName,
15176
+ stateTypeName: stateName2,
14688
15177
  transitionTypeName: transitionName,
14689
15178
  operation: "prepare_create_reach",
14690
15179
  targetState: stateNameValue,
@@ -14750,7 +15239,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14750
15239
  name: String!
14751
15240
  state_machine: StateMachine!
14752
15241
  add_state(name: String!, is_final: Boolean, label: String, description: String): StateMachineMutation!
14753
- 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!
15242
+ 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!
14754
15243
  activate_transition(name: String!): StateMachineMutation!
14755
15244
  }
14756
15245
 
@@ -14767,6 +15256,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14767
15256
  type StateMachineSnapshotMutation {
14768
15257
  snapshot: StateMachineSnapshot!
14769
15258
  activate_transition(name: String!): StateMachineSnapshotMutation!
15259
+ commit_transition(name: String!, outcome: String!, to: String!, commit_id: String!, source_version: String): StateMachineSnapshotMutation!
14770
15260
  observe_state(state: String!, force: Boolean, source: String): StateMachineSnapshotMutation!
14771
15261
  }
14772
15262
 
@@ -14813,6 +15303,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14813
15303
  permission_json: String
14814
15304
  risk: String
14815
15305
  expected_outcome_json: String
15306
+ outcomes_json: String
14816
15307
  }
14817
15308
 
14818
15309
  type StateMachinePath {
@@ -14823,6 +15314,10 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14823
15314
  type StateMachineTransitionEvent {
14824
15315
  sequence: Int!
14825
15316
  occurred_at: Float!
15317
+ commit_id: String
15318
+ outcome: String
15319
+ source_version: String
15320
+ projected_from_mismatch: String
14826
15321
  transition: StateMachineTransition!
14827
15322
  from: StateMachineState!
14828
15323
  to: StateMachineState!
@@ -14888,7 +15383,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14888
15383
  requirements_json,
14889
15384
  permission_json,
14890
15385
  risk,
14891
- expected_outcome_json
15386
+ expected_outcome_json,
15387
+ outcomes_json
14892
15388
  }) => {
14893
15389
  await run(
14894
15390
  value.target.add_state_machine_transition(
@@ -14904,7 +15400,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14904
15400
  requirementsJson: requirements_json,
14905
15401
  permissionJson: permission_json,
14906
15402
  risk,
14907
- expectedOutcomeJson: expected_outcome_json
15403
+ expectedOutcomeJson: expected_outcome_json,
15404
+ outcomesJson: outcomes_json
14908
15405
  }
14909
15406
  )
14910
15407
  );
@@ -14925,6 +15422,19 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14925
15422
  );
14926
15423
  return value;
14927
15424
  },
15425
+ commit_transition: async (value, { name, outcome, to, commit_id, source_version }) => {
15426
+ await run(
15427
+ value.target.commit_state_machine_transition(
15428
+ value.name,
15429
+ name,
15430
+ outcome,
15431
+ to,
15432
+ commit_id,
15433
+ source_version
15434
+ )
15435
+ );
15436
+ return value;
15437
+ },
14928
15438
  observe_state: async (value, { state, force, source }) => {
14929
15439
  await run(
14930
15440
  value.target.observe_state_machine_state(
@@ -14954,7 +15464,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14954
15464
  requirements_json: (value) => value.requirements_json || null,
14955
15465
  permission_json: (value) => value.permission_json || null,
14956
15466
  risk: (value) => value.risk || null,
14957
- expected_outcome_json: (value) => value.expected_outcome_json || null
15467
+ expected_outcome_json: (value) => value.expected_outcome_json || null,
15468
+ outcomes_json: (value) => value.outcomes_json || null
14958
15469
  },
14959
15470
  StateMachinePath: {
14960
15471
  states: (value) => value.states,
@@ -14963,6 +15474,10 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
14963
15474
  StateMachineTransitionEvent: {
14964
15475
  sequence: (value) => value.sequence,
14965
15476
  occurred_at: (value) => value.occurred_at,
15477
+ commit_id: (value) => value.commit_id || null,
15478
+ outcome: (value) => value.outcome || null,
15479
+ source_version: (value) => value.source_version || null,
15480
+ projected_from_mismatch: (value) => value.projected_from_mismatch || null,
14966
15481
  transition: (value) => value.transition,
14967
15482
  from: (value) => value.from,
14968
15483
  to: (value) => value.to
@@ -15036,6 +15551,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15036
15551
  permission_json
15037
15552
  risk
15038
15553
  expected_outcome_json
15554
+ outcomes_json
15039
15555
  }
15040
15556
  }`
15041
15557
  ]
@@ -15763,6 +16279,133 @@ var Environment = class _Environment {
15763
16279
  getAwaitingCount: async () => this.getAwaitingRecordCount()
15764
16280
  };
15765
16281
  }
16282
+ /**
16283
+ * Acknowledge a declared product mutation that already happened outside a
16284
+ * Granular-run effect (for example, in a webhook consumer). These methods
16285
+ * run the declaration's pure projection mapper; they never call its handler.
16286
+ */
16287
+ get commit() {
16288
+ return {
16289
+ effect: async (effect, productResult, options = {}) => this.persistExternalEffect(effect, productResult, options),
16290
+ transition: async (transition, productResult, options = {}) => this.persistExternalTransition(transition, productResult, options),
16291
+ get: async (commitId) => this.controlPlaneRequest(
16292
+ `/control/environments/${this.environmentId}/commits/${encodeURIComponent(
16293
+ commitId
16294
+ )}`
16295
+ ),
16296
+ retry: async (commitId) => this.controlPlaneRequest(
16297
+ `/control/environments/${this.environmentId}/commits/${encodeURIComponent(
16298
+ commitId
16299
+ )}/retry`,
16300
+ { method: "POST" }
16301
+ )
16302
+ };
16303
+ }
16304
+ /** Inspect or retry the agent synchronization of a product snapshot. */
16305
+ get observation() {
16306
+ return {
16307
+ get: async (observationId) => this.controlPlaneRequest(
16308
+ `/control/environments/${this.environmentId}/observations/${encodeURIComponent(
16309
+ observationId
16310
+ )}`
16311
+ ),
16312
+ retry: async (observationId) => this.controlPlaneRequest(
16313
+ `/control/environments/${this.environmentId}/observations/${encodeURIComponent(
16314
+ observationId
16315
+ )}/retry`,
16316
+ { method: "POST" }
16317
+ )
16318
+ };
16319
+ }
16320
+ async persistExternalEffect(effect, productResult, options) {
16321
+ const projection = effect.commit.project(productResult);
16322
+ validateProjectionResult(effect.commit, projection);
16323
+ this.requireExternalIdentity(projection.source.version, options);
16324
+ return this.controlPlaneRequest(
16325
+ `/control/environments/${this.environmentId}/external-commits`,
16326
+ {
16327
+ method: "POST",
16328
+ body: JSON.stringify({
16329
+ kind: "effect",
16330
+ effectKey: computeEffectKey2(effect),
16331
+ ...options.sourceEventId?.trim() ? { sourceEventId: options.sourceEventId.trim() } : {},
16332
+ projection
16333
+ })
16334
+ }
16335
+ );
16336
+ }
16337
+ async persistExternalTransition(transition, productResult, options) {
16338
+ const metadata = getDefinedTransitionMetadata(transition);
16339
+ if (!metadata) {
16340
+ throw new Error(
16341
+ "environment.commit.transition requires a transition returned by defineStateMachine"
16342
+ );
16343
+ }
16344
+ const projection = transition.effect.commit.project(productResult);
16345
+ validateProjectionResult(transition.effect.commit, projection);
16346
+ this.requireExternalIdentity(projection.source.version, options);
16347
+ if (!Object.prototype.hasOwnProperty.call(
16348
+ transition.outcomes,
16349
+ projection.outcome.key
16350
+ )) {
16351
+ throw new Error(
16352
+ `Transition ${metadata.machine}.${metadata.transition} does not declare outcome ${projection.outcome.key}`
16353
+ );
16354
+ }
16355
+ if (!projection.primaryTarget) {
16356
+ throw new Error(
16357
+ "An external transition projection requires primaryTarget to identify the transitioned product record"
16358
+ );
16359
+ }
16360
+ return this.controlPlaneRequest(
16361
+ `/control/environments/${this.environmentId}/external-commits`,
16362
+ {
16363
+ method: "POST",
16364
+ body: JSON.stringify({
16365
+ kind: "transition",
16366
+ effectKey: computeEffectKey2(transition.effect),
16367
+ ...options.sourceEventId?.trim() ? { sourceEventId: options.sourceEventId.trim() } : {},
16368
+ projection,
16369
+ transition: {
16370
+ className: projection.primaryTarget.className,
16371
+ objectId: projection.primaryTarget.id,
16372
+ ...projection.primaryTarget.path ? { objectPath: projection.primaryTarget.path } : {},
16373
+ machine: metadata.machine,
16374
+ transition: metadata.transition,
16375
+ from: transition.from
16376
+ }
16377
+ })
16378
+ }
16379
+ );
16380
+ }
16381
+ requireExternalIdentity(sourceVersion, options) {
16382
+ if (!options.sourceEventId?.trim() && !sourceVersion?.trim()) {
16383
+ throw new Error(
16384
+ "An external commit requires sourceEventId or a source version from its projection mapper"
16385
+ );
16386
+ }
16387
+ }
16388
+ /**
16389
+ * Synchronize a versioned product snapshot without claiming an effect or
16390
+ * lifecycle transition. This records no transition history.
16391
+ */
16392
+ async observe(mapper, productResult) {
16393
+ const declaration = { kind: "effect"};
16394
+ const projection = mapper(productResult);
16395
+ validateProjectionResult(declaration, projection);
16396
+ if (!projection.source.version?.trim()) {
16397
+ throw new Error(
16398
+ "environment.observe requires a monotonic source version or serialized adapter sequence"
16399
+ );
16400
+ }
16401
+ return this.controlPlaneRequest(
16402
+ `/control/environments/${this.environmentId}/observations`,
16403
+ {
16404
+ method: "POST",
16405
+ body: JSON.stringify({ projection })
16406
+ }
16407
+ );
16408
+ }
15766
16409
  /**
15767
16410
  * Mirror product-owned workflow state into Granular without making Granular
15768
16411
  * own the customer application's state machine.
@@ -15802,8 +16445,8 @@ var Environment = class _Environment {
15802
16445
  * `await env.recordState({ className: "spend_request", id, machine: "lifecycle", state: "policy_review", source: "customer_backend" })`
15803
16446
  */
15804
16447
  state(target) {
15805
- const observe = async (machineName, stateName, input = {}) => {
15806
- const observedState = input.observedState || input.state || stateName;
16448
+ const observe = async (machineName, stateName2, input = {}) => {
16449
+ const observedState = input.observedState || input.state || stateName2;
15807
16450
  if (!observedState) {
15808
16451
  throw new Error("State observation requires a target state");
15809
16452
  }
@@ -15829,7 +16472,7 @@ var Environment = class _Environment {
15829
16472
  {
15830
16473
  get: (_machineTarget, stateProperty) => {
15831
16474
  if (stateProperty === "to") {
15832
- return (stateName, input) => observe(machineProperty, stateName, input || {});
16475
+ return (stateName2, input) => observe(machineProperty, stateName2, input || {});
15833
16476
  }
15834
16477
  if (typeof stateProperty !== "string") return void 0;
15835
16478
  return (input) => observe(
@@ -17169,6 +17812,14 @@ var EnvironmentSession = class extends Session {
17169
17812
  }
17170
17813
  };
17171
17814
  }
17815
+ get mutations() {
17816
+ return {
17817
+ list: (options = {}) => this.sessionDataRequest(
17818
+ "/mutations",
17819
+ options
17820
+ )
17821
+ };
17822
+ }
17172
17823
  get artifacts() {
17173
17824
  return {
17174
17825
  list: (options = {}) => {
@@ -18668,6 +19319,7 @@ var Granular = class _Granular {
18668
19319
  const serialized = {
18669
19320
  effectKey: computeEffectKey2(effect),
18670
19321
  name: effect.name,
19322
+ ...effect.label ? { label: effect.label } : {},
18671
19323
  description: effect.description,
18672
19324
  inputSchema: effect.inputSchema,
18673
19325
  stability: effect.stability || "stable",
@@ -18691,6 +19343,9 @@ var Granular = class _Granular {
18691
19343
  if (effect.metamodels !== void 0) {
18692
19344
  serialized.metamodels = effect.metamodels;
18693
19345
  }
19346
+ if (effect.commit !== void 0) {
19347
+ serialized.commit = { kind: effect.commit.kind };
19348
+ }
18694
19349
  return serialized;
18695
19350
  }
18696
19351
  async publishSandboxEffectCatalog(host) {
@@ -18911,16 +19566,60 @@ var Granular = class _Granular {
18911
19566
  };
18912
19567
  wsClient.registerRpcHandler("effect.invoke", async (params) => {
18913
19568
  const request = params;
18914
- return invokeRegisteredEffect(
19569
+ let commitReceipt;
19570
+ const result = await invokeRegisteredEffect(
18915
19571
  this.getSandboxEffectMap(sandboxId),
18916
19572
  request,
18917
19573
  {
19574
+ commitAcknowledged: (receipt) => {
19575
+ commitReceipt = receipt;
19576
+ },
19577
+ commit: {
19578
+ persist: (commitRequest) => this.request(
19579
+ `/control/environments/${encodeURIComponent(
19580
+ commitRequest.environmentId
19581
+ )}/commits`,
19582
+ {
19583
+ method: "POST",
19584
+ body: JSON.stringify({
19585
+ kind: commitRequest.kind,
19586
+ invocationId: commitRequest.invocationId,
19587
+ idempotencyKey: commitRequest.idempotencyKey,
19588
+ effectKey: commitRequest.effectKey,
19589
+ projection: commitRequest.projection
19590
+ })
19591
+ }
19592
+ ),
19593
+ mappingFailed: (failure) => this.request(
19594
+ `/control/environments/${encodeURIComponent(
19595
+ failure.environmentId
19596
+ )}/commit-invocations/${encodeURIComponent(
19597
+ failure.invocationId
19598
+ )}`,
19599
+ {
19600
+ method: "PATCH",
19601
+ body: JSON.stringify({
19602
+ status: "mapping_failed",
19603
+ error: {
19604
+ code: "commit_projection_mapping_failed",
19605
+ message: failure.message,
19606
+ retryable: false
19607
+ }
19608
+ })
19609
+ }
19610
+ )
19611
+ },
18918
19612
  feedback: {
18919
19613
  invocationId: request.callId,
18920
19614
  publish: (method, publishParams) => wsClient.call(method, publishParams)
18921
19615
  }
18922
19616
  }
18923
19617
  );
19618
+ return {
19619
+ __granularEffectInvocationResult: true,
19620
+ result,
19621
+ ...commitReceipt ? { commit: commitReceipt } : {}
19622
+ };
18924
19623
  });
18925
19624
  wsClient.on("open", () => {
18926
19625
  void this.synchronizeEffectHost(host).catch((error) => {
@@ -19492,6 +20191,275 @@ var Granular = class _Granular {
19492
20191
  }
19493
20192
  };
19494
20193
 
20194
+ // src/record-snapshot-projection.ts
20195
+ function stableValue(value) {
20196
+ if (Array.isArray(value)) return value.map(stableValue);
20197
+ if (value && typeof value === "object") {
20198
+ return Object.fromEntries(
20199
+ Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, stableValue(item)])
20200
+ );
20201
+ }
20202
+ return value;
20203
+ }
20204
+ function stableJson(value) {
20205
+ return JSON.stringify(stableValue(value));
20206
+ }
20207
+ function recordKey(record) {
20208
+ return `${record.className}\0${record.id}`;
20209
+ }
20210
+ function recordReference(className, id) {
20211
+ return { className, id };
20212
+ }
20213
+ function changedRecordSnapshots(beforeRecords, afterRecords) {
20214
+ const before = new Map(
20215
+ beforeRecords.map((record) => [recordKey(record), record])
20216
+ );
20217
+ const after = new Map(
20218
+ afterRecords.map((record) => [recordKey(record), record])
20219
+ );
20220
+ const changedKeys = /* @__PURE__ */ new Set();
20221
+ for (const key of /* @__PURE__ */ new Set([...before.keys(), ...after.keys()])) {
20222
+ if (stableJson(before.get(key)) !== stableJson(after.get(key))) {
20223
+ changedKeys.add(key);
20224
+ }
20225
+ }
20226
+ return {
20227
+ beforeRecords: beforeRecords.filter(
20228
+ (record) => changedKeys.has(recordKey(record))
20229
+ ),
20230
+ afterRecords: afterRecords.filter(
20231
+ (record) => changedKeys.has(recordKey(record))
20232
+ )
20233
+ };
20234
+ }
20235
+ function relationshipTargets(manifest) {
20236
+ const targets = /* @__PURE__ */ new Map();
20237
+ for (const volume of manifest.volumes) {
20238
+ for (const operation of volume.operations) {
20239
+ const relationship = operation.defineRelationship;
20240
+ if (!relationship) continue;
20241
+ targets.set(`${relationship.left}\0${relationship.leftSubmodel}`, {
20242
+ className: relationship.right
20243
+ });
20244
+ targets.set(`${relationship.right}\0${relationship.rightSubmodel}`, {
20245
+ className: relationship.left
20246
+ });
20247
+ }
20248
+ }
20249
+ return targets;
20250
+ }
20251
+ function stateName(value) {
20252
+ return typeof value === "string" ? value : value.state;
20253
+ }
20254
+ function asIds(value) {
20255
+ return value === void 0 ? [] : Array.isArray(value) ? value : [value];
20256
+ }
20257
+ function changedObjectProjection(before, after) {
20258
+ const beforeFields = before?.fields || {};
20259
+ const afterFields = after.fields || {};
20260
+ const fields = { ...afterFields };
20261
+ for (const fieldName of Object.keys(beforeFields)) {
20262
+ if (!(fieldName in afterFields)) fields[fieldName] = null;
20263
+ }
20264
+ if (before && before.label === after.label && stableJson(beforeFields) === stableJson(afterFields)) {
20265
+ return null;
20266
+ }
20267
+ return {
20268
+ kind: "object",
20269
+ operation: before ? "updated" : "created",
20270
+ record: {
20271
+ className: after.className,
20272
+ id: after.id,
20273
+ ...after.label !== void 0 ? { label: after.label } : {},
20274
+ fields
20275
+ }
20276
+ };
20277
+ }
20278
+ function projectionChanges(input) {
20279
+ const before = new Map(
20280
+ input.beforeRecords.map((record) => [recordKey(record), record])
20281
+ );
20282
+ const after = new Map(
20283
+ input.afterRecords.map((record) => [recordKey(record), record])
20284
+ );
20285
+ const targets = relationshipTargets(input.manifest);
20286
+ const changes = [];
20287
+ for (const record of input.afterRecords) {
20288
+ const previous = before.get(recordKey(record));
20289
+ const objectChange = changedObjectProjection(previous, record);
20290
+ if (objectChange) changes.push(objectChange);
20291
+ const relationshipNames = /* @__PURE__ */ new Set([
20292
+ ...Object.keys(previous?.relationships || {}),
20293
+ ...Object.keys(record.relationships || {})
20294
+ ]);
20295
+ for (const relationshipName of [...relationshipNames].sort()) {
20296
+ const target = targets.get(
20297
+ `${record.className}\0${relationshipName}`
20298
+ );
20299
+ if (!target) {
20300
+ throw new Error(
20301
+ `No ontology relationship target is declared for ${record.className}.${relationshipName}`
20302
+ );
20303
+ }
20304
+ const previousIds = new Set(
20305
+ asIds(previous?.relationships?.[relationshipName])
20306
+ );
20307
+ const currentIds = new Set(
20308
+ asIds(record.relationships?.[relationshipName])
20309
+ );
20310
+ for (const id of [...previousIds].sort()) {
20311
+ if (currentIds.has(id)) continue;
20312
+ changes.push({
20313
+ kind: "relationship",
20314
+ operation: "disconnected",
20315
+ relationship: relationshipName,
20316
+ from: recordReference(record.className, record.id),
20317
+ to: recordReference(target.className, id)
20318
+ });
20319
+ }
20320
+ for (const id of [...currentIds].sort()) {
20321
+ if (previousIds.has(id)) continue;
20322
+ changes.push({
20323
+ kind: "relationship",
20324
+ operation: "connected",
20325
+ relationship: relationshipName,
20326
+ from: recordReference(record.className, record.id),
20327
+ to: recordReference(target.className, id)
20328
+ });
20329
+ }
20330
+ }
20331
+ const machines = /* @__PURE__ */ new Set([
20332
+ ...Object.keys(previous?.states || {}),
20333
+ ...Object.keys(record.states || {})
20334
+ ]);
20335
+ for (const machine of [...machines].sort()) {
20336
+ const next = record.states?.[machine];
20337
+ if (next === void 0) continue;
20338
+ const prior = previous?.states?.[machine];
20339
+ if (prior !== void 0 && stateName(prior) === stateName(next)) continue;
20340
+ if (input.transition && input.transition.className === record.className && input.transition.objectId === record.id && input.transition.machine === machine) {
20341
+ continue;
20342
+ }
20343
+ changes.push({
20344
+ kind: "state_observation",
20345
+ target: recordReference(record.className, record.id),
20346
+ machine,
20347
+ state: stateName(next)
20348
+ });
20349
+ }
20350
+ }
20351
+ for (const record of input.beforeRecords) {
20352
+ if (after.has(recordKey(record))) continue;
20353
+ changes.push({
20354
+ kind: "object",
20355
+ operation: "deleted",
20356
+ target: recordReference(record.className, record.id)
20357
+ });
20358
+ }
20359
+ return changes;
20360
+ }
20361
+ function valueAtPath(value, path) {
20362
+ return path.split(".").filter(Boolean).reduce((current, segment) => {
20363
+ if (!current || typeof current !== "object" || Array.isArray(current)) {
20364
+ return void 0;
20365
+ }
20366
+ return current[segment];
20367
+ }, value);
20368
+ }
20369
+ function primaryTarget(input) {
20370
+ if (input.transition) {
20371
+ return recordReference(
20372
+ input.transition.className,
20373
+ input.transition.objectId
20374
+ );
20375
+ }
20376
+ const creates = input.effect.metamodels?.creates;
20377
+ if (creates) {
20378
+ const declaration = typeof creates === "string" ? { className: creates } : creates;
20379
+ const id = declaration.idPath ? valueAtPath(input.result, declaration.idPath) : void 0;
20380
+ if (typeof id === "string" && id) {
20381
+ return recordReference(declaration.className, id);
20382
+ }
20383
+ }
20384
+ if (input.effect.className && !input.effect.static) {
20385
+ const objectId = input.effectInput && typeof input.effectInput === "object" && !Array.isArray(input.effectInput) ? input.effectInput._objectId : void 0;
20386
+ if (typeof objectId === "string" && objectId) {
20387
+ return recordReference(input.effect.className, objectId);
20388
+ }
20389
+ }
20390
+ const created = input.changes.filter(
20391
+ (change) => change.kind === "object" && change.operation === "created"
20392
+ );
20393
+ const onlyCreated = created.length === 1 ? created[0] : void 0;
20394
+ return onlyCreated ? recordReference(onlyCreated.record.className, onlyCreated.record.id) : void 0;
20395
+ }
20396
+ function transitionOutcome(transition, afterRecords) {
20397
+ if (!transition) {
20398
+ throw new Error("A transition commit has no authored transition context.");
20399
+ }
20400
+ const target = afterRecords.find(
20401
+ (record) => record.className === transition.className && record.id === transition.objectId
20402
+ );
20403
+ const observed = target?.states?.[transition.machine];
20404
+ if (!observed) {
20405
+ throw new Error(
20406
+ `The product result did not expose ${transition.className}:${transition.objectId}.${transition.machine}`
20407
+ );
20408
+ }
20409
+ const finalState = stateName(observed);
20410
+ const matches = Object.entries(transition.outcomes).filter(
20411
+ ([, outcome]) => outcome.to === finalState || outcome.to === "$current" && finalState === transition.from
20412
+ );
20413
+ if (matches.length !== 1) {
20414
+ throw new Error(
20415
+ `Product state ${finalState} maps to ${matches.length} authored outcomes for ${transition.machine}.${transition.transition}`
20416
+ );
20417
+ }
20418
+ const [key, declaration] = matches[0];
20419
+ return {
20420
+ key,
20421
+ ...declaration.disposition === "error" ? {
20422
+ error: {
20423
+ code: `product_outcome_${key}`,
20424
+ message: declaration.label || `The product completed in ${finalState} instead of continuing.`,
20425
+ retryable: false
20426
+ }
20427
+ } : {}
20428
+ };
20429
+ }
20430
+ function projectRecordSnapshotMutation(input) {
20431
+ const changes = projectionChanges({
20432
+ manifest: input.manifest,
20433
+ beforeRecords: input.source.beforeRecords,
20434
+ afterRecords: input.source.afterRecords,
20435
+ transition: input.transition
20436
+ });
20437
+ const target = primaryTarget({
20438
+ effect: input.effect,
20439
+ effectInput: input.effectInput,
20440
+ result: input.result,
20441
+ changes,
20442
+ transition: input.transition
20443
+ });
20444
+ const base = {
20445
+ source: {
20446
+ reference: input.source.reference,
20447
+ ...input.source.version ? { version: input.source.version } : {}
20448
+ },
20449
+ ...target ? { primaryTarget: target } : {},
20450
+ changes,
20451
+ safeSummary: {
20452
+ effect: input.effect.name,
20453
+ affectedChanges: changes.length
20454
+ }
20455
+ };
20456
+ if (input.commitKind === "effect") return base;
20457
+ return {
20458
+ ...base,
20459
+ outcome: transitionOutcome(input.transition, input.source.afterRecords)
20460
+ };
20461
+ }
20462
+
19495
20463
  // src/agent-harness-templates/action-presentation/0.1.0/manifest.json
19496
20464
  var manifest_default = {
19497
20465
  id: "action-presentation",
@@ -22529,10 +23497,15 @@ exports.buildOpenAISpendEventId = buildOpenAISpendEventId;
22529
23497
  exports.buildSessionTranscript = buildSessionTranscript;
22530
23498
  exports.buildSessionTranscriptFromFeedItems = buildSessionTranscriptFromFeedItems;
22531
23499
  exports.calculateOpenAITokenSpend = calculateOpenAITokenSpend;
23500
+ exports.canonicalizeCommitValue = canonicalizeCommitValue;
23501
+ exports.changedRecordSnapshots = changedRecordSnapshots;
22532
23502
  exports.consumeGranularReasoningOnlyChunk = consumeGranularReasoningOnlyChunk;
22533
23503
  exports.consumeGranularReasoningTraceChunk = consumeGranularReasoningTraceChunk;
22534
23504
  exports.createFeedPublisher = createFeedPublisher;
22535
23505
  exports.createHarnessVerifierSnapshot = createHarnessVerifierSnapshot;
23506
+ exports.defineEffect = defineEffect;
23507
+ exports.defineProjection = defineProjection;
23508
+ exports.defineStateMachine = defineStateMachine;
22536
23509
  exports.emitFeedDiagnostic = emitFeedDiagnostic;
22537
23510
  exports.emitFeedDiagnosticToDefaultSink = emitFeedDiagnosticToDefaultSink;
22538
23511
  exports.emptyFeedSnapshot = emptyFeedSnapshot;
@@ -22565,6 +23538,7 @@ exports.projectConversationReferentFocus = projectConversationReferentFocus;
22565
23538
  exports.projectConversationReferentSummary = projectConversationReferentSummary;
22566
23539
  exports.projectHeapSummary = projectHeapSummary;
22567
23540
  exports.projectLoopSummary = projectLoopSummary;
23541
+ exports.projectRecordSnapshotMutation = projectRecordSnapshotMutation;
22568
23542
  exports.projectSessionFileSummary = projectSessionFileSummary;
22569
23543
  exports.projectWorkflowFocus = projectWorkflowFocus;
22570
23544
  exports.projectWorkflowSummary = projectWorkflowSummary;
@@ -22582,6 +23556,7 @@ exports.scorePromptChoiceMatch = scorePromptChoiceMatch;
22582
23556
  exports.stripGranularReasoningTrace = stripGranularReasoningTrace;
22583
23557
  exports.toGranularHttpBase = toGranularHttpBase;
22584
23558
  exports.validateHarnessTemplateManifest = validateHarnessTemplateManifest;
23559
+ exports.validateProjectionResult = validateProjectionResult;
22585
23560
  exports.validationRuleFailureMessage = validationRuleFailureMessage;
22586
23561
  //# sourceMappingURL=index.js.map
22587
23562
  //# sourceMappingURL=index.js.map