@saptools/service-flow 0.1.51 → 0.1.53
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 +13 -0
- package/README.md +16 -14
- package/TECHNICAL-NOTE.md +18 -6
- package/dist/{chunk-YZJKE5UX.js → chunk-LFH7C46B.js} +4290 -1261
- package/dist/chunk-LFH7C46B.js.map +1 -0
- package/dist/cli.js +435 -515
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +45 -7
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/000-clean.ts +82 -0
- package/src/cli/001-doctor-projection.ts +140 -0
- package/src/cli/doctor.ts +20 -3
- package/src/cli.ts +75 -57
- package/src/db/connection.ts +1 -1
- package/src/db/migrations.ts +5 -3
- package/src/db/repositories.ts +130 -24
- package/src/db/schema.ts +7 -2
- package/src/indexer/repository-indexer.ts +57 -29
- package/src/indexer/workspace-indexer.ts +84 -6
- package/src/linker/000-implementation-candidates.ts +641 -0
- package/src/linker/001-implementation-evidence-projection.ts +119 -0
- package/src/linker/002-call-evidence.ts +226 -0
- package/src/linker/cross-repo-linker.ts +27 -441
- package/src/linker/dynamic-edge-resolver.ts +35 -0
- package/src/linker/external-http-target.ts +24 -3
- package/src/linker/helper-package-linker.ts +18 -2
- package/src/linker/service-resolver.ts +45 -2
- package/src/output/doctor-output.ts +13 -4
- package/src/output/table-output.ts +33 -5
- package/src/parsers/cds-parser.ts +8 -2
- package/src/parsers/decorator-parser.ts +382 -48
- package/src/parsers/handler-registration-parser.ts +38 -21
- package/src/parsers/imported-wrapper-parser.ts +18 -5
- package/src/parsers/outbound-call-parser.ts +25 -8
- package/src/parsers/package-json-parser.ts +36 -11
- package/src/parsers/service-binding-parser-helpers.ts +8 -1
- package/src/parsers/service-binding-parser.ts +6 -3
- package/src/parsers/symbol-parser.ts +13 -3
- package/src/parsers/ts-project.ts +54 -0
- package/src/trace/000-dynamic-target-types.ts +96 -0
- package/src/trace/001-dynamic-identity.ts +308 -0
- package/src/trace/002-trace-diagnostics.ts +54 -0
- package/src/trace/003-dynamic-references.ts +283 -0
- package/src/trace/004-dynamic-candidate-sources.ts +155 -0
- package/src/trace/005-implementation-selection.ts +187 -0
- package/src/trace/006-contextual-projection.ts +30 -0
- package/src/trace/007-implementation-start-diagnostic.ts +61 -0
- package/src/trace/dynamic-targets.ts +582 -306
- package/src/trace/evidence.ts +331 -46
- package/src/trace/implementation-hints.ts +148 -8
- package/src/trace/selectors.ts +551 -3
- package/src/trace/trace-engine.ts +129 -135
- package/src/types.ts +27 -1
- package/src/utils/000-bounded-projection.ts +161 -0
- package/dist/chunk-YZJKE5UX.js.map +0 -1
package/src/trace/evidence.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { normalizeODataOperationInvocationPath } from '../linker/odata-path-norm
|
|
|
4
4
|
import { resolveOperation, type OperationTarget } from '../linker/service-resolver.js';
|
|
5
5
|
import type { DynamicMode } from '../types.js';
|
|
6
6
|
import { analyzeDynamicTargetCandidates, type DynamicTargetAnalysis, type DynamicTargetCandidate } from './dynamic-targets.js';
|
|
7
|
+
import { boundCandidateLikeEvidence } from '../utils/000-bounded-projection.js';
|
|
7
8
|
|
|
8
9
|
export interface TraceGraphRow extends Record<string, unknown> {
|
|
9
10
|
id: number;
|
|
@@ -24,6 +25,16 @@ interface Candidate {
|
|
|
24
25
|
operationName?: string;
|
|
25
26
|
score?: number;
|
|
26
27
|
}
|
|
28
|
+
interface RuntimeDiagnosticTotals {
|
|
29
|
+
missing: Set<string>;
|
|
30
|
+
candidateCount: number;
|
|
31
|
+
viableCandidateCount: number;
|
|
32
|
+
rejectedCandidateCount: number;
|
|
33
|
+
maxCandidates: number;
|
|
34
|
+
candidateSuggestions: Record<string, unknown>[];
|
|
35
|
+
rejectedCandidates: Record<string, unknown>[];
|
|
36
|
+
suggestedVarSets: Record<string, unknown>[];
|
|
37
|
+
}
|
|
27
38
|
|
|
28
39
|
export function baseTraceEvidence(
|
|
29
40
|
row: TraceGraphRow,
|
|
@@ -38,6 +49,8 @@ export function baseTraceEvidence(
|
|
|
38
49
|
persistedGraphEdgeId: row.id > 0 ? row.id : undefined,
|
|
39
50
|
outboundCallId: call.id,
|
|
40
51
|
callSite: { sourceFile: call.source_file, sourceLine: call.source_line },
|
|
52
|
+
callType: call.call_type,
|
|
53
|
+
repoId: call.repo_id,
|
|
41
54
|
sourceFile: call.source_file,
|
|
42
55
|
sourceLine: call.source_line,
|
|
43
56
|
file: call.source_file,
|
|
@@ -56,44 +69,144 @@ export function runtimeResolution(
|
|
|
56
69
|
workspaceId: number | undefined,
|
|
57
70
|
contextualUnresolvedReason?: string,
|
|
58
71
|
): { row: TraceGraphRow; evidence: Record<string, unknown>; target?: OperationTarget; unresolvedReason?: string } {
|
|
59
|
-
const substituted = evidenceWithRuntimeVariables(evidence, options.vars);
|
|
60
72
|
const dynamicMode = options.dynamicMode ?? 'strict';
|
|
73
|
+
const candidateCap = positiveCandidateCap(options.maxDynamicCandidates);
|
|
74
|
+
const boundedEvidence = boundCandidateLikeEvidence(evidence, candidateCap);
|
|
75
|
+
if (!isDynamicRemoteOperationEdge(row, evidence))
|
|
76
|
+
return unchangedRuntimeResolution(
|
|
77
|
+
row,
|
|
78
|
+
boundDynamicEvidence(boundedEvidence, candidateCap),
|
|
79
|
+
contextualUnresolvedReason,
|
|
80
|
+
);
|
|
81
|
+
const substituted = evidenceWithRuntimeVariables(boundedEvidence, options.vars);
|
|
61
82
|
const analysis = analyzeDynamicTargetCandidates(
|
|
62
|
-
db,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
83
|
+
db, substituted, workspaceId, dynamicMode, candidateCap,
|
|
84
|
+
);
|
|
85
|
+
const enriched = boundDynamicEvidence(
|
|
86
|
+
analysis ? evidenceWithDynamicAnalysis(substituted, analysis) : substituted,
|
|
87
|
+
candidateCap,
|
|
67
88
|
);
|
|
68
|
-
|
|
89
|
+
if (analysis && analysis.viableCandidateCount === 0
|
|
90
|
+
&& Object.keys(analysis.appliedSuppliedVariables).length > 0)
|
|
91
|
+
return noCandidateRuntimeResolution(row, enriched);
|
|
69
92
|
if (dynamicMode === 'infer') {
|
|
70
93
|
const inferred = inferredTarget(analysis);
|
|
71
|
-
if (inferred)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
94
|
+
if (inferred)
|
|
95
|
+
return resolvedRuntimeResolution(row, enriched, inferred, inferred.reasons);
|
|
96
|
+
}
|
|
97
|
+
if (analysis && analysis.missingVariables.length > 0) {
|
|
98
|
+
const unresolvedReason = contextualUnresolvedReason
|
|
99
|
+
?? row.unresolved_reason
|
|
100
|
+
?? 'Dynamic target still requires runtime variables';
|
|
101
|
+
return {
|
|
102
|
+
row,
|
|
103
|
+
evidence: withEffectiveResolution(enriched, row, unresolvedReason),
|
|
104
|
+
unresolvedReason,
|
|
105
|
+
};
|
|
76
106
|
}
|
|
77
|
-
if (!
|
|
107
|
+
if (!hasApplicableRuntimeVariables(evidence, options.vars)) {
|
|
78
108
|
const unresolvedReason = contextualUnresolvedReason ?? row.unresolved_reason;
|
|
79
109
|
const withSections = withEffectiveResolution(enriched, row, unresolvedReason);
|
|
80
110
|
return { row, evidence: withSections, unresolvedReason };
|
|
81
111
|
}
|
|
82
112
|
const resolution = resolveRuntimeOperation(db, enriched, workspaceId);
|
|
83
|
-
if (resolution.target)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
113
|
+
if (resolution.target)
|
|
114
|
+
return resolvedRuntimeResolution(
|
|
115
|
+
row, enriched, resolution.target, resolution.reasons,
|
|
116
|
+
);
|
|
87
117
|
const unresolvedReason = runtimeUnresolvedReason(resolution);
|
|
88
118
|
return { row, evidence: withEffectiveResolution(enriched, row, unresolvedReason, resolution), unresolvedReason };
|
|
89
119
|
}
|
|
90
120
|
|
|
121
|
+
function unchangedRuntimeResolution(
|
|
122
|
+
row: TraceGraphRow,
|
|
123
|
+
evidence: Record<string, unknown>,
|
|
124
|
+
contextualUnresolvedReason: string | undefined,
|
|
125
|
+
): ReturnType<typeof runtimeResolution> {
|
|
126
|
+
const unresolvedReason = contextualUnresolvedReason ?? row.unresolved_reason;
|
|
127
|
+
return {
|
|
128
|
+
row,
|
|
129
|
+
evidence: withEffectiveResolution(evidence, row, unresolvedReason),
|
|
130
|
+
unresolvedReason,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function noCandidateRuntimeResolution(
|
|
135
|
+
row: TraceGraphRow,
|
|
136
|
+
evidence: Record<string, unknown>,
|
|
137
|
+
): ReturnType<typeof runtimeResolution> {
|
|
138
|
+
const unresolvedReason = 'No candidate remained after runtime substitution';
|
|
139
|
+
return {
|
|
140
|
+
row,
|
|
141
|
+
evidence: withEffectiveResolution(evidence, row, unresolvedReason),
|
|
142
|
+
unresolvedReason,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function resolvedRuntimeResolution(
|
|
147
|
+
row: TraceGraphRow,
|
|
148
|
+
evidence: Record<string, unknown>,
|
|
149
|
+
target: OperationTarget,
|
|
150
|
+
reasons: string[],
|
|
151
|
+
): ReturnType<typeof runtimeResolution> {
|
|
152
|
+
const resolvedRow = {
|
|
153
|
+
...row,
|
|
154
|
+
status: 'resolved',
|
|
155
|
+
to_kind: 'operation',
|
|
156
|
+
to_id: String(target.operationId),
|
|
157
|
+
unresolved_reason: undefined,
|
|
158
|
+
confidence: Math.max(0, Math.min(1, target.score)),
|
|
159
|
+
};
|
|
160
|
+
const resolution = {
|
|
161
|
+
status: 'resolved' as const,
|
|
162
|
+
target,
|
|
163
|
+
candidates: [],
|
|
164
|
+
reasons,
|
|
165
|
+
};
|
|
166
|
+
return {
|
|
167
|
+
row: resolvedRow,
|
|
168
|
+
evidence: withEffectiveResolution(evidence, resolvedRow, undefined, resolution),
|
|
169
|
+
target,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
91
173
|
export function runtimeVariableDiagnostic(edges: Array<{ evidence: Record<string, unknown> }>): Record<string, unknown> | undefined {
|
|
174
|
+
const totals = runtimeDiagnosticTotals(edges);
|
|
175
|
+
const missingVariables = [...totals.missing].sort();
|
|
176
|
+
if (missingVariables.length === 0) return undefined;
|
|
177
|
+
const shownSuggestions = totals.candidateSuggestions.slice(0, totals.maxCandidates);
|
|
178
|
+
const shownRejected = totals.rejectedCandidates.slice(0, totals.maxCandidates);
|
|
179
|
+
const shownCandidateCount = shownSuggestions.length;
|
|
180
|
+
return {
|
|
181
|
+
severity: 'warning',
|
|
182
|
+
code: 'trace_runtime_variables_missing',
|
|
183
|
+
message: `Runtime variables are required to resolve dynamic trace targets: ${missingVariables.join(', ')}`,
|
|
184
|
+
missingVariables,
|
|
185
|
+
suggestions: missingVariables.map((key) => `--var ${key}=<value>`),
|
|
186
|
+
candidateCount: totals.candidateCount,
|
|
187
|
+
viableCandidateCount: totals.viableCandidateCount,
|
|
188
|
+
rejectedCandidateCount: totals.rejectedCandidateCount,
|
|
189
|
+
shownCandidateCount,
|
|
190
|
+
omittedCandidateCount: Math.max(0, totals.viableCandidateCount - shownCandidateCount),
|
|
191
|
+
maxDynamicCandidates: totals.maxCandidates,
|
|
192
|
+
shownRejectedCandidateCount: shownRejected.length,
|
|
193
|
+
omittedRejectedCandidateCount: Math.max(0, totals.rejectedCandidateCount - shownRejected.length),
|
|
194
|
+
candidateSuggestions: shownSuggestions,
|
|
195
|
+
rejectedCandidates: shownRejected,
|
|
196
|
+
suggestedVarSets: uniqueCliRows(totals.suggestedVarSets).slice(0, totals.maxCandidates),
|
|
197
|
+
copyableExamples: copyableExamples(totals.suggestedVarSets, totals.candidateCount, totals.maxCandidates),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function runtimeDiagnosticTotals(
|
|
202
|
+
edges: Array<{ evidence: Record<string, unknown> }>): RuntimeDiagnosticTotals {
|
|
92
203
|
const missing = new Set<string>();
|
|
93
204
|
let candidateCount = 0;
|
|
94
|
-
let
|
|
95
|
-
let
|
|
205
|
+
let viableCandidateCount = 0;
|
|
206
|
+
let rejectedCandidateCount = 0;
|
|
207
|
+
let maxCandidates = 5;
|
|
96
208
|
const candidateSuggestions: Record<string, unknown>[] = [];
|
|
209
|
+
const rejectedCandidates: Record<string, unknown>[] = [];
|
|
97
210
|
const suggestedVarSets: Record<string, unknown>[] = [];
|
|
98
211
|
for (const edge of edges) {
|
|
99
212
|
const effective = parseObject(edge.evidence.effectiveResolution);
|
|
@@ -104,33 +217,79 @@ export function runtimeVariableDiagnostic(edges: Array<{ evidence: Record<string
|
|
|
104
217
|
for (const value of Object.values(substitutions as Record<string, RuntimeSubstitution>))
|
|
105
218
|
for (const key of value.missing ?? []) missing.add(key);
|
|
106
219
|
const exploration = parseObject(edge.evidence.dynamicTargetExploration);
|
|
220
|
+
maxCandidates = positiveCandidateCap(numeric(exploration.maxCandidates) || maxCandidates);
|
|
107
221
|
candidateCount += numeric(exploration.candidateCount);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
222
|
+
viableCandidateCount += numeric(exploration.viableCandidateCount);
|
|
223
|
+
rejectedCandidateCount += numeric(exploration.rejectedCandidateCount);
|
|
224
|
+
appendBounded(
|
|
225
|
+
candidateSuggestions,
|
|
226
|
+
recordArray(edge.evidence.dynamicTargetCandidateSuggestions),
|
|
227
|
+
maxCandidates,
|
|
228
|
+
);
|
|
229
|
+
appendBounded(
|
|
230
|
+
rejectedCandidates,
|
|
231
|
+
recordArray(exploration.rejectedCandidates),
|
|
232
|
+
maxCandidates,
|
|
233
|
+
);
|
|
234
|
+
appendBounded(
|
|
235
|
+
suggestedVarSets,
|
|
236
|
+
recordArray(exploration.suggestedVarSets),
|
|
237
|
+
maxCandidates,
|
|
238
|
+
);
|
|
112
239
|
}
|
|
113
|
-
const missingVariables = [...missing].sort();
|
|
114
|
-
if (missingVariables.length === 0) return undefined;
|
|
115
240
|
return {
|
|
116
|
-
|
|
117
|
-
code: 'trace_runtime_variables_missing',
|
|
118
|
-
message: `Runtime variables are required to resolve dynamic trace targets: ${missingVariables.join(', ')}`,
|
|
119
|
-
missingVariables,
|
|
120
|
-
suggestions: missingVariables.map((key) => `--var ${key}=<value>`),
|
|
241
|
+
missing,
|
|
121
242
|
candidateCount,
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
typeof row.cli === 'string' ? [row.cli] : []),
|
|
129
|
-
...(candidateCount > 0 ? ['--dynamic-mode candidates --max-dynamic-candidates 20'] : []),
|
|
130
|
-
],
|
|
243
|
+
viableCandidateCount,
|
|
244
|
+
rejectedCandidateCount,
|
|
245
|
+
maxCandidates,
|
|
246
|
+
candidateSuggestions,
|
|
247
|
+
rejectedCandidates,
|
|
248
|
+
suggestedVarSets,
|
|
131
249
|
};
|
|
132
250
|
}
|
|
133
251
|
|
|
252
|
+
export function runtimeNoCandidateDiagnostics(
|
|
253
|
+
edges: Array<{ evidence: Record<string, unknown> }>,
|
|
254
|
+
): Array<Record<string, unknown>> {
|
|
255
|
+
const seen = new Set<string>();
|
|
256
|
+
return edges.flatMap((edge) => {
|
|
257
|
+
const exploration = parseObject(edge.evidence.dynamicTargetExploration);
|
|
258
|
+
const suppliedVariables = parseObject(exploration.suppliedVariables);
|
|
259
|
+
const appliedSuppliedVariables = parseObject(
|
|
260
|
+
exploration.appliedSuppliedVariables,
|
|
261
|
+
);
|
|
262
|
+
if (numeric(exploration.viableCandidateCount) !== 0
|
|
263
|
+
|| Object.keys(appliedSuppliedVariables).length === 0) return [];
|
|
264
|
+
const callSite = parseObject(edge.evidence.callSite);
|
|
265
|
+
const key = JSON.stringify([callSite, suppliedVariables]);
|
|
266
|
+
if (seen.has(key)) return [];
|
|
267
|
+
seen.add(key);
|
|
268
|
+
const maxCandidates = positiveCandidateCap(numeric(exploration.maxCandidates));
|
|
269
|
+
return [{
|
|
270
|
+
severity: 'warning',
|
|
271
|
+
code: 'no_candidate_after_runtime_substitution',
|
|
272
|
+
message: 'No dynamic target candidate remained after applying runtime variables',
|
|
273
|
+
suppliedVariables,
|
|
274
|
+
appliedSuppliedVariables,
|
|
275
|
+
substitutedSignals: parseObject(exploration.substitutedSignals),
|
|
276
|
+
candidateCount: numeric(exploration.candidateCount),
|
|
277
|
+
viableCandidateCount: 0,
|
|
278
|
+
rejectedCandidateCount: numeric(exploration.rejectedCandidateCount),
|
|
279
|
+
shownCandidateCount: numeric(exploration.shownCandidateCount),
|
|
280
|
+
omittedCandidateCount: numeric(exploration.omittedCandidateCount),
|
|
281
|
+
shownRejectedCandidateCount: numeric(
|
|
282
|
+
exploration.shownRejectedCandidateCount,
|
|
283
|
+
),
|
|
284
|
+
omittedRejectedCandidateCount: numeric(
|
|
285
|
+
exploration.omittedRejectedCandidateCount,
|
|
286
|
+
),
|
|
287
|
+
rejectedCandidates: recordArray(exploration.rejectedCandidates).slice(0, maxCandidates),
|
|
288
|
+
callSite,
|
|
289
|
+
}];
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
134
293
|
export function edgeTarget(row: TraceGraphRow, evidence: Record<string, unknown>): string {
|
|
135
294
|
const effective = parseObject(evidence.effectiveResolution);
|
|
136
295
|
const targetServicePath = stringValue(effective.targetServicePath ?? evidence.targetServicePath);
|
|
@@ -212,11 +371,18 @@ function resolveRuntimeOperation(db: Db, evidence: Record<string, unknown>, work
|
|
|
212
371
|
|
|
213
372
|
function evidenceWithRuntimeVariables(evidence: Record<string, unknown>, vars: Record<string, string> | undefined): Record<string, unknown> {
|
|
214
373
|
const substitutions = runtimeSubstitutions(evidence, vars ?? {});
|
|
215
|
-
const
|
|
374
|
+
const suppliedRuntimeVariables = Object.fromEntries(
|
|
375
|
+
Object.entries(vars ?? {}).sort(([left], [right]) => left.localeCompare(right)),
|
|
376
|
+
);
|
|
377
|
+
const next: Record<string, unknown> = {
|
|
378
|
+
...evidence,
|
|
379
|
+
runtimeSubstitutions: substitutions,
|
|
380
|
+
suppliedRuntimeVariables,
|
|
381
|
+
};
|
|
216
382
|
for (const [key, value] of Object.entries(substitutions)) if (value.effective) next[key] = value.effective;
|
|
217
383
|
const missing = Object.values(substitutions).flatMap((value) => value.missing);
|
|
218
384
|
if (missing.length > 0) next.missingRuntimeVariables = [...new Set(missing)].sort();
|
|
219
|
-
if (Object.keys(
|
|
385
|
+
if (Object.keys(suppliedRuntimeVariables).length > 0) next.runtimeVariablesApplied = true;
|
|
220
386
|
return next;
|
|
221
387
|
}
|
|
222
388
|
|
|
@@ -224,22 +390,111 @@ function evidenceWithDynamicAnalysis(
|
|
|
224
390
|
evidence: Record<string, unknown>,
|
|
225
391
|
analysis: DynamicTargetAnalysis,
|
|
226
392
|
): Record<string, unknown> {
|
|
393
|
+
const persistedCandidates = recordArray(evidence.candidates);
|
|
394
|
+
const persistedScores = recordArray(evidence.candidateScores);
|
|
395
|
+
const persistedCandidateCount = numeric(evidence.persistedCandidateCount)
|
|
396
|
+
|| numeric(evidence.candidateCount)
|
|
397
|
+
|| persistedCandidates.length;
|
|
398
|
+
const persistedScoreCount = numeric(evidence.candidateScoreCount)
|
|
399
|
+
|| persistedScores.length;
|
|
227
400
|
return {
|
|
228
401
|
...evidence,
|
|
402
|
+
candidates: persistedCandidates.slice(0, analysis.maxCandidates),
|
|
403
|
+
candidateScores: persistedScores.slice(0, analysis.maxCandidates),
|
|
404
|
+
persistedCandidateCount,
|
|
405
|
+
persistedCandidateOmittedCount: Math.max(
|
|
406
|
+
0,
|
|
407
|
+
persistedCandidateCount - Math.min(persistedCandidates.length, analysis.maxCandidates),
|
|
408
|
+
),
|
|
409
|
+
persistedCandidateScoreCount: persistedScoreCount,
|
|
410
|
+
persistedCandidateScoreOmittedCount: Math.max(
|
|
411
|
+
0, persistedScoreCount - Math.min(persistedScores.length, analysis.maxCandidates),
|
|
412
|
+
),
|
|
229
413
|
dynamicTargetExploration: {
|
|
230
414
|
mode: analysis.mode,
|
|
415
|
+
maxCandidates: analysis.maxCandidates,
|
|
231
416
|
missingVariables: analysis.missingVariables,
|
|
417
|
+
requiredVariables: analysis.requiredVariables,
|
|
418
|
+
suppliedVariables: analysis.suppliedVariables,
|
|
419
|
+
appliedSuppliedVariables: analysis.appliedSuppliedVariables,
|
|
420
|
+
substitutedSignals: analysis.substitutedSignals,
|
|
232
421
|
candidateCount: analysis.candidateCount,
|
|
422
|
+
viableCandidateCount: analysis.viableCandidateCount,
|
|
423
|
+
rejectedCandidateCount: analysis.rejectedCandidateCount,
|
|
233
424
|
shownCandidateCount: analysis.shownCandidateCount,
|
|
234
425
|
omittedCandidateCount: analysis.omittedCandidateCount,
|
|
426
|
+
shownRejectedCandidateCount: analysis.shownRejectedCandidateCount,
|
|
427
|
+
omittedRejectedCandidateCount: analysis.omittedRejectedCandidateCount,
|
|
428
|
+
rejectedCandidates: analysis.rejectedCandidates,
|
|
235
429
|
suggestedVarSets: analysis.suggestedVarSets,
|
|
430
|
+
suggestedVarSetCount: analysis.suggestedVarSetCount,
|
|
431
|
+
shownSuggestedVarSetCount: analysis.shownSuggestedVarSetCount,
|
|
432
|
+
omittedSuggestedVarSetCount: analysis.omittedSuggestedVarSetCount,
|
|
433
|
+
routingContext: analysis.routingContext,
|
|
236
434
|
},
|
|
237
435
|
dynamicTargetCandidates: analysis.candidates,
|
|
238
436
|
dynamicTargetCandidateSuggestions: analysis.shownCandidates,
|
|
437
|
+
dynamicTargetCandidateSuggestionCount: analysis.viableCandidateCount,
|
|
438
|
+
shownDynamicTargetCandidateSuggestionCount: analysis.shownCandidateCount,
|
|
439
|
+
omittedDynamicTargetCandidateSuggestionCount: analysis.omittedCandidateCount,
|
|
440
|
+
rejectedCandidates: analysis.rejectedCandidates,
|
|
239
441
|
dynamicTargetInference: analysis.inference,
|
|
240
442
|
};
|
|
241
443
|
}
|
|
242
444
|
|
|
445
|
+
const boundedDynamicListKeys = new Set([
|
|
446
|
+
'candidates',
|
|
447
|
+
'candidateScores',
|
|
448
|
+
'dynamicTargetCandidates',
|
|
449
|
+
'dynamicTargetCandidateSuggestions',
|
|
450
|
+
'candidateSuggestions',
|
|
451
|
+
'suggestedVarSets',
|
|
452
|
+
'rejectedCandidates',
|
|
453
|
+
'rejectedCandidateSuggestions',
|
|
454
|
+
'copyableExamples',
|
|
455
|
+
'conflicts',
|
|
456
|
+
'bindingAlternatives',
|
|
457
|
+
]);
|
|
458
|
+
|
|
459
|
+
function boundDynamicEvidence(
|
|
460
|
+
evidence: Record<string, unknown>,
|
|
461
|
+
limit: number,
|
|
462
|
+
): Record<string, unknown> {
|
|
463
|
+
const candidateCount = numeric(evidence.persistedCandidateCount)
|
|
464
|
+
|| numeric(evidence.candidateCount)
|
|
465
|
+
|| (Array.isArray(evidence.candidates) ? evidence.candidates.length : 0);
|
|
466
|
+
const projected = boundDynamicValue(evidence, limit);
|
|
467
|
+
const next = parseObject(projected);
|
|
468
|
+
if (candidateCount === 0) return next;
|
|
469
|
+
return {
|
|
470
|
+
...next,
|
|
471
|
+
persistedCandidateCount: candidateCount,
|
|
472
|
+
persistedCandidateOmittedCount: Math.max(0, candidateCount - limit),
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function boundDynamicValue(
|
|
477
|
+
value: unknown,
|
|
478
|
+
limit: number,
|
|
479
|
+
key?: string,
|
|
480
|
+
parentKey?: string,
|
|
481
|
+
): unknown {
|
|
482
|
+
if (Array.isArray(value)) {
|
|
483
|
+
const bounded = Boolean(key && (boundedDynamicListKeys.has(key)
|
|
484
|
+
|| parentKey === 'derivationProvenance'
|
|
485
|
+
|| parentKey === 'conflicts' && key === 'sources'));
|
|
486
|
+
const input = bounded ? value.slice(0, limit) : value;
|
|
487
|
+
const projected = input.map((item) =>
|
|
488
|
+
boundDynamicValue(item, limit, undefined, key ?? parentKey));
|
|
489
|
+
return projected;
|
|
490
|
+
}
|
|
491
|
+
if (!value || typeof value !== 'object') return value;
|
|
492
|
+
return Object.fromEntries(Object.entries(value).map(([childKey, child]) => [
|
|
493
|
+
childKey,
|
|
494
|
+
boundDynamicValue(child, limit, childKey, key ?? parentKey),
|
|
495
|
+
]));
|
|
496
|
+
}
|
|
497
|
+
|
|
243
498
|
function runtimeSubstitutions(evidence: Record<string, unknown>, vars: Record<string, string>): Record<string, RuntimeSubstitution> {
|
|
244
499
|
const substitutions: Record<string, RuntimeSubstitution> = {};
|
|
245
500
|
for (const key of ['servicePath', 'operationPath', 'serviceAliasExpr', 'serviceAlias', 'destination']) {
|
|
@@ -259,10 +514,21 @@ function substitutionValue(
|
|
|
259
514
|
return normalized?.wasInvocation ? normalized.normalizedOperationPath : value;
|
|
260
515
|
}
|
|
261
516
|
|
|
262
|
-
function
|
|
263
|
-
|
|
517
|
+
function isDynamicRemoteOperationEdge(
|
|
518
|
+
row: TraceGraphRow,
|
|
519
|
+
evidence: Record<string, unknown>,
|
|
520
|
+
): boolean {
|
|
521
|
+
if (evidence.callType !== 'remote_action') return false;
|
|
264
522
|
if (!['dynamic', 'ambiguous', 'unresolved'].includes(String(row.status ?? ''))) return false;
|
|
265
|
-
|
|
523
|
+
return ['DYNAMIC_EDGE_CANDIDATE', 'UNRESOLVED_EDGE', 'REMOTE_CALL_RESOLVES_TO_OPERATION']
|
|
524
|
+
.includes(row.edge_type);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function hasApplicableRuntimeVariables(
|
|
528
|
+
evidence: Record<string, unknown>,
|
|
529
|
+
vars: Record<string, string> | undefined,
|
|
530
|
+
): boolean {
|
|
531
|
+
if (!vars || Object.keys(vars).length === 0) return false;
|
|
266
532
|
return ['servicePath', 'operationPath', 'serviceAliasExpr', 'serviceAlias', 'destination'].some((key) => hasRuntimeVariable(evidence[key], vars));
|
|
267
533
|
}
|
|
268
534
|
|
|
@@ -283,11 +549,12 @@ function targetFromCandidate(candidate: DynamicTargetCandidate): OperationTarget
|
|
|
283
549
|
servicePath: candidate.servicePath,
|
|
284
550
|
operationPath: candidate.operationPath,
|
|
285
551
|
operationName: candidate.operationName,
|
|
552
|
+
repoId: candidate.repoId,
|
|
286
553
|
packageName: candidate.packageName,
|
|
287
554
|
score: candidate.score,
|
|
288
555
|
reasons: candidate.reasons,
|
|
289
|
-
sourceFile:
|
|
290
|
-
sourceLine:
|
|
556
|
+
sourceFile: candidate.sourceFile,
|
|
557
|
+
sourceLine: candidate.sourceLine,
|
|
291
558
|
};
|
|
292
559
|
}
|
|
293
560
|
|
|
@@ -320,6 +587,11 @@ function recordArray(value: unknown): Record<string, unknown>[] {
|
|
|
320
587
|
: [];
|
|
321
588
|
}
|
|
322
589
|
|
|
590
|
+
function appendBounded<T>(target: T[], values: T[], limit: number): void {
|
|
591
|
+
const remaining = Math.max(0, limit - target.length);
|
|
592
|
+
if (remaining > 0) target.push(...values.slice(0, remaining));
|
|
593
|
+
}
|
|
594
|
+
|
|
323
595
|
function uniqueCliRows(rows: Record<string, unknown>[]): Record<string, unknown>[] {
|
|
324
596
|
const seen = new Set<string>();
|
|
325
597
|
return rows.filter((row) => {
|
|
@@ -330,6 +602,19 @@ function uniqueCliRows(rows: Record<string, unknown>[]): Record<string, unknown>
|
|
|
330
602
|
});
|
|
331
603
|
}
|
|
332
604
|
|
|
605
|
+
function copyableExamples(
|
|
606
|
+
suggestedVarSets: Record<string, unknown>[],
|
|
607
|
+
candidateCount: number,
|
|
608
|
+
limit: number,
|
|
609
|
+
): string[] {
|
|
610
|
+
const variableExamples = uniqueCliRows(suggestedVarSets).flatMap((row) =>
|
|
611
|
+
typeof row.cli === 'string' ? [row.cli] : []);
|
|
612
|
+
const exploration = candidateCount > 0
|
|
613
|
+
? ['--dynamic-mode candidates --max-dynamic-candidates 20']
|
|
614
|
+
: [];
|
|
615
|
+
return [...variableExamples, ...exploration].slice(0, limit);
|
|
616
|
+
}
|
|
617
|
+
|
|
333
618
|
function positiveCandidateCap(value: number | undefined): number {
|
|
334
619
|
return Number.isFinite(value) && Number(value) > 0 ? Math.floor(Number(value)) : 5;
|
|
335
620
|
}
|