@saptools/service-flow 0.1.48 → 0.1.49

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.
@@ -1276,7 +1276,7 @@ function collectClassHelpers(sf) {
1276
1276
  // src/parsers/outbound-call-parser.ts
1277
1277
  import fs7 from "fs/promises";
1278
1278
  import path10 from "path";
1279
- import ts7 from "typescript";
1279
+ import ts8 from "typescript";
1280
1280
 
1281
1281
  // src/linker/external-http-target.ts
1282
1282
  import { createHash } from "crypto";
@@ -1552,7 +1552,242 @@ function topLevelQueryIndex(text) {
1552
1552
 
1553
1553
  // src/parsers/imported-wrapper-parser.ts
1554
1554
  import path9 from "path";
1555
+ import ts7 from "typescript";
1556
+
1557
+ // src/parsers/operation-path-analysis.ts
1555
1558
  import ts6 from "typescript";
1559
+ var maxAliasDepth = 6;
1560
+ function analyzeOperationPath(expression, use, method = "POST") {
1561
+ if (!expression) return emptyAnalysis();
1562
+ const state = collectExpressionState(expression, use, 0, /* @__PURE__ */ new Set());
1563
+ const paths = unique(state.paths.map(normalizeRawPath));
1564
+ const normalized = unique(paths.flatMap((value) => normalizedCandidate(value, method)));
1565
+ const status = pathStatus(paths, state.placeholders, state.dynamic);
1566
+ const runtimeIdentifier = state.dynamic.at(-1)?.expression;
1567
+ return {
1568
+ status,
1569
+ rawExpression: expression.getText(expression.getSourceFile()),
1570
+ normalizedOperationPath: status === "static" && normalized.length === 1 ? normalized[0] : void 0,
1571
+ candidateRawPaths: paths,
1572
+ candidateNormalizedOperationPaths: normalized,
1573
+ placeholderKeys: unique([...state.placeholders, ...runtimeIdentifier ? [runtimeIdentifier] : []]),
1574
+ sourceKind: unique(state.sourceKinds).join("+") || "unknown",
1575
+ candidateIdentifier: ts6.isIdentifier(expression) ? expression.text : void 0,
1576
+ runtimeIdentifier,
1577
+ dynamicReassignments: state.dynamic,
1578
+ lexicalScope: lexicalEvidence(expression, use)
1579
+ };
1580
+ }
1581
+ function operationPathExpression(analysis) {
1582
+ if (analysis.status === "ambiguous" || analysis.status === "unknown") return void 0;
1583
+ if (analysis.sourceKind.includes("parameter_binding")) return void 0;
1584
+ if (analysis.candidateRawPaths.length === 1 && analysis.dynamicReassignments.length === 0)
1585
+ return analysis.candidateRawPaths[0];
1586
+ if (!analysis.runtimeIdentifier || analysis.sourceKind.includes("binding_not_found"))
1587
+ return void 0;
1588
+ return isRuntimeIdentifier(analysis.runtimeIdentifier) ? `\${${analysis.runtimeIdentifier}}` : void 0;
1589
+ }
1590
+ function pathUnresolvedReason(analysis) {
1591
+ if (analysis.status === "ambiguous") return "ambiguous_operation_path_candidates";
1592
+ if (analysis.status === "dynamic" && !operationPathExpression(analysis))
1593
+ return "dynamic_operation_path_identifier";
1594
+ if (analysis.dynamicReassignments.length > 0) return "dynamic_operation_path_identifier";
1595
+ return void 0;
1596
+ }
1597
+ function collectExpressionState(expression, use, depth, seen) {
1598
+ const unwrapped = unwrap(expression);
1599
+ if (ts6.isStringLiteral(unwrapped)) return staticState(unwrapped.text, "string_literal");
1600
+ if (ts6.isNoSubstitutionTemplateLiteral(unwrapped))
1601
+ return staticState(unwrapped.text, "no_substitution_template");
1602
+ if (ts6.isTemplateExpression(unwrapped)) return templateState(unwrapped);
1603
+ if (ts6.isConditionalExpression(unwrapped))
1604
+ return mergeStates([
1605
+ collectExpressionState(unwrapped.whenTrue, use, depth + 1, seen),
1606
+ collectExpressionState(unwrapped.whenFalse, use, depth + 1, seen)
1607
+ ], "conditional_candidates");
1608
+ if (ts6.isIdentifier(unwrapped))
1609
+ return collectIdentifierState(unwrapped, use, depth, seen);
1610
+ return dynamicState(unwrapped);
1611
+ }
1612
+ function collectIdentifierState(identifier, use, depth, seen) {
1613
+ if (depth >= maxAliasDepth)
1614
+ return dynamicState(identifier, "alias_depth_exceeded");
1615
+ const binding = resolveBinding(identifier, use);
1616
+ if (!binding || seen.has(binding.declaration))
1617
+ return dynamicState(identifier, binding ? "alias_cycle" : "binding_not_found");
1618
+ seen.add(binding.declaration);
1619
+ if (ts6.isParameter(binding.declaration))
1620
+ return dynamicState(identifier, "parameter_binding");
1621
+ const expressions = reachingExpressions(binding.declaration, use);
1622
+ if (expressions.length === 0) return dynamicState(identifier, "initializer_missing");
1623
+ const states = expressions.map((item) => collectExpressionState(item.expression, item.node, depth + 1, seen));
1624
+ const merged = mergeStates(states, binding.immutable ? "const_alias" : "mutable_alias");
1625
+ if (merged.dynamic.length === 0) return merged;
1626
+ return {
1627
+ ...merged,
1628
+ dynamic: merged.dynamic.map((item) => item.expression === identifier.text ? item : item)
1629
+ };
1630
+ }
1631
+ function reachingExpressions(declaration, use) {
1632
+ const rows2 = [];
1633
+ if (declaration.initializer) rows2.push({ expression: declaration.initializer, node: declaration });
1634
+ if ((declaration.parent.flags & ts6.NodeFlags.Const) !== 0) return rows2;
1635
+ const source = use.getSourceFile();
1636
+ const visit = (node) => {
1637
+ if (node.getStart(source) >= use.getStart(source)) return;
1638
+ if (node !== source && ts6.isFunctionLike(node) && !contains(node, use)) return;
1639
+ if (isAssignmentTo(node, declaration, use))
1640
+ rows2.push({ expression: node.right, node });
1641
+ ts6.forEachChild(node, visit);
1642
+ };
1643
+ visit(source);
1644
+ return rows2.sort((left, right) => left.node.getStart(source) - right.node.getStart(source));
1645
+ }
1646
+ function isAssignmentTo(node, declaration, use) {
1647
+ if (!ts6.isBinaryExpression(node) || node.operatorToken.kind !== ts6.SyntaxKind.EqualsToken)
1648
+ return false;
1649
+ if (!ts6.isIdentifier(node.left) || !ts6.isIdentifier(declaration.name)) return false;
1650
+ return resolveBinding(node.left, node)?.declaration === declaration && contains(declarationScope(declaration), use);
1651
+ }
1652
+ function resolveBinding(identifier, use) {
1653
+ const source = use.getSourceFile();
1654
+ const matches2 = [];
1655
+ const visit = (node) => {
1656
+ if (isNamedDeclaration(node, identifier.text) && isAccessible(node, use))
1657
+ matches2.push(node);
1658
+ ts6.forEachChild(node, visit);
1659
+ };
1660
+ visit(source);
1661
+ const declaration = matches2.sort((left, right) => right.getStart(source) - left.getStart(source))[0];
1662
+ if (!declaration) return void 0;
1663
+ const immutable = ts6.isVariableDeclaration(declaration) && (declaration.parent.flags & ts6.NodeFlags.Const) !== 0;
1664
+ return { declaration, immutable };
1665
+ }
1666
+ function isNamedDeclaration(node, name) {
1667
+ return (ts6.isVariableDeclaration(node) || ts6.isParameter(node)) && ts6.isIdentifier(node.name) && node.name.text === name;
1668
+ }
1669
+ function isAccessible(declaration, use) {
1670
+ const source = use.getSourceFile();
1671
+ if (declaration.name.getStart(source) >= use.getStart(source)) return false;
1672
+ const scope = declarationScope(declaration);
1673
+ return ts6.isSourceFile(scope) || contains(scope, use);
1674
+ }
1675
+ function declarationScope(declaration) {
1676
+ if (ts6.isParameter(declaration)) return declaration.parent;
1677
+ if (ts6.isCatchClause(declaration.parent)) return declaration.parent.block;
1678
+ const list = declaration.parent;
1679
+ if (isLoopInitializer(list.parent)) return list.parent.statement;
1680
+ const blockScoped = (list.flags & (ts6.NodeFlags.Const | ts6.NodeFlags.Let)) !== 0;
1681
+ let current = list.parent;
1682
+ if (!blockScoped) {
1683
+ while (current.parent && !ts6.isFunctionLike(current) && !ts6.isSourceFile(current))
1684
+ current = current.parent;
1685
+ return current;
1686
+ }
1687
+ while (current.parent && !isLexicalScope(current)) current = current.parent;
1688
+ return current;
1689
+ }
1690
+ function isLoopInitializer(node) {
1691
+ return ts6.isForStatement(node) || ts6.isForInStatement(node) || ts6.isForOfStatement(node);
1692
+ }
1693
+ function isLexicalScope(node) {
1694
+ return ts6.isBlock(node) || ts6.isSourceFile(node) || ts6.isModuleBlock(node) || ts6.isCaseBlock(node) || ts6.isFunctionLike(node);
1695
+ }
1696
+ function templateState(expression) {
1697
+ const value = expression.getText(expression.getSourceFile()).slice(1, -1);
1698
+ return {
1699
+ paths: [value],
1700
+ placeholders: expression.templateSpans.map((span) => span.expression.getText(expression.getSourceFile())),
1701
+ dynamic: [],
1702
+ sourceKinds: ["template_with_placeholders"]
1703
+ };
1704
+ }
1705
+ function staticState(path11, sourceKind) {
1706
+ return { paths: [path11], placeholders: [], dynamic: [], sourceKinds: [sourceKind] };
1707
+ }
1708
+ function dynamicState(expression, sourceKind = "dynamic_expression") {
1709
+ return {
1710
+ paths: [],
1711
+ placeholders: [],
1712
+ dynamic: [{
1713
+ expression: expression.getText(expression.getSourceFile()),
1714
+ sourceLine: sourceLine(expression)
1715
+ }],
1716
+ sourceKinds: [sourceKind]
1717
+ };
1718
+ }
1719
+ function mergeStates(states, sourceKind) {
1720
+ return {
1721
+ paths: states.flatMap((state) => state.paths),
1722
+ placeholders: states.flatMap((state) => state.placeholders),
1723
+ dynamic: states.flatMap((state) => state.dynamic),
1724
+ sourceKinds: [sourceKind, ...states.flatMap((state) => state.sourceKinds)]
1725
+ };
1726
+ }
1727
+ function pathStatus(paths, placeholders3, dynamic) {
1728
+ if (dynamic.length > 0) return "dynamic";
1729
+ if (paths.length > 1) return "ambiguous";
1730
+ if (placeholders3.length > 0) return "dynamic";
1731
+ if (paths.length === 1) return "static";
1732
+ return "unknown";
1733
+ }
1734
+ function normalizedCandidate(value, method) {
1735
+ const invocation = normalizeODataOperationInvocationPath(value);
1736
+ if (invocation?.wasInvocation) return [invocation.normalizedOperationPath];
1737
+ const intent = classifyODataPathIntent(value, method);
1738
+ if (intent.kind.startsWith("entity_")) return [];
1739
+ if (!value.startsWith("/") || value.slice(1).includes("/") || value.includes("?")) return [];
1740
+ return [value];
1741
+ }
1742
+ function lexicalEvidence(expression, use) {
1743
+ if (!ts6.isIdentifier(expression))
1744
+ return { assignmentLines: [], sourceOrderSafe: expression.getStart() < use.getStart() };
1745
+ const binding = resolveBinding(expression, use);
1746
+ if (!binding) return { assignmentLines: [], sourceOrderSafe: false };
1747
+ const assignmentLines = ts6.isVariableDeclaration(binding.declaration) ? reachingExpressions(binding.declaration, use).slice(binding.declaration.initializer ? 1 : 0).map((item) => sourceLine(item.node)) : [];
1748
+ return {
1749
+ declarationLine: sourceLine(binding.declaration),
1750
+ assignmentLines,
1751
+ sourceOrderSafe: true
1752
+ };
1753
+ }
1754
+ function unwrap(expression) {
1755
+ if (ts6.isAwaitExpression(expression) || ts6.isParenthesizedExpression(expression))
1756
+ return unwrap(expression.expression);
1757
+ if (ts6.isAsExpression(expression) || ts6.isSatisfiesExpression(expression) || ts6.isTypeAssertionExpression(expression))
1758
+ return unwrap(expression.expression);
1759
+ return expression;
1760
+ }
1761
+ function normalizeRawPath(value) {
1762
+ return value.startsWith("/") ? value : `/${value}`;
1763
+ }
1764
+ function isRuntimeIdentifier(value) {
1765
+ return /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(value);
1766
+ }
1767
+ function contains(parent, child) {
1768
+ const source = child.getSourceFile();
1769
+ return child.getStart(source) >= parent.getStart(source) && child.getEnd() <= parent.getEnd();
1770
+ }
1771
+ function sourceLine(node) {
1772
+ const source = node.getSourceFile();
1773
+ return source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
1774
+ }
1775
+ function unique(values) {
1776
+ return [...new Set(values)].sort();
1777
+ }
1778
+ function emptyAnalysis() {
1779
+ return {
1780
+ status: "unknown",
1781
+ candidateRawPaths: [],
1782
+ candidateNormalizedOperationPaths: [],
1783
+ placeholderKeys: [],
1784
+ sourceKind: "missing",
1785
+ dynamicReassignments: [],
1786
+ lexicalScope: { assignmentLines: [], sourceOrderSafe: false }
1787
+ };
1788
+ }
1789
+
1790
+ // src/parsers/imported-wrapper-parser.ts
1556
1791
  async function parseImportedWrapperCalls(repoPath, filePath, source, serviceBindings) {
1557
1792
  const imports = await importsFor(repoPath, filePath, source);
1558
1793
  const importedByLocal = new Map(imports.filter((item) => item.sourceFile).map((item) => [item.localName, item]));
@@ -1560,7 +1795,7 @@ async function parseImportedWrapperCalls(repoPath, filePath, source, serviceBind
1560
1795
  const out = [];
1561
1796
  const cache = /* @__PURE__ */ new Map();
1562
1797
  for (const call of calls) {
1563
- if (!ts6.isIdentifier(call.expression)) continue;
1798
+ if (!ts7.isIdentifier(call.expression)) continue;
1564
1799
  const imported = importedByLocal.get(call.expression.text);
1565
1800
  if (!imported?.sourceFile) continue;
1566
1801
  const spec = await loadWrapperSpec(repoPath, imported, cache, 0);
@@ -1572,8 +1807,8 @@ async function parseImportedWrapperCalls(repoPath, filePath, source, serviceBind
1572
1807
  function collectImportedCalls(source, imports) {
1573
1808
  const calls = [];
1574
1809
  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);
1810
+ if (ts7.isCallExpression(node) && ts7.isIdentifier(node.expression) && imports.has(node.expression.text)) calls.push(node);
1811
+ ts7.forEachChild(node, visit);
1577
1812
  };
1578
1813
  visit(source);
1579
1814
  return calls;
@@ -1600,20 +1835,20 @@ function directSendSpec(source, sourceFile, name, fn) {
1600
1835
  const params = parameterNames(fn);
1601
1836
  const sends = [];
1602
1837
  visitFunctionBody(fn, (node) => {
1603
- if (ts6.isCallExpression(node) && ts6.isPropertyAccessExpression(node.expression) && node.expression.name.text === "send") sends.push(node);
1838
+ if (ts7.isCallExpression(node) && ts7.isPropertyAccessExpression(node.expression) && node.expression.name.text === "send") sends.push(node);
1604
1839
  });
1605
1840
  if (sends.length !== 1) return void 0;
1606
1841
  const send = sends[0];
1607
- const receiver = send && ts6.isPropertyAccessExpression(send.expression) && ts6.isIdentifier(send.expression.expression) ? send.expression.expression.text : void 0;
1842
+ const receiver = send && ts7.isPropertyAccessExpression(send.expression) && ts7.isIdentifier(send.expression.expression) ? send.expression.expression.text : void 0;
1608
1843
  const object = send?.arguments[0];
1609
- if (!receiver || !object || !ts6.isObjectLiteralExpression(object)) return void 0;
1844
+ if (!receiver || !object || !ts7.isObjectLiteralExpression(object)) return void 0;
1610
1845
  const pathExpr = propertyExpression(object, "path");
1611
1846
  const methodExpr = propertyExpression(object, "method");
1612
- const pathName = pathExpr && ts6.isIdentifier(pathExpr) ? pathExpr.text : void 0;
1847
+ const pathName = pathExpr && ts7.isIdentifier(pathExpr) ? pathExpr.text : void 0;
1613
1848
  const clientIndex = params.indexOf(receiver);
1614
1849
  const pathIndex = pathName ? params.indexOf(pathName) : -1;
1615
1850
  if (clientIndex < 0 || pathIndex < 0) return void 0;
1616
- const methodName = methodExpr && ts6.isIdentifier(methodExpr) ? methodExpr.text : void 0;
1851
+ const methodName = methodExpr && ts7.isIdentifier(methodExpr) ? methodExpr.text : void 0;
1617
1852
  const methodIndex = methodName ? params.indexOf(methodName) : -1;
1618
1853
  return { clientIndex, pathIndex, methodIndex: methodIndex >= 0 ? methodIndex : void 0, methodLiteral: literal(methodExpr), sourceFile, sourceLine: lineOf3(source, fn), chain: [name] };
1619
1854
  }
@@ -1622,11 +1857,11 @@ async function nestedSendSpec(repoPath, sourceFile, source, name, fn, cache, dep
1622
1857
  const byLocal = new Map(imports.filter((item) => item.sourceFile).map((item) => [item.localName, item]));
1623
1858
  const calls = [];
1624
1859
  visitFunctionBody(fn, (node) => {
1625
- if (ts6.isCallExpression(node) && ts6.isIdentifier(node.expression) && byLocal.has(node.expression.text)) calls.push(node);
1860
+ if (ts7.isCallExpression(node) && ts7.isIdentifier(node.expression) && byLocal.has(node.expression.text)) calls.push(node);
1626
1861
  });
1627
1862
  if (calls.length !== 1) return void 0;
1628
1863
  const call = calls[0];
1629
- const imported = call && ts6.isIdentifier(call.expression) ? byLocal.get(call.expression.text) : void 0;
1864
+ const imported = call && ts7.isIdentifier(call.expression) ? byLocal.get(call.expression.text) : void 0;
1630
1865
  const nested = imported ? await loadWrapperSpec(repoPath, imported, cache, depth + 1) : void 0;
1631
1866
  if (!call || !nested) return void 0;
1632
1867
  const params = parameterNames(fn);
@@ -1638,12 +1873,12 @@ async function nestedSendSpec(repoPath, sourceFile, source, name, fn, cache, dep
1638
1873
  }
1639
1874
  function wrapperCallFact(source, filePath, call, spec, serviceBindings) {
1640
1875
  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);
1876
+ if (!client || !ts7.isIdentifier(client) || !serviceBindings.has(client.text)) return void 0;
1643
1877
  const methodValue = spec.methodIndex === void 0 ? spec.methodLiteral : literal(call.arguments[spec.methodIndex]);
1644
1878
  const method = (methodValue ?? "POST").toUpperCase();
1645
- const rawPath = pathValue.rawExpression;
1646
- const operationPathExpr = pathValue.value ? normalizeOperationPath(pathValue.value) : runtimePathExpression(rawPath);
1879
+ const pathAnalysis = analyzeOperationPath(call.arguments[spec.pathIndex], call, method);
1880
+ const operationPathExpr = operationPathExpression(pathAnalysis);
1881
+ const unresolvedReason = pathUnresolvedReason(pathAnalysis);
1647
1882
  return {
1648
1883
  callType: "remote_action",
1649
1884
  serviceVariableName: client.text,
@@ -1653,59 +1888,41 @@ function wrapperCallFact(source, filePath, call, spec, serviceBindings) {
1653
1888
  sourceFile: normalizePath(filePath),
1654
1889
  sourceLine: lineOf3(source, call),
1655
1890
  confidence: operationPathExpr ? 0.85 : 0.5,
1656
- unresolvedReason: pathValue.value ? void 0 : "dynamic_operation_path_identifier",
1891
+ unresolvedReason,
1657
1892
  evidence: {
1658
1893
  parser: "typescript_ast",
1659
- classifier: pathValue.value ? "imported_wrapper_literal_path" : "imported_wrapper_dynamic_path",
1894
+ classifier: importedWrapperClassifier(pathAnalysis.status),
1660
1895
  receiver: client.text,
1661
1896
  wrapperFunction: spec.chain[0],
1662
1897
  wrapperChain: spec.chain,
1663
1898
  callerSite: { sourceFile: normalizePath(filePath), sourceLine: lineOf3(source, call) },
1664
1899
  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
1900
+ rawPathExpression: pathAnalysis.rawExpression,
1901
+ missingPathIdentifier: pathAnalysis.runtimeIdentifier,
1902
+ pathAnalysis,
1903
+ odataPathIntent: operationPathExpr ? classifyODataPathIntent(operationPathExpr, method) : void 0
1668
1904
  }
1669
1905
  };
1670
1906
  }
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;
1907
+ function importedWrapperClassifier(status) {
1908
+ if (status === "static") return "imported_wrapper_literal_path";
1909
+ if (status === "ambiguous") return "imported_wrapper_ambiguous_path";
1910
+ return "imported_wrapper_dynamic_path";
1694
1911
  }
1695
1912
  function findFunction(source, exportedName) {
1696
1913
  const localName = exportedLocalName(source, exportedName);
1697
1914
  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;
1915
+ if (ts7.isFunctionDeclaration(statement) && statement.name?.text === localName) return { name: exportedName, fn: statement };
1916
+ if (!ts7.isVariableStatement(statement)) continue;
1700
1917
  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 };
1918
+ 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
1919
  }
1703
1920
  }
1704
1921
  return void 0;
1705
1922
  }
1706
1923
  function exportedLocalName(source, exportedName) {
1707
1924
  for (const statement of source.statements) {
1708
- if (!ts6.isExportDeclaration(statement) || !statement.exportClause || !ts6.isNamedExports(statement.exportClause)) continue;
1925
+ if (!ts7.isExportDeclaration(statement) || !statement.exportClause || !ts7.isNamedExports(statement.exportClause)) continue;
1709
1926
  const match = statement.exportClause.elements.find((item) => item.name.text === exportedName);
1710
1927
  if (match) return match.propertyName?.text ?? match.name.text;
1711
1928
  }
@@ -1713,36 +1930,30 @@ function exportedLocalName(source, exportedName) {
1713
1930
  }
1714
1931
  function visitFunctionBody(fn, visitor) {
1715
1932
  const visit = (node) => {
1716
- if (node !== fn && ts6.isFunctionLike(node)) return;
1933
+ if (node !== fn && ts7.isFunctionLike(node)) return;
1717
1934
  visitor(node);
1718
- ts6.forEachChild(node, visit);
1935
+ ts7.forEachChild(node, visit);
1719
1936
  };
1720
1937
  if (fn.body) visit(fn.body);
1721
1938
  }
1722
1939
  function parameterNames(fn) {
1723
- return fn.parameters.map((parameter) => ts6.isIdentifier(parameter.name) ? parameter.name.text : "");
1940
+ return fn.parameters.map((parameter) => ts7.isIdentifier(parameter.name) ? parameter.name.text : "");
1724
1941
  }
1725
1942
  function mappedParameterIndex(expr, parameters) {
1726
- return expr && ts6.isIdentifier(expr) ? parameters.indexOf(expr.text) : -1;
1943
+ return expr && ts7.isIdentifier(expr) ? parameters.indexOf(expr.text) : -1;
1727
1944
  }
1728
1945
  function propertyExpression(object, key) {
1729
1946
  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;
1947
+ if (ts7.isPropertyAssignment(property) && propertyName(property.name) === key) return property.initializer;
1948
+ if (ts7.isShorthandPropertyAssignment(property) && property.name.text === key) return property.name;
1732
1949
  }
1733
1950
  return void 0;
1734
1951
  }
1735
1952
  function propertyName(name) {
1736
- return ts6.isIdentifier(name) || ts6.isStringLiteralLike(name) ? name.text : void 0;
1953
+ return ts7.isIdentifier(name) || ts7.isStringLiteralLike(name) ? name.text : void 0;
1737
1954
  }
1738
1955
  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;
1956
+ return expr && (ts7.isStringLiteralLike(expr) || ts7.isNoSubstitutionTemplateLiteral(expr)) ? expr.text : void 0;
1746
1957
  }
1747
1958
 
1748
1959
  // src/parsers/outbound-call-parser.ts
@@ -1751,47 +1962,47 @@ function lineOf4(text, idx) {
1751
1962
  }
1752
1963
  function entityFromExpression(expr) {
1753
1964
  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;
1965
+ if (ts8.isIdentifier(expr) || ts8.isStringLiteral(expr) || ts8.isNoSubstitutionTemplateLiteral(expr)) return expr.text;
1966
+ if (ts8.isPropertyAccessExpression(expr) && expr.expression.kind === ts8.SyntaxKind.ThisKeyword) return expr.name.text;
1967
+ if (ts8.isElementAccessExpression(expr) && expr.argumentExpression && (ts8.isStringLiteral(expr.argumentExpression) || ts8.isNoSubstitutionTemplateLiteral(expr.argumentExpression))) return expr.argumentExpression.text;
1757
1968
  return void 0;
1758
1969
  }
1759
1970
  function expressionName(expr) {
1760
- if (ts7.isIdentifier(expr)) return expr.text;
1761
- if (ts7.isPropertyAccessExpression(expr)) return `${expressionName(expr.expression)}.${expr.name.text}`;
1971
+ if (ts8.isIdentifier(expr)) return expr.text;
1972
+ if (ts8.isPropertyAccessExpression(expr)) return `${expressionName(expr.expression)}.${expr.name.text}`;
1762
1973
  return expr.getText();
1763
1974
  }
1764
1975
  function variableInitializers(source) {
1765
1976
  const initializers = /* @__PURE__ */ new Map();
1766
1977
  for (const statement of source.statements) {
1767
- if (!ts7.isVariableStatement(statement) || (statement.declarationList.flags & ts7.NodeFlags.Const) === 0) continue;
1978
+ if (!ts8.isVariableStatement(statement) || (statement.declarationList.flags & ts8.NodeFlags.Const) === 0) continue;
1768
1979
  for (const declaration of statement.declarationList.declarations) {
1769
- if (ts7.isIdentifier(declaration.name) && declaration.initializer) initializers.set(declaration.name.text, declaration.initializer);
1980
+ if (ts8.isIdentifier(declaration.name) && declaration.initializer) initializers.set(declaration.name.text, declaration.initializer);
1770
1981
  }
1771
1982
  }
1772
1983
  return initializers;
1773
1984
  }
1774
1985
  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)) {
1986
+ if (ts8.isParenthesizedExpression(expr) || ts8.isAwaitExpression(expr)) return queryEntityFromAst(expr.expression, initializers);
1987
+ if (ts8.isIdentifier(expr) && initializers.has(expr.text)) return queryEntityFromAst(initializers.get(expr.text), initializers);
1988
+ if (ts8.isCallExpression(expr)) {
1778
1989
  const name = expressionName(expr.expression);
1779
1990
  if (name === "cds.run") return queryEntityFromAst(expr.arguments[0], initializers);
1780
1991
  if (["SELECT.one.from", "SELECT.from", "SELECT.one", "INSERT.into", "UPSERT.into", "DELETE.from", "UPDATE.entity"].includes(name)) return entityFromExpression(expr.arguments[0]);
1781
1992
  if (name === "UPDATE") return entityFromExpression(expr.arguments[0]);
1782
- const receiver = ts7.isPropertyAccessExpression(expr.expression) ? expr.expression.expression : void 0;
1993
+ const receiver = ts8.isPropertyAccessExpression(expr.expression) ? expr.expression.expression : void 0;
1783
1994
  if (receiver) return queryEntityFromAst(receiver, initializers);
1784
1995
  }
1785
1996
  return void 0;
1786
1997
  }
