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