@saptools/service-flow 0.1.24 → 0.1.26

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,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.26
4
+
5
+ - Resolve helper-internal remote sends contextually in trace output when service clients are passed through positional arguments or one-level destructured object parameters.
6
+ - Add auditable contextual binding evidence for propagated helper client receivers, including caller argument, caller object property, callee parameter, callee receiver, and propagation source.
7
+ - Keep nested `this.<property>.<method>()` symbol-call resolution conservative unless explicit same-file or relative-import helper instance evidence exists.
8
+ - Update strict doctor diagnostics for trace-time contextual propagation opportunities and nested `this` receiver quality signals.
9
+
10
+ ## 0.1.25
11
+
12
+ - Make class-instance symbol-call indexing conservative so only same-file and relatively imported helper classes are traced, while built-in collection, date, URL, error, typed-array, promise, and abort-controller instances are ignored.
13
+ - Persist identifier and one-level destructured object parameter metadata for executable symbols, including class methods, so contextual service-client binding propagation can map helper arguments to callee receiver names.
14
+ - Propagate contextual service bindings through positional helper arguments and destructured object helper parameters with auditable caller and callee evidence.
15
+ - Refine unresolved operation diagnostics when indexed candidates exist but service context is absent or the resolution score is below threshold.
16
+
3
17
  ## 0.1.24
4
18
 
5
19
  - Add conservative class instance method symbol-call resolution for same-file and relatively imported helper classes, with auditable class-instance evidence.
@@ -1767,13 +1767,21 @@ function insertCallEdge(db, workspaceId, call, vars, generation) {
1767
1767
  }
1768
1768
  const edgeType = callType === "local_db_query" ? "HANDLER_RUNS_DB_QUERY" : callType === "external_http" ? "HANDLER_CALLS_EXTERNAL_HTTP" : callType === "async_emit" ? "HANDLER_EMITS_EVENT" : callType === "async_subscribe" ? "EVENT_CONSUMED_BY_HANDLER" : resolution.status === "dynamic" ? "DYNAMIC_EDGE_CANDIDATE" : "UNRESOLVED_EDGE";
1769
1769
  const status = edgeType === "DYNAMIC_EDGE_CANDIDATE" ? "dynamic" : resolution.status === "ambiguous" ? "ambiguous" : edgeType === "UNRESOLVED_EDGE" ? "unresolved" : "terminal";
1770
- const unresolvedReason = status === "terminal" ? null : String(call.unresolved_reason ?? (resolution.status === "ambiguous" ? "Ambiguous operation candidates require a strong service signal" : resolution.status === "dynamic" ? `Dynamic target requires runtime variable overrides: ${(resolution.reasons.length ? resolution.reasons : ["missing runtime variables"]).join(", ")}` : "No indexed target operation matched"));
1770
+ const unresolvedReason = status === "terminal" ? null : String(call.unresolved_reason ?? unresolvedOperationReason(resolution));
1771
1771
  const targetKind = callType === "local_db_query" ? "db_entity" : callType.startsWith("async_") ? "event" : "external";
1772
1772
  const targetId = callType === "local_db_query" ? String(call.query_entity ?? "unknown") : callType === "remote_action" ? op ? `Remote action: ${op}` : call.unresolved_reason === "dynamic_operation_path_identifier" ? "Remote action: dynamic path" : "Remote action: unknown path" : String(call.event_name_expr ?? op ?? call.id);
1773
1773
  const graphLevelDynamic = edgeType === "DYNAMIC_EDGE_CANDIDATE" && resolution.status === "dynamic";
1774
1774
  db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, edgeType, status, "call", String(call.id), targetKind, targetId, Number(call.confidence ?? 0.2), JSON.stringify(evidence), graphLevelDynamic ? 1 : 0, unresolvedReason, generation);
1775
1775
  return { status, callType };
1776
1776
  }