1787
1998
  function extractQueryEntity(expr) {
1788
- const source = ts7.createSourceFile("query.ts", `const __query = (${expr});`, ts7.ScriptTarget.Latest, true, ts7.ScriptKind.TS);
1999
+ const source = ts8.createSourceFile("query.ts", `const __query = (${expr});`, ts8.ScriptTarget.Latest, true, ts8.ScriptKind.TS);
1789
2000
  const initializers = variableInitializers(source);
1790
2001
  let found;
1791
2002
  const visit = (node) => {
1792
2003
  if (found) return;
1793
- if (ts7.isParenthesizedExpression(node)) found = queryEntityFromAst(node.expression, initializers);
1794
- ts7.forEachChild(node, visit);
2004
+ if (ts8.isParenthesizedExpression(node)) found = queryEntityFromAst(node.expression, initializers);
2005
+ ts8.forEachChild(node, visit);
1795
2006
  };
1796
2007
  visit(source);
1797
2008
  return found;
@@ -1805,7 +2016,7 @@ function parserEvidence(source, node, extra) {
1805
2016
  return { parser: "typescript_ast", startOffset: node.getStart(source), endOffset: node.getEnd(), ...extra };
1806
2017
  }
1807
2018
  function isStringLike(expr) {
1808
- return Boolean(expr && (ts7.isStringLiteral(expr) || ts7.isNoSubstitutionTemplateLiteral(expr)));
2019
+ return Boolean(expr && (ts8.isStringLiteral(expr) || ts8.isNoSubstitutionTemplateLiteral(expr)));
1809
2020
  }
1810
2021
  function literalText(expr) {
1811
2022
  if (isStringLike(expr)) return expr.text;
@@ -1813,151 +2024,118 @@ function literalText(expr) {
1813
2024
  }
1814
2025
  function objectPropertyText(object, key) {
1815
2026
  const prop = object.properties.find(
1816
- (property) => ts7.isPropertyAssignment(property) && nameOfProperty(property.name) === key || ts7.isShorthandPropertyAssignment(property) && property.name.text === key
2027
+ (property) => ts8.isPropertyAssignment(property) && nameOfProperty(property.name) === key || ts8.isShorthandPropertyAssignment(property) && property.name.text === key
1817
2028
  );
1818
2029
  if (!prop) return void 0;
1819
- return ts7.isShorthandPropertyAssignment(prop) ? prop.name.text : prop.initializer.getText();
2030
+ return ts8.isShorthandPropertyAssignment(prop) ? prop.name.text : prop.initializer.getText();
1820
2031
  }
1821
2032
  function objectPropertyIsShorthand(object, key) {
1822
- return object.properties.some((property) => ts7.isShorthandPropertyAssignment(property) && property.name.text === key);
2033
+ return object.properties.some((property) => ts8.isShorthandPropertyAssignment(property) && property.name.text === key);
1823
2034
  }
1824
2035
  function nameOfProperty(name) {
1825
- if (ts7.isIdentifier(name) || ts7.isStringLiteral(name) || ts7.isNumericLiteral(name)) return name.text;
2036
+ if (ts8.isIdentifier(name) || ts8.isStringLiteral(name) || ts8.isNumericLiteral(name)) return name.text;
1826
2037
  return void 0;
1827
2038
  }
1828
- var maxAliasDepth = 5;
2039
+ var maxAliasDepth2 = 5;
1829
2040
  function safeRaw(expr) {
1830
- if (ts7.isStringLiteral(expr) || ts7.isNoSubstitutionTemplateLiteral(expr) || ts7.isIdentifier(expr) || ts7.isTemplateExpression(expr)) return expr.getText(expr.getSourceFile());
2041
+ if (ts8.isStringLiteral(expr) || ts8.isNoSubstitutionTemplateLiteral(expr) || ts8.isIdentifier(expr) || ts8.isTemplateExpression(expr)) return expr.getText(expr.getSourceFile());
1831
2042
  return void 0;
1832
2043
  }
1833
2044
  function placeholders2(expr) {
1834
2045
  return expr.templateSpans.map((span) => span.expression.getText(expr.getSourceFile()));
1835
2046
  }
1836
2047
  function isFunctionLikeScope(node) {
1837
- return ts7.isFunctionLike(node) || ts7.isSourceFile(node);
2048
+ return ts8.isFunctionLike(node) || ts8.isSourceFile(node);
1838
2049
  }
1839
2050
  function nodeContains(parent, child) {
1840
2051
  const source = child.getSourceFile();
1841
2052
  return child.getStart(source) >= parent.getStart(source) && child.getEnd() <= parent.getEnd();
1842
2053
  }
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;
2054
+ function declarationScope2(node) {
2055
+ if (ts8.isParameter(node)) return node.parent;
2056
+ if (ts8.isCatchClause(node.parent) && node.parent.variableDeclaration === node) return node.parent;
1846
2057
  const list = node.parent;
1847
- const blockScoped = (list.flags & (ts7.NodeFlags.Const | ts7.NodeFlags.Let)) !== 0;
2058
+ const blockScoped = (list.flags & (ts8.NodeFlags.Const | ts8.NodeFlags.Let)) !== 0;
1848
2059
  let current = list.parent;
1849
2060
  if (!blockScoped) {
1850
2061
  while (current.parent && !isFunctionLikeScope(current)) current = current.parent;
1851
2062
  return current;
1852
2063
  }
1853
- while (current.parent && !ts7.isBlock(current) && !ts7.isSourceFile(current) && !ts7.isModuleBlock(current) && !ts7.isCaseBlock(current) && !isLoopInitializerScope(node, current) && !isFunctionLikeScope(current)) current = current.parent;
2064
+ while (current.parent && !ts8.isBlock(current) && !ts8.isSourceFile(current) && !ts8.isModuleBlock(current) && !ts8.isCaseBlock(current) && !isLoopInitializerScope(node, current) && !isFunctionLikeScope(current)) current = current.parent;
1854
2065
  return current;
1855
2066
  }
1856
2067
  function isLoopInitializerScope(declaration, scope) {
1857
2068
  const list = declaration.parent;
1858
- return ts7.isForStatement(scope) && scope.initializer === list || (ts7.isForInStatement(scope) || ts7.isForOfStatement(scope)) && scope.initializer === list;
2069
+ return ts8.isForStatement(scope) && scope.initializer === list || (ts8.isForInStatement(scope) || ts8.isForOfStatement(scope)) && scope.initializer === list;
1859
2070
  }
1860
2071
  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;
2072
+ if (ts8.isParameter(declaration)) return void 0;
2073
+ return ts8.isCatchClause(declaration.parent) && declaration.parent.variableDeclaration === declaration ? declaration.parent : void 0;
1863
2074
  }
1864
2075
  function isAccessibleDeclaration(declaration, use) {
1865
2076
  const source = use.getSourceFile();
1866
2077
  if (declaration.name.getStart(source) >= use.getStart(source)) return false;
1867
2078
  const catchScope = catchBindingScope(declaration);
1868
2079
  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);
2080
+ const scope = declarationScope2(declaration);
2081
+ if (ts8.isForStatement(scope) || ts8.isForInStatement(scope) || ts8.isForOfStatement(scope)) return nodeContains(scope.statement, use);
2082
+ return ts8.isSourceFile(scope) || nodeContains(scope, use);
1872
2083
  }
