@saptools/service-flow 0.1.45 → 0.1.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.46
4
+
5
+ - Improved ambiguous implementation diagnostics with ready-to-copy scoped hint suggestions for each blocked helper hop.
6
+ - Persisted deterministic service-client ownership across helper boundaries, object and tuple destructuring, returned clients, and transaction aliases while preserving dynamic evidence for ambiguous flows.
7
+ - Tightened OData operation-invocation normalization and dynamic wrapper-path evidence without promoting entity addressing paths to operation calls.
8
+ - Kept strict doctor output concise by default with actionable categories and detail-mode evidence expansion.
9
+
3
10
  ## 0.1.45
4
11
 
5
12
  - Added repeatable scoped implementation hints with explicit selection and mismatch evidence while preserving conservative automatic and legacy repository selection.
@@ -2316,6 +2316,171 @@ function substituteVariables(template, vars) {
2316
2316
  };
2317
2317
  }
2318
2318
 
2319
+ // src/trace/implementation-hints.ts
2320
+ function parseImplementationHint(value) {
2321
+ const hint = {};
2322
+ for (const part of value.split(",")) {
2323
+ const separator = part.indexOf("=");
2324
+ if (separator <= 0 || separator === part.length - 1) throw new Error(`Invalid implementation hint field: ${part}`);
2325
+ assignHintField(hint, part.slice(0, separator).trim(), part.slice(separator + 1).trim());
2326
+ }
2327
+ if (!hint.implementationRepo) throw new Error("Scoped implementation hint requires an implementation repo selection");
2328
+ return { ...hint, implementationRepo: hint.implementationRepo };
2329
+ }
2330
+ function selectImplementation(rawEvidence, hints, legacyRepo) {
2331
+ const evidence = asEvidence(rawEvidence);
2332
+ const scoped = hints ?? [];
2333
+ const matchingHints = scoped.filter((hint2) => hintMatchesEdge(hint2, evidence));
2334
+ if (matchingHints.length === 0) {
2335
+ if (legacyRepo) return selectCandidate(evidence, legacyHint(legacyRepo), "implementation_repo_hint");
2336
+ const reason = scoped.length > 0 ? "no_scoped_hint_matched_edge" : "no_implementation_hint_supplied";
2337
+ return { blocksAutomatic: false, evidence: { status: "not_matched", reason, strategy: "scoped_implementation_hint" } };
2338
+ }
2339
+ if (matchingHints.length > 1) {
2340
+ return {
2341
+ blocksAutomatic: true,
2342
+ evidence: {
2343
+ status: "tied",
2344
+ reason: "multiple_scoped_hints_matched_edge",
2345
+ strategy: "scoped_implementation_hint",
2346
+ matchedHints: matchingHints,
2347
+ candidateCount: matchingHints.length
2348
+ }
2349
+ };
2350
+ }
2351
+ const hint = matchingHints[0];
2352
+ return hint ? selectCandidate(evidence, hint, "scoped_implementation_hint") : { blocksAutomatic: false, evidence: { status: "not_matched" } };
2353
+ }
2354
+ function implementationHintDiagnostic(selection, suggestions) {
2355
+ if (!selection.blocksAutomatic || selection.methodId) return void 0;
2356
+ return {
2357
+ severity: "warning",
2358
+ code: "implementation_hint_mismatch",
2359
+ message: "Implementation hint did not select exactly one viable candidate",
2360
+ hintStatus: selection.evidence.status,
2361
+ candidateCount: selection.evidence.candidateCount,
2362
+ implementationHintSuggestions: Array.isArray(suggestions) && suggestions.length > 0 ? suggestions : void 0,
2363
+ implementationSelection: selection.evidence
2364
+ };
2365
+ }
2366
+ function implementationHintSuggestions(rawEvidence) {
2367
+ const evidence = asEvidence(rawEvidence);
2368
+ const accepted = (evidence.candidates ?? []).filter((candidate) => candidate.accepted);
2369
+ if (accepted.length < 2) return [];
2370
+ const repos = selectableRepositories(accepted);
2371
+ return accepted.flatMap((candidate) => {
2372
+ const repo = candidate.handlerPackage?.name;
2373
+ if (!repo || !repos.includes(repo)) return [];
2374
+ const hint = suggestionHint(evidence, candidate, repo);
2375
+ return [{
2376
+ servicePath: hint.servicePath,
2377
+ operationPath: hint.operationPath,
2378
+ ambiguityReason: evidence.ambiguityReasons?.[0],
2379
+ candidateFamily: hint.candidateFamily,
2380
+ selectableImplementationRepositories: repos,
2381
+ implementationRepo: repo,
2382
+ hint,
2383
+ cli: `--implementation-hint ${hintString(hint)}`
2384
+ }];
2385
+ });
2386
+ }
2387
+ function selectableRepositories(candidates) {
2388
+ const repos = new Set(candidates.flatMap((candidate) => candidate.handlerPackage?.name ? [candidate.handlerPackage.name] : []));
2389
+ return [...repos].filter((repo) => candidates.filter((candidate) => candidateMatchesRepo(candidate, repo)).length === 1).sort();
2390
+ }
2391
+ function assignHintField(hint, key, value) {
2392
+ if (key === "service" || key === "servicePath") hint.servicePath = value;
2393
+ else if (key === "operation" || key === "operationPath") hint.operationPath = value;
2394
+ else if (key === "package" || key === "packageName") hint.packageName = value;
2395
+ else if (key === "repository" || key === "repositoryName") hint.repositoryName = value;
2396
+ else if (key === "family" || key === "candidateFamily") hint.candidateFamily = value;
2397
+ else if (key === "repo" || key === "implementationRepo" || key === "select") hint.implementationRepo = value;
2398
+ else throw new Error(`Unknown implementation hint field: ${key}`);
2399
+ }
2400
+ function selectCandidate(evidence, hint, strategy) {
2401
+ const matches2 = (evidence.candidates ?? []).filter((candidate) => candidate.accepted && candidateMatchesRepo(candidate, hint.implementationRepo));
2402
+ const selected = matches2.length === 1 ? matches2[0] : void 0;
2403
+ if (!selected || selected.methodId === void 0) {
2404
+ return {
2405
+ blocksAutomatic: true,
2406
+ evidence: {
2407
+ status: matches2.length > 1 ? "tied" : "not_matched",
2408
+ reason: matches2.length > 1 ? "hint_matched_multiple_candidates" : "hint_matched_zero_candidates",
2409
+ strategy,
2410
+ matchedHint: hint,
2411
+ selectedRepo: hint.implementationRepo,
2412
+ candidateCount: matches2.length
2413
+ }
2414
+ };
2415
+ }
2416
+ return {
2417
+ methodId: String(selected.methodId),
2418
+ blocksAutomatic: false,
2419
+ evidence: {
2420
+ status: "selected",
2421
+ guided: true,
2422
+ strategy,
2423
+ matchedHint: hint,
2424
+ selectedRepo: hint.implementationRepo,
2425
+ selectedMethodId: selected.methodId,
2426
+ ambiguityReason: evidence.ambiguityReasons?.[0]
2427
+ }
2428
+ };
2429
+ }
2430
+ function suggestionHint(evidence, candidate, repo) {
2431
+ const servicePath = evidence.servicePath ?? candidate.servicePath;
2432
+ const operationPath = evidence.operationPath ?? candidate.operationPath;
2433
+ const family = usefulCandidateFamily(evidence, candidate);
2434
+ return {
2435
+ ...servicePath ? { servicePath } : {},
2436
+ ...operationPath ? { operationPath } : {},
2437
+ ...evidence.modelPackage?.packageName ? { packageName: evidence.modelPackage.packageName } : {},
2438
+ ...evidence.modelPackage?.name ? { repositoryName: evidence.modelPackage.name } : {},
2439
+ ...family ? { candidateFamily: family } : {},
2440
+ implementationRepo: repo
2441
+ };
2442
+ }
2443
+ function usefulCandidateFamily(evidence, candidate) {
2444
+ const family = candidate.handlerPackage?.packageName;
2445
+ if (!family) return void 0;
2446
+ if ((evidence.candidateFamilies ?? []).some((item) => item.packageName === family)) return family;
2447
+ const acceptedFamilies = new Set(
2448
+ (evidence.candidates ?? []).filter((item) => item.accepted).flatMap((item) => item.handlerPackage?.packageName ? [item.handlerPackage.packageName] : [])
2449
+ );
2450
+ return acceptedFamilies.size > 1 ? family : void 0;
2451
+ }
2452
+ function hintString(hint) {
2453
+ const fields = [
2454
+ ["service", hint.servicePath],
2455
+ ["operation", hint.operationPath],
2456
+ ["package", hint.packageName],
2457
+ ["repository", hint.repositoryName],
2458
+ ["family", hint.candidateFamily],
2459
+ ["repo", hint.implementationRepo]
2460
+ ];
2461
+ return fields.flatMap(([key, value]) => value ? [`${key}=${value}`] : []).join(",");
2462
+ }
2463
+ function hintMatchesEdge(hint, evidence) {
2464
+ const model = evidence.modelPackage ?? evidence.candidates?.[0]?.modelPackage;
2465
+ const familyNames = /* @__PURE__ */ new Set([
2466
+ ...(evidence.candidateFamilies ?? []).flatMap((family) => family.packageName ? [family.packageName] : []),
2467
+ ...(evidence.candidates ?? []).flatMap((candidate) => candidate.handlerPackage?.packageName ? [candidate.handlerPackage.packageName] : [])
2468
+ ]);
2469
+ return matches(hint.servicePath, evidence.servicePath ?? evidence.candidates?.[0]?.servicePath) && matches(hint.operationPath, evidence.operationPath ?? evidence.candidates?.[0]?.operationPath) && matches(hint.packageName, model?.packageName) && matches(hint.repositoryName, model?.name) && (!hint.candidateFamily || familyNames.has(hint.candidateFamily));
2470
+ }
2471
+ function candidateMatchesRepo(candidate, value) {
2472
+ return candidate.handlerPackage?.name === value || candidate.handlerPackage?.packageName === value || candidate.sourceFile?.startsWith(value) === true;
2473
+ }
2474
+ function matches(expected, actual) {
2475
+ return expected === void 0 || expected === actual;
2476
+ }
2477
+ function legacyHint(implementationRepo) {
2478
+ return { implementationRepo };
2479
+ }
2480
+ function asEvidence(value) {
2481
+ return value;
2482
+ }
2483
+
2319
2484
  // src/linker/remote-query-target.ts
