@saptools/service-flow 0.1.66 → 0.1.68

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 (80) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +20 -8
  3. package/TECHNICAL-NOTE.md +27 -0
  4. package/dist/{chunk-TOVX4WYH.js → chunk-AEM4JY22.js} +12676 -6714
  5. package/dist/chunk-AEM4JY22.js.map +1 -0
  6. package/dist/cli.js +2325 -413
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +61 -2
  9. package/dist/index.js +1 -1
  10. package/package.json +1 -1
  11. package/src/cli/003-doctor-package-resolution.ts +68 -0
  12. package/src/cli/doctor.ts +25 -14
  13. package/src/db/000-call-fact-repository.ts +475 -342
  14. package/src/db/001-fact-lifecycle.ts +60 -30
  15. package/src/db/002-fact-json-inventory.ts +169 -0
  16. package/src/db/003-current-fact-semantics.ts +698 -0
  17. package/src/db/004-package-target-invalidation.ts +173 -0
  18. package/src/db/005-schema-structure.ts +201 -0
  19. package/src/db/006-relative-symbol-resolution.ts +443 -0
  20. package/src/db/007-package-fact-semantics.ts +573 -0
  21. package/src/db/008-relative-fact-semantics.ts +207 -0
  22. package/src/db/009-binding-fact-semantics.ts +347 -0
  23. package/src/db/010-package-symbol-surface-semantics.ts +320 -0
  24. package/src/db/011-symbol-call-semantics.ts +144 -0
  25. package/src/db/012-binding-reference-proof.ts +264 -0
  26. package/src/db/migrations.ts +16 -3
  27. package/src/db/repositories.ts +113 -6
  28. package/src/db/schema.ts +4 -2
  29. package/src/index.ts +12 -0
  30. package/src/indexer/repository-indexer.ts +72 -11
  31. package/src/indexer/workspace-indexer.ts +123 -32
  32. package/src/linker/003-package-import-symbol-resolver.ts +363 -131
  33. package/src/linker/004-event-subscription-handler-linker.ts +2 -0
  34. package/src/linker/005-odata-path-structure.ts +371 -0
  35. package/src/linker/cross-repo-linker.ts +2 -1
  36. package/src/linker/odata-path-normalizer.ts +273 -180
  37. package/src/linker/service-resolver.ts +197 -77
  38. package/src/parsers/002-symbol-import-bindings.ts +516 -0
  39. package/src/parsers/003-package-public-surface.ts +661 -0
  40. package/src/parsers/004-fact-identity.ts +108 -0
  41. package/src/parsers/005-event-subscription-facts.ts +281 -0
  42. package/src/parsers/006-binding-identity.ts +343 -0
  43. package/src/parsers/007-source-fact-reconciliation.ts +105 -0
  44. package/src/parsers/008-package-surface-publication.ts +82 -0
  45. package/src/parsers/009-symbol-call-facts.ts +528 -0
  46. package/src/parsers/010-package-public-surface-analysis.ts +352 -0
  47. package/src/parsers/011-binding-lexical-scope.ts +583 -0
  48. package/src/parsers/012-package-fact-contract.ts +306 -0
  49. package/src/parsers/013-executable-body-eligibility.ts +35 -0
  50. package/src/parsers/014-service-binding-helper-flow.ts +240 -0
  51. package/src/parsers/015-service-binding-collector.ts +693 -0
  52. package/src/parsers/016-local-symbol-reference.ts +261 -0
  53. package/src/parsers/017-symbol-derived-contexts.ts +268 -0
  54. package/src/parsers/018-package-commonjs-syntax.ts +142 -0
  55. package/src/parsers/019-binding-assignment-targets.ts +76 -0
  56. package/src/parsers/020-stable-local-value.ts +217 -0
  57. package/src/parsers/021-binding-visibility.ts +152 -0
  58. package/src/parsers/operation-path-analysis.ts +6 -1
  59. package/src/parsers/outbound-call-parser.ts +19 -6
  60. package/src/parsers/package-json-parser.ts +45 -3
  61. package/src/parsers/service-binding-parser-helpers.ts +86 -15
  62. package/src/parsers/service-binding-parser.ts +147 -597
  63. package/src/parsers/symbol-parser.ts +482 -353
  64. package/src/trace/002-trace-diagnostics.ts +36 -1
  65. package/src/trace/016-compact-projector.ts +16 -17
  66. package/src/trace/020-compact-field-projection.ts +48 -37
  67. package/src/trace/021-compact-decision-normalization.ts +105 -0
  68. package/src/trace/022-trace-fact-preflight.ts +21 -0
  69. package/src/trace/023-nested-event-scopes.ts +23 -0
  70. package/src/trace/024-compact-observation-decision.ts +76 -0
  71. package/src/trace/025-trace-implementation-scope.ts +123 -0
  72. package/src/trace/026-trace-start-scope.ts +335 -0
  73. package/src/trace/027-trace-scope-execution.ts +566 -0
  74. package/src/trace/028-trace-operation-execution.ts +336 -0
  75. package/src/trace/029-trace-start-implementation.ts +172 -0
  76. package/src/trace/trace-engine.ts +122 -624
  77. package/src/types.ts +56 -0
  78. package/src/utils/001-placeholders.ts +188 -10
  79. package/src/version.ts +1 -1
  80. package/dist/chunk-TOVX4WYH.js.map +0 -1
@@ -1,264 +1,137 @@
1
1
  import type { Db } from '../db/connection.js';
2
- import { reposByName } from '../db/repositories.js';
3
- import type { ImplementationHint, TraceEdge, TraceOptions, TraceResult, TraceStart } from '../types.js';
4
- import { baseTraceEvidence, edgeTarget, runtimeNoCandidateDiagnostics, runtimeResolution, runtimeVariableDiagnostic, type TraceGraphRow } from './evidence.js';
5
- import { dynamicCandidateBranches } from './dynamic-branches.js';
2
+ import { schemaLifecycleDiagnostic } from '../db/001-fact-lifecycle.js';
3
+ import type {
4
+ TraceEdge,
5
+ TraceOptions,
6
+ TraceResult,
7
+ TraceStart,
8
+ } from '../types.js';
9
+ import {
10
+ runtimeNoCandidateDiagnostics,
11
+ runtimeVariableDiagnostic,
12
+ } from './evidence.js';
6
13
  import {
7
14
  loadTraceDiagnostics,
8
15
  prependTraceDiagnostic,
9
16
  } from './002-trace-diagnostics.js';