1873
- function resolveBinding(identifier, use) {
2084
+ function resolveBinding2(identifier, use) {
1874
2085
  const source = use.getSourceFile();
1875
2086
  let best;
1876
2087
  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);
2088
+ if (ts8.isVariableDeclaration(node) && ts8.isIdentifier(node.name) && node.name.text === identifier.text && isAccessibleDeclaration(node, use)) best = node;
2089
+ if (ts8.isParameter(node) && ts8.isIdentifier(node.name) && node.name.text === identifier.text && isAccessibleDeclaration(node, use)) best = node;
2090
+ ts8.forEachChild(node, visit);
1880
2091
  };
1881
2092
  visit(source);
1882
2093
  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"] };
2094
+ const immutable = ts8.isVariableDeclaration(best) && (best.parent.flags & ts8.NodeFlags.Const) !== 0;
2095
+ 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
2096
  }
1886
2097
  function resolveExpression(expr, use, policy, depth = 0, seen = /* @__PURE__ */ new Set()) {
1887
2098
  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)) {
2099
+ if (ts8.isStringLiteral(expr)) return { status: "static", sourceKind: "string_literal", value: expr.text, rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["string_literal"] };
2100
+ if (ts8.isNoSubstitutionTemplateLiteral(expr)) return { status: "static", sourceKind: "no_substitution_template", value: expr.text, rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["no_substitution_template"] };
2101
+ if (ts8.isTemplateExpression(expr)) {
1891
2102
  const keys = placeholders2(expr);
1892
2103
  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
2104
  return { status: "dynamic", sourceKind: "template_with_substitutions", placeholderKeys: keys, evidence: ["template_substitutions_not_static_external_target"] };
1894
2105
  }
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);
2106
+ if (ts8.isIdentifier(expr)) {
2107
+ if (depth >= maxAliasDepth2) return { status: "unknown", sourceKind: "const_alias", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["alias_depth_exceeded"], constName: expr.text };
2108
+ const binding = resolveBinding2(expr, use);
1898
2109
  if (!binding.declaration || !binding.initializer || !binding.immutable) return { status: "dynamic", sourceKind: "dynamic_expression", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: binding.evidence, constName: expr.text };
1899
2110
  if (seen.has(binding.declaration)) return { status: "unknown", sourceKind: "const_alias", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ["alias_cycle_detected"], constName: expr.text };
1900
2111
  seen.add(binding.declaration);
1901
2112
  const resolved2 = resolveExpression(binding.initializer, binding.declaration, policy, depth + 1, seen);
1902
2113
  return { ...resolved2, sourceKind: "const_alias", rawExpression: safeRaw(expr), constName: expr.text, evidence: [...binding.evidence, ...resolved2.evidence] };
1903
2114
  }