1777
+ function unresolvedOperationReason(resolution) {
1778
+ if (resolution.status === "dynamic") return `Dynamic target requires runtime variable overrides: ${(resolution.reasons.length ? resolution.reasons : ["missing runtime variables"]).join(", ")}`;
1779
+ if (resolution.candidates.length === 0) return "No indexed target operation matched";
1780
+ if (resolution.reasons.includes("operation_path_only_has_no_strong_target_signal")) return "Operation candidates found but no strong service signal is available";
1781
+ if (resolution.reasons.includes("candidate_score_below_resolution_threshold")) return "Operation candidates found but resolution score is below threshold";
1782
+ if (resolution.status === "ambiguous") return "Ambiguous operation candidates require a strong service signal";
1783
+ return "Operation candidates found but resolution could not select a target";
1784
+ }
1777
1785
  function parseJson(value) {
1778
1786
  if (!value) return void 0;
1779
1787
  try {
@@ -2269,6 +2277,21 @@ function knownBindingsForCalls(db, calls) {
2269
2277
  }
2270
2278
  return map;
2271
2279
  }
2280
+ function knownBindingsForScope(db, repoId, symbolIds, files) {
2281
+ const map = /* @__PURE__ */ new Map();
2282
+ if (repoId === void 0) return map;
2283
+ const rows2 = db.prepare("SELECT id,variable_name variableName,alias,alias_expr aliasExpr,destination_expr destinationExpr,service_path_expr servicePathExpr,source_file sourceFile,source_line sourceLine FROM service_bindings WHERE repo_id=?").all(repoId);
2284
+ for (const row of rows2) {
2285
+ if (!row.variableName) continue;
2286
+ if (files && !files.has(String(row.sourceFile))) continue;
2287
+ if (symbolIds && symbolIds.size > 0) {
2288
+ const owner = db.prepare("SELECT id FROM symbols WHERE id IN (" + [...symbolIds].map(() => "?").join(",") + ") AND source_file=? AND start_line<=? AND end_line>=? LIMIT 1").get(...symbolIds, row.sourceFile, row.sourceLine, row.sourceLine);
2289
+ if (!owner) continue;
2290
+ }
2291
+ map.set(row.variableName, { ...row, bindingId: Number(row.id), source: "local_service_binding", calleeReceiver: row.variableName });
2292
+ }
2293
+ return map;
2294
+ }
2272
2295
  function contextForSymbolCall(db, symbolCall, callerBindings) {
2273
2296
  const next = /* @__PURE__ */ new Map();
2274
2297
  if (callerBindings.size === 0) return next;
@@ -2276,19 +2299,23 @@ function contextForSymbolCall(db, symbolCall, callerBindings) {
2276
2299
  const callee = db.prepare("SELECT evidence_json evidenceJson FROM symbols WHERE id=?").get(symbolCall.callee_symbol_id);
2277
2300
  const calleeEvidence = parseEvidence(callee?.evidenceJson);
2278
2301
  const params = Array.isArray(calleeEvidence.parameters) ? calleeEvidence.parameters.filter((item) => typeof item === "string") : [];
2302
+ const parameterBindings = Array.isArray(calleeEvidence.parameterBindings) ? calleeEvidence.parameterBindings.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
2279
2303
  const args = Array.isArray(callEvidence2.callArguments) ? callEvidence2.callArguments : [];
2280
2304
  args.forEach((arg, index) => {
2281
- const param = params[index];
2282
- if (!param) return;
2305
+ const paramBinding = parameterBindings.find((binding) => binding.index === index);
2306
+ const param = paramBinding?.kind === "identifier" && typeof paramBinding.name === "string" ? paramBinding.name : params[index];
2283
2307
  if (arg.kind === "identifier" && typeof arg.name === "string") {
2284
2308
  const binding = callerBindings.get(arg.name);
2285
- if (binding) next.set(param, { ...binding, source: "local_symbol_argument", callerArgument: arg.name, calleeParameter: param, calleeReceiver: param });
2309
+ if (binding && param) next.set(param, { ...binding, source: "local_symbol_argument", callerArgument: arg.name, calleeParameter: param, calleeReceiver: param });
2286
2310
  }
2287
2311
  if (arg.kind === "object_literal" && Array.isArray(arg.properties)) {
2288
2312
  for (const prop of arg.properties) {
2289
2313
  if (typeof prop.property !== "string" || typeof prop.argument !== "string") continue;
2290
2314
  const binding = callerBindings.get(prop.argument);
2291
- if (binding) next.set(`${param}.${prop.property}`, { ...binding, source: "local_symbol_object_argument", callerProperty: prop.property, callerArgument: prop.argument, calleeParameter: param, calleeReceiver: `${param}.${prop.property}` });
2315
+ if (!binding) continue;
2316
+ const destructured = paramBinding?.kind === "object_pattern" && Array.isArray(paramBinding.properties) ? paramBinding.properties.find((item) => item.property === prop.property && typeof item.local === "string") : void 0;
2317
+ if (destructured && typeof destructured.local === "string") next.set(destructured.local, { ...binding, source: "local_symbol_destructured_object_argument", callerProperty: prop.property, callerArgument: prop.argument, calleeParameter: String(index), calleeReceiver: destructured.local });
2318
+ else if (param) next.set(`${param}.${prop.property}`, { ...binding, source: "local_symbol_object_argument", callerProperty: prop.property, callerArgument: prop.argument, calleeParameter: param, calleeReceiver: `${param}.${prop.property}` });
2292
2319
  }
2293
2320
  }
2294
2321
  });
@@ -2361,7 +2388,8 @@ function trace(db, start, options) {
2361
2388
  while (queue.length > 0) {
2362
2389
  const current = queue.shift();
2363
2390
  if (!current || current.depth > maxDepth) continue;
2364
- const key = `${current.repoId ?? "*"}:${[...current.symbolIds ?? /* @__PURE__ */ new Set(["*"])].sort().join(",")}:${[...current.files ?? /* @__PURE__ */ new Set(["*"])].sort().join(",")}`;
2391
+ const contextKey = [...(current.context ?? /* @__PURE__ */ new Map()).keys()].sort().join(",");
2392
+ const key = `${current.repoId ?? "*"}:${[...current.symbolIds ?? /* @__PURE__ */ new Set(["*"])].sort().join(",")}:${[...current.files ?? /* @__PURE__ */ new Set(["*"])].sort().join(",")}:${contextKey}`;
2365
2393
  if (seenScopes.has(key)) continue;
2366
2394
  seenScopes.add(key);
2367
2395
  const calls = db.prepare(
@@ -2370,7 +2398,7 @@ function trace(db, start, options) {
2370
2398
  const filtered = calls.filter(
2371
2399
  (c) => (!current.symbolIds || current.symbolIds.has(Number(c.source_symbol_id))) && (!current.files || current.files.has(String(c.source_file))) && includeCall(String(c.call_type), options)
2372
2400
  );
2373
- const callerBindings = new Map([...current.context ?? /* @__PURE__ */ new Map(), ...knownBindingsForCalls(db, filtered)]);
2401
+ const callerBindings = new Map([...current.context ?? /* @__PURE__ */ new Map(), ...knownBindingsForScope(db, current.repoId, current.symbolIds, current.files), ...knownBindingsForCalls(db, filtered)]);
2374
2402
  if (current.symbolIds && current.symbolIds.size > 0 && current.depth < maxDepth) {
2375
2403
  const symbolRows = db.prepare(`SELECT sc.*,s.repo_id calleeRepoId,s.source_file calleeFile FROM symbol_calls sc LEFT JOIN symbols s ON s.id=sc.callee_symbol_id WHERE sc.caller_symbol_id IN (${[...current.symbolIds].map(() => "?").join(",")}) ORDER BY sc.source_file,sc.source_line`).all(...current.symbolIds);
2376
2404
  for (const symbolCall of symbolRows) {
@@ -2401,7 +2429,7 @@ function trace(db, start, options) {
2401
2429
  line: call.source_line,
2402
2430
  callType: call.call_type
2403
2431
  });
2404
- const contextual = contextualRuntimeResolution(db, call, (current.context ?? /* @__PURE__ */ new Map()).get(receiverFromEvidence(call.evidence_json) ?? ""), workspaceIdForCall(db, String(call.id)));
2432
+ const contextual = contextualRuntimeResolution(db, call, callerBindings.get(receiverFromEvidence(call.evidence_json) ?? ""), workspaceIdForCall(db, String(call.id)));
2405
2433
  const graphRows = contextual.row ? [contextual.row] : graph.get(Number(call.id)) ?? [];
2406
2434
  for (const row of graphRows) {
2407
2435
  if (seenEdges.has(Number(row.id))) continue;
@@ -2503,4 +2531,4 @@ export {
2503
2531
  linkWorkspace,
2504
2532
  trace
2505
2533
  };
2506
- //# sourceMappingURL=chunk-RXJNPONU.js.map
2534
+ //# sourceMappingURL=chunk-Q3J2QDNX.js.map