10
- import { implementationHintDiagnostic } from './implementation-hints.js';
11
- import {
12
- contextualImplementationSelection,
13
- hintedImplementationSelection,
14
- } from './005-implementation-selection.js';
15
- import { implementationStartDiagnostic } from './007-implementation-start-diagnostic.js';
16
- import {
17
- contextualRuntimeResolution,
18
- type ContextBinding,
19
- } from './008-contextual-runtime-state.js';
20
- import {
21
- handlerMethodNode,
22
- withSelectedHandlerProvenance,
23
- type SelectedHandlerEvidence,
24
- } from './009-selected-handler-provenance.js';
25
- import { schemaLifecycleDiagnostic } from '../db/001-fact-lifecycle.js';
26
17
  import { TraversalScopeScheduler } from './010-traversal-scope.js';
27
- import { planEventSubscriberTransitions } from './011-event-subscriber-traversal.js';
28
- import {
29
- graphForCalls,
30
- operationNode,
31
- symbolNode,
32
- type TraceGraphEdgeRow as GraphRow,
33
- } from './012-trace-graph-lookups.js';
34
- import {
35
- createTraceRootPlan,
36
- enqueueCausalScope,
37
- nextPendingRoot,
38
- } from './013-trace-root-scopes.js';
39
- import {
40
- contextForSymbolCall,
41
- knownBindingsForCalls,
42
- knownBindingsForScope,
43
- parseTraceEvidence as parseEvidence,
44
- receiverFromTraceEvidence as receiverFromEvidence,
45
- } from './017-trace-context.js';
18
+ import { createTraceRootPlan } from './013-trace-root-scopes.js';
46
19
  import type { CompactTraceObserver } from './014-compact-contract.js';
47
20
  import { TraceEdgeRecorder } from './015-trace-edge-recorder.js';
21
+ import type { ImplementationHintOptions } from './025-trace-implementation-scope.js';
48
22
  import {
49
- recordCycleObservation,
50
- recordDynamicBranchObservation,
51
- recordEventBridgeObservation,
52
- recordEventCycleObservation,
53
- recordImplementationObservation,
54
- recordLocalCallObservation,
55
- recordOutboundObservation,
56
- } from './019-trace-edge-semantics.js';
23
+ resolveTraceStartScope,
24
+ type TraceStartScope,
25
+ } from './026-trace-start-scope.js';
57
26
  import {
58
- ambiguousStartDiagnostic,
59
- selectorNotFoundDiagnostic,
60
- selectorRepoAmbiguousDiagnostic,
61
- selectorRepoNotFoundDiagnostic,
62
- sourceScopeForSelector,
63
- } from './selectors.js';
64
- interface RepoRef {
65
- id: number;
66
- name: string;
67
- packageName?: string;
68
- }
69
- interface StartScope {
70
- repo?: RepoRef;
71
- executionRepoId?: number;
72
- sourceFiles?: Set<string>;
73
- symbolIds?: Set<number>;
74
- selectorMatched: boolean;
75
- startOperationId?: string;
76
- startDiagnostics?: Array<Record<string, unknown>>;
77
- }
78
- interface CallRow extends Record<string, unknown> {
79
- id: number;
80
- repo_id: number;
81
- repoName: string;
82
- source_file: string;
83
- source_line: number;
84
- call_type: string;
85
- confidence: number;
86
- source_symbol_id?: number;
87
- workspaceId: number;
88
- graphGeneration: number;
89
- }
90
- interface ImplementationHintOptions {
91
- implementationRepo?: string;
92
- implementationHints?: ImplementationHint[];
93
- }
27
+ executeTraceScopes,
28
+ type TraceExecutionRuntime,
29
+ } from './027-trace-scope-execution.js';
30
+ import { recordTraceStartImplementation } from './029-trace-start-implementation.js';
31
+ import { selectorNotFoundDiagnostic } from './selectors.js';
32
+
94
33
  const compactObserverKey = Symbol('service-flow.compact-trace-observer');
34
+
95
35
  type ObservedTraceOptions = TraceOptions & {
96
36
  [compactObserverKey]?: CompactTraceObserver;
97
37
  };
38
+
98
39
  function compactObserver(options: TraceOptions): CompactTraceObserver | undefined {
99
40
  const observed: ObservedTraceOptions = options;
100
41
  return observed[compactObserverKey];
101
42
  }
102
- function normalizeOperation(value: string | undefined): string | undefined {
103
- if (!value) return undefined;
104
- return value.startsWith('/') ? value.slice(1) : value;
105
- }
43
+
106
44
  function positiveDepth(value: number): number {
107
45
  return Number.isFinite(value) && value > 0 ? Math.floor(value) : 25;
108
46
  }
