@saptools/service-flow 0.1.52 → 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 +6 -0
- package/README.md +9 -12
- package/TECHNICAL-NOTE.md +11 -3
- package/dist/{chunk-PTLDSHRC.js → chunk-LFH7C46B.js} +2214 -1044
- package/dist/chunk-LFH7C46B.js.map +1 -0
- package/dist/cli.js +157 -11
- 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 +140 -0
- package/src/cli/doctor.ts +20 -3
- package/src/db/repositories.ts +10 -2
- 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 -453
- 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 +4 -2
- package/src/trace/000-dynamic-target-types.ts +12 -0
- package/src/trace/001-dynamic-identity.ts +45 -17
- package/src/trace/003-dynamic-references.ts +236 -35
- 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 +205 -159
- package/src/trace/evidence.ts +35 -9
- package/src/trace/implementation-hints.ts +148 -8
- package/src/trace/selectors.ts +74 -9
- package/src/trace/trace-engine.ts +39 -52
- package/src/utils/000-bounded-projection.ts +161 -0
- package/dist/chunk-PTLDSHRC.js.map +0 -1
|
@@ -1,5 +1,129 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// src/utils/000-bounded-projection.ts
|
|
4
|
+
var DEFAULT_EVIDENCE_CANDIDATE_LIMIT = 5;
|
|
5
|
+
function projectBounded(values, compare, limit = DEFAULT_EVIDENCE_CANDIDATE_LIMIT) {
|
|
6
|
+
const normalizedLimit = positiveLimit(limit);
|
|
7
|
+
const sorted = [...values].sort(compare);
|
|
8
|
+
const items = sorted.slice(0, normalizedLimit);
|
|
9
|
+
return {
|
|
10
|
+
totalCount: sorted.length,
|
|
11
|
+
shownCount: items.length,
|
|
12
|
+
omittedCount: Math.max(0, sorted.length - items.length),
|
|
13
|
+
items
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function positiveLimit(value) {
|
|
17
|
+
return Number.isFinite(value) && Number(value) > 0 ? Math.floor(Number(value)) : DEFAULT_EVIDENCE_CANDIDATE_LIMIT;
|
|
18
|
+
}
|
|
19
|
+
var candidateLikeCollections = /* @__PURE__ */ new Set([
|
|
20
|
+
"candidates",
|
|
21
|
+
"candidateScores",
|
|
22
|
+
"candidateFamilies",
|
|
23
|
+
"candidateEvidence",
|
|
24
|
+
"candidatePaths",
|
|
25
|
+
"candidateRawPaths",
|
|
26
|
+
"candidateNormalizedOperationPaths",
|
|
27
|
+
"normalizedCandidateOperations",
|
|
28
|
+
"candidateLiterals",
|
|
29
|
+
"bindingCandidates",
|
|
30
|
+
"bindingAlternatives",
|
|
31
|
+
"implementationHintSuggestions",
|
|
32
|
+
"selectableImplementationRepositories",
|
|
33
|
+
"matchedHints",
|
|
34
|
+
"candidateSuggestions",
|
|
35
|
+
"dynamicTargetCandidates",
|
|
36
|
+
"dynamicTargetCandidateSuggestions",
|
|
37
|
+
"rejectedCandidates",
|
|
38
|
+
"suggestedVarSets",
|
|
39
|
+
"copyableExamples",
|
|
40
|
+
"selectorSuggestions",
|
|
41
|
+
"serviceSuggestions",
|
|
42
|
+
"repositories",
|
|
43
|
+
"examples",
|
|
44
|
+
"expandedExamples",
|
|
45
|
+
"registrations"
|
|
46
|
+
]);
|
|
47
|
+
function boundCandidateLikeEvidence(evidence, limit = DEFAULT_EVIDENCE_CANDIDATE_LIMIT) {
|
|
48
|
+
const output = {};
|
|
49
|
+
for (const [key, value] of Object.entries(evidence)) {
|
|
50
|
+
if (!Array.isArray(value) || !candidateLikeCollections.has(key)) {
|
|
51
|
+
output[key] = boundNestedEvidence(value, limit);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const projection = projectBounded(value, compareEvidenceValue, limit);
|
|
55
|
+
output[key] = projection.items.map((item) => boundNestedEvidence(item, limit));
|
|
56
|
+
addCollectionCounts(output, evidence, key, projection);
|
|
57
|
+
}
|
|
58
|
+
return output;
|
|
59
|
+
}
|
|
60
|
+
function boundNestedEvidence(value, limit) {
|
|
61
|
+
if (Array.isArray(value)) return value.map((item) => boundNestedEvidence(item, limit));
|
|
62
|
+
if (!isEvidenceRecord(value)) return value;
|
|
63
|
+
return boundCandidateLikeEvidence(value, limit);
|
|
64
|
+
}
|
|
65
|
+
function isEvidenceRecord(value) {
|
|
66
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
67
|
+
}
|
|
68
|
+
function addCollectionCounts(output, input, key, projection) {
|
|
69
|
+
const stem = collectionStem(key);
|
|
70
|
+
const countName = `${stem}Count`;
|
|
71
|
+
const shownName = `shown${upperFirst(stem)}Count`;
|
|
72
|
+
const omittedName = `omitted${upperFirst(stem)}Count`;
|
|
73
|
+
const total = Math.max(numericValue(input[countName]), projection.totalCount);
|
|
74
|
+
output[countName] = total;
|
|
75
|
+
output[shownName] = projection.shownCount;
|
|
76
|
+
output[omittedName] = Math.max(0, total - projection.shownCount);
|
|
77
|
+
}
|
|
78
|
+
function collectionStem(key) {
|
|
79
|
+
const stems = {
|
|
80
|
+
candidates: "candidate",
|
|
81
|
+
candidateScores: "candidateScore",
|
|
82
|
+
candidateFamilies: "candidateFamily",
|
|
83
|
+
candidateEvidence: "candidateEvidence",
|
|
84
|
+
candidatePaths: "candidatePath",
|
|
85
|
+
candidateRawPaths: "candidateRawPath",
|
|
86
|
+
candidateNormalizedOperationPaths: "candidateNormalizedOperationPath",
|
|
87
|
+
normalizedCandidateOperations: "normalizedCandidateOperation",
|
|
88
|
+
candidateLiterals: "candidateLiteral",
|
|
89
|
+
bindingCandidates: "bindingCandidate",
|
|
90
|
+
bindingAlternatives: "bindingAlternative",
|
|
91
|
+
implementationHintSuggestions: "implementationHintSuggestion",
|
|
92
|
+
selectableImplementationRepositories: "selectableImplementationRepository",
|
|
93
|
+
matchedHints: "matchedHint",
|
|
94
|
+
candidateSuggestions: "candidateSuggestion",
|
|
95
|
+
dynamicTargetCandidates: "dynamicTargetCandidate",
|
|
96
|
+
dynamicTargetCandidateSuggestions: "dynamicTargetCandidateSuggestion",
|
|
97
|
+
rejectedCandidates: "rejectedCandidate",
|
|
98
|
+
suggestedVarSets: "suggestedVarSet",
|
|
99
|
+
copyableExamples: "copyableExample",
|
|
100
|
+
selectorSuggestions: "selectorSuggestion",
|
|
101
|
+
serviceSuggestions: "serviceSuggestion",
|
|
102
|
+
repositories: "repository",
|
|
103
|
+
examples: "example",
|
|
104
|
+
expandedExamples: "expandedExample",
|
|
105
|
+
registrations: "registration"
|
|
106
|
+
};
|
|
107
|
+
return stems[key] ?? "candidate";
|
|
108
|
+
}
|
|
109
|
+
function compareEvidenceValue(left, right) {
|
|
110
|
+
return stableProjectionValue(left).localeCompare(stableProjectionValue(right));
|
|
111
|
+
}
|
|
112
|
+
function stableProjectionValue(value) {
|
|
113
|
+
if (Array.isArray(value))
|
|
114
|
+
return `[${value.map(stableProjectionValue).join(",")}]`;
|
|
115
|
+
if (isEvidenceRecord(value)) {
|
|
116
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableProjectionValue(value[key])}`).join(",")}}`;
|
|
117
|
+
}
|
|
118
|
+
return JSON.stringify(value) ?? "";
|
|
119
|
+
}
|
|
120
|
+
function numericValue(value) {
|
|
121
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
122
|
+
}
|
|
123
|
+
function upperFirst(value) {
|
|
124
|
+
return value ? `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}` : value;
|
|
125
|
+
}
|
|
126
|
+
|
|
3
127
|
// src/db/repositories.ts
|
|
4
128
|
function upsertWorkspace(db, rootPath, dbPath) {
|
|
5
129
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -337,8 +461,8 @@ function insertCalls(db, repoId, rows2) {
|
|
|
337
461
|
function resolvePersistedBinding(db, repoId, call) {
|
|
338
462
|
if (!call.serviceVariableName)
|
|
339
463
|
return { bindingId: null, evidence: { status: "not_applicable", candidateCount: 0 } };
|
|
340
|
-
const
|
|
341
|
-
const prior =
|
|
464
|
+
const candidates2 = bindingCandidates(db, repoId, call);
|
|
465
|
+
const prior = candidates2.filter((candidate) => candidate.sourceLine <= call.sourceLine);
|
|
342
466
|
const families = new Set(prior.map(bindingSignature));
|
|
343
467
|
if (prior.length > 0 && families.size === 1) {
|
|
344
468
|
const selected = prior.at(-1);
|
|
@@ -354,11 +478,11 @@ function resolvePersistedBinding(db, repoId, call) {
|
|
|
354
478
|
evidence: bindingEvidence("ambiguous", prior)
|
|
355
479
|
};
|
|
356
480
|
}
|
|
357
|
-
if (
|
|
481
|
+
if (candidates2.length > 0) {
|
|
358
482
|
return {
|
|
359
483
|
bindingId: null,
|
|
360
484
|
unresolvedReason: "service_binding_declared_after_call",
|
|
361
|
-
evidence: bindingEvidence("rejected_future_binding",
|
|
485
|
+
evidence: bindingEvidence("rejected_future_binding", candidates2)
|
|
362
486
|
};
|
|
363
487
|
}
|
|
364
488
|
return {
|
|
@@ -426,13 +550,16 @@ function callSymbolId(db, repoId, call) {
|
|
|
426
550
|
);
|
|
427
551
|
return typeof row?.id === "number" ? row.id : void 0;
|
|
428
552
|
}
|
|
429
|
-
function bindingEvidence(status,
|
|
553
|
+
function bindingEvidence(status, candidates2, selected) {
|
|
554
|
+
const projection = projectBounded(candidates2, (left, right) => Number(right.id === selected?.id) - Number(left.id === selected?.id) || left.sourceFile.localeCompare(right.sourceFile) || left.sourceLine - right.sourceLine || left.id - right.id);
|
|
430
555
|
return {
|
|
431
556
|
status,
|
|
432
|
-
candidateCount:
|
|
557
|
+
candidateCount: projection.totalCount,
|
|
558
|
+
shownCandidateCount: projection.shownCount,
|
|
559
|
+
omittedCandidateCount: projection.omittedCount,
|
|
433
560
|
selectedBindingId: selected?.id,
|
|
434
561
|
sourceOrderRule: "binding_source_line_must_not_follow_call",
|
|
435
|
-
candidates:
|
|
562
|
+
candidates: projection.items.map((candidate) => ({
|
|
436
563
|
bindingId: candidate.id,
|
|
437
564
|
symbolId: candidate.symbolId,
|
|
438
565
|
variableName: candidate.variableName,
|
|
@@ -2208,8 +2335,20 @@ function externalHttpTarget(call) {
|
|
|
2208
2335
|
const expression = typeof target.expression === "string" ? target.expression : void 0;
|
|
2209
2336
|
if (kind === "destination" && target.dynamic === true) {
|
|
2210
2337
|
const shape = typeof target.expressionShape === "string" ? target.expressionShape : "expression";
|
|
2211
|
-
const
|
|
2212
|
-
|
|
2338
|
+
const candidates2 = staticDestinationCandidates(target.candidateLiterals);
|
|
2339
|
+
const projection = projectBounded(candidates2, (left, right) => left.localeCompare(right));
|
|
2340
|
+
return {
|
|
2341
|
+
kind,
|
|
2342
|
+
toKind: "external_destination",
|
|
2343
|
+
toId: `destination:dynamic:${hash(`${shape}:${candidates2.join("|")}`)}`,
|
|
2344
|
+
label: "External destination: dynamic destination",
|
|
2345
|
+
method,
|
|
2346
|
+
dynamic: true,
|
|
2347
|
+
expression: projection.items.length ? `candidates:${projection.items.join("|")}` : `shape:${shape}`,
|
|
2348
|
+
candidateLiteralCount: projection.totalCount,
|
|
2349
|
+
shownCandidateLiteralCount: projection.shownCount,
|
|
2350
|
+
omittedCandidateLiteralCount: projection.omittedCount
|
|
2351
|
+
};
|
|
2213
2352
|
}
|
|
2214
2353
|
if (kind === "destination" && expression) return { kind, toKind: "external_destination", toId: `destination:${expression}`, label: `External destination: ${expression}`, method, dynamic: false, expression };
|
|
2215
2354
|
if (kind === "static_url" && expression) {
|
|
@@ -2219,6 +2358,10 @@ function externalHttpTarget(call) {
|
|
|
2219
2358
|
if (kind === "url_expression" && expression) return { kind, toKind: "external_endpoint", toId: `dynamic:${hash(expression)}`, label: `External endpoint: ${methodPrefix(method)}dynamic URL`, method, dynamic: true, expression: `expr:${hash(expression)}` };
|
|
2220
2359
|
return { kind: "unknown", toKind: "external_endpoint", toId: "unknown", label: "External endpoint: unknown", method, dynamic: false };
|
|
2221
2360
|
}
|
|
2361
|
+
function staticDestinationCandidates(value) {
|
|
2362
|
+
const candidates2 = Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
2363
|
+
return [...new Set(candidates2)].sort();
|
|
2364
|
+
}
|
|
2222
2365
|
function safeParse(value) {
|
|
2223
2366
|
try {
|
|
2224
2367
|
const parsed = JSON.parse(value);
|
|
@@ -3115,8 +3258,8 @@ function destinationTargetFromExpression(expr, use) {
|
|
|
3115
3258
|
const resolved3 = resolveExpression(expr, use, "external");
|
|
3116
3259
|
const text = resolved3.value;
|
|
3117
3260
|
if (resolved3.status === "static" && text && !hasTemplatePlaceholder(text)) return { kind: "destination", expression: text, dynamic: false, sourceKind: resolved3.sourceKind };
|
|
3118
|
-
const
|
|
3119
|
-
if (
|
|
3261
|
+
const candidates2 = staticConditionalCandidates(expr, /* @__PURE__ */ new Map());
|
|
3262
|
+
if (candidates2) return { kind: "destination", dynamic: true, expressionShape: "conditional", candidateLiterals: candidates2 };
|
|
3120
3263
|
const shape = destinationExpressionShape(expr);
|
|
3121
3264
|
if (shape) return { kind: "destination", dynamic: true, expressionShape: shape };
|
|
3122
3265
|
return void 0;
|
|
@@ -3448,6 +3591,22 @@ function applyVariables(template, vars) {
|
|
|
3448
3591
|
function extractPlaceholders(template) {
|
|
3449
3592
|
return [...(template ?? "").matchAll(PLACEHOLDER)].map((m) => (m[1] ?? "").trim()).filter(Boolean);
|
|
3450
3593
|
}
|
|
3594
|
+
function matchRuntimeTemplate(template, concrete) {
|
|
3595
|
+
if (!template || !concrete) return void 0;
|
|
3596
|
+
const keys = extractPlaceholders(template);
|
|
3597
|
+
if (keys.length === 0) return template === concrete ? {} : void 0;
|
|
3598
|
+
const match = new RegExp(`^${runtimeTemplatePattern(template)}$`).exec(concrete);
|
|
3599
|
+
if (!match) return void 0;
|
|
3600
|
+
const values = {};
|
|
3601
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
3602
|
+
const key = keys[index];
|
|
3603
|
+
const value = match[index + 1];
|
|
3604
|
+
if (!key || value === void 0) return void 0;
|
|
3605
|
+
if (values[key] !== void 0 && values[key] !== value) return void 0;
|
|
3606
|
+
values[key] = value;
|
|
3607
|
+
}
|
|
3608
|
+
return values;
|
|
3609
|
+
}
|
|
3451
3610
|
function substituteVariables(template, vars) {
|
|
3452
3611
|
if (!template) return { placeholders: [], missing: [], supplied: [], changed: false };
|
|
3453
3612
|
const placeholders3 = [...new Set(extractPlaceholders(template))];
|
|
@@ -3466,6 +3625,19 @@ function substituteVariables(template, vars) {
|
|
|
3466
3625
|
changed: effective !== template
|
|
3467
3626
|
};
|
|
3468
3627
|
}
|
|
3628
|
+
function runtimeTemplatePattern(template) {
|
|
3629
|
+
let pattern = "";
|
|
3630
|
+
let lastIndex = 0;
|
|
3631
|
+
for (const match of template.matchAll(PLACEHOLDER)) {
|
|
3632
|
+
pattern += escapeRegex(template.slice(lastIndex, match.index));
|
|
3633
|
+
pattern += "([^/]+?)";
|
|
3634
|
+
lastIndex = (match.index ?? 0) + match[0].length;
|
|
3635
|
+
}
|
|
3636
|
+
return `${pattern}${escapeRegex(template.slice(lastIndex))}`;
|
|
3637
|
+
}
|
|
3638
|
+
function escapeRegex(value) {
|
|
3639
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3640
|
+
}
|
|
3469
3641
|
|
|
3470
3642
|
// src/trace/implementation-hints.ts
|
|
3471
3643
|
function parseImplementationHint(value) {
|
|
@@ -3478,8 +3650,8 @@ function parseImplementationHint(value) {
|
|
|
3478
3650
|
if (!hint.implementationRepo) throw new Error("Scoped implementation hint requires an implementation repo selection");
|
|
3479
3651
|
return { ...hint, implementationRepo: hint.implementationRepo };
|
|
3480
3652
|
}
|
|
3481
|
-
function selectImplementation(rawEvidence, hints, legacyRepo) {
|
|
3482
|
-
const evidence = asEvidence(rawEvidence);
|
|
3653
|
+
function selectImplementation(rawEvidence, hints, legacyRepo, canonicalEvidence) {
|
|
3654
|
+
const evidence = asEvidence(canonicalEvidence ?? rawEvidence);
|
|
3483
3655
|
const scoped = hints ?? [];
|
|
3484
3656
|
const matchingHints = scoped.filter((hint2) => hintMatchesEdge(hint2, evidence));
|
|
3485
3657
|
if (matchingHints.length === 0) {
|
|
@@ -3488,38 +3660,60 @@ function selectImplementation(rawEvidence, hints, legacyRepo) {
|
|
|
3488
3660
|
return { blocksAutomatic: false, evidence: { status: "not_matched", reason, strategy: "scoped_implementation_hint" } };
|
|
3489
3661
|
}
|
|
3490
3662
|
if (matchingHints.length > 1) {
|
|
3663
|
+
const projection = projectBounded(matchingHints, compareHints);
|
|
3491
3664
|
return {
|
|
3492
3665
|
blocksAutomatic: true,
|
|
3493
3666
|
evidence: {
|
|
3494
3667
|
status: "tied",
|
|
3495
3668
|
reason: "multiple_scoped_hints_matched_edge",
|
|
3496
3669
|
strategy: "scoped_implementation_hint",
|
|
3497
|
-
matchedHints:
|
|
3498
|
-
candidateCount: matchingHints.length
|
|
3670
|
+
matchedHints: projection.items,
|
|
3671
|
+
candidateCount: matchingHints.length,
|
|
3672
|
+
matchedHintCount: projection.totalCount,
|
|
3673
|
+
shownMatchedHintCount: projection.shownCount,
|
|
3674
|
+
omittedMatchedHintCount: projection.omittedCount
|
|
3499
3675
|
}
|
|
3500
3676
|
};
|
|
3501
3677
|
}
|
|
3502
3678
|
const hint = matchingHints[0];
|
|
3503
3679
|
return hint ? selectCandidate(evidence, hint, "scoped_implementation_hint") : { blocksAutomatic: false, evidence: { status: "not_matched" } };
|
|
3504
3680
|
}
|
|
3505
|
-
function implementationHintDiagnostic(selection,
|
|
3681
|
+
function implementationHintDiagnostic(selection, suggestionEvidence) {
|
|
3506
3682
|
if (!selection.blocksAutomatic || selection.methodId) return void 0;
|
|
3683
|
+
const suggestions = projectedSuggestions(suggestionEvidence);
|
|
3507
3684
|
return {
|
|
3508
3685
|
severity: "warning",
|
|
3509
3686
|
code: "implementation_hint_mismatch",
|
|
3510
3687
|
message: "Implementation hint did not select exactly one viable candidate",
|
|
3511
3688
|
hintStatus: selection.evidence.status,
|
|
3512
3689
|
candidateCount: selection.evidence.candidateCount,
|
|
3513
|
-
implementationHintSuggestions:
|
|
3690
|
+
implementationHintSuggestions: suggestions.suggestions.length > 0 ? suggestions.suggestions : void 0,
|
|
3691
|
+
implementationHintSuggestionCount: suggestions.suggestionCount,
|
|
3692
|
+
shownImplementationHintSuggestionCount: suggestions.shownSuggestionCount,
|
|
3693
|
+
omittedImplementationHintSuggestionCount: suggestions.omittedSuggestionCount,
|
|
3514
3694
|
implementationSelection: selection.evidence
|
|
3515
3695
|
};
|
|
3516
3696
|
}
|
|
3517
3697
|
function implementationHintSuggestions(rawEvidence) {
|
|
3698
|
+
return implementationHintSuggestionProjection(rawEvidence).suggestions;
|
|
3699
|
+
}
|
|
3700
|
+
function implementationHintSuggestionProjection(rawEvidence) {
|
|
3518
3701
|
const evidence = asEvidence(rawEvidence);
|
|
3519
3702
|
const accepted = (evidence.candidates ?? []).filter((candidate) => candidate.accepted);
|
|
3520
|
-
if (accepted.length < 2)
|
|
3703
|
+
if (accepted.length < 2) {
|
|
3704
|
+
return {
|
|
3705
|
+
suggestions: [],
|
|
3706
|
+
suggestionCount: 0,
|
|
3707
|
+
shownSuggestionCount: 0,
|
|
3708
|
+
omittedSuggestionCount: 0
|
|
3709
|
+
};
|
|
3710
|
+
}
|
|
3521
3711
|
const repos = selectableRepositories(accepted);
|
|
3522
|
-
|
|
3712
|
+
const repositoryProjection = projectBounded(
|
|
3713
|
+
repos,
|
|
3714
|
+
(left, right) => left.localeCompare(right)
|
|
3715
|
+
);
|
|
3716
|
+
const suggestions = accepted.flatMap((candidate) => {
|
|
3523
3717
|
const repo = candidate.handlerPackage?.name;
|
|
3524
3718
|
if (!repo || !repos.includes(repo)) return [];
|
|
3525
3719
|
const hint = suggestionHint(evidence, candidate, repo);
|
|
@@ -3528,16 +3722,58 @@ function implementationHintSuggestions(rawEvidence) {
|
|
|
3528
3722
|
operationPath: hint.operationPath,
|
|
3529
3723
|
ambiguityReason: evidence.ambiguityReasons?.[0],
|
|
3530
3724
|
candidateFamily: hint.candidateFamily,
|
|
3531
|
-
selectableImplementationRepositories:
|
|
3725
|
+
selectableImplementationRepositories: repositoryProjection.items,
|
|
3726
|
+
selectableImplementationRepositoryCount: repositoryProjection.totalCount,
|
|
3727
|
+
shownSelectableImplementationRepositoryCount: repositoryProjection.shownCount,
|
|
3728
|
+
omittedSelectableImplementationRepositoryCount: repositoryProjection.omittedCount,
|
|
3532
3729
|
implementationRepo: repo,
|
|
3533
3730
|
hint,
|
|
3534
3731
|
cli: `--implementation-hint ${hintString(hint)}`
|
|
3535
3732
|
}];
|
|
3536
3733
|
});
|
|
3734
|
+
const projection = projectBounded(suggestions, compareSuggestion);
|
|
3735
|
+
return {
|
|
3736
|
+
suggestions: projection.items,
|
|
3737
|
+
suggestionCount: projection.totalCount,
|
|
3738
|
+
shownSuggestionCount: projection.shownCount,
|
|
3739
|
+
omittedSuggestionCount: projection.omittedCount
|
|
3740
|
+
};
|
|
3741
|
+
}
|
|
3742
|
+
function projectedSuggestions(value) {
|
|
3743
|
+
const evidence = objectRecord(value);
|
|
3744
|
+
const values = Array.isArray(value) ? recordSuggestions(value) : recordSuggestions(evidence.implementationHintSuggestions);
|
|
3745
|
+
const projection = projectBounded(values, compareSuggestion);
|
|
3746
|
+
const total = Math.max(
|
|
3747
|
+
numericValue2(evidence.implementationHintSuggestionCount),
|
|
3748
|
+
projection.totalCount
|
|
3749
|
+
);
|
|
3750
|
+
return {
|
|
3751
|
+
suggestions: projection.items,
|
|
3752
|
+
suggestionCount: total,
|
|
3753
|
+
shownSuggestionCount: projection.shownCount,
|
|
3754
|
+
omittedSuggestionCount: Math.max(0, total - projection.shownCount)
|
|
3755
|
+
};
|
|
3756
|
+
}
|
|
3757
|
+
function compareHints(left, right) {
|
|
3758
|
+
return hintString(left).localeCompare(hintString(right));
|
|
3759
|
+
}
|
|
3760
|
+
function compareSuggestion(left, right) {
|
|
3761
|
+
return String(left.cli ?? "").localeCompare(String(right.cli ?? "")) || String(left.implementationRepo ?? "").localeCompare(
|
|
3762
|
+
String(right.implementationRepo ?? "")
|
|
3763
|
+
);
|
|
3764
|
+
}
|
|
3765
|
+
function objectRecord(value) {
|
|
3766
|
+
return isRecord(value) ? value : {};
|
|
3767
|
+
}
|
|
3768
|
+
function recordSuggestions(value) {
|
|
3769
|
+
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
3537
3770
|
}
|
|
3538
|
-
function
|
|
3539
|
-
|
|
3540
|
-
|
|
3771
|
+
function numericValue2(value) {
|
|
3772
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
3773
|
+
}
|
|
3774
|
+
function selectableRepositories(candidates2) {
|
|
3775
|
+
const repos = new Set(candidates2.flatMap((candidate) => candidate.handlerPackage?.name ? [candidate.handlerPackage.name] : []));
|
|
3776
|
+
return [...repos].filter((repo) => candidates2.filter((candidate) => candidateMatchesRepo(candidate, repo)).length === 1).sort();
|
|
3541
3777
|
}
|
|
3542
3778
|
function assignHintField(hint, key, value) {
|
|
3543
3779
|
if (key === "service" || key === "servicePath") hint.servicePath = value;
|
|
@@ -3629,7 +3865,45 @@ function legacyHint(implementationRepo) {
|
|
|
3629
3865
|
return { implementationRepo };
|
|
3630
3866
|
}
|
|
3631
3867
|
function asEvidence(value) {
|
|
3632
|
-
return
|
|
3868
|
+
return {
|
|
3869
|
+
servicePath: stringValue3(value.servicePath),
|
|
3870
|
+
operationPath: stringValue3(value.operationPath),
|
|
3871
|
+
ambiguityReasons: stringArray(value.ambiguityReasons),
|
|
3872
|
+
candidateFamilies: candidateFamilies(value.candidateFamilies),
|
|
3873
|
+
candidates: candidates(value.candidates),
|
|
3874
|
+
modelPackage: packageValue(value.modelPackage)
|
|
3875
|
+
};
|
|
3876
|
+
}
|
|
3877
|
+
function candidates(value) {
|
|
3878
|
+
return recordSuggestions(value).map((candidate) => ({
|
|
3879
|
+
accepted: candidate.accepted === true,
|
|
3880
|
+
methodId: numericValue2(candidate.methodId) || void 0,
|
|
3881
|
+
sourceFile: stringValue3(candidate.sourceFile),
|
|
3882
|
+
handlerPackage: packageValue(candidate.handlerPackage),
|
|
3883
|
+
modelPackage: packageValue(candidate.modelPackage),
|
|
3884
|
+
servicePath: stringValue3(candidate.servicePath),
|
|
3885
|
+
operationPath: stringValue3(candidate.operationPath)
|
|
3886
|
+
}));
|
|
3887
|
+
}
|
|
3888
|
+
function candidateFamilies(value) {
|
|
3889
|
+
return recordSuggestions(value).map((family) => ({
|
|
3890
|
+
packageName: stringValue3(family.packageName)
|
|
3891
|
+
}));
|
|
3892
|
+
}
|
|
3893
|
+
function packageValue(value) {
|
|
3894
|
+
const candidate = objectRecord(value);
|
|
3895
|
+
const name = stringValue3(candidate.name);
|
|
3896
|
+
const packageName = stringValue3(candidate.packageName);
|
|
3897
|
+
return name || packageName ? { name, packageName } : void 0;
|
|
3898
|
+
}
|
|
3899
|
+
function stringArray(value) {
|
|
3900
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
3901
|
+
}
|
|
3902
|
+
function stringValue3(value) {
|
|
3903
|
+
return typeof value === "string" ? value : void 0;
|
|
3904
|
+
}
|
|
3905
|
+
function isRecord(value) {
|
|
3906
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
3633
3907
|
}
|
|
3634
3908
|
|
|
3635
3909
|
// src/linker/remote-query-target.ts
|
|
@@ -3656,485 +3930,242 @@ function buildRemoteQueryTarget(input) {
|
|
|
3656
3930
|
};
|
|
3657
3931
|
}
|
|
3658
3932
|
|
|
3659
|
-
// src/linker/
|
|
3660
|
-
function
|
|
3661
|
-
const
|
|
3662
|
-
const
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
)
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
names.simplePath,
|
|
3671
|
-
names.name,
|
|
3672
|
-
names.simpleName
|
|
3933
|
+
// src/linker/001-implementation-evidence-projection.ts
|
|
3934
|
+
function boundedImplementationEvidence(evidence, targetCandidateCount) {
|
|
3935
|
+
const candidates2 = recordArray(evidence.candidates);
|
|
3936
|
+
const candidateProjection = projectBounded(candidates2, compareCandidateEvidence);
|
|
3937
|
+
const families = recordArray(evidence.candidateFamilies);
|
|
3938
|
+
const familyProjection = projectBounded(families, compareFamilies);
|
|
3939
|
+
const hints = recordArray(evidence.implementationHintSuggestions);
|
|
3940
|
+
const hintProjection = projectBounded(hints, compareHints2);
|
|
3941
|
+
const hintCount = Math.max(
|
|
3942
|
+
numberValue(evidence.implementationHintSuggestionCount),
|
|
3943
|
+
hintProjection.totalCount
|
|
3673
3944
|
);
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3945
|
+
const targets = Math.max(0, targetCandidateCount);
|
|
3946
|
+
return {
|
|
3947
|
+
...evidence,
|
|
3948
|
+
candidates: candidateProjection.items.map(boundedCandidateEvidence),
|
|
3949
|
+
candidateCount: candidateProjection.totalCount,
|
|
3950
|
+
shownCandidateCount: candidateProjection.shownCount,
|
|
3951
|
+
omittedCandidateCount: candidateProjection.omittedCount,
|
|
3952
|
+
candidateFamilies: familyProjection.items.map(boundedFamilyEvidence),
|
|
3953
|
+
candidateFamilyCount: familyProjection.totalCount,
|
|
3954
|
+
shownCandidateFamilyCount: familyProjection.shownCount,
|
|
3955
|
+
omittedCandidateFamilyCount: familyProjection.omittedCount,
|
|
3956
|
+
implementationHintSuggestions: hintProjection.items,
|
|
3957
|
+
implementationHintSuggestionCount: hintCount,
|
|
3958
|
+
shownImplementationHintSuggestionCount: hintProjection.shownCount,
|
|
3959
|
+
omittedImplementationHintSuggestionCount: Math.max(0, hintCount - hintProjection.shownCount),
|
|
3960
|
+
candidateTargetCount: targets,
|
|
3961
|
+
shownCandidateTargetCount: Math.min(targets, candidateProjection.shownCount),
|
|
3962
|
+
omittedCandidateTargetCount: Math.max(0, targets - candidateProjection.shownCount)
|
|
3963
|
+
};
|
|
3679
3964
|
}
|
|
3680
|
-
function
|
|
3681
|
-
const
|
|
3682
|
-
|
|
3683
|
-
|
|
3965
|
+
function boundedImplementationTargetIds(candidates2) {
|
|
3966
|
+
const projection = projectBounded(candidates2, compareTargetCandidates);
|
|
3967
|
+
return {
|
|
3968
|
+
totalCount: projection.totalCount,
|
|
3969
|
+
shownCount: projection.shownCount,
|
|
3970
|
+
omittedCount: projection.omittedCount,
|
|
3971
|
+
items: projection.items.map((candidate) => String(candidate.methodId ?? ""))
|
|
3972
|
+
};
|
|
3684
3973
|
}
|
|
3685
|
-
function
|
|
3686
|
-
|
|
3687
|
-
const
|
|
3688
|
-
return
|
|
3974
|
+
function boundedCandidateEvidence(candidate) {
|
|
3975
|
+
const registrations = recordArray(candidate.registrations);
|
|
3976
|
+
const projection = projectBounded(registrations, compareRegistrations);
|
|
3977
|
+
return {
|
|
3978
|
+
...candidate,
|
|
3979
|
+
registrations: projection.items,
|
|
3980
|
+
registrationCount: projection.totalCount,
|
|
3981
|
+
shownRegistrationCount: projection.shownCount,
|
|
3982
|
+
omittedRegistrationCount: projection.omittedCount
|
|
3983
|
+
};
|
|
3689
3984
|
}
|
|
3690
|
-
function
|
|
3691
|
-
const
|
|
3692
|
-
|
|
3693
|
-
return {
|
|
3694
|
-
status: "dynamic",
|
|
3695
|
-
candidates: signals.operationPath ? rows(db, signals.operationPath, workspaceId).map((candidate) => ({
|
|
3696
|
-
...candidate,
|
|
3697
|
-
score: 0.2,
|
|
3698
|
-
reasons: ["operation_path_match"]
|
|
3699
|
-
})) : [],
|
|
3700
|
-
reasons: [...new Set(missing)].map((name) => `missing_variable:${name}`)
|
|
3701
|
-
};
|
|
3702
|
-
if (!signals.operationPath)
|
|
3703
|
-
return {
|
|
3704
|
-
status: "unresolved",
|
|
3705
|
-
candidates: [],
|
|
3706
|
-
reasons: ["missing_operation_path"]
|
|
3707
|
-
};
|
|
3708
|
-
const allCandidates = rows(db, signals.operationPath, workspaceId).map((c) => ({
|
|
3709
|
-
...c,
|
|
3710
|
-
score: 0.2,
|
|
3711
|
-
reasons: ["operation_path_match"]
|
|
3712
|
-
}));
|
|
3713
|
-
let candidates = allCandidates.filter((c) => matchesLocalRepo(db, c.operationId, signals.repoId));
|
|
3714
|
-
if (candidates.length === 0 && signals.repoId !== void 0 && signals.serviceName) {
|
|
3715
|
-
candidates = implementationContextCandidates(db, allCandidates, signals.repoId, signals.serviceName);
|
|
3716
|
-
if (candidates.length === 0)
|
|
3717
|
-
return {
|
|
3718
|
-
status: "unresolved",
|
|
3719
|
-
candidates: allCandidates.filter((c) => serviceMatches(c, signals.serviceName)),
|
|
3720
|
-
reasons: allCandidates.some((c) => serviceMatches(c, signals.serviceName)) ? ["local_service_candidate_without_caller_ownership"] : ["no_operation_candidates"]
|
|
3721
|
-
};
|
|
3722
|
-
}
|
|
3723
|
-
if (candidates.length === 0)
|
|
3724
|
-
return {
|
|
3725
|
-
status: "unresolved",
|
|
3726
|
-
candidates: [],
|
|
3727
|
-
reasons: ["no_operation_candidates"]
|
|
3728
|
-
};
|
|
3729
|
-
const hasStrongSignal = Boolean(
|
|
3730
|
-
signals.servicePath || signals.serviceName || signals.alias || signals.destination || signals.hasExplicitOverride
|
|
3731
|
-
);
|
|
3732
|
-
for (const c of candidates) {
|
|
3733
|
-
if (signals.servicePath && c.servicePath === signals.servicePath) {
|
|
3734
|
-
c.score += 0.75;
|
|
3735
|
-
c.reasons.push("exact_service_path");
|
|
3736
|
-
}
|
|
3737
|
-
if (signals.servicePath && c.servicePath !== signals.servicePath) {
|
|
3738
|
-
c.score -= 0.1;
|
|
3739
|
-
c.reasons.push("service_path_mismatch");
|
|
3740
|
-
}
|
|
3741
|
-
if (signals.serviceName) {
|
|
3742
|
-
const simple = signals.serviceName.split(".").at(-1) ?? signals.serviceName;
|
|
3743
|
-
if (c.qualifiedName === signals.serviceName) {
|
|
3744
|
-
c.score += 0.8;
|
|
3745
|
-
c.reasons.push("exact_local_qualified_service_name");
|
|
3746
|
-
} else if (c.serviceName === signals.serviceName || c.serviceName === simple) {
|
|
3747
|
-
c.score += 0.75;
|
|
3748
|
-
c.reasons.push("exact_local_simple_service_name");
|
|
3749
|
-
} else if (c.servicePath === signals.serviceName || c.servicePath === `/${signals.serviceName}` || c.servicePath === `/${simple}`) {
|
|
3750
|
-
c.score += 0.7;
|
|
3751
|
-
c.reasons.push("exact_local_service_path");
|
|
3752
|
-
} else if (c.servicePath.endsWith(`/${simple}`)) {
|
|
3753
|
-
c.score += candidates.filter((candidate) => candidate.servicePath.endsWith(`/${simple}`)).length === 1 ? 0.65 : 0.2;
|
|
3754
|
-
c.reasons.push("suffix_local_service_path");
|
|
3755
|
-
} else c.reasons.push("local_service_name_mismatch");
|
|
3756
|
-
}
|
|
3757
|
-
if (signals.hasExplicitOverride) {
|
|
3758
|
-
c.score += 0.2;
|
|
3759
|
-
c.reasons.push(signals.repoId !== void 0 ? "explicit_local_service_call" : "explicit_dynamic_override");
|
|
3760
|
-
}
|
|
3761
|
-
if (signals.repoId !== void 0 && candidates.length === 1 && signals.serviceName && c.reasons.includes("local_service_name_mismatch") && operationMatches(c, signals.operationPath)) {
|
|
3762
|
-
c.score = Math.max(c.score, 0.9);
|
|
3763
|
-
c.reasons.push("same_repo_unique_operation_path_with_lookup_mismatch");
|
|
3764
|
-
}
|
|
3765
|
-
}
|
|
3766
|
-
for (const c of candidates) c.score = Math.max(0, Math.min(1, c.score));
|
|
3767
|
-
candidates.sort(
|
|
3768
|
-
(a, b) => b.score - a.score || a.repoName.localeCompare(b.repoName)
|
|
3769
|
-
);
|
|
3770
|
-
const best = candidates[0];
|
|
3771
|
-
const second = candidates[1];
|
|
3772
|
-
if (signals.isDynamic && !signals.hasExplicitOverride && !signals.servicePath)
|
|
3773
|
-
return {
|
|
3774
|
-
status: "dynamic",
|
|
3775
|
-
candidates,
|
|
3776
|
-
reasons: ["dynamic_target_without_override"]
|
|
3777
|
-
};
|
|
3778
|
-
if (!hasStrongSignal)
|
|
3779
|
-
return {
|
|
3780
|
-
status: candidates.length > 1 ? "ambiguous" : "unresolved",
|
|
3781
|
-
candidates,
|
|
3782
|
-
reasons: ["operation_path_only_has_no_strong_target_signal"]
|
|
3783
|
-
};
|
|
3784
|
-
if (best && best.score >= 0.9 && (best.servicePath === signals.servicePath || Boolean(signals.serviceName && (!best.reasons.includes("local_service_name_mismatch") || best.reasons.includes("same_repo_unique_operation_path_with_lookup_mismatch")))) && operationMatches(best, signals.operationPath) && (!second || best.score - second.score >= 0.25))
|
|
3785
|
-
return {
|
|
3786
|
-
status: "resolved",
|
|
3787
|
-
target: best,
|
|
3788
|
-
candidates,
|
|
3789
|
-
reasons: best.reasons
|
|
3790
|
-
};
|
|
3985
|
+
function boundedFamilyEvidence(family) {
|
|
3986
|
+
const repositories = stringArray2(family.repositories);
|
|
3987
|
+
const projection = projectBounded(repositories, (left, right) => left.localeCompare(right));
|
|
3791
3988
|
return {
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3989
|
+
...family,
|
|
3990
|
+
repositories: projection.items,
|
|
3991
|
+
repositoryCount: projection.totalCount,
|
|
3992
|
+
shownRepositoryCount: projection.shownCount,
|
|
3993
|
+
omittedRepositoryCount: projection.omittedCount
|
|
3795
3994
|
};
|
|
3796
3995
|
}
|
|
3797
|
-
function
|
|
3798
|
-
|
|
3799
|
-
const simple = serviceName.split(".").at(-1) ?? serviceName;
|
|
3800
|
-
return candidate.qualifiedName === serviceName || candidate.serviceName === serviceName || candidate.serviceName === simple || candidate.servicePath === serviceName || candidate.servicePath === `/${serviceName}` || candidate.servicePath === `/${simple}` || candidate.servicePath.endsWith(`/${simple}`);
|
|
3996
|
+
function compareTargetCandidates(left, right) {
|
|
3997
|
+
return Number(right.score ?? 0) - Number(left.score ?? 0) || String(left.className ?? "").localeCompare(String(right.className ?? "")) || Number(left.methodId ?? 0) - Number(right.methodId ?? 0);
|
|
3801
3998
|
}
|
|
3802
|
-
function
|
|
3803
|
-
|
|
3804
|
-
const owned = matching.map((candidate) => ownershipReason(db, candidate, callerRepoId)).filter((item) => Boolean(item));
|
|
3805
|
-
if (owned.length === 0) return [];
|
|
3806
|
-
const direct = owned.filter((item) => item.reason !== "caller_depends_on_model_package");
|
|
3807
|
-
const chosen = direct.length > 0 ? direct : owned.length === 1 ? owned : [];
|
|
3808
|
-
return chosen.map((item) => ({ ...item.candidate, score: 0.95, reasons: [...item.candidate.reasons, "implementation_context_caller_ownership", item.reason] }));
|
|
3999
|
+
function compareCandidateEvidence(left, right) {
|
|
4000
|
+
return Number(left.rank ?? 0) - Number(right.rank ?? 0) || compareTargetCandidates(left, right);
|
|
3809
4001
|
}
|
|
3810
|
-
function
|
|
3811
|
-
|
|
3812
|
-
if (edge?.status === "resolved") {
|
|
3813
|
-
const row = db.prepare("SELECT hc.repo_id repoId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id WHERE hm.id=?").get(edge.to_id);
|
|
3814
|
-
if (row?.repoId === callerRepoId) return { candidate, reason: "resolved_implementation_handler_repo_matches_caller" };
|
|
3815
|
-
}
|
|
3816
|
-
if (edge?.evidence_json) {
|
|
3817
|
-
const evidence = JSON.parse(edge.evidence_json);
|
|
3818
|
-
const hit = evidence.candidates?.find((item) => item.accepted && (Number(item.handlerPackage?.id) === callerRepoId || Number(item.applicationPackage?.id) === callerRepoId));
|
|
3819
|
-
if (hit) return { candidate, reason: edge.status === "ambiguous" ? "ambiguous_implementation_candidate_repo_matches_caller" : "registration_package_matches_caller" };
|
|
3820
|
-
}
|
|
3821
|
-
const dep = db.prepare("SELECT 1 FROM graph_edges WHERE edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND status='resolved' AND from_kind='repo' AND from_id=? AND to_id=?").get(String(callerRepoId), String(candidate.repoId));
|
|
3822
|
-
if (dep) return { candidate, reason: "caller_depends_on_model_package" };
|
|
3823
|
-
return void 0;
|
|
4002
|
+
function compareFamilies(left, right) {
|
|
4003
|
+
return String(left.packageName ?? "").localeCompare(String(right.packageName ?? "")) || String(left.reason ?? "").localeCompare(String(right.reason ?? ""));
|
|
3824
4004
|
}
|
|
3825
|
-
function
|
|
3826
|
-
|
|
3827
|
-
const row = db.prepare("SELECT s.repo_id repoId FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?").get(operationId);
|
|
3828
|
-
return row?.repoId === repoId;
|
|
4005
|
+
function compareHints2(left, right) {
|
|
4006
|
+
return String(left.cli ?? "").localeCompare(String(right.cli ?? "")) || String(left.implementationRepo ?? "").localeCompare(String(right.implementationRepo ?? ""));
|
|
3829
4007
|
}
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
function normalizeName(value) {
|
|
3833
|
-
return value.toLowerCase().replace(/^@[^/]+\//, "").replace(/[^a-z0-9]+/g, "");
|
|
4008
|
+
function compareRegistrations(left, right) {
|
|
4009
|
+
return String(left.file ?? "").localeCompare(String(right.file ?? "")) || Number(left.line ?? 0) - Number(right.line ?? 0) || Number(left.id ?? 0) - Number(right.id ?? 0);
|
|
3834
4010
|
}
|
|
3835
|
-
function
|
|
3836
|
-
|
|
3837
|
-
if (exact.length > 0) return { candidates: exact, strategy: "exact_package_name" };
|
|
3838
|
-
const normalized = normalizeName(dep);
|
|
3839
|
-
return { candidates: repos.filter((repo) => repo.id !== sourceId && normalizeName(repo.name) === normalized), strategy: "normalized_directory" };
|
|
4011
|
+
function recordArray(value) {
|
|
4012
|
+
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
3840
4013
|
}
|
|
3841
|
-
function
|
|
3842
|
-
|
|
3843
|
-
const summary = { edgeCount: 0, resolvedCount: 0, ambiguousCount: 0 };
|
|
3844
|
-
for (const repo of repos) {
|
|
3845
|
-
const deps = JSON.parse(repo.dependencies_json);
|
|
3846
|
-
for (const dep of Object.keys(deps)) {
|
|
3847
|
-
const result = candidatesForDependency(repos, dep, repo.id);
|
|
3848
|
-
if (result.candidates.length === 0) continue;
|
|
3849
|
-
const status = result.candidates.length === 1 ? "resolved" : "ambiguous";
|
|
3850
|
-
const helper = status === "resolved" ? result.candidates[0] : void 0;
|
|
3851
|
-
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(
|
|
3852
|
-
workspaceId,
|
|
3853
|
-
"REPO_IMPORTS_HELPER_PACKAGE",
|
|
3854
|
-
status,
|
|
3855
|
-
"repo",
|
|
3856
|
-
String(repo.id),
|
|
3857
|
-
helper ? "repo" : "repo_candidates",
|
|
3858
|
-
helper ? String(helper.id) : result.candidates.map((candidate) => candidate.id).join(","),
|
|
3859
|
-
helper ? 1 : 0.5,
|
|
3860
|
-
JSON.stringify({ dependency: dep, candidates: result.candidates.map((candidate) => ({ id: candidate.id, name: candidate.name, packageName: candidate.package_name })), match: result.strategy }),
|
|
3861
|
-
0,
|
|
3862
|
-
helper ? null : "Ambiguous dependency package candidates",
|
|
3863
|
-
generation
|
|
3864
|
-
);
|
|
3865
|
-
summary.edgeCount += 1;
|
|
3866
|
-
if (helper) summary.resolvedCount += 1;
|
|
3867
|
-
else summary.ambiguousCount += 1;
|
|
3868
|
-
}
|
|
3869
|
-
}
|
|
3870
|
-
return summary;
|
|
3871
|
-
}
|
|
3872
|
-
|
|
3873
|
-
// src/linker/cross-repo-linker.ts
|
|
3874
|
-
function linkWorkspace(db, workspaceId, vars = {}) {
|
|
3875
|
-
return db.transaction(() => {
|
|
3876
|
-
const generation = nextGraphGeneration(db, workspaceId);
|
|
3877
|
-
db.prepare("DELETE FROM graph_edges WHERE workspace_id=?").run(workspaceId);
|
|
3878
|
-
const deps = linkHelperPackages(db, workspaceId, generation);
|
|
3879
|
-
const impl = linkImplementations(db, workspaceId, generation);
|
|
3880
|
-
const callSummary = linkCalls(db, workspaceId, vars, generation);
|
|
3881
|
-
db.prepare("UPDATE repositories SET graph_generation=?, graph_stale_reason=NULL, graph_stale_at=NULL WHERE workspace_id=?").run(generation, workspaceId);
|
|
3882
|
-
return { ...callSummary, edgeCount: deps.edgeCount + callSummary.edgeCount + impl.edgeCount, dependencyResolvedCount: deps.resolvedCount, dependencyAmbiguousCount: deps.ambiguousCount, implementationResolvedCount: impl.resolvedCount, implementationAmbiguousCount: impl.ambiguousCount, implementationUnresolvedCount: impl.unresolvedCount };
|
|
3883
|
-
});
|
|
4014
|
+
function stringArray2(value) {
|
|
4015
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
3884
4016
|
}
|
|
3885
|
-
function
|
|
3886
|
-
|
|
3887
|
-
return Number(row?.generation ?? 0) + 1;
|
|
4017
|
+
function numberValue(value) {
|
|
4018
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
3888
4019
|
}
|
|
3889
|
-
|
|
4020
|
+
|
|
4021
|
+
// src/linker/000-implementation-candidates.ts
|
|
4022
|
+
function linkImplementations(db, workspaceId, generation) {
|
|
4023
|
+
const operations = workspaceOperations(db, workspaceId);
|
|
3890
4024
|
let edgeCount = 0;
|
|
3891
|
-
let unresolvedCount = 0;
|
|
3892
4025
|
let resolvedCount = 0;
|
|
3893
|
-
let remoteResolvedCount = 0;
|
|
3894
|
-
let localResolvedCount = 0;
|
|
3895
4026
|
let ambiguousCount = 0;
|
|
3896
|
-
let
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
const
|
|
4027
|
+
let unresolvedCount = 0;
|
|
4028
|
+
for (const operation of operations) {
|
|
4029
|
+
const decision = implementationDecision(db, workspaceId, operation, true);
|
|
4030
|
+
if (decision.candidates.length === 0) continue;
|
|
4031
|
+
const status = decision.unique ? "resolved" : decision.accepted.length > 0 ? "ambiguous" : "unresolved";
|
|
4032
|
+
insertImplementationEdge(db, workspaceId, generation, operation, decision, status);
|
|
3901
4033
|
edgeCount += 1;
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
unresolvedCount += result.status === "unresolved" ? 1 : 0;
|
|
3906
|
-
ambiguousCount += result.status === "ambiguous" ? 1 : 0;
|
|
3907
|
-
dynamicCount += result.status === "dynamic" ? 1 : 0;
|
|
3908
|
-
terminalCount += result.status === "terminal" ? 1 : 0;
|
|
3909
|
-
}
|
|
3910
|
-
return { edgeCount, unresolvedCount, resolvedCount, remoteResolvedCount, localResolvedCount, ambiguousCount, dynamicCount, terminalCount };
|
|
3911
|
-
}
|
|
3912
|
-
function insertCallEdge(db, workspaceId, call, vars, generation) {
|
|
3913
|
-
const callType = String(call.call_type);
|
|
3914
|
-
const rawOp = applyVariables(String(call.operation_path_expr ?? ""), vars);
|
|
3915
|
-
const intent = classifyODataPathIntent(rawOp, call.method);
|
|
3916
|
-
const isEntityQueryIntent = ["entity_query", "entity_key_read", "entity_navigation_query"].includes(intent.kind);
|
|
3917
|
-
const resolutionRawOp = callType === "remote_query" && isEntityQueryIntent ? intent.pathWithoutQuery : rawOp;
|
|
3918
|
-
const normalized = normalizeODataOperationInvocationPath(resolutionRawOp);
|
|
3919
|
-
const op = normalized?.normalizedOperationPath ?? resolutionRawOp;
|
|
3920
|
-
const servicePath = applyVariables(call.servicePathExpr ?? call.requireServicePath, vars);
|
|
3921
|
-
const destination = call.destinationExpr ?? call.requireDestination;
|
|
3922
|
-
const isDynamic = Boolean(Number(call.isDynamic ?? 0));
|
|
3923
|
-
const isRemoteEntityCall = callType.startsWith("remote_entity_");
|
|
3924
|
-
const indexedOperationCandidateCount = operationCandidateCount(db, workspaceId, op, intent.topLevelOperationName);
|
|
3925
|
-
const credibleOperationSignal = Boolean(normalized?.wasInvocation) || Boolean(intent.topLevelOperationNameCandidate) && indexedOperationCandidateCount > 0;
|
|
3926
|
-
const strongEntitySignal = ["entity_media", "entity_delete", "entity_key_read", "entity_navigation_query"].includes(intent.kind) || intent.kind === "entity_mutation" && (intent.hasEntityKeyPredicate || intent.hasNavigationSuffix);
|
|
3927
|
-
const operationLikeRemoteEntity = isRemoteEntityCall && Boolean(op) && credibleOperationSignal && (!strongEntitySignal || indexedOperationCandidateCount > 0);
|
|
3928
|
-
const isOperationCall = operationLikeRemoteEntity || (callType === "remote_action" || callType === "local_service_call" || callType === "remote_query" && Boolean(op));
|
|
3929
|
-
const resolution = isOperationCall ? resolveOperation(db, { servicePath, operationPath: op, serviceName: call.local_service_name, repoId: callType === "local_service_call" ? Number(call.repo_id) : void 0, alias: applyVariables(call.aliasExpr ?? call.alias, vars), destination: destination ? applyVariables(destination, vars) : void 0, isDynamic, hasExplicitOverride: Object.keys(vars).length > 0 || callType === "local_service_call" }, workspaceId) : { status: "unresolved", candidates: [], reasons: [] };
|
|
3930
|
-
const evidence = {
|
|
3931
|
-
...callEvidence(call, resolution, servicePath, op, destination ? applyVariables(destination, vars) : void 0, normalized, intent),
|
|
3932
|
-
indexedOperationCandidateCount,
|
|
3933
|
-
parserCallType: callType,
|
|
3934
|
-
entityOperationPrecedence: operationPrecedence(
|
|
3935
|
-
callType,
|
|
3936
|
-
intent,
|
|
3937
|
-
indexedOperationCandidateCount,
|
|
3938
|
-
Boolean(resolution.target)
|
|
3939
|
-
)
|
|
3940
|
-
};
|
|
3941
|
-
const pathAnalysis = objectValue(objectJson(call.evidence_json)?.pathAnalysis);
|
|
3942
|
-
if (callType === "remote_action" && pathAnalysis?.status === "ambiguous") {
|
|
3943
|
-
const candidatePaths = Array.isArray(pathAnalysis.candidateRawPaths) ? pathAnalysis.candidateRawPaths : [];
|
|
3944
|
-
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(
|
|
3945
|
-
workspaceId,
|
|
3946
|
-
"UNRESOLVED_EDGE",
|
|
3947
|
-
"ambiguous",
|
|
3948
|
-
"call",
|
|
3949
|
-
String(call.id),
|
|
3950
|
-
"operation_candidates",
|
|
3951
|
-
candidatePaths.join(","),
|
|
3952
|
-
Number(call.confidence ?? 0.5),
|
|
3953
|
-
JSON.stringify(evidence),
|
|
3954
|
-
0,
|
|
3955
|
-
"Ambiguous operation path candidates require explicit disambiguation",
|
|
3956
|
-
generation
|
|
3957
|
-
);
|
|
3958
|
-
return { status: "ambiguous", callType };
|
|
3959
|
-
}
|
|
3960
|
-
if (isRemoteEntityCall && (resolution.target || resolution.candidates.length > 0 || resolution.status === "dynamic")) {
|
|
3961
|
-
if (resolution.target) {
|
|
3962
|
-
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "REMOTE_CALL_RESOLVES_TO_OPERATION", "resolved", "call", String(call.id), "operation", String(resolution.target.operationId), resolution.target.score, JSON.stringify({ ...evidence, operationEntityPrecedence: "indexed_operation_over_parser_entity" }), 0, generation);
|
|
3963
|
-
return { status: "resolved", callType };
|
|
3964
|
-
}
|
|
3965
|
-
const status2 = resolution.status === "dynamic" ? "dynamic" : resolution.status === "ambiguous" ? "ambiguous" : "unresolved";
|
|
3966
|
-
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, status2 === "dynamic" ? "DYNAMIC_EDGE_CANDIDATE" : "UNRESOLVED_EDGE", status2, "call", String(call.id), "operation_candidate", op ? `Remote action: ${op}` : "Remote action: unknown path", Number(call.confidence ?? 0.2), JSON.stringify({ ...evidence, operationEntityPrecedence: resolution.candidates.length > 0 ? "parser_entity_with_indexed_operation_candidates" : "parser_entity_operation_candidate_without_indexed_match" }), status2 === "dynamic" ? 1 : 0, unresolvedOperationReason(resolution), generation);
|
|
3967
|
-
return { status: status2, callType };
|
|
3968
|
-
}
|
|
3969
|
-
if (isRemoteEntityCall) {
|
|
3970
|
-
const target = buildRemoteQueryTarget({ queryEntity: intent.entitySegment ?? call.query_entity, servicePath, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination: destination ? applyVariables(destination, vars) : void 0, isDynamic, parserWarning: evidence.parserWarning });
|
|
3971
|
-
const entityKind = callType.replace("remote_entity_", "remote_entity_");
|
|
3972
|
-
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "HANDLER_ACCESSES_REMOTE_ENTITY", "terminal", "call", String(call.id), target.toKind, target.toId, Number(call.confidence ?? 0.5), JSON.stringify({ ...evidence, ...target.evidence, remoteEntityAccess: entityKind }), 0, generation);
|
|
3973
|
-
return { status: "terminal", callType };
|
|
3974
|
-
}
|
|
3975
|
-
if (callType === "remote_query" && (isEntityQueryIntent || !op) && !resolution.target) {
|
|
3976
|
-
const target = buildRemoteQueryTarget({ queryEntity: call.query_entity, servicePath, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination: destination ? applyVariables(destination, vars) : void 0, isDynamic, parserWarning: evidence.parserWarning });
|
|
3977
|
-
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "HANDLER_RUNS_REMOTE_QUERY", "terminal", "call", String(call.id), target.toKind, target.toId, Number(call.confidence ?? 0.5), JSON.stringify({ ...evidence, ...target.evidence }), 0, generation);
|
|
3978
|
-
return { status: "terminal", callType };
|
|
3979
|
-
}
|
|
3980
|
-
if (callType === "local_service_call" && call.unresolved_reason === "transport_client_method" && !resolution.target && resolution.candidates.length === 0) {
|
|
3981
|
-
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "HANDLER_CALLS_TRANSPORT_METHOD", "terminal", "call", String(call.id), "transport_method", String(op || "transport_client_method"), Number(call.confidence ?? 0.5), JSON.stringify({ ...evidence, classification: "transport_client_method" }), 0, generation);
|
|
3982
|
-
return { status: "terminal", callType };
|
|
3983
|
-
}
|
|
3984
|
-
if (resolution.target) {
|
|
3985
|
-
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, callType === "local_service_call" ? "LOCAL_CALL_RESOLVES_TO_OPERATION" : "REMOTE_CALL_RESOLVES_TO_OPERATION", "resolved", "call", String(call.id), "operation", String(resolution.target.operationId), resolution.target.score, JSON.stringify(evidence), 0, generation);
|
|
3986
|
-
return { status: "resolved", callType };
|
|
4034
|
+
if (status === "resolved") resolvedCount += 1;
|
|
4035
|
+
else if (status === "ambiguous") ambiguousCount += 1;
|
|
4036
|
+
else unresolvedCount += 1;
|
|
3987
4037
|
}
|
|
3988
|
-
|
|
3989
|
-
const status = edgeType === "DYNAMIC_EDGE_CANDIDATE" ? "dynamic" : resolution.status === "ambiguous" ? "ambiguous" : edgeType === "UNRESOLVED_EDGE" ? "unresolved" : "terminal";
|
|
3990
|
-
const unresolvedReason = status === "terminal" ? null : String(call.unresolved_reason ?? unresolvedOperationReason(resolution));
|
|
3991
|
-
const externalTarget = callType === "external_http" ? externalHttpTarget(call) : void 0;
|
|
3992
|
-
const targetKind = callType === "local_db_query" ? "db_entity" : callType.startsWith("async_") ? "event" : callType === "external_http" ? externalTarget?.toKind ?? "external_endpoint" : "operation_candidate";
|
|
3993
|
-
const targetId = callType === "local_db_query" ? String(call.query_entity ?? "unknown") : callType === "remote_action" ? op ? `Remote action: ${op}` : call.unresolved_reason === "dynamic_operation_path_identifier" ? "Remote action: dynamic path" : "Remote action: unknown path" : callType === "external_http" ? String(externalTarget?.toId ?? "unknown") : String(call.event_name_expr ?? op ?? "unknown");
|
|
3994
|
-
const graphLevelDynamic = edgeType === "DYNAMIC_EDGE_CANDIDATE" && resolution.status === "dynamic";
|
|
3995
|
-
const finalEvidence = externalTarget ? { ...evidence, externalTarget } : evidence;
|
|
3996
|
-
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, edgeType, status, "call", String(call.id), targetKind, targetId, Number(call.confidence ?? 0.2), JSON.stringify(finalEvidence), graphLevelDynamic ? 1 : 0, unresolvedReason, generation);
|
|
3997
|
-
return { status, callType };
|
|
4038
|
+
return { edgeCount, resolvedCount, ambiguousCount, unresolvedCount };
|
|
3998
4039
|
}
|
|
3999
|
-
function
|
|
4000
|
-
|
|
4001
|
-
const
|
|
4002
|
-
|
|
4003
|
-
return
|
|
4040
|
+
function canonicalImplementationEvidence(db, operationId) {
|
|
4041
|
+
const operation = operationById(db, operationId);
|
|
4042
|
+
const workspaceId = numberValue2(operation?.workspaceId);
|
|
4043
|
+
if (!operation || workspaceId === void 0) return void 0;
|
|
4044
|
+
return implementationDecision(db, workspaceId, operation, false).evidenceWithHints;
|
|
4045
|
+
}
|
|
4046
|
+
function workspaceOperations(db, workspaceId) {
|
|
4047
|
+
const rows2 = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,
|
|
4048
|
+
o.operation_name operationName,o.provenance provenance,o.base_operation_id baseOperationId,
|
|
4049
|
+
s.service_path servicePath,s.repo_id modelRepoId,r.name modelRepo,
|
|
4050
|
+
r.package_name modelPackage,r.kind modelKind
|
|
4051
|
+
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id
|
|
4052
|
+
JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=?`).all(workspaceId);
|
|
4053
|
+
return rows2;
|
|
4004
4054
|
}
|
|
4005
|
-
function
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4055
|
+
function operationById(db, operationId) {
|
|
4056
|
+
const row = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,
|
|
4057
|
+
o.operation_name operationName,o.provenance provenance,o.base_operation_id baseOperationId,
|
|
4058
|
+
s.service_path servicePath,s.repo_id modelRepoId,r.name modelRepo,
|
|
4059
|
+
r.package_name modelPackage,r.kind modelKind,r.workspace_id workspaceId
|
|
4060
|
+
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id
|
|
4061
|
+
JOIN repositories r ON r.id=s.repo_id WHERE o.id=?`).get(operationId);
|
|
4062
|
+
return row;
|
|
4063
|
+
}
|
|
4064
|
+
function implementationDecision(db, workspaceId, operation, recordDiagnostics) {
|
|
4065
|
+
const implementationContext = implementationContextForOperation(db, operation);
|
|
4066
|
+
const candidates2 = rankedImplementationCandidates(
|
|
4067
|
+
db,
|
|
4068
|
+
workspaceId,
|
|
4069
|
+
implementationContext,
|
|
4070
|
+
recordDiagnostics
|
|
4071
|
+
);
|
|
4072
|
+
const accepted = candidates2.filter((candidate) => candidate.accepted);
|
|
4073
|
+
const topScore = accepted[0]?.score ?? 0;
|
|
4074
|
+
const winners = accepted.filter((candidate) => candidate.score === topScore);
|
|
4075
|
+
const duplicateFamilies = duplicatePackageFamilies(accepted);
|
|
4076
|
+
const duplicatePackageAmbiguous = duplicateFamilies.length > 0 && !accepted.some(hasDirectOwnershipEvidence);
|
|
4077
|
+
const selected = duplicatePackageAmbiguous ? accepted : winners;
|
|
4078
|
+
const unique3 = !duplicatePackageAmbiguous && winners.length === 1 ? winners[0] : void 0;
|
|
4079
|
+
const ambiguityReasons = duplicatePackageAmbiguous ? ["duplicate_package_name_candidates"] : winners.length > 1 ? ["multiple_equal_score_implementation_candidates"] : [];
|
|
4080
|
+
const evidence = implementationEvidence(
|
|
4081
|
+
operation,
|
|
4082
|
+
implementationContext,
|
|
4083
|
+
candidates2,
|
|
4084
|
+
duplicateFamilies,
|
|
4085
|
+
ambiguityReasons
|
|
4086
|
+
);
|
|
4087
|
+
const hintProjection = implementationHintSuggestionProjection(evidence);
|
|
4027
4088
|
return {
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4089
|
+
candidates: candidates2,
|
|
4090
|
+
accepted,
|
|
4091
|
+
selected,
|
|
4092
|
+
unique: unique3,
|
|
4093
|
+
evidence,
|
|
4094
|
+
evidenceWithHints: unique3 ? evidence : {
|
|
4095
|
+
...evidence,
|
|
4096
|
+
implementationHintSuggestions: hintProjection.suggestions,
|
|
4097
|
+
implementationHintSuggestionCount: hintProjection.suggestionCount,
|
|
4098
|
+
shownImplementationHintSuggestionCount: hintProjection.shownSuggestionCount,
|
|
4099
|
+
omittedImplementationHintSuggestionCount: hintProjection.omittedSuggestionCount
|
|
4100
|
+
}
|
|
4031
4101
|
};
|
|
4032
4102
|
}
|
|
4033
|
-
function
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
function compactCandidateScores(candidates) {
|
|
4059
|
-
return candidates.flatMap((candidate) => {
|
|
4060
|
-
const row = objectValue(candidate);
|
|
4061
|
-
if (!row) return [];
|
|
4062
|
-
return [{
|
|
4063
|
-
repo: row.repoName,
|
|
4064
|
-
servicePath: row.servicePath,
|
|
4065
|
-
operationPath: row.operationPath,
|
|
4066
|
-
score: row.score,
|
|
4067
|
-
reasons: Array.isArray(row.reasons) ? row.reasons.filter((reason) => typeof reason === "string") : ["operation_path_match"]
|
|
4068
|
-
}];
|
|
4069
|
-
});
|
|
4070
|
-
}
|
|
4071
|
-
function placeholderKeys(values) {
|
|
4072
|
-
const keys = values.flatMap((value) => [...(value ?? "").matchAll(/\$\{([^}]*)\}/g)].map((match) => (match[1] ?? "").trim()).filter(Boolean));
|
|
4073
|
-
return [...new Set(keys)].sort();
|
|
4074
|
-
}
|
|
4075
|
-
function objectValue(value) {
|
|
4076
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4077
|
-
}
|
|
4078
|
-
function stringValue3(value) {
|
|
4079
|
-
return typeof value === "string" ? value : void 0;
|
|
4103
|
+
function insertImplementationEdge(db, workspaceId, generation, operation, decision, status) {
|
|
4104
|
+
const targetCandidates = status === "unresolved" ? decision.candidates : decision.selected;
|
|
4105
|
+
const targetProjection = boundedImplementationTargetIds(targetCandidates);
|
|
4106
|
+
const targetIds = targetProjection.items;
|
|
4107
|
+
const toId = status === "resolved" ? graphId(decision.unique?.methodId) : targetIds.join(",");
|
|
4108
|
+
const reason = status === "unresolved" ? "No implementation candidate passed policy" : status === "ambiguous" ? "Ambiguous registered handler implementation candidates" : null;
|
|
4109
|
+
db.prepare(`INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,
|
|
4110
|
+
to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation)
|
|
4111
|
+
VALUES(?,?,?,?,?,?,?,?,?,?,?,?)`).run(
|
|
4112
|
+
workspaceId,
|
|
4113
|
+
"OPERATION_IMPLEMENTED_BY_HANDLER",
|
|
4114
|
+
status,
|
|
4115
|
+
"operation",
|
|
4116
|
+
graphId(operation.operationId),
|
|
4117
|
+
status === "resolved" ? "handler_method" : "handler_method_candidates",
|
|
4118
|
+
toId,
|
|
4119
|
+
status === "resolved" ? 0.95 : status === "ambiguous" ? 0.5 : 0,
|
|
4120
|
+
JSON.stringify(boundedImplementationEvidence(
|
|
4121
|
+
decision.evidenceWithHints,
|
|
4122
|
+
targetProjection.totalCount
|
|
4123
|
+
)),
|
|
4124
|
+
0,
|
|
4125
|
+
reason,
|
|
4126
|
+
generation
|
|
4127
|
+
);
|
|
4080
4128
|
}
|
|
4081
|
-
function
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
const ambiguityReasons = duplicatePackageAmbiguous ? ["duplicate_package_name_candidates"] : winners.length > 1 ? ["multiple_equal_score_implementation_candidates"] : [];
|
|
4099
|
-
const evidence = {
|
|
4100
|
-
servicePath: operation.servicePath,
|
|
4101
|
-
operationPath: operation.operationPath,
|
|
4102
|
-
operationName: operation.operationName,
|
|
4103
|
-
modelPackage: { id: operation.modelRepoId, name: operation.modelRepo, packageName: operation.modelPackage },
|
|
4104
|
-
implementationSource: implementationContext.operationId === operation.operationId ? "direct_or_concrete_override" : "inherited_from_base_operation",
|
|
4105
|
-
baseOperationId: operation.baseOperationId,
|
|
4106
|
-
implementationOperationId: implementationContext.operationId,
|
|
4107
|
-
ambiguityReasons,
|
|
4108
|
-
candidateFamilies: duplicateFamilies,
|
|
4109
|
-
candidates: candidates.map((candidate, index) => candidateEvidence(candidate, index + 1))
|
|
4110
|
-
};
|
|
4111
|
-
const evidenceWithHints = unique3 ? evidence : { ...evidence, implementationHintSuggestions: implementationHintSuggestions(evidence) };
|
|
4112
|
-
if (accepted.length === 0) {
|
|
4113
|
-
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", "unresolved", "operation", graphId(operation.operationId), "handler_method_candidates", candidates.map((row) => graphId(row.methodId)).join(","), 0, JSON.stringify(evidenceWithHints), 0, "No implementation candidate passed policy", generation);
|
|
4114
|
-
edgeCount += 1;
|
|
4115
|
-
unresolvedCount += 1;
|
|
4116
|
-
continue;
|
|
4117
|
-
}
|
|
4118
|
-
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "OPERATION_IMPLEMENTED_BY_HANDLER", unique3 ? "resolved" : "ambiguous", "operation", graphId(operation.operationId), unique3 ? "handler_method" : "handler_method_candidates", unique3 ? graphId(unique3.methodId) : selected.map((row) => graphId(row.methodId)).join(","), unique3 ? 0.95 : 0.5, JSON.stringify(evidenceWithHints), 0, unique3 ? null : "Ambiguous registered handler implementation candidates", generation);
|
|
4119
|
-
edgeCount += 1;
|
|
4120
|
-
if (unique3) resolvedCount += 1;
|
|
4121
|
-
else ambiguousCount += 1;
|
|
4122
|
-
}
|
|
4123
|
-
return { edgeCount, resolvedCount, ambiguousCount, unresolvedCount };
|
|
4129
|
+
function implementationEvidence(operation, context, candidates2, duplicateFamilies, ambiguityReasons) {
|
|
4130
|
+
return {
|
|
4131
|
+
servicePath: operation.servicePath,
|
|
4132
|
+
operationPath: operation.operationPath,
|
|
4133
|
+
operationName: operation.operationName,
|
|
4134
|
+
modelPackage: {
|
|
4135
|
+
id: operation.modelRepoId,
|
|
4136
|
+
name: operation.modelRepo,
|
|
4137
|
+
packageName: operation.modelPackage
|
|
4138
|
+
},
|
|
4139
|
+
implementationSource: context.operationId === operation.operationId ? "direct_or_concrete_override" : "inherited_from_base_operation",
|
|
4140
|
+
baseOperationId: operation.baseOperationId,
|
|
4141
|
+
implementationOperationId: context.operationId,
|
|
4142
|
+
ambiguityReasons,
|
|
4143
|
+
candidateFamilies: duplicateFamilies,
|
|
4144
|
+
candidates: candidates2.map((candidate, index) => candidateEvidence(candidate, index + 1))
|
|
4145
|
+
};
|
|
4124
4146
|
}
|
|
4125
4147
|
function implementationContextForOperation(db, operation) {
|
|
4126
4148
|
if (operation.provenance !== "inherited" || !operation.baseOperationId) return operation;
|
|
4127
|
-
const base = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4149
|
+
const base = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,
|
|
4150
|
+
o.operation_name operationName,o.provenance provenance,o.base_operation_id baseOperationId,
|
|
4151
|
+
s.service_path servicePath,s.repo_id modelRepoId,r.name modelRepo,
|
|
4152
|
+
r.package_name modelPackage,r.kind modelKind
|
|
4153
|
+
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id
|
|
4154
|
+
JOIN repositories r ON r.id=s.repo_id WHERE o.id=?`).get(operation.baseOperationId);
|
|
4155
|
+
return base ? {
|
|
4156
|
+
...base,
|
|
4157
|
+
effectiveOperationId: operation.operationId,
|
|
4158
|
+
effectiveServicePath: operation.servicePath,
|
|
4159
|
+
effectiveOperationPath: operation.operationPath
|
|
4160
|
+
} : operation;
|
|
4161
|
+
}
|
|
4162
|
+
function rankedImplementationCandidates(db, workspaceId, operation, recordDiagnostics) {
|
|
4132
4163
|
const rows2 = implementationCandidates(db, workspaceId, operation);
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
).sort((
|
|
4164
|
+
if (recordDiagnostics) recordRegistrationInvariantDiagnostics(
|
|
4165
|
+
db,
|
|
4166
|
+
rows2.filter((row) => !validRegistrationPair(row))
|
|
4167
|
+
);
|
|
4168
|
+
return deduplicateCandidates(rows2.filter(validRegistrationPair).map((row) => scoreImplementationCandidate(row, operation))).sort((left, right) => right.score - left.score || String(left.className).localeCompare(String(right.className)) || left.methodId - right.methodId);
|
|
4138
4169
|
}
|
|
4139
4170
|
function validRegistrationPair(row) {
|
|
4140
4171
|
if (row.registrationHandlerClassId === null || row.registrationHandlerClassId === void 0)
|
|
@@ -4146,7 +4177,7 @@ function registrationPairingStrategy(row) {
|
|
|
4146
4177
|
return "exact_handler_class_id";
|
|
4147
4178
|
if (Number(row.applicationRepoId) === Number(row.handlerRepoId))
|
|
4148
4179
|
return "same_repository_class_name_fallback";
|
|
4149
|
-
const source =
|
|
4180
|
+
const source = stringValue4(row.importSource);
|
|
4150
4181
|
const separator = source?.lastIndexOf("#") ?? -1;
|
|
4151
4182
|
if (!source || separator <= 0) return "unproven";
|
|
4152
4183
|
const moduleName = source.slice(0, separator);
|
|
@@ -4155,23 +4186,20 @@ function registrationPairingStrategy(row) {
|
|
|
4155
4186
|
return moduleName === row.handlerPackage && matchesClass ? "explicit_package_import" : "unproven";
|
|
4156
4187
|
}
|
|
4157
4188
|
function recordRegistrationInvariantDiagnostics(db, rows2) {
|
|
4158
|
-
const insert = db.prepare(`INSERT INTO diagnostics(repo_id,severity,code,message,
|
|
4189
|
+
const insert = db.prepare(`INSERT INTO diagnostics(repo_id,severity,code,message,
|
|
4190
|
+
source_file,source_line)
|
|
4159
4191
|
SELECT ?,'error','handler_registration_class_mismatch',
|
|
4160
4192
|
'Implementation candidate registration did not match its persisted handler class id',?,?
|
|
4161
|
-
WHERE NOT EXISTS (
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
row.applicationRepoId,
|
|
4172
|
-
row.registrationFile,
|
|
4173
|
-
row.registrationLine
|
|
4174
|
-
);
|
|
4193
|
+
WHERE NOT EXISTS (SELECT 1 FROM diagnostics WHERE repo_id=?
|
|
4194
|
+
AND code='handler_registration_class_mismatch' AND source_file=? AND source_line=?)`);
|
|
4195
|
+
for (const row of rows2) insert.run(
|
|
4196
|
+
row.applicationRepoId,
|
|
4197
|
+
row.registrationFile,
|
|
4198
|
+
row.registrationLine,
|
|
4199
|
+
row.applicationRepoId,
|
|
4200
|
+
row.registrationFile,
|
|
4201
|
+
row.registrationLine
|
|
4202
|
+
);
|
|
4175
4203
|
}
|
|
4176
4204
|
function deduplicateCandidates(rows2) {
|
|
4177
4205
|
const merged = /* @__PURE__ */ new Map();
|
|
@@ -4183,7 +4211,10 @@ function deduplicateCandidates(rows2) {
|
|
|
4183
4211
|
merged.set(key, { ...row, registrations: [registration] });
|
|
4184
4212
|
continue;
|
|
4185
4213
|
}
|
|
4186
|
-
existing.registrations = uniqueRegistrations([
|
|
4214
|
+
existing.registrations = uniqueRegistrations([
|
|
4215
|
+
...existing.registrations ?? [],
|
|
4216
|
+
registration
|
|
4217
|
+
]);
|
|
4187
4218
|
existing.score = Math.max(existing.score, row.score);
|
|
4188
4219
|
existing.accepted = existing.accepted || row.accepted;
|
|
4189
4220
|
existing.acceptedReasons = [.../* @__PURE__ */ new Set([...existing.acceptedReasons, ...row.acceptedReasons])];
|
|
@@ -4211,85 +4242,89 @@ function uniqueRegistrations(rows2) {
|
|
|
4211
4242
|
return true;
|
|
4212
4243
|
});
|
|
4213
4244
|
}
|
|
4214
|
-
function duplicatePackageFamilies(
|
|
4245
|
+
function duplicatePackageFamilies(candidates2) {
|
|
4215
4246
|
const byPackage = /* @__PURE__ */ new Map();
|
|
4216
|
-
for (const candidate of
|
|
4217
|
-
const packageName =
|
|
4218
|
-
if (
|
|
4219
|
-
|
|
4247
|
+
for (const candidate of candidates2) {
|
|
4248
|
+
const packageName = stringValue4(candidate.handlerPackage);
|
|
4249
|
+
if (packageName) byPackage.set(packageName, [
|
|
4250
|
+
...byPackage.get(packageName) ?? [],
|
|
4251
|
+
candidate
|
|
4252
|
+
]);
|
|
4220
4253
|
}
|
|
4221
|
-
return [...byPackage.entries()].filter(([, rows2]) => new Set(rows2.map((row) => Number(row.handlerRepoId))).size > 1).map(([packageName, rows2]) => ({
|
|
4254
|
+
return [...byPackage.entries()].filter(([, rows2]) => new Set(rows2.map((row) => Number(row.handlerRepoId))).size > 1).map(([packageName, rows2]) => ({
|
|
4255
|
+
reason: "duplicate_package_name_candidates",
|
|
4256
|
+
packageName,
|
|
4257
|
+
count: rows2.length,
|
|
4258
|
+
repositories: rows2.map((row) => row.handlerRepo).sort()
|
|
4259
|
+
}));
|
|
4222
4260
|
}
|
|
4223
4261
|
function hasDirectOwnershipEvidence(candidate) {
|
|
4224
|
-
return candidate.acceptedReasons.some((reason) =>
|
|
4262
|
+
return candidate.acceptedReasons.some((reason) => [
|
|
4263
|
+
"model package equals registration package",
|
|
4264
|
+
"model package equals handler package",
|
|
4265
|
+
"registration package contains exact local service path"
|
|
4266
|
+
].includes(reason));
|
|
4225
4267
|
}
|
|
4226
4268
|
function implementationCandidates(db, workspaceId, operation) {
|
|
4227
4269
|
const modelRepoGraphId = graphId(operation.modelRepoId);
|
|
4228
4270
|
return db.prepare(`SELECT DISTINCT
|
|
4229
|
-
hm.id methodId,
|
|
4230
|
-
hm.method_name methodName,
|
|
4231
|
-
hm.decorator_value decoratorValue,
|
|
4271
|
+
hm.id methodId,hm.method_name methodName,hm.decorator_value decoratorValue,
|
|
4232
4272
|
hm.decorator_raw_expression decoratorRawExpression,
|
|
4233
|
-
hm.decorator_resolution_json decoratorResolutionJson,
|
|
4234
|
-
hc.
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
hr.
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
hr.registration_line registrationLine,
|
|
4244
|
-
hr.registration_kind registrationKind,
|
|
4245
|
-
hr.import_source importSource,
|
|
4246
|
-
handlerRepo.id handlerRepoId,
|
|
4247
|
-
handlerRepo.name handlerRepo,
|
|
4248
|
-
handlerRepo.package_name handlerPackage,
|
|
4249
|
-
appRepo.name applicationRepo,
|
|
4250
|
-
appRepo.package_name applicationPackage,
|
|
4251
|
-
? modelRepoId,
|
|
4252
|
-
? modelRepo,
|
|
4253
|
-
? modelPackage,
|
|
4254
|
-
? modelKind,
|
|
4255
|
-
? servicePath,
|
|
4256
|
-
? operationPath,
|
|
4257
|
-
? operationName,
|
|
4273
|
+
hm.decorator_resolution_json decoratorResolutionJson,hc.id classId,
|
|
4274
|
+
hc.class_name className,hc.source_file sourceFile,hc.source_line sourceLine,
|
|
4275
|
+
hr.id registrationId,hr.handler_class_id registrationHandlerClassId,
|
|
4276
|
+
hr.class_name registrationClassName,hr.repo_id applicationRepoId,
|
|
4277
|
+
hr.registration_file registrationFile,hr.registration_line registrationLine,
|
|
4278
|
+
hr.registration_kind registrationKind,hr.import_source importSource,
|
|
4279
|
+
handlerRepo.id handlerRepoId,handlerRepo.name handlerRepo,
|
|
4280
|
+
handlerRepo.package_name handlerPackage,appRepo.name applicationRepo,
|
|
4281
|
+
appRepo.package_name applicationPackage,? modelRepoId,? modelRepo,? modelPackage,
|
|
4282
|
+
? modelKind,? servicePath,? operationPath,? operationName,
|
|
4258
4283
|
CASE WHEN appRepo.id=? THEN 1 ELSE 0 END modelIsApplicationRepo,
|
|
4259
4284
|
CASE WHEN handlerRepo.id=? THEN 1 ELSE 0 END modelIsHandlerRepo,
|
|
4260
4285
|
CASE WHEN appRepo.id=handlerRepo.id THEN 1 ELSE 0 END sameRepoRegistration,
|
|
4261
|
-
CASE WHEN EXISTS (SELECT 1 FROM cds_services localService
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
CASE WHEN EXISTS (SELECT 1 FROM
|
|
4265
|
-
|
|
4266
|
-
CASE WHEN EXISTS (SELECT 1 FROM
|
|
4267
|
-
|
|
4268
|
-
|
|
4286
|
+
CASE WHEN EXISTS (SELECT 1 FROM cds_services localService
|
|
4287
|
+
WHERE localService.repo_id=appRepo.id AND localService.service_path=?)
|
|
4288
|
+
THEN 1 ELSE 0 END localServicePathMatch,
|
|
4289
|
+
CASE WHEN EXISTS (SELECT 1 FROM cds_services localService
|
|
4290
|
+
WHERE localService.repo_id=appRepo.id) THEN 1 ELSE 0 END applicationHasLocalServices,
|
|
4291
|
+
CASE WHEN EXISTS (SELECT 1 FROM handler_registrations localReg
|
|
4292
|
+
JOIN handler_classes localClass ON ((localReg.handler_class_id IS NOT NULL
|
|
4293
|
+
AND localClass.id=localReg.handler_class_id) OR (localReg.handler_class_id IS NULL
|
|
4294
|
+
AND localClass.class_name=localReg.class_name AND localClass.repo_id=localReg.repo_id))
|
|
4295
|
+
JOIN handler_methods localMethod ON localMethod.handler_class_id=localClass.id
|
|
4296
|
+
WHERE localReg.repo_id=appRepo.id
|
|
4297
|
+
AND COALESCE(json_extract(localMethod.decorator_resolution_json,'$.handlerKind'),
|
|
4298
|
+
CASE WHEN localMethod.decorator_kind='Event' THEN 'event'
|
|
4299
|
+
WHEN localMethod.decorator_kind IN ('Action','Func','On') THEN 'operation'
|
|
4300
|
+
ELSE 'unsupported' END)='operation'
|
|
4301
|
+
AND COALESCE(json_extract(localMethod.decorator_resolution_json,'$.executable'),
|
|
4302
|
+
CASE WHEN localMethod.decorator_kind IN ('Action','Func','On') THEN 1 ELSE 0 END)=1
|
|
4303
|
+
AND (localMethod.decorator_value=? OR localMethod.decorator_value=?
|
|
4304
|
+
OR localMethod.method_name=? OR localMethod.decorator_raw_expression LIKE ?))
|
|
4305
|
+
THEN 1 ELSE 0 END applicationHasLocalRegistrationForOperation,
|
|
4306
|
+
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep
|
|
4307
|
+
WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved'
|
|
4308
|
+
AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT)
|
|
4309
|
+
AND dep.to_id=?) THEN 1 ELSE 0 END appDependsOnModel,
|
|
4310
|
+
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep
|
|
4311
|
+
WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved'
|
|
4312
|
+
AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT)
|
|
4313
|
+
AND dep.to_id=CAST(handlerRepo.id AS TEXT)) THEN 1 ELSE 0 END appDependsOnHandler,
|
|
4314
|
+
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep
|
|
4315
|
+
WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved'
|
|
4316
|
+
AND dep.from_kind='repo' AND dep.from_id=CAST(handlerRepo.id AS TEXT)
|
|
4317
|
+
AND dep.to_id=?) THEN 1 ELSE 0 END handlerDependsOnModel
|
|
4318
|
+
FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
4269
4319
|
JOIN repositories handlerRepo ON handlerRepo.id=hc.repo_id
|
|
4270
|
-
JOIN handler_registrations hr ON (
|
|
4271
|
-
(hr.handler_class_id IS
|
|
4272
|
-
OR
|
|
4273
|
-
hr.
|
|
4274
|
-
|
|
4275
|
-
(hr.
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
AND substr(hr.import_source,1,instr(hr.import_source,'#')-1)
|
|
4279
|
-
=handlerRepo.package_name
|
|
4280
|
-
AND (
|
|
4281
|
-
substr(hr.import_source,instr(hr.import_source,'#')+1)
|
|
4282
|
-
=hc.class_name
|
|
4283
|
-
OR (
|
|
4284
|
-
substr(hr.import_source,instr(hr.import_source,'#')+1)
|
|
4285
|
-
='default'
|
|
4286
|
-
AND hr.class_name=hc.class_name
|
|
4287
|
-
)
|
|
4288
|
-
)
|
|
4289
|
-
)
|
|
4290
|
-
)
|
|
4291
|
-
)
|
|
4292
|
-
)
|
|
4320
|
+
JOIN handler_registrations hr ON ((hr.handler_class_id IS NOT NULL
|
|
4321
|
+
AND hr.handler_class_id=hc.id) OR (hr.handler_class_id IS NULL AND
|
|
4322
|
+
((hr.class_name=hc.class_name AND hr.repo_id=hc.repo_id) OR
|
|
4323
|
+
(instr(hr.import_source,'#')>1 AND substr(hr.import_source,1,
|
|
4324
|
+
instr(hr.import_source,'#')-1)=handlerRepo.package_name AND
|
|
4325
|
+
(substr(hr.import_source,instr(hr.import_source,'#')+1)=hc.class_name OR
|
|
4326
|
+
(substr(hr.import_source,instr(hr.import_source,'#')+1)='default'
|
|
4327
|
+
AND hr.class_name=hc.class_name))))))
|
|
4293
4328
|
JOIN repositories appRepo ON appRepo.id=hr.repo_id
|
|
4294
4329
|
WHERE appRepo.workspace_id=?
|
|
4295
4330
|
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
|
|
@@ -4297,9 +4332,9 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
4297
4332
|
WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
|
|
4298
4333
|
ELSE 'unsupported' END)='operation'
|
|
4299
4334
|
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
|
|
4300
|
-
CASE WHEN hm.decorator_kind IN ('Action','Func','On')
|
|
4301
|
-
|
|
4302
|
-
|
|
4335
|
+
CASE WHEN hm.decorator_kind IN ('Action','Func','On') THEN 1 ELSE 0 END)=1
|
|
4336
|
+
AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=?
|
|
4337
|
+
OR hm.decorator_raw_expression LIKE ?)`).all(
|
|
4303
4338
|
operation.modelRepoId,
|
|
4304
4339
|
operation.modelRepo,
|
|
4305
4340
|
operation.modelPackage,
|
|
@@ -4313,143 +4348,775 @@ function implementationCandidates(db, workspaceId, operation) {
|
|
|
4313
4348
|
normalizedOperation(String(operation.operationPath ?? "")),
|
|
4314
4349
|
operation.operationName,
|
|
4315
4350
|
operation.operationName,
|
|
4316
|
-
`%${
|
|
4351
|
+
`%${upperFirst2(normalizedOperation(String(operation.operationPath ?? operation.operationName ?? "")))}%`,
|
|
4317
4352
|
modelRepoGraphId,
|
|
4318
4353
|
modelRepoGraphId,
|
|
4319
4354
|
workspaceId,
|
|
4320
4355
|
normalizedOperation(String(operation.operationPath ?? "")),
|
|
4321
4356
|
operation.operationName,
|
|
4322
4357
|
operation.operationName,
|
|
4323
|
-
`%${
|
|
4358
|
+
`%${upperFirst2(normalizedOperation(String(operation.operationPath ?? operation.operationName ?? "")))}%`
|
|
4324
4359
|
);
|
|
4325
4360
|
}
|
|
4326
4361
|
function scoreImplementationCandidate(row, operation) {
|
|
4327
4362
|
const acceptedReasons = [];
|
|
4328
4363
|
const rejectedReasons = [];
|
|
4329
|
-
|
|
4364
|
+
const methodSignal = implementationMethodSignal(row, operation);
|
|
4365
|
+
acceptedReasons.push(...methodSignal.acceptedReasons);
|
|
4366
|
+
rejectedReasons.push(...methodSignal.rejectedReasons);
|
|
4367
|
+
const signals = implementationOwnershipSignals(row, methodSignal.matches);
|
|
4368
|
+
acceptedReasons.push(...signals.acceptedReasons);
|
|
4369
|
+
rejectedReasons.push(...signals.rejectedReasons);
|
|
4370
|
+
const accepted = methodSignal.matches && !methodSignal.contradicted && !signals.contradicted && signals.hasOwnership;
|
|
4371
|
+
if (!accepted && rejectedReasons.length === 0)
|
|
4372
|
+
rejectedReasons.push("candidate did not meet implementation ownership policy");
|
|
4373
|
+
return {
|
|
4374
|
+
...row,
|
|
4375
|
+
methodId: Number(row.methodId),
|
|
4376
|
+
score: signals.score,
|
|
4377
|
+
accepted,
|
|
4378
|
+
acceptedReasons,
|
|
4379
|
+
rejectedReasons
|
|
4380
|
+
};
|
|
4381
|
+
}
|
|
4382
|
+
function implementationOwnershipSignals(row, methodMatches) {
|
|
4383
|
+
const acceptedReasons = [];
|
|
4384
|
+
const rejectedReasons = [];
|
|
4330
4385
|
const modelIsApplicationRepo = flag(row.modelIsApplicationRepo);
|
|
4331
4386
|
const modelIsHandlerRepo = flag(row.modelIsHandlerRepo);
|
|
4332
4387
|
const localServicePathMatch = flag(row.localServicePathMatch);
|
|
4333
4388
|
const applicationHasLocalServices = flag(row.applicationHasLocalServices);
|
|
4334
4389
|
const appDependsOnModel = flag(row.appDependsOnModel);
|
|
4335
|
-
const applicationHasLocalRegistrationForOperation = flag(row.applicationHasLocalRegistrationForOperation);
|
|
4336
4390
|
const appDependsOnHandler = flag(row.appDependsOnHandler);
|
|
4337
4391
|
const handlerDependsOnModel = flag(row.handlerDependsOnModel);
|
|
4338
|
-
const importSource =
|
|
4392
|
+
const importSource = Boolean(stringValue4(row.importSource));
|
|
4339
4393
|
const sameRepoRegistration = flag(row.sameRepoRegistration);
|
|
4340
|
-
const modelOriented = row.modelKind === "cap-db-model" || !applicationHasLocalRegistrationForOperation;
|
|
4341
|
-
const
|
|
4342
|
-
const
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
if (localServicePathMatch)
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
acceptedReasons
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4394
|
+
const modelOriented = row.modelKind === "cap-db-model" || !flag(row.applicationHasLocalRegistrationForOperation);
|
|
4395
|
+
const helperOwned = modelOriented && methodMatches && sameRepoRegistration && importSource && !applicationHasLocalServices && !modelIsApplicationRepo && !modelIsHandlerRepo && !localServicePathMatch && !appDependsOnModel && !appDependsOnHandler && !handlerDependsOnModel;
|
|
4396
|
+
const score = ownershipScore({
|
|
4397
|
+
modelIsApplicationRepo,
|
|
4398
|
+
modelIsHandlerRepo,
|
|
4399
|
+
localServicePathMatch,
|
|
4400
|
+
appDependsOnModel,
|
|
4401
|
+
appDependsOnHandler,
|
|
4402
|
+
handlerDependsOnModel,
|
|
4403
|
+
helperOwned,
|
|
4404
|
+
importSource
|
|
4405
|
+
}, acceptedReasons);
|
|
4406
|
+
const hasOwnership = modelIsApplicationRepo || modelIsHandlerRepo;
|
|
4407
|
+
const hasCrossPackage = appDependsOnModel && (modelIsHandlerRepo || appDependsOnHandler || !importSource);
|
|
4408
|
+
const contradicted = applicationHasLocalServices && !localServicePathMatch && !appDependsOnModel && !hasOwnership;
|
|
4409
|
+
if (applicationHasLocalServices && !localServicePathMatch && !appDependsOnModel && !modelIsApplicationRepo)
|
|
4410
|
+
rejectedReasons.push(`registration package has local services but none match ${String(row.servicePath ?? "")}`);
|
|
4411
|
+
if (!hasOwnership && !localServicePathMatch && !hasCrossPackage && !helperOwned)
|
|
4412
|
+
rejectedReasons.push("missing direct ownership, exact local service path, or validated cross-package dependency evidence");
|
|
4413
|
+
return {
|
|
4414
|
+
score,
|
|
4415
|
+
hasOwnership: hasOwnership || localServicePathMatch || hasCrossPackage || handlerDependsOnModel || helperOwned,
|
|
4416
|
+
contradicted,
|
|
4417
|
+
acceptedReasons,
|
|
4418
|
+
rejectedReasons
|
|
4419
|
+
};
|
|
4420
|
+
}
|
|
4421
|
+
function ownershipScore(signals, acceptedReasons) {
|
|
4422
|
+
let score = 0;
|
|
4423
|
+
const values = [
|
|
4424
|
+
["modelIsApplicationRepo", 100, "model package equals registration package"],
|
|
4425
|
+
["modelIsHandlerRepo", 100, "model package equals handler package"],
|
|
4426
|
+
["localServicePathMatch", 80, "registration package contains exact local service path"],
|
|
4427
|
+
["appDependsOnModel", 70, "registration package depends on model package"],
|
|
4428
|
+
["appDependsOnHandler", 30, "registration package depends on handler package"],
|
|
4429
|
+
["handlerDependsOnModel", 20, "handler package depends on model package"],
|
|
4430
|
+
["helperOwned", 60, "unique registered helper implementation for model-only operation"],
|
|
4431
|
+
["importSource", 10, "registration imports handler class"]
|
|
4432
|
+
];
|
|
4433
|
+
for (const [key, amount, reason] of values) {
|
|
4434
|
+
if (!signals[key]) continue;
|
|
4435
|
+
score += amount;
|
|
4436
|
+
acceptedReasons.push(reason);
|
|
4437
|
+
}
|
|
4438
|
+
return score;
|
|
4439
|
+
}
|
|
4440
|
+
function candidateEvidence(candidate, rank) {
|
|
4441
|
+
return {
|
|
4442
|
+
rank,
|
|
4443
|
+
score: candidate.score,
|
|
4444
|
+
accepted: candidate.accepted,
|
|
4445
|
+
acceptedReasons: candidate.acceptedReasons,
|
|
4446
|
+
rejectedReasons: candidate.rejectedReasons,
|
|
4447
|
+
methodId: candidate.methodId,
|
|
4448
|
+
classId: candidate.classId,
|
|
4449
|
+
className: candidate.className,
|
|
4450
|
+
sourceFile: candidate.sourceFile,
|
|
4451
|
+
sourceLine: candidate.sourceLine,
|
|
4452
|
+
decoratorResolution: objectJson(candidate.decoratorResolutionJson),
|
|
4453
|
+
registration: registrationEvidence(candidate),
|
|
4454
|
+
registrations: candidate.registrations ?? [],
|
|
4455
|
+
registrationPairing: {
|
|
4456
|
+
strategy: registrationPairingStrategy(candidate),
|
|
4457
|
+
registrationId: candidate.registrationId,
|
|
4458
|
+
registrationHandlerClassId: candidate.registrationHandlerClassId,
|
|
4459
|
+
candidateHandlerClassId: candidate.classId,
|
|
4460
|
+
invariantStatus: validRegistrationPair(candidate) ? "valid" : "invalid"
|
|
4461
|
+
},
|
|
4462
|
+
applicationPackage: {
|
|
4463
|
+
id: candidate.applicationRepoId,
|
|
4464
|
+
name: candidate.applicationRepo,
|
|
4465
|
+
packageName: candidate.applicationPackage
|
|
4466
|
+
},
|
|
4467
|
+
handlerPackage: {
|
|
4468
|
+
id: candidate.handlerRepoId,
|
|
4469
|
+
name: candidate.handlerRepo,
|
|
4470
|
+
packageName: candidate.handlerPackage
|
|
4471
|
+
},
|
|
4472
|
+
modelPackage: {
|
|
4473
|
+
id: candidate.modelRepoId,
|
|
4474
|
+
name: candidate.modelRepo,
|
|
4475
|
+
packageName: candidate.modelPackage
|
|
4476
|
+
},
|
|
4477
|
+
servicePath: candidate.servicePath,
|
|
4478
|
+
operationPath: candidate.operationPath,
|
|
4479
|
+
operationName: candidate.operationName,
|
|
4480
|
+
signals: {
|
|
4481
|
+
directOwnership: {
|
|
4482
|
+
modelIsApplicationRepo: flag(candidate.modelIsApplicationRepo),
|
|
4483
|
+
modelIsHandlerRepo: flag(candidate.modelIsHandlerRepo)
|
|
4484
|
+
},
|
|
4485
|
+
localServicePathMatch: flag(candidate.localServicePathMatch),
|
|
4486
|
+
applicationHasLocalServices: flag(candidate.applicationHasLocalServices),
|
|
4487
|
+
applicationHasLocalRegistrationForOperation: flag(candidate.applicationHasLocalRegistrationForOperation),
|
|
4488
|
+
appDependsOnModel: flag(candidate.appDependsOnModel),
|
|
4489
|
+
appDependsOnHandler: flag(candidate.appDependsOnHandler),
|
|
4490
|
+
handlerDependsOnModel: flag(candidate.handlerDependsOnModel),
|
|
4491
|
+
sameRepoRegistration: flag(candidate.sameRepoRegistration)
|
|
4492
|
+
}
|
|
4493
|
+
};
|
|
4494
|
+
}
|
|
4495
|
+
function implementationMethodSignal(row, operation) {
|
|
4496
|
+
const resolution = objectJson(row.decoratorResolutionJson) ?? {};
|
|
4497
|
+
if (resolution.handlerKind && resolution.handlerKind !== "operation")
|
|
4498
|
+
return { matches: false, contradicted: true, acceptedReasons: [], rejectedReasons: ["non_operation_handler_kind"] };
|
|
4499
|
+
if (resolution.executable === false)
|
|
4500
|
+
return { matches: false, contradicted: true, acceptedReasons: [], rejectedReasons: ["handler_method_not_executable"] };
|
|
4501
|
+
const operationName = normalizedOperationName(String(
|
|
4502
|
+
operation.operationPath ?? operation.operationName ?? ""
|
|
4503
|
+
));
|
|
4504
|
+
const decorator = normalizeDecoratorOperationSignal(
|
|
4505
|
+
stringValue4(row.decoratorValue),
|
|
4506
|
+
stringValue4(row.decoratorRawExpression),
|
|
4507
|
+
operationName
|
|
4508
|
+
);
|
|
4509
|
+
if (decorator.status === "resolved" && decorator.operationName === operationName)
|
|
4510
|
+
return { matches: true, contradicted: false, acceptedReasons: ["decorator targets operation"], rejectedReasons: [] };
|
|
4511
|
+
if (decorator.status === "resolved")
|
|
4512
|
+
return { matches: false, contradicted: true, acceptedReasons: [], rejectedReasons: ["method_name_matches_but_decorator_targets_different_operation"] };
|
|
4513
|
+
return String(row.methodName ?? "") === operationName ? { matches: true, contradicted: false, acceptedReasons: ["method name fallback matched operation"], rejectedReasons: [] } : { matches: false, contradicted: false, acceptedReasons: [], rejectedReasons: ["method name does not match operation"] };
|
|
4514
|
+
}
|
|
4515
|
+
function objectJson(value) {
|
|
4516
|
+
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
4517
|
+
try {
|
|
4518
|
+
const parsed = JSON.parse(value);
|
|
4519
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
4520
|
+
} catch {
|
|
4521
|
+
return void 0;
|
|
4522
|
+
}
|
|
4523
|
+
}
|
|
4524
|
+
function upperFirst2(value) {
|
|
4525
|
+
return value ? `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}` : value;
|
|
4526
|
+
}
|
|
4527
|
+
function flag(value) {
|
|
4528
|
+
return Boolean(Number(value ?? 0));
|
|
4529
|
+
}
|
|
4530
|
+
function graphId(value) {
|
|
4531
|
+
return String(value ?? "");
|
|
4532
|
+
}
|
|
4533
|
+
function normalizedOperation(value) {
|
|
4534
|
+
return value.startsWith("/") ? value.slice(1) : value;
|
|
4535
|
+
}
|
|
4536
|
+
function stringValue4(value) {
|
|
4537
|
+
return typeof value === "string" ? value : void 0;
|
|
4538
|
+
}
|
|
4539
|
+
function numberValue2(value) {
|
|
4540
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
4541
|
+
}
|
|
4542
|
+
|
|
4543
|
+
// src/linker/service-resolver.ts
|
|
4544
|
+
function rows(db, operationPath, workspaceId) {
|
|
4545
|
+
const names = operationLookupNames(operationPath);
|
|
4546
|
+
const result = db.prepare(
|
|
4547
|
+
`SELECT o.id operationId,r.id repoId,r.name repoName,r.package_name packageName,s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,o.source_line sourceLine,0 score
|
|
4548
|
+
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
|
|
4549
|
+
WHERE (? IS NULL OR r.workspace_id=?) AND (o.operation_path IN (?,?) OR o.operation_name IN (?,?)) ORDER BY r.name,s.service_path,o.operation_name`
|
|
4550
|
+
).all(
|
|
4551
|
+
workspaceId,
|
|
4552
|
+
workspaceId,
|
|
4553
|
+
names.path,
|
|
4554
|
+
names.simplePath,
|
|
4555
|
+
names.name,
|
|
4556
|
+
names.simpleName
|
|
4557
|
+
);
|
|
4558
|
+
return result.map((row) => ({
|
|
4559
|
+
...row,
|
|
4560
|
+
score: Number(row.score ?? 0),
|
|
4561
|
+
reasons: []
|
|
4562
|
+
}));
|
|
4563
|
+
}
|
|
4564
|
+
function operationLookupNames(operationPath) {
|
|
4565
|
+
const name = operationPath.replace(/^\//, "");
|
|
4566
|
+
const simpleName = name.split(".").at(-1) ?? name;
|
|
4567
|
+
return { path: operationPath, simplePath: `/${simpleName}`, name, simpleName };
|
|
4568
|
+
}
|
|
4569
|
+
function operationMatches(candidate, operationPath) {
|
|
4570
|
+
if (!operationPath) return false;
|
|
4571
|
+
const names = operationLookupNames(operationPath);
|
|
4572
|
+
return candidate.operationPath === names.path || candidate.operationPath === names.simplePath || candidate.operationName === names.name || candidate.operationName === names.simpleName;
|
|
4573
|
+
}
|
|
4574
|
+
function resolveOperation(db, signals, workspaceId) {
|
|
4575
|
+
const missing = [signals.servicePath, signals.alias, signals.destination, signals.operationPath].flatMap((value) => [...(value ?? "").matchAll(/\$\{([^}]*)\}/g)].map((match) => (match[1] ?? "").trim())).filter(Boolean);
|
|
4576
|
+
if (missing.length > 0)
|
|
4577
|
+
return {
|
|
4578
|
+
status: "dynamic",
|
|
4579
|
+
candidates: signals.operationPath ? rows(db, signals.operationPath, workspaceId).map((candidate) => ({
|
|
4580
|
+
...candidate,
|
|
4581
|
+
score: 0.2,
|
|
4582
|
+
reasons: ["operation_path_match"]
|
|
4583
|
+
})) : [],
|
|
4584
|
+
reasons: [...new Set(missing)].map((name) => `missing_variable:${name}`)
|
|
4585
|
+
};
|
|
4586
|
+
if (!signals.operationPath)
|
|
4587
|
+
return {
|
|
4588
|
+
status: "unresolved",
|
|
4589
|
+
candidates: [],
|
|
4590
|
+
reasons: ["missing_operation_path"]
|
|
4591
|
+
};
|
|
4592
|
+
const allCandidates = rows(db, signals.operationPath, workspaceId).map((c) => ({
|
|
4593
|
+
...c,
|
|
4594
|
+
score: 0.2,
|
|
4595
|
+
reasons: ["operation_path_match"]
|
|
4596
|
+
}));
|
|
4597
|
+
let candidates2 = allCandidates.filter((c) => matchesLocalRepo(db, c.operationId, signals.repoId));
|
|
4598
|
+
if (candidates2.length === 0 && signals.repoId !== void 0 && signals.serviceName) {
|
|
4599
|
+
candidates2 = implementationContextCandidates(db, allCandidates, signals.repoId, signals.serviceName);
|
|
4600
|
+
if (candidates2.length === 0)
|
|
4601
|
+
return {
|
|
4602
|
+
status: "unresolved",
|
|
4603
|
+
candidates: allCandidates.filter((c) => serviceMatches(c, signals.serviceName)),
|
|
4604
|
+
reasons: allCandidates.some((c) => serviceMatches(c, signals.serviceName)) ? ["local_service_candidate_without_caller_ownership"] : ["no_operation_candidates"]
|
|
4605
|
+
};
|
|
4606
|
+
}
|
|
4607
|
+
if (candidates2.length === 0)
|
|
4608
|
+
return {
|
|
4609
|
+
status: "unresolved",
|
|
4610
|
+
candidates: [],
|
|
4611
|
+
reasons: ["no_operation_candidates"]
|
|
4612
|
+
};
|
|
4613
|
+
const hasStrongSignal = Boolean(
|
|
4614
|
+
signals.servicePath || signals.serviceName || signals.alias || signals.destination || signals.hasExplicitOverride
|
|
4615
|
+
);
|
|
4616
|
+
for (const c of candidates2) {
|
|
4617
|
+
if (signals.servicePath && c.servicePath === signals.servicePath) {
|
|
4618
|
+
c.score += 0.75;
|
|
4619
|
+
c.reasons.push("exact_service_path");
|
|
4620
|
+
}
|
|
4621
|
+
if (signals.servicePath && c.servicePath !== signals.servicePath) {
|
|
4622
|
+
c.score -= 0.1;
|
|
4623
|
+
c.reasons.push("service_path_mismatch");
|
|
4624
|
+
}
|
|
4625
|
+
if (signals.serviceName) {
|
|
4626
|
+
const simple = signals.serviceName.split(".").at(-1) ?? signals.serviceName;
|
|
4627
|
+
if (c.qualifiedName === signals.serviceName) {
|
|
4628
|
+
c.score += 0.8;
|
|
4629
|
+
c.reasons.push("exact_local_qualified_service_name");
|
|
4630
|
+
} else if (c.serviceName === signals.serviceName || c.serviceName === simple) {
|
|
4631
|
+
c.score += 0.75;
|
|
4632
|
+
c.reasons.push("exact_local_simple_service_name");
|
|
4633
|
+
} else if (c.servicePath === signals.serviceName || c.servicePath === `/${signals.serviceName}` || c.servicePath === `/${simple}`) {
|
|
4634
|
+
c.score += 0.7;
|
|
4635
|
+
c.reasons.push("exact_local_service_path");
|
|
4636
|
+
} else if (c.servicePath.endsWith(`/${simple}`)) {
|
|
4637
|
+
c.score += candidates2.filter((candidate) => candidate.servicePath.endsWith(`/${simple}`)).length === 1 ? 0.65 : 0.2;
|
|
4638
|
+
c.reasons.push("suffix_local_service_path");
|
|
4639
|
+
} else c.reasons.push("local_service_name_mismatch");
|
|
4640
|
+
}
|
|
4641
|
+
if (signals.hasExplicitOverride) {
|
|
4642
|
+
c.score += 0.2;
|
|
4643
|
+
c.reasons.push(signals.repoId !== void 0 ? "explicit_local_service_call" : "explicit_dynamic_override");
|
|
4644
|
+
}
|
|
4645
|
+
if (signals.repoId !== void 0 && candidates2.length === 1 && signals.serviceName && c.reasons.includes("local_service_name_mismatch") && operationMatches(c, signals.operationPath)) {
|
|
4646
|
+
c.score = Math.max(c.score, 0.9);
|
|
4647
|
+
c.reasons.push("same_repo_unique_operation_path_with_lookup_mismatch");
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4650
|
+
for (const c of candidates2) c.score = Math.max(0, Math.min(1, c.score));
|
|
4651
|
+
candidates2.sort(
|
|
4652
|
+
(a, b) => b.score - a.score || a.repoName.localeCompare(b.repoName)
|
|
4653
|
+
);
|
|
4654
|
+
const best = candidates2[0];
|
|
4655
|
+
const second = candidates2[1];
|
|
4656
|
+
if (signals.isDynamic && !signals.hasExplicitOverride && !signals.servicePath)
|
|
4657
|
+
return {
|
|
4658
|
+
status: "dynamic",
|
|
4659
|
+
candidates: candidates2,
|
|
4660
|
+
reasons: ["dynamic_target_without_override"]
|
|
4661
|
+
};
|
|
4662
|
+
if (!hasStrongSignal)
|
|
4663
|
+
return {
|
|
4664
|
+
status: candidates2.length > 1 ? "ambiguous" : "unresolved",
|
|
4665
|
+
candidates: candidates2,
|
|
4666
|
+
reasons: ["operation_path_only_has_no_strong_target_signal"]
|
|
4667
|
+
};
|
|
4668
|
+
if (best && best.score >= 0.9 && (best.servicePath === signals.servicePath || Boolean(signals.serviceName && (!best.reasons.includes("local_service_name_mismatch") || best.reasons.includes("same_repo_unique_operation_path_with_lookup_mismatch")))) && operationMatches(best, signals.operationPath) && (!second || best.score - second.score >= 0.25))
|
|
4669
|
+
return {
|
|
4670
|
+
status: "resolved",
|
|
4671
|
+
target: best,
|
|
4672
|
+
candidates: candidates2,
|
|
4673
|
+
reasons: best.reasons
|
|
4674
|
+
};
|
|
4675
|
+
return {
|
|
4676
|
+
status: candidates2.length > 1 ? "ambiguous" : "unresolved",
|
|
4677
|
+
candidates: candidates2,
|
|
4678
|
+
reasons: ["candidate_score_below_resolution_threshold"]
|
|
4679
|
+
};
|
|
4680
|
+
}
|
|
4681
|
+
function serviceMatches(candidate, serviceName) {
|
|
4682
|
+
if (!serviceName) return false;
|
|
4683
|
+
const simple = serviceName.split(".").at(-1) ?? serviceName;
|
|
4684
|
+
return candidate.qualifiedName === serviceName || candidate.serviceName === serviceName || candidate.serviceName === simple || candidate.servicePath === serviceName || candidate.servicePath === `/${serviceName}` || candidate.servicePath === `/${simple}` || candidate.servicePath.endsWith(`/${simple}`);
|
|
4685
|
+
}
|
|
4686
|
+
function implementationContextCandidates(db, candidates2, callerRepoId, serviceName) {
|
|
4687
|
+
const matching = candidates2.filter((candidate) => serviceMatches(candidate, serviceName));
|
|
4688
|
+
const owned = matching.map((candidate) => ownershipReason(db, candidate, callerRepoId)).filter((item) => Boolean(item));
|
|
4689
|
+
if (owned.length === 0) return [];
|
|
4690
|
+
const direct = owned.filter((item) => item.reason !== "caller_depends_on_model_package");
|
|
4691
|
+
const chosen = direct.length > 0 ? direct : owned.length === 1 ? owned : [];
|
|
4692
|
+
return chosen.map((item) => ({ ...item.candidate, score: 0.95, reasons: [...item.candidate.reasons, "implementation_context_caller_ownership", item.reason] }));
|
|
4693
|
+
}
|
|
4694
|
+
function ownershipReason(db, candidate, callerRepoId) {
|
|
4695
|
+
const edge = db.prepare("SELECT status,evidence_json,to_id 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 LIMIT 1").get(String(candidate.operationId));
|
|
4696
|
+
if (edge?.status === "resolved") {
|
|
4697
|
+
const row = db.prepare("SELECT hc.repo_id repoId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id WHERE hm.id=?").get(edge.to_id);
|
|
4698
|
+
if (row?.repoId === callerRepoId) return { candidate, reason: "resolved_implementation_handler_repo_matches_caller" };
|
|
4699
|
+
}
|
|
4700
|
+
if (edge?.evidence_json) {
|
|
4701
|
+
const stored = parsedRecord(edge.evidence_json);
|
|
4702
|
+
const evidence = canonicalImplementationEvidence(db, candidate.operationId) ?? stored;
|
|
4703
|
+
const hit = implementationEvidenceCandidates(evidence).find((item) => item.accepted && (item.handlerRepoId === callerRepoId || item.applicationRepoId === callerRepoId));
|
|
4704
|
+
if (hit) return { candidate, reason: edge.status === "ambiguous" ? "ambiguous_implementation_candidate_repo_matches_caller" : "registration_package_matches_caller" };
|
|
4705
|
+
}
|
|
4706
|
+
const dep = db.prepare("SELECT 1 FROM graph_edges WHERE edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND status='resolved' AND from_kind='repo' AND from_id=? AND to_id=?").get(String(callerRepoId), String(candidate.repoId));
|
|
4707
|
+
if (dep) return { candidate, reason: "caller_depends_on_model_package" };
|
|
4708
|
+
return void 0;
|
|
4709
|
+
}
|
|
4710
|
+
function implementationEvidenceCandidates(evidence) {
|
|
4711
|
+
const candidates2 = evidence.candidates;
|
|
4712
|
+
if (!Array.isArray(candidates2)) return [];
|
|
4713
|
+
return candidates2.flatMap((candidate) => {
|
|
4714
|
+
if (!isRecord2(candidate)) return [];
|
|
4715
|
+
const row = candidate;
|
|
4716
|
+
const handler = recordValue(row.handlerPackage);
|
|
4717
|
+
const application = recordValue(row.applicationPackage);
|
|
4718
|
+
return [{
|
|
4719
|
+
accepted: row.accepted === true,
|
|
4720
|
+
handlerRepoId: numberValue3(handler.id),
|
|
4721
|
+
applicationRepoId: numberValue3(application.id)
|
|
4722
|
+
}];
|
|
4723
|
+
});
|
|
4724
|
+
}
|
|
4725
|
+
function recordValue(value) {
|
|
4726
|
+
return isRecord2(value) ? value : {};
|
|
4727
|
+
}
|
|
4728
|
+
function parsedRecord(value) {
|
|
4729
|
+
try {
|
|
4730
|
+
const parsed = JSON.parse(value);
|
|
4731
|
+
return recordValue(parsed);
|
|
4732
|
+
} catch {
|
|
4733
|
+
return {};
|
|
4734
|
+
}
|
|
4735
|
+
}
|
|
4736
|
+
function isRecord2(value) {
|
|
4737
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
4738
|
+
}
|
|
4739
|
+
function numberValue3(value) {
|
|
4740
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
4741
|
+
}
|
|
4742
|
+
function matchesLocalRepo(db, operationId, repoId) {
|
|
4743
|
+
if (repoId === void 0) return true;
|
|
4744
|
+
const row = db.prepare("SELECT s.repo_id repoId FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?").get(operationId);
|
|
4745
|
+
return row?.repoId === repoId;
|
|
4746
|
+
}
|
|
4747
|
+
|
|
4748
|
+
// src/linker/helper-package-linker.ts
|
|
4749
|
+
function normalizeName(value) {
|
|
4750
|
+
return value.toLowerCase().replace(/^@[^/]+\//, "").replace(/[^a-z0-9]+/g, "");
|
|
4751
|
+
}
|
|
4752
|
+
function candidatesForDependency(repos, dep, sourceId) {
|
|
4753
|
+
const exact = repos.filter((repo) => repo.id !== sourceId && repo.package_name === dep);
|
|
4754
|
+
if (exact.length > 0) return { candidates: exact, strategy: "exact_package_name" };
|
|
4755
|
+
const normalized = normalizeName(dep);
|
|
4756
|
+
return { candidates: repos.filter((repo) => repo.id !== sourceId && normalizeName(repo.name) === normalized), strategy: "normalized_directory" };
|
|
4757
|
+
}
|
|
4758
|
+
function linkHelperPackages(db, workspaceId, generation) {
|
|
4759
|
+
const repos = db.prepare("SELECT id,name,package_name,dependencies_json FROM repositories WHERE workspace_id=?").all(workspaceId);
|
|
4760
|
+
const summary = { edgeCount: 0, resolvedCount: 0, ambiguousCount: 0 };
|
|
4761
|
+
for (const repo of repos) {
|
|
4762
|
+
const deps = JSON.parse(repo.dependencies_json);
|
|
4763
|
+
for (const dep of Object.keys(deps)) {
|
|
4764
|
+
const result = candidatesForDependency(repos, dep, repo.id);
|
|
4765
|
+
if (result.candidates.length === 0) continue;
|
|
4766
|
+
const status = result.candidates.length === 1 ? "resolved" : "ambiguous";
|
|
4767
|
+
const helper = status === "resolved" ? result.candidates[0] : void 0;
|
|
4768
|
+
const projection = projectBounded(result.candidates, (left, right) => left.name.localeCompare(right.name) || String(left.package_name ?? "").localeCompare(String(right.package_name ?? "")) || left.id - right.id);
|
|
4769
|
+
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(
|
|
4770
|
+
workspaceId,
|
|
4771
|
+
"REPO_IMPORTS_HELPER_PACKAGE",
|
|
4772
|
+
status,
|
|
4773
|
+
"repo",
|
|
4774
|
+
String(repo.id),
|
|
4775
|
+
helper ? "repo" : "repo_candidates",
|
|
4776
|
+
helper ? String(helper.id) : projection.items.map((candidate) => candidate.id).join(","),
|
|
4777
|
+
helper ? 1 : 0.5,
|
|
4778
|
+
JSON.stringify({
|
|
4779
|
+
dependency: dep,
|
|
4780
|
+
candidates: projection.items.map((candidate) => ({
|
|
4781
|
+
id: candidate.id,
|
|
4782
|
+
name: candidate.name,
|
|
4783
|
+
packageName: candidate.package_name
|
|
4784
|
+
})),
|
|
4785
|
+
candidateCount: projection.totalCount,
|
|
4786
|
+
shownCandidateCount: projection.shownCount,
|
|
4787
|
+
omittedCandidateCount: projection.omittedCount,
|
|
4788
|
+
match: result.strategy
|
|
4789
|
+
}),
|
|
4790
|
+
0,
|
|
4791
|
+
helper ? null : "Ambiguous dependency package candidates",
|
|
4792
|
+
generation
|
|
4793
|
+
);
|
|
4794
|
+
summary.edgeCount += 1;
|
|
4795
|
+
if (helper) summary.resolvedCount += 1;
|
|
4796
|
+
else summary.ambiguousCount += 1;
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4799
|
+
return summary;
|
|
4800
|
+
}
|
|
4801
|
+
|
|
4802
|
+
// src/linker/002-call-evidence.ts
|
|
4803
|
+
function linkedCallEvidence(call, resolution, servicePath, operationPath, destination, normalized, intent) {
|
|
4804
|
+
const candidates2 = boundedCallCandidates(resolution.candidates);
|
|
4805
|
+
return {
|
|
4806
|
+
...callLocationEvidence(call),
|
|
4807
|
+
...selectedBindingEvidence(call),
|
|
4808
|
+
...routingEvidence(call, servicePath, operationPath, destination, normalized, intent),
|
|
4809
|
+
...candidateEvidence2(candidates2, resolution),
|
|
4810
|
+
outboundEvidence: boundCandidateLikeEvidence(objectJson2(call.evidence_json) ?? {}),
|
|
4811
|
+
analysisCompleteness: call.unresolved_reason ? "partial" : "complete",
|
|
4812
|
+
parserWarning: call.unresolved_reason ? { code: "parser_warning", message: call.unresolved_reason } : void 0
|
|
4813
|
+
};
|
|
4814
|
+
}
|
|
4815
|
+
function ambiguousPathCandidates(pathAnalysis) {
|
|
4816
|
+
const values = Array.isArray(pathAnalysis.candidateRawPaths) ? pathAnalysis.candidateRawPaths.filter((value) => typeof value === "string") : [];
|
|
4817
|
+
return projectBounded(values, (left, right) => left.localeCompare(right));
|
|
4818
|
+
}
|
|
4819
|
+
function objectJson2(value) {
|
|
4820
|
+
const parsed = parseJson(value);
|
|
4821
|
+
return isRecord3(parsed) ? parsed : void 0;
|
|
4822
|
+
}
|
|
4823
|
+
function objectValue(value) {
|
|
4824
|
+
return isRecord3(value) ? value : void 0;
|
|
4825
|
+
}
|
|
4826
|
+
function callLocationEvidence(call) {
|
|
4827
|
+
return {
|
|
4828
|
+
sourceFile: call.source_file,
|
|
4829
|
+
sourceLine: call.source_line,
|
|
4830
|
+
file: call.source_file,
|
|
4831
|
+
line: call.source_line,
|
|
4832
|
+
callId: call.id,
|
|
4833
|
+
repo: call.repoName
|
|
4834
|
+
};
|
|
4835
|
+
}
|
|
4836
|
+
function selectedBindingEvidence(call) {
|
|
4837
|
+
if (!call.selectedBindingId) return {};
|
|
4838
|
+
return {
|
|
4839
|
+
selectedBindingId: call.selectedBindingId,
|
|
4840
|
+
selectedBinding: {
|
|
4841
|
+
bindingId: call.selectedBindingId,
|
|
4842
|
+
alias: call.alias,
|
|
4843
|
+
aliasExpr: call.aliasExpr,
|
|
4844
|
+
destinationExpr: call.destinationExpr,
|
|
4845
|
+
servicePathExpr: call.servicePathExpr,
|
|
4846
|
+
sourceFile: call.bindingSourceFile,
|
|
4847
|
+
sourceLine: call.bindingSourceLine,
|
|
4848
|
+
helperChain: parseJson(call.helperChainJson)
|
|
4849
|
+
}
|
|
4850
|
+
};
|
|
4851
|
+
}
|
|
4852
|
+
function routingEvidence(call, servicePath, operationPath, destination, normalized, intent) {
|
|
4853
|
+
const routingPlaceholderKeys = placeholderKeys([
|
|
4854
|
+
servicePath,
|
|
4855
|
+
destination,
|
|
4856
|
+
stringValue5(call.aliasExpr),
|
|
4857
|
+
stringValue5(call.alias)
|
|
4858
|
+
]);
|
|
4859
|
+
return {
|
|
4860
|
+
serviceAlias: call.alias,
|
|
4861
|
+
serviceAliasExpr: call.aliasExpr,
|
|
4862
|
+
destination,
|
|
4863
|
+
servicePath,
|
|
4864
|
+
operationPath,
|
|
4865
|
+
rawOperationPath: normalized?.wasInvocation ? normalized.rawOperationPath : intent?.rawPath,
|
|
4866
|
+
normalizedOperationPath: normalized?.wasInvocation ? normalized.normalizedOperationPath : void 0,
|
|
4867
|
+
invocationArguments: normalized?.wasInvocation ? normalized.invocationArguments : void 0,
|
|
4868
|
+
invocationArgumentPlaceholderKeys: normalized?.invocationArgumentPlaceholderKeys.length ? normalized.invocationArgumentPlaceholderKeys : void 0,
|
|
4869
|
+
routingPlaceholderKeys: routingPlaceholderKeys.length ? routingPlaceholderKeys : void 0,
|
|
4870
|
+
odataOperationNormalizationReason: normalized?.normalizationReason,
|
|
4871
|
+
odataOperationNormalizationRejectedReason: normalized?.normalizationRejectedReason,
|
|
4872
|
+
localServiceName: call.local_service_name,
|
|
4873
|
+
localServiceLookup: call.local_service_lookup,
|
|
4874
|
+
aliasChain: parseJson(call.alias_chain_json),
|
|
4875
|
+
transport: call.call_type === "local_service_call" ? "local" : void 0,
|
|
4876
|
+
helperChain: parseJson(call.helperChainJson),
|
|
4877
|
+
odataPathIntent: intent,
|
|
4878
|
+
queryStringPresent: intent?.hasQueryString || void 0,
|
|
4879
|
+
queryPlaceholderKeys: intent?.placeholderKeys.length ? intent.placeholderKeys : void 0,
|
|
4880
|
+
bindingHasDynamicExpression: Boolean(Number(call.isDynamic ?? 0)) || void 0
|
|
4881
|
+
};
|
|
4882
|
+
}
|
|
4883
|
+
function candidateEvidence2(candidates2, resolution) {
|
|
4884
|
+
return {
|
|
4885
|
+
targetRepo: resolution.target?.repoName,
|
|
4886
|
+
targetServicePath: resolution.target?.servicePath,
|
|
4887
|
+
targetOperationPath: resolution.target?.operationPath,
|
|
4888
|
+
targetOperation: resolution.target?.operationName,
|
|
4889
|
+
candidates: candidates2.items,
|
|
4890
|
+
candidateScores: compactCandidateScores(candidates2.items),
|
|
4891
|
+
candidateCount: candidates2.totalCount,
|
|
4892
|
+
shownCandidateCount: candidates2.shownCount,
|
|
4893
|
+
omittedCandidateCount: candidates2.omittedCount,
|
|
4894
|
+
candidateScoreCount: candidates2.totalCount,
|
|
4895
|
+
shownCandidateScoreCount: candidates2.shownCount,
|
|
4896
|
+
omittedCandidateScoreCount: candidates2.omittedCount,
|
|
4897
|
+
resolutionStatus: resolution.status,
|
|
4898
|
+
resolutionReasons: resolution.reasons
|
|
4899
|
+
};
|
|
4900
|
+
}
|
|
4901
|
+
function boundedCallCandidates(candidates2) {
|
|
4902
|
+
const rows2 = candidates2.flatMap((candidate) => {
|
|
4903
|
+
const row = objectValue(candidate);
|
|
4904
|
+
return row ? [row] : [];
|
|
4905
|
+
});
|
|
4906
|
+
return projectBounded(rows2, compareCallCandidate);
|
|
4907
|
+
}
|
|
4908
|
+
function compareCallCandidate(left, right) {
|
|
4909
|
+
return Number(right.score ?? 0) - Number(left.score ?? 0) || String(left.repoName ?? "").localeCompare(String(right.repoName ?? "")) || String(left.servicePath ?? "").localeCompare(String(right.servicePath ?? "")) || String(left.operationPath ?? "").localeCompare(String(right.operationPath ?? "")) || Number(left.operationId ?? 0) - Number(right.operationId ?? 0);
|
|
4910
|
+
}
|
|
4911
|
+
function compactCandidateScores(candidates2) {
|
|
4912
|
+
return candidates2.map((candidate) => ({
|
|
4913
|
+
repo: candidate.repoName,
|
|
4914
|
+
servicePath: candidate.servicePath,
|
|
4915
|
+
operationPath: candidate.operationPath,
|
|
4916
|
+
score: candidate.score,
|
|
4917
|
+
reasons: Array.isArray(candidate.reasons) ? candidate.reasons.filter((reason) => typeof reason === "string") : ["operation_path_match"]
|
|
4918
|
+
}));
|
|
4919
|
+
}
|
|
4920
|
+
function placeholderKeys(values) {
|
|
4921
|
+
const keys = values.flatMap((value) => [...(value ?? "").matchAll(/\$\{([^}]*)\}/g)].map((match) => (match[1] ?? "").trim()).filter(Boolean));
|
|
4922
|
+
return [...new Set(keys)].sort();
|
|
4923
|
+
}
|
|
4924
|
+
function parseJson(value) {
|
|
4925
|
+
if (!value) return void 0;
|
|
4926
|
+
try {
|
|
4927
|
+
const parsed = JSON.parse(String(value));
|
|
4928
|
+
return parsed;
|
|
4929
|
+
} catch {
|
|
4930
|
+
return void 0;
|
|
4931
|
+
}
|
|
4932
|
+
}
|
|
4933
|
+
function stringValue5(value) {
|
|
4934
|
+
return typeof value === "string" ? value : void 0;
|
|
4935
|
+
}
|
|
4936
|
+
function isRecord3(value) {
|
|
4937
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
4938
|
+
}
|
|
4939
|
+
|
|
4940
|
+
// src/linker/cross-repo-linker.ts
|
|
4941
|
+
function linkWorkspace(db, workspaceId, vars = {}) {
|
|
4942
|
+
return db.transaction(() => {
|
|
4943
|
+
const generation = nextGraphGeneration(db, workspaceId);
|
|
4944
|
+
db.prepare("DELETE FROM graph_edges WHERE workspace_id=?").run(workspaceId);
|
|
4945
|
+
const deps = linkHelperPackages(db, workspaceId, generation);
|
|
4946
|
+
const impl = linkImplementations(db, workspaceId, generation);
|
|
4947
|
+
const callSummary = linkCalls(db, workspaceId, vars, generation);
|
|
4948
|
+
db.prepare("UPDATE repositories SET graph_generation=?, graph_stale_reason=NULL, graph_stale_at=NULL WHERE workspace_id=?").run(generation, workspaceId);
|
|
4949
|
+
return { ...callSummary, edgeCount: deps.edgeCount + callSummary.edgeCount + impl.edgeCount, dependencyResolvedCount: deps.resolvedCount, dependencyAmbiguousCount: deps.ambiguousCount, implementationResolvedCount: impl.resolvedCount, implementationAmbiguousCount: impl.ambiguousCount, implementationUnresolvedCount: impl.unresolvedCount };
|
|
4950
|
+
});
|
|
4951
|
+
}
|
|
4952
|
+
function nextGraphGeneration(db, workspaceId) {
|
|
4953
|
+
const row = db.prepare("SELECT COALESCE(MAX(graph_generation),0) generation FROM repositories WHERE workspace_id=?").get(workspaceId);
|
|
4954
|
+
return Number(row?.generation ?? 0) + 1;
|
|
4955
|
+
}
|
|
4956
|
+
function linkCalls(db, workspaceId, vars, generation) {
|
|
4957
|
+
let edgeCount = 0;
|
|
4958
|
+
let unresolvedCount = 0;
|
|
4959
|
+
let resolvedCount = 0;
|
|
4960
|
+
let remoteResolvedCount = 0;
|
|
4961
|
+
let localResolvedCount = 0;
|
|
4962
|
+
let ambiguousCount = 0;
|
|
4963
|
+
let dynamicCount = 0;
|
|
4964
|
+
let terminalCount = 0;
|
|
4965
|
+
const calls = db.prepare(`SELECT c.*,r.name repoName,b.id selectedBindingId,b.alias,b.alias_expr aliasExpr,b.destination_expr destinationExpr,b.service_path_expr servicePathExpr,b.is_dynamic isDynamic,b.placeholders_json placeholdersJson,b.source_file bindingSourceFile,b.source_line bindingSourceLine,b.helper_chain_json helperChainJson,req.service_path requireServicePath,req.destination requireDestination FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id LEFT JOIN service_bindings b ON b.id=c.service_binding_id LEFT JOIN cds_requires req ON req.repo_id=c.repo_id AND req.alias=b.alias WHERE r.workspace_id=?`).all(workspaceId);
|
|
4966
|
+
for (const call of calls) {
|
|
4967
|
+
const result = insertCallEdge(db, workspaceId, call, vars, generation);
|
|
4968
|
+
edgeCount += 1;
|
|
4969
|
+
resolvedCount += result.status === "resolved" ? 1 : 0;
|
|
4970
|
+
remoteResolvedCount += result.status === "resolved" && result.callType !== "local_service_call" ? 1 : 0;
|
|
4971
|
+
localResolvedCount += result.status === "resolved" && result.callType === "local_service_call" ? 1 : 0;
|
|
4972
|
+
unresolvedCount += result.status === "unresolved" ? 1 : 0;
|
|
4973
|
+
ambiguousCount += result.status === "ambiguous" ? 1 : 0;
|
|
4974
|
+
dynamicCount += result.status === "dynamic" ? 1 : 0;
|
|
4975
|
+
terminalCount += result.status === "terminal" ? 1 : 0;
|
|
4976
|
+
}
|
|
4977
|
+
return { edgeCount, unresolvedCount, resolvedCount, remoteResolvedCount, localResolvedCount, ambiguousCount, dynamicCount, terminalCount };
|
|
4978
|
+
}
|
|
4979
|
+
function insertCallEdge(db, workspaceId, call, vars, generation) {
|
|
4980
|
+
const callType = String(call.call_type);
|
|
4981
|
+
const rawOp = applyVariables(String(call.operation_path_expr ?? ""), vars);
|
|
4982
|
+
const intent = classifyODataPathIntent(rawOp, call.method);
|
|
4983
|
+
const isEntityQueryIntent = ["entity_query", "entity_key_read", "entity_navigation_query"].includes(intent.kind);
|
|
4984
|
+
const resolutionRawOp = callType === "remote_query" && isEntityQueryIntent ? intent.pathWithoutQuery : rawOp;
|
|
4985
|
+
const normalized = normalizeODataOperationInvocationPath(resolutionRawOp);
|
|
4986
|
+
const op = normalized?.normalizedOperationPath ?? resolutionRawOp;
|
|
4987
|
+
const servicePath = applyVariables(call.servicePathExpr ?? call.requireServicePath, vars);
|
|
4988
|
+
const destination = call.destinationExpr ?? call.requireDestination;
|
|
4989
|
+
const isDynamic = Boolean(Number(call.isDynamic ?? 0));
|
|
4990
|
+
const isRemoteEntityCall = callType.startsWith("remote_entity_");
|
|
4991
|
+
const indexedOperationCandidateCount = operationCandidateCount(db, workspaceId, op, intent.topLevelOperationName);
|
|
4992
|
+
const credibleOperationSignal = Boolean(normalized?.wasInvocation) || Boolean(intent.topLevelOperationNameCandidate) && indexedOperationCandidateCount > 0;
|
|
4993
|
+
const strongEntitySignal = ["entity_media", "entity_delete", "entity_key_read", "entity_navigation_query"].includes(intent.kind) || intent.kind === "entity_mutation" && (intent.hasEntityKeyPredicate || intent.hasNavigationSuffix);
|
|
4994
|
+
const operationLikeRemoteEntity = isRemoteEntityCall && Boolean(op) && credibleOperationSignal && (!strongEntitySignal || indexedOperationCandidateCount > 0);
|
|
4995
|
+
const isOperationCall = operationLikeRemoteEntity || (callType === "remote_action" || callType === "local_service_call" || callType === "remote_query" && Boolean(op));
|
|
4996
|
+
const resolution = isOperationCall ? resolveOperation(db, { servicePath, operationPath: op, serviceName: call.local_service_name, repoId: callType === "local_service_call" ? Number(call.repo_id) : void 0, alias: applyVariables(call.aliasExpr ?? call.alias, vars), destination: destination ? applyVariables(destination, vars) : void 0, isDynamic, hasExplicitOverride: Object.keys(vars).length > 0 || callType === "local_service_call" }, workspaceId) : { status: "unresolved", candidates: [], reasons: [] };
|
|
4997
|
+
const evidence = {
|
|
4998
|
+
...linkedCallEvidence(
|
|
4999
|
+
call,
|
|
5000
|
+
resolution,
|
|
5001
|
+
servicePath,
|
|
5002
|
+
op,
|
|
5003
|
+
destination ? applyVariables(destination, vars) : void 0,
|
|
5004
|
+
normalized,
|
|
5005
|
+
intent
|
|
5006
|
+
),
|
|
5007
|
+
indexedOperationCandidateCount,
|
|
5008
|
+
parserCallType: callType,
|
|
5009
|
+
entityOperationPrecedence: operationPrecedence(
|
|
5010
|
+
callType,
|
|
5011
|
+
intent,
|
|
5012
|
+
indexedOperationCandidateCount,
|
|
5013
|
+
Boolean(resolution.target)
|
|
5014
|
+
)
|
|
5015
|
+
};
|
|
5016
|
+
const pathAnalysis = objectValue(objectJson2(call.evidence_json)?.pathAnalysis);
|
|
5017
|
+
if (callType === "remote_action" && pathAnalysis?.status === "ambiguous") {
|
|
5018
|
+
const candidatePaths = ambiguousPathCandidates(pathAnalysis);
|
|
5019
|
+
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(
|
|
5020
|
+
workspaceId,
|
|
5021
|
+
"UNRESOLVED_EDGE",
|
|
5022
|
+
"ambiguous",
|
|
5023
|
+
"call",
|
|
5024
|
+
String(call.id),
|
|
5025
|
+
"operation_candidates",
|
|
5026
|
+
candidatePaths.items.join(","),
|
|
5027
|
+
Number(call.confidence ?? 0.5),
|
|
5028
|
+
JSON.stringify({
|
|
5029
|
+
...evidence,
|
|
5030
|
+
ambiguousOperationPathCandidateCount: candidatePaths.totalCount,
|
|
5031
|
+
shownAmbiguousOperationPathCandidateCount: candidatePaths.shownCount,
|
|
5032
|
+
omittedAmbiguousOperationPathCandidateCount: candidatePaths.omittedCount
|
|
5033
|
+
}),
|
|
5034
|
+
0,
|
|
5035
|
+
"Ambiguous operation path candidates require explicit disambiguation",
|
|
5036
|
+
generation
|
|
5037
|
+
);
|
|
5038
|
+
return { status: "ambiguous", callType };
|
|
4380
5039
|
}
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
const accepted = methodMatches && !methodSignal.contradicted && !contradicted && (hasOwnership || localServicePathMatch || hasCrossPackage || handlerDependsOnModel || helperOwned);
|
|
4386
|
-
if (!accepted && rejectedReasons.length === 0) rejectedReasons.push("candidate did not meet implementation ownership policy");
|
|
4387
|
-
return { ...row, methodId: Number(row.methodId), score, accepted, acceptedReasons, rejectedReasons };
|
|
4388
|
-
}
|
|
4389
|
-
function candidateEvidence(candidate, rank) {
|
|
4390
|
-
return {
|
|
4391
|
-
rank,
|
|
4392
|
-
score: candidate.score,
|
|
4393
|
-
accepted: candidate.accepted,
|
|
4394
|
-
acceptedReasons: candidate.acceptedReasons,
|
|
4395
|
-
rejectedReasons: candidate.rejectedReasons,
|
|
4396
|
-
methodId: candidate.methodId,
|
|
4397
|
-
classId: candidate.classId,
|
|
4398
|
-
className: candidate.className,
|
|
4399
|
-
sourceFile: candidate.sourceFile,
|
|
4400
|
-
sourceLine: candidate.sourceLine,
|
|
4401
|
-
decoratorResolution: objectJson(candidate.decoratorResolutionJson),
|
|
4402
|
-
registration: registrationEvidence(candidate),
|
|
4403
|
-
registrations: candidate.registrations ?? [],
|
|
4404
|
-
registrationPairing: {
|
|
4405
|
-
strategy: registrationPairingStrategy(candidate),
|
|
4406
|
-
registrationId: candidate.registrationId,
|
|
4407
|
-
registrationHandlerClassId: candidate.registrationHandlerClassId,
|
|
4408
|
-
candidateHandlerClassId: candidate.classId,
|
|
4409
|
-
invariantStatus: validRegistrationPair(candidate) ? "valid" : "invalid"
|
|
4410
|
-
},
|
|
4411
|
-
applicationPackage: { id: candidate.applicationRepoId, name: candidate.applicationRepo, packageName: candidate.applicationPackage },
|
|
4412
|
-
handlerPackage: { id: candidate.handlerRepoId, name: candidate.handlerRepo, packageName: candidate.handlerPackage },
|
|
4413
|
-
modelPackage: { id: candidate.modelRepoId, name: candidate.modelRepo, packageName: candidate.modelPackage },
|
|
4414
|
-
servicePath: candidate.servicePath,
|
|
4415
|
-
operationPath: candidate.operationPath,
|
|
4416
|
-
operationName: candidate.operationName,
|
|
4417
|
-
signals: {
|
|
4418
|
-
directOwnership: { modelIsApplicationRepo: flag(candidate.modelIsApplicationRepo), modelIsHandlerRepo: flag(candidate.modelIsHandlerRepo) },
|
|
4419
|
-
localServicePathMatch: flag(candidate.localServicePathMatch),
|
|
4420
|
-
applicationHasLocalServices: flag(candidate.applicationHasLocalServices),
|
|
4421
|
-
applicationHasLocalRegistrationForOperation: flag(candidate.applicationHasLocalRegistrationForOperation),
|
|
4422
|
-
appDependsOnModel: flag(candidate.appDependsOnModel),
|
|
4423
|
-
appDependsOnHandler: flag(candidate.appDependsOnHandler),
|
|
4424
|
-
handlerDependsOnModel: flag(candidate.handlerDependsOnModel),
|
|
4425
|
-
sameRepoRegistration: flag(candidate.sameRepoRegistration)
|
|
5040
|
+
if (isRemoteEntityCall && (resolution.target || resolution.candidates.length > 0 || resolution.status === "dynamic")) {
|
|
5041
|
+
if (resolution.target) {
|
|
5042
|
+
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "REMOTE_CALL_RESOLVES_TO_OPERATION", "resolved", "call", String(call.id), "operation", String(resolution.target.operationId), resolution.target.score, JSON.stringify({ ...evidence, operationEntityPrecedence: "indexed_operation_over_parser_entity" }), 0, generation);
|
|
5043
|
+
return { status: "resolved", callType };
|
|
4426
5044
|
}
|
|
4427
|
-
|
|
4428
|
-
}
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
if (
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
if (
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
}
|
|
4442
|
-
|
|
4443
|
-
|
|
5045
|
+
const status2 = resolution.status === "dynamic" ? "dynamic" : resolution.status === "ambiguous" ? "ambiguous" : "unresolved";
|
|
5046
|
+
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, status2 === "dynamic" ? "DYNAMIC_EDGE_CANDIDATE" : "UNRESOLVED_EDGE", status2, "call", String(call.id), "operation_candidate", op ? `Remote action: ${op}` : "Remote action: unknown path", Number(call.confidence ?? 0.2), JSON.stringify({ ...evidence, operationEntityPrecedence: resolution.candidates.length > 0 ? "parser_entity_with_indexed_operation_candidates" : "parser_entity_operation_candidate_without_indexed_match" }), status2 === "dynamic" ? 1 : 0, unresolvedOperationReason(resolution), generation);
|
|
5047
|
+
return { status: status2, callType };
|
|
5048
|
+
}
|
|
5049
|
+
if (isRemoteEntityCall) {
|
|
5050
|
+
const target = buildRemoteQueryTarget({ queryEntity: intent.entitySegment ?? call.query_entity, servicePath, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination: destination ? applyVariables(destination, vars) : void 0, isDynamic, parserWarning: evidence.parserWarning });
|
|
5051
|
+
const entityKind = callType.replace("remote_entity_", "remote_entity_");
|
|
5052
|
+
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "HANDLER_ACCESSES_REMOTE_ENTITY", "terminal", "call", String(call.id), target.toKind, target.toId, Number(call.confidence ?? 0.5), JSON.stringify({ ...evidence, ...target.evidence, remoteEntityAccess: entityKind }), 0, generation);
|
|
5053
|
+
return { status: "terminal", callType };
|
|
5054
|
+
}
|
|
5055
|
+
if (callType === "remote_query" && (isEntityQueryIntent || !op) && !resolution.target) {
|
|
5056
|
+
const target = buildRemoteQueryTarget({ queryEntity: call.query_entity, servicePath, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination: destination ? applyVariables(destination, vars) : void 0, isDynamic, parserWarning: evidence.parserWarning });
|
|
5057
|
+
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "HANDLER_RUNS_REMOTE_QUERY", "terminal", "call", String(call.id), target.toKind, target.toId, Number(call.confidence ?? 0.5), JSON.stringify({ ...evidence, ...target.evidence }), 0, generation);
|
|
5058
|
+
return { status: "terminal", callType };
|
|
5059
|
+
}
|
|
5060
|
+
if (callType === "local_service_call" && call.unresolved_reason === "transport_client_method" && !resolution.target && resolution.candidates.length === 0) {
|
|
5061
|
+
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, "HANDLER_CALLS_TRANSPORT_METHOD", "terminal", "call", String(call.id), "transport_method", String(op || "transport_client_method"), Number(call.confidence ?? 0.5), JSON.stringify({ ...evidence, classification: "transport_client_method" }), 0, generation);
|
|
5062
|
+
return { status: "terminal", callType };
|
|
5063
|
+
}
|
|
5064
|
+
if (resolution.target) {
|
|
5065
|
+
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, callType === "local_service_call" ? "LOCAL_CALL_RESOLVES_TO_OPERATION" : "REMOTE_CALL_RESOLVES_TO_OPERATION", "resolved", "call", String(call.id), "operation", String(resolution.target.operationId), resolution.target.score, JSON.stringify(evidence), 0, generation);
|
|
5066
|
+
return { status: "resolved", callType };
|
|
5067
|
+
}
|
|
5068
|
+
const edgeType = callType === "local_db_query" ? "HANDLER_RUNS_DB_QUERY" : callType === "external_http" ? "HANDLER_CALLS_EXTERNAL_HTTP" : callType === "async_emit" ? "HANDLER_EMITS_EVENT" : callType === "async_subscribe" ? "EVENT_CONSUMED_BY_HANDLER" : resolution.status === "dynamic" ? "DYNAMIC_EDGE_CANDIDATE" : "UNRESOLVED_EDGE";
|
|
5069
|
+
const status = edgeType === "DYNAMIC_EDGE_CANDIDATE" ? "dynamic" : resolution.status === "ambiguous" ? "ambiguous" : edgeType === "UNRESOLVED_EDGE" ? "unresolved" : "terminal";
|
|
5070
|
+
const unresolvedReason = status === "terminal" ? null : String(call.unresolved_reason ?? unresolvedOperationReason(resolution));
|
|
5071
|
+
const externalTarget = callType === "external_http" ? externalHttpTarget(call) : void 0;
|
|
5072
|
+
const targetKind = callType === "local_db_query" ? "db_entity" : callType.startsWith("async_") ? "event" : callType === "external_http" ? externalTarget?.toKind ?? "external_endpoint" : "operation_candidate";
|
|
5073
|
+
const targetId = callType === "local_db_query" ? String(call.query_entity ?? "unknown") : callType === "remote_action" ? op ? `Remote action: ${op}` : call.unresolved_reason === "dynamic_operation_path_identifier" ? "Remote action: dynamic path" : "Remote action: unknown path" : callType === "external_http" ? String(externalTarget?.toId ?? "unknown") : String(call.event_name_expr ?? op ?? "unknown");
|
|
5074
|
+
const graphLevelDynamic = edgeType === "DYNAMIC_EDGE_CANDIDATE" && resolution.status === "dynamic";
|
|
5075
|
+
const finalEvidence = externalTarget ? { ...evidence, externalTarget } : evidence;
|
|
5076
|
+
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, edgeType, status, "call", String(call.id), targetKind, targetId, Number(call.confidence ?? 0.2), JSON.stringify(finalEvidence), graphLevelDynamic ? 1 : 0, unresolvedReason, generation);
|
|
5077
|
+
return { status, callType };
|
|
4444
5078
|
}
|
|
4445
|
-
function
|
|
4446
|
-
|
|
5079
|
+
function operationCandidateCount(db, workspaceId, operationPath, operationName) {
|
|
5080
|
+
if (!operationPath && !operationName) return 0;
|
|
5081
|
+
const normalizedName = operationName ?? operationPath?.replace(/^\//, "").split(".").at(-1);
|
|
5082
|
+
const row = db.prepare(`SELECT COUNT(*) count FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (o.operation_path=? OR o.operation_path=? OR o.operation_name=?)`).get(workspaceId, operationPath, normalizedName ? `/${normalizedName}` : operationPath, normalizedName);
|
|
5083
|
+
return Number(row?.count ?? 0);
|
|
4447
5084
|
}
|
|
4448
|
-
function
|
|
4449
|
-
|
|
5085
|
+
function operationPrecedence(callType, intent, indexedOperationCandidateCount, resolvedOperation) {
|
|
5086
|
+
if (resolvedOperation) {
|
|
5087
|
+
return {
|
|
5088
|
+
decision: "operation",
|
|
5089
|
+
reason: "indexed_operation_with_strong_service_context",
|
|
5090
|
+
indexedOperationCandidateCount
|
|
5091
|
+
};
|
|
5092
|
+
}
|
|
5093
|
+
if (callType === "remote_action" && intent.kind === "operation_invocation") {
|
|
5094
|
+
return {
|
|
5095
|
+
decision: "operation_candidate",
|
|
5096
|
+
rejectionReason: indexedOperationCandidateCount > 0 ? "indexed_candidates_lack_unique_strong_service_context" : "no_indexed_operation_candidate",
|
|
5097
|
+
indexedOperationCandidateCount
|
|
5098
|
+
};
|
|
5099
|
+
}
|
|
5100
|
+
if (intent.kind.startsWith("entity_")) {
|
|
5101
|
+
return {
|
|
5102
|
+
decision: "entity",
|
|
5103
|
+
rejectionReason: indexedOperationCandidateCount > 0 ? "entity_shape_has_precedence_without_resolved_operation_context" : "entity_shape_has_no_indexed_operation_evidence",
|
|
5104
|
+
indexedOperationCandidateCount
|
|
5105
|
+
};
|
|
5106
|
+
}
|
|
5107
|
+
return {
|
|
5108
|
+
decision: "unresolved",
|
|
5109
|
+
rejectionReason: "path_has_no_safe_entity_or_operation_precedence",
|
|
5110
|
+
indexedOperationCandidateCount
|
|
5111
|
+
};
|
|
4450
5112
|
}
|
|
4451
|
-
function
|
|
4452
|
-
|
|
5113
|
+
function unresolvedOperationReason(resolution) {
|
|
5114
|
+
if (resolution.status === "dynamic") return `Dynamic target requires runtime variable overrides: ${(resolution.reasons.length ? resolution.reasons : ["missing runtime variables"]).join(", ")}`;
|
|
5115
|
+
if (resolution.candidates.length === 0) return "No indexed target operation matched";
|
|
5116
|
+
if (resolution.reasons.includes("operation_path_only_has_no_strong_target_signal")) return "Operation candidates found but no strong service signal is available";
|
|
5117
|
+
if (resolution.reasons.includes("candidate_score_below_resolution_threshold")) return "Operation candidates found but resolution score is below threshold";
|
|
5118
|
+
if (resolution.status === "ambiguous") return "Ambiguous operation candidates require a strong service signal";
|
|
5119
|
+
return "Operation candidates found but resolution could not select a target";
|
|
4453
5120
|
}
|
|
4454
5121
|
|
|
4455
5122
|
// src/trace/selectors.ts
|
|
@@ -4470,23 +5137,34 @@ function selectorRepoNotFoundDiagnostic(requested) {
|
|
|
4470
5137
|
requestedRepository: requested
|
|
4471
5138
|
};
|
|
4472
5139
|
}
|
|
4473
|
-
function selectorRepoAmbiguousDiagnostic(requested,
|
|
4474
|
-
const uniqueName = (value) =>
|
|
4475
|
-
const uniquePackage = (value) =>
|
|
4476
|
-
const suggestions =
|
|
5140
|
+
function selectorRepoAmbiguousDiagnostic(requested, candidates2) {
|
|
5141
|
+
const uniqueName = (value) => candidates2.filter((candidate) => candidate.name === value).length === 1;
|
|
5142
|
+
const uniquePackage = (value) => candidates2.filter((candidate) => candidate.packageName === value).length === 1;
|
|
5143
|
+
const suggestions = candidates2.flatMap((candidate) => {
|
|
4477
5144
|
if (uniqueName(candidate.name)) return [`--repo ${candidate.name}`];
|
|
4478
5145
|
if (candidate.packageName && uniquePackage(candidate.packageName))
|
|
4479
5146
|
return [`--repo ${candidate.packageName}`];
|
|
4480
5147
|
return [];
|
|
4481
5148
|
});
|
|
5149
|
+
const candidateProjection = projectBounded(candidates2, (left, right) => left.name.localeCompare(right.name) || String(left.packageName ?? "").localeCompare(String(right.packageName ?? "")) || left.id - right.id);
|
|
5150
|
+
const suggestionProjection = projectBounded(
|
|
5151
|
+
[...new Set(suggestions)],
|
|
5152
|
+
(left, right) => left.localeCompare(right)
|
|
5153
|
+
);
|
|
4482
5154
|
return {
|
|
4483
5155
|
severity: "warning",
|
|
4484
5156
|
code: "selector_repo_ambiguous",
|
|
4485
5157
|
message: `Repository selector matched multiple indexed repositories: ${requested}`,
|
|
4486
5158
|
selectorKind: "repo",
|
|
4487
5159
|
requestedRepository: requested,
|
|
4488
|
-
candidates,
|
|
4489
|
-
|
|
5160
|
+
candidates: candidateProjection.items,
|
|
5161
|
+
candidateCount: candidateProjection.totalCount,
|
|
5162
|
+
shownCandidateCount: candidateProjection.shownCount,
|
|
5163
|
+
omittedCandidateCount: candidateProjection.omittedCount,
|
|
5164
|
+
selectorSuggestions: suggestionProjection.items,
|
|
5165
|
+
selectorSuggestionCount: suggestionProjection.totalCount,
|
|
5166
|
+
shownSelectorSuggestionCount: suggestionProjection.shownCount,
|
|
5167
|
+
omittedSelectorSuggestionCount: suggestionProjection.omittedCount,
|
|
4490
5168
|
remediation: suggestions.length > 0 ? "Use one of the unique --repo selectors shown." : "Repository names and package names must be unique before this selector can be traced safely."
|
|
4491
5169
|
};
|
|
4492
5170
|
}
|
|
@@ -4628,21 +5306,23 @@ function operationHandlerRows(db, repoId, operation, servicePath, workspaceId) {
|
|
|
4628
5306
|
);
|
|
4629
5307
|
}
|
|
4630
5308
|
function operationHandlerScope(rows2, fallbackRepoId, requested) {
|
|
4631
|
-
const
|
|
4632
|
-
if (
|
|
5309
|
+
const candidates2 = handlerSelectorCandidates(rows2, "method");
|
|
5310
|
+
if (candidates2.length < 2) return executableScope(rows2, fallbackRepoId);
|
|
4633
5311
|
const classes = /* @__PURE__ */ new Map();
|
|
4634
|
-
for (const candidate of
|
|
5312
|
+
for (const candidate of candidates2) {
|
|
4635
5313
|
const key = `${String(candidate.repoName)}:${String(candidate.className)}`;
|
|
4636
5314
|
classes.set(key, /* @__PURE__ */ new Set([
|
|
4637
5315
|
...classes.get(key) ?? [],
|
|
4638
5316
|
String(candidate.handlerClassId)
|
|
4639
5317
|
]));
|
|
4640
5318
|
}
|
|
4641
|
-
const suggestions =
|
|
5319
|
+
const suggestions = candidates2.flatMap((candidate) => {
|
|
4642
5320
|
if (typeof candidate.repoName !== "string" || typeof candidate.className !== "string") return [];
|
|
4643
5321
|
const key = `${candidate.repoName}:${candidate.className}`;
|
|
4644
5322
|
return classes.get(key)?.size === 1 ? [`--repo ${candidate.repoName} --handler ${candidate.className}`] : [];
|
|
4645
5323
|
});
|
|
5324
|
+
const projection = boundedSelectorCandidates(candidates2);
|
|
5325
|
+
const suggestionProjection = boundedSelectorSuggestions(suggestions);
|
|
4646
5326
|
return { diagnostics: [{
|
|
4647
5327
|
severity: "warning",
|
|
4648
5328
|
code: "trace_start_ambiguous",
|
|
@@ -4651,8 +5331,14 @@ function operationHandlerScope(rows2, fallbackRepoId, requested) {
|
|
|
4651
5331
|
normalizedSelectorValue: requested,
|
|
4652
5332
|
resolutionStage: "handler",
|
|
4653
5333
|
resolutionStatus: "ambiguous_handler_operation",
|
|
4654
|
-
candidates,
|
|
4655
|
-
|
|
5334
|
+
candidates: projection.items,
|
|
5335
|
+
candidateCount: projection.totalCount,
|
|
5336
|
+
shownCandidateCount: projection.shownCount,
|
|
5337
|
+
omittedCandidateCount: projection.omittedCount,
|
|
5338
|
+
selectorSuggestions: suggestionProjection.items,
|
|
5339
|
+
selectorSuggestionCount: suggestionProjection.totalCount,
|
|
5340
|
+
shownSelectorSuggestionCount: suggestionProjection.shownCount,
|
|
5341
|
+
omittedSelectorSuggestionCount: suggestionProjection.omittedCount,
|
|
4656
5342
|
remediation: "Select one handler class explicitly; no operation was chosen automatically."
|
|
4657
5343
|
}] };
|
|
4658
5344
|
}
|
|
@@ -4694,7 +5380,7 @@ function handlerClassScope(rows2, requested) {
|
|
|
4694
5380
|
const ambiguity = handlerSelectorAmbiguity(rows2, requested, "class");
|
|
4695
5381
|
if (ambiguity) return { diagnostics: [ambiguity] };
|
|
4696
5382
|
const executable = rows2.filter((row) => typeof row.symbolId === "number");
|
|
4697
|
-
const repoId =
|
|
5383
|
+
const repoId = numericValue3(rows2[0]?.repoId);
|
|
4698
5384
|
if (executable.length > 0) {
|
|
4699
5385
|
const scope = executableScope(executable, repoId);
|
|
4700
5386
|
const warning = executable.some((row) => typeof row.methodId === "number") ? handlerDecoratorsNotIndexedDiagnostic(rows2[0]) : handlerMethodsNotIndexedDiagnostic(rows2[0]);
|
|
@@ -4711,17 +5397,17 @@ function handlerMethodScope(rows2, fallbackRepoId, requested) {
|
|
|
4711
5397
|
return ambiguity ? { diagnostics: [ambiguity] } : executableScope(rows2, fallbackRepoId);
|
|
4712
5398
|
}
|
|
4713
5399
|
function handlerSelectorAmbiguity(rows2, requested, matchKind) {
|
|
4714
|
-
const
|
|
4715
|
-
if (
|
|
5400
|
+
const candidates2 = handlerSelectorCandidates(rows2, matchKind);
|
|
5401
|
+
if (candidates2.length < 2) return void 0;
|
|
4716
5402
|
const repoCounts = /* @__PURE__ */ new Map();
|
|
4717
|
-
for (const candidate of
|
|
5403
|
+
for (const candidate of candidates2) {
|
|
4718
5404
|
if (typeof candidate.repoName !== "string") continue;
|
|
4719
5405
|
repoCounts.set(
|
|
4720
5406
|
candidate.repoName,
|
|
4721
5407
|
(repoCounts.get(candidate.repoName) ?? 0) + 1
|
|
4722
5408
|
);
|
|
4723
5409
|
}
|
|
4724
|
-
const suggestions =
|
|
5410
|
+
const suggestions = candidates2.flatMap((candidate) => {
|
|
4725
5411
|
const repoName = typeof candidate.repoName === "string" ? candidate.repoName : void 0;
|
|
4726
5412
|
if (repoName && repoCounts.get(repoName) === 1)
|
|
4727
5413
|
return [`--repo ${repoName} --handler ${requested}`];
|
|
@@ -4729,6 +5415,8 @@ function handlerSelectorAmbiguity(rows2, requested, matchKind) {
|
|
|
4729
5415
|
return [`${repoName ? `--repo ${repoName} ` : ""}--handler ${candidate.className}`];
|
|
4730
5416
|
return [];
|
|
4731
5417
|
});
|
|
5418
|
+
const projection = boundedSelectorCandidates(candidates2);
|
|
5419
|
+
const suggestionProjection = boundedSelectorSuggestions(suggestions);
|
|
4732
5420
|
return {
|
|
4733
5421
|
severity: "warning",
|
|
4734
5422
|
code: "trace_start_ambiguous",
|
|
@@ -4737,16 +5425,22 @@ function handlerSelectorAmbiguity(rows2, requested, matchKind) {
|
|
|
4737
5425
|
requestedHandler: requested,
|
|
4738
5426
|
resolutionStage: "handler",
|
|
4739
5427
|
resolutionStatus: "ambiguous_handler",
|
|
4740
|
-
candidates,
|
|
4741
|
-
|
|
5428
|
+
candidates: projection.items,
|
|
5429
|
+
candidateCount: projection.totalCount,
|
|
5430
|
+
shownCandidateCount: projection.shownCount,
|
|
5431
|
+
omittedCandidateCount: projection.omittedCount,
|
|
5432
|
+
selectorSuggestions: suggestionProjection.items,
|
|
5433
|
+
selectorSuggestionCount: suggestionProjection.totalCount,
|
|
5434
|
+
shownSelectorSuggestionCount: suggestionProjection.shownCount,
|
|
5435
|
+
omittedSelectorSuggestionCount: suggestionProjection.omittedCount,
|
|
4742
5436
|
remediation: suggestions.length > 0 ? "Use one of the scoped handler selectors shown." : "No current CLI selector uniquely identifies these duplicate handler classes."
|
|
4743
5437
|
};
|
|
4744
5438
|
}
|
|
4745
5439
|
function handlerSelectorCandidates(rows2, matchKind) {
|
|
4746
|
-
const
|
|
5440
|
+
const candidates2 = /* @__PURE__ */ new Map();
|
|
4747
5441
|
for (const row of rows2) {
|
|
4748
5442
|
const identity = matchKind === "class" ? `class:${String(row.handlerClassId)}` : `method:${String(row.repoId)}:${String(row.symbolId ?? row.methodId)}`;
|
|
4749
|
-
|
|
5443
|
+
candidates2.set(identity, {
|
|
4750
5444
|
handlerClassId: row.handlerClassId,
|
|
4751
5445
|
repoId: row.repoId,
|
|
4752
5446
|
repoName: row.repoName,
|
|
@@ -4756,7 +5450,7 @@ function handlerSelectorCandidates(rows2, matchKind) {
|
|
|
4756
5450
|
matchKind
|
|
4757
5451
|
});
|
|
4758
5452
|
}
|
|
4759
|
-
return [...
|
|
5453
|
+
return [...candidates2.values()].sort((left, right) => String(left.repoName ?? "").localeCompare(String(right.repoName ?? "")) || String(left.className ?? "").localeCompare(String(right.className ?? "")) || String(left.sourceFile ?? "").localeCompare(String(right.sourceFile ?? "")));
|
|
4760
5454
|
}
|
|
4761
5455
|
function executableScope(rows2, fallbackRepoId) {
|
|
4762
5456
|
const files = rows2.flatMap((row) => row.sourceFile ? [row.sourceFile] : []);
|
|
@@ -4765,7 +5459,7 @@ function executableScope(rows2, fallbackRepoId) {
|
|
|
4765
5459
|
return {
|
|
4766
5460
|
files: new Set(files),
|
|
4767
5461
|
symbols: new Set(symbols),
|
|
4768
|
-
repoId:
|
|
5462
|
+
repoId: numericValue3(rows2[0]?.repoId) ?? fallbackRepoId
|
|
4769
5463
|
};
|
|
4770
5464
|
}
|
|
4771
5465
|
function handlerMethodsNotIndexedDiagnostic(row) {
|
|
@@ -4825,15 +5519,18 @@ function arrayEvidence(evidenceJson, key) {
|
|
|
4825
5519
|
const value = evidenceRecord(evidenceJson)[key];
|
|
4826
5520
|
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
4827
5521
|
}
|
|
4828
|
-
function
|
|
5522
|
+
function numericValue3(value) {
|
|
4829
5523
|
return typeof value === "number" ? value : void 0;
|
|
4830
5524
|
}
|
|
4831
5525
|
function normalizeOperation(value) {
|
|
4832
5526
|
if (!value) return void 0;
|
|
4833
5527
|
return value.startsWith("/") ? value.slice(1) : value;
|
|
4834
5528
|
}
|
|
4835
|
-
function ambiguousStartDiagnostic(requested,
|
|
4836
|
-
const serviceSuggestions = [...new Set(
|
|
5529
|
+
function ambiguousStartDiagnostic(requested, candidates2, message) {
|
|
5530
|
+
const serviceSuggestions = [...new Set(candidates2.flatMap((row) => typeof row.servicePath === "string" ? [`--service ${row.servicePath}`] : []))].sort();
|
|
5531
|
+
const projection = boundedSelectorCandidates(candidates2);
|
|
5532
|
+
const serviceProjection = boundedSelectorSuggestions(serviceSuggestions);
|
|
5533
|
+
const selectorProjection = boundedSelectorSuggestions(fullSelectorSuggestions(candidates2));
|
|
4837
5534
|
return {
|
|
4838
5535
|
severity: "warning",
|
|
4839
5536
|
code: "trace_start_ambiguous",
|
|
@@ -4841,14 +5538,29 @@ function ambiguousStartDiagnostic(requested, candidates, message) {
|
|
|
4841
5538
|
normalizedSelectorValue: requested,
|
|
4842
5539
|
resolutionStage: "operation",
|
|
4843
5540
|
resolutionStatus: "ambiguous_operation",
|
|
4844
|
-
candidates,
|
|
4845
|
-
|
|
4846
|
-
|
|
5541
|
+
candidates: projection.items,
|
|
5542
|
+
candidateCount: projection.totalCount,
|
|
5543
|
+
shownCandidateCount: projection.shownCount,
|
|
5544
|
+
omittedCandidateCount: projection.omittedCount,
|
|
5545
|
+
serviceSuggestions: serviceProjection.items,
|
|
5546
|
+
serviceSuggestionCount: serviceProjection.totalCount,
|
|
5547
|
+
shownServiceSuggestionCount: serviceProjection.shownCount,
|
|
5548
|
+
omittedServiceSuggestionCount: serviceProjection.omittedCount,
|
|
5549
|
+
selectorSuggestions: selectorProjection.items,
|
|
5550
|
+
selectorSuggestionCount: selectorProjection.totalCount,
|
|
5551
|
+
shownSelectorSuggestionCount: selectorProjection.shownCount,
|
|
5552
|
+
omittedSelectorSuggestionCount: selectorProjection.omittedCount
|
|
4847
5553
|
};
|
|
4848
5554
|
}
|
|
4849
|
-
function
|
|
4850
|
-
|
|
4851
|
-
|
|
5555
|
+
function boundedSelectorCandidates(candidates2) {
|
|
5556
|
+
return projectBounded(candidates2, (left, right) => String(left.repoName ?? "").localeCompare(String(right.repoName ?? "")) || String(left.servicePath ?? "").localeCompare(String(right.servicePath ?? "")) || String(left.className ?? "").localeCompare(String(right.className ?? "")) || String(left.sourceFile ?? "").localeCompare(String(right.sourceFile ?? "")) || Number(left.sourceLine ?? 0) - Number(right.sourceLine ?? 0) || Number(left.handlerClassId ?? left.operationId ?? 0) - Number(right.handlerClassId ?? right.operationId ?? 0));
|
|
5557
|
+
}
|
|
5558
|
+
function boundedSelectorSuggestions(suggestions) {
|
|
5559
|
+
return projectBounded([...new Set(suggestions)], (left, right) => left.localeCompare(right));
|
|
5560
|
+
}
|
|
5561
|
+
function fullSelectorSuggestions(candidates2) {
|
|
5562
|
+
const includeRepo = new Set(candidates2.map((row) => row.repoName)).size > 1;
|
|
5563
|
+
return [...new Set(candidates2.flatMap((row) => {
|
|
4852
5564
|
if (typeof row.servicePath !== "string" || typeof row.operationPath !== "string") return [];
|
|
4853
5565
|
const repoSelector = includeRepo && typeof row.repoName === "string" ? `--repo ${row.repoName} ` : "";
|
|
4854
5566
|
return [
|
|
@@ -4857,13 +5569,136 @@ function fullSelectorSuggestions(candidates) {
|
|
|
4857
5569
|
}))].sort();
|
|
4858
5570
|
}
|
|
4859
5571
|
|
|
5572
|
+
// src/trace/004-dynamic-candidate-sources.ts
|
|
5573
|
+
function dynamicCandidateTargets(db, effectiveOperationPath, originalOperationPath, embedded, workspaceId, requireCanonical) {
|
|
5574
|
+
const canonical = queryOperationTargets(
|
|
5575
|
+
db,
|
|
5576
|
+
effectiveOperationPath,
|
|
5577
|
+
originalOperationPath,
|
|
5578
|
+
workspaceId
|
|
5579
|
+
);
|
|
5580
|
+
if (canonical.length > 0 || requireCanonical) return canonical;
|
|
5581
|
+
return targetsFromEvidence(embedded);
|
|
5582
|
+
}
|
|
5583
|
+
function targetsFromEvidence(value) {
|
|
5584
|
+
if (!Array.isArray(value)) return [];
|
|
5585
|
+
return value.flatMap((item) => {
|
|
5586
|
+
const row = record(item);
|
|
5587
|
+
const operationId = numberValue4(row.operationId);
|
|
5588
|
+
const repoName = stringValue6(row.repoName);
|
|
5589
|
+
const servicePath = stringValue6(row.servicePath);
|
|
5590
|
+
const operationPath = stringValue6(row.operationPath);
|
|
5591
|
+
const operationName = stringValue6(row.operationName) ?? operationPath?.replace(/^\//, "");
|
|
5592
|
+
if (operationId === void 0 || !repoName || !servicePath || !operationPath || !operationName)
|
|
5593
|
+
return [];
|
|
5594
|
+
return [{
|
|
5595
|
+
operationId,
|
|
5596
|
+
repoId: numberValue4(row.repoId),
|
|
5597
|
+
repoName,
|
|
5598
|
+
packageName: stringValue6(row.packageName),
|
|
5599
|
+
serviceName: stringValue6(row.serviceName) ?? "",
|
|
5600
|
+
qualifiedName: stringValue6(row.qualifiedName) ?? "",
|
|
5601
|
+
servicePath,
|
|
5602
|
+
operationPath,
|
|
5603
|
+
operationName,
|
|
5604
|
+
sourceFile: stringValue6(row.sourceFile) ?? "",
|
|
5605
|
+
sourceLine: numberValue4(row.sourceLine) ?? 0,
|
|
5606
|
+
score: numberValue4(row.score) ?? 0,
|
|
5607
|
+
reasons: stringArray3(row.reasons)
|
|
5608
|
+
}];
|
|
5609
|
+
});
|
|
5610
|
+
}
|
|
5611
|
+
function queryOperationTargets(db, effectiveOperationPath, originalOperationPath, workspaceId) {
|
|
5612
|
+
const operationPath = effectiveOperationPath ?? originalOperationPath;
|
|
5613
|
+
if (!operationPath) return [];
|
|
5614
|
+
if (extractPlaceholders(operationPath).length > 0)
|
|
5615
|
+
return templateOperationTargets(db, operationPath, workspaceId);
|
|
5616
|
+
return exactOperationTargets(db, operationPath, workspaceId);
|
|
5617
|
+
}
|
|
5618
|
+
function exactOperationTargets(db, operationPath, workspaceId) {
|
|
5619
|
+
const simple = operationPath.replace(/^\//, "").split(".").at(-1) ?? operationPath;
|
|
5620
|
+
const rows2 = recordRows(db.prepare(
|
|
5621
|
+
`SELECT o.id operationId,r.id repoId,r.name repoName,r.package_name packageName,
|
|
5622
|
+
s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,
|
|
5623
|
+
o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,
|
|
5624
|
+
o.source_line sourceLine FROM cds_operations o
|
|
5625
|
+
JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
|
|
5626
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
5627
|
+
AND (o.operation_path IN (?,?) OR o.operation_name=?)
|
|
5628
|
+
ORDER BY r.name,s.service_path,o.operation_name,o.id`
|
|
5629
|
+
).all(workspaceId, workspaceId, operationPath, `/${simple}`, simple));
|
|
5630
|
+
return rows2.flatMap(targetFromRow);
|
|
5631
|
+
}
|
|
5632
|
+
function templateOperationTargets(db, operationTemplate, workspaceId) {
|
|
5633
|
+
const rows2 = recordRows(db.prepare(
|
|
5634
|
+
`SELECT o.id operationId,r.id repoId,r.name repoName,r.package_name packageName,
|
|
5635
|
+
s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,
|
|
5636
|
+
o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,
|
|
5637
|
+
o.source_line sourceLine FROM cds_operations o
|
|
5638
|
+
JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
|
|
5639
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
5640
|
+
ORDER BY r.name,s.service_path,o.operation_name,o.id`
|
|
5641
|
+
).all(workspaceId, workspaceId));
|
|
5642
|
+
return rows2.flatMap((row) => {
|
|
5643
|
+
const operationPath = stringValue6(row.operationPath);
|
|
5644
|
+
return matchRuntimeTemplate(operationTemplate, operationPath) ? targetFromRow(row) : [];
|
|
5645
|
+
});
|
|
5646
|
+
}
|
|
5647
|
+
function targetFromRow(row) {
|
|
5648
|
+
const operationId = numberValue4(row.operationId);
|
|
5649
|
+
const repoName = stringValue6(row.repoName);
|
|
5650
|
+
const servicePath = stringValue6(row.servicePath);
|
|
5651
|
+
const operationPath = stringValue6(row.operationPath);
|
|
5652
|
+
const operationName = stringValue6(row.operationName);
|
|
5653
|
+
if (operationId === void 0 || !repoName || !servicePath || !operationPath || !operationName)
|
|
5654
|
+
return [];
|
|
5655
|
+
return [{
|
|
5656
|
+
operationId,
|
|
5657
|
+
repoId: numberValue4(row.repoId),
|
|
5658
|
+
repoName,
|
|
5659
|
+
packageName: stringValue6(row.packageName),
|
|
5660
|
+
serviceName: stringValue6(row.serviceName) ?? "",
|
|
5661
|
+
qualifiedName: stringValue6(row.qualifiedName) ?? "",
|
|
5662
|
+
servicePath,
|
|
5663
|
+
operationPath,
|
|
5664
|
+
operationName,
|
|
5665
|
+
sourceFile: stringValue6(row.sourceFile) ?? "",
|
|
5666
|
+
sourceLine: numberValue4(row.sourceLine) ?? 0,
|
|
5667
|
+
score: 0.2,
|
|
5668
|
+
reasons: ["operation_path_match"]
|
|
5669
|
+
}];
|
|
5670
|
+
}
|
|
5671
|
+
function record(value) {
|
|
5672
|
+
return isRecord4(value) ? value : {};
|
|
5673
|
+
}
|
|
5674
|
+
function recordRows(value) {
|
|
5675
|
+
return Array.isArray(value) ? value.filter(isRecord4) : [];
|
|
5676
|
+
}
|
|
5677
|
+
function isRecord4(value) {
|
|
5678
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
5679
|
+
}
|
|
5680
|
+
function stringValue6(value) {
|
|
5681
|
+
return typeof value === "string" ? value : void 0;
|
|
5682
|
+
}
|
|
5683
|
+
function numberValue4(value) {
|
|
5684
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5685
|
+
}
|
|
5686
|
+
function stringArray3(value) {
|
|
5687
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
5688
|
+
}
|
|
5689
|
+
|
|
4860
5690
|
// src/trace/001-dynamic-identity.ts
|
|
4861
|
-
function uniqueIdentityDerivations(db,
|
|
4862
|
-
const identities = workspaceIdentities(db,
|
|
4863
|
-
const proposals =
|
|
4864
|
-
const
|
|
5691
|
+
function uniqueIdentityDerivations(db, candidates2, templates) {
|
|
5692
|
+
const identities = workspaceIdentities(db, candidates2);
|
|
5693
|
+
const proposals = candidates2.flatMap((candidate) => {
|
|
5694
|
+
const implementation = routeImplementationEvidence(
|
|
5695
|
+
db,
|
|
5696
|
+
candidate.candidateOperationId
|
|
5697
|
+
);
|
|
4865
5698
|
const identity = identities.find((item) => item.repoId === candidate.repoId);
|
|
4866
|
-
|
|
5699
|
+
if (!identity || !implementation || !routeOwnerAgrees(candidate, implementation))
|
|
5700
|
+
return [];
|
|
5701
|
+
return identityProposals(candidate, identity, templates, implementation);
|
|
4867
5702
|
});
|
|
4868
5703
|
const matches2 = workspaceIdentityMatches(identities, templates);
|
|
4869
5704
|
const competing = competingIdentityKeys(matches2);
|
|
@@ -4875,7 +5710,7 @@ function uniqueIdentityDerivations(db, candidates, templates) {
|
|
|
4875
5710
|
provenance: proposal.provenance
|
|
4876
5711
|
}));
|
|
4877
5712
|
}
|
|
4878
|
-
function identityProposals(candidate, identity, templates) {
|
|
5713
|
+
function identityProposals(candidate, identity, templates, implementation) {
|
|
4879
5714
|
const routeTemplates = [templates.alias, templates.destination].filter((value) => Boolean(value));
|
|
4880
5715
|
const identities = [
|
|
4881
5716
|
{ name: identity.packageName, sourceKind: "package_identity", npmPackage: true },
|
|
@@ -4886,11 +5721,12 @@ function identityProposals(candidate, identity, templates) {
|
|
|
4886
5721
|
template,
|
|
4887
5722
|
identity2.name,
|
|
4888
5723
|
identity2.sourceKind,
|
|
4889
|
-
identity2.npmPackage
|
|
5724
|
+
identity2.npmPackage,
|
|
5725
|
+
implementation
|
|
4890
5726
|
)));
|
|
4891
5727
|
return deduplicateProposals(proposals);
|
|
4892
5728
|
}
|
|
4893
|
-
function proposalForIdentity(candidate, template, identity, sourceKind, npmPackage) {
|
|
5729
|
+
function proposalForIdentity(candidate, template, identity, sourceKind, npmPackage, implementation) {
|
|
4894
5730
|
const match = matchIdentityTemplate(template, identity, npmPackage);
|
|
4895
5731
|
if (!match) return [];
|
|
4896
5732
|
return [{
|
|
@@ -4905,7 +5741,13 @@ function proposalForIdentity(candidate, template, identity, sourceKind, npmPacka
|
|
|
4905
5741
|
template,
|
|
4906
5742
|
matchedName: identity,
|
|
4907
5743
|
normalizedForm: match.normalizedIdentity,
|
|
4908
|
-
sourceRepo: candidate.repoName
|
|
5744
|
+
sourceRepo: candidate.repoName,
|
|
5745
|
+
routeOwner: candidate.repoName,
|
|
5746
|
+
candidateOperationId: candidate.candidateOperationId,
|
|
5747
|
+
effectiveBaseOperationId: implementation.baseOperationId,
|
|
5748
|
+
candidateOperationProvenance: implementation.operationProvenance,
|
|
5749
|
+
implementationEdgeStatus: implementation.edgeStatus,
|
|
5750
|
+
implementationHandlerRepo: implementation.handlerRepo
|
|
4909
5751
|
}
|
|
4910
5752
|
}];
|
|
4911
5753
|
}
|
|
@@ -4918,7 +5760,7 @@ function matchIdentityTemplate(template, identity, npmPackage) {
|
|
|
4918
5760
|
const [prefix, suffix, extra] = normalizedTemplate.split(sentinel);
|
|
4919
5761
|
if (!prefix || !suffix || extra !== void 0) return void 0;
|
|
4920
5762
|
const normalizedIdentity = normalizeIdentity(identity, npmPackage);
|
|
4921
|
-
const match = new RegExp(`^${
|
|
5763
|
+
const match = new RegExp(`^${escapeRegex2(prefix)}([a-z0-9]+)${escapeRegex2(suffix)}$`).exec(normalizedIdentity);
|
|
4922
5764
|
if (!match?.[1]) return void 0;
|
|
4923
5765
|
return { key: matches2[0][1].trim(), value: match[1], normalizedIdentity };
|
|
4924
5766
|
}
|
|
@@ -4966,8 +5808,8 @@ function workspaceIdentityMatches(identities, templates) {
|
|
|
4966
5808
|
}));
|
|
4967
5809
|
});
|
|
4968
5810
|
}
|
|
4969
|
-
function workspaceIdentities(db,
|
|
4970
|
-
const repoIds = [...new Set(
|
|
5811
|
+
function workspaceIdentities(db, candidates2) {
|
|
5812
|
+
const repoIds = [...new Set(candidates2.flatMap((candidate) => candidate.repoId === void 0 ? [] : [candidate.repoId]))].sort((a, b) => a - b);
|
|
4971
5813
|
if (repoIds.length === 0) return [];
|
|
4972
5814
|
const placeholders3 = repoIds.map(() => "?").join(",");
|
|
4973
5815
|
const rows2 = db.prepare(`SELECT id repoId,name repoName,package_name packageName
|
|
@@ -4975,12 +5817,12 @@ function workspaceIdentities(db, candidates) {
|
|
|
4975
5817
|
SELECT DISTINCT workspace_id FROM repositories WHERE id IN (${placeholders3})
|
|
4976
5818
|
) ORDER BY workspace_id,name,absolute_path,id`).all(...repoIds);
|
|
4977
5819
|
return rows2.flatMap((row) => {
|
|
4978
|
-
const repoId =
|
|
4979
|
-
const repoName =
|
|
5820
|
+
const repoId = numberValue5(row.repoId);
|
|
5821
|
+
const repoName = stringValue7(row.repoName);
|
|
4980
5822
|
return repoId === void 0 || !repoName ? [] : [{
|
|
4981
5823
|
repoId,
|
|
4982
5824
|
repoName,
|
|
4983
|
-
packageName:
|
|
5825
|
+
packageName: stringValue7(row.packageName)
|
|
4984
5826
|
}];
|
|
4985
5827
|
});
|
|
4986
5828
|
}
|
|
@@ -4994,57 +5836,168 @@ function deduplicateProposals(rows2) {
|
|
|
4994
5836
|
return true;
|
|
4995
5837
|
});
|
|
4996
5838
|
}
|
|
4997
|
-
function
|
|
4998
|
-
return candidate.repoId !== void 0 &&
|
|
5839
|
+
function routeOwnerAgrees(candidate, implementation) {
|
|
5840
|
+
return candidate.repoId !== void 0 && implementation?.routeRepoId === candidate.repoId && implementation.edgeStatus === "resolved" && candidate.viable && candidate.reasons.includes("service_path_template_match");
|
|
4999
5841
|
}
|
|
5000
|
-
function
|
|
5842
|
+
function routeImplementationEvidence(db, operationId) {
|
|
5001
5843
|
const rows2 = db.prepare(
|
|
5002
|
-
`SELECT
|
|
5003
|
-
|
|
5844
|
+
`SELECT s.repo_id routeRepoId,o.provenance operationProvenance,
|
|
5845
|
+
o.base_operation_id baseOperationId,e.status edgeStatus,
|
|
5846
|
+
r.name handlerRepo
|
|
5847
|
+
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id
|
|
5848
|
+
JOIN graph_edges e ON e.edge_type='OPERATION_IMPLEMENTED_BY_HANDLER'
|
|
5849
|
+
AND e.from_kind='operation' AND e.from_id=CAST(o.id AS TEXT)
|
|
5850
|
+
JOIN handler_methods hm ON hm.id=CAST(e.to_id AS INTEGER)
|
|
5004
5851
|
JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
5005
5852
|
JOIN repositories r ON r.id=hc.repo_id
|
|
5006
5853
|
WHERE e.edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND e.status='resolved'
|
|
5007
|
-
AND
|
|
5008
|
-
ORDER BY
|
|
5854
|
+
AND o.id=?
|
|
5855
|
+
ORDER BY e.id,hm.id`
|
|
5009
5856
|
).all(String(operationId));
|
|
5010
5857
|
if (rows2.length !== 1) return void 0;
|
|
5011
5858
|
const row = rows2[0];
|
|
5012
5859
|
if (!row) return void 0;
|
|
5013
5860
|
return {
|
|
5014
|
-
|
|
5861
|
+
routeRepoId: numberValue5(row.routeRepoId),
|
|
5862
|
+
handlerRepo: stringValue7(row.handlerRepo),
|
|
5863
|
+
operationProvenance: stringValue7(row.operationProvenance),
|
|
5864
|
+
baseOperationId: numberValue5(row.baseOperationId),
|
|
5865
|
+
edgeStatus: stringValue7(row.edgeStatus)
|
|
5866
|
+
};
|
|
5867
|
+
}
|
|
5868
|
+
function normalizeIdentity(value, npmPackage = false) {
|
|
5869
|
+
const unscoped = npmPackage && /^@[^/]+\/[^/]+$/.test(value) ? value.slice(value.indexOf("/") + 1) : value;
|
|
5870
|
+
return unscoped.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
5871
|
+
}
|
|
5872
|
+
function stringValue7(value) {
|
|
5873
|
+
return typeof value === "string" ? value : void 0;
|
|
5874
|
+
}
|
|
5875
|
+
function numberValue5(value) {
|
|
5876
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5877
|
+
}
|
|
5878
|
+
function escapeRegex2(value) {
|
|
5879
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5880
|
+
}
|
|
5881
|
+
|
|
5882
|
+
// src/trace/003-dynamic-references.ts
|
|
5883
|
+
function dynamicRoutingContext(db, workspaceId, evidence) {
|
|
5884
|
+
const selected = selectedBinding(db, workspaceId, evidence);
|
|
5885
|
+
const persisted = persistedBindingResolution(evidence);
|
|
5886
|
+
const alternatives = boundedAlternatives(
|
|
5887
|
+
persisted.candidates,
|
|
5888
|
+
persisted.candidateCount
|
|
5889
|
+
);
|
|
5890
|
+
if (selected) {
|
|
5891
|
+
const requires = exactRequireReferences(db, workspaceId, selected);
|
|
5892
|
+
return {
|
|
5893
|
+
...contextBase(selected, persisted.status, alternatives),
|
|
5894
|
+
selectedBinding: selected,
|
|
5895
|
+
references: [selected, ...requires],
|
|
5896
|
+
fallbackUsed: false
|
|
5897
|
+
};
|
|
5898
|
+
}
|
|
5899
|
+
const callerRepoId = numberValue6(evidence.repoId);
|
|
5900
|
+
const callerRepo = stringValue8(evidence.repo);
|
|
5901
|
+
return {
|
|
5902
|
+
outboundCallId: numberValue6(evidence.outboundCallId ?? evidence.callId),
|
|
5903
|
+
callerRepoId,
|
|
5904
|
+
callerRepo,
|
|
5905
|
+
bindingResolutionStatus: persisted.status,
|
|
5906
|
+
bindingAlternatives: alternatives.items,
|
|
5907
|
+
bindingAlternativeCount: alternatives.totalCount,
|
|
5908
|
+
shownBindingAlternativeCount: alternatives.shownCount,
|
|
5909
|
+
omittedBindingAlternativeCount: alternatives.omittedCount,
|
|
5910
|
+
references: fallbackReferences(db, workspaceId, callerRepoId, callerRepo),
|
|
5911
|
+
fallbackUsed: true
|
|
5912
|
+
};
|
|
5913
|
+
}
|
|
5914
|
+
function dynamicReferenceProvenance(reference, kind, template, value) {
|
|
5915
|
+
const sourceKind = reference.selection === "selected_binding" ? `selected_binding.${kind}` : reference.selection === "selected_binding_require" ? `selected_binding_require.${kind}` : `${reference.sourceKind}.${kind}`;
|
|
5916
|
+
return {
|
|
5917
|
+
sourceKind,
|
|
5918
|
+
value,
|
|
5919
|
+
rule: "exact_indexed_reference_template_match",
|
|
5920
|
+
template,
|
|
5921
|
+
sourceRepo: reference.repoName,
|
|
5922
|
+
sourceFile: reference.sourceFile,
|
|
5923
|
+
sourceLine: reference.sourceLine,
|
|
5924
|
+
selection: reference.selection,
|
|
5925
|
+
bindingId: reference.bindingId
|
|
5015
5926
|
};
|
|
5016
5927
|
}
|
|
5017
|
-
function
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5928
|
+
function contextBase(selected, status, alternatives) {
|
|
5929
|
+
return {
|
|
5930
|
+
outboundCallId: selected.outboundCallId,
|
|
5931
|
+
callerRepoId: selected.callerRepoId,
|
|
5932
|
+
callerRepo: selected.repoName,
|
|
5933
|
+
selectedBindingId: selected.bindingId,
|
|
5934
|
+
bindingResolutionStatus: status,
|
|
5935
|
+
bindingAlternatives: alternatives.items,
|
|
5936
|
+
bindingAlternativeCount: alternatives.totalCount,
|
|
5937
|
+
shownBindingAlternativeCount: alternatives.shownCount,
|
|
5938
|
+
omittedBindingAlternativeCount: alternatives.omittedCount
|
|
5939
|
+
};
|
|
5023
5940
|
}
|
|
5024
|
-
function
|
|
5025
|
-
|
|
5941
|
+
function selectedBinding(db, workspaceId, evidence) {
|
|
5942
|
+
const callId = numberValue6(evidence.outboundCallId ?? evidence.callId);
|
|
5943
|
+
if (callId === void 0) return void 0;
|
|
5944
|
+
const row = db.prepare(`SELECT c.id outboundCallId,c.repo_id callerRepoId,r.name repoName,
|
|
5945
|
+
b.id bindingId,b.alias,b.alias_expr aliasExpr,b.destination_expr destination,
|
|
5946
|
+
b.service_path_expr servicePath,b.source_file sourceFile,b.source_line sourceLine,
|
|
5947
|
+
b.helper_chain_json helperChainJson
|
|
5948
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
5949
|
+
JOIN service_bindings b ON b.id=c.service_binding_id AND b.repo_id=c.repo_id
|
|
5950
|
+
WHERE c.id=? AND (? IS NULL OR r.workspace_id=?)`).get(
|
|
5951
|
+
callId,
|
|
5952
|
+
workspaceId,
|
|
5953
|
+
workspaceId
|
|
5954
|
+
);
|
|
5955
|
+
return selectedReferenceFromRow(row);
|
|
5026
5956
|
}
|
|
5027
|
-
function
|
|
5028
|
-
|
|
5957
|
+
function selectedReferenceFromRow(row) {
|
|
5958
|
+
const outboundCallId = numberValue6(row?.outboundCallId);
|
|
5959
|
+
const callerRepoId = numberValue6(row?.callerRepoId);
|
|
5960
|
+
const reference = referenceFromRow(
|
|
5961
|
+
row,
|
|
5962
|
+
"service_binding",
|
|
5963
|
+
"selected_binding"
|
|
5964
|
+
)[0];
|
|
5965
|
+
return reference && outboundCallId !== void 0 && callerRepoId !== void 0 ? { ...reference, outboundCallId, callerRepoId } : void 0;
|
|
5966
|
+
}
|
|
5967
|
+
function exactRequireReferences(db, workspaceId, selected) {
|
|
5968
|
+
if (!(selected.aliasExpr ?? selected.alias)) return [];
|
|
5969
|
+
const rows2 = db.prepare(`SELECT req.alias,req.destination,req.service_path servicePath,
|
|
5970
|
+
r.name repoName,'package.json' sourceFile,1 sourceLine
|
|
5971
|
+
FROM cds_requires req JOIN repositories r ON r.id=req.repo_id
|
|
5972
|
+
WHERE req.repo_id=? AND (? IS NULL OR r.workspace_id=?)
|
|
5973
|
+
ORDER BY req.alias,req.id`).all(
|
|
5974
|
+
selected.callerRepoId,
|
|
5975
|
+
workspaceId,
|
|
5976
|
+
workspaceId
|
|
5977
|
+
);
|
|
5978
|
+
return rows2.flatMap((row) => referenceFromRow(
|
|
5979
|
+
row,
|
|
5980
|
+
"cds_require",
|
|
5981
|
+
"selected_binding_require",
|
|
5982
|
+
selected.bindingId
|
|
5983
|
+
));
|
|
5029
5984
|
}
|
|
5030
|
-
|
|
5031
|
-
// src/trace/003-dynamic-references.ts
|
|
5032
|
-
function dynamicReferenceRows(db, workspaceId, callerRepoId, callerRepo) {
|
|
5985
|
+
function fallbackReferences(db, workspaceId, callerRepoId, callerRepo) {
|
|
5033
5986
|
if (callerRepoId === void 0 && callerRepo === void 0) return [];
|
|
5034
|
-
const rows2 = db.prepare(
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
b.
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
'package.json',1,1
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5987
|
+
const rows2 = db.prepare(`SELECT b.id bindingId,COALESCE(b.alias,b.alias_expr) alias,
|
|
5988
|
+
b.alias_expr aliasExpr,b.destination_expr destination,b.service_path_expr servicePath,
|
|
5989
|
+
'service_binding' sourceKind,r.name repoName,b.source_file sourceFile,
|
|
5990
|
+
b.source_line sourceLine,b.helper_chain_json helperChainJson,0 sourcePriority
|
|
5991
|
+
FROM service_bindings b JOIN repositories r ON r.id=b.repo_id
|
|
5992
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
5993
|
+
AND ((? IS NOT NULL AND r.id=?) OR (? IS NULL AND r.name=?))
|
|
5994
|
+
UNION ALL
|
|
5995
|
+
SELECT NULL,req.alias,req.alias,req.destination,req.service_path,
|
|
5996
|
+
'cds_require',r.name,'package.json',1,NULL,1
|
|
5997
|
+
FROM cds_requires req JOIN repositories r ON r.id=req.repo_id
|
|
5998
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
5999
|
+
AND ((? IS NOT NULL AND r.id=?) OR (? IS NULL AND r.name=?))
|
|
6000
|
+
ORDER BY sourcePriority,repoName,sourceFile,sourceLine`).all(
|
|
5048
6001
|
workspaceId,
|
|
5049
6002
|
workspaceId,
|
|
5050
6003
|
callerRepoId,
|
|
@@ -5058,61 +6011,90 @@ function dynamicReferenceRows(db, workspaceId, callerRepoId, callerRepo) {
|
|
|
5058
6011
|
callerRepoId,
|
|
5059
6012
|
callerRepo
|
|
5060
6013
|
);
|
|
5061
|
-
return rows2.flatMap(
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
sourceKind: `${reference.sourceKind}.${kind}`,
|
|
5066
|
-
value,
|
|
5067
|
-
rule: "exact_indexed_reference_template_match",
|
|
5068
|
-
template,
|
|
5069
|
-
sourceRepo: reference.repoName,
|
|
5070
|
-
sourceFile: reference.sourceFile,
|
|
5071
|
-
sourceLine: reference.sourceLine
|
|
5072
|
-
};
|
|
6014
|
+
return rows2.flatMap((row) => {
|
|
6015
|
+
const sourceKind = row.sourceKind;
|
|
6016
|
+
return sourceKind === "service_binding" || sourceKind === "cds_require" ? referenceFromRow(row, sourceKind, "fallback") : [];
|
|
6017
|
+
});
|
|
5073
6018
|
}
|
|
5074
|
-
function referenceFromRow(row) {
|
|
5075
|
-
const
|
|
5076
|
-
|
|
5077
|
-
if (sourceKind !== "service_binding" && sourceKind !== "cds_require" || !repoName) return [];
|
|
6019
|
+
function referenceFromRow(row, sourceKind, selection, bindingId = numberValue6(row?.bindingId)) {
|
|
6020
|
+
const repoName = stringValue8(row?.repoName);
|
|
6021
|
+
if (!repoName) return [];
|
|
5078
6022
|
return [{
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
6023
|
+
bindingId,
|
|
6024
|
+
alias: stringValue8(row?.alias),
|
|
6025
|
+
aliasExpr: stringValue8(row?.aliasExpr),
|
|
6026
|
+
destination: stringValue8(row?.destination),
|
|
6027
|
+
servicePath: stringValue8(row?.servicePath),
|
|
5082
6028
|
sourceKind,
|
|
6029
|
+
selection,
|
|
5083
6030
|
repoName,
|
|
5084
|
-
sourceFile:
|
|
5085
|
-
sourceLine:
|
|
6031
|
+
sourceFile: stringValue8(row?.sourceFile),
|
|
6032
|
+
sourceLine: numberValue6(row?.sourceLine),
|
|
6033
|
+
helperChain: parsedJson(row?.helperChainJson)
|
|
5086
6034
|
}];
|
|
5087
6035
|
}
|
|
5088
|
-
function
|
|
6036
|
+
function persistedBindingResolution(evidence) {
|
|
6037
|
+
const outbound = record2(evidence.outboundEvidence);
|
|
6038
|
+
const resolution = record2(outbound.serviceBindingResolution);
|
|
6039
|
+
return {
|
|
6040
|
+
status: stringValue8(resolution.status) ?? "unknown",
|
|
6041
|
+
candidates: recordArray2(resolution.candidates),
|
|
6042
|
+
candidateCount: numberValue6(resolution.candidateCount) ?? 0
|
|
6043
|
+
};
|
|
6044
|
+
}
|
|
6045
|
+
function boundedAlternatives(rows2, reportedCount) {
|
|
6046
|
+
const projection = projectBounded(rows2, (left, right) => Number(left.bindingId ?? 0) - Number(right.bindingId ?? 0) || String(left.sourceFile ?? "").localeCompare(String(right.sourceFile ?? "")) || Number(left.sourceLine ?? 0) - Number(right.sourceLine ?? 0));
|
|
6047
|
+
const totalCount = Math.max(reportedCount, projection.totalCount);
|
|
6048
|
+
return {
|
|
6049
|
+
...projection,
|
|
6050
|
+
totalCount,
|
|
6051
|
+
omittedCount: Math.max(0, totalCount - projection.shownCount)
|
|
6052
|
+
};
|
|
6053
|
+
}
|
|
6054
|
+
function parsedJson(value) {
|
|
6055
|
+
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
6056
|
+
try {
|
|
6057
|
+
return JSON.parse(value);
|
|
6058
|
+
} catch {
|
|
6059
|
+
return void 0;
|
|
6060
|
+
}
|
|
6061
|
+
}
|
|
6062
|
+
function record2(value) {
|
|
6063
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
6064
|
+
}
|
|
6065
|
+
function recordArray2(value) {
|
|
6066
|
+
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
6067
|
+
}
|
|
6068
|
+
function stringValue8(value) {
|
|
5089
6069
|
return typeof value === "string" ? value : void 0;
|
|
5090
6070
|
}
|
|
5091
|
-
function
|
|
6071
|
+
function numberValue6(value) {
|
|
5092
6072
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5093
6073
|
}
|
|
5094
6074
|
|
|
5095
6075
|
// src/trace/dynamic-targets.ts
|
|
5096
6076
|
function analyzeDynamicTargetCandidates(db, evidence, workspaceId, mode, maxCandidates) {
|
|
5097
|
-
const inputs = analysisInputs(evidence);
|
|
6077
|
+
const inputs = analysisInputs(db, evidence, workspaceId);
|
|
5098
6078
|
if (inputs.required.length === 0) return void 0;
|
|
5099
|
-
const targets =
|
|
5100
|
-
const references = dynamicReferenceRows(
|
|
6079
|
+
const targets = dynamicCandidateTargets(
|
|
5101
6080
|
db,
|
|
6081
|
+
inputs.effective.operationPath,
|
|
6082
|
+
inputs.original.operationPath,
|
|
6083
|
+
evidence.candidates,
|
|
5102
6084
|
workspaceId,
|
|
5103
|
-
inputs.
|
|
5104
|
-
inputs.callerRepo
|
|
6085
|
+
inputs.routing.outboundCallId !== void 0
|
|
5105
6086
|
);
|
|
5106
|
-
const
|
|
5107
|
-
applyUniqueIdentityEvidence(db,
|
|
5108
|
-
finalizeCandidates(
|
|
5109
|
-
const ranked = stableRank(
|
|
6087
|
+
const candidates2 = buildCandidates(db, targets, inputs.routing.references, inputs);
|
|
6088
|
+
applyUniqueIdentityEvidence(db, candidates2, inputs);
|
|
6089
|
+
finalizeCandidates(candidates2, inputs.order);
|
|
6090
|
+
const ranked = stableRank(candidates2);
|
|
5110
6091
|
const inference = inferenceDecision(ranked);
|
|
5111
6092
|
applyModeState(ranked, mode, inference);
|
|
5112
6093
|
const viable = ranked.filter((candidate) => candidate.viable);
|
|
5113
6094
|
const rejected = ranked.filter((candidate) => candidate.rejected);
|
|
5114
6095
|
const shown = viable.slice(0, maxCandidates).map((candidate) => boundedCandidate(candidate, maxCandidates));
|
|
5115
6096
|
const shownRejected = rejected.slice(0, maxCandidates).map((candidate) => boundedCandidate(candidate, maxCandidates));
|
|
6097
|
+
const suggestionProjection = suggestedVarSets(viable, inputs.order, maxCandidates);
|
|
5116
6098
|
return {
|
|
5117
6099
|
mode,
|
|
5118
6100
|
maxCandidates,
|
|
@@ -5131,16 +6113,21 @@ function analyzeDynamicTargetCandidates(db, evidence, workspaceId, mode, maxCand
|
|
|
5131
6113
|
candidates: shown,
|
|
5132
6114
|
shownCandidates: shown,
|
|
5133
6115
|
rejectedCandidates: shownRejected,
|
|
5134
|
-
suggestedVarSets:
|
|
5135
|
-
|
|
6116
|
+
suggestedVarSets: suggestionProjection.items,
|
|
6117
|
+
suggestedVarSetCount: suggestionProjection.totalCount,
|
|
6118
|
+
shownSuggestedVarSetCount: suggestionProjection.shownCount,
|
|
6119
|
+
omittedSuggestedVarSetCount: suggestionProjection.omittedCount,
|
|
6120
|
+
inference,
|
|
6121
|
+
routingContext: routingEvidence2(inputs.routing)
|
|
5136
6122
|
};
|
|
5137
6123
|
}
|
|
5138
|
-
function analysisInputs(evidence) {
|
|
5139
|
-
const
|
|
5140
|
-
const
|
|
6124
|
+
function analysisInputs(db, evidence, workspaceId) {
|
|
6125
|
+
const routing = dynamicRoutingContext(db, workspaceId, evidence);
|
|
6126
|
+
const supplied = stringRecord(evidence.suppliedRuntimeVariables);
|
|
6127
|
+
const original = templatesFromEvidence(evidence, routing);
|
|
6128
|
+
const effective = effectiveTemplates(original, supplied);
|
|
5141
6129
|
const requiredSources = placeholderSources(original);
|
|
5142
6130
|
const required = Object.keys(requiredSources);
|
|
5143
|
-
const supplied = stringRecord(evidence.suppliedRuntimeVariables);
|
|
5144
6131
|
return {
|
|
5145
6132
|
original,
|
|
5146
6133
|
effective,
|
|
@@ -5148,94 +6135,51 @@ function analysisInputs(evidence) {
|
|
|
5148
6135
|
requiredSources,
|
|
5149
6136
|
supplied,
|
|
5150
6137
|
order: variableOrder(original, required),
|
|
5151
|
-
callerRepo:
|
|
5152
|
-
callerRepoId:
|
|
6138
|
+
callerRepo: routing.callerRepo ?? stringValue9(evidence.repo),
|
|
6139
|
+
callerRepoId: routing.callerRepoId ?? numberValue7(evidence.repoId),
|
|
6140
|
+
routing
|
|
5153
6141
|
};
|
|
5154
6142
|
}
|
|
5155
|
-
function candidateTargets(db, evidence, workspaceId) {
|
|
5156
|
-
const embedded = rowsFromEvidence(evidence.candidates);
|
|
5157
|
-
if (embedded.length > 0) return embedded;
|
|
5158
|
-
const operationPath = effectiveSignal(evidence, "operationPath");
|
|
5159
|
-
if (!operationPath || extractPlaceholders(operationPath).length > 0) return [];
|
|
5160
|
-
return queryOperationTargets(db, operationPath, workspaceId);
|
|
5161
|
-
}
|
|
5162
|
-
function rowsFromEvidence(value) {
|
|
5163
|
-
if (!Array.isArray(value)) return [];
|
|
5164
|
-
return value.flatMap((item) => {
|
|
5165
|
-
const row = record(item);
|
|
5166
|
-
const operationId = numberValue3(row.operationId);
|
|
5167
|
-
const repoName = stringValue6(row.repoName);
|
|
5168
|
-
const servicePath = stringValue6(row.servicePath);
|
|
5169
|
-
const operationPath = stringValue6(row.operationPath);
|
|
5170
|
-
const operationName = stringValue6(row.operationName) ?? operationPath?.replace(/^\//, "");
|
|
5171
|
-
if (operationId === void 0 || !repoName || !servicePath || !operationPath || !operationName) return [];
|
|
5172
|
-
return [{
|
|
5173
|
-
operationId,
|
|
5174
|
-
repoId: numberValue3(row.repoId),
|
|
5175
|
-
repoName,
|
|
5176
|
-
packageName: stringValue6(row.packageName),
|
|
5177
|
-
serviceName: stringValue6(row.serviceName) ?? "",
|
|
5178
|
-
qualifiedName: stringValue6(row.qualifiedName) ?? "",
|
|
5179
|
-
servicePath,
|
|
5180
|
-
operationPath,
|
|
5181
|
-
operationName,
|
|
5182
|
-
sourceFile: stringValue6(row.sourceFile) ?? "",
|
|
5183
|
-
sourceLine: numberValue3(row.sourceLine) ?? 0,
|
|
5184
|
-
score: numberValue3(row.score) ?? 0,
|
|
5185
|
-
reasons: stringArray(row.reasons)
|
|
5186
|
-
}];
|
|
5187
|
-
});
|
|
5188
|
-
}
|
|
5189
|
-
function queryOperationTargets(db, operationPath, workspaceId) {
|
|
5190
|
-
const simple = operationPath.replace(/^\//, "").split(".").at(-1) ?? operationPath;
|
|
5191
|
-
const rows2 = db.prepare(
|
|
5192
|
-
`SELECT o.id operationId,r.id repoId,r.name repoName,r.package_name packageName,
|
|
5193
|
-
s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,
|
|
5194
|
-
o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,
|
|
5195
|
-
o.source_line sourceLine FROM cds_operations o
|
|
5196
|
-
JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
|
|
5197
|
-
WHERE (? IS NULL OR r.workspace_id=?)
|
|
5198
|
-
AND (o.operation_path IN (?,?) OR o.operation_name=?)
|
|
5199
|
-
ORDER BY r.name,s.service_path,o.operation_name,o.id`
|
|
5200
|
-
).all(workspaceId, workspaceId, operationPath, `/${simple}`, simple);
|
|
5201
|
-
return rows2.flatMap(operationTargetFromRow);
|
|
5202
|
-
}
|
|
5203
|
-
function operationTargetFromRow(row) {
|
|
5204
|
-
const operationId = numberValue3(row.operationId);
|
|
5205
|
-
const repoName = stringValue6(row.repoName);
|
|
5206
|
-
const servicePath = stringValue6(row.servicePath);
|
|
5207
|
-
const operationPath = stringValue6(row.operationPath);
|
|
5208
|
-
const operationName = stringValue6(row.operationName);
|
|
5209
|
-
if (operationId === void 0 || !repoName || !servicePath || !operationPath || !operationName) return [];
|
|
5210
|
-
return [{
|
|
5211
|
-
operationId,
|
|
5212
|
-
repoId: numberValue3(row.repoId),
|
|
5213
|
-
repoName,
|
|
5214
|
-
packageName: stringValue6(row.packageName),
|
|
5215
|
-
serviceName: stringValue6(row.serviceName) ?? "",
|
|
5216
|
-
qualifiedName: stringValue6(row.qualifiedName) ?? "",
|
|
5217
|
-
servicePath,
|
|
5218
|
-
operationPath,
|
|
5219
|
-
operationName,
|
|
5220
|
-
sourceFile: stringValue6(row.sourceFile) ?? "",
|
|
5221
|
-
sourceLine: numberValue3(row.sourceLine) ?? 0,
|
|
5222
|
-
score: 0.2,
|
|
5223
|
-
reasons: ["operation_path_match"]
|
|
5224
|
-
}];
|
|
5225
|
-
}
|
|
5226
6143
|
function buildCandidates(db, targets, references, inputs) {
|
|
5227
6144
|
return targets.map((target) => {
|
|
5228
6145
|
const state = emptyCandidate(target, inputs);
|
|
5229
6146
|
applyDirectSignal(state, inputs, "operationPath", target.operationPath, 0.25);
|
|
5230
6147
|
applyDirectSignal(state, inputs, "servicePath", target.servicePath, 0.35);
|
|
5231
|
-
const matchingReferences = references.filter((reference) => reference.servicePath
|
|
5232
|
-
|
|
5233
|
-
|
|
6148
|
+
const matchingReferences = references.filter((reference) => referenceMatchesCandidate(reference, target.servicePath) && referenceMatchesSelectedAlias(reference, inputs.routing.selectedBinding));
|
|
6149
|
+
const referencesForSignals = fallbackReferencesForCandidate(
|
|
6150
|
+
state,
|
|
6151
|
+
matchingReferences,
|
|
6152
|
+
inputs.routing.fallbackUsed
|
|
6153
|
+
);
|
|
6154
|
+
applyReferenceSignal(state, inputs, referencesForSignals, "alias");
|
|
6155
|
+
applyReferenceSignal(state, inputs, referencesForSignals, "destination");
|
|
5234
6156
|
if (hasResolvedImplementation(db, target.operationId))
|
|
5235
6157
|
addScore(state, 0.1, "implementation_edge_resolved");
|
|
5236
6158
|
return state;
|
|
5237
6159
|
});
|
|
5238
6160
|
}
|
|
6161
|
+
function fallbackReferencesForCandidate(state, references, fallbackUsed) {
|
|
6162
|
+
if (!fallbackUsed) return references;
|
|
6163
|
+
const unique3 = uniqueFallbackReferences(references);
|
|
6164
|
+
if (unique3.length <= 1) return unique3;
|
|
6165
|
+
addReason(state, "fallback_reference_ambiguous");
|
|
6166
|
+
addInferenceBlock(state, "fallback_reference_ambiguous");
|
|
6167
|
+
return [];
|
|
6168
|
+
}
|
|
6169
|
+
function uniqueFallbackReferences(references) {
|
|
6170
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6171
|
+
return references.filter((reference) => {
|
|
6172
|
+
const signature = [
|
|
6173
|
+
reference.sourceKind,
|
|
6174
|
+
reference.alias,
|
|
6175
|
+
reference.destination,
|
|
6176
|
+
reference.servicePath
|
|
6177
|
+
].join("\0");
|
|
6178
|
+
if (seen.has(signature)) return false;
|
|
6179
|
+
seen.add(signature);
|
|
6180
|
+
return true;
|
|
6181
|
+
});
|
|
6182
|
+
}
|
|
5239
6183
|
function emptyCandidate(target, inputs) {
|
|
5240
6184
|
return {
|
|
5241
6185
|
candidateOperationId: target.operationId,
|
|
@@ -5274,20 +6218,25 @@ function emptyCandidate(target, inputs) {
|
|
|
5274
6218
|
function applyDirectSignal(state, inputs, kind, concrete, score) {
|
|
5275
6219
|
const effective = inputs.effective[kind];
|
|
5276
6220
|
const original = inputs.original[kind];
|
|
5277
|
-
if (effective && !
|
|
6221
|
+
if (effective && !matchRuntimeTemplate(effective, concrete)) {
|
|
5278
6222
|
reject(state, `${signalCode(kind)}_contradicts_runtime_substitution`);
|
|
5279
6223
|
return;
|
|
5280
6224
|
}
|
|
5281
6225
|
if (!effective) return;
|
|
5282
6226
|
const suppliedKeys = extractPlaceholders(original).filter((key) => inputs.supplied[key] !== void 0);
|
|
5283
6227
|
state.explicitSignalStrength += suppliedKeys.length;
|
|
5284
|
-
const matched =
|
|
6228
|
+
const matched = matchRuntimeTemplate(original, concrete) ?? {};
|
|
6229
|
+
const fromSelectedBinding = kind === "servicePath" && inputs.routing.selectedBinding !== void 0;
|
|
5285
6230
|
for (const [key, value] of Object.entries(matched)) {
|
|
5286
6231
|
addDerivation(state, key, value, {
|
|
5287
|
-
sourceKind: `${signalCode(kind)}_template`,
|
|
6232
|
+
sourceKind: fromSelectedBinding ? `selected_binding.${signalCode(kind)}_template` : `${signalCode(kind)}_template`,
|
|
5288
6233
|
value,
|
|
5289
|
-
rule: "exact_template_match",
|
|
5290
|
-
template: original
|
|
6234
|
+
rule: fromSelectedBinding ? "exact_selected_binding_template_match" : "exact_template_match",
|
|
6235
|
+
template: original,
|
|
6236
|
+
sourceRepo: fromSelectedBinding ? inputs.routing.selectedBinding?.repoName : void 0,
|
|
6237
|
+
sourceFile: fromSelectedBinding ? inputs.routing.selectedBinding?.sourceFile : void 0,
|
|
6238
|
+
sourceLine: fromSelectedBinding ? inputs.routing.selectedBinding?.sourceLine : void 0,
|
|
6239
|
+
selection: fromSelectedBinding ? "selected_binding" : "call_evidence"
|
|
5291
6240
|
});
|
|
5292
6241
|
}
|
|
5293
6242
|
addScore(state, score, `${signalCode(kind)}_template_match`);
|
|
@@ -5305,7 +6254,7 @@ function applyReferenceSignal(state, inputs, references, kind) {
|
|
|
5305
6254
|
}
|
|
5306
6255
|
let matchedSignal = false;
|
|
5307
6256
|
for (const { reference, concrete } of values) {
|
|
5308
|
-
const matched =
|
|
6257
|
+
const matched = matchRuntimeTemplate(original, concrete);
|
|
5309
6258
|
if (!matched) continue;
|
|
5310
6259
|
matchedSignal = true;
|
|
5311
6260
|
for (const [key, value] of Object.entries(matched)) {
|
|
@@ -5322,9 +6271,9 @@ function applyReferenceSignal(state, inputs, references, kind) {
|
|
|
5322
6271
|
addScore(state, 0.2, `${kind}_template_match`);
|
|
5323
6272
|
}
|
|
5324
6273
|
}
|
|
5325
|
-
function applyUniqueIdentityEvidence(db,
|
|
5326
|
-
for (const derivation of uniqueIdentityDerivations(db,
|
|
5327
|
-
const candidate =
|
|
6274
|
+
function applyUniqueIdentityEvidence(db, candidates2, inputs) {
|
|
6275
|
+
for (const derivation of uniqueIdentityDerivations(db, candidates2, inputs.original)) {
|
|
6276
|
+
const candidate = candidates2.find((item) => item.candidateOperationId === derivation.operationId);
|
|
5328
6277
|
if (!candidate) continue;
|
|
5329
6278
|
addDerivation(candidate, derivation.key, derivation.value, derivation.provenance);
|
|
5330
6279
|
addScore(candidate, 0.2, "exact_identity_template_match");
|
|
@@ -5362,8 +6311,8 @@ function uniqueProvenance(rows2) {
|
|
|
5362
6311
|
return true;
|
|
5363
6312
|
});
|
|
5364
6313
|
}
|
|
5365
|
-
function finalizeCandidates(
|
|
5366
|
-
for (const candidate of
|
|
6314
|
+
function finalizeCandidates(candidates2, order) {
|
|
6315
|
+
for (const candidate of candidates2) {
|
|
5367
6316
|
candidate.missingVariables = order.filter((key) => candidate.completeVariables[key] === void 0);
|
|
5368
6317
|
candidate.viable = candidate.rejectedReasons.length === 0;
|
|
5369
6318
|
candidate.rejected = !candidate.viable;
|
|
@@ -5375,15 +6324,17 @@ function finalizeCandidates(candidates, order) {
|
|
|
5375
6324
|
candidate.cli = candidate.missingVariables.length === 0 && candidate.viable ? cliFor(candidate.completeVariables, order) : void 0;
|
|
5376
6325
|
}
|
|
5377
6326
|
}
|
|
5378
|
-
function stableRank(
|
|
5379
|
-
return [...
|
|
6327
|
+
function stableRank(candidates2) {
|
|
6328
|
+
return [...candidates2].sort((left, right) => Number(right.viable) - Number(left.viable) || right.score - left.score || right.explicitSignalStrength - left.explicitSignalStrength || left.repoName.localeCompare(right.repoName) || String(left.packageName ?? "").localeCompare(String(right.packageName ?? "")) || left.servicePath.localeCompare(right.servicePath) || left.operationPath.localeCompare(right.operationPath) || left.operationName.localeCompare(right.operationName) || left.candidateOperationId - right.candidateOperationId);
|
|
5380
6329
|
}
|
|
5381
|
-
function inferenceDecision(
|
|
5382
|
-
const viable =
|
|
6330
|
+
function inferenceDecision(candidates2) {
|
|
6331
|
+
const viable = candidates2.filter((candidate) => candidate.viable);
|
|
5383
6332
|
const first = viable[0];
|
|
5384
6333
|
const second = viable[1];
|
|
5385
6334
|
if (!first || first.missingVariables.length > 0)
|
|
5386
6335
|
return { status: "unresolved", reason: "missing_required_runtime_variable" };
|
|
6336
|
+
if (first.inferenceBlockReasons.length > 0)
|
|
6337
|
+
return { status: "unresolved", reason: first.inferenceBlockReasons[0] };
|
|
5387
6338
|
if (first.score < 0.85)
|
|
5388
6339
|
return { status: "unresolved", reason: "candidate_score_below_inference_threshold" };
|
|
5389
6340
|
const scoreGap = second ? Number((first.score - second.score).toFixed(12)) : void 0;
|
|
@@ -5402,57 +6353,90 @@ function inferenceDecision(candidates) {
|
|
|
5402
6353
|
};
|
|
5403
6354
|
}
|
|
5404
6355
|
function boundedCandidate(candidate, limit) {
|
|
5405
|
-
const
|
|
5406
|
-
Object.entries(candidate.derivationProvenance).sort(([left], [right]) => left.localeCompare(right)).map(([key, rows2]) => [key, rows2
|
|
6356
|
+
const provenanceProjections = Object.fromEntries(
|
|
6357
|
+
Object.entries(candidate.derivationProvenance).sort(([left], [right]) => left.localeCompare(right)).map(([key, rows2]) => [key, projectBounded(rows2, compareProvenance, limit)])
|
|
5407
6358
|
);
|
|
5408
|
-
const
|
|
6359
|
+
const derivationProvenance = Object.fromEntries(Object.entries(provenanceProjections).map(([key, projection]) => [key, projection.items]));
|
|
6360
|
+
const derivationProvenanceCounts = Object.fromEntries(Object.entries(provenanceProjections).map(([key, projection]) => [key, {
|
|
6361
|
+
provenanceCount: projection.totalCount,
|
|
6362
|
+
shownProvenanceCount: projection.shownCount,
|
|
6363
|
+
omittedProvenanceCount: projection.omittedCount
|
|
6364
|
+
}]));
|
|
6365
|
+
const conflicts = projectBounded(candidate.conflicts, compareConflict, limit);
|
|
6366
|
+
return {
|
|
6367
|
+
...candidate,
|
|
6368
|
+
derivationProvenance,
|
|
6369
|
+
derivationProvenanceCounts,
|
|
6370
|
+
conflicts: conflicts.items.map(boundedConflict),
|
|
6371
|
+
conflictCount: conflicts.totalCount,
|
|
6372
|
+
shownConflictCount: conflicts.shownCount,
|
|
6373
|
+
omittedConflictCount: conflicts.omittedCount
|
|
6374
|
+
};
|
|
6375
|
+
}
|
|
6376
|
+
function compareProvenance(left, right) {
|
|
6377
|
+
return left.sourceKind.localeCompare(right.sourceKind) || String(left.matchedName ?? "").localeCompare(String(right.matchedName ?? "")) || left.value.localeCompare(right.value);
|
|
6378
|
+
}
|
|
6379
|
+
function compareConflict(left, right) {
|
|
6380
|
+
return left.key.localeCompare(right.key) || left.reason.localeCompare(right.reason) || left.values.join("\0").localeCompare(right.values.join("\0"));
|
|
6381
|
+
}
|
|
6382
|
+
function boundedConflict(conflict) {
|
|
6383
|
+
const sources = projectBounded(conflict.sources, (left, right) => left.localeCompare(right));
|
|
6384
|
+
return {
|
|
5409
6385
|
...conflict,
|
|
5410
|
-
sources:
|
|
5411
|
-
|
|
5412
|
-
|
|
6386
|
+
sources: sources.items,
|
|
6387
|
+
sourceCount: sources.totalCount,
|
|
6388
|
+
shownSourceCount: sources.shownCount,
|
|
6389
|
+
omittedSourceCount: sources.omittedCount
|
|
6390
|
+
};
|
|
5413
6391
|
}
|
|
5414
|
-
function applyModeState(
|
|
5415
|
-
const selectedId = mode === "infer" && inference.status === "resolved" ?
|
|
5416
|
-
for (const candidate of
|
|
6392
|
+
function applyModeState(candidates2, mode, inference) {
|
|
6393
|
+
const selectedId = mode === "infer" && inference.status === "resolved" ? numberValue7(inference.candidateOperationId) : void 0;
|
|
6394
|
+
for (const candidate of candidates2) {
|
|
5417
6395
|
candidate.selected = selectedId === candidate.candidateOperationId;
|
|
5418
6396
|
candidate.exploratory = mode === "candidates" && candidate.viable;
|
|
5419
6397
|
}
|
|
5420
6398
|
}
|
|
5421
|
-
function suggestedVarSets(
|
|
6399
|
+
function suggestedVarSets(candidates2, order, limit) {
|
|
5422
6400
|
const seen = /* @__PURE__ */ new Set();
|
|
5423
6401
|
const rows2 = [];
|
|
5424
|
-
for (const candidate of
|
|
6402
|
+
for (const candidate of candidates2) {
|
|
5425
6403
|
if (!candidate.cli || candidate.missingVariables.length > 0) continue;
|
|
5426
6404
|
if (seen.has(candidate.cli)) continue;
|
|
5427
6405
|
seen.add(candidate.cli);
|
|
5428
6406
|
rows2.push({ variables: orderedVariables(candidate.completeVariables, order), cli: candidate.cli });
|
|
5429
|
-
if (rows2.length >= limit) break;
|
|
5430
6407
|
}
|
|
5431
|
-
return rows2;
|
|
6408
|
+
return projectBounded(rows2, (left, right) => left.cli.localeCompare(right.cli), limit);
|
|
5432
6409
|
}
|
|
5433
6410
|
function hasResolvedImplementation(db, operationId) {
|
|
5434
6411
|
return Boolean(db.prepare(
|
|
5435
6412
|
"SELECT 1 FROM graph_edges WHERE edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND status='resolved' AND from_kind='operation' AND from_id=? LIMIT 1"
|
|
5436
6413
|
).get(String(operationId)));
|
|
5437
6414
|
}
|
|
5438
|
-
function templatesFromEvidence(evidence,
|
|
6415
|
+
function templatesFromEvidence(evidence, routing) {
|
|
6416
|
+
const selected = routing.selectedBinding;
|
|
5439
6417
|
return {
|
|
5440
|
-
servicePath: substitutionSignal(evidence, "servicePath",
|
|
5441
|
-
operationPath: substitutionSignal(evidence, "operationPath",
|
|
5442
|
-
alias: substitutionSignal(
|
|
6418
|
+
servicePath: selected?.servicePath ?? substitutionSignal(evidence, "servicePath", "original"),
|
|
6419
|
+
operationPath: substitutionSignal(evidence, "operationPath", "original"),
|
|
6420
|
+
alias: selected?.aliasExpr ?? selected?.alias ?? substitutionSignal(
|
|
5443
6421
|
evidence,
|
|
5444
6422
|
evidence.serviceAliasExpr !== void 0 ? "serviceAliasExpr" : "serviceAlias",
|
|
5445
|
-
|
|
6423
|
+
"original"
|
|
5446
6424
|
),
|
|
5447
|
-
destination: substitutionSignal(evidence, "destination",
|
|
6425
|
+
destination: selected?.destination ?? substitutionSignal(evidence, "destination", "original")
|
|
5448
6426
|
};
|
|
5449
6427
|
}
|
|
5450
|
-
function
|
|
5451
|
-
const
|
|
5452
|
-
return
|
|
6428
|
+
function effectiveTemplates(templates, supplied) {
|
|
6429
|
+
const operationPath = applyVariables(templates.operationPath, supplied);
|
|
6430
|
+
return {
|
|
6431
|
+
servicePath: applyVariables(templates.servicePath, supplied),
|
|
6432
|
+
operationPath: normalizeODataOperationInvocationPath(operationPath)?.normalizedOperationPath ?? operationPath,
|
|
6433
|
+
alias: applyVariables(templates.alias, supplied),
|
|
6434
|
+
destination: applyVariables(templates.destination, supplied)
|
|
6435
|
+
};
|
|
5453
6436
|
}
|
|
5454
|
-
function
|
|
5455
|
-
|
|
6437
|
+
function substitutionSignal(evidence, key, phase) {
|
|
6438
|
+
const substitution = record3(record3(evidence.runtimeSubstitutions)[key]);
|
|
6439
|
+
return stringValue9(substitution[phase]) ?? stringValue9(evidence[key]);
|
|
5456
6440
|
}
|
|
5457
6441
|
function placeholderSources(templates) {
|
|
5458
6442
|
const sources = {};
|
|
@@ -5472,31 +6456,13 @@ function variableOrder(templates, required) {
|
|
|
5472
6456
|
].flatMap((value) => extractPlaceholders(value));
|
|
5473
6457
|
return [.../* @__PURE__ */ new Set([...ordered, ...required])];
|
|
5474
6458
|
}
|
|
5475
|
-
function
|
|
5476
|
-
|
|
5477
|
-
const keys = extractPlaceholders(template);
|
|
5478
|
-
if (keys.length === 0) return template === concrete ? {} : void 0;
|
|
5479
|
-
const match = new RegExp(`^${templateToPattern(template)}$`).exec(concrete);
|
|
5480
|
-
if (!match) return void 0;
|
|
5481
|
-
const values = {};
|
|
5482
|
-
for (let index = 0; index < keys.length; index += 1) {
|
|
5483
|
-
const key = keys[index];
|
|
5484
|
-
const value = match[index + 1];
|
|
5485
|
-
if (!key || value === void 0) return void 0;
|
|
5486
|
-
if (values[key] !== void 0 && values[key] !== value) return void 0;
|
|
5487
|
-
values[key] = value;
|
|
5488
|
-
}
|
|
5489
|
-
return values;
|
|
6459
|
+
function referenceMatchesCandidate(reference, servicePath) {
|
|
6460
|
+
return matchRuntimeTemplate(reference.servicePath, servicePath) !== void 0;
|
|
5490
6461
|
}
|
|
5491
|
-
function
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
pattern += escapeRegex2(template.slice(lastIndex, match.index));
|
|
5496
|
-
pattern += "([^/]+?)";
|
|
5497
|
-
lastIndex = (match.index ?? 0) + match[0].length;
|
|
5498
|
-
}
|
|
5499
|
-
return `${pattern}${escapeRegex2(template.slice(lastIndex))}`;
|
|
6462
|
+
function referenceMatchesSelectedAlias(reference, selected) {
|
|
6463
|
+
if (reference.selection !== "selected_binding_require") return true;
|
|
6464
|
+
const template = selected?.aliasExpr ?? selected?.alias;
|
|
6465
|
+
return matchRuntimeTemplate(template, reference.alias) !== void 0;
|
|
5500
6466
|
}
|
|
5501
6467
|
function cliFor(variables, order) {
|
|
5502
6468
|
return order.filter((key) => variables[key] !== void 0).map((key) => `--var ${shellArgument(`${key}=${variables[key]}`)}`).join(" ");
|
|
@@ -5530,35 +6496,59 @@ function addInferenceBlock(state, reason) {
|
|
|
5530
6496
|
function requiredSuppliedVariables(inputs) {
|
|
5531
6497
|
return Object.fromEntries(inputs.required.flatMap((key) => inputs.supplied[key] === void 0 ? [] : [[key, inputs.supplied[key]]]));
|
|
5532
6498
|
}
|
|
6499
|
+
function routingEvidence2(routing) {
|
|
6500
|
+
const binding = routing.selectedBinding;
|
|
6501
|
+
return {
|
|
6502
|
+
outboundCallId: routing.outboundCallId,
|
|
6503
|
+
callerRepoId: routing.callerRepoId,
|
|
6504
|
+
callerRepo: routing.callerRepo,
|
|
6505
|
+
selectedBindingId: routing.selectedBindingId,
|
|
6506
|
+
bindingResolutionStatus: routing.bindingResolutionStatus,
|
|
6507
|
+
selectedBinding: binding ? {
|
|
6508
|
+
bindingId: binding.bindingId,
|
|
6509
|
+
alias: binding.alias,
|
|
6510
|
+
aliasExpr: binding.aliasExpr,
|
|
6511
|
+
destination: binding.destination,
|
|
6512
|
+
destinationExpr: binding.destination,
|
|
6513
|
+
servicePath: binding.servicePath,
|
|
6514
|
+
servicePathExpr: binding.servicePath,
|
|
6515
|
+
sourceFile: binding.sourceFile,
|
|
6516
|
+
sourceLine: binding.sourceLine,
|
|
6517
|
+
helperChain: binding.helperChain
|
|
6518
|
+
} : void 0,
|
|
6519
|
+
bindingAlternativeCount: routing.bindingAlternativeCount,
|
|
6520
|
+
shownBindingAlternativeCount: routing.shownBindingAlternativeCount,
|
|
6521
|
+
omittedBindingAlternativeCount: routing.omittedBindingAlternativeCount,
|
|
6522
|
+
bindingAlternatives: routing.bindingAlternatives,
|
|
6523
|
+
fallbackUsed: routing.fallbackUsed
|
|
6524
|
+
};
|
|
6525
|
+
}
|
|
5533
6526
|
function signalCode(kind) {
|
|
5534
6527
|
return kind === "servicePath" ? "service_path" : "operation_path";
|
|
5535
6528
|
}
|
|
5536
|
-
function
|
|
6529
|
+
function record3(value) {
|
|
5537
6530
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
5538
6531
|
}
|
|
5539
6532
|
function stringRecord(value) {
|
|
5540
|
-
const entries = Object.entries(
|
|
6533
|
+
const entries = Object.entries(record3(value)).filter((entry) => typeof entry[1] === "string").sort(([left], [right]) => left.localeCompare(right));
|
|
5541
6534
|
return Object.fromEntries(entries);
|
|
5542
6535
|
}
|
|
5543
|
-
function
|
|
6536
|
+
function stringValue9(value) {
|
|
5544
6537
|
return typeof value === "string" ? value : void 0;
|
|
5545
6538
|
}
|
|
5546
|
-
function
|
|
6539
|
+
function numberValue7(value) {
|
|
5547
6540
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5548
6541
|
}
|
|
5549
|
-
function
|
|
6542
|
+
function stringArray4(value) {
|
|
5550
6543
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
5551
6544
|
}
|
|
5552
6545
|
function nonEmptyStrings(value, fallback) {
|
|
5553
|
-
const values =
|
|
6546
|
+
const values = stringArray4(value).filter((item) => item.length > 0);
|
|
5554
6547
|
return values.length > 0 ? values : fallback;
|
|
5555
6548
|
}
|
|
5556
6549
|
function isConcrete(value) {
|
|
5557
6550
|
return typeof value === "string" && value.length > 0 && extractPlaceholders(value).length === 0;
|
|
5558
6551
|
}
|
|
5559
|
-
function escapeRegex2(value) {
|
|
5560
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5561
|
-
}
|
|
5562
6552
|
|
|
5563
6553
|
// src/trace/evidence.ts
|
|
5564
6554
|
function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence) {
|
|
@@ -5583,13 +6573,14 @@ function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence) {
|
|
|
5583
6573
|
function runtimeResolution(db, row, evidence, options, workspaceId, contextualUnresolvedReason) {
|
|
5584
6574
|
const dynamicMode = options.dynamicMode ?? "strict";
|
|
5585
6575
|
const candidateCap = positiveCandidateCap(options.maxDynamicCandidates);
|
|
6576
|
+
const boundedEvidence = boundCandidateLikeEvidence(evidence, candidateCap);
|
|
5586
6577
|
if (!isDynamicRemoteOperationEdge(row, evidence))
|
|
5587
6578
|
return unchangedRuntimeResolution(
|
|
5588
6579
|
row,
|
|
5589
|
-
boundDynamicEvidence(
|
|
6580
|
+
boundDynamicEvidence(boundedEvidence, candidateCap),
|
|
5590
6581
|
contextualUnresolvedReason
|
|
5591
6582
|
);
|
|
5592
|
-
const substituted = evidenceWithRuntimeVariables(
|
|
6583
|
+
const substituted = evidenceWithRuntimeVariables(boundedEvidence, options.vars);
|
|
5593
6584
|
const analysis = analyzeDynamicTargetCandidates(
|
|
5594
6585
|
db,
|
|
5595
6586
|
substituted,
|
|
@@ -5601,13 +6592,21 @@ function runtimeResolution(db, row, evidence, options, workspaceId, contextualUn
|
|
|
5601
6592
|
analysis ? evidenceWithDynamicAnalysis(substituted, analysis) : substituted,
|
|
5602
6593
|
candidateCap
|
|
5603
6594
|
);
|
|
5604
|
-
if (analysis && analysis.
|
|
6595
|
+
if (analysis && analysis.viableCandidateCount === 0 && Object.keys(analysis.appliedSuppliedVariables).length > 0)
|
|
5605
6596
|
return noCandidateRuntimeResolution(row, enriched);
|
|
5606
6597
|
if (dynamicMode === "infer") {
|
|
5607
6598
|
const inferred = inferredTarget(analysis);
|
|
5608
6599
|
if (inferred)
|
|
5609
6600
|
return resolvedRuntimeResolution(row, enriched, inferred, inferred.reasons);
|
|
5610
6601
|
}
|
|
6602
|
+
if (analysis && analysis.missingVariables.length > 0) {
|
|
6603
|
+
const unresolvedReason2 = contextualUnresolvedReason ?? row.unresolved_reason ?? "Dynamic target still requires runtime variables";
|
|
6604
|
+
return {
|
|
6605
|
+
row,
|
|
6606
|
+
evidence: withEffectiveResolution(enriched, row, unresolvedReason2),
|
|
6607
|
+
unresolvedReason: unresolvedReason2
|
|
6608
|
+
};
|
|
6609
|
+
}
|
|
5611
6610
|
if (!hasApplicableRuntimeVariables(evidence, options.vars)) {
|
|
5612
6611
|
const unresolvedReason2 = contextualUnresolvedReason ?? row.unresolved_reason;
|
|
5613
6612
|
const withSections = withEffectiveResolution(enriched, row, unresolvedReason2);
|
|
@@ -5621,8 +6620,6 @@ function runtimeResolution(db, row, evidence, options, workspaceId, contextualUn
|
|
|
5621
6620
|
resolution.target,
|
|
5622
6621
|
resolution.reasons
|
|
5623
6622
|
);
|
|
5624
|
-
if (analysis && analysis.viableCandidateCount === 0 && Object.keys(analysis.appliedSuppliedVariables).length > 0)
|
|
5625
|
-
return noCandidateRuntimeResolution(row, enriched);
|
|
5626
6623
|
const unresolvedReason = runtimeUnresolvedReason(resolution);
|
|
5627
6624
|
return { row, evidence: withEffectiveResolution(enriched, row, unresolvedReason, resolution), unresolvedReason };
|
|
5628
6625
|
}
|
|
@@ -5714,17 +6711,17 @@ function runtimeDiagnosticTotals(edges) {
|
|
|
5714
6711
|
rejectedCandidateCount += numeric(exploration.rejectedCandidateCount);
|
|
5715
6712
|
appendBounded(
|
|
5716
6713
|
candidateSuggestions,
|
|
5717
|
-
|
|
6714
|
+
recordArray3(edge.evidence.dynamicTargetCandidateSuggestions),
|
|
5718
6715
|
maxCandidates
|
|
5719
6716
|
);
|
|
5720
6717
|
appendBounded(
|
|
5721
6718
|
rejectedCandidates,
|
|
5722
|
-
|
|
6719
|
+
recordArray3(exploration.rejectedCandidates),
|
|
5723
6720
|
maxCandidates
|
|
5724
6721
|
);
|
|
5725
6722
|
appendBounded(
|
|
5726
6723
|
suggestedVarSets2,
|
|
5727
|
-
|
|
6724
|
+
recordArray3(exploration.suggestedVarSets),
|
|
5728
6725
|
maxCandidates
|
|
5729
6726
|
);
|
|
5730
6727
|
}
|
|
@@ -5771,27 +6768,27 @@ function runtimeNoCandidateDiagnostics(edges) {
|
|
|
5771
6768
|
omittedRejectedCandidateCount: numeric(
|
|
5772
6769
|
exploration.omittedRejectedCandidateCount
|
|
5773
6770
|
),
|
|
5774
|
-
rejectedCandidates:
|
|
6771
|
+
rejectedCandidates: recordArray3(exploration.rejectedCandidates).slice(0, maxCandidates),
|
|
5775
6772
|
callSite
|
|
5776
6773
|
}];
|
|
5777
6774
|
});
|
|
5778
6775
|
}
|
|
5779
6776
|
function edgeTarget(row, evidence) {
|
|
5780
6777
|
const effective = parseObject(evidence.effectiveResolution);
|
|
5781
|
-
const targetServicePath =
|
|
5782
|
-
const targetOperationPath =
|
|
6778
|
+
const targetServicePath = stringValue10(effective.targetServicePath ?? evidence.targetServicePath);
|
|
6779
|
+
const targetOperationPath = stringValue10(effective.targetOperationPath ?? evidence.targetOperationPath);
|
|
5783
6780
|
if (targetServicePath && targetOperationPath) return `${targetServicePath}${targetOperationPath}`;
|
|
5784
6781
|
const runtimeCandidate = evidence.runtimeResolvedCandidate;
|
|
5785
6782
|
if (runtimeCandidate?.servicePath && runtimeCandidate.operationPath) return `${runtimeCandidate.servicePath}${runtimeCandidate.operationPath}`;
|
|
5786
|
-
const servicePath =
|
|
5787
|
-
const operationPath =
|
|
5788
|
-
const targetOperation =
|
|
5789
|
-
const targetRepo =
|
|
6783
|
+
const servicePath = stringValue10(evidence.servicePath);
|
|
6784
|
+
const operationPath = stringValue10(evidence.operationPath);
|
|
6785
|
+
const targetOperation = stringValue10(evidence.targetOperation);
|
|
6786
|
+
const targetRepo = stringValue10(evidence.targetRepo) ?? "";
|
|
5790
6787
|
if (row.edge_type === "HANDLER_RUNS_DB_QUERY") return `Entity: ${row.to_id || "unknown"}`;
|
|
5791
|
-
if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return
|
|
6788
|
+
if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return stringValue10(evidence.remoteQueryTarget) ?? `Remote query: ${row.to_id || "unknown"}`;
|
|
5792
6789
|
if (row.edge_type === "HANDLER_CALLS_EXTERNAL_HTTP") {
|
|
5793
6790
|
const target = parseObject(evidence.externalTarget);
|
|
5794
|
-
return
|
|
6791
|
+
return stringValue10(target.label) ?? `External endpoint: ${row.to_id || "unknown"}`;
|
|
5795
6792
|
}
|
|
5796
6793
|
if (servicePath && operationPath) return `${servicePath}${operationPath}`;
|
|
5797
6794
|
return targetOperation ? `${targetRepo}:${targetOperation}` : row.to_id;
|
|
@@ -5830,12 +6827,12 @@ function withEffectiveResolution(evidence, row, unresolvedReason, resolution) {
|
|
|
5830
6827
|
return { ...rest, effectiveResolution: current, linker: { status: current.status, confidence: current.confidence, reason: unresolvedReason, edgeType: current.edgeType } };
|
|
5831
6828
|
}
|
|
5832
6829
|
function resolveRuntimeOperation(db, evidence, workspaceId) {
|
|
5833
|
-
const servicePath =
|
|
5834
|
-
const rawOperationPath =
|
|
6830
|
+
const servicePath = stringValue10(evidence.servicePath);
|
|
6831
|
+
const rawOperationPath = stringValue10(evidence.operationPath);
|
|
5835
6832
|
const normalized = normalizeODataOperationInvocationPath(rawOperationPath);
|
|
5836
|
-
const operationPath = normalized?.wasInvocation ? normalized.normalizedOperationPath :
|
|
5837
|
-
const alias =
|
|
5838
|
-
const destination =
|
|
6833
|
+
const operationPath = normalized?.wasInvocation ? normalized.normalizedOperationPath : stringValue10(evidence.normalizedOperationPath) ?? rawOperationPath;
|
|
6834
|
+
const alias = stringValue10(evidence.serviceAliasExpr ?? evidence.serviceAlias);
|
|
6835
|
+
const destination = stringValue10(evidence.destination);
|
|
5839
6836
|
return resolveOperation(db, { servicePath, operationPath, alias, destination, hasExplicitOverride: true, isDynamic: true }, workspaceId);
|
|
5840
6837
|
}
|
|
5841
6838
|
function evidenceWithRuntimeVariables(evidence, vars) {
|
|
@@ -5855,16 +6852,23 @@ function evidenceWithRuntimeVariables(evidence, vars) {
|
|
|
5855
6852
|
return next;
|
|
5856
6853
|
}
|
|
5857
6854
|
function evidenceWithDynamicAnalysis(evidence, analysis) {
|
|
5858
|
-
const persistedCandidates =
|
|
5859
|
-
const persistedScores =
|
|
6855
|
+
const persistedCandidates = recordArray3(evidence.candidates);
|
|
6856
|
+
const persistedScores = recordArray3(evidence.candidateScores);
|
|
6857
|
+
const persistedCandidateCount = numeric(evidence.persistedCandidateCount) || numeric(evidence.candidateCount) || persistedCandidates.length;
|
|
6858
|
+
const persistedScoreCount = numeric(evidence.candidateScoreCount) || persistedScores.length;
|
|
5860
6859
|
return {
|
|
5861
6860
|
...evidence,
|
|
5862
6861
|
candidates: persistedCandidates.slice(0, analysis.maxCandidates),
|
|
5863
6862
|
candidateScores: persistedScores.slice(0, analysis.maxCandidates),
|
|
5864
|
-
persistedCandidateCount
|
|
6863
|
+
persistedCandidateCount,
|
|
5865
6864
|
persistedCandidateOmittedCount: Math.max(
|
|
5866
6865
|
0,
|
|
5867
|
-
persistedCandidates.length
|
|
6866
|
+
persistedCandidateCount - Math.min(persistedCandidates.length, analysis.maxCandidates)
|
|
6867
|
+
),
|
|
6868
|
+
persistedCandidateScoreCount: persistedScoreCount,
|
|
6869
|
+
persistedCandidateScoreOmittedCount: Math.max(
|
|
6870
|
+
0,
|
|
6871
|
+
persistedScoreCount - Math.min(persistedScores.length, analysis.maxCandidates)
|
|
5868
6872
|
),
|
|
5869
6873
|
dynamicTargetExploration: {
|
|
5870
6874
|
mode: analysis.mode,
|
|
@@ -5882,10 +6886,17 @@ function evidenceWithDynamicAnalysis(evidence, analysis) {
|
|
|
5882
6886
|
shownRejectedCandidateCount: analysis.shownRejectedCandidateCount,
|
|
5883
6887
|
omittedRejectedCandidateCount: analysis.omittedRejectedCandidateCount,
|
|
5884
6888
|
rejectedCandidates: analysis.rejectedCandidates,
|
|
5885
|
-
suggestedVarSets: analysis.suggestedVarSets
|
|
6889
|
+
suggestedVarSets: analysis.suggestedVarSets,
|
|
6890
|
+
suggestedVarSetCount: analysis.suggestedVarSetCount,
|
|
6891
|
+
shownSuggestedVarSetCount: analysis.shownSuggestedVarSetCount,
|
|
6892
|
+
omittedSuggestedVarSetCount: analysis.omittedSuggestedVarSetCount,
|
|
6893
|
+
routingContext: analysis.routingContext
|
|
5886
6894
|
},
|
|
5887
6895
|
dynamicTargetCandidates: analysis.candidates,
|
|
5888
6896
|
dynamicTargetCandidateSuggestions: analysis.shownCandidates,
|
|
6897
|
+
dynamicTargetCandidateSuggestionCount: analysis.viableCandidateCount,
|
|
6898
|
+
shownDynamicTargetCandidateSuggestionCount: analysis.shownCandidateCount,
|
|
6899
|
+
omittedDynamicTargetCandidateSuggestionCount: analysis.omittedCandidateCount,
|
|
5889
6900
|
rejectedCandidates: analysis.rejectedCandidates,
|
|
5890
6901
|
dynamicTargetInference: analysis.inference
|
|
5891
6902
|
};
|
|
@@ -5900,10 +6911,11 @@ var boundedDynamicListKeys = /* @__PURE__ */ new Set([
|
|
|
5900
6911
|
"rejectedCandidates",
|
|
5901
6912
|
"rejectedCandidateSuggestions",
|
|
5902
6913
|
"copyableExamples",
|
|
5903
|
-
"conflicts"
|
|
6914
|
+
"conflicts",
|
|
6915
|
+
"bindingAlternatives"
|
|
5904
6916
|
]);
|
|
5905
6917
|
function boundDynamicEvidence(evidence, limit) {
|
|
5906
|
-
const candidateCount = numeric(evidence.persistedCandidateCount) || (Array.isArray(evidence.candidates) ? evidence.candidates.length : 0);
|
|
6918
|
+
const candidateCount = numeric(evidence.persistedCandidateCount) || numeric(evidence.candidateCount) || (Array.isArray(evidence.candidates) ? evidence.candidates.length : 0);
|
|
5907
6919
|
const projected = boundDynamicValue(evidence, limit);
|
|
5908
6920
|
const next = parseObject(projected);
|
|
5909
6921
|
if (candidateCount === 0) return next;
|
|
@@ -5935,7 +6947,7 @@ function runtimeSubstitutions(evidence, vars) {
|
|
|
5935
6947
|
return substitutions;
|
|
5936
6948
|
}
|
|
5937
6949
|
function substitutionValue(evidence, key) {
|
|
5938
|
-
const value =
|
|
6950
|
+
const value = stringValue10(evidence[key]);
|
|
5939
6951
|
if (key !== "operationPath") return value;
|
|
5940
6952
|
const normalized = normalizeODataOperationInvocationPath(value);
|
|
5941
6953
|
return normalized?.wasInvocation ? normalized.normalizedOperationPath : value;
|
|
@@ -5984,13 +6996,13 @@ function runtimeUnresolvedReason(resolution) {
|
|
|
5984
6996
|
function parseObject(value) {
|
|
5985
6997
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
5986
6998
|
}
|
|
5987
|
-
function
|
|
6999
|
+
function stringValue10(value) {
|
|
5988
7000
|
return typeof value === "string" ? value : void 0;
|
|
5989
7001
|
}
|
|
5990
7002
|
function numeric(value) {
|
|
5991
7003
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
5992
7004
|
}
|
|
5993
|
-
function
|
|
7005
|
+
function recordArray3(value) {
|
|
5994
7006
|
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
5995
7007
|
}
|
|
5996
7008
|
function appendBounded(target, values, limit) {
|
|
@@ -6017,8 +7029,8 @@ function positiveCandidateCap(value) {
|
|
|
6017
7029
|
|
|
6018
7030
|
// src/trace/dynamic-branches.ts
|
|
6019
7031
|
function dynamicCandidateBranches(depth, call, evidence) {
|
|
6020
|
-
const exploration =
|
|
6021
|
-
return
|
|
7032
|
+
const exploration = objectRecord2(evidence.dynamicTargetExploration);
|
|
7033
|
+
return recordArray4(evidence.dynamicTargetCandidateSuggestions).map((candidate) => ({
|
|
6022
7034
|
step: depth,
|
|
6023
7035
|
type: "dynamic_candidate_branch",
|
|
6024
7036
|
from: `${call.repoName}:${call.source_file}:${call.source_line}`,
|
|
@@ -6028,19 +7040,19 @@ function dynamicCandidateBranches(depth, call, evidence) {
|
|
|
6028
7040
|
exploratory: true,
|
|
6029
7041
|
dynamicMode: String(exploration.mode ?? "candidates"),
|
|
6030
7042
|
selected: false,
|
|
6031
|
-
omittedCandidateCount:
|
|
7043
|
+
omittedCandidateCount: numericValue4(exploration.omittedCandidateCount)
|
|
6032
7044
|
},
|
|
6033
|
-
confidence:
|
|
7045
|
+
confidence: numericValue4(candidate.score),
|
|
6034
7046
|
unresolvedReason: "Exploratory dynamic target candidate; provide runtime variables to select it"
|
|
6035
7047
|
}));
|
|
6036
7048
|
}
|
|
6037
|
-
function
|
|
7049
|
+
function objectRecord2(value) {
|
|
6038
7050
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
6039
7051
|
}
|
|
6040
|
-
function
|
|
7052
|
+
function recordArray4(value) {
|
|
6041
7053
|
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
6042
7054
|
}
|
|
6043
|
-
function
|
|
7055
|
+
function numericValue4(value) {
|
|
6044
7056
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
6045
7057
|
}
|
|
6046
7058
|
|
|
@@ -6067,19 +7079,189 @@ function prependTraceDiagnostic(diagnostics, diagnostic) {
|
|
|
6067
7079
|
}
|
|
6068
7080
|
function sameDiagnosticLocation(left, right) {
|
|
6069
7081
|
if (left.code !== right.code) return false;
|
|
6070
|
-
const leftFile =
|
|
6071
|
-
const rightFile =
|
|
7082
|
+
const leftFile = stringValue11(left.sourceFile);
|
|
7083
|
+
const rightFile = stringValue11(right.sourceFile);
|
|
6072
7084
|
if (leftFile || rightFile)
|
|
6073
|
-
return leftFile === rightFile &&
|
|
7085
|
+
return leftFile === rightFile && numericValue5(left.sourceLine) === numericValue5(right.sourceLine);
|
|
6074
7086
|
return String(left.message ?? "") === String(right.message ?? "");
|
|
6075
7087
|
}
|
|
6076
|
-
function
|
|
7088
|
+
function stringValue11(value) {
|
|
6077
7089
|
return typeof value === "string" ? value : void 0;
|
|
6078
7090
|
}
|
|
6079
|
-
function
|
|
7091
|
+
function numericValue5(value) {
|
|
7092
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
7093
|
+
}
|
|
7094
|
+
|
|
7095
|
+
// src/trace/005-implementation-selection.ts
|
|
7096
|
+
function hintedImplementationSelection(db, edge, operationId, options) {
|
|
7097
|
+
if (!edge || edge.status !== "ambiguous")
|
|
7098
|
+
return { blocksAutomatic: false, evidence: { status: "not_applicable" } };
|
|
7099
|
+
return selectImplementation(
|
|
7100
|
+
parsedEvidence(edge.evidence_json),
|
|
7101
|
+
options.implementationHints,
|
|
7102
|
+
options.implementationRepo,
|
|
7103
|
+
canonicalImplementationEvidence(db, operationId)
|
|
7104
|
+
);
|
|
7105
|
+
}
|
|
7106
|
+
function contextualImplementationSelection(db, edge, operationId, callerRepoId, remoteEvidence, options) {
|
|
7107
|
+
const hinted = hintedImplementationSelection(db, edge, operationId, options);
|
|
7108
|
+
if (hinted.methodId || hinted.blocksAutomatic || !edge || edge.status !== "ambiguous" || callerRepoId === void 0) return hinted;
|
|
7109
|
+
const candidates2 = implementationCandidates2(
|
|
7110
|
+
canonicalImplementationEvidence(db, operationId) ?? parsedEvidence(edge.evidence_json)
|
|
7111
|
+
);
|
|
7112
|
+
const scores = candidates2.filter((candidate) => candidate.accepted).map((candidate) => contextualScore(candidate, callerRepoId, remoteEvidence)).sort(compareScore);
|
|
7113
|
+
if (scores.length === 0)
|
|
7114
|
+
return { blocksAutomatic: false, evidence: { status: "not_applicable", candidateScores: [] } };
|
|
7115
|
+
const [first, second] = scores;
|
|
7116
|
+
if (first?.methodId !== void 0 && first.score > 0 && (!second || first.score > second.score))
|
|
7117
|
+
return selectedContext(first, scores);
|
|
7118
|
+
return tiedContext(hinted, scores);
|
|
7119
|
+
}
|
|
7120
|
+
function selectedContext(first, scores) {
|
|
7121
|
+
const projection = projectBounded(scores, compareScore);
|
|
7122
|
+
return {
|
|
7123
|
+
methodId: String(first.methodId),
|
|
7124
|
+
blocksAutomatic: false,
|
|
7125
|
+
evidence: {
|
|
7126
|
+
status: "selected",
|
|
7127
|
+
selectedMethodId: first.methodId,
|
|
7128
|
+
candidateScores: projection.items,
|
|
7129
|
+
candidateScoreCount: projection.totalCount,
|
|
7130
|
+
shownCandidateScoreCount: projection.shownCount,
|
|
7131
|
+
omittedCandidateScoreCount: projection.omittedCount
|
|
7132
|
+
}
|
|
7133
|
+
};
|
|
7134
|
+
}
|
|
7135
|
+
function tiedContext(hinted, scores) {
|
|
7136
|
+
if (hinted.evidence.reason === "no_scoped_hint_matched_edge") return hinted;
|
|
7137
|
+
const projection = projectBounded(scores, compareScore);
|
|
7138
|
+
return {
|
|
7139
|
+
blocksAutomatic: false,
|
|
7140
|
+
evidence: {
|
|
7141
|
+
status: "tied",
|
|
7142
|
+
tieReason: scores.length > 1 ? "duplicate_helper_implementation_candidates" : "no_unique_materially_stronger_candidate",
|
|
7143
|
+
candidateScores: projection.items,
|
|
7144
|
+
candidateScoreCount: projection.totalCount,
|
|
7145
|
+
shownCandidateScoreCount: projection.shownCount,
|
|
7146
|
+
omittedCandidateScoreCount: projection.omittedCount
|
|
7147
|
+
}
|
|
7148
|
+
};
|
|
7149
|
+
}
|
|
7150
|
+
function implementationCandidates2(evidence) {
|
|
7151
|
+
const rows2 = Array.isArray(evidence.candidates) ? evidence.candidates : [];
|
|
7152
|
+
return rows2.flatMap((value) => {
|
|
7153
|
+
const row = record4(value);
|
|
7154
|
+
return Object.keys(row).length === 0 ? [] : [{
|
|
7155
|
+
accepted: row.accepted === true,
|
|
7156
|
+
methodId: numberValue8(row.methodId),
|
|
7157
|
+
score: numberValue8(row.score) ?? 0,
|
|
7158
|
+
handlerPackage: record4(row.handlerPackage),
|
|
7159
|
+
applicationPackage: record4(row.applicationPackage)
|
|
7160
|
+
}];
|
|
7161
|
+
});
|
|
7162
|
+
}
|
|
7163
|
+
function contextualScore(candidate, callerRepoId, remoteEvidence) {
|
|
7164
|
+
const reasons = [];
|
|
7165
|
+
let score = candidate.score;
|
|
7166
|
+
if (numberValue8(candidate.handlerPackage.id) === callerRepoId) {
|
|
7167
|
+
score += 10;
|
|
7168
|
+
reasons.push("handler_package_matches_caller_repository");
|
|
7169
|
+
}
|
|
7170
|
+
if (numberValue8(candidate.applicationPackage.id) === callerRepoId) {
|
|
7171
|
+
score += 10;
|
|
7172
|
+
reasons.push("registration_package_matches_caller_repository");
|
|
7173
|
+
}
|
|
7174
|
+
if (hasRemoteContext(remoteEvidence)) {
|
|
7175
|
+
score += 1;
|
|
7176
|
+
reasons.push("remote_call_context_available");
|
|
7177
|
+
}
|
|
7178
|
+
return {
|
|
7179
|
+
methodId: candidate.methodId,
|
|
7180
|
+
score,
|
|
7181
|
+
reasons,
|
|
7182
|
+
handlerPackage: candidate.handlerPackage,
|
|
7183
|
+
applicationPackage: candidate.applicationPackage
|
|
7184
|
+
};
|
|
7185
|
+
}
|
|
7186
|
+
function hasRemoteContext(evidence) {
|
|
7187
|
+
return typeof evidence.effectiveServicePath === "string" || typeof evidence.effectiveDestination === "string" || typeof evidence.effectiveAlias === "string";
|
|
7188
|
+
}
|
|
7189
|
+
function compareScore(left, right) {
|
|
7190
|
+
return right.score - left.score || Number(left.methodId ?? 0) - Number(right.methodId ?? 0);
|
|
7191
|
+
}
|
|
7192
|
+
function parsedEvidence(value) {
|
|
7193
|
+
try {
|
|
7194
|
+
return record4(JSON.parse(String(value ?? "{}")));
|
|
7195
|
+
} catch {
|
|
7196
|
+
return {};
|
|
7197
|
+
}
|
|
7198
|
+
}
|
|
7199
|
+
function record4(value) {
|
|
7200
|
+
return isRecord5(value) ? value : {};
|
|
7201
|
+
}
|
|
7202
|
+
function isRecord5(value) {
|
|
7203
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
7204
|
+
}
|
|
7205
|
+
function numberValue8(value) {
|
|
6080
7206
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
6081
7207
|
}
|
|
6082
7208
|
|
|
7209
|
+
// src/trace/006-contextual-projection.ts
|
|
7210
|
+
function boundedContextCandidates(values) {
|
|
7211
|
+
const candidates2 = values.flatMap((value) => {
|
|
7212
|
+
return isRecord6(value) ? [value] : [];
|
|
7213
|
+
});
|
|
7214
|
+
const projection = projectBounded(candidates2, (left, right) => Number(right.score ?? 0) - Number(left.score ?? 0) || String(left.repoName ?? "").localeCompare(String(right.repoName ?? "")) || String(left.servicePath ?? "").localeCompare(String(right.servicePath ?? "")) || String(left.sourceFile ?? "").localeCompare(String(right.sourceFile ?? "")) || Number(left.sourceLine ?? 0) - Number(right.sourceLine ?? 0) || Number(left.bindingId ?? left.operationId ?? 0) - Number(right.bindingId ?? right.operationId ?? 0));
|
|
7215
|
+
return {
|
|
7216
|
+
candidates: projection.items,
|
|
7217
|
+
candidateCount: projection.totalCount,
|
|
7218
|
+
shownCandidateCount: projection.shownCount,
|
|
7219
|
+
omittedCandidateCount: projection.omittedCount
|
|
7220
|
+
};
|
|
7221
|
+
}
|
|
7222
|
+
function isRecord6(value) {
|
|
7223
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
7224
|
+
}
|
|
7225
|
+
|
|
7226
|
+
// src/trace/007-implementation-start-diagnostic.ts
|
|
7227
|
+
function implementationStartDiagnostic(edge, evidence) {
|
|
7228
|
+
return {
|
|
7229
|
+
severity: "warning",
|
|
7230
|
+
code: edge.status === "ambiguous" ? "trace_start_ambiguous" : "trace_start_implementation_unresolved",
|
|
7231
|
+
message: `Indexed operation matched but implementation edge is ${String(
|
|
7232
|
+
edge.status ?? "unresolved"
|
|
7233
|
+
)}`,
|
|
7234
|
+
resolutionStage: "implementation",
|
|
7235
|
+
resolutionStatus: edge.status === "ambiguous" ? "ambiguous_implementation" : "rejected_implementation",
|
|
7236
|
+
implementationEdgeId: edge.id,
|
|
7237
|
+
implementationStatus: edge.status,
|
|
7238
|
+
implementationAmbiguityReasons: evidence.ambiguityReasons,
|
|
7239
|
+
implementationRejectionReasons: implementationRejectionReasons(evidence),
|
|
7240
|
+
implementationHintSuggestions: evidence.implementationHintSuggestions,
|
|
7241
|
+
implementationHintSuggestionCount: evidence.implementationHintSuggestionCount,
|
|
7242
|
+
shownImplementationHintSuggestionCount: evidence.shownImplementationHintSuggestionCount,
|
|
7243
|
+
omittedImplementationHintSuggestionCount: evidence.omittedImplementationHintSuggestionCount,
|
|
7244
|
+
candidates: evidence.candidates,
|
|
7245
|
+
candidateCount: evidence.candidateCount,
|
|
7246
|
+
shownCandidateCount: evidence.shownCandidateCount,
|
|
7247
|
+
omittedCandidateCount: evidence.omittedCandidateCount
|
|
7248
|
+
};
|
|
7249
|
+
}
|
|
7250
|
+
function implementationRejectionReasons(evidence) {
|
|
7251
|
+
const candidates2 = recordArray5(evidence.candidates);
|
|
7252
|
+
const reasons = candidates2.flatMap((candidate) => stringArray5(candidate.rejectedReasons));
|
|
7253
|
+
return [...new Set(reasons)].sort();
|
|
7254
|
+
}
|
|
7255
|
+
function recordArray5(value) {
|
|
7256
|
+
return Array.isArray(value) ? value.filter(isRecord7) : [];
|
|
7257
|
+
}
|
|
7258
|
+
function stringArray5(value) {
|
|
7259
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
7260
|
+
}
|
|
7261
|
+
function isRecord7(value) {
|
|
7262
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
7263
|
+
}
|
|
7264
|
+
|
|
6083
7265
|
// src/trace/trace-engine.ts
|
|
6084
7266
|
function normalizeOperation2(value) {
|
|
6085
7267
|
if (!value) return void 0;
|
|
@@ -6108,24 +7290,24 @@ function operationStartScope(db, repoId, start, hintOptions, workspaceId) {
|
|
|
6108
7290
|
const operationId = String(rows2[0]?.operationId);
|
|
6109
7291
|
const impl = implementationScope(db, operationId);
|
|
6110
7292
|
if (impl.edge?.status === "resolved" && impl.files.size > 0) return { files: impl.files, symbols: impl.symbolId ? /* @__PURE__ */ new Set([impl.symbolId]) : void 0, repoId: impl.repoId, operationId, diagnostics: [] };
|
|
6111
|
-
const hinted =
|
|
7293
|
+
const hinted = hintedImplementationSelection(
|
|
7294
|
+
db,
|
|
7295
|
+
impl.edge,
|
|
7296
|
+
operationId,
|
|
7297
|
+
hintOptions
|
|
7298
|
+
);
|
|
6112
7299
|
if (hinted.methodId) {
|
|
6113
7300
|
const hintedScope = handlerScope(db, hinted.methodId);
|
|
6114
7301
|
if (hintedScope?.files.size) return { files: hintedScope.files, symbols: hintedScope.symbolId ? /* @__PURE__ */ new Set([hintedScope.symbolId]) : void 0, repoId: hintedScope.repoId, operationId, diagnostics: [] };
|
|
6115
7302
|
}
|
|
6116
7303
|
if (impl.edge) {
|
|
6117
7304
|
const evidence = parseEvidence(impl.edge.evidence_json);
|
|
6118
|
-
const hintDiagnostic = implementationHintDiagnostic(hinted, evidence
|
|
6119
|
-
const diagnostics = [
|
|
7305
|
+
const hintDiagnostic = implementationHintDiagnostic(hinted, evidence);
|
|
7306
|
+
const diagnostics = [implementationStartDiagnostic(impl.edge, evidence)];
|
|
6120
7307
|
return { operationId, diagnostics: hintDiagnostic ? [hintDiagnostic, ...diagnostics] : diagnostics };
|
|
6121
7308
|
}
|
|
6122
7309
|
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" }] };
|
|
6123
7310
|
}
|
|
6124
|
-
function implementationRejectionReasons(evidence) {
|
|
6125
|
-
const candidates = Array.isArray(evidence.candidates) ? evidence.candidates.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
6126
|
-
const reasons = candidates.flatMap((candidate) => Array.isArray(candidate.rejectedReasons) ? candidate.rejectedReasons.filter((reason) => typeof reason === "string") : []);
|
|
6127
|
-
return [...new Set(reasons)].sort();
|
|
6128
|
-
}
|
|
6129
7311
|
function sourceFilesForStart(db, repoId, start, workspaceId) {
|
|
6130
7312
|
return sourceScopeForSelector(db, repoId, start, workspaceId);
|
|
6131
7313
|
}
|
|
@@ -6212,38 +7394,6 @@ function implementationScope(db, operationId) {
|
|
|
6212
7394
|
return { repoId: row?.repoId, files: /* @__PURE__ */ new Set(), edge };
|
|
6213
7395
|
return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []), symbolId: row?.symbolId, edge };
|
|
6214
7396
|
}
|
|
6215
|
-
function implementationMethodIdFromHint(edge, options) {
|
|
6216
|
-
if (!edge || edge.status !== "ambiguous") return { blocksAutomatic: false, evidence: { status: "not_applicable" } };
|
|
6217
|
-
return selectImplementation(parseEvidence(edge.evidence_json), options.implementationHints, options.implementationRepo);
|
|
6218
|
-
}
|
|
6219
|
-
function contextImplementationMethodId(edge, callerRepoId, remoteEvidence, hintOptions) {
|
|
6220
|
-
const hinted = implementationMethodIdFromHint(edge, hintOptions);
|
|
6221
|
-
if (hinted.methodId) return hinted;
|
|
6222
|
-
if (hinted.blocksAutomatic) return hinted;
|
|
6223
|
-
if (!edge || edge.status !== "ambiguous" || callerRepoId === void 0) return hinted;
|
|
6224
|
-
const evidence = JSON.parse(String(edge.evidence_json || "{}"));
|
|
6225
|
-
const scores = (evidence.candidates ?? []).filter((item) => item.accepted).map((item) => {
|
|
6226
|
-
const reasons = [];
|
|
6227
|
-
let score = Number(item.score ?? 0);
|
|
6228
|
-
if (Number(item.handlerPackage?.id) === callerRepoId) {
|
|
6229
|
-
score += 10;
|
|
6230
|
-
reasons.push("handler_package_matches_caller_repository");
|
|
6231
|
-
}
|
|
6232
|
-
if (Number(item.applicationPackage?.id) === callerRepoId) {
|
|
6233
|
-
score += 10;
|
|
6234
|
-
reasons.push("registration_package_matches_caller_repository");
|
|
6235
|
-
}
|
|
6236
|
-
if (typeof remoteEvidence.effectiveServicePath === "string" || typeof remoteEvidence.effectiveDestination === "string" || typeof remoteEvidence.effectiveAlias === "string") {
|
|
6237
|
-
score += 1;
|
|
6238
|
-
reasons.push("remote_call_context_available");
|
|
6239
|
-
}
|
|
6240
|
-
return { methodId: item.methodId, score, reasons, handlerPackage: item.handlerPackage, applicationPackage: item.applicationPackage };
|
|
6241
|
-
}).sort((a, b) => b.score - a.score);
|
|
6242
|
-
if (scores.length === 0) return { blocksAutomatic: false, evidence: { status: "not_applicable", candidateScores: [] } };
|
|
6243
|
-
const [first, second] = scores;
|
|
6244
|
-
if (first && first.methodId !== void 0 && first.score > 0 && (!second || first.score > second.score)) return { methodId: String(first.methodId), blocksAutomatic: false, evidence: { status: "selected", selectedMethodId: first.methodId, candidateScores: scores } };
|
|
6245
|
-
return { blocksAutomatic: false, evidence: hinted.evidence.reason === "no_scoped_hint_matched_edge" ? hinted.evidence : { status: "tied", tieReason: scores.length > 1 ? "duplicate_helper_implementation_candidates" : "no_unique_materially_stronger_candidate", candidateScores: scores } };
|
|
6246
|
-
}
|
|
6247
7397
|
function handlerScope(db, methodId) {
|
|
6248
7398
|
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.qualified_name=hc.class_name || '.' || hm.method_name AND s.start_line=hm.source_line WHERE hm.id=?").get(methodId);
|
|
6249
7399
|
if (!row || typeof row.symbolId !== "number") return void 0;
|
|
@@ -6289,11 +7439,14 @@ function workspaceIdForCall(db, callId) {
|
|
|
6289
7439
|
function parseEvidence(value) {
|
|
6290
7440
|
try {
|
|
6291
7441
|
const parsed = JSON.parse(String(value || "{}"));
|
|
6292
|
-
return
|
|
7442
|
+
return isEvidenceRecord2(parsed) ? boundCandidateLikeEvidence(parsed) : {};
|
|
6293
7443
|
} catch {
|
|
6294
7444
|
return {};
|
|
6295
7445
|
}
|
|
6296
7446
|
}
|
|
7447
|
+
function isEvidenceRecord2(value) {
|
|
7448
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
7449
|
+
}
|
|
6297
7450
|
function receiverFromEvidence(value) {
|
|
6298
7451
|
const evidence = parseEvidence(value);
|
|
6299
7452
|
return typeof evidence.receiver === "string" ? evidence.receiver : void 0;
|
|
@@ -6341,7 +7494,7 @@ function knownBindingsForScope(db, repoId, symbolIds, files) {
|
|
|
6341
7494
|
map.set(row.variableName, candidate);
|
|
6342
7495
|
continue;
|
|
6343
7496
|
}
|
|
6344
|
-
const
|
|
7497
|
+
const candidates2 = uniqueBindingCandidates([
|
|
6345
7498
|
...existing.bindingCandidates ?? [bindingCandidateEvidence(existing)],
|
|
6346
7499
|
bindingCandidateEvidence(candidate)
|
|
6347
7500
|
]);
|
|
@@ -6350,7 +7503,7 @@ function knownBindingsForScope(db, repoId, symbolIds, files) {
|
|
|
6350
7503
|
bindingId: void 0,
|
|
6351
7504
|
source: "ambiguous_local_service_bindings",
|
|
6352
7505
|
resolutionStatus: "ambiguous",
|
|
6353
|
-
bindingCandidates:
|
|
7506
|
+
bindingCandidates: candidates2
|
|
6354
7507
|
});
|
|
6355
7508
|
}
|
|
6356
7509
|
return map;
|
|
@@ -6366,9 +7519,9 @@ function bindingCandidateEvidence(binding) {
|
|
|
6366
7519
|
servicePathExpr: binding.servicePathExpr
|
|
6367
7520
|
};
|
|
6368
7521
|
}
|
|
6369
|
-
function uniqueBindingCandidates(
|
|
7522
|
+
function uniqueBindingCandidates(candidates2) {
|
|
6370
7523
|
const seen = /* @__PURE__ */ new Set();
|
|
6371
|
-
return
|
|
7524
|
+
return candidates2.filter((candidate) => {
|
|
6372
7525
|
const key = JSON.stringify(candidate);
|
|
6373
7526
|
if (seen.has(key)) return false;
|
|
6374
7527
|
seen.add(key);
|
|
@@ -6378,13 +7531,13 @@ function uniqueBindingCandidates(candidates) {
|
|
|
6378
7531
|
function contextForSymbolCall(db, symbolCall, callerBindings) {
|
|
6379
7532
|
const next = /* @__PURE__ */ new Map();
|
|
6380
7533
|
if (callerBindings.size === 0) return next;
|
|
6381
|
-
const
|
|
7534
|
+
const callEvidence = parseEvidence(symbolCall.evidence_json);
|
|
6382
7535
|
const callee = db.prepare("SELECT evidence_json evidenceJson,source_file sourceFile,start_line startLine FROM symbols WHERE id=?").get(symbolCall.callee_symbol_id);
|
|
6383
7536
|
const calleeEvidence = parseEvidence(callee?.evidenceJson);
|
|
6384
7537
|
const params = Array.isArray(calleeEvidence.parameters) ? calleeEvidence.parameters.filter((item) => typeof item === "string") : [];
|
|
6385
7538
|
const parameterBindings = Array.isArray(calleeEvidence.parameterBindings) ? calleeEvidence.parameterBindings.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
6386
7539
|
const parameterPropertyAliases = Array.isArray(calleeEvidence.parameterPropertyAliases) ? calleeEvidence.parameterPropertyAliases.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
6387
|
-
const args = Array.isArray(
|
|
7540
|
+
const args = Array.isArray(callEvidence.callArguments) ? callEvidence.callArguments : [];
|
|
6388
7541
|
const provenance = {
|
|
6389
7542
|
callerSite: { sourceFile: String(symbolCall.source_file ?? ""), sourceLine: Number(symbolCall.source_line ?? 0) },
|
|
6390
7543
|
calleeSite: { sourceFile: callee?.sourceFile, sourceLine: callee?.startLine }
|
|
@@ -6425,16 +7578,20 @@ function contextForSymbolCall(db, symbolCall, callerBindings) {
|
|
|
6425
7578
|
function contextualRuntimeResolution(db, call, binding, workspaceId, persistedRows = []) {
|
|
6426
7579
|
if (!binding || String(call.call_type) !== "remote_action" || call.operation_path_expr === void 0 || call.operation_path_expr === null) return {};
|
|
6427
7580
|
if (binding.resolutionStatus === "ambiguous") {
|
|
7581
|
+
const candidates3 = boundedContextCandidates(binding.bindingCandidates ?? []);
|
|
6428
7582
|
return {
|
|
6429
7583
|
evidence: {
|
|
6430
7584
|
contextualServiceBindingAttempted: true,
|
|
6431
7585
|
contextualBinding: {
|
|
6432
7586
|
source: binding.source,
|
|
6433
7587
|
status: "tied",
|
|
6434
|
-
candidates:
|
|
7588
|
+
candidates: candidates3.candidates,
|
|
7589
|
+
candidateCount: candidates3.candidateCount,
|
|
7590
|
+
shownCandidateCount: candidates3.shownCandidateCount,
|
|
7591
|
+
omittedCandidateCount: candidates3.omittedCandidateCount
|
|
6435
7592
|
},
|
|
6436
7593
|
contextualResolutionStatus: "ambiguous",
|
|
6437
|
-
contextualCandidateCount:
|
|
7594
|
+
contextualCandidateCount: candidates3.candidateCount
|
|
6438
7595
|
},
|
|
6439
7596
|
unresolvedReason: "Ambiguous contextual service binding candidates"
|
|
6440
7597
|
};
|
|
@@ -6444,7 +7601,8 @@ function contextualRuntimeResolution(db, call, binding, workspaceId, persistedRo
|
|
|
6444
7601
|
const servicePath = binding.effectiveServicePath ?? binding.servicePathExpr ?? binding.requireServicePath;
|
|
6445
7602
|
const destination = binding.effectiveDestination ?? binding.destinationExpr ?? binding.requireDestination;
|
|
6446
7603
|
const resolution = resolveOperation(db, { servicePath, operationPath: op, alias: binding.aliasExpr ?? binding.alias, destination, hasExplicitOverride: true, isDynamic: false }, workspaceId);
|
|
6447
|
-
const
|
|
7604
|
+
const candidates2 = boundedContextCandidates(resolution.candidates);
|
|
7605
|
+
const evidence = { 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 : void 0, invocationArgumentPlaceholderKeys: normalized?.invocationArgumentPlaceholderKeys.length ? normalized.invocationArgumentPlaceholderKeys : void 0, servicePath, serviceAlias: binding.alias, serviceAliasExpr: binding.aliasExpr, destination, requireServicePath: binding.requireServicePath, requireDestination: binding.requireDestination, effectiveServicePath: binding.effectiveServicePath, effectiveDestination: binding.effectiveDestination, contextualResolutionStatus: resolution.status, contextualCandidateCount: candidates2.candidateCount, shownContextualCandidateCount: candidates2.shownCandidateCount, omittedContextualCandidateCount: candidates2.omittedCandidateCount, candidates: candidates2.candidates, contextualResolutionReasons: resolution.reasons, resolutionReasons: resolution.reasons };
|
|
6448
7606
|
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" };
|
|
6449
7607
|
const resolvedEvidence = { ...evidence, contextualServiceBindingSelected: true, targetRepo: resolution.target.repoName, targetServicePath: resolution.target.servicePath, targetOperationPath: resolution.target.operationPath, targetOperation: resolution.target.operationName };
|
|
6450
7608
|
const persistedResolved = persistedRows.find((item) => item.status === "resolved");
|
|
@@ -6478,7 +7636,12 @@ function trace(db, start, options) {
|
|
|
6478
7636
|
const op = operationNode(db, scope.startOperationId);
|
|
6479
7637
|
const impl = implementationScope(db, scope.startOperationId);
|
|
6480
7638
|
if (op) nodes.set(String(op.id), op);
|
|
6481
|
-
const startSelection =
|
|
7639
|
+
const startSelection = hintedImplementationSelection(
|
|
7640
|
+
db,
|
|
7641
|
+
impl.edge,
|
|
7642
|
+
scope.startOperationId,
|
|
7643
|
+
hintOptions
|
|
7644
|
+
);
|
|
6482
7645
|
if (impl.edge && (impl.edge.status === "resolved" || startSelection.methodId)) {
|
|
6483
7646
|
const selectedMethodId = impl.edge.status === "resolved" ? impl.edge.to_id : startSelection.methodId;
|
|
6484
7647
|
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 : void 0 };
|
|
@@ -6513,7 +7676,7 @@ function trace(db, start, options) {
|
|
|
6513
7676
|
const nextKey = `${nextRepoId}:${[...nextSymbols].join(",")}:${[...nextFiles].join(",")}`;
|
|
6514
7677
|
const calleeNode = symbolNode(db, Number(symbolCall.callee_symbol_id));
|
|
6515
7678
|
if (calleeNode) nodes.set(String(calleeNode.id), calleeNode);
|
|
6516
|
-
const evidence = { ...
|
|
7679
|
+
const evidence = { ...parseEvidence(symbolCall.evidence_json), sourceFile: symbolCall.source_file, sourceLine: symbolCall.source_line, calleeSymbolId: symbolCall.callee_symbol_id, calleeSymbolName: calleeNode?.symbolName, calleeSymbolFile: calleeNode?.sourceFile, resolutionStatus: symbolCall.status };
|
|
6517
7680
|
edges.push({ step: current.depth, type: "local_symbol_call", from: String(symbolCall.callee_expression), to: calleeNode?.label ? String(calleeNode.label) : `symbol:${String(symbolCall.callee_symbol_id)}`, evidence, confidence: Number(symbolCall.confidence ?? 0.8), unresolvedReason: String(symbolCall.status) === "resolved" ? void 0 : symbolCall.unresolved_reason ? String(symbolCall.unresolved_reason) : void 0 });
|
|
6518
7681
|
if (seenScopes.has(nextKey)) edges.push({ step: current.depth, type: "cycle", from: String(symbolCall.callee_expression), to: nextKey, evidence: { cycle: true, symbolCallId: symbolCall.id }, confidence: 1, unresolvedReason: "Cycle detected; downstream symbol already visited" });
|
|
6519
7682
|
else queue.push({ repoId: nextRepoId, files: nextFiles, symbolIds: nextSymbols, depth: current.depth + 1, context: contextForSymbolCall(db, symbolCall, callerBindings) });
|
|
@@ -6539,9 +7702,7 @@ function trace(db, start, options) {
|
|
|
6539
7702
|
for (const row of graphRows) {
|
|
6540
7703
|
if (seenEdges.has(Number(row.id))) continue;
|
|
6541
7704
|
seenEdges.add(Number(row.id));
|
|
6542
|
-
const persistedEvidence =
|
|
6543
|
-
String(row.evidence_json || "{}")
|
|
6544
|
-
);
|
|
7705
|
+
const persistedEvidence = parseEvidence(row.evidence_json);
|
|
6545
7706
|
const rawEvidence = baseTraceEvidence(row, call, persistedEvidence, contextual.evidence);
|
|
6546
7707
|
const effective = runtimeResolution(db, row, rawEvidence, {
|
|
6547
7708
|
vars: options.vars,
|
|
@@ -6567,16 +7728,23 @@ function trace(db, start, options) {
|
|
|
6567
7728
|
confidence: Number(effectiveRow.confidence ?? call.confidence),
|
|
6568
7729
|
unresolvedReason: effective.unresolvedReason
|
|
6569
7730
|
});
|
|
6570
|
-
if ((options.dynamicMode ?? "strict") === "candidates")
|
|
7731
|
+
if ((options.dynamicMode ?? "strict") === "candidates" && effectiveRow.status !== "resolved")
|
|
6571
7732
|
edges.push(...dynamicCandidateBranches(current.depth, call, evidence));
|
|
6572
7733
|
if (effectiveRow.to_kind === "operation") {
|
|
6573
7734
|
const implementation = implementationScope(db, effectiveRow.to_id);
|
|
6574
|
-
const contextSelection =
|
|
7735
|
+
const contextSelection = contextualImplementationSelection(
|
|
7736
|
+
db,
|
|
7737
|
+
implementation.edge,
|
|
7738
|
+
effectiveRow.to_id,
|
|
7739
|
+
current.repoId,
|
|
7740
|
+
evidence,
|
|
7741
|
+
hintOptions
|
|
7742
|
+
);
|
|
6575
7743
|
const contextMethodId = contextSelection.methodId;
|
|
6576
7744
|
const contextNode = contextMethodId ? handlerMethodNode(db, contextMethodId) : void 0;
|
|
6577
7745
|
if (implementation.edge) {
|
|
6578
|
-
const implEvidence =
|
|
6579
|
-
const hintDiagnostic = implementationHintDiagnostic(contextSelection, implEvidence
|
|
7746
|
+
const implEvidence = parseEvidence(implementation.edge.evidence_json);
|
|
7747
|
+
const hintDiagnostic = implementationHintDiagnostic(contextSelection, implEvidence);
|
|
6580
7748
|
if (hintDiagnostic) prependTraceDiagnostic(diagnostics, hintDiagnostic);
|
|
6581
7749
|
const handlerNode = implementation.edge.status === "resolved" ? handlerMethodNode(db, implementation.edge.to_id) : contextNode;
|
|
6582
7750
|
const implTo = handlerNode?.label ? String(handlerNode.label) : `${implementation.edge.to_kind}:${implementation.edge.to_id}`;
|
|
@@ -6630,6 +7798,8 @@ function trace(db, start, options) {
|
|
|
6630
7798
|
}
|
|
6631
7799
|
|
|
6632
7800
|
export {
|
|
7801
|
+
projectBounded,
|
|
7802
|
+
stableProjectionValue,
|
|
6633
7803
|
upsertWorkspace,
|
|
6634
7804
|
getWorkspace,
|
|
6635
7805
|
upsertRepository,
|
|
@@ -6671,4 +7841,4 @@ export {
|
|
|
6671
7841
|
selectorRepoAmbiguousDiagnostic,
|
|
6672
7842
|
trace
|
|
6673
7843
|
};
|
|
6674
|
-
//# sourceMappingURL=chunk-
|
|
7844
|
+
//# sourceMappingURL=chunk-LFH7C46B.js.map
|