2320
2485
  function buildRemoteQueryTarget(input) {
2321
2486
  const entity = typeof input.queryEntity === "string" && input.queryEntity.trim() ? input.queryEntity.trim() : void 0;
@@ -2760,13 +2925,14 @@ function linkImplementations(db, workspaceId, generation) {
2760
2925
  candidateFamilies: duplicateFamilies,
2761
2926
  candidates: candidates.map((candidate, index) => candidateEvidence(candidate, index + 1))
2762
2927
  };
2928
+ const evidenceWithHints = unique ? evidence : { ...evidence, implementationHintSuggestions: implementationHintSuggestions(evidence) };
2763
2929
  if (accepted.length === 0) {
2764
- 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(evidence), 0, "No implementation candidate passed policy", generation);
2930
+ 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);
2765
2931
  edgeCount += 1;
2766
2932
  unresolvedCount += 1;
2767
2933
  continue;
2768
2934
  }
2769
- 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", unique ? "resolved" : "ambiguous", "operation", graphId(operation.operationId), unique ? "handler_method" : "handler_method_candidates", unique ? graphId(unique.methodId) : selected.map((row) => graphId(row.methodId)).join(","), unique ? 0.95 : 0.5, JSON.stringify(evidence), 0, unique ? null : "Ambiguous registered handler implementation candidates", generation);
2935
+ 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", unique ? "resolved" : "ambiguous", "operation", graphId(operation.operationId), unique ? "handler_method" : "handler_method_candidates", unique ? graphId(unique.methodId) : selected.map((row) => graphId(row.methodId)).join(","), unique ? 0.95 : 0.5, JSON.stringify(evidenceWithHints), 0, unique ? null : "Ambiguous registered handler implementation candidates", generation);
2770
2936
  edgeCount += 1;