109
- function operationStartScope(db: Db, repoId: number | undefined, start: TraceStart, hintOptions: ImplementationHintOptions, workspaceId?: number): { files?: Set<string>; symbols?: Set<number>; repoId?: number; operationId?: string; diagnostics?: Array<Record<string, unknown>> } | undefined {
110
- const requested = normalizeOperation(start.operationPath ?? start.operation);
111
- if (!requested) return undefined;
112
- const rows = db.prepare(`SELECT o.id operationId,o.operation_name operationName,o.operation_path operationPath,o.source_file sourceFile,o.source_line sourceLine,s.service_path servicePath,r.id repoId,r.name repoName
113
- FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
114
- WHERE (? IS NULL OR r.workspace_id=?) AND (? IS NULL OR r.id=?)
115
- AND (? IS NULL OR s.service_path=?) AND (o.operation_name=? OR o.operation_path=? OR o.operation_path=?)
116
- ORDER BY r.name,s.service_path,o.operation_name,o.id`).all(workspaceId, workspaceId, repoId, repoId, start.servicePath, start.servicePath, requested, requested, requested.startsWith('/') ? requested : `/${requested}`) as Array<Record<string, unknown>>;
117
- if (rows.length === 0) return undefined;
118
- const repoCount = new Set(rows.map((row) => String(row.repoName))).size;
119
- const serviceCount = new Set(rows.map((row) => `${String(row.repoName)}:${String(row.servicePath)}`)).size;
120
- if (!repoId && repoCount > 1)
121
- return { diagnostics: [ambiguousStartDiagnostic(requested, rows, 'Operation trace start matched multiple repositories; add --repo to disambiguate')] };
122
- if (!start.servicePath && serviceCount > 1)
123
- return { diagnostics: [ambiguousStartDiagnostic(requested, rows, 'Operation trace start matched multiple services; add --service to disambiguate')] };
124
- if (rows.length !== 1)
125
- return { diagnostics: [ambiguousStartDiagnostic(requested, rows, 'Operation trace start matched multiple indexed operations')] };
126
- const operationId = String(rows[0]?.operationId);
127
- const impl = implementationScope(db, operationId);
128
- if (impl.edge?.status === 'resolved' && impl.files.size > 0) return { files: impl.files, symbols: impl.symbolId ? new Set([impl.symbolId]) : undefined, repoId: impl.repoId, operationId, diagnostics: [] };
129
- const hinted = hintedImplementationSelection(
130
- db, impl.edge, operationId, hintOptions,
131
- );
132
- if (hinted.methodId) {
133
- const hintedScope = handlerScope(db, hinted.methodId);
134
- if (hintedScope?.files.size) return { files: hintedScope.files, symbols: hintedScope.symbolId ? new Set([hintedScope.symbolId]) : undefined, repoId: hintedScope.repoId, operationId, diagnostics: [] };
135
- }
136
- if (impl.edge) {
137
- const evidence = parseEvidence(impl.edge.evidence_json);
138
- const hintDiagnostic = implementationHintDiagnostic(hinted, evidence);
139
- const diagnostics = [implementationStartDiagnostic(impl.edge, evidence)];
140
- return { operationId, diagnostics: hintDiagnostic ? [hintDiagnostic, ...diagnostics] : diagnostics };
141
- }
142
- 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' }] };
47
+
48
+ function hasSelector(start: TraceStart): boolean {
49
+ return Boolean(start.repo || start.handler || start.operation
50
+ || start.operationPath || start.servicePath);
143
51
  }
144
- function sourceFilesForStart(
52
+
53
+ function staleRows(
145
54
  db: Db,
146
55
  repoId: number | undefined,
147
- start: TraceStart,
148
56
  workspaceId: number | undefined,
149
- ): ReturnType<typeof sourceScopeForSelector> {
150
- return sourceScopeForSelector(db, repoId, start, workspaceId);
151
- }
152
- function startScope(db: Db, start: TraceStart, hintOptions: ImplementationHintOptions, workspaceId?: number): StartScope {
153
- const repos: RepoRef[] = start.repo
154
- ? reposByName(db, start.repo, workspaceId).map((row) => ({
155
- id: row.id,
156
- name: row.name,
157
- packageName: row.package_name ?? undefined,
158
- }))
159
- : [];
160
- if (start.repo && repos.length === 0) return {
161
- selectorMatched: false,
162
- startDiagnostics: [selectorRepoNotFoundDiagnostic(start.repo)],
163
- };
164
- if (start.repo && repos.length > 1) return {
165
- selectorMatched: false,
166
- startDiagnostics: [selectorRepoAmbiguousDiagnostic(start.repo, repos)],
167
- };
168
- const repo = repos[0];
169
- const operationScope = operationStartScope(
170
- db, repo?.id, start, hintOptions, workspaceId,
171
- );
172
- const terminalOperationScope = operationScope && !operationScope.files && (operationScope.diagnostics ?? []).some((d) => d.resolutionStage === 'operation' || d.resolutionStage === 'implementation');
173
- const sourceScope = operationScope?.files || terminalOperationScope ? operationScope : sourceFilesForStart(db, repo?.id, start, workspaceId);
174
- const terminalSelectorScope = Boolean(sourceScope?.diagnostics?.length && !sourceScope.files);
175
- const sourceFiles = sourceScope?.files;
176
- const hasSelector = Boolean(
177
- start.handler ?? start.operation ?? start.operationPath ?? start.servicePath,
178
- );
179
- if (start.servicePath && !start.operation && !start.operationPath && !start.handler)
180
- return { repo, selectorMatched: false };
181
- return {
182
- repo,
183
- executionRepoId: sourceScope?.repoId ?? repo?.id,
184
- sourceFiles,
185
- symbolIds: sourceScope?.symbols,
186
- selectorMatched: !terminalOperationScope && !terminalSelectorScope
187
- && (!hasSelector || sourceFiles !== undefined),
188
- startOperationId: operationScope?.operationId,
189
- startDiagnostics: operationScope?.diagnostics?.length
190
- ? operationScope.diagnostics
191
- : sourceScope?.diagnostics,
192
- };
193
- }
194
- function handlerFilesForOperation(db: Db, operationId: string): Set<string> {
195
- const op = db
196
- .prepare(
197
- `SELECT o.operation_name operationName,o.operation_path operationPath,s.repo_id repoId
198
- FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?`,
199
- )
200
- .get(operationId) as
201
- | { operationName?: string; operationPath?: string; repoId?: number }
202
- | undefined;
203
- if (!op) return new Set();
204
- const operation = normalizeOperation(op.operationPath ?? op.operationName);
205
- const rows = db
206
- .prepare(
207
- `SELECT DISTINCT hc.source_file sourceFile,sym.id symbolId FROM handler_classes hc
208
- JOIN handler_methods hm ON hm.handler_class_id=hc.id
209
- LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
210
- AND sym.source_file=hc.source_file
211
- AND sym.qualified_name=hc.class_name || '.' || hm.method_name
212
- AND sym.start_line=hm.source_line
213
- WHERE hc.repo_id=?
214
- AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
215
- CASE WHEN hm.decorator_kind='Event' THEN 'event'
216
- WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
217
- ELSE 'unsupported' END)='operation'
218
- AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
219
- CASE WHEN hm.decorator_kind IN ('Action','Func','On')
220
- THEN 1 ELSE 0 END)=1
221
- AND (hm.decorator_value=? OR hm.method_name=? OR hm.decorator_value=?)`,
222
- )
223
- .all(op.repoId, operation, operation, op.operationName) as Array<{
224
- sourceFile?: string;
225
- }>;
226
- return new Set(rows.map((row) => row.sourceFile).filter(Boolean) as string[]);
227
- }
228
- function implementationEdge(db: Db, operationId: string): GraphRow | undefined {
229
- 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) as GraphRow | undefined;
57
+ includeAll: boolean,
58
+ ): Array<{ name?: string; reason?: string }> {
59
+ if (repoId === undefined && !includeAll) return [];
60
+ return db.prepare(`SELECT name,graph_stale_reason reason FROM repositories
61
+ WHERE graph_stale_reason IS NOT NULL AND (? IS NULL OR id=?)
62
+ AND (? IS NULL OR workspace_id=?)
63
+ ORDER BY name,id`).all(
64
+ repoId, repoId, workspaceId, workspaceId,
65
+ ) as Array<{ name?: string; reason?: string }>;
230
66
  }
231
- function implementationScope(db: Db, operationId: string): { repoId?: number; files: Set<string>; symbolId?: number; edge?: GraphRow } {
232
- const edge = implementationEdge(db, operationId);
233
- if (!edge || edge.status !== 'resolved') return { files: new Set(), edge };
234
- 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(edge.to_id) as { repoId?: number; sourceFile?: string; symbolId?: number } | undefined;
235
- if (!row || typeof row.symbolId !== 'number')
236
- return { repoId: row?.repoId, files: new Set(), edge };
237
- return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []), symbolId: row?.symbolId, edge };
67
+
68
+ function addStaleDiagnostics(
69
+ diagnostics: Array<Record<string, unknown>>,
70
+ rows: Array<{ name?: string; reason?: string }>,
71
+ ): void {
72
+ for (const row of rows)
73
+ prependTraceDiagnostic(diagnostics, {
74
+ severity: 'warning',
75
+ code: 'graph_stale',
76
+ message: `Graph is stale for ${row.name ?? 'repository'}: ${row.reason ?? 'facts_changed'}. Run service-flow link.`,
77
+ });
238
78
  }
