@saptools/service-flow 0.1.17 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +0 -4
- package/TECHNICAL-NOTE.md +14 -0
- package/dist/{chunk-HBR27SPD.js → chunk-YG66SHAR.js} +153 -24
- package/dist/chunk-YG66SHAR.js.map +1 -0
- package/dist/cli.js +52 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-HBR27SPD.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.19
|
|
4
|
+
|
|
5
|
+
- Normalize balanced top-level OData action/function invocation paths before remote operation resolution, including namespace-qualified invocation lookup, while preserving raw and normalized evidence.
|
|
6
|
+
- Model remote service-client queries as terminal remote-query edges with stable semantic targets instead of unresolved operation calls.
|
|
7
|
+
- Add strict doctor aggregates for remote-query target quality and OData invocation resolution quality.
|
|
8
|
+
- Document remote query terminal semantics and normalized invocation resolution behavior.
|
|
9
|
+
|
|
10
|
+
## 0.1.18
|
|
11
|
+
|
|
12
|
+
- Applied conservative CAP receiver eligibility to `.emit()` and `.publish()` so generic realtime, socket, DOM, and EventEmitter-style calls no longer become CAP async event facts without explicit CAP messaging/service evidence.
|
|
13
|
+
- Added structured parser evidence for local CAP service calls, including source offsets, service lookup/name, operation, and alias chain.
|
|
14
|
+
- Propagated outbound parser evidence into call-derived graph and JSON trace evidence under `outboundEvidence` for remote, local, DB, external, and async edges.
|
|
15
|
+
- Clarified graph-level dynamic flags so terminal DB, external, and event edges stay static while dynamic binding details remain in edge evidence.
|
|
16
|
+
- Expanded `doctor --strict` with outbound evidence, graph evidence propagation, event receiver classification, and dynamic terminal-edge consistency aggregates.
|
|
17
|
+
|
|
3
18
|
## 0.1.17
|
|
4
19
|
|
|
5
20
|
- Replaced remaining raw-text outbound call detection with TypeScript AST classification so comments and strings do not create outbound facts or graph edges.
|
package/README.md
CHANGED
|
@@ -18,10 +18,6 @@ Index independent Git repositories, persist CAP/CDS facts in SQLite, resolve cro
|
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
|
-
## 0.1.17 quality update
|
|
22
|
-
|
|
23
|
-
`service-flow` 0.1.17 hardens outbound extraction after the 0.1.16 audit. Outbound calls are classified from the TypeScript AST, so comments and strings are ignored; CAP/service lifecycle registrations that remain indexed receive synthetic event-registration owners; generic event emitters and response `.send()` calls are not treated as CAP service-flow edges; and `doctor --strict` reports actionable ownerless categories. The previous 0.1.16 source ownership and proxy-evidence improvements remain in place. It indexes class property arrow/function members, creates conservative synthetic callback symbols only for top-level framework callbacks containing supported outbound calls, prefers explicit outbound source-symbol names when provided, hardens proxy-member resolution away from ambiguous global name-only matches, and splits link output into remote/local/unresolved/ambiguous/dynamic/terminal operation-call buckets.
|
|
24
|
-
|
|
25
21
|
## ✨ Features
|
|
26
22
|
|
|
27
23
|
- 🧭 **Cross-repository CAP tracing** — starts from a repo, service, operation path, operation name, or handler and follows the indexed flow across workspace boundaries
|
package/TECHNICAL-NOTE.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Service Flow Resolution Notes
|
|
2
2
|
|
|
3
|
+
## 0.1.19 remote invocation and query notes
|
|
4
|
+
|
|
5
|
+
- Remote action/function paths are scanned for a balanced top-level OData invocation suffix. Single-segment operation imports like `/readConfig(...)` normalize to `/readConfig`; namespace-qualified operation imports keep the qualified request segment for evidence and can resolve against the indexed simple CDS operation when service signals are strong. Navigation/property paths like `/Orders(id='123')/items` are left unchanged. Graph evidence keeps both `rawOperationPath` and `normalizedOperationPath` when normalization occurs.
|
|
6
|
+
- Remote `send({ query })` calls without explicit operation-path evidence now become `HANDLER_RUNS_REMOTE_QUERY` terminal edges. Static entities produce `Remote entity: ...` targets; dynamic or unknown entities produce `Remote query: unknown` with parser-warning evidence.
|
|
7
|
+
- Strict doctor includes remote-query target-quality and OData invocation-resolution aggregates to catch numeric query targets and unresolved normalized invocation paths.
|
|
8
|
+
|
|
9
|
+
## 0.1.18 auditability notes
|
|
10
|
+
|
|
11
|
+
- CAP async event parsing treats `.emit()`, `.publish()`, and `.on()` consistently: a row is indexed only when the direct or chained root receiver has explicit CAP service or messaging evidence such as `cds` or a variable initialized from `cds.connect.to(...)`. Generic realtime/socket/EventEmitter receivers are ignored for CAP graph purposes by default.
|
|
12
|
+
- Local CAP service calls now carry TypeScript AST evidence with classifier, source offsets, service lookup/name, operation, and alias chain.
|
|
13
|
+
- Call-derived graph evidence nests persisted outbound parser evidence as `outboundEvidence`, allowing JSON trace output to explain parser classification without colliding with linker fields.
|
|
14
|
+
- `graph_edges.is_dynamic` means the edge itself requires runtime operation-target resolution. Terminal database, external HTTP, and async event edges keep `is_dynamic=0`; dynamic binding provenance remains in `evidence_json.bindingHasDynamicExpression`.
|
|
15
|
+
- Strict doctor adds aggregate checks for outbound evidence JSON quality, graph evidence propagation, event receiver classification, and dynamic terminal-edge consistency.
|
|
16
|
+
|
|
3
17
|
- Imported helper bindings: TypeScript imports are resolved for relative modules. When a caller assigns `const client = await connectToService()`, the analyzer follows the imported symbol to an exported helper that returns `cds.connect.to(...)` and persists caller-variable evidence plus the helper source/export chain.
|
|
4
18
|
- Candidate ranking: operation-path matches start as weak candidates. A resolved operation edge requires a strong signal such as exact service path, CDS alias/destination context, or explicit dynamic variable overrides. Otherwise candidates are preserved in edge evidence as ambiguous or unresolved.
|
|
5
19
|
- Edge states: `REMOTE_CALL_RESOLVES_TO_OPERATION` is used only above the resolution threshold; `DYNAMIC_EDGE_CANDIDATE` preserves runtime-dependent service paths/destinations; `UNRESOLVED_EDGE` carries candidate counts and reasons when static evidence is insufficient.
|
|
@@ -602,7 +602,7 @@ function collectServiceVariables(source) {
|
|
|
602
602
|
const visit = (node) => {
|
|
603
603
|
if (ts4.isVariableDeclaration(node) && ts4.isIdentifier(node.name) && node.initializer) {
|
|
604
604
|
const text = node.initializer.getText(source);
|
|
605
|
-
if (/cds\.connect\.(to|messaging)\s*\(/.test(text)
|
|
605
|
+
if (/cds\.connect\.(to|messaging)\s*\(/.test(text)) vars.add(node.name.text);
|
|
606
606
|
}
|
|
607
607
|
ts4.forEachChild(node, visit);
|
|
608
608
|
};
|
|
@@ -611,14 +611,25 @@ function collectServiceVariables(source) {
|
|
|
611
611
|
}
|
|
612
612
|
function receiverName(expr) {
|
|
613
613
|
if (ts4.isIdentifier(expr)) return expr.text;
|
|
614
|
-
if (ts4.isPropertyAccessExpression(expr)) return expr.getText();
|
|
614
|
+
if (ts4.isPropertyAccessExpression(expr)) return expr.getText(sourceOf(expr));
|
|
615
615
|
return void 0;
|
|
616
616
|
}
|
|
617
|
-
function
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
if (
|
|
617
|
+
function sourceOf(node) {
|
|
618
|
+
return node.getSourceFile();
|
|
619
|
+
}
|
|
620
|
+
function rootReceiverName(expr) {
|
|
621
|
+
if (ts4.isIdentifier(expr)) return expr.text;
|
|
622
|
+
if (ts4.isPropertyAccessExpression(expr)) return rootReceiverName(expr.expression);
|
|
623
|
+
if (ts4.isCallExpression(expr)) return rootReceiverName(expr.expression);
|
|
624
|
+
return void 0;
|
|
625
|
+
}
|
|
626
|
+
function isSupportedEventReceiver(receiver, rootReceiver, serviceVariables) {
|
|
627
|
+
const candidate = rootReceiver ?? receiver;
|
|
628
|
+
if (!candidate) return false;
|
|
629
|
+
if (candidate === "cds") return true;
|
|
630
|
+
if (serviceVariables.has(candidate)) return true;
|
|
631
|
+
if (receiver && serviceVariables.has(receiver)) return true;
|
|
632
|
+
if (/^(srv|service|serviceClient|messaging|messageClient|eventClient)$/.test(candidate)) return true;
|
|
622
633
|
return false;
|
|
623
634
|
}
|
|
624
635
|
function classifyOutboundCallsInSource(source, filePath) {
|
|
@@ -648,9 +659,10 @@ function classifyOutboundCallsInSource(source, filePath) {
|
|
|
648
659
|
}
|
|
649
660
|
} else if (ts4.isPropertyAccessExpression(expr) && ["emit", "publish", "on"].includes(expr.name.text)) {
|
|
650
661
|
const receiver = receiverName(expr.expression);
|
|
651
|
-
|
|
662
|
+
const rootReceiver = rootReceiverName(expr.expression);
|
|
663
|
+
if (isSupportedEventReceiver(receiver, rootReceiver, serviceVariables)) {
|
|
652
664
|
const eventName = literalText(node.arguments[0]);
|
|
653
|
-
if (eventName) add(node, { callType: expr.name.text === "on" ? "async_subscribe" : "async_emit", serviceVariableName: receiver, eventNameExpr: eventName }, { receiver, classifier: expr.name.text === "on" ? "cap_service_event_subscription" : "cap_service_event_emit" });
|
|
665
|
+
if (eventName) add(node, { callType: expr.name.text === "on" ? "async_subscribe" : "async_emit", serviceVariableName: rootReceiver ?? receiver, eventNameExpr: eventName }, { receiver, rootReceiver, classifier: expr.name.text === "on" ? "cap_service_event_subscription" : "cap_service_event_emit", receiverClassification: "cap_evidence" });
|
|
654
666
|
}
|
|
655
667
|
} else if (exprText === "axios" || exprText === "executeHttpRequest" || exprText === "useOrFetchDestination") {
|
|
656
668
|
add(node, { callType: "external_http", payloadSummary: summarizeExpression(node.arguments.map((arg) => arg.getText(source)).join(", ")), confidence: 0.7, unresolvedReason: "External HTTP destination is outside indexed CAP services" });
|
|
@@ -693,7 +705,14 @@ function parseLocalServiceCalls(text, filePath) {
|
|
|
693
705
|
sourceFile: normalizePath(filePath),
|
|
694
706
|
sourceLine: lineOf3(text, node.getStart(source)),
|
|
695
707
|
confidence: 0.9,
|
|
696
|
-
unresolvedReason: ["send", "emit", "publish", "on"].includes(parsed.operation) ? "transport_client_method" : void 0
|
|
708
|
+
unresolvedReason: ["send", "emit", "publish", "on"].includes(parsed.operation) ? "transport_client_method" : void 0,
|
|
709
|
+
evidence: parserEvidence(source, node, {
|
|
710
|
+
classifier: "local_cap_service_call",
|
|
711
|
+
localServiceLookup: parsed.lookup,
|
|
712
|
+
localServiceName: parsed.service,
|
|
713
|
+
operation: parsed.operation,
|
|
714
|
+
aliasChain: parsed.chain
|
|
715
|
+
})
|
|
697
716
|
});
|
|
698
717
|
}
|
|
699
718
|
ts4.forEachChild(node, visit);
|
|
@@ -1113,19 +1132,103 @@ function substituteVariables(template, vars) {
|
|
|
1113
1132
|
};
|
|
1114
1133
|
}
|
|
1115
1134
|
|
|
1135
|
+
// src/linker/odata-path-normalizer.ts
|
|
1136
|
+
function normalizeODataOperationInvocationPath(path9) {
|
|
1137
|
+
if (path9 === void 0) return void 0;
|
|
1138
|
+
const raw = path9.trim();
|
|
1139
|
+
if (!raw) return void 0;
|
|
1140
|
+
const open = raw.indexOf("(");
|
|
1141
|
+
if (open < 0) return { rawOperationPath: raw, normalizedOperationPath: raw, wasInvocation: false };
|
|
1142
|
+
if (!raw.startsWith("/") || raw.slice(1, open).includes("/")) return { rawOperationPath: raw, normalizedOperationPath: raw, wasInvocation: false };
|
|
1143
|
+
const close = matchingClose(raw, open);
|
|
1144
|
+
if (close === void 0 || raw.slice(close + 1).trim().length > 0) return { rawOperationPath: raw, normalizedOperationPath: raw, wasInvocation: false };
|
|
1145
|
+
const operationSegment = raw.slice(0, open).trim();
|
|
1146
|
+
if (operationSegment.length <= 1) return { rawOperationPath: raw, normalizedOperationPath: raw, wasInvocation: false };
|
|
1147
|
+
return { rawOperationPath: raw, normalizedOperationPath: operationSegment, wasInvocation: true };
|
|
1148
|
+
}
|
|
1149
|
+
function matchingClose(text, openIndex) {
|
|
1150
|
+
let depth = 0;
|
|
1151
|
+
let quote;
|
|
1152
|
+
for (let index = openIndex; index < text.length; index += 1) {
|
|
1153
|
+
const char = text[index];
|
|
1154
|
+
const prev = text[index - 1];
|
|
1155
|
+
if (quote) {
|
|
1156
|
+
if (prev === "\\") continue;
|
|
1157
|
+
if (quote === "single" && char === "'" || quote === "double" && char === '"' || quote === "template" && char === "`") quote = void 0;
|
|
1158
|
+
continue;
|
|
1159
|
+
}
|
|
1160
|
+
if (char === "'") {
|
|
1161
|
+
quote = "single";
|
|
1162
|
+
continue;
|
|
1163
|
+
}
|
|
1164
|
+
if (char === '"') {
|
|
1165
|
+
quote = "double";
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
1168
|
+
if (char === "`") {
|
|
1169
|
+
quote = "template";
|
|
1170
|
+
continue;
|
|
1171
|
+
}
|
|
1172
|
+
if (char === "(") depth += 1;
|
|
1173
|
+
if (char === ")") {
|
|
1174
|
+
depth -= 1;
|
|
1175
|
+
if (depth === 0) return index;
|
|
1176
|
+
if (depth < 0) return void 0;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
return void 0;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// src/linker/remote-query-target.ts
|
|
1183
|
+
function buildRemoteQueryTarget(input) {
|
|
1184
|
+
const entity = typeof input.queryEntity === "string" && input.queryEntity.trim() ? input.queryEntity.trim() : void 0;
|
|
1185
|
+
const servicePath = input.servicePath?.trim();
|
|
1186
|
+
const prefix = servicePath ? `${servicePath}:` : "";
|
|
1187
|
+
const label = entity ? `Remote entity: ${prefix}${entity}` : "Remote query: unknown";
|
|
1188
|
+
return {
|
|
1189
|
+
toKind: entity ? "remote_entity" : "remote_query",
|
|
1190
|
+
toId: entity ? `${prefix}${entity}` : "unknown",
|
|
1191
|
+
label,
|
|
1192
|
+
evidence: {
|
|
1193
|
+
remoteQueryTarget: label,
|
|
1194
|
+
queryEntity: entity,
|
|
1195
|
+
queryTargetKind: entity ? "remote_entity" : "remote_query_unknown",
|
|
1196
|
+
queryEntityDynamic: entity ? void 0 : Boolean(input.isDynamic) || void 0,
|
|
1197
|
+
serviceAlias: input.serviceAlias,
|
|
1198
|
+
serviceAliasExpr: input.serviceAliasExpr,
|
|
1199
|
+
destination: input.destination,
|
|
1200
|
+
servicePath,
|
|
1201
|
+
parserWarning: entity ? input.parserWarning : input.parserWarning ?? { code: "query_entity_unknown", message: "Remote query entity is dynamic or unavailable" }
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1116
1206
|
// src/linker/service-resolver.ts
|
|
1117
1207
|
function rows(db, operationPath, workspaceId) {
|
|
1208
|
+
const names = operationLookupNames(operationPath);
|
|
1118
1209
|
return db.prepare(
|
|
1119
1210
|
`SELECT o.id operationId,r.id repoId,r.name repoName,r.package_name packageName,s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,o.source_line sourceLine,0 score,'' reasons
|
|
1120
1211
|
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
|
|
1121
|
-
WHERE (? IS NULL OR r.workspace_id=?) AND (o.operation_path
|
|
1212
|
+
WHERE (? IS NULL OR r.workspace_id=?) AND (o.operation_path IN (?,?) OR o.operation_name IN (?,?)) ORDER BY r.name,s.service_path,o.operation_name`
|
|
1122
1213
|
).all(
|
|
1123
1214
|
workspaceId,
|
|
1124
1215
|
workspaceId,
|
|
1125
|
-
|
|
1126
|
-
|
|
1216
|
+
names.path,
|
|
1217
|
+
names.simplePath,
|
|
1218
|
+
names.name,
|
|
1219
|
+
names.simpleName
|
|
1127
1220
|
);
|
|
1128
1221
|
}
|
|
1222
|
+
function operationLookupNames(operationPath) {
|
|
1223
|
+
const name = operationPath.replace(/^\//, "");
|
|
1224
|
+
const simpleName = name.split(".").at(-1) ?? name;
|
|
1225
|
+
return { path: operationPath, simplePath: `/${simpleName}`, name, simpleName };
|
|
1226
|
+
}
|
|
1227
|
+
function operationMatches(candidate, operationPath) {
|
|
1228
|
+
if (!operationPath) return false;
|
|
1229
|
+
const names = operationLookupNames(operationPath);
|
|
1230
|
+
return candidate.operationPath === names.path || candidate.operationPath === names.simplePath || candidate.operationName === names.name || candidate.operationName === names.simpleName;
|
|
1231
|
+
}
|
|
1129
1232
|
function resolveOperation(db, signals, workspaceId) {
|
|
1130
1233
|
const missing = [signals.servicePath, signals.alias, signals.destination, signals.operationPath].flatMap((value) => [...(value ?? "").matchAll(/\$\{\s*(\w+)\s*\}/g)].map((match) => match[1] ?? "")).filter(Boolean);
|
|
1131
1234
|
if (missing.length > 0)
|
|
@@ -1193,7 +1296,7 @@ function resolveOperation(db, signals, workspaceId) {
|
|
|
1193
1296
|
c.score += 0.2;
|
|
1194
1297
|
c.reasons.push(signals.repoId !== void 0 ? "explicit_local_service_call" : "explicit_dynamic_override");
|
|
1195
1298
|
}
|
|
1196
|
-
if (signals.repoId !== void 0 && candidates.length === 1 && signals.serviceName && c.reasons.includes("local_service_name_mismatch") && (c
|
|
1299
|
+
if (signals.repoId !== void 0 && candidates.length === 1 && signals.serviceName && c.reasons.includes("local_service_name_mismatch") && operationMatches(c, signals.operationPath)) {
|
|
1197
1300
|
c.score = Math.max(c.score, 0.9);
|
|
1198
1301
|
c.reasons.push("same_repo_unique_operation_path_with_lookup_mismatch");
|
|
1199
1302
|
}
|
|
@@ -1216,7 +1319,7 @@ function resolveOperation(db, signals, workspaceId) {
|
|
|
1216
1319
|
candidates,
|
|
1217
1320
|
reasons: ["operation_path_only_has_no_strong_target_signal"]
|
|
1218
1321
|
};
|
|
1219
|
-
if (best && best.score >= 0.9 && (best.servicePath === signals.servicePath || Boolean(signals.serviceName && (!best.reasons.includes("local_service_name_mismatch") || best.reasons.includes("same_repo_unique_operation_path_with_lookup_mismatch")))) && (best
|
|
1322
|
+
if (best && best.score >= 0.9 && (best.servicePath === signals.servicePath || Boolean(signals.serviceName && (!best.reasons.includes("local_service_name_mismatch") || best.reasons.includes("same_repo_unique_operation_path_with_lookup_mismatch")))) && operationMatches(best, signals.operationPath) && (!second || best.score - second.score >= 0.25))
|
|
1220
1323
|
return {
|
|
1221
1324
|
status: "resolved",
|
|
1222
1325
|
target: best,
|
|
@@ -1346,19 +1449,26 @@ function linkCalls(db, workspaceId, vars, generation) {
|
|
|
1346
1449
|
}
|
|
1347
1450
|
function insertCallEdge(db, workspaceId, call, vars, generation) {
|
|
1348
1451
|
const callType = String(call.call_type);
|
|
1349
|
-
const
|
|
1452
|
+
const rawOp = applyVariables(String(call.operation_path_expr ?? ""), vars);
|
|
1453
|
+
const normalized = normalizeODataOperationInvocationPath(rawOp);
|
|
1454
|
+
const op = normalized?.normalizedOperationPath ?? rawOp;
|
|
1350
1455
|
const servicePath = applyVariables(call.servicePathExpr ?? call.requireServicePath, vars);
|
|
1351
1456
|
const destination = call.destinationExpr ?? call.requireDestination;
|
|
1352
1457
|
const isDynamic = Boolean(Number(call.isDynamic ?? 0));
|
|
1353
|
-
const isOperationCall = callType
|
|
1458
|
+
const isOperationCall = callType === "remote_action" || callType === "local_service_call" || callType === "remote_query" && Boolean(op);
|
|
1354
1459
|
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: [] };
|
|
1355
|
-
const evidence = callEvidence(call, resolution, servicePath, op, destination ? applyVariables(destination, vars) : void 0);
|
|
1460
|
+
const evidence = callEvidence(call, resolution, servicePath, op, destination ? applyVariables(destination, vars) : void 0, normalized);
|
|
1461
|
+
if (callType === "remote_query" && !op) {
|
|
1462
|
+
const target = buildRemoteQueryTarget({ queryEntity: call.query_entity, servicePath, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination: destination ? applyVariables(destination, vars) : void 0, isDynamic, parserWarning: evidence.parserWarning });
|
|
1463
|
+
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, "HANDLER_RUNS_REMOTE_QUERY", "terminal", "call", String(call.id), target.toKind, target.toId, Number(call.confidence ?? 0.5), JSON.stringify({ ...evidence, ...target.evidence }), 0, generation);
|
|
1464
|
+
return { status: "terminal", callType };
|
|
1465
|
+
}
|
|
1356
1466
|
if (callType === "local_service_call" && call.unresolved_reason === "transport_client_method" && !resolution.target && resolution.candidates.length === 0) {
|
|
1357
1467
|
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, "HANDLER_CALLS_EXTERNAL_HTTP", "terminal", "call", String(call.id), "external", String(op || "transport_client_method"), Number(call.confidence ?? 0.5), JSON.stringify({ ...evidence, classification: "transport_client_method" }), 0, generation);
|
|
1358
1468
|
return { status: "terminal", callType };
|
|
1359
1469
|
}
|
|
1360
1470
|
if (resolution.target) {
|
|
1361
|
-
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, callType === "local_service_call" ? "LOCAL_CALL_RESOLVES_TO_OPERATION" : "REMOTE_CALL_RESOLVES_TO_OPERATION", "resolved", "call", String(call.id), "operation", String(resolution.target.operationId), resolution.target.score, JSON.stringify(evidence),
|
|
1471
|
+
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, callType === "local_service_call" ? "LOCAL_CALL_RESOLVES_TO_OPERATION" : "REMOTE_CALL_RESOLVES_TO_OPERATION", "resolved", "call", String(call.id), "operation", String(resolution.target.operationId), resolution.target.score, JSON.stringify(evidence), 0, generation);
|
|
1362
1472
|
return { status: "resolved", callType };
|
|
1363
1473
|
}
|
|
1364
1474
|
const edgeType = callType === "local_db_query" ? "HANDLER_RUNS_DB_QUERY" : callType === "external_http" ? "HANDLER_CALLS_EXTERNAL_HTTP" : callType === "async_emit" ? "HANDLER_EMITS_EVENT" : callType === "async_subscribe" ? "EVENT_CONSUMED_BY_HANDLER" : resolution.status === "dynamic" ? "DYNAMIC_EDGE_CANDIDATE" : "UNRESOLVED_EDGE";
|
|
@@ -1366,11 +1476,25 @@ function insertCallEdge(db, workspaceId, call, vars, generation) {
|
|
|
1366
1476
|
const unresolvedReason = status === "terminal" ? null : String(call.unresolved_reason ?? (resolution.status === "ambiguous" ? "Ambiguous operation candidates require a strong service signal" : resolution.status === "dynamic" ? `Dynamic target requires runtime variable overrides: ${(resolution.reasons.length ? resolution.reasons : ["missing runtime variables"]).join(", ")}` : "No indexed target operation matched"));
|
|
1367
1477
|
const targetKind = callType === "local_db_query" ? "db_entity" : callType.startsWith("async_") ? "event" : "external";
|
|
1368
1478
|
const targetId = callType === "local_db_query" ? String(call.query_entity ?? "unknown") : String(call.event_name_expr ?? op ?? call.id);
|
|
1369
|
-
|
|
1479
|
+
const graphLevelDynamic = edgeType === "DYNAMIC_EDGE_CANDIDATE" && resolution.status === "dynamic";
|
|
1480
|
+
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, edgeType, status, "call", String(call.id), targetKind, targetId, Number(call.confidence ?? 0.2), JSON.stringify(evidence), graphLevelDynamic ? 1 : 0, unresolvedReason, generation);
|
|
1370
1481
|
return { status, callType };
|
|
1371
1482
|
}
|
|
1372
|
-
function
|
|
1373
|
-
|
|
1483
|
+
function parseJson(value) {
|
|
1484
|
+
if (!value) return void 0;
|
|
1485
|
+
try {
|
|
1486
|
+
return JSON.parse(String(value));
|
|
1487
|
+
} catch {
|
|
1488
|
+
return void 0;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
function objectJson(value) {
|
|
1492
|
+
const parsed = parseJson(value);
|
|
1493
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
1494
|
+
}
|
|
1495
|
+
function callEvidence(call, resolution, servicePath, op, destination, normalized) {
|
|
1496
|
+
const bindingHasDynamicExpression = Boolean(Number(call.isDynamic ?? 0));
|
|
1497
|
+
return { sourceFile: call.source_file, sourceLine: call.source_line, file: call.source_file, line: call.source_line, callId: call.id, repo: call.repoName, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination, servicePath, operationPath: op, rawOperationPath: normalized?.wasInvocation ? normalized.rawOperationPath : void 0, normalizedOperationPath: normalized?.wasInvocation ? normalized.normalizedOperationPath : void 0, localServiceName: call.local_service_name, localServiceLookup: call.local_service_lookup, aliasChain: parseJson(call.alias_chain_json), transport: call.call_type === "local_service_call" ? "local" : void 0, targetRepo: resolution.target?.repoName, targetServicePath: resolution.target?.servicePath, targetOperationPath: resolution.target?.operationPath, targetOperation: resolution.target?.operationName, helperChain: parseJson(call.helperChainJson), candidates: resolution.candidates, candidateCount: resolution.candidates.length, resolutionStatus: resolution.status, resolutionReasons: resolution.reasons, bindingHasDynamicExpression: bindingHasDynamicExpression || void 0, outboundEvidence: objectJson(call.evidence_json), analysisCompleteness: call.unresolved_reason ? "partial" : "complete", parserWarning: call.unresolved_reason ? { code: "parser_warning", message: call.unresolved_reason } : void 0 };
|
|
1374
1498
|
}
|
|
1375
1499
|
function linkImplementations(db, workspaceId, generation) {
|
|
1376
1500
|
const operations = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,o.operation_name operationName,s.service_path servicePath,s.repo_id modelRepoId,r.name modelRepo,r.package_name modelPackage,r.kind modelKind 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=?`).all(workspaceId);
|
|
@@ -1800,7 +1924,7 @@ function runtimeResolution(db, row, evidence, vars) {
|
|
|
1800
1924
|
return { row, evidence, unresolvedReason: row.unresolved_reason };
|
|
1801
1925
|
const nextEvidence = evidenceWithRuntimeVariables(evidence, vars);
|
|
1802
1926
|
const servicePath = typeof nextEvidence.servicePath === "string" ? nextEvidence.servicePath : void 0;
|
|
1803
|
-
const operationPath = typeof nextEvidence.operationPath === "string" ? nextEvidence.operationPath : void 0;
|
|
1927
|
+
const operationPath = typeof nextEvidence.normalizedOperationPath === "string" ? nextEvidence.normalizedOperationPath : typeof nextEvidence.operationPath === "string" ? nextEvidence.operationPath : void 0;
|
|
1804
1928
|
const alias = typeof nextEvidence.serviceAliasExpr === "string" ? nextEvidence.serviceAliasExpr : typeof nextEvidence.serviceAlias === "string" ? nextEvidence.serviceAlias : void 0;
|
|
1805
1929
|
const destination = typeof nextEvidence.destination === "string" ? nextEvidence.destination : void 0;
|
|
1806
1930
|
const resolution = resolveOperation(db, { servicePath, operationPath, alias, destination, hasExplicitOverride: true, isDynamic: true }, workspaceIdForCall(db, row.from_id));
|
|
@@ -1817,11 +1941,15 @@ function edgeTarget(row, evidence) {
|
|
|
1817
1941
|
const runtimeCandidate = evidence.runtimeResolvedCandidate;
|
|
1818
1942
|
if (runtimeCandidate?.servicePath && runtimeCandidate.operationPath)
|
|
1819
1943
|
return `${runtimeCandidate.servicePath}${runtimeCandidate.operationPath}`;
|
|
1944
|
+
const targetServicePath = typeof evidence.targetServicePath === "string" ? evidence.targetServicePath : void 0;
|
|
1945
|
+
const targetOperationPath = typeof evidence.targetOperationPath === "string" ? evidence.targetOperationPath : void 0;
|
|
1946
|
+
if (targetServicePath && targetOperationPath) return `${targetServicePath}${targetOperationPath}`;
|
|
1820
1947
|
const servicePath = typeof evidence.servicePath === "string" ? evidence.servicePath : void 0;
|
|
1821
1948
|
const operationPath = typeof evidence.operationPath === "string" ? evidence.operationPath : void 0;
|
|
1822
1949
|
const targetOperation = typeof evidence.targetOperation === "string" ? evidence.targetOperation : void 0;
|
|
1823
1950
|
const targetRepo = typeof evidence.targetRepo === "string" ? evidence.targetRepo : "";
|
|
1824
1951
|
if (row.edge_type === "HANDLER_RUNS_DB_QUERY") return `Entity: ${row.to_id || "unknown"}`;
|
|
1952
|
+
if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return typeof evidence.remoteQueryTarget === "string" ? evidence.remoteQueryTarget : `Remote query: ${row.to_id || "unknown"}`;
|
|
1825
1953
|
return servicePath && operationPath ? `${servicePath}${operationPath}` : targetOperation ? `${targetRepo}:${targetOperation}` : row.to_id;
|
|
1826
1954
|
}
|
|
1827
1955
|
function trace(db, start, options) {
|
|
@@ -2000,7 +2128,8 @@ export {
|
|
|
2000
2128
|
applyVariables,
|
|
2001
2129
|
extractPlaceholders,
|
|
2002
2130
|
substituteVariables,
|
|
2131
|
+
normalizeODataOperationInvocationPath,
|
|
2003
2132
|
linkWorkspace,
|
|
2004
2133
|
trace
|
|
2005
2134
|
};
|
|
2006
|
-
//# sourceMappingURL=chunk-
|
|
2135
|
+
//# sourceMappingURL=chunk-YG66SHAR.js.map
|