2771
2937
  if (unique) resolvedCount += 1;
2772
2938
  else ambiguousCount += 1;
@@ -3005,112 +3171,6 @@ function normalizedOperation(value) {
3005
3171
  return value.startsWith("/") ? value.slice(1) : value;
3006
3172
  }
3007
3173
 
3008
- // src/trace/implementation-hints.ts
3009
- function parseImplementationHint(value) {
3010
- const hint = {};
3011
- for (const part of value.split(",")) {
3012
- const separator = part.indexOf("=");
3013
- if (separator <= 0 || separator === part.length - 1) throw new Error(`Invalid implementation hint field: ${part}`);
3014
- assignHintField(hint, part.slice(0, separator).trim(), part.slice(separator + 1).trim());
3015
- }
3016
- if (!hint.implementationRepo) throw new Error("Scoped implementation hint requires an implementation repo selection");
3017
- return { ...hint, implementationRepo: hint.implementationRepo };
3018
- }
3019
- function selectImplementation(rawEvidence, hints, legacyRepo) {
3020
- const evidence = asEvidence(rawEvidence);
3021
- const scoped = hints ?? [];
3022
- const matchingHints = scoped.filter((hint2) => hintMatchesEdge(hint2, evidence));
3023
- if (matchingHints.length === 0) {
3024
- if (legacyRepo) return selectCandidate(evidence, legacyHint(legacyRepo), "implementation_repo_hint");
3025
- const reason = scoped.length > 0 ? "no_scoped_hint_matched_edge" : "no_implementation_hint_supplied";
3026
- return { blocksAutomatic: false, evidence: { status: "not_matched", reason, strategy: "scoped_implementation_hint" } };
3027
- }
3028
- if (matchingHints.length > 1) {
3029
- return {
3030
- blocksAutomatic: true,
3031
- evidence: {
3032
- status: "tied",
3033
- reason: "multiple_scoped_hints_matched_edge",
3034
- strategy: "scoped_implementation_hint",
3035
- matchedHints: matchingHints,
3036
- candidateCount: matchingHints.length
3037
- }
3038
- };
3039
- }
3040
- const hint = matchingHints[0];
3041
- return hint ? selectCandidate(evidence, hint, "scoped_implementation_hint") : { blocksAutomatic: false, evidence: { status: "not_matched" } };
3042
- }
3043
- function implementationHintDiagnostic(selection) {
3044
- if (!selection.blocksAutomatic || selection.methodId) return void 0;
3045
- return {
3046
- severity: "warning",
3047
- code: "implementation_hint_mismatch",
3048
- message: "Implementation hint did not select exactly one viable candidate",
3049
- hintStatus: selection.evidence.status,
3050
- candidateCount: selection.evidence.candidateCount,
3051
- implementationSelection: selection.evidence
3052
- };
3053
- }
3054
- function assignHintField(hint, key, value) {
3055
- if (key === "service" || key === "servicePath") hint.servicePath = value;
3056
- else if (key === "operation" || key === "operationPath") hint.operationPath = value;
3057
- else if (key === "package" || key === "packageName") hint.packageName = value;
3058
- else if (key === "repository" || key === "repositoryName") hint.repositoryName = value;
3059
- else if (key === "family" || key === "candidateFamily") hint.candidateFamily = value;
3060
- else if (key === "repo" || key === "implementationRepo" || key === "select") hint.implementationRepo = value;
3061
- else throw new Error(`Unknown implementation hint field: ${key}`);
3062
- }
3063
- function selectCandidate(evidence, hint, strategy) {
3064
- const matches2 = (evidence.candidates ?? []).filter((candidate) => candidate.accepted && candidateMatchesRepo(candidate, hint.implementationRepo));
3065
- const selected = matches2.length === 1 ? matches2[0] : void 0;
3066
- if (!selected || selected.methodId === void 0) {
3067
- return {
3068
- blocksAutomatic: true,
3069
- evidence: {
3070
- status: matches2.length > 1 ? "tied" : "not_matched",
3071
- reason: matches2.length > 1 ? "hint_matched_multiple_candidates" : "hint_matched_zero_candidates",
3072
- strategy,
3073
- matchedHint: hint,
3074
- selectedRepo: hint.implementationRepo,
3075
- candidateCount: matches2.length
3076
- }
3077
- };
3078
- }
3079
- return {
3080
- methodId: String(selected.methodId),
3081
- blocksAutomatic: false,
3082
- evidence: {
3083
- status: "selected",
3084
- guided: true,
3085
- strategy,
3086
- matchedHint: hint,
3087
- selectedRepo: hint.implementationRepo,
3088
- selectedMethodId: selected.methodId,
3089
- ambiguityReason: evidence.ambiguityReasons?.[0]
3090
- }
3091
- };
3092
- }
3093
- function hintMatchesEdge(hint, evidence) {
3094
- const model = evidence.modelPackage ?? evidence.candidates?.[0]?.modelPackage;
3095
- const familyNames = /* @__PURE__ */ new Set([
3096
- ...(evidence.candidateFamilies ?? []).flatMap((family) => family.packageName ? [family.packageName] : []),
3097
- ...(evidence.candidates ?? []).flatMap((candidate) => candidate.handlerPackage?.packageName ? [candidate.handlerPackage.packageName] : [])
3098
- ]);
3099
- return matches(hint.servicePath, evidence.servicePath ?? evidence.candidates?.[0]?.servicePath) && matches(hint.operationPath, evidence.operationPath ?? evidence.candidates?.[0]?.operationPath) && matches(hint.packageName, model?.packageName) && matches(hint.repositoryName, model?.name) && (!hint.candidateFamily || familyNames.has(hint.candidateFamily));
3100
- }
3101
- function candidateMatchesRepo(candidate, value) {
3102
- return candidate.handlerPackage?.name === value || candidate.handlerPackage?.packageName === value || candidate.sourceFile?.startsWith(value) === true;
3103
- }
3104
- function matches(expected, actual) {
3105
- return expected === void 0 || expected === actual;
3106
- }
3107
- function legacyHint(implementationRepo) {
3108
- return { implementationRepo };
3109
- }
3110
- function asEvidence(value) {
3111
- return value;
3112
- }
3113
-
3114
3174
  // src/trace/evidence.ts