239
- function handlerScope(db: Db, methodId: string): { repoId?: number; files: Set<string>; symbolId?: number } | undefined {
240
- 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) as { repoId?: number; sourceFile?: string; symbolId?: number } | undefined;
241
- if (!row || typeof row.symbolId !== 'number') return undefined;
242
- return { repoId: row.repoId, files: new Set(row.sourceFile ? [row.sourceFile] : []), symbolId: row.symbolId };
79
+
80
+ function initialDiagnostics(
81
+ db: Db,
82
+ start: TraceStart,
83
+ scope: TraceStartScope,
84
+ workspaceId: number | undefined,
85
+ ): Array<Record<string, unknown>> {
86
+ const selected = hasSelector(start);
87
+ const repoId = scope.executionRepoId ?? scope.repo?.id;
88
+ const diagnostics = loadTraceDiagnostics(db, repoId, !selected, workspaceId);
89
+ addStaleDiagnostics(
90
+ diagnostics, staleRows(db, repoId, workspaceId, !selected),
91
+ );
92
+ for (const diagnostic of scope.startDiagnostics ?? [])
93
+ prependTraceDiagnostic(diagnostics, diagnostic);
94
+ if (!scope.selectorMatched && !scope.startDiagnostics?.length)
95
+ prependTraceDiagnostic(diagnostics, selectorNotFoundDiagnostic(start));
96
+ return diagnostics;
243
97
  }
244
- function traceEdgeType(call: CallRow, row: GraphRow): string {
245
- if (row.to_kind === 'operation' && row.edge_type === 'REMOTE_CALL_RESOLVES_TO_OPERATION') return 'remote_action';
246
- if (row.to_kind === 'operation' && row.edge_type === 'LOCAL_CALL_RESOLVES_TO_OPERATION') return 'local_service_call';
247
- return String(call.call_type);
98
+
99
+ function finalizeDiagnostics(
100
+ diagnostics: Array<Record<string, unknown>>,
101
+ edges: TraceEdge[],
102
+ ): void {
103
+ const runtimeDiagnostic = runtimeVariableDiagnostic(edges);
104
+ if (runtimeDiagnostic)
105
+ prependTraceDiagnostic(diagnostics, runtimeDiagnostic);
106
+ for (const diagnostic of runtimeNoCandidateDiagnostics(edges))
107
+ prependTraceDiagnostic(diagnostics, diagnostic);
248
108
  }
249
- function includeCall(
250
- type: string,
251
- options: {
252
- includeExternal?: boolean;
253
- includeDb?: boolean;
254
- includeAsync?: boolean;
255
- },
256
- ): boolean {
257
- if (!options.includeDb && type === 'local_db_query') return false;
258
- if (!options.includeExternal && type === 'external_http') return false;
259
- if (!options.includeAsync && type.startsWith('async_')) return false;
260
- return true;
109
+
110
+ function traceRuntime(
111
+ db: Db,
112
+ options: TraceOptions,
113
+ hintOptions: ImplementationHintOptions,
114
+ roots: ReturnType<typeof createTraceRootPlan>,
115
+ diagnostics: Array<Record<string, unknown>>,
116
+ edges: TraceEdge[],
117
+ observer: CompactTraceObserver | undefined,
118
+ scheduler: TraversalScopeScheduler,
119
+ ): TraceExecutionRuntime {
120
+ return {
121
+ db,
122
+ options,
123
+ hintOptions,
124
+ workspaceId: roots.workspaceId,
125
+ maxDepth: positiveDepth(options.depth),
126
+ scheduler,
127
+ queue: roots.queue,
128
+ pendingRoots: roots.pendingRoots,
129
+ diagnostics,
130
+ nodes: new Map(),
131
+ recorder: new TraceEdgeRecorder(edges, observer),
132
+ };
261
133
  }
134
+
262
135
  export function trace(
263
136
  db: Db,
264
137
  start: TraceStart,
@@ -268,406 +141,31 @@ export function trace(
268
141
  const schemaLifecycle = schemaLifecycleDiagnostic(db);
269
142
  if (schemaLifecycle)
270
143
  return { start, nodes: [], edges: [], diagnostics: [schemaLifecycle] };
271
- const hintOptions = { implementationRepo: options.implementationRepo, implementationHints: options.implementationHints };
272
- const scope = startScope(db, start, hintOptions, options.workspaceId);
273
- const hasSelector = Boolean(start.repo || start.handler || start.operation
274
- || start.operationPath || start.servicePath);
275
- const diagnosticRepoId = scope.executionRepoId ?? scope.repo?.id;
144
+ const hints: ImplementationHintOptions = {
145
+ implementationRepo: options.implementationRepo,
146
+ implementationHints: options.implementationHints,
147
+ };
148
+ const scope = resolveTraceStartScope(db, start, hints, options.workspaceId);
276
149
  const scheduler = new TraversalScopeScheduler();
277
150
  const roots = createTraceRootPlan(db, scheduler, {
278
- repoId: diagnosticRepoId, files: scope.sourceFiles,
279
- symbolIds: scope.symbolIds, selectorMatched: scope.selectorMatched,
151
+ repoId: scope.executionRepoId ?? scope.repo?.id,
152
+ files: scope.sourceFiles,
153
+ symbolIds: scope.symbolIds,
154
+ selectorMatched: scope.selectorMatched,
280
155
  }, options.workspaceId, Boolean(options.includeAsync));
281
156
  observer?.setWorkspaceId?.(roots.workspaceId);
282
157
  if (roots.diagnostic)
283
158
  return { start, nodes: [], edges: [], diagnostics: [roots.diagnostic] };
284
- const { workspaceId, queue, pendingRoots } = roots;
285
- const diagnostics = loadTraceDiagnostics(
286
- db,
287
- diagnosticRepoId,
288
- !hasSelector,
289
- workspaceId,
290
- );
291
- const stale = diagnosticRepoId !== undefined || !hasSelector
292
- ? db.prepare('SELECT name,graph_stale_reason reason FROM repositories WHERE graph_stale_reason IS NOT NULL AND (? IS NULL OR id=?) AND (? IS NULL OR workspace_id=?) ORDER BY name,id').all(diagnosticRepoId, diagnosticRepoId, workspaceId, workspaceId) as Array<{ name?: string; reason?: string }>
293
- : [];
294
- for (const row of stale)
295
- prependTraceDiagnostic(diagnostics, { severity: 'warning', code: 'graph_stale', message: `Graph is stale for ${row.name ?? 'repository'}: ${row.reason ?? 'facts_changed'}. Run service-flow link.` });
296
- for (const diagnostic of scope.startDiagnostics ?? [])
297
- prependTraceDiagnostic(diagnostics, diagnostic);
298
- if (!scope.selectorMatched && !(scope.startDiagnostics?.length))
299
- prependTraceDiagnostic(diagnostics, selectorNotFoundDiagnostic(start));
300
- const maxDepth = positiveDepth(options.depth);
159
+ const diagnostics = initialDiagnostics(db, start, scope, roots.workspaceId);
301
160
  const edges: TraceEdge[] = [];
302
- const recorder = new TraceEdgeRecorder(edges, observer);
303
- const nodes = new Map<string, Record<string, unknown>>();
304
- if (scope.startOperationId && scope.selectorMatched) {
305
- const op = operationNode(db, scope.startOperationId);
306
- const impl = implementationScope(db, scope.startOperationId);
307
- if (op) nodes.set(String(op.id), op);
308
- const startSelection = hintedImplementationSelection(
309
- db, impl.edge, scope.startOperationId, hintOptions,
310
- );
311
- if (impl.edge && (impl.edge.status === 'resolved' || startSelection.methodId)) {
312
- const selectedMethodId = impl.edge.status === 'resolved' ? impl.edge.to_id : startSelection.methodId;
313
- const implEvidence = {
314
- ...parseEvidence(impl.edge.evidence_json),
315
- startResolution: {
316
- strategy: 'indexed_operation_graph',
317
- matchedOperationId: scope.startOperationId,
318
- implementationEdgeId: impl.edge.id,
319
- implementationStatus: impl.edge.status,
320
- selectedHandlerMethodId: selectedMethodId,
321
- },
322
- implementationSelection: startSelection.methodId
323
- ? startSelection.evidence : undefined,
324
- };
325
- const selected: SelectedHandlerEvidence = selectedMethodId
326
- ? withSelectedHandlerProvenance(
327
- implEvidence, selectedMethodId, handlerMethodNode(db, selectedMethodId),
328
- )
329
- : { evidence: implEvidence };
330
- if (selected.diagnostic) prependTraceDiagnostic(diagnostics, selected.diagnostic);
331
- if (selected.handler) nodes.set(String(selected.handler.id), selected.handler);
332
- const unresolvedReason = selected.unresolvedReason
333
- ?? (impl.edge.status === 'resolved' || startSelection.methodId
334
- ? undefined : String(impl.edge.unresolved_reason ?? impl.edge.status));
335
- const selectedScope = selectedMethodId
336
- ? handlerScope(db, selectedMethodId) : undefined;
337
- const edge: TraceEdge = { 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 };
338
- recordImplementationObservation(recorder, edge, {
339
- operationId: scope.startOperationId,
340
- handlerMethodId: selectedMethodId,
341
- handlerSymbolId: selectedScope?.symbolId,
342
- graphEdgeId: impl.edge.id,
343
- persistedStatus: impl.edge.status,
344
- persistedTargetKind: impl.edge.to_kind,
345
- persistedTargetId: impl.edge.to_id,
346
- effectiveStatus: startSelection.methodId
347
- ? 'resolved' : String(impl.edge.status ?? 'unresolved'),
348
- strategy: String(startSelection.evidence.strategy
349
- ?? 'indexed_operation_graph'),
350
- guided: startSelection.evidence.guided === true,
351
- unresolvedReason, evidence: selected.evidence, site: op ?? {},
352
- });
353
- }
354
- }
355
- while (queue.length > 0 || pendingRoots.length > 0) {
356
- if ((queue[0]?.depth ?? Number.POSITIVE_INFINITY) > 1
357
- && workspaceId !== undefined) {
358
- const root = nextPendingRoot(pendingRoots, scheduler, workspaceId);
359
- if (root) queue.unshift(root);
360
- }
361
- const current = queue.shift();
362
- if (!current || current.depth > maxDepth) continue;
363
- if (!scheduler.markExpanded(current.state)) continue;
364
- const calls = db
365
- .prepare(
366
- `SELECT c.*,r.name repoName,r.workspace_id workspaceId,
367
- r.graph_generation graphGeneration
368
- FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
369
- WHERE (? IS NULL OR c.repo_id=?) AND (? IS NULL OR r.workspace_id=?)
370
- ORDER BY c.source_file COLLATE BINARY,c.call_site_start_offset,
371
- c.call_site_end_offset,c.source_line,c.id`,
372
- )
373
- .all(current.repoId, current.repoId, workspaceId, workspaceId) as CallRow[];
374
- const filtered = calls.filter(
375
- (c) =>
376
- (current.unownedOnly ? c.source_symbol_id == null
377
- : !current.symbolIds || current.symbolIds.has(Number(c.source_symbol_id)))
378
- && (!current.files || current.files.has(String(c.source_file))) &&
379
- includeCall(String(c.call_type), options),
380
- );
381
- const callerBindings = new Map<string, ContextBinding>([...current.context, ...knownBindingsForScope(db, current.repoId, current.symbolIds, current.files), ...knownBindingsForCalls(db, filtered)]);
382
-
383
- if (!current.rootObservationOnly && current.symbolIds
384
- && current.symbolIds.size > 0 && current.depth < maxDepth) {
385
- const symbolRows = db.prepare(`SELECT sc.*,s.repo_id calleeRepoId,
386
- s.source_file calleeFile FROM symbol_calls sc
387
- LEFT JOIN symbols s ON s.id=sc.callee_symbol_id
388
- WHERE sc.call_role='ordinary_call'
389
- AND sc.caller_symbol_id IN (${[...current.symbolIds].map(() => '?').join(',')})
390
- ORDER BY sc.source_file COLLATE BINARY,sc.call_site_start_offset,
391
- sc.call_site_end_offset,sc.source_line,sc.id`).all(
392
- ...current.symbolIds,
393
- ) as Array<Record<string, unknown>>;
394
- for (const symbolCall of symbolRows) {
395
- if (!symbolCall.callee_symbol_id) continue;
396
- const nextSymbols = new Set([Number(symbolCall.callee_symbol_id)]);
397
- const nextFiles = new Set([String(symbolCall.calleeFile)]);
398
- const nextRepoId = Number(symbolCall.calleeRepoId);
399
- const nextContext = contextForSymbolCall(db, symbolCall, callerBindings);
400
- const scheduling = scheduler.schedule({
401
- workspaceId,
402
- repoId: nextRepoId,
403
- files: nextFiles,
404
- symbolIds: nextSymbols,
405
- context: nextContext,
406
- }, current.state);
407
- const calleeNode = symbolNode(db, Number(symbolCall.callee_symbol_id));
408
- if (calleeNode) nodes.set(String(calleeNode.id), calleeNode);
409
- 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 };
410
- const unresolvedReason = String(symbolCall.status) === 'resolved'
411
- ? undefined : symbolCall.unresolved_reason
412
- ? String(symbolCall.unresolved_reason) : undefined;
413
- const edge: TraceEdge = { 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 };
414
- const target = recordLocalCallObservation(recorder, edge, {
415
- symbolCall, evidence, unresolvedReason,
416
- });
417
- if (scheduling.kind === 'cycle') {
418
- const cycleEvidence = { cycle: true,
419
- cycleReason: 'structural_ancestry_cycle',
420
- symbolCallId: symbolCall.id };
421
- const cycleEdge: TraceEdge = { step: current.depth, type: 'cycle', from: String(symbolCall.callee_expression), to: scheduling.state.structuralKey, evidence: cycleEvidence, confidence: 1, unresolvedReason: 'Cycle detected in structural ancestry; downstream symbol was not expanded' };
422
- recordCycleObservation(recorder, cycleEdge, target, {
423
- workspaceId, repositoryId: nextRepoId, sourceFiles: nextFiles,
424
- symbolIds: nextSymbols,
425
- structuralKey: scheduling.state.structuralKey,
426
- }, { symbolCallId: symbolCall.id,
427
- symbolId: symbolCall.callee_symbol_id }, symbolCall);
428
- }
429
- if (scheduling.kind === 'scheduled') enqueueCausalScope(
430
- queue, pendingRoots, { repoId: nextRepoId, files: nextFiles,
431
- symbolIds: nextSymbols, depth: current.depth + 1,
432
- context: nextContext, state: scheduling.state });
433
- }
434
- }
435
- const graph = graphForCalls(
436
- db,
437
- filtered.map((c) => Number(c.id)),
438
- );
439
- for (const call of filtered) {
440
- const callNode = `call:${call.id}`;
441
- nodes.set(callNode, {
442
- id: callNode,
443
- kind: 'outbound_call',
444
- repo: call.repoName,
445
- file: call.source_file,
446
- line: call.source_line,
447
- callType: call.call_type,
448
- });
449
- const persistedRowsForCall = graph.get(Number(call.id)) ?? [];
450
- const contextual = contextualRuntimeResolution(db, call, callerBindings.get(receiverFromEvidence(call.evidence_json) ?? ''), call.workspaceId, persistedRowsForCall);
451
- const graphRows = contextual.row ? [contextual.row] : persistedRowsForCall;
452
- for (const row of graphRows) {
453
- const persistedEvidence = parseEvidence(row.evidence_json);
454
- const rawEvidence = baseTraceEvidence(row as TraceGraphRow, call, persistedEvidence, contextual.evidence);
455
- const effective = runtimeResolution(db, row as TraceGraphRow, rawEvidence, {
456
- vars: options.vars,
457
- dynamicMode: options.dynamicMode ?? 'strict',
458
- maxDynamicCandidates: options.maxDynamicCandidates,
459
- }, call.workspaceId, contextual.state);
460
- const evidence = effective.evidence;
461
- const effectiveRow = effective.row;
462
- const targetNode = `${effectiveRow.to_kind}:${effectiveRow.to_id}`;
463
- const opNode = effectiveRow.to_kind === 'operation' ? operationNode(db, effectiveRow.to_id) : undefined;
464
- nodes.set(targetNode, opNode ?? {
465
- id: targetNode,
466
- kind: effectiveRow.to_kind,
467
- label: effectiveRow.to_kind === 'db_entity' ? `Entity: ${effectiveRow.to_id || 'unknown'}` : effectiveRow.to_id,
468
- });
469
- const to = edgeTarget(effectiveRow, evidence);
470
- const edge: TraceEdge = {
471
- step: current.depth,
472
- type: traceEdgeType(call, effectiveRow),
473
- from: `${call.repoName}:${call.source_file}:${call.source_line}`,
474
- to,
475
- evidence,
476
- confidence: Number(effectiveRow.confidence ?? call.confidence),
477
- unresolvedReason: effective.unresolvedReason,
478
- };
479
- const semanticWorkspaceId = workspaceId ?? call.workspaceId;
480
- const semantic = recordOutboundObservation(recorder, edge, {
481
- call, row: effectiveRow, evidence,
482
- workspaceId: semanticWorkspaceId,
483
- dynamicMode: options.dynamicMode,
484
- unresolvedReason: effective.unresolvedReason,
485
- });
486
- if (options.includeAsync && call.call_type === 'async_emit'
487
- && effectiveRow.edge_type === 'HANDLER_EMITS_EVENT'
488
- && typeof call.event_name_expr === 'string') {
489
- const plans = planEventSubscriberTransitions(db, {
490
- workspaceId: workspaceId ?? call.workspaceId,
491
- graphGeneration: call.graphGeneration,
492
- eventName: call.event_name_expr,
493
- }, scheduler, current.state, current.depth, maxDepth);
494
- for (const plan of plans) {
495
- const nodeId = String(plan.node.id);
496
- const targetLabel = String(plan.node.label ?? nodeId);
497
- nodes.set(nodeId, plan.node);
498
- const bridgeEdge: TraceEdge = {
499
- step: current.depth,
500
- type: 'event_name_matches_subscription_handler',
501
- from: plan.transition.eventName,
502
- to: targetLabel,
503
- evidence: plan.evidence,
504
- confidence: plan.transition.confidence,
505
- unresolvedReason: plan.transition.unresolvedReason,
506
- };
507
- const handler = plan.transition.handler;
508
- const bridgeTarget = recordEventBridgeObservation(
509
- recorder, bridgeEdge, plan, semanticWorkspaceId, plans.length,
510
- );
511
- if (plan.bodyExpansion === 'cycle_blocked' && plan.state) {
512
- const cycleEvidence = { cycle: true,
513
- cycleReason: 'structural_ancestry_cycle',
514
- graphEdgeId: plan.transition.graphEdgeId };
515
- const cycleEdge: TraceEdge = { step: current.depth, type: 'cycle', from: targetLabel, to: plan.state.structuralKey, evidence: cycleEvidence, confidence: 1, unresolvedReason: 'Cycle detected across an event subscriber boundary; downstream symbol was not expanded' };
516
- recordEventCycleObservation(recorder, cycleEdge, plan,
517
- bridgeTarget, semanticWorkspaceId);
518
- }
519
- if (plan.bodyExpansion === 'scheduled' && plan.state && handler) {
520
- const files = new Set([handler.sourceFile]);
521
- const symbolIds = new Set([handler.symbolId]);
522
- enqueueCausalScope(queue, pendingRoots, {
523
- repoId: handler.repoId, files, symbolIds,
524
- depth: current.depth + 1, context: new Map(), state: plan.state,
525
- });
526
- }
527
- }
528
- }
529
- if ((options.dynamicMode ?? 'strict') === 'candidates'
530
- && effectiveRow.status !== 'resolved') {
531
- for (const branch of dynamicCandidateBranches(
532
- current.depth, call, evidence,
533
- )) {
534
- recordDynamicBranchObservation(
535
- recorder, branch, call, semantic.source, evidence,
536
- semanticWorkspaceId,
537
- );
538
- }
539
- }
540
- if (effectiveRow.to_kind === 'operation') {
541
- const implementation = implementationScope(db, effectiveRow.to_id);
542
- const contextSelection = contextualImplementationSelection(
543
- db, implementation.edge, effectiveRow.to_id, current.repoId, evidence,
544
- hintOptions,
545
- );
546
- const contextMethodId = contextSelection.methodId;
547
- let selectedHandlerAvailable = true;
548
- if (implementation.edge) {
549
- const implEvidence = parseEvidence(implementation.edge.evidence_json);
550
- const hintDiagnostic = implementationHintDiagnostic(contextSelection, implEvidence);
551
- if (hintDiagnostic) prependTraceDiagnostic(diagnostics, hintDiagnostic);
552
- const selectedMethodId = implementation.edge.status === 'resolved'
553
- ? implementation.edge.to_id : contextMethodId;
554
- const selectionEvidence = contextMethodId
555
- ? {
556
- ...implEvidence,
557
- contextualImplementationSelected:
558
- contextSelection.evidence.strategy !== 'implementation_repo_hint',
559
- contextualImplementation: contextSelection.evidence,
560
- implementationSelection: contextSelection.evidence,
561
- }
562
- : {
563
- ...implEvidence,
564
- contextualImplementation: contextSelection.evidence,
565
- implementationSelection: contextSelection.evidence,
566
- };
567
- const selected: SelectedHandlerEvidence = selectedMethodId
568
- ? withSelectedHandlerProvenance(
569
- selectionEvidence,
570
- selectedMethodId,
571
- handlerMethodNode(db, selectedMethodId),
572
- )
573
- : { evidence: selectionEvidence };
574
- selectedHandlerAvailable = !selected.unresolvedReason;
575
- if (selected.diagnostic) prependTraceDiagnostic(diagnostics, selected.diagnostic);
576
- const implTo = selected.handler?.label
577
- ? String(selected.handler.label)
578
- : `${implementation.edge.to_kind}:${implementation.edge.to_id}`;
579
- if (selected.handler) nodes.set(String(selected.handler.id), selected.handler);
580
- const unresolvedReason = selected.unresolvedReason
581
- ?? (implementation.edge.status === 'resolved' || contextMethodId
582
- ? undefined
583
- : String(implementation.edge.unresolved_reason
584
- ?? implementation.edge.status));
585
- const implementationTraceEdge: TraceEdge = {
586
- step: current.depth,
587
- type: 'operation_implemented_by_handler',
588
- from: to,
589
- to: implTo,
590
- evidence: selected.evidence,
591
- confidence: Number(implementation.edge.confidence ?? 0),
592
- unresolvedReason,
593
- };
594
- const selectedScope = selectedMethodId
595
- ? handlerScope(db, selectedMethodId) : undefined;
596
- recordImplementationObservation(recorder, implementationTraceEdge, {
597
- operationId: effectiveRow.to_id,
598
- handlerMethodId: selectedMethodId,
599
- handlerSymbolId: selectedScope?.symbolId,
600
- graphEdgeId: implementation.edge.id,
601
- persistedStatus: implementation.edge.status,
602
- persistedTargetKind: implementation.edge.to_kind,
603
- persistedTargetId: implementation.edge.to_id,
604
- effectiveStatus: contextMethodId
605
- ? 'resolved' : String(implementation.edge.status),
606
- strategy: String(contextSelection.evidence.strategy
607
- ?? (contextMethodId ? 'contextual_implementation_selection'
608
- : 'indexed_operation_graph')),
609
- guided: contextSelection.evidence.guided === true,
610
- contextual: Boolean(contextMethodId
611
- && contextSelection.evidence.strategy
612
- !== 'implementation_repo_hint'),
613
- unresolvedReason, evidence: selected.evidence, site: call,
614
- });
615
- }
616
- if (current.depth >= maxDepth) continue;
617
- const contextScope = contextMethodId ? handlerScope(db, contextMethodId) : undefined;
618
- const files = contextScope?.files ?? (implementation.files.size > 0 ? implementation.files : handlerFilesForOperation(db, effectiveRow.to_id));
619
- const symbolIds = contextScope?.symbolId ? new Set([contextScope.symbolId]) : implementation.symbolId ? new Set([implementation.symbolId]) : undefined;
620
- if (selectedHandlerAvailable
621
- && (implementation.edge?.status === 'resolved' || contextScope)
622
- && files.size > 0) {
623
- const targetRepoId = contextScope?.repoId ?? implementation.repoId ?? (db
624
- .prepare(
625
- 'SELECT s.repo_id repoId FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?',
626
- )
627
- .get(effectiveRow.to_id)?.repoId as number | undefined);
628
- const nextContext = new Map<string, ContextBinding>();
629
- const scheduling = scheduler.schedule({
630
- workspaceId: workspaceId ?? call.workspaceId,
631
- repoId: targetRepoId,
632
- files,
633
- symbolIds,
634
- context: nextContext,
635
- }, current.state);
636
- if (scheduling.kind === 'cycle') {
637
- const cycleEvidence = { ...evidence, cycle: true,
638
- cycleReason: 'structural_ancestry_cycle' };
639
- const cycleEdge: TraceEdge = {
640
- step: current.depth,
641
- type: 'cycle',
642
- from: to,
643
- to: scheduling.state.structuralKey,
644
- evidence: cycleEvidence,
645
- confidence: 1,
646
- unresolvedReason:
647
- 'Cycle detected in structural ancestry; downstream scope was not expanded',
648
- };
649
- recordCycleObservation(recorder, cycleEdge, semantic.target, {
650
- workspaceId: semanticWorkspaceId, repositoryId: targetRepoId,
651
- sourceFiles: files, symbolIds,
652
- structuralKey: scheduling.state.structuralKey,
653
- }, { graphEdgeId: evidence.persistedGraphEdgeId,
654
- outboundCallId: call.id,
655
- operationId: effectiveRow.to_id }, call);
656
- }
657
- if (scheduling.kind === 'scheduled') enqueueCausalScope(
658
- queue, pendingRoots, { repoId: targetRepoId, files, symbolIds,
659
- depth: current.depth + 1, context: nextContext,
660
- state: scheduling.state });
661
- }
662
- }
663
- }
664
- }
665
- }
666
- const runtimeDiagnostic = runtimeVariableDiagnostic(edges);
667
- if (runtimeDiagnostic) prependTraceDiagnostic(diagnostics, runtimeDiagnostic);
668
- for (const diagnostic of runtimeNoCandidateDiagnostics(edges))
669
- prependTraceDiagnostic(diagnostics, diagnostic);
670
- return { start, nodes: [...nodes.values()], edges, diagnostics };
161
+ const runtime = traceRuntime(
162
+ db, options, hints, roots, diagnostics, edges, observer, scheduler,
163
+ );
164
+ if (scope.startOperationId && scope.selectorMatched)
165
+ recordTraceStartImplementation(runtime, scope.startOperationId);
166
+ executeTraceScopes(runtime);
167
+ finalizeDiagnostics(diagnostics, edges);
168
+ return { start, nodes: [...runtime.nodes.values()], edges, diagnostics };
671
169
  }
672
170
 
673
171
  export function traceWithObserver(