1904
- return { status: "dynamic", sourceKind: "dynamic_expression", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: [`unsupported_${ts7.SyntaxKind[expr.kind] ?? "expression"}`] };
2115
+ return { status: "dynamic", sourceKind: "dynamic_expression", rawExpression: safeRaw(expr), placeholderKeys: [], evidence: [`unsupported_${ts8.SyntaxKind[expr.kind] ?? "expression"}`] };
1905
2116
  }
1906
2117
  function staticExpressionText(expr, initializers) {
1907
2118
  if (!expr) return void 0;
1908
2119
  if (isStringLike(expr)) return expr.text;
1909
- if (ts7.isIdentifier(expr) && initializers.has(expr.text)) return staticExpressionText(initializers.get(expr.text), initializers);
2120
+ if (ts8.isIdentifier(expr) && initializers.has(expr.text)) return staticExpressionText(initializers.get(expr.text), initializers);
1910
2121
  return void 0;
1911
2122
  }
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
2123
  function operationPathFromStatic(text) {
1918
2124
  return text ? `/${stripQuotes(text).replace(/^\//, "")}` : void 0;
1919
2125
  }
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
2126
  function destinationExpressionShape(expr) {
1949
2127
  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";
2128
+ if (ts8.isIdentifier(expr)) return "identifier";
2129
+ if (ts8.isPropertyAccessExpression(expr) || ts8.isElementAccessExpression(expr)) return "property_read";
2130
+ if (ts8.isCallExpression(expr)) return "function_call";
2131
+ if (ts8.isConditionalExpression(expr)) return "conditional";
2132
+ if (ts8.isBinaryExpression(expr)) return "binary_expression";
2133
+ if (ts8.isTemplateExpression(expr)) return "template_expression";
2134
+ return ts8.SyntaxKind[expr.kind] ?? "expression";
1957
2135
  }
1958
2136
  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;
2137
+ const resolved2 = expr && ts8.isIdentifier(expr) && initializers.has(expr.text) ? initializers.get(expr.text) : expr;
2138
+ if (!resolved2 || !ts8.isConditionalExpression(resolved2)) return void 0;
1961
2139
  const left = staticExpressionText(resolved2.whenTrue, initializers);
1962
2140
  const right = staticExpressionText(resolved2.whenFalse, initializers);
1963
2141
  if (!left || !right) return void 0;
@@ -1965,8 +2143,8 @@ function staticConditionalCandidates(expr, initializers) {
1965
2143
  }
1966
2144
  function propertyInitializer(object, key) {
1967
2145
  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;
2146
+ if (ts8.isPropertyAssignment(property) && nameOfProperty(property.name) === key) return property.initializer;
2147
+ if (ts8.isShorthandPropertyAssignment(property) && property.name.text === key) return property.name;
1970
2148
  }
1971
2149
  return void 0;
1972
2150
  }
@@ -1979,6 +2157,30 @@ function safeOperationName(value) {
1979
2157
  if (!value || !/^[A-Za-z_$][\w$]*(?:[./][A-Za-z_$][\w$]*)*$/.test(value)) return void 0;
1980
2158
  return operationPathFromStatic(value);
1981
2159
  }