3115
3175
  function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence) {
3116
3176
  const evidence = { ...persistedEvidence, ...contextualEvidence ?? {} };
@@ -3292,8 +3352,8 @@ function operationStartScope(db, repoId, start, hintOptions) {
3292
3352
  }
3293
3353
  if (impl.edge) {
3294
3354
  const evidence = parseEvidence(impl.edge.evidence_json);
3295
- const hintDiagnostic = implementationHintDiagnostic(hinted);
3296
- 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, candidates: evidence.candidates }];
3355
+ const hintDiagnostic = implementationHintDiagnostic(hinted, evidence.implementationHintSuggestions);
3356
+ 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, implementationHintSuggestions: evidence.implementationHintSuggestions, candidates: evidence.candidates }];
3297
3357
  return { operationId, diagnostics: hintDiagnostic ? [hintDiagnostic, ...diagnostics] : diagnostics };
3298
3358
  }
3299
3359
  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" }] };
@@ -3691,9 +3751,9 @@ function trace(db, start, options) {
3691
3751
  const contextMethodId = contextSelection.methodId;
3692
3752
  const contextNode = contextMethodId ? handlerMethodNode(db, contextMethodId) : void 0;
3693
3753
  if (implementation.edge) {
3694
- const hintDiagnostic = implementationHintDiagnostic(contextSelection);
3695
- if (hintDiagnostic) diagnostics.unshift(hintDiagnostic);
3696
3754
  const implEvidence = JSON.parse(String(implementation.edge.evidence_json || "{}"));
3755
+ const hintDiagnostic = implementationHintDiagnostic(contextSelection, implEvidence.implementationHintSuggestions);
3756
+ if (hintDiagnostic) diagnostics.unshift(hintDiagnostic);
3697
3757
  const handlerNode = implementation.edge.status === "resolved" ? handlerMethodNode(db, implementation.edge.to_id) : contextNode;
3698
3758
  const implTo = handlerNode?.label ? String(handlerNode.label) : `${implementation.edge.to_kind}:${implementation.edge.to_id}`;
3699
3759
  if (handlerNode) nodes.set(String(handlerNode.id), handlerNode);
@@ -3761,8 +3821,9 @@ export {
3761
3821
  applyVariables,
3762
3822
  extractPlaceholders,
3763
3823
  substituteVariables,
3764
- linkWorkspace,
3765
3824
  parseImplementationHint,
3825
+ implementationHintSuggestions,
3826
+ linkWorkspace,
3766
3827
  trace
3767
3828
  };
3768
- //# sourceMappingURL=chunk-BXSCE5CR.js.map
3829
+ //# sourceMappingURL=chunk-EGY2A4AT.js.map