@saptools/service-flow 0.1.32 → 0.1.34
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 +14 -0
- package/README.md +4 -0
- package/TECHNICAL-NOTE.md +8 -0
- package/dist/{chunk-VT5FVMOA.js → chunk-5SR4SFSU.js} +70 -11
- package/dist/chunk-5SR4SFSU.js.map +1 -0
- package/dist/cli.js +62 -8
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-VT5FVMOA.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.34
|
|
4
|
+
|
|
5
|
+
- Prefer indexed CDS operation evidence over heuristic remote-entity classification for service-client operation invocations, while keeping true collection, entity, delete, navigation, and media paths terminal.
|
|
6
|
+
- Added strict doctor collision diagnostics for terminal remote entity edges that look like operation invocations with indexed operation candidates.
|
|
7
|
+
- Persist repository fact analyzer versions and warn during link/strict doctor when force reindex is required after an analyzer upgrade.
|
|
8
|
+
|
|
9
|
+
## 0.1.33
|
|
10
|
+
|
|
11
|
+
- Preserved persisted graph decisions and call-site evidence during trace and graph rendering while keeping contextual runtime resolution as enrichment.
|
|
12
|
+
- Classified OData entity reads, mutations, deletes, navigation, media streams, and uppercase entity candidates as terminal remote entity edges instead of unresolved operation candidates.
|
|
13
|
+
- Kept dynamic external HTTP destinations dynamic with stable synthetic ids, neutral labels, bounded safe candidates, and sanitized URL evidence.
|
|
14
|
+
- Bumped the SQLite schema capability to version 7 and added strict doctor diagnostics for legacy schema drift and reindex-required external metadata.
|
|
15
|
+
- Standardized terminal trace-start diagnostics so non-traversable starts return zero graph nodes and edges by default.
|
|
16
|
+
|
|
3
17
|
## 0.1.32
|
|
4
18
|
|
|
5
19
|
- Hardened operation-first trace starts to fail closed on ambiguous, rejected, or non-executable implementation evidence.
|
package/README.md
CHANGED
|
@@ -68,6 +68,10 @@ npm install @saptools/service-flow
|
|
|
68
68
|
- Doctor treats a `running` index run as abandoned only after 60 minutes and includes the run id/start time. Active short-lived concurrent runs are not default warnings.
|
|
69
69
|
- Fresh databases include foreign keys for key graph, run, and diagnostic tables. Migrated legacy stores that still lack that metadata are reported by doctor with `legacy_schema_weaker_foreign_keys`; rebuild into a fresh database if strict structural parity is required.
|
|
70
70
|
- Parser warnings describe analysis completeness, while routing status describes graph behavior. A terminal DB edge can remain terminal while still exposing parser warning evidence about an unknown entity.
|
|
71
|
+
- Persisted graph rows take precedence during `trace` and `graph`: resolved call edges keep their `graph_edges.id`, `outbound_calls.id`, call-site file/line, outbound parser evidence, linker status/reason, and selected target evidence. Contextual runtime resolution can enrich evidence but does not replace an already resolved persisted target. Terminal start diagnostics such as ambiguous operations or rejected implementations return zero nodes and zero edges by default; candidates remain in structured diagnostics for automation.
|
|
72
|
+
- OData entity paths are conservative terminal remote entity edges. Reads, mutations, deletes, navigation paths, media-stream paths such as `/Documents(ID)/content`, and uppercase unknown entity-set candidates do not inflate unresolved operation counts. Lowercase action/function-style paths remain eligible for indexed operation resolution.
|
|
73
|
+
- External HTTP destinations are static only when a safe literal or local const literal proves the value. Identifier, property-read, function-call, and arbitrary destination expressions are dynamic with stable `destination:dynamic:<hash>` ids and neutral labels; conditional literal branches expose only safe candidate names.
|
|
74
|
+
- Schema version 7 uses an explicit reindex-required upgrade policy for legacy schema drift. `doctor --strict` reports `schema_legacy_columns_present`, `external_target_columns_missing_data`, and `reindex_required_after_upgrade` with remediation when relink alone cannot make an upgraded database equivalent to a fresh index.
|
|
71
75
|
|
|
72
76
|
|
|
73
77
|
## 🚀 Quick Start
|
package/TECHNICAL-NOTE.md
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
# Service Flow Resolution Notes
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## 0.1.33 trace, entity, destination, and upgrade notes
|
|
5
|
+
|
|
6
|
+
- Trace and graph rendering use persisted resolved graph rows as authoritative base edges. Runtime/contextual resolution may add evidence such as substitutions or binding propagation, but persisted graph edge ids, outbound call ids, call-site file/line, parser evidence, linker status, selected target id, and target evidence remain present in effective edges.
|
|
7
|
+
- The trace-start machine contract is fail-closed: terminal start diagnostics produce zero graph nodes and zero graph edges by default. Candidate operations, implementation candidates, rejected edges, and selected ids are diagnostics-only evidence.
|
|
8
|
+
- Service-client OData entity paths are separated from action/function invocations. Entity reads, mutations, deletes, navigation, media stream calls, and uppercase entity-set candidates become terminal remote entity graph edges; lowercase operation-looking paths still go through operation resolution when indexed evidence exists.
|
|
9
|
+
- External HTTP destination extraction uses conservative static evaluation. Literals and safe local const literals are static; all other expressions stay dynamic unless a conditional has all-static branches, in which case only the candidate literals and sanitized expression shape are persisted.
|
|
10
|
+
- Schema version 7 follows an explicit reindex-required upgrade policy. Legacy external-target columns on `symbols` or missing queryable external metadata are strict doctor warnings with rebuild/reindex remediation instead of silent relink-only drift.
|
|
11
|
+
|
|
4
12
|
## 0.1.31 selector and external target notes
|
|
5
13
|
|
|
6
14
|
Operation trace selectors are resolved from CDS operation facts before handler-source fallback. The start resolver scopes by repository and service path, requires disambiguation when multiple repositories or services match, and only queues traversal from a resolved implementation edge. Generated operation decorator constants are normalized in `operation-decorator-normalizer` so linker, trace fallback, diagnostics, and tests share one conservative implementation.
|
|
@@ -559,16 +559,27 @@ function classifyODataPathIntent(path9, method) {
|
|
|
559
559
|
const placeholderKeys = [...new Set(extractTemplatePlaceholders(rawPath))];
|
|
560
560
|
const base = { rawPath, method: normalizedMethod, pathWithoutQuery, queryString, hasQueryString: queryIndex >= 0, entitySegment, placeholderKeys };
|
|
561
561
|
if (!rawPath || !rawPath.startsWith("/")) return { ...base, kind: "unknown", reason: "path_missing_or_not_absolute" };
|
|
562
|
-
|
|
562
|
+
const upperEntityLike = /^[A-Z][A-Za-z0-9_]*$/.test(entitySegment ?? firstSegment);
|
|
563
|
+
const mediaLike = ["content", "$value"].includes((segments.at(-1) ?? "").toLowerCase());
|
|
564
|
+
const invocation = normalizeODataOperationInvocationPath(pathWithoutQuery);
|
|
565
|
+
if (normalizedMethod !== "GET") {
|
|
566
|
+
if (invocation?.wasInvocation && looksLikeLowerCamelInvocation(firstSegment)) return { ...base, kind: "operation_invocation", reason: "non_get_balanced_top_level_operation_invocation" };
|
|
567
|
+
if (mediaLike) return { ...base, kind: "entity_media", reason: "non_get_entity_media_stream_path" };
|
|
568
|
+
if (hasNavigationSegments || firstSegment.includes("(")) return { ...base, kind: normalizedMethod === "DELETE" ? "entity_delete" : "entity_mutation", reason: "non_get_entity_path_shape" };
|
|
569
|
+
if (upperEntityLike) return { ...base, kind: normalizedMethod === "DELETE" ? "entity_delete" : "entity_mutation", reason: "non_get_entity_path_shape" };
|
|
570
|
+
return { ...base, kind: "operation_invocation", reason: "non_get_lowercase_path_may_be_operation" };
|
|
571
|
+
}
|
|
563
572
|
if (queryIndex >= 0) {
|
|
564
573
|
if (hasNavigationSegments) return { ...base, kind: "entity_navigation_query", reason: "get_path_has_navigation_and_query_string" };
|
|
565
574
|
if (looksLikeLowerCamelInvocation(firstSegment)) return { ...base, kind: "unknown", reason: "get_invocation_with_query_string_requires_indexed_operation_evidence" };
|
|
566
575
|
return { ...base, kind: "entity_query", reason: "get_collection_path_has_query_string" };
|
|
567
576
|
}
|
|
568
|
-
if (hasNavigationSegments) return { ...base, kind: "entity_navigation_query", reason: "get_path_has_navigation_segments" };
|
|
577
|
+
if (hasNavigationSegments) return mediaLike ? { ...base, kind: "entity_media", reason: "get_entity_media_stream_path" } : { ...base, kind: "entity_navigation_query", reason: "get_path_has_navigation_segments" };
|
|
569
578
|
if (firstSegment.includes("(")) {
|
|
579
|
+
if (invocation?.wasInvocation && looksLikeLowerCamelInvocation(firstSegment)) return { ...base, kind: "operation_invocation", reason: "get_balanced_top_level_operation_invocation" };
|
|
570
580
|
return looksLikeLowerCamelInvocation(firstSegment) ? { ...base, kind: "operation_invocation", reason: "get_single_lower_camel_segment_has_top_level_invocation" } : { ...base, kind: "entity_key_read", reason: "get_entity_segment_has_key_predicate" };
|
|
571
581
|
}
|
|
582
|
+
if (/^[A-Z][A-Za-z0-9_]*$/.test(firstSegment)) return { ...base, kind: "entity_candidate", reason: "uppercase_collection_segment_without_indexed_entity_evidence" };
|
|
572
583
|
return { ...base, kind: "unknown", reason: "get_path_has_no_query_key_or_navigation_signal" };
|
|
573
584
|
}
|
|
574
585
|
function entitySegmentFromPath(path9) {
|
|
@@ -748,6 +759,11 @@ function externalHttpTarget(call) {
|
|
|
748
759
|
const method = typeof call.method === "string" ? call.method : typeof target.method === "string" ? target.method : void 0;
|
|
749
760
|
const kind = typeof target.kind === "string" ? target.kind : "unknown";
|
|
750
761
|
const expression = typeof target.expression === "string" ? target.expression : void 0;
|
|
762
|
+
if (kind === "destination" && target.dynamic === true) {
|
|
763
|
+
const shape = typeof target.expressionShape === "string" ? target.expressionShape : "expression";
|
|
764
|
+
const candidates = Array.isArray(target.candidateLiterals) ? target.candidateLiterals.filter((item) => typeof item === "string") : [];
|
|
765
|
+
return { kind, toKind: "external_destination", toId: `destination:dynamic:${hash(`${shape}:${candidates.join("|")}`)}`, label: "External destination: dynamic destination", method, dynamic: true, expression: candidates.length ? `candidates:${candidates.join("|")}` : `shape:${shape}` };
|
|
766
|
+
}
|
|
751
767
|
if (kind === "destination" && expression) return { kind, toKind: "external_destination", toId: `destination:${expression}`, label: `External destination: ${expression}`, method, dynamic: false, expression };
|
|
752
768
|
if (kind === "static_url" && expression) {
|
|
753
769
|
const redacted = redactUrl(expression);
|
|
@@ -850,6 +866,24 @@ function staticExpressionText(expr, initializers) {
|
|
|
850
866
|
if (ts4.isIdentifier(expr) && initializers.has(expr.text)) return staticExpressionText(initializers.get(expr.text), initializers);
|
|
851
867
|
return void 0;
|
|
852
868
|
}
|
|
869
|
+
function destinationExpressionShape(expr) {
|
|
870
|
+
if (!expr) return void 0;
|
|
871
|
+
if (ts4.isIdentifier(expr)) return "identifier";
|
|
872
|
+
if (ts4.isPropertyAccessExpression(expr) || ts4.isElementAccessExpression(expr)) return "property_read";
|
|
873
|
+
if (ts4.isCallExpression(expr)) return "function_call";
|
|
874
|
+
if (ts4.isConditionalExpression(expr)) return "conditional";
|
|
875
|
+
if (ts4.isBinaryExpression(expr)) return "binary_expression";
|
|
876
|
+
if (ts4.isTemplateExpression(expr)) return "template_expression";
|
|
877
|
+
return ts4.SyntaxKind[expr.kind] ?? "expression";
|
|
878
|
+
}
|
|
879
|
+
function staticConditionalCandidates(expr, initializers) {
|
|
880
|
+
const resolved2 = expr && ts4.isIdentifier(expr) && initializers.has(expr.text) ? initializers.get(expr.text) : expr;
|
|
881
|
+
if (!resolved2 || !ts4.isConditionalExpression(resolved2)) return void 0;
|
|
882
|
+
const left = staticExpressionText(resolved2.whenTrue, initializers);
|
|
883
|
+
const right = staticExpressionText(resolved2.whenFalse, initializers);
|
|
884
|
+
if (!left || !right) return void 0;
|
|
885
|
+
return [.../* @__PURE__ */ new Set([left, right])];
|
|
886
|
+
}
|
|
853
887
|
function propertyInitializer(object, key) {
|
|
854
888
|
for (const property of object.properties) {
|
|
855
889
|
if (ts4.isPropertyAssignment(property) && nameOfProperty(property.name) === key) return property.initializer;
|
|
@@ -870,7 +904,10 @@ function urlTargetFromExpression(expr, initializers) {
|
|
|
870
904
|
function destinationTargetFromExpression(expr, initializers) {
|
|
871
905
|
const text = staticExpressionText(expr, initializers);
|
|
872
906
|
if (text) return { kind: "destination", expression: text, dynamic: false };
|
|
873
|
-
|
|
907
|
+
const candidates = staticConditionalCandidates(expr, initializers);
|
|
908
|
+
if (candidates) return { kind: "destination", dynamic: true, expressionShape: "conditional", candidateLiterals: candidates };
|
|
909
|
+
const shape = destinationExpressionShape(expr);
|
|
910
|
+
if (shape) return { kind: "destination", dynamic: true, expressionShape: shape };
|
|
874
911
|
return void 0;
|
|
875
912
|
}
|
|
876
913
|
function externalHttpEvidence(node, source, initializers) {
|
|
@@ -1018,8 +1055,10 @@ function classifyOutboundCallsInSource(source, filePath) {
|
|
|
1018
1055
|
const shorthandPath = objectPropertyIsShorthand(objectArg, "path");
|
|
1019
1056
|
const operationPathExpr = op && !shorthandPath ? `/${stripQuotes(op).replace(/^\//, "")}` : void 0;
|
|
1020
1057
|
const intent = classifyODataPathIntent(operationPathExpr, method);
|
|
1058
|
+
const entityCallTypes = { entity_mutation: "remote_entity_mutation", entity_delete: "remote_entity_delete", entity_media: "remote_entity_media", entity_candidate: "remote_entity_candidate" };
|
|
1059
|
+
const entityCallType = entityCallTypes[intent.kind];
|
|
1021
1060
|
const isODataQueryRead = method.toUpperCase() === "GET" && ["entity_query", "entity_key_read", "entity_navigation_query"].includes(intent.kind);
|
|
1022
|
-
add(node, { callType: query
|
|
1061
|
+
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 && shorthandPath ? "dynamic_operation_path_identifier" : void 0 }, { receiver, classifier: "service_client_send_object", operationPathExpression: shorthandPath ? op : void 0, odataPathIntent: operationPathExpr ? intent : void 0, parserWarning: shorthandPath ? "dynamic_operation_path_identifier" : void 0 });
|
|
1023
1062
|
}
|
|
1024
1063
|
} else if (ts4.isCallExpression(expr) && ts4.isIdentifier(expr.expression) && wrapperSpecs.has(expr.expression.text) || ts4.isIdentifier(expr) && wrapperSpecs.has(expr.text)) {
|
|
1025
1064
|
const wrapperName = ts4.isIdentifier(expr) ? expr.text : ts4.isCallExpression(expr) && ts4.isIdentifier(expr.expression) ? expr.expression.text : "";
|
|
@@ -2066,9 +2105,26 @@ function insertCallEdge(db, workspaceId, call, vars, generation) {
|
|
|
2066
2105
|
const servicePath = applyVariables(call.servicePathExpr ?? call.requireServicePath, vars);
|
|
2067
2106
|
const destination = call.destinationExpr ?? call.requireDestination;
|
|
2068
2107
|
const isDynamic = Boolean(Number(call.isDynamic ?? 0));
|
|
2069
|
-
const
|
|
2108
|
+
const isRemoteEntityCall = callType.startsWith("remote_entity_");
|
|
2109
|
+
const operationLikeRemoteEntity = isRemoteEntityCall && Boolean(op) && !["entity_media", "entity_delete", "entity_key_read", "entity_navigation_query"].includes(intent.kind);
|
|
2110
|
+
const isOperationCall = operationLikeRemoteEntity || (callType === "remote_action" || callType === "local_service_call" || callType === "remote_query" && Boolean(op));
|
|
2070
2111
|
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: [] };
|
|
2071
2112
|
const evidence = callEvidence(call, resolution, servicePath, op, destination ? applyVariables(destination, vars) : void 0, normalized, intent);
|
|
2113
|
+
if (isRemoteEntityCall && (resolution.target || resolution.candidates.length > 0 || resolution.status === "dynamic")) {
|
|
2114
|
+
if (resolution.target) {
|
|
2115
|
+
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);
|
|
2116
|
+
return { status: "resolved", callType };
|
|
2117
|
+
}
|
|
2118
|
+
const status2 = resolution.status === "dynamic" ? "dynamic" : resolution.status === "ambiguous" ? "ambiguous" : "unresolved";
|
|
2119
|
+
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, status2 === "dynamic" ? "DYNAMIC_EDGE_CANDIDATE" : "UNRESOLVED_EDGE", status2, "call", String(call.id), "operation_candidate", op ? `Remote action: ${op}` : "Remote action: unknown path", Number(call.confidence ?? 0.2), JSON.stringify({ ...evidence, operationEntityPrecedence: "parser_entity_with_indexed_operation_candidates" }), status2 === "dynamic" ? 1 : 0, unresolvedOperationReason(resolution), generation);
|
|
2120
|
+
return { status: status2, callType };
|
|
2121
|
+
}
|
|
2122
|
+
if (isRemoteEntityCall) {
|
|
2123
|
+
const target = buildRemoteQueryTarget({ queryEntity: intent.entitySegment ?? call.query_entity, servicePath, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination: destination ? applyVariables(destination, vars) : void 0, isDynamic, parserWarning: evidence.parserWarning });
|
|
2124
|
+
const entityKind = callType.replace("remote_entity_", "remote_entity_");
|
|
2125
|
+
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_ACCESSES_REMOTE_ENTITY", "terminal", "call", String(call.id), target.toKind, target.toId, Number(call.confidence ?? 0.5), JSON.stringify({ ...evidence, ...target.evidence, remoteEntityAccess: entityKind }), 0, generation);
|
|
2126
|
+
return { status: "terminal", callType };
|
|
2127
|
+
}
|
|
2072
2128
|
if (callType === "remote_query" && (isEntityQueryIntent || !op) && !resolution.target) {
|
|
2073
2129
|
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 });
|
|
2074
2130
|
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);
|
|
@@ -2673,7 +2729,7 @@ function contextForSymbolCall(db, symbolCall, callerBindings) {
|
|
|
2673
2729
|
});
|
|
2674
2730
|
return next;
|
|
2675
2731
|
}
|
|
2676
|
-
function contextualRuntimeResolution(db, call, binding, workspaceId) {
|
|
2732
|
+
function contextualRuntimeResolution(db, call, binding, workspaceId, persistedRows = []) {
|
|
2677
2733
|
if (!binding || String(call.call_type) !== "remote_action" || call.operation_path_expr === void 0 || call.operation_path_expr === null) return {};
|
|
2678
2734
|
const normalized = normalizeODataOperationInvocationPath(String(call.operation_path_expr));
|
|
2679
2735
|
const op = normalized?.normalizedOperationPath ?? (String(call.operation_path_expr).startsWith("/") ? String(call.operation_path_expr) : `/${String(call.operation_path_expr)}`);
|
|
@@ -2683,6 +2739,8 @@ function contextualRuntimeResolution(db, call, binding, workspaceId) {
|
|
|
2683
2739
|
const evidence = { contextualServiceBindingAttempted: true, contextualBinding: { source: binding.source, callerArgument: binding.callerArgument, callerProperty: binding.callerProperty, calleeParameter: binding.calleeParameter, calleeReceiver: binding.calleeReceiver, bindingSourceFile: binding.sourceFile, bindingSourceLine: binding.sourceLine, alias: binding.alias, aliasExpr: binding.aliasExpr, requireServicePath: binding.requireServicePath, requireDestination: binding.requireDestination, effectiveServicePath: binding.effectiveServicePath, effectiveDestination: binding.effectiveDestination }, operationPath: op, rawOperationPath: normalized?.rawOperationPath, normalizedOperationPath: normalized?.wasInvocation ? normalized.normalizedOperationPath : void 0, invocationArgumentPlaceholderKeys: normalized?.invocationArgumentPlaceholderKeys.length ? normalized.invocationArgumentPlaceholderKeys : void 0, servicePath, serviceAlias: binding.alias, serviceAliasExpr: binding.aliasExpr, destination, requireServicePath: binding.requireServicePath, requireDestination: binding.requireDestination, effectiveServicePath: binding.effectiveServicePath, effectiveDestination: binding.effectiveDestination, contextualResolutionStatus: resolution.status, contextualCandidateCount: resolution.candidates.length, candidates: resolution.candidates, contextualResolutionReasons: resolution.reasons, resolutionReasons: resolution.reasons };
|
|
2684
2740
|
if (!resolution.target) return { evidence, unresolvedReason: resolution.status === "ambiguous" ? "Ambiguous contextual operation candidates" : resolution.status === "dynamic" ? `Dynamic contextual target is missing runtime variables: ${resolution.reasons.join(", ")}` : "No contextual operation candidate matched" };
|
|
2685
2741
|
const resolvedEvidence = { ...evidence, contextualServiceBindingSelected: true, targetRepo: resolution.target.repoName, targetServicePath: resolution.target.servicePath, targetOperationPath: resolution.target.operationPath, targetOperation: resolution.target.operationName };
|
|
2742
|
+
const persistedResolved = persistedRows.find((item) => item.status === "resolved");
|
|
2743
|
+
if (persistedResolved) return { row: void 0, evidence: { ...resolvedEvidence, contextualPreservedPersistedResolvedEdge: true }, unresolvedReason: void 0 };
|
|
2686
2744
|
return { row: { id: -Number(call.id), edge_type: "REMOTE_CALL_RESOLVES_TO_OPERATION", from_id: String(call.id), to_kind: "operation", to_id: String(resolution.target.operationId), confidence: resolution.target.score, evidence_json: JSON.stringify(resolvedEvidence), status: "resolved" }, evidence: resolvedEvidence, unresolvedReason: void 0 };
|
|
2687
2745
|
}
|
|
2688
2746
|
function edgeTarget(row, evidence) {
|
|
@@ -2724,7 +2782,7 @@ function trace(db, start, options) {
|
|
|
2724
2782
|
const nodes = /* @__PURE__ */ new Map();
|
|
2725
2783
|
const seenEdges = /* @__PURE__ */ new Set();
|
|
2726
2784
|
const queue = scope.selectorMatched ? [{ repoId: scope.repo?.id, files: scope.sourceFiles, symbolIds: scope.symbolIds, depth: 1, context: /* @__PURE__ */ new Map() }] : [];
|
|
2727
|
-
if (scope.startOperationId) {
|
|
2785
|
+
if (scope.startOperationId && scope.selectorMatched) {
|
|
2728
2786
|
const op = operationNode(db, scope.startOperationId);
|
|
2729
2787
|
const impl = implementationScope(db, scope.startOperationId);
|
|
2730
2788
|
if (op) nodes.set(String(op.id), op);
|
|
@@ -2781,15 +2839,16 @@ function trace(db, start, options) {
|
|
|
2781
2839
|
line: call.source_line,
|
|
2782
2840
|
callType: call.call_type
|
|
2783
2841
|
});
|
|
2784
|
-
const
|
|
2785
|
-
const
|
|
2842
|
+
const persistedRowsForCall = graph.get(Number(call.id)) ?? [];
|
|
2843
|
+
const contextual = contextualRuntimeResolution(db, call, callerBindings.get(receiverFromEvidence(call.evidence_json) ?? ""), workspaceIdForCall(db, String(call.id)), persistedRowsForCall);
|
|
2844
|
+
const graphRows = contextual.row ? [contextual.row] : persistedRowsForCall;
|
|
2786
2845
|
for (const row of graphRows) {
|
|
2787
2846
|
if (seenEdges.has(Number(row.id))) continue;
|
|
2788
2847
|
seenEdges.add(Number(row.id));
|
|
2789
2848
|
const persistedEvidence = JSON.parse(
|
|
2790
2849
|
String(row.evidence_json || "{}")
|
|
2791
2850
|
);
|
|
2792
|
-
const rawEvidence = contextual.evidence ? {
|
|
2851
|
+
const rawEvidence = { ...persistedEvidence, ...contextual.evidence ?? {}, graphEdgeId: row.id, persistedGraphEdgeId: row.id > 0 ? row.id : void 0, outboundCallId: call.id, callSite: { sourceFile: call.source_file, sourceLine: call.source_line }, sourceFile: call.source_file, sourceLine: call.source_line, file: call.source_file, line: call.source_line, linker: { status: row.status, confidence: row.confidence, reason: row.unresolved_reason, edgeType: row.edge_type }, persistedTarget: { kind: row.to_kind, id: row.to_id }, contextualResolutionParticipated: Boolean(contextual.evidence?.contextualServiceBindingAttempted) };
|
|
2793
2852
|
const effective = runtimeResolution(db, row, rawEvidence, options.vars);
|
|
2794
2853
|
const evidence = effective.evidence;
|
|
2795
2854
|
const effectiveRow = effective.row;
|
|
@@ -2884,4 +2943,4 @@ export {
|
|
|
2884
2943
|
linkWorkspace,
|
|
2885
2944
|
trace
|
|
2886
2945
|
};
|
|
2887
|
-
//# sourceMappingURL=chunk-
|
|
2946
|
+
//# sourceMappingURL=chunk-5SR4SFSU.js.map
|