@saptools/service-flow 0.1.52 → 0.1.54

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