@saptools/service-flow 0.1.32 → 0.1.33
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 +8 -0
- package/README.md +4 -0
- package/TECHNICAL-NOTE.md +8 -0
- package/dist/{chunk-VT5FVMOA.js → chunk-CWJYVIG2.js} +56 -11
- package/dist/chunk-CWJYVIG2.js.map +1 -0
- package/dist/cli.js +22 -5
- 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,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.33
|
|
4
|
+
|
|
5
|
+
- Preserved persisted graph decisions and call-site evidence during trace and graph rendering while keeping contextual runtime resolution as enrichment.
|
|
6
|
+
- Classified OData entity reads, mutations, deletes, navigation, media streams, and uppercase entity candidates as terminal remote entity edges instead of unresolved operation candidates.
|
|
7
|
+
- Kept dynamic external HTTP destinations dynamic with stable synthetic ids, neutral labels, bounded safe candidates, and sanitized URL evidence.
|
|
8
|
+
- Bumped the SQLite schema capability to version 7 and added strict doctor diagnostics for legacy schema drift and reindex-required external metadata.
|
|
9
|
+
- Standardized terminal trace-start diagnostics so non-traversable starts return zero graph nodes and edges by default.
|
|
10
|
+
|
|
3
11
|
## 0.1.32
|
|
4
12
|
|
|
5
13
|
- 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,23 @@ 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
|
+
if (normalizedMethod !== "GET") {
|
|
565
|
+
if (mediaLike) return { ...base, kind: "entity_media", reason: "non_get_entity_media_stream_path" };
|
|
566
|
+
if (upperEntityLike || firstSegment.includes("(") || hasNavigationSegments) return { ...base, kind: normalizedMethod === "DELETE" ? "entity_delete" : "entity_mutation", reason: "non_get_entity_path_shape" };
|
|
567
|
+
return { ...base, kind: "operation_invocation", reason: "non_get_lowercase_path_may_be_operation" };
|
|
568
|
+
}
|
|
563
569
|
if (queryIndex >= 0) {
|
|
564
570
|
if (hasNavigationSegments) return { ...base, kind: "entity_navigation_query", reason: "get_path_has_navigation_and_query_string" };
|
|
565
571
|
if (looksLikeLowerCamelInvocation(firstSegment)) return { ...base, kind: "unknown", reason: "get_invocation_with_query_string_requires_indexed_operation_evidence" };
|
|
566
572
|
return { ...base, kind: "entity_query", reason: "get_collection_path_has_query_string" };
|
|
567
573
|
}
|
|
568
|
-
if (hasNavigationSegments) return { ...base, kind: "entity_navigation_query", reason: "get_path_has_navigation_segments" };
|
|
574
|
+
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
575
|
if (firstSegment.includes("(")) {
|
|
570
576
|
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
577
|
}
|
|
578
|
+
if (/^[A-Z][A-Za-z0-9_]*$/.test(firstSegment)) return { ...base, kind: "entity_candidate", reason: "uppercase_collection_segment_without_indexed_entity_evidence" };
|
|
572
579
|
return { ...base, kind: "unknown", reason: "get_path_has_no_query_key_or_navigation_signal" };
|
|
573
580
|
}
|
|
574
581
|
function entitySegmentFromPath(path9) {
|
|
@@ -748,6 +755,11 @@ function externalHttpTarget(call) {
|
|
|
748
755
|
const method = typeof call.method === "string" ? call.method : typeof target.method === "string" ? target.method : void 0;
|
|
749
756
|
const kind = typeof target.kind === "string" ? target.kind : "unknown";
|
|
750
757
|
const expression = typeof target.expression === "string" ? target.expression : void 0;
|
|
758
|
+
if (kind === "destination" && target.dynamic === true) {
|
|
759
|
+
const shape = typeof target.expressionShape === "string" ? target.expressionShape : "expression";
|
|
760
|
+
const candidates = Array.isArray(target.candidateLiterals) ? target.candidateLiterals.filter((item) => typeof item === "string") : [];
|
|
761
|
+
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}` };
|
|
762
|
+
}
|
|
751
763
|
if (kind === "destination" && expression) return { kind, toKind: "external_destination", toId: `destination:${expression}`, label: `External destination: ${expression}`, method, dynamic: false, expression };
|
|
752
764
|
if (kind === "static_url" && expression) {
|
|
753
765
|
const redacted = redactUrl(expression);
|
|
@@ -850,6 +862,24 @@ function staticExpressionText(expr, initializers) {
|
|
|
850
862
|
if (ts4.isIdentifier(expr) && initializers.has(expr.text)) return staticExpressionText(initializers.get(expr.text), initializers);
|
|
851
863
|
return void 0;
|
|
852
864
|
}
|
|
865
|
+
function destinationExpressionShape(expr) {
|
|
866
|
+
if (!expr) return void 0;
|
|
867
|
+
if (ts4.isIdentifier(expr)) return "identifier";
|
|
868
|
+
if (ts4.isPropertyAccessExpression(expr) || ts4.isElementAccessExpression(expr)) return "property_read";
|
|
869
|
+
if (ts4.isCallExpression(expr)) return "function_call";
|
|
870
|
+
if (ts4.isConditionalExpression(expr)) return "conditional";
|
|
871
|
+
if (ts4.isBinaryExpression(expr)) return "binary_expression";
|
|
872
|
+
if (ts4.isTemplateExpression(expr)) return "template_expression";
|
|
873
|
+
return ts4.SyntaxKind[expr.kind] ?? "expression";
|
|
874
|
+
}
|
|
875
|
+
function staticConditionalCandidates(expr, initializers) {
|
|
876
|
+
const resolved2 = expr && ts4.isIdentifier(expr) && initializers.has(expr.text) ? initializers.get(expr.text) : expr;
|
|
877
|
+
if (!resolved2 || !ts4.isConditionalExpression(resolved2)) return void 0;
|
|
878
|
+
const left = staticExpressionText(resolved2.whenTrue, initializers);
|
|
879
|
+
const right = staticExpressionText(resolved2.whenFalse, initializers);
|
|
880
|
+
if (!left || !right) return void 0;
|
|
881
|
+
return [.../* @__PURE__ */ new Set([left, right])];
|
|
882
|
+
}
|
|
853
883
|
function propertyInitializer(object, key) {
|
|
854
884
|
for (const property of object.properties) {
|
|
855
885
|
if (ts4.isPropertyAssignment(property) && nameOfProperty(property.name) === key) return property.initializer;
|
|
@@ -870,7 +900,10 @@ function urlTargetFromExpression(expr, initializers) {
|
|
|
870
900
|
function destinationTargetFromExpression(expr, initializers) {
|
|
871
901
|
const text = staticExpressionText(expr, initializers);
|
|
872
902
|
if (text) return { kind: "destination", expression: text, dynamic: false };
|
|
873
|
-
|
|
903
|
+
const candidates = staticConditionalCandidates(expr, initializers);
|
|
904
|
+
if (candidates) return { kind: "destination", dynamic: true, expressionShape: "conditional", candidateLiterals: candidates };
|
|
905
|
+
const shape = destinationExpressionShape(expr);
|
|
906
|
+
if (shape) return { kind: "destination", dynamic: true, expressionShape: shape };
|
|
874
907
|
return void 0;
|
|
875
908
|
}
|
|
876
909
|
function externalHttpEvidence(node, source, initializers) {
|
|
@@ -1018,8 +1051,10 @@ function classifyOutboundCallsInSource(source, filePath) {
|
|
|
1018
1051
|
const shorthandPath = objectPropertyIsShorthand(objectArg, "path");
|
|
1019
1052
|
const operationPathExpr = op && !shorthandPath ? `/${stripQuotes(op).replace(/^\//, "")}` : void 0;
|
|
1020
1053
|
const intent = classifyODataPathIntent(operationPathExpr, method);
|
|
1054
|
+
const entityCallTypes = { entity_mutation: "remote_entity_mutation", entity_delete: "remote_entity_delete", entity_media: "remote_entity_media", entity_candidate: "remote_entity_candidate" };
|
|
1055
|
+
const entityCallType = entityCallTypes[intent.kind];
|
|
1021
1056
|
const isODataQueryRead = method.toUpperCase() === "GET" && ["entity_query", "entity_key_read", "entity_navigation_query"].includes(intent.kind);
|
|
1022
|
-
add(node, { callType: query
|
|
1057
|
+
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
1058
|
}
|
|
1024
1059
|
} else if (ts4.isCallExpression(expr) && ts4.isIdentifier(expr.expression) && wrapperSpecs.has(expr.expression.text) || ts4.isIdentifier(expr) && wrapperSpecs.has(expr.text)) {
|
|
1025
1060
|
const wrapperName = ts4.isIdentifier(expr) ? expr.text : ts4.isCallExpression(expr) && ts4.isIdentifier(expr.expression) ? expr.expression.text : "";
|
|
@@ -2066,9 +2101,16 @@ function insertCallEdge(db, workspaceId, call, vars, generation) {
|
|
|
2066
2101
|
const servicePath = applyVariables(call.servicePathExpr ?? call.requireServicePath, vars);
|
|
2067
2102
|
const destination = call.destinationExpr ?? call.requireDestination;
|
|
2068
2103
|
const isDynamic = Boolean(Number(call.isDynamic ?? 0));
|
|
2069
|
-
const
|
|
2104
|
+
const isRemoteEntityCall = callType.startsWith("remote_entity_");
|
|
2105
|
+
const isOperationCall = !isRemoteEntityCall && (callType === "remote_action" || callType === "local_service_call" || callType === "remote_query" && Boolean(op));
|
|
2070
2106
|
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
2107
|
const evidence = callEvidence(call, resolution, servicePath, op, destination ? applyVariables(destination, vars) : void 0, normalized, intent);
|
|
2108
|
+
if (isRemoteEntityCall) {
|
|
2109
|
+
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 });
|
|
2110
|
+
const entityKind = callType.replace("remote_entity_", "remote_entity_");
|
|
2111
|
+
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);
|
|
2112
|
+
return { status: "terminal", callType };
|
|
2113
|
+
}
|
|
2072
2114
|
if (callType === "remote_query" && (isEntityQueryIntent || !op) && !resolution.target) {
|
|
2073
2115
|
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
2116
|
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 +2715,7 @@ function contextForSymbolCall(db, symbolCall, callerBindings) {
|
|
|
2673
2715
|
});
|
|
2674
2716
|
return next;
|
|
2675
2717
|
}
|
|
2676
|
-
function contextualRuntimeResolution(db, call, binding, workspaceId) {
|
|
2718
|
+
function contextualRuntimeResolution(db, call, binding, workspaceId, persistedRows = []) {
|
|
2677
2719
|
if (!binding || String(call.call_type) !== "remote_action" || call.operation_path_expr === void 0 || call.operation_path_expr === null) return {};
|
|
2678
2720
|
const normalized = normalizeODataOperationInvocationPath(String(call.operation_path_expr));
|
|
2679
2721
|
const op = normalized?.normalizedOperationPath ?? (String(call.operation_path_expr).startsWith("/") ? String(call.operation_path_expr) : `/${String(call.operation_path_expr)}`);
|
|
@@ -2683,6 +2725,8 @@ function contextualRuntimeResolution(db, call, binding, workspaceId) {
|
|
|
2683
2725
|
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
2726
|
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
2727
|
const resolvedEvidence = { ...evidence, contextualServiceBindingSelected: true, targetRepo: resolution.target.repoName, targetServicePath: resolution.target.servicePath, targetOperationPath: resolution.target.operationPath, targetOperation: resolution.target.operationName };
|
|
2728
|
+
const persistedResolved = persistedRows.find((item) => item.status === "resolved");
|
|
2729
|
+
if (persistedResolved) return { row: void 0, evidence: { ...resolvedEvidence, contextualPreservedPersistedResolvedEdge: true }, unresolvedReason: void 0 };
|
|
2686
2730
|
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
2731
|
}
|
|
2688
2732
|
function edgeTarget(row, evidence) {
|
|
@@ -2724,7 +2768,7 @@ function trace(db, start, options) {
|
|
|
2724
2768
|
const nodes = /* @__PURE__ */ new Map();
|
|
2725
2769
|
const seenEdges = /* @__PURE__ */ new Set();
|
|
2726
2770
|
const queue = scope.selectorMatched ? [{ repoId: scope.repo?.id, files: scope.sourceFiles, symbolIds: scope.symbolIds, depth: 1, context: /* @__PURE__ */ new Map() }] : [];
|
|
2727
|
-
if (scope.startOperationId) {
|
|
2771
|
+
if (scope.startOperationId && scope.selectorMatched) {
|
|
2728
2772
|
const op = operationNode(db, scope.startOperationId);
|
|
2729
2773
|
const impl = implementationScope(db, scope.startOperationId);
|
|
2730
2774
|
if (op) nodes.set(String(op.id), op);
|
|
@@ -2781,15 +2825,16 @@ function trace(db, start, options) {
|
|
|
2781
2825
|
line: call.source_line,
|
|
2782
2826
|
callType: call.call_type
|
|
2783
2827
|
});
|
|
2784
|
-
const
|
|
2785
|
-
const
|
|
2828
|
+
const persistedRowsForCall = graph.get(Number(call.id)) ?? [];
|
|
2829
|
+
const contextual = contextualRuntimeResolution(db, call, callerBindings.get(receiverFromEvidence(call.evidence_json) ?? ""), workspaceIdForCall(db, String(call.id)), persistedRowsForCall);
|
|
2830
|
+
const graphRows = contextual.row ? [contextual.row] : persistedRowsForCall;
|
|
2786
2831
|
for (const row of graphRows) {
|
|
2787
2832
|
if (seenEdges.has(Number(row.id))) continue;
|
|
2788
2833
|
seenEdges.add(Number(row.id));
|
|
2789
2834
|
const persistedEvidence = JSON.parse(
|
|
2790
2835
|
String(row.evidence_json || "{}")
|
|
2791
2836
|
);
|
|
2792
|
-
const rawEvidence = contextual.evidence ? {
|
|
2837
|
+
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
2838
|
const effective = runtimeResolution(db, row, rawEvidence, options.vars);
|
|
2794
2839
|
const evidence = effective.evidence;
|
|
2795
2840
|
const effectiveRow = effective.row;
|
|
@@ -2884,4 +2929,4 @@ export {
|
|
|
2884
2929
|
linkWorkspace,
|
|
2885
2930
|
trace
|
|
2886
2931
|
};
|
|
2887
|
-
//# sourceMappingURL=chunk-
|
|
2932
|
+
//# sourceMappingURL=chunk-CWJYVIG2.js.map
|