@saptools/service-flow 0.1.52 → 0.1.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +11 -12
- package/TECHNICAL-NOTE.md +18 -3
- package/dist/{chunk-PTLDSHRC.js → chunk-ERIZHM5C.js} +2646 -1067
- package/dist/chunk-ERIZHM5C.js.map +1 -0
- package/dist/cli.js +162 -13
- 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 +136 -0
- package/src/cli/doctor.ts +20 -3
- package/src/db/repositories.ts +10 -2
- package/src/linker/000-implementation-candidates.ts +674 -0
- package/src/linker/001-implementation-evidence-projection.ts +191 -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 +15 -4
- 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/008-contextual-runtime-state.ts +294 -0
- package/src/trace/009-selected-handler-provenance.ts +186 -0
- package/src/trace/dynamic-targets.ts +205 -159
- package/src/trace/evidence.ts +132 -34
- package/src/trace/implementation-hints.ts +148 -8
- package/src/trace/selectors.ts +74 -9
- package/src/trace/trace-engine.ts +97 -125
- package/src/utils/000-bounded-projection.ts +166 -0
- package/dist/chunk-PTLDSHRC.js.map +0 -1
|
@@ -1,5 +1,128 @@
|
|
|
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 projectBoundedInOrder(values, limit = DEFAULT_EVIDENCE_CANDIDATE_LIMIT) {
|
|
17
|
+
const normalizedLimit = positiveLimit(limit);
|
|
18
|
+
const items = values.slice(0, normalizedLimit);
|
|
19
|
+
return {
|
|
20
|
+
totalCount: values.length,
|
|
21
|
+
shownCount: items.length,
|
|
22
|
+
omittedCount: Math.max(0, values.length - items.length),
|
|
23
|
+
items
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function positiveLimit(value) {
|
|
27
|
+
return Number.isFinite(value) && Number(value) > 0 ? Math.floor(Number(value)) : DEFAULT_EVIDENCE_CANDIDATE_LIMIT;
|
|
28
|
+
}
|
|
29
|
+
var candidateLikeCollections = /* @__PURE__ */ new Set([
|
|
30
|
+
"candidates",
|
|
31
|
+
"candidateScores",
|
|
32
|
+
"candidateFamilies",
|
|
33
|
+
"candidateEvidence",
|
|
34
|
+
"candidatePaths",
|
|
35
|
+
"candidateRawPaths",
|
|
36
|
+
"candidateNormalizedOperationPaths",
|
|
37
|
+
"normalizedCandidateOperations",
|
|
38
|
+
"candidateLiterals",
|
|
39
|
+
"bindingCandidates",
|
|
40
|
+
"bindingAlternatives",
|
|
41
|
+
"implementationHintSuggestions",
|
|
42
|
+
"selectableImplementationRepositories",
|
|
43
|
+
"matchedHints",
|
|
44
|
+
"candidateSuggestions",
|
|
45
|
+
"dynamicTargetCandidates",
|
|
46
|
+
"dynamicTargetCandidateSuggestions",
|
|
47
|
+
"rejectedCandidates",
|
|
48
|
+
"suggestedVarSets",
|
|
49
|
+
"copyableExamples",
|
|
50
|
+
"selectorSuggestions",
|
|
51
|
+
"serviceSuggestions",
|
|
52
|
+
"repositories",
|
|
53
|
+
"examples",
|
|
54
|
+
"expandedExamples",
|
|
55
|
+
"registrations"
|
|
56
|
+
]);
|
|
57
|
+
function boundCandidateLikeEvidence(evidence, limit = DEFAULT_EVIDENCE_CANDIDATE_LIMIT) {
|
|
58
|
+
const output = {};
|
|
59
|
+
for (const [key, value] of Object.entries(evidence)) {
|
|
60
|
+
if (!Array.isArray(value) || !candidateLikeCollections.has(key)) {
|
|
61
|
+
output[key] = boundNestedEvidence(value, limit);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const projection = projectBoundedInOrder(value, limit);
|
|
65
|
+
output[key] = projection.items.map((item) => boundNestedEvidence(item, limit));
|
|
66
|
+
addCollectionCounts(output, evidence, key, projection);
|
|
67
|
+
}
|
|
68
|
+
return output;
|
|
69
|
+
}
|
|
70
|
+
function boundNestedEvidence(value, limit) {
|
|
71
|
+
if (Array.isArray(value)) return value.map((item) => boundNestedEvidence(item, limit));
|
|
72
|
+
if (!isEvidenceRecord(value)) return value;
|
|
73
|
+
return boundCandidateLikeEvidence(value, limit);
|
|
74
|
+
}
|
|
75
|
+
function isEvidenceRecord(value) {
|
|
76
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
77
|
+
}
|
|
78
|
+
function addCollectionCounts(output, input, key, projection) {
|
|
79
|
+
const stem = collectionStem(key);
|
|
80
|
+
const countName = `${stem}Count`;
|
|
81
|
+
const shownName = `shown${upperFirst(stem)}Count`;
|
|
82
|
+
const omittedName = `omitted${upperFirst(stem)}Count`;
|
|
83
|
+
const total = Math.max(numericValue(input[countName]), projection.totalCount);
|
|
84
|
+
output[countName] = total;
|
|
85
|
+
output[shownName] = projection.shownCount;
|
|
86
|
+
output[omittedName] = Math.max(0, total - projection.shownCount);
|
|
87
|
+
}
|
|
88
|
+
function collectionStem(key) {
|
|
89
|
+
const stems = {
|
|
90
|
+
candidates: "candidate",
|
|
91
|
+
candidateScores: "candidateScore",
|
|
92
|
+
candidateFamilies: "candidateFamily",
|
|
93
|
+
candidateEvidence: "candidateEvidence",
|
|
94
|
+
candidatePaths: "candidatePath",
|
|
95
|
+
candidateRawPaths: "candidateRawPath",
|
|
96
|
+
candidateNormalizedOperationPaths: "candidateNormalizedOperationPath",
|
|
97
|
+
normalizedCandidateOperations: "normalizedCandidateOperation",
|
|
98
|
+
candidateLiterals: "candidateLiteral",
|
|
99
|
+
bindingCandidates: "bindingCandidate",
|
|
100
|
+
bindingAlternatives: "bindingAlternative",
|
|
101
|
+
implementationHintSuggestions: "implementationHintSuggestion",
|
|
102
|
+
selectableImplementationRepositories: "selectableImplementationRepository",
|
|
103
|
+
matchedHints: "matchedHint",
|
|
104
|
+
candidateSuggestions: "candidateSuggestion",
|
|
105
|
+
dynamicTargetCandidates: "dynamicTargetCandidate",
|
|
106
|
+
dynamicTargetCandidateSuggestions: "dynamicTargetCandidateSuggestion",
|
|
107
|
+
rejectedCandidates: "rejectedCandidate",
|
|
108
|
+
suggestedVarSets: "suggestedVarSet",
|
|
109
|
+
copyableExamples: "copyableExample",
|
|
110
|
+
selectorSuggestions: "selectorSuggestion",
|
|
111
|
+
serviceSuggestions: "serviceSuggestion",
|
|
112
|
+
repositories: "repository",
|
|
113
|
+
examples: "example",
|
|
114
|
+
expandedExamples: "expandedExample",
|
|
115
|
+
registrations: "registration"
|
|
116
|
+
};
|
|
117
|
+
return stems[key] ?? "candidate";
|
|
118
|
+
}
|
|
119
|
+
function numericValue(value) {
|
|
120
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
121
|
+
}
|
|
122
|
+
function upperFirst(value) {
|
|
123
|
+
return value ? `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}` : value;
|
|
124
|
+
}
|
|
125
|
+
|
|
3
126
|
// src/db/repositories.ts
|
|
4
127
|
function upsertWorkspace(db, rootPath, dbPath) {
|
|
5
128
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -337,8 +460,8 @@ function insertCalls(db, repoId, rows2) {
|
|
|
337
460
|
function resolvePersistedBinding(db, repoId, call) {
|
|
338
461
|
if (!call.serviceVariableName)
|
|
339
462
|
return { bindingId: null, evidence: { status: "not_applicable", candidateCount: 0 } };
|
|
340
|
-
const
|
|
341
|
-
const prior =
|
|
463
|
+
const candidates2 = bindingCandidates(db, repoId, call);
|
|
464
|
+
const prior = candidates2.filter((candidate) => candidate.sourceLine <= call.sourceLine);
|
|
342
465
|
const families = new Set(prior.map(bindingSignature));
|
|
343
466
|
if (prior.length > 0 && families.size === 1) {
|
|
344
467
|
const selected = prior.at(-1);
|
|
@@ -354,11 +477,11 @@ function resolvePersistedBinding(db, repoId, call) {
|
|
|
354
477
|
evidence: bindingEvidence("ambiguous", prior)
|
|
355
478
|
};
|
|
356
479
|
}
|
|
357
|
-
if (
|
|
480
|
+
if (candidates2.length > 0) {
|
|
358
481
|
return {
|
|
359
482
|
bindingId: null,
|
|
360
483
|
unresolvedReason: "service_binding_declared_after_call",
|
|
361
|
-
evidence: bindingEvidence("rejected_future_binding",
|
|
484
|
+
evidence: bindingEvidence("rejected_future_binding", candidates2)
|
|
362
485
|
};
|
|
363
486
|
}
|
|
364
487
|
return {
|
|
@@ -426,13 +549,16 @@ function callSymbolId(db, repoId, call) {
|
|
|
426
549
|
);
|
|
427
550
|
return typeof row?.id === "number" ? row.id : void 0;
|
|
428
551
|
}
|
|
429
|
-
function bindingEvidence(status,
|
|
552
|
+
function bindingEvidence(status, candidates2, selected) {
|
|
553
|
+
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
554
|
return {
|
|
431
555
|
status,
|
|
432
|
-
candidateCount:
|
|
556
|
+
candidateCount: projection.totalCount,
|
|
557
|
+
shownCandidateCount: projection.shownCount,
|
|
558
|
+
omittedCandidateCount: projection.omittedCount,
|
|
433
559
|
selectedBindingId: selected?.id,
|
|
434
560
|
sourceOrderRule: "binding_source_line_must_not_follow_call",
|
|
435
|
-
candidates:
|
|
561
|
+
candidates: projection.items.map((candidate) => ({
|
|
436
562
|
bindingId: candidate.id,
|
|
437
563
|
symbolId: candidate.symbolId,
|
|
438
564
|
variableName: candidate.variableName,
|
|
@@ -2208,8 +2334,20 @@ function externalHttpTarget(call) {
|
|
|
2208
2334
|
const expression = typeof target.expression === "string" ? target.expression : void 0;
|
|
2209
2335
|
if (kind === "destination" && target.dynamic === true) {
|
|
2210
2336
|
const shape = typeof target.expressionShape === "string" ? target.expressionShape : "expression";
|
|
2211
|
-
const
|
|
2212
|
-
|
|
2337
|
+
const candidates2 = staticDestinationCandidates(target.candidateLiterals);
|
|
2338
|
+
const projection = projectBounded(candidates2, (left, right) => left.localeCompare(right));
|
|
2339
|
+
return {
|
|
2340
|
+
kind,
|
|
2341
|
+
toKind: "external_destination",
|
|
2342
|
+
toId: `destination:dynamic:${hash(`${shape}:${candidates2.join("|")}`)}`,
|
|
2343
|
+
label: "External destination: dynamic destination",
|
|
2344
|
+
method,
|
|
2345
|
+
dynamic: true,
|
|
2346
|
+
expression: projection.items.length ? `candidates:${projection.items.join("|")}` : `shape:${shape}`,
|
|
2347
|
+
candidateLiteralCount: projection.totalCount,
|
|
2348
|
+
shownCandidateLiteralCount: projection.shownCount,
|
|
2349
|
+
omittedCandidateLiteralCount: projection.omittedCount
|
|
2350
|
+
};
|
|
2213
2351
|
}
|
|
2214
2352
|
if (kind === "destination" && expression) return { kind, toKind: "external_destination", toId: `destination:${expression}`, label: `External destination: ${expression}`, method, dynamic: false, expression };
|
|
2215
2353
|
if (kind === "static_url" && expression) {
|
|
@@ -2219,6 +2357,10 @@ function externalHttpTarget(call) {
|
|
|
2219
2357
|
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
2358
|
return { kind: "unknown", toKind: "external_endpoint", toId: "unknown", label: "External endpoint: unknown", method, dynamic: false };
|
|
2221
2359
|
}
|
|
2360
|
+
function staticDestinationCandidates(value) {
|
|
2361
|
+
const candidates2 = Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
2362
|
+
return [...new Set(candidates2)].sort();
|
|
2363
|
+
}
|
|
2222
2364
|
function safeParse(value) {
|
|
2223
2365
|
try {
|
|
2224
2366
|
const parsed = JSON.parse(value);
|
|
@@ -3115,8 +3257,8 @@ function destinationTargetFromExpression(expr, use) {
|
|
|
3115
3257
|
const resolved3 = resolveExpression(expr, use, "external");
|
|
3116
3258
|
const text = resolved3.value;
|
|
3117
3259
|
if (resolved3.status === "static" && text && !hasTemplatePlaceholder(text)) return { kind: "destination", expression: text, dynamic: false, sourceKind: resolved3.sourceKind };
|
|
3118
|
-
const
|
|
3119
|
-
if (
|
|
3260
|
+
const candidates2 = staticConditionalCandidates(expr, /* @__PURE__ */ new Map());
|
|
3261
|
+
if (candidates2) return { kind: "destination", dynamic: true, expressionShape: "conditional", candidateLiterals: candidates2 };
|
|
3120
3262
|
const shape = destinationExpressionShape(expr);
|
|
3121
3263
|
if (shape) return { kind: "destination", dynamic: true, expressionShape: shape };
|
|
3122
3264
|
return void 0;
|
|
@@ -3448,6 +3590,22 @@ function applyVariables(template, vars) {
|
|
|
3448
3590
|
function extractPlaceholders(template) {
|
|
3449
3591
|
return [...(template ?? "").matchAll(PLACEHOLDER)].map((m) => (m[1] ?? "").trim()).filter(Boolean);
|
|
3450
3592
|
}
|
|
3593
|
+
function matchRuntimeTemplate(template, concrete) {
|
|
3594
|
+
if (!template || !concrete) return void 0;
|
|
3595
|
+
const keys = extractPlaceholders(template);
|
|
3596
|
+
if (keys.length === 0) return template === concrete ? {} : void 0;
|
|
3597
|
+
const match = new RegExp(`^${runtimeTemplatePattern(template)}$`).exec(concrete);
|
|
3598
|
+
if (!match) return void 0;
|
|
3599
|
+
const values = {};
|
|
3600
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
3601
|
+
const key = keys[index];
|
|
3602
|
+
const value = match[index + 1];
|
|
3603
|
+
if (!key || value === void 0) return void 0;
|
|
3604
|
+
if (values[key] !== void 0 && values[key] !== value) return void 0;
|
|
3605
|
+
values[key] = value;
|
|
3606
|
+
}
|
|
3607
|
+
return values;
|
|
3608
|
+
}
|
|
3451
3609
|
function substituteVariables(template, vars) {
|
|
3452
3610
|
if (!template) return { placeholders: [], missing: [], supplied: [], changed: false };
|
|
3453
3611
|
const placeholders3 = [...new Set(extractPlaceholders(template))];
|
|
@@ -3466,6 +3624,19 @@ function substituteVariables(template, vars) {
|
|
|
3466
3624
|
changed: effective !== template
|
|
3467
3625
|
};
|
|
3468
3626
|
}
|
|
3627
|
+
function runtimeTemplatePattern(template) {
|
|
3628
|
+
let pattern = "";
|
|
3629
|
+
let lastIndex = 0;
|
|
3630
|
+
for (const match of template.matchAll(PLACEHOLDER)) {
|
|
3631
|
+
pattern += escapeRegex(template.slice(lastIndex, match.index));
|
|
3632
|
+
pattern += "([^/]+?)";
|
|
3633
|
+
lastIndex = (match.index ?? 0) + match[0].length;
|
|
3634
|
+
}
|
|
3635
|
+
return `${pattern}${escapeRegex(template.slice(lastIndex))}`;
|
|
3636
|
+
}
|
|
3637
|
+
function escapeRegex(value) {
|
|
3638
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3639
|
+
}
|
|
3469
3640
|
|
|
3470
3641
|
// src/trace/implementation-hints.ts
|
|
3471
3642
|
function parseImplementationHint(value) {
|
|
@@ -3478,8 +3649,8 @@ function parseImplementationHint(value) {
|
|
|
3478
3649
|
if (!hint.implementationRepo) throw new Error("Scoped implementation hint requires an implementation repo selection");
|
|
3479
3650
|
return { ...hint, implementationRepo: hint.implementationRepo };
|
|
3480
3651
|
}
|
|
3481
|
-
function selectImplementation(rawEvidence, hints, legacyRepo) {
|
|
3482
|
-
const evidence = asEvidence(rawEvidence);
|
|
3652
|
+
function selectImplementation(rawEvidence, hints, legacyRepo, canonicalEvidence) {
|
|
3653
|
+
const evidence = asEvidence(canonicalEvidence ?? rawEvidence);
|
|
3483
3654
|
const scoped = hints ?? [];
|
|
3484
3655
|
const matchingHints = scoped.filter((hint2) => hintMatchesEdge(hint2, evidence));
|
|
3485
3656
|
if (matchingHints.length === 0) {
|
|
@@ -3488,38 +3659,60 @@ function selectImplementation(rawEvidence, hints, legacyRepo) {
|
|
|
3488
3659
|
return { blocksAutomatic: false, evidence: { status: "not_matched", reason, strategy: "scoped_implementation_hint" } };
|
|
3489
3660
|
}
|
|
3490
3661
|
if (matchingHints.length > 1) {
|
|
3662
|
+
const projection = projectBounded(matchingHints, compareHints);
|
|
3491
3663
|
return {
|
|
3492
3664
|
blocksAutomatic: true,
|
|
3493
3665
|
evidence: {
|
|
3494
3666
|
status: "tied",
|
|
3495
3667
|
reason: "multiple_scoped_hints_matched_edge",
|
|
3496
3668
|
strategy: "scoped_implementation_hint",
|
|
3497
|
-
matchedHints:
|
|
3498
|
-
candidateCount: matchingHints.length
|
|
3669
|
+
matchedHints: projection.items,
|
|
3670
|
+
candidateCount: matchingHints.length,
|
|
3671
|
+
matchedHintCount: projection.totalCount,
|
|
3672
|
+
shownMatchedHintCount: projection.shownCount,
|
|
3673
|
+
omittedMatchedHintCount: projection.omittedCount
|
|
3499
3674
|
}
|
|
3500
3675
|
};
|
|
3501
3676
|
}
|
|
3502
3677
|
const hint = matchingHints[0];
|
|
3503
3678
|
return hint ? selectCandidate(evidence, hint, "scoped_implementation_hint") : { blocksAutomatic: false, evidence: { status: "not_matched" } };
|
|
3504
3679
|
}
|
|
3505
|
-
function implementationHintDiagnostic(selection,
|
|
3680
|
+
function implementationHintDiagnostic(selection, suggestionEvidence) {
|
|
3506
3681
|
if (!selection.blocksAutomatic || selection.methodId) return void 0;
|
|
3682
|
+
const suggestions = projectedSuggestions(suggestionEvidence);
|
|
3507
3683
|
return {
|
|
3508
3684
|
severity: "warning",
|
|
3509
3685
|
code: "implementation_hint_mismatch",
|
|
3510
3686
|
message: "Implementation hint did not select exactly one viable candidate",
|
|
3511
3687
|
hintStatus: selection.evidence.status,
|
|
3512
3688
|
candidateCount: selection.evidence.candidateCount,
|
|
3513
|
-
implementationHintSuggestions:
|
|
3689
|
+
implementationHintSuggestions: suggestions.suggestions.length > 0 ? suggestions.suggestions : void 0,
|
|
3690
|
+
implementationHintSuggestionCount: suggestions.suggestionCount,
|
|
3691
|
+
shownImplementationHintSuggestionCount: suggestions.shownSuggestionCount,
|
|
3692
|
+
omittedImplementationHintSuggestionCount: suggestions.omittedSuggestionCount,
|
|
3514
3693
|
implementationSelection: selection.evidence
|
|
3515
3694
|
};
|
|
3516
3695
|
}
|
|
3517
3696
|
function implementationHintSuggestions(rawEvidence) {
|
|
3697
|
+
return implementationHintSuggestionProjection(rawEvidence).suggestions;
|
|
3698
|
+
}
|
|
3699
|
+
function implementationHintSuggestionProjection(rawEvidence) {
|
|
3518
3700
|
const evidence = asEvidence(rawEvidence);
|
|
3519
3701
|
const accepted = (evidence.candidates ?? []).filter((candidate) => candidate.accepted);
|
|
3520
|
-
if (accepted.length < 2)
|
|
3702
|
+
if (accepted.length < 2) {
|
|
3703
|
+
return {
|
|
3704
|
+
suggestions: [],
|
|
3705
|
+
suggestionCount: 0,
|
|
3706
|
+
shownSuggestionCount: 0,
|
|
3707
|
+
omittedSuggestionCount: 0
|
|
3708
|
+
};
|
|
3709
|
+
}
|
|
3521
3710
|
const repos = selectableRepositories(accepted);
|
|
3522
|
-
|
|
3711
|
+
const repositoryProjection = projectBounded(
|
|
3712
|
+
repos,
|
|
3713
|
+
(left, right) => left.localeCompare(right)
|
|
3714
|
+
);
|
|
3715
|
+
const suggestions = accepted.flatMap((candidate) => {
|
|
3523
3716
|
const repo = candidate.handlerPackage?.name;
|
|
3524
3717
|
if (!repo || !repos.includes(repo)) return [];
|
|
3525
3718
|
const hint = suggestionHint(evidence, candidate, repo);
|
|
@@ -3528,16 +3721,58 @@ function implementationHintSuggestions(rawEvidence) {
|
|
|
3528
3721
|
operationPath: hint.operationPath,
|
|
3529
3722
|
ambiguityReason: evidence.ambiguityReasons?.[0],
|
|
3530
3723
|
candidateFamily: hint.candidateFamily,
|
|
3531
|
-
selectableImplementationRepositories:
|
|
3724
|
+
selectableImplementationRepositories: repositoryProjection.items,
|
|
3725
|
+
selectableImplementationRepositoryCount: repositoryProjection.totalCount,
|
|
3726
|
+
shownSelectableImplementationRepositoryCount: repositoryProjection.shownCount,
|
|
3727
|
+
omittedSelectableImplementationRepositoryCount: repositoryProjection.omittedCount,
|
|
3532
3728
|
implementationRepo: repo,
|
|
3533
3729
|
hint,
|
|
3534
3730
|
cli: `--implementation-hint ${hintString(hint)}`
|
|
3535
3731
|
}];
|
|
3536
3732
|
});
|
|
3733
|
+
const projection = projectBounded(suggestions, compareSuggestion);
|
|
3734
|
+
return {
|
|
3735
|
+
suggestions: projection.items,
|
|
3736
|
+
suggestionCount: projection.totalCount,
|
|
3737
|
+
shownSuggestionCount: projection.shownCount,
|
|
3738
|
+
omittedSuggestionCount: projection.omittedCount
|
|
3739
|
+
};
|
|
3740
|
+
}
|
|
3741
|
+
function projectedSuggestions(value) {
|
|
3742
|
+
const evidence = objectRecord(value);
|
|
3743
|
+
const values = Array.isArray(value) ? recordSuggestions(value) : recordSuggestions(evidence.implementationHintSuggestions);
|
|
3744
|
+
const projection = projectBounded(values, compareSuggestion);
|
|
3745
|
+
const total = Math.max(
|
|
3746
|
+
numericValue2(evidence.implementationHintSuggestionCount),
|
|
3747
|
+
projection.totalCount
|
|
3748
|
+
);
|
|
3749
|
+
return {
|
|
3750
|
+
suggestions: projection.items,
|
|
3751
|
+
suggestionCount: total,
|
|
3752
|
+
shownSuggestionCount: projection.shownCount,
|
|
3753
|
+
omittedSuggestionCount: Math.max(0, total - projection.shownCount)
|
|
3754
|
+
};
|
|
3755
|
+
}
|
|
3756
|
+
function compareHints(left, right) {
|
|
3757
|
+
return hintString(left).localeCompare(hintString(right));
|
|
3758
|
+
}
|
|
3759
|
+
function compareSuggestion(left, right) {
|
|
3760
|
+
return String(left.cli ?? "").localeCompare(String(right.cli ?? "")) || String(left.implementationRepo ?? "").localeCompare(
|
|
3761
|
+
String(right.implementationRepo ?? "")
|
|
3762
|
+
);
|
|
3763
|
+
}
|
|
3764
|
+
function objectRecord(value) {
|
|
3765
|
+
return isRecord(value) ? value : {};
|
|
3766
|
+
}
|
|
3767
|
+
function recordSuggestions(value) {
|
|
3768
|
+
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
3537
3769
|
}
|
|
3538
|
-
function
|
|
3539
|
-
|
|
3540
|
-
|
|
3770
|
+
function numericValue2(value) {
|
|
3771
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
3772
|
+
}
|
|
3773
|
+
function selectableRepositories(candidates2) {
|
|
3774
|
+
const repos = new Set(candidates2.flatMap((candidate) => candidate.handlerPackage?.name ? [candidate.handlerPackage.name] : []));
|
|
3775
|
+
return [...repos].filter((repo) => candidates2.filter((candidate) => candidateMatchesRepo(candidate, repo)).length === 1).sort();
|
|
3541
3776
|
}
|
|
3542
3777
|
function assignHintField(hint, key, value) {
|
|
3543
3778
|
if (key === "service" || key === "servicePath") hint.servicePath = value;
|
|
@@ -3629,7 +3864,45 @@ function legacyHint(implementationRepo) {
|
|
|
3629
3864
|
return { implementationRepo };
|
|
3630
3865
|
}
|
|
3631
3866
|
function asEvidence(value) {
|
|
3632
|
-
return
|
|
3867
|
+
return {
|
|
3868
|
+
servicePath: stringValue3(value.servicePath),
|
|
3869
|
+
operationPath: stringValue3(value.operationPath),
|
|
3870
|
+
ambiguityReasons: stringArray(value.ambiguityReasons),
|
|
3871
|
+
candidateFamilies: candidateFamilies(value.candidateFamilies),
|
|
3872
|
+
candidates: candidates(value.candidates),
|
|
3873
|
+
modelPackage: packageValue(value.modelPackage)
|
|
3874
|
+
};
|
|
3875
|
+
}
|
|
3876
|
+
function candidates(value) {
|
|
3877
|
+
return recordSuggestions(value).map((candidate) => ({
|
|
3878
|
+
accepted: candidate.accepted === true,
|
|
3879
|
+
methodId: numericValue2(candidate.methodId) || void 0,
|
|
3880
|
+
sourceFile: stringValue3(candidate.sourceFile),
|
|
3881
|
+
handlerPackage: packageValue(candidate.handlerPackage),
|
|
3882
|
+
modelPackage: packageValue(candidate.modelPackage),
|
|
3883
|
+
servicePath: stringValue3(candidate.servicePath),
|
|
3884
|
+
operationPath: stringValue3(candidate.operationPath)
|
|
3885
|
+
}));
|
|
3886
|
+
}
|
|
3887
|
+
function candidateFamilies(value) {
|
|
3888
|
+
return recordSuggestions(value).map((family) => ({
|
|
3889
|
+
packageName: stringValue3(family.packageName)
|
|
3890
|
+
}));
|
|
3891
|
+
}
|
|
3892
|
+
function packageValue(value) {
|
|
3893
|
+
const candidate = objectRecord(value);
|
|
3894
|
+
const name = stringValue3(candidate.name);
|
|
3895
|
+
const packageName = stringValue3(candidate.packageName);
|
|
3896
|
+
return name || packageName ? { name, packageName } : void 0;
|
|
3897
|
+
}
|
|
3898
|
+
function stringArray(value) {
|
|
3899
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
3900
|
+
}
|
|
3901
|
+
function stringValue3(value) {
|
|
3902
|
+
return typeof value === "string" ? value : void 0;
|
|
3903
|
+
}
|
|
3904
|
+
function isRecord(value) {
|
|
3905
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
3633
3906
|
}
|
|
3634
3907
|
|
|
3635
3908
|
// src/linker/remote-query-target.ts
|
|
@@ -3656,198 +3929,889 @@ function buildRemoteQueryTarget(input) {
|
|
|
3656
3929
|
};
|
|
3657
3930
|
}
|
|
3658
3931
|
|
|
3659
|
-
// src/linker/
|
|
3660
|
-
function
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3932
|
+
// src/linker/001-implementation-evidence-projection.ts
|
|
3933
|
+
function selectedHandlerProvenance(source) {
|
|
3934
|
+
return {
|
|
3935
|
+
status: "selected",
|
|
3936
|
+
accepted: true,
|
|
3937
|
+
graphTargetId: String(source.methodId),
|
|
3938
|
+
methodId: source.methodId,
|
|
3939
|
+
className: source.className,
|
|
3940
|
+
methodName: source.methodName,
|
|
3941
|
+
repository: {
|
|
3942
|
+
id: source.repositoryId,
|
|
3943
|
+
name: source.repositoryName,
|
|
3944
|
+
packageName: source.repositoryPackageName
|
|
3945
|
+
},
|
|
3946
|
+
sourceFile: source.sourceFile,
|
|
3947
|
+
sourceLine: source.sourceLine
|
|
3948
|
+
};
|
|
3949
|
+
}
|
|
3950
|
+
function displayImplementationCandidates(candidates2, selectedMethodId) {
|
|
3951
|
+
return [...candidates2].sort((left, right) => compareDisplayCandidate(
|
|
3952
|
+
left,
|
|
3953
|
+
right,
|
|
3954
|
+
selectedMethodId
|
|
3955
|
+
)).map((candidate, index) => ({
|
|
3956
|
+
...candidate,
|
|
3957
|
+
discoveryRank: candidate.rank,
|
|
3958
|
+
displayRank: index + 1,
|
|
3959
|
+
selected: selectedMethodId !== void 0 && Number(candidate.methodId) === selectedMethodId
|
|
3678
3960
|
}));
|
|
3679
3961
|
}
|
|
3680
|
-
function
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3962
|
+
function compareDisplayCandidate(left, right, selectedMethodId) {
|
|
3963
|
+
return Number(Number(right.methodId) === selectedMethodId) - Number(Number(left.methodId) === selectedMethodId) || Number(right.accepted === true) - Number(left.accepted === true) || numberValue(left.rank) - numberValue(right.rank) || compareTargetCandidates(left, right);
|
|
3964
|
+
}
|
|
3965
|
+
function boundedImplementationEvidence(evidence, targetCandidateCount) {
|
|
3966
|
+
const candidates2 = recordArray(evidence.candidates);
|
|
3967
|
+
const candidateProjection = projectBoundedInOrder(candidates2);
|
|
3968
|
+
const families = recordArray(evidence.candidateFamilies);
|
|
3969
|
+
const familyProjection = projectBounded(families, compareFamilies);
|
|
3970
|
+
const hints = recordArray(evidence.implementationHintSuggestions);
|
|
3971
|
+
const hintProjection = projectBounded(hints, compareHints2);
|
|
3972
|
+
const hintCount = Math.max(
|
|
3973
|
+
numberValue(evidence.implementationHintSuggestionCount),
|
|
3974
|
+
hintProjection.totalCount
|
|
3975
|
+
);
|
|
3976
|
+
const targets = Math.max(0, targetCandidateCount);
|
|
3977
|
+
return {
|
|
3978
|
+
...evidence,
|
|
3979
|
+
candidates: candidateProjection.items.map(boundedCandidateEvidence),
|
|
3980
|
+
candidateCount: candidateProjection.totalCount,
|
|
3981
|
+
shownCandidateCount: candidateProjection.shownCount,
|
|
3982
|
+
omittedCandidateCount: candidateProjection.omittedCount,
|
|
3983
|
+
candidateFamilies: familyProjection.items.map(boundedFamilyEvidence),
|
|
3984
|
+
candidateFamilyCount: familyProjection.totalCount,
|
|
3985
|
+
shownCandidateFamilyCount: familyProjection.shownCount,
|
|
3986
|
+
omittedCandidateFamilyCount: familyProjection.omittedCount,
|
|
3987
|
+
implementationHintSuggestions: hintProjection.items,
|
|
3988
|
+
implementationHintSuggestionCount: hintCount,
|
|
3989
|
+
shownImplementationHintSuggestionCount: hintProjection.shownCount,
|
|
3990
|
+
omittedImplementationHintSuggestionCount: Math.max(0, hintCount - hintProjection.shownCount),
|
|
3991
|
+
candidateTargetCount: targets,
|
|
3992
|
+
shownCandidateTargetCount: Math.min(targets, candidateProjection.shownCount),
|
|
3993
|
+
omittedCandidateTargetCount: Math.max(0, targets - candidateProjection.shownCount)
|
|
3994
|
+
};
|
|
3684
3995
|
}
|
|
3685
|
-
function
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3996
|
+
function boundedImplementationTargetIds(candidates2) {
|
|
3997
|
+
const projection = projectBounded(candidates2, compareTargetCandidates);
|
|
3998
|
+
return {
|
|
3999
|
+
totalCount: projection.totalCount,
|
|
4000
|
+
shownCount: projection.shownCount,
|
|
4001
|
+
omittedCount: projection.omittedCount,
|
|
4002
|
+
items: projection.items.map((candidate) => String(candidate.methodId ?? ""))
|
|
4003
|
+
};
|
|
3689
4004
|
}
|
|
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
|
-
};
|
|
4005
|
+
function boundedCandidateEvidence(candidate) {
|
|
4006
|
+
const registrations = recordArray(candidate.registrations);
|
|
4007
|
+
const projection = projectBounded(registrations, compareRegistrations);
|
|
3791
4008
|
return {
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
4009
|
+
...candidate,
|
|
4010
|
+
registrations: projection.items,
|
|
4011
|
+
registrationCount: projection.totalCount,
|
|
4012
|
+
shownRegistrationCount: projection.shownCount,
|
|
4013
|
+
omittedRegistrationCount: projection.omittedCount
|
|
3795
4014
|
};
|
|
3796
4015
|
}
|
|
3797
|
-
function
|
|
3798
|
-
|
|
3799
|
-
const
|
|
3800
|
-
return
|
|
4016
|
+
function boundedFamilyEvidence(family) {
|
|
4017
|
+
const repositories = stringArray2(family.repositories);
|
|
4018
|
+
const projection = projectBounded(repositories, (left, right) => left.localeCompare(right));
|
|
4019
|
+
return {
|
|
4020
|
+
...family,
|
|
4021
|
+
repositories: projection.items,
|
|
4022
|
+
repositoryCount: projection.totalCount,
|
|
4023
|
+
shownRepositoryCount: projection.shownCount,
|
|
4024
|
+
omittedRepositoryCount: projection.omittedCount
|
|
4025
|
+
};
|
|
3801
4026
|
}
|
|
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] }));
|
|
4027
|
+
function compareTargetCandidates(left, right) {
|
|
4028
|
+
return Number(right.score ?? 0) - Number(left.score ?? 0) || String(left.className ?? "").localeCompare(String(right.className ?? "")) || Number(left.methodId ?? 0) - Number(right.methodId ?? 0);
|
|
3809
4029
|
}
|
|
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;
|
|
4030
|
+
function compareFamilies(left, right) {
|
|
4031
|
+
return String(left.packageName ?? "").localeCompare(String(right.packageName ?? "")) || String(left.reason ?? "").localeCompare(String(right.reason ?? ""));
|
|
3824
4032
|
}
|
|
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;
|
|
4033
|
+
function compareHints2(left, right) {
|
|
4034
|
+
return String(left.cli ?? "").localeCompare(String(right.cli ?? "")) || String(left.implementationRepo ?? "").localeCompare(String(right.implementationRepo ?? ""));
|
|
3829
4035
|
}
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
function normalizeName(value) {
|
|
3833
|
-
return value.toLowerCase().replace(/^@[^/]+\//, "").replace(/[^a-z0-9]+/g, "");
|
|
4036
|
+
function compareRegistrations(left, right) {
|
|
4037
|
+
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
4038
|
}
|
|
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" };
|
|
4039
|
+
function recordArray(value) {
|
|
4040
|
+
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
3840
4041
|
}
|
|
3841
|
-
function
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
4042
|
+
function stringArray2(value) {
|
|
4043
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
4044
|
+
}
|
|
4045
|
+
function numberValue(value) {
|
|
4046
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
4047
|
+
}
|
|
4048
|
+
|
|
4049
|
+
// src/linker/000-implementation-candidates.ts
|
|
4050
|
+
function linkImplementations(db, workspaceId, generation) {
|
|
4051
|
+
const operations = workspaceOperations(db, workspaceId);
|
|
4052
|
+
let edgeCount = 0;
|
|
4053
|
+
let resolvedCount = 0;
|
|
4054
|
+
let ambiguousCount = 0;
|
|
4055
|
+
let unresolvedCount = 0;
|
|
4056
|
+
for (const operation of operations) {
|
|
4057
|
+
const decision = implementationDecision(db, workspaceId, operation, true);
|
|
4058
|
+
if (decision.candidates.length === 0) continue;
|
|
4059
|
+
const status = decision.unique ? "resolved" : decision.accepted.length > 0 ? "ambiguous" : "unresolved";
|
|
4060
|
+
insertImplementationEdge(db, workspaceId, generation, operation, decision, status);
|
|
4061
|
+
edgeCount += 1;
|
|
4062
|
+
if (status === "resolved") resolvedCount += 1;
|
|
4063
|
+
else if (status === "ambiguous") ambiguousCount += 1;
|
|
4064
|
+
else unresolvedCount += 1;
|
|
4065
|
+
}
|
|
4066
|
+
return { edgeCount, resolvedCount, ambiguousCount, unresolvedCount };
|
|
4067
|
+
}
|
|
4068
|
+
function canonicalImplementationEvidence(db, operationId) {
|
|
4069
|
+
const operation = operationById(db, operationId);
|
|
4070
|
+
const workspaceId = numberValue2(operation?.workspaceId);
|
|
4071
|
+
if (!operation || workspaceId === void 0) return void 0;
|
|
4072
|
+
return implementationDecision(db, workspaceId, operation, false).evidenceWithHints;
|
|
4073
|
+
}
|
|
4074
|
+
function workspaceOperations(db, workspaceId) {
|
|
4075
|
+
const rows2 = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,
|
|
4076
|
+
o.operation_name operationName,o.provenance provenance,o.base_operation_id baseOperationId,
|
|
4077
|
+
s.service_path servicePath,s.repo_id modelRepoId,r.name modelRepo,
|
|
4078
|
+
r.package_name modelPackage,r.kind modelKind
|
|
4079
|
+
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id
|
|
4080
|
+
JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=?`).all(workspaceId);
|
|
4081
|
+
return rows2;
|
|
4082
|
+
}
|
|
4083
|
+
function operationById(db, operationId) {
|
|
4084
|
+
const row = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,
|
|
4085
|
+
o.operation_name operationName,o.provenance provenance,o.base_operation_id baseOperationId,
|
|
4086
|
+
s.service_path servicePath,s.repo_id modelRepoId,r.name modelRepo,
|
|
4087
|
+
r.package_name modelPackage,r.kind modelKind,r.workspace_id workspaceId
|
|
4088
|
+
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id
|
|
4089
|
+
JOIN repositories r ON r.id=s.repo_id WHERE o.id=?`).get(operationId);
|
|
4090
|
+
return row;
|
|
4091
|
+
}
|
|
4092
|
+
function implementationDecision(db, workspaceId, operation, recordDiagnostics) {
|
|
4093
|
+
const implementationContext = implementationContextForOperation(db, operation);
|
|
4094
|
+
const candidates2 = rankedImplementationCandidates(
|
|
4095
|
+
db,
|
|
4096
|
+
workspaceId,
|
|
4097
|
+
implementationContext,
|
|
4098
|
+
recordDiagnostics
|
|
4099
|
+
);
|
|
4100
|
+
const accepted = candidates2.filter((candidate) => candidate.accepted);
|
|
4101
|
+
const topScore = accepted[0]?.score ?? 0;
|
|
4102
|
+
const winners = accepted.filter((candidate) => candidate.score === topScore);
|
|
4103
|
+
const duplicateFamilies = duplicatePackageFamilies(accepted);
|
|
4104
|
+
const duplicatePackageAmbiguous = duplicateFamilies.length > 0 && !accepted.some(hasDirectOwnershipEvidence);
|
|
4105
|
+
const selected = duplicatePackageAmbiguous ? accepted : winners;
|
|
4106
|
+
const unique3 = !duplicatePackageAmbiguous && winners.length === 1 ? winners[0] : void 0;
|
|
4107
|
+
const ambiguityReasons = duplicatePackageAmbiguous ? ["duplicate_package_name_candidates"] : winners.length > 1 ? ["multiple_equal_score_implementation_candidates"] : [];
|
|
4108
|
+
const evidence = implementationEvidence(
|
|
4109
|
+
operation,
|
|
4110
|
+
implementationContext,
|
|
4111
|
+
candidates2,
|
|
4112
|
+
duplicateFamilies,
|
|
4113
|
+
ambiguityReasons,
|
|
4114
|
+
unique3
|
|
4115
|
+
);
|
|
4116
|
+
const hintProjection = implementationHintSuggestionProjection(evidence);
|
|
4117
|
+
return {
|
|
4118
|
+
candidates: candidates2,
|
|
4119
|
+
accepted,
|
|
4120
|
+
selected,
|
|
4121
|
+
unique: unique3,
|
|
4122
|
+
evidence,
|
|
4123
|
+
evidenceWithHints: unique3 ? evidence : {
|
|
4124
|
+
...evidence,
|
|
4125
|
+
implementationHintSuggestions: hintProjection.suggestions,
|
|
4126
|
+
implementationHintSuggestionCount: hintProjection.suggestionCount,
|
|
4127
|
+
shownImplementationHintSuggestionCount: hintProjection.shownSuggestionCount,
|
|
4128
|
+
omittedImplementationHintSuggestionCount: hintProjection.omittedSuggestionCount
|
|
4129
|
+
}
|
|
4130
|
+
};
|
|
4131
|
+
}
|
|
4132
|
+
function insertImplementationEdge(db, workspaceId, generation, operation, decision, status) {
|
|
4133
|
+
const targetCandidates = status === "unresolved" ? decision.candidates : decision.selected;
|
|
4134
|
+
const targetProjection = boundedImplementationTargetIds(targetCandidates);
|
|
4135
|
+
const targetIds = targetProjection.items;
|
|
4136
|
+
const toId = status === "resolved" ? graphId(decision.unique?.methodId) : targetIds.join(",");
|
|
4137
|
+
const reason = status === "unresolved" ? "No implementation candidate passed policy" : status === "ambiguous" ? "Ambiguous registered handler implementation candidates" : null;
|
|
4138
|
+
db.prepare(`INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,
|
|
4139
|
+
to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation)
|
|
4140
|
+
VALUES(?,?,?,?,?,?,?,?,?,?,?,?)`).run(
|
|
4141
|
+
workspaceId,
|
|
4142
|
+
"OPERATION_IMPLEMENTED_BY_HANDLER",
|
|
4143
|
+
status,
|
|
4144
|
+
"operation",
|
|
4145
|
+
graphId(operation.operationId),
|
|
4146
|
+
status === "resolved" ? "handler_method" : "handler_method_candidates",
|
|
4147
|
+
toId,
|
|
4148
|
+
status === "resolved" ? 0.95 : status === "ambiguous" ? 0.5 : 0,
|
|
4149
|
+
JSON.stringify(boundedImplementationEvidence(
|
|
4150
|
+
decision.evidenceWithHints,
|
|
4151
|
+
targetProjection.totalCount
|
|
4152
|
+
)),
|
|
4153
|
+
0,
|
|
4154
|
+
reason,
|
|
4155
|
+
generation
|
|
4156
|
+
);
|
|
4157
|
+
}
|
|
4158
|
+
function implementationEvidence(operation, context, candidates2, duplicateFamilies, ambiguityReasons, selected) {
|
|
4159
|
+
return {
|
|
4160
|
+
servicePath: operation.servicePath,
|
|
4161
|
+
operationPath: operation.operationPath,
|
|
4162
|
+
operationName: operation.operationName,
|
|
4163
|
+
modelPackage: {
|
|
4164
|
+
id: operation.modelRepoId,
|
|
4165
|
+
name: operation.modelRepo,
|
|
4166
|
+
packageName: operation.modelPackage
|
|
4167
|
+
},
|
|
4168
|
+
implementationSource: context.operationId === operation.operationId ? "direct_or_concrete_override" : "inherited_from_base_operation",
|
|
4169
|
+
baseOperationId: operation.baseOperationId,
|
|
4170
|
+
implementationOperationId: context.operationId,
|
|
4171
|
+
ambiguityReasons,
|
|
4172
|
+
candidateFamilies: duplicateFamilies,
|
|
4173
|
+
selectedHandler: selected ? selectedHandlerProvenance(selectedHandlerSource(selected)) : void 0,
|
|
4174
|
+
candidates: displayImplementationCandidates(
|
|
4175
|
+
candidates2.map((candidate, index) => candidateEvidence(candidate, index + 1)),
|
|
4176
|
+
selected?.methodId
|
|
4177
|
+
)
|
|
4178
|
+
};
|
|
4179
|
+
}
|
|
4180
|
+
function implementationContextForOperation(db, operation) {
|
|
4181
|
+
if (operation.provenance !== "inherited" || !operation.baseOperationId) return operation;
|
|
4182
|
+
const base = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,
|
|
4183
|
+
o.operation_name operationName,o.provenance provenance,o.base_operation_id baseOperationId,
|
|
4184
|
+
s.service_path servicePath,s.repo_id modelRepoId,r.name modelRepo,
|
|
4185
|
+
r.package_name modelPackage,r.kind modelKind
|
|
4186
|
+
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id
|
|
4187
|
+
JOIN repositories r ON r.id=s.repo_id WHERE o.id=?`).get(operation.baseOperationId);
|
|
4188
|
+
return base ? {
|
|
4189
|
+
...base,
|
|
4190
|
+
effectiveOperationId: operation.operationId,
|
|
4191
|
+
effectiveServicePath: operation.servicePath,
|
|
4192
|
+
effectiveOperationPath: operation.operationPath
|
|
4193
|
+
} : operation;
|
|
4194
|
+
}
|
|
4195
|
+
function rankedImplementationCandidates(db, workspaceId, operation, recordDiagnostics) {
|
|
4196
|
+
const rows2 = implementationCandidates(db, workspaceId, operation);
|
|
4197
|
+
if (recordDiagnostics) recordRegistrationInvariantDiagnostics(
|
|
4198
|
+
db,
|
|
4199
|
+
rows2.filter((row) => !validRegistrationPair(row))
|
|
4200
|
+
);
|
|
4201
|
+
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);
|
|
4202
|
+
}
|
|
4203
|
+
function validRegistrationPair(row) {
|
|
4204
|
+
if (row.registrationHandlerClassId === null || row.registrationHandlerClassId === void 0)
|
|
4205
|
+
return registrationPairingStrategy(row) !== "unproven";
|
|
4206
|
+
return Number(row.registrationHandlerClassId) === Number(row.classId);
|
|
4207
|
+
}
|
|
4208
|
+
function registrationPairingStrategy(row) {
|
|
4209
|
+
if (row.registrationHandlerClassId !== null && row.registrationHandlerClassId !== void 0)
|
|
4210
|
+
return "exact_handler_class_id";
|
|
4211
|
+
if (Number(row.applicationRepoId) === Number(row.handlerRepoId))
|
|
4212
|
+
return "same_repository_class_name_fallback";
|
|
4213
|
+
const source = stringValue4(row.importSource);
|
|
4214
|
+
const separator = source?.lastIndexOf("#") ?? -1;
|
|
4215
|
+
if (!source || separator <= 0) return "unproven";
|
|
4216
|
+
const moduleName = source.slice(0, separator);
|
|
4217
|
+
const importedName = source.slice(separator + 1);
|
|
4218
|
+
const matchesClass = importedName === row.className || importedName === "default" && row.registrationClassName === row.className;
|
|
4219
|
+
return moduleName === row.handlerPackage && matchesClass ? "explicit_package_import" : "unproven";
|
|
4220
|
+
}
|
|
4221
|
+
function recordRegistrationInvariantDiagnostics(db, rows2) {
|
|
4222
|
+
const insert = db.prepare(`INSERT INTO diagnostics(repo_id,severity,code,message,
|
|
4223
|
+
source_file,source_line)
|
|
4224
|
+
SELECT ?,'error','handler_registration_class_mismatch',
|
|
4225
|
+
'Implementation candidate registration did not match its persisted handler class id',?,?
|
|
4226
|
+
WHERE NOT EXISTS (SELECT 1 FROM diagnostics WHERE repo_id=?
|
|
4227
|
+
AND code='handler_registration_class_mismatch' AND source_file=? AND source_line=?)`);
|
|
4228
|
+
for (const row of rows2) insert.run(
|
|
4229
|
+
row.applicationRepoId,
|
|
4230
|
+
row.registrationFile,
|
|
4231
|
+
row.registrationLine,
|
|
4232
|
+
row.applicationRepoId,
|
|
4233
|
+
row.registrationFile,
|
|
4234
|
+
row.registrationLine
|
|
4235
|
+
);
|
|
4236
|
+
}
|
|
4237
|
+
function deduplicateCandidates(rows2) {
|
|
4238
|
+
const merged = /* @__PURE__ */ new Map();
|
|
4239
|
+
for (const row of rows2) {
|
|
4240
|
+
const key = [row.methodId, row.classId, row.handlerRepoId].join(":");
|
|
4241
|
+
const registration = registrationEvidence(row);
|
|
4242
|
+
const existing = merged.get(key);
|
|
4243
|
+
if (!existing) {
|
|
4244
|
+
merged.set(key, { ...row, registrations: [registration] });
|
|
4245
|
+
continue;
|
|
4246
|
+
}
|
|
4247
|
+
existing.registrations = uniqueRegistrations([
|
|
4248
|
+
...existing.registrations ?? [],
|
|
4249
|
+
registration
|
|
4250
|
+
]);
|
|
4251
|
+
existing.score = Math.max(existing.score, row.score);
|
|
4252
|
+
existing.accepted = existing.accepted || row.accepted;
|
|
4253
|
+
existing.acceptedReasons = [.../* @__PURE__ */ new Set([...existing.acceptedReasons, ...row.acceptedReasons])];
|
|
4254
|
+
existing.rejectedReasons = [.../* @__PURE__ */ new Set([...existing.rejectedReasons, ...row.rejectedReasons])];
|
|
4255
|
+
}
|
|
4256
|
+
return [...merged.values()];
|
|
4257
|
+
}
|
|
4258
|
+
function registrationEvidence(row) {
|
|
4259
|
+
return {
|
|
4260
|
+
id: row.registrationId,
|
|
4261
|
+
handlerClassId: row.registrationHandlerClassId,
|
|
4262
|
+
file: row.registrationFile,
|
|
4263
|
+
line: row.registrationLine,
|
|
4264
|
+
kind: row.registrationKind,
|
|
4265
|
+
importSource: row.importSource,
|
|
4266
|
+
pairingStrategy: registrationPairingStrategy(row)
|
|
4267
|
+
};
|
|
4268
|
+
}
|
|
4269
|
+
function uniqueRegistrations(rows2) {
|
|
4270
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4271
|
+
return rows2.filter((row) => {
|
|
4272
|
+
const key = JSON.stringify(row);
|
|
4273
|
+
if (seen.has(key)) return false;
|
|
4274
|
+
seen.add(key);
|
|
4275
|
+
return true;
|
|
4276
|
+
});
|
|
4277
|
+
}
|
|
4278
|
+
function duplicatePackageFamilies(candidates2) {
|
|
4279
|
+
const byPackage = /* @__PURE__ */ new Map();
|
|
4280
|
+
for (const candidate of candidates2) {
|
|
4281
|
+
const packageName = stringValue4(candidate.handlerPackage);
|
|
4282
|
+
if (packageName) byPackage.set(packageName, [
|
|
4283
|
+
...byPackage.get(packageName) ?? [],
|
|
4284
|
+
candidate
|
|
4285
|
+
]);
|
|
4286
|
+
}
|
|
4287
|
+
return [...byPackage.entries()].filter(([, rows2]) => new Set(rows2.map((row) => Number(row.handlerRepoId))).size > 1).map(([packageName, rows2]) => ({
|
|
4288
|
+
reason: "duplicate_package_name_candidates",
|
|
4289
|
+
packageName,
|
|
4290
|
+
count: rows2.length,
|
|
4291
|
+
repositories: rows2.map((row) => row.handlerRepo).sort()
|
|
4292
|
+
}));
|
|
4293
|
+
}
|
|
4294
|
+
function hasDirectOwnershipEvidence(candidate) {
|
|
4295
|
+
return candidate.acceptedReasons.some((reason) => [
|
|
4296
|
+
"model package equals registration package",
|
|
4297
|
+
"model package equals handler package",
|
|
4298
|
+
"registration package contains exact local service path"
|
|
4299
|
+
].includes(reason));
|
|
4300
|
+
}
|
|
4301
|
+
function implementationCandidates(db, workspaceId, operation) {
|
|
4302
|
+
const modelRepoGraphId = graphId(operation.modelRepoId);
|
|
4303
|
+
return db.prepare(`SELECT DISTINCT
|
|
4304
|
+
hm.id methodId,hm.method_name methodName,hm.decorator_value decoratorValue,
|
|
4305
|
+
hm.decorator_raw_expression decoratorRawExpression,
|
|
4306
|
+
hm.decorator_resolution_json decoratorResolutionJson,hc.id classId,
|
|
4307
|
+
hc.class_name className,hc.source_file sourceFile,hm.source_line sourceLine,
|
|
4308
|
+
hr.id registrationId,hr.handler_class_id registrationHandlerClassId,
|
|
4309
|
+
hr.class_name registrationClassName,hr.repo_id applicationRepoId,
|
|
4310
|
+
hr.registration_file registrationFile,hr.registration_line registrationLine,
|
|
4311
|
+
hr.registration_kind registrationKind,hr.import_source importSource,
|
|
4312
|
+
handlerRepo.id handlerRepoId,handlerRepo.name handlerRepo,
|
|
4313
|
+
handlerRepo.package_name handlerPackage,appRepo.name applicationRepo,
|
|
4314
|
+
appRepo.package_name applicationPackage,? modelRepoId,? modelRepo,? modelPackage,
|
|
4315
|
+
? modelKind,? servicePath,? operationPath,? operationName,
|
|
4316
|
+
CASE WHEN appRepo.id=? THEN 1 ELSE 0 END modelIsApplicationRepo,
|
|
4317
|
+
CASE WHEN handlerRepo.id=? THEN 1 ELSE 0 END modelIsHandlerRepo,
|
|
4318
|
+
CASE WHEN appRepo.id=handlerRepo.id THEN 1 ELSE 0 END sameRepoRegistration,
|
|
4319
|
+
CASE WHEN EXISTS (SELECT 1 FROM cds_services localService
|
|
4320
|
+
WHERE localService.repo_id=appRepo.id AND localService.service_path=?)
|
|
4321
|
+
THEN 1 ELSE 0 END localServicePathMatch,
|
|
4322
|
+
CASE WHEN EXISTS (SELECT 1 FROM cds_services localService
|
|
4323
|
+
WHERE localService.repo_id=appRepo.id) THEN 1 ELSE 0 END applicationHasLocalServices,
|
|
4324
|
+
CASE WHEN EXISTS (SELECT 1 FROM handler_registrations localReg
|
|
4325
|
+
JOIN handler_classes localClass ON ((localReg.handler_class_id IS NOT NULL
|
|
4326
|
+
AND localClass.id=localReg.handler_class_id) OR (localReg.handler_class_id IS NULL
|
|
4327
|
+
AND localClass.class_name=localReg.class_name AND localClass.repo_id=localReg.repo_id))
|
|
4328
|
+
JOIN handler_methods localMethod ON localMethod.handler_class_id=localClass.id
|
|
4329
|
+
WHERE localReg.repo_id=appRepo.id
|
|
4330
|
+
AND COALESCE(json_extract(localMethod.decorator_resolution_json,'$.handlerKind'),
|
|
4331
|
+
CASE WHEN localMethod.decorator_kind='Event' THEN 'event'
|
|
4332
|
+
WHEN localMethod.decorator_kind IN ('Action','Func','On') THEN 'operation'
|
|
4333
|
+
ELSE 'unsupported' END)='operation'
|
|
4334
|
+
AND COALESCE(json_extract(localMethod.decorator_resolution_json,'$.executable'),
|
|
4335
|
+
CASE WHEN localMethod.decorator_kind IN ('Action','Func','On') THEN 1 ELSE 0 END)=1
|
|
4336
|
+
AND (localMethod.decorator_value=? OR localMethod.decorator_value=?
|
|
4337
|
+
OR localMethod.method_name=? OR localMethod.decorator_raw_expression LIKE ?))
|
|
4338
|
+
THEN 1 ELSE 0 END applicationHasLocalRegistrationForOperation,
|
|
4339
|
+
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep
|
|
4340
|
+
WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved'
|
|
4341
|
+
AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT)
|
|
4342
|
+
AND dep.to_id=?) THEN 1 ELSE 0 END appDependsOnModel,
|
|
4343
|
+
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep
|
|
4344
|
+
WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved'
|
|
4345
|
+
AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT)
|
|
4346
|
+
AND dep.to_id=CAST(handlerRepo.id AS TEXT)) THEN 1 ELSE 0 END appDependsOnHandler,
|
|
4347
|
+
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep
|
|
4348
|
+
WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved'
|
|
4349
|
+
AND dep.from_kind='repo' AND dep.from_id=CAST(handlerRepo.id AS TEXT)
|
|
4350
|
+
AND dep.to_id=?) THEN 1 ELSE 0 END handlerDependsOnModel
|
|
4351
|
+
FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
4352
|
+
JOIN repositories handlerRepo ON handlerRepo.id=hc.repo_id
|
|
4353
|
+
JOIN handler_registrations hr ON ((hr.handler_class_id IS NOT NULL
|
|
4354
|
+
AND hr.handler_class_id=hc.id) OR (hr.handler_class_id IS NULL AND
|
|
4355
|
+
((hr.class_name=hc.class_name AND hr.repo_id=hc.repo_id) OR
|
|
4356
|
+
(instr(hr.import_source,'#')>1 AND substr(hr.import_source,1,
|
|
4357
|
+
instr(hr.import_source,'#')-1)=handlerRepo.package_name AND
|
|
4358
|
+
(substr(hr.import_source,instr(hr.import_source,'#')+1)=hc.class_name OR
|
|
4359
|
+
(substr(hr.import_source,instr(hr.import_source,'#')+1)='default'
|
|
4360
|
+
AND hr.class_name=hc.class_name))))))
|
|
4361
|
+
JOIN repositories appRepo ON appRepo.id=hr.repo_id
|
|
4362
|
+
WHERE appRepo.workspace_id=?
|
|
4363
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
|
|
4364
|
+
CASE WHEN hm.decorator_kind='Event' THEN 'event'
|
|
4365
|
+
WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
|
|
4366
|
+
ELSE 'unsupported' END)='operation'
|
|
4367
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
|
|
4368
|
+
CASE WHEN hm.decorator_kind IN ('Action','Func','On') THEN 1 ELSE 0 END)=1
|
|
4369
|
+
AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=?
|
|
4370
|
+
OR hm.decorator_raw_expression LIKE ?)`).all(
|
|
4371
|
+
operation.modelRepoId,
|
|
4372
|
+
operation.modelRepo,
|
|
4373
|
+
operation.modelPackage,
|
|
4374
|
+
operation.modelKind,
|
|
4375
|
+
operation.servicePath,
|
|
4376
|
+
operation.operationPath,
|
|
4377
|
+
operation.operationName,
|
|
4378
|
+
operation.modelRepoId,
|
|
4379
|
+
operation.modelRepoId,
|
|
4380
|
+
operation.servicePath,
|
|
4381
|
+
normalizedOperation(String(operation.operationPath ?? "")),
|
|
4382
|
+
operation.operationName,
|
|
4383
|
+
operation.operationName,
|
|
4384
|
+
`%${upperFirst2(normalizedOperation(String(operation.operationPath ?? operation.operationName ?? "")))}%`,
|
|
4385
|
+
modelRepoGraphId,
|
|
4386
|
+
modelRepoGraphId,
|
|
4387
|
+
workspaceId,
|
|
4388
|
+
normalizedOperation(String(operation.operationPath ?? "")),
|
|
4389
|
+
operation.operationName,
|
|
4390
|
+
operation.operationName,
|
|
4391
|
+
`%${upperFirst2(normalizedOperation(String(operation.operationPath ?? operation.operationName ?? "")))}%`
|
|
4392
|
+
);
|
|
4393
|
+
}
|
|
4394
|
+
function scoreImplementationCandidate(row, operation) {
|
|
4395
|
+
const acceptedReasons = [];
|
|
4396
|
+
const rejectedReasons = [];
|
|
4397
|
+
const methodSignal = implementationMethodSignal(row, operation);
|
|
4398
|
+
acceptedReasons.push(...methodSignal.acceptedReasons);
|
|
4399
|
+
rejectedReasons.push(...methodSignal.rejectedReasons);
|
|
4400
|
+
const signals = implementationOwnershipSignals(row, methodSignal.matches);
|
|
4401
|
+
acceptedReasons.push(...signals.acceptedReasons);
|
|
4402
|
+
rejectedReasons.push(...signals.rejectedReasons);
|
|
4403
|
+
const accepted = methodSignal.matches && !methodSignal.contradicted && !signals.contradicted && signals.hasOwnership;
|
|
4404
|
+
if (!accepted && rejectedReasons.length === 0)
|
|
4405
|
+
rejectedReasons.push("candidate did not meet implementation ownership policy");
|
|
4406
|
+
return {
|
|
4407
|
+
...row,
|
|
4408
|
+
methodId: Number(row.methodId),
|
|
4409
|
+
score: signals.score,
|
|
4410
|
+
accepted,
|
|
4411
|
+
acceptedReasons,
|
|
4412
|
+
rejectedReasons
|
|
4413
|
+
};
|
|
4414
|
+
}
|
|
4415
|
+
function implementationOwnershipSignals(row, methodMatches) {
|
|
4416
|
+
const acceptedReasons = [];
|
|
4417
|
+
const rejectedReasons = [];
|
|
4418
|
+
const modelIsApplicationRepo = flag(row.modelIsApplicationRepo);
|
|
4419
|
+
const modelIsHandlerRepo = flag(row.modelIsHandlerRepo);
|
|
4420
|
+
const localServicePathMatch = flag(row.localServicePathMatch);
|
|
4421
|
+
const applicationHasLocalServices = flag(row.applicationHasLocalServices);
|
|
4422
|
+
const appDependsOnModel = flag(row.appDependsOnModel);
|
|
4423
|
+
const appDependsOnHandler = flag(row.appDependsOnHandler);
|
|
4424
|
+
const handlerDependsOnModel = flag(row.handlerDependsOnModel);
|
|
4425
|
+
const importSource = Boolean(stringValue4(row.importSource));
|
|
4426
|
+
const sameRepoRegistration = flag(row.sameRepoRegistration);
|
|
4427
|
+
const modelOriented = row.modelKind === "cap-db-model" || !flag(row.applicationHasLocalRegistrationForOperation);
|
|
4428
|
+
const helperOwned = modelOriented && methodMatches && sameRepoRegistration && importSource && !applicationHasLocalServices && !modelIsApplicationRepo && !modelIsHandlerRepo && !localServicePathMatch && !appDependsOnModel && !appDependsOnHandler && !handlerDependsOnModel;
|
|
4429
|
+
const score = ownershipScore({
|
|
4430
|
+
modelIsApplicationRepo,
|
|
4431
|
+
modelIsHandlerRepo,
|
|
4432
|
+
localServicePathMatch,
|
|
4433
|
+
appDependsOnModel,
|
|
4434
|
+
appDependsOnHandler,
|
|
4435
|
+
handlerDependsOnModel,
|
|
4436
|
+
helperOwned,
|
|
4437
|
+
importSource
|
|
4438
|
+
}, acceptedReasons);
|
|
4439
|
+
const hasOwnership = modelIsApplicationRepo || modelIsHandlerRepo;
|
|
4440
|
+
const hasCrossPackage = appDependsOnModel && (modelIsHandlerRepo || appDependsOnHandler || !importSource);
|
|
4441
|
+
const contradicted = applicationHasLocalServices && !localServicePathMatch && !appDependsOnModel && !hasOwnership;
|
|
4442
|
+
if (applicationHasLocalServices && !localServicePathMatch && !appDependsOnModel && !modelIsApplicationRepo)
|
|
4443
|
+
rejectedReasons.push(`registration package has local services but none match ${String(row.servicePath ?? "")}`);
|
|
4444
|
+
if (!hasOwnership && !localServicePathMatch && !hasCrossPackage && !helperOwned)
|
|
4445
|
+
rejectedReasons.push("missing direct ownership, exact local service path, or validated cross-package dependency evidence");
|
|
4446
|
+
return {
|
|
4447
|
+
score,
|
|
4448
|
+
hasOwnership: hasOwnership || localServicePathMatch || hasCrossPackage || handlerDependsOnModel || helperOwned,
|
|
4449
|
+
contradicted,
|
|
4450
|
+
acceptedReasons,
|
|
4451
|
+
rejectedReasons
|
|
4452
|
+
};
|
|
4453
|
+
}
|
|
4454
|
+
function ownershipScore(signals, acceptedReasons) {
|
|
4455
|
+
let score = 0;
|
|
4456
|
+
const values = [
|
|
4457
|
+
["modelIsApplicationRepo", 100, "model package equals registration package"],
|
|
4458
|
+
["modelIsHandlerRepo", 100, "model package equals handler package"],
|
|
4459
|
+
["localServicePathMatch", 80, "registration package contains exact local service path"],
|
|
4460
|
+
["appDependsOnModel", 70, "registration package depends on model package"],
|
|
4461
|
+
["appDependsOnHandler", 30, "registration package depends on handler package"],
|
|
4462
|
+
["handlerDependsOnModel", 20, "handler package depends on model package"],
|
|
4463
|
+
["helperOwned", 60, "unique registered helper implementation for model-only operation"],
|
|
4464
|
+
["importSource", 10, "registration imports handler class"]
|
|
4465
|
+
];
|
|
4466
|
+
for (const [key, amount, reason] of values) {
|
|
4467
|
+
if (!signals[key]) continue;
|
|
4468
|
+
score += amount;
|
|
4469
|
+
acceptedReasons.push(reason);
|
|
4470
|
+
}
|
|
4471
|
+
return score;
|
|
4472
|
+
}
|
|
4473
|
+
function candidateEvidence(candidate, rank) {
|
|
4474
|
+
return {
|
|
4475
|
+
rank,
|
|
4476
|
+
rankKind: "discovery_score",
|
|
4477
|
+
score: candidate.score,
|
|
4478
|
+
accepted: candidate.accepted,
|
|
4479
|
+
acceptedReasons: candidate.acceptedReasons,
|
|
4480
|
+
rejectedReasons: candidate.rejectedReasons,
|
|
4481
|
+
methodId: candidate.methodId,
|
|
4482
|
+
classId: candidate.classId,
|
|
4483
|
+
className: candidate.className,
|
|
4484
|
+
sourceFile: candidate.sourceFile,
|
|
4485
|
+
sourceLine: candidate.sourceLine,
|
|
4486
|
+
decoratorResolution: objectJson(candidate.decoratorResolutionJson),
|
|
4487
|
+
registration: registrationEvidence(candidate),
|
|
4488
|
+
registrations: candidate.registrations ?? [],
|
|
4489
|
+
registrationPairing: {
|
|
4490
|
+
strategy: registrationPairingStrategy(candidate),
|
|
4491
|
+
registrationId: candidate.registrationId,
|
|
4492
|
+
registrationHandlerClassId: candidate.registrationHandlerClassId,
|
|
4493
|
+
candidateHandlerClassId: candidate.classId,
|
|
4494
|
+
invariantStatus: validRegistrationPair(candidate) ? "valid" : "invalid"
|
|
4495
|
+
},
|
|
4496
|
+
applicationPackage: {
|
|
4497
|
+
id: candidate.applicationRepoId,
|
|
4498
|
+
name: candidate.applicationRepo,
|
|
4499
|
+
packageName: candidate.applicationPackage
|
|
4500
|
+
},
|
|
4501
|
+
handlerPackage: {
|
|
4502
|
+
id: candidate.handlerRepoId,
|
|
4503
|
+
name: candidate.handlerRepo,
|
|
4504
|
+
packageName: candidate.handlerPackage
|
|
4505
|
+
},
|
|
4506
|
+
modelPackage: {
|
|
4507
|
+
id: candidate.modelRepoId,
|
|
4508
|
+
name: candidate.modelRepo,
|
|
4509
|
+
packageName: candidate.modelPackage
|
|
4510
|
+
},
|
|
4511
|
+
servicePath: candidate.servicePath,
|
|
4512
|
+
operationPath: candidate.operationPath,
|
|
4513
|
+
operationName: candidate.operationName,
|
|
4514
|
+
signals: {
|
|
4515
|
+
directOwnership: {
|
|
4516
|
+
modelIsApplicationRepo: flag(candidate.modelIsApplicationRepo),
|
|
4517
|
+
modelIsHandlerRepo: flag(candidate.modelIsHandlerRepo)
|
|
4518
|
+
},
|
|
4519
|
+
localServicePathMatch: flag(candidate.localServicePathMatch),
|
|
4520
|
+
applicationHasLocalServices: flag(candidate.applicationHasLocalServices),
|
|
4521
|
+
applicationHasLocalRegistrationForOperation: flag(candidate.applicationHasLocalRegistrationForOperation),
|
|
4522
|
+
appDependsOnModel: flag(candidate.appDependsOnModel),
|
|
4523
|
+
appDependsOnHandler: flag(candidate.appDependsOnHandler),
|
|
4524
|
+
handlerDependsOnModel: flag(candidate.handlerDependsOnModel),
|
|
4525
|
+
sameRepoRegistration: flag(candidate.sameRepoRegistration)
|
|
4526
|
+
}
|
|
4527
|
+
};
|
|
4528
|
+
}
|
|
4529
|
+
function selectedHandlerSource(candidate) {
|
|
4530
|
+
return {
|
|
4531
|
+
methodId: candidate.methodId,
|
|
4532
|
+
className: stringValue4(candidate.className),
|
|
4533
|
+
methodName: stringValue4(candidate.methodName),
|
|
4534
|
+
repositoryId: numberValue2(candidate.handlerRepoId),
|
|
4535
|
+
repositoryName: stringValue4(candidate.handlerRepo),
|
|
4536
|
+
repositoryPackageName: stringValue4(candidate.handlerPackage),
|
|
4537
|
+
sourceFile: stringValue4(candidate.sourceFile),
|
|
4538
|
+
sourceLine: numberValue2(candidate.sourceLine)
|
|
4539
|
+
};
|
|
4540
|
+
}
|
|
4541
|
+
function implementationMethodSignal(row, operation) {
|
|
4542
|
+
const resolution = objectJson(row.decoratorResolutionJson) ?? {};
|
|
4543
|
+
if (resolution.handlerKind && resolution.handlerKind !== "operation")
|
|
4544
|
+
return { matches: false, contradicted: true, acceptedReasons: [], rejectedReasons: ["non_operation_handler_kind"] };
|
|
4545
|
+
if (resolution.executable === false)
|
|
4546
|
+
return { matches: false, contradicted: true, acceptedReasons: [], rejectedReasons: ["handler_method_not_executable"] };
|
|
4547
|
+
const operationName = normalizedOperationName(String(
|
|
4548
|
+
operation.operationPath ?? operation.operationName ?? ""
|
|
4549
|
+
));
|
|
4550
|
+
const decorator = normalizeDecoratorOperationSignal(
|
|
4551
|
+
stringValue4(row.decoratorValue),
|
|
4552
|
+
stringValue4(row.decoratorRawExpression),
|
|
4553
|
+
operationName
|
|
4554
|
+
);
|
|
4555
|
+
if (decorator.status === "resolved" && decorator.operationName === operationName)
|
|
4556
|
+
return { matches: true, contradicted: false, acceptedReasons: ["decorator targets operation"], rejectedReasons: [] };
|
|
4557
|
+
if (decorator.status === "resolved")
|
|
4558
|
+
return { matches: false, contradicted: true, acceptedReasons: [], rejectedReasons: ["method_name_matches_but_decorator_targets_different_operation"] };
|
|
4559
|
+
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"] };
|
|
4560
|
+
}
|
|
4561
|
+
function objectJson(value) {
|
|
4562
|
+
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
4563
|
+
try {
|
|
4564
|
+
const parsed = JSON.parse(value);
|
|
4565
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
4566
|
+
} catch {
|
|
4567
|
+
return void 0;
|
|
4568
|
+
}
|
|
4569
|
+
}
|
|
4570
|
+
function upperFirst2(value) {
|
|
4571
|
+
return value ? `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}` : value;
|
|
4572
|
+
}
|
|
4573
|
+
function flag(value) {
|
|
4574
|
+
return Boolean(Number(value ?? 0));
|
|
4575
|
+
}
|
|
4576
|
+
function graphId(value) {
|
|
4577
|
+
return String(value ?? "");
|
|
4578
|
+
}
|
|
4579
|
+
function normalizedOperation(value) {
|
|
4580
|
+
return value.startsWith("/") ? value.slice(1) : value;
|
|
4581
|
+
}
|
|
4582
|
+
function stringValue4(value) {
|
|
4583
|
+
return typeof value === "string" ? value : void 0;
|
|
4584
|
+
}
|
|
4585
|
+
function numberValue2(value) {
|
|
4586
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
4587
|
+
}
|
|
4588
|
+
|
|
4589
|
+
// src/linker/service-resolver.ts
|
|
4590
|
+
function rows(db, operationPath, workspaceId) {
|
|
4591
|
+
const names = operationLookupNames(operationPath);
|
|
4592
|
+
const result = db.prepare(
|
|
4593
|
+
`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
|
|
4594
|
+
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
|
|
4595
|
+
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`
|
|
4596
|
+
).all(
|
|
4597
|
+
workspaceId,
|
|
4598
|
+
workspaceId,
|
|
4599
|
+
names.path,
|
|
4600
|
+
names.simplePath,
|
|
4601
|
+
names.name,
|
|
4602
|
+
names.simpleName
|
|
4603
|
+
);
|
|
4604
|
+
return result.map((row) => ({
|
|
4605
|
+
...row,
|
|
4606
|
+
score: Number(row.score ?? 0),
|
|
4607
|
+
reasons: []
|
|
4608
|
+
}));
|
|
4609
|
+
}
|
|
4610
|
+
function operationLookupNames(operationPath) {
|
|
4611
|
+
const name = operationPath.replace(/^\//, "");
|
|
4612
|
+
const simpleName = name.split(".").at(-1) ?? name;
|
|
4613
|
+
return { path: operationPath, simplePath: `/${simpleName}`, name, simpleName };
|
|
4614
|
+
}
|
|
4615
|
+
function operationMatches(candidate, operationPath) {
|
|
4616
|
+
if (!operationPath) return false;
|
|
4617
|
+
const names = operationLookupNames(operationPath);
|
|
4618
|
+
return candidate.operationPath === names.path || candidate.operationPath === names.simplePath || candidate.operationName === names.name || candidate.operationName === names.simpleName;
|
|
4619
|
+
}
|
|
4620
|
+
function resolveOperation(db, signals, workspaceId) {
|
|
4621
|
+
const missing = [signals.servicePath, signals.alias, signals.destination, signals.operationPath].flatMap((value) => [...(value ?? "").matchAll(/\$\{([^}]*)\}/g)].map((match) => (match[1] ?? "").trim())).filter(Boolean);
|
|
4622
|
+
if (missing.length > 0)
|
|
4623
|
+
return {
|
|
4624
|
+
status: "dynamic",
|
|
4625
|
+
candidates: signals.operationPath ? rows(db, signals.operationPath, workspaceId).map((candidate) => ({
|
|
4626
|
+
...candidate,
|
|
4627
|
+
score: 0.2,
|
|
4628
|
+
reasons: ["operation_path_match"]
|
|
4629
|
+
})) : [],
|
|
4630
|
+
reasons: [...new Set(missing)].map((name) => `missing_variable:${name}`)
|
|
4631
|
+
};
|
|
4632
|
+
if (!signals.operationPath)
|
|
4633
|
+
return {
|
|
4634
|
+
status: "unresolved",
|
|
4635
|
+
candidates: [],
|
|
4636
|
+
reasons: ["missing_operation_path"]
|
|
4637
|
+
};
|
|
4638
|
+
const allCandidates = rows(db, signals.operationPath, workspaceId).map((c) => ({
|
|
4639
|
+
...c,
|
|
4640
|
+
score: 0.2,
|
|
4641
|
+
reasons: ["operation_path_match"]
|
|
4642
|
+
}));
|
|
4643
|
+
let candidates2 = allCandidates.filter((c) => matchesLocalRepo(db, c.operationId, signals.repoId));
|
|
4644
|
+
if (candidates2.length === 0 && signals.repoId !== void 0 && signals.serviceName) {
|
|
4645
|
+
candidates2 = implementationContextCandidates(db, allCandidates, signals.repoId, signals.serviceName);
|
|
4646
|
+
if (candidates2.length === 0)
|
|
4647
|
+
return {
|
|
4648
|
+
status: "unresolved",
|
|
4649
|
+
candidates: allCandidates.filter((c) => serviceMatches(c, signals.serviceName)),
|
|
4650
|
+
reasons: allCandidates.some((c) => serviceMatches(c, signals.serviceName)) ? ["local_service_candidate_without_caller_ownership"] : ["no_operation_candidates"]
|
|
4651
|
+
};
|
|
4652
|
+
}
|
|
4653
|
+
if (candidates2.length === 0)
|
|
4654
|
+
return {
|
|
4655
|
+
status: "unresolved",
|
|
4656
|
+
candidates: [],
|
|
4657
|
+
reasons: ["no_operation_candidates"]
|
|
4658
|
+
};
|
|
4659
|
+
const hasStrongSignal = Boolean(
|
|
4660
|
+
signals.servicePath || signals.serviceName || signals.alias || signals.destination || signals.hasExplicitOverride
|
|
4661
|
+
);
|
|
4662
|
+
for (const c of candidates2) {
|
|
4663
|
+
if (signals.servicePath && c.servicePath === signals.servicePath) {
|
|
4664
|
+
c.score += 0.75;
|
|
4665
|
+
c.reasons.push("exact_service_path");
|
|
4666
|
+
}
|
|
4667
|
+
if (signals.servicePath && c.servicePath !== signals.servicePath) {
|
|
4668
|
+
c.score -= 0.1;
|
|
4669
|
+
c.reasons.push("service_path_mismatch");
|
|
4670
|
+
}
|
|
4671
|
+
if (signals.serviceName) {
|
|
4672
|
+
const simple = signals.serviceName.split(".").at(-1) ?? signals.serviceName;
|
|
4673
|
+
if (c.qualifiedName === signals.serviceName) {
|
|
4674
|
+
c.score += 0.8;
|
|
4675
|
+
c.reasons.push("exact_local_qualified_service_name");
|
|
4676
|
+
} else if (c.serviceName === signals.serviceName || c.serviceName === simple) {
|
|
4677
|
+
c.score += 0.75;
|
|
4678
|
+
c.reasons.push("exact_local_simple_service_name");
|
|
4679
|
+
} else if (c.servicePath === signals.serviceName || c.servicePath === `/${signals.serviceName}` || c.servicePath === `/${simple}`) {
|
|
4680
|
+
c.score += 0.7;
|
|
4681
|
+
c.reasons.push("exact_local_service_path");
|
|
4682
|
+
} else if (c.servicePath.endsWith(`/${simple}`)) {
|
|
4683
|
+
c.score += candidates2.filter((candidate) => candidate.servicePath.endsWith(`/${simple}`)).length === 1 ? 0.65 : 0.2;
|
|
4684
|
+
c.reasons.push("suffix_local_service_path");
|
|
4685
|
+
} else c.reasons.push("local_service_name_mismatch");
|
|
4686
|
+
}
|
|
4687
|
+
if (signals.hasExplicitOverride) {
|
|
4688
|
+
c.score += 0.2;
|
|
4689
|
+
c.reasons.push(signals.repoId !== void 0 ? "explicit_local_service_call" : "explicit_dynamic_override");
|
|
4690
|
+
}
|
|
4691
|
+
if (signals.repoId !== void 0 && candidates2.length === 1 && signals.serviceName && c.reasons.includes("local_service_name_mismatch") && operationMatches(c, signals.operationPath)) {
|
|
4692
|
+
c.score = Math.max(c.score, 0.9);
|
|
4693
|
+
c.reasons.push("same_repo_unique_operation_path_with_lookup_mismatch");
|
|
4694
|
+
}
|
|
4695
|
+
}
|
|
4696
|
+
for (const c of candidates2) c.score = Math.max(0, Math.min(1, c.score));
|
|
4697
|
+
candidates2.sort(
|
|
4698
|
+
(a, b) => b.score - a.score || a.repoName.localeCompare(b.repoName)
|
|
4699
|
+
);
|
|
4700
|
+
const best = candidates2[0];
|
|
4701
|
+
const second = candidates2[1];
|
|
4702
|
+
if (signals.isDynamic && !signals.hasExplicitOverride && !signals.servicePath)
|
|
4703
|
+
return {
|
|
4704
|
+
status: "dynamic",
|
|
4705
|
+
candidates: candidates2,
|
|
4706
|
+
reasons: ["dynamic_target_without_override"]
|
|
4707
|
+
};
|
|
4708
|
+
if (!hasStrongSignal)
|
|
4709
|
+
return {
|
|
4710
|
+
status: candidates2.length > 1 ? "ambiguous" : "unresolved",
|
|
4711
|
+
candidates: candidates2,
|
|
4712
|
+
reasons: ["operation_path_only_has_no_strong_target_signal"]
|
|
4713
|
+
};
|
|
4714
|
+
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))
|
|
4715
|
+
return {
|
|
4716
|
+
status: "resolved",
|
|
4717
|
+
target: best,
|
|
4718
|
+
candidates: candidates2,
|
|
4719
|
+
reasons: best.reasons
|
|
4720
|
+
};
|
|
4721
|
+
return {
|
|
4722
|
+
status: candidates2.length > 1 ? "ambiguous" : "unresolved",
|
|
4723
|
+
candidates: candidates2,
|
|
4724
|
+
reasons: ["candidate_score_below_resolution_threshold"]
|
|
4725
|
+
};
|
|
4726
|
+
}
|
|
4727
|
+
function serviceMatches(candidate, serviceName) {
|
|
4728
|
+
if (!serviceName) return false;
|
|
4729
|
+
const simple = serviceName.split(".").at(-1) ?? serviceName;
|
|
4730
|
+
return candidate.qualifiedName === serviceName || candidate.serviceName === serviceName || candidate.serviceName === simple || candidate.servicePath === serviceName || candidate.servicePath === `/${serviceName}` || candidate.servicePath === `/${simple}` || candidate.servicePath.endsWith(`/${simple}`);
|
|
4731
|
+
}
|
|
4732
|
+
function implementationContextCandidates(db, candidates2, callerRepoId, serviceName) {
|
|
4733
|
+
const matching = candidates2.filter((candidate) => serviceMatches(candidate, serviceName));
|
|
4734
|
+
const owned = matching.map((candidate) => ownershipReason(db, candidate, callerRepoId)).filter((item) => Boolean(item));
|
|
4735
|
+
if (owned.length === 0) return [];
|
|
4736
|
+
const direct = owned.filter((item) => item.reason !== "caller_depends_on_model_package");
|
|
4737
|
+
const chosen = direct.length > 0 ? direct : owned.length === 1 ? owned : [];
|
|
4738
|
+
return chosen.map((item) => ({ ...item.candidate, score: 0.95, reasons: [...item.candidate.reasons, "implementation_context_caller_ownership", item.reason] }));
|
|
4739
|
+
}
|
|
4740
|
+
function ownershipReason(db, candidate, callerRepoId) {
|
|
4741
|
+
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));
|
|
4742
|
+
if (edge?.status === "resolved") {
|
|
4743
|
+
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);
|
|
4744
|
+
if (row?.repoId === callerRepoId) return { candidate, reason: "resolved_implementation_handler_repo_matches_caller" };
|
|
4745
|
+
}
|
|
4746
|
+
if (edge?.evidence_json) {
|
|
4747
|
+
const stored = parsedRecord(edge.evidence_json);
|
|
4748
|
+
const evidence = canonicalImplementationEvidence(db, candidate.operationId) ?? stored;
|
|
4749
|
+
const hit = implementationEvidenceCandidates(evidence).find((item) => item.accepted && (item.handlerRepoId === callerRepoId || item.applicationRepoId === callerRepoId));
|
|
4750
|
+
if (hit) return { candidate, reason: edge.status === "ambiguous" ? "ambiguous_implementation_candidate_repo_matches_caller" : "registration_package_matches_caller" };
|
|
4751
|
+
}
|
|
4752
|
+
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));
|
|
4753
|
+
if (dep) return { candidate, reason: "caller_depends_on_model_package" };
|
|
4754
|
+
return void 0;
|
|
4755
|
+
}
|
|
4756
|
+
function implementationEvidenceCandidates(evidence) {
|
|
4757
|
+
const candidates2 = evidence.candidates;
|
|
4758
|
+
if (!Array.isArray(candidates2)) return [];
|
|
4759
|
+
return candidates2.flatMap((candidate) => {
|
|
4760
|
+
if (!isRecord2(candidate)) return [];
|
|
4761
|
+
const row = candidate;
|
|
4762
|
+
const handler = recordValue(row.handlerPackage);
|
|
4763
|
+
const application = recordValue(row.applicationPackage);
|
|
4764
|
+
return [{
|
|
4765
|
+
accepted: row.accepted === true,
|
|
4766
|
+
handlerRepoId: numberValue3(handler.id),
|
|
4767
|
+
applicationRepoId: numberValue3(application.id)
|
|
4768
|
+
}];
|
|
4769
|
+
});
|
|
4770
|
+
}
|
|
4771
|
+
function recordValue(value) {
|
|
4772
|
+
return isRecord2(value) ? value : {};
|
|
4773
|
+
}
|
|
4774
|
+
function parsedRecord(value) {
|
|
4775
|
+
try {
|
|
4776
|
+
const parsed = JSON.parse(value);
|
|
4777
|
+
return recordValue(parsed);
|
|
4778
|
+
} catch {
|
|
4779
|
+
return {};
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4782
|
+
function isRecord2(value) {
|
|
4783
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
4784
|
+
}
|
|
4785
|
+
function numberValue3(value) {
|
|
4786
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
4787
|
+
}
|
|
4788
|
+
function matchesLocalRepo(db, operationId, repoId) {
|
|
4789
|
+
if (repoId === void 0) return true;
|
|
4790
|
+
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);
|
|
4791
|
+
return row?.repoId === repoId;
|
|
4792
|
+
}
|
|
4793
|
+
|
|
4794
|
+
// src/linker/helper-package-linker.ts
|
|
4795
|
+
function normalizeName(value) {
|
|
4796
|
+
return value.toLowerCase().replace(/^@[^/]+\//, "").replace(/[^a-z0-9]+/g, "");
|
|
4797
|
+
}
|
|
4798
|
+
function candidatesForDependency(repos, dep, sourceId) {
|
|
4799
|
+
const exact = repos.filter((repo) => repo.id !== sourceId && repo.package_name === dep);
|
|
4800
|
+
if (exact.length > 0) return { candidates: exact, strategy: "exact_package_name" };
|
|
4801
|
+
const normalized = normalizeName(dep);
|
|
4802
|
+
return { candidates: repos.filter((repo) => repo.id !== sourceId && normalizeName(repo.name) === normalized), strategy: "normalized_directory" };
|
|
4803
|
+
}
|
|
4804
|
+
function linkHelperPackages(db, workspaceId, generation) {
|
|
4805
|
+
const repos = db.prepare("SELECT id,name,package_name,dependencies_json FROM repositories WHERE workspace_id=?").all(workspaceId);
|
|
4806
|
+
const summary = { edgeCount: 0, resolvedCount: 0, ambiguousCount: 0 };
|
|
4807
|
+
for (const repo of repos) {
|
|
4808
|
+
const deps = JSON.parse(repo.dependencies_json);
|
|
4809
|
+
for (const dep of Object.keys(deps)) {
|
|
3847
4810
|
const result = candidatesForDependency(repos, dep, repo.id);
|
|
3848
4811
|
if (result.candidates.length === 0) continue;
|
|
3849
4812
|
const status = result.candidates.length === 1 ? "resolved" : "ambiguous";
|
|
3850
4813
|
const helper = status === "resolved" ? result.candidates[0] : void 0;
|
|
4814
|
+
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);
|
|
3851
4815
|
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
4816
|
workspaceId,
|
|
3853
4817
|
"REPO_IMPORTS_HELPER_PACKAGE",
|
|
@@ -3855,9 +4819,20 @@ function linkHelperPackages(db, workspaceId, generation) {
|
|
|
3855
4819
|
"repo",
|
|
3856
4820
|
String(repo.id),
|
|
3857
4821
|
helper ? "repo" : "repo_candidates",
|
|
3858
|
-
helper ? String(helper.id) :
|
|
4822
|
+
helper ? String(helper.id) : projection.items.map((candidate) => candidate.id).join(","),
|
|
3859
4823
|
helper ? 1 : 0.5,
|
|
3860
|
-
JSON.stringify({
|
|
4824
|
+
JSON.stringify({
|
|
4825
|
+
dependency: dep,
|
|
4826
|
+
candidates: projection.items.map((candidate) => ({
|
|
4827
|
+
id: candidate.id,
|
|
4828
|
+
name: candidate.name,
|
|
4829
|
+
packageName: candidate.package_name
|
|
4830
|
+
})),
|
|
4831
|
+
candidateCount: projection.totalCount,
|
|
4832
|
+
shownCandidateCount: projection.shownCount,
|
|
4833
|
+
omittedCandidateCount: projection.omittedCount,
|
|
4834
|
+
match: result.strategy
|
|
4835
|
+
}),
|
|
3861
4836
|
0,
|
|
3862
4837
|
helper ? null : "Ambiguous dependency package candidates",
|
|
3863
4838
|
generation
|
|
@@ -3867,7 +4842,145 @@ function linkHelperPackages(db, workspaceId, generation) {
|
|
|
3867
4842
|
else summary.ambiguousCount += 1;
|
|
3868
4843
|
}
|
|
3869
4844
|
}
|
|
3870
|
-
return summary;
|
|
4845
|
+
return summary;
|
|
4846
|
+
}
|
|
4847
|
+
|
|
4848
|
+
// src/linker/002-call-evidence.ts
|
|
4849
|
+
function linkedCallEvidence(call, resolution, servicePath, operationPath, destination, normalized, intent) {
|
|
4850
|
+
const candidates2 = boundedCallCandidates(resolution.candidates);
|
|
4851
|
+
return {
|
|
4852
|
+
...callLocationEvidence(call),
|
|
4853
|
+
...selectedBindingEvidence(call),
|
|
4854
|
+
...routingEvidence(call, servicePath, operationPath, destination, normalized, intent),
|
|
4855
|
+
...candidateEvidence2(candidates2, resolution),
|
|
4856
|
+
outboundEvidence: boundCandidateLikeEvidence(objectJson2(call.evidence_json) ?? {}),
|
|
4857
|
+
analysisCompleteness: call.unresolved_reason ? "partial" : "complete",
|
|
4858
|
+
parserWarning: call.unresolved_reason ? { code: "parser_warning", message: call.unresolved_reason } : void 0
|
|
4859
|
+
};
|
|
4860
|
+
}
|
|
4861
|
+
function ambiguousPathCandidates(pathAnalysis) {
|
|
4862
|
+
const values = Array.isArray(pathAnalysis.candidateRawPaths) ? pathAnalysis.candidateRawPaths.filter((value) => typeof value === "string") : [];
|
|
4863
|
+
return projectBounded(values, (left, right) => left.localeCompare(right));
|
|
4864
|
+
}
|
|
4865
|
+
function objectJson2(value) {
|
|
4866
|
+
const parsed = parseJson(value);
|
|
4867
|
+
return isRecord3(parsed) ? parsed : void 0;
|
|
4868
|
+
}
|
|
4869
|
+
function objectValue(value) {
|
|
4870
|
+
return isRecord3(value) ? value : void 0;
|
|
4871
|
+
}
|
|
4872
|
+
function callLocationEvidence(call) {
|
|
4873
|
+
return {
|
|
4874
|
+
sourceFile: call.source_file,
|
|
4875
|
+
sourceLine: call.source_line,
|
|
4876
|
+
file: call.source_file,
|
|
4877
|
+
line: call.source_line,
|
|
4878
|
+
callId: call.id,
|
|
4879
|
+
repo: call.repoName
|
|
4880
|
+
};
|
|
4881
|
+
}
|
|
4882
|
+
function selectedBindingEvidence(call) {
|
|
4883
|
+
if (!call.selectedBindingId) return {};
|
|
4884
|
+
return {
|
|
4885
|
+
selectedBindingId: call.selectedBindingId,
|
|
4886
|
+
selectedBinding: {
|
|
4887
|
+
bindingId: call.selectedBindingId,
|
|
4888
|
+
alias: call.alias,
|
|
4889
|
+
aliasExpr: call.aliasExpr,
|
|
4890
|
+
destinationExpr: call.destinationExpr,
|
|
4891
|
+
servicePathExpr: call.servicePathExpr,
|
|
4892
|
+
sourceFile: call.bindingSourceFile,
|
|
4893
|
+
sourceLine: call.bindingSourceLine,
|
|
4894
|
+
helperChain: parseJson(call.helperChainJson)
|
|
4895
|
+
}
|
|
4896
|
+
};
|
|
4897
|
+
}
|
|
4898
|
+
function routingEvidence(call, servicePath, operationPath, destination, normalized, intent) {
|
|
4899
|
+
const routingPlaceholderKeys = placeholderKeys([
|
|
4900
|
+
servicePath,
|
|
4901
|
+
destination,
|
|
4902
|
+
stringValue5(call.aliasExpr),
|
|
4903
|
+
stringValue5(call.alias)
|
|
4904
|
+
]);
|
|
4905
|
+
return {
|
|
4906
|
+
serviceAlias: call.alias,
|
|
4907
|
+
serviceAliasExpr: call.aliasExpr,
|
|
4908
|
+
destination,
|
|
4909
|
+
servicePath,
|
|
4910
|
+
operationPath,
|
|
4911
|
+
rawOperationPath: normalized?.wasInvocation ? normalized.rawOperationPath : intent?.rawPath,
|
|
4912
|
+
normalizedOperationPath: normalized?.wasInvocation ? normalized.normalizedOperationPath : void 0,
|
|
4913
|
+
invocationArguments: normalized?.wasInvocation ? normalized.invocationArguments : void 0,
|
|
4914
|
+
invocationArgumentPlaceholderKeys: normalized?.invocationArgumentPlaceholderKeys.length ? normalized.invocationArgumentPlaceholderKeys : void 0,
|
|
4915
|
+
routingPlaceholderKeys: routingPlaceholderKeys.length ? routingPlaceholderKeys : void 0,
|
|
4916
|
+
odataOperationNormalizationReason: normalized?.normalizationReason,
|
|
4917
|
+
odataOperationNormalizationRejectedReason: normalized?.normalizationRejectedReason,
|
|
4918
|
+
localServiceName: call.local_service_name,
|
|
4919
|
+
localServiceLookup: call.local_service_lookup,
|
|
4920
|
+
aliasChain: parseJson(call.alias_chain_json),
|
|
4921
|
+
transport: call.call_type === "local_service_call" ? "local" : void 0,
|
|
4922
|
+
helperChain: parseJson(call.helperChainJson),
|
|
4923
|
+
odataPathIntent: intent,
|
|
4924
|
+
queryStringPresent: intent?.hasQueryString || void 0,
|
|
4925
|
+
queryPlaceholderKeys: intent?.placeholderKeys.length ? intent.placeholderKeys : void 0,
|
|
4926
|
+
bindingHasDynamicExpression: Boolean(Number(call.isDynamic ?? 0)) || void 0
|
|
4927
|
+
};
|
|
4928
|
+
}
|
|
4929
|
+
function candidateEvidence2(candidates2, resolution) {
|
|
4930
|
+
return {
|
|
4931
|
+
targetRepo: resolution.target?.repoName,
|
|
4932
|
+
targetServicePath: resolution.target?.servicePath,
|
|
4933
|
+
targetOperationPath: resolution.target?.operationPath,
|
|
4934
|
+
targetOperation: resolution.target?.operationName,
|
|
4935
|
+
candidates: candidates2.items,
|
|
4936
|
+
candidateScores: compactCandidateScores(candidates2.items),
|
|
4937
|
+
candidateCount: candidates2.totalCount,
|
|
4938
|
+
shownCandidateCount: candidates2.shownCount,
|
|
4939
|
+
omittedCandidateCount: candidates2.omittedCount,
|
|
4940
|
+
candidateScoreCount: candidates2.totalCount,
|
|
4941
|
+
shownCandidateScoreCount: candidates2.shownCount,
|
|
4942
|
+
omittedCandidateScoreCount: candidates2.omittedCount,
|
|
4943
|
+
resolutionStatus: resolution.status,
|
|
4944
|
+
resolutionReasons: resolution.reasons
|
|
4945
|
+
};
|
|
4946
|
+
}
|
|
4947
|
+
function boundedCallCandidates(candidates2) {
|
|
4948
|
+
const rows2 = candidates2.flatMap((candidate) => {
|
|
4949
|
+
const row = objectValue(candidate);
|
|
4950
|
+
return row ? [row] : [];
|
|
4951
|
+
});
|
|
4952
|
+
return projectBounded(rows2, compareCallCandidate);
|
|
4953
|
+
}
|
|
4954
|
+
function compareCallCandidate(left, right) {
|
|
4955
|
+
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);
|
|
4956
|
+
}
|
|
4957
|
+
function compactCandidateScores(candidates2) {
|
|
4958
|
+
return candidates2.map((candidate) => ({
|
|
4959
|
+
repo: candidate.repoName,
|
|
4960
|
+
servicePath: candidate.servicePath,
|
|
4961
|
+
operationPath: candidate.operationPath,
|
|
4962
|
+
score: candidate.score,
|
|
4963
|
+
reasons: Array.isArray(candidate.reasons) ? candidate.reasons.filter((reason) => typeof reason === "string") : ["operation_path_match"]
|
|
4964
|
+
}));
|
|
4965
|
+
}
|
|
4966
|
+
function placeholderKeys(values) {
|
|
4967
|
+
const keys = values.flatMap((value) => [...(value ?? "").matchAll(/\$\{([^}]*)\}/g)].map((match) => (match[1] ?? "").trim()).filter(Boolean));
|
|
4968
|
+
return [...new Set(keys)].sort();
|
|
4969
|
+
}
|
|
4970
|
+
function parseJson(value) {
|
|
4971
|
+
if (!value) return void 0;
|
|
4972
|
+
try {
|
|
4973
|
+
const parsed = JSON.parse(String(value));
|
|
4974
|
+
return parsed;
|
|
4975
|
+
} catch {
|
|
4976
|
+
return void 0;
|
|
4977
|
+
}
|
|
4978
|
+
}
|
|
4979
|
+
function stringValue5(value) {
|
|
4980
|
+
return typeof value === "string" ? value : void 0;
|
|
4981
|
+
}
|
|
4982
|
+
function isRecord3(value) {
|
|
4983
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
3871
4984
|
}
|
|
3872
4985
|
|
|
3873
4986
|
// src/linker/cross-repo-linker.ts
|
|
@@ -3895,7 +5008,7 @@ function linkCalls(db, workspaceId, vars, generation) {
|
|
|
3895
5008
|
let ambiguousCount = 0;
|
|
3896
5009
|
let dynamicCount = 0;
|
|
3897
5010
|
let terminalCount = 0;
|
|
3898
|
-
const calls = db.prepare(`SELECT c.*,r.name repoName,b.alias,b.alias_expr aliasExpr,b.destination_expr destinationExpr,b.service_path_expr servicePathExpr,b.is_dynamic isDynamic,b.placeholders_json placeholdersJson,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);
|
|
5011
|
+
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);
|
|
3899
5012
|
for (const call of calls) {
|
|
3900
5013
|
const result = insertCallEdge(db, workspaceId, call, vars, generation);
|
|
3901
5014
|
edgeCount += 1;
|
|
@@ -3928,7 +5041,15 @@ function insertCallEdge(db, workspaceId, call, vars, generation) {
|
|
|
3928
5041
|
const isOperationCall = operationLikeRemoteEntity || (callType === "remote_action" || callType === "local_service_call" || callType === "remote_query" && Boolean(op));
|
|
3929
5042
|
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
5043
|
const evidence = {
|
|
3931
|
-
...
|
|
5044
|
+
...linkedCallEvidence(
|
|
5045
|
+
call,
|
|
5046
|
+
resolution,
|
|
5047
|
+
servicePath,
|
|
5048
|
+
op,
|
|
5049
|
+
destination ? applyVariables(destination, vars) : void 0,
|
|
5050
|
+
normalized,
|
|
5051
|
+
intent
|
|
5052
|
+
),
|
|
3932
5053
|
indexedOperationCandidateCount,
|
|
3933
5054
|
parserCallType: callType,
|
|
3934
5055
|
entityOperationPrecedence: operationPrecedence(
|
|
@@ -3938,9 +5059,9 @@ function insertCallEdge(db, workspaceId, call, vars, generation) {
|
|
|
3938
5059
|
Boolean(resolution.target)
|
|
3939
5060
|
)
|
|
3940
5061
|
};
|
|
3941
|
-
const pathAnalysis = objectValue(
|
|
5062
|
+
const pathAnalysis = objectValue(objectJson2(call.evidence_json)?.pathAnalysis);
|
|
3942
5063
|
if (callType === "remote_action" && pathAnalysis?.status === "ambiguous") {
|
|
3943
|
-
const candidatePaths =
|
|
5064
|
+
const candidatePaths = ambiguousPathCandidates(pathAnalysis);
|
|
3944
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,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(
|
|
3945
5066
|
workspaceId,
|
|
3946
5067
|
"UNRESOLVED_EDGE",
|
|
@@ -3948,9 +5069,14 @@ function insertCallEdge(db, workspaceId, call, vars, generation) {
|
|
|
3948
5069
|
"call",
|
|
3949
5070
|
String(call.id),
|
|
3950
5071
|
"operation_candidates",
|
|
3951
|
-
candidatePaths.join(","),
|
|
5072
|
+
candidatePaths.items.join(","),
|
|
3952
5073
|
Number(call.confidence ?? 0.5),
|
|
3953
|
-
JSON.stringify(
|
|
5074
|
+
JSON.stringify({
|
|
5075
|
+
...evidence,
|
|
5076
|
+
ambiguousOperationPathCandidateCount: candidatePaths.totalCount,
|
|
5077
|
+
shownAmbiguousOperationPathCandidateCount: candidatePaths.shownCount,
|
|
5078
|
+
omittedAmbiguousOperationPathCandidateCount: candidatePaths.omittedCount
|
|
5079
|
+
}),
|
|
3954
5080
|
0,
|
|
3955
5081
|
"Ambiguous operation path candidates require explicit disambiguation",
|
|
3956
5082
|
generation
|
|
@@ -4023,433 +5149,20 @@ function operationPrecedence(callType, intent, indexedOperationCandidateCount, r
|
|
|
4023
5149
|
rejectionReason: indexedOperationCandidateCount > 0 ? "entity_shape_has_precedence_without_resolved_operation_context" : "entity_shape_has_no_indexed_operation_evidence",
|
|
4024
5150
|
indexedOperationCandidateCount
|
|
4025
5151
|
};
|
|
4026
|
-
}
|
|
4027
|
-
return {
|
|
4028
|
-
decision: "unresolved",
|
|
4029
|
-
rejectionReason: "path_has_no_safe_entity_or_operation_precedence",
|
|
4030
|
-
indexedOperationCandidateCount
|
|
4031
|
-
};
|
|
4032
|
-
}
|
|
4033
|
-
function unresolvedOperationReason(resolution) {
|
|
4034
|
-
if (resolution.status === "dynamic") return `Dynamic target requires runtime variable overrides: ${(resolution.reasons.length ? resolution.reasons : ["missing runtime variables"]).join(", ")}`;
|
|
4035
|
-
if (resolution.candidates.length === 0) return "No indexed target operation matched";
|
|
4036
|
-
if (resolution.reasons.includes("operation_path_only_has_no_strong_target_signal")) return "Operation candidates found but no strong service signal is available";
|
|
4037
|
-
if (resolution.reasons.includes("candidate_score_below_resolution_threshold")) return "Operation candidates found but resolution score is below threshold";
|
|
4038
|
-
if (resolution.status === "ambiguous") return "Ambiguous operation candidates require a strong service signal";
|
|
4039
|
-
return "Operation candidates found but resolution could not select a target";
|
|
4040
|
-
}
|
|
4041
|
-
function parseJson(value) {
|
|
4042
|
-
if (!value) return void 0;
|
|
4043
|
-
try {
|
|
4044
|
-
return JSON.parse(String(value));
|
|
4045
|
-
} catch {
|
|
4046
|
-
return void 0;
|
|
4047
|
-
}
|
|
4048
|
-
}
|
|
4049
|
-
function objectJson(value) {
|
|
4050
|
-
const parsed = parseJson(value);
|
|
4051
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
4052
|
-
}
|
|
4053
|
-
function callEvidence(call, resolution, servicePath, op, destination, normalized, odataPathIntent) {
|
|
4054
|
-
const bindingHasDynamicExpression = Boolean(Number(call.isDynamic ?? 0));
|
|
4055
|
-
const routingPlaceholderKeys = placeholderKeys([servicePath, destination, stringValue3(call.aliasExpr), stringValue3(call.alias)]);
|
|
4056
|
-
return { sourceFile: call.source_file, sourceLine: call.source_line, file: call.source_file, line: call.source_line, callId: call.id, repo: call.repoName, serviceAlias: call.alias, serviceAliasExpr: call.aliasExpr, destination, servicePath, operationPath: op, rawOperationPath: normalized?.wasInvocation ? normalized.rawOperationPath : odataPathIntent?.rawPath, normalizedOperationPath: normalized?.wasInvocation ? normalized.normalizedOperationPath : void 0, invocationArguments: normalized?.wasInvocation ? normalized.invocationArguments : void 0, invocationArgumentPlaceholderKeys: normalized?.invocationArgumentPlaceholderKeys.length ? normalized.invocationArgumentPlaceholderKeys : void 0, routingPlaceholderKeys: routingPlaceholderKeys.length ? routingPlaceholderKeys : void 0, odataOperationNormalizationReason: normalized?.normalizationReason, odataOperationNormalizationRejectedReason: normalized?.normalizationRejectedReason, localServiceName: call.local_service_name, localServiceLookup: call.local_service_lookup, aliasChain: parseJson(call.alias_chain_json), transport: call.call_type === "local_service_call" ? "local" : void 0, targetRepo: resolution.target?.repoName, targetServicePath: resolution.target?.servicePath, targetOperationPath: resolution.target?.operationPath, targetOperation: resolution.target?.operationName, helperChain: parseJson(call.helperChainJson), candidates: resolution.candidates, candidateScores: compactCandidateScores(resolution.candidates), candidateCount: resolution.candidates.length, resolutionStatus: resolution.status, resolutionReasons: resolution.reasons, odataPathIntent, queryStringPresent: odataPathIntent?.hasQueryString || void 0, queryPlaceholderKeys: odataPathIntent?.placeholderKeys.length ? odataPathIntent.placeholderKeys : void 0, bindingHasDynamicExpression: bindingHasDynamicExpression || void 0, outboundEvidence: objectJson(call.evidence_json), analysisCompleteness: call.unresolved_reason ? "partial" : "complete", parserWarning: call.unresolved_reason ? { code: "parser_warning", message: call.unresolved_reason } : void 0 };
|
|
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;
|
|
4080
|
-
}
|
|
4081
|
-
function linkImplementations(db, workspaceId, generation) {
|
|
4082
|
-
const operations = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,o.operation_name operationName,o.provenance provenance,o.base_operation_id baseOperationId,s.service_path servicePath,s.repo_id modelRepoId,r.name modelRepo,r.package_name modelPackage,r.kind modelKind 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=?`).all(workspaceId);
|
|
4083
|
-
let edgeCount = 0;
|
|
4084
|
-
let resolvedCount = 0;
|
|
4085
|
-
let ambiguousCount = 0;
|
|
4086
|
-
let unresolvedCount = 0;
|
|
4087
|
-
for (const operation of operations) {
|
|
4088
|
-
const implementationContext = implementationContextForOperation(db, operation);
|
|
4089
|
-
const candidates = rankedImplementationCandidates(db, workspaceId, implementationContext);
|
|
4090
|
-
if (candidates.length === 0) continue;
|
|
4091
|
-
const accepted = candidates.filter((candidate) => candidate.accepted);
|
|
4092
|
-
const topScore = accepted[0]?.score ?? 0;
|
|
4093
|
-
const winners = accepted.filter((candidate) => candidate.score === topScore);
|
|
4094
|
-
const duplicateFamilies = duplicatePackageFamilies(accepted);
|
|
4095
|
-
const duplicatePackageAmbiguous = duplicateFamilies.length > 0 && !accepted.some(hasDirectOwnershipEvidence);
|
|
4096
|
-
const selected = duplicatePackageAmbiguous ? accepted : winners;
|
|
4097
|
-
const unique3 = !duplicatePackageAmbiguous && winners.length === 1 ? winners[0] : void 0;
|
|
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 };
|
|
4124
|
-
}
|
|
4125
|
-
function implementationContextForOperation(db, operation) {
|
|
4126
|
-
if (operation.provenance !== "inherited" || !operation.baseOperationId) return operation;
|
|
4127
|
-
const base = db.prepare(`SELECT o.id operationId,o.operation_path operationPath,o.operation_name operationName,o.provenance provenance,o.base_operation_id baseOperationId,s.service_path servicePath,s.repo_id modelRepoId,r.name modelRepo,r.package_name modelPackage,r.kind modelKind FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE o.id=?`).get(operation.baseOperationId);
|
|
4128
|
-
if (!base) return operation;
|
|
4129
|
-
return { ...base, effectiveOperationId: operation.operationId, effectiveServicePath: operation.servicePath, effectiveOperationPath: operation.operationPath };
|
|
4130
|
-
}
|
|
4131
|
-
function rankedImplementationCandidates(db, workspaceId, operation) {
|
|
4132
|
-
const rows2 = implementationCandidates(db, workspaceId, operation);
|
|
4133
|
-
const invalid = rows2.filter((row) => !validRegistrationPair(row));
|
|
4134
|
-
recordRegistrationInvariantDiagnostics(db, invalid);
|
|
4135
|
-
return deduplicateCandidates(
|
|
4136
|
-
rows2.filter(validRegistrationPair).map((row) => scoreImplementationCandidate(row, operation))
|
|
4137
|
-
).sort((a, b) => b.score - a.score || String(a.className).localeCompare(String(b.className)) || a.methodId - b.methodId);
|
|
4138
|
-
}
|
|
4139
|
-
function validRegistrationPair(row) {
|
|
4140
|
-
if (row.registrationHandlerClassId === null || row.registrationHandlerClassId === void 0)
|
|
4141
|
-
return registrationPairingStrategy(row) !== "unproven";
|
|
4142
|
-
return Number(row.registrationHandlerClassId) === Number(row.classId);
|
|
4143
|
-
}
|
|
4144
|
-
function registrationPairingStrategy(row) {
|
|
4145
|
-
if (row.registrationHandlerClassId !== null && row.registrationHandlerClassId !== void 0)
|
|
4146
|
-
return "exact_handler_class_id";
|
|
4147
|
-
if (Number(row.applicationRepoId) === Number(row.handlerRepoId))
|
|
4148
|
-
return "same_repository_class_name_fallback";
|
|
4149
|
-
const source = stringValue3(row.importSource);
|
|
4150
|
-
const separator = source?.lastIndexOf("#") ?? -1;
|
|
4151
|
-
if (!source || separator <= 0) return "unproven";
|
|
4152
|
-
const moduleName = source.slice(0, separator);
|
|
4153
|
-
const importedName = source.slice(separator + 1);
|
|
4154
|
-
const matchesClass = importedName === row.className || importedName === "default" && row.registrationClassName === row.className;
|
|
4155
|
-
return moduleName === row.handlerPackage && matchesClass ? "explicit_package_import" : "unproven";
|
|
4156
|
-
}
|
|
4157
|
-
function recordRegistrationInvariantDiagnostics(db, rows2) {
|
|
4158
|
-
const insert = db.prepare(`INSERT INTO diagnostics(repo_id,severity,code,message,source_file,source_line)
|
|
4159
|
-
SELECT ?,'error','handler_registration_class_mismatch',
|
|
4160
|
-
'Implementation candidate registration did not match its persisted handler class id',?,?
|
|
4161
|
-
WHERE NOT EXISTS (
|
|
4162
|
-
SELECT 1 FROM diagnostics
|
|
4163
|
-
WHERE repo_id=? AND code='handler_registration_class_mismatch'
|
|
4164
|
-
AND source_file=? AND source_line=?
|
|
4165
|
-
)`);
|
|
4166
|
-
for (const row of rows2)
|
|
4167
|
-
insert.run(
|
|
4168
|
-
row.applicationRepoId,
|
|
4169
|
-
row.registrationFile,
|
|
4170
|
-
row.registrationLine,
|
|
4171
|
-
row.applicationRepoId,
|
|
4172
|
-
row.registrationFile,
|
|
4173
|
-
row.registrationLine
|
|
4174
|
-
);
|
|
4175
|
-
}
|
|
4176
|
-
function deduplicateCandidates(rows2) {
|
|
4177
|
-
const merged = /* @__PURE__ */ new Map();
|
|
4178
|
-
for (const row of rows2) {
|
|
4179
|
-
const key = [row.methodId, row.classId, row.handlerRepoId].join(":");
|
|
4180
|
-
const registration = registrationEvidence(row);
|
|
4181
|
-
const existing = merged.get(key);
|
|
4182
|
-
if (!existing) {
|
|
4183
|
-
merged.set(key, { ...row, registrations: [registration] });
|
|
4184
|
-
continue;
|
|
4185
|
-
}
|
|
4186
|
-
existing.registrations = uniqueRegistrations([...existing.registrations ?? [], registration]);
|
|
4187
|
-
existing.score = Math.max(existing.score, row.score);
|
|
4188
|
-
existing.accepted = existing.accepted || row.accepted;
|
|
4189
|
-
existing.acceptedReasons = [.../* @__PURE__ */ new Set([...existing.acceptedReasons, ...row.acceptedReasons])];
|
|
4190
|
-
existing.rejectedReasons = [.../* @__PURE__ */ new Set([...existing.rejectedReasons, ...row.rejectedReasons])];
|
|
4191
|
-
}
|
|
4192
|
-
return [...merged.values()];
|
|
4193
|
-
}
|
|
4194
|
-
function registrationEvidence(row) {
|
|
4195
|
-
return {
|
|
4196
|
-
id: row.registrationId,
|
|
4197
|
-
handlerClassId: row.registrationHandlerClassId,
|
|
4198
|
-
file: row.registrationFile,
|
|
4199
|
-
line: row.registrationLine,
|
|
4200
|
-
kind: row.registrationKind,
|
|
4201
|
-
importSource: row.importSource,
|
|
4202
|
-
pairingStrategy: registrationPairingStrategy(row)
|
|
4203
|
-
};
|
|
4204
|
-
}
|
|
4205
|
-
function uniqueRegistrations(rows2) {
|
|
4206
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4207
|
-
return rows2.filter((row) => {
|
|
4208
|
-
const key = JSON.stringify(row);
|
|
4209
|
-
if (seen.has(key)) return false;
|
|
4210
|
-
seen.add(key);
|
|
4211
|
-
return true;
|
|
4212
|
-
});
|
|
4213
|
-
}
|
|
4214
|
-
function duplicatePackageFamilies(candidates) {
|
|
4215
|
-
const byPackage = /* @__PURE__ */ new Map();
|
|
4216
|
-
for (const candidate of candidates) {
|
|
4217
|
-
const packageName = typeof candidate.handlerPackage === "string" ? candidate.handlerPackage : void 0;
|
|
4218
|
-
if (!packageName) continue;
|
|
4219
|
-
byPackage.set(packageName, [...byPackage.get(packageName) ?? [], candidate]);
|
|
4220
|
-
}
|
|
4221
|
-
return [...byPackage.entries()].filter(([, rows2]) => new Set(rows2.map((row) => Number(row.handlerRepoId))).size > 1).map(([packageName, rows2]) => ({ reason: "duplicate_package_name_candidates", packageName, count: rows2.length, repositories: rows2.map((row) => row.handlerRepo).sort() }));
|
|
4222
|
-
}
|
|
4223
|
-
function hasDirectOwnershipEvidence(candidate) {
|
|
4224
|
-
return candidate.acceptedReasons.some((reason) => reason === "model package equals registration package" || reason === "model package equals handler package" || reason === "registration package contains exact local service path");
|
|
4225
|
-
}
|
|
4226
|
-
function implementationCandidates(db, workspaceId, operation) {
|
|
4227
|
-
const modelRepoGraphId = graphId(operation.modelRepoId);
|
|
4228
|
-
return db.prepare(`SELECT DISTINCT
|
|
4229
|
-
hm.id methodId,
|
|
4230
|
-
hm.method_name methodName,
|
|
4231
|
-
hm.decorator_value decoratorValue,
|
|
4232
|
-
hm.decorator_raw_expression decoratorRawExpression,
|
|
4233
|
-
hm.decorator_resolution_json decoratorResolutionJson,
|
|
4234
|
-
hc.id classId,
|
|
4235
|
-
hc.class_name className,
|
|
4236
|
-
hc.source_file sourceFile,
|
|
4237
|
-
hc.source_line sourceLine,
|
|
4238
|
-
hr.id registrationId,
|
|
4239
|
-
hr.handler_class_id registrationHandlerClassId,
|
|
4240
|
-
hr.class_name registrationClassName,
|
|
4241
|
-
hr.repo_id applicationRepoId,
|
|
4242
|
-
hr.registration_file registrationFile,
|
|
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,
|
|
4258
|
-
CASE WHEN appRepo.id=? THEN 1 ELSE 0 END modelIsApplicationRepo,
|
|
4259
|
-
CASE WHEN handlerRepo.id=? THEN 1 ELSE 0 END modelIsHandlerRepo,
|
|
4260
|
-
CASE WHEN appRepo.id=handlerRepo.id THEN 1 ELSE 0 END sameRepoRegistration,
|
|
4261
|
-
CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id AND localService.service_path=?) THEN 1 ELSE 0 END localServicePathMatch,
|
|
4262
|
-
CASE WHEN EXISTS (SELECT 1 FROM cds_services localService WHERE localService.repo_id=appRepo.id) THEN 1 ELSE 0 END applicationHasLocalServices,
|
|
4263
|
-
CASE WHEN EXISTS (SELECT 1 FROM handler_registrations localReg JOIN handler_classes localClass ON ((localReg.handler_class_id IS NOT NULL AND localClass.id=localReg.handler_class_id) OR (localReg.handler_class_id IS NULL AND localClass.class_name=localReg.class_name AND localClass.repo_id=localReg.repo_id)) JOIN handler_methods localMethod ON localMethod.handler_class_id=localClass.id WHERE localReg.repo_id=appRepo.id AND COALESCE(json_extract(localMethod.decorator_resolution_json,'$.handlerKind'),CASE WHEN localMethod.decorator_kind='Event' THEN 'event' WHEN localMethod.decorator_kind IN ('Action','Func','On') THEN 'operation' ELSE 'unsupported' END)='operation' AND COALESCE(json_extract(localMethod.decorator_resolution_json,'$.executable'),CASE WHEN localMethod.decorator_kind IN ('Action','Func','On') THEN 1 ELSE 0 END)=1 AND (localMethod.decorator_value=? OR localMethod.decorator_value=? OR localMethod.method_name=? OR localMethod.decorator_raw_expression LIKE ?)) THEN 1 ELSE 0 END applicationHasLocalRegistrationForOperation,
|
|
4264
|
-
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT) AND dep.to_id=?) THEN 1 ELSE 0 END appDependsOnModel,
|
|
4265
|
-
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(appRepo.id AS TEXT) AND dep.to_id=CAST(handlerRepo.id AS TEXT)) THEN 1 ELSE 0 END appDependsOnHandler,
|
|
4266
|
-
CASE WHEN EXISTS (SELECT 1 FROM graph_edges dep WHERE dep.edge_type='REPO_IMPORTS_HELPER_PACKAGE' AND dep.status='resolved' AND dep.from_kind='repo' AND dep.from_id=CAST(handlerRepo.id AS TEXT) AND dep.to_id=?) THEN 1 ELSE 0 END handlerDependsOnModel
|
|
4267
|
-
FROM handler_methods hm
|
|
4268
|
-
JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
4269
|
-
JOIN repositories handlerRepo ON handlerRepo.id=hc.repo_id
|
|
4270
|
-
JOIN handler_registrations hr ON (
|
|
4271
|
-
(hr.handler_class_id IS NOT NULL AND hr.handler_class_id=hc.id)
|
|
4272
|
-
OR (
|
|
4273
|
-
hr.handler_class_id IS NULL
|
|
4274
|
-
AND (
|
|
4275
|
-
(hr.class_name=hc.class_name AND hr.repo_id=hc.repo_id)
|
|
4276
|
-
OR (
|
|
4277
|
-
instr(hr.import_source,'#')>1
|
|
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
|
-
)
|
|
4293
|
-
JOIN repositories appRepo ON appRepo.id=hr.repo_id
|
|
4294
|
-
WHERE appRepo.workspace_id=?
|
|
4295
|
-
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
|
|
4296
|
-
CASE WHEN hm.decorator_kind='Event' THEN 'event'
|
|
4297
|
-
WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
|
|
4298
|
-
ELSE 'unsupported' END)='operation'
|
|
4299
|
-
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
|
|
4300
|
-
CASE WHEN hm.decorator_kind IN ('Action','Func','On')
|
|
4301
|
-
THEN 1 ELSE 0 END)=1
|
|
4302
|
-
AND (hm.decorator_value=? OR hm.decorator_value=? OR hm.method_name=? OR hm.decorator_raw_expression LIKE ?)`).all(
|
|
4303
|
-
operation.modelRepoId,
|
|
4304
|
-
operation.modelRepo,
|
|
4305
|
-
operation.modelPackage,
|
|
4306
|
-
operation.modelKind,
|
|
4307
|
-
operation.servicePath,
|
|
4308
|
-
operation.operationPath,
|
|
4309
|
-
operation.operationName,
|
|
4310
|
-
operation.modelRepoId,
|
|
4311
|
-
operation.modelRepoId,
|
|
4312
|
-
operation.servicePath,
|
|
4313
|
-
normalizedOperation(String(operation.operationPath ?? "")),
|
|
4314
|
-
operation.operationName,
|
|
4315
|
-
operation.operationName,
|
|
4316
|
-
`%${upperFirst(normalizedOperation(String(operation.operationPath ?? operation.operationName ?? "")))}%`,
|
|
4317
|
-
modelRepoGraphId,
|
|
4318
|
-
modelRepoGraphId,
|
|
4319
|
-
workspaceId,
|
|
4320
|
-
normalizedOperation(String(operation.operationPath ?? "")),
|
|
4321
|
-
operation.operationName,
|
|
4322
|
-
operation.operationName,
|
|
4323
|
-
`%${upperFirst(normalizedOperation(String(operation.operationPath ?? operation.operationName ?? "")))}%`
|
|
4324
|
-
);
|
|
4325
|
-
}
|
|
4326
|
-
function scoreImplementationCandidate(row, operation) {
|
|
4327
|
-
const acceptedReasons = [];
|
|
4328
|
-
const rejectedReasons = [];
|
|
4329
|
-
let score = 0;
|
|
4330
|
-
const modelIsApplicationRepo = flag(row.modelIsApplicationRepo);
|
|
4331
|
-
const modelIsHandlerRepo = flag(row.modelIsHandlerRepo);
|
|
4332
|
-
const localServicePathMatch = flag(row.localServicePathMatch);
|
|
4333
|
-
const applicationHasLocalServices = flag(row.applicationHasLocalServices);
|
|
4334
|
-
const appDependsOnModel = flag(row.appDependsOnModel);
|
|
4335
|
-
const applicationHasLocalRegistrationForOperation = flag(row.applicationHasLocalRegistrationForOperation);
|
|
4336
|
-
const appDependsOnHandler = flag(row.appDependsOnHandler);
|
|
4337
|
-
const handlerDependsOnModel = flag(row.handlerDependsOnModel);
|
|
4338
|
-
const importSource = typeof row.importSource === "string" && row.importSource.length > 0;
|
|
4339
|
-
const sameRepoRegistration = flag(row.sameRepoRegistration);
|
|
4340
|
-
const modelOriented = row.modelKind === "cap-db-model" || !applicationHasLocalRegistrationForOperation;
|
|
4341
|
-
const methodSignal = implementationMethodSignal(row, operation);
|
|
4342
|
-
const methodMatches = methodSignal.matches;
|
|
4343
|
-
acceptedReasons.push(...methodSignal.acceptedReasons);
|
|
4344
|
-
rejectedReasons.push(...methodSignal.rejectedReasons);
|
|
4345
|
-
const registeredAndLinked = sameRepoRegistration && importSource;
|
|
4346
|
-
const helperOwned = modelOriented && methodMatches && registeredAndLinked && sameRepoRegistration && !applicationHasLocalServices && !modelIsApplicationRepo && !modelIsHandlerRepo && !localServicePathMatch && !appDependsOnModel && !appDependsOnHandler && !handlerDependsOnModel;
|
|
4347
|
-
if (modelIsApplicationRepo) {
|
|
4348
|
-
score += 100;
|
|
4349
|
-
acceptedReasons.push("model package equals registration package");
|
|
4350
|
-
}
|
|
4351
|
-
if (modelIsHandlerRepo) {
|
|
4352
|
-
score += 100;
|
|
4353
|
-
acceptedReasons.push("model package equals handler package");
|
|
4354
|
-
}
|
|
4355
|
-
if (localServicePathMatch) {
|
|
4356
|
-
score += 80;
|
|
4357
|
-
acceptedReasons.push("registration package contains exact local service path");
|
|
4358
|
-
} else if (applicationHasLocalServices && !appDependsOnModel && !modelIsApplicationRepo) {
|
|
4359
|
-
rejectedReasons.push(`registration package has local services but none match ${String(operation.servicePath ?? "")}`);
|
|
4360
|
-
}
|
|
4361
|
-
if (appDependsOnModel) {
|
|
4362
|
-
score += 70;
|
|
4363
|
-
acceptedReasons.push("registration package depends on model package");
|
|
4364
|
-
}
|
|
4365
|
-
if (appDependsOnHandler) {
|
|
4366
|
-
score += 30;
|
|
4367
|
-
acceptedReasons.push("registration package depends on handler package");
|
|
4368
|
-
}
|
|
4369
|
-
if (handlerDependsOnModel) {
|
|
4370
|
-
score += 20;
|
|
4371
|
-
acceptedReasons.push("handler package depends on model package");
|
|
4372
|
-
}
|
|
4373
|
-
if (helperOwned) {
|
|
4374
|
-
score += 60;
|
|
4375
|
-
acceptedReasons.push("unique registered helper implementation for model-only operation");
|
|
4376
|
-
}
|
|
4377
|
-
if (importSource) {
|
|
4378
|
-
score += 10;
|
|
4379
|
-
acceptedReasons.push("registration imports handler class");
|
|
4380
|
-
}
|
|
4381
|
-
const hasOwnership = modelIsApplicationRepo || modelIsHandlerRepo;
|
|
4382
|
-
const hasCrossPackage = appDependsOnModel && (modelIsHandlerRepo || appDependsOnHandler || !importSource);
|
|
4383
|
-
const contradicted = applicationHasLocalServices && !localServicePathMatch && !appDependsOnModel && !hasOwnership;
|
|
4384
|
-
if (!hasOwnership && !localServicePathMatch && !hasCrossPackage && !helperOwned) rejectedReasons.push("missing direct ownership, exact local service path, or validated cross-package dependency evidence");
|
|
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)
|
|
4426
|
-
}
|
|
5152
|
+
}
|
|
5153
|
+
return {
|
|
5154
|
+
decision: "unresolved",
|
|
5155
|
+
rejectionReason: "path_has_no_safe_entity_or_operation_precedence",
|
|
5156
|
+
indexedOperationCandidateCount
|
|
4427
5157
|
};
|
|
4428
5158
|
}
|
|
4429
|
-
function
|
|
4430
|
-
|
|
4431
|
-
if (resolution.
|
|
4432
|
-
|
|
4433
|
-
if (resolution.
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
const decorator = normalizeDecoratorOperationSignal(typeof row.decoratorValue === "string" ? row.decoratorValue : void 0, typeof row.decoratorRawExpression === "string" ? row.decoratorRawExpression : void 0, op);
|
|
4437
|
-
if (decorator.status === "resolved" && decorator.operationName === op) return { matches: true, contradicted: false, acceptedReasons: ["decorator targets operation"], rejectedReasons: [] };
|
|
4438
|
-
if (decorator.status === "resolved" && decorator.operationName !== op) return { matches: false, contradicted: true, acceptedReasons: [], rejectedReasons: ["method_name_matches_but_decorator_targets_different_operation"] };
|
|
4439
|
-
if (String(row.methodName ?? "") === op) return { matches: true, contradicted: false, acceptedReasons: ["method name fallback matched operation"], rejectedReasons: [] };
|
|
4440
|
-
return { matches: false, contradicted: false, acceptedReasons: [], rejectedReasons: ["method name does not match operation"] };
|
|
4441
|
-
}
|
|
4442
|
-
function upperFirst(value) {
|
|
4443
|
-
return value ? `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}` : value;
|
|
4444
|
-
}
|
|
4445
|
-
function flag(value) {
|
|
4446
|
-
return Boolean(Number(value ?? 0));
|
|
4447
|
-
}
|
|
4448
|
-
function graphId(value) {
|
|
4449
|
-
return String(value);
|
|
4450
|
-
}
|
|
4451
|
-
function normalizedOperation(value) {
|
|
4452
|
-
return value.startsWith("/") ? value.slice(1) : value;
|
|
5159
|
+
function unresolvedOperationReason(resolution) {
|
|
5160
|
+
if (resolution.status === "dynamic") return `Dynamic target requires runtime variable overrides: ${(resolution.reasons.length ? resolution.reasons : ["missing runtime variables"]).join(", ")}`;
|
|
5161
|
+
if (resolution.candidates.length === 0) return "No indexed target operation matched";
|
|
5162
|
+
if (resolution.reasons.includes("operation_path_only_has_no_strong_target_signal")) return "Operation candidates found but no strong service signal is available";
|
|
5163
|
+
if (resolution.reasons.includes("candidate_score_below_resolution_threshold")) return "Operation candidates found but resolution score is below threshold";
|
|
5164
|
+
if (resolution.status === "ambiguous") return "Ambiguous operation candidates require a strong service signal";
|
|
5165
|
+
return "Operation candidates found but resolution could not select a target";
|
|
4453
5166
|
}
|
|
4454
5167
|
|
|
4455
5168
|
// src/trace/selectors.ts
|
|
@@ -4470,23 +5183,34 @@ function selectorRepoNotFoundDiagnostic(requested) {
|
|
|
4470
5183
|
requestedRepository: requested
|
|
4471
5184
|
};
|
|
4472
5185
|
}
|
|
4473
|
-
function selectorRepoAmbiguousDiagnostic(requested,
|
|
4474
|
-
const uniqueName = (value) =>
|
|
4475
|
-
const uniquePackage = (value) =>
|
|
4476
|
-
const suggestions =
|
|
5186
|
+
function selectorRepoAmbiguousDiagnostic(requested, candidates2) {
|
|
5187
|
+
const uniqueName = (value) => candidates2.filter((candidate) => candidate.name === value).length === 1;
|
|
5188
|
+
const uniquePackage = (value) => candidates2.filter((candidate) => candidate.packageName === value).length === 1;
|
|
5189
|
+
const suggestions = candidates2.flatMap((candidate) => {
|
|
4477
5190
|
if (uniqueName(candidate.name)) return [`--repo ${candidate.name}`];
|
|
4478
5191
|
if (candidate.packageName && uniquePackage(candidate.packageName))
|
|
4479
5192
|
return [`--repo ${candidate.packageName}`];
|
|
4480
5193
|
return [];
|
|
4481
5194
|
});
|
|
5195
|
+
const candidateProjection = projectBounded(candidates2, (left, right) => left.name.localeCompare(right.name) || String(left.packageName ?? "").localeCompare(String(right.packageName ?? "")) || left.id - right.id);
|
|
5196
|
+
const suggestionProjection = projectBounded(
|
|
5197
|
+
[...new Set(suggestions)],
|
|
5198
|
+
(left, right) => left.localeCompare(right)
|
|
5199
|
+
);
|
|
4482
5200
|
return {
|
|
4483
5201
|
severity: "warning",
|
|
4484
5202
|
code: "selector_repo_ambiguous",
|
|
4485
5203
|
message: `Repository selector matched multiple indexed repositories: ${requested}`,
|
|
4486
5204
|
selectorKind: "repo",
|
|
4487
5205
|
requestedRepository: requested,
|
|
4488
|
-
candidates,
|
|
4489
|
-
|
|
5206
|
+
candidates: candidateProjection.items,
|
|
5207
|
+
candidateCount: candidateProjection.totalCount,
|
|
5208
|
+
shownCandidateCount: candidateProjection.shownCount,
|
|
5209
|
+
omittedCandidateCount: candidateProjection.omittedCount,
|
|
5210
|
+
selectorSuggestions: suggestionProjection.items,
|
|
5211
|
+
selectorSuggestionCount: suggestionProjection.totalCount,
|
|
5212
|
+
shownSelectorSuggestionCount: suggestionProjection.shownCount,
|
|
5213
|
+
omittedSelectorSuggestionCount: suggestionProjection.omittedCount,
|
|
4490
5214
|
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
5215
|
};
|
|
4492
5216
|
}
|
|
@@ -4628,21 +5352,23 @@ function operationHandlerRows(db, repoId, operation, servicePath, workspaceId) {
|
|
|
4628
5352
|
);
|
|
4629
5353
|
}
|
|
4630
5354
|
function operationHandlerScope(rows2, fallbackRepoId, requested) {
|
|
4631
|
-
const
|
|
4632
|
-
if (
|
|
5355
|
+
const candidates2 = handlerSelectorCandidates(rows2, "method");
|
|
5356
|
+
if (candidates2.length < 2) return executableScope(rows2, fallbackRepoId);
|
|
4633
5357
|
const classes = /* @__PURE__ */ new Map();
|
|
4634
|
-
for (const candidate of
|
|
5358
|
+
for (const candidate of candidates2) {
|
|
4635
5359
|
const key = `${String(candidate.repoName)}:${String(candidate.className)}`;
|
|
4636
5360
|
classes.set(key, /* @__PURE__ */ new Set([
|
|
4637
5361
|
...classes.get(key) ?? [],
|
|
4638
5362
|
String(candidate.handlerClassId)
|
|
4639
5363
|
]));
|
|
4640
5364
|
}
|
|
4641
|
-
const suggestions =
|
|
5365
|
+
const suggestions = candidates2.flatMap((candidate) => {
|
|
4642
5366
|
if (typeof candidate.repoName !== "string" || typeof candidate.className !== "string") return [];
|
|
4643
5367
|
const key = `${candidate.repoName}:${candidate.className}`;
|
|
4644
5368
|
return classes.get(key)?.size === 1 ? [`--repo ${candidate.repoName} --handler ${candidate.className}`] : [];
|
|
4645
5369
|
});
|
|
5370
|
+
const projection = boundedSelectorCandidates(candidates2);
|
|
5371
|
+
const suggestionProjection = boundedSelectorSuggestions(suggestions);
|
|
4646
5372
|
return { diagnostics: [{
|
|
4647
5373
|
severity: "warning",
|
|
4648
5374
|
code: "trace_start_ambiguous",
|
|
@@ -4651,8 +5377,14 @@ function operationHandlerScope(rows2, fallbackRepoId, requested) {
|
|
|
4651
5377
|
normalizedSelectorValue: requested,
|
|
4652
5378
|
resolutionStage: "handler",
|
|
4653
5379
|
resolutionStatus: "ambiguous_handler_operation",
|
|
4654
|
-
candidates,
|
|
4655
|
-
|
|
5380
|
+
candidates: projection.items,
|
|
5381
|
+
candidateCount: projection.totalCount,
|
|
5382
|
+
shownCandidateCount: projection.shownCount,
|
|
5383
|
+
omittedCandidateCount: projection.omittedCount,
|
|
5384
|
+
selectorSuggestions: suggestionProjection.items,
|
|
5385
|
+
selectorSuggestionCount: suggestionProjection.totalCount,
|
|
5386
|
+
shownSelectorSuggestionCount: suggestionProjection.shownCount,
|
|
5387
|
+
omittedSelectorSuggestionCount: suggestionProjection.omittedCount,
|
|
4656
5388
|
remediation: "Select one handler class explicitly; no operation was chosen automatically."
|
|
4657
5389
|
}] };
|
|
4658
5390
|
}
|
|
@@ -4694,7 +5426,7 @@ function handlerClassScope(rows2, requested) {
|
|
|
4694
5426
|
const ambiguity = handlerSelectorAmbiguity(rows2, requested, "class");
|
|
4695
5427
|
if (ambiguity) return { diagnostics: [ambiguity] };
|
|
4696
5428
|
const executable = rows2.filter((row) => typeof row.symbolId === "number");
|
|
4697
|
-
const repoId =
|
|
5429
|
+
const repoId = numericValue3(rows2[0]?.repoId);
|
|
4698
5430
|
if (executable.length > 0) {
|
|
4699
5431
|
const scope = executableScope(executable, repoId);
|
|
4700
5432
|
const warning = executable.some((row) => typeof row.methodId === "number") ? handlerDecoratorsNotIndexedDiagnostic(rows2[0]) : handlerMethodsNotIndexedDiagnostic(rows2[0]);
|
|
@@ -4711,17 +5443,17 @@ function handlerMethodScope(rows2, fallbackRepoId, requested) {
|
|
|
4711
5443
|
return ambiguity ? { diagnostics: [ambiguity] } : executableScope(rows2, fallbackRepoId);
|
|
4712
5444
|
}
|
|
4713
5445
|
function handlerSelectorAmbiguity(rows2, requested, matchKind) {
|
|
4714
|
-
const
|
|
4715
|
-
if (
|
|
5446
|
+
const candidates2 = handlerSelectorCandidates(rows2, matchKind);
|
|
5447
|
+
if (candidates2.length < 2) return void 0;
|
|
4716
5448
|
const repoCounts = /* @__PURE__ */ new Map();
|
|
4717
|
-
for (const candidate of
|
|
5449
|
+
for (const candidate of candidates2) {
|
|
4718
5450
|
if (typeof candidate.repoName !== "string") continue;
|
|
4719
5451
|
repoCounts.set(
|
|
4720
5452
|
candidate.repoName,
|
|
4721
5453
|
(repoCounts.get(candidate.repoName) ?? 0) + 1
|
|
4722
5454
|
);
|
|
4723
5455
|
}
|
|
4724
|
-
const suggestions =
|
|
5456
|
+
const suggestions = candidates2.flatMap((candidate) => {
|
|
4725
5457
|
const repoName = typeof candidate.repoName === "string" ? candidate.repoName : void 0;
|
|
4726
5458
|
if (repoName && repoCounts.get(repoName) === 1)
|
|
4727
5459
|
return [`--repo ${repoName} --handler ${requested}`];
|
|
@@ -4729,6 +5461,8 @@ function handlerSelectorAmbiguity(rows2, requested, matchKind) {
|
|
|
4729
5461
|
return [`${repoName ? `--repo ${repoName} ` : ""}--handler ${candidate.className}`];
|
|
4730
5462
|
return [];
|
|
4731
5463
|
});
|
|
5464
|
+
const projection = boundedSelectorCandidates(candidates2);
|
|
5465
|
+
const suggestionProjection = boundedSelectorSuggestions(suggestions);
|
|
4732
5466
|
return {
|
|
4733
5467
|
severity: "warning",
|
|
4734
5468
|
code: "trace_start_ambiguous",
|
|
@@ -4737,16 +5471,22 @@ function handlerSelectorAmbiguity(rows2, requested, matchKind) {
|
|
|
4737
5471
|
requestedHandler: requested,
|
|
4738
5472
|
resolutionStage: "handler",
|
|
4739
5473
|
resolutionStatus: "ambiguous_handler",
|
|
4740
|
-
candidates,
|
|
4741
|
-
|
|
5474
|
+
candidates: projection.items,
|
|
5475
|
+
candidateCount: projection.totalCount,
|
|
5476
|
+
shownCandidateCount: projection.shownCount,
|
|
5477
|
+
omittedCandidateCount: projection.omittedCount,
|
|
5478
|
+
selectorSuggestions: suggestionProjection.items,
|
|
5479
|
+
selectorSuggestionCount: suggestionProjection.totalCount,
|
|
5480
|
+
shownSelectorSuggestionCount: suggestionProjection.shownCount,
|
|
5481
|
+
omittedSelectorSuggestionCount: suggestionProjection.omittedCount,
|
|
4742
5482
|
remediation: suggestions.length > 0 ? "Use one of the scoped handler selectors shown." : "No current CLI selector uniquely identifies these duplicate handler classes."
|
|
4743
5483
|
};
|
|
4744
5484
|
}
|
|
4745
5485
|
function handlerSelectorCandidates(rows2, matchKind) {
|
|
4746
|
-
const
|
|
5486
|
+
const candidates2 = /* @__PURE__ */ new Map();
|
|
4747
5487
|
for (const row of rows2) {
|
|
4748
5488
|
const identity = matchKind === "class" ? `class:${String(row.handlerClassId)}` : `method:${String(row.repoId)}:${String(row.symbolId ?? row.methodId)}`;
|
|
4749
|
-
|
|
5489
|
+
candidates2.set(identity, {
|
|
4750
5490
|
handlerClassId: row.handlerClassId,
|
|
4751
5491
|
repoId: row.repoId,
|
|
4752
5492
|
repoName: row.repoName,
|
|
@@ -4756,7 +5496,7 @@ function handlerSelectorCandidates(rows2, matchKind) {
|
|
|
4756
5496
|
matchKind
|
|
4757
5497
|
});
|
|
4758
5498
|
}
|
|
4759
|
-
return [...
|
|
5499
|
+
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
5500
|
}
|
|
4761
5501
|
function executableScope(rows2, fallbackRepoId) {
|
|
4762
5502
|
const files = rows2.flatMap((row) => row.sourceFile ? [row.sourceFile] : []);
|
|
@@ -4765,7 +5505,7 @@ function executableScope(rows2, fallbackRepoId) {
|
|
|
4765
5505
|
return {
|
|
4766
5506
|
files: new Set(files),
|
|
4767
5507
|
symbols: new Set(symbols),
|
|
4768
|
-
repoId:
|
|
5508
|
+
repoId: numericValue3(rows2[0]?.repoId) ?? fallbackRepoId
|
|
4769
5509
|
};
|
|
4770
5510
|
}
|
|
4771
5511
|
function handlerMethodsNotIndexedDiagnostic(row) {
|
|
@@ -4825,15 +5565,18 @@ function arrayEvidence(evidenceJson, key) {
|
|
|
4825
5565
|
const value = evidenceRecord(evidenceJson)[key];
|
|
4826
5566
|
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
4827
5567
|
}
|
|
4828
|
-
function
|
|
5568
|
+
function numericValue3(value) {
|
|
4829
5569
|
return typeof value === "number" ? value : void 0;
|
|
4830
5570
|
}
|
|
4831
5571
|
function normalizeOperation(value) {
|
|
4832
5572
|
if (!value) return void 0;
|
|
4833
5573
|
return value.startsWith("/") ? value.slice(1) : value;
|
|
4834
5574
|
}
|
|
4835
|
-
function ambiguousStartDiagnostic(requested,
|
|
4836
|
-
const serviceSuggestions = [...new Set(
|
|
5575
|
+
function ambiguousStartDiagnostic(requested, candidates2, message) {
|
|
5576
|
+
const serviceSuggestions = [...new Set(candidates2.flatMap((row) => typeof row.servicePath === "string" ? [`--service ${row.servicePath}`] : []))].sort();
|
|
5577
|
+
const projection = boundedSelectorCandidates(candidates2);
|
|
5578
|
+
const serviceProjection = boundedSelectorSuggestions(serviceSuggestions);
|
|
5579
|
+
const selectorProjection = boundedSelectorSuggestions(fullSelectorSuggestions(candidates2));
|
|
4837
5580
|
return {
|
|
4838
5581
|
severity: "warning",
|
|
4839
5582
|
code: "trace_start_ambiguous",
|
|
@@ -4841,14 +5584,29 @@ function ambiguousStartDiagnostic(requested, candidates, message) {
|
|
|
4841
5584
|
normalizedSelectorValue: requested,
|
|
4842
5585
|
resolutionStage: "operation",
|
|
4843
5586
|
resolutionStatus: "ambiguous_operation",
|
|
4844
|
-
candidates,
|
|
4845
|
-
|
|
4846
|
-
|
|
5587
|
+
candidates: projection.items,
|
|
5588
|
+
candidateCount: projection.totalCount,
|
|
5589
|
+
shownCandidateCount: projection.shownCount,
|
|
5590
|
+
omittedCandidateCount: projection.omittedCount,
|
|
5591
|
+
serviceSuggestions: serviceProjection.items,
|
|
5592
|
+
serviceSuggestionCount: serviceProjection.totalCount,
|
|
5593
|
+
shownServiceSuggestionCount: serviceProjection.shownCount,
|
|
5594
|
+
omittedServiceSuggestionCount: serviceProjection.omittedCount,
|
|
5595
|
+
selectorSuggestions: selectorProjection.items,
|
|
5596
|
+
selectorSuggestionCount: selectorProjection.totalCount,
|
|
5597
|
+
shownSelectorSuggestionCount: selectorProjection.shownCount,
|
|
5598
|
+
omittedSelectorSuggestionCount: selectorProjection.omittedCount
|
|
4847
5599
|
};
|
|
4848
5600
|
}
|
|
4849
|
-
function
|
|
4850
|
-
|
|
4851
|
-
|
|
5601
|
+
function boundedSelectorCandidates(candidates2) {
|
|
5602
|
+
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));
|
|
5603
|
+
}
|
|
5604
|
+
function boundedSelectorSuggestions(suggestions) {
|
|
5605
|
+
return projectBounded([...new Set(suggestions)], (left, right) => left.localeCompare(right));
|
|
5606
|
+
}
|
|
5607
|
+
function fullSelectorSuggestions(candidates2) {
|
|
5608
|
+
const includeRepo = new Set(candidates2.map((row) => row.repoName)).size > 1;
|
|
5609
|
+
return [...new Set(candidates2.flatMap((row) => {
|
|
4852
5610
|
if (typeof row.servicePath !== "string" || typeof row.operationPath !== "string") return [];
|
|
4853
5611
|
const repoSelector = includeRepo && typeof row.repoName === "string" ? `--repo ${row.repoName} ` : "";
|
|
4854
5612
|
return [
|
|
@@ -4857,13 +5615,136 @@ function fullSelectorSuggestions(candidates) {
|
|
|
4857
5615
|
}))].sort();
|
|
4858
5616
|
}
|
|
4859
5617
|
|
|
5618
|
+
// src/trace/004-dynamic-candidate-sources.ts
|
|
5619
|
+
function dynamicCandidateTargets(db, effectiveOperationPath, originalOperationPath, embedded, workspaceId, requireCanonical) {
|
|
5620
|
+
const canonical = queryOperationTargets(
|
|
5621
|
+
db,
|
|
5622
|
+
effectiveOperationPath,
|
|
5623
|
+
originalOperationPath,
|
|
5624
|
+
workspaceId
|
|
5625
|
+
);
|
|
5626
|
+
if (canonical.length > 0 || requireCanonical) return canonical;
|
|
5627
|
+
return targetsFromEvidence(embedded);
|
|
5628
|
+
}
|
|
5629
|
+
function targetsFromEvidence(value) {
|
|
5630
|
+
if (!Array.isArray(value)) return [];
|
|
5631
|
+
return value.flatMap((item) => {
|
|
5632
|
+
const row = record(item);
|
|
5633
|
+
const operationId = numberValue4(row.operationId);
|
|
5634
|
+
const repoName = stringValue6(row.repoName);
|
|
5635
|
+
const servicePath = stringValue6(row.servicePath);
|
|
5636
|
+
const operationPath = stringValue6(row.operationPath);
|
|
5637
|
+
const operationName = stringValue6(row.operationName) ?? operationPath?.replace(/^\//, "");
|
|
5638
|
+
if (operationId === void 0 || !repoName || !servicePath || !operationPath || !operationName)
|
|
5639
|
+
return [];
|
|
5640
|
+
return [{
|
|
5641
|
+
operationId,
|
|
5642
|
+
repoId: numberValue4(row.repoId),
|
|
5643
|
+
repoName,
|
|
5644
|
+
packageName: stringValue6(row.packageName),
|
|
5645
|
+
serviceName: stringValue6(row.serviceName) ?? "",
|
|
5646
|
+
qualifiedName: stringValue6(row.qualifiedName) ?? "",
|
|
5647
|
+
servicePath,
|
|
5648
|
+
operationPath,
|
|
5649
|
+
operationName,
|
|
5650
|
+
sourceFile: stringValue6(row.sourceFile) ?? "",
|
|
5651
|
+
sourceLine: numberValue4(row.sourceLine) ?? 0,
|
|
5652
|
+
score: numberValue4(row.score) ?? 0,
|
|
5653
|
+
reasons: stringArray3(row.reasons)
|
|
5654
|
+
}];
|
|
5655
|
+
});
|
|
5656
|
+
}
|
|
5657
|
+
function queryOperationTargets(db, effectiveOperationPath, originalOperationPath, workspaceId) {
|
|
5658
|
+
const operationPath = effectiveOperationPath ?? originalOperationPath;
|
|
5659
|
+
if (!operationPath) return [];
|
|
5660
|
+
if (extractPlaceholders(operationPath).length > 0)
|
|
5661
|
+
return templateOperationTargets(db, operationPath, workspaceId);
|
|
5662
|
+
return exactOperationTargets(db, operationPath, workspaceId);
|
|
5663
|
+
}
|
|
5664
|
+
function exactOperationTargets(db, operationPath, workspaceId) {
|
|
5665
|
+
const simple = operationPath.replace(/^\//, "").split(".").at(-1) ?? operationPath;
|
|
5666
|
+
const rows2 = recordRows(db.prepare(
|
|
5667
|
+
`SELECT o.id operationId,r.id repoId,r.name repoName,r.package_name packageName,
|
|
5668
|
+
s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,
|
|
5669
|
+
o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,
|
|
5670
|
+
o.source_line sourceLine FROM cds_operations o
|
|
5671
|
+
JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
|
|
5672
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
5673
|
+
AND (o.operation_path IN (?,?) OR o.operation_name=?)
|
|
5674
|
+
ORDER BY r.name,s.service_path,o.operation_name,o.id`
|
|
5675
|
+
).all(workspaceId, workspaceId, operationPath, `/${simple}`, simple));
|
|
5676
|
+
return rows2.flatMap(targetFromRow);
|
|
5677
|
+
}
|
|
5678
|
+
function templateOperationTargets(db, operationTemplate, workspaceId) {
|
|
5679
|
+
const rows2 = recordRows(db.prepare(
|
|
5680
|
+
`SELECT o.id operationId,r.id repoId,r.name repoName,r.package_name packageName,
|
|
5681
|
+
s.service_name serviceName,s.qualified_name qualifiedName,s.service_path servicePath,
|
|
5682
|
+
o.operation_path operationPath,o.operation_name operationName,o.source_file sourceFile,
|
|
5683
|
+
o.source_line sourceLine FROM cds_operations o
|
|
5684
|
+
JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
|
|
5685
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
5686
|
+
ORDER BY r.name,s.service_path,o.operation_name,o.id`
|
|
5687
|
+
).all(workspaceId, workspaceId));
|
|
5688
|
+
return rows2.flatMap((row) => {
|
|
5689
|
+
const operationPath = stringValue6(row.operationPath);
|
|
5690
|
+
return matchRuntimeTemplate(operationTemplate, operationPath) ? targetFromRow(row) : [];
|
|
5691
|
+
});
|
|
5692
|
+
}
|
|
5693
|
+
function targetFromRow(row) {
|
|
5694
|
+
const operationId = numberValue4(row.operationId);
|
|
5695
|
+
const repoName = stringValue6(row.repoName);
|
|
5696
|
+
const servicePath = stringValue6(row.servicePath);
|
|
5697
|
+
const operationPath = stringValue6(row.operationPath);
|
|
5698
|
+
const operationName = stringValue6(row.operationName);
|
|
5699
|
+
if (operationId === void 0 || !repoName || !servicePath || !operationPath || !operationName)
|
|
5700
|
+
return [];
|
|
5701
|
+
return [{
|
|
5702
|
+
operationId,
|
|
5703
|
+
repoId: numberValue4(row.repoId),
|
|
5704
|
+
repoName,
|
|
5705
|
+
packageName: stringValue6(row.packageName),
|
|
5706
|
+
serviceName: stringValue6(row.serviceName) ?? "",
|
|
5707
|
+
qualifiedName: stringValue6(row.qualifiedName) ?? "",
|
|
5708
|
+
servicePath,
|
|
5709
|
+
operationPath,
|
|
5710
|
+
operationName,
|
|
5711
|
+
sourceFile: stringValue6(row.sourceFile) ?? "",
|
|
5712
|
+
sourceLine: numberValue4(row.sourceLine) ?? 0,
|
|
5713
|
+
score: 0.2,
|
|
5714
|
+
reasons: ["operation_path_match"]
|
|
5715
|
+
}];
|
|
5716
|
+
}
|
|
5717
|
+
function record(value) {
|
|
5718
|
+
return isRecord4(value) ? value : {};
|
|
5719
|
+
}
|
|
5720
|
+
function recordRows(value) {
|
|
5721
|
+
return Array.isArray(value) ? value.filter(isRecord4) : [];
|
|
5722
|
+
}
|
|
5723
|
+
function isRecord4(value) {
|
|
5724
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
5725
|
+
}
|
|
5726
|
+
function stringValue6(value) {
|
|
5727
|
+
return typeof value === "string" ? value : void 0;
|
|
5728
|
+
}
|
|
5729
|
+
function numberValue4(value) {
|
|
5730
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5731
|
+
}
|
|
5732
|
+
function stringArray3(value) {
|
|
5733
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
5734
|
+
}
|
|
5735
|
+
|
|
4860
5736
|
// src/trace/001-dynamic-identity.ts
|
|
4861
|
-
function uniqueIdentityDerivations(db,
|
|
4862
|
-
const identities = workspaceIdentities(db,
|
|
4863
|
-
const proposals =
|
|
4864
|
-
const
|
|
5737
|
+
function uniqueIdentityDerivations(db, candidates2, templates) {
|
|
5738
|
+
const identities = workspaceIdentities(db, candidates2);
|
|
5739
|
+
const proposals = candidates2.flatMap((candidate) => {
|
|
5740
|
+
const implementation = routeImplementationEvidence(
|
|
5741
|
+
db,
|
|
5742
|
+
candidate.candidateOperationId
|
|
5743
|
+
);
|
|
4865
5744
|
const identity = identities.find((item) => item.repoId === candidate.repoId);
|
|
4866
|
-
|
|
5745
|
+
if (!identity || !implementation || !routeOwnerAgrees(candidate, implementation))
|
|
5746
|
+
return [];
|
|
5747
|
+
return identityProposals(candidate, identity, templates, implementation);
|
|
4867
5748
|
});
|
|
4868
5749
|
const matches2 = workspaceIdentityMatches(identities, templates);
|
|
4869
5750
|
const competing = competingIdentityKeys(matches2);
|
|
@@ -4875,7 +5756,7 @@ function uniqueIdentityDerivations(db, candidates, templates) {
|
|
|
4875
5756
|
provenance: proposal.provenance
|
|
4876
5757
|
}));
|
|
4877
5758
|
}
|
|
4878
|
-
function identityProposals(candidate, identity, templates) {
|
|
5759
|
+
function identityProposals(candidate, identity, templates, implementation) {
|
|
4879
5760
|
const routeTemplates = [templates.alias, templates.destination].filter((value) => Boolean(value));
|
|
4880
5761
|
const identities = [
|
|
4881
5762
|
{ name: identity.packageName, sourceKind: "package_identity", npmPackage: true },
|
|
@@ -4886,11 +5767,12 @@ function identityProposals(candidate, identity, templates) {
|
|
|
4886
5767
|
template,
|
|
4887
5768
|
identity2.name,
|
|
4888
5769
|
identity2.sourceKind,
|
|
4889
|
-
identity2.npmPackage
|
|
5770
|
+
identity2.npmPackage,
|
|
5771
|
+
implementation
|
|
4890
5772
|
)));
|
|
4891
5773
|
return deduplicateProposals(proposals);
|
|
4892
5774
|
}
|
|
4893
|
-
function proposalForIdentity(candidate, template, identity, sourceKind, npmPackage) {
|
|
5775
|
+
function proposalForIdentity(candidate, template, identity, sourceKind, npmPackage, implementation) {
|
|
4894
5776
|
const match = matchIdentityTemplate(template, identity, npmPackage);
|
|
4895
5777
|
if (!match) return [];
|
|
4896
5778
|
return [{
|
|
@@ -4905,7 +5787,13 @@ function proposalForIdentity(candidate, template, identity, sourceKind, npmPacka
|
|
|
4905
5787
|
template,
|
|
4906
5788
|
matchedName: identity,
|
|
4907
5789
|
normalizedForm: match.normalizedIdentity,
|
|
4908
|
-
sourceRepo: candidate.repoName
|
|
5790
|
+
sourceRepo: candidate.repoName,
|
|
5791
|
+
routeOwner: candidate.repoName,
|
|
5792
|
+
candidateOperationId: candidate.candidateOperationId,
|
|
5793
|
+
effectiveBaseOperationId: implementation.baseOperationId,
|
|
5794
|
+
candidateOperationProvenance: implementation.operationProvenance,
|
|
5795
|
+
implementationEdgeStatus: implementation.edgeStatus,
|
|
5796
|
+
implementationHandlerRepo: implementation.handlerRepo
|
|
4909
5797
|
}
|
|
4910
5798
|
}];
|
|
4911
5799
|
}
|
|
@@ -4918,7 +5806,7 @@ function matchIdentityTemplate(template, identity, npmPackage) {
|
|
|
4918
5806
|
const [prefix, suffix, extra] = normalizedTemplate.split(sentinel);
|
|
4919
5807
|
if (!prefix || !suffix || extra !== void 0) return void 0;
|
|
4920
5808
|
const normalizedIdentity = normalizeIdentity(identity, npmPackage);
|
|
4921
|
-
const match = new RegExp(`^${
|
|
5809
|
+
const match = new RegExp(`^${escapeRegex2(prefix)}([a-z0-9]+)${escapeRegex2(suffix)}$`).exec(normalizedIdentity);
|
|
4922
5810
|
if (!match?.[1]) return void 0;
|
|
4923
5811
|
return { key: matches2[0][1].trim(), value: match[1], normalizedIdentity };
|
|
4924
5812
|
}
|
|
@@ -4966,8 +5854,8 @@ function workspaceIdentityMatches(identities, templates) {
|
|
|
4966
5854
|
}));
|
|
4967
5855
|
});
|
|
4968
5856
|
}
|
|
4969
|
-
function workspaceIdentities(db,
|
|
4970
|
-
const repoIds = [...new Set(
|
|
5857
|
+
function workspaceIdentities(db, candidates2) {
|
|
5858
|
+
const repoIds = [...new Set(candidates2.flatMap((candidate) => candidate.repoId === void 0 ? [] : [candidate.repoId]))].sort((a, b) => a - b);
|
|
4971
5859
|
if (repoIds.length === 0) return [];
|
|
4972
5860
|
const placeholders3 = repoIds.map(() => "?").join(",");
|
|
4973
5861
|
const rows2 = db.prepare(`SELECT id repoId,name repoName,package_name packageName
|
|
@@ -4975,12 +5863,12 @@ function workspaceIdentities(db, candidates) {
|
|
|
4975
5863
|
SELECT DISTINCT workspace_id FROM repositories WHERE id IN (${placeholders3})
|
|
4976
5864
|
) ORDER BY workspace_id,name,absolute_path,id`).all(...repoIds);
|
|
4977
5865
|
return rows2.flatMap((row) => {
|
|
4978
|
-
const repoId =
|
|
4979
|
-
const repoName =
|
|
5866
|
+
const repoId = numberValue5(row.repoId);
|
|
5867
|
+
const repoName = stringValue7(row.repoName);
|
|
4980
5868
|
return repoId === void 0 || !repoName ? [] : [{
|
|
4981
5869
|
repoId,
|
|
4982
5870
|
repoName,
|
|
4983
|
-
packageName:
|
|
5871
|
+
packageName: stringValue7(row.packageName)
|
|
4984
5872
|
}];
|
|
4985
5873
|
});
|
|
4986
5874
|
}
|
|
@@ -4994,57 +5882,168 @@ function deduplicateProposals(rows2) {
|
|
|
4994
5882
|
return true;
|
|
4995
5883
|
});
|
|
4996
5884
|
}
|
|
4997
|
-
function
|
|
4998
|
-
return candidate.repoId !== void 0 &&
|
|
5885
|
+
function routeOwnerAgrees(candidate, implementation) {
|
|
5886
|
+
return candidate.repoId !== void 0 && implementation?.routeRepoId === candidate.repoId && implementation.edgeStatus === "resolved" && candidate.viable && candidate.reasons.includes("service_path_template_match");
|
|
4999
5887
|
}
|
|
5000
|
-
function
|
|
5888
|
+
function routeImplementationEvidence(db, operationId) {
|
|
5001
5889
|
const rows2 = db.prepare(
|
|
5002
|
-
`SELECT
|
|
5003
|
-
|
|
5890
|
+
`SELECT s.repo_id routeRepoId,o.provenance operationProvenance,
|
|
5891
|
+
o.base_operation_id baseOperationId,e.status edgeStatus,
|
|
5892
|
+
r.name handlerRepo
|
|
5893
|
+
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id
|
|
5894
|
+
JOIN graph_edges e ON e.edge_type='OPERATION_IMPLEMENTED_BY_HANDLER'
|
|
5895
|
+
AND e.from_kind='operation' AND e.from_id=CAST(o.id AS TEXT)
|
|
5896
|
+
JOIN handler_methods hm ON hm.id=CAST(e.to_id AS INTEGER)
|
|
5004
5897
|
JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
5005
5898
|
JOIN repositories r ON r.id=hc.repo_id
|
|
5006
5899
|
WHERE e.edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND e.status='resolved'
|
|
5007
|
-
AND
|
|
5008
|
-
ORDER BY
|
|
5900
|
+
AND o.id=?
|
|
5901
|
+
ORDER BY e.id,hm.id`
|
|
5009
5902
|
).all(String(operationId));
|
|
5010
5903
|
if (rows2.length !== 1) return void 0;
|
|
5011
5904
|
const row = rows2[0];
|
|
5012
5905
|
if (!row) return void 0;
|
|
5013
5906
|
return {
|
|
5014
|
-
|
|
5907
|
+
routeRepoId: numberValue5(row.routeRepoId),
|
|
5908
|
+
handlerRepo: stringValue7(row.handlerRepo),
|
|
5909
|
+
operationProvenance: stringValue7(row.operationProvenance),
|
|
5910
|
+
baseOperationId: numberValue5(row.baseOperationId),
|
|
5911
|
+
edgeStatus: stringValue7(row.edgeStatus)
|
|
5015
5912
|
};
|
|
5016
5913
|
}
|
|
5017
5914
|
function normalizeIdentity(value, npmPackage = false) {
|
|
5018
5915
|
const unscoped = npmPackage && /^@[^/]+\/[^/]+$/.test(value) ? value.slice(value.indexOf("/") + 1) : value;
|
|
5019
5916
|
return unscoped.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
5020
5917
|
}
|
|
5021
|
-
function
|
|
5918
|
+
function stringValue7(value) {
|
|
5022
5919
|
return typeof value === "string" ? value : void 0;
|
|
5023
5920
|
}
|
|
5024
|
-
function
|
|
5921
|
+
function numberValue5(value) {
|
|
5025
5922
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5026
5923
|
}
|
|
5027
|
-
function
|
|
5924
|
+
function escapeRegex2(value) {
|
|
5028
5925
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5029
5926
|
}
|
|
5030
5927
|
|
|
5031
5928
|
// src/trace/003-dynamic-references.ts
|
|
5032
|
-
function
|
|
5929
|
+
function dynamicRoutingContext(db, workspaceId, evidence) {
|
|
5930
|
+
const selected = selectedBinding(db, workspaceId, evidence);
|
|
5931
|
+
const persisted = persistedBindingResolution(evidence);
|
|
5932
|
+
const alternatives = boundedAlternatives(
|
|
5933
|
+
persisted.candidates,
|
|
5934
|
+
persisted.candidateCount
|
|
5935
|
+
);
|
|
5936
|
+
if (selected) {
|
|
5937
|
+
const requires = exactRequireReferences(db, workspaceId, selected);
|
|
5938
|
+
return {
|
|
5939
|
+
...contextBase(selected, persisted.status, alternatives),
|
|
5940
|
+
selectedBinding: selected,
|
|
5941
|
+
references: [selected, ...requires],
|
|
5942
|
+
fallbackUsed: false
|
|
5943
|
+
};
|
|
5944
|
+
}
|
|
5945
|
+
const callerRepoId = numberValue6(evidence.repoId);
|
|
5946
|
+
const callerRepo = stringValue8(evidence.repo);
|
|
5947
|
+
return {
|
|
5948
|
+
outboundCallId: numberValue6(evidence.outboundCallId ?? evidence.callId),
|
|
5949
|
+
callerRepoId,
|
|
5950
|
+
callerRepo,
|
|
5951
|
+
bindingResolutionStatus: persisted.status,
|
|
5952
|
+
bindingAlternatives: alternatives.items,
|
|
5953
|
+
bindingAlternativeCount: alternatives.totalCount,
|
|
5954
|
+
shownBindingAlternativeCount: alternatives.shownCount,
|
|
5955
|
+
omittedBindingAlternativeCount: alternatives.omittedCount,
|
|
5956
|
+
references: fallbackReferences(db, workspaceId, callerRepoId, callerRepo),
|
|
5957
|
+
fallbackUsed: true
|
|
5958
|
+
};
|
|
5959
|
+
}
|
|
5960
|
+
function dynamicReferenceProvenance(reference, kind, template, value) {
|
|
5961
|
+
const sourceKind = reference.selection === "selected_binding" ? `selected_binding.${kind}` : reference.selection === "selected_binding_require" ? `selected_binding_require.${kind}` : `${reference.sourceKind}.${kind}`;
|
|
5962
|
+
return {
|
|
5963
|
+
sourceKind,
|
|
5964
|
+
value,
|
|
5965
|
+
rule: "exact_indexed_reference_template_match",
|
|
5966
|
+
template,
|
|
5967
|
+
sourceRepo: reference.repoName,
|
|
5968
|
+
sourceFile: reference.sourceFile,
|
|
5969
|
+
sourceLine: reference.sourceLine,
|
|
5970
|
+
selection: reference.selection,
|
|
5971
|
+
bindingId: reference.bindingId
|
|
5972
|
+
};
|
|
5973
|
+
}
|
|
5974
|
+
function contextBase(selected, status, alternatives) {
|
|
5975
|
+
return {
|
|
5976
|
+
outboundCallId: selected.outboundCallId,
|
|
5977
|
+
callerRepoId: selected.callerRepoId,
|
|
5978
|
+
callerRepo: selected.repoName,
|
|
5979
|
+
selectedBindingId: selected.bindingId,
|
|
5980
|
+
bindingResolutionStatus: status,
|
|
5981
|
+
bindingAlternatives: alternatives.items,
|
|
5982
|
+
bindingAlternativeCount: alternatives.totalCount,
|
|
5983
|
+
shownBindingAlternativeCount: alternatives.shownCount,
|
|
5984
|
+
omittedBindingAlternativeCount: alternatives.omittedCount
|
|
5985
|
+
};
|
|
5986
|
+
}
|
|
5987
|
+
function selectedBinding(db, workspaceId, evidence) {
|
|
5988
|
+
const callId = numberValue6(evidence.outboundCallId ?? evidence.callId);
|
|
5989
|
+
if (callId === void 0) return void 0;
|
|
5990
|
+
const row = db.prepare(`SELECT c.id outboundCallId,c.repo_id callerRepoId,r.name repoName,
|
|
5991
|
+
b.id bindingId,b.alias,b.alias_expr aliasExpr,b.destination_expr destination,
|
|
5992
|
+
b.service_path_expr servicePath,b.source_file sourceFile,b.source_line sourceLine,
|
|
5993
|
+
b.helper_chain_json helperChainJson
|
|
5994
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
5995
|
+
JOIN service_bindings b ON b.id=c.service_binding_id AND b.repo_id=c.repo_id
|
|
5996
|
+
WHERE c.id=? AND (? IS NULL OR r.workspace_id=?)`).get(
|
|
5997
|
+
callId,
|
|
5998
|
+
workspaceId,
|
|
5999
|
+
workspaceId
|
|
6000
|
+
);
|
|
6001
|
+
return selectedReferenceFromRow(row);
|
|
6002
|
+
}
|
|
6003
|
+
function selectedReferenceFromRow(row) {
|
|
6004
|
+
const outboundCallId = numberValue6(row?.outboundCallId);
|
|
6005
|
+
const callerRepoId = numberValue6(row?.callerRepoId);
|
|
6006
|
+
const reference = referenceFromRow(
|
|
6007
|
+
row,
|
|
6008
|
+
"service_binding",
|
|
6009
|
+
"selected_binding"
|
|
6010
|
+
)[0];
|
|
6011
|
+
return reference && outboundCallId !== void 0 && callerRepoId !== void 0 ? { ...reference, outboundCallId, callerRepoId } : void 0;
|
|
6012
|
+
}
|
|
6013
|
+
function exactRequireReferences(db, workspaceId, selected) {
|
|
6014
|
+
if (!(selected.aliasExpr ?? selected.alias)) return [];
|
|
6015
|
+
const rows2 = db.prepare(`SELECT req.alias,req.destination,req.service_path servicePath,
|
|
6016
|
+
r.name repoName,'package.json' sourceFile,1 sourceLine
|
|
6017
|
+
FROM cds_requires req JOIN repositories r ON r.id=req.repo_id
|
|
6018
|
+
WHERE req.repo_id=? AND (? IS NULL OR r.workspace_id=?)
|
|
6019
|
+
ORDER BY req.alias,req.id`).all(
|
|
6020
|
+
selected.callerRepoId,
|
|
6021
|
+
workspaceId,
|
|
6022
|
+
workspaceId
|
|
6023
|
+
);
|
|
6024
|
+
return rows2.flatMap((row) => referenceFromRow(
|
|
6025
|
+
row,
|
|
6026
|
+
"cds_require",
|
|
6027
|
+
"selected_binding_require",
|
|
6028
|
+
selected.bindingId
|
|
6029
|
+
));
|
|
6030
|
+
}
|
|
6031
|
+
function fallbackReferences(db, workspaceId, callerRepoId, callerRepo) {
|
|
5033
6032
|
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
|
-
|
|
6033
|
+
const rows2 = db.prepare(`SELECT b.id bindingId,COALESCE(b.alias,b.alias_expr) alias,
|
|
6034
|
+
b.alias_expr aliasExpr,b.destination_expr destination,b.service_path_expr servicePath,
|
|
6035
|
+
'service_binding' sourceKind,r.name repoName,b.source_file sourceFile,
|
|
6036
|
+
b.source_line sourceLine,b.helper_chain_json helperChainJson,0 sourcePriority
|
|
6037
|
+
FROM service_bindings b JOIN repositories r ON r.id=b.repo_id
|
|
6038
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
6039
|
+
AND ((? IS NOT NULL AND r.id=?) OR (? IS NULL AND r.name=?))
|
|
6040
|
+
UNION ALL
|
|
6041
|
+
SELECT NULL,req.alias,req.alias,req.destination,req.service_path,
|
|
6042
|
+
'cds_require',r.name,'package.json',1,NULL,1
|
|
6043
|
+
FROM cds_requires req JOIN repositories r ON r.id=req.repo_id
|
|
6044
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
6045
|
+
AND ((? IS NOT NULL AND r.id=?) OR (? IS NULL AND r.name=?))
|
|
6046
|
+
ORDER BY sourcePriority,repoName,sourceFile,sourceLine`).all(
|
|
5048
6047
|
workspaceId,
|
|
5049
6048
|
workspaceId,
|
|
5050
6049
|
callerRepoId,
|
|
@@ -5058,61 +6057,90 @@ function dynamicReferenceRows(db, workspaceId, callerRepoId, callerRepo) {
|
|
|
5058
6057
|
callerRepoId,
|
|
5059
6058
|
callerRepo
|
|
5060
6059
|
);
|
|
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
|
-
};
|
|
6060
|
+
return rows2.flatMap((row) => {
|
|
6061
|
+
const sourceKind = row.sourceKind;
|
|
6062
|
+
return sourceKind === "service_binding" || sourceKind === "cds_require" ? referenceFromRow(row, sourceKind, "fallback") : [];
|
|
6063
|
+
});
|
|
5073
6064
|
}
|
|
5074
|
-
function referenceFromRow(row) {
|
|
5075
|
-
const
|
|
5076
|
-
|
|
5077
|
-
if (sourceKind !== "service_binding" && sourceKind !== "cds_require" || !repoName) return [];
|
|
6065
|
+
function referenceFromRow(row, sourceKind, selection, bindingId = numberValue6(row?.bindingId)) {
|
|
6066
|
+
const repoName = stringValue8(row?.repoName);
|
|
6067
|
+
if (!repoName) return [];
|
|
5078
6068
|
return [{
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
6069
|
+
bindingId,
|
|
6070
|
+
alias: stringValue8(row?.alias),
|
|
6071
|
+
aliasExpr: stringValue8(row?.aliasExpr),
|
|
6072
|
+
destination: stringValue8(row?.destination),
|
|
6073
|
+
servicePath: stringValue8(row?.servicePath),
|
|
5082
6074
|
sourceKind,
|
|
6075
|
+
selection,
|
|
5083
6076
|
repoName,
|
|
5084
|
-
sourceFile:
|
|
5085
|
-
sourceLine:
|
|
6077
|
+
sourceFile: stringValue8(row?.sourceFile),
|
|
6078
|
+
sourceLine: numberValue6(row?.sourceLine),
|
|
6079
|
+
helperChain: parsedJson(row?.helperChainJson)
|
|
5086
6080
|
}];
|
|
5087
6081
|
}
|
|
5088
|
-
function
|
|
6082
|
+
function persistedBindingResolution(evidence) {
|
|
6083
|
+
const outbound = record2(evidence.outboundEvidence);
|
|
6084
|
+
const resolution = record2(outbound.serviceBindingResolution);
|
|
6085
|
+
return {
|
|
6086
|
+
status: stringValue8(resolution.status) ?? "unknown",
|
|
6087
|
+
candidates: recordArray2(resolution.candidates),
|
|
6088
|
+
candidateCount: numberValue6(resolution.candidateCount) ?? 0
|
|
6089
|
+
};
|
|
6090
|
+
}
|
|
6091
|
+
function boundedAlternatives(rows2, reportedCount) {
|
|
6092
|
+
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));
|
|
6093
|
+
const totalCount = Math.max(reportedCount, projection.totalCount);
|
|
6094
|
+
return {
|
|
6095
|
+
...projection,
|
|
6096
|
+
totalCount,
|
|
6097
|
+
omittedCount: Math.max(0, totalCount - projection.shownCount)
|
|
6098
|
+
};
|
|
6099
|
+
}
|
|
6100
|
+
function parsedJson(value) {
|
|
6101
|
+
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
6102
|
+
try {
|
|
6103
|
+
return JSON.parse(value);
|
|
6104
|
+
} catch {
|
|
6105
|
+
return void 0;
|
|
6106
|
+
}
|
|
6107
|
+
}
|
|
6108
|
+
function record2(value) {
|
|
6109
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
6110
|
+
}
|
|
6111
|
+
function recordArray2(value) {
|
|
6112
|
+
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
6113
|
+
}
|
|
6114
|
+
function stringValue8(value) {
|
|
5089
6115
|
return typeof value === "string" ? value : void 0;
|
|
5090
6116
|
}
|
|
5091
|
-
function
|
|
6117
|
+
function numberValue6(value) {
|
|
5092
6118
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5093
6119
|
}
|
|
5094
6120
|
|
|
5095
6121
|
// src/trace/dynamic-targets.ts
|
|
5096
6122
|
function analyzeDynamicTargetCandidates(db, evidence, workspaceId, mode, maxCandidates) {
|
|
5097
|
-
const inputs = analysisInputs(evidence);
|
|
6123
|
+
const inputs = analysisInputs(db, evidence, workspaceId);
|
|
5098
6124
|
if (inputs.required.length === 0) return void 0;
|
|
5099
|
-
const targets =
|
|
5100
|
-
const references = dynamicReferenceRows(
|
|
6125
|
+
const targets = dynamicCandidateTargets(
|
|
5101
6126
|
db,
|
|
6127
|
+
inputs.effective.operationPath,
|
|
6128
|
+
inputs.original.operationPath,
|
|
6129
|
+
evidence.candidates,
|
|
5102
6130
|
workspaceId,
|
|
5103
|
-
inputs.
|
|
5104
|
-
inputs.callerRepo
|
|
6131
|
+
inputs.routing.outboundCallId !== void 0
|
|
5105
6132
|
);
|
|
5106
|
-
const
|
|
5107
|
-
applyUniqueIdentityEvidence(db,
|
|
5108
|
-
finalizeCandidates(
|
|
5109
|
-
const ranked = stableRank(
|
|
6133
|
+
const candidates2 = buildCandidates(db, targets, inputs.routing.references, inputs);
|
|
6134
|
+
applyUniqueIdentityEvidence(db, candidates2, inputs);
|
|
6135
|
+
finalizeCandidates(candidates2, inputs.order);
|
|
6136
|
+
const ranked = stableRank(candidates2);
|
|
5110
6137
|
const inference = inferenceDecision(ranked);
|
|
5111
6138
|
applyModeState(ranked, mode, inference);
|
|
5112
6139
|
const viable = ranked.filter((candidate) => candidate.viable);
|
|
5113
6140
|
const rejected = ranked.filter((candidate) => candidate.rejected);
|
|
5114
6141
|
const shown = viable.slice(0, maxCandidates).map((candidate) => boundedCandidate(candidate, maxCandidates));
|
|
5115
6142
|
const shownRejected = rejected.slice(0, maxCandidates).map((candidate) => boundedCandidate(candidate, maxCandidates));
|
|
6143
|
+
const suggestionProjection = suggestedVarSets(viable, inputs.order, maxCandidates);
|
|
5116
6144
|
return {
|
|
5117
6145
|
mode,
|
|
5118
6146
|
maxCandidates,
|
|
@@ -5131,16 +6159,21 @@ function analyzeDynamicTargetCandidates(db, evidence, workspaceId, mode, maxCand
|
|
|
5131
6159
|
candidates: shown,
|
|
5132
6160
|
shownCandidates: shown,
|
|
5133
6161
|
rejectedCandidates: shownRejected,
|
|
5134
|
-
suggestedVarSets:
|
|
5135
|
-
|
|
6162
|
+
suggestedVarSets: suggestionProjection.items,
|
|
6163
|
+
suggestedVarSetCount: suggestionProjection.totalCount,
|
|
6164
|
+
shownSuggestedVarSetCount: suggestionProjection.shownCount,
|
|
6165
|
+
omittedSuggestedVarSetCount: suggestionProjection.omittedCount,
|
|
6166
|
+
inference,
|
|
6167
|
+
routingContext: routingEvidence2(inputs.routing)
|
|
5136
6168
|
};
|
|
5137
6169
|
}
|
|
5138
|
-
function analysisInputs(evidence) {
|
|
5139
|
-
const
|
|
5140
|
-
const
|
|
6170
|
+
function analysisInputs(db, evidence, workspaceId) {
|
|
6171
|
+
const routing = dynamicRoutingContext(db, workspaceId, evidence);
|
|
6172
|
+
const supplied = stringRecord(evidence.suppliedRuntimeVariables);
|
|
6173
|
+
const original = templatesFromEvidence(evidence, routing);
|
|
6174
|
+
const effective = effectiveTemplates(original, supplied);
|
|
5141
6175
|
const requiredSources = placeholderSources(original);
|
|
5142
6176
|
const required = Object.keys(requiredSources);
|
|
5143
|
-
const supplied = stringRecord(evidence.suppliedRuntimeVariables);
|
|
5144
6177
|
return {
|
|
5145
6178
|
original,
|
|
5146
6179
|
effective,
|
|
@@ -5148,94 +6181,51 @@ function analysisInputs(evidence) {
|
|
|
5148
6181
|
requiredSources,
|
|
5149
6182
|
supplied,
|
|
5150
6183
|
order: variableOrder(original, required),
|
|
5151
|
-
callerRepo:
|
|
5152
|
-
callerRepoId:
|
|
6184
|
+
callerRepo: routing.callerRepo ?? stringValue9(evidence.repo),
|
|
6185
|
+
callerRepoId: routing.callerRepoId ?? numberValue7(evidence.repoId),
|
|
6186
|
+
routing
|
|
5153
6187
|
};
|
|
5154
6188
|
}
|
|
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
6189
|
function buildCandidates(db, targets, references, inputs) {
|
|
5227
6190
|
return targets.map((target) => {
|
|
5228
6191
|
const state = emptyCandidate(target, inputs);
|
|
5229
6192
|
applyDirectSignal(state, inputs, "operationPath", target.operationPath, 0.25);
|
|
5230
6193
|
applyDirectSignal(state, inputs, "servicePath", target.servicePath, 0.35);
|
|
5231
|
-
const matchingReferences = references.filter((reference) => reference.servicePath
|
|
5232
|
-
|
|
5233
|
-
|
|
6194
|
+
const matchingReferences = references.filter((reference) => referenceMatchesCandidate(reference, target.servicePath) && referenceMatchesSelectedAlias(reference, inputs.routing.selectedBinding));
|
|
6195
|
+
const referencesForSignals = fallbackReferencesForCandidate(
|
|
6196
|
+
state,
|
|
6197
|
+
matchingReferences,
|
|
6198
|
+
inputs.routing.fallbackUsed
|
|
6199
|
+
);
|
|
6200
|
+
applyReferenceSignal(state, inputs, referencesForSignals, "alias");
|
|
6201
|
+
applyReferenceSignal(state, inputs, referencesForSignals, "destination");
|
|
5234
6202
|
if (hasResolvedImplementation(db, target.operationId))
|
|
5235
6203
|
addScore(state, 0.1, "implementation_edge_resolved");
|
|
5236
6204
|
return state;
|
|
5237
6205
|
});
|
|
5238
6206
|
}
|
|
6207
|
+
function fallbackReferencesForCandidate(state, references, fallbackUsed) {
|
|
6208
|
+
if (!fallbackUsed) return references;
|
|
6209
|
+
const unique3 = uniqueFallbackReferences(references);
|
|
6210
|
+
if (unique3.length <= 1) return unique3;
|
|
6211
|
+
addReason(state, "fallback_reference_ambiguous");
|
|
6212
|
+
addInferenceBlock(state, "fallback_reference_ambiguous");
|
|
6213
|
+
return [];
|
|
6214
|
+
}
|
|
6215
|
+
function uniqueFallbackReferences(references) {
|
|
6216
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6217
|
+
return references.filter((reference) => {
|
|
6218
|
+
const signature = [
|
|
6219
|
+
reference.sourceKind,
|
|
6220
|
+
reference.alias,
|
|
6221
|
+
reference.destination,
|
|
6222
|
+
reference.servicePath
|
|
6223
|
+
].join("\0");
|
|
6224
|
+
if (seen.has(signature)) return false;
|
|
6225
|
+
seen.add(signature);
|
|
6226
|
+
return true;
|
|
6227
|
+
});
|
|
6228
|
+
}
|
|
5239
6229
|
function emptyCandidate(target, inputs) {
|
|
5240
6230
|
return {
|
|
5241
6231
|
candidateOperationId: target.operationId,
|
|
@@ -5274,20 +6264,25 @@ function emptyCandidate(target, inputs) {
|
|
|
5274
6264
|
function applyDirectSignal(state, inputs, kind, concrete, score) {
|
|
5275
6265
|
const effective = inputs.effective[kind];
|
|
5276
6266
|
const original = inputs.original[kind];
|
|
5277
|
-
if (effective && !
|
|
6267
|
+
if (effective && !matchRuntimeTemplate(effective, concrete)) {
|
|
5278
6268
|
reject(state, `${signalCode(kind)}_contradicts_runtime_substitution`);
|
|
5279
6269
|
return;
|
|
5280
6270
|
}
|
|
5281
6271
|
if (!effective) return;
|
|
5282
6272
|
const suppliedKeys = extractPlaceholders(original).filter((key) => inputs.supplied[key] !== void 0);
|
|
5283
6273
|
state.explicitSignalStrength += suppliedKeys.length;
|
|
5284
|
-
const matched =
|
|
6274
|
+
const matched = matchRuntimeTemplate(original, concrete) ?? {};
|
|
6275
|
+
const fromSelectedBinding = kind === "servicePath" && inputs.routing.selectedBinding !== void 0;
|
|
5285
6276
|
for (const [key, value] of Object.entries(matched)) {
|
|
5286
6277
|
addDerivation(state, key, value, {
|
|
5287
|
-
sourceKind: `${signalCode(kind)}_template`,
|
|
6278
|
+
sourceKind: fromSelectedBinding ? `selected_binding.${signalCode(kind)}_template` : `${signalCode(kind)}_template`,
|
|
5288
6279
|
value,
|
|
5289
|
-
rule: "exact_template_match",
|
|
5290
|
-
template: original
|
|
6280
|
+
rule: fromSelectedBinding ? "exact_selected_binding_template_match" : "exact_template_match",
|
|
6281
|
+
template: original,
|
|
6282
|
+
sourceRepo: fromSelectedBinding ? inputs.routing.selectedBinding?.repoName : void 0,
|
|
6283
|
+
sourceFile: fromSelectedBinding ? inputs.routing.selectedBinding?.sourceFile : void 0,
|
|
6284
|
+
sourceLine: fromSelectedBinding ? inputs.routing.selectedBinding?.sourceLine : void 0,
|
|
6285
|
+
selection: fromSelectedBinding ? "selected_binding" : "call_evidence"
|
|
5291
6286
|
});
|
|
5292
6287
|
}
|
|
5293
6288
|
addScore(state, score, `${signalCode(kind)}_template_match`);
|
|
@@ -5305,7 +6300,7 @@ function applyReferenceSignal(state, inputs, references, kind) {
|
|
|
5305
6300
|
}
|
|
5306
6301
|
let matchedSignal = false;
|
|
5307
6302
|
for (const { reference, concrete } of values) {
|
|
5308
|
-
const matched =
|
|
6303
|
+
const matched = matchRuntimeTemplate(original, concrete);
|
|
5309
6304
|
if (!matched) continue;
|
|
5310
6305
|
matchedSignal = true;
|
|
5311
6306
|
for (const [key, value] of Object.entries(matched)) {
|
|
@@ -5322,9 +6317,9 @@ function applyReferenceSignal(state, inputs, references, kind) {
|
|
|
5322
6317
|
addScore(state, 0.2, `${kind}_template_match`);
|
|
5323
6318
|
}
|
|
5324
6319
|
}
|
|
5325
|
-
function applyUniqueIdentityEvidence(db,
|
|
5326
|
-
for (const derivation of uniqueIdentityDerivations(db,
|
|
5327
|
-
const candidate =
|
|
6320
|
+
function applyUniqueIdentityEvidence(db, candidates2, inputs) {
|
|
6321
|
+
for (const derivation of uniqueIdentityDerivations(db, candidates2, inputs.original)) {
|
|
6322
|
+
const candidate = candidates2.find((item) => item.candidateOperationId === derivation.operationId);
|
|
5328
6323
|
if (!candidate) continue;
|
|
5329
6324
|
addDerivation(candidate, derivation.key, derivation.value, derivation.provenance);
|
|
5330
6325
|
addScore(candidate, 0.2, "exact_identity_template_match");
|
|
@@ -5362,8 +6357,8 @@ function uniqueProvenance(rows2) {
|
|
|
5362
6357
|
return true;
|
|
5363
6358
|
});
|
|
5364
6359
|
}
|
|
5365
|
-
function finalizeCandidates(
|
|
5366
|
-
for (const candidate of
|
|
6360
|
+
function finalizeCandidates(candidates2, order) {
|
|
6361
|
+
for (const candidate of candidates2) {
|
|
5367
6362
|
candidate.missingVariables = order.filter((key) => candidate.completeVariables[key] === void 0);
|
|
5368
6363
|
candidate.viable = candidate.rejectedReasons.length === 0;
|
|
5369
6364
|
candidate.rejected = !candidate.viable;
|
|
@@ -5375,15 +6370,17 @@ function finalizeCandidates(candidates, order) {
|
|
|
5375
6370
|
candidate.cli = candidate.missingVariables.length === 0 && candidate.viable ? cliFor(candidate.completeVariables, order) : void 0;
|
|
5376
6371
|
}
|
|
5377
6372
|
}
|
|
5378
|
-
function stableRank(
|
|
5379
|
-
return [...
|
|
6373
|
+
function stableRank(candidates2) {
|
|
6374
|
+
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
6375
|
}
|
|
5381
|
-
function inferenceDecision(
|
|
5382
|
-
const viable =
|
|
6376
|
+
function inferenceDecision(candidates2) {
|
|
6377
|
+
const viable = candidates2.filter((candidate) => candidate.viable);
|
|
5383
6378
|
const first = viable[0];
|
|
5384
6379
|
const second = viable[1];
|
|
5385
6380
|
if (!first || first.missingVariables.length > 0)
|
|
5386
6381
|
return { status: "unresolved", reason: "missing_required_runtime_variable" };
|
|
6382
|
+
if (first.inferenceBlockReasons.length > 0)
|
|
6383
|
+
return { status: "unresolved", reason: first.inferenceBlockReasons[0] };
|
|
5387
6384
|
if (first.score < 0.85)
|
|
5388
6385
|
return { status: "unresolved", reason: "candidate_score_below_inference_threshold" };
|
|
5389
6386
|
const scoreGap = second ? Number((first.score - second.score).toFixed(12)) : void 0;
|
|
@@ -5402,57 +6399,90 @@ function inferenceDecision(candidates) {
|
|
|
5402
6399
|
};
|
|
5403
6400
|
}
|
|
5404
6401
|
function boundedCandidate(candidate, limit) {
|
|
5405
|
-
const
|
|
5406
|
-
Object.entries(candidate.derivationProvenance).sort(([left], [right]) => left.localeCompare(right)).map(([key, rows2]) => [key, rows2
|
|
6402
|
+
const provenanceProjections = Object.fromEntries(
|
|
6403
|
+
Object.entries(candidate.derivationProvenance).sort(([left], [right]) => left.localeCompare(right)).map(([key, rows2]) => [key, projectBounded(rows2, compareProvenance, limit)])
|
|
5407
6404
|
);
|
|
5408
|
-
const
|
|
6405
|
+
const derivationProvenance = Object.fromEntries(Object.entries(provenanceProjections).map(([key, projection]) => [key, projection.items]));
|
|
6406
|
+
const derivationProvenanceCounts = Object.fromEntries(Object.entries(provenanceProjections).map(([key, projection]) => [key, {
|
|
6407
|
+
provenanceCount: projection.totalCount,
|
|
6408
|
+
shownProvenanceCount: projection.shownCount,
|
|
6409
|
+
omittedProvenanceCount: projection.omittedCount
|
|
6410
|
+
}]));
|
|
6411
|
+
const conflicts = projectBounded(candidate.conflicts, compareConflict, limit);
|
|
6412
|
+
return {
|
|
6413
|
+
...candidate,
|
|
6414
|
+
derivationProvenance,
|
|
6415
|
+
derivationProvenanceCounts,
|
|
6416
|
+
conflicts: conflicts.items.map(boundedConflict),
|
|
6417
|
+
conflictCount: conflicts.totalCount,
|
|
6418
|
+
shownConflictCount: conflicts.shownCount,
|
|
6419
|
+
omittedConflictCount: conflicts.omittedCount
|
|
6420
|
+
};
|
|
6421
|
+
}
|
|
6422
|
+
function compareProvenance(left, right) {
|
|
6423
|
+
return left.sourceKind.localeCompare(right.sourceKind) || String(left.matchedName ?? "").localeCompare(String(right.matchedName ?? "")) || left.value.localeCompare(right.value);
|
|
6424
|
+
}
|
|
6425
|
+
function compareConflict(left, right) {
|
|
6426
|
+
return left.key.localeCompare(right.key) || left.reason.localeCompare(right.reason) || left.values.join("\0").localeCompare(right.values.join("\0"));
|
|
6427
|
+
}
|
|
6428
|
+
function boundedConflict(conflict) {
|
|
6429
|
+
const sources = projectBounded(conflict.sources, (left, right) => left.localeCompare(right));
|
|
6430
|
+
return {
|
|
5409
6431
|
...conflict,
|
|
5410
|
-
sources:
|
|
5411
|
-
|
|
5412
|
-
|
|
6432
|
+
sources: sources.items,
|
|
6433
|
+
sourceCount: sources.totalCount,
|
|
6434
|
+
shownSourceCount: sources.shownCount,
|
|
6435
|
+
omittedSourceCount: sources.omittedCount
|
|
6436
|
+
};
|
|
5413
6437
|
}
|
|
5414
|
-
function applyModeState(
|
|
5415
|
-
const selectedId = mode === "infer" && inference.status === "resolved" ?
|
|
5416
|
-
for (const candidate of
|
|
6438
|
+
function applyModeState(candidates2, mode, inference) {
|
|
6439
|
+
const selectedId = mode === "infer" && inference.status === "resolved" ? numberValue7(inference.candidateOperationId) : void 0;
|
|
6440
|
+
for (const candidate of candidates2) {
|
|
5417
6441
|
candidate.selected = selectedId === candidate.candidateOperationId;
|
|
5418
6442
|
candidate.exploratory = mode === "candidates" && candidate.viable;
|
|
5419
6443
|
}
|
|
5420
6444
|
}
|
|
5421
|
-
function suggestedVarSets(
|
|
6445
|
+
function suggestedVarSets(candidates2, order, limit) {
|
|
5422
6446
|
const seen = /* @__PURE__ */ new Set();
|
|
5423
6447
|
const rows2 = [];
|
|
5424
|
-
for (const candidate of
|
|
6448
|
+
for (const candidate of candidates2) {
|
|
5425
6449
|
if (!candidate.cli || candidate.missingVariables.length > 0) continue;
|
|
5426
6450
|
if (seen.has(candidate.cli)) continue;
|
|
5427
6451
|
seen.add(candidate.cli);
|
|
5428
6452
|
rows2.push({ variables: orderedVariables(candidate.completeVariables, order), cli: candidate.cli });
|
|
5429
|
-
if (rows2.length >= limit) break;
|
|
5430
6453
|
}
|
|
5431
|
-
return rows2;
|
|
6454
|
+
return projectBounded(rows2, (left, right) => left.cli.localeCompare(right.cli), limit);
|
|
5432
6455
|
}
|
|
5433
6456
|
function hasResolvedImplementation(db, operationId) {
|
|
5434
6457
|
return Boolean(db.prepare(
|
|
5435
6458
|
"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
6459
|
).get(String(operationId)));
|
|
5437
6460
|
}
|
|
5438
|
-
function templatesFromEvidence(evidence,
|
|
6461
|
+
function templatesFromEvidence(evidence, routing) {
|
|
6462
|
+
const selected = routing.selectedBinding;
|
|
5439
6463
|
return {
|
|
5440
|
-
servicePath: substitutionSignal(evidence, "servicePath",
|
|
5441
|
-
operationPath: substitutionSignal(evidence, "operationPath",
|
|
5442
|
-
alias: substitutionSignal(
|
|
6464
|
+
servicePath: selected?.servicePath ?? substitutionSignal(evidence, "servicePath", "original"),
|
|
6465
|
+
operationPath: substitutionSignal(evidence, "operationPath", "original"),
|
|
6466
|
+
alias: selected?.aliasExpr ?? selected?.alias ?? substitutionSignal(
|
|
5443
6467
|
evidence,
|
|
5444
6468
|
evidence.serviceAliasExpr !== void 0 ? "serviceAliasExpr" : "serviceAlias",
|
|
5445
|
-
|
|
6469
|
+
"original"
|
|
5446
6470
|
),
|
|
5447
|
-
destination: substitutionSignal(evidence, "destination",
|
|
6471
|
+
destination: selected?.destination ?? substitutionSignal(evidence, "destination", "original")
|
|
5448
6472
|
};
|
|
5449
6473
|
}
|
|
5450
|
-
function
|
|
5451
|
-
const
|
|
5452
|
-
return
|
|
6474
|
+
function effectiveTemplates(templates, supplied) {
|
|
6475
|
+
const operationPath = applyVariables(templates.operationPath, supplied);
|
|
6476
|
+
return {
|
|
6477
|
+
servicePath: applyVariables(templates.servicePath, supplied),
|
|
6478
|
+
operationPath: normalizeODataOperationInvocationPath(operationPath)?.normalizedOperationPath ?? operationPath,
|
|
6479
|
+
alias: applyVariables(templates.alias, supplied),
|
|
6480
|
+
destination: applyVariables(templates.destination, supplied)
|
|
6481
|
+
};
|
|
5453
6482
|
}
|
|
5454
|
-
function
|
|
5455
|
-
|
|
6483
|
+
function substitutionSignal(evidence, key, phase) {
|
|
6484
|
+
const substitution = record3(record3(evidence.runtimeSubstitutions)[key]);
|
|
6485
|
+
return stringValue9(substitution[phase]) ?? stringValue9(evidence[key]);
|
|
5456
6486
|
}
|
|
5457
6487
|
function placeholderSources(templates) {
|
|
5458
6488
|
const sources = {};
|
|
@@ -5472,31 +6502,13 @@ function variableOrder(templates, required) {
|
|
|
5472
6502
|
].flatMap((value) => extractPlaceholders(value));
|
|
5473
6503
|
return [.../* @__PURE__ */ new Set([...ordered, ...required])];
|
|
5474
6504
|
}
|
|
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;
|
|
6505
|
+
function referenceMatchesCandidate(reference, servicePath) {
|
|
6506
|
+
return matchRuntimeTemplate(reference.servicePath, servicePath) !== void 0;
|
|
5490
6507
|
}
|
|
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))}`;
|
|
6508
|
+
function referenceMatchesSelectedAlias(reference, selected) {
|
|
6509
|
+
if (reference.selection !== "selected_binding_require") return true;
|
|
6510
|
+
const template = selected?.aliasExpr ?? selected?.alias;
|
|
6511
|
+
return matchRuntimeTemplate(template, reference.alias) !== void 0;
|
|
5500
6512
|
}
|
|
5501
6513
|
function cliFor(variables, order) {
|
|
5502
6514
|
return order.filter((key) => variables[key] !== void 0).map((key) => `--var ${shellArgument(`${key}=${variables[key]}`)}`).join(" ");
|
|
@@ -5530,39 +6542,252 @@ function addInferenceBlock(state, reason) {
|
|
|
5530
6542
|
function requiredSuppliedVariables(inputs) {
|
|
5531
6543
|
return Object.fromEntries(inputs.required.flatMap((key) => inputs.supplied[key] === void 0 ? [] : [[key, inputs.supplied[key]]]));
|
|
5532
6544
|
}
|
|
6545
|
+
function routingEvidence2(routing) {
|
|
6546
|
+
const binding = routing.selectedBinding;
|
|
6547
|
+
return {
|
|
6548
|
+
outboundCallId: routing.outboundCallId,
|
|
6549
|
+
callerRepoId: routing.callerRepoId,
|
|
6550
|
+
callerRepo: routing.callerRepo,
|
|
6551
|
+
selectedBindingId: routing.selectedBindingId,
|
|
6552
|
+
bindingResolutionStatus: routing.bindingResolutionStatus,
|
|
6553
|
+
selectedBinding: binding ? {
|
|
6554
|
+
bindingId: binding.bindingId,
|
|
6555
|
+
alias: binding.alias,
|
|
6556
|
+
aliasExpr: binding.aliasExpr,
|
|
6557
|
+
destination: binding.destination,
|
|
6558
|
+
destinationExpr: binding.destination,
|
|
6559
|
+
servicePath: binding.servicePath,
|
|
6560
|
+
servicePathExpr: binding.servicePath,
|
|
6561
|
+
sourceFile: binding.sourceFile,
|
|
6562
|
+
sourceLine: binding.sourceLine,
|
|
6563
|
+
helperChain: binding.helperChain
|
|
6564
|
+
} : void 0,
|
|
6565
|
+
bindingAlternativeCount: routing.bindingAlternativeCount,
|
|
6566
|
+
shownBindingAlternativeCount: routing.shownBindingAlternativeCount,
|
|
6567
|
+
omittedBindingAlternativeCount: routing.omittedBindingAlternativeCount,
|
|
6568
|
+
bindingAlternatives: routing.bindingAlternatives,
|
|
6569
|
+
fallbackUsed: routing.fallbackUsed
|
|
6570
|
+
};
|
|
6571
|
+
}
|
|
5533
6572
|
function signalCode(kind) {
|
|
5534
6573
|
return kind === "servicePath" ? "service_path" : "operation_path";
|
|
5535
6574
|
}
|
|
5536
|
-
function
|
|
6575
|
+
function record3(value) {
|
|
5537
6576
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
5538
6577
|
}
|
|
5539
6578
|
function stringRecord(value) {
|
|
5540
|
-
const entries = Object.entries(
|
|
6579
|
+
const entries = Object.entries(record3(value)).filter((entry) => typeof entry[1] === "string").sort(([left], [right]) => left.localeCompare(right));
|
|
5541
6580
|
return Object.fromEntries(entries);
|
|
5542
6581
|
}
|
|
5543
|
-
function
|
|
6582
|
+
function stringValue9(value) {
|
|
5544
6583
|
return typeof value === "string" ? value : void 0;
|
|
5545
6584
|
}
|
|
5546
|
-
function
|
|
6585
|
+
function numberValue7(value) {
|
|
5547
6586
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5548
6587
|
}
|
|
5549
|
-
function
|
|
6588
|
+
function stringArray4(value) {
|
|
5550
6589
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
5551
6590
|
}
|
|
5552
6591
|
function nonEmptyStrings(value, fallback) {
|
|
5553
|
-
const values =
|
|
6592
|
+
const values = stringArray4(value).filter((item) => item.length > 0);
|
|
5554
6593
|
return values.length > 0 ? values : fallback;
|
|
5555
6594
|
}
|
|
5556
6595
|
function isConcrete(value) {
|
|
5557
6596
|
return typeof value === "string" && value.length > 0 && extractPlaceholders(value).length === 0;
|
|
5558
6597
|
}
|
|
5559
|
-
|
|
5560
|
-
|
|
6598
|
+
|
|
6599
|
+
// src/trace/006-contextual-projection.ts
|
|
6600
|
+
function boundedContextCandidates(values) {
|
|
6601
|
+
const candidates2 = values.flatMap((value) => {
|
|
6602
|
+
return isRecord5(value) ? [value] : [];
|
|
6603
|
+
});
|
|
6604
|
+
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));
|
|
6605
|
+
return {
|
|
6606
|
+
candidates: projection.items,
|
|
6607
|
+
candidateCount: projection.totalCount,
|
|
6608
|
+
shownCandidateCount: projection.shownCount,
|
|
6609
|
+
omittedCandidateCount: projection.omittedCount
|
|
6610
|
+
};
|
|
6611
|
+
}
|
|
6612
|
+
function isRecord5(value) {
|
|
6613
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
6614
|
+
}
|
|
6615
|
+
|
|
6616
|
+
// src/trace/008-contextual-runtime-state.ts
|
|
6617
|
+
function dynamicMissingReason(keys) {
|
|
6618
|
+
const missing = normalizedKeys(keys);
|
|
6619
|
+
return missing.length > 0 ? `Dynamic target is missing runtime variables: ${missing.join(", ")}` : "Dynamic target still requires runtime variables";
|
|
6620
|
+
}
|
|
6621
|
+
function isStructuralContextualBlocker(state) {
|
|
6622
|
+
return state?.category === "ambiguous_binding" || state?.category === "ambiguous_operation" || state?.category === "no_matching_operation" || state?.category === "other_blocker";
|
|
6623
|
+
}
|
|
6624
|
+
function contextualRuntimeResolution(db, call, binding, workspaceId, persistedRows = []) {
|
|
6625
|
+
if (!binding || call.call_type !== "remote_action" || call.operation_path_expr === void 0 || call.operation_path_expr === null)
|
|
6626
|
+
return { state: { category: "none" } };
|
|
6627
|
+
if (binding.resolutionStatus === "ambiguous")
|
|
6628
|
+
return ambiguousBindingResolution(binding);
|
|
6629
|
+
return selectedBindingResolution(db, call, binding, workspaceId, persistedRows);
|
|
6630
|
+
}
|
|
6631
|
+
function ambiguousBindingResolution(binding) {
|
|
6632
|
+
const candidates2 = boundedContextCandidates(binding.bindingCandidates ?? []);
|
|
6633
|
+
const state = {
|
|
6634
|
+
category: "ambiguous_binding",
|
|
6635
|
+
message: "Ambiguous contextual service binding candidates",
|
|
6636
|
+
resolutionStatus: "ambiguous"
|
|
6637
|
+
};
|
|
6638
|
+
return {
|
|
6639
|
+
evidence: {
|
|
6640
|
+
contextualServiceBindingAttempted: true,
|
|
6641
|
+
contextualBinding: {
|
|
6642
|
+
source: binding.source,
|
|
6643
|
+
status: "tied",
|
|
6644
|
+
candidates: candidates2.candidates,
|
|
6645
|
+
candidateCount: candidates2.candidateCount,
|
|
6646
|
+
shownCandidateCount: candidates2.shownCandidateCount,
|
|
6647
|
+
omittedCandidateCount: candidates2.omittedCandidateCount
|
|
6648
|
+
},
|
|
6649
|
+
contextualResolutionStatus: "ambiguous",
|
|
6650
|
+
contextualCandidateCount: candidates2.candidateCount,
|
|
6651
|
+
contextualPreSubstitutionState: historicalState(state)
|
|
6652
|
+
},
|
|
6653
|
+
state
|
|
6654
|
+
};
|
|
6655
|
+
}
|
|
6656
|
+
function selectedBindingResolution(db, call, binding, workspaceId, persistedRows) {
|
|
6657
|
+
const normalized = normalizeODataOperationInvocationPath(
|
|
6658
|
+
String(call.operation_path_expr)
|
|
6659
|
+
);
|
|
6660
|
+
const operationPath = normalized?.normalizedOperationPath ?? withLeadingSlash(String(call.operation_path_expr));
|
|
6661
|
+
const servicePath = binding.effectiveServicePath ?? binding.servicePathExpr ?? binding.requireServicePath;
|
|
6662
|
+
const destination = binding.effectiveDestination ?? binding.destinationExpr ?? binding.requireDestination;
|
|
6663
|
+
const resolution = resolveOperation(db, {
|
|
6664
|
+
servicePath,
|
|
6665
|
+
operationPath,
|
|
6666
|
+
alias: binding.aliasExpr ?? binding.alias,
|
|
6667
|
+
destination,
|
|
6668
|
+
hasExplicitOverride: true,
|
|
6669
|
+
isDynamic: false
|
|
6670
|
+
}, workspaceId);
|
|
6671
|
+
const state = stateForResolution(resolution);
|
|
6672
|
+
const evidence = contextualEvidence(
|
|
6673
|
+
binding,
|
|
6674
|
+
normalized,
|
|
6675
|
+
operationPath,
|
|
6676
|
+
servicePath,
|
|
6677
|
+
destination,
|
|
6678
|
+
resolution,
|
|
6679
|
+
state
|
|
6680
|
+
);
|
|
6681
|
+
if (!resolution.target) return { evidence, state };
|
|
6682
|
+
const resolvedEvidence = {
|
|
6683
|
+
...evidence,
|
|
6684
|
+
contextualServiceBindingSelected: true,
|
|
6685
|
+
targetRepo: resolution.target.repoName,
|
|
6686
|
+
targetServicePath: resolution.target.servicePath,
|
|
6687
|
+
targetOperationPath: resolution.target.operationPath,
|
|
6688
|
+
targetOperation: resolution.target.operationName
|
|
6689
|
+
};
|
|
6690
|
+
if (persistedRows.some((row) => row.status === "resolved"))
|
|
6691
|
+
return { evidence: { ...resolvedEvidence, contextualPreservedPersistedResolvedEdge: true }, state };
|
|
6692
|
+
return {
|
|
6693
|
+
row: {
|
|
6694
|
+
id: -call.id,
|
|
6695
|
+
edge_type: "REMOTE_CALL_RESOLVES_TO_OPERATION",
|
|
6696
|
+
from_id: String(call.id),
|
|
6697
|
+
to_kind: "operation",
|
|
6698
|
+
to_id: String(resolution.target.operationId),
|
|
6699
|
+
confidence: resolution.target.score,
|
|
6700
|
+
evidence_json: JSON.stringify(resolvedEvidence),
|
|
6701
|
+
status: "resolved"
|
|
6702
|
+
},
|
|
6703
|
+
evidence: resolvedEvidence,
|
|
6704
|
+
state
|
|
6705
|
+
};
|
|
6706
|
+
}
|
|
6707
|
+
function contextualEvidence(binding, normalized, operationPath, servicePath, destination, resolution, state) {
|
|
6708
|
+
const candidates2 = boundedContextCandidates(resolution.candidates);
|
|
6709
|
+
return {
|
|
6710
|
+
contextualServiceBindingAttempted: true,
|
|
6711
|
+
contextualBinding: bindingEvidence2(binding),
|
|
6712
|
+
operationPath,
|
|
6713
|
+
rawOperationPath: normalized?.rawOperationPath,
|
|
6714
|
+
normalizedOperationPath: normalized?.wasInvocation ? normalized.normalizedOperationPath : void 0,
|
|
6715
|
+
invocationArgumentPlaceholderKeys: normalized?.invocationArgumentPlaceholderKeys.length ? normalized.invocationArgumentPlaceholderKeys : void 0,
|
|
6716
|
+
servicePath,
|
|
6717
|
+
serviceAlias: binding.alias,
|
|
6718
|
+
serviceAliasExpr: binding.aliasExpr,
|
|
6719
|
+
destination,
|
|
6720
|
+
requireServicePath: binding.requireServicePath,
|
|
6721
|
+
requireDestination: binding.requireDestination,
|
|
6722
|
+
effectiveServicePath: binding.effectiveServicePath,
|
|
6723
|
+
effectiveDestination: binding.effectiveDestination,
|
|
6724
|
+
contextualResolutionStatus: resolution.status,
|
|
6725
|
+
contextualCandidateCount: candidates2.candidateCount,
|
|
6726
|
+
shownContextualCandidateCount: candidates2.shownCandidateCount,
|
|
6727
|
+
omittedContextualCandidateCount: candidates2.omittedCandidateCount,
|
|
6728
|
+
candidates: candidates2.candidates,
|
|
6729
|
+
contextualResolutionReasons: resolution.reasons,
|
|
6730
|
+
resolutionReasons: resolution.reasons,
|
|
6731
|
+
contextualPreSubstitutionState: historicalState(state)
|
|
6732
|
+
};
|
|
6733
|
+
}
|
|
6734
|
+
function bindingEvidence2(binding) {
|
|
6735
|
+
return {
|
|
6736
|
+
source: binding.source,
|
|
6737
|
+
callerArgument: binding.callerArgument,
|
|
6738
|
+
callerProperty: binding.callerProperty,
|
|
6739
|
+
calleeParameter: binding.calleeParameter,
|
|
6740
|
+
calleeReceiver: binding.calleeReceiver,
|
|
6741
|
+
callerSite: binding.callerSite,
|
|
6742
|
+
calleeSite: binding.calleeSite,
|
|
6743
|
+
bindingSourceFile: binding.sourceFile,
|
|
6744
|
+
bindingSourceLine: binding.sourceLine,
|
|
6745
|
+
alias: binding.alias,
|
|
6746
|
+
aliasExpr: binding.aliasExpr,
|
|
6747
|
+
requireServicePath: binding.requireServicePath,
|
|
6748
|
+
requireDestination: binding.requireDestination,
|
|
6749
|
+
effectiveServicePath: binding.effectiveServicePath,
|
|
6750
|
+
effectiveDestination: binding.effectiveDestination
|
|
6751
|
+
};
|
|
6752
|
+
}
|
|
6753
|
+
function stateForResolution(resolution) {
|
|
6754
|
+
if (resolution.status === "resolved") return { category: "none" };
|
|
6755
|
+
if (resolution.status === "dynamic") {
|
|
6756
|
+
const missingVariables = missingVariableKeys(resolution.reasons);
|
|
6757
|
+
return {
|
|
6758
|
+
category: "dynamic_missing",
|
|
6759
|
+
message: dynamicMissingReason(missingVariables),
|
|
6760
|
+
missingVariables,
|
|
6761
|
+
resolutionStatus: resolution.status
|
|
6762
|
+
};
|
|
6763
|
+
}
|
|
6764
|
+
if (resolution.status === "ambiguous") return {
|
|
6765
|
+
category: "ambiguous_operation",
|
|
6766
|
+
message: "Ambiguous contextual operation candidates",
|
|
6767
|
+
resolutionStatus: resolution.status
|
|
6768
|
+
};
|
|
6769
|
+
return {
|
|
6770
|
+
category: resolution.status === "unresolved" ? "no_matching_operation" : "other_blocker",
|
|
6771
|
+
message: resolution.status === "unresolved" ? "No contextual operation candidate matched" : "Contextual operation resolution is blocked",
|
|
6772
|
+
resolutionStatus: resolution.status
|
|
6773
|
+
};
|
|
6774
|
+
}
|
|
6775
|
+
function historicalState(state) {
|
|
6776
|
+
return { ...state, phase: "before_runtime_substitution" };
|
|
6777
|
+
}
|
|
6778
|
+
function missingVariableKeys(reasons) {
|
|
6779
|
+
return normalizedKeys(reasons.flatMap((reason) => reason.startsWith("missing_variable:") ? [reason.slice("missing_variable:".length)] : []));
|
|
6780
|
+
}
|
|
6781
|
+
function normalizedKeys(keys) {
|
|
6782
|
+
return [...new Set(keys.filter((key) => key.length > 0))].sort();
|
|
6783
|
+
}
|
|
6784
|
+
function withLeadingSlash(value) {
|
|
6785
|
+
return value.startsWith("/") ? value : `/${value}`;
|
|
5561
6786
|
}
|
|
5562
6787
|
|
|
5563
6788
|
// src/trace/evidence.ts
|
|
5564
|
-
function baseTraceEvidence(row, call, persistedEvidence,
|
|
5565
|
-
const evidence = { ...persistedEvidence, ...
|
|
6789
|
+
function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence2) {
|
|
6790
|
+
const evidence = { ...persistedEvidence, ...contextualEvidence2 ?? {} };
|
|
5566
6791
|
return {
|
|
5567
6792
|
...evidence,
|
|
5568
6793
|
graphEdgeId: row.id,
|
|
@@ -5576,20 +6801,21 @@ function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence) {
|
|
|
5576
6801
|
file: call.source_file,
|
|
5577
6802
|
line: call.source_line,
|
|
5578
6803
|
persistedTarget: { kind: row.to_kind, id: row.to_id },
|
|
5579
|
-
contextualResolutionParticipated: Boolean(
|
|
6804
|
+
contextualResolutionParticipated: Boolean(contextualEvidence2?.contextualServiceBindingAttempted),
|
|
5580
6805
|
persistedResolution: persistedResolution(row)
|
|
5581
6806
|
};
|
|
5582
6807
|
}
|
|
5583
|
-
function runtimeResolution(db, row, evidence, options, workspaceId,
|
|
6808
|
+
function runtimeResolution(db, row, evidence, options, workspaceId, contextualState) {
|
|
5584
6809
|
const dynamicMode = options.dynamicMode ?? "strict";
|
|
5585
6810
|
const candidateCap = positiveCandidateCap(options.maxDynamicCandidates);
|
|
6811
|
+
const boundedEvidence = boundCandidateLikeEvidence(evidence, candidateCap);
|
|
5586
6812
|
if (!isDynamicRemoteOperationEdge(row, evidence))
|
|
5587
6813
|
return unchangedRuntimeResolution(
|
|
5588
6814
|
row,
|
|
5589
|
-
boundDynamicEvidence(
|
|
5590
|
-
|
|
6815
|
+
boundDynamicEvidence(boundedEvidence, candidateCap),
|
|
6816
|
+
contextualState
|
|
5591
6817
|
);
|
|
5592
|
-
const substituted = evidenceWithRuntimeVariables(
|
|
6818
|
+
const substituted = evidenceWithRuntimeVariables(boundedEvidence, options.vars);
|
|
5593
6819
|
const analysis = analyzeDynamicTargetCandidates(
|
|
5594
6820
|
db,
|
|
5595
6821
|
substituted,
|
|
@@ -5601,44 +6827,98 @@ function runtimeResolution(db, row, evidence, options, workspaceId, contextualUn
|
|
|
5601
6827
|
analysis ? evidenceWithDynamicAnalysis(substituted, analysis) : substituted,
|
|
5602
6828
|
candidateCap
|
|
5603
6829
|
);
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
6830
|
+
const appliedRuntimeValues = hasApplicableRuntimeVariables(evidence, options.vars);
|
|
6831
|
+
const analyzed = analyzedRuntimeResolution(
|
|
6832
|
+
row,
|
|
6833
|
+
enriched,
|
|
6834
|
+
analysis,
|
|
6835
|
+
dynamicMode,
|
|
6836
|
+
appliedRuntimeValues,
|
|
6837
|
+
contextualState
|
|
6838
|
+
);
|
|
6839
|
+
if (analyzed) return analyzed;
|
|
6840
|
+
if (!appliedRuntimeValues) {
|
|
6841
|
+
const unresolvedReason = contextualReason(contextualState) ?? row.unresolved_reason;
|
|
6842
|
+
const withSections = withEffectiveResolution(
|
|
6843
|
+
enriched,
|
|
6844
|
+
row,
|
|
6845
|
+
unresolvedReason,
|
|
6846
|
+
void 0,
|
|
6847
|
+
contextualState
|
|
6848
|
+
);
|
|
6849
|
+
return { row, evidence: withSections, unresolvedReason };
|
|
6850
|
+
}
|
|
6851
|
+
return resolveSuppliedRuntimeOperation(db, row, enriched, workspaceId, contextualState);
|
|
6852
|
+
}
|
|
6853
|
+
function analyzedRuntimeResolution(row, evidence, analysis, dynamicMode, appliedRuntimeValues, contextualState) {
|
|
6854
|
+
if (analysis && analysis.viableCandidateCount === 0 && Object.keys(analysis.appliedSuppliedVariables).length > 0)
|
|
6855
|
+
return noCandidateRuntimeResolution(row, evidence, contextualState);
|
|
6856
|
+
const inferred = dynamicMode === "infer" ? inferredTarget(analysis) : void 0;
|
|
6857
|
+
if (inferred && !isStructuralContextualBlocker(contextualState))
|
|
6858
|
+
return resolvedRuntimeResolution(row, evidence, inferred, inferred.reasons);
|
|
6859
|
+
if (analysis && analysis.missingVariables.length > 0 && appliedRuntimeValues) {
|
|
6860
|
+
const unresolvedReason = dynamicMissingReason(analysis.missingVariables);
|
|
6861
|
+
return {
|
|
6862
|
+
row,
|
|
6863
|
+
evidence: withEffectiveResolution(
|
|
6864
|
+
evidence,
|
|
6865
|
+
row,
|
|
6866
|
+
unresolvedReason,
|
|
6867
|
+
void 0,
|
|
6868
|
+
contextualState
|
|
6869
|
+
),
|
|
6870
|
+
unresolvedReason
|
|
6871
|
+
};
|
|
6872
|
+
}
|
|
6873
|
+
return isStructuralContextualBlocker(contextualState) ? unchangedRuntimeResolution(row, evidence, contextualState) : void 0;
|
|
6874
|
+
}
|
|
6875
|
+
function resolveSuppliedRuntimeOperation(db, row, evidence, workspaceId, contextualState) {
|
|
6876
|
+
const resolution = resolveRuntimeOperation(db, evidence, workspaceId);
|
|
5617
6877
|
if (resolution.target)
|
|
5618
6878
|
return resolvedRuntimeResolution(
|
|
5619
6879
|
row,
|
|
5620
|
-
|
|
6880
|
+
evidence,
|
|
5621
6881
|
resolution.target,
|
|
5622
6882
|
resolution.reasons
|
|
5623
6883
|
);
|
|
5624
|
-
if (analysis && analysis.viableCandidateCount === 0 && Object.keys(analysis.appliedSuppliedVariables).length > 0)
|
|
5625
|
-
return noCandidateRuntimeResolution(row, enriched);
|
|
5626
6884
|
const unresolvedReason = runtimeUnresolvedReason(resolution);
|
|
5627
|
-
return {
|
|
6885
|
+
return {
|
|
6886
|
+
row,
|
|
6887
|
+
evidence: withEffectiveResolution(
|
|
6888
|
+
evidence,
|
|
6889
|
+
row,
|
|
6890
|
+
unresolvedReason,
|
|
6891
|
+
resolution,
|
|
6892
|
+
contextualState
|
|
6893
|
+
),
|
|
6894
|
+
unresolvedReason
|
|
6895
|
+
};
|
|
5628
6896
|
}
|
|
5629
|
-
function unchangedRuntimeResolution(row, evidence,
|
|
5630
|
-
const unresolvedReason =
|
|
6897
|
+
function unchangedRuntimeResolution(row, evidence, contextualState) {
|
|
6898
|
+
const unresolvedReason = contextualReason(contextualState) ?? row.unresolved_reason;
|
|
5631
6899
|
return {
|
|
5632
6900
|
row,
|
|
5633
|
-
evidence: withEffectiveResolution(
|
|
6901
|
+
evidence: withEffectiveResolution(
|
|
6902
|
+
evidence,
|
|
6903
|
+
row,
|
|
6904
|
+
unresolvedReason,
|
|
6905
|
+
void 0,
|
|
6906
|
+
contextualState
|
|
6907
|
+
),
|
|
5634
6908
|
unresolvedReason
|
|
5635
6909
|
};
|
|
5636
6910
|
}
|
|
5637
|
-
function noCandidateRuntimeResolution(row, evidence) {
|
|
6911
|
+
function noCandidateRuntimeResolution(row, evidence, contextualState) {
|
|
5638
6912
|
const unresolvedReason = "No candidate remained after runtime substitution";
|
|
5639
6913
|
return {
|
|
5640
6914
|
row,
|
|
5641
|
-
evidence: withEffectiveResolution(
|
|
6915
|
+
evidence: withEffectiveResolution(
|
|
6916
|
+
evidence,
|
|
6917
|
+
row,
|
|
6918
|
+
unresolvedReason,
|
|
6919
|
+
void 0,
|
|
6920
|
+
contextualState
|
|
6921
|
+
),
|
|
5642
6922
|
unresolvedReason
|
|
5643
6923
|
};
|
|
5644
6924
|
}
|
|
@@ -5714,17 +6994,17 @@ function runtimeDiagnosticTotals(edges) {
|
|
|
5714
6994
|
rejectedCandidateCount += numeric(exploration.rejectedCandidateCount);
|
|
5715
6995
|
appendBounded(
|
|
5716
6996
|
candidateSuggestions,
|
|
5717
|
-
|
|
6997
|
+
recordArray3(edge.evidence.dynamicTargetCandidateSuggestions),
|
|
5718
6998
|
maxCandidates
|
|
5719
6999
|
);
|
|
5720
7000
|
appendBounded(
|
|
5721
7001
|
rejectedCandidates,
|
|
5722
|
-
|
|
7002
|
+
recordArray3(exploration.rejectedCandidates),
|
|
5723
7003
|
maxCandidates
|
|
5724
7004
|
);
|
|
5725
7005
|
appendBounded(
|
|
5726
7006
|
suggestedVarSets2,
|
|
5727
|
-
|
|
7007
|
+
recordArray3(exploration.suggestedVarSets),
|
|
5728
7008
|
maxCandidates
|
|
5729
7009
|
);
|
|
5730
7010
|
}
|
|
@@ -5771,27 +7051,27 @@ function runtimeNoCandidateDiagnostics(edges) {
|
|
|
5771
7051
|
omittedRejectedCandidateCount: numeric(
|
|
5772
7052
|
exploration.omittedRejectedCandidateCount
|
|
5773
7053
|
),
|
|
5774
|
-
rejectedCandidates:
|
|
7054
|
+
rejectedCandidates: recordArray3(exploration.rejectedCandidates).slice(0, maxCandidates),
|
|
5775
7055
|
callSite
|
|
5776
7056
|
}];
|
|
5777
7057
|
});
|
|
5778
7058
|
}
|
|
5779
7059
|
function edgeTarget(row, evidence) {
|
|
5780
7060
|
const effective = parseObject(evidence.effectiveResolution);
|
|
5781
|
-
const targetServicePath =
|
|
5782
|
-
const targetOperationPath =
|
|
7061
|
+
const targetServicePath = stringValue10(effective.targetServicePath ?? evidence.targetServicePath);
|
|
7062
|
+
const targetOperationPath = stringValue10(effective.targetOperationPath ?? evidence.targetOperationPath);
|
|
5783
7063
|
if (targetServicePath && targetOperationPath) return `${targetServicePath}${targetOperationPath}`;
|
|
5784
7064
|
const runtimeCandidate = evidence.runtimeResolvedCandidate;
|
|
5785
7065
|
if (runtimeCandidate?.servicePath && runtimeCandidate.operationPath) return `${runtimeCandidate.servicePath}${runtimeCandidate.operationPath}`;
|
|
5786
|
-
const servicePath =
|
|
5787
|
-
const operationPath =
|
|
5788
|
-
const targetOperation =
|
|
5789
|
-
const targetRepo =
|
|
7066
|
+
const servicePath = stringValue10(evidence.servicePath);
|
|
7067
|
+
const operationPath = stringValue10(evidence.operationPath);
|
|
7068
|
+
const targetOperation = stringValue10(evidence.targetOperation);
|
|
7069
|
+
const targetRepo = stringValue10(evidence.targetRepo) ?? "";
|
|
5790
7070
|
if (row.edge_type === "HANDLER_RUNS_DB_QUERY") return `Entity: ${row.to_id || "unknown"}`;
|
|
5791
|
-
if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return
|
|
7071
|
+
if (row.edge_type === "HANDLER_RUNS_REMOTE_QUERY") return stringValue10(evidence.remoteQueryTarget) ?? `Remote query: ${row.to_id || "unknown"}`;
|
|
5792
7072
|
if (row.edge_type === "HANDLER_CALLS_EXTERNAL_HTTP") {
|
|
5793
7073
|
const target = parseObject(evidence.externalTarget);
|
|
5794
|
-
return
|
|
7074
|
+
return stringValue10(target.label) ?? `External endpoint: ${row.to_id || "unknown"}`;
|
|
5795
7075
|
}
|
|
5796
7076
|
if (servicePath && operationPath) return `${servicePath}${operationPath}`;
|
|
5797
7077
|
return targetOperation ? `${targetRepo}:${targetOperation}` : row.to_id;
|
|
@@ -5807,7 +7087,7 @@ function persistedResolution(row) {
|
|
|
5807
7087
|
edgeType: row.edge_type
|
|
5808
7088
|
};
|
|
5809
7089
|
}
|
|
5810
|
-
function effectiveResolution(row, evidence, unresolvedReason, resolution) {
|
|
7090
|
+
function effectiveResolution(row, evidence, unresolvedReason, resolution, contextualState) {
|
|
5811
7091
|
const target = resolution?.target;
|
|
5812
7092
|
return {
|
|
5813
7093
|
status: target ? "resolved" : row.status,
|
|
@@ -5820,22 +7100,42 @@ function effectiveResolution(row, evidence, unresolvedReason, resolution) {
|
|
|
5820
7100
|
confidence: target?.score ?? row.confidence,
|
|
5821
7101
|
reasons: resolution?.reasons ?? evidence.resolutionReasons,
|
|
5822
7102
|
unresolvedReason,
|
|
5823
|
-
edgeType: target ? "REMOTE_CALL_RESOLVES_TO_OPERATION" : row.edge_type
|
|
7103
|
+
edgeType: target ? "REMOTE_CALL_RESOLVES_TO_OPERATION" : row.edge_type,
|
|
7104
|
+
contextualBlocker: isStructuralContextualBlocker(contextualState) ? contextualState : void 0
|
|
5824
7105
|
};
|
|
5825
7106
|
}
|
|
5826
|
-
function withEffectiveResolution(evidence, row, unresolvedReason, resolution) {
|
|
5827
|
-
const current = effectiveResolution(
|
|
7107
|
+
function withEffectiveResolution(evidence, row, unresolvedReason, resolution, contextualState) {
|
|
7108
|
+
const current = effectiveResolution(
|
|
7109
|
+
row,
|
|
7110
|
+
evidence,
|
|
7111
|
+
unresolvedReason,
|
|
7112
|
+
resolution,
|
|
7113
|
+
contextualState
|
|
7114
|
+
);
|
|
5828
7115
|
const rest = { ...evidence };
|
|
5829
7116
|
delete rest.runtimeResolvedCandidate;
|
|
5830
|
-
return {
|
|
7117
|
+
return {
|
|
7118
|
+
...rest,
|
|
7119
|
+
effectiveResolution: current,
|
|
7120
|
+
linker: {
|
|
7121
|
+
status: current.status,
|
|
7122
|
+
confidence: current.confidence,
|
|
7123
|
+
reason: unresolvedReason,
|
|
7124
|
+
edgeType: current.edgeType,
|
|
7125
|
+
contextualBlocker: current.contextualBlocker
|
|
7126
|
+
}
|
|
7127
|
+
};
|
|
7128
|
+
}
|
|
7129
|
+
function contextualReason(state) {
|
|
7130
|
+
return state?.category === "none" ? void 0 : state?.message;
|
|
5831
7131
|
}
|
|
5832
7132
|
function resolveRuntimeOperation(db, evidence, workspaceId) {
|
|
5833
|
-
const servicePath =
|
|
5834
|
-
const rawOperationPath =
|
|
7133
|
+
const servicePath = stringValue10(evidence.servicePath);
|
|
7134
|
+
const rawOperationPath = stringValue10(evidence.operationPath);
|
|
5835
7135
|
const normalized = normalizeODataOperationInvocationPath(rawOperationPath);
|
|
5836
|
-
const operationPath = normalized?.wasInvocation ? normalized.normalizedOperationPath :
|
|
5837
|
-
const alias =
|
|
5838
|
-
const destination =
|
|
7136
|
+
const operationPath = normalized?.wasInvocation ? normalized.normalizedOperationPath : stringValue10(evidence.normalizedOperationPath) ?? rawOperationPath;
|
|
7137
|
+
const alias = stringValue10(evidence.serviceAliasExpr ?? evidence.serviceAlias);
|
|
7138
|
+
const destination = stringValue10(evidence.destination);
|
|
5839
7139
|
return resolveOperation(db, { servicePath, operationPath, alias, destination, hasExplicitOverride: true, isDynamic: true }, workspaceId);
|
|
5840
7140
|
}
|
|
5841
7141
|
function evidenceWithRuntimeVariables(evidence, vars) {
|
|
@@ -5855,16 +7155,23 @@ function evidenceWithRuntimeVariables(evidence, vars) {
|
|
|
5855
7155
|
return next;
|
|
5856
7156
|
}
|
|
5857
7157
|
function evidenceWithDynamicAnalysis(evidence, analysis) {
|
|
5858
|
-
const persistedCandidates =
|
|
5859
|
-
const persistedScores =
|
|
7158
|
+
const persistedCandidates = recordArray3(evidence.candidates);
|
|
7159
|
+
const persistedScores = recordArray3(evidence.candidateScores);
|
|
7160
|
+
const persistedCandidateCount = numeric(evidence.persistedCandidateCount) || numeric(evidence.candidateCount) || persistedCandidates.length;
|
|
7161
|
+
const persistedScoreCount = numeric(evidence.candidateScoreCount) || persistedScores.length;
|
|
5860
7162
|
return {
|
|
5861
7163
|
...evidence,
|
|
5862
7164
|
candidates: persistedCandidates.slice(0, analysis.maxCandidates),
|
|
5863
7165
|
candidateScores: persistedScores.slice(0, analysis.maxCandidates),
|
|
5864
|
-
persistedCandidateCount
|
|
7166
|
+
persistedCandidateCount,
|
|
5865
7167
|
persistedCandidateOmittedCount: Math.max(
|
|
5866
7168
|
0,
|
|
5867
|
-
persistedCandidates.length
|
|
7169
|
+
persistedCandidateCount - Math.min(persistedCandidates.length, analysis.maxCandidates)
|
|
7170
|
+
),
|
|
7171
|
+
persistedCandidateScoreCount: persistedScoreCount,
|
|
7172
|
+
persistedCandidateScoreOmittedCount: Math.max(
|
|
7173
|
+
0,
|
|
7174
|
+
persistedScoreCount - Math.min(persistedScores.length, analysis.maxCandidates)
|
|
5868
7175
|
),
|
|
5869
7176
|
dynamicTargetExploration: {
|
|
5870
7177
|
mode: analysis.mode,
|
|
@@ -5882,10 +7189,17 @@ function evidenceWithDynamicAnalysis(evidence, analysis) {
|
|
|
5882
7189
|
shownRejectedCandidateCount: analysis.shownRejectedCandidateCount,
|
|
5883
7190
|
omittedRejectedCandidateCount: analysis.omittedRejectedCandidateCount,
|
|
5884
7191
|
rejectedCandidates: analysis.rejectedCandidates,
|
|
5885
|
-
suggestedVarSets: analysis.suggestedVarSets
|
|
7192
|
+
suggestedVarSets: analysis.suggestedVarSets,
|
|
7193
|
+
suggestedVarSetCount: analysis.suggestedVarSetCount,
|
|
7194
|
+
shownSuggestedVarSetCount: analysis.shownSuggestedVarSetCount,
|
|
7195
|
+
omittedSuggestedVarSetCount: analysis.omittedSuggestedVarSetCount,
|
|
7196
|
+
routingContext: analysis.routingContext
|
|
5886
7197
|
},
|
|
5887
7198
|
dynamicTargetCandidates: analysis.candidates,
|
|
5888
7199
|
dynamicTargetCandidateSuggestions: analysis.shownCandidates,
|
|
7200
|
+
dynamicTargetCandidateSuggestionCount: analysis.viableCandidateCount,
|
|
7201
|
+
shownDynamicTargetCandidateSuggestionCount: analysis.shownCandidateCount,
|
|
7202
|
+
omittedDynamicTargetCandidateSuggestionCount: analysis.omittedCandidateCount,
|
|
5889
7203
|
rejectedCandidates: analysis.rejectedCandidates,
|
|
5890
7204
|
dynamicTargetInference: analysis.inference
|
|
5891
7205
|
};
|
|
@@ -5900,10 +7214,11 @@ var boundedDynamicListKeys = /* @__PURE__ */ new Set([
|
|
|
5900
7214
|
"rejectedCandidates",
|
|
5901
7215
|
"rejectedCandidateSuggestions",
|
|
5902
7216
|
"copyableExamples",
|
|
5903
|
-
"conflicts"
|
|
7217
|
+
"conflicts",
|
|
7218
|
+
"bindingAlternatives"
|
|
5904
7219
|
]);
|
|
5905
7220
|
function boundDynamicEvidence(evidence, limit) {
|
|
5906
|
-
const candidateCount = numeric(evidence.persistedCandidateCount) || (Array.isArray(evidence.candidates) ? evidence.candidates.length : 0);
|
|
7221
|
+
const candidateCount = numeric(evidence.persistedCandidateCount) || numeric(evidence.candidateCount) || (Array.isArray(evidence.candidates) ? evidence.candidates.length : 0);
|
|
5907
7222
|
const projected = boundDynamicValue(evidence, limit);
|
|
5908
7223
|
const next = parseObject(projected);
|
|
5909
7224
|
if (candidateCount === 0) return next;
|
|
@@ -5935,7 +7250,7 @@ function runtimeSubstitutions(evidence, vars) {
|
|
|
5935
7250
|
return substitutions;
|
|
5936
7251
|
}
|
|
5937
7252
|
function substitutionValue(evidence, key) {
|
|
5938
|
-
const value =
|
|
7253
|
+
const value = stringValue10(evidence[key]);
|
|
5939
7254
|
if (key !== "operationPath") return value;
|
|
5940
7255
|
const normalized = normalizeODataOperationInvocationPath(value);
|
|
5941
7256
|
return normalized?.wasInvocation ? normalized.normalizedOperationPath : value;
|
|
@@ -5984,13 +7299,13 @@ function runtimeUnresolvedReason(resolution) {
|
|
|
5984
7299
|
function parseObject(value) {
|
|
5985
7300
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
5986
7301
|
}
|
|
5987
|
-
function
|
|
7302
|
+
function stringValue10(value) {
|
|
5988
7303
|
return typeof value === "string" ? value : void 0;
|
|
5989
7304
|
}
|
|
5990
7305
|
function numeric(value) {
|
|
5991
7306
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
5992
7307
|
}
|
|
5993
|
-
function
|
|
7308
|
+
function recordArray3(value) {
|
|
5994
7309
|
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
5995
7310
|
}
|
|
5996
7311
|
function appendBounded(target, values, limit) {
|
|
@@ -6017,8 +7332,8 @@ function positiveCandidateCap(value) {
|
|
|
6017
7332
|
|
|
6018
7333
|
// src/trace/dynamic-branches.ts
|
|
6019
7334
|
function dynamicCandidateBranches(depth, call, evidence) {
|
|
6020
|
-
const exploration =
|
|
6021
|
-
return
|
|
7335
|
+
const exploration = objectRecord2(evidence.dynamicTargetExploration);
|
|
7336
|
+
return recordArray4(evidence.dynamicTargetCandidateSuggestions).map((candidate) => ({
|
|
6022
7337
|
step: depth,
|
|
6023
7338
|
type: "dynamic_candidate_branch",
|
|
6024
7339
|
from: `${call.repoName}:${call.source_file}:${call.source_line}`,
|
|
@@ -6028,19 +7343,19 @@ function dynamicCandidateBranches(depth, call, evidence) {
|
|
|
6028
7343
|
exploratory: true,
|
|
6029
7344
|
dynamicMode: String(exploration.mode ?? "candidates"),
|
|
6030
7345
|
selected: false,
|
|
6031
|
-
omittedCandidateCount:
|
|
7346
|
+
omittedCandidateCount: numericValue4(exploration.omittedCandidateCount)
|
|
6032
7347
|
},
|
|
6033
|
-
confidence:
|
|
7348
|
+
confidence: numericValue4(candidate.score),
|
|
6034
7349
|
unresolvedReason: "Exploratory dynamic target candidate; provide runtime variables to select it"
|
|
6035
7350
|
}));
|
|
6036
7351
|
}
|
|
6037
|
-
function
|
|
7352
|
+
function objectRecord2(value) {
|
|
6038
7353
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
6039
7354
|
}
|
|
6040
|
-
function
|
|
7355
|
+
function recordArray4(value) {
|
|
6041
7356
|
return Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
6042
7357
|
}
|
|
6043
|
-
function
|
|
7358
|
+
function numericValue4(value) {
|
|
6044
7359
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
6045
7360
|
}
|
|
6046
7361
|
|
|
@@ -6067,16 +7382,300 @@ function prependTraceDiagnostic(diagnostics, diagnostic) {
|
|
|
6067
7382
|
}
|
|
6068
7383
|
function sameDiagnosticLocation(left, right) {
|
|
6069
7384
|
if (left.code !== right.code) return false;
|
|
6070
|
-
const leftFile =
|
|
6071
|
-
const rightFile =
|
|
7385
|
+
const leftFile = stringValue11(left.sourceFile);
|
|
7386
|
+
const rightFile = stringValue11(right.sourceFile);
|
|
6072
7387
|
if (leftFile || rightFile)
|
|
6073
|
-
return leftFile === rightFile &&
|
|
7388
|
+
return leftFile === rightFile && numericValue5(left.sourceLine) === numericValue5(right.sourceLine);
|
|
6074
7389
|
return String(left.message ?? "") === String(right.message ?? "");
|
|
6075
7390
|
}
|
|
6076
|
-
function
|
|
7391
|
+
function stringValue11(value) {
|
|
6077
7392
|
return typeof value === "string" ? value : void 0;
|
|
6078
7393
|
}
|
|
6079
|
-
function
|
|
7394
|
+
function numericValue5(value) {
|
|
7395
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
7396
|
+
}
|
|
7397
|
+
|
|
7398
|
+
// src/trace/005-implementation-selection.ts
|
|
7399
|
+
function hintedImplementationSelection(db, edge, operationId, options) {
|
|
7400
|
+
if (!edge || edge.status !== "ambiguous")
|
|
7401
|
+
return { blocksAutomatic: false, evidence: { status: "not_applicable" } };
|
|
7402
|
+
return selectImplementation(
|
|
7403
|
+
parsedEvidence(edge.evidence_json),
|
|
7404
|
+
options.implementationHints,
|
|
7405
|
+
options.implementationRepo,
|
|
7406
|
+
canonicalImplementationEvidence(db, operationId)
|
|
7407
|
+
);
|
|
7408
|
+
}
|
|
7409
|
+
function contextualImplementationSelection(db, edge, operationId, callerRepoId, remoteEvidence, options) {
|
|
7410
|
+
const hinted = hintedImplementationSelection(db, edge, operationId, options);
|
|
7411
|
+
if (hinted.methodId || hinted.blocksAutomatic || !edge || edge.status !== "ambiguous" || callerRepoId === void 0) return hinted;
|
|
7412
|
+
const candidates2 = implementationCandidates2(
|
|
7413
|
+
canonicalImplementationEvidence(db, operationId) ?? parsedEvidence(edge.evidence_json)
|
|
7414
|
+
);
|
|
7415
|
+
const scores = candidates2.filter((candidate) => candidate.accepted).map((candidate) => contextualScore(candidate, callerRepoId, remoteEvidence)).sort(compareScore);
|
|
7416
|
+
if (scores.length === 0)
|
|
7417
|
+
return { blocksAutomatic: false, evidence: { status: "not_applicable", candidateScores: [] } };
|
|
7418
|
+
const [first, second] = scores;
|
|
7419
|
+
if (first?.methodId !== void 0 && first.score > 0 && (!second || first.score > second.score))
|
|
7420
|
+
return selectedContext(first, scores);
|
|
7421
|
+
return tiedContext(hinted, scores);
|
|
7422
|
+
}
|
|
7423
|
+
function selectedContext(first, scores) {
|
|
7424
|
+
const projection = projectBounded(scores, compareScore);
|
|
7425
|
+
return {
|
|
7426
|
+
methodId: String(first.methodId),
|
|
7427
|
+
blocksAutomatic: false,
|
|
7428
|
+
evidence: {
|
|
7429
|
+
status: "selected",
|
|
7430
|
+
selectedMethodId: first.methodId,
|
|
7431
|
+
candidateScores: projection.items,
|
|
7432
|
+
candidateScoreCount: projection.totalCount,
|
|
7433
|
+
shownCandidateScoreCount: projection.shownCount,
|
|
7434
|
+
omittedCandidateScoreCount: projection.omittedCount
|
|
7435
|
+
}
|
|
7436
|
+
};
|
|
7437
|
+
}
|
|
7438
|
+
function tiedContext(hinted, scores) {
|
|
7439
|
+
if (hinted.evidence.reason === "no_scoped_hint_matched_edge") return hinted;
|
|
7440
|
+
const projection = projectBounded(scores, compareScore);
|
|
7441
|
+
return {
|
|
7442
|
+
blocksAutomatic: false,
|
|
7443
|
+
evidence: {
|
|
7444
|
+
status: "tied",
|
|
7445
|
+
tieReason: scores.length > 1 ? "duplicate_helper_implementation_candidates" : "no_unique_materially_stronger_candidate",
|
|
7446
|
+
candidateScores: projection.items,
|
|
7447
|
+
candidateScoreCount: projection.totalCount,
|
|
7448
|
+
shownCandidateScoreCount: projection.shownCount,
|
|
7449
|
+
omittedCandidateScoreCount: projection.omittedCount
|
|
7450
|
+
}
|
|
7451
|
+
};
|
|
7452
|
+
}
|
|
7453
|
+
function implementationCandidates2(evidence) {
|
|
7454
|
+
const rows2 = Array.isArray(evidence.candidates) ? evidence.candidates : [];
|
|
7455
|
+
return rows2.flatMap((value) => {
|
|
7456
|
+
const row = record4(value);
|
|
7457
|
+
return Object.keys(row).length === 0 ? [] : [{
|
|
7458
|
+
accepted: row.accepted === true,
|
|
7459
|
+
methodId: numberValue8(row.methodId),
|
|
7460
|
+
score: numberValue8(row.score) ?? 0,
|
|
7461
|
+
handlerPackage: record4(row.handlerPackage),
|
|
7462
|
+
applicationPackage: record4(row.applicationPackage)
|
|
7463
|
+
}];
|
|
7464
|
+
});
|
|
7465
|
+
}
|
|
7466
|
+
function contextualScore(candidate, callerRepoId, remoteEvidence) {
|
|
7467
|
+
const reasons = [];
|
|
7468
|
+
let score = candidate.score;
|
|
7469
|
+
if (numberValue8(candidate.handlerPackage.id) === callerRepoId) {
|
|
7470
|
+
score += 10;
|
|
7471
|
+
reasons.push("handler_package_matches_caller_repository");
|
|
7472
|
+
}
|
|
7473
|
+
if (numberValue8(candidate.applicationPackage.id) === callerRepoId) {
|
|
7474
|
+
score += 10;
|
|
7475
|
+
reasons.push("registration_package_matches_caller_repository");
|
|
7476
|
+
}
|
|
7477
|
+
if (hasRemoteContext(remoteEvidence)) {
|
|
7478
|
+
score += 1;
|
|
7479
|
+
reasons.push("remote_call_context_available");
|
|
7480
|
+
}
|
|
7481
|
+
return {
|
|
7482
|
+
methodId: candidate.methodId,
|
|
7483
|
+
score,
|
|
7484
|
+
reasons,
|
|
7485
|
+
handlerPackage: candidate.handlerPackage,
|
|
7486
|
+
applicationPackage: candidate.applicationPackage
|
|
7487
|
+
};
|
|
7488
|
+
}
|
|
7489
|
+
function hasRemoteContext(evidence) {
|
|
7490
|
+
return typeof evidence.effectiveServicePath === "string" || typeof evidence.effectiveDestination === "string" || typeof evidence.effectiveAlias === "string";
|
|
7491
|
+
}
|
|
7492
|
+
function compareScore(left, right) {
|
|
7493
|
+
return right.score - left.score || Number(left.methodId ?? 0) - Number(right.methodId ?? 0);
|
|
7494
|
+
}
|
|
7495
|
+
function parsedEvidence(value) {
|
|
7496
|
+
try {
|
|
7497
|
+
return record4(JSON.parse(String(value ?? "{}")));
|
|
7498
|
+
} catch {
|
|
7499
|
+
return {};
|
|
7500
|
+
}
|
|
7501
|
+
}
|
|
7502
|
+
function record4(value) {
|
|
7503
|
+
return isRecord6(value) ? value : {};
|
|
7504
|
+
}
|
|
7505
|
+
function isRecord6(value) {
|
|
7506
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
7507
|
+
}
|
|
7508
|
+
function numberValue8(value) {
|
|
7509
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
7510
|
+
}
|
|
7511
|
+
|
|
7512
|
+
// src/trace/007-implementation-start-diagnostic.ts
|
|
7513
|
+
function implementationStartDiagnostic(edge, evidence) {
|
|
7514
|
+
return {
|
|
7515
|
+
severity: "warning",
|
|
7516
|
+
code: edge.status === "ambiguous" ? "trace_start_ambiguous" : "trace_start_implementation_unresolved",
|
|
7517
|
+
message: `Indexed operation matched but implementation edge is ${String(
|
|
7518
|
+
edge.status ?? "unresolved"
|
|
7519
|
+
)}`,
|
|
7520
|
+
resolutionStage: "implementation",
|
|
7521
|
+
resolutionStatus: edge.status === "ambiguous" ? "ambiguous_implementation" : "rejected_implementation",
|
|
7522
|
+
implementationEdgeId: edge.id,
|
|
7523
|
+
implementationStatus: edge.status,
|
|
7524
|
+
implementationAmbiguityReasons: evidence.ambiguityReasons,
|
|
7525
|
+
implementationRejectionReasons: implementationRejectionReasons(evidence),
|
|
7526
|
+
implementationHintSuggestions: evidence.implementationHintSuggestions,
|
|
7527
|
+
implementationHintSuggestionCount: evidence.implementationHintSuggestionCount,
|
|
7528
|
+
shownImplementationHintSuggestionCount: evidence.shownImplementationHintSuggestionCount,
|
|
7529
|
+
omittedImplementationHintSuggestionCount: evidence.omittedImplementationHintSuggestionCount,
|
|
7530
|
+
candidates: evidence.candidates,
|
|
7531
|
+
candidateCount: evidence.candidateCount,
|
|
7532
|
+
shownCandidateCount: evidence.shownCandidateCount,
|
|
7533
|
+
omittedCandidateCount: evidence.omittedCandidateCount
|
|
7534
|
+
};
|
|
7535
|
+
}
|
|
7536
|
+
function implementationRejectionReasons(evidence) {
|
|
7537
|
+
const candidates2 = recordArray5(evidence.candidates);
|
|
7538
|
+
const reasons = candidates2.flatMap((candidate) => stringArray5(candidate.rejectedReasons));
|
|
7539
|
+
return [...new Set(reasons)].sort();
|
|
7540
|
+
}
|
|
7541
|
+
function recordArray5(value) {
|
|
7542
|
+
return Array.isArray(value) ? value.filter(isRecord7) : [];
|
|
7543
|
+
}
|
|
7544
|
+
function stringArray5(value) {
|
|
7545
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
7546
|
+
}
|
|
7547
|
+
function isRecord7(value) {
|
|
7548
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
7549
|
+
}
|
|
7550
|
+
|
|
7551
|
+
// src/trace/009-selected-handler-provenance.ts
|
|
7552
|
+
function handlerMethodNode(db, methodId) {
|
|
7553
|
+
const row = db.prepare(`SELECT hm.id methodId,hm.method_name methodName,
|
|
7554
|
+
hm.source_line sourceLine,hc.class_name className,hc.source_file sourceFile,
|
|
7555
|
+
r.name repoName,r.id repoId,r.package_name packageName
|
|
7556
|
+
FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
7557
|
+
JOIN repositories r ON r.id=hc.repo_id WHERE hm.id=?`).get(methodId);
|
|
7558
|
+
return handlerNodeFromRow(row);
|
|
7559
|
+
}
|
|
7560
|
+
function withSelectedHandlerProvenance(evidence, targetId, handler) {
|
|
7561
|
+
if (!handler) return unavailableHandlerEvidence(evidence, targetId);
|
|
7562
|
+
const selected = selectedHandlerProvenance(handlerSource(handler));
|
|
7563
|
+
const stored = record5(evidence.selectedHandler);
|
|
7564
|
+
const mismatchedFields = storedSelectedHandlerMismatches(
|
|
7565
|
+
stored,
|
|
7566
|
+
selected,
|
|
7567
|
+
targetId
|
|
7568
|
+
);
|
|
7569
|
+
if (mismatchedFields.length === 0)
|
|
7570
|
+
return { handler, evidence: { ...evidence, selectedHandler: selected } };
|
|
7571
|
+
return {
|
|
7572
|
+
handler,
|
|
7573
|
+
evidence: {
|
|
7574
|
+
...evidence,
|
|
7575
|
+
selectedHandler: selected,
|
|
7576
|
+
selectedHandlerProvenanceAudit: {
|
|
7577
|
+
status: "mismatch",
|
|
7578
|
+
graphTargetId: targetId,
|
|
7579
|
+
mismatchedFields,
|
|
7580
|
+
persistedSelectedHandler: stored
|
|
7581
|
+
}
|
|
7582
|
+
},
|
|
7583
|
+
diagnostic: {
|
|
7584
|
+
severity: "warning",
|
|
7585
|
+
code: "selected_handler_provenance_mismatch",
|
|
7586
|
+
message: "Persisted selected handler provenance did not match the resolved graph target",
|
|
7587
|
+
graphTargetId: targetId,
|
|
7588
|
+
mismatchedFields,
|
|
7589
|
+
sourceFile: handler.sourceFile,
|
|
7590
|
+
sourceLine: handler.sourceLine
|
|
7591
|
+
}
|
|
7592
|
+
};
|
|
7593
|
+
}
|
|
7594
|
+
function unavailableHandlerEvidence(evidence, targetId) {
|
|
7595
|
+
return {
|
|
7596
|
+
evidence: {
|
|
7597
|
+
...evidence,
|
|
7598
|
+
selectedHandlerProvenanceAudit: {
|
|
7599
|
+
status: "unavailable",
|
|
7600
|
+
graphTargetId: targetId,
|
|
7601
|
+
reason: "handler_target_not_indexed"
|
|
7602
|
+
}
|
|
7603
|
+
},
|
|
7604
|
+
diagnostic: {
|
|
7605
|
+
severity: "warning",
|
|
7606
|
+
code: "selected_handler_target_not_found",
|
|
7607
|
+
message: "Resolved implementation target is not an indexed handler method",
|
|
7608
|
+
graphTargetId: targetId
|
|
7609
|
+
},
|
|
7610
|
+
unresolvedReason: "Resolved implementation target is not an indexed handler method"
|
|
7611
|
+
};
|
|
7612
|
+
}
|
|
7613
|
+
function handlerNodeFromRow(row) {
|
|
7614
|
+
const methodId = numberValue9(row?.methodId);
|
|
7615
|
+
const methodName2 = stringValue12(row?.methodName);
|
|
7616
|
+
const className = stringValue12(row?.className);
|
|
7617
|
+
const sourceFile = stringValue12(row?.sourceFile);
|
|
7618
|
+
const sourceLine2 = numberValue9(row?.sourceLine);
|
|
7619
|
+
const repoId = numberValue9(row?.repoId);
|
|
7620
|
+
const repoName = stringValue12(row?.repoName);
|
|
7621
|
+
if (methodId === void 0 || !methodName2 || !className || !sourceFile || sourceLine2 === void 0 || repoId === void 0 || !repoName)
|
|
7622
|
+
return void 0;
|
|
7623
|
+
return {
|
|
7624
|
+
id: `handler_method:${methodId}`,
|
|
7625
|
+
kind: "handler_method",
|
|
7626
|
+
label: `${repoName}:${className}.${methodName2}`,
|
|
7627
|
+
methodId,
|
|
7628
|
+
methodName: methodName2,
|
|
7629
|
+
className,
|
|
7630
|
+
sourceFile,
|
|
7631
|
+
sourceLine: sourceLine2,
|
|
7632
|
+
repoId,
|
|
7633
|
+
repoName,
|
|
7634
|
+
packageName: stringValue12(row?.packageName)
|
|
7635
|
+
};
|
|
7636
|
+
}
|
|
7637
|
+
function handlerSource(node) {
|
|
7638
|
+
return {
|
|
7639
|
+
methodId: node.methodId,
|
|
7640
|
+
methodName: node.methodName,
|
|
7641
|
+
className: node.className,
|
|
7642
|
+
repositoryId: node.repoId,
|
|
7643
|
+
repositoryName: node.repoName,
|
|
7644
|
+
repositoryPackageName: node.packageName,
|
|
7645
|
+
sourceFile: node.sourceFile,
|
|
7646
|
+
sourceLine: node.sourceLine
|
|
7647
|
+
};
|
|
7648
|
+
}
|
|
7649
|
+
function storedSelectedHandlerMismatches(stored, selected, targetId) {
|
|
7650
|
+
if (Object.keys(stored).length === 0) return [];
|
|
7651
|
+
const expectedRepository = record5(stored.repository);
|
|
7652
|
+
const selectedRepository = selected.repository ?? {};
|
|
7653
|
+
return [
|
|
7654
|
+
mismatch("graphTargetId", stored.graphTargetId, targetId),
|
|
7655
|
+
mismatch("methodId", stored.methodId, selected.methodId),
|
|
7656
|
+
mismatch("className", stored.className, selected.className),
|
|
7657
|
+
mismatch("methodName", stored.methodName, selected.methodName),
|
|
7658
|
+
mismatch("sourceFile", stored.sourceFile, selected.sourceFile),
|
|
7659
|
+
mismatch("sourceLine", stored.sourceLine, selected.sourceLine),
|
|
7660
|
+
mismatch("repository.id", expectedRepository.id, selectedRepository.id),
|
|
7661
|
+
mismatch("repository.name", expectedRepository.name, selectedRepository.name),
|
|
7662
|
+
mismatch(
|
|
7663
|
+
"repository.packageName",
|
|
7664
|
+
expectedRepository.packageName,
|
|
7665
|
+
selectedRepository.packageName
|
|
7666
|
+
)
|
|
7667
|
+
].flatMap((field) => field ? [field] : []);
|
|
7668
|
+
}
|
|
7669
|
+
function mismatch(field, stored, actual) {
|
|
7670
|
+
return stored === void 0 || stored === actual ? void 0 : field;
|
|
7671
|
+
}
|
|
7672
|
+
function record5(value) {
|
|
7673
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
7674
|
+
}
|
|
7675
|
+
function stringValue12(value) {
|
|
7676
|
+
return typeof value === "string" ? value : void 0;
|
|
7677
|
+
}
|
|
7678
|
+
function numberValue9(value) {
|
|
6080
7679
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
6081
7680
|
}
|
|
6082
7681
|
|
|
@@ -6108,24 +7707,24 @@ function operationStartScope(db, repoId, start, hintOptions, workspaceId) {
|
|
|
6108
7707
|
const operationId = String(rows2[0]?.operationId);
|
|
6109
7708
|
const impl = implementationScope(db, operationId);
|
|
6110
7709
|
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 =
|
|
7710
|
+
const hinted = hintedImplementationSelection(
|
|
7711
|
+
db,
|
|
7712
|
+
impl.edge,
|
|
7713
|
+
operationId,
|
|
7714
|
+
hintOptions
|
|
7715
|
+
);
|
|
6112
7716
|
if (hinted.methodId) {
|
|
6113
7717
|
const hintedScope = handlerScope(db, hinted.methodId);
|
|
6114
7718
|
if (hintedScope?.files.size) return { files: hintedScope.files, symbols: hintedScope.symbolId ? /* @__PURE__ */ new Set([hintedScope.symbolId]) : void 0, repoId: hintedScope.repoId, operationId, diagnostics: [] };
|
|
6115
7719
|
}
|
|
6116
7720
|
if (impl.edge) {
|
|
6117
7721
|
const evidence = parseEvidence(impl.edge.evidence_json);
|
|
6118
|
-
const hintDiagnostic = implementationHintDiagnostic(hinted, evidence
|
|
6119
|
-
const diagnostics = [
|
|
7722
|
+
const hintDiagnostic = implementationHintDiagnostic(hinted, evidence);
|
|
7723
|
+
const diagnostics = [implementationStartDiagnostic(impl.edge, evidence)];
|
|
6120
7724
|
return { operationId, diagnostics: hintDiagnostic ? [hintDiagnostic, ...diagnostics] : diagnostics };
|
|
6121
7725
|
}
|
|
6122
7726
|
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
7727
|
}
|
|
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
7728
|
function sourceFilesForStart(db, repoId, start, workspaceId) {
|
|
6130
7729
|
return sourceScopeForSelector(db, repoId, start, workspaceId);
|
|
6131
7730
|
}
|
|
@@ -6199,11 +7798,6 @@ function handlerFilesForOperation(db, operationId) {
|
|
|
6199
7798
|
function implementationEdge(db, operationId) {
|
|
6200
7799
|
return db.prepare("SELECT * FROM graph_edges WHERE edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND from_kind='operation' AND from_id=? ORDER BY CASE status WHEN 'resolved' THEN 0 WHEN 'ambiguous' THEN 1 ELSE 2 END,id LIMIT 1").get(operationId);
|
|
6201
7800
|
}
|
|
6202
|
-
function handlerMethodNode(db, methodId) {
|
|
6203
|
-
const row = db.prepare(`SELECT hm.id methodId,hm.method_name methodName,hm.decorator_value decoratorValue,hm.source_line sourceLine,hc.class_name className,hc.source_file sourceFile,r.name repoName,r.id repoId FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id JOIN repositories r ON r.id=hc.repo_id WHERE hm.id=?`).get(methodId);
|
|
6204
|
-
if (!row) return void 0;
|
|
6205
|
-
return { id: `handler_method:${methodId}`, kind: "handler_method", label: `${String(row.repoName)}:${String(row.className)}.${String(row.methodName)}`, ...row };
|
|
6206
|
-
}
|
|
6207
7801
|
function implementationScope(db, operationId) {
|
|
6208
7802
|
const edge = implementationEdge(db, operationId);
|
|
6209
7803
|
if (!edge || edge.status !== "resolved") return { files: /* @__PURE__ */ new Set(), edge };
|
|
@@ -6212,38 +7806,6 @@ function implementationScope(db, operationId) {
|
|
|
6212
7806
|
return { repoId: row?.repoId, files: /* @__PURE__ */ new Set(), edge };
|
|
6213
7807
|
return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []), symbolId: row?.symbolId, edge };
|
|
6214
7808
|
}
|
|
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
7809
|
function handlerScope(db, methodId) {
|
|
6248
7810
|
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
7811
|
if (!row || typeof row.symbolId !== "number") return void 0;
|
|
@@ -6289,11 +7851,14 @@ function workspaceIdForCall(db, callId) {
|
|
|
6289
7851
|
function parseEvidence(value) {
|
|
6290
7852
|
try {
|
|
6291
7853
|
const parsed = JSON.parse(String(value || "{}"));
|
|
6292
|
-
return
|
|
7854
|
+
return isEvidenceRecord2(parsed) ? boundCandidateLikeEvidence(parsed) : {};
|
|
6293
7855
|
} catch {
|
|
6294
7856
|
return {};
|
|
6295
7857
|
}
|
|
6296
7858
|
}
|
|
7859
|
+
function isEvidenceRecord2(value) {
|
|
7860
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
7861
|
+
}
|
|
6297
7862
|
function receiverFromEvidence(value) {
|
|
6298
7863
|
const evidence = parseEvidence(value);
|
|
6299
7864
|
return typeof evidence.receiver === "string" ? evidence.receiver : void 0;
|
|
@@ -6341,7 +7906,7 @@ function knownBindingsForScope(db, repoId, symbolIds, files) {
|
|
|
6341
7906
|
map.set(row.variableName, candidate);
|
|
6342
7907
|
continue;
|
|
6343
7908
|
}
|
|
6344
|
-
const
|
|
7909
|
+
const candidates2 = uniqueBindingCandidates([
|
|
6345
7910
|
...existing.bindingCandidates ?? [bindingCandidateEvidence(existing)],
|
|
6346
7911
|
bindingCandidateEvidence(candidate)
|
|
6347
7912
|
]);
|
|
@@ -6350,7 +7915,7 @@ function knownBindingsForScope(db, repoId, symbolIds, files) {
|
|
|
6350
7915
|
bindingId: void 0,
|
|
6351
7916
|
source: "ambiguous_local_service_bindings",
|
|
6352
7917
|
resolutionStatus: "ambiguous",
|
|
6353
|
-
bindingCandidates:
|
|
7918
|
+
bindingCandidates: candidates2
|
|
6354
7919
|
});
|
|
6355
7920
|
}
|
|
6356
7921
|
return map;
|
|
@@ -6366,9 +7931,9 @@ function bindingCandidateEvidence(binding) {
|
|
|
6366
7931
|
servicePathExpr: binding.servicePathExpr
|
|
6367
7932
|
};
|
|
6368
7933
|
}
|
|
6369
|
-
function uniqueBindingCandidates(
|
|
7934
|
+
function uniqueBindingCandidates(candidates2) {
|
|
6370
7935
|
const seen = /* @__PURE__ */ new Set();
|
|
6371
|
-
return
|
|
7936
|
+
return candidates2.filter((candidate) => {
|
|
6372
7937
|
const key = JSON.stringify(candidate);
|
|
6373
7938
|
if (seen.has(key)) return false;
|
|
6374
7939
|
seen.add(key);
|
|
@@ -6378,13 +7943,13 @@ function uniqueBindingCandidates(candidates) {
|
|
|
6378
7943
|
function contextForSymbolCall(db, symbolCall, callerBindings) {
|
|
6379
7944
|
const next = /* @__PURE__ */ new Map();
|
|
6380
7945
|
if (callerBindings.size === 0) return next;
|
|
6381
|
-
const
|
|
7946
|
+
const callEvidence = parseEvidence(symbolCall.evidence_json);
|
|
6382
7947
|
const callee = db.prepare("SELECT evidence_json evidenceJson,source_file sourceFile,start_line startLine FROM symbols WHERE id=?").get(symbolCall.callee_symbol_id);
|
|
6383
7948
|
const calleeEvidence = parseEvidence(callee?.evidenceJson);
|
|
6384
7949
|
const params = Array.isArray(calleeEvidence.parameters) ? calleeEvidence.parameters.filter((item) => typeof item === "string") : [];
|
|
6385
7950
|
const parameterBindings = Array.isArray(calleeEvidence.parameterBindings) ? calleeEvidence.parameterBindings.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
6386
7951
|
const parameterPropertyAliases = Array.isArray(calleeEvidence.parameterPropertyAliases) ? calleeEvidence.parameterPropertyAliases.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
6387
|
-
const args = Array.isArray(
|
|
7952
|
+
const args = Array.isArray(callEvidence.callArguments) ? callEvidence.callArguments : [];
|
|
6388
7953
|
const provenance = {
|
|
6389
7954
|
callerSite: { sourceFile: String(symbolCall.source_file ?? ""), sourceLine: Number(symbolCall.source_line ?? 0) },
|
|
6390
7955
|
calleeSite: { sourceFile: callee?.sourceFile, sourceLine: callee?.startLine }
|
|
@@ -6422,35 +7987,6 @@ function contextForSymbolCall(db, symbolCall, callerBindings) {
|
|
|
6422
7987
|
});
|
|
6423
7988
|
return next;
|
|
6424
7989
|
}
|
|
6425
|
-
function contextualRuntimeResolution(db, call, binding, workspaceId, persistedRows = []) {
|
|
6426
|
-
if (!binding || String(call.call_type) !== "remote_action" || call.operation_path_expr === void 0 || call.operation_path_expr === null) return {};
|
|
6427
|
-
if (binding.resolutionStatus === "ambiguous") {
|
|
6428
|
-
return {
|
|
6429
|
-
evidence: {
|
|
6430
|
-
contextualServiceBindingAttempted: true,
|
|
6431
|
-
contextualBinding: {
|
|
6432
|
-
source: binding.source,
|
|
6433
|
-
status: "tied",
|
|
6434
|
-
candidates: binding.bindingCandidates
|
|
6435
|
-
},
|
|
6436
|
-
contextualResolutionStatus: "ambiguous",
|
|
6437
|
-
contextualCandidateCount: binding.bindingCandidates?.length ?? 0
|
|
6438
|
-
},
|
|
6439
|
-
unresolvedReason: "Ambiguous contextual service binding candidates"
|
|
6440
|
-
};
|
|
6441
|
-
}
|
|
6442
|
-
const normalized = normalizeODataOperationInvocationPath(String(call.operation_path_expr));
|
|
6443
|
-
const op = normalized?.normalizedOperationPath ?? (String(call.operation_path_expr).startsWith("/") ? String(call.operation_path_expr) : `/${String(call.operation_path_expr)}`);
|
|
6444
|
-
const servicePath = binding.effectiveServicePath ?? binding.servicePathExpr ?? binding.requireServicePath;
|
|
6445
|
-
const destination = binding.effectiveDestination ?? binding.destinationExpr ?? binding.requireDestination;
|
|
6446
|
-
const resolution = resolveOperation(db, { servicePath, operationPath: op, alias: binding.aliasExpr ?? binding.alias, destination, hasExplicitOverride: true, isDynamic: false }, workspaceId);
|
|
6447
|
-
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: resolution.candidates.length, candidates: resolution.candidates, contextualResolutionReasons: resolution.reasons, resolutionReasons: resolution.reasons };
|
|
6448
|
-
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
|
-
const resolvedEvidence = { ...evidence, contextualServiceBindingSelected: true, targetRepo: resolution.target.repoName, targetServicePath: resolution.target.servicePath, targetOperationPath: resolution.target.operationPath, targetOperation: resolution.target.operationName };
|
|
6450
|
-
const persistedResolved = persistedRows.find((item) => item.status === "resolved");
|
|
6451
|
-
if (persistedResolved) return { row: void 0, evidence: { ...resolvedEvidence, contextualPreservedPersistedResolvedEdge: true }, unresolvedReason: void 0 };
|
|
6452
|
-
return { row: { id: -Number(call.id), edge_type: "REMOTE_CALL_RESOLVES_TO_OPERATION", from_id: String(call.id), to_kind: "operation", to_id: String(resolution.target.operationId), confidence: resolution.target.score, evidence_json: JSON.stringify(resolvedEvidence), status: "resolved" }, evidence: resolvedEvidence, unresolvedReason: void 0 };
|
|
6453
|
-
}
|
|
6454
7990
|
function trace(db, start, options) {
|
|
6455
7991
|
const hintOptions = { implementationRepo: options.implementationRepo, implementationHints: options.implementationHints };
|
|
6456
7992
|
const scope = startScope(db, start, hintOptions, options.workspaceId);
|
|
@@ -6478,14 +8014,34 @@ function trace(db, start, options) {
|
|
|
6478
8014
|
const op = operationNode(db, scope.startOperationId);
|
|
6479
8015
|
const impl = implementationScope(db, scope.startOperationId);
|
|
6480
8016
|
if (op) nodes.set(String(op.id), op);
|
|
6481
|
-
const startSelection =
|
|
8017
|
+
const startSelection = hintedImplementationSelection(
|
|
8018
|
+
db,
|
|
8019
|
+
impl.edge,
|
|
8020
|
+
scope.startOperationId,
|
|
8021
|
+
hintOptions
|
|
8022
|
+
);
|
|
6482
8023
|
if (impl.edge && (impl.edge.status === "resolved" || startSelection.methodId)) {
|
|
6483
8024
|
const selectedMethodId = impl.edge.status === "resolved" ? impl.edge.to_id : startSelection.methodId;
|
|
6484
|
-
const implEvidence = {
|
|
6485
|
-
|
|
6486
|
-
|
|
8025
|
+
const implEvidence = {
|
|
8026
|
+
...parseEvidence(impl.edge.evidence_json),
|
|
8027
|
+
startResolution: {
|
|
8028
|
+
strategy: "indexed_operation_graph",
|
|
8029
|
+
matchedOperationId: scope.startOperationId,
|
|
8030
|
+
implementationEdgeId: impl.edge.id,
|
|
8031
|
+
implementationStatus: impl.edge.status,
|
|
8032
|
+
selectedHandlerMethodId: selectedMethodId
|
|
8033
|
+
},
|
|
8034
|
+
implementationSelection: startSelection.methodId ? startSelection.evidence : void 0
|
|
8035
|
+
};
|
|
8036
|
+
const selected = selectedMethodId ? withSelectedHandlerProvenance(
|
|
8037
|
+
implEvidence,
|
|
8038
|
+
selectedMethodId,
|
|
8039
|
+
handlerMethodNode(db, selectedMethodId)
|
|
8040
|
+
) : { evidence: implEvidence };
|
|
8041
|
+
if (selected.diagnostic) prependTraceDiagnostic(diagnostics, selected.diagnostic);
|
|
8042
|
+
if (selected.handler) nodes.set(String(selected.handler.id), selected.handler);
|
|
6487
8043
|
seenEdges.add(Number(impl.edge.id));
|
|
6488
|
-
edges.push({ step: 1, type: "operation_implemented_by_handler", from: op?.label ? String(op.label) : `operation:${scope.startOperationId}`, to:
|
|
8044
|
+
edges.push({ step: 1, type: "operation_implemented_by_handler", from: op?.label ? String(op.label) : `operation:${scope.startOperationId}`, to: selected.handler?.label ? String(selected.handler.label) : `${impl.edge.to_kind}:${impl.edge.to_id}`, evidence: selected.evidence, confidence: Number(impl.edge.confidence ?? 0), unresolvedReason: selected.unresolvedReason ?? (impl.edge.status === "resolved" || startSelection.methodId ? void 0 : String(impl.edge.unresolved_reason ?? impl.edge.status)) });
|
|
6489
8045
|
}
|
|
6490
8046
|
}
|
|
6491
8047
|
const seenScopes = /* @__PURE__ */ new Set();
|
|
@@ -6513,7 +8069,7 @@ function trace(db, start, options) {
|
|
|
6513
8069
|
const nextKey = `${nextRepoId}:${[...nextSymbols].join(",")}:${[...nextFiles].join(",")}`;
|
|
6514
8070
|
const calleeNode = symbolNode(db, Number(symbolCall.callee_symbol_id));
|
|
6515
8071
|
if (calleeNode) nodes.set(String(calleeNode.id), calleeNode);
|
|
6516
|
-
const evidence = { ...
|
|
8072
|
+
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
8073
|
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
8074
|
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
8075
|
else queue.push({ repoId: nextRepoId, files: nextFiles, symbolIds: nextSymbols, depth: current.depth + 1, context: contextForSymbolCall(db, symbolCall, callerBindings) });
|
|
@@ -6539,15 +8095,13 @@ function trace(db, start, options) {
|
|
|
6539
8095
|
for (const row of graphRows) {
|
|
6540
8096
|
if (seenEdges.has(Number(row.id))) continue;
|
|
6541
8097
|
seenEdges.add(Number(row.id));
|
|
6542
|
-
const persistedEvidence =
|
|
6543
|
-
String(row.evidence_json || "{}")
|
|
6544
|
-
);
|
|
8098
|
+
const persistedEvidence = parseEvidence(row.evidence_json);
|
|
6545
8099
|
const rawEvidence = baseTraceEvidence(row, call, persistedEvidence, contextual.evidence);
|
|
6546
8100
|
const effective = runtimeResolution(db, row, rawEvidence, {
|
|
6547
8101
|
vars: options.vars,
|
|
6548
8102
|
dynamicMode: options.dynamicMode ?? "strict",
|
|
6549
8103
|
maxDynamicCandidates: options.maxDynamicCandidates
|
|
6550
|
-
}, workspaceIdForCall(db, String(call.id)), contextual.
|
|
8104
|
+
}, workspaceIdForCall(db, String(call.id)), contextual.state);
|
|
6551
8105
|
const evidence = effective.evidence;
|
|
6552
8106
|
const effectiveRow = effective.row;
|
|
6553
8107
|
const targetNode = `${effectiveRow.to_kind}:${effectiveRow.to_id}`;
|
|
@@ -6567,35 +8121,59 @@ function trace(db, start, options) {
|
|
|
6567
8121
|
confidence: Number(effectiveRow.confidence ?? call.confidence),
|
|
6568
8122
|
unresolvedReason: effective.unresolvedReason
|
|
6569
8123
|
});
|
|
6570
|
-
if ((options.dynamicMode ?? "strict") === "candidates")
|
|
8124
|
+
if ((options.dynamicMode ?? "strict") === "candidates" && effectiveRow.status !== "resolved")
|
|
6571
8125
|
edges.push(...dynamicCandidateBranches(current.depth, call, evidence));
|
|
6572
8126
|
if (effectiveRow.to_kind === "operation") {
|
|
6573
8127
|
const implementation = implementationScope(db, effectiveRow.to_id);
|
|
6574
|
-
const contextSelection =
|
|
8128
|
+
const contextSelection = contextualImplementationSelection(
|
|
8129
|
+
db,
|
|
8130
|
+
implementation.edge,
|
|
8131
|
+
effectiveRow.to_id,
|
|
8132
|
+
current.repoId,
|
|
8133
|
+
evidence,
|
|
8134
|
+
hintOptions
|
|
8135
|
+
);
|
|
6575
8136
|
const contextMethodId = contextSelection.methodId;
|
|
6576
|
-
|
|
8137
|
+
let selectedHandlerAvailable = true;
|
|
6577
8138
|
if (implementation.edge) {
|
|
6578
|
-
const implEvidence =
|
|
6579
|
-
const hintDiagnostic = implementationHintDiagnostic(contextSelection, implEvidence
|
|
8139
|
+
const implEvidence = parseEvidence(implementation.edge.evidence_json);
|
|
8140
|
+
const hintDiagnostic = implementationHintDiagnostic(contextSelection, implEvidence);
|
|
6580
8141
|
if (hintDiagnostic) prependTraceDiagnostic(diagnostics, hintDiagnostic);
|
|
6581
|
-
const
|
|
6582
|
-
const
|
|
6583
|
-
|
|
8142
|
+
const selectedMethodId = implementation.edge.status === "resolved" ? implementation.edge.to_id : contextMethodId;
|
|
8143
|
+
const selectionEvidence = contextMethodId ? {
|
|
8144
|
+
...implEvidence,
|
|
8145
|
+
contextualImplementationSelected: contextSelection.evidence.strategy !== "implementation_repo_hint",
|
|
8146
|
+
contextualImplementation: contextSelection.evidence,
|
|
8147
|
+
implementationSelection: contextSelection.evidence
|
|
8148
|
+
} : {
|
|
8149
|
+
...implEvidence,
|
|
8150
|
+
contextualImplementation: contextSelection.evidence,
|
|
8151
|
+
implementationSelection: contextSelection.evidence
|
|
8152
|
+
};
|
|
8153
|
+
const selected = selectedMethodId ? withSelectedHandlerProvenance(
|
|
8154
|
+
selectionEvidence,
|
|
8155
|
+
selectedMethodId,
|
|
8156
|
+
handlerMethodNode(db, selectedMethodId)
|
|
8157
|
+
) : { evidence: selectionEvidence };
|
|
8158
|
+
selectedHandlerAvailable = !selected.unresolvedReason;
|
|
8159
|
+
if (selected.diagnostic) prependTraceDiagnostic(diagnostics, selected.diagnostic);
|
|
8160
|
+
const implTo = selected.handler?.label ? String(selected.handler.label) : `${implementation.edge.to_kind}:${implementation.edge.to_id}`;
|
|
8161
|
+
if (selected.handler) nodes.set(String(selected.handler.id), selected.handler);
|
|
6584
8162
|
edges.push({
|
|
6585
8163
|
step: current.depth,
|
|
6586
8164
|
type: "operation_implemented_by_handler",
|
|
6587
8165
|
from: to,
|
|
6588
8166
|
to: implTo,
|
|
6589
|
-
evidence:
|
|
8167
|
+
evidence: selected.evidence,
|
|
6590
8168
|
confidence: Number(implementation.edge.confidence ?? 0),
|
|
6591
|
-
unresolvedReason: implementation.edge.status === "resolved" || contextMethodId ? void 0 : String(implementation.edge.unresolved_reason ?? implementation.edge.status)
|
|
8169
|
+
unresolvedReason: selected.unresolvedReason ?? (implementation.edge.status === "resolved" || contextMethodId ? void 0 : String(implementation.edge.unresolved_reason ?? implementation.edge.status))
|
|
6592
8170
|
});
|
|
6593
8171
|
}
|
|
6594
8172
|
if (current.depth >= maxDepth) continue;
|
|
6595
8173
|
const contextScope = contextMethodId ? handlerScope(db, contextMethodId) : void 0;
|
|
6596
8174
|
const files = contextScope?.files ?? (implementation.files.size > 0 ? implementation.files : handlerFilesForOperation(db, effectiveRow.to_id));
|
|
6597
8175
|
const symbolIds = contextScope?.symbolId ? /* @__PURE__ */ new Set([contextScope.symbolId]) : implementation.symbolId ? /* @__PURE__ */ new Set([implementation.symbolId]) : void 0;
|
|
6598
|
-
if ((implementation.edge?.status === "resolved" || contextScope) && files.size > 0) {
|
|
8176
|
+
if (selectedHandlerAvailable && (implementation.edge?.status === "resolved" || contextScope) && files.size > 0) {
|
|
6599
8177
|
const targetRepoId = contextScope?.repoId ?? implementation.repoId ?? db.prepare(
|
|
6600
8178
|
"SELECT s.repo_id repoId FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?"
|
|
6601
8179
|
).get(effectiveRow.to_id)?.repoId;
|
|
@@ -6630,6 +8208,7 @@ function trace(db, start, options) {
|
|
|
6630
8208
|
}
|
|
6631
8209
|
|
|
6632
8210
|
export {
|
|
8211
|
+
projectBoundedInOrder,
|
|
6633
8212
|
upsertWorkspace,
|
|
6634
8213
|
getWorkspace,
|
|
6635
8214
|
upsertRepository,
|
|
@@ -6671,4 +8250,4 @@ export {
|
|
|
6671
8250
|
selectorRepoAmbiguousDiagnostic,
|
|
6672
8251
|
trace
|
|
6673
8252
|
};
|
|
6674
|
-
//# sourceMappingURL=chunk-
|
|
8253
|
+
//# sourceMappingURL=chunk-ERIZHM5C.js.map
|