@saptools/service-flow 0.1.53 → 0.1.54
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 +6 -0
- package/README.md +2 -0
- package/TECHNICAL-NOTE.md +7 -0
- package/dist/{chunk-LFH7C46B.js → chunk-ERIZHM5C.js} +535 -126
- package/dist/chunk-ERIZHM5C.js.map +1 -0
- package/dist/cli.js +13 -10
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/001-doctor-projection.ts +3 -7
- package/src/linker/000-implementation-candidates.ts +35 -2
- package/src/linker/001-implementation-evidence-projection.ts +78 -6
- package/src/output/table-output.ts +11 -2
- package/src/trace/008-contextual-runtime-state.ts +294 -0
- package/src/trace/009-selected-handler-provenance.ts +186 -0
- package/src/trace/evidence.ts +103 -31
- package/src/trace/trace-engine.ts +67 -82
- package/src/utils/000-bounded-projection.ts +20 -15
- package/dist/chunk-LFH7C46B.js.map +0 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type { Db } from '../db/connection.js';
|
|
2
|
+
import {
|
|
3
|
+
selectedHandlerProvenance,
|
|
4
|
+
type SelectedHandlerProvenance,
|
|
5
|
+
type SelectedHandlerSource,
|
|
6
|
+
} from '../linker/001-implementation-evidence-projection.js';
|
|
7
|
+
|
|
8
|
+
export interface HandlerMethodNode extends Record<string, unknown> {
|
|
9
|
+
id: string;
|
|
10
|
+
kind: 'handler_method';
|
|
11
|
+
label: string;
|
|
12
|
+
methodId: number;
|
|
13
|
+
methodName: string;
|
|
14
|
+
className: string;
|
|
15
|
+
sourceFile: string;
|
|
16
|
+
sourceLine: number;
|
|
17
|
+
repoId: number;
|
|
18
|
+
repoName: string;
|
|
19
|
+
packageName?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SelectedHandlerEvidence {
|
|
23
|
+
handler?: HandlerMethodNode;
|
|
24
|
+
evidence: Record<string, unknown>;
|
|
25
|
+
diagnostic?: Record<string, unknown>;
|
|
26
|
+
unresolvedReason?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function handlerMethodNode(
|
|
30
|
+
db: Db,
|
|
31
|
+
methodId: string,
|
|
32
|
+
): HandlerMethodNode | undefined {
|
|
33
|
+
const row = db.prepare(`SELECT hm.id methodId,hm.method_name methodName,
|
|
34
|
+
hm.source_line sourceLine,hc.class_name className,hc.source_file sourceFile,
|
|
35
|
+
r.name repoName,r.id repoId,r.package_name packageName
|
|
36
|
+
FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
37
|
+
JOIN repositories r ON r.id=hc.repo_id WHERE hm.id=?`).get(methodId) as
|
|
38
|
+
| Record<string, unknown>
|
|
39
|
+
| undefined;
|
|
40
|
+
return handlerNodeFromRow(row);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function withSelectedHandlerProvenance(
|
|
44
|
+
evidence: Record<string, unknown>,
|
|
45
|
+
targetId: string,
|
|
46
|
+
handler: HandlerMethodNode | undefined,
|
|
47
|
+
): SelectedHandlerEvidence {
|
|
48
|
+
if (!handler) return unavailableHandlerEvidence(evidence, targetId);
|
|
49
|
+
const selected = selectedHandlerProvenance(handlerSource(handler));
|
|
50
|
+
const stored = record(evidence.selectedHandler);
|
|
51
|
+
const mismatchedFields = storedSelectedHandlerMismatches(
|
|
52
|
+
stored, selected, targetId,
|
|
53
|
+
);
|
|
54
|
+
if (mismatchedFields.length === 0)
|
|
55
|
+
return { handler, evidence: { ...evidence, selectedHandler: selected } };
|
|
56
|
+
return {
|
|
57
|
+
handler,
|
|
58
|
+
evidence: {
|
|
59
|
+
...evidence,
|
|
60
|
+
selectedHandler: selected,
|
|
61
|
+
selectedHandlerProvenanceAudit: {
|
|
62
|
+
status: 'mismatch',
|
|
63
|
+
graphTargetId: targetId,
|
|
64
|
+
mismatchedFields,
|
|
65
|
+
persistedSelectedHandler: stored,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
diagnostic: {
|
|
69
|
+
severity: 'warning',
|
|
70
|
+
code: 'selected_handler_provenance_mismatch',
|
|
71
|
+
message: 'Persisted selected handler provenance did not match the resolved graph target',
|
|
72
|
+
graphTargetId: targetId,
|
|
73
|
+
mismatchedFields,
|
|
74
|
+
sourceFile: handler.sourceFile,
|
|
75
|
+
sourceLine: handler.sourceLine,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function unavailableHandlerEvidence(
|
|
81
|
+
evidence: Record<string, unknown>,
|
|
82
|
+
targetId: string,
|
|
83
|
+
): SelectedHandlerEvidence {
|
|
84
|
+
return {
|
|
85
|
+
evidence: {
|
|
86
|
+
...evidence,
|
|
87
|
+
selectedHandlerProvenanceAudit: {
|
|
88
|
+
status: 'unavailable',
|
|
89
|
+
graphTargetId: targetId,
|
|
90
|
+
reason: 'handler_target_not_indexed',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
diagnostic: {
|
|
94
|
+
severity: 'warning',
|
|
95
|
+
code: 'selected_handler_target_not_found',
|
|
96
|
+
message: 'Resolved implementation target is not an indexed handler method',
|
|
97
|
+
graphTargetId: targetId,
|
|
98
|
+
},
|
|
99
|
+
unresolvedReason: 'Resolved implementation target is not an indexed handler method',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function handlerNodeFromRow(
|
|
104
|
+
row: Record<string, unknown> | undefined,
|
|
105
|
+
): HandlerMethodNode | undefined {
|
|
106
|
+
const methodId = numberValue(row?.methodId);
|
|
107
|
+
const methodName = stringValue(row?.methodName);
|
|
108
|
+
const className = stringValue(row?.className);
|
|
109
|
+
const sourceFile = stringValue(row?.sourceFile);
|
|
110
|
+
const sourceLine = numberValue(row?.sourceLine);
|
|
111
|
+
const repoId = numberValue(row?.repoId);
|
|
112
|
+
const repoName = stringValue(row?.repoName);
|
|
113
|
+
if (methodId === undefined || !methodName || !className || !sourceFile
|
|
114
|
+
|| sourceLine === undefined || repoId === undefined || !repoName)
|
|
115
|
+
return undefined;
|
|
116
|
+
return {
|
|
117
|
+
id: `handler_method:${methodId}`,
|
|
118
|
+
kind: 'handler_method',
|
|
119
|
+
label: `${repoName}:${className}.${methodName}`,
|
|
120
|
+
methodId,
|
|
121
|
+
methodName,
|
|
122
|
+
className,
|
|
123
|
+
sourceFile,
|
|
124
|
+
sourceLine,
|
|
125
|
+
repoId,
|
|
126
|
+
repoName,
|
|
127
|
+
packageName: stringValue(row?.packageName),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function handlerSource(node: HandlerMethodNode): SelectedHandlerSource {
|
|
132
|
+
return {
|
|
133
|
+
methodId: node.methodId,
|
|
134
|
+
methodName: node.methodName,
|
|
135
|
+
className: node.className,
|
|
136
|
+
repositoryId: node.repoId,
|
|
137
|
+
repositoryName: node.repoName,
|
|
138
|
+
repositoryPackageName: node.packageName,
|
|
139
|
+
sourceFile: node.sourceFile,
|
|
140
|
+
sourceLine: node.sourceLine,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function storedSelectedHandlerMismatches(
|
|
145
|
+
stored: Record<string, unknown>,
|
|
146
|
+
selected: SelectedHandlerProvenance,
|
|
147
|
+
targetId: string,
|
|
148
|
+
): string[] {
|
|
149
|
+
if (Object.keys(stored).length === 0) return [];
|
|
150
|
+
const expectedRepository = record(stored.repository);
|
|
151
|
+
const selectedRepository = selected.repository ?? {};
|
|
152
|
+
return [
|
|
153
|
+
mismatch('graphTargetId', stored.graphTargetId, targetId),
|
|
154
|
+
mismatch('methodId', stored.methodId, selected.methodId),
|
|
155
|
+
mismatch('className', stored.className, selected.className),
|
|
156
|
+
mismatch('methodName', stored.methodName, selected.methodName),
|
|
157
|
+
mismatch('sourceFile', stored.sourceFile, selected.sourceFile),
|
|
158
|
+
mismatch('sourceLine', stored.sourceLine, selected.sourceLine),
|
|
159
|
+
mismatch('repository.id', expectedRepository.id, selectedRepository.id),
|
|
160
|
+
mismatch('repository.name', expectedRepository.name, selectedRepository.name),
|
|
161
|
+
mismatch('repository.packageName', expectedRepository.packageName,
|
|
162
|
+
selectedRepository.packageName),
|
|
163
|
+
].flatMap((field) => field ? [field] : []);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function mismatch(
|
|
167
|
+
field: string,
|
|
168
|
+
stored: unknown,
|
|
169
|
+
actual: unknown,
|
|
170
|
+
): string | undefined {
|
|
171
|
+
return stored === undefined || stored === actual ? undefined : field;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function record(value: unknown): Record<string, unknown> {
|
|
175
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
176
|
+
? value as Record<string, unknown>
|
|
177
|
+
: {};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function stringValue(value: unknown): string | undefined {
|
|
181
|
+
return typeof value === 'string' ? value : undefined;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function numberValue(value: unknown): number | undefined {
|
|
185
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
186
|
+
}
|
package/src/trace/evidence.ts
CHANGED
|
@@ -5,6 +5,11 @@ import { resolveOperation, type OperationTarget } from '../linker/service-resolv
|
|
|
5
5
|
import type { DynamicMode } from '../types.js';
|
|
6
6
|
import { analyzeDynamicTargetCandidates, type DynamicTargetAnalysis, type DynamicTargetCandidate } from './dynamic-targets.js';
|
|
7
7
|
import { boundCandidateLikeEvidence } from '../utils/000-bounded-projection.js';
|
|
8
|
+
import {
|
|
9
|
+
dynamicMissingReason,
|
|
10
|
+
isStructuralContextualBlocker,
|
|
11
|
+
type ContextualRuntimeState,
|
|
12
|
+
} from './008-contextual-runtime-state.js';
|
|
8
13
|
|
|
9
14
|
export interface TraceGraphRow extends Record<string, unknown> {
|
|
10
15
|
id: number;
|
|
@@ -35,6 +40,12 @@ interface RuntimeDiagnosticTotals {
|
|
|
35
40
|
rejectedCandidates: Record<string, unknown>[];
|
|
36
41
|
suggestedVarSets: Record<string, unknown>[];
|
|
37
42
|
}
|
|
43
|
+
interface RuntimeResolutionResult {
|
|
44
|
+
row: TraceGraphRow;
|
|
45
|
+
evidence: Record<string, unknown>;
|
|
46
|
+
target?: OperationTarget;
|
|
47
|
+
unresolvedReason?: string;
|
|
48
|
+
}
|
|
38
49
|
|
|
39
50
|
export function baseTraceEvidence(
|
|
40
51
|
row: TraceGraphRow,
|
|
@@ -67,8 +78,8 @@ export function runtimeResolution(
|
|
|
67
78
|
evidence: Record<string, unknown>,
|
|
68
79
|
options: { vars?: Record<string, string>; dynamicMode?: DynamicMode; maxDynamicCandidates?: number },
|
|
69
80
|
workspaceId: number | undefined,
|
|
70
|
-
|
|
71
|
-
):
|
|
81
|
+
contextualState?: ContextualRuntimeState,
|
|
82
|
+
): RuntimeResolutionResult {
|
|
72
83
|
const dynamicMode = options.dynamicMode ?? 'strict';
|
|
73
84
|
const candidateCap = positiveCandidateCap(options.maxDynamicCandidates);
|
|
74
85
|
const boundedEvidence = boundCandidateLikeEvidence(evidence, candidateCap);
|
|
@@ -76,7 +87,7 @@ export function runtimeResolution(
|
|
|
76
87
|
return unchangedRuntimeResolution(
|
|
77
88
|
row,
|
|
78
89
|
boundDynamicEvidence(boundedEvidence, candidateCap),
|
|
79
|
-
|
|
90
|
+
contextualState,
|
|
80
91
|
);
|
|
81
92
|
const substituted = evidenceWithRuntimeVariables(boundedEvidence, options.vars);
|
|
82
93
|
const analysis = analyzeDynamicTargetCandidates(
|
|
@@ -86,47 +97,83 @@ export function runtimeResolution(
|
|
|
86
97
|
analysis ? evidenceWithDynamicAnalysis(substituted, analysis) : substituted,
|
|
87
98
|
candidateCap,
|
|
88
99
|
);
|
|
100
|
+
const appliedRuntimeValues = hasApplicableRuntimeVariables(evidence, options.vars);
|
|
101
|
+
const analyzed = analyzedRuntimeResolution(
|
|
102
|
+
row, enriched, analysis, dynamicMode, appliedRuntimeValues, contextualState,
|
|
103
|
+
);
|
|
104
|
+
if (analyzed) return analyzed;
|
|
105
|
+
if (!appliedRuntimeValues) {
|
|
106
|
+
const unresolvedReason = contextualReason(contextualState) ?? row.unresolved_reason;
|
|
107
|
+
const withSections = withEffectiveResolution(
|
|
108
|
+
enriched, row, unresolvedReason, undefined, contextualState,
|
|
109
|
+
);
|
|
110
|
+
return { row, evidence: withSections, unresolvedReason };
|
|
111
|
+
}
|
|
112
|
+
return resolveSuppliedRuntimeOperation(db, row, enriched, workspaceId, contextualState);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function analyzedRuntimeResolution(
|
|
116
|
+
row: TraceGraphRow,
|
|
117
|
+
evidence: Record<string, unknown>,
|
|
118
|
+
analysis: DynamicTargetAnalysis | undefined,
|
|
119
|
+
dynamicMode: DynamicMode,
|
|
120
|
+
appliedRuntimeValues: boolean,
|
|
121
|
+
contextualState: ContextualRuntimeState | undefined,
|
|
122
|
+
): RuntimeResolutionResult | undefined {
|
|
89
123
|
if (analysis && analysis.viableCandidateCount === 0
|
|
90
124
|
&& Object.keys(analysis.appliedSuppliedVariables).length > 0)
|
|
91
|
-
return noCandidateRuntimeResolution(row,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (analysis && analysis.missingVariables.length > 0) {
|
|
98
|
-
const unresolvedReason = contextualUnresolvedReason
|
|
99
|
-
?? row.unresolved_reason
|
|
100
|
-
?? 'Dynamic target still requires runtime variables';
|
|
125
|
+
return noCandidateRuntimeResolution(row, evidence, contextualState);
|
|
126
|
+
const inferred = dynamicMode === 'infer' ? inferredTarget(analysis) : undefined;
|
|
127
|
+
if (inferred && !isStructuralContextualBlocker(contextualState))
|
|
128
|
+
return resolvedRuntimeResolution(row, evidence, inferred, inferred.reasons);
|
|
129
|
+
if (analysis && analysis.missingVariables.length > 0 && appliedRuntimeValues) {
|
|
130
|
+
const unresolvedReason = dynamicMissingReason(analysis.missingVariables);
|
|
101
131
|
return {
|
|
102
132
|
row,
|
|
103
|
-
evidence: withEffectiveResolution(
|
|
133
|
+
evidence: withEffectiveResolution(
|
|
134
|
+
evidence, row, unresolvedReason, undefined, contextualState,
|
|
135
|
+
),
|
|
104
136
|
unresolvedReason,
|
|
105
137
|
};
|
|
106
138
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
139
|
+
return isStructuralContextualBlocker(contextualState)
|
|
140
|
+
? unchangedRuntimeResolution(row, evidence, contextualState)
|
|
141
|
+
: undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function resolveSuppliedRuntimeOperation(
|
|
145
|
+
db: Db,
|
|
146
|
+
row: TraceGraphRow,
|
|
147
|
+
evidence: Record<string, unknown>,
|
|
148
|
+
workspaceId: number | undefined,
|
|
149
|
+
contextualState: ContextualRuntimeState | undefined,
|
|
150
|
+
): RuntimeResolutionResult {
|
|
151
|
+
const resolution = resolveRuntimeOperation(db, evidence, workspaceId);
|
|
113
152
|
if (resolution.target)
|
|
114
153
|
return resolvedRuntimeResolution(
|
|
115
|
-
row,
|
|
154
|
+
row, evidence, resolution.target, resolution.reasons,
|
|
116
155
|
);
|
|
117
156
|
const unresolvedReason = runtimeUnresolvedReason(resolution);
|
|
118
|
-
return {
|
|
157
|
+
return {
|
|
158
|
+
row,
|
|
159
|
+
evidence: withEffectiveResolution(
|
|
160
|
+
evidence, row, unresolvedReason, resolution, contextualState,
|
|
161
|
+
),
|
|
162
|
+
unresolvedReason,
|
|
163
|
+
};
|
|
119
164
|
}
|
|
120
165
|
|
|
121
166
|
function unchangedRuntimeResolution(
|
|
122
167
|
row: TraceGraphRow,
|
|
123
168
|
evidence: Record<string, unknown>,
|
|
124
|
-
|
|
125
|
-
):
|
|
126
|
-
const unresolvedReason =
|
|
169
|
+
contextualState: ContextualRuntimeState | undefined,
|
|
170
|
+
): RuntimeResolutionResult {
|
|
171
|
+
const unresolvedReason = contextualReason(contextualState) ?? row.unresolved_reason;
|
|
127
172
|
return {
|
|
128
173
|
row,
|
|
129
|
-
evidence: withEffectiveResolution(
|
|
174
|
+
evidence: withEffectiveResolution(
|
|
175
|
+
evidence, row, unresolvedReason, undefined, contextualState,
|
|
176
|
+
),
|
|
130
177
|
unresolvedReason,
|
|
131
178
|
};
|
|
132
179
|
}
|
|
@@ -134,11 +181,14 @@ function unchangedRuntimeResolution(
|
|
|
134
181
|
function noCandidateRuntimeResolution(
|
|
135
182
|
row: TraceGraphRow,
|
|
136
183
|
evidence: Record<string, unknown>,
|
|
137
|
-
|
|
184
|
+
contextualState: ContextualRuntimeState | undefined,
|
|
185
|
+
): RuntimeResolutionResult {
|
|
138
186
|
const unresolvedReason = 'No candidate remained after runtime substitution';
|
|
139
187
|
return {
|
|
140
188
|
row,
|
|
141
|
-
evidence: withEffectiveResolution(
|
|
189
|
+
evidence: withEffectiveResolution(
|
|
190
|
+
evidence, row, unresolvedReason, undefined, contextualState,
|
|
191
|
+
),
|
|
142
192
|
unresolvedReason,
|
|
143
193
|
};
|
|
144
194
|
}
|
|
@@ -148,7 +198,7 @@ function resolvedRuntimeResolution(
|
|
|
148
198
|
evidence: Record<string, unknown>,
|
|
149
199
|
target: OperationTarget,
|
|
150
200
|
reasons: string[],
|
|
151
|
-
):
|
|
201
|
+
): RuntimeResolutionResult {
|
|
152
202
|
const resolvedRow = {
|
|
153
203
|
...row,
|
|
154
204
|
status: 'resolved',
|
|
@@ -328,6 +378,7 @@ function effectiveResolution(
|
|
|
328
378
|
evidence: Record<string, unknown>,
|
|
329
379
|
unresolvedReason: string | undefined,
|
|
330
380
|
resolution?: ReturnType<typeof resolveRuntimeOperation>,
|
|
381
|
+
contextualState?: ContextualRuntimeState,
|
|
331
382
|
): Record<string, unknown> {
|
|
332
383
|
const target = resolution?.target;
|
|
333
384
|
return {
|
|
@@ -342,6 +393,8 @@ function effectiveResolution(
|
|
|
342
393
|
reasons: resolution?.reasons ?? evidence.resolutionReasons,
|
|
343
394
|
unresolvedReason,
|
|
344
395
|
edgeType: target ? 'REMOTE_CALL_RESOLVES_TO_OPERATION' : row.edge_type,
|
|
396
|
+
contextualBlocker: isStructuralContextualBlocker(contextualState)
|
|
397
|
+
? contextualState : undefined,
|
|
345
398
|
};
|
|
346
399
|
}
|
|
347
400
|
|
|
@@ -350,11 +403,30 @@ function withEffectiveResolution(
|
|
|
350
403
|
row: TraceGraphRow,
|
|
351
404
|
unresolvedReason: string | undefined,
|
|
352
405
|
resolution?: ReturnType<typeof resolveRuntimeOperation>,
|
|
406
|
+
contextualState?: ContextualRuntimeState,
|
|
353
407
|
): Record<string, unknown> {
|
|
354
|
-
const current = effectiveResolution(
|
|
408
|
+
const current = effectiveResolution(
|
|
409
|
+
row, evidence, unresolvedReason, resolution, contextualState,
|
|
410
|
+
);
|
|
355
411
|
const rest = { ...evidence };
|
|
356
412
|
delete rest.runtimeResolvedCandidate;
|
|
357
|
-
return {
|
|
413
|
+
return {
|
|
414
|
+
...rest,
|
|
415
|
+
effectiveResolution: current,
|
|
416
|
+
linker: {
|
|
417
|
+
status: current.status,
|
|
418
|
+
confidence: current.confidence,
|
|
419
|
+
reason: unresolvedReason,
|
|
420
|
+
edgeType: current.edgeType,
|
|
421
|
+
contextualBlocker: current.contextualBlocker,
|
|
422
|
+
},
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function contextualReason(
|
|
427
|
+
state: ContextualRuntimeState | undefined,
|
|
428
|
+
): string | undefined {
|
|
429
|
+
return state?.category === 'none' ? undefined : state?.message;
|
|
358
430
|
}
|
|
359
431
|
|
|
360
432
|
function resolveRuntimeOperation(db: Db, evidence: Record<string, unknown>, workspaceId: number | undefined): ReturnType<typeof resolveOperation> {
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { Db } from '../db/connection.js';
|
|
2
2
|
import { reposByName } from '../db/repositories.js';
|
|
3
3
|
import { extractPlaceholders } from '../linker/dynamic-edge-resolver.js';
|
|
4
|
-
import { normalizeODataOperationInvocationPath } from '../linker/odata-path-normalizer.js';
|
|
5
|
-
import { resolveOperation } from '../linker/service-resolver.js';
|
|
6
4
|
import type { ImplementationHint, TraceEdge, TraceOptions, TraceResult, TraceStart } from '../types.js';
|
|
7
5
|
import { baseTraceEvidence, edgeTarget, runtimeNoCandidateDiagnostics, runtimeResolution, runtimeVariableDiagnostic, type TraceGraphRow } from './evidence.js';
|
|
8
6
|
import { dynamicCandidateBranches } from './dynamic-branches.js';
|
|
@@ -15,8 +13,16 @@ import {
|
|
|
15
13
|
contextualImplementationSelection,
|
|
16
14
|
hintedImplementationSelection,
|
|
17
15
|
} from './005-implementation-selection.js';
|
|
18
|
-
import { boundedContextCandidates } from './006-contextual-projection.js';
|
|
19
16
|
import { implementationStartDiagnostic } from './007-implementation-start-diagnostic.js';
|
|
17
|
+
import {
|
|
18
|
+
contextualRuntimeResolution,
|
|
19
|
+
type ContextBinding,
|
|
20
|
+
} from './008-contextual-runtime-state.js';
|
|
21
|
+
import {
|
|
22
|
+
handlerMethodNode,
|
|
23
|
+
withSelectedHandlerProvenance,
|
|
24
|
+
type SelectedHandlerEvidence,
|
|
25
|
+
} from './009-selected-handler-provenance.js';
|
|
20
26
|
import { boundCandidateLikeEvidence } from '../utils/000-bounded-projection.js';
|
|
21
27
|
import {
|
|
22
28
|
ambiguousStartDiagnostic,
|
|
@@ -60,32 +66,6 @@ interface GraphRow extends Record<string, unknown> {
|
|
|
60
66
|
unresolved_reason?: string;
|
|
61
67
|
status?: string;
|
|
62
68
|
}
|
|
63
|
-
interface ContextBinding {
|
|
64
|
-
bindingId?: number;
|
|
65
|
-
alias?: string;
|
|
66
|
-
aliasExpr?: string;
|
|
67
|
-
destinationExpr?: string;
|
|
68
|
-
servicePathExpr?: string;
|
|
69
|
-
requireServicePath?: string;
|
|
70
|
-
requireDestination?: string;
|
|
71
|
-
effectiveServicePath?: string;
|
|
72
|
-
effectiveDestination?: string;
|
|
73
|
-
sourceFile?: string;
|
|
74
|
-
sourceLine?: number;
|
|
75
|
-
source: string;
|
|
76
|
-
callerArgument?: string;
|
|
77
|
-
callerProperty?: string;
|
|
78
|
-
calleeParameter?: string;
|
|
79
|
-
calleeObjectProperty?: string;
|
|
80
|
-
calleeLocalDestructuredIdentifier?: string;
|
|
81
|
-
parameterPropertyAliasKind?: unknown;
|
|
82
|
-
parameterPropertyAliasLine?: unknown;
|
|
83
|
-
calleeReceiver: string;
|
|
84
|
-
callerSite?: { sourceFile?: string; sourceLine?: number };
|
|
85
|
-
calleeSite?: { sourceFile?: string; sourceLine?: number };
|
|
86
|
-
resolutionStatus?: 'selected' | 'ambiguous';
|
|
87
|
-
bindingCandidates?: Array<Record<string, unknown>>;
|
|
88
|
-
}
|
|
89
69
|
interface ImplementationHintOptions {
|
|
90
70
|
implementationRepo?: string;
|
|
91
71
|
implementationHints?: ImplementationHint[];
|
|
@@ -219,11 +199,6 @@ function handlerFilesForOperation(db: Db, operationId: string): Set<string> {
|
|
|
219
199
|
function implementationEdge(db: Db, operationId: string): GraphRow | undefined {
|
|
220
200
|
return db.prepare("SELECT * FROM graph_edges WHERE edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND from_kind='operation' AND from_id=? ORDER BY CASE status WHEN 'resolved' THEN 0 WHEN 'ambiguous' THEN 1 ELSE 2 END,id LIMIT 1").get(operationId) as GraphRow | undefined;
|
|
221
201
|
}
|
|
222
|
-
function handlerMethodNode(db: Db, methodId: string): Record<string, unknown> | undefined {
|
|
223
|
-
const row = db.prepare(`SELECT hm.id methodId,hm.method_name methodName,hm.decorator_value decoratorValue,hm.source_line sourceLine,hc.class_name className,hc.source_file sourceFile,r.name repoName,r.id repoId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id JOIN repositories r ON r.id=hc.repo_id WHERE hm.id=?`).get(methodId) as Record<string, unknown> | undefined;
|
|
224
|
-
if (!row) return undefined;
|
|
225
|
-
return { id: `handler_method:${methodId}`, kind: 'handler_method', label: `${String(row.repoName)}:${String(row.className)}.${String(row.methodName)}`, ...row };
|
|
226
|
-
}
|
|
227
202
|
function implementationScope(db: Db, operationId: string): { repoId?: number; files: Set<string>; symbolId?: number; edge?: GraphRow } {
|
|
228
203
|
const edge = implementationEdge(db, operationId);
|
|
229
204
|
if (!edge || edge.status !== 'resolved') return { files: new Set(), edge };
|
|
@@ -436,40 +411,6 @@ function contextForSymbolCall(db: Db, symbolCall: Record<string, unknown>, calle
|
|
|
436
411
|
});
|
|
437
412
|
return next;
|
|
438
413
|
}
|
|
439
|
-
function contextualRuntimeResolution(db: Db, call: CallRow, binding: ContextBinding | undefined, workspaceId: number | undefined, persistedRows: GraphRow[] = []): { row?: GraphRow; evidence?: Record<string, unknown>; unresolvedReason?: string } {
|
|
440
|
-
if (!binding || String(call.call_type) !== 'remote_action' || call.operation_path_expr === undefined || call.operation_path_expr === null) return {};
|
|
441
|
-
if (binding.resolutionStatus === 'ambiguous') {
|
|
442
|
-
const candidates = boundedContextCandidates(binding.bindingCandidates ?? []);
|
|
443
|
-
return {
|
|
444
|
-
evidence: {
|
|
445
|
-
contextualServiceBindingAttempted: true,
|
|
446
|
-
contextualBinding: {
|
|
447
|
-
source: binding.source,
|
|
448
|
-
status: 'tied',
|
|
449
|
-
candidates: candidates.candidates,
|
|
450
|
-
candidateCount: candidates.candidateCount,
|
|
451
|
-
shownCandidateCount: candidates.shownCandidateCount,
|
|
452
|
-
omittedCandidateCount: candidates.omittedCandidateCount,
|
|
453
|
-
},
|
|
454
|
-
contextualResolutionStatus: 'ambiguous',
|
|
455
|
-
contextualCandidateCount: candidates.candidateCount,
|
|
456
|
-
},
|
|
457
|
-
unresolvedReason: 'Ambiguous contextual service binding candidates',
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
const normalized = normalizeODataOperationInvocationPath(String(call.operation_path_expr));
|
|
461
|
-
const op = normalized?.normalizedOperationPath ?? (String(call.operation_path_expr).startsWith('/') ? String(call.operation_path_expr) : `/${String(call.operation_path_expr)}`);
|
|
462
|
-
const servicePath = binding.effectiveServicePath ?? binding.servicePathExpr ?? binding.requireServicePath;
|
|
463
|
-
const destination = binding.effectiveDestination ?? binding.destinationExpr ?? binding.requireDestination;
|
|
464
|
-
const resolution = resolveOperation(db, { servicePath, operationPath: op, alias: binding.aliasExpr ?? binding.alias, destination, hasExplicitOverride: true, isDynamic: false }, workspaceId);
|
|
465
|
-
const candidates = boundedContextCandidates(resolution.candidates);
|
|
466
|
-
const evidence: Record<string, unknown> = { contextualServiceBindingAttempted: true, contextualBinding: { source: binding.source, callerArgument: binding.callerArgument, callerProperty: binding.callerProperty, calleeParameter: binding.calleeParameter, calleeReceiver: binding.calleeReceiver, callerSite: binding.callerSite, calleeSite: binding.calleeSite, 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 : undefined, invocationArgumentPlaceholderKeys: normalized?.invocationArgumentPlaceholderKeys.length ? normalized.invocationArgumentPlaceholderKeys : undefined, servicePath, serviceAlias: binding.alias, serviceAliasExpr: binding.aliasExpr, destination, requireServicePath: binding.requireServicePath, requireDestination: binding.requireDestination, effectiveServicePath: binding.effectiveServicePath, effectiveDestination: binding.effectiveDestination, contextualResolutionStatus: resolution.status, contextualCandidateCount: candidates.candidateCount, shownContextualCandidateCount: candidates.shownCandidateCount, omittedContextualCandidateCount: candidates.omittedCandidateCount, candidates: candidates.candidates, contextualResolutionReasons: resolution.reasons, resolutionReasons: resolution.reasons };
|
|
467
|
-
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' };
|
|
468
|
-
const resolvedEvidence = { ...evidence, contextualServiceBindingSelected: true, targetRepo: resolution.target.repoName, targetServicePath: resolution.target.servicePath, targetOperationPath: resolution.target.operationPath, targetOperation: resolution.target.operationName };
|
|
469
|
-
const persistedResolved = persistedRows.find((item) => item.status === 'resolved');
|
|
470
|
-
if (persistedResolved) return { row: undefined, evidence: { ...resolvedEvidence, contextualPreservedPersistedResolvedEdge: true }, unresolvedReason: undefined };
|
|
471
|
-
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 };
|
|
472
|
-
}
|
|
473
414
|
export function trace(
|
|
474
415
|
db: Db,
|
|
475
416
|
start: TraceStart,
|
|
@@ -512,11 +453,27 @@ export function trace(
|
|
|
512
453
|
);
|
|
513
454
|
if (impl.edge && (impl.edge.status === 'resolved' || startSelection.methodId)) {
|
|
514
455
|
const selectedMethodId = impl.edge.status === 'resolved' ? impl.edge.to_id : startSelection.methodId;
|
|
515
|
-
const implEvidence = {
|
|
516
|
-
|
|
517
|
-
|
|
456
|
+
const implEvidence = {
|
|
457
|
+
...parseEvidence(impl.edge.evidence_json),
|
|
458
|
+
startResolution: {
|
|
459
|
+
strategy: 'indexed_operation_graph',
|
|
460
|
+
matchedOperationId: scope.startOperationId,
|
|
461
|
+
implementationEdgeId: impl.edge.id,
|
|
462
|
+
implementationStatus: impl.edge.status,
|
|
463
|
+
selectedHandlerMethodId: selectedMethodId,
|
|
464
|
+
},
|
|
465
|
+
implementationSelection: startSelection.methodId
|
|
466
|
+
? startSelection.evidence : undefined,
|
|
467
|
+
};
|
|
468
|
+
const selected: SelectedHandlerEvidence = selectedMethodId
|
|
469
|
+
? withSelectedHandlerProvenance(
|
|
470
|
+
implEvidence, selectedMethodId, handlerMethodNode(db, selectedMethodId),
|
|
471
|
+
)
|
|
472
|
+
: { evidence: implEvidence };
|
|
473
|
+
if (selected.diagnostic) prependTraceDiagnostic(diagnostics, selected.diagnostic);
|
|
474
|
+
if (selected.handler) nodes.set(String(selected.handler.id), selected.handler);
|
|
518
475
|
seenEdges.add(Number(impl.edge.id));
|
|
519
|
-
edges.push({ step: 1, type: 'operation_implemented_by_handler', from: op?.label ? String(op.label) : `operation:${scope.startOperationId}`, to:
|
|
476
|
+
edges.push({ step: 1, type: 'operation_implemented_by_handler', from: op?.label ? String(op.label) : `operation:${scope.startOperationId}`, to: selected.handler?.label ? String(selected.handler.label) : `${impl.edge.to_kind}:${impl.edge.to_id}`, evidence: selected.evidence, confidence: Number(impl.edge.confidence ?? 0), unresolvedReason: selected.unresolvedReason ?? (impl.edge.status === 'resolved' || startSelection.methodId ? undefined : String(impl.edge.unresolved_reason ?? impl.edge.status)) });
|
|
520
477
|
}
|
|
521
478
|
}
|
|
522
479
|
const seenScopes = new Set<string>();
|
|
@@ -581,7 +538,7 @@ export function trace(
|
|
|
581
538
|
vars: options.vars,
|
|
582
539
|
dynamicMode: options.dynamicMode ?? 'strict',
|
|
583
540
|
maxDynamicCandidates: options.maxDynamicCandidates,
|
|
584
|
-
}, workspaceIdForCall(db, String(call.id)), contextual.
|
|
541
|
+
}, workspaceIdForCall(db, String(call.id)), contextual.state);
|
|
585
542
|
const evidence = effective.evidence;
|
|
586
543
|
const effectiveRow = effective.row;
|
|
587
544
|
const targetNode = `${effectiveRow.to_kind}:${effectiveRow.to_id}`;
|
|
@@ -611,31 +568,59 @@ export function trace(
|
|
|
611
568
|
hintOptions,
|
|
612
569
|
);
|
|
613
570
|
const contextMethodId = contextSelection.methodId;
|
|
614
|
-
|
|
571
|
+
let selectedHandlerAvailable = true;
|
|
615
572
|
if (implementation.edge) {
|
|
616
573
|
const implEvidence = parseEvidence(implementation.edge.evidence_json);
|
|
617
574
|
const hintDiagnostic = implementationHintDiagnostic(contextSelection, implEvidence);
|
|
618
575
|
if (hintDiagnostic) prependTraceDiagnostic(diagnostics, hintDiagnostic);
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
|
|
576
|
+
const selectedMethodId = implementation.edge.status === 'resolved'
|
|
577
|
+
? implementation.edge.to_id : contextMethodId;
|
|
578
|
+
const selectionEvidence = contextMethodId
|
|
579
|
+
? {
|
|
580
|
+
...implEvidence,
|
|
581
|
+
contextualImplementationSelected:
|
|
582
|
+
contextSelection.evidence.strategy !== 'implementation_repo_hint',
|
|
583
|
+
contextualImplementation: contextSelection.evidence,
|
|
584
|
+
implementationSelection: contextSelection.evidence,
|
|
585
|
+
}
|
|
586
|
+
: {
|
|
587
|
+
...implEvidence,
|
|
588
|
+
contextualImplementation: contextSelection.evidence,
|
|
589
|
+
implementationSelection: contextSelection.evidence,
|
|
590
|
+
};
|
|
591
|
+
const selected: SelectedHandlerEvidence = selectedMethodId
|
|
592
|
+
? withSelectedHandlerProvenance(
|
|
593
|
+
selectionEvidence,
|
|
594
|
+
selectedMethodId,
|
|
595
|
+
handlerMethodNode(db, selectedMethodId),
|
|
596
|
+
)
|
|
597
|
+
: { evidence: selectionEvidence };
|
|
598
|
+
selectedHandlerAvailable = !selected.unresolvedReason;
|
|
599
|
+
if (selected.diagnostic) prependTraceDiagnostic(diagnostics, selected.diagnostic);
|
|
600
|
+
const implTo = selected.handler?.label
|
|
601
|
+
? String(selected.handler.label)
|
|
602
|
+
: `${implementation.edge.to_kind}:${implementation.edge.to_id}`;
|
|
603
|
+
if (selected.handler) nodes.set(String(selected.handler.id), selected.handler);
|
|
622
604
|
edges.push({
|
|
623
605
|
step: current.depth,
|
|
624
606
|
type: 'operation_implemented_by_handler',
|
|
625
607
|
from: to,
|
|
626
608
|
to: implTo,
|
|
627
|
-
evidence:
|
|
628
|
-
? { ...implEvidence, contextualImplementationSelected: contextSelection.evidence.strategy !== 'implementation_repo_hint', contextualImplementation: contextSelection.evidence, implementationSelection: contextSelection.evidence }
|
|
629
|
-
: { ...implEvidence, contextualImplementation: contextSelection.evidence, implementationSelection: contextSelection.evidence },
|
|
609
|
+
evidence: selected.evidence,
|
|
630
610
|
confidence: Number(implementation.edge.confidence ?? 0),
|
|
631
|
-
unresolvedReason:
|
|
611
|
+
unresolvedReason: selected.unresolvedReason
|
|
612
|
+
?? (implementation.edge.status === 'resolved' || contextMethodId
|
|
613
|
+
? undefined
|
|
614
|
+
: String(implementation.edge.unresolved_reason ?? implementation.edge.status)),
|
|
632
615
|
});
|
|
633
616
|
}
|
|
634
617
|
if (current.depth >= maxDepth) continue;
|
|
635
618
|
const contextScope = contextMethodId ? handlerScope(db, contextMethodId) : undefined;
|
|
636
619
|
const files = contextScope?.files ?? (implementation.files.size > 0 ? implementation.files : handlerFilesForOperation(db, effectiveRow.to_id));
|
|
637
620
|
const symbolIds = contextScope?.symbolId ? new Set([contextScope.symbolId]) : implementation.symbolId ? new Set([implementation.symbolId]) : undefined;
|
|
638
|
-
if (
|
|
621
|
+
if (selectedHandlerAvailable
|
|
622
|
+
&& (implementation.edge?.status === 'resolved' || contextScope)
|
|
623
|
+
&& files.size > 0) {
|
|
639
624
|
const targetRepoId = contextScope?.repoId ?? implementation.repoId ?? (db
|
|
640
625
|
.prepare(
|
|
641
626
|
'SELECT s.repo_id repoId FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?',
|