@saptools/service-flow 0.1.48 → 0.1.50

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.
@@ -315,6 +315,46 @@ import fs4 from "fs/promises";
315
315
  import path5 from "path";
316
316
  import ts2 from "typescript";
317
317
 
318
+ // src/linker/operation-decorator-normalizer.ts
319
+ function lowerFirst(value) {
320
+ return value ? `${value[0]?.toLowerCase() ?? ""}${value.slice(1)}` : value;
321
+ }
322
+ function normalizedOperationName(value) {
323
+ return value.replace(/^\//, "");
324
+ }
325
+ function clean(value) {
326
+ return value.replace(/^[`'"]|[`'"]$/g, "");
327
+ }
328
+ function generatedOperationNameFromConstant(value) {
329
+ for (const prefix of ["Action", "Func"]) {
330
+ if (value.startsWith(prefix) && value.length > prefix.length && /^[A-Z]/.test(value.slice(prefix.length))) return lowerFirst(value.slice(prefix.length));
331
+ }
332
+ return void 0;
333
+ }
334
+ function resolved(value, raw) {
335
+ const literal2 = clean(value);
336
+ const generated = generatedOperationNameFromConstant(literal2);
337
+ return { status: "resolved", operationName: generated ?? normalizedOperationName(literal2), raw };
338
+ }
339
+ function normalizeDecoratorOperationSignal(value, raw, candidateOperation) {
340
+ if (value) return resolved(value, raw);
341
+ if (!raw || raw.trim().length === 0) return { status: "none", raw };
342
+ const expression = raw.trim();
343
+ const nameMatch = /(?:^|\.)(Action[A-Z][\w$]*|Func[A-Z][\w$]*)\.name$/.exec(expression);
344
+ if (nameMatch?.[1]) return resolved(nameMatch[1], expression);
345
+ const stringMatch = /^String\(([A-Za-z_$][\w$]*)\)$/.exec(expression);
346
+ if (stringMatch?.[1]) {
347
+ const identifier = stringMatch[1];
348
+ const generated = generatedOperationNameFromConstant(identifier);
349
+ const normalizedCandidate2 = candidateOperation ? normalizedOperationName(candidateOperation) : void 0;
350
+ if (generated) return { status: "resolved", operationName: generated, raw: expression };
351
+ if (normalizedCandidate2 && identifier === normalizedCandidate2) return { status: "resolved", operationName: identifier, raw: expression };
352
+ return { status: "unsupported", raw: expression, reason: "string_wrapper_identifier_not_resolved" };
353
+ }
354
+ if (/^[`'"]/.test(expression) && !/[`'"]$/.test(expression)) return { status: "malformed", raw: expression, reason: "unterminated_literal" };
355
+ return { status: "unsupported", raw: expression, reason: "unsupported_decorator_expression" };
356
+ }
357
+
318
358
  // src/parsers/ts-project.ts
319
359
  import ts from "typescript";
320
360
  function createSourceFile(filePath, text) {
@@ -340,16 +380,102 @@ function callName(d) {
340
380
  }
341
381
  function firstArg(d) {
342
382
  const e = d.expression;
343
- return ts2.isCallExpression(e) && e.arguments[0] ? e.arguments[0].getText() : "";
383
+ return ts2.isCallExpression(e) ? e.arguments[0] : void 0;
384
+ }
385
+ function unwrapExpression(expression) {
386
+ let current = expression;
387
+ while (ts2.isParenthesizedExpression(current) || ts2.isAsExpression(current) || ts2.isTypeAssertionExpression(current) || ts2.isSatisfiesExpression(current)) current = current.expression;
388
+ return current;
389
+ }
390
+ function stringValue(expression) {
391
+ if (!expression) return void 0;
392
+ const unwrapped = unwrapExpression(expression);
393
+ return ts2.isStringLiteralLike(unwrapped) ? unwrapped.text : void 0;
394
+ }
395
+ function propertyName(node) {
396
+ if (ts2.isIdentifier(node) || ts2.isStringLiteralLike(node)) return node.text;
397
+ return void 0;
398
+ }
399
+ function collectEnumMembers(statement, lookups) {
400
+ for (const member of statement.members) {
401
+ const name = propertyName(member.name);
402
+ const value = stringValue(member.initializer);
403
+ if (name && value !== void 0)
404
+ lookups.enumMembers.set(`${statement.name.text}.${name}`, value);
405
+ }
406
+ }
407
+ function collectObjectProperties(name, initializer, lookups) {
408
+ const object = unwrapExpression(initializer);
409
+ if (!ts2.isObjectLiteralExpression(object)) return;
410
+ for (const property of object.properties) {
411
+ if (!ts2.isPropertyAssignment(property)) continue;
412
+ const key = propertyName(property.name);
413
+ const value = stringValue(property.initializer);
414
+ if (key && value !== void 0)
415
+ lookups.objectProperties.set(`${name}.${key}`, value);
416
+ }
417
+ }
418
+ function collectStringLookups(source) {
419
+ const lookups = {
420
+ identifiers: /* @__PURE__ */ new Map(),
421
+ enumMembers: /* @__PURE__ */ new Map(),
422
+ objectProperties: /* @__PURE__ */ new Map()
423
+ };
424
+ for (const statement of source.statements) {
425
+ if (ts2.isEnumDeclaration(statement)) collectEnumMembers(statement, lookups);
426
+ if (!ts2.isVariableStatement(statement) || !(statement.declarationList.flags & ts2.NodeFlags.Const)) continue;
427
+ for (const declaration of statement.declarationList.declarations) {
428
+ if (!ts2.isIdentifier(declaration.name) || !declaration.initializer) continue;
429
+ const value = stringValue(declaration.initializer);
430
+ if (value !== void 0) lookups.identifiers.set(declaration.name.text, value);
431
+ collectObjectProperties(declaration.name.text, declaration.initializer, lookups);
432
+ }
433
+ }
434
+ return lookups;
435
+ }
436
+ function unresolved(rawExpression, reason) {
437
+ return { rawExpression, resolutionKind: "unresolved", unresolvedReason: reason };
438
+ }
439
+ function generatedConstant(rawExpression) {
440
+ const match = /(?:^|\.)(Action[A-Z][\w$]*|Func[A-Z][\w$]*)\.name$/.exec(rawExpression.trim());
441
+ return match?.[1] ? generatedOperationNameFromConstant(match[1]) : void 0;
442
+ }
443
+ function resolveDecoratorArgument(argument, lookups) {
444
+ if (!argument) return unresolved("", "decorator_argument_missing");
445
+ const rawExpression = argument.getText();
446
+ const expression = unwrapExpression(argument);
447
+ if (ts2.isStringLiteralLike(expression))
448
+ return { rawExpression, resolvedValue: expression.text, resolutionKind: "literal" };
449
+ if (ts2.isIdentifier(expression)) {
450
+ const value = lookups.identifiers.get(expression.text);
451
+ return value === void 0 ? unresolved(rawExpression, "identifier_not_resolved_to_local_const_string") : { rawExpression, resolvedValue: value, resolutionKind: "const_identifier" };
452
+ }
453
+ if (ts2.isPropertyAccessExpression(expression) && ts2.isIdentifier(expression.expression)) {
454
+ const key = `${expression.expression.text}.${expression.name.text}`;
455
+ const enumValue = lookups.enumMembers.get(key);
456
+ if (enumValue !== void 0)
457
+ return { rawExpression, resolvedValue: enumValue, resolutionKind: "enum_member" };
458
+ const objectValue2 = lookups.objectProperties.get(key);
459
+ if (objectValue2 !== void 0)
460
+ return { rawExpression, resolvedValue: objectValue2, resolutionKind: "const_object_property" };
461
+ }
462
+ const generatedValue = generatedConstant(rawExpression);
463
+ if (generatedValue !== void 0)
464
+ return {
465
+ rawExpression,
466
+ resolvedValue: generatedValue,
467
+ resolutionKind: "generated_constant_name"
468
+ };
469
+ if (ts2.isPropertyAccessExpression(expression))
470
+ return unresolved(rawExpression, "property_access_not_resolved_to_local_string");
471
+ return unresolved(rawExpression, "unsupported_decorator_expression");
344
472
  }
345
473
  async function parseDecorators(repoPath, filePath) {
346
474
  const text = await fs4.readFile(path5.join(repoPath, filePath), "utf8");
347
475
  const sf = createSourceFile(filePath, text);
348
- const constants = /* @__PURE__ */ new Map();
476
+ const lookups = collectStringLookups(sf);
349
477
  const handlers = [];
350
478
  function visit(node) {
351
- if (ts2.isVariableDeclaration(node) && ts2.isIdentifier(node.name) && node.initializer && ts2.isStringLiteralLike(node.initializer))
352
- constants.set(node.name.text, node.initializer.text);
353
479
  if (ts2.isClassDeclaration(node)) {
354
480
  const className = node.name?.text ?? "AnonymousHandler";
355
481
  const hasHandler = decs(node).some((d) => callName(d) === "Handler");
@@ -357,13 +483,13 @@ async function parseDecorators(repoPath, filePath) {
357
483
  (m) => decs(m).filter(
358
484
  (d) => ["Func", "Action", "On", "Event"].includes(callName(d))
359
485
  ).map((d) => {
360
- const raw = firstArg(d);
361
- const value = raw.startsWith('"') || raw.startsWith("'") || raw.startsWith("`") ? stripQuotes(raw) : constants.get(raw) ?? (raw.endsWith(".name") ? raw.split(".").at(-2) : void 0);
486
+ const decoratorResolution = resolveDecoratorArgument(firstArg(d), lookups);
362
487
  return {
363
488
  methodName: m.name.getText(),
364
489
  decoratorKind: callName(d),
365
- decoratorValue: value,
366
- decoratorRawExpression: raw,
490
+ decoratorValue: decoratorResolution.resolvedValue,
491
+ decoratorRawExpression: decoratorResolution.rawExpression,
492
+ decoratorResolution,
367
493
  sourceFile: normalizePath(filePath),
368
494
  sourceLine: line(sf, m.getStart())
369
495
  };
@@ -588,7 +714,7 @@ import ts4 from "typescript";
588
714
  function lineOf3(sf, node) {
589
715
  return sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
590
716
  }
591
- function stringValue(node) {
717
+ function stringValue2(node) {
592
718
  if (!node) return void 0;
593
719
  if (ts4.isStringLiteralLike(node) || ts4.isNoSubstitutionTemplateLiteral(node)) return node.text;
594
720
  if (ts4.isTemplateExpression(node)) return node.getText().replace(/^`|`$/g, "");
@@ -609,7 +735,7 @@ function connectFactFromCall(call) {
609
735
  let alias;
610
736
  let aliasExpr;
611
737
  if (ts4.isStringLiteralLike(first) || ts4.isNoSubstitutionTemplateLiteral(first)) alias = first.text;
612
- else if (!ts4.isObjectLiteralExpression(first)) aliasExpr = stringValue(first);
738
+ else if (!ts4.isObjectLiteralExpression(first)) aliasExpr = stringValue2(first);
613
739
  if ((ts4.isStringLiteralLike(first) || ts4.isNoSubstitutionTemplateLiteral(first)) && !objectArg) return { alias: first.text, isDynamic: false, placeholders: [] };
614
740
  if (!objectArg && aliasExpr) return { aliasExpr, isDynamic: true, placeholders: placeholders(aliasExpr) };
615
741
  const expressions = objectArg ? objectExpressions(objectArg) : {};
@@ -622,8 +748,8 @@ function objectExpressions(objectArg) {
622
748
  for (const prop of obj.properties) {
623
749
  if (!ts4.isPropertyAssignment(prop)) continue;
624
750
  const name = ts4.isIdentifier(prop.name) || ts4.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
625
- if (name === "destination") out.destinationExpr = stringValue(prop.initializer);
626
- if (name === "path" || name === "servicePath") out.servicePathExpr = stringValue(prop.initializer);
751
+ if (name === "destination") out.destinationExpr = stringValue2(prop.initializer);
752
+ if (name === "path" || name === "servicePath") out.servicePathExpr = stringValue2(prop.initializer);
627
753
  if (ts4.isObjectLiteralExpression(prop.initializer)) visitObject(prop.initializer);
628
754
  }
629
755
  }
@@ -733,8 +859,8 @@ function collectReturnedObjectBindings(fn) {
733
859
  for (const prop of node.expression.properties) {
734
860
  if (ts5.isShorthandPropertyAssignment(prop)) returns.set(prop.name.text, prop.name.text);
735
861
  if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.initializer)) {
736
- const propertyName2 = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
737
- if (propertyName2) returns.set(propertyName2, prop.initializer.text);
862
+ const propertyName3 = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
863
+ if (propertyName3) returns.set(propertyName3, prop.initializer.text);
738
864
  }
739
865
  }
740
866
  }
@@ -742,9 +868,9 @@ function collectReturnedObjectBindings(fn) {
742
868
  }
743
869
  visit(fn);
744
870
  const out = /* @__PURE__ */ new Map();
745
- for (const [propertyName2, variableName] of returns) {
871
+ for (const [propertyName3, variableName] of returns) {
746
872
  const fact = bindings.get(variableName);
747
- if (fact) out.set(propertyName2, fact);
873
+ if (fact) out.set(propertyName3, fact);
748
874
  }
749
875
  return out;
750
876
  }
@@ -1028,8 +1154,8 @@ async function parseServiceBindings(repoPath, filePath) {
1028
1154
  if (helpers.length === 0) return;
1029
1155
  for (const el of decl.name.elements) {
1030
1156
  if (!ts5.isIdentifier(el.name)) continue;
1031
- const propertyName2 = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
1032
- const matches2 = helpers.filter((row) => row.helper.returnedProperty === propertyName2);
1157
+ const propertyName3 = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
1158
+ const matches2 = helpers.filter((row) => row.helper.returnedProperty === propertyName3);
1033
1159
  if (matches2.length !== 1) continue;
1034
1160
  const resolved2 = matches2[0];
1035
1161
  out.push({
@@ -1042,7 +1168,7 @@ async function parseServiceBindings(repoPath, filePath) {
1042
1168
  placeholders: resolved2.helper.placeholders,
1043
1169
  sourceFile: normalizePath(filePath),
1044
1170
  sourceLine: lineOf3(sourceFileAst, decl),
1045
- helperChain: [...resolved2.helper.helperChain ?? [], { callerVariable: el.name.text, helperFunction: call.expression.getText(sourceFileAst), returnedProperty: propertyName2, importSource: resolved2.imp?.sourceFile, exportedSymbol: resolved2.imp?.exportedName, helperSourceFile: resolved2.helper.sourceFile, helperSourceLine: resolved2.helper.sourceLine }]
1171
+ helperChain: [...resolved2.helper.helperChain ?? [], { callerVariable: el.name.text, helperFunction: call.expression.getText(sourceFileAst), returnedProperty: propertyName3, importSource: resolved2.imp?.sourceFile, exportedSymbol: resolved2.imp?.exportedName, helperSourceFile: resolved2.helper.sourceFile, helperSourceLine: resolved2.helper.sourceLine }]
1046
1172
  });
1047
1173
  }
1048
1174
  }
@@ -1052,17 +1178,17 @@ async function parseServiceBindings(repoPath, filePath) {
1052
1178
  const helpers = await helpersForCall(call);
1053
1179
  if (helpers.length === 0) return;
1054
1180
  for (const prop of pattern.properties) {
1055
- let propertyName2;
1181
+ let propertyName3;
1056
1182
  let targetName;
1057
1183
  if (ts5.isShorthandPropertyAssignment(prop)) {
1058
- propertyName2 = prop.name.text;
1184
+ propertyName3 = prop.name.text;
1059
1185
  targetName = prop.name.text;
1060
1186
  } else if (ts5.isPropertyAssignment(prop)) {
1061
- propertyName2 = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
1187
+ propertyName3 = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
1062
1188
  targetName = ts5.isIdentifier(prop.initializer) ? prop.initializer.text : void 0;
1063
1189
  }
1064
- if (!propertyName2 || !targetName) continue;
1065
- const matches2 = helpers.filter((row) => row.helper.returnedProperty === propertyName2);
1190
+ if (!propertyName3 || !targetName) continue;
1191
+ const matches2 = helpers.filter((row) => row.helper.returnedProperty === propertyName3);
1066
1192
  if (matches2.length !== 1) continue;
1067
1193
  const resolved2 = matches2[0];
1068
1194
  out.push({
@@ -1075,7 +1201,7 @@ async function parseServiceBindings(repoPath, filePath) {
1075
1201
  placeholders: resolved2.helper.placeholders,
1076
1202
  sourceFile: normalizePath(filePath),
1077
1203
  sourceLine: lineOf3(sourceFileAst, node),
1078
- helperChain: [...resolved2.helper.helperChain ?? [], { callerVariable: targetName, assignedFrom: call.expression.getText(sourceFileAst), aliasKind: "assignment", scopeRule: "same-file-source-order", returnedProperty: propertyName2, importSource: resolved2.imp?.sourceFile, exportedSymbol: resolved2.imp?.exportedName, helperSourceFile: resolved2.helper.sourceFile, helperSourceLine: resolved2.helper.sourceLine }]
1204
+ helperChain: [...resolved2.helper.helperChain ?? [], { callerVariable: targetName, assignedFrom: call.expression.getText(sourceFileAst), aliasKind: "assignment", scopeRule: "same-file-source-order", returnedProperty: propertyName3, importSource: resolved2.imp?.sourceFile, exportedSymbol: resolved2.imp?.exportedName, helperSourceFile: resolved2.helper.sourceFile, helperSourceLine: resolved2.helper.sourceLine }]
1079
1205
  });
1080
1206
  }
1081
1207
  }
@@ -1087,9 +1213,9 @@ async function parseServiceBindings(repoPath, filePath) {
1087
1213
  if (target.expression.kind !== ts5.SyntaxKind.ThisKeyword) return;
1088
1214
  for (const el of decl.name.elements) {
1089
1215
  if (!ts5.isIdentifier(el.name)) continue;
1090
- const propertyName2 = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
1216
+ const propertyName3 = el.propertyName && ts5.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
1091
1217
  const helper = classHelpers.find(
1092
- (h) => h.helperName === target.name.text && h.propertyName === propertyName2
1218
+ (h) => h.helperName === target.name.text && h.propertyName === propertyName3
1093
1219
  );
1094
1220
  if (!helper) continue;
1095
1221
  out.push({
@@ -1242,13 +1368,13 @@ function collectClassHelpers(sf) {
1242
1368
  });
1243
1369
  }
1244
1370
  if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.initializer)) {
1245
- const propertyName2 = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
1246
- const fact = propertyName2 ? bindings.get(prop.initializer.text) : void 0;
1247
- if (propertyName2 && fact)
1371
+ const propertyName3 = ts5.isIdentifier(prop.name) || ts5.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
1372
+ const fact = propertyName3 ? bindings.get(prop.initializer.text) : void 0;
1373
+ if (propertyName3 && fact)
1248
1374
  helpers.push({
1249
1375
  className,
1250
1376
  helperName,
1251
- propertyName: propertyName2,
1377
+ propertyName: propertyName3,
1252
1378
  variableName: prop.initializer.text,
1253
1379
  fact,
1254
1380
  sourceLine: lineOf3(sf, prop)
@@ -1276,7 +1402,7 @@ function collectClassHelpers(sf) {
1276
1402
  // src/parsers/outbound-call-parser.ts
1277
1403
  import fs7 from "fs/promises";
1278
1404
  import path10 from "path";
1279
- import ts7 from "typescript";
1405
+ import ts8 from "typescript";
1280
1406
 
1281
1407
  // src/linker/external-http-target.ts
1282
1408
  import { createHash } from "crypto";
@@ -1552,7 +1678,242 @@ function topLevelQueryIndex(text) {
1552
1678
 
1553
1679
  // src/parsers/imported-wrapper-parser.ts
1554
1680
  import path9 from "path";
1681
+ import ts7 from "typescript";
1682
+
1683
+ // src/parsers/operation-path-analysis.ts
1555
1684
  import ts6 from "typescript";
1685
+ var maxAliasDepth = 6;
1686
+ function analyzeOperationPath(expression, use, method = "POST") {
1687
+ if (!expression) return emptyAnalysis();
1688
+ const state = collectExpressionState(expression, use, 0, /* @__PURE__ */ new Set());
1689
+ const paths = unique(state.paths.map(normalizeRawPath));
1690
+ const normalized = unique(paths.flatMap((value) => normalizedCandidate(value, method)));
1691
+ const status = pathStatus(paths, state.placeholders, state.dynamic);
1692
+ const runtimeIdentifier = state.dynamic.at(-1)?.expression;
1693
+ return {
1694
+ status,
1695
+ rawExpression: expression.getText(expression.getSourceFile()),
1696
+ normalizedOperationPath: status === "static" && normalized.length === 1 ? normalized[0] : void 0,
1697
+ candidateRawPaths: paths,
1698
+ candidateNormalizedOperationPaths: normalized,
1699
+ placeholderKeys: unique([...state.placeholders, ...runtimeIdentifier ? [runtimeIdentifier] : []]),
1700
+ sourceKind: unique(state.sourceKinds).join("+") || "unknown",
1701
+ candidateIdentifier: ts6.isIdentifier(expression) ? expression.text : void 0,
1702
+ runtimeIdentifier,
1703
+ dynamicReassignments: state.dynamic,
1704
+ lexicalScope: lexicalEvidence(expression, use)
1705
+ };
1706
+ }
1707
+ function operationPathExpression(analysis) {
1708
+ if (analysis.status === "ambiguous" || analysis.status === "unknown") return void 0;
1709
+ if (analysis.sourceKind.includes("parameter_binding")) return void 0;
1710
+ if (analysis.candidateRawPaths.length === 1 && analysis.dynamicReassignments.length === 0)
1711
+ return analysis.candidateRawPaths[0];
1712
+ if (!analysis.runtimeIdentifier || analysis.sourceKind.includes("binding_not_found"))
1713
+ return void 0;
1714
+ return isRuntimeIdentifier(analysis.runtimeIdentifier) ? `\${${analysis.runtimeIdentifier}}` : void 0;
1715
+ }
1716
+ function pathUnresolvedReason(analysis) {
1717
+ if (analysis.status === "ambiguous") return "ambiguous_operation_path_candidates";
1718
+ if (analysis.status === "dynamic" && !operationPathExpression(analysis))
1719
+ return "dynamic_operation_path_identifier";
1720
+ if (analysis.dynamicReassignments.length > 0) return "dynamic_operation_path_identifier";
1721
+ return void 0;
1722
+ }
1723
+ function collectExpressionState(expression, use, depth, seen) {
1724
+ const unwrapped = unwrap(expression);
1725
+ if (ts6.isStringLiteral(unwrapped)) return staticState(unwrapped.text, "string_literal");
1726
+ if (ts6.isNoSubstitutionTemplateLiteral(unwrapped))
1727
+ return staticState(unwrapped.text, "no_substitution_template");
1728
+ if (ts6.isTemplateExpression(unwrapped)) return templateState(unwrapped);
1729
+ if (ts6.isConditionalExpression(unwrapped))
1730
+ return mergeStates([
1731
+ collectExpressionState(unwrapped.whenTrue, use, depth + 1, seen),
1732
+ collectExpressionState(unwrapped.whenFalse, use, depth + 1, seen)
1733
+ ], "conditional_candidates");
1734
+ if (ts6.isIdentifier(unwrapped))
1735
+ return collectIdentifierState(unwrapped, use, depth, seen);
1736
+ return dynamicState(unwrapped);
1737
+ }
1738
+ function collectIdentifierState(identifier, use, depth, seen) {
1739
+ if (depth >= maxAliasDepth)
1740
+ return dynamicState(identifier, "alias_depth_exceeded");
1741
+ const binding = resolveBinding(identifier, use);
1742
+ if (!binding || seen.has(binding.declaration))
1743
+ return dynamicState(identifier, binding ? "alias_cycle" : "binding_not_found");
1744
+ seen.add(binding.declaration);
1745
+ if (ts6.isParameter(binding.declaration))
1746
+ return dynamicState(identifier, "parameter_binding");
1747
+ const expressions = reachingExpressions(binding.declaration, use);
1748
+ if (expressions.length === 0) return dynamicState(identifier, "initializer_missing");
1749
+ const states = expressions.map((item) => collectExpressionState(item.expression, item.node, depth + 1, seen));
1750
+ const merged = mergeStates(states, binding.immutable ? "const_alias" : "mutable_alias");
1751
+ if (merged.dynamic.length === 0) return merged;
1752
+ return {
1753
+ ...merged,
1754
+ dynamic: merged.dynamic.map((item) => item.expression === identifier.text ? item : item)
1755
+ };
1756
+ }
1757
+ function reachingExpressions(declaration, use) {
1758
+ const rows2 = [];
1759
+ if (declaration.initializer) rows2.push({ expression: declaration.initializer, node: declaration });
1760
+ if ((declaration.parent.flags & ts6.NodeFlags.Const) !== 0) return rows2;
1761
+ const source = use.getSourceFile();
1762
+ const visit = (node) => {
1763
+ if (node.getStart(source) >= use.getStart(source)) return;
1764
+ if (node !== source && ts6.isFunctionLike(node) && !contains(node, use)) return;
1765
+ if (isAssignmentTo(node, declaration, use))
1766
+ rows2.push({ expression: node.right, node });
1767
+ ts6.forEachChild(node, visit);
1768
+ };
1769
+ visit(source);
1770
+ return rows2.sort((left, right) => left.node.getStart(source) - right.node.getStart(source));
1771
+ }
1772
+ function isAssignmentTo(node, declaration, use) {
1773
+ if (!ts6.isBinaryExpression(node) || node.operatorToken.kind !== ts6.SyntaxKind.EqualsToken)
1774
+ return false;
1775
+ if (!ts6.isIdentifier(node.left) || !ts6.isIdentifier(declaration.name)) return false;
1776
+ return resolveBinding(node.left, node)?.declaration === declaration && contains(declarationScope(declaration), use);
1777
+ }
1778
+ function resolveBinding(identifier, use) {
1779
+ const source = use.getSourceFile();
1780
+ const matches2 = [];
1781
+ const visit = (node) => {
1782
+ if (isNamedDeclaration(node, identifier.text) && isAccessible(node, use))
1783
+ matches2.push(node);
1784
+ ts6.forEachChild(node, visit);
1785
+ };
1786
+ visit(source);
1787
+ const declaration = matches2.sort((left, right) => right.getStart(source) - left.getStart(source))[0];
1788
+ if (!declaration) return void 0;
1789
+ const immutable = ts6.isVariableDeclaration(declaration) && (declaration.parent.flags & ts6.NodeFlags.Const) !== 0;
1790
+ return { declaration, immutable };
1791
+ }
1792
+ function isNamedDeclaration(node, name) {
1793
+ return (ts6.isVariableDeclaration(node) || ts6.isParameter(node)) && ts6.isIdentifier(node.name) && node.name.text === name;
1794
+ }
1795
+ function isAccessible(declaration, use) {
1796
+ const source = use.getSourceFile();
1797
+ if (declaration.name.getStart(source) >= use.getStart(source)) return false;
1798
+ const scope = declarationScope(declaration);
1799
+ return ts6.isSourceFile(scope) || contains(scope, use);
1800
+ }
1801
+ function declarationScope(declaration) {
1802
+ if (ts6.isParameter(declaration)) return declaration.parent;
1803
+ if (ts6.isCatchClause(declaration.parent)) return declaration.parent.block;
1804
+ const list = declaration.parent;
1805
+ if (isLoopInitializer(list.parent)) return list.parent.statement;
1806
+ const blockScoped = (list.flags & (ts6.NodeFlags.Const | ts6.NodeFlags.Let)) !== 0;
1807
+ let current = list.parent;
1808
+ if (!blockScoped) {
1809
+ while (current.parent && !ts6.isFunctionLike(current) && !ts6.isSourceFile(current))
1810
+ current = current.parent;
1811
+ return current;
1812
+ }
1813
+ while (current.parent && !isLexicalScope(current)) current = current.parent;
1814
+ return current;
1815
+ }
1816
+ function isLoopInitializer(node) {
1817
+ return ts6.isForStatement(node) || ts6.isForInStatement(node) || ts6.isForOfStatement(node);
1818
+ }
1819
+ function isLexicalScope(node) {
1820
+ return ts6.isBlock(node) || ts6.isSourceFile(node) || ts6.isModuleBlock(node) || ts6.isCaseBlock(node) || ts6.isFunctionLike(node);
1821
+ }
1822
+ function templateState(expression) {
1823
+ const value = expression.getText(expression.getSourceFile()).slice(1, -1);
1824
+ return {
1825
+ paths: [value],
1826
+ placeholders: expression.templateSpans.map((span) => span.expression.getText(expression.getSourceFile())),
1827
+ dynamic: [],
1828
+ sourceKinds: ["template_with_placeholders"]
1829
+ };
1830
+ }
1831
+ function staticState(path11, sourceKind) {
1832
+ return { paths: [path11], placeholders: [], dynamic: [], sourceKinds: [sourceKind] };
1833
+ }
1834
+ function dynamicState(expression, sourceKind = "dynamic_expression") {
1835
+ return {
1836
+ paths: [],
1837
+ placeholders: [],
1838
+ dynamic: [{
1839
+ expression: expression.getText(expression.getSourceFile()),
1840
+ sourceLine: sourceLine(expression)
1841
+ }],
1842
+ sourceKinds: [sourceKind]
1843
+ };
1844
+ }
1845
+ function mergeStates(states, sourceKind) {
1846
+ return {
1847
+ paths: states.flatMap((state) => state.paths),
1848
+ placeholders: states.flatMap((state) => state.placeholders),
1849
+ dynamic: states.flatMap((state) => state.dynamic),
1850
+ sourceKinds: [sourceKind, ...states.flatMap((state) => state.sourceKinds)]
1851
+ };
1852
+ }
1853
+ function pathStatus(paths, placeholders3, dynamic) {
1854
+ if (dynamic.length > 0) return "dynamic";
1855
+ if (paths.length > 1) return "ambiguous";
1856
+ if (placeholders3.length > 0) return "dynamic";
1857
+ if (paths.length === 1) return "static";
1858
+ return "unknown";
1859
+ }
1860
+ function normalizedCandidate(value, method) {
1861
+ const invocation = normalizeODataOperationInvocationPath(value);
1862
+ if (invocation?.wasInvocation) return [invocation.normalizedOperationPath];
1863
+ const intent = classifyODataPathIntent(value, method);
1864
+ if (intent.kind.startsWith("entity_")) return [];
1865
+ if (!value.startsWith("/") || value.slice(1).includes("/") || value.includes("?")) return [];
1866
+ return [value];
1867
+ }
1868
+ function lexicalEvidence(expression, use) {
1869
+ if (!ts6.isIdentifier(expression))
1870
+ return { assignmentLines: [], sourceOrderSafe: expression.getStart() < use.getStart() };
1871
+ const binding = resolveBinding(expression, use);
1872
+ if (!binding) return { assignmentLines: [], sourceOrderSafe: false };
1873
+ const assignmentLines = ts6.isVariableDeclaration(binding.declaration) ? reachingExpressions(binding.declaration, use).slice(binding.declaration.initializer ? 1 : 0).map((item) => sourceLine(item.node)) : [];
1874
+ return {
1875
+ declarationLine: sourceLine(binding.declaration),
1876
+ assignmentLines,
1877
+ sourceOrderSafe: true
1878
+ };
1879
+ }
1880
+ function unwrap(expression) {
1881
+ if (ts6.isAwaitExpression(expression) || ts6.isParenthesizedExpression(expression))
1882
+ return unwrap(expression.expression);
1883
+ if (ts6.isAsExpression(expression) || ts6.isSatisfiesExpression(expression) || ts6.isTypeAssertionExpression(expression))
1884
+ return unwrap(expression.expression);
1885
+ return expression;
1886
+ }
1887
+ function normalizeRawPath(value) {
1888
+ return value.startsWith("/") ? value : `/${value}`;
1889
+ }
1890
+ function isRuntimeIdentifier(value) {
1891
+ return /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(value);
1892
+ }
1893
+ function contains(parent, child) {
1894
+ const source = child.getSourceFile();
1895
+ return child.getStart(source) >= parent.getStart(source) && child.getEnd() <= parent.getEnd();
1896
+ }
1897
+ function sourceLine(node) {
1898
+ const source = node.getSourceFile();
1899
+ return source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
1900
+ }
1901
+ function unique(values) {
1902
+ return [...new Set(values)].sort();
1903
+ }
1904
+ function emptyAnalysis() {
1905
+ return {
1906
+ status: "unknown",
1907
+ candidateRawPaths: [],
1908
+ candidateNormalizedOperationPaths: [],
1909
+ placeholderKeys: [],
1910
+ sourceKind: "missing",
1911
+ dynamicReassignments: [],
1912
+ lexicalScope: { assignmentLines: [], sourceOrderSafe: false }
1913
+ };
1914
+ }
1915
+
1916
+ // src/parsers/imported-wrapper-parser.ts
1556
1917
  async function parseImportedWrapperCalls(repoPath, filePath, source, serviceBindings) {
1557
1918
  const imports = await importsFor(repoPath, filePath, source);
1558
1919
  const importedByLocal = new Map(imports.filter((item) => item.sourceFile).map((item) => [item.localName, item]));
@@ -1560,7 +1921,7 @@ async function parseImportedWrapperCalls(repoPath, filePath, source, serviceBind
1560
1921
  const out = [];
1561
1922
  const cache = /* @__PURE__ */ new Map();
1562
1923
  for (const call of calls) {
1563
- if (!ts6.isIdentifier(call.expression)) continue;
1924
+ if (!ts7.isIdentifier(call.expression)) continue;
1564
1925
  const imported = importedByLocal.get(call.expression.text);
1565
1926
  if (!imported?.sourceFile) continue;
1566
1927
  const spec = await loadWrapperSpec(repoPath, imported, cache, 0);
@@ -1572,8 +1933,8 @@ async function parseImportedWrapperCalls(repoPath, filePath, source, serviceBind
1572
1933
  function collectImportedCalls(source, imports) {
1573
1934
  const calls = [];
1574
1935
  const visit = (node) => {
1575
- if (ts6.isCallExpression(node) && ts6.isIdentifier(node.expression) && imports.has(node.expression.text)) calls.push(node);
1576
- ts6.forEachChild(node, visit);
1936
+ if (ts7.isCallExpression(node) && ts7.isIdentifier(node.expression) && imports.has(node.expression.text)) calls.push(node);
1937
+ ts7.forEachChild(node, visit);
1577
1938
  };
1578
1939
  visit(source);
1579
1940
  return calls;
@@ -1600,20 +1961,20 @@ function directSendSpec(source, sourceFile, name, fn) {
1600
1961
  const params = parameterNames(fn);
1601
1962
  const sends = [];
1602
1963
  visitFunctionBody(fn, (node) => {
1603
- if (ts6.isCallExpression(node) && ts6.isPropertyAccessExpression(node.expression) && node.expression.name.text === "send") sends.push(node);
1964
+ if (ts7.isCallExpression(node) && ts7.isPropertyAccessExpression(node.expression) && node.expression.name.text === "send") sends.push(node);
1604
1965
  });
1605
1966
  if (sends.length !== 1) return void 0;
1606
1967
  const send = sends[0];
1607
- const receiver = send && ts6.isPropertyAccessExpression(send.expression) && ts6.isIdentifier(send.expression.expression) ? send.expression.expression.text : void 0;
1968
+ const receiver = send && ts7.isPropertyAccessExpression(send.expression) && ts7.isIdentifier(send.expression.expression) ? send.expression.expression.text : void 0;
1608
1969
  const object = send?.arguments[0];
1609
- if (!receiver || !object || !ts6.isObjectLiteralExpression(object)) return void 0;
1970
+ if (!receiver || !object || !ts7.isObjectLiteralExpression(object)) return void 0;
1610
1971
  const pathExpr = propertyExpression(object, "path");
1611
1972
  const methodExpr = propertyExpression(object, "method");
1612
- const pathName = pathExpr && ts6.isIdentifier(pathExpr) ? pathExpr.text : void 0;
1973
+ const pathName = pathExpr && ts7.isIdentifier(pathExpr) ? pathExpr.text : void 0;
1613
1974
  const clientIndex = params.indexOf(receiver);
1614
1975
  const pathIndex = pathName ? params.indexOf(pathName) : -1;
1615
1976
  if (clientIndex < 0 || pathIndex < 0) return void 0;
1616
- const methodName = methodExpr && ts6.isIdentifier(methodExpr) ? methodExpr.text : void 0;
1977
+ const methodName = methodExpr && ts7.isIdentifier(methodExpr) ? methodExpr.text : void 0;
1617
1978
  const methodIndex = methodName ? params.indexOf(methodName) : -1;
1618
1979
  return { clientIndex, pathIndex, methodIndex: methodIndex >= 0 ? methodIndex : void 0, methodLiteral: literal(methodExpr), sourceFile, sourceLine: lineOf3(source, fn), chain: [name] };
1619
1980
  }
@@ -1622,11 +1983,11 @@ async function nestedSendSpec(repoPath, sourceFile, source, name, fn, cache, dep
1622
1983
  const byLocal = new Map(imports.filter((item) => item.sourceFile).map((item) => [item.localName, item]));
1623
1984
  const calls = [];
1624
1985
  visitFunctionBody(fn, (node) => {
1625
- if (ts6.isCallExpression(node) && ts6.isIdentifier(node.expression) && byLocal.has(node.expression.text)) calls.push(node);
1986
+ if (ts7.isCallExpression(node) && ts7.isIdentifier(node.expression) && byLocal.has(node.expression.text)) calls.push(node);
1626
1987
  });
1627
1988
  if (calls.length !== 1) return void 0;
1628
1989
  const call = calls[0];
1629
- const imported = call && ts6.isIdentifier(call.expression) ? byLocal.get(call.expression.text) : void 0;
1990
+ const imported = call && ts7.isIdentifier(call.expression) ? byLocal.get(call.expression.text) : void 0;
1630
1991
  const nested = imported ? await loadWrapperSpec(repoPath, imported, cache, depth + 1) : void 0;
1631
1992
  if (!call || !nested) return void 0;
1632
1993
  const params = parameterNames(fn);
@@ -1638,12 +1999,12 @@ async function nestedSendSpec(repoPath, sourceFile, source, name, fn, cache, dep
1638
1999
  }
1639
2000
  function wrapperCallFact(source, filePath, call, spec, serviceBindings) {
1640
2001
  const client = call.arguments[spec.clientIndex];
1641
- if (!client || !ts6.isIdentifier(client) || !serviceBindings.has(client.text)) return void 0;
1642
- const pathValue = resolveCallerPath(call.arguments[spec.pathIndex], call);
2002
+ if (!client || !ts7.isIdentifier(client) || !serviceBindings.has(client.text)) return void 0;
1643
2003
  const methodValue = spec.methodIndex === void 0 ? spec.methodLiteral : literal(call.arguments[spec.methodIndex]);
1644
2004
  const method = (methodValue ?? "POST").toUpperCase();
1645
- const rawPath = pathValue.rawExpression;
1646
- const operationPathExpr = pathValue.value ? normalizeOperationPath(pathValue.value) : runtimePathExpression(rawPath);
2005
+ const pathAnalysis = analyzeOperationPath(call.arguments[spec.pathIndex], call, method);
2006
+ const operationPathExpr = operationPathExpression(pathAnalysis);
2007
+ const unresolvedReason = pathUnresolvedReason(pathAnalysis);
1647
2008
  return {
1648
2009
  callType: "remote_action",
1649
2010
  serviceVariableName: client.text,
@@ -1653,59 +2014,41 @@ function wrapperCallFact(source, filePath, call, spec, serviceBindings) {
1653
2014
  sourceFile: normalizePath(filePath),
1654
2015
  sourceLine: lineOf3(source, call),
1655
2016
  confidence: operationPathExpr ? 0.85 : 0.5,
1656
- unresolvedReason: pathValue.value ? void 0 : "dynamic_operation_path_identifier",
2017
+ unresolvedReason,
1657
2018
  evidence: {
1658
2019
  parser: "typescript_ast",
1659
- classifier: pathValue.value ? "imported_wrapper_literal_path" : "imported_wrapper_dynamic_path",
2020
+ classifier: importedWrapperClassifier(pathAnalysis.status),
1660
2021
  receiver: client.text,
1661
2022
  wrapperFunction: spec.chain[0],
1662
2023
  wrapperChain: spec.chain,
1663
2024
  callerSite: { sourceFile: normalizePath(filePath), sourceLine: lineOf3(source, call) },
1664
2025
  calleeSite: { sourceFile: spec.sourceFile, sourceLine: spec.sourceLine },
1665
- rawPathExpression: rawPath,
1666
- missingPathIdentifier: pathValue.value ? void 0 : rawPath,
1667
- odataPathIntent: pathValue.value ? classifyODataPathIntent(operationPathExpr, method) : void 0
2026
+ rawPathExpression: pathAnalysis.rawExpression,
2027
+ missingPathIdentifier: pathAnalysis.runtimeIdentifier,
2028
+ pathAnalysis,
2029
+ odataPathIntent: operationPathExpr ? classifyODataPathIntent(operationPathExpr, method) : void 0
1668
2030
  }
1669
2031
  };
1670
2032
  }
1671
- function resolveCallerPath(expr, use) {
1672
- if (!expr) return { sourceKind: "missing", rawExpression: "" };
1673
- if (ts6.isStringLiteralLike(expr) || ts6.isNoSubstitutionTemplateLiteral(expr)) return { value: expr.text, sourceKind: "literal", rawExpression: expr.getText() };
1674
- if (ts6.isTemplateExpression(expr)) return { value: expr.getText().slice(1, -1), sourceKind: "template", rawExpression: expr.getText() };
1675
- if (ts6.isIdentifier(expr)) {
1676
- const initializer = constInitializer(expr.text, use);
1677
- if (initializer) {
1678
- const resolved2 = resolveCallerPath(initializer, initializer);
1679
- return { ...resolved2, sourceKind: "const", rawExpression: expr.text };
1680
- }
1681
- }
1682
- return { sourceKind: "dynamic", rawExpression: expr.getText() };
1683
- }
1684
- function constInitializer(name, use) {
1685
- let found;
1686
- const source = use.getSourceFile();
1687
- const visit = (node) => {
1688
- if (node.getStart(source) >= use.getStart(source)) return;
1689
- if (ts6.isVariableDeclaration(node) && ts6.isIdentifier(node.name) && node.name.text === name && node.initializer && (node.parent.flags & ts6.NodeFlags.Const) !== 0) found = node.initializer;
1690
- ts6.forEachChild(node, visit);
1691
- };
1692
- visit(source);
1693
- return found;
2033
+ function importedWrapperClassifier(status) {
2034
+ if (status === "static") return "imported_wrapper_literal_path";
2035
+ if (status === "ambiguous") return "imported_wrapper_ambiguous_path";
2036
+ return "imported_wrapper_dynamic_path";
1694
2037
  }
1695
2038
  function findFunction(source, exportedName) {
1696
2039
  const localName = exportedLocalName(source, exportedName);
1697
2040
  for (const statement of source.statements) {
1698
- if (ts6.isFunctionDeclaration(statement) && statement.name?.text === localName) return { name: exportedName, fn: statement };
1699
- if (!ts6.isVariableStatement(statement)) continue;
2041
+ if (ts7.isFunctionDeclaration(statement) && statement.name?.text === localName) return { name: exportedName, fn: statement };
2042
+ if (!ts7.isVariableStatement(statement)) continue;
1700
2043
  for (const declaration of statement.declarationList.declarations) {
1701
- if (ts6.isIdentifier(declaration.name) && declaration.name.text === localName && declaration.initializer && (ts6.isArrowFunction(declaration.initializer) || ts6.isFunctionExpression(declaration.initializer))) return { name: exportedName, fn: declaration.initializer };
2044
+ if (ts7.isIdentifier(declaration.name) && declaration.name.text === localName && declaration.initializer && (ts7.isArrowFunction(declaration.initializer) || ts7.isFunctionExpression(declaration.initializer))) return { name: exportedName, fn: declaration.initializer };
1702
2045
  }
1703
2046
  }
1704
2047
  return void 0;
1705
2048
  }
1706
2049
  function exportedLocalName(source, exportedName) {
1707
2050
  for (const statement of source.statements) {
1708
- if (!ts6.isExportDeclaration(statement) || !statement.exportClause || !ts6.isNamedExports(statement.exportClause)) continue;
2051
+ if (!ts7.isExportDeclaration(statement) || !statement.exportClause || !ts7.isNamedExports(statement.exportClause)) continue;
1709
2052
  const match = statement.exportClause.elements.find((item) => item.name.text === exportedName);
1710
2053
  if (match) return match.propertyName?.text ?? match.name.text;
1711
2054
  }
@@ -1713,36 +2056,30 @@ function exportedLocalName(source, exportedName) {
1713
2056
  }
1714
2057
  function visitFunctionBody(fn, visitor) {
1715
2058
  const visit = (node) => {
1716
- if (node !== fn && ts6.isFunctionLike(node)) return;
2059
+ if (node !== fn && ts7.isFunctionLike(node)) return;
1717
2060
  visitor(node);
1718
- ts6.forEachChild(node, visit);
2061
+ ts7.forEachChild(node, visit);
1719
2062
  };
1720
2063
  if (fn.body) visit(fn.body);
1721
2064
  }
1722
2065
  function parameterNames(fn) {
1723
- return fn.parameters.map((parameter) => ts6.isIdentifier(parameter.name) ? parameter.name.text : "");
2066
+ return fn.parameters.map((parameter) => ts7.isIdentifier(parameter.name) ? parameter.name.text : "");
1724
2067
  }
1725
2068
  function mappedParameterIndex(expr, parameters) {
1726
- return expr && ts6.isIdentifier(expr) ? parameters.indexOf(expr.text) : -1;
2069
+ return expr && ts7.isIdentifier(expr) ? parameters.indexOf(expr.text) : -1;
1727
2070
  }
1728
2071
  function propertyExpression(object, key) {
1729
2072
  for (const property of object.properties) {
1730
- if (ts6.isPropertyAssignment(property) && propertyName(property.name) === key) return property.initializer;
1731
- if (ts6.isShorthandPropertyAssignment(property) && property.name.text === key) return property.name;
2073
+ if (ts7.isPropertyAssignment(property) && propertyName2(property.name) === key) return property.initializer;
2074
+ if (ts7.isShorthandPropertyAssignment(property) && property.name.text === key) return property.name;
1732
2075
  }
1733
2076
  return void 0;
1734
2077
  }
1735
- function propertyName(name) {
1736
- return ts6.isIdentifier(name) || ts6.isStringLiteralLike(name) ? name.text : void 0;
2078
+ function propertyName2(name) {
2079
+ return ts7.isIdentifier(name) || ts7.isStringLiteralLike(name) ? name.text : void 0;
1737
2080
  }
1738
2081
  function literal(expr) {
1739
- return expr && (ts6.isStringLiteralLike(expr) || ts6.isNoSubstitutionTemplateLiteral(expr)) ? expr.text : void 0;
1740
- }
1741
- function normalizeOperationPath(value) {
1742
- return value.startsWith("/") ? value : `/${value}`;
1743
- }
1744
- function runtimePathExpression(value) {
1745
- return value ? `\${${value}}` : void 0;
2082
+ return expr && (ts7.isStringLiteralLike(expr) || ts7.isNoSubstitutionTemplateLiteral(expr)) ? expr.text : void 0;
1746
2083
  }
1747
2084
 
1748
2085
  // src/parsers/outbound-call-parser.ts
@@ -1751,47 +2088,47 @@ function lineOf4(text, idx) {
1751
2088
  }
1752
2089
  function entityFromExpression(expr) {
1753
2090
  if (!expr) return void 0;
1754
- if (ts7.isIdentifier(expr) || ts7.isStringLiteral(expr) || ts7.isNoSubstitutionTemplateLiteral(expr)) return expr.text;
1755
- if (ts7.isPropertyAccessExpression(expr) && expr.expression.kind === ts7.SyntaxKind.ThisKeyword) return expr.name.text;
1756
- if (ts7.isElementAccessExpression(expr) && expr.argumentExpression && (ts7.isStringLiteral(expr.argumentExpression) || ts7.isNoSubstitutionTemplateLiteral(expr.argumentExpression))) return expr.argumentExpression.text;
2091
+ if (ts8.isIdentifier(expr) || ts8.isStringLiteral(expr) || ts8.isNoSubstitutionTemplateLiteral(expr)) return expr.text;
2092
+ if (ts8.isPropertyAccessExpression(expr) && expr.expression.kind === ts8.SyntaxKind.ThisKeyword) return expr.name.text;
2093
+ if (ts8.isElementAccessExpression(expr) && expr.argumentExpression && (ts8.isStringLiteral(expr.argumentExpression) || ts8.isNoSubstitutionTemplateLiteral(expr.argumentExpression))) return expr.argumentExpression.text;
1757
2094
  return void 0;
1758
2095
  }
1759
2096
  function expressionName(expr) {
1760
- if (ts7.isIdentifier(expr)) return expr.text;
1761
- if (ts7.isPropertyAccessExpression(expr)) return `${expressionName(expr.expression)}.${expr.name.text}`;
2097
+ if (ts8.isIdentifier(expr)) return expr.text;
2098
+ if (ts8.isPropertyAccessExpression(expr)) return `${expressionName(expr.expression)}.${expr.name.text}`;
1762
2099
  return expr.getText();
1763
2100
  }
1764
2101
  function variableInitializers(source) {
1765
2102
  const initializers = /* @__PURE__ */ new Map();
1766
2103
  for (const statement of source.statements) {
1767
- if (!ts7.isVariableStatement(statement) || (statement.declarationList.flags & ts7.NodeFlags.Const) === 0) continue;
2104
+ if (!ts8.isVariableStatement(statement) || (statement.declarationList.flags & ts8.NodeFlags.Const) === 0) continue;
1768
2105
  for (const declaration of statement.declarationList.declarations) {
1769
- if (ts7.isIdentifier(declaration.name) && declaration.initializer) initializers.set(declaration.name.text, declaration.initializer);
2106
+ if (ts8.isIdentifier(declaration.name) && declaration.initializer) initializers.set(declaration.name.text, declaration.initializer);
1770
2107
  }
1771
2108
  }
1772
2109
  return initializers;
1773
2110
  }
1774
2111
  function queryEntityFromAst(expr, initializers = /* @__PURE__ */ new Map()) {
1775
- if (ts7.isParenthesizedExpression(expr) || ts7.isAwaitExpression(expr)) return queryEntityFromAst(expr.expression, initializers);
1776
- if (ts7.isIdentifier(expr) && initializers.has(expr.text)) return queryEntityFromAst(initializers.get(expr.text), initializers);
1777
- if (ts7.isCallExpression(expr)) {
2112
+ if (ts8.isParenthesizedExpression(expr) || ts8.isAwaitExpression(expr)) return queryEntityFromAst(expr.expression, initializers);
2113
+ if (ts8.isIdentifier(expr) && initializers.has(expr.text)) return queryEntityFromAst(initializers.get(expr.text), initializers);
2114
+ if (ts8.isCallExpression(expr)) {
1778
2115
  const name = expressionName(expr.expression);
1779
2116
  if (name === "cds.run") return queryEntityFromAst(expr.arguments[0], initializers);
1780
2117
  if (["SELECT.one.from", "SELECT.from", "SELECT.one", "INSERT.into", "UPSERT.into", "DELETE.from", "UPDATE.entity"].includes(name)) return entityFromExpression(expr.arguments[0]);
1781
2118
  if (name === "UPDATE") return entityFromExpression(expr.arguments[0]);
1782
- const receiver = ts7.isPropertyAccessExpression(expr.expression) ? expr.expression.expression : void 0;
2119
+ const receiver = ts8.isPropertyAccessExpression(expr.expression) ? expr.expression.expression : void 0;
1783
2120
  if (receiver) return queryEntityFromAst(receiver, initializers);
1784
2121
  }
1785
2122
  return void 0;
1786
2123
  }
1787
2124
  function extractQueryEntity(expr) {
1788
- const source = ts7.createSourceFile("query.ts", `const __query = (${expr});`, ts7.ScriptTarget.Latest, true, ts7.ScriptKind.TS);
2125
+ const source = ts8.createSourceFile("query.ts", `const __query = (${expr});`, ts8.ScriptTarget.Latest, true, ts8.ScriptKind.TS);
1789
2126
  const initializers = variableInitializers(source);
1790
2127
  let found;
1791
2128
  const visit = (node) => {
1792
2129
  if (found) return;
1793
- if (ts7.isParenthesizedExpression(node)) found = queryEntityFromAst(node.expression, initializers);
1794
- ts7.forEachChild(node, visit);
2130
+ if (ts8.isParenthesizedExpression(node)) found = queryEntityFromAst(node.expression, initializers);
2131
+ ts8.forEachChild(node, visit);
1795
2132
  };
1796
2133
  visit(source);
1797
2134
  return found;
@@ -1805,7 +2142,7 @@ function parserEvidence(source, node, extra) {
1805
2142
  return { parser: "typescript_ast", startOffset: node.getStart(source), endOffset: node.getEnd(), ...extra };
1806
2143
  }
1807
2144
  function isStringLike(expr) {
1808
- return Boolean(expr && (ts7.isStringLiteral(expr) || ts7.isNoSubstitutionTemplateLiteral(expr)));
2145
+ return Boolean(expr && (ts8.isStringLiteral(expr) || ts8.isNoSubstitutionTemplateLiteral(expr)));
1809
2146
  }
1810
2147
  function literalText(expr) {
1811
2148
  if (isStringLike(expr)) return expr.text;
@@ -1813,151 +2150,118 @@ function literalText(expr) {
1813
2150
  }
1814
2151
  function objectPropertyText(object, key) {
1815
2152
  const prop = object.properties.find(
1816
- (property) => ts7.isPropertyAssignment(property) && nameOfProperty(property.name) === key || ts7.isShorthandPropertyAssignment(property) && property.name.text === key
2153
+ (property) => ts8.isPropertyAssignment(property) && nameOfProperty(property.name) === key || ts8.isShorthandPropertyAssignment(property) && property.name.text === key
1817
2154
  );
1818
2155
  if (!prop) return void 0;
1819
- return ts7.isShorthandPropertyAssignment(prop) ? prop.name.text : prop.initializer.getText();
2156
+ return ts8.isShorthandPropertyAssignment(prop) ? prop.name.text : prop.initializer.getText();
1820
2157
  }
1821
2158
  function objectPropertyIsShorthand(object, key) {
1822
- return object.properties.some((property) => ts7.isShorthandPropertyAssignment(property) && property.name.text === key);
2159
+ return object.properties.some((property) => ts8.isShorthandPropertyAssignment(property) && property.name.text === key);
1823
2160
  }
1824
2161
  function nameOfProperty(name) {
1825
- if (ts7.isIdentifier(name) || ts7.isStringLiteral(name) || ts7.isNumericLiteral(name)) return name.text;
2162
+ if (ts8.isIdentifier(name) || ts8.isStringLiteral(name) || ts8.isNumericLiteral(name)) return name.text;
1826
2163
  return void 0;
1827
2164
  }
1828
- var maxAliasDepth = 5;
2165
+ var maxAliasDepth2 = 5;
1829
2166
  function safeRaw(expr) {
1830
- if (ts7.isStringLiteral(expr) || ts7.isNoSubstitutionTemplateLiteral(expr) || ts7.isIdentifier(expr) || ts7.isTemplateExpression(expr)) return expr.getText(expr.getSourceFile());
2167
+ if (ts8.isStringLiteral(expr) || ts8.isNoSubstitutionTemplateLiteral(expr) || ts8.isIdentifier(expr) || ts8.isTemplateExpression(expr)) return expr.getText(expr.getSourceFile());
1831
2168
  return void 0;
1832
2169
  }
1833
2170
  function placeholders2(expr) {
1834
2171
  return expr.templateSpans.map((span) => span.expression.getText(expr.getSourceFile()));
1835
2172
  }
1836
2173
  function isFunctionLikeScope(node) {
1837
- return ts7.isFunctionLike(node) || ts7.isSourceFile(node);
2174
+ return ts8.isFunctionLike(node) || ts8.isSourceFile(node);
1838
2175
  }
1839
2176
  function nodeContains(parent, child) {
1840
2177
  const source = child.getSourceFile();
1841
2178
  return child.getStart(source) >= parent.getStart(source) && child.getEnd() <= parent.getEnd();
1842
2179
  }
1843
- function declarationScope(node) {
1844
- if (ts7.isParameter(node)) return node.parent;
1845
- if (ts7.isCatchClause(node.parent) && node.parent.variableDeclaration === node) return node.parent;
2180
+ function declarationScope2(node) {
2181
+ if (ts8.isParameter(node)) return node.parent;
2182
+ if (ts8.isCatchClause(node.parent) && node.parent.variableDeclaration === node) return node.parent;
1846
2183
  const list = node.parent;
1847
- const blockScoped = (list.flags & (ts7.NodeFlags.Const | ts7.NodeFlags.Let)) !== 0;
2184
+ const blockScoped = (list.flags & (ts8.NodeFlags.Const | ts8.NodeFlags.Let)) !== 0;
1848
2185
  let current = list.parent;
1849
2186
  if (!blockScoped) {
1850
2187
  while (current.parent && !isFunctionLikeScope(current)) current = current.parent;
1851
2188
  return current;
1852
2189
  }
1853
- while (current.parent && !ts7.isBlock(current) && !ts7.isSourceFile(current) && !ts7.isModuleBlock(current) && !ts7.isCaseBlock(current) && !isLoopInitializerScope(node, current) && !isFunctionLikeScope(current)) current = current.parent;
2190
+ while (current.parent && !ts8.isBlock(current) && !ts8.isSourceFile(current) && !ts8.isModuleBlock(current) && !ts8.isCaseBlock(current) && !isLoopInitializerScope(node, current) && !isFunctionLikeScope(current)) current = current.parent;
1854
2191
  return current;
1855
2192
  }
1856
2193
  function isLoopInitializerScope(declaration, scope) {
1857
2194
  const list = declaration.parent;
1858
- return ts7.isForStatement(scope) && scope.initializer === list || (ts7.isForInStatement(scope) || ts7.isForOfStatement(scope)) && scope.initializer === list;
2195
+ return ts8.isForStatement(scope) && scope.initializer === list || (ts8.isForInStatement(scope) || ts8.isForOfStatement(scope)) && scope.initializer === list;
1859
2196
  }
1860
2197
  function catchBindingScope(declaration) {
1861
- if (ts7.isParameter(declaration)) return void 0;
1862
- return ts7.isCatchClause(declaration.parent) && declaration.parent.variableDeclaration === declaration ? declaration.parent : void 0;
2198
+ if (ts8.isParameter(declaration)) return void 0;
2199
+ return ts8.isCatchClause(declaration.parent) && declaration.parent.variableDeclaration === declaration ? declaration.parent : void 0;
1863
2200
  }
1864
2201
  function isAccessibleDeclaration(declaration, use) {
1865
2202
  const source = use.getSourceFile();
1866
2203
  if (declaration.name.getStart(source) >= use.getStart(source)) return false;
1867
2204
  const catchScope = catchBindingScope(declaration);
1868
2205
  if (catchScope) return nodeContains(catchScope.block, use);
1869
- const scope = declarationScope(declaration);
1870
- if (ts7.isForStatement(scope) || ts7.isForInStatement(scope) || ts7.isForOfStatement(scope)) return nodeContains(scope.statement, use);
1871
- return ts7.isSourceFile(scope) || nodeContains(scope, use);
2206
+ const scope = declarationScope2(declaration);
2207
+ if (ts8.isForStatement(scope) || ts8.isForInStatement(scope) || ts8.isForOfStatement(scope)) return nodeContains(scope.statement, use);
2208
+ return ts8.isSourceFile(scope) || nodeContains(scope, use);
1872
2209
  }
1873
- function resolveBinding(identifier, use) {
2210
+ function resolveBinding2(identifier, use) {
1874
2211
  const source = use.getSourceFile();
1875
2212
  let best;
1876
2213
  const visit = (node) => {
1877
- if (ts7.isVariableDeclaration(node) && ts7.isIdentifier(node.name) && node.name.text === identifier.text && isAccessibleDeclaration(node, use)) best = node;
1878
- if (ts7.isParameter(node) && ts7.isIdentifier(node.name) && node.name.text === identifier.text && isAccessibleDeclaration(node, use)) best = node;
1879
- ts7.forEachChild(node, visit);
2214
+ if (ts8.isVariableDeclaration(node) && ts8.isIdentifier(node.name) && node.name.text === identifier.text && isAccessibleDeclaration(node, use)) best = node;
2215
+ if (ts8.isParameter(node) && ts8.isIdentifier(node.name) && node.name.text === identifier.text && isAccessibleDeclaration(node, use)) best = node;
2216
+ ts8.forEachChild(node, visit);
1880
2217
  };
1881
2218
  visit(source);
1882
2219
  if (!best) return { immutable: false, evidence: ["binding_not_found"] };
1883
- const immutable = ts7.isVariableDeclaration(best) && (best.parent.flags & ts7.NodeFlags.Const) !== 0;
1884
- return { declaration: best, initializer: ts7.isVariableDeclaration(best) ? best.initializer : void 0, immutable, evidence: [immutable ? "lexical_const_binding_before_use" : "lexical_mutable_or_parameter_binding"] };
2220
+ const immutable = ts8.isVariableDeclaration(best) && (best.parent.flags & ts8.NodeFlags.Const) !== 0;
2221
+ return { declaration: best, initializer: ts8.isVariableDeclaration(best) ? best.initializer : void 0, immutable, evidence: [immutable ? "lexical_const_binding_before_use" : "lexical_mutable_or_parameter_binding"] };
1885
2222
  }
1886
2223
  function resolveExpression(expr, use, policy, depth = 0, seen = /* @__PURE__ */ new Set()) {
1887
2224
  if (!expr) return { status: "unknown", sourceKind: "dynamic_expression", placeholderKeys: [], evidence: ["expression_missing"] };
1888
- if (ts7.isStringLiteral(expr)) return { status: "static", sourceKind: "string_literal", value: expr.text, rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["string_literal"] };
1889
- if (ts7.isNoSubstitutionTemplateLiteral(expr)) return { status: "static", sourceKind: "no_substitution_template", value: expr.text, rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["no_substitution_template"] };
1890
- if (ts7.isTemplateExpression(expr)) {
2225
+ if (ts8.isStringLiteral(expr)) return { status: "static", sourceKind: "string_literal", value: expr.text, rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["string_literal"] };
2226
+ if (ts8.isNoSubstitutionTemplateLiteral(expr)) return { status: "static", sourceKind: "no_substitution_template", value: expr.text, rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["no_substitution_template"] };
2227
+ if (ts8.isTemplateExpression(expr)) {
1891
2228
  const keys = placeholders2(expr);
1892
2229
  if (policy === "operation_path") return { status: "dynamic", sourceKind: "template_with_substitutions", value: stripQuotes(expr.getText(expr.getSourceFile())), rawExpression: safeRaw(expr), placeholderKeys: keys, evidence: ["operation_path_template_placeholders_retained"] };
1893
2230
  return { status: "dynamic", sourceKind: "template_with_substitutions", placeholderKeys: keys, evidence: ["template_substitutions_not_static_external_target"] };
1894
2231
  }
1895
- if (ts7.isIdentifier(expr)) {
1896
- if (depth >= maxAliasDepth) return { status: "unknown", sourceKind: "const_alias", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["alias_depth_exceeded"], constName: expr.text };
1897
- const binding = resolveBinding(expr, use);
2232
+ if (ts8.isIdentifier(expr)) {
2233
+ if (depth >= maxAliasDepth2) return { status: "unknown", sourceKind: "const_alias", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["alias_depth_exceeded"], constName: expr.text };
2234
+ const binding = resolveBinding2(expr, use);
1898
2235
  if (!binding.declaration || !binding.initializer || !binding.immutable) return { status: "dynamic", sourceKind: "dynamic_expression", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: binding.evidence, constName: expr.text };
1899
2236
  if (seen.has(binding.declaration)) return { status: "unknown", sourceKind: "const_alias", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["alias_cycle_detected"], constName: expr.text };
1900
2237
  seen.add(binding.declaration);
1901
2238
  const resolved2 = resolveExpression(binding.initializer, binding.declaration, policy, depth + 1, seen);
1902
2239
  return { ...resolved2, sourceKind: "const_alias", rawExpression: safeRaw(expr), constName: expr.text, evidence: [...binding.evidence, ...resolved2.evidence] };
1903
2240
  }
1904
- return { status: "dynamic", sourceKind: "dynamic_expression", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: [`unsupported_${ts7.SyntaxKind[expr.kind] ?? "expression"}`] };
2241
+ return { status: "dynamic", sourceKind: "dynamic_expression", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: [`unsupported_${ts8.SyntaxKind[expr.kind] ?? "expression"}`] };
1905
2242
  }
1906
2243
  function staticExpressionText(expr, initializers) {
1907
2244
  if (!expr) return void 0;
1908
2245
  if (isStringLike(expr)) return expr.text;
1909
- if (ts7.isIdentifier(expr) && initializers.has(expr.text)) return staticExpressionText(initializers.get(expr.text), initializers);
2246
+ if (ts8.isIdentifier(expr) && initializers.has(expr.text)) return staticExpressionText(initializers.get(expr.text), initializers);
1910
2247
  return void 0;
1911
2248
  }
1912
- function staticPathExpression(expr, use) {
1913
- const resolution = resolveExpression(expr, use, "operation_path");
1914
- const sourceKind = resolution.sourceKind === "const_alias" ? "const" : resolution.sourceKind === "template_with_substitutions" || resolution.sourceKind === "no_substitution_template" ? "template" : resolution.status === "static" ? "literal" : "dynamic";
1915
- return { text: resolution.value, sourceKind, rawExpression: resolution.rawExpression, constName: resolution.constName, resolution };
1916
- }
1917
2249
  function operationPathFromStatic(text) {
1918
2250
  return text ? `/${stripQuotes(text).replace(/^\//, "")}` : void 0;
1919
2251
  }
1920
- function staticPathCandidates(identifier, call, method) {
1921
- const binding = resolveBinding(identifier, call);
1922
- const declaration = binding.declaration;
1923
- if (!declaration) return void 0;
1924
- const source = call.getSourceFile();
1925
- const paths = [];
1926
- let hasDynamicAssignments = false;
1927
- const addExpr = (expr, origin) => {
1928
- const resolved2 = staticPathExpression(expr, origin);
1929
- if (resolved2.text) paths.push(operationPathFromStatic(resolved2.text) ?? resolved2.text);
1930
- else hasDynamicAssignments = true;
1931
- };
1932
- if (ts7.isVariableDeclaration(declaration)) addExpr(declaration.initializer, declaration);
1933
- const visit = (node) => {
1934
- if (node !== source && isFunctionLikeScope(node) && !nodeContains(node, call)) return;
1935
- if (node.getStart(source) >= call.getStart(source)) return;
1936
- if (ts7.isBinaryExpression(node) && node.operatorToken.kind === ts7.SyntaxKind.EqualsToken && ts7.isIdentifier(node.left)) {
1937
- const target = resolveBinding(node.left, node);
1938
- if (target.declaration === declaration) addExpr(node.right, node);
1939
- }
1940
- ts7.forEachChild(node, visit);
1941
- };
1942
- visit(source);
1943
- const candidatePaths = [...new Set(paths)];
1944
- if (candidatePaths.length === 0) return void 0;
1945
- const normalizedCandidateOperations = [...new Set(candidatePaths.map((candidate) => classifyODataPathIntent(candidate, method).topLevelOperationName).filter((value) => Boolean(value)))];
1946
- return { candidatePaths, normalizedCandidateOperations, candidateSourceKind: "static candidates observed before call in lexical scope", candidateIdentifier: identifier.text, hasDynamicAssignments, conservativeReason: hasDynamicAssignments ? "dynamic_assignment_observed" : normalizedCandidateOperations.length > 1 ? "candidate_tie" : void 0 };
1947
- }
1948
2252
  function destinationExpressionShape(expr) {
1949
2253
  if (!expr) return void 0;
1950
- if (ts7.isIdentifier(expr)) return "identifier";
1951
- if (ts7.isPropertyAccessExpression(expr) || ts7.isElementAccessExpression(expr)) return "property_read";
1952
- if (ts7.isCallExpression(expr)) return "function_call";
1953
- if (ts7.isConditionalExpression(expr)) return "conditional";
1954
- if (ts7.isBinaryExpression(expr)) return "binary_expression";
1955
- if (ts7.isTemplateExpression(expr)) return "template_expression";
1956
- return ts7.SyntaxKind[expr.kind] ?? "expression";
2254
+ if (ts8.isIdentifier(expr)) return "identifier";
2255
+ if (ts8.isPropertyAccessExpression(expr) || ts8.isElementAccessExpression(expr)) return "property_read";
2256
+ if (ts8.isCallExpression(expr)) return "function_call";
2257
+ if (ts8.isConditionalExpression(expr)) return "conditional";
2258
+ if (ts8.isBinaryExpression(expr)) return "binary_expression";
2259
+ if (ts8.isTemplateExpression(expr)) return "template_expression";
2260
+ return ts8.SyntaxKind[expr.kind] ?? "expression";
1957
2261
  }
1958
2262
  function staticConditionalCandidates(expr, initializers) {
1959
- const resolved2 = expr && ts7.isIdentifier(expr) && initializers.has(expr.text) ? initializers.get(expr.text) : expr;
1960
- if (!resolved2 || !ts7.isConditionalExpression(resolved2)) return void 0;
2263
+ const resolved2 = expr && ts8.isIdentifier(expr) && initializers.has(expr.text) ? initializers.get(expr.text) : expr;
2264
+ if (!resolved2 || !ts8.isConditionalExpression(resolved2)) return void 0;
1961
2265
  const left = staticExpressionText(resolved2.whenTrue, initializers);
1962
2266
  const right = staticExpressionText(resolved2.whenFalse, initializers);
1963
2267
  if (!left || !right) return void 0;
@@ -1965,8 +2269,8 @@ function staticConditionalCandidates(expr, initializers) {
1965
2269
  }
1966
2270
  function propertyInitializer(object, key) {
1967
2271
  for (const property of object.properties) {
1968
- if (ts7.isPropertyAssignment(property) && nameOfProperty(property.name) === key) return property.initializer;
1969
- if (ts7.isShorthandPropertyAssignment(property) && property.name.text === key) return property.name;
2272
+ if (ts8.isPropertyAssignment(property) && nameOfProperty(property.name) === key) return property.initializer;
2273
+ if (ts8.isShorthandPropertyAssignment(property) && property.name.text === key) return property.name;
1970
2274
  }
1971
2275
  return void 0;
1972
2276
  }
@@ -1979,6 +2283,30 @@ function safeOperationName(value) {
1979
2283
  if (!value || !/^[A-Za-z_$][\w$]*(?:[./][A-Za-z_$][\w$]*)*$/.test(value)) return void 0;
1980
2284
  return operationPathFromStatic(value);
1981
2285
  }
2286
+ function wrapperSourceKind(sourceKind) {
2287
+ if (sourceKind.includes("const_alias")) return "const";
2288
+ if (sourceKind.includes("template")) return "template";
2289
+ if (sourceKind.includes("string_literal")) return "literal";
2290
+ return sourceKind.includes("conditional") ? "ambiguous" : "dynamic";
2291
+ }
2292
+ function literalPathSource(analysis) {
2293
+ if (analysis.status !== "static") return void 0;
2294
+ if (analysis.sourceKind.includes("const_alias")) return "same_scope_const_initializer";
2295
+ if (analysis.sourceKind.includes("no_substitution_template")) return "template";
2296
+ return analysis.sourceKind.includes("string_literal") ? "literal" : analysis.sourceKind;
2297
+ }
2298
+ function legacyPathCandidates(analysis) {
2299
+ if (analysis.candidateRawPaths.length < 2 && analysis.dynamicReassignments.length === 0)
2300
+ return void 0;
2301
+ return {
2302
+ candidatePaths: analysis.candidateRawPaths,
2303
+ normalizedCandidateOperations: analysis.candidateNormalizedOperationPaths.map((value) => value.replace(/^\//, "")),
2304
+ candidateSourceKind: analysis.sourceKind,
2305
+ candidateIdentifier: analysis.candidateIdentifier,
2306
+ hasDynamicAssignments: analysis.dynamicReassignments.length > 0,
2307
+ conservativeReason: analysis.dynamicReassignments.length > 0 ? "dynamic_assignment_observed" : "candidate_tie"
2308
+ };
2309
+ }
1982
2310
  function hasTemplatePlaceholder(value) {
1983
2311
  return /\$\{|%7B|%7D/i.test(value);
1984
2312
  }
@@ -2003,7 +2331,7 @@ function externalHttpEvidence(node, source) {
2003
2331
  const exprText = expr.getText(source);
2004
2332
  if (exprText === "useOrFetchDestination") {
2005
2333
  const objectArg = node.arguments[0];
2006
- if (objectArg && ts7.isObjectLiteralExpression(objectArg)) {
2334
+ if (objectArg && ts8.isObjectLiteralExpression(objectArg)) {
2007
2335
  const destination = destinationTargetFromExpression(propertyInitializer(objectArg, "destinationName"), node);
2008
2336
  return { externalTarget: destination ?? { kind: "unknown", dynamic: false }, classifier: "sap_destination_lookup", sourceCallShape: "useOrFetchDestination" };
2009
2337
  }
@@ -2011,13 +2339,13 @@ function externalHttpEvidence(node, source) {
2011
2339
  if (exprText === "executeHttpRequest") {
2012
2340
  const destination = destinationTargetFromExpression(node.arguments[0], node);
2013
2341
  const config = node.arguments[1];
2014
- const method = config && ts7.isObjectLiteralExpression(config) ? httpMethodFromObject(config, node) : void 0;
2015
- const url = config && ts7.isObjectLiteralExpression(config) ? urlTargetFromExpression(propertyInitializer(config, "url"), node) : { kind: "unknown", dynamic: false };
2342
+ const method = config && ts8.isObjectLiteralExpression(config) ? httpMethodFromObject(config, node) : void 0;
2343
+ const url = config && ts8.isObjectLiteralExpression(config) ? urlTargetFromExpression(propertyInitializer(config, "url"), node) : { kind: "unknown", dynamic: false };
2016
2344
  return { method, externalTarget: destination ? { ...url, destination } : url, classifier: "sap_execute_http_request", sourceCallShape: "executeHttpRequest" };
2017
2345
  }
2018
2346
  if (exprText === "axios") {
2019
2347
  const config = node.arguments[0];
2020
- if (config && ts7.isObjectLiteralExpression(config)) {
2348
+ if (config && ts8.isObjectLiteralExpression(config)) {
2021
2349
  const method = httpMethodFromObject(config, node);
2022
2350
  return { method, externalTarget: urlTargetFromExpression(propertyInitializer(config, "url"), node), classifier: "axios_config_call", sourceCallShape: "axios(config)" };
2023
2351
  }
@@ -2025,10 +2353,10 @@ function externalHttpEvidence(node, source) {
2025
2353
  }
2026
2354
  if (exprText === "fetch") {
2027
2355
  const init = node.arguments[1];
2028
- const method = init && ts7.isObjectLiteralExpression(init) ? httpMethodFromObject(init, node) : void 0;
2356
+ const method = init && ts8.isObjectLiteralExpression(init) ? httpMethodFromObject(init, node) : void 0;
2029
2357
  return { method, externalTarget: urlTargetFromExpression(node.arguments[0], node), classifier: "fetch_call", sourceCallShape: "fetch" };
2030
2358
  }
2031
- if (ts7.isPropertyAccessExpression(expr) && ["get", "post", "put", "patch", "delete", "head"].includes(expr.name.text) && expr.expression.getText(source) === "axios") {
2359
+ if (ts8.isPropertyAccessExpression(expr) && ["get", "post", "put", "patch", "delete", "head"].includes(expr.name.text) && expr.expression.getText(source) === "axios") {
2032
2360
  return { method: expr.name.text.toUpperCase(), externalTarget: urlTargetFromExpression(node.arguments[0], node), classifier: "axios_member_call", sourceCallShape: `axios.${expr.name.text}` };
2033
2361
  }
2034
2362
  return void 0;
@@ -2036,27 +2364,27 @@ function externalHttpEvidence(node, source) {
2036
2364
  function collectServiceVariables(source) {
2037
2365
  const vars = /* @__PURE__ */ new Set(["cds", "messaging", "messageClient", "eventClient"]);
2038
2366
  const visit = (node) => {
2039
- if (ts7.isVariableDeclaration(node) && ts7.isIdentifier(node.name) && node.initializer) {
2367
+ if (ts8.isVariableDeclaration(node) && ts8.isIdentifier(node.name) && node.initializer) {
2040
2368
  const text = node.initializer.getText(source);
2041
2369
  if (/cds\.connect\.(to|messaging)\s*\(/.test(text)) vars.add(node.name.text);
2042
2370
  }
2043
- ts7.forEachChild(node, visit);
2371
+ ts8.forEachChild(node, visit);
2044
2372
  };
2045
2373
  visit(source);
2046
2374
  return vars;
2047
2375
  }
2048
2376
  function receiverName(expr) {
2049
- if (ts7.isIdentifier(expr)) return expr.text;
2050
- if (ts7.isPropertyAccessExpression(expr)) return expr.getText(sourceOf(expr));
2377
+ if (ts8.isIdentifier(expr)) return expr.text;
2378
+ if (ts8.isPropertyAccessExpression(expr)) return expr.getText(sourceOf(expr));
2051
2379
  return void 0;
2052
2380
  }
2053
2381
  function sourceOf(node) {
2054
2382
  return node.getSourceFile();
2055
2383
  }
2056
2384
  function rootReceiverName(expr) {
2057
- if (ts7.isIdentifier(expr)) return expr.text;
2058
- if (ts7.isPropertyAccessExpression(expr)) return rootReceiverName(expr.expression);
2059
- if (ts7.isCallExpression(expr)) return rootReceiverName(expr.expression);
2385
+ if (ts8.isIdentifier(expr)) return expr.text;
2386
+ if (ts8.isPropertyAccessExpression(expr)) return rootReceiverName(expr.expression);
2387
+ if (ts8.isCallExpression(expr)) return rootReceiverName(expr.expression);
2060
2388
  return void 0;
2061
2389
  }
2062
2390
  function isSupportedEventReceiver(receiver, rootReceiver, serviceVariables) {
@@ -2073,38 +2401,38 @@ function collectWrapperSpecs(source) {
2073
2401
  const serviceVariables = collectServiceVariables(source);
2074
2402
  const calledNames = /* @__PURE__ */ new Set();
2075
2403
  const collectCalls = (node) => {
2076
- if (ts7.isCallExpression(node)) {
2077
- if (ts7.isIdentifier(node.expression)) calledNames.add(node.expression.text);
2078
- if (ts7.isCallExpression(node.expression) && ts7.isIdentifier(node.expression.expression)) calledNames.add(node.expression.expression.text);
2079
- }
2080
- ts7.forEachChild(node, collectCalls);
2404
+ if (ts8.isCallExpression(node) && ts8.isIdentifier(node.expression))
2405
+ calledNames.add(node.expression.text);
2406
+ if (ts8.isCallExpression(node) && ts8.isCallExpression(node.expression) && ts8.isIdentifier(node.expression.expression))
2407
+ calledNames.add(node.expression.expression.text);
2408
+ ts8.forEachChild(node, collectCalls);
2081
2409
  };
2082
2410
  collectCalls(source);
2083
2411
  const scanFunction = (name, fn) => {
2084
- if (!calledNames.has(name)) return;
2085
- const params = fn.parameters.map((param) => ts7.isIdentifier(param.name) ? param.name.text : void 0);
2412
+ if (!calledNames.has(name) && !isExportedWrapper(fn)) return;
2413
+ const params = fn.parameters.map((param) => ts8.isIdentifier(param.name) ? param.name.text : void 0);
2086
2414
  const sends = [];
2087
2415
  const visit = (node) => {
2088
- if (ts7.isCallExpression(node) && ts7.isPropertyAccessExpression(node.expression) && node.expression.name.text === "send" && ts7.isIdentifier(node.expression.expression)) {
2416
+ if (ts8.isCallExpression(node) && ts8.isPropertyAccessExpression(node.expression) && node.expression.name.text === "send" && ts8.isIdentifier(node.expression.expression)) {
2089
2417
  const objectArg = node.arguments[0];
2090
- if (objectArg && ts7.isObjectLiteralExpression(objectArg)) {
2418
+ if (objectArg && ts8.isObjectLiteralExpression(objectArg)) {
2091
2419
  const pathProp = propertyInitializer(objectArg, "path");
2092
2420
  const methodProp = propertyInitializer(objectArg, "method");
2093
- const pathName = pathProp && ts7.isIdentifier(pathProp) ? pathProp.text : void 0;
2094
- const methodName = methodProp && ts7.isIdentifier(methodProp) ? methodProp.text : void 0;
2421
+ const pathName = pathProp && ts8.isIdentifier(pathProp) ? pathProp.text : void 0;
2422
+ const methodName = methodProp && ts8.isIdentifier(methodProp) ? methodProp.text : void 0;
2095
2423
  const methodLiteral = resolveExpression(methodProp, node, "literal").value;
2096
2424
  if (pathName) sends.push({ client: node.expression.expression.text, path: pathName, method: methodName, methodLiteral, start: node.getStart(source), end: node.getEnd() });
2097
2425
  }
2098
2426
  }
2099
- if (ts7.isCallExpression(node) && ts7.isIdentifier(node.expression) && specs.has(node.expression.text)) {
2427
+ if (ts8.isCallExpression(node) && ts8.isIdentifier(node.expression) && specs.has(node.expression.text)) {
2100
2428
  const nested = specs.get(node.expression.text);
2101
2429
  const pathArg = nested ? node.arguments[nested.pathIndex] : void 0;
2102
2430
  const clientArg = nested?.clientIndex === void 0 ? void 0 : node.arguments[nested.clientIndex];
2103
- const pathName = pathArg && ts7.isIdentifier(pathArg) ? pathArg.text : void 0;
2104
- const clientName = clientArg && ts7.isIdentifier(clientArg) ? clientArg.text : nested?.clientName;
2431
+ const pathName = pathArg && ts8.isIdentifier(pathArg) ? pathArg.text : void 0;
2432
+ const clientName = clientArg && ts8.isIdentifier(clientArg) ? clientArg.text : nested?.clientName;
2105
2433
  if (nested && pathName && clientName) sends.push({ client: clientName, path: pathName, method: nested.methodName, methodLiteral: nested.methodLiteral, nestedWrapperFunction: node.expression.text, start: node.getStart(source), end: node.getEnd() });
2106
2434
  }
2107
- ts7.forEachChild(node, visit);
2435
+ ts8.forEachChild(node, visit);
2108
2436
  };
2109
2437
  visit(fn);
2110
2438
  if (sends.length !== 1) return;
@@ -2116,13 +2444,18 @@ function collectWrapperSpecs(source) {
2116
2444
  if (pathIndex >= 0 && (clientIndex >= 0 || capturesKnownClient)) specs.set(name, { clientIndex: clientIndex >= 0 ? clientIndex : void 0, clientName: clientIndex >= 0 ? void 0 : found.client, pathIndex, methodIndex: methodIndex >= 0 ? methodIndex : void 0, methodName: found.method, methodLiteral: found.methodLiteral, nestedWrapperFunction: found.nestedWrapperFunction, definitionLine: lineOf4(source.text, fn.getStart(source)), internalStart: found.start, internalEnd: found.end });
2117
2445
  };
2118
2446
  const visitTop = (node) => {
2119
- if (ts7.isFunctionDeclaration(node) && node.name) scanFunction(node.name.text, node);
2120
- if (ts7.isVariableDeclaration(node) && ts7.isIdentifier(node.name) && node.initializer && (ts7.isArrowFunction(node.initializer) || ts7.isFunctionExpression(node.initializer))) scanFunction(node.name.text, node.initializer);
2121
- ts7.forEachChild(node, visitTop);
2447
+ if (ts8.isFunctionDeclaration(node) && node.name) scanFunction(node.name.text, node);
2448
+ if (ts8.isVariableDeclaration(node) && ts8.isIdentifier(node.name) && node.initializer && (ts8.isArrowFunction(node.initializer) || ts8.isFunctionExpression(node.initializer))) scanFunction(node.name.text, node.initializer);
2449
+ ts8.forEachChild(node, visitTop);
2122
2450
  };
2123
2451
  visitTop(source);
2124
2452
  return specs;
2125
2453
  }
2454
+ function isExportedWrapper(fn) {
2455
+ const declaration = ts8.isFunctionDeclaration(fn) ? fn : ts8.isVariableDeclaration(fn.parent) ? fn.parent.parent.parent : void 0;
2456
+ if (!declaration || !ts8.canHaveModifiers(declaration)) return false;
2457
+ return ts8.getModifiers(declaration)?.some((modifier) => modifier.kind === ts8.SyntaxKind.ExportKeyword) ?? false;
2458
+ }
2126
2459
  function classifyOutboundCallsInSource(source, filePath) {
2127
2460
  const calls = [];
2128
2461
  const sourceFile = normalizePath(filePath);
@@ -2134,7 +2467,7 @@ function classifyOutboundCallsInSource(source, filePath) {
2134
2467
  calls.push({ node, fact: { ...fact, sourceFile, sourceLine: lineOf4(source.text, node.getStart(source)), confidence: fact.confidence ?? 0.8, evidence: parserEvidence(source, node, extra) } });
2135
2468
  };
2136
2469
  const visit = (node) => {
2137
- if (ts7.isCallExpression(node)) {
2470
+ if (ts8.isCallExpression(node)) {
2138
2471
  if (wrapperInternalRanges.some((range) => node.getStart(source) >= range.start && node.getEnd() <= range.end)) {
2139
2472
  return;
2140
2473
  }
@@ -2145,24 +2478,23 @@ function classifyOutboundCallsInSource(source, filePath) {
2145
2478
  const entity = arg ? queryEntityFromAst(arg, initializers) : void 0;
2146
2479
  const payload = arg?.getText(source) ?? "";
2147
2480
  add(node, { callType: "local_db_query", queryEntity: entity, payloadSummary: summarizeExpression(payload), confidence: entity ? 0.9 : 0.55, unresolvedReason: entity ? void 0 : queryWarning(payload) });
2148
- } else if (ts7.isPropertyAccessExpression(expr) && expr.name.text === "send" && (ts7.isIdentifier(expr.expression) || ts7.isPropertyAccessExpression(expr.expression))) {
2481
+ } else if (ts8.isPropertyAccessExpression(expr) && expr.name.text === "send" && (ts8.isIdentifier(expr.expression) || ts8.isPropertyAccessExpression(expr.expression))) {
2149
2482
  const objectArg = node.arguments[0];
2150
- if (objectArg && ts7.isObjectLiteralExpression(objectArg)) {
2483
+ if (objectArg && ts8.isObjectLiteralExpression(objectArg)) {
2151
2484
  const receiver = receiverName(expr.expression);
2152
2485
  const query = objectPropertyText(objectArg, "query");
2153
2486
  const method = stripQuotes(resolveExpression(propertyInitializer(objectArg, "method"), node, "literal").value ?? objectPropertyText(objectArg, "method") ?? "POST");
2154
2487
  const pathExpr = propertyInitializer(objectArg, "path") ?? propertyInitializer(objectArg, "event");
2155
- const resolvedPath = staticPathExpression(pathExpr, node);
2156
- const op = pathExpr ? resolvedPath.text ?? pathExpr.getText(source) : void 0;
2488
+ const pathAnalysis = analyzeOperationPath(pathExpr, node, method);
2489
+ const op = pathExpr ? operationPathExpression(pathAnalysis) ?? pathExpr.getText(source) : void 0;
2157
2490
  const shorthandPath = objectPropertyIsShorthand(objectArg, "path");
2158
- const candidateEvidence2 = pathExpr && ts7.isIdentifier(pathExpr) && !resolvedPath.text ? staticPathCandidates(pathExpr, node, method) : void 0;
2159
- const candidateOperationPath = candidateEvidence2 && !candidateEvidence2.hasDynamicAssignments && candidateEvidence2.normalizedCandidateOperations.length === 1 && candidateEvidence2.candidatePaths.length > 0 ? candidateEvidence2.candidatePaths[0] : void 0;
2160
- const operationPathExpr = operationPathFromStatic(resolvedPath.text) ?? candidateOperationPath;
2491
+ const operationPathExpr = operationPathExpression(pathAnalysis);
2161
2492
  const intent = classifyODataPathIntent(operationPathExpr, method);
2162
2493
  const entityCallTypes = { entity_mutation: "remote_entity_mutation", entity_delete: "remote_entity_delete", entity_media: "remote_entity_media", entity_candidate: "remote_entity_candidate" };
2163
2494
  const entityCallType = entityCallTypes[intent.kind];
2164
2495
  const isODataQueryRead = method.toUpperCase() === "GET" && ["entity_query", "entity_key_read", "entity_navigation_query"].includes(intent.kind);
2165
- add(node, { callType: query ? "remote_query" : entityCallType ?? (isODataQueryRead ? "remote_query" : "remote_action"), serviceVariableName: receiver, method, operationPathExpr, queryEntity: query ? extractQueryEntity(query) : isODataQueryRead ? intent.entitySegment : void 0, payloadSummary: summarizeExpression(objectArg.getText(source)), confidence: op || query ? 0.8 : 0.4, unresolvedReason: !query && pathExpr && !operationPathExpr ? "dynamic_operation_path_identifier" : void 0 }, { receiver, classifier: "service_client_send_object", operationPathExpression: shorthandPath ? op : void 0, rawPathExpression: resolvedPath.rawExpression, literalPathSource: resolvedPath.text ? resolvedPath.sourceKind === "const" ? "same_scope_const_initializer" : resolvedPath.sourceKind : void 0, odataPathIntent: operationPathExpr ? intent : void 0, staticPathCandidates: candidateEvidence2, parserWarning: !query && pathExpr && !operationPathExpr ? "dynamic_operation_path_identifier" : void 0 });
2496
+ const unresolvedReason = !query && pathExpr ? pathUnresolvedReason(pathAnalysis) : void 0;
2497
+ add(node, { callType: query ? "remote_query" : entityCallType ?? (isODataQueryRead ? "remote_query" : "remote_action"), serviceVariableName: receiver, method, operationPathExpr, queryEntity: query ? extractQueryEntity(query) : isODataQueryRead ? intent.entitySegment : void 0, payloadSummary: summarizeExpression(objectArg.getText(source)), confidence: op || query ? 0.8 : 0.4, unresolvedReason }, { receiver, classifier: "service_client_send_object", operationPathExpression: shorthandPath ? op : void 0, rawPathExpression: pathAnalysis.rawExpression, literalPathSource: literalPathSource(pathAnalysis), odataPathIntent: operationPathExpr ? intent : void 0, pathAnalysis, staticPathCandidates: legacyPathCandidates(pathAnalysis), parserWarning: unresolvedReason });
2166
2498
  } else {
2167
2499
  const receiver = receiverName(expr.expression);
2168
2500
  const rootReceiver = rootReceiverName(expr.expression);
@@ -2171,33 +2503,35 @@ function classifyOutboundCallsInSource(source, filePath) {
2171
2503
  const pathArg = node.arguments[1];
2172
2504
  const supported = method && supportedHttpMethods.has(method);
2173
2505
  if (receiver && supported && serviceVariables.has(rootReceiver ?? receiver)) {
2174
- const resolvedPath = staticPathExpression(pathArg, node);
2175
- const operationPathExpr = operationPathFromStatic(resolvedPath.text);
2506
+ const pathAnalysis = analyzeOperationPath(pathArg, node, method);
2507
+ const operationPathExpr = operationPathExpression(pathAnalysis);
2176
2508
  const intent = classifyODataPathIntent(operationPathExpr, method);
2177
- add(node, { callType: "remote_action", serviceVariableName: rootReceiver ?? receiver, method, operationPathExpr, payloadSummary: summarizeExpression(node.getText(source)), confidence: operationPathExpr ? 0.8 : 0.45, unresolvedReason: operationPathExpr ? void 0 : "dynamic_operation_path_identifier" }, { receiver, rootReceiver, classifier: "service_client_send_method_path", rawPathExpression: resolvedPath.rawExpression, literalPathSource: resolvedPath.text ? resolvedPath.sourceKind === "const" ? "same_scope_const_initializer" : resolvedPath.sourceKind : void 0, odataPathIntent: operationPathExpr ? intent : void 0, parserWarning: operationPathExpr ? void 0 : "dynamic_operation_path_identifier" });
2509
+ const unresolvedReason = pathUnresolvedReason(pathAnalysis);
2510
+ add(node, { callType: "remote_action", serviceVariableName: rootReceiver ?? receiver, method, operationPathExpr, payloadSummary: summarizeExpression(node.getText(source)), confidence: operationPathExpr ? 0.8 : 0.45, unresolvedReason }, { receiver, rootReceiver, classifier: "service_client_send_method_path", rawPathExpression: pathAnalysis.rawExpression, literalPathSource: literalPathSource(pathAnalysis), odataPathIntent: operationPathExpr ? intent : void 0, pathAnalysis, staticPathCandidates: legacyPathCandidates(pathAnalysis), parserWarning: unresolvedReason });
2178
2511
  } else if (receiver && serviceVariables.has(rootReceiver ?? receiver)) {
2179
2512
  const operationPathExpr = safeOperationName(firstArg2.value);
2180
2513
  add(node, { callType: "remote_action", serviceVariableName: rootReceiver ?? receiver, operationPathExpr, payloadSummary: summarizeExpression(node.getText(source)), confidence: operationPathExpr ? 0.75 : 0.35, unresolvedReason: operationPathExpr ? void 0 : "unsupported_cap_send_signature" }, { receiver, rootReceiver, classifier: operationPathExpr ? "service_client_send_operation_event" : "service_client_send_unsupported_signature", rawOperationExpression: firstArg2.rawExpression, literalOperationSource: firstArg2.value ? firstArg2.sourceKind : void 0, parserWarning: operationPathExpr ? void 0 : "unsupported_cap_send_signature" });
2181
2514
  }
2182
2515
  }
2183
- } else if (ts7.isCallExpression(expr) && ts7.isIdentifier(expr.expression) && wrapperSpecs.has(expr.expression.text) || ts7.isIdentifier(expr) && wrapperSpecs.has(expr.text)) {
2184
- const wrapperName = ts7.isIdentifier(expr) ? expr.text : ts7.isCallExpression(expr) && ts7.isIdentifier(expr.expression) ? expr.expression.text : "";
2185
- const wrapperArgs = ts7.isIdentifier(expr) ? node.arguments : ts7.isCallExpression(expr) ? expr.arguments : node.arguments;
2516
+ } else if (ts8.isCallExpression(expr) && ts8.isIdentifier(expr.expression) && wrapperSpecs.has(expr.expression.text) || ts8.isIdentifier(expr) && wrapperSpecs.has(expr.text)) {
2517
+ const wrapperName = ts8.isIdentifier(expr) ? expr.text : ts8.isCallExpression(expr) && ts8.isIdentifier(expr.expression) ? expr.expression.text : "";
2518
+ const wrapperArgs = ts8.isIdentifier(expr) ? node.arguments : ts8.isCallExpression(expr) ? expr.arguments : node.arguments;
2186
2519
  const spec = wrapperSpecs.get(wrapperName);
2187
2520
  const clientArg = spec?.clientIndex === void 0 ? void 0 : wrapperArgs[spec.clientIndex];
2188
2521
  const pathArg = spec ? wrapperArgs[spec.pathIndex] : void 0;
2189
2522
  const methodArg = spec?.methodIndex === void 0 ? void 0 : wrapperArgs[spec.methodIndex];
2190
- const receiver = clientArg && ts7.isIdentifier(clientArg) ? clientArg.text : spec?.clientName;
2523
+ const receiver = clientArg && ts8.isIdentifier(clientArg) ? clientArg.text : spec?.clientName;
2191
2524
  const method = stripQuotes(resolveExpression(methodArg, node, "literal").value ?? spec?.methodLiteral ?? "POST");
2192
- const resolvedPath = staticPathExpression(pathArg, node);
2193
- const operationPathExpr = operationPathFromStatic(resolvedPath.text);
2525
+ const pathAnalysis = analyzeOperationPath(pathArg, node, method);
2526
+ const operationPathExpr = operationPathExpression(pathAnalysis);
2194
2527
  const normalizedOperationPath = operationPathExpr ? classifyODataPathIntent(operationPathExpr, method).topLevelOperationName : void 0;
2528
+ const unresolvedReason = pathUnresolvedReason(pathAnalysis);
2195
2529
  if (spec && receiver && operationPathExpr) {
2196
- add(node, { callType: "remote_action", serviceVariableName: receiver, method, operationPathExpr, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.75 }, { receiver, classifier: resolvedPath.sourceKind === "literal" ? "higher_order_wrapper_literal_path" : "higher_order_wrapper_static_path", wrapperFunction: wrapperName, nestedWrapperFunction: spec.nestedWrapperFunction, wrapperDefinitionLine: spec.definitionLine, callerLine: lineOf4(source.text, node.getStart(source)), wrapperPathSourceKind: resolvedPath.sourceKind, rawPathExpression: resolvedPath.rawExpression, normalizedOperationPath, literalPathSource: resolvedPath.sourceKind === "const" ? "same_scope_const_initializer" : `wrapper_call_${resolvedPath.sourceKind}`, literalCallerArgumentDetected: true });
2530
+ add(node, { callType: "remote_action", serviceVariableName: receiver, method, operationPathExpr, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.75, unresolvedReason }, { receiver, classifier: pathAnalysis.sourceKind.includes("string_literal") ? "higher_order_wrapper_literal_path" : "higher_order_wrapper_static_path", wrapperFunction: wrapperName, nestedWrapperFunction: spec.nestedWrapperFunction, wrapperDefinitionLine: spec.definitionLine, callerLine: lineOf4(source.text, node.getStart(source)), wrapperPathSourceKind: wrapperSourceKind(pathAnalysis.sourceKind), rawPathExpression: pathAnalysis.rawExpression, normalizedOperationPath, literalPathSource: pathAnalysis.sourceKind.includes("const_alias") ? "same_scope_const_initializer" : `wrapper_call_${wrapperSourceKind(pathAnalysis.sourceKind)}`, literalCallerArgumentDetected: true, pathAnalysis });
2197
2531
  } else if (spec && receiver) {
2198
- add(node, { callType: "remote_action", serviceVariableName: receiver, method, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.45, unresolvedReason: "dynamic_operation_path_identifier" }, { receiver, classifier: "higher_order_wrapper_dynamic_path", wrapperFunction: wrapperName, wrapperDefinitionLine: spec.definitionLine, callerLine: lineOf4(source.text, node.getStart(source)), wrapperPathSourceKind: resolvedPath.sourceKind, rawPathExpression: resolvedPath.rawExpression, parserWarning: "dynamic_operation_path_identifier" });
2532
+ add(node, { callType: "remote_action", serviceVariableName: receiver, method, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.45, unresolvedReason }, { receiver, classifier: pathAnalysis.status === "ambiguous" ? "higher_order_wrapper_ambiguous_path" : "higher_order_wrapper_dynamic_path", wrapperFunction: wrapperName, wrapperDefinitionLine: spec.definitionLine, callerLine: lineOf4(source.text, node.getStart(source)), wrapperPathSourceKind: wrapperSourceKind(pathAnalysis.sourceKind), rawPathExpression: pathAnalysis.rawExpression, pathAnalysis, parserWarning: unresolvedReason });
2199
2533
  }
2200
- } else if (ts7.isPropertyAccessExpression(expr) && ["emit", "publish", "on"].includes(expr.name.text)) {
2534
+ } else if (ts8.isPropertyAccessExpression(expr) && ["emit", "publish", "on"].includes(expr.name.text)) {
2201
2535
  const receiver = receiverName(expr.expression);
2202
2536
  const rootReceiver = rootReceiverName(expr.expression);
2203
2537
  if (isSupportedEventReceiver(receiver, rootReceiver, serviceVariables)) {
@@ -2213,7 +2547,7 @@ function classifyOutboundCallsInSource(source, filePath) {
2213
2547
  }
2214
2548
  }
2215
2549
  }
2216
- ts7.forEachChild(node, visit);
2550
+ ts8.forEachChild(node, visit);
2217
2551
  };
2218
2552
  visit(source);
2219
2553
  return calls;
@@ -2226,21 +2560,21 @@ function containsSupportedOutboundCall(node) {
2226
2560
  }
2227
2561
  async function parseOutboundCalls(repoPath, filePath) {
2228
2562
  const text = await fs7.readFile(path10.join(repoPath, filePath), "utf8");
2229
- const source = ts7.createSourceFile(filePath, text, ts7.ScriptTarget.Latest, true, filePath.endsWith(".ts") ? ts7.ScriptKind.TS : ts7.ScriptKind.JS);
2563
+ const source = ts8.createSourceFile(filePath, text, ts8.ScriptTarget.Latest, true, filePath.endsWith(".ts") ? ts8.ScriptKind.TS : ts8.ScriptKind.JS);
2230
2564
  const bindingNames = new Set((await parseServiceBindings(repoPath, filePath)).map((binding) => binding.variableName));
2231
2565
  const importedWrappers = await parseImportedWrapperCalls(repoPath, filePath, source, bindingNames);
2232
2566
  return [...classifyOutboundCallsInSource(source, filePath).map((call) => call.fact), ...importedWrappers, ...parseLocalServiceCalls(text, filePath)];
2233
2567
  }
2234
2568
  function parseLocalServiceCalls(text, filePath) {
2235
- const source = ts7.createSourceFile(filePath, text, ts7.ScriptTarget.Latest, true, filePath.endsWith(".ts") ? ts7.ScriptKind.TS : ts7.ScriptKind.JS);
2569
+ const source = ts8.createSourceFile(filePath, text, ts8.ScriptTarget.Latest, true, filePath.endsWith(".ts") ? ts8.ScriptKind.TS : ts8.ScriptKind.JS);
2236
2570
  const aliases = /* @__PURE__ */ new Map();
2237
2571
  const calls = [];
2238
2572
  const visit = (node) => {
2239
- if (ts7.isVariableDeclaration(node) && ts7.isIdentifier(node.name) && node.initializer) {
2573
+ if (ts8.isVariableDeclaration(node) && ts8.isIdentifier(node.name) && node.initializer) {
2240
2574
  const origin = serviceLookup(node.initializer, aliases);
2241
2575
  if (origin) aliases.set(node.name.text, { ...origin, chain: [...origin.chain, node.name.text] });
2242
2576
  }
2243
- if (ts7.isCallExpression(node)) {
2577
+ if (ts8.isCallExpression(node)) {
2244
2578
  const parsed = serviceOperationCall(node, aliases);
2245
2579
  if (parsed && parsed.operation !== "entities") calls.push({
2246
2580
  callType: "local_service_call",
@@ -2263,20 +2597,20 @@ function parseLocalServiceCalls(text, filePath) {
2263
2597
  })
2264
2598
  });
2265
2599
  }
2266
- ts7.forEachChild(node, visit);
2600
+ ts8.forEachChild(node, visit);
2267
2601
  };
2268
2602
  visit(source);
2269
2603
  return calls;
2270
2604
  }
2271
2605
  function serviceLookup(expr, aliases) {
2272
- if (ts7.isIdentifier(expr)) return aliases.get(expr.text);
2273
- if (ts7.isPropertyAccessExpression(expr) && expr.expression.getText() === "cds.services") return { service: expr.name.text, lookup: expr.getText(), chain: [expr.getText()] };
2274
- if (ts7.isElementAccessExpression(expr) && expr.expression.getText() === "cds.services" && ts7.isStringLiteral(expr.argumentExpression)) return { service: expr.argumentExpression.text, lookup: expr.getText(), chain: [expr.getText()] };
2606
+ if (ts8.isIdentifier(expr)) return aliases.get(expr.text);
2607
+ if (ts8.isPropertyAccessExpression(expr) && expr.expression.getText() === "cds.services") return { service: expr.name.text, lookup: expr.getText(), chain: [expr.getText()] };
2608
+ if (ts8.isElementAccessExpression(expr) && expr.expression.getText() === "cds.services" && ts8.isStringLiteral(expr.argumentExpression)) return { service: expr.argumentExpression.text, lookup: expr.getText(), chain: [expr.getText()] };
2275
2609
  return void 0;
2276
2610
  }
2277
2611
  function serviceOperationCall(node, aliases) {
2278
2612
  const expr = node.expression;
2279
- if (!ts7.isPropertyAccessExpression(expr)) return void 0;
2613
+ if (!ts8.isPropertyAccessExpression(expr)) return void 0;
2280
2614
  const origin = serviceLookup(expr.expression, aliases);
2281
2615
  if (!origin) return void 0;
2282
2616
  if (expr.name.text === "send") {
@@ -2710,46 +3044,6 @@ function linkHelperPackages(db, workspaceId, generation) {
2710
3044
  return summary;
2711
3045
  }
2712
3046
 
2713
- // src/linker/operation-decorator-normalizer.ts
2714
- function lowerFirst(value) {
2715
- return value ? `${value[0]?.toLowerCase() ?? ""}${value.slice(1)}` : value;
2716
- }
2717
- function normalizedOperationName(value) {
2718
- return value.replace(/^\//, "");
2719
- }
2720
- function clean(value) {
2721
- return value.replace(/^[`'"]|[`'"]$/g, "");
2722
- }
2723
- function generatedFromConstantName(value) {
2724
- for (const prefix of ["Action", "Func"]) {
2725
- if (value.startsWith(prefix) && value.length > prefix.length && /^[A-Z]/.test(value.slice(prefix.length))) return lowerFirst(value.slice(prefix.length));
2726
- }
2727
- return void 0;
2728
- }
2729
- function resolved(value, raw) {
2730
- const literal2 = clean(value);
2731
- const generated = generatedFromConstantName(literal2);
2732
- return { status: "resolved", operationName: generated ?? normalizedOperationName(literal2), raw };
2733
- }
2734
- function normalizeDecoratorOperationSignal(value, raw, candidateOperation) {
2735
- if (value) return resolved(value, raw);
2736
- if (!raw || raw.trim().length === 0) return { status: "none", raw };
2737
- const expression = raw.trim();
2738
- const nameMatch = /(?:^|\.)(Action[A-Z][\w$]*|Func[A-Z][\w$]*)\.name$/.exec(expression);
2739
- if (nameMatch?.[1]) return resolved(nameMatch[1], expression);
2740
- const stringMatch = /^String\(([A-Za-z_$][\w$]*)\)$/.exec(expression);
2741
- if (stringMatch?.[1]) {
2742
- const identifier = stringMatch[1];
2743
- const generated = generatedFromConstantName(identifier);
2744
- const normalizedCandidate = candidateOperation ? normalizedOperationName(candidateOperation) : void 0;
2745
- if (generated) return { status: "resolved", operationName: generated, raw: expression };
2746
- if (normalizedCandidate && identifier === normalizedCandidate) return { status: "resolved", operationName: identifier, raw: expression };
2747
- return { status: "unsupported", raw: expression, reason: "string_wrapper_identifier_not_resolved" };
2748
- }
2749
- if (/^[`'"]/.test(expression) && !/[`'"]$/.test(expression)) return { status: "malformed", raw: expression, reason: "unterminated_literal" };
2750
- return { status: "unsupported", raw: expression, reason: "unsupported_decorator_expression" };
2751
- }
2752
-
2753
3047
  // src/linker/cross-repo-linker.ts
2754
3048
  function linkWorkspace(db, workspaceId, vars = {}) {
2755
3049
  return db.transaction(() => {
@@ -2807,7 +3101,36 @@ function insertCallEdge(db, workspaceId, call, vars, generation) {
2807
3101
  const operationLikeRemoteEntity = isRemoteEntityCall && Boolean(op) && credibleOperationSignal && (!strongEntitySignal || indexedOperationCandidateCount > 0);
2808
3102
  const isOperationCall = operationLikeRemoteEntity || (callType === "remote_action" || callType === "local_service_call" || callType === "remote_query" && Boolean(op));
2809
3103
  const resolution = isOperationCall ? resolveOperation(db, { servicePath, operationPath: op, serviceName: call.local_service_name, repoId: callType === "local_service_call" ? Number(call.repo_id) : void 0, alias: applyVariables(call.aliasExpr ?? call.alias, vars), destination: destination ? applyVariables(destination, vars) : void 0, isDynamic, hasExplicitOverride: Object.keys(vars).length > 0 || callType === "local_service_call" }, workspaceId) : { status: "unresolved", candidates: [], reasons: [] };
2810
- const evidence = { ...callEvidence(call, resolution, servicePath, op, destination ? applyVariables(destination, vars) : void 0, normalized, intent), indexedOperationCandidateCount, parserCallType: callType };
3104
+ const evidence = {
3105
+ ...callEvidence(call, resolution, servicePath, op, destination ? applyVariables(destination, vars) : void 0, normalized, intent),
3106
+ indexedOperationCandidateCount,
3107
+ parserCallType: callType,
3108
+ entityOperationPrecedence: operationPrecedence(
3109
+ callType,
3110
+ intent,
3111
+ indexedOperationCandidateCount,
3112
+ Boolean(resolution.target)
3113
+ )
3114
+ };
3115
+ const pathAnalysis = objectValue(objectJson(call.evidence_json)?.pathAnalysis);
3116
+ if (callType === "remote_action" && pathAnalysis?.status === "ambiguous") {
3117
+ const candidatePaths = Array.isArray(pathAnalysis.candidateRawPaths) ? pathAnalysis.candidateRawPaths : [];
3118
+ db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(
3119
+ workspaceId,
3120
+ "UNRESOLVED_EDGE",
3121
+ "ambiguous",
3122
+ "call",
3123
+ String(call.id),
3124
+ "operation_candidates",
3125
+ candidatePaths.join(","),
3126
+ Number(call.confidence ?? 0.5),
3127
+ JSON.stringify(evidence),
3128
+ 0,
3129
+ "Ambiguous operation path candidates require explicit disambiguation",
3130
+ generation
3131
+ );
3132
+ return { status: "ambiguous", callType };
3133
+ }
2811
3134
  if (isRemoteEntityCall && (resolution.target || resolution.candidates.length > 0 || resolution.status === "dynamic")) {
2812
3135
  if (resolution.target) {
2813
3136
  db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "REMOTE_CALL_RESOLVES_TO_OPERATION", "resolved", "call", String(call.id), "operation", String(resolution.target.operationId), resolution.target.score, JSON.stringify({ ...evidence, operationEntityPrecedence: "indexed_operation_over_parser_entity" }), 0, generation);
@@ -2853,6 +3176,34 @@ function operationCandidateCount(db, workspaceId, operationPath, operationName)
2853
3176
  const row = db.prepare(`SELECT COUNT(*) count FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (o.operation_path=? OR o.operation_path=? OR o.operation_name=?)`).get(workspaceId, operationPath, normalizedName ? `/${normalizedName}` : operationPath, normalizedName);
2854
3177
  return Number(row?.count ?? 0);
2855
3178
  }
3179
+ function operationPrecedence(callType, intent, indexedOperationCandidateCount, resolvedOperation) {
3180
+ if (resolvedOperation) {
3181
+ return {
3182
+ decision: "operation",
3183
+ reason: "indexed_operation_with_strong_service_context",
3184
+ indexedOperationCandidateCount
3185
+ };
3186
+ }
3187
+ if (callType === "remote_action" && intent.kind === "operation_invocation") {
3188
+ return {
3189
+ decision: "operation_candidate",
3190
+ rejectionReason: indexedOperationCandidateCount > 0 ? "indexed_candidates_lack_unique_strong_service_context" : "no_indexed_operation_candidate",
3191
+ indexedOperationCandidateCount
3192
+ };
3193
+ }
3194
+ if (intent.kind.startsWith("entity_")) {
3195
+ return {
3196
+ decision: "entity",
3197
+ rejectionReason: indexedOperationCandidateCount > 0 ? "entity_shape_has_precedence_without_resolved_operation_context" : "entity_shape_has_no_indexed_operation_evidence",
3198
+ indexedOperationCandidateCount
3199
+ };
3200
+ }
3201
+ return {
3202
+ decision: "unresolved",
3203
+ rejectionReason: "path_has_no_safe_entity_or_operation_precedence",
3204
+ indexedOperationCandidateCount
3205
+ };
3206
+ }
2856
3207
  function unresolvedOperationReason(resolution) {
2857
3208
  if (resolution.status === "dynamic") return `Dynamic target requires runtime variable overrides: ${(resolution.reasons.length ? resolution.reasons : ["missing runtime variables"]).join(", ")}`;
2858
3209
  if (resolution.candidates.length === 0) return "No indexed target operation matched";
@@ -2875,7 +3226,7 @@ function objectJson(value) {
2875
3226
  }
2876
3227
  function callEvidence(call, resolution, servicePath, op, destination, normalized, odataPathIntent) {
2877
3228
  const bindingHasDynamicExpression = Boolean(Number(call.isDynamic ?? 0));
2878
- const routingPlaceholderKeys = placeholderKeys([servicePath, destination, stringValue2(call.aliasExpr), stringValue2(call.alias)]);
3229
+ const routingPlaceholderKeys = placeholderKeys([servicePath, destination, stringValue3(call.aliasExpr), stringValue3(call.alias)]);
2879
3230
  return { sourceFile: call.source_file, sourceLine: call.source_line, file: call.source_file, line: call.source_line, callId: call.id, repo: call.repoName, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination, servicePath, operationPath: op, rawOperationPath: normalized?.wasInvocation ? normalized.rawOperationPath : odataPathIntent?.rawPath, normalizedOperationPath: normalized?.wasInvocation ? normalized.normalizedOperationPath : void 0, invocationArguments: normalized?.wasInvocation ? normalized.invocationArguments : void 0, invocationArgumentPlaceholderKeys: normalized?.invocationArgumentPlaceholderKeys.length ? normalized.invocationArgumentPlaceholderKeys : void 0, routingPlaceholderKeys: routingPlaceholderKeys.length ? routingPlaceholderKeys : void 0, odataOperationNormalizationReason: normalized?.normalizationReason, odataOperationNormalizationRejectedReason: normalized?.normalizationRejectedReason, localServiceName: call.local_service_name, localServiceLookup: call.local_service_lookup, aliasChain: parseJson(call.alias_chain_json), transport: call.call_type === "local_service_call" ? "local" : void 0, targetRepo: resolution.target?.repoName, targetServicePath: resolution.target?.servicePath, targetOperationPath: resolution.target?.operationPath, targetOperation: resolution.target?.operationName, helperChain: parseJson(call.helperChainJson), candidates: resolution.candidates, candidateScores: compactCandidateScores(resolution.candidates), candidateCount: resolution.candidates.length, resolutionStatus: resolution.status, resolutionReasons: resolution.reasons, odataPathIntent, queryStringPresent: odataPathIntent?.hasQueryString || void 0, queryPlaceholderKeys: odataPathIntent?.placeholderKeys.length ? odataPathIntent.placeholderKeys : void 0, bindingHasDynamicExpression: bindingHasDynamicExpression || void 0, outboundEvidence: objectJson(call.evidence_json), analysisCompleteness: call.unresolved_reason ? "partial" : "complete", parserWarning: call.unresolved_reason ? { code: "parser_warning", message: call.unresolved_reason } : void 0 };
2880
3231
  }
2881
3232
  function compactCandidateScores(candidates) {
@@ -2892,7 +3243,7 @@ function placeholderKeys(values) {
2892
3243
  function objectValue(value) {
2893
3244
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2894
3245
  }
2895
- function stringValue2(value) {
3246
+ function stringValue3(value) {
2896
3247
  return typeof value === "string" ? value : void 0;
2897
3248
  }
2898
3249
  function linkImplementations(db, workspaceId, generation) {
@@ -2911,7 +3262,7 @@ function linkImplementations(db, workspaceId, generation) {
2911
3262
  const duplicateFamilies = duplicatePackageFamilies(accepted);
2912
3263
  const duplicatePackageAmbiguous = duplicateFamilies.length > 0 && !accepted.some(hasDirectOwnershipEvidence);
2913
3264
  const selected = duplicatePackageAmbiguous ? accepted : winners;
2914
- const unique = !duplicatePackageAmbiguous && winners.length === 1 ? winners[0] : void 0;
3265
+ const unique2 = !duplicatePackageAmbiguous && winners.length === 1 ? winners[0] : void 0;
2915
3266
  const ambiguityReasons = duplicatePackageAmbiguous ? ["duplicate_package_name_candidates"] : winners.length > 1 ? ["multiple_equal_score_implementation_candidates"] : [];
2916
3267
  const evidence = {
2917
3268
  servicePath: operation.servicePath,
@@ -2925,16 +3276,16 @@ function linkImplementations(db, workspaceId, generation) {
2925
3276
  candidateFamilies: duplicateFamilies,
2926
3277
  candidates: candidates.map((candidate, index) => candidateEvidence(candidate, index + 1))
2927
3278
  };
2928
- const evidenceWithHints = unique ? evidence : { ...evidence, implementationHintSuggestions: implementationHintSuggestions(evidence) };
3279
+ const evidenceWithHints = unique2 ? evidence : { ...evidence, implementationHintSuggestions: implementationHintSuggestions(evidence) };
2929
3280
  if (accepted.length === 0) {
2930
3281
  db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", "unresolved", "operation", graphId(operation.operationId), "handler_method_candidates", candidates.map((row) => graphId(row.methodId)).join(","), 0, JSON.stringify(evidenceWithHints), 0, "No implementation candidate passed policy", generation);
2931
3282
  edgeCount += 1;
2932
3283
  unresolvedCount += 1;
2933
3284
  continue;
2934
3285
  }
2935
- db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", unique ? "resolved" : "ambiguous", "operation", graphId(operation.operationId), unique ? "handler_method" : "handler_method_candidates", unique ? graphId(unique.methodId) : selected.map((row) => graphId(row.methodId)).join(","), unique ? 0.95 : 0.5, JSON.stringify(evidenceWithHints), 0, unique ? null : "Ambiguous registered handler implementation candidates", generation);
3286
+ db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", unique2 ? "resolved" : "ambiguous", "operation", graphId(operation.operationId), unique2 ? "handler_method" : "handler_method_candidates", unique2 ? graphId(unique2.methodId) : selected.map((row) => graphId(row.methodId)).join(","), unique2 ? 0.95 : 0.5, JSON.stringify(evidenceWithHints), 0, unique2 ? null : "Ambiguous registered handler implementation candidates", generation);
2936
3287
  edgeCount += 1;
2937
- if (unique) resolvedCount += 1;
3288
+ if (unique2) resolvedCount += 1;
2938
3289
  else ambiguousCount += 1;
2939
3290
  }
2940
3291
  return { edgeCount, resolvedCount, ambiguousCount, unresolvedCount };
@@ -2947,13 +3298,54 @@ function implementationContextForOperation(db, operation) {
2947
3298
  }
2948
3299
  function rankedImplementationCandidates(db, workspaceId, operation) {
2949
3300
  const rows2 = implementationCandidates(db, workspaceId, operation);
2950
- return deduplicateCandidates(rows2.map((row) => scoreImplementationCandidate(row, operation))).sort((a, b) => b.score - a.score || String(a.className).localeCompare(String(b.className)) || a.methodId - b.methodId);
3301
+ const invalid = rows2.filter((row) => !validRegistrationPair(row));
3302
+ recordRegistrationInvariantDiagnostics(db, invalid);
3303
+ return deduplicateCandidates(
3304
+ rows2.filter(validRegistrationPair).map((row) => scoreImplementationCandidate(row, operation))
3305
+ ).sort((a, b) => b.score - a.score || String(a.className).localeCompare(String(b.className)) || a.methodId - b.methodId);
3306
+ }
3307
+ function validRegistrationPair(row) {
3308
+ if (row.registrationHandlerClassId === null || row.registrationHandlerClassId === void 0)
3309
+ return registrationPairingStrategy(row) !== "unproven";
3310
+ return Number(row.registrationHandlerClassId) === Number(row.classId);
3311
+ }
3312
+ function registrationPairingStrategy(row) {
3313
+ if (row.registrationHandlerClassId !== null && row.registrationHandlerClassId !== void 0)
3314
+ return "exact_handler_class_id";
3315
+ if (Number(row.applicationRepoId) === Number(row.handlerRepoId))
3316
+ return "same_repository_class_name_fallback";
3317
+ const source = stringValue3(row.importSource);
3318
+ const separator = source?.lastIndexOf("#") ?? -1;
3319
+ if (!source || separator <= 0) return "unproven";
3320
+ const moduleName = source.slice(0, separator);
3321
+ const importedName = source.slice(separator + 1);
3322
+ const matchesClass = importedName === row.className || importedName === "default" && row.registrationClassName === row.className;
3323
+ return moduleName === row.handlerPackage && matchesClass ? "explicit_package_import" : "unproven";
3324
+ }
3325
+ function recordRegistrationInvariantDiagnostics(db, rows2) {
3326
+ const insert = db.prepare(`INSERT INTO diagnostics(repo_id,severity,code,message,source_file,source_line)
3327
+ SELECT ?,'error','handler_registration_class_mismatch',
3328
+ 'Implementation candidate registration did not match its persisted handler class id',?,?
3329
+ WHERE NOT EXISTS (
3330
+ SELECT 1 FROM diagnostics
3331
+ WHERE repo_id=? AND code='handler_registration_class_mismatch'
3332
+ AND source_file=? AND source_line=?
3333
+ )`);
3334
+ for (const row of rows2)
3335
+ insert.run(
3336
+ row.applicationRepoId,
3337
+ row.registrationFile,
3338
+ row.registrationLine,
3339
+ row.applicationRepoId,
3340
+ row.registrationFile,
3341
+ row.registrationLine
3342
+ );
2951
3343
  }
2952
3344
  function deduplicateCandidates(rows2) {
2953
3345
  const merged = /* @__PURE__ */ new Map();
2954
3346
  for (const row of rows2) {
2955
3347
  const key = [row.methodId, row.classId, row.handlerRepoId].join(":");
2956
- const registration = { file: row.registrationFile, line: row.registrationLine, kind: row.registrationKind, importSource: row.importSource };
3348
+ const registration = registrationEvidence(row);
2957
3349
  const existing = merged.get(key);
2958
3350
  if (!existing) {
2959
3351
  merged.set(key, { ...row, registrations: [registration] });
@@ -2967,6 +3359,17 @@ function deduplicateCandidates(rows2) {
2967
3359
  }
2968
3360
  return [...merged.values()];
2969
3361
  }
3362
+ function registrationEvidence(row) {
3363
+ return {
3364
+ id: row.registrationId,
3365
+ handlerClassId: row.registrationHandlerClassId,
3366
+ file: row.registrationFile,
3367
+ line: row.registrationLine,
3368
+ kind: row.registrationKind,
3369
+ importSource: row.importSource,
3370
+ pairingStrategy: registrationPairingStrategy(row)
3371
+ };
3372
+ }
2970
3373
  function uniqueRegistrations(rows2) {
2971
3374
  const seen = /* @__PURE__ */ new Set();
2972
3375
  return rows2.filter((row) => {
@@ -2995,10 +3398,14 @@ function implementationCandidates(db, workspaceId, operation) {
2995
3398
  hm.method_name methodName,
2996
3399
  hm.decorator_value decoratorValue,
2997
3400
  hm.decorator_raw_expression decoratorRawExpression,
3401
+ hm.decorator_resolution_json decoratorResolutionJson,
2998
3402
  hc.id classId,
2999
3403
  hc.class_name className,
3000
3404
  hc.source_file sourceFile,
3001
3405
  hc.source_line sourceLine,
3406
+ hr.id registrationId,
3407
+ hr.handler_class_id registrationHandlerClassId,
3408
+ hr.class_name registrationClassName,
3002
3409
  hr.repo_id applicationRepoId,
3003
3410
  hr.registration_file registrationFile,
3004
3411
  hr.registration_line registrationLine,
@@ -3021,14 +3428,36 @@ function implementationCandidates(db, workspaceId, operation) {
3021
3428
  CASE WHEN appRepo.id=handlerRepo.id THEN 1 ELSE 0 END sameRepoRegistration,
3022
3429
  CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id AND localService.service_path=?) THEN 1 ELSE 0 END localServicePathMatch,
3023
3430
  CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id) THEN 1 ELSE 0 END applicationHasLocalServices,
3024
- CASE WHEN EXISTS (SELECT 1 FROM handler_registrations localReg JOIN handler_classes localClass ON (localClass.id=localReg.handler_class_id OR localClass.class_name=localReg.class_name) JOIN handler_methods localMethod ON localMethod.handler_class_id=localClass.id WHERE localReg.repo_id=appRepo.id AND (localMethod.decorator_value=? OR localMethod.decorator_value=? OR localMethod.method_name=? OR localMethod.decorator_raw_expression LIKE ?)) THEN 1 ELSE 0 END applicationHasLocalRegistrationForOperation,
3431
+ CASE WHEN EXISTS (SELECT 1 FROM handler_registrations localReg JOIN handler_classes localClass ON ((localReg.handler_class_id IS NOT NULL AND localClass.id=localReg.handler_class_id) OR (localReg.handler_class_id IS NULL AND localClass.class_name=localReg.class_name AND localClass.repo_id=localReg.repo_id)) JOIN handler_methods localMethod ON localMethod.handler_class_id=localClass.id WHERE localReg.repo_id=appRepo.id AND (localMethod.decorator_value=? OR localMethod.decorator_value=? OR localMethod.method_name=? OR localMethod.decorator_raw_expression LIKE ?)) THEN 1 ELSE 0 END applicationHasLocalRegistrationForOperation,
3025
3432
  CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT) AND dep.to_id=?) THEN 1 ELSE 0 END appDependsOnModel,
3026
3433
  CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT) AND dep.to_id=CAST(handlerRepo.id AS TEXT)) THEN 1 ELSE 0 END appDependsOnHandler,
3027
3434
  CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(handlerRepo.id AS TEXT) AND dep.to_id=?) THEN 1 ELSE 0 END handlerDependsOnModel
3028
3435
  FROM handler_methods hm
3029
3436
  JOIN handler_classes hc ON hc.id=hm.handler_class_id
3030
3437
  JOIN repositories handlerRepo ON handlerRepo.id=hc.repo_id
3031
- JOIN handler_registrations hr ON (hr.handler_class_id=hc.id OR (hr.class_name=hc.class_name AND (hr.repo_id=hc.repo_id OR hr.import_source IS NOT NULL)))
3438
+ JOIN handler_registrations hr ON (
3439
+ (hr.handler_class_id IS NOT NULL AND hr.handler_class_id=hc.id)
3440
+ OR (
3441
+ hr.handler_class_id IS NULL
3442
+ AND (
3443
+ (hr.class_name=hc.class_name AND hr.repo_id=hc.repo_id)
3444
+ OR (
3445
+ instr(hr.import_source,'#')>1
3446
+ AND substr(hr.import_source,1,instr(hr.import_source,'#')-1)
3447
+ =handlerRepo.package_name
3448
+ AND (
3449
+ substr(hr.import_source,instr(hr.import_source,'#')+1)
3450
+ =hc.class_name
3451
+ OR (
3452
+ substr(hr.import_source,instr(hr.import_source,'#')+1)
3453
+ ='default'
3454
+ AND hr.class_name=hc.class_name
3455
+ )
3456
+ )
3457
+ )
3458
+ )
3459
+ )
3460
+ )
3032
3461
  JOIN repositories appRepo ON appRepo.id=hr.repo_id
3033
3462
  WHERE appRepo.workspace_id=?
3034
3463
  AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=? OR hm.decorator_raw_expression LIKE ?)`).all(
@@ -3130,8 +3559,16 @@ function candidateEvidence(candidate, rank) {
3130
3559
  className: candidate.className,
3131
3560
  sourceFile: candidate.sourceFile,
3132
3561
  sourceLine: candidate.sourceLine,
3133
- registration: { file: candidate.registrationFile, line: candidate.registrationLine, kind: candidate.registrationKind, importSource: candidate.importSource },
3562
+ decoratorResolution: objectJson(candidate.decoratorResolutionJson),
3563
+ registration: registrationEvidence(candidate),
3134
3564
  registrations: candidate.registrations ?? [],
3565
+ registrationPairing: {
3566
+ strategy: registrationPairingStrategy(candidate),
3567
+ registrationId: candidate.registrationId,
3568
+ registrationHandlerClassId: candidate.registrationHandlerClassId,
3569
+ candidateHandlerClassId: candidate.classId,
3570
+ invariantStatus: validRegistrationPair(candidate) ? "valid" : "invalid"
3571
+ },
3135
3572
  applicationPackage: { id: candidate.applicationRepoId, name: candidate.applicationRepo, packageName: candidate.applicationPackage },
3136
3573
  handlerPackage: { id: candidate.handlerRepoId, name: candidate.handlerRepo, packageName: candidate.handlerPackage },
3137
3574
  modelPackage: { id: candidate.modelRepoId, name: candidate.modelRepo, packageName: candidate.modelPackage },
@@ -3171,6 +3608,40 @@ function normalizedOperation(value) {
3171
3608
  return value.startsWith("/") ? value.slice(1) : value;
3172
3609
  }
3173
3610
 
3611
+ // src/trace/selectors.ts
3612
+ function parseVars(values) {
3613
+ const out = {};
3614
+ for (const value of values ?? []) {
3615
+ const [key, ...rest] = value.split("=");
3616
+ if (key && rest.length > 0) out[key] = rest.join("=");
3617
+ }
3618
+ return out;
3619
+ }
3620
+ function ambiguousStartDiagnostic(requested, candidates, message) {
3621
+ const serviceSuggestions = [...new Set(candidates.flatMap((row) => typeof row.servicePath === "string" ? [`--service ${row.servicePath}`] : []))].sort();
3622
+ return {
3623
+ severity: "warning",
3624
+ code: "trace_start_ambiguous",
3625
+ message,
3626
+ normalizedSelectorValue: requested,
3627
+ resolutionStage: "operation",
3628
+ resolutionStatus: "ambiguous_operation",
3629
+ candidates,
3630
+ serviceSuggestions,
3631
+ selectorSuggestions: fullSelectorSuggestions(candidates)
3632
+ };
3633
+ }
3634
+ function fullSelectorSuggestions(candidates) {
3635
+ const includeRepo = new Set(candidates.map((row) => row.repoName)).size > 1;
3636
+ return [...new Set(candidates.flatMap((row) => {
3637
+ if (typeof row.servicePath !== "string" || typeof row.operationPath !== "string") return [];
3638
+ const repoSelector = includeRepo && typeof row.repoName === "string" ? `--repo ${row.repoName} ` : "";
3639
+ return [
3640
+ `${repoSelector}--service ${row.servicePath} --path ${row.operationPath}`
3641
+ ];
3642
+ }))].sort();
3643
+ }
3644
+
3174
3645
  // src/trace/evidence.ts
3175
3646
  function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence) {
3176
3647
  const evidence = { ...persistedEvidence, ...contextualEvidence ?? {} };
@@ -3189,11 +3660,12 @@ function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence) {
3189
3660
  persistedResolution: persistedResolution(row)
3190
3661
  };
3191
3662
  }
3192
- function runtimeResolution(db, row, evidence, vars, workspaceId) {
3663
+ function runtimeResolution(db, row, evidence, vars, workspaceId, contextualUnresolvedReason) {
3193
3664
  const substituted = evidenceWithRuntimeVariables(evidence, vars);
3194
3665
  if (!isRemoteRuntimeCandidate(row, evidence, vars)) {
3195
- const withSections = withEffectiveResolution(substituted, row, row.unresolved_reason);
3196
- return { row, evidence: withSections, unresolvedReason: row.unresolved_reason };
3666
+ const unresolvedReason2 = contextualUnresolvedReason ?? row.unresolved_reason;
3667
+ const withSections = withEffectiveResolution(substituted, row, unresolvedReason2);
3668
+ return { row, evidence: withSections, unresolvedReason: unresolvedReason2 };
3197
3669
  }
3198
3670
  const resolution = resolveRuntimeOperation(db, substituted, workspaceId);
3199
3671
  if (resolution.target) {
@@ -3206,6 +3678,9 @@ function runtimeResolution(db, row, evidence, vars, workspaceId) {
3206
3678
  function runtimeVariableDiagnostic(edges) {
3207
3679
  const missing = /* @__PURE__ */ new Set();
3208
3680
  for (const edge of edges) {
3681
+ const effective = parseObject(edge.evidence.effectiveResolution);
3682
+ if (!["dynamic", "unresolved", "ambiguous"].includes(String(effective.status ?? "")))
3683
+ continue;
3209
3684
  const substitutions = edge.evidence.runtimeSubstitutions;
3210
3685
  if (!substitutions || typeof substitutions !== "object" || Array.isArray(substitutions)) continue;
3211
3686
  for (const value of Object.values(substitutions))
@@ -3223,20 +3698,20 @@ function runtimeVariableDiagnostic(edges) {
3223
3698
  }
3224
3699
  function edgeTarget(row, evidence) {
3225
3700
  const effective = parseObject(evidence.effectiveResolution);
3226
- const targetServicePath = stringValue3(effective.targetServicePath ?? evidence.targetServicePath);
3227
- const targetOperationPath = stringValue3(effective.targetOperationPath ?? evidence.targetOperationPath);
3701
+ const targetServicePath = stringValue4(effective.targetServicePath ?? evidence.targetServicePath);
3702
+ const targetOperationPath = stringValue4(effective.targetOperationPath ?? evidence.targetOperationPath);
3228
3703
  if (targetServicePath && targetOperationPath) return `${targetServicePath}${targetOperationPath}`;
3229
3704
  const runtimeCandidate = evidence.runtimeResolvedCandidate;
3230
3705
  if (runtimeCandidate?.servicePath && runtimeCandidate.operationPath) return `${runtimeCandidate.servicePath}${runtimeCandidate.operationPath}`;
3231
- const servicePath = stringValue3(evidence.servicePath);
3232
- const operationPath = stringValue3(evidence.operationPath);
3233
- const targetOperation = stringValue3(evidence.targetOperation);
3234
- const targetRepo = stringValue3(evidence.targetRepo) ?? "";
3706
+ const servicePath = stringValue4(evidence.servicePath);
3707
+ const operationPath = stringValue4(evidence.operationPath);
3708
+ const targetOperation = stringValue4(evidence.targetOperation);
3709
+ const targetRepo = stringValue4(evidence.targetRepo) ?? "";
3235
3710
  if (row.edge_type === "HANDLER_RUNS_DB_QUERY") return `Entity: ${row.to_id || "unknown"}`;
3236
- if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return stringValue3(evidence.remoteQueryTarget) ?? `Remote query: ${row.to_id || "unknown"}`;
3711
+ if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return stringValue4(evidence.remoteQueryTarget) ?? `Remote query: ${row.to_id || "unknown"}`;
3237
3712
  if (row.edge_type === "HANDLER_CALLS_EXTERNAL_HTTP") {
3238
3713
  const target = parseObject(evidence.externalTarget);
3239
- return stringValue3(target.label) ?? `External endpoint: ${row.to_id || "unknown"}`;
3714
+ return stringValue4(target.label) ?? `External endpoint: ${row.to_id || "unknown"}`;
3240
3715
  }
3241
3716
  if (servicePath && operationPath) return `${servicePath}${operationPath}`;
3242
3717
  return targetOperation ? `${targetRepo}:${targetOperation}` : row.to_id;
@@ -3275,12 +3750,12 @@ function withEffectiveResolution(evidence, row, unresolvedReason, resolution) {
3275
3750
  return { ...rest, effectiveResolution: current, linker: { status: current.status, confidence: current.confidence, reason: unresolvedReason, edgeType: current.edgeType } };
3276
3751
  }
3277
3752
  function resolveRuntimeOperation(db, evidence, workspaceId) {
3278
- const servicePath = stringValue3(evidence.servicePath);
3279
- const rawOperationPath = stringValue3(evidence.operationPath);
3753
+ const servicePath = stringValue4(evidence.servicePath);
3754
+ const rawOperationPath = stringValue4(evidence.operationPath);
3280
3755
  const normalized = normalizeODataOperationInvocationPath(rawOperationPath);
3281
- const operationPath = normalized?.wasInvocation ? normalized.normalizedOperationPath : stringValue3(evidence.normalizedOperationPath) ?? rawOperationPath;
3282
- const alias = stringValue3(evidence.serviceAliasExpr ?? evidence.serviceAlias);
3283
- const destination = stringValue3(evidence.destination);
3756
+ const operationPath = normalized?.wasInvocation ? normalized.normalizedOperationPath : stringValue4(evidence.normalizedOperationPath) ?? rawOperationPath;
3757
+ const alias = stringValue4(evidence.serviceAliasExpr ?? evidence.serviceAlias);
3758
+ const destination = stringValue4(evidence.destination);
3284
3759
  return resolveOperation(db, { servicePath, operationPath, alias, destination, hasExplicitOverride: true, isDynamic: true }, workspaceId);
3285
3760
  }
3286
3761
  function evidenceWithRuntimeVariables(evidence, vars) {
@@ -3295,11 +3770,17 @@ function evidenceWithRuntimeVariables(evidence, vars) {
3295
3770
  function runtimeSubstitutions(evidence, vars) {
3296
3771
  const substitutions = {};
3297
3772
  for (const key of ["servicePath", "operationPath", "serviceAliasExpr", "serviceAlias", "destination"]) {
3298
- const substitution = substituteVariables(stringValue3(evidence[key]), vars);
3773
+ const substitution = substituteVariables(substitutionValue(evidence, key), vars);
3299
3774
  if (substitution.placeholders.length > 0) substitutions[key] = substitution;
3300
3775
  }
3301
3776
  return substitutions;
3302
3777
  }
3778
+ function substitutionValue(evidence, key) {
3779
+ const value = stringValue4(evidence[key]);
3780
+ if (key !== "operationPath") return value;
3781
+ const normalized = normalizeODataOperationInvocationPath(value);
3782
+ return normalized?.wasInvocation ? normalized.normalizedOperationPath : value;
3783
+ }
3303
3784
  function isRemoteRuntimeCandidate(row, evidence, vars) {
3304
3785
  if (!vars || Object.keys(vars).length === 0) return false;
3305
3786
  if (!["dynamic", "ambiguous", "unresolved"].includes(String(row.status ?? ""))) return false;
@@ -3317,7 +3798,7 @@ function runtimeUnresolvedReason(resolution) {
3317
3798
  function parseObject(value) {
3318
3799
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
3319
3800
  }
3320
- function stringValue3(value) {
3801
+ function stringValue4(value) {
3321
3802
  return typeof value === "string" ? value : void 0;
3322
3803
  }
3323
3804
 
@@ -3332,32 +3813,40 @@ function positiveDepth(value) {
3332
3813
  function operationStartScope(db, repoId, start, hintOptions) {
3333
3814
  const requested = normalizeOperation(start.operationPath ?? start.operation);
3334
3815
  if (!requested) return void 0;
3335
- const rows2 = db.prepare(`SELECT o.id operationId,o.operation_name operationName,o.operation_path operationPath,s.service_path servicePath,r.id repoId,r.name repoName
3816
+ const rows2 = db.prepare(`SELECT o.id operationId,o.operation_name operationName,o.operation_path operationPath,o.source_file sourceFile,o.source_line sourceLine,s.service_path servicePath,r.id repoId,r.name repoName
3336
3817
  FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
3337
3818
  WHERE (? IS NULL OR r.id=?) AND (? IS NULL OR s.service_path=?) AND (o.operation_name=? OR o.operation_path=? OR o.operation_path=?)
3338
3819
  ORDER BY r.name,s.service_path,o.operation_name,o.id`).all(repoId, repoId, start.servicePath, start.servicePath, requested, requested, requested.startsWith("/") ? requested : `/${requested}`);
3339
3820
  if (rows2.length === 0) return void 0;
3340
3821
  const repoCount = new Set(rows2.map((row) => String(row.repoName))).size;
3341
3822
  const serviceCount = new Set(rows2.map((row) => `${String(row.repoName)}:${String(row.servicePath)}`)).size;
3342
- if (!repoId && repoCount > 1) return { diagnostics: [{ severity: "warning", code: "trace_start_ambiguous", message: "Operation trace start matched multiple repositories; add --repo to disambiguate", normalizedSelectorValue: requested, resolutionStage: "operation", resolutionStatus: "ambiguous_operation", candidates: rows2 }] };
3343
- if (!start.servicePath && serviceCount > 1) return { diagnostics: [{ severity: "warning", code: "trace_start_ambiguous", message: "Operation trace start matched multiple services; add --service to disambiguate", normalizedSelectorValue: requested, resolutionStage: "operation", resolutionStatus: "ambiguous_operation", candidates: rows2 }] };
3344
- if (rows2.length !== 1) return { diagnostics: [{ severity: "warning", code: "trace_start_ambiguous", message: "Operation trace start matched multiple indexed operations", normalizedSelectorValue: requested, resolutionStage: "operation", resolutionStatus: "ambiguous_operation", candidates: rows2 }] };
3823
+ if (!repoId && repoCount > 1)
3824
+ return { diagnostics: [ambiguousStartDiagnostic(requested, rows2, "Operation trace start matched multiple repositories; add --repo to disambiguate")] };
3825
+ if (!start.servicePath && serviceCount > 1)
3826
+ return { diagnostics: [ambiguousStartDiagnostic(requested, rows2, "Operation trace start matched multiple services; add --service to disambiguate")] };
3827
+ if (rows2.length !== 1)
3828
+ return { diagnostics: [ambiguousStartDiagnostic(requested, rows2, "Operation trace start matched multiple indexed operations")] };
3345
3829
  const operationId = String(rows2[0]?.operationId);
3346
3830
  const impl = implementationScope(db, operationId);
3347
- if (impl.edge?.status === "resolved" && impl.files.size > 0) return { files: impl.files, symbols: impl.symbolId ? /* @__PURE__ */ new Set([impl.symbolId]) : void 0, operationId, diagnostics: [] };
3831
+ if (impl.edge?.status === "resolved" && impl.files.size > 0) return { files: impl.files, symbols: impl.symbolId ? /* @__PURE__ */ new Set([impl.symbolId]) : void 0, repoId: impl.repoId, operationId, diagnostics: [] };
3348
3832
  const hinted = implementationMethodIdFromHint(impl.edge, hintOptions);
3349
3833
  if (hinted.methodId) {
3350
3834
  const hintedScope = handlerScope(db, hinted.methodId);
3351
- if (hintedScope?.files.size) return { files: hintedScope.files, symbols: hintedScope.symbolId ? /* @__PURE__ */ new Set([hintedScope.symbolId]) : void 0, operationId, diagnostics: [] };
3835
+ if (hintedScope?.files.size) return { files: hintedScope.files, symbols: hintedScope.symbolId ? /* @__PURE__ */ new Set([hintedScope.symbolId]) : void 0, repoId: hintedScope.repoId, operationId, diagnostics: [] };
3352
3836
  }
3353
3837
  if (impl.edge) {
3354
3838
  const evidence = parseEvidence(impl.edge.evidence_json);
3355
3839
  const hintDiagnostic = implementationHintDiagnostic(hinted, evidence.implementationHintSuggestions);
3356
- const diagnostics = [{ severity: "warning", code: impl.edge.status === "ambiguous" ? "trace_start_ambiguous" : "trace_start_implementation_unresolved", message: `Indexed operation matched but implementation edge is ${String(impl.edge.status ?? "unresolved")}`, resolutionStage: "implementation", resolutionStatus: impl.edge.status === "ambiguous" ? "ambiguous_implementation" : "rejected_implementation", implementationEdgeId: impl.edge.id, implementationStatus: impl.edge.status, implementationAmbiguityReasons: evidence.ambiguityReasons, implementationHintSuggestions: evidence.implementationHintSuggestions, candidates: evidence.candidates }];
3840
+ const diagnostics = [{ severity: "warning", code: impl.edge.status === "ambiguous" ? "trace_start_ambiguous" : "trace_start_implementation_unresolved", message: `Indexed operation matched but implementation edge is ${String(impl.edge.status ?? "unresolved")}`, resolutionStage: "implementation", resolutionStatus: impl.edge.status === "ambiguous" ? "ambiguous_implementation" : "rejected_implementation", implementationEdgeId: impl.edge.id, implementationStatus: impl.edge.status, implementationAmbiguityReasons: evidence.ambiguityReasons, implementationRejectionReasons: implementationRejectionReasons(evidence), implementationHintSuggestions: evidence.implementationHintSuggestions, candidates: evidence.candidates }];
3357
3841
  return { operationId, diagnostics: hintDiagnostic ? [hintDiagnostic, ...diagnostics] : diagnostics };
3358
3842
  }
3359
3843
  return { operationId, diagnostics: [{ severity: "warning", code: "trace_start_implementation_unresolved", message: "Indexed operation matched but no implementation candidate exists", resolutionStage: "implementation", resolutionStatus: "operation_without_implementation" }] };
3360
3844
  }
3845
+ function implementationRejectionReasons(evidence) {
3846
+ const candidates = Array.isArray(evidence.candidates) ? evidence.candidates.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
3847
+ const reasons = candidates.flatMap((candidate) => Array.isArray(candidate.rejectedReasons) ? candidate.rejectedReasons.filter((reason) => typeof reason === "string") : []);
3848
+ return [...new Set(reasons)].sort();
3849
+ }
3361
3850
  function sourceFilesForStart(db, repoId, start) {
3362
3851
  const handler = start.handler;
3363
3852
  const operation = normalizeOperation(start.operation ?? start.operationPath);
@@ -3385,7 +3874,7 @@ function sourceFilesForStart(db, repoId, start) {
3385
3874
  operation,
3386
3875
  operation
3387
3876
  );
3388
- if (rows2.length > 0) return { files: new Set(rows2.map((row) => row.sourceFile).filter(Boolean)), symbols: new Set(rows2.map((row) => Number(row.symbolId)).filter(Boolean)) };
3877
+ if (rows2.length > 0) return { files: new Set(rows2.map((row) => row.sourceFile).filter(Boolean)), symbols: new Set(rows2.map((row) => Number(row.symbolId)).filter(Boolean)), repoId };
3389
3878
  if (start.servicePath && operation) {
3390
3879
  const implRows = db.prepare(`SELECT DISTINCT hc.source_file sourceFile,sym.id symbolId
3391
3880
  FROM cds_services s JOIN cds_operations o ON o.service_id=s.id
@@ -3394,7 +3883,7 @@ function sourceFilesForStart(db, repoId, start) {
3394
3883
  JOIN handler_classes hc ON hc.id=hm.handler_class_id
3395
3884
  LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id AND sym.source_file=hc.source_file AND sym.name=hm.method_name
3396
3885
  WHERE (? IS NULL OR s.repo_id=?) AND s.service_path=? AND (o.operation_path=? OR o.operation_name=?)`).all(repoId, repoId, start.servicePath, operation, operation);
3397
- if (implRows.length > 0) return { files: new Set(implRows.map((row) => row.sourceFile).filter(Boolean)), symbols: new Set(implRows.map((row) => Number(row.symbolId)).filter(Boolean)) };
3886
+ if (implRows.length > 0) return { files: new Set(implRows.map((row) => row.sourceFile).filter(Boolean)), symbols: new Set(implRows.map((row) => Number(row.symbolId)).filter(Boolean)), repoId };
3398
3887
  }
3399
3888
  return void 0;
3400
3889
  }
@@ -3414,6 +3903,7 @@ function startScope(db, start, hintOptions) {
3414
3903
  return { repo, selectorMatched: false };
3415
3904
  return {
3416
3905
  repo,
3906
+ executionRepoId: sourceScope?.repoId ?? repo?.id,
3417
3907
  sourceFiles,
3418
3908
  symbolIds: sourceScope?.symbols,
3419
3909
  selectorMatched: !terminalOperationScope && (!hasSelector || sourceFiles !== void 0),
@@ -3560,20 +4050,59 @@ function knownBindingsForCalls(db, calls) {
3560
4050
  function knownBindingsForScope(db, repoId, symbolIds, files) {
3561
4051
  const map = /* @__PURE__ */ new Map();
3562
4052
  if (repoId === void 0) return map;
3563
- const rows2 = db.prepare(`SELECT b.id,b.variable_name variableName,b.alias,b.alias_expr aliasExpr,b.destination_expr destinationExpr,b.service_path_expr servicePathExpr,b.source_file sourceFile,b.source_line sourceLine,req.service_path requireServicePath,req.destination requireDestination
4053
+ const rows2 = db.prepare(`SELECT b.id,b.symbol_id symbolId,b.variable_name variableName,b.alias,b.alias_expr aliasExpr,b.destination_expr destinationExpr,b.service_path_expr servicePathExpr,b.source_file sourceFile,b.source_line sourceLine,req.service_path requireServicePath,req.destination requireDestination
3564
4054
  FROM service_bindings b LEFT JOIN cds_requires req ON req.repo_id=b.repo_id AND req.alias=b.alias
3565
4055
  WHERE b.repo_id=?`).all(repoId);
3566
4056
  for (const row of rows2) {
3567
4057
  if (!row.variableName) continue;
3568
4058
  if (files && !files.has(String(row.sourceFile))) continue;
3569
- if (symbolIds && symbolIds.size > 0) {
3570
- const owner = db.prepare("SELECT id FROM symbols WHERE id IN (" + [...symbolIds].map(() => "?").join(",") + ") AND source_file=? AND start_line<=? AND end_line>=? LIMIT 1").get(...symbolIds, row.sourceFile, row.sourceLine, row.sourceLine);
3571
- if (!owner) continue;
4059
+ if (symbolIds && symbolIds.size > 0 && row.symbolId != null && !symbolIds.has(Number(row.symbolId))) continue;
4060
+ const candidate = enrichBinding({
4061
+ ...row,
4062
+ bindingId: Number(row.id),
4063
+ source: "local_service_binding",
4064
+ calleeReceiver: row.variableName,
4065
+ resolutionStatus: "selected"
4066
+ });
4067
+ const existing = map.get(row.variableName);
4068
+ if (!existing) {
4069
+ map.set(row.variableName, candidate);
4070
+ continue;
3572
4071
  }
3573
- map.set(row.variableName, enrichBinding({ ...row, bindingId: Number(row.id), source: "local_service_binding", calleeReceiver: row.variableName }));
4072
+ const candidates = uniqueBindingCandidates([
4073
+ ...existing.bindingCandidates ?? [bindingCandidateEvidence(existing)],
4074
+ bindingCandidateEvidence(candidate)
4075
+ ]);
4076
+ map.set(row.variableName, {
4077
+ ...candidate,
4078
+ bindingId: void 0,
4079
+ source: "ambiguous_local_service_bindings",
4080
+ resolutionStatus: "ambiguous",
4081
+ bindingCandidates: candidates
4082
+ });
3574
4083
  }
3575
4084
  return map;
3576
4085
  }
4086
+ function bindingCandidateEvidence(binding) {
4087
+ return {
4088
+ bindingId: binding.bindingId,
4089
+ sourceFile: binding.sourceFile,
4090
+ sourceLine: binding.sourceLine,
4091
+ alias: binding.alias,
4092
+ aliasExpr: binding.aliasExpr,
4093
+ destinationExpr: binding.destinationExpr,
4094
+ servicePathExpr: binding.servicePathExpr
4095
+ };
4096
+ }
4097
+ function uniqueBindingCandidates(candidates) {
4098
+ const seen = /* @__PURE__ */ new Set();
4099
+ return candidates.filter((candidate) => {
4100
+ const key = JSON.stringify(candidate);
4101
+ if (seen.has(key)) return false;
4102
+ seen.add(key);
4103
+ return true;
4104
+ });
4105
+ }
3577
4106
  function contextForSymbolCall(db, symbolCall, callerBindings) {
3578
4107
  const next = /* @__PURE__ */ new Map();
3579
4108
  if (callerBindings.size === 0) return next;
@@ -3623,6 +4152,21 @@ function contextForSymbolCall(db, symbolCall, callerBindings) {
3623
4152
  }
3624
4153
  function contextualRuntimeResolution(db, call, binding, workspaceId, persistedRows = []) {
3625
4154
  if (!binding || String(call.call_type) !== "remote_action" || call.operation_path_expr === void 0 || call.operation_path_expr === null) return {};
4155
+ if (binding.resolutionStatus === "ambiguous") {
4156
+ return {
4157
+ evidence: {
4158
+ contextualServiceBindingAttempted: true,
4159
+ contextualBinding: {
4160
+ source: binding.source,
4161
+ status: "tied",
4162
+ candidates: binding.bindingCandidates
4163
+ },
4164
+ contextualResolutionStatus: "ambiguous",
4165
+ contextualCandidateCount: binding.bindingCandidates?.length ?? 0
4166
+ },
4167
+ unresolvedReason: "Ambiguous contextual service binding candidates"
4168
+ };
4169
+ }
3626
4170
  const normalized = normalizeODataOperationInvocationPath(String(call.operation_path_expr));
3627
4171
  const op = normalized?.normalizedOperationPath ?? (String(call.operation_path_expr).startsWith("/") ? String(call.operation_path_expr) : `/${String(call.operation_path_expr)}`);
3628
4172
  const servicePath = binding.effectiveServicePath ?? binding.servicePathExpr ?? binding.requireServicePath;
@@ -3655,7 +4199,7 @@ function trace(db, start, options) {
3655
4199
  const edges = [];
3656
4200
  const nodes = /* @__PURE__ */ new Map();
3657
4201
  const seenEdges = /* @__PURE__ */ new Set();
3658
- const queue = scope.selectorMatched ? [{ repoId: scope.repo?.id, files: scope.sourceFiles, symbolIds: scope.symbolIds, depth: 1, context: /* @__PURE__ */ new Map() }] : [];
4202
+ const queue = scope.selectorMatched ? [{ repoId: scope.executionRepoId, files: scope.sourceFiles, symbolIds: scope.symbolIds, depth: 1, context: /* @__PURE__ */ new Map() }] : [];
3659
4203
  if (scope.startOperationId && scope.selectorMatched) {
3660
4204
  const op = operationNode(db, scope.startOperationId);
3661
4205
  const impl = implementationScope(db, scope.startOperationId);
@@ -3725,7 +4269,7 @@ function trace(db, start, options) {
3725
4269
  String(row.evidence_json || "{}")
3726
4270
  );
3727
4271
  const rawEvidence = baseTraceEvidence(row, call, persistedEvidence, contextual.evidence);
3728
- const effective = runtimeResolution(db, row, rawEvidence, options.vars, workspaceIdForCall(db, String(call.id)));
4272
+ const effective = runtimeResolution(db, row, rawEvidence, options.vars, workspaceIdForCall(db, String(call.id)), contextual.unresolvedReason);
3729
4273
  const evidence = effective.evidence;
3730
4274
  const effectiveRow = effective.row;
3731
4275
  const targetNode = `${effectiveRow.to_kind}:${effectiveRow.to_id}`;
@@ -3824,6 +4368,7 @@ export {
3824
4368
  parseImplementationHint,
3825
4369
  implementationHintSuggestions,
3826
4370
  linkWorkspace,
4371
+ parseVars,
3827
4372
  trace
3828
4373
  };
3829
- //# sourceMappingURL=chunk-EGY2A4AT.js.map
4374
+ //# sourceMappingURL=chunk-52OUS3MO.js.map