@saptools/service-flow 0.1.43 → 0.1.44
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 +7 -1
- package/dist/{chunk-KHQK7CFH.js → chunk-UKNPHTUS.js} +445 -307
- package/dist/chunk-UKNPHTUS.js.map +1 -0
- package/dist/cli.js +417 -466
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/doctor.ts +398 -0
- package/src/cli.ts +9 -452
- package/src/indexer/repository-indexer.ts +52 -26
- package/src/indexer/workspace-indexer.ts +17 -5
- package/src/linker/cross-repo-linker.ts +22 -2
- package/src/parsers/outbound-call-parser.ts +12 -4
- package/src/parsers/service-binding-parser-helpers.ts +160 -0
- package/src/parsers/service-binding-parser.ts +57 -242
- package/src/trace/evidence.ts +205 -0
- package/src/trace/trace-engine.ts +57 -106
- package/dist/chunk-KHQK7CFH.js.map +0 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Db } from '../db/connection.js';
|
|
2
|
-
import { extractPlaceholders
|
|
2
|
+
import { extractPlaceholders } from '../linker/dynamic-edge-resolver.js';
|
|
3
3
|
import { normalizeODataOperationInvocationPath } from '../linker/odata-path-normalizer.js';
|
|
4
|
-
import { resolveOperation
|
|
4
|
+
import { resolveOperation } from '../linker/service-resolver.js';
|
|
5
5
|
import type { TraceEdge, TraceResult, TraceStart } from '../types.js';
|
|
6
|
+
import { baseTraceEvidence, edgeTarget, runtimeResolution, runtimeVariableDiagnostic, type TraceGraphRow } from './evidence.js';
|
|
6
7
|
|
|
7
8
|
interface RepoRef {
|
|
8
9
|
id: number;
|
|
@@ -59,14 +60,10 @@ interface ContextBinding {
|
|
|
59
60
|
parameterPropertyAliasLine?: unknown;
|
|
60
61
|
calleeReceiver: string;
|
|
61
62
|
}
|
|
62
|
-
interface
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
repoName?: string;
|
|
66
|
-
operationName?: string;
|
|
67
|
-
score?: number;
|
|
63
|
+
interface ImplementationSelection {
|
|
64
|
+
methodId?: string;
|
|
65
|
+
evidence: Record<string, unknown>;
|
|
68
66
|
}
|
|
69
|
-
|
|
70
67
|
function normalizeOperation(value: string | undefined): string | undefined {
|
|
71
68
|
if (!value) return undefined;
|
|
72
69
|
return value.startsWith('/') ? value.slice(1) : value;
|
|
@@ -75,7 +72,7 @@ function positiveDepth(value: number): number {
|
|
|
75
72
|
return Number.isFinite(value) && value > 0 ? Math.floor(value) : 25;
|
|
76
73
|
}
|
|
77
74
|
|
|
78
|
-
function operationStartScope(db: Db, repoId: number | undefined, start: TraceStart): { files?: Set<string>; symbols?: Set<number>; operationId?: string; diagnostics?: Array<Record<string, unknown>> } | undefined {
|
|
75
|
+
function operationStartScope(db: Db, repoId: number | undefined, start: TraceStart, implementationRepo?: string): { files?: Set<string>; symbols?: Set<number>; operationId?: string; diagnostics?: Array<Record<string, unknown>> } | undefined {
|
|
79
76
|
const requested = normalizeOperation(start.operationPath ?? start.operation);
|
|
80
77
|
if (!requested) return undefined;
|
|
81
78
|
const rows = db.prepare(`SELECT o.id operationId,o.operation_name operationName,o.operation_path operationPath,s.service_path servicePath,r.id repoId,r.name repoName
|
|
@@ -91,7 +88,15 @@ function operationStartScope(db: Db, repoId: number | undefined, start: TraceSta
|
|
|
91
88
|
const operationId = String(rows[0]?.operationId);
|
|
92
89
|
const impl = implementationScope(db, operationId);
|
|
93
90
|
if (impl.edge?.status === 'resolved' && impl.files.size > 0) return { files: impl.files, symbols: impl.symbolId ? new Set([impl.symbolId]) : undefined, operationId, diagnostics: [] };
|
|
94
|
-
|
|
91
|
+
const hinted = implementationMethodIdFromHint(impl.edge, implementationRepo);
|
|
92
|
+
if (hinted.methodId) {
|
|
93
|
+
const hintedScope = handlerScope(db, hinted.methodId);
|
|
94
|
+
if (hintedScope?.files.size) return { files: hintedScope.files, symbols: hintedScope.symbolId ? new Set([hintedScope.symbolId]) : undefined, operationId, diagnostics: [] };
|
|
95
|
+
}
|
|
96
|
+
if (impl.edge) {
|
|
97
|
+
const evidence = parseEvidence(impl.edge.evidence_json);
|
|
98
|
+
return { operationId, 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, candidates: evidence.candidates }] };
|
|
99
|
+
}
|
|
95
100
|
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' }] };
|
|
96
101
|
}
|
|
97
102
|
|
|
@@ -141,7 +146,7 @@ function sourceFilesForStart(
|
|
|
141
146
|
}
|
|
142
147
|
return undefined;
|
|
143
148
|
}
|
|
144
|
-
function startScope(db: Db, start: TraceStart): StartScope {
|
|
149
|
+
function startScope(db: Db, start: TraceStart, implementationRepo?: string): StartScope {
|
|
145
150
|
const repo = start.repo
|
|
146
151
|
? (db
|
|
147
152
|
.prepare(
|
|
@@ -150,7 +155,7 @@ function startScope(db: Db, start: TraceStart): StartScope {
|
|
|
150
155
|
.get(start.repo, start.repo) as RepoRef | undefined)
|
|
151
156
|
: undefined;
|
|
152
157
|
if (start.repo && !repo) return { repo, selectorMatched: false };
|
|
153
|
-
const operationScope = operationStartScope(db, repo?.id, start);
|
|
158
|
+
const operationScope = operationStartScope(db, repo?.id, start, implementationRepo);
|
|
154
159
|
const terminalOperationScope = operationScope && !operationScope.files && (operationScope.diagnostics ?? []).some((d) => d.resolutionStage === 'operation' || d.resolutionStage === 'implementation');
|
|
155
160
|
const sourceScope = operationScope?.files || terminalOperationScope ? operationScope : sourceFilesForStart(db, repo?.id, start);
|
|
156
161
|
const sourceFiles = sourceScope?.files;
|
|
@@ -206,7 +211,31 @@ function implementationScope(db: Db, operationId: string): { repoId?: number; fi
|
|
|
206
211
|
const row = db.prepare('SELECT hc.repo_id repoId,hc.source_file sourceFile,s.id symbolId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id LEFT JOIN symbols s ON s.repo_id=hc.repo_id AND s.source_file=hc.source_file AND s.name=hm.method_name WHERE hm.id=?').get(edge.to_id) as { repoId?: number; sourceFile?: string; symbolId?: number } | undefined;
|
|
207
212
|
return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []), symbolId: row?.symbolId, edge };
|
|
208
213
|
}
|
|
209
|
-
function
|
|
214
|
+
function implementationMethodIdFromHint(edge: GraphRow | undefined, implementationRepo: string | undefined): ImplementationSelection {
|
|
215
|
+
if (!edge || edge.status !== 'ambiguous' || !implementationRepo) return { evidence: { status: 'not_applicable' } };
|
|
216
|
+
const evidence = parseEvidence(edge.evidence_json) as { ambiguityReasons?: string[]; candidates?: Array<{ accepted?: boolean; methodId?: number; handlerPackage?: { name?: string; packageName?: string }; sourceFile?: string }> };
|
|
217
|
+
const matches = (evidence.candidates ?? []).filter((item) => item.accepted && (
|
|
218
|
+
item.handlerPackage?.name === implementationRepo ||
|
|
219
|
+
item.handlerPackage?.packageName === implementationRepo ||
|
|
220
|
+
item.sourceFile?.startsWith(implementationRepo)
|
|
221
|
+
));
|
|
222
|
+
if (matches.length !== 1 || matches[0]?.methodId === undefined) return { evidence: { status: matches.length > 1 ? 'tied' : 'not_matched', strategy: 'implementation_repo_hint', selectedRepo: implementationRepo, candidateCount: matches.length } };
|
|
223
|
+
return {
|
|
224
|
+
methodId: String(matches[0].methodId),
|
|
225
|
+
evidence: {
|
|
226
|
+
status: 'selected',
|
|
227
|
+
guided: true,
|
|
228
|
+
strategy: 'implementation_repo_hint',
|
|
229
|
+
selectedRepo: implementationRepo,
|
|
230
|
+
selectedMethodId: matches[0].methodId,
|
|
231
|
+
ambiguityReason: evidence.ambiguityReasons?.[0],
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function contextImplementationMethodId(edge: GraphRow | undefined, callerRepoId: number | undefined, remoteEvidence: Record<string, unknown> = {}, implementationRepo?: string): ImplementationSelection {
|
|
237
|
+
const hinted = implementationMethodIdFromHint(edge, implementationRepo);
|
|
238
|
+
if (hinted.methodId) return hinted;
|
|
210
239
|
if (!edge || edge.status !== 'ambiguous' || callerRepoId === undefined) return { evidence: { status: 'not_applicable' } };
|
|
211
240
|
const evidence = JSON.parse(String(edge.evidence_json || '{}')) as { candidates?: Array<{ accepted?: boolean; methodId?: number; handlerPackage?: { id?: number; name?: string }; applicationPackage?: { id?: number; name?: string }; reasons?: string[]; score?: number }> };
|
|
212
241
|
const scores = (evidence.candidates ?? []).filter((item) => item.accepted).map((item) => {
|
|
@@ -262,38 +291,6 @@ function graphForCalls(db: Db, callIds: number[]): Map<number, GraphRow[]> {
|
|
|
262
291
|
}
|
|
263
292
|
return map;
|
|
264
293
|
}
|
|
265
|
-
function hasRuntimeVariable(value: unknown, vars: Record<string, string>): boolean {
|
|
266
|
-
return typeof value === 'string' && extractPlaceholders(value).some((key) => Object.hasOwn(vars, key));
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function isRemoteRuntimeCandidate(row: GraphRow, evidence: Record<string, unknown>, vars: Record<string, string> | undefined): boolean {
|
|
270
|
-
if (!vars || Object.keys(vars).length === 0) return false;
|
|
271
|
-
if (!['dynamic', 'ambiguous', 'unresolved'].includes(String(row.status ?? ''))) return false;
|
|
272
|
-
if (!['DYNAMIC_EDGE_CANDIDATE', 'UNRESOLVED_EDGE', 'REMOTE_CALL_RESOLVES_TO_OPERATION'].includes(row.edge_type)) return false;
|
|
273
|
-
if (row.status === 'resolved') return false;
|
|
274
|
-
return ['servicePath', 'operationPath', 'serviceAliasExpr', 'serviceAlias', 'destination'].some((key) => hasRuntimeVariable(evidence[key], vars));
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function evidenceWithRuntimeVariables(
|
|
278
|
-
evidence: Record<string, unknown>,
|
|
279
|
-
vars: Record<string, string> | undefined,
|
|
280
|
-
): Record<string, unknown> {
|
|
281
|
-
if (!vars || Object.keys(vars).length === 0) return evidence;
|
|
282
|
-
const substitutions: Record<string, RuntimeSubstitution> = {};
|
|
283
|
-
for (const key of ['servicePath', 'operationPath', 'serviceAliasExpr', 'serviceAlias', 'destination']) {
|
|
284
|
-
const substitution = substituteVariables(typeof evidence[key] === 'string' ? String(evidence[key]) : undefined, vars);
|
|
285
|
-
if (substitution.placeholders.length > 0) substitutions[key] = substitution;
|
|
286
|
-
}
|
|
287
|
-
const next: Record<string, unknown> = { ...evidence, runtimeVariablesApplied: true, runtimeSubstitutions: substitutions };
|
|
288
|
-
for (const [key, value] of Object.entries(substitutions)) {
|
|
289
|
-
if (value.effective) next[key] = value.effective;
|
|
290
|
-
}
|
|
291
|
-
const missing = Object.values(substitutions).flatMap((value) => value.missing);
|
|
292
|
-
if (missing.length > 0) next.missingRuntimeVariables = [...new Set(missing)];
|
|
293
|
-
return next;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
294
|
function symbolNode(db: Db, symbolId: number): Record<string, unknown> | undefined {
|
|
298
295
|
const row = db.prepare(`SELECT s.id symbolId,s.name symbolName,s.qualified_name qualifiedName,s.source_file sourceFile,s.start_line startLine,s.end_line endLine,r.name repoName,r.id repoId FROM symbols s JOIN repositories r ON r.id=s.repo_id WHERE s.id=?`).get(symbolId) as Record<string, unknown> | undefined;
|
|
299
296
|
if (!row) return undefined;
|
|
@@ -309,24 +306,6 @@ function operationNode(db: Db, operationId: string): Record<string, unknown> | u
|
|
|
309
306
|
function workspaceIdForCall(db: Db, callId: string): number | undefined {
|
|
310
307
|
return (db.prepare('SELECT r.workspace_id workspaceId FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id WHERE c.id=?').get(callId) as { workspaceId?: number } | undefined)?.workspaceId;
|
|
311
308
|
}
|
|
312
|
-
function runtimeResolution(db: Db, row: GraphRow, evidence: Record<string, unknown>, vars: Record<string, string> | undefined): { row: GraphRow; evidence: Record<string, unknown>; target?: OperationTarget; unresolvedReason?: string } {
|
|
313
|
-
if (!isRemoteRuntimeCandidate(row, evidence, vars))
|
|
314
|
-
return { row, evidence, unresolvedReason: row.unresolved_reason };
|
|
315
|
-
const nextEvidence = evidenceWithRuntimeVariables(evidence, vars);
|
|
316
|
-
const servicePath = typeof nextEvidence.servicePath === 'string' ? nextEvidence.servicePath : undefined;
|
|
317
|
-
const operationPath = typeof nextEvidence.normalizedOperationPath === 'string' ? nextEvidence.normalizedOperationPath : typeof nextEvidence.operationPath === 'string' ? nextEvidence.operationPath : undefined;
|
|
318
|
-
const alias = typeof nextEvidence.serviceAliasExpr === 'string' ? nextEvidence.serviceAliasExpr : typeof nextEvidence.serviceAlias === 'string' ? nextEvidence.serviceAlias : undefined;
|
|
319
|
-
const destination = typeof nextEvidence.destination === 'string' ? nextEvidence.destination : undefined;
|
|
320
|
-
const resolution = resolveOperation(db, { servicePath, operationPath, alias, destination, hasExplicitOverride: true, isDynamic: true }, workspaceIdForCall(db, row.from_id));
|
|
321
|
-
nextEvidence.runtimeResolutionStatus = resolution.status;
|
|
322
|
-
nextEvidence.runtimeResolutionReasons = resolution.reasons;
|
|
323
|
-
if (resolution.target) {
|
|
324
|
-
nextEvidence.runtimeResolvedCandidate = resolution.target;
|
|
325
|
-
return { row: { ...row, to_kind: 'operation', to_id: String(resolution.target.operationId), unresolved_reason: undefined, confidence: Math.max(0, Math.min(1, resolution.target.score)) }, evidence: nextEvidence, target: resolution.target };
|
|
326
|
-
}
|
|
327
|
-
const unresolvedReason = resolution.status === 'dynamic' ? `Dynamic target is missing runtime variables: ${resolution.reasons.join(', ')}` : resolution.status === 'ambiguous' ? 'Ambiguous runtime operation candidates' : 'No runtime operation candidate matched substituted service and operation path';
|
|
328
|
-
return { row, evidence: nextEvidence, unresolvedReason };
|
|
329
|
-
}
|
|
330
309
|
function parseEvidence(value: unknown): Record<string, unknown> {
|
|
331
310
|
try {
|
|
332
311
|
const parsed = JSON.parse(String(value || '{}')) as unknown;
|
|
@@ -429,39 +408,6 @@ function contextualRuntimeResolution(db: Db, call: CallRow, binding: ContextBind
|
|
|
429
408
|
if (persistedResolved) return { row: undefined, evidence: { ...resolvedEvidence, contextualPreservedPersistedResolvedEdge: true }, unresolvedReason: undefined };
|
|
430
409
|
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: undefined };
|
|
431
410
|
}
|
|
432
|
-
function edgeTarget(row: GraphRow, evidence: Record<string, unknown>): string {
|
|
433
|
-
const runtimeCandidate = evidence.runtimeResolvedCandidate as
|
|
434
|
-
| Candidate
|
|
435
|
-
| undefined;
|
|
436
|
-
if (runtimeCandidate?.servicePath && runtimeCandidate.operationPath)
|
|
437
|
-
return `${runtimeCandidate.servicePath}${runtimeCandidate.operationPath}`;
|
|
438
|
-
const targetServicePath = typeof evidence.targetServicePath === 'string' ? evidence.targetServicePath : undefined;
|
|
439
|
-
const targetOperationPath = typeof evidence.targetOperationPath === 'string' ? evidence.targetOperationPath : undefined;
|
|
440
|
-
if (targetServicePath && targetOperationPath) return `${targetServicePath}${targetOperationPath}`;
|
|
441
|
-
const servicePath =
|
|
442
|
-
typeof evidence.servicePath === 'string' ? evidence.servicePath : undefined;
|
|
443
|
-
const operationPath =
|
|
444
|
-
typeof evidence.operationPath === 'string'
|
|
445
|
-
? evidence.operationPath
|
|
446
|
-
: undefined;
|
|
447
|
-
const targetOperation =
|
|
448
|
-
typeof evidence.targetOperation === 'string'
|
|
449
|
-
? evidence.targetOperation
|
|
450
|
-
: undefined;
|
|
451
|
-
const targetRepo =
|
|
452
|
-
typeof evidence.targetRepo === 'string' ? evidence.targetRepo : '';
|
|
453
|
-
if (row.edge_type === 'HANDLER_RUNS_DB_QUERY') return `Entity: ${row.to_id || 'unknown'}`;
|
|
454
|
-
if (row.edge_type === 'HANDLER_RUNS_REMOTE_QUERY') return typeof evidence.remoteQueryTarget === 'string' ? evidence.remoteQueryTarget : `Remote query: ${row.to_id || 'unknown'}`;
|
|
455
|
-
if (row.edge_type === 'HANDLER_CALLS_EXTERNAL_HTTP') {
|
|
456
|
-
const target = evidence.externalTarget as Record<string, unknown> | undefined;
|
|
457
|
-
return typeof target?.label === 'string' ? target.label : `External endpoint: ${row.to_id || 'unknown'}`;
|
|
458
|
-
}
|
|
459
|
-
return servicePath && operationPath
|
|
460
|
-
? `${servicePath}${operationPath}`
|
|
461
|
-
: targetOperation
|
|
462
|
-
? `${targetRepo}:${targetOperation}`
|
|
463
|
-
: row.to_id;
|
|
464
|
-
}
|
|
465
411
|
export function trace(
|
|
466
412
|
db: Db,
|
|
467
413
|
start: TraceStart,
|
|
@@ -471,9 +417,10 @@ export function trace(
|
|
|
471
417
|
includeExternal?: boolean;
|
|
472
418
|
includeDb?: boolean;
|
|
473
419
|
includeAsync?: boolean;
|
|
420
|
+
implementationRepo?: string;
|
|
474
421
|
},
|
|
475
422
|
): TraceResult {
|
|
476
|
-
const scope = startScope(db, start);
|
|
423
|
+
const scope = startScope(db, start, options.implementationRepo);
|
|
477
424
|
const diagnostics = db
|
|
478
425
|
.prepare(
|
|
479
426
|
'SELECT severity,code,message,source_file sourceFile,source_line sourceLine FROM diagnostics WHERE (? IS NULL OR repo_id=?)',
|
|
@@ -501,12 +448,14 @@ export function trace(
|
|
|
501
448
|
const op = operationNode(db, scope.startOperationId);
|
|
502
449
|
const impl = implementationScope(db, scope.startOperationId);
|
|
503
450
|
if (op) nodes.set(String(op.id), op);
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
const
|
|
451
|
+
const startSelection = implementationMethodIdFromHint(impl.edge, options.implementationRepo);
|
|
452
|
+
if (impl.edge && (impl.edge.status === 'resolved' || startSelection.methodId)) {
|
|
453
|
+
const selectedMethodId = impl.edge.status === 'resolved' ? impl.edge.to_id : startSelection.methodId;
|
|
454
|
+
const implEvidence = { ...parseEvidence(impl.edge.evidence_json), startResolution: { strategy: 'indexed_operation_graph', matchedOperationId: scope.startOperationId, implementationEdgeId: impl.edge.id, implementationStatus: impl.edge.status, selectedHandlerMethodId: selectedMethodId }, implementationSelection: startSelection.methodId ? startSelection.evidence : undefined };
|
|
455
|
+
const handlerNode = selectedMethodId ? handlerMethodNode(db, selectedMethodId) : undefined;
|
|
507
456
|
if (handlerNode) nodes.set(String(handlerNode.id), handlerNode);
|
|
508
457
|
seenEdges.add(Number(impl.edge.id));
|
|
509
|
-
edges.push({ step: 1, type: 'operation_implemented_by_handler', from: op?.label ? String(op.label) : `operation:${scope.startOperationId}`, to: handlerNode?.label ? String(handlerNode.label) : `${impl.edge.to_kind}:${impl.edge.to_id}`, evidence: implEvidence, confidence: Number(impl.edge.confidence ?? 0), unresolvedReason: impl.edge.status === 'resolved' ? undefined : String(impl.edge.unresolved_reason ?? impl.edge.status) });
|
|
458
|
+
edges.push({ step: 1, type: 'operation_implemented_by_handler', from: op?.label ? String(op.label) : `operation:${scope.startOperationId}`, to: handlerNode?.label ? String(handlerNode.label) : `${impl.edge.to_kind}:${impl.edge.to_id}`, evidence: implEvidence, confidence: Number(impl.edge.confidence ?? 0), unresolvedReason: impl.edge.status === 'resolved' || startSelection.methodId ? undefined : String(impl.edge.unresolved_reason ?? impl.edge.status) });
|
|
510
459
|
}
|
|
511
460
|
}
|
|
512
461
|
const seenScopes = new Set<string>();
|
|
@@ -568,8 +517,8 @@ export function trace(
|
|
|
568
517
|
const persistedEvidence = JSON.parse(
|
|
569
518
|
String(row.evidence_json || '{}'),
|
|
570
519
|
) as Record<string, unknown>;
|
|
571
|
-
const rawEvidence =
|
|
572
|
-
const effective = runtimeResolution(db, row, rawEvidence, options.vars);
|
|
520
|
+
const rawEvidence = baseTraceEvidence(row as TraceGraphRow, call, persistedEvidence, contextual.evidence);
|
|
521
|
+
const effective = runtimeResolution(db, row as TraceGraphRow, rawEvidence, options.vars, workspaceIdForCall(db, String(call.id)));
|
|
573
522
|
const evidence = effective.evidence;
|
|
574
523
|
const effectiveRow = effective.row;
|
|
575
524
|
const targetNode = `${effectiveRow.to_kind}:${effectiveRow.to_id}`;
|
|
@@ -591,7 +540,7 @@ export function trace(
|
|
|
591
540
|
});
|
|
592
541
|
if (effectiveRow.to_kind === 'operation') {
|
|
593
542
|
const implementation = implementationScope(db, effectiveRow.to_id);
|
|
594
|
-
const contextSelection = contextImplementationMethodId(implementation.edge, current.repoId, evidence);
|
|
543
|
+
const contextSelection = contextImplementationMethodId(implementation.edge, current.repoId, evidence, options.implementationRepo);
|
|
595
544
|
const contextMethodId = contextSelection.methodId;
|
|
596
545
|
const contextNode = contextMethodId ? handlerMethodNode(db, contextMethodId) : undefined;
|
|
597
546
|
if (implementation.edge) {
|
|
@@ -604,7 +553,7 @@ export function trace(
|
|
|
604
553
|
type: 'operation_implemented_by_handler',
|
|
605
554
|
from: to,
|
|
606
555
|
to: implTo,
|
|
607
|
-
evidence: contextMethodId ? { ...implEvidence, contextualImplementationSelected:
|
|
556
|
+
evidence: contextMethodId ? { ...implEvidence, contextualImplementationSelected: contextSelection.evidence.strategy !== 'implementation_repo_hint', contextualImplementation: contextSelection.evidence, implementationSelection: contextSelection.evidence } : { ...implEvidence, contextualImplementation: contextSelection.evidence },
|
|
608
557
|
confidence: Number(implementation.edge.confidence ?? 0),
|
|
609
558
|
unresolvedReason: implementation.edge.status === 'resolved' || contextMethodId ? undefined : String(implementation.edge.unresolved_reason ?? implementation.edge.status),
|
|
610
559
|
});
|
|
@@ -643,5 +592,7 @@ export function trace(
|
|
|
643
592
|
}
|
|
644
593
|
}
|
|
645
594
|
}
|
|
595
|
+
const runtimeDiagnostic = runtimeVariableDiagnostic(edges);
|
|
596
|
+
if (runtimeDiagnostic) diagnostics.unshift(runtimeDiagnostic);
|
|
646
597
|
return { start, nodes: [...nodes.values()], edges, diagnostics };
|
|
647
598
|
}
|