2160
+ function wrapperSourceKind(sourceKind) {
2161
+ if (sourceKind.includes("const_alias")) return "const";
2162
+ if (sourceKind.includes("template")) return "template";
2163
+ if (sourceKind.includes("string_literal")) return "literal";
2164
+ return sourceKind.includes("conditional") ? "ambiguous" : "dynamic";
2165
+ }
2166
+ function literalPathSource(analysis) {
2167
+ if (analysis.status !== "static") return void 0;
2168
+ if (analysis.sourceKind.includes("const_alias")) return "same_scope_const_initializer";
2169
+ if (analysis.sourceKind.includes("no_substitution_template")) return "template";
2170
+ return analysis.sourceKind.includes("string_literal") ? "literal" : analysis.sourceKind;
2171
+ }
2172
+ function legacyPathCandidates(analysis) {
2173
+ if (analysis.candidateRawPaths.length < 2 && analysis.dynamicReassignments.length === 0)
2174
+ return void 0;
2175
+ return {
2176
+ candidatePaths: analysis.candidateRawPaths,
2177
+ normalizedCandidateOperations: analysis.candidateNormalizedOperationPaths.map((value) => value.replace(/^\//, "")),
2178
+ candidateSourceKind: analysis.sourceKind,
2179
+ candidateIdentifier: analysis.candidateIdentifier,
2180
+ hasDynamicAssignments: analysis.dynamicReassignments.length > 0,
2181
+ conservativeReason: analysis.dynamicReassignments.length > 0 ? "dynamic_assignment_observed" : "candidate_tie"
2182
+ };
2183
+ }
1982
2184
  function hasTemplatePlaceholder(value) {
1983
2185
  return /\$\{|%7B|%7D/i.test(value);
1984
2186
  }
@@ -2003,7 +2205,7 @@ function externalHttpEvidence(node, source) {
2003
2205
  const exprText = expr.getText(source);
2004
2206
  if (exprText === "useOrFetchDestination") {
2005
2207
  const objectArg = node.arguments[0];
2006
- if (objectArg && ts7.isObjectLiteralExpression(objectArg)) {
2208
+ if (objectArg && ts8.isObjectLiteralExpression(objectArg)) {
2007
2209
  const destination = destinationTargetFromExpression(propertyInitializer(objectArg, "destinationName"), node);
2008
2210
  return { externalTarget: destination ?? { kind: "unknown", dynamic: false }, classifier: "sap_destination_lookup", sourceCallShape: "useOrFetchDestination" };
2009
2211
  }
@@ -2011,13 +2213,13 @@ function externalHttpEvidence(node, source) {
2011
2213
  if (exprText === "executeHttpRequest") {
2012
2214
  const destination = destinationTargetFromExpression(node.arguments[0], node);
2013
2215
  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 };
2216
+ const method = config && ts8.isObjectLiteralExpression(config) ? httpMethodFromObject(config, node) : void 0;
2217
+ const url = config && ts8.isObjectLiteralExpression(config) ? urlTargetFromExpression(propertyInitializer(config, "url"), node) : { kind: "unknown", dynamic: false };
2016
2218
  return { method, externalTarget: destination ? { ...url, destination } : url, classifier: "sap_execute_http_request", sourceCallShape: "executeHttpRequest" };
2017
2219
  }
2018
2220
  if (exprText === "axios") {
2019
2221
  const config = node.arguments[0];
2020
- if (config && ts7.isObjectLiteralExpression(config)) {
2222
+ if (config && ts8.isObjectLiteralExpression(config)) {
2021
2223
  const method = httpMethodFromObject(config, node);
2022
2224
  return { method, externalTarget: urlTargetFromExpression(propertyInitializer(config, "url"), node), classifier: "axios_config_call", sourceCallShape: "axios(config)" };
2023
2225
  }
@@ -2025,10 +2227,10 @@ function externalHttpEvidence(node, source) {
2025
2227
  }
2026
2228
  if (exprText === "fetch") {
2027
2229
  const init = node.arguments[1];
2028
- const method = init && ts7.isObjectLiteralExpression(init) ? httpMethodFromObject(init, node) : void 0;
2230
+ const method = init && ts8.isObjectLiteralExpression(init) ? httpMethodFromObject(init, node) : void 0;
2029
2231
  return { method, externalTarget: urlTargetFromExpression(node.arguments[0], node), classifier: "fetch_call", sourceCallShape: "fetch" };
2030
2232
  }
2031
- if (ts7.isPropertyAccessExpression(expr) && ["get", "post", "put", "patch", "delete", "head"].includes(expr.name.text) && expr.expression.getText(source) === "axios") {
2233
+ if (ts8.isPropertyAccessExpression(expr) && ["get", "post", "put", "patch", "delete", "head"].includes(expr.name.text) && expr.expression.getText(source) === "axios") {
2032
2234
  return { method: expr.name.text.toUpperCase(), externalTarget: urlTargetFromExpression(node.arguments[0], node), classifier: "axios_member_call", sourceCallShape: `axios.${expr.name.text}` };
2033
2235
  }
2034
2236
  return void 0;
@@ -2036,27 +2238,27 @@ function externalHttpEvidence(node, source) {
2036
2238
  function collectServiceVariables(source) {
2037
2239
  const vars = /* @__PURE__ */ new Set(["cds", "messaging", "messageClient", "eventClient"]);
2038
2240
  const visit = (node) => {
2039
- if (ts7.isVariableDeclaration(node) && ts7.isIdentifier(node.name) && node.initializer) {
2241
+ if (ts8.isVariableDeclaration(node) && ts8.isIdentifier(node.name) && node.initializer) {
2040
2242
  const text = node.initializer.getText(source);
2041
2243
  if (/cds\.connect\.(to|messaging)\s*\(/.test(text)) vars.add(node.name.text);
2042
2244
  }
2043
- ts7.forEachChild(node, visit);
2245
+ ts8.forEachChild(node, visit);
2044
2246
  };
2045
2247
  visit(source);
2046
2248
  return vars;
2047
2249
  }
2048
2250
  function receiverName(expr) {
2049
- if (ts7.isIdentifier(expr)) return expr.text;
2050
- if (ts7.isPropertyAccessExpression(expr)) return expr.getText(sourceOf(expr));
2251
+ if (ts8.isIdentifier(expr)) return expr.text;
2252
+ if (ts8.isPropertyAccessExpression(expr)) return expr.getText(sourceOf(expr));
2051
2253
  return void 0;
2052
2254
  }
2053
2255
  function sourceOf(node) {
2054
2256
  return node.getSourceFile();
2055
2257
  }
2056
2258
  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);
2259
+ if (ts8.isIdentifier(expr)) return expr.text;
2260
+ if (ts8.isPropertyAccessExpression(expr)) return rootReceiverName(expr.expression);
2261
+ if (ts8.isCallExpression(expr)) return rootReceiverName(expr.expression);
2060
2262
  return void 0;
2061
2263
  }
2062
2264
  function isSupportedEventReceiver(receiver, rootReceiver, serviceVariables) {
@@ -2073,38 +2275,38 @@ function collectWrapperSpecs(source) {
2073
2275
  const serviceVariables = collectServiceVariables(source);
2074
2276
  const calledNames = /* @__PURE__ */ new Set();
2075
2277
  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);
2278
+ if (ts8.isCallExpression(node) && ts8.isIdentifier(node.expression))
2279
+ calledNames.add(node.expression.text);
2280
+ if (ts8.isCallExpression(node) && ts8.isCallExpression(node.expression) && ts8.isIdentifier(node.expression.expression))
2281
+ calledNames.add(node.expression.expression.text);
2282
+ ts8.forEachChild(node, collectCalls);
2081
2283
  };
2082
2284
  collectCalls(source);
2083
2285
  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);
2286
+ if (!calledNames.has(name) && !isExportedWrapper(fn)) return;
2287
+ const params = fn.parameters.map((param) => ts8.isIdentifier(param.name) ? param.name.text : void 0);
2086
2288
  const sends = [];
2087
2289
  const visit = (node) => {
2088
- if (ts7.isCallExpression(node) && ts7.isPropertyAccessExpression(node.expression) && node.expression.name.text === "send" && ts7.isIdentifier(node.expression.expression)) {
2290
+ if (ts8.isCallExpression(node) && ts8.isPropertyAccessExpression(node.expression) && node.expression.name.text === "send" && ts8.isIdentifier(node.expression.expression)) {
2089
2291
  const objectArg = node.arguments[0];
2090
- if (objectArg && ts7.isObjectLiteralExpression(objectArg)) {
2292
+ if (objectArg && ts8.isObjectLiteralExpression(objectArg)) {
2091
2293
  const pathProp = propertyInitializer(objectArg, "path");
2092
2294
  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;
2295
+ const pathName = pathProp && ts8.isIdentifier(pathProp) ? pathProp.text : void 0;
2296
+ const methodName = methodProp && ts8.isIdentifier(methodProp) ? methodProp.text : void 0;
2095
2297
  const methodLiteral = resolveExpression(methodProp, node, "literal").value;
2096
2298
  if (pathName) sends.push({ client: node.expression.expression.text, path: pathName, method: methodName, methodLiteral, start: node.getStart(source), end: node.getEnd() });
2097
2299
  }
2098
2300
  }
2099
- if (ts7.isCallExpression(node) && ts7.isIdentifier(node.expression) && specs.has(node.expression.text)) {
2301
+ if (ts8.isCallExpression(node) && ts8.isIdentifier(node.expression) && specs.has(node.expression.text)) {
2100
2302
  const nested = specs.get(node.expression.text);
2101
2303
  const pathArg = nested ? node.arguments[nested.pathIndex] : void 0;
2102
2304
  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;
2305
+ const pathName = pathArg && ts8.isIdentifier(pathArg) ? pathArg.text : void 0;
2306
+ const clientName = clientArg && ts8.isIdentifier(clientArg) ? clientArg.text : nested?.clientName;
2105
2307
  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
2308
  }
2107
- ts7.forEachChild(node, visit);
2309
+ ts8.forEachChild(node, visit);
2108
2310
  };
2109
2311
  visit(fn);
2110
2312
  if (sends.length !== 1) return;
@@ -2116,13 +2318,18 @@ function collectWrapperSpecs(source) {
2116
2318
  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
2319
  };
2118
2320
  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);
2321
+ if (ts8.isFunctionDeclaration(node) && node.name) scanFunction(node.name.text, node);
2322
+ if (ts8.isVariableDeclaration(node) && ts8.isIdentifier(node.name) && node.initializer && (ts8.isArrowFunction(node.initializer) || ts8.isFunctionExpression(node.initializer))) scanFunction(node.name.text, node.initializer);
2323
+ ts8.forEachChild(node, visitTop);
2122
2324
  };
2123
2325
  visitTop(source);
2124
2326
  return specs;
2125
2327
  }
2328
+ function isExportedWrapper(fn) {
2329
+ const declaration = ts8.isFunctionDeclaration(fn) ? fn : ts8.isVariableDeclaration(fn.parent) ? fn.parent.parent.parent : void 0;
2330
+ if (!declaration || !ts8.canHaveModifiers(declaration)) return false;
2331
+ return ts8.getModifiers(declaration)?.some((modifier) => modifier.kind === ts8.SyntaxKind.ExportKeyword) ?? false;
2332
+ }
2126
2333
  function classifyOutboundCallsInSource(source, filePath) {
2127
2334
  const calls = [];
2128
2335
  const sourceFile = normalizePath(filePath);
@@ -2134,7 +2341,7 @@ function classifyOutboundCallsInSource(source, filePath) {
2134
2341
  calls.push({ node, fact: { ...fact, sourceFile, sourceLine: lineOf4(source.text, node.getStart(source)), confidence: fact.confidence ?? 0.8, evidence: parserEvidence(source, node, extra) } });
2135
2342
  };
2136
2343
  const visit = (node) => {
2137
- if (ts7.isCallExpression(node)) {
2344
+ if (ts8.isCallExpression(node)) {
2138
2345
  if (wrapperInternalRanges.some((range) => node.getStart(source) >= range.start && node.getEnd() <= range.end)) {
2139
2346
  return;
2140
2347
  }
@@ -2145,24 +2352,23 @@ function classifyOutboundCallsInSource(source, filePath) {
2145
2352
  const entity = arg ? queryEntityFromAst(arg, initializers) : void 0;
2146
2353
  const payload = arg?.getText(source) ?? "";
2147
2354
  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))) {
2355
+ } else if (ts8.isPropertyAccessExpression(expr) && expr.name.text === "send" && (ts8.isIdentifier(expr.expression) || ts8.isPropertyAccessExpression(expr.expression))) {
2149
2356
  const objectArg = node.arguments[0];
2150
- if (objectArg && ts7.isObjectLiteralExpression(objectArg)) {
2357
+ if (objectArg && ts8.isObjectLiteralExpression(objectArg)) {
2151
2358
  const receiver = receiverName(expr.expression);
2152
2359
  const query = objectPropertyText(objectArg, "query");
2153
2360
  const method = stripQuotes(resolveExpression(propertyInitializer(objectArg, "method"), node, "literal").value ?? objectPropertyText(objectArg, "method") ?? "POST");
2154
2361
  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;
2362
+ const pathAnalysis = analyzeOperationPath(pathExpr, node, method);
2363
+ const op = pathExpr ? operationPathExpression(pathAnalysis) ?? pathExpr.getText(source) : void 0;
2157
2364
  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;
2365
+ const operationPathExpr = operationPathExpression(pathAnalysis);
2161
2366
  const intent = classifyODataPathIntent(operationPathExpr, method);
2162
2367
  const entityCallTypes = { entity_mutation: "remote_entity_mutation", entity_delete: "remote_entity_delete", entity_media: "remote_entity_media", entity_candidate: "remote_entity_candidate" };
2163
2368
  const entityCallType = entityCallTypes[intent.kind];
2164
2369
  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 });
2370
+ const unresolvedReason = !query && pathExpr ? pathUnresolvedReason(pathAnalysis) : void 0;
2371
+ 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
2372
  } else {
2167
2373
  const receiver = receiverName(expr.expression);
2168
2374
  const rootReceiver = rootReceiverName(expr.expression);
@@ -2171,33 +2377,35 @@ function classifyOutboundCallsInSource(source, filePath) {
2171
2377
  const pathArg = node.arguments[1];
2172
2378
  const supported = method && supportedHttpMethods.has(method);
2173
2379
  if (receiver && supported && serviceVariables.has(rootReceiver ?? receiver)) {
2174
- const resolvedPath = staticPathExpression(pathArg, node);
2175
- const operationPathExpr = operationPathFromStatic(resolvedPath.text);
2380
+ const pathAnalysis = analyzeOperationPath(pathArg, node, method);
2381
+ const operationPathExpr = operationPathExpression(pathAnalysis);
2176
2382
  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" });
2383
+ const unresolvedReason = pathUnresolvedReason(pathAnalysis);
2384
+ 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
2385
  } else if (receiver && serviceVariables.has(rootReceiver ?? receiver)) {
2179
2386
  const operationPathExpr = safeOperationName(firstArg2.value);
2180
2387
  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
2388
  }
2182
2389
  }
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;
2390
+ } else if (ts8.isCallExpression(expr) && ts8.isIdentifier(expr.expression) && wrapperSpecs.has(expr.expression.text) || ts8.isIdentifier(expr) && wrapperSpecs.has(expr.text)) {
2391
+ const wrapperName = ts8.isIdentifier(expr) ? expr.text : ts8.isCallExpression(expr) && ts8.isIdentifier(expr.expression) ? expr.expression.text : "";
2392
+ const wrapperArgs = ts8.isIdentifier(expr) ? node.arguments : ts8.isCallExpression(expr) ? expr.arguments : node.arguments;
2186
2393
  const spec = wrapperSpecs.get(wrapperName);
2187
2394
  const clientArg = spec?.clientIndex === void 0 ? void 0 : wrapperArgs[spec.clientIndex];
2188
2395
  const pathArg = spec ? wrapperArgs[spec.pathIndex] : void 0;
2189
2396
  const methodArg = spec?.methodIndex === void 0 ? void 0 : wrapperArgs[spec.methodIndex];
2190
- const receiver = clientArg && ts7.isIdentifier(clientArg) ? clientArg.text : spec?.clientName;
2397
+ const receiver = clientArg && ts8.isIdentifier(clientArg) ? clientArg.text : spec?.clientName;
2191
2398
  const method = stripQuotes(resolveExpression(methodArg, node, "literal").value ?? spec?.methodLiteral ?? "POST");
2192
- const resolvedPath = staticPathExpression(pathArg, node);
2193
- const operationPathExpr = operationPathFromStatic(resolvedPath.text);
2399
+ const pathAnalysis = analyzeOperationPath(pathArg, node, method);
2400
+ const operationPathExpr = operationPathExpression(pathAnalysis);
2194
2401
  const normalizedOperationPath = operationPathExpr ? classifyODataPathIntent(operationPathExpr, method).topLevelOperationName : void 0;
2402
+ const unresolvedReason = pathUnresolvedReason(pathAnalysis);
2195
2403
  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 });
2404
+ 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
2405
  } 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" });
2406
+ 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
2407
  }
2200
- } else if (ts7.isPropertyAccessExpression(expr) && ["emit", "publish", "on"].includes(expr.name.text)) {
2408
+ } else if (ts8.isPropertyAccessExpression(expr) && ["emit", "publish", "on"].includes(expr.name.text)) {
2201
2409
  const receiver = receiverName(expr.expression);
2202
2410
  const rootReceiver = rootReceiverName(expr.expression);
2203
2411
  if (isSupportedEventReceiver(receiver, rootReceiver, serviceVariables)) {
@@ -2213,7 +2421,7 @@ function classifyOutboundCallsInSource(source, filePath) {
2213
2421
  }
2214
2422
  }
2215
2423
  }
2216
- ts7.forEachChild(node, visit);
2424
+ ts8.forEachChild(node, visit);
2217
2425
  };
2218
2426
  visit(source);
2219
2427
  return calls;
@@ -2226,21 +2434,21 @@ function containsSupportedOutboundCall(node) {
2226
2434
  }
2227
2435
  async function parseOutboundCalls(repoPath, filePath) {
2228
2436
  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);
2437
+ const source = ts8.createSourceFile(filePath, text, ts8.ScriptTarget.Latest, true, filePath.endsWith(".ts") ? ts8.ScriptKind.TS : ts8.ScriptKind.JS);
2230
2438
  const bindingNames = new Set((await parseServiceBindings(repoPath, filePath)).map((binding) => binding.variableName));
2231
2439
  const importedWrappers = await parseImportedWrapperCalls(repoPath, filePath, source, bindingNames);
2232
2440
  return [...classifyOutboundCallsInSource(source, filePath).map((call) => call.fact), ...importedWrappers, ...parseLocalServiceCalls(text, filePath)];
2233
2441
  }
2234
2442
  function parseLocalServiceCalls(text, filePath) {
2235
- const source = ts7.createSourceFile(filePath, text, ts7.ScriptTarget.Latest, true, filePath.endsWith(".ts") ? ts7.ScriptKind.TS : ts7.ScriptKind.JS);
2443
+ const source = ts8.createSourceFile(filePath, text, ts8.ScriptTarget.Latest, true, filePath.endsWith(".ts") ? ts8.ScriptKind.TS : ts8.ScriptKind.JS);
2236
2444
  const aliases = /* @__PURE__ */ new Map();
2237
2445
  const calls = [];
2238
2446
  const visit = (node) => {
2239
- if (ts7.isVariableDeclaration(node) && ts7.isIdentifier(node.name) && node.initializer) {
2447
+ if (ts8.isVariableDeclaration(node) && ts8.isIdentifier(node.name) && node.initializer) {
2240
2448
  const origin = serviceLookup(node.initializer, aliases);
2241
2449
  if (origin) aliases.set(node.name.text, { ...origin, chain: [...origin.chain, node.name.text] });
2242
2450
  }
2243
- if (ts7.isCallExpression(node)) {
2451
+ if (ts8.isCallExpression(node)) {
2244
2452
  const parsed = serviceOperationCall(node, aliases);
2245
2453
  if (parsed && parsed.operation !== "entities") calls.push({
2246
2454
  callType: "local_service_call",
@@ -2263,20 +2471,20 @@ function parseLocalServiceCalls(text, filePath) {
2263
2471
  })
2264
2472
  });
2265
2473
  }
2266
- ts7.forEachChild(node, visit);
2474
+ ts8.forEachChild(node, visit);
2267
2475
  };
2268
2476
  visit(source);
2269
2477
  return calls;
2270
2478
  }
2271
2479
  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()] };
2480
+ if (ts8.isIdentifier(expr)) return aliases.get(expr.text);
2481
+ if (ts8.isPropertyAccessExpression(expr) && expr.expression.getText() === "cds.services") return { service: expr.name.text, lookup: expr.getText(), chain: [expr.getText()] };
2482
+ 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
2483
  return void 0;
2276
2484
  }
2277
2485
  function serviceOperationCall(node, aliases) {
2278
2486
  const expr = node.expression;
2279
- if (!ts7.isPropertyAccessExpression(expr)) return void 0;
2487
+ if (!ts8.isPropertyAccessExpression(expr)) return void 0;
2280
2488
  const origin = serviceLookup(expr.expression, aliases);
2281
2489
  if (!origin) return void 0;
2282
2490
  if (expr.name.text === "send") {
@@ -2741,9 +2949,9 @@ function normalizeDecoratorOperationSignal(value, raw, candidateOperation) {
2741
2949
  if (stringMatch?.[1]) {
2742
2950
  const identifier = stringMatch[1];
2743
2951
  const generated = generatedFromConstantName(identifier);
2744
- const normalizedCandidate = candidateOperation ? normalizedOperationName(candidateOperation) : void 0;
2952
+ const normalizedCandidate2 = candidateOperation ? normalizedOperationName(candidateOperation) : void 0;
2745
2953
  if (generated) return { status: "resolved", operationName: generated, raw: expression };
2746
- if (normalizedCandidate && identifier === normalizedCandidate) return { status: "resolved", operationName: identifier, raw: expression };
2954
+ if (normalizedCandidate2 && identifier === normalizedCandidate2) return { status: "resolved", operationName: identifier, raw: expression };
2747
2955
  return { status: "unsupported", raw: expression, reason: "string_wrapper_identifier_not_resolved" };
2748
2956
  }
2749
2957
  if (/^[`'"]/.test(expression) && !/[`'"]$/.test(expression)) return { status: "malformed", raw: expression, reason: "unterminated_literal" };
@@ -2807,7 +3015,36 @@ function insertCallEdge(db, workspaceId, call, vars, generation) {
2807
3015
  const operationLikeRemoteEntity = isRemoteEntityCall && Boolean(op) && credibleOperationSignal && (!strongEntitySignal || indexedOperationCandidateCount > 0);
2808
3016
  const isOperationCall = operationLikeRemoteEntity || (callType === "remote_action" || callType === "local_service_call" || callType === "remote_query" && Boolean(op));
2809
3017
  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 };
3018
+ const evidence = {
3019
+ ...callEvidence(call, resolution, servicePath, op, destination ? applyVariables(destination, vars) : void 0, normalized, intent),
3020
+ indexedOperationCandidateCount,
3021
+ parserCallType: callType,
3022
+ entityOperationPrecedence: operationPrecedence(
3023
+ callType,
3024
+ intent,
3025
+ indexedOperationCandidateCount,
3026
+ Boolean(resolution.target)
3027
+ )
3028
+ };
3029
+ const pathAnalysis = objectValue(objectJson(call.evidence_json)?.pathAnalysis);
3030
+ if (callType === "remote_action" && pathAnalysis?.status === "ambiguous") {
3031
+ const candidatePaths = Array.isArray(pathAnalysis.candidateRawPaths) ? pathAnalysis.candidateRawPaths : [];
3032
+ 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(
3033
+ workspaceId,
3034
+ "UNRESOLVED_EDGE",
3035
+ "ambiguous",
3036
+ "call",
3037
+ String(call.id),
3038
+ "operation_candidates",
3039
+ candidatePaths.join(","),
3040
+ Number(call.confidence ?? 0.5),
3041
+ JSON.stringify(evidence),
3042
+ 0,
3043
+ "Ambiguous operation path candidates require explicit disambiguation",
3044
+ generation
3045
+ );
3046
+ return { status: "ambiguous", callType };
3047
+ }
2811
3048
  if (isRemoteEntityCall && (resolution.target || resolution.candidates.length > 0 || resolution.status === "dynamic")) {
2812
3049
  if (resolution.target) {
2813
3050
  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 +3090,34 @@ function operationCandidateCount(db, workspaceId, operationPath, operationName)
2853
3090
  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
3091
  return Number(row?.count ?? 0);
2855
3092
  }
3093
+ function operationPrecedence(callType, intent, indexedOperationCandidateCount, resolvedOperation) {
3094
+ if (resolvedOperation) {
3095
+ return {
3096
+ decision: "operation",
3097
+ reason: "indexed_operation_with_strong_service_context",
3098
+ indexedOperationCandidateCount
3099
+ };
3100
+ }
3101
+ if (callType === "remote_action" && intent.kind === "operation_invocation") {
3102
+ return {
3103
+ decision: "operation_candidate",
3104
+ rejectionReason: indexedOperationCandidateCount > 0 ? "indexed_candidates_lack_unique_strong_service_context" : "no_indexed_operation_candidate",
3105
+ indexedOperationCandidateCount
3106
+ };
3107
+ }
3108
+ if (intent.kind.startsWith("entity_")) {
3109
+ return {
3110
+ decision: "entity",
3111
+ rejectionReason: indexedOperationCandidateCount > 0 ? "entity_shape_has_precedence_without_resolved_operation_context" : "entity_shape_has_no_indexed_operation_evidence",
3112
+ indexedOperationCandidateCount
3113
+ };
3114
+ }
3115
+ return {
3116
+ decision: "unresolved",
3117
+ rejectionReason: "path_has_no_safe_entity_or_operation_precedence",
3118
+ indexedOperationCandidateCount
3119
+ };
3120
+ }
2856
3121
  function unresolvedOperationReason(resolution) {
2857
3122
  if (resolution.status === "dynamic") return `Dynamic target requires runtime variable overrides: ${(resolution.reasons.length ? resolution.reasons : ["missing runtime variables"]).join(", ")}`;
2858
3123
  if (resolution.candidates.length === 0) return "No indexed target operation matched";
@@ -2911,7 +3176,7 @@ function linkImplementations(db, workspaceId, generation) {
2911
3176
  const duplicateFamilies = duplicatePackageFamilies(accepted);
2912
3177
  const duplicatePackageAmbiguous = duplicateFamilies.length > 0 && !accepted.some(hasDirectOwnershipEvidence);
2913
3178
  const selected = duplicatePackageAmbiguous ? accepted : winners;
2914
- const unique = !duplicatePackageAmbiguous && winners.length === 1 ? winners[0] : void 0;
3179
+ const unique2 = !duplicatePackageAmbiguous && winners.length === 1 ? winners[0] : void 0;
2915
3180
  const ambiguityReasons = duplicatePackageAmbiguous ? ["duplicate_package_name_candidates"] : winners.length > 1 ? ["multiple_equal_score_implementation_candidates"] : [];
2916
3181
  const evidence = {
2917
3182
  servicePath: operation.servicePath,
@@ -2925,16 +3190,16 @@ function linkImplementations(db, workspaceId, generation) {
2925
3190
  candidateFamilies: duplicateFamilies,
2926
3191
  candidates: candidates.map((candidate, index) => candidateEvidence(candidate, index + 1))
2927
3192
  };
2928
- const evidenceWithHints = unique ? evidence : { ...evidence, implementationHintSuggestions: implementationHintSuggestions(evidence) };
3193
+ const evidenceWithHints = unique2 ? evidence : { ...evidence, implementationHintSuggestions: implementationHintSuggestions(evidence) };
2929
3194
  if (accepted.length === 0) {
2930
3195
  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
3196
  edgeCount += 1;
2932
3197
  unresolvedCount += 1;
2933
3198
  continue;
2934
3199
  }
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);
3200
+ 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
3201
  edgeCount += 1;
2937
- if (unique) resolvedCount += 1;
3202
+ if (unique2) resolvedCount += 1;
2938
3203
  else ambiguousCount += 1;
2939
3204
  }
2940
3205
  return { edgeCount, resolvedCount, ambiguousCount, unresolvedCount };
@@ -3189,11 +3454,12 @@ function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence) {
3189
3454
  persistedResolution: persistedResolution(row)
3190
3455
  };
3191
3456
  }
3192
- function runtimeResolution(db, row, evidence, vars, workspaceId) {
3457
+ function runtimeResolution(db, row, evidence, vars, workspaceId, contextualUnresolvedReason) {
3193
3458
  const substituted = evidenceWithRuntimeVariables(evidence, vars);
3194
3459
  if (!isRemoteRuntimeCandidate(row, evidence, vars)) {
3195
- const withSections = withEffectiveResolution(substituted, row, row.unresolved_reason);
3196
- return { row, evidence: withSections, unresolvedReason: row.unresolved_reason };
3460
+ const unresolvedReason2 = contextualUnresolvedReason ?? row.unresolved_reason;
3461
+ const withSections = withEffectiveResolution(substituted, row, unresolvedReason2);
3462
+ return { row, evidence: withSections, unresolvedReason: unresolvedReason2 };
3197
3463
  }
3198
3464
  const resolution = resolveRuntimeOperation(db, substituted, workspaceId);
3199
3465
  if (resolution.target) {
@@ -3206,6 +3472,9 @@ function runtimeResolution(db, row, evidence, vars, workspaceId) {
3206
3472
  function runtimeVariableDiagnostic(edges) {
3207
3473
  const missing = /* @__PURE__ */ new Set();
3208
3474
  for (const edge of edges) {
3475
+ const effective = parseObject(edge.evidence.effectiveResolution);
3476
+ if (!["dynamic", "unresolved", "ambiguous"].includes(String(effective.status ?? "")))
3477
+ continue;
3209
3478
  const substitutions = edge.evidence.runtimeSubstitutions;
3210
3479
  if (!substitutions || typeof substitutions !== "object" || Array.isArray(substitutions)) continue;
3211
3480
  for (const value of Object.values(substitutions))
@@ -3295,11 +3564,17 @@ function evidenceWithRuntimeVariables(evidence, vars) {
3295
3564
  function runtimeSubstitutions(evidence, vars) {
3296
3565
  const substitutions = {};
3297
3566
  for (const key of ["servicePath", "operationPath", "serviceAliasExpr", "serviceAlias", "destination"]) {
3298
- const substitution = substituteVariables(stringValue3(evidence[key]), vars);
3567
+ const substitution = substituteVariables(substitutionValue(evidence, key), vars);
3299
3568
  if (substitution.placeholders.length > 0) substitutions[key] = substitution;
3300
3569
  }
3301
3570
  return substitutions;
3302
3571
  }
3572
+ function substitutionValue(evidence, key) {
3573
+ const value = stringValue3(evidence[key]);
3574
+ if (key !== "operationPath") return value;
3575
+ const normalized = normalizeODataOperationInvocationPath(value);
3576
+ return normalized?.wasInvocation ? normalized.normalizedOperationPath : value;
3577
+ }
3303
3578
  function isRemoteRuntimeCandidate(row, evidence, vars) {
3304
3579
  if (!vars || Object.keys(vars).length === 0) return false;
3305
3580
  if (!["dynamic", "ambiguous", "unresolved"].includes(String(row.status ?? ""))) return false;
@@ -3332,32 +3607,53 @@ function positiveDepth(value) {
3332
3607
  function operationStartScope(db, repoId, start, hintOptions) {
3333
3608
  const requested = normalizeOperation(start.operationPath ?? start.operation);
3334
3609
  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
3610
+ 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
3611
  FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
3337
3612
  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
3613
  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
3614
  if (rows2.length === 0) return void 0;
3340
3615
  const repoCount = new Set(rows2.map((row) => String(row.repoName))).size;
3341
3616
  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 }] };
3617
+ if (!repoId && repoCount > 1)
3618
+ return { diagnostics: [ambiguousStartDiagnostic(requested, rows2, "Operation trace start matched multiple repositories; add --repo to disambiguate")] };
3619
+ if (!start.servicePath && serviceCount > 1)
3620
+ return { diagnostics: [ambiguousStartDiagnostic(requested, rows2, "Operation trace start matched multiple services; add --service to disambiguate")] };
3621
+ if (rows2.length !== 1)
3622
+ return { diagnostics: [ambiguousStartDiagnostic(requested, rows2, "Operation trace start matched multiple indexed operations")] };
3345
3623
  const operationId = String(rows2[0]?.operationId);
3346
3624
  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: [] };
3625
+ 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
3626
  const hinted = implementationMethodIdFromHint(impl.edge, hintOptions);
3349
3627
  if (hinted.methodId) {
3350
3628
  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: [] };
3629
+ if (hintedScope?.files.size) return { files: hintedScope.files, symbols: hintedScope.symbolId ? /* @__PURE__ */ new Set([hintedScope.symbolId]) : void 0, repoId: hintedScope.repoId, operationId, diagnostics: [] };
3352
3630
  }
3353
3631
  if (impl.edge) {
3354
3632
  const evidence = parseEvidence(impl.edge.evidence_json);
3355
3633
  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 }];
3634
+ 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
3635
  return { operationId, diagnostics: hintDiagnostic ? [hintDiagnostic, ...diagnostics] : diagnostics };
3358
3636
  }
3359
3637
  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
3638
  }
3639
+ function implementationRejectionReasons(evidence) {
3640
+ const candidates = Array.isArray(evidence.candidates) ? evidence.candidates.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
3641
+ const reasons = candidates.flatMap((candidate) => Array.isArray(candidate.rejectedReasons) ? candidate.rejectedReasons.filter((reason) => typeof reason === "string") : []);
3642
+ return [...new Set(reasons)].sort();
3643
+ }
3644
+ function ambiguousStartDiagnostic(requested, candidates, message) {
3645
+ const serviceSuggestions = [...new Set(candidates.flatMap((row) => typeof row.servicePath === "string" ? [`--service ${row.servicePath}`] : []))].sort();
3646
+ return {
3647
+ severity: "warning",
3648
+ code: "trace_start_ambiguous",
3649
+ message,
3650
+ normalizedSelectorValue: requested,
3651
+ resolutionStage: "operation",
3652
+ resolutionStatus: "ambiguous_operation",
3653
+ candidates,
3654
+ serviceSuggestions
3655
+ };
3656
+ }
3361
3657
  function sourceFilesForStart(db, repoId, start) {
3362
3658
  const handler = start.handler;
3363
3659
  const operation = normalizeOperation(start.operation ?? start.operationPath);
@@ -3385,7 +3681,7 @@ function sourceFilesForStart(db, repoId, start) {
3385
3681
  operation,
3386
3682
  operation
3387
3683
  );
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)) };
3684
+ 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
3685
  if (start.servicePath && operation) {
3390
3686
  const implRows = db.prepare(`SELECT DISTINCT hc.source_file sourceFile,sym.id symbolId
3391
3687
  FROM cds_services s JOIN cds_operations o ON o.service_id=s.id
@@ -3394,7 +3690,7 @@ function sourceFilesForStart(db, repoId, start) {
3394
3690
  JOIN handler_classes hc ON hc.id=hm.handler_class_id
3395
3691
  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
3692
  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)) };
3693
+ 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
3694
  }
3399
3695
  return void 0;
3400
3696
  }
@@ -3414,6 +3710,7 @@ function startScope(db, start, hintOptions) {
3414
3710
  return { repo, selectorMatched: false };
3415
3711
  return {
3416
3712
  repo,
3713
+ executionRepoId: sourceScope?.repoId ?? repo?.id,
3417
3714
  sourceFiles,
3418
3715
  symbolIds: sourceScope?.symbols,
3419
3716
  selectorMatched: !terminalOperationScope && (!hasSelector || sourceFiles !== void 0),
@@ -3560,20 +3857,59 @@ function knownBindingsForCalls(db, calls) {
3560
3857
  function knownBindingsForScope(db, repoId, symbolIds, files) {
3561
3858
  const map = /* @__PURE__ */ new Map();
3562
3859
  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
3860
+ 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
3861
  FROM service_bindings b LEFT JOIN cds_requires req ON req.repo_id=b.repo_id AND req.alias=b.alias
3565
3862
  WHERE b.repo_id=?`).all(repoId);
3566
3863
  for (const row of rows2) {
3567
3864
  if (!row.variableName) continue;
3568
3865
  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;
3866
+ if (symbolIds && symbolIds.size > 0 && row.symbolId != null && !symbolIds.has(Number(row.symbolId))) continue;
3867
+ const candidate = enrichBinding({
3868
+ ...row,
3869
+ bindingId: Number(row.id),
3870
+ source: "local_service_binding",
3871
+ calleeReceiver: row.variableName,
3872
+ resolutionStatus: "selected"
3873
+ });
3874
+ const existing = map.get(row.variableName);
3875
+ if (!existing) {
3876
+ map.set(row.variableName, candidate);
3877
+ continue;
3572
3878
  }
3573
- map.set(row.variableName, enrichBinding({ ...row, bindingId: Number(row.id), source: "local_service_binding", calleeReceiver: row.variableName }));
3879
+ const candidates = uniqueBindingCandidates([
3880
+ ...existing.bindingCandidates ?? [bindingCandidateEvidence(existing)],
3881
+ bindingCandidateEvidence(candidate)
3882
+ ]);
3883
+ map.set(row.variableName, {
3884
+ ...candidate,
3885
+ bindingId: void 0,
3886
+ source: "ambiguous_local_service_bindings",
3887
+ resolutionStatus: "ambiguous",
3888
+ bindingCandidates: candidates
3889
+ });
3574
3890
  }
3575
3891
  return map;
3576
3892
  }
3893
+ function bindingCandidateEvidence(binding) {
3894
+ return {
3895
+ bindingId: binding.bindingId,
3896
+ sourceFile: binding.sourceFile,
3897
+ sourceLine: binding.sourceLine,
3898
+ alias: binding.alias,
3899
+ aliasExpr: binding.aliasExpr,
3900
+ destinationExpr: binding.destinationExpr,
3901
+ servicePathExpr: binding.servicePathExpr
3902
+ };
3903
+ }
3904
+ function uniqueBindingCandidates(candidates) {
3905
+ const seen = /* @__PURE__ */ new Set();
3906
+ return candidates.filter((candidate) => {
3907
+ const key = JSON.stringify(candidate);
3908
+ if (seen.has(key)) return false;
3909
+ seen.add(key);
3910
+ return true;
3911
+ });
3912
+ }
3577
3913
  function contextForSymbolCall(db, symbolCall, callerBindings) {
3578
3914
  const next = /* @__PURE__ */ new Map();
3579
3915
  if (callerBindings.size === 0) return next;
@@ -3623,6 +3959,21 @@ function contextForSymbolCall(db, symbolCall, callerBindings) {
3623
3959
  }
3624
3960
  function contextualRuntimeResolution(db, call, binding, workspaceId, persistedRows = []) {
3625
3961
  if (!binding || String(call.call_type) !== "remote_action" || call.operation_path_expr === void 0 || call.operation_path_expr === null) return {};
3962
+ if (binding.resolutionStatus === "ambiguous") {
3963
+ return {
3964
+ evidence: {
3965
+ contextualServiceBindingAttempted: true,
3966
+ contextualBinding: {
3967
+ source: binding.source,
3968
+ status: "tied",
3969
+ candidates: binding.bindingCandidates
3970
+ },
3971
+ contextualResolutionStatus: "ambiguous",
3972
+ contextualCandidateCount: binding.bindingCandidates?.length ?? 0
3973
+ },
3974
+ unresolvedReason: "Ambiguous contextual service binding candidates"
3975
+ };
3976
+ }
3626
3977
  const normalized = normalizeODataOperationInvocationPath(String(call.operation_path_expr));
3627
3978
  const op = normalized?.normalizedOperationPath ?? (String(call.operation_path_expr).startsWith("/") ? String(call.operation_path_expr) : `/${String(call.operation_path_expr)}`);
3628
3979
  const servicePath = binding.effectiveServicePath ?? binding.servicePathExpr ?? binding.requireServicePath;
@@ -3655,7 +4006,7 @@ function trace(db, start, options) {
3655
4006
  const edges = [];
3656
4007
  const nodes = /* @__PURE__ */ new Map();
3657
4008
  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() }] : [];
4009
+ const queue = scope.selectorMatched ? [{ repoId: scope.executionRepoId, files: scope.sourceFiles, symbolIds: scope.symbolIds, depth: 1, context: /* @__PURE__ */ new Map() }] : [];
3659
4010
  if (scope.startOperationId && scope.selectorMatched) {
3660
4011
  const op = operationNode(db, scope.startOperationId);
3661
4012
  const impl = implementationScope(db, scope.startOperationId);
@@ -3725,7 +4076,7 @@ function trace(db, start, options) {
3725
4076
  String(row.evidence_json || "{}")
3726
4077
  );
3727
4078
  const rawEvidence = baseTraceEvidence(row, call, persistedEvidence, contextual.evidence);
3728
- const effective = runtimeResolution(db, row, rawEvidence, options.vars, workspaceIdForCall(db, String(call.id)));
4079
+ const effective = runtimeResolution(db, row, rawEvidence, options.vars, workspaceIdForCall(db, String(call.id)), contextual.unresolvedReason);
3729
4080
  const evidence = effective.evidence;
3730
4081
  const effectiveRow = effective.row;
3731
4082
  const targetNode = `${effectiveRow.to_kind}:${effectiveRow.to_id}`;
@@ -3826,4 +4177,4 @@ export {
3826
4177
  linkWorkspace,
3827
4178
  trace
3828
4179
  };
3829
- //# sourceMappingURL=chunk-EGY2A4AT.js.map
4180
+ //# sourceMappingURL=chunk-XOROZHW4.js.map