@saptools/service-flow 0.1.52 → 0.1.53

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