@saptools/service-flow 0.1.67 → 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.
- package/CHANGELOG.md +10 -0
- package/README.md +20 -8
- package/TECHNICAL-NOTE.md +27 -0
- package/dist/{chunk-ZQABU7MR.js → chunk-AEM4JY22.js} +12676 -6714
- package/dist/chunk-AEM4JY22.js.map +1 -0
- package/dist/cli.js +2325 -413
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +61 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/003-doctor-package-resolution.ts +68 -0
- package/src/cli/doctor.ts +25 -14
- package/src/db/000-call-fact-repository.ts +475 -342
- package/src/db/001-fact-lifecycle.ts +60 -30
- package/src/db/002-fact-json-inventory.ts +169 -0
- package/src/db/003-current-fact-semantics.ts +698 -0
- package/src/db/004-package-target-invalidation.ts +173 -0
- package/src/db/005-schema-structure.ts +201 -0
- package/src/db/006-relative-symbol-resolution.ts +443 -0
- package/src/db/007-package-fact-semantics.ts +573 -0
- package/src/db/008-relative-fact-semantics.ts +207 -0
- package/src/db/009-binding-fact-semantics.ts +347 -0
- package/src/db/010-package-symbol-surface-semantics.ts +320 -0
- package/src/db/011-symbol-call-semantics.ts +144 -0
- package/src/db/012-binding-reference-proof.ts +264 -0
- package/src/db/migrations.ts +16 -3
- package/src/db/repositories.ts +113 -6
- package/src/db/schema.ts +4 -2
- package/src/index.ts +12 -0
- package/src/indexer/repository-indexer.ts +72 -11
- package/src/indexer/workspace-indexer.ts +123 -32
- package/src/linker/003-package-import-symbol-resolver.ts +363 -131
- package/src/linker/004-event-subscription-handler-linker.ts +2 -0
- package/src/linker/005-odata-path-structure.ts +371 -0
- package/src/linker/cross-repo-linker.ts +2 -1
- package/src/linker/odata-path-normalizer.ts +273 -180
- package/src/linker/service-resolver.ts +197 -77
- package/src/parsers/002-symbol-import-bindings.ts +516 -0
- package/src/parsers/003-package-public-surface.ts +661 -0
- package/src/parsers/004-fact-identity.ts +108 -0
- package/src/parsers/005-event-subscription-facts.ts +281 -0
- package/src/parsers/006-binding-identity.ts +343 -0
- package/src/parsers/007-source-fact-reconciliation.ts +105 -0
- package/src/parsers/008-package-surface-publication.ts +82 -0
- package/src/parsers/009-symbol-call-facts.ts +528 -0
- package/src/parsers/010-package-public-surface-analysis.ts +352 -0
- package/src/parsers/011-binding-lexical-scope.ts +583 -0
- package/src/parsers/012-package-fact-contract.ts +306 -0
- package/src/parsers/013-executable-body-eligibility.ts +35 -0
- package/src/parsers/014-service-binding-helper-flow.ts +240 -0
- package/src/parsers/015-service-binding-collector.ts +693 -0
- package/src/parsers/016-local-symbol-reference.ts +261 -0
- package/src/parsers/017-symbol-derived-contexts.ts +268 -0
- package/src/parsers/018-package-commonjs-syntax.ts +142 -0
- package/src/parsers/019-binding-assignment-targets.ts +76 -0
- package/src/parsers/020-stable-local-value.ts +217 -0
- package/src/parsers/021-binding-visibility.ts +152 -0
- package/src/parsers/operation-path-analysis.ts +6 -1
- package/src/parsers/outbound-call-parser.ts +19 -6
- package/src/parsers/package-json-parser.ts +45 -3
- package/src/parsers/service-binding-parser-helpers.ts +86 -15
- package/src/parsers/service-binding-parser.ts +147 -597
- package/src/parsers/symbol-parser.ts +482 -353
- package/src/trace/002-trace-diagnostics.ts +36 -1
- package/src/trace/016-compact-projector.ts +16 -17
- package/src/trace/020-compact-field-projection.ts +48 -37
- package/src/trace/021-compact-decision-normalization.ts +105 -0
- package/src/trace/022-trace-fact-preflight.ts +21 -0
- package/src/trace/023-nested-event-scopes.ts +23 -0
- package/src/trace/024-compact-observation-decision.ts +76 -0
- package/src/trace/025-trace-implementation-scope.ts +123 -0
- package/src/trace/026-trace-start-scope.ts +335 -0
- package/src/trace/027-trace-scope-execution.ts +566 -0
- package/src/trace/028-trace-operation-execution.ts +336 -0
- package/src/trace/029-trace-start-implementation.ts +172 -0
- package/src/trace/trace-engine.ts +122 -624
- package/src/types.ts +56 -0
- package/src/utils/001-placeholders.ts +188 -10
- package/src/version.ts +1 -1
- package/dist/chunk-ZQABU7MR.js.map +0 -1
|
@@ -1,264 +1,137 @@
|
|
|
1
1
|
import type { Db } from '../db/connection.js';
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
|
|
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 {
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
52
|
+
|
|
53
|
+
function staleRows(
|
|
145
54
|
db: Db,
|
|
146
55
|
repoId: number | undefined,
|
|
147
|
-
start: TraceStart,
|
|
148
56
|
workspaceId: number | undefined,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
return
|
|
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
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const
|
|
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:
|
|
279
|
-
|
|
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
|
|
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
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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(
|