@saptools/service-flow 0.1.68 → 0.1.70

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 (124) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +11 -5
  3. package/TECHNICAL-NOTE.md +13 -0
  4. package/dist/{chunk-AEM4JY22.js → chunk-GSLFY6J2.js} +6986 -5503
  5. package/dist/chunk-GSLFY6J2.js.map +1 -0
  6. package/dist/cli.js +334 -122
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +7 -1
  9. package/dist/index.js +1 -1
  10. package/package.json +1 -1
  11. package/src/cli/{002-doctor-lifecycle.ts → doctor-lifecycle.ts} +1 -1
  12. package/src/cli/{001-doctor-projection.ts → doctor-projection.ts} +1 -1
  13. package/src/cli/doctor.ts +5 -5
  14. package/src/cli/index-summary.ts +22 -0
  15. package/src/cli.ts +155 -91
  16. package/src/db/{009-binding-fact-semantics.ts → binding-fact-semantics.ts} +6 -1
  17. package/src/db/binding-helper-provenance.ts +17 -0
  18. package/src/db/{012-binding-reference-proof.ts → binding-reference-proof.ts} +4 -0
  19. package/src/db/{000-call-fact-repository.ts → call-fact-repository.ts} +49 -23
  20. package/src/db/{003-current-fact-semantics.ts → current-fact-semantics.ts} +5 -4
  21. package/src/db/{001-fact-lifecycle.ts → fact-lifecycle.ts} +3 -3
  22. package/src/db/index-publication-failure.ts +91 -0
  23. package/src/db/{007-package-fact-semantics.ts → package-fact-semantics.ts} +6 -6
  24. package/src/db/{010-package-symbol-surface-semantics.ts → package-symbol-surface-semantics.ts} +2 -2
  25. package/src/db/{004-package-target-invalidation.ts → package-target-invalidation.ts} +11 -1
  26. package/src/db/{008-relative-fact-semantics.ts → relative-fact-semantics.ts} +9 -6
  27. package/src/db/{006-relative-symbol-resolution.ts → relative-symbol-resolution.ts} +28 -7
  28. package/src/db/repositories.ts +24 -7
  29. package/src/db/{011-symbol-call-semantics.ts → symbol-call-semantics.ts} +1 -1
  30. package/src/index.ts +3 -3
  31. package/src/indexer/cds-extension-resolver.ts +27 -3
  32. package/src/indexer/repository-indexer.ts +70 -9
  33. package/src/indexer/workspace-indexer.ts +142 -30
  34. package/src/linker/call-edge-insertion.ts +568 -0
  35. package/src/linker/{002-call-evidence.ts → call-evidence.ts} +2 -2
  36. package/src/linker/cross-repo-linker.ts +6 -169
  37. package/src/linker/dynamic-edge-resolver.ts +1 -1
  38. package/src/linker/{004-event-subscription-handler-linker.ts → event-subscription-handler-linker.ts} +32 -11
  39. package/src/linker/event-template-link.ts +72 -0
  40. package/src/linker/external-http-target.ts +1 -1
  41. package/src/linker/helper-package-linker.ts +1 -1
  42. package/src/linker/{000-implementation-candidates.ts → implementation-candidates.ts} +1 -1
  43. package/src/linker/{001-implementation-evidence-projection.ts → implementation-evidence-projection.ts} +1 -1
  44. package/src/linker/odata-path-normalizer.ts +1 -1
  45. package/src/linker/{005-odata-path-structure.ts → odata-path-structure.ts} +1 -1
  46. package/src/linker/{003-package-import-symbol-resolver.ts → package-import-symbol-resolver.ts} +3 -3
  47. package/src/linker/service-resolver.ts +2 -2
  48. package/src/output/{001-compact-json-output.ts → compact-json-output.ts} +1 -1
  49. package/src/parsers/{006-binding-identity.ts → binding-identity.ts} +9 -4
  50. package/src/parsers/{011-binding-lexical-scope.ts → binding-lexical-scope.ts} +1 -1
  51. package/src/parsers/{021-binding-visibility.ts → binding-visibility.ts} +18 -2
  52. package/src/parsers/{000-direct-query-execution.ts → direct-query-execution.ts} +11 -0
  53. package/src/parsers/{005-event-subscription-facts.ts → event-subscription-facts.ts} +2 -2
  54. package/src/parsers/{016-local-symbol-reference.ts → local-symbol-reference.ts} +2 -2
  55. package/src/parsers/operation-path-analysis.ts +1 -1
  56. package/src/parsers/outbound-call-classifier.ts +692 -0
  57. package/src/parsers/outbound-call-parser.ts +146 -509
  58. package/src/parsers/outbound-expression-analysis.ts +700 -0
  59. package/src/parsers/{018-package-commonjs-syntax.ts → package-commonjs-syntax.ts} +1 -1
  60. package/src/parsers/{012-package-fact-contract.ts → package-fact-contract.ts} +3 -3
  61. package/src/parsers/{010-package-public-surface-analysis.ts → package-public-surface-analysis.ts} +2 -2
  62. package/src/parsers/{003-package-public-surface.ts → package-public-surface.ts} +3 -3
  63. package/src/parsers/{008-package-surface-publication.ts → package-surface-publication.ts} +1 -1
  64. package/src/parsers/{001-query-entity-resolution.ts → query-entity-resolution.ts} +1 -1
  65. package/src/parsers/{015-service-binding-collector.ts → service-binding-collector.ts} +3 -3
  66. package/src/parsers/{014-service-binding-helper-flow.ts → service-binding-helper-flow.ts} +72 -6
  67. package/src/parsers/service-binding-parser-helpers.ts +1 -1
  68. package/src/parsers/service-binding-parser.ts +2 -2
  69. package/src/parsers/{007-source-fact-reconciliation.ts → source-fact-reconciliation.ts} +3 -3
  70. package/src/parsers/{020-stable-local-value.ts → stable-local-value.ts} +2 -2
  71. package/src/parsers/{009-symbol-call-facts.ts → symbol-call-facts.ts} +3 -3
  72. package/src/parsers/{017-symbol-derived-contexts.ts → symbol-derived-contexts.ts} +2 -2
  73. package/src/parsers/symbol-parser.ts +43 -11
  74. package/src/trace/{014-compact-contract.ts → compact-contract.ts} +6 -0
  75. package/src/trace/{021-compact-decision-normalization.ts → compact-decision-normalization.ts} +77 -11
  76. package/src/trace/{020-compact-field-projection.ts → compact-field-projection.ts} +77 -4
  77. package/src/trace/{024-compact-observation-decision.ts → compact-observation-decision.ts} +10 -5
  78. package/src/trace/{016-compact-projector.ts → compact-projector.ts} +6 -7
  79. package/src/trace/{018-compact-trace.ts → compact-trace.ts} +3 -3
  80. package/src/trace/{006-contextual-projection.ts → contextual-projection.ts} +1 -1
  81. package/src/trace/{008-contextual-runtime-state.ts → contextual-runtime-state.ts} +1 -1
  82. package/src/trace/{001-dynamic-identity.ts → dynamic-identity.ts} +2 -2
  83. package/src/trace/{003-dynamic-references.ts → dynamic-references.ts} +2 -2
  84. package/src/trace/dynamic-targets.ts +6 -6
  85. package/src/trace/event-runtime-resolution.ts +151 -0
  86. package/src/trace/{011-event-subscriber-traversal.ts → event-subscriber-traversal.ts} +101 -9
  87. package/src/trace/evidence.ts +2 -2
  88. package/src/trace/implementation-hints.ts +10 -7
  89. package/src/trace/{005-implementation-selection.ts → implementation-selection.ts} +2 -2
  90. package/src/trace/{007-implementation-start-diagnostic.ts → implementation-start-diagnostic.ts} +1 -0
  91. package/src/trace/local-call-expansion.ts +37 -0
  92. package/src/trace/{009-selected-handler-provenance.ts → selected-handler-provenance.ts} +1 -1
  93. package/src/trace/selectors.ts +2 -1
  94. package/src/trace/{017-trace-context.ts → trace-context.ts} +2 -2
  95. package/src/trace/{015-trace-edge-recorder.ts → trace-edge-recorder.ts} +62 -5
  96. package/src/trace/{019-trace-edge-semantics.ts → trace-edge-semantics.ts} +9 -13
  97. package/src/trace/trace-engine.ts +10 -10
  98. package/src/trace/{022-trace-fact-preflight.ts → trace-fact-preflight.ts} +2 -2
  99. package/src/trace/{025-trace-implementation-scope.ts → trace-implementation-scope.ts} +1 -1
  100. package/src/trace/{028-trace-operation-execution.ts → trace-operation-execution.ts} +10 -10
  101. package/src/trace/{013-trace-root-scopes.ts → trace-root-scopes.ts} +5 -6
  102. package/src/trace/{027-trace-scope-execution.ts → trace-scope-execution.ts} +44 -44
  103. package/src/trace/{029-trace-start-implementation.ts → trace-start-implementation.ts} +7 -7
  104. package/src/trace/{026-trace-start-scope.ts → trace-start-scope.ts} +6 -5
  105. package/src/trace/{010-traversal-scope.ts → traversal-scope.ts} +1 -1
  106. package/src/types.ts +2 -1
  107. package/src/version.ts +1 -1
  108. package/dist/chunk-AEM4JY22.js.map +0 -1
  109. /package/src/cli/{000-clean.ts → clean.ts} +0 -0
  110. /package/src/cli/{003-doctor-package-resolution.ts → doctor-package-resolution.ts} +0 -0
  111. /package/src/db/{002-fact-json-inventory.ts → fact-json-inventory.ts} +0 -0
  112. /package/src/db/{005-schema-structure.ts → schema-structure.ts} +0 -0
  113. /package/src/output/{000-stdout-policy.ts → stdout-policy.ts} +0 -0
  114. /package/src/parsers/{019-binding-assignment-targets.ts → binding-assignment-targets.ts} +0 -0
  115. /package/src/parsers/{013-executable-body-eligibility.ts → executable-body-eligibility.ts} +0 -0
  116. /package/src/parsers/{004-fact-identity.ts → fact-identity.ts} +0 -0
  117. /package/src/parsers/{002-symbol-import-bindings.ts → symbol-import-bindings.ts} +0 -0
  118. /package/src/trace/{004-dynamic-candidate-sources.ts → dynamic-candidate-sources.ts} +0 -0
  119. /package/src/trace/{000-dynamic-target-types.ts → dynamic-target-types.ts} +0 -0
  120. /package/src/trace/{023-nested-event-scopes.ts → nested-event-scopes.ts} +0 -0
  121. /package/src/trace/{002-trace-diagnostics.ts → trace-diagnostics.ts} +0 -0
  122. /package/src/trace/{012-trace-graph-lookups.ts → trace-graph-lookups.ts} +0 -0
  123. /package/src/utils/{000-bounded-projection.ts → bounded-projection.ts} +0 -0
  124. /package/src/utils/{001-placeholders.ts → placeholders.ts} +0 -0
@@ -4,7 +4,7 @@ import type {
4
4
  TraceOptions,
5
5
  TraceStart,
6
6
  } from '../types.js';
7
- import { compareBinary } from './010-traversal-scope.js';
7
+ import { compareBinary } from './traversal-scope.js';
8
8
  import type {
9
9
  CompactDecisionInput,
10
10
  CompactDecisionV1,
@@ -16,12 +16,15 @@ import type {
16
16
  CompactQueryV1,
17
17
  CompactStartV1,
18
18
  CompactStatusCountsV1,
19
- } from './014-compact-contract.js';
19
+ } from './compact-contract.js';
20
20
  import {
21
21
  compactMissingRemediation,
22
+ isSafeCompactReferenceName,
23
+ isSafeCompactSelectorSuggestion,
24
+ projectCompactReferenceGroup,
22
25
  projectCompactMissingNames,
23
26
  type CompactMissingNameProjection,
24
- } from './021-compact-decision-normalization.js';
27
+ } from './compact-decision-normalization.js';
25
28
 
26
29
  const compactDiagnosticMessages: Readonly<Record<string, string>> = {
27
30
  schema_upgrade_required: 'The database schema must be upgraded before tracing.',
@@ -31,8 +34,19 @@ const compactDiagnosticMessages: Readonly<Record<string, string>> = {
31
34
  implementation_hint_mismatch: 'The implementation hint did not select one implementation.',
32
35
  selected_handler_provenance_mismatch: 'Selected handler provenance did not match its graph target.',
33
36
  selected_handler_target_not_found: 'The selected handler target is not indexed.',
37
+ trace_start_ambiguous: 'The trace start selector is ambiguous.',
38
+ trace_start_not_found: 'The trace start selector did not match an indexed start.',
34
39
  trace_start_implementation_unresolved: 'The trace start implementation is unresolved.',
35
40
  };
41
+ const selectorDiagnosticCodes = new Set([
42
+ 'handler_decorators_not_indexed',
43
+ 'handler_methods_not_indexed',
44
+ 'selector_repo_ambiguous',
45
+ 'selector_repo_not_found',
46
+ 'trace_start_ambiguous',
47
+ 'trace_start_implementation_unresolved',
48
+ 'trace_start_not_found',
49
+ ]);
36
50
 
37
51
  export function projectCompactDecision(
38
52
  input: CompactDecisionInput | undefined,
@@ -87,6 +101,8 @@ function addImplementationDecision(
87
101
  out.implementationGuided = input.implementationGuided;
88
102
  if (input.implementationContextual !== undefined)
89
103
  out.implementationContextual = input.implementationContextual;
104
+ if (input.tiedCandidateRepos)
105
+ out.tiedCandidateRepos = input.tiedCandidateRepos;
90
106
  }
91
107
 
92
108
  function addEventDecision(out: CompactDecisionV1, input: CompactDecisionInput): void {
@@ -243,6 +259,10 @@ function compactDiagnosticDetails(
243
259
  const out: CompactDiagnosticDetailsV1 = {};
244
260
  const reasonCode = compactSafeCode(value.reasonCode);
245
261
  if (reasonCode) out.reasonCode = reasonCode;
262
+ if (selectorDiagnosticCodes.has(code)) addDiagnosticSelector(out, value);
263
+ if (code === 'reindex_required') addInvalidFactCategories(out, value);
264
+ if (code === 'implementation_hint_mismatch')
265
+ addImplementationHintCandidates(out, value);
246
266
  if (code === 'trace_runtime_variables_missing') addDiagnosticNames(out, value);
247
267
  addDiagnosticCounts(out, value);
248
268
  const hint = compactDiagnosticRemediation(code, out);
@@ -255,6 +275,51 @@ function compactDiagnosticDetails(
255
275
  return out;
256
276
  }
257
277
 
278
+ function addDiagnosticSelector(
279
+ out: CompactDiagnosticDetailsV1,
280
+ value: Record<string, unknown>,
281
+ ): void {
282
+ const selectorKind = compactSafeCode(value.selectorKind);
283
+ if (selectorKind) out.selectorKind = selectorKind;
284
+ const suggestions = projectCompactReferenceGroup(
285
+ compactStringArray(value.selectorSuggestions),
286
+ value.selectorSuggestionCount,
287
+ isSafeCompactSelectorSuggestion,
288
+ );
289
+ if (suggestions) out.selectorSuggestions = suggestions;
290
+ }
291
+
292
+ function addInvalidFactCategories(
293
+ out: CompactDiagnosticDetailsV1,
294
+ value: Record<string, unknown>,
295
+ ): void {
296
+ const categories = compactRecordArray(value.invalidFactCategories)
297
+ .flatMap((item) =>
298
+ typeof item.category === 'string' ? [item.category] : []);
299
+ const projection = projectCompactReferenceGroup(
300
+ categories, value.invalidFactCategoryCount,
301
+ (item) => compactSafeCode(item) === item,
302
+ );
303
+ if (projection) out.invalidFactCategories = projection;
304
+ }
305
+
306
+ function addImplementationHintCandidates(
307
+ out: CompactDiagnosticDetailsV1,
308
+ value: Record<string, unknown>,
309
+ ): void {
310
+ const repos = compactRecordArray(value.implementationHintSuggestions)
311
+ .flatMap((item) =>
312
+ typeof item.implementationRepo === 'string'
313
+ ? [item.implementationRepo] : []);
314
+ const uniqueCount = new Set(repos.map((repo) => repo.trim())).size;
315
+ if (uniqueCount < 2) return;
316
+ const projection = projectCompactReferenceGroup(
317
+ repos, value.implementationHintSuggestionCount,
318
+ isSafeCompactReferenceName,
319
+ );
320
+ if (projection) out.tiedCandidateRepos = projection;
321
+ }
322
+
258
323
  function addDiagnosticNames(
259
324
  out: CompactDiagnosticDetailsV1,
260
325
  value: Record<string, unknown>,
@@ -345,7 +410,8 @@ function compactDiagnosticHintCount(
345
410
 
346
411
  function compactRemediationHint(code: string): string | undefined {
347
412
  if (code === 'provide_runtime_variables') return 'Provide the missing variable names listed in details.';
348
- if (code === 'select_implementation') return 'Select one implementation with a scoped implementation hint.';
413
+ if (code === 'select_implementation')
414
+ return 'Use --implementation-hint with service, operation, package, repository, family, and repo keys; repo is required.';
349
415
  if (code === 'reindex_and_link') return 'Force reindex, then force relink the workspace.';
350
416
  if (code === 'inspect_detailed_edge') return 'Inspect the correlated detailed trace edge.';
351
417
  return undefined;
@@ -389,6 +455,13 @@ function compactStringArray(value: unknown): string[] {
389
455
  ? value.filter((item): item is string => typeof item === 'string') : [];
390
456
  }
391
457
 
458
+ function compactRecordArray(value: unknown): Array<Record<string, unknown>> {
459
+ return Array.isArray(value)
460
+ ? value.filter((item): item is Record<string, unknown> =>
461
+ Boolean(item && typeof item === 'object' && !Array.isArray(item)))
462
+ : [];
463
+ }
464
+
392
465
  function compactArrayLength(value: unknown): number {
393
466
  return Array.isArray(value) ? value.length : 0;
394
467
  }
@@ -2,14 +2,15 @@ import type {
2
2
  CompactDecisionTargetInput,
3
3
  CompactDecisionV1,
4
4
  CompactEdgeObservation,
5
- } from './014-compact-contract.js';
6
- import { projectCompactDecision } from './020-compact-field-projection.js';
5
+ } from './compact-contract.js';
6
+ import { projectCompactDecision } from './compact-field-projection.js';
7
7
  import { normalizeCompactDecisionEquivalence } from
8
- './021-compact-decision-normalization.js';
8
+ './compact-decision-normalization.js';
9
9
 
10
10
  export interface CompactDecisionNode {
11
11
  key?: string;
12
12
  decisionTarget?: string;
13
+ projectedIdentity?: boolean;
13
14
  }
14
15
 
15
16
  function resolvedNode(
@@ -40,7 +41,10 @@ function sameCanonicalNode(
40
41
  left: CompactDecisionNode | undefined,
41
42
  right: CompactDecisionNode,
42
43
  ): boolean {
43
- return left?.key !== undefined && left.key === right.key;
44
+ if (left?.key === undefined) return false;
45
+ return left.projectedIdentity === true
46
+ ? left.key === right.decisionTarget
47
+ : left.key === right.key;
44
48
  }
45
49
 
46
50
  function persistedCanonical(
@@ -49,7 +53,7 @@ function persistedCanonical(
49
53
  target: CompactDecisionNode,
50
54
  ): boolean {
51
55
  if (!sameCanonicalNode(persisted, target)) return false;
52
- return effective?.key === undefined || effective.key === target.key;
56
+ return effective?.key === undefined || sameCanonicalNode(effective, target);
53
57
  }
54
58
 
55
59
  export function projectObservationDecision(
@@ -71,6 +75,7 @@ export function projectObservationDecision(
71
75
  target.decisionTarget,
72
76
  sameCanonicalNode(effective, target),
73
77
  persistedCanonical(persisted, effective, target),
78
+ source?.persistedResolutionStatus,
74
79
  );
75
80
  return decision;
76
81
  }
@@ -1,6 +1,6 @@
1
1
  import type { Db } from '../db/connection.js';
2
2
  import { redactText } from '../utils/redaction.js';
3
- import { compareBinary as binaryCompare } from './010-traversal-scope.js';
3
+ import { compareBinary as binaryCompare } from './traversal-scope.js';
4
4
  import {
5
5
  type CompactDecisionTargetInput,
6
6
  type CompactProjectedDiagnostic,
@@ -17,16 +17,16 @@ import {
17
17
  type CompactSemanticEndpoint,
18
18
  type CompactSourceSite,
19
19
  type CompactStatus,
20
- } from './014-compact-contract.js';
20
+ } from './compact-contract.js';
21
21
  import {
22
22
  compactCompleteness, compactSafeCode, compactStatusCounts, compactStatusTotal,
23
23
  projectCompactDecisionTarget, projectCompactDiagnostics,
24
24
  projectCompactQuery, projectCompactStart,
25
- } from './020-compact-field-projection.js';
25
+ } from './compact-field-projection.js';
26
26
  import {
27
27
  projectObservationDecision,
28
28
  type CompactDecisionNode,
29
- } from './024-compact-observation-decision.js';
29
+ } from './compact-observation-decision.js';
30
30
 
31
31
  const REFERENCE_LIMIT = 5;
32
32
 
@@ -111,9 +111,8 @@ function decisionInputNode(
111
111
  return symbolNode(db, numeric) ?? {};
112
112
  if (target.kind === 'handler_method' && numeric !== undefined)
113
113
  return handlerNode(db, numeric) ?? {};
114
- return {
115
- decisionTarget: projectCompactDecisionTarget(target.kind, target.id),
116
- };
114
+ const decisionTarget = projectCompactDecisionTarget(target.kind, target.id);
115
+ return { key: decisionTarget, decisionTarget, projectedIdentity: true };
117
116
  }
118
117
 
119
118
  function resolveEndpoint(
@@ -1,8 +1,8 @@
1
1
  import type { Db } from '../db/connection.js';
2
2
  import { CURRENT_SCHEMA_VERSION } from '../db/migrations.js';
3
- import type { CompactGraphV1, CompactSourceContext } from './014-compact-contract.js';
4
- import { CompactObservationCollector } from './014-compact-contract.js';
5
- import { projectCompactGraph } from './016-compact-projector.js';
3
+ import type { CompactGraphV1, CompactSourceContext } from './compact-contract.js';
4
+ import { CompactObservationCollector } from './compact-contract.js';
5
+ import { projectCompactGraph } from './compact-projector.js';
6
6
  import { traceWithObserver } from './trace-engine.js';
7
7
  import type { TraceOptions, TraceResult, TraceStart } from '../types.js';
8
8
 
@@ -1,4 +1,4 @@
1
- import { projectBounded } from '../utils/000-bounded-projection.js';
1
+ import { projectBounded } from '../utils/bounded-projection.js';
2
2
 
3
3
  export function boundedContextCandidates(values: unknown[]): {
4
4
  candidates: Array<Record<string, unknown>>;
@@ -1,7 +1,7 @@
1
1
  import type { Db } from '../db/connection.js';
2
2
  import { normalizeODataOperationInvocationPath } from '../linker/odata-path-normalizer.js';
3
3
  import { resolveOperation, type OperationResolution } from '../linker/service-resolver.js';
4
- import { boundedContextCandidates } from './006-contextual-projection.js';
4
+ import { boundedContextCandidates } from './contextual-projection.js';
5
5
 
6
6
  export interface ContextBinding {
7
7
  bindingId?: number;
@@ -1,10 +1,10 @@
1
1
  import type { Db } from '../db/connection.js';
2
- import { scanPlaceholders } from '../utils/001-placeholders.js';
2
+ import { scanPlaceholders } from '../utils/placeholders.js';
3
3
  import type {
4
4
  DynamicTargetCandidate,
5
5
  DynamicTemplates,
6
6
  DynamicVariableProvenance,
7
- } from './000-dynamic-target-types.js';
7
+ } from './dynamic-target-types.js';
8
8
 
9
9
  interface RouteImplementationEvidence {
10
10
  routeRepoId?: number;
@@ -2,8 +2,8 @@ import type { Db } from '../db/connection.js';
2
2
  import {
3
3
  projectBounded,
4
4
  type BoundedProjection,
5
- } from '../utils/000-bounded-projection.js';
6
- import type { DynamicVariableProvenance } from './000-dynamic-target-types.js';
5
+ } from '../utils/bounded-projection.js';
6
+ import type { DynamicVariableProvenance } from './dynamic-target-types.js';
7
7
 
8
8
  export interface DynamicReferenceRow {
9
9
  bindingId?: number;
@@ -7,25 +7,25 @@ import {
7
7
  import { normalizeODataOperationInvocationPath } from '../linker/odata-path-normalizer.js';
8
8
  import type { OperationTarget } from '../linker/service-resolver.js';
9
9
  import type { DynamicMode } from '../types.js';
10
- import { dynamicCandidateTargets } from './004-dynamic-candidate-sources.js';
11
- import { projectBounded } from '../utils/000-bounded-projection.js';
12
- import { uniqueIdentityDerivations } from './001-dynamic-identity.js';
10
+ import { dynamicCandidateTargets } from './dynamic-candidate-sources.js';
11
+ import { projectBounded } from '../utils/bounded-projection.js';
12
+ import { uniqueIdentityDerivations } from './dynamic-identity.js';
13
13
  import {
14
14
  dynamicReferenceProvenance,
15
15
  dynamicRoutingContext,
16
16
  type DynamicReferenceRow,
17
17
  type DynamicRoutingContext,
18
- } from './003-dynamic-references.js';
18
+ } from './dynamic-references.js';
19
19
  import type {
20
20
  DynamicTargetAnalysis,
21
21
  DynamicTargetCandidate,
22
22
  DynamicTemplates,
23
23
  DynamicVariableProvenance,
24
- } from './000-dynamic-target-types.js';
24
+ } from './dynamic-target-types.js';
25
25
  export type {
26
26
  DynamicTargetAnalysis,
27
27
  DynamicTargetCandidate,
28
- } from './000-dynamic-target-types.js';
28
+ } from './dynamic-target-types.js';
29
29
  type Templates = DynamicTemplates;
30
30
  type VariableProvenance = DynamicVariableProvenance;
31
31
  interface AnalysisInputs {
@@ -0,0 +1,151 @@
1
+ import {
2
+ substituteVariables,
3
+ type RuntimeSubstitution,
4
+ } from '../linker/dynamic-edge-resolver.js';
5
+ import type { Db } from '../db/connection.js';
6
+ import {
7
+ eventSubscriberMissingVariables,
8
+ planEventSubscriberTransitions,
9
+ type EventSubscriberTransitionQuery,
10
+ type PlannedEventSubscriberTransition,
11
+ } from './event-subscriber-traversal.js';
12
+ import type {
13
+ TraversalScopeScheduler,
14
+ TraversalScopeState,
15
+ } from './traversal-scope.js';
16
+ import type { TraceGraphRow } from './evidence.js';
17
+
18
+ export interface EventRuntimeResolution {
19
+ row: TraceGraphRow;
20
+ evidence: Record<string, unknown>;
21
+ unresolvedReason?: string;
22
+ }
23
+
24
+ export interface EventSubscriberRuntimePlan {
25
+ plans: PlannedEventSubscriberTransition[];
26
+ diagnostic?: Record<string, unknown>;
27
+ }
28
+
29
+ function record(value: unknown): Record<string, unknown> | undefined {
30
+ return value && typeof value === 'object' && !Array.isArray(value)
31
+ ? value as Record<string, unknown> : undefined;
32
+ }
33
+
34
+ function eventTemplate(evidence: Record<string, unknown>): string | undefined {
35
+ const resolution = record(evidence.eventTemplateResolution);
36
+ return typeof resolution?.original === 'string'
37
+ ? resolution.original : undefined;
38
+ }
39
+
40
+ function missingReason(substitution: RuntimeSubstitution): string {
41
+ return `Dynamic target requires runtime variable overrides: ${
42
+ substitution.missing.join(', ')}`;
43
+ }
44
+
45
+ function missingSubscriberDiagnostic(
46
+ missing: readonly string[],
47
+ ): Record<string, unknown> | undefined {
48
+ if (missing.length === 0) return undefined;
49
+ return {
50
+ severity: 'warning',
51
+ code: 'trace_runtime_variables_missing',
52
+ message: `Runtime variables are required to resolve dynamic event subscribers: ${
53
+ missing.join(', ')}`,
54
+ missingVariables: missing,
55
+ suggestions: missing.map((key) => `--var ${key}=<value>`),
56
+ source: 'event_subscription',
57
+ };
58
+ }
59
+
60
+ export function runtimeEventSubscriberPlans(
61
+ db: Db,
62
+ query: EventSubscriberTransitionQuery,
63
+ scheduler: TraversalScopeScheduler,
64
+ parent: TraversalScopeState,
65
+ depth: number,
66
+ maxDepth: number,
67
+ ): EventSubscriberRuntimePlan {
68
+ const missing = eventSubscriberMissingVariables(db, query);
69
+ return {
70
+ plans: planEventSubscriberTransitions(
71
+ db, query, scheduler, parent, depth, maxDepth,
72
+ ),
73
+ diagnostic: missingSubscriberDiagnostic(missing),
74
+ };
75
+ }
76
+
77
+ function eventEvidence(
78
+ evidence: Record<string, unknown>,
79
+ substitution: RuntimeSubstitution,
80
+ row: TraceGraphRow,
81
+ unresolvedReason?: string,
82
+ ): Record<string, unknown> {
83
+ const effectiveResolution = {
84
+ status: unresolvedReason ? 'dynamic' : 'terminal',
85
+ targetKind: row.to_kind,
86
+ targetId: row.to_id,
87
+ confidence: row.confidence,
88
+ unresolvedReason,
89
+ edgeType: row.edge_type,
90
+ };
91
+ return {
92
+ ...evidence,
93
+ runtimeSubstitutions: { eventName: substitution },
94
+ suppliedRuntimeVariables: Object.fromEntries(
95
+ substitution.supplied.map((key) => [key, true]),
96
+ ),
97
+ ...(substitution.supplied.length > 0
98
+ ? { runtimeVariablesApplied: true } : {}),
99
+ ...(substitution.missing.length > 0
100
+ ? { missingRuntimeVariables: substitution.missing } : {}),
101
+ effectiveResolution,
102
+ linker: {
103
+ status: effectiveResolution.status,
104
+ confidence: row.confidence,
105
+ reason: unresolvedReason,
106
+ edgeType: row.edge_type,
107
+ },
108
+ };
109
+ }
110
+
111
+ function runtimeEventRow(
112
+ row: TraceGraphRow,
113
+ evidence: Record<string, unknown>,
114
+ substitution: RuntimeSubstitution,
115
+ template: string,
116
+ ): TraceGraphRow {
117
+ const missing = substitution.missing.length > 0;
118
+ const eventName = substitution.effective ?? template;
119
+ const callType = evidence.callType;
120
+ const edgeType = callType === 'async_emit'
121
+ ? 'HANDLER_EMITS_EVENT' : 'EVENT_CONSUMED_BY_HANDLER';
122
+ return {
123
+ ...row,
124
+ edge_type: missing ? 'DYNAMIC_EDGE_CANDIDATE' : edgeType,
125
+ status: missing ? 'dynamic' : 'terminal',
126
+ to_kind: missing ? 'event_candidate' : 'event',
127
+ to_id: missing ? `Event: ${eventName}` : eventName,
128
+ unresolved_reason: missing ? missingReason(substitution) : undefined,
129
+ };
130
+ }
131
+
132
+ export function runtimeEventResolution(
133
+ row: TraceGraphRow,
134
+ evidence: Record<string, unknown>,
135
+ variables: Record<string, string> | undefined,
136
+ ): EventRuntimeResolution | undefined {
137
+ const template = eventTemplate(evidence);
138
+ if (!template || row.to_kind === 'event' && variables === undefined)
139
+ return undefined;
140
+ const substitution = substituteVariables(template, variables ?? {});
141
+ const effectiveRow = runtimeEventRow(
142
+ row, evidence, substitution, template,
143
+ );
144
+ return {
145
+ row: effectiveRow,
146
+ evidence: eventEvidence(
147
+ evidence, substitution, effectiveRow, effectiveRow.unresolved_reason,
148
+ ),
149
+ unresolvedReason: effectiveRow.unresolved_reason,
150
+ };
151
+ }
@@ -1,8 +1,13 @@
1
1
  import type { Db } from '../db/connection.js';
2
2
  import {
3
+ matchRuntimeTemplate,
4
+ substituteVariables,
5
+ } from '../linker/dynamic-edge-resolver.js';
6
+ import {
7
+ compareBinary,
3
8
  type TraversalScopeScheduler,
4
9
  type TraversalScopeState,
5
- } from './010-traversal-scope.js';
10
+ } from './traversal-scope.js';
6
11
 
7
12
  export type EventSubscriberTransitionStatus =
8
13
  | 'resolved'
@@ -53,6 +58,8 @@ export interface EventSubscriberTransition {
53
58
  candidateCount: number;
54
59
  symbolCallUnresolvedReason?: string;
55
60
  omittedSymbolCallUnresolvedReasonCharacterCount?: number;
61
+ matchStrategy?: string;
62
+ dispatchCertainty?: string;
56
63
  handler?: EventSubscriberSymbolTarget;
57
64
  }
58
65
 
@@ -60,6 +67,7 @@ export interface EventSubscriberTransitionQuery {
60
67
  workspaceId: number;
61
68
  graphGeneration: number;
62
69
  eventName: string;
70
+ vars?: Record<string, string>;
63
71
  }
64
72
 
65
73
  export type EventBodyExpansion =
@@ -156,8 +164,8 @@ export function eventTransitionEvidence(
156
164
  subscribeCallId: transition.subscribeCallId,
157
165
  symbolCallId: transition.symbolCallId,
158
166
  eventName: transition.eventName,
159
- matchStrategy: 'workspace_exact_event_name',
160
- dispatchCertainty: 'static_name_only',
167
+ matchStrategy: transition.matchStrategy ?? 'workspace_exact_event_name',
168
+ dispatchCertainty: transition.dispatchCertainty ?? 'static_name_only',
161
169
  associationBasis: transition.associationBasis,
162
170
  dispatchScope: transition.dispatchScope,
163
171
  roleSiteMatchCount: transition.roleSiteMatchCount,
@@ -194,7 +202,8 @@ export function loadEventSubscriberTransitions(
194
202
  query: EventSubscriberTransitionQuery,
195
203
  ): EventSubscriberTransition[] {
196
204
  const rows = db.prepare(`SELECT ge.id graphEdgeId,ge.generation graphGeneration,
197
- ge.from_id eventName,ge.status,ge.to_kind targetKind,ge.to_id targetId,
205
+ ge.from_kind fromKind,ge.from_id eventName,ge.status,
206
+ ge.to_kind targetKind,ge.to_id targetId,
198
207
  ge.confidence,ge.unresolved_reason unresolvedReason,ge.evidence_json evidenceJson,
199
208
  subscribe.id subscribeCallId,subscribe.repo_id subscriptionRepoId,
200
209
  subscribe.source_file sourceFile,subscribe.source_line sourceLine,
@@ -218,19 +227,100 @@ export function loadEventSubscriberTransitions(
218
227
  LEFT JOIN repositories handler_repo
219
228
  ON handler_repo.id=handler.repo_id AND handler_repo.workspace_id=ge.workspace_id
220
229
  WHERE ge.workspace_id=? AND ge.generation=?
221
- AND ge.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY' AND ge.from_kind='event'
222
- AND ge.from_id COLLATE BINARY=? COLLATE BINARY
230
+ AND ge.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
231
+ AND ge.from_kind IN ('event','event_candidate')
223
232
  ORDER BY COALESCE(subscription_repo.name,'') COLLATE BINARY,
224
233
  COALESCE(subscription_repo.id,0),COALESCE(subscribe.source_file,'') COLLATE BINARY,
225
234
  subscribe.call_site_start_offset,subscribe.call_site_end_offset,ge.id`).all(
226
- query.workspaceId, query.graphGeneration, query.eventName,
235
+ query.workspaceId, query.graphGeneration,
227
236
  );
228
- return rows.flatMap((row) => {
229
- const transition = transitionFromRow(row);
237
+ return rows.flatMap((raw) => {
238
+ const row = eventRowForQuery(raw, query);
239
+ const transition = row ? transitionFromRow(row) : undefined;
230
240
  return transition ? [transition] : [];
231
241
  });
232
242
  }
233
243
 
244
+ function eventRowForQuery(
245
+ row: Record<string, unknown>,
246
+ query: EventSubscriberTransitionQuery,
247
+ ): Record<string, unknown> | undefined {
248
+ const variables = query.vars;
249
+ if (variables === undefined) return exactPersistedEventRow(row, query);
250
+ const evidence = parseEvidence(row.evidenceJson);
251
+ const resolution = isRecord(evidence.eventTemplateResolution)
252
+ ? evidence.eventTemplateResolution : {};
253
+ if (typeof resolution.original !== 'string')
254
+ return exactPersistedEventRow(row, query);
255
+ return runtimeMatchedEventRow(
256
+ row, query, evidence, resolution.original, variables,
257
+ );
258
+ }
259
+
260
+ function exactPersistedEventRow(
261
+ row: Record<string, unknown>,
262
+ query: EventSubscriberTransitionQuery,
263
+ ): Record<string, unknown> | undefined {
264
+ return row.fromKind === 'event' && row.eventName === query.eventName
265
+ ? row : undefined;
266
+ }
267
+
268
+ function runtimeMatchedEventRow(
269
+ row: Record<string, unknown>,
270
+ query: EventSubscriberTransitionQuery,
271
+ evidence: Record<string, unknown>,
272
+ template: string,
273
+ variables: Record<string, string>,
274
+ ): Record<string, unknown> | undefined {
275
+ const substitution = substituteVariables(template, variables);
276
+ if (substitution.missing.length > 0
277
+ || substitution.effective !== query.eventName) return undefined;
278
+ const resolvedAssociation = evidence.associationStatus === 'resolved';
279
+ return {
280
+ ...row,
281
+ eventName: query.eventName,
282
+ status: typeof evidence.associationStatus === 'string'
283
+ ? evidence.associationStatus : row.status,
284
+ unresolvedReason: resolvedAssociation ? null : row.unresolvedReason,
285
+ evidenceJson: JSON.stringify({
286
+ ...evidence,
287
+ eventSubscriptionRuntimeSubstitution: substitution,
288
+ matchStrategy: 'workspace_exact_event_name_after_runtime_substitution',
289
+ dispatchCertainty: 'runtime_variables_exact',
290
+ }),
291
+ };
292
+ }
293
+
294
+ export function eventSubscriberMissingVariables(
295
+ db: Db,
296
+ query: EventSubscriberTransitionQuery,
297
+ ): string[] {
298
+ if (query.vars === undefined) return [];
299
+ const variables = query.vars;
300
+ const rows = db.prepare(`SELECT ge.from_id eventName,
301
+ ge.evidence_json evidenceJson FROM graph_edges ge
302
+ WHERE ge.workspace_id=? AND ge.generation=?
303
+ AND ge.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
304
+ AND ge.from_kind='event_candidate'`).all(
305
+ query.workspaceId, query.graphGeneration,
306
+ );
307
+ const missing = rows.flatMap((row) => {
308
+ const evidence = parseEvidence(row.evidenceJson);
309
+ const resolution = isRecord(evidence.eventTemplateResolution)
310
+ ? evidence.eventTemplateResolution : {};
311
+ const template = typeof resolution.original === 'string'
312
+ ? resolution.original : undefined;
313
+ if (!template) return [];
314
+ const substitution = substituteVariables(template, variables);
315
+ if (substitution.missing.length === 0
316
+ || matchRuntimeTemplate(
317
+ substitution.effective, query.eventName,
318
+ ) === undefined) return [];
319
+ return substitution.missing;
320
+ });
321
+ return [...new Set(missing)].sort(compareBinary);
322
+ }
323
+
234
324
  function transitionFromRow(
235
325
  row: Record<string, unknown>,
236
326
  ): EventSubscriberTransition | undefined {
@@ -290,6 +380,8 @@ function associationEvidence(
290
380
  omittedSymbolCallUnresolvedReasonCharacterCount: symbolCallUnresolvedReason
291
381
  ? nonNegativeCount(evidence.omittedSymbolCallUnresolvedReasonCharacterCount)
292
382
  : undefined,
383
+ matchStrategy: stringValue(evidence.matchStrategy),
384
+ dispatchCertainty: stringValue(evidence.dispatchCertainty),
293
385
  };
294
386
  }
295
387
 
@@ -4,12 +4,12 @@ import { normalizeODataOperationInvocationPath } from '../linker/odata-path-norm
4
4
  import { resolveOperation, type OperationTarget } from '../linker/service-resolver.js';
5
5
  import type { DynamicMode } from '../types.js';
6
6
  import { analyzeDynamicTargetCandidates, type DynamicTargetAnalysis, type DynamicTargetCandidate } from './dynamic-targets.js';
7
- import { boundCandidateLikeEvidence } from '../utils/000-bounded-projection.js';
7
+ import { boundCandidateLikeEvidence } from '../utils/bounded-projection.js';
8
8
  import {
9
9
  dynamicMissingReason,
10
10
  isStructuralContextualBlocker,
11
11
  type ContextualRuntimeState,
12
- } from './008-contextual-runtime-state.js';
12
+ } from './contextual-runtime-state.js';
13
13
 
14
14
  export interface TraceGraphRow extends Record<string, unknown> {
15
15
  id: number;