@saptools/service-flow 0.1.64 → 0.1.66
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 +18 -0
- package/README.md +67 -11
- package/TECHNICAL-NOTE.md +41 -1
- package/dist/{chunk-TBH33OYC.js → chunk-TOVX4WYH.js} +3765 -643
- package/dist/chunk-TOVX4WYH.js.map +1 -0
- package/dist/cli.js +231 -292
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +196 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/002-doctor-lifecycle.ts +66 -0
- package/src/cli/doctor.ts +14 -27
- package/src/cli.ts +130 -102
- package/src/db/000-call-fact-repository.ts +537 -0
- package/src/db/001-fact-lifecycle.ts +111 -0
- package/src/db/migrations.ts +16 -1
- package/src/db/repositories.ts +1 -315
- package/src/db/schema.ts +4 -2
- package/src/index.ts +22 -0
- package/src/linker/004-event-subscription-handler-linker.ts +300 -0
- package/src/linker/cross-repo-linker.ts +23 -2
- package/src/output/001-compact-json-output.ts +6 -0
- package/src/parsers/imported-wrapper-parser.ts +4 -0
- package/src/parsers/outbound-call-parser.ts +11 -1
- package/src/parsers/symbol-parser.ts +110 -2
- package/src/trace/010-traversal-scope.ts +188 -0
- package/src/trace/011-event-subscriber-traversal.ts +366 -0
- package/src/trace/012-trace-graph-lookups.ts +74 -0
- package/src/trace/013-trace-root-scopes.ts +279 -0
- package/src/trace/014-compact-contract.ts +347 -0
- package/src/trace/015-trace-edge-recorder.ts +336 -0
- package/src/trace/016-compact-projector.ts +697 -0
- package/src/trace/017-trace-context.ts +378 -0
- package/src/trace/018-compact-trace.ts +87 -0
- package/src/trace/019-trace-edge-semantics.ts +328 -0
- package/src/trace/020-compact-field-projection.ts +387 -0
- package/src/trace/dynamic-branches.ts +35 -13
- package/src/trace/trace-engine.ts +271 -245
- package/src/types.ts +10 -0
- package/src/version.ts +1 -1
- package/dist/chunk-TBH33OYC.js.map +0 -1
|
@@ -1,6 +1,85 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// package.json
|
|
4
|
+
var package_default = {
|
|
5
|
+
name: "@saptools/service-flow",
|
|
6
|
+
version: "0.1.66",
|
|
7
|
+
description: "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
|
|
8
|
+
type: "module",
|
|
9
|
+
publishConfig: {
|
|
10
|
+
access: "public",
|
|
11
|
+
registry: "https://registry.npmjs.org/"
|
|
12
|
+
},
|
|
13
|
+
bin: {
|
|
14
|
+
"service-flow": "dist/cli.js"
|
|
15
|
+
},
|
|
16
|
+
main: "./dist/index.js",
|
|
17
|
+
types: "./dist/index.d.ts",
|
|
18
|
+
exports: {
|
|
19
|
+
".": {
|
|
20
|
+
types: "./dist/index.d.ts",
|
|
21
|
+
import: "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
files: [
|
|
25
|
+
"CHANGELOG.md",
|
|
26
|
+
"README.md",
|
|
27
|
+
"TECHNICAL-NOTE.md",
|
|
28
|
+
"dist",
|
|
29
|
+
"src"
|
|
30
|
+
],
|
|
31
|
+
engines: {
|
|
32
|
+
node: ">=24.0.0"
|
|
33
|
+
},
|
|
34
|
+
scripts: {
|
|
35
|
+
build: "tsup",
|
|
36
|
+
typecheck: "tsc --noEmit",
|
|
37
|
+
lint: "eslint src tests",
|
|
38
|
+
test: "vitest run tests/unit",
|
|
39
|
+
"test:unit": "vitest run tests/unit",
|
|
40
|
+
"test:e2e": "vitest run tests/e2e",
|
|
41
|
+
"test:e2e:fake": "vitest run tests/e2e"
|
|
42
|
+
},
|
|
43
|
+
keywords: [
|
|
44
|
+
"sap",
|
|
45
|
+
"cap",
|
|
46
|
+
"cds",
|
|
47
|
+
"btp",
|
|
48
|
+
"cli",
|
|
49
|
+
"service-graph",
|
|
50
|
+
"call-graph",
|
|
51
|
+
"sqlite",
|
|
52
|
+
"saptools"
|
|
53
|
+
],
|
|
54
|
+
author: "Dong Tran",
|
|
55
|
+
license: "MIT",
|
|
56
|
+
repository: {
|
|
57
|
+
type: "git",
|
|
58
|
+
url: "git+https://github.com/dongitran/saptools.git",
|
|
59
|
+
directory: "packages/service-flow"
|
|
60
|
+
},
|
|
61
|
+
homepage: "https://github.com/dongitran/saptools/tree/main/packages/service-flow#readme",
|
|
62
|
+
bugs: {
|
|
63
|
+
url: "https://github.com/dongitran/saptools/issues"
|
|
64
|
+
},
|
|
65
|
+
dependencies: {
|
|
66
|
+
commander: "13.1.0",
|
|
67
|
+
picocolors: "1.1.1",
|
|
68
|
+
typescript: "5.9.3",
|
|
69
|
+
zod: "4.4.3"
|
|
70
|
+
},
|
|
71
|
+
devDependencies: {
|
|
72
|
+
"@vitest/coverage-v8": "3.2.4",
|
|
73
|
+
tsup: "8.5.1",
|
|
74
|
+
vitest: "3.2.4"
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// src/version.ts
|
|
79
|
+
var VERSION = package_default.version;
|
|
80
|
+
var ANALYZER_VERSION = "0.1.66-facts.1";
|
|
81
|
+
|
|
82
|
+
// src/db/000-call-fact-repository.ts
|
|
4
83
|
import { posix } from "path";
|
|
5
84
|
|
|
6
85
|
// src/utils/000-bounded-projection.ts
|
|
@@ -126,6 +205,406 @@ function upperFirst(value) {
|
|
|
126
205
|
return value ? `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}` : value;
|
|
127
206
|
}
|
|
128
207
|
|
|
208
|
+
// src/db/000-call-fact-repository.ts
|
|
209
|
+
function insertSymbolCalls(db, repoId, rows2) {
|
|
210
|
+
const callerStmt = db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1");
|
|
211
|
+
const insertStmt = db.prepare("INSERT INTO symbol_calls(repo_id,caller_symbol_id,callee_symbol_id,callee_expression,import_source,source_file,source_line,call_site_start_offset,call_site_end_offset,call_role,status,confidence,evidence_json,unresolved_reason) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
|
|
212
|
+
for (const r of rows2) {
|
|
213
|
+
const caller = callerStmt.get(repoId, r.sourceFile, r.callerQualifiedName);
|
|
214
|
+
const target = resolveSymbolCallTarget(db, repoId, r);
|
|
215
|
+
insertStmt.run(
|
|
216
|
+
repoId,
|
|
217
|
+
caller?.id,
|
|
218
|
+
target.id,
|
|
219
|
+
r.calleeExpression,
|
|
220
|
+
r.importSource,
|
|
221
|
+
r.sourceFile,
|
|
222
|
+
r.sourceLine,
|
|
223
|
+
r.callSiteStartOffset,
|
|
224
|
+
r.callSiteEndOffset,
|
|
225
|
+
r.callRole,
|
|
226
|
+
target.status,
|
|
227
|
+
0.8,
|
|
228
|
+
JSON.stringify({
|
|
229
|
+
...r.evidence,
|
|
230
|
+
candidateStrategy: target.strategy,
|
|
231
|
+
candidateCount: target.candidateCount,
|
|
232
|
+
resolvedModulePath: target.resolvedModulePath
|
|
233
|
+
}),
|
|
234
|
+
target.reason
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
var stripExt = (value) => value.replace(/\.(ts|tsx|js|jsx|cds)$/, "");
|
|
239
|
+
function symbolTargetRows(rows2) {
|
|
240
|
+
return rows2.flatMap((row) => typeof row.id === "number" ? [{
|
|
241
|
+
id: row.id,
|
|
242
|
+
kind: typeof row.kind === "string" ? row.kind : void 0,
|
|
243
|
+
sourceFile: nullableString(row.sourceFile),
|
|
244
|
+
evidenceJson: nullableString(row.evidenceJson)
|
|
245
|
+
}] : []);
|
|
246
|
+
}
|
|
247
|
+
function relativeModuleTargets(callerSourceFile, importSource) {
|
|
248
|
+
const base = posix.dirname(callerSourceFile);
|
|
249
|
+
const joined = stripExt(posix.normalize(posix.join(base, importSource)));
|
|
250
|
+
return /* @__PURE__ */ new Set([joined, `${joined}/index`]);
|
|
251
|
+
}
|
|
252
|
+
function moduleRows(rows2, r) {
|
|
253
|
+
if (!r.importSource) return [];
|
|
254
|
+
const targets = relativeModuleTargets(r.sourceFile, r.importSource);
|
|
255
|
+
return rows2.filter((row) => typeof row.sourceFile === "string" && targets.has(stripExt(row.sourceFile)));
|
|
256
|
+
}
|
|
257
|
+
function resolvedSymbol(row, strategy, candidateCount, moduleScoped = false) {
|
|
258
|
+
return {
|
|
259
|
+
id: row.id,
|
|
260
|
+
status: "resolved",
|
|
261
|
+
reason: null,
|
|
262
|
+
strategy,
|
|
263
|
+
candidateCount,
|
|
264
|
+
resolvedModulePath: moduleScoped && row.sourceFile ? stripExt(row.sourceFile) : void 0
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function exportedSymbolRows(db, repoId, r) {
|
|
268
|
+
return symbolTargetRows(db.prepare("SELECT id,kind,source_file sourceFile,evidence_json evidenceJson FROM symbols WHERE repo_id=? AND source_file<>? AND exported=1 AND (exported_name=? OR name=? OR qualified_name=?) ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName, r.calleeLocalName, r.calleeLocalName));
|
|
269
|
+
}
|
|
270
|
+
function isRelativeImportedSymbolCall(r) {
|
|
271
|
+
return Boolean(r.importSource?.startsWith("."));
|
|
272
|
+
}
|
|
273
|
+
function sameFileResolution(db, repoId, r, relation) {
|
|
274
|
+
const bareImport = relation === "relative_import" && isRelativeImportedSymbolCall(r) && !String(r.calleeLocalName).includes(".");
|
|
275
|
+
if (bareImport || relation === "relative_import_namespace_member" || relation === "package_import") return void 0;
|
|
276
|
+
const rows2 = symbolTargetRows(db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND (name=? OR qualified_name=?) ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName, r.calleeLocalName));
|
|
277
|
+
if (rows2.length === 1 && rows2[0]) return resolvedSymbol(rows2[0], "same_file_exact", 1);
|
|
278
|
+
return rows2.length > 1 ? {
|
|
279
|
+
id: null,
|
|
280
|
+
status: "ambiguous",
|
|
281
|
+
reason: "Multiple same-file symbol targets matched exactly",
|
|
282
|
+
strategy: "same_file_exact",
|
|
283
|
+
candidateCount: rows2.length
|
|
284
|
+
} : void 0;
|
|
285
|
+
}
|
|
286
|
+
function classInstanceResolution(db, repoId, r, relation) {
|
|
287
|
+
if (relation !== "class_instance_method" || !isRelativeImportedSymbolCall(r))
|
|
288
|
+
return void 0;
|
|
289
|
+
const rows2 = symbolTargetRows(db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file<>? AND qualified_name=? ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName));
|
|
290
|
+
if (rows2.length === 1 && rows2[0])
|
|
291
|
+
return resolvedSymbol(rows2[0], "relative_import_class_instance_method", 1);
|
|
292
|
+
return rows2.length > 1 ? {
|
|
293
|
+
id: null,
|
|
294
|
+
status: "ambiguous",
|
|
295
|
+
reason: "Multiple relative class instance method targets matched exactly",
|
|
296
|
+
strategy: "relative_import_class_instance_method",
|
|
297
|
+
candidateCount: rows2.length
|
|
298
|
+
} : void 0;
|
|
299
|
+
}
|
|
300
|
+
function namespaceResolution(db, repoId, r, relation) {
|
|
301
|
+
if (relation !== "relative_import_namespace_member" || !isRelativeImportedSymbolCall(r)) return void 0;
|
|
302
|
+
const rows2 = moduleRows(exportedSymbolRows(db, repoId, r), r);
|
|
303
|
+
if (rows2.length === 1 && rows2[0])
|
|
304
|
+
return resolvedSymbol(rows2[0], "relative_import_namespace_member", 1, true);
|
|
305
|
+
if (rows2.length > 1) return {
|
|
306
|
+
id: null,
|
|
307
|
+
status: "ambiguous",
|
|
308
|
+
reason: "Multiple namespace member targets matched the imported module",
|
|
309
|
+
strategy: "relative_import_namespace_member",
|
|
310
|
+
candidateCount: rows2.length
|
|
311
|
+
};
|
|
312
|
+
return {
|
|
313
|
+
id: null,
|
|
314
|
+
status: "unresolved",
|
|
315
|
+
reason: "No namespace member target matched the imported module",
|
|
316
|
+
strategy: "relative_import_namespace_member",
|
|
317
|
+
candidateCount: 0
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function proxyResolution(rows2, r, relation) {
|
|
321
|
+
if (relation !== "relative_import_proxy_member" || rows2.length <= 1) return void 0;
|
|
322
|
+
const mapped = rows2.filter(isExportedObjectMapping);
|
|
323
|
+
if (mapped.length > 0) {
|
|
324
|
+
const concrete = rows2.find((row) => row.kind !== "object_alias") ?? mapped[0];
|
|
325
|
+
return {
|
|
326
|
+
id: concrete?.id ?? null,
|
|
327
|
+
status: "resolved",
|
|
328
|
+
reason: null,
|
|
329
|
+
strategy: "proxy_member_exported_object_map",
|
|
330
|
+
candidateCount: rows2.length
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
const scoped = moduleRows(rows2, r);
|
|
334
|
+
if (scoped.length === 1 && scoped[0])
|
|
335
|
+
return resolvedSymbol(scoped[0], "relative_import_path_disambiguated", rows2.length, true);
|
|
336
|
+
return {
|
|
337
|
+
id: null,
|
|
338
|
+
status: "ambiguous",
|
|
339
|
+
reason: "Proxy member target requires explicit factory/module/type evidence; global member name is ambiguous",
|
|
340
|
+
strategy: "proxy_member_no_global_name_fallback",
|
|
341
|
+
candidateCount: rows2.length
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function isExportedObjectMapping(row) {
|
|
345
|
+
const evidence = String(row.evidenceJson ?? "");
|
|
346
|
+
return evidence.includes("exported_object_shorthand") || evidence.includes("exported_object_literal");
|
|
347
|
+
}
|
|
348
|
+
function exportedResolution(rows2, r, relation) {
|
|
349
|
+
if (rows2.length === 1 && rows2[0]) return resolvedSymbol(
|
|
350
|
+
rows2[0],
|
|
351
|
+
relation === "relative_import_proxy_member" ? "proxy_member_unique_exported_candidate" : "relative_import_exported_exact",
|
|
352
|
+
1,
|
|
353
|
+
moduleRows(rows2, r).length === 1
|
|
354
|
+
);
|
|
355
|
+
if (rows2.length <= 1) return void 0;
|
|
356
|
+
const scoped = isRelativeImportedSymbolCall(r) ? moduleRows(rows2, r) : [];
|
|
357
|
+
if (scoped.length === 1 && scoped[0])
|
|
358
|
+
return resolvedSymbol(scoped[0], "relative_import_path_disambiguated", rows2.length, true);
|
|
359
|
+
return {
|
|
360
|
+
id: null,
|
|
361
|
+
status: "ambiguous",
|
|
362
|
+
reason: "Multiple exported symbol targets matched exactly",
|
|
363
|
+
strategy: "exported_exact",
|
|
364
|
+
candidateCount: rows2.length
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
function accessorResolution(db, repoId, r, relation) {
|
|
368
|
+
if (relation !== "relative_import" || !isRelativeImportedSymbolCall(r) || !/^[^.]+\.[^.]+$/.test(String(r.calleeLocalName))) return void 0;
|
|
369
|
+
const methodRows = symbolTargetRows(db.prepare("SELECT id,kind,source_file sourceFile FROM symbols WHERE repo_id=? AND source_file<>? AND kind='method' AND qualified_name=? ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName));
|
|
370
|
+
const scoped = moduleRows(methodRows, r);
|
|
371
|
+
if (scoped.length === 1 && scoped[0]) return resolvedSymbol(
|
|
372
|
+
scoped[0],
|
|
373
|
+
"relative_import_static_accessor_instance_method",
|
|
374
|
+
1,
|
|
375
|
+
true
|
|
376
|
+
);
|
|
377
|
+
return scoped.length > 1 ? {
|
|
378
|
+
id: null,
|
|
379
|
+
status: "ambiguous",
|
|
380
|
+
reason: "Multiple static-accessor instance method targets matched the imported module",
|
|
381
|
+
strategy: "relative_import_static_accessor_instance_method",
|
|
382
|
+
candidateCount: scoped.length
|
|
383
|
+
} : void 0;
|
|
384
|
+
}
|
|
385
|
+
function resolveSymbolCallTarget(db, repoId, r) {
|
|
386
|
+
const relation = r.evidence.relation;
|
|
387
|
+
const early = sameFileResolution(db, repoId, r, relation) ?? classInstanceResolution(db, repoId, r, relation) ?? namespaceResolution(db, repoId, r, relation);
|
|
388
|
+
if (early) return early;
|
|
389
|
+
const rows2 = relation === "package_import" ? [] : exportedSymbolRows(db, repoId, r);
|
|
390
|
+
const matched = proxyResolution(rows2, r, relation) ?? exportedResolution(rows2, r, relation) ?? accessorResolution(db, repoId, r, relation);
|
|
391
|
+
if (matched) return matched;
|
|
392
|
+
if (relation === "package_import") return {
|
|
393
|
+
id: null,
|
|
394
|
+
status: "unresolved",
|
|
395
|
+
reason: "Package import target resolution requires a post-publication workspace pass",
|
|
396
|
+
strategy: "package_import_unresolved",
|
|
397
|
+
candidateCount: 0
|
|
398
|
+
};
|
|
399
|
+
return {
|
|
400
|
+
id: null,
|
|
401
|
+
status: "unresolved",
|
|
402
|
+
reason: "No local symbol target matched exactly",
|
|
403
|
+
strategy: relation === "relative_import_proxy_member" ? "proxy_member_no_global_name_fallback" : "exact_symbol_match",
|
|
404
|
+
candidateCount: 0
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function insertCalls(db, repoId, rows2) {
|
|
408
|
+
const stmt = outboundCallInsertStatement(db);
|
|
409
|
+
for (const row of rows2) insertOutboundCall(db, stmt, repoId, row);
|
|
410
|
+
}
|
|
411
|
+
function outboundCallInsertStatement(db) {
|
|
412
|
+
return db.prepare(`INSERT INTO outbound_calls(
|
|
413
|
+
repo_id,source_symbol_id,call_type,method,operation_path_expr,query_entity,
|
|
414
|
+
event_name_expr,payload_summary,source_file,source_line,call_site_start_offset,
|
|
415
|
+
call_site_end_offset,confidence,unresolved_reason,local_service_name,
|
|
416
|
+
local_service_lookup,alias_chain_json,evidence_json,external_target_kind,
|
|
417
|
+
external_target_id,external_target_label,external_target_dynamic,service_binding_id
|
|
418
|
+
) VALUES(
|
|
419
|
+
?,COALESCE(
|
|
420
|
+
(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1),
|
|
421
|
+
(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND start_line<=? AND end_line>=? ORDER BY (end_line-start_line),id LIMIT 1)
|
|
422
|
+
),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?
|
|
423
|
+
)`);
|
|
424
|
+
}
|
|
425
|
+
function insertOutboundCall(db, stmt, repoId, call) {
|
|
426
|
+
const binding = resolvePersistedBinding(db, repoId, call);
|
|
427
|
+
const external = externalTargetValues(call.externalTarget);
|
|
428
|
+
const evidence = {
|
|
429
|
+
...call.evidence ?? {},
|
|
430
|
+
serviceBindingResolution: binding.evidence
|
|
431
|
+
};
|
|
432
|
+
stmt.run(
|
|
433
|
+
repoId,
|
|
434
|
+
repoId,
|
|
435
|
+
call.sourceFile,
|
|
436
|
+
call.sourceSymbolQualifiedName,
|
|
437
|
+
repoId,
|
|
438
|
+
call.sourceFile,
|
|
439
|
+
call.sourceLine,
|
|
440
|
+
call.sourceLine,
|
|
441
|
+
call.callType,
|
|
442
|
+
call.method,
|
|
443
|
+
call.operationPathExpr,
|
|
444
|
+
call.queryEntity,
|
|
445
|
+
call.eventNameExpr,
|
|
446
|
+
call.payloadSummary,
|
|
447
|
+
call.sourceFile,
|
|
448
|
+
call.sourceLine,
|
|
449
|
+
call.callSiteStartOffset,
|
|
450
|
+
call.callSiteEndOffset,
|
|
451
|
+
call.confidence,
|
|
452
|
+
call.unresolvedReason ?? binding.unresolvedReason,
|
|
453
|
+
call.localServiceName,
|
|
454
|
+
call.localServiceLookup,
|
|
455
|
+
serializedAliasChain(call.aliasChain),
|
|
456
|
+
JSON.stringify(evidence),
|
|
457
|
+
external.kind,
|
|
458
|
+
external.stableId,
|
|
459
|
+
external.label,
|
|
460
|
+
external.dynamic,
|
|
461
|
+
binding.bindingId
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
function serializedAliasChain(aliasChain) {
|
|
465
|
+
return aliasChain ? JSON.stringify(aliasChain) : null;
|
|
466
|
+
}
|
|
467
|
+
function externalTargetValues(target) {
|
|
468
|
+
if (!target) return { kind: null, stableId: null, label: null, dynamic: 0 };
|
|
469
|
+
return {
|
|
470
|
+
kind: target.kind,
|
|
471
|
+
stableId: target.stableId,
|
|
472
|
+
label: target.label,
|
|
473
|
+
dynamic: target.dynamic ? 1 : 0
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
function resolvePersistedBinding(db, repoId, call) {
|
|
477
|
+
if (!call.serviceVariableName)
|
|
478
|
+
return { bindingId: null, evidence: { status: "not_applicable", candidateCount: 0 } };
|
|
479
|
+
const candidates2 = bindingCandidates(db, repoId, call);
|
|
480
|
+
const prior = candidates2.filter((candidate) => candidate.sourceLine <= call.sourceLine);
|
|
481
|
+
const families = new Set(prior.map(bindingSignature));
|
|
482
|
+
if (prior.length > 0 && families.size === 1) {
|
|
483
|
+
const selected = prior.at(-1);
|
|
484
|
+
return {
|
|
485
|
+
bindingId: selected?.id ?? null,
|
|
486
|
+
evidence: bindingEvidence("selected", prior, selected)
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
if (prior.length > 1) {
|
|
490
|
+
return {
|
|
491
|
+
bindingId: null,
|
|
492
|
+
unresolvedReason: "ambiguous_service_binding_candidates",
|
|
493
|
+
evidence: bindingEvidence("ambiguous", prior)
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
if (candidates2.length > 0) {
|
|
497
|
+
return {
|
|
498
|
+
bindingId: null,
|
|
499
|
+
unresolvedReason: "service_binding_declared_after_call",
|
|
500
|
+
evidence: bindingEvidence("rejected_future_binding", candidates2)
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
bindingId: null,
|
|
505
|
+
evidence: bindingEvidence("unrecoverable", [])
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
function bindingCandidates(db, repoId, call) {
|
|
509
|
+
const ownerId = callSymbolId(db, repoId, call);
|
|
510
|
+
const rows2 = db.prepare(`
|
|
511
|
+
SELECT id,symbol_id symbolId,variable_name variableName,alias,alias_expr aliasExpr,
|
|
512
|
+
destination_expr destinationExpr,service_path_expr servicePathExpr,
|
|
513
|
+
source_file sourceFile,source_line sourceLine,helper_chain_json helperChainJson
|
|
514
|
+
FROM service_bindings
|
|
515
|
+
WHERE repo_id=? AND variable_name=? AND source_file=?
|
|
516
|
+
AND (? IS NULL OR symbol_id IS NULL OR symbol_id=?)
|
|
517
|
+
ORDER BY source_line,id
|
|
518
|
+
`).all(
|
|
519
|
+
repoId,
|
|
520
|
+
call.serviceVariableName,
|
|
521
|
+
call.sourceFile,
|
|
522
|
+
ownerId,
|
|
523
|
+
ownerId
|
|
524
|
+
);
|
|
525
|
+
return rows2.flatMap((row) => {
|
|
526
|
+
if (typeof row.id !== "number" || typeof row.variableName !== "string" || typeof row.sourceFile !== "string" || typeof row.sourceLine !== "number")
|
|
527
|
+
return [];
|
|
528
|
+
return [{
|
|
529
|
+
id: row.id,
|
|
530
|
+
symbolId: nullableNumber(row.symbolId),
|
|
531
|
+
variableName: row.variableName,
|
|
532
|
+
alias: nullableString(row.alias),
|
|
533
|
+
aliasExpr: nullableString(row.aliasExpr),
|
|
534
|
+
destinationExpr: nullableString(row.destinationExpr),
|
|
535
|
+
servicePathExpr: nullableString(row.servicePathExpr),
|
|
536
|
+
sourceFile: row.sourceFile,
|
|
537
|
+
sourceLine: row.sourceLine,
|
|
538
|
+
helperChainJson: nullableString(row.helperChainJson)
|
|
539
|
+
}];
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
function nullableString(value) {
|
|
543
|
+
return value === null || typeof value === "string" ? value : void 0;
|
|
544
|
+
}
|
|
545
|
+
function nullableNumber(value) {
|
|
546
|
+
return value === null || typeof value === "number" ? value : void 0;
|
|
547
|
+
}
|
|
548
|
+
function callSymbolId(db, repoId, call) {
|
|
549
|
+
const row = db.prepare(`
|
|
550
|
+
SELECT id FROM symbols
|
|
551
|
+
WHERE repo_id=? AND source_file=?
|
|
552
|
+
AND ((? IS NOT NULL AND qualified_name=?)
|
|
553
|
+
OR (start_line<=? AND end_line>=?))
|
|
554
|
+
ORDER BY CASE WHEN qualified_name=? THEN 0 ELSE 1 END,
|
|
555
|
+
(end_line-start_line),id
|
|
556
|
+
LIMIT 1
|
|
557
|
+
`).get(
|
|
558
|
+
repoId,
|
|
559
|
+
call.sourceFile,
|
|
560
|
+
call.sourceSymbolQualifiedName,
|
|
561
|
+
call.sourceSymbolQualifiedName,
|
|
562
|
+
call.sourceLine,
|
|
563
|
+
call.sourceLine,
|
|
564
|
+
call.sourceSymbolQualifiedName
|
|
565
|
+
);
|
|
566
|
+
return typeof row?.id === "number" ? row.id : void 0;
|
|
567
|
+
}
|
|
568
|
+
function bindingEvidence(status, candidates2, selected) {
|
|
569
|
+
const projection = projectBounded(candidates2, (left, right) => Number(right.id === selected?.id) - Number(left.id === selected?.id) || left.sourceFile.localeCompare(right.sourceFile) || left.sourceLine - right.sourceLine || left.id - right.id);
|
|
570
|
+
return {
|
|
571
|
+
status,
|
|
572
|
+
candidateCount: projection.totalCount,
|
|
573
|
+
shownCandidateCount: projection.shownCount,
|
|
574
|
+
omittedCandidateCount: projection.omittedCount,
|
|
575
|
+
selectedBindingId: selected?.id,
|
|
576
|
+
sourceOrderRule: "binding_source_line_must_not_follow_call",
|
|
577
|
+
candidates: projection.items.map((candidate) => ({
|
|
578
|
+
bindingId: candidate.id,
|
|
579
|
+
symbolId: candidate.symbolId,
|
|
580
|
+
variableName: candidate.variableName,
|
|
581
|
+
alias: candidate.alias,
|
|
582
|
+
aliasExpr: candidate.aliasExpr,
|
|
583
|
+
destinationExpr: candidate.destinationExpr,
|
|
584
|
+
servicePathExpr: candidate.servicePathExpr,
|
|
585
|
+
sourceFile: candidate.sourceFile,
|
|
586
|
+
sourceLine: candidate.sourceLine,
|
|
587
|
+
helperChain: parseBindingChain(candidate.helperChainJson)
|
|
588
|
+
}))
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
function bindingSignature(candidate) {
|
|
592
|
+
return JSON.stringify([
|
|
593
|
+
candidate.alias,
|
|
594
|
+
candidate.aliasExpr,
|
|
595
|
+
candidate.destinationExpr,
|
|
596
|
+
candidate.servicePathExpr
|
|
597
|
+
]);
|
|
598
|
+
}
|
|
599
|
+
function parseBindingChain(value) {
|
|
600
|
+
if (!value) return void 0;
|
|
601
|
+
try {
|
|
602
|
+
return JSON.parse(value);
|
|
603
|
+
} catch {
|
|
604
|
+
return void 0;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
129
608
|
// src/db/repositories.ts
|
|
130
609
|
function upsertWorkspace(db, rootPath, dbPath) {
|
|
131
610
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -384,265 +863,6 @@ function insertExecutableSymbols(db, repoId, rows2) {
|
|
|
384
863
|
const stmt = db.prepare("INSERT INTO symbols(repo_id,file_id,kind,name,qualified_name,exported,start_line,end_line,start_offset,end_offset,source_file,exported_name,evidence_json) VALUES(?,(SELECT id FROM files WHERE repo_id=? AND relative_path=?),?,?,?,?,?,?,?,?,?,?,?)");
|
|
385
864
|
for (const r of rows2) stmt.run(repoId, repoId, r.sourceFile, r.kind, r.localName, r.qualifiedName, r.exported ? 1 : 0, r.startLine, r.endLine, r.startOffset, r.endOffset, r.sourceFile, r.exportedName, r.importExportEvidence ? JSON.stringify(r.importExportEvidence) : null);
|
|
386
865
|
}
|
|
387
|
-
function insertSymbolCalls(db, repoId, rows2) {
|
|
388
|
-
const callerStmt = db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1");
|
|
389
|
-
const insertStmt = db.prepare("INSERT INTO symbol_calls(repo_id,caller_symbol_id,callee_symbol_id,callee_expression,import_source,source_file,source_line,status,confidence,evidence_json,unresolved_reason) VALUES(?,?,?,?,?,?,?,?,?,?,?)");
|
|
390
|
-
for (const r of rows2) {
|
|
391
|
-
const caller = callerStmt.get(repoId, r.sourceFile, r.callerQualifiedName);
|
|
392
|
-
const target = resolveSymbolCallTarget(db, repoId, r);
|
|
393
|
-
insertStmt.run(repoId, caller?.id, target.id, r.calleeExpression, r.importSource, r.sourceFile, r.sourceLine, target.status, 0.8, JSON.stringify({ ...r.evidence, candidateStrategy: target.strategy, candidateCount: target.candidateCount, resolvedModulePath: target.resolvedModulePath }), target.reason);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
var stripExt = (value) => value.replace(/\.(ts|tsx|js|jsx|cds)$/, "");
|
|
397
|
-
function symbolTargetRows(rows2) {
|
|
398
|
-
return rows2.flatMap((row) => typeof row.id === "number" ? [{ id: row.id, kind: typeof row.kind === "string" ? row.kind : void 0, sourceFile: nullableString(row.sourceFile), evidenceJson: nullableString(row.evidenceJson) }] : []);
|
|
399
|
-
}
|
|
400
|
-
function relativeModuleTargets(callerSourceFile, importSource) {
|
|
401
|
-
const base = posix.dirname(callerSourceFile);
|
|
402
|
-
const joined = stripExt(posix.normalize(posix.join(base, importSource)));
|
|
403
|
-
return /* @__PURE__ */ new Set([joined, `${joined}/index`]);
|
|
404
|
-
}
|
|
405
|
-
function moduleRows(rows2, r) {
|
|
406
|
-
if (!r.importSource) return [];
|
|
407
|
-
const targets = relativeModuleTargets(r.sourceFile, r.importSource);
|
|
408
|
-
return rows2.filter((row) => typeof row.sourceFile === "string" && targets.has(stripExt(row.sourceFile)));
|
|
409
|
-
}
|
|
410
|
-
function resolvedSymbol(row, strategy, candidateCount, moduleScoped = false) {
|
|
411
|
-
return { id: row.id, status: "resolved", reason: null, strategy, candidateCount, resolvedModulePath: moduleScoped && row.sourceFile ? stripExt(row.sourceFile) : void 0 };
|
|
412
|
-
}
|
|
413
|
-
function exportedSymbolRows(db, repoId, r) {
|
|
414
|
-
return symbolTargetRows(db.prepare("SELECT id,kind,source_file sourceFile,evidence_json evidenceJson FROM symbols WHERE repo_id=? AND source_file<>? AND exported=1 AND (exported_name=? OR name=? OR qualified_name=?) ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName, r.calleeLocalName, r.calleeLocalName));
|
|
415
|
-
}
|
|
416
|
-
function isRelativeImportedSymbolCall(r) {
|
|
417
|
-
return Boolean(r.importSource?.startsWith("."));
|
|
418
|
-
}
|
|
419
|
-
function sameFileResolution(db, repoId, r, relation) {
|
|
420
|
-
const bareImport = relation === "relative_import" && isRelativeImportedSymbolCall(r) && !String(r.calleeLocalName).includes(".");
|
|
421
|
-
if (bareImport || relation === "relative_import_namespace_member" || relation === "package_import") return void 0;
|
|
422
|
-
const rows2 = symbolTargetRows(db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND (name=? OR qualified_name=?) ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName, r.calleeLocalName));
|
|
423
|
-
if (rows2.length === 1 && rows2[0]) return resolvedSymbol(rows2[0], "same_file_exact", 1);
|
|
424
|
-
return rows2.length > 1 ? { id: null, status: "ambiguous", reason: "Multiple same-file symbol targets matched exactly", strategy: "same_file_exact", candidateCount: rows2.length } : void 0;
|
|
425
|
-
}
|
|
426
|
-
function classInstanceResolution(db, repoId, r, relation) {
|
|
427
|
-
if (relation !== "class_instance_method" || !isRelativeImportedSymbolCall(r)) return void 0;
|
|
428
|
-
const rows2 = symbolTargetRows(db.prepare("SELECT id FROM symbols WHERE repo_id=? AND source_file<>? AND qualified_name=? ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName));
|
|
429
|
-
if (rows2.length === 1 && rows2[0]) return resolvedSymbol(rows2[0], "relative_import_class_instance_method", 1);
|
|
430
|
-
return rows2.length > 1 ? { id: null, status: "ambiguous", reason: "Multiple relative class instance method targets matched exactly", strategy: "relative_import_class_instance_method", candidateCount: rows2.length } : void 0;
|
|
431
|
-
}
|
|
432
|
-
function namespaceResolution(db, repoId, r, relation) {
|
|
433
|
-
if (relation !== "relative_import_namespace_member" || !isRelativeImportedSymbolCall(r)) return void 0;
|
|
434
|
-
const rows2 = moduleRows(exportedSymbolRows(db, repoId, r), r);
|
|
435
|
-
if (rows2.length === 1 && rows2[0]) return resolvedSymbol(rows2[0], "relative_import_namespace_member", 1, true);
|
|
436
|
-
if (rows2.length > 1) return { id: null, status: "ambiguous", reason: "Multiple namespace member targets matched the imported module", strategy: "relative_import_namespace_member", candidateCount: rows2.length };
|
|
437
|
-
return { id: null, status: "unresolved", reason: "No namespace member target matched the imported module", strategy: "relative_import_namespace_member", candidateCount: 0 };
|
|
438
|
-
}
|
|
439
|
-
function proxyResolution(rows2, r, relation) {
|
|
440
|
-
if (relation !== "relative_import_proxy_member" || rows2.length <= 1) return void 0;
|
|
441
|
-
const mapped = rows2.filter((row) => String(row.evidenceJson ?? "").includes("exported_object_shorthand") || String(row.evidenceJson ?? "").includes("exported_object_literal"));
|
|
442
|
-
if (mapped.length > 0) {
|
|
443
|
-
const concrete = rows2.find((row) => row.kind !== "object_alias") ?? mapped[0];
|
|
444
|
-
return { id: concrete?.id ?? null, status: "resolved", reason: null, strategy: "proxy_member_exported_object_map", candidateCount: rows2.length };
|
|
445
|
-
}
|
|
446
|
-
const scoped = moduleRows(rows2, r);
|
|
447
|
-
if (scoped.length === 1 && scoped[0]) return resolvedSymbol(scoped[0], "relative_import_path_disambiguated", rows2.length, true);
|
|
448
|
-
return { id: null, status: "ambiguous", reason: "Proxy member target requires explicit factory/module/type evidence; global member name is ambiguous", strategy: "proxy_member_no_global_name_fallback", candidateCount: rows2.length };
|
|
449
|
-
}
|
|
450
|
-
function exportedResolution(rows2, r, relation) {
|
|
451
|
-
if (rows2.length === 1 && rows2[0]) return resolvedSymbol(rows2[0], relation === "relative_import_proxy_member" ? "proxy_member_unique_exported_candidate" : "relative_import_exported_exact", 1, moduleRows(rows2, r).length === 1);
|
|
452
|
-
if (rows2.length <= 1) return void 0;
|
|
453
|
-
const scoped = isRelativeImportedSymbolCall(r) ? moduleRows(rows2, r) : [];
|
|
454
|
-
if (scoped.length === 1 && scoped[0]) return resolvedSymbol(scoped[0], "relative_import_path_disambiguated", rows2.length, true);
|
|
455
|
-
return { id: null, status: "ambiguous", reason: "Multiple exported symbol targets matched exactly", strategy: "exported_exact", candidateCount: rows2.length };
|
|
456
|
-
}
|
|
457
|
-
function accessorResolution(db, repoId, r, relation) {
|
|
458
|
-
if (relation !== "relative_import" || !isRelativeImportedSymbolCall(r) || !/^[^.]+\.[^.]+$/.test(String(r.calleeLocalName))) return void 0;
|
|
459
|
-
const methodRows = symbolTargetRows(db.prepare("SELECT id,kind,source_file sourceFile FROM symbols WHERE repo_id=? AND source_file<>? AND kind='method' AND qualified_name=? ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName));
|
|
460
|
-
const scoped = moduleRows(methodRows, r);
|
|
461
|
-
if (scoped.length === 1 && scoped[0]) return resolvedSymbol(scoped[0], "relative_import_static_accessor_instance_method", 1, true);
|
|
462
|
-
return scoped.length > 1 ? { id: null, status: "ambiguous", reason: "Multiple static-accessor instance method targets matched the imported module", strategy: "relative_import_static_accessor_instance_method", candidateCount: scoped.length } : void 0;
|
|
463
|
-
}
|
|
464
|
-
function resolveSymbolCallTarget(db, repoId, r) {
|
|
465
|
-
const relation = r.evidence.relation;
|
|
466
|
-
const early = sameFileResolution(db, repoId, r, relation) ?? classInstanceResolution(db, repoId, r, relation) ?? namespaceResolution(db, repoId, r, relation);
|
|
467
|
-
if (early) return early;
|
|
468
|
-
const rows2 = relation === "package_import" ? [] : exportedSymbolRows(db, repoId, r);
|
|
469
|
-
const matched = proxyResolution(rows2, r, relation) ?? exportedResolution(rows2, r, relation) ?? accessorResolution(db, repoId, r, relation);
|
|
470
|
-
if (matched) return matched;
|
|
471
|
-
if (relation === "package_import") return { id: null, status: "unresolved", reason: "Package import target resolution requires a post-publication workspace pass", strategy: "package_import_unresolved", candidateCount: 0 };
|
|
472
|
-
return { id: null, status: "unresolved", reason: "No local symbol target matched exactly", strategy: relation === "relative_import_proxy_member" ? "proxy_member_no_global_name_fallback" : "exact_symbol_match", candidateCount: 0 };
|
|
473
|
-
}
|
|
474
|
-
function insertCalls(db, repoId, rows2) {
|
|
475
|
-
const stmt = db.prepare(
|
|
476
|
-
"INSERT INTO outbound_calls(repo_id,source_symbol_id,call_type,method,operation_path_expr,query_entity,event_name_expr,payload_summary,source_file,source_line,confidence,unresolved_reason,local_service_name,local_service_lookup,alias_chain_json,evidence_json,external_target_kind,external_target_id,external_target_label,external_target_dynamic,service_binding_id) VALUES(?,COALESCE((SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1),(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND start_line<=? AND end_line>=? ORDER BY (end_line-start_line),id LIMIT 1)),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
|
|
477
|
-
);
|
|
478
|
-
for (const r of rows2) {
|
|
479
|
-
const binding = resolvePersistedBinding(db, repoId, r);
|
|
480
|
-
const evidence = {
|
|
481
|
-
...r.evidence ?? {},
|
|
482
|
-
serviceBindingResolution: binding.evidence
|
|
483
|
-
};
|
|
484
|
-
stmt.run(
|
|
485
|
-
repoId,
|
|
486
|
-
repoId,
|
|
487
|
-
r.sourceFile,
|
|
488
|
-
r.sourceSymbolQualifiedName,
|
|
489
|
-
repoId,
|
|
490
|
-
r.sourceFile,
|
|
491
|
-
r.sourceLine,
|
|
492
|
-
r.sourceLine,
|
|
493
|
-
r.callType,
|
|
494
|
-
r.method,
|
|
495
|
-
r.operationPathExpr,
|
|
496
|
-
r.queryEntity,
|
|
497
|
-
r.eventNameExpr,
|
|
498
|
-
r.payloadSummary,
|
|
499
|
-
r.sourceFile,
|
|
500
|
-
r.sourceLine,
|
|
501
|
-
r.confidence,
|
|
502
|
-
r.unresolvedReason ?? binding.unresolvedReason,
|
|
503
|
-
r.localServiceName,
|
|
504
|
-
r.localServiceLookup,
|
|
505
|
-
r.aliasChain ? JSON.stringify(r.aliasChain) : null,
|
|
506
|
-
JSON.stringify(evidence),
|
|
507
|
-
r.externalTarget?.kind ?? null,
|
|
508
|
-
r.externalTarget?.stableId ?? null,
|
|
509
|
-
r.externalTarget?.label ?? null,
|
|
510
|
-
r.externalTarget?.dynamic ? 1 : 0,
|
|
511
|
-
binding.bindingId
|
|
512
|
-
);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
function resolvePersistedBinding(db, repoId, call) {
|
|
516
|
-
if (!call.serviceVariableName)
|
|
517
|
-
return { bindingId: null, evidence: { status: "not_applicable", candidateCount: 0 } };
|
|
518
|
-
const candidates2 = bindingCandidates(db, repoId, call);
|
|
519
|
-
const prior = candidates2.filter((candidate) => candidate.sourceLine <= call.sourceLine);
|
|
520
|
-
const families = new Set(prior.map(bindingSignature));
|
|
521
|
-
if (prior.length > 0 && families.size === 1) {
|
|
522
|
-
const selected = prior.at(-1);
|
|
523
|
-
return {
|
|
524
|
-
bindingId: selected?.id ?? null,
|
|
525
|
-
evidence: bindingEvidence("selected", prior, selected)
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
if (prior.length > 1) {
|
|
529
|
-
return {
|
|
530
|
-
bindingId: null,
|
|
531
|
-
unresolvedReason: "ambiguous_service_binding_candidates",
|
|
532
|
-
evidence: bindingEvidence("ambiguous", prior)
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
if (candidates2.length > 0) {
|
|
536
|
-
return {
|
|
537
|
-
bindingId: null,
|
|
538
|
-
unresolvedReason: "service_binding_declared_after_call",
|
|
539
|
-
evidence: bindingEvidence("rejected_future_binding", candidates2)
|
|
540
|
-
};
|
|
541
|
-
}
|
|
542
|
-
return {
|
|
543
|
-
bindingId: null,
|
|
544
|
-
evidence: bindingEvidence("unrecoverable", [])
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
function bindingCandidates(db, repoId, call) {
|
|
548
|
-
const ownerId = callSymbolId(db, repoId, call);
|
|
549
|
-
const rows2 = db.prepare(`
|
|
550
|
-
SELECT id,symbol_id symbolId,variable_name variableName,alias,alias_expr aliasExpr,
|
|
551
|
-
destination_expr destinationExpr,service_path_expr servicePathExpr,
|
|
552
|
-
source_file sourceFile,source_line sourceLine,helper_chain_json helperChainJson
|
|
553
|
-
FROM service_bindings
|
|
554
|
-
WHERE repo_id=? AND variable_name=? AND source_file=?
|
|
555
|
-
AND (? IS NULL OR symbol_id IS NULL OR symbol_id=?)
|
|
556
|
-
ORDER BY source_line,id
|
|
557
|
-
`).all(
|
|
558
|
-
repoId,
|
|
559
|
-
call.serviceVariableName,
|
|
560
|
-
call.sourceFile,
|
|
561
|
-
ownerId,
|
|
562
|
-
ownerId
|
|
563
|
-
);
|
|
564
|
-
return rows2.flatMap((row) => {
|
|
565
|
-
if (typeof row.id !== "number" || typeof row.variableName !== "string" || typeof row.sourceFile !== "string" || typeof row.sourceLine !== "number")
|
|
566
|
-
return [];
|
|
567
|
-
return [{
|
|
568
|
-
id: row.id,
|
|
569
|
-
symbolId: nullableNumber(row.symbolId),
|
|
570
|
-
variableName: row.variableName,
|
|
571
|
-
alias: nullableString(row.alias),
|
|
572
|
-
aliasExpr: nullableString(row.aliasExpr),
|
|
573
|
-
destinationExpr: nullableString(row.destinationExpr),
|
|
574
|
-
servicePathExpr: nullableString(row.servicePathExpr),
|
|
575
|
-
sourceFile: row.sourceFile,
|
|
576
|
-
sourceLine: row.sourceLine,
|
|
577
|
-
helperChainJson: nullableString(row.helperChainJson)
|
|
578
|
-
}];
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
function nullableString(value) {
|
|
582
|
-
return value === null || typeof value === "string" ? value : void 0;
|
|
583
|
-
}
|
|
584
|
-
function nullableNumber(value) {
|
|
585
|
-
return value === null || typeof value === "number" ? value : void 0;
|
|
586
|
-
}
|
|
587
|
-
function callSymbolId(db, repoId, call) {
|
|
588
|
-
const row = db.prepare(`
|
|
589
|
-
SELECT id FROM symbols
|
|
590
|
-
WHERE repo_id=? AND source_file=?
|
|
591
|
-
AND ((? IS NOT NULL AND qualified_name=?)
|
|
592
|
-
OR (start_line<=? AND end_line>=?))
|
|
593
|
-
ORDER BY CASE WHEN qualified_name=? THEN 0 ELSE 1 END,
|
|
594
|
-
(end_line-start_line),id
|
|
595
|
-
LIMIT 1
|
|
596
|
-
`).get(
|
|
597
|
-
repoId,
|
|
598
|
-
call.sourceFile,
|
|
599
|
-
call.sourceSymbolQualifiedName,
|
|
600
|
-
call.sourceSymbolQualifiedName,
|
|
601
|
-
call.sourceLine,
|
|
602
|
-
call.sourceLine,
|
|
603
|
-
call.sourceSymbolQualifiedName
|
|
604
|
-
);
|
|
605
|
-
return typeof row?.id === "number" ? row.id : void 0;
|
|
606
|
-
}
|
|
607
|
-
function bindingEvidence(status, candidates2, selected) {
|
|
608
|
-
const projection = projectBounded(candidates2, (left, right) => Number(right.id === selected?.id) - Number(left.id === selected?.id) || left.sourceFile.localeCompare(right.sourceFile) || left.sourceLine - right.sourceLine || left.id - right.id);
|
|
609
|
-
return {
|
|
610
|
-
status,
|
|
611
|
-
candidateCount: projection.totalCount,
|
|
612
|
-
shownCandidateCount: projection.shownCount,
|
|
613
|
-
omittedCandidateCount: projection.omittedCount,
|
|
614
|
-
selectedBindingId: selected?.id,
|
|
615
|
-
sourceOrderRule: "binding_source_line_must_not_follow_call",
|
|
616
|
-
candidates: projection.items.map((candidate) => ({
|
|
617
|
-
bindingId: candidate.id,
|
|
618
|
-
symbolId: candidate.symbolId,
|
|
619
|
-
variableName: candidate.variableName,
|
|
620
|
-
alias: candidate.alias,
|
|
621
|
-
aliasExpr: candidate.aliasExpr,
|
|
622
|
-
destinationExpr: candidate.destinationExpr,
|
|
623
|
-
servicePathExpr: candidate.servicePathExpr,
|
|
624
|
-
sourceFile: candidate.sourceFile,
|
|
625
|
-
sourceLine: candidate.sourceLine,
|
|
626
|
-
helperChain: parseBindingChain(candidate.helperChainJson)
|
|
627
|
-
}))
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
function bindingSignature(candidate) {
|
|
631
|
-
return JSON.stringify([
|
|
632
|
-
candidate.alias,
|
|
633
|
-
candidate.aliasExpr,
|
|
634
|
-
candidate.destinationExpr,
|
|
635
|
-
candidate.servicePathExpr
|
|
636
|
-
]);
|
|
637
|
-
}
|
|
638
|
-
function parseBindingChain(value) {
|
|
639
|
-
if (!value) return void 0;
|
|
640
|
-
try {
|
|
641
|
-
return JSON.parse(value);
|
|
642
|
-
} catch {
|
|
643
|
-
return void 0;
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
866
|
|
|
647
867
|
// src/discovery/discover-repositories.ts
|
|
648
868
|
import fs from "fs/promises";
|
|
@@ -3059,10 +3279,14 @@ function wrapperCallFact(source, filePath, call, spec, serviceBindings) {
|
|
|
3059
3279
|
payloadSummary: call.getText(source),
|
|
3060
3280
|
sourceFile: normalizePath(filePath),
|
|
3061
3281
|
sourceLine: lineOf3(source, call),
|
|
3282
|
+
callSiteStartOffset: call.getStart(source),
|
|
3283
|
+
callSiteEndOffset: call.getEnd(),
|
|
3062
3284
|
confidence: operationPathExpr ? 0.85 : 0.5,
|
|
3063
3285
|
unresolvedReason,
|
|
3064
3286
|
evidence: {
|
|
3065
3287
|
parser: "typescript_ast",
|
|
3288
|
+
startOffset: call.getStart(source),
|
|
3289
|
+
endOffset: call.getEnd(),
|
|
3066
3290
|
classifier: importedWrapperClassifier(pathAnalysis.status),
|
|
3067
3291
|
receiver: client.text,
|
|
3068
3292
|
wrapperFunction: spec.chain[0],
|
|
@@ -4012,7 +4236,15 @@ function classifyOutboundCallsInSource(source, filePath) {
|
|
|
4012
4236
|
const wrapperSpecs = collectWrapperSpecs(source);
|
|
4013
4237
|
const wrapperInternalRanges = [...wrapperSpecs.values()].map((spec) => ({ start: spec.internalStart, end: spec.internalEnd }));
|
|
4014
4238
|
const add = (node, fact, extra) => {
|
|
4015
|
-
calls.push({ node, fact: {
|
|
4239
|
+
calls.push({ node, fact: {
|
|
4240
|
+
...fact,
|
|
4241
|
+
sourceFile,
|
|
4242
|
+
sourceLine: lineOf4(source.text, node.getStart(source)),
|
|
4243
|
+
callSiteStartOffset: node.getStart(source),
|
|
4244
|
+
callSiteEndOffset: node.getEnd(),
|
|
4245
|
+
confidence: fact.confidence ?? 0.8,
|
|
4246
|
+
evidence: parserEvidence(source, node, extra)
|
|
4247
|
+
} });
|
|
4016
4248
|
};
|
|
4017
4249
|
const visit = (node) => {
|
|
4018
4250
|
if (ts10.isCallExpression(node)) {
|
|
@@ -4167,6 +4399,8 @@ function parseLocalServiceCalls(text, filePath, source = ts10.createSourceFile(
|
|
|
4167
4399
|
aliasChain: parsed.chain,
|
|
4168
4400
|
sourceFile: normalizePath(filePath),
|
|
4169
4401
|
sourceLine: lineOf4(text, node.getStart(source)),
|
|
4402
|
+
callSiteStartOffset: node.getStart(source),
|
|
4403
|
+
callSiteEndOffset: node.getEnd(),
|
|
4170
4404
|
confidence: 0.9,
|
|
4171
4405
|
unresolvedReason: ["send", "emit", "publish", "on"].includes(parsed.operation) ? "transport_client_method" : void 0,
|
|
4172
4406
|
evidence: parserEvidence(source, node, {
|
|
@@ -5757,51 +5991,516 @@ function linkPackageImportSymbolCalls(db, workspaceId) {
|
|
|
5757
5991
|
return summary;
|
|
5758
5992
|
}
|
|
5759
5993
|
|
|
5760
|
-
// src/linker/
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5994
|
+
// src/linker/004-event-subscription-handler-linker.ts
|
|
5995
|
+
var symbolCallReasonLimit = 512;
|
|
5996
|
+
function subscriptionRows(db, workspaceId) {
|
|
5997
|
+
return db.prepare(`SELECT c.id,r.workspace_id workspaceId,c.repo_id repoId,r.name repoName,
|
|
5998
|
+
c.source_symbol_id sourceSymbolId,c.event_name_expr eventName,
|
|
5999
|
+
c.source_file sourceFile,c.source_line sourceLine,
|
|
6000
|
+
c.call_site_start_offset startOffset,c.call_site_end_offset endOffset,
|
|
6001
|
+
c.confidence
|
|
6002
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
6003
|
+
WHERE r.workspace_id=? AND c.call_type='async_subscribe'
|
|
6004
|
+
ORDER BY r.name COLLATE BINARY,r.id,c.source_file COLLATE BINARY,
|
|
6005
|
+
c.call_site_start_offset,c.call_site_end_offset,c.id`).all(
|
|
6006
|
+
workspaceId
|
|
6007
|
+
);
|
|
5772
6008
|
}
|
|
5773
|
-
function
|
|
5774
|
-
|
|
5775
|
-
return
|
|
6009
|
+
function roleSiteRows(db, subscription) {
|
|
6010
|
+
if (typeof subscription.startOffset !== "number" || typeof subscription.endOffset !== "number") return [];
|
|
6011
|
+
return db.prepare(`SELECT sc.id,sc.caller_symbol_id callerSymbolId,
|
|
6012
|
+
sc.callee_symbol_id calleeSymbolId,sc.status,
|
|
6013
|
+
sc.unresolved_reason unresolvedReason,sc.confidence,sc.source_line sourceLine,
|
|
6014
|
+
json_extract(sc.evidence_json,'$.factOrigin') factOrigin,
|
|
6015
|
+
json_extract(sc.evidence_json,'$.wrapperFunction') wrapperFunction,
|
|
6016
|
+
json_extract(sc.evidence_json,'$.candidateStrategy') strategy,
|
|
6017
|
+
json_extract(sc.evidence_json,'$.candidateCount') candidateCount,
|
|
6018
|
+
target.source_file targetSourceFile,target.start_line targetSourceLine,
|
|
6019
|
+
target_repo.workspace_id targetWorkspaceId
|
|
6020
|
+
FROM symbol_calls sc LEFT JOIN symbols target ON target.id=sc.callee_symbol_id
|
|
6021
|
+
LEFT JOIN repositories target_repo ON target_repo.id=target.repo_id
|
|
6022
|
+
WHERE sc.repo_id=? AND sc.source_file=?
|
|
6023
|
+
AND sc.call_site_start_offset=? AND sc.call_site_end_offset=?
|
|
6024
|
+
AND sc.call_role='event_subscribe_handler'
|
|
6025
|
+
ORDER BY sc.id`).all(
|
|
6026
|
+
subscription.repoId,
|
|
6027
|
+
subscription.sourceFile,
|
|
6028
|
+
subscription.startOffset,
|
|
6029
|
+
subscription.endOffset
|
|
6030
|
+
);
|
|
5776
6031
|
}
|
|
5777
|
-
function
|
|
5778
|
-
|
|
5779
|
-
let unresolvedCount = 0;
|
|
5780
|
-
let resolvedCount = 0;
|
|
5781
|
-
let remoteResolvedCount = 0;
|
|
5782
|
-
let localResolvedCount = 0;
|
|
5783
|
-
let ambiguousCount = 0;
|
|
5784
|
-
let dynamicCount = 0;
|
|
5785
|
-
let terminalCount = 0;
|
|
5786
|
-
const calls = db.prepare(`SELECT c.*,r.name repoName,b.id selectedBindingId,b.alias,b.alias_expr aliasExpr,b.destination_expr destinationExpr,b.service_path_expr servicePathExpr,b.is_dynamic isDynamic,b.placeholders_json placeholdersJson,b.source_file bindingSourceFile,b.source_line bindingSourceLine,b.helper_chain_json helperChainJson,req.service_path requireServicePath,req.destination requireDestination FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id LEFT JOIN service_bindings b ON b.id=c.service_binding_id LEFT JOIN cds_requires req ON req.repo_id=c.repo_id AND req.alias=b.alias WHERE r.workspace_id=?`).all(workspaceId);
|
|
5787
|
-
for (const call of calls) {
|
|
5788
|
-
const result = insertCallEdge(db, workspaceId, call, vars, generation);
|
|
5789
|
-
edgeCount += 1;
|
|
5790
|
-
resolvedCount += result.status === "resolved" ? 1 : 0;
|
|
5791
|
-
remoteResolvedCount += result.status === "resolved" && result.callType !== "local_service_call" ? 1 : 0;
|
|
5792
|
-
localResolvedCount += result.status === "resolved" && result.callType === "local_service_call" ? 1 : 0;
|
|
5793
|
-
unresolvedCount += result.status === "unresolved" ? 1 : 0;
|
|
5794
|
-
ambiguousCount += result.status === "ambiguous" ? 1 : 0;
|
|
5795
|
-
dynamicCount += result.status === "dynamic" ? 1 : 0;
|
|
5796
|
-
terminalCount += result.status === "terminal" ? 1 : 0;
|
|
5797
|
-
}
|
|
5798
|
-
return { edgeCount, unresolvedCount, resolvedCount, remoteResolvedCount, localResolvedCount, ambiguousCount, dynamicCount, terminalCount };
|
|
6032
|
+
function invalidSpan(subscription) {
|
|
6033
|
+
return typeof subscription.startOffset !== "number" || typeof subscription.endOffset !== "number" || subscription.startOffset < 0 || subscription.endOffset <= subscription.startOffset;
|
|
5799
6034
|
}
|
|
5800
|
-
function
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
6035
|
+
function associationFor(subscription, matches2) {
|
|
6036
|
+
if (invalidSpan(subscription)) return missingAssociation(
|
|
6037
|
+
subscription.id,
|
|
6038
|
+
matches2.length,
|
|
6039
|
+
"subscription_call_span_missing"
|
|
6040
|
+
);
|
|
6041
|
+
if (matches2.length === 0) return missingAssociation(
|
|
6042
|
+
subscription.id,
|
|
6043
|
+
0,
|
|
6044
|
+
"subscription_handler_role_site_missing"
|
|
6045
|
+
);
|
|
6046
|
+
if (matches2.length > 1) return ambiguousRoleSiteAssociation(
|
|
6047
|
+
subscription.id,
|
|
6048
|
+
matches2
|
|
6049
|
+
);
|
|
6050
|
+
const call = matches2[0];
|
|
6051
|
+
return call ? singleCallAssociation(subscription, call) : missingAssociation(
|
|
6052
|
+
subscription.id,
|
|
6053
|
+
0,
|
|
6054
|
+
"subscription_handler_role_site_missing"
|
|
6055
|
+
);
|
|
6056
|
+
}
|
|
6057
|
+
function ambiguousRoleSiteAssociation(subscriptionId, matches2) {
|
|
6058
|
+
return {
|
|
6059
|
+
status: "ambiguous",
|
|
6060
|
+
toKind: "subscription_handler",
|
|
6061
|
+
toId: String(subscriptionId),
|
|
6062
|
+
reasonCode: "multiple_handler_role_site_matches",
|
|
6063
|
+
matchCount: matches2.length,
|
|
6064
|
+
missing: false,
|
|
6065
|
+
factOrigin: agreedOrMixed(matches2.map((match) => match.factOrigin)),
|
|
6066
|
+
symbolCallResolutionStatus: agreedOrMixed(
|
|
6067
|
+
matches2.map((match) => match.status)
|
|
6068
|
+
)
|
|
6069
|
+
};
|
|
6070
|
+
}
|
|
6071
|
+
function singleCallAssociation(subscription, call) {
|
|
6072
|
+
const mismatch2 = associationMismatch(subscription, call);
|
|
6073
|
+
if (mismatch2) return missingAssociation(subscription.id, 1, mismatch2, call);
|
|
6074
|
+
return handlerReferenceAssociation(call);
|
|
6075
|
+
}
|
|
6076
|
+
function associationMismatch(subscription, call) {
|
|
6077
|
+
if (subscription.sourceSymbolId != null && call.callerSymbolId !== subscription.sourceSymbolId)
|
|
6078
|
+
return "subscription_handler_caller_mismatch";
|
|
6079
|
+
if (call.sourceLine !== subscription.sourceLine)
|
|
6080
|
+
return "subscription_handler_source_line_mismatch";
|
|
6081
|
+
if (call.targetWorkspaceId != null && call.targetWorkspaceId !== subscription.workspaceId)
|
|
6082
|
+
return "subscription_handler_target_workspace_mismatch";
|
|
6083
|
+
return void 0;
|
|
6084
|
+
}
|
|
6085
|
+
function handlerReferenceAssociation(call) {
|
|
6086
|
+
if (call.status === "resolved" && typeof call.calleeSymbolId === "number")
|
|
6087
|
+
return { status: "resolved", toKind: "symbol", toId: String(call.calleeSymbolId), call, matchCount: 1, missing: false };
|
|
6088
|
+
if (call.status === "ambiguous") return {
|
|
6089
|
+
status: "ambiguous",
|
|
6090
|
+
toKind: "symbol_reference",
|
|
6091
|
+
toId: String(call.id),
|
|
6092
|
+
reasonCode: "subscription_handler_reference_ambiguous",
|
|
6093
|
+
call,
|
|
6094
|
+
matchCount: 1,
|
|
6095
|
+
missing: false
|
|
6096
|
+
};
|
|
6097
|
+
return {
|
|
6098
|
+
status: "unresolved",
|
|
6099
|
+
toKind: "symbol_reference",
|
|
6100
|
+
toId: String(call.id),
|
|
6101
|
+
reasonCode: call.status === "resolved" ? "resolved_handler_symbol_missing" : "subscription_handler_reference_unresolved",
|
|
6102
|
+
call,
|
|
6103
|
+
matchCount: 1,
|
|
6104
|
+
missing: false
|
|
6105
|
+
};
|
|
6106
|
+
}
|
|
6107
|
+
function agreedOrMixed(values) {
|
|
6108
|
+
const distinct = new Set(values.map((value2) => value2 ?? "missing"));
|
|
6109
|
+
if (distinct.size > 1) return "mixed";
|
|
6110
|
+
const value = distinct.values().next().value;
|
|
6111
|
+
return value === "missing" ? void 0 : value;
|
|
6112
|
+
}
|
|
6113
|
+
function missingAssociation(subscriptionId, matchCount, reasonCode, call) {
|
|
6114
|
+
return {
|
|
6115
|
+
status: "unresolved",
|
|
6116
|
+
toKind: "subscription_handler",
|
|
6117
|
+
toId: String(subscriptionId),
|
|
6118
|
+
reasonCode,
|
|
6119
|
+
call,
|
|
6120
|
+
matchCount,
|
|
6121
|
+
missing: true
|
|
6122
|
+
};
|
|
6123
|
+
}
|
|
6124
|
+
function evidenceFor(subscription, association) {
|
|
6125
|
+
const call = association.call ?? {};
|
|
6126
|
+
const symbolCallReason = boundedSymbolCallReason(call.unresolvedReason);
|
|
6127
|
+
return {
|
|
6128
|
+
eventName: subscription.eventName,
|
|
6129
|
+
associationBasis: "exact_subscription_call_span",
|
|
6130
|
+
dispatchScope: "workspace_event_name_only",
|
|
6131
|
+
subscribeCallId: subscription.id,
|
|
6132
|
+
symbolCallId: call.id,
|
|
6133
|
+
roleSiteMatchCount: association.matchCount,
|
|
6134
|
+
callRole: association.matchCount > 0 ? "event_subscribe_handler" : void 0,
|
|
6135
|
+
factOrigin: association.factOrigin ?? call.factOrigin,
|
|
6136
|
+
repositoryId: subscription.repoId,
|
|
6137
|
+
repositoryName: subscription.repoName,
|
|
6138
|
+
sourceFile: subscription.sourceFile,
|
|
6139
|
+
sourceLine: subscription.sourceLine,
|
|
6140
|
+
callSiteStartOffset: subscription.startOffset,
|
|
6141
|
+
callSiteEndOffset: subscription.endOffset,
|
|
6142
|
+
handlerSymbolId: call.calleeSymbolId,
|
|
6143
|
+
handlerSourceFile: call.targetSourceFile,
|
|
6144
|
+
handlerSourceLine: call.targetSourceLine,
|
|
6145
|
+
wrapperFunction: call.wrapperFunction,
|
|
6146
|
+
associationStatus: association.status,
|
|
6147
|
+
symbolCallResolutionStatus: association.symbolCallResolutionStatus ?? call.status,
|
|
6148
|
+
resolutionStatus: association.status,
|
|
6149
|
+
resolutionStrategy: call.strategy,
|
|
6150
|
+
candidateCount: call.candidateCount,
|
|
6151
|
+
reasonCode: association.reasonCode,
|
|
6152
|
+
...symbolCallReason
|
|
6153
|
+
};
|
|
6154
|
+
}
|
|
6155
|
+
function boundedSymbolCallReason(reason) {
|
|
6156
|
+
if (!reason) return {};
|
|
6157
|
+
const value = reason.slice(0, symbolCallReasonLimit);
|
|
6158
|
+
return {
|
|
6159
|
+
symbolCallUnresolvedReason: value,
|
|
6160
|
+
omittedSymbolCallUnresolvedReasonCharacterCount: Math.max(0, reason.length - value.length)
|
|
6161
|
+
};
|
|
6162
|
+
}
|
|
6163
|
+
function insertAssociationEdge(db, workspaceId, generation, subscription, association) {
|
|
6164
|
+
db.prepare(`INSERT INTO graph_edges(
|
|
6165
|
+
workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,
|
|
6166
|
+
confidence,evidence_json,is_dynamic,unresolved_reason,generation
|
|
6167
|
+
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)`).run(
|
|
6168
|
+
workspaceId,
|
|
6169
|
+
"EVENT_SUBSCRIPTION_HANDLED_BY",
|
|
6170
|
+
association.status,
|
|
6171
|
+
"event",
|
|
6172
|
+
subscription.eventName,
|
|
6173
|
+
association.toKind,
|
|
6174
|
+
association.toId,
|
|
6175
|
+
association.call?.confidence ?? subscription.confidence,
|
|
6176
|
+
JSON.stringify(evidenceFor(subscription, association)),
|
|
6177
|
+
0,
|
|
6178
|
+
association.reasonCode ?? association.call?.unresolvedReason ?? null,
|
|
6179
|
+
generation
|
|
6180
|
+
);
|
|
6181
|
+
}
|
|
6182
|
+
function linkEventSubscriptionHandlers(db, workspaceId, generation) {
|
|
6183
|
+
const summary = {
|
|
6184
|
+
edgeCount: 0,
|
|
6185
|
+
resolvedCount: 0,
|
|
6186
|
+
ambiguousCount: 0,
|
|
6187
|
+
unresolvedCount: 0,
|
|
6188
|
+
missingAssociationCount: 0
|
|
6189
|
+
};
|
|
6190
|
+
for (const subscription of subscriptionRows(db, workspaceId)) {
|
|
6191
|
+
const association = associationFor(
|
|
6192
|
+
subscription,
|
|
6193
|
+
roleSiteRows(db, subscription)
|
|
6194
|
+
);
|
|
6195
|
+
insertAssociationEdge(
|
|
6196
|
+
db,
|
|
6197
|
+
workspaceId,
|
|
6198
|
+
generation,
|
|
6199
|
+
subscription,
|
|
6200
|
+
association
|
|
6201
|
+
);
|
|
6202
|
+
summary.edgeCount += 1;
|
|
6203
|
+
summary.resolvedCount += association.status === "resolved" ? 1 : 0;
|
|
6204
|
+
summary.ambiguousCount += association.status === "ambiguous" ? 1 : 0;
|
|
6205
|
+
summary.unresolvedCount += association.status === "unresolved" ? 1 : 0;
|
|
6206
|
+
summary.missingAssociationCount += association.missing ? 1 : 0;
|
|
6207
|
+
}
|
|
6208
|
+
return summary;
|
|
6209
|
+
}
|
|
6210
|
+
|
|
6211
|
+
// src/db/schema.ts
|
|
6212
|
+
var schemaTablesSql = `
|
|
6213
|
+
CREATE TABLE IF NOT EXISTS workspaces (id INTEGER PRIMARY KEY, root_path TEXT UNIQUE NOT NULL, db_path TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL);
|
|
6214
|
+
CREATE TABLE IF NOT EXISTS repositories (id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, name TEXT NOT NULL, absolute_path TEXT NOT NULL, relative_path TEXT NOT NULL, package_name TEXT, package_version TEXT, dependencies_json TEXT DEFAULT '{}', kind TEXT NOT NULL, is_git_repo INTEGER NOT NULL, last_indexed_at TEXT, index_status TEXT DEFAULT 'pending', error_count INTEGER DEFAULT 0, fingerprint TEXT, fact_generation INTEGER NOT NULL DEFAULT 0, graph_generation INTEGER NOT NULL DEFAULT 0, graph_stale_reason TEXT, graph_stale_at TEXT, fact_analyzer_version TEXT, UNIQUE(workspace_id, absolute_path), FOREIGN KEY(workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE);
|
|
6215
|
+
CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, relative_path TEXT NOT NULL, extension TEXT NOT NULL, sha256 TEXT NOT NULL, size_bytes INTEGER NOT NULL, last_indexed_at TEXT NOT NULL, UNIQUE(repo_id, relative_path), FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE);
|
|
6216
|
+
CREATE TABLE IF NOT EXISTS cds_requires (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, alias TEXT NOT NULL, kind TEXT, model TEXT, destination TEXT, service_path TEXT, request_timeout INTEGER, raw_json TEXT NOT NULL, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE);
|
|
6217
|
+
CREATE TABLE IF NOT EXISTS cds_services (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, namespace TEXT, service_name TEXT NOT NULL, qualified_name TEXT NOT NULL, service_path TEXT NOT NULL, is_extend INTEGER NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, extension_local_ref TEXT, extension_imported_symbol TEXT, extension_local_alias TEXT, extension_module_specifier TEXT, extension_import_kind TEXT, extension_base_service_id INTEGER, extension_base_status TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(extension_base_service_id) REFERENCES cds_services(id) ON DELETE SET NULL);
|
|
6218
|
+
CREATE TABLE IF NOT EXISTS cds_operations (id INTEGER PRIMARY KEY, service_id INTEGER NOT NULL, operation_type TEXT NOT NULL, operation_name TEXT NOT NULL, operation_path TEXT NOT NULL, params_json TEXT NOT NULL, return_type TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, provenance TEXT NOT NULL DEFAULT 'direct', base_operation_id INTEGER, FOREIGN KEY(service_id) REFERENCES cds_services(id) ON DELETE CASCADE, FOREIGN KEY(base_operation_id) REFERENCES cds_operations(id) ON DELETE SET NULL);
|
|
6219
|
+
CREATE TABLE IF NOT EXISTS symbols (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, file_id INTEGER, kind TEXT NOT NULL, name TEXT NOT NULL, qualified_name TEXT NOT NULL, exported INTEGER NOT NULL, start_line INTEGER NOT NULL, end_line INTEGER NOT NULL, start_offset INTEGER, end_offset INTEGER, source_file TEXT, exported_name TEXT, evidence_json TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(file_id) REFERENCES files(id) ON DELETE CASCADE);
|
|
6220
|
+
CREATE TABLE IF NOT EXISTS handler_classes (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, symbol_id INTEGER, class_name TEXT NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(symbol_id) REFERENCES symbols(id) ON DELETE SET NULL);
|
|
6221
|
+
CREATE TABLE IF NOT EXISTS handler_methods (id INTEGER PRIMARY KEY, handler_class_id INTEGER NOT NULL, method_name TEXT NOT NULL, decorator_kind TEXT NOT NULL, decorator_value TEXT, decorator_raw_expression TEXT NOT NULL, decorator_resolution_json TEXT NOT NULL DEFAULT '{}', source_file TEXT NOT NULL, source_line INTEGER NOT NULL, FOREIGN KEY(handler_class_id) REFERENCES handler_classes(id) ON DELETE CASCADE);
|
|
6222
|
+
CREATE TABLE IF NOT EXISTS handler_registrations (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, handler_class_id INTEGER, class_name TEXT, import_source TEXT, registration_file TEXT NOT NULL, registration_line INTEGER NOT NULL, registration_kind TEXT NOT NULL, confidence REAL NOT NULL, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(handler_class_id) REFERENCES handler_classes(id) ON DELETE SET NULL);
|
|
6223
|
+
CREATE TABLE IF NOT EXISTS service_bindings (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, symbol_id INTEGER, variable_name TEXT NOT NULL, alias TEXT, alias_expr TEXT, destination_expr TEXT, service_path_expr TEXT, is_dynamic INTEGER NOT NULL, placeholders_json TEXT NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, helper_chain_json TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(symbol_id) REFERENCES symbols(id) ON DELETE SET NULL);
|
|
6224
|
+
CREATE TABLE IF NOT EXISTS outbound_calls (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, source_symbol_id INTEGER, call_type TEXT NOT NULL, service_binding_id INTEGER, method TEXT, operation_path_expr TEXT, query_entity TEXT, event_name_expr TEXT, payload_summary TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, call_site_start_offset INTEGER, call_site_end_offset INTEGER, confidence REAL NOT NULL, unresolved_reason TEXT, local_service_name TEXT, local_service_lookup TEXT, alias_chain_json TEXT, evidence_json TEXT, external_target_kind TEXT, external_target_id TEXT, external_target_label TEXT, external_target_dynamic INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(source_symbol_id) REFERENCES symbols(id) ON DELETE SET NULL, FOREIGN KEY(service_binding_id) REFERENCES service_bindings(id) ON DELETE SET NULL);
|
|
6225
|
+
CREATE TABLE IF NOT EXISTS symbol_calls (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, caller_symbol_id INTEGER NOT NULL, callee_symbol_id INTEGER, callee_expression TEXT NOT NULL, import_source TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, call_site_start_offset INTEGER, call_site_end_offset INTEGER, call_role TEXT NOT NULL DEFAULT 'legacy_unknown', status TEXT NOT NULL, confidence REAL NOT NULL, evidence_json TEXT NOT NULL, unresolved_reason TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(caller_symbol_id) REFERENCES symbols(id) ON DELETE CASCADE, FOREIGN KEY(callee_symbol_id) REFERENCES symbols(id) ON DELETE SET NULL);
|
|
6226
|
+
CREATE TABLE IF NOT EXISTS graph_edges (id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, edge_type TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'unresolved', from_kind TEXT NOT NULL, from_id TEXT NOT NULL, to_kind TEXT NOT NULL, to_id TEXT NOT NULL, confidence REAL NOT NULL, evidence_json TEXT NOT NULL, is_dynamic INTEGER NOT NULL, unresolved_reason TEXT, generation INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE);
|
|
6227
|
+
CREATE TABLE IF NOT EXISTS index_runs (id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, started_at TEXT NOT NULL, finished_at TEXT, status TEXT NOT NULL, repo_count INTEGER NOT NULL, file_count INTEGER NOT NULL, diagnostic_count INTEGER NOT NULL, error_message TEXT, owner_pid INTEGER, FOREIGN KEY(workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE);
|
|
6228
|
+
CREATE TABLE IF NOT EXISTS diagnostics (id INTEGER PRIMARY KEY, repo_id INTEGER, file_id INTEGER, severity TEXT NOT NULL, code TEXT NOT NULL, message TEXT NOT NULL, source_file TEXT, source_line INTEGER, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(file_id) REFERENCES files(id) ON DELETE SET NULL);
|
|
6229
|
+
`;
|
|
6230
|
+
var schemaIndexesSql = `
|
|
6231
|
+
CREATE INDEX IF NOT EXISTS idx_repo_name ON repositories(name);
|
|
6232
|
+
CREATE INDEX IF NOT EXISTS idx_service_path ON cds_services(service_path);
|
|
6233
|
+
CREATE INDEX IF NOT EXISTS idx_extension_import ON cds_services(extension_module_specifier, extension_imported_symbol, is_extend);
|
|
6234
|
+
CREATE INDEX IF NOT EXISTS idx_operation_name ON cds_operations(operation_name, operation_path);
|
|
6235
|
+
CREATE INDEX IF NOT EXISTS idx_operation_base ON cds_operations(base_operation_id);
|
|
6236
|
+
CREATE INDEX IF NOT EXISTS idx_calls_repo ON outbound_calls(repo_id, call_type);
|
|
6237
|
+
CREATE INDEX IF NOT EXISTS idx_symbol_calls_caller ON symbol_calls(repo_id, caller_symbol_id);
|
|
6238
|
+
CREATE INDEX IF NOT EXISTS idx_outbound_call_site ON outbound_calls(repo_id, source_file, call_site_start_offset, call_site_end_offset, call_type);
|
|
6239
|
+
CREATE INDEX IF NOT EXISTS idx_symbol_call_site_role ON symbol_calls(repo_id, source_file, call_site_start_offset, call_site_end_offset, call_role);
|
|
6240
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS search_index USING fts5(kind, name, path, repo);
|
|
6241
|
+
`;
|
|
6242
|
+
var schemaSql = `${schemaTablesSql}
|
|
6243
|
+
${schemaIndexesSql}`;
|
|
6244
|
+
|
|
6245
|
+
// src/db/migrations.ts
|
|
6246
|
+
var CURRENT_SCHEMA_VERSION = 12;
|
|
6247
|
+
var columns = {
|
|
6248
|
+
handler_methods: [
|
|
6249
|
+
{ name: "decorator_resolution_json", ddl: "ALTER TABLE handler_methods ADD COLUMN decorator_resolution_json TEXT NOT NULL DEFAULT '{}'" }
|
|
6250
|
+
],
|
|
6251
|
+
service_bindings: [
|
|
6252
|
+
{ name: "helper_chain_json", ddl: "ALTER TABLE service_bindings ADD COLUMN helper_chain_json TEXT" },
|
|
6253
|
+
{ name: "alias_expr", ddl: "ALTER TABLE service_bindings ADD COLUMN alias_expr TEXT" }
|
|
6254
|
+
],
|
|
6255
|
+
repositories: [
|
|
6256
|
+
{ name: "fingerprint", ddl: "ALTER TABLE repositories ADD COLUMN fingerprint TEXT" },
|
|
6257
|
+
{ name: "fact_generation", ddl: "ALTER TABLE repositories ADD COLUMN fact_generation INTEGER NOT NULL DEFAULT 0" },
|
|
6258
|
+
{ name: "graph_generation", ddl: "ALTER TABLE repositories ADD COLUMN graph_generation INTEGER NOT NULL DEFAULT 0" },
|
|
6259
|
+
{ name: "graph_stale_reason", ddl: "ALTER TABLE repositories ADD COLUMN graph_stale_reason TEXT" },
|
|
6260
|
+
{ name: "graph_stale_at", ddl: "ALTER TABLE repositories ADD COLUMN graph_stale_at TEXT" },
|
|
6261
|
+
{ name: "fact_analyzer_version", ddl: "ALTER TABLE repositories ADD COLUMN fact_analyzer_version TEXT DEFAULT 'legacy'" }
|
|
6262
|
+
],
|
|
6263
|
+
graph_edges: [
|
|
6264
|
+
{ name: "status", ddl: "ALTER TABLE graph_edges ADD COLUMN status TEXT NOT NULL DEFAULT 'unresolved'" },
|
|
6265
|
+
{ name: "generation", ddl: "ALTER TABLE graph_edges ADD COLUMN generation INTEGER NOT NULL DEFAULT 0" }
|
|
6266
|
+
],
|
|
6267
|
+
handler_registrations: [
|
|
6268
|
+
{ name: "class_name", ddl: "ALTER TABLE handler_registrations ADD COLUMN class_name TEXT" },
|
|
6269
|
+
{ name: "import_source", ddl: "ALTER TABLE handler_registrations ADD COLUMN import_source TEXT" }
|
|
6270
|
+
],
|
|
6271
|
+
symbols: [
|
|
6272
|
+
{ name: "start_offset", ddl: "ALTER TABLE symbols ADD COLUMN start_offset INTEGER" },
|
|
6273
|
+
{ name: "end_offset", ddl: "ALTER TABLE symbols ADD COLUMN end_offset INTEGER" },
|
|
6274
|
+
{ name: "source_file", ddl: "ALTER TABLE symbols ADD COLUMN source_file TEXT" },
|
|
6275
|
+
{ name: "exported_name", ddl: "ALTER TABLE symbols ADD COLUMN exported_name TEXT" },
|
|
6276
|
+
{ name: "evidence_json", ddl: "ALTER TABLE symbols ADD COLUMN evidence_json TEXT" }
|
|
6277
|
+
],
|
|
6278
|
+
cds_services: [
|
|
6279
|
+
{ name: "extension_local_ref", ddl: "ALTER TABLE cds_services ADD COLUMN extension_local_ref TEXT" },
|
|
6280
|
+
{ name: "extension_imported_symbol", ddl: "ALTER TABLE cds_services ADD COLUMN extension_imported_symbol TEXT" },
|
|
6281
|
+
{ name: "extension_local_alias", ddl: "ALTER TABLE cds_services ADD COLUMN extension_local_alias TEXT" },
|
|
6282
|
+
{ name: "extension_module_specifier", ddl: "ALTER TABLE cds_services ADD COLUMN extension_module_specifier TEXT" },
|
|
6283
|
+
{ name: "extension_import_kind", ddl: "ALTER TABLE cds_services ADD COLUMN extension_import_kind TEXT" },
|
|
6284
|
+
{ name: "extension_base_service_id", ddl: "ALTER TABLE cds_services ADD COLUMN extension_base_service_id INTEGER" },
|
|
6285
|
+
{ name: "extension_base_status", ddl: "ALTER TABLE cds_services ADD COLUMN extension_base_status TEXT" }
|
|
6286
|
+
],
|
|
6287
|
+
cds_operations: [
|
|
6288
|
+
{ name: "provenance", ddl: "ALTER TABLE cds_operations ADD COLUMN provenance TEXT NOT NULL DEFAULT 'direct'" },
|
|
6289
|
+
{ name: "base_operation_id", ddl: "ALTER TABLE cds_operations ADD COLUMN base_operation_id INTEGER" }
|
|
6290
|
+
],
|
|
6291
|
+
outbound_calls: [
|
|
6292
|
+
{ name: "local_service_name", ddl: "ALTER TABLE outbound_calls ADD COLUMN local_service_name TEXT" },
|
|
6293
|
+
{ name: "local_service_lookup", ddl: "ALTER TABLE outbound_calls ADD COLUMN local_service_lookup TEXT" },
|
|
6294
|
+
{ name: "alias_chain_json", ddl: "ALTER TABLE outbound_calls ADD COLUMN alias_chain_json TEXT" },
|
|
6295
|
+
{ name: "evidence_json", ddl: "ALTER TABLE outbound_calls ADD COLUMN evidence_json TEXT" },
|
|
6296
|
+
{ name: "external_target_kind", ddl: "ALTER TABLE outbound_calls ADD COLUMN external_target_kind TEXT" },
|
|
6297
|
+
{ name: "external_target_id", ddl: "ALTER TABLE outbound_calls ADD COLUMN external_target_id TEXT" },
|
|
6298
|
+
{ name: "external_target_label", ddl: "ALTER TABLE outbound_calls ADD COLUMN external_target_label TEXT" },
|
|
6299
|
+
{ name: "external_target_dynamic", ddl: "ALTER TABLE outbound_calls ADD COLUMN external_target_dynamic INTEGER NOT NULL DEFAULT 0" },
|
|
6300
|
+
{ name: "call_site_start_offset", ddl: "ALTER TABLE outbound_calls ADD COLUMN call_site_start_offset INTEGER" },
|
|
6301
|
+
{ name: "call_site_end_offset", ddl: "ALTER TABLE outbound_calls ADD COLUMN call_site_end_offset INTEGER" }
|
|
6302
|
+
],
|
|
6303
|
+
symbol_calls: [
|
|
6304
|
+
{ name: "call_site_start_offset", ddl: "ALTER TABLE symbol_calls ADD COLUMN call_site_start_offset INTEGER" },
|
|
6305
|
+
{ name: "call_site_end_offset", ddl: "ALTER TABLE symbol_calls ADD COLUMN call_site_end_offset INTEGER" },
|
|
6306
|
+
{ name: "call_role", ddl: "ALTER TABLE symbol_calls ADD COLUMN call_role TEXT NOT NULL DEFAULT 'legacy_unknown'" }
|
|
6307
|
+
],
|
|
6308
|
+
index_runs: [
|
|
6309
|
+
{ name: "error_message", ddl: "ALTER TABLE index_runs ADD COLUMN error_message TEXT" },
|
|
6310
|
+
{ name: "owner_pid", ddl: "ALTER TABLE index_runs ADD COLUMN owner_pid INTEGER" }
|
|
6311
|
+
]
|
|
6312
|
+
};
|
|
6313
|
+
function hasColumn(db, table, column) {
|
|
6314
|
+
return db.prepare(`PRAGMA table_info(${table})`).all().some((row) => row.name === column);
|
|
6315
|
+
}
|
|
6316
|
+
function userVersion(db) {
|
|
6317
|
+
const row = db.pragma("user_version")[0];
|
|
6318
|
+
return Number(row?.user_version ?? 0);
|
|
6319
|
+
}
|
|
6320
|
+
function addMissingColumns(db) {
|
|
6321
|
+
for (const [table, tableColumns] of Object.entries(columns)) {
|
|
6322
|
+
for (const column of tableColumns) {
|
|
6323
|
+
if (!hasColumn(db, table, column.name)) db.prepare(column.ddl).run();
|
|
6324
|
+
}
|
|
6325
|
+
}
|
|
6326
|
+
}
|
|
6327
|
+
function normalizeLegacyStatus(db) {
|
|
6328
|
+
db.prepare("UPDATE graph_edges SET status=CASE WHEN edge_type='REMOTE_CALL_RESOLVES_TO_OPERATION' THEN 'resolved' WHEN edge_type IN ('HANDLER_RUNS_DB_QUERY','HANDLER_CALLS_EXTERNAL_HTTP','HANDLER_EMITS_EVENT','EVENT_CONSUMED_BY_HANDLER') THEN 'terminal' WHEN edge_type='DYNAMIC_EDGE_CANDIDATE' THEN 'dynamic' WHEN status='ambiguous' THEN 'ambiguous' ELSE status END").run();
|
|
6329
|
+
db.prepare("UPDATE repositories SET graph_stale_reason='schema_migration_requires_relink', graph_stale_at=COALESCE(graph_stale_at, datetime('now')) WHERE EXISTS (SELECT 1 FROM graph_edges WHERE graph_edges.workspace_id=repositories.workspace_id) AND graph_generation=0").run();
|
|
6330
|
+
}
|
|
6331
|
+
function markCallSiteMigrationStale(db, priorVersion) {
|
|
6332
|
+
if (priorVersion >= 12) return;
|
|
6333
|
+
db.prepare(`UPDATE repositories
|
|
6334
|
+
SET graph_stale_reason='schema_v12_call_sites_require_reindex',
|
|
6335
|
+
graph_stale_at=COALESCE(graph_stale_at,datetime('now'))
|
|
6336
|
+
WHERE index_status='indexed' OR last_indexed_at IS NOT NULL`).run();
|
|
6337
|
+
}
|
|
6338
|
+
function migrate(db) {
|
|
6339
|
+
db.transaction(() => {
|
|
6340
|
+
const version = userVersion(db);
|
|
6341
|
+
if (version > CURRENT_SCHEMA_VERSION) throw new Error(`Unsupported future service-flow schema version ${version}`);
|
|
6342
|
+
db.exec(schemaTablesSql);
|
|
6343
|
+
addMissingColumns(db);
|
|
6344
|
+
db.exec(schemaIndexesSql);
|
|
6345
|
+
normalizeLegacyStatus(db);
|
|
6346
|
+
markCallSiteMigrationStale(db, version);
|
|
6347
|
+
const violations = db.pragma("foreign_key_check");
|
|
6348
|
+
if (violations.length > 0) throw new Error("SQLite foreign_key_check failed during migration");
|
|
6349
|
+
db.pragma(`user_version = ${CURRENT_SCHEMA_VERSION}`);
|
|
6350
|
+
});
|
|
6351
|
+
}
|
|
6352
|
+
function schemaVersion(db) {
|
|
6353
|
+
return userVersion(db);
|
|
6354
|
+
}
|
|
6355
|
+
|
|
6356
|
+
// src/db/001-fact-lifecycle.ts
|
|
6357
|
+
var remediation = [
|
|
6358
|
+
"service-flow index --workspace /workspace --force",
|
|
6359
|
+
"service-flow link --workspace /workspace --force"
|
|
6360
|
+
].join("\n");
|
|
6361
|
+
function count(db, sql, ...params) {
|
|
6362
|
+
const row = db.prepare(sql).get(...params);
|
|
6363
|
+
return Number(row?.count ?? 0);
|
|
6364
|
+
}
|
|
6365
|
+
function oldAnalyzerCount(db, workspaceId) {
|
|
6366
|
+
return count(
|
|
6367
|
+
db,
|
|
6368
|
+
`SELECT COUNT(*) count FROM repositories
|
|
6369
|
+
WHERE (? IS NULL OR workspace_id=?)
|
|
6370
|
+
AND (COALESCE(index_status,'pending')<>'indexed'
|
|
6371
|
+
OR COALESCE(fact_analyzer_version,'legacy')<>?)`,
|
|
6372
|
+
workspaceId,
|
|
6373
|
+
workspaceId,
|
|
6374
|
+
ANALYZER_VERSION
|
|
6375
|
+
);
|
|
6376
|
+
}
|
|
6377
|
+
function invalidCurrentFactCount(db, workspaceId) {
|
|
6378
|
+
const outbound = count(
|
|
6379
|
+
db,
|
|
6380
|
+
`SELECT COUNT(*) count
|
|
6381
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
6382
|
+
WHERE (? IS NULL OR r.workspace_id=?) AND r.fact_analyzer_version=?
|
|
6383
|
+
AND (typeof(c.call_site_start_offset)<>'integer'
|
|
6384
|
+
OR typeof(c.call_site_end_offset)<>'integer'
|
|
6385
|
+
OR c.call_site_start_offset<0
|
|
6386
|
+
OR c.call_site_end_offset<=c.call_site_start_offset)`,
|
|
6387
|
+
workspaceId,
|
|
6388
|
+
workspaceId,
|
|
6389
|
+
ANALYZER_VERSION
|
|
6390
|
+
);
|
|
6391
|
+
const symbols = count(
|
|
6392
|
+
db,
|
|
6393
|
+
`SELECT COUNT(*) count
|
|
6394
|
+
FROM symbol_calls c JOIN repositories r ON r.id=c.repo_id
|
|
6395
|
+
WHERE (? IS NULL OR r.workspace_id=?)
|
|
6396
|
+
AND (c.call_role='legacy_unknown'
|
|
6397
|
+
OR (r.fact_analyzer_version=? AND (
|
|
6398
|
+
typeof(c.call_site_start_offset)<>'integer'
|
|
6399
|
+
OR typeof(c.call_site_end_offset)<>'integer'
|
|
6400
|
+
OR c.call_site_start_offset<0
|
|
6401
|
+
OR c.call_site_end_offset<=c.call_site_start_offset
|
|
6402
|
+
OR c.call_role NOT IN ('ordinary_call','event_subscribe_handler'))))`,
|
|
6403
|
+
workspaceId,
|
|
6404
|
+
workspaceId,
|
|
6405
|
+
ANALYZER_VERSION
|
|
6406
|
+
);
|
|
6407
|
+
return outbound + symbols;
|
|
6408
|
+
}
|
|
6409
|
+
function factLifecycleDiagnostic(db, workspaceId) {
|
|
6410
|
+
return schemaLifecycleDiagnostic(db) ?? currentFactLifecycleDiagnostic(db, workspaceId);
|
|
6411
|
+
}
|
|
6412
|
+
function schemaLifecycleDiagnostic(db) {
|
|
6413
|
+
const currentSchema = schemaVersion(db);
|
|
6414
|
+
if (currentSchema > CURRENT_SCHEMA_VERSION) return {
|
|
6415
|
+
severity: "error",
|
|
6416
|
+
code: "unsupported_future_schema",
|
|
6417
|
+
message: `Database schema ${currentSchema} is newer than the supported schema ${CURRENT_SCHEMA_VERSION}; upgrade service-flow before reading this database.`,
|
|
6418
|
+
remediation: "Install a service-flow version that supports this database schema.",
|
|
6419
|
+
currentSchemaVersion: currentSchema,
|
|
6420
|
+
supportedSchemaVersion: CURRENT_SCHEMA_VERSION
|
|
6421
|
+
};
|
|
6422
|
+
if (currentSchema < CURRENT_SCHEMA_VERSION) return {
|
|
6423
|
+
severity: "error",
|
|
6424
|
+
code: "schema_upgrade_required",
|
|
6425
|
+
message: `Database schema ${currentSchema} must be upgraded to ${CURRENT_SCHEMA_VERSION} before this command can read current call-site facts.`,
|
|
6426
|
+
remediation,
|
|
6427
|
+
currentSchemaVersion: currentSchema,
|
|
6428
|
+
requiredSchemaVersion: CURRENT_SCHEMA_VERSION
|
|
6429
|
+
};
|
|
6430
|
+
return void 0;
|
|
6431
|
+
}
|
|
6432
|
+
function currentFactLifecycleDiagnostic(db, workspaceId) {
|
|
6433
|
+
const staleRepositories = oldAnalyzerCount(db, workspaceId);
|
|
6434
|
+
const invalidFacts = invalidCurrentFactCount(db, workspaceId);
|
|
6435
|
+
if (staleRepositories === 0 && invalidFacts === 0) return void 0;
|
|
6436
|
+
return {
|
|
6437
|
+
severity: "error",
|
|
6438
|
+
code: "reindex_required",
|
|
6439
|
+
message: "Call-site facts are stale or lack typed roles and exact spans; force index and link before tracing or rebuilding graph edges.",
|
|
6440
|
+
remediation,
|
|
6441
|
+
staleRepositoryCount: staleRepositories,
|
|
6442
|
+
invalidCallFactCount: invalidFacts,
|
|
6443
|
+
requiredAnalyzerVersion: ANALYZER_VERSION
|
|
6444
|
+
};
|
|
6445
|
+
}
|
|
6446
|
+
function assertWorkspaceLinkable(db, workspaceId) {
|
|
6447
|
+
const diagnostic = factLifecycleDiagnostic(db, workspaceId);
|
|
6448
|
+
if (!diagnostic) return;
|
|
6449
|
+
throw new Error(`${diagnostic.code}: ${diagnostic.message}
|
|
6450
|
+
${diagnostic.remediation}`);
|
|
6451
|
+
}
|
|
6452
|
+
|
|
6453
|
+
// src/linker/cross-repo-linker.ts
|
|
6454
|
+
function linkWorkspace(db, workspaceId, vars = {}) {
|
|
6455
|
+
return db.transaction(() => {
|
|
6456
|
+
assertWorkspaceLinkable(db, workspaceId);
|
|
6457
|
+
const generation = nextGraphGeneration(db, workspaceId);
|
|
6458
|
+
db.prepare("DELETE FROM graph_edges WHERE workspace_id=?").run(workspaceId);
|
|
6459
|
+
const deps = linkHelperPackages(db, workspaceId, generation);
|
|
6460
|
+
linkPackageImportSymbolCalls(db, workspaceId);
|
|
6461
|
+
const subscriptions = linkEventSubscriptionHandlers(
|
|
6462
|
+
db,
|
|
6463
|
+
workspaceId,
|
|
6464
|
+
generation
|
|
6465
|
+
);
|
|
6466
|
+
const impl = linkImplementations(db, workspaceId, generation);
|
|
6467
|
+
const callSummary = linkCalls(db, workspaceId, vars, generation);
|
|
6468
|
+
db.prepare("UPDATE repositories SET graph_generation=?, graph_stale_reason=NULL, graph_stale_at=NULL WHERE workspace_id=?").run(generation, workspaceId);
|
|
6469
|
+
return { ...callSummary, edgeCount: deps.edgeCount + callSummary.edgeCount + impl.edgeCount + subscriptions.edgeCount, dependencyResolvedCount: deps.resolvedCount, dependencyAmbiguousCount: deps.ambiguousCount, implementationResolvedCount: impl.resolvedCount, implementationAmbiguousCount: impl.ambiguousCount, implementationUnresolvedCount: impl.unresolvedCount, subscriptionHandlerResolvedCount: subscriptions.resolvedCount, subscriptionHandlerAmbiguousCount: subscriptions.ambiguousCount, subscriptionHandlerUnresolvedCount: subscriptions.unresolvedCount, subscriptionHandlerMissingAssociationCount: subscriptions.missingAssociationCount };
|
|
6470
|
+
});
|
|
6471
|
+
}
|
|
6472
|
+
function nextGraphGeneration(db, workspaceId) {
|
|
6473
|
+
const row = db.prepare("SELECT COALESCE(MAX(graph_generation),0) generation FROM repositories WHERE workspace_id=?").get(workspaceId);
|
|
6474
|
+
return Number(row?.generation ?? 0) + 1;
|
|
6475
|
+
}
|
|
6476
|
+
function linkCalls(db, workspaceId, vars, generation) {
|
|
6477
|
+
let edgeCount = 0;
|
|
6478
|
+
let unresolvedCount = 0;
|
|
6479
|
+
let resolvedCount = 0;
|
|
6480
|
+
let remoteResolvedCount = 0;
|
|
6481
|
+
let localResolvedCount = 0;
|
|
6482
|
+
let ambiguousCount = 0;
|
|
6483
|
+
let dynamicCount = 0;
|
|
6484
|
+
let terminalCount = 0;
|
|
6485
|
+
const calls = db.prepare(`SELECT c.*,r.name repoName,b.id selectedBindingId,b.alias,b.alias_expr aliasExpr,b.destination_expr destinationExpr,b.service_path_expr servicePathExpr,b.is_dynamic isDynamic,b.placeholders_json placeholdersJson,b.source_file bindingSourceFile,b.source_line bindingSourceLine,b.helper_chain_json helperChainJson,req.service_path requireServicePath,req.destination requireDestination FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id LEFT JOIN service_bindings b ON b.id=c.service_binding_id LEFT JOIN cds_requires req ON req.repo_id=c.repo_id AND req.alias=b.alias WHERE r.workspace_id=?`).all(workspaceId);
|
|
6486
|
+
for (const call of calls) {
|
|
6487
|
+
const result = insertCallEdge(db, workspaceId, call, vars, generation);
|
|
6488
|
+
edgeCount += 1;
|
|
6489
|
+
resolvedCount += result.status === "resolved" ? 1 : 0;
|
|
6490
|
+
remoteResolvedCount += result.status === "resolved" && result.callType !== "local_service_call" ? 1 : 0;
|
|
6491
|
+
localResolvedCount += result.status === "resolved" && result.callType === "local_service_call" ? 1 : 0;
|
|
6492
|
+
unresolvedCount += result.status === "unresolved" ? 1 : 0;
|
|
6493
|
+
ambiguousCount += result.status === "ambiguous" ? 1 : 0;
|
|
6494
|
+
dynamicCount += result.status === "dynamic" ? 1 : 0;
|
|
6495
|
+
terminalCount += result.status === "terminal" ? 1 : 0;
|
|
6496
|
+
}
|
|
6497
|
+
return { edgeCount, unresolvedCount, resolvedCount, remoteResolvedCount, localResolvedCount, ambiguousCount, dynamicCount, terminalCount };
|
|
6498
|
+
}
|
|
6499
|
+
function insertCallEdge(db, workspaceId, call, vars, generation) {
|
|
6500
|
+
const callType = String(call.call_type);
|
|
6501
|
+
const rawOp = applyVariables(String(call.operation_path_expr ?? ""), vars);
|
|
6502
|
+
const intent = classifyODataPathIntent(rawOp, call.method);
|
|
6503
|
+
const isEntityQueryIntent = ["entity_query", "entity_key_read", "entity_navigation_query"].includes(intent.kind);
|
|
5805
6504
|
const resolutionRawOp = callType === "remote_query" && isEntityQueryIntent ? intent.pathWithoutQuery : rawOp;
|
|
5806
6505
|
const normalized = normalizeODataOperationInvocationPath(resolutionRawOp);
|
|
5807
6506
|
const op = normalized?.normalizedOperationPath ?? resolutionRawOp;
|
|
@@ -5890,11 +6589,11 @@ function insertCallEdge(db, workspaceId, call, vars, generation) {
|
|
|
5890
6589
|
const status = edgeType === "DYNAMIC_EDGE_CANDIDATE" ? "dynamic" : resolution.status === "ambiguous" ? "ambiguous" : edgeType === "UNRESOLVED_EDGE" ? "unresolved" : "terminal";
|
|
5891
6590
|
const unresolvedReason = status === "terminal" ? null : String(call.unresolved_reason ?? unresolvedOperationReason(resolution));
|
|
5892
6591
|
const externalTarget = callType === "external_http" ? externalHttpTarget(call) : void 0;
|
|
5893
|
-
const
|
|
6592
|
+
const targetKind2 = callType === "local_db_query" ? "db_entity" : callType.startsWith("async_") ? "event" : callType === "external_http" ? externalTarget?.toKind ?? "external_endpoint" : "operation_candidate";
|
|
5894
6593
|
const targetId = callType === "local_db_query" ? String(call.query_entity ?? "unknown") : callType === "remote_action" ? op ? `Remote action: ${op}` : call.unresolved_reason === "dynamic_operation_path_identifier" ? "Remote action: dynamic path" : "Remote action: unknown path" : callType === "external_http" ? String(externalTarget?.toId ?? "unknown") : String(call.event_name_expr ?? op ?? "unknown");
|
|
5895
6594
|
const graphLevelDynamic = edgeType === "DYNAMIC_EDGE_CANDIDATE" && resolution.status === "dynamic";
|
|
5896
6595
|
const finalEvidence = externalTarget ? { ...evidence, externalTarget } : evidence;
|
|
5897
|
-
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, edgeType, status, "call", String(call.id),
|
|
6596
|
+
db.prepare("INSERT INTO graph_edges(workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,confidence,evidence_json,is_dynamic,unresolved_reason,generation) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)").run(workspaceId, edgeType, status, "call", String(call.id), targetKind2, targetId, Number(call.confidence ?? 0.2), JSON.stringify(finalEvidence), graphLevelDynamic ? 1 : 0, unresolvedReason, generation);
|
|
5898
6597
|
return { status, callType };
|
|
5899
6598
|
}
|
|
5900
6599
|
function operationCandidateCount(db, workspaceId, operationPath, operationName) {
|
|
@@ -6733,18 +7432,18 @@ function dynamicRoutingContext(db, workspaceId, evidence) {
|
|
|
6733
7432
|
fallbackUsed: true
|
|
6734
7433
|
};
|
|
6735
7434
|
}
|
|
6736
|
-
function dynamicReferenceProvenance(
|
|
6737
|
-
const sourceKind =
|
|
7435
|
+
function dynamicReferenceProvenance(reference2, kind, template, value) {
|
|
7436
|
+
const sourceKind = reference2.selection === "selected_binding" ? `selected_binding.${kind}` : reference2.selection === "selected_binding_require" ? `selected_binding_require.${kind}` : `${reference2.sourceKind}.${kind}`;
|
|
6738
7437
|
return {
|
|
6739
7438
|
sourceKind,
|
|
6740
7439
|
value,
|
|
6741
7440
|
rule: "exact_indexed_reference_template_match",
|
|
6742
7441
|
template,
|
|
6743
|
-
sourceRepo:
|
|
6744
|
-
sourceFile:
|
|
6745
|
-
sourceLine:
|
|
6746
|
-
selection:
|
|
6747
|
-
bindingId:
|
|
7442
|
+
sourceRepo: reference2.repoName,
|
|
7443
|
+
sourceFile: reference2.sourceFile,
|
|
7444
|
+
sourceLine: reference2.sourceLine,
|
|
7445
|
+
selection: reference2.selection,
|
|
7446
|
+
bindingId: reference2.bindingId
|
|
6748
7447
|
};
|
|
6749
7448
|
}
|
|
6750
7449
|
function contextBase(selected, status, alternatives) {
|
|
@@ -6779,12 +7478,12 @@ function selectedBinding(db, workspaceId, evidence) {
|
|
|
6779
7478
|
function selectedReferenceFromRow(row) {
|
|
6780
7479
|
const outboundCallId = numberValue6(row?.outboundCallId);
|
|
6781
7480
|
const callerRepoId = numberValue6(row?.callerRepoId);
|
|
6782
|
-
const
|
|
7481
|
+
const reference2 = referenceFromRow(
|
|
6783
7482
|
row,
|
|
6784
7483
|
"service_binding",
|
|
6785
7484
|
"selected_binding"
|
|
6786
7485
|
)[0];
|
|
6787
|
-
return
|
|
7486
|
+
return reference2 && outboundCallId !== void 0 && callerRepoId !== void 0 ? { ...reference2, outboundCallId, callerRepoId } : void 0;
|
|
6788
7487
|
}
|
|
6789
7488
|
function exactRequireReferences(db, workspaceId, selected) {
|
|
6790
7489
|
if (!(selected.aliasExpr ?? selected.alias)) return [];
|
|
@@ -6967,7 +7666,7 @@ function buildCandidates(db, targets, references, inputs) {
|
|
|
6967
7666
|
const state = emptyCandidate(target, inputs);
|
|
6968
7667
|
applyDirectSignal(state, inputs, "operationPath", target.operationPath, 0.25);
|
|
6969
7668
|
applyDirectSignal(state, inputs, "servicePath", target.servicePath, 0.35);
|
|
6970
|
-
const matchingReferences = references.filter((
|
|
7669
|
+
const matchingReferences = references.filter((reference2) => referenceMatchesCandidate(reference2, target.servicePath) && referenceMatchesSelectedAlias(reference2, inputs.routing.selectedBinding));
|
|
6971
7670
|
const referencesForSignals = fallbackReferencesForCandidate(
|
|
6972
7671
|
state,
|
|
6973
7672
|
matchingReferences,
|
|
@@ -6990,12 +7689,12 @@ function fallbackReferencesForCandidate(state, references, fallbackUsed) {
|
|
|
6990
7689
|
}
|
|
6991
7690
|
function uniqueFallbackReferences(references) {
|
|
6992
7691
|
const seen = /* @__PURE__ */ new Set();
|
|
6993
|
-
return references.filter((
|
|
7692
|
+
return references.filter((reference2) => {
|
|
6994
7693
|
const signature = [
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
6998
|
-
|
|
7694
|
+
reference2.sourceKind,
|
|
7695
|
+
reference2.alias,
|
|
7696
|
+
reference2.destination,
|
|
7697
|
+
reference2.servicePath
|
|
6999
7698
|
].join("\0");
|
|
7000
7699
|
if (seen.has(signature)) return false;
|
|
7001
7700
|
seen.add(signature);
|
|
@@ -7067,15 +7766,15 @@ function applyReferenceSignal(state, inputs, references, kind) {
|
|
|
7067
7766
|
const original = inputs.original[kind];
|
|
7068
7767
|
const effective = inputs.effective[kind];
|
|
7069
7768
|
if (!original || extractPlaceholders(original).length === 0) return;
|
|
7070
|
-
const values = references.flatMap((
|
|
7071
|
-
const concrete = kind === "alias" ?
|
|
7072
|
-
return isConcrete(concrete) ? [{ reference, concrete }] : [];
|
|
7769
|
+
const values = references.flatMap((reference2) => {
|
|
7770
|
+
const concrete = kind === "alias" ? reference2.alias : reference2.destination;
|
|
7771
|
+
return isConcrete(concrete) ? [{ reference: reference2, concrete }] : [];
|
|
7073
7772
|
});
|
|
7074
7773
|
if (effective && extractPlaceholders(effective).length === 0 && values.length > 0 && !values.some(({ concrete }) => concrete === effective)) {
|
|
7075
7774
|
reject(state, `${kind}_contradicts_runtime_substitution`);
|
|
7076
7775
|
}
|
|
7077
7776
|
let matchedSignal = false;
|
|
7078
|
-
for (const { reference, concrete } of values) {
|
|
7777
|
+
for (const { reference: reference2, concrete } of values) {
|
|
7079
7778
|
const matched = matchRuntimeTemplate(original, concrete);
|
|
7080
7779
|
if (!matched) continue;
|
|
7081
7780
|
matchedSignal = true;
|
|
@@ -7084,7 +7783,7 @@ function applyReferenceSignal(state, inputs, references, kind) {
|
|
|
7084
7783
|
state,
|
|
7085
7784
|
key,
|
|
7086
7785
|
value,
|
|
7087
|
-
dynamicReferenceProvenance(
|
|
7786
|
+
dynamicReferenceProvenance(reference2, kind, original, value)
|
|
7088
7787
|
);
|
|
7089
7788
|
}
|
|
7090
7789
|
}
|
|
@@ -7278,13 +7977,13 @@ function variableOrder(templates, required) {
|
|
|
7278
7977
|
].flatMap((value) => extractPlaceholders(value));
|
|
7279
7978
|
return [.../* @__PURE__ */ new Set([...ordered, ...required])];
|
|
7280
7979
|
}
|
|
7281
|
-
function referenceMatchesCandidate(
|
|
7282
|
-
return matchRuntimeTemplate(
|
|
7980
|
+
function referenceMatchesCandidate(reference2, servicePath) {
|
|
7981
|
+
return matchRuntimeTemplate(reference2.servicePath, servicePath) !== void 0;
|
|
7283
7982
|
}
|
|
7284
|
-
function referenceMatchesSelectedAlias(
|
|
7285
|
-
if (
|
|
7983
|
+
function referenceMatchesSelectedAlias(reference2, selected) {
|
|
7984
|
+
if (reference2.selection !== "selected_binding_require") return true;
|
|
7286
7985
|
const template = selected?.aliasExpr ?? selected?.alias;
|
|
7287
|
-
return matchRuntimeTemplate(template,
|
|
7986
|
+
return matchRuntimeTemplate(template, reference2.alias) !== void 0;
|
|
7288
7987
|
}
|
|
7289
7988
|
function cliFor(variables, order) {
|
|
7290
7989
|
return order.filter((key) => variables[key] !== void 0).map((key) => `--var ${shellArgument(`${key}=${variables[key]}`)}`).join(" ");
|
|
@@ -7582,7 +8281,7 @@ function baseTraceEvidence(row, call, persistedEvidence, contextualEvidence2) {
|
|
|
7582
8281
|
};
|
|
7583
8282
|
}
|
|
7584
8283
|
function runtimeResolution(db, row, evidence, options, workspaceId, contextualState) {
|
|
7585
|
-
const
|
|
8284
|
+
const dynamicMode2 = options.dynamicMode ?? "strict";
|
|
7586
8285
|
const candidateCap = positiveCandidateCap(options.maxDynamicCandidates);
|
|
7587
8286
|
const boundedEvidence = boundCandidateLikeEvidence(evidence, candidateCap);
|
|
7588
8287
|
if (!isDynamicRemoteOperationEdge(row, evidence))
|
|
@@ -7596,7 +8295,7 @@ function runtimeResolution(db, row, evidence, options, workspaceId, contextualSt
|
|
|
7596
8295
|
db,
|
|
7597
8296
|
substituted,
|
|
7598
8297
|
workspaceId,
|
|
7599
|
-
|
|
8298
|
+
dynamicMode2,
|
|
7600
8299
|
candidateCap
|
|
7601
8300
|
);
|
|
7602
8301
|
const enriched = boundDynamicEvidence(
|
|
@@ -7608,7 +8307,7 @@ function runtimeResolution(db, row, evidence, options, workspaceId, contextualSt
|
|
|
7608
8307
|
row,
|
|
7609
8308
|
enriched,
|
|
7610
8309
|
analysis,
|
|
7611
|
-
|
|
8310
|
+
dynamicMode2,
|
|
7612
8311
|
appliedRuntimeValues,
|
|
7613
8312
|
contextualState
|
|
7614
8313
|
);
|
|
@@ -7626,10 +8325,10 @@ function runtimeResolution(db, row, evidence, options, workspaceId, contextualSt
|
|
|
7626
8325
|
}
|
|
7627
8326
|
return resolveSuppliedRuntimeOperation(db, row, enriched, workspaceId, contextualState);
|
|
7628
8327
|
}
|
|
7629
|
-
function analyzedRuntimeResolution(row, evidence, analysis,
|
|
8328
|
+
function analyzedRuntimeResolution(row, evidence, analysis, dynamicMode2, appliedRuntimeValues, contextualState) {
|
|
7630
8329
|
if (analysis && analysis.viableCandidateCount === 0 && Object.keys(analysis.appliedSuppliedVariables).length > 0)
|
|
7631
8330
|
return noCandidateRuntimeResolution(row, evidence, contextualState);
|
|
7632
|
-
const inferred =
|
|
8331
|
+
const inferred = dynamicMode2 === "infer" ? inferredTarget(analysis) : void 0;
|
|
7633
8332
|
if (inferred && !isStructuralContextualBlocker(contextualState))
|
|
7634
8333
|
return resolvedRuntimeResolution(row, evidence, inferred, inferred.reasons);
|
|
7635
8334
|
if (analysis && analysis.missingVariables.length > 0 && appliedRuntimeValues) {
|
|
@@ -8110,19 +8809,25 @@ function positiveCandidateCap(value) {
|
|
|
8110
8809
|
function dynamicCandidateBranches(depth, call, evidence) {
|
|
8111
8810
|
const exploration = objectRecord2(evidence.dynamicTargetExploration);
|
|
8112
8811
|
return recordArray4(evidence.dynamicTargetCandidateSuggestions).map((candidate) => ({
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8812
|
+
edge: {
|
|
8813
|
+
step: depth,
|
|
8814
|
+
type: "dynamic_candidate_branch",
|
|
8815
|
+
from: `${call.repoName}:${call.source_file}:${call.source_line}`,
|
|
8816
|
+
to: `${String(candidate.servicePath ?? "")}${String(candidate.operationPath ?? "")}`,
|
|
8817
|
+
evidence: {
|
|
8818
|
+
...candidate,
|
|
8819
|
+
exploratory: true,
|
|
8820
|
+
dynamicMode: String(exploration.mode ?? "candidates"),
|
|
8821
|
+
selected: false,
|
|
8822
|
+
omittedCandidateCount: numericValue4(exploration.omittedCandidateCount)
|
|
8823
|
+
},
|
|
8824
|
+
confidence: numericValue4(candidate.score),
|
|
8825
|
+
unresolvedReason: "Exploratory dynamic target candidate; provide runtime variables to select it"
|
|
8123
8826
|
},
|
|
8124
|
-
|
|
8125
|
-
|
|
8827
|
+
operationId: optionalNumber(candidate.candidateOperationId),
|
|
8828
|
+
repositoryId: optionalNumber(candidate.repoId),
|
|
8829
|
+
servicePath: optionalString(candidate.servicePath),
|
|
8830
|
+
operationPath: optionalString(candidate.operationPath)
|
|
8126
8831
|
}));
|
|
8127
8832
|
}
|
|
8128
8833
|
function objectRecord2(value) {
|
|
@@ -8134,6 +8839,12 @@ function recordArray4(value) {
|
|
|
8134
8839
|
function numericValue4(value) {
|
|
8135
8840
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
8136
8841
|
}
|
|
8842
|
+
function optionalNumber(value) {
|
|
8843
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
8844
|
+
}
|
|
8845
|
+
function optionalString(value) {
|
|
8846
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
8847
|
+
}
|
|
8137
8848
|
|
|
8138
8849
|
// src/trace/002-trace-diagnostics.ts
|
|
8139
8850
|
function loadTraceDiagnostics(db, repoId, includeWorkspaceDiagnostics, workspaceId) {
|
|
@@ -8455,155 +9166,377 @@ function numberValue9(value) {
|
|
|
8455
9166
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
8456
9167
|
}
|
|
8457
9168
|
|
|
8458
|
-
// src/trace/
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
|
|
9169
|
+
// src/trace/010-traversal-scope.ts
|
|
9170
|
+
import { createHash as createHash2 } from "crypto";
|
|
9171
|
+
function compareBinary(left, right) {
|
|
9172
|
+
const leftPoints = [...left];
|
|
9173
|
+
const rightPoints = [...right];
|
|
9174
|
+
const length = Math.min(leftPoints.length, rightPoints.length);
|
|
9175
|
+
for (let index = 0; index < length; index += 1) {
|
|
9176
|
+
const leftPoint = leftPoints[index]?.codePointAt(0) ?? 0;
|
|
9177
|
+
const rightPoint = rightPoints[index]?.codePointAt(0) ?? 0;
|
|
9178
|
+
if (leftPoint !== rightPoint) return leftPoint < rightPoint ? -1 : 1;
|
|
9179
|
+
}
|
|
9180
|
+
return leftPoints.length - rightPoints.length;
|
|
9181
|
+
}
|
|
9182
|
+
function resolveTraversalWorkspaceId(db, requestedWorkspaceId, repoId) {
|
|
9183
|
+
if (requestedWorkspaceId !== void 0) return requestedWorkspaceId;
|
|
9184
|
+
if (repoId !== void 0) {
|
|
9185
|
+
const row = db.prepare(
|
|
9186
|
+
"SELECT workspace_id workspaceId FROM repositories WHERE id=?"
|
|
9187
|
+
).get(repoId);
|
|
9188
|
+
return typeof row?.workspaceId === "number" ? row.workspaceId : void 0;
|
|
9189
|
+
}
|
|
9190
|
+
const rows2 = db.prepare(`SELECT DISTINCT w.id workspaceId FROM workspaces w
|
|
9191
|
+
JOIN repositories r ON r.workspace_id=w.id ORDER BY w.id LIMIT 2`).all();
|
|
9192
|
+
return rows2.length === 1 && typeof rows2[0]?.workspaceId === "number" ? rows2[0].workspaceId : void 0;
|
|
9193
|
+
}
|
|
9194
|
+
function structuralScopeKey(workspaceId, repoId, files, symbolIds) {
|
|
9195
|
+
return JSON.stringify([
|
|
9196
|
+
workspaceId ?? null,
|
|
9197
|
+
repoId ?? null,
|
|
9198
|
+
files ? [...files].sort(compareBinary) : null,
|
|
9199
|
+
symbolIds ? [...symbolIds].sort((left, right) => left - right) : null
|
|
9200
|
+
]);
|
|
8465
9201
|
}
|
|
8466
|
-
function
|
|
8467
|
-
const
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
)
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
9202
|
+
function canonicalContextFingerprint(context) {
|
|
9203
|
+
const entries = [...(context ?? /* @__PURE__ */ new Map()).entries()].map(([name, binding]) => `${JSON.stringify(name)}:${canonicalValue(binding)}`).sort(compareBinary);
|
|
9204
|
+
return createHash2("sha256").update(`[${entries.join(",")}]`).digest("hex");
|
|
9205
|
+
}
|
|
9206
|
+
function evaluationScopeKey(structuralKey, context) {
|
|
9207
|
+
return JSON.stringify([structuralKey, canonicalContextFingerprint(context)]);
|
|
9208
|
+
}
|
|
9209
|
+
var TraversalScopeScheduler = class {
|
|
9210
|
+
#scheduled = /* @__PURE__ */ new Set();
|
|
9211
|
+
#expanded = /* @__PURE__ */ new Set();
|
|
9212
|
+
#structuralByEvaluation = /* @__PURE__ */ new Map();
|
|
9213
|
+
#childrenByEvaluation = /* @__PURE__ */ new Map();
|
|
9214
|
+
schedule(identity, parent) {
|
|
9215
|
+
const state = scopeState(identity, parent);
|
|
9216
|
+
this.#rememberState(state);
|
|
9217
|
+
if (parent) this.#rememberState(parent);
|
|
9218
|
+
const cycle = parent ? parent.ancestry.has(state.structuralKey) || this.#reachesStructural(state.evaluationKey, parent.structuralKey) : false;
|
|
9219
|
+
if (parent) this.#recordEdge(parent.evaluationKey, state.evaluationKey);
|
|
9220
|
+
if (cycle)
|
|
9221
|
+
return { kind: "cycle", state, alreadyExpanded: this.#expanded.has(state.evaluationKey) };
|
|
9222
|
+
if (this.#scheduled.has(state.evaluationKey))
|
|
9223
|
+
return { kind: "converged", state, alreadyExpanded: this.#expanded.has(state.evaluationKey) };
|
|
9224
|
+
this.#scheduled.add(state.evaluationKey);
|
|
9225
|
+
return { kind: "scheduled", state, alreadyExpanded: false };
|
|
9226
|
+
}
|
|
9227
|
+
markExpanded(state) {
|
|
9228
|
+
if (this.#expanded.has(state.evaluationKey)) return false;
|
|
9229
|
+
this.#expanded.add(state.evaluationKey);
|
|
9230
|
+
return true;
|
|
8495
9231
|
}
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
9232
|
+
#rememberState(state) {
|
|
9233
|
+
this.#structuralByEvaluation.set(state.evaluationKey, state.structuralKey);
|
|
9234
|
+
}
|
|
9235
|
+
#recordEdge(parent, child) {
|
|
9236
|
+
const children = this.#childrenByEvaluation.get(parent) ?? /* @__PURE__ */ new Set();
|
|
9237
|
+
children.add(child);
|
|
9238
|
+
this.#childrenByEvaluation.set(parent, children);
|
|
9239
|
+
}
|
|
9240
|
+
#reachesStructural(start, targetStructuralKey) {
|
|
9241
|
+
const pending = [start];
|
|
9242
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9243
|
+
let cursor = 0;
|
|
9244
|
+
while (cursor < pending.length) {
|
|
9245
|
+
const current = pending[cursor];
|
|
9246
|
+
cursor += 1;
|
|
9247
|
+
if (seen.has(current)) continue;
|
|
9248
|
+
seen.add(current);
|
|
9249
|
+
if (this.#structuralByEvaluation.get(current) === targetStructuralKey)
|
|
9250
|
+
return true;
|
|
9251
|
+
pending.push(...this.#childrenByEvaluation.get(current) ?? []);
|
|
9252
|
+
}
|
|
9253
|
+
return false;
|
|
8501
9254
|
}
|
|
8502
|
-
|
|
9255
|
+
};
|
|
9256
|
+
function scopeState(identity, parent) {
|
|
9257
|
+
const structuralKey = structuralScopeKey(
|
|
9258
|
+
identity.workspaceId,
|
|
9259
|
+
identity.repoId,
|
|
9260
|
+
identity.files,
|
|
9261
|
+
identity.symbolIds
|
|
9262
|
+
);
|
|
9263
|
+
const ancestry = new Set(parent?.ancestry ?? []);
|
|
9264
|
+
ancestry.add(structuralKey);
|
|
9265
|
+
return {
|
|
9266
|
+
structuralKey,
|
|
9267
|
+
evaluationKey: evaluationScopeKey(structuralKey, identity.context),
|
|
9268
|
+
ancestry
|
|
9269
|
+
};
|
|
9270
|
+
}
|
|
9271
|
+
function canonicalValue(value) {
|
|
9272
|
+
if (value === null) return "null";
|
|
9273
|
+
if (value === void 0) return "undefined";
|
|
9274
|
+
if (typeof value === "string") return `string:${JSON.stringify(value)}`;
|
|
9275
|
+
if (typeof value === "boolean") return `boolean:${String(value)}`;
|
|
9276
|
+
if (typeof value === "number") return canonicalNumber(value);
|
|
9277
|
+
if (typeof value === "bigint") return `bigint:${String(value)}`;
|
|
9278
|
+
if (Array.isArray(value))
|
|
9279
|
+
return `array:[${value.map(canonicalValue).join(",")}]`;
|
|
9280
|
+
if (!isRecord8(value)) return `unsupported:${typeof value}`;
|
|
9281
|
+
const entries = Object.entries(value).sort(([left], [right]) => compareBinary(left, right));
|
|
9282
|
+
return `object:{${entries.map(([key, child]) => `${JSON.stringify(key)}:${canonicalValue(child)}`).join(",")}}`;
|
|
9283
|
+
}
|
|
9284
|
+
function canonicalNumber(value) {
|
|
9285
|
+
if (Number.isNaN(value)) return "number:NaN";
|
|
9286
|
+
if (value === Number.POSITIVE_INFINITY) return "number:Infinity";
|
|
9287
|
+
if (value === Number.NEGATIVE_INFINITY) return "number:-Infinity";
|
|
9288
|
+
if (Object.is(value, -0)) return "number:-0";
|
|
9289
|
+
return `number:${String(value)}`;
|
|
9290
|
+
}
|
|
9291
|
+
function isRecord8(value) {
|
|
9292
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
8503
9293
|
}
|
|
8504
|
-
|
|
8505
|
-
|
|
9294
|
+
|
|
9295
|
+
// src/trace/011-event-subscriber-traversal.ts
|
|
9296
|
+
function planEventSubscriberTransitions(db, query, scheduler, parent, depth, maxDepth) {
|
|
9297
|
+
return loadEventSubscriberTransitions(db, query).map((transition) => {
|
|
9298
|
+
const handler = transition.handler;
|
|
9299
|
+
if (!handler) return plannedTransition(transition, "not_resolved");
|
|
9300
|
+
if (depth >= maxDepth)
|
|
9301
|
+
return plannedTransition(transition, "depth_limited");
|
|
9302
|
+
const state = scheduler.schedule({
|
|
9303
|
+
workspaceId: query.workspaceId,
|
|
9304
|
+
repoId: handler.repoId,
|
|
9305
|
+
files: /* @__PURE__ */ new Set([handler.sourceFile]),
|
|
9306
|
+
symbolIds: /* @__PURE__ */ new Set([handler.symbolId]),
|
|
9307
|
+
context: /* @__PURE__ */ new Map()
|
|
9308
|
+
}, parent);
|
|
9309
|
+
const bodyExpansion = state.kind === "scheduled" ? "scheduled" : state.kind === "cycle" ? "cycle_blocked" : state.alreadyExpanded ? "already_expanded" : "already_scheduled";
|
|
9310
|
+
return plannedTransition(transition, bodyExpansion, state.state);
|
|
9311
|
+
});
|
|
8506
9312
|
}
|
|
8507
|
-
function
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
selectorMatched: false,
|
|
8515
|
-
startDiagnostics: [selectorRepoNotFoundDiagnostic(start.repo)]
|
|
9313
|
+
function plannedTransition(transition, bodyExpansion, state) {
|
|
9314
|
+
return {
|
|
9315
|
+
transition,
|
|
9316
|
+
node: eventSubscriberNode(transition),
|
|
9317
|
+
evidence: eventTransitionEvidence(transition, bodyExpansion),
|
|
9318
|
+
bodyExpansion,
|
|
9319
|
+
state
|
|
8516
9320
|
};
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
9321
|
+
}
|
|
9322
|
+
function eventSubscriberNode(transition) {
|
|
9323
|
+
const handler = transition.handler;
|
|
9324
|
+
if (!handler) return {
|
|
9325
|
+
id: `event_subscription:${transition.graphEdgeId}`,
|
|
9326
|
+
kind: transition.targetKind,
|
|
9327
|
+
label: `${transition.targetKind}:${transition.targetId}`,
|
|
9328
|
+
graphEdgeId: transition.graphEdgeId
|
|
8520
9329
|
};
|
|
8521
|
-
const
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
9330
|
+
const fileName = handler.sourceFile.split("/").at(-1) ?? handler.sourceFile;
|
|
9331
|
+
return {
|
|
9332
|
+
id: `symbol:${handler.symbolId}`,
|
|
9333
|
+
kind: "symbol",
|
|
9334
|
+
label: `${fileName}:${handler.qualifiedName}`,
|
|
9335
|
+
symbolId: handler.symbolId,
|
|
9336
|
+
symbolName: handler.qualifiedName,
|
|
9337
|
+
qualifiedName: handler.qualifiedName,
|
|
9338
|
+
sourceFile: handler.sourceFile,
|
|
9339
|
+
startLine: handler.sourceLine,
|
|
9340
|
+
endLine: handler.endLine,
|
|
9341
|
+
repoName: handler.repoName,
|
|
9342
|
+
repoId: handler.repoId
|
|
9343
|
+
};
|
|
9344
|
+
}
|
|
9345
|
+
function eventTransitionEvidence(transition, bodyExpansion) {
|
|
9346
|
+
return {
|
|
9347
|
+
graphEdgeId: transition.graphEdgeId,
|
|
9348
|
+
graphGeneration: transition.graphGeneration,
|
|
9349
|
+
subscribeCallId: transition.subscribeCallId,
|
|
9350
|
+
symbolCallId: transition.symbolCallId,
|
|
9351
|
+
eventName: transition.eventName,
|
|
9352
|
+
matchStrategy: "workspace_exact_event_name",
|
|
9353
|
+
dispatchCertainty: "static_name_only",
|
|
9354
|
+
associationBasis: transition.associationBasis,
|
|
9355
|
+
dispatchScope: transition.dispatchScope,
|
|
9356
|
+
roleSiteMatchCount: transition.roleSiteMatchCount,
|
|
9357
|
+
callRole: transition.callRole,
|
|
9358
|
+
factOrigin: transition.factOrigin,
|
|
9359
|
+
repositoryId: transition.subscriptionRepoId,
|
|
9360
|
+
repositoryName: transition.subscriptionRepoName,
|
|
9361
|
+
sourceFile: transition.sourceFile,
|
|
9362
|
+
sourceLine: transition.sourceLine,
|
|
9363
|
+
callSiteStartOffset: transition.callSiteStartOffset,
|
|
9364
|
+
callSiteEndOffset: transition.callSiteEndOffset,
|
|
9365
|
+
wrapperFunction: transition.wrapperFunction,
|
|
9366
|
+
handlerSymbolId: transition.handler?.symbolId,
|
|
9367
|
+
handlerSourceFile: transition.handler?.sourceFile,
|
|
9368
|
+
handlerSourceLine: transition.handler?.sourceLine,
|
|
9369
|
+
associationStatus: transition.associationStatus ?? transition.status,
|
|
9370
|
+
symbolCallResolutionStatus: transition.symbolCallResolutionStatus,
|
|
9371
|
+
resolutionStatus: transition.status,
|
|
9372
|
+
resolutionStrategy: transition.resolutionStrategy,
|
|
9373
|
+
candidateCount: transition.candidateCount,
|
|
9374
|
+
symbolCallUnresolvedReason: transition.symbolCallUnresolvedReason,
|
|
9375
|
+
omittedSymbolCallUnresolvedReasonCharacterCount: transition.omittedSymbolCallUnresolvedReasonCharacterCount,
|
|
9376
|
+
reasonCode: transition.reasonCode,
|
|
9377
|
+
bodyExpansion,
|
|
9378
|
+
cycle: bodyExpansion === "cycle_blocked" || void 0,
|
|
9379
|
+
cycleReason: bodyExpansion === "cycle_blocked" ? "structural_ancestry_cycle" : void 0
|
|
9380
|
+
};
|
|
9381
|
+
}
|
|
9382
|
+
function loadEventSubscriberTransitions(db, query) {
|
|
9383
|
+
const rows2 = db.prepare(`SELECT ge.id graphEdgeId,ge.generation graphGeneration,
|
|
9384
|
+
ge.from_id eventName,ge.status,ge.to_kind targetKind,ge.to_id targetId,
|
|
9385
|
+
ge.confidence,ge.unresolved_reason unresolvedReason,ge.evidence_json evidenceJson,
|
|
9386
|
+
subscribe.id subscribeCallId,subscribe.repo_id subscriptionRepoId,
|
|
9387
|
+
subscribe.source_file sourceFile,subscribe.source_line sourceLine,
|
|
9388
|
+
subscribe.call_site_start_offset callSiteStartOffset,
|
|
9389
|
+
subscribe.call_site_end_offset callSiteEndOffset,
|
|
9390
|
+
subscription_repo.name subscriptionRepoName,
|
|
9391
|
+
handler.id handlerSymbolId,handler.kind handlerKind,
|
|
9392
|
+
handler.qualified_name handlerQualifiedName,handler.source_file handlerSourceFile,
|
|
9393
|
+
handler.start_line handlerSourceLine,handler.end_line handlerEndLine,
|
|
9394
|
+
handler.start_offset handlerStartOffset,handler.end_offset handlerEndOffset,
|
|
9395
|
+
handler_repo.id handlerRepoId,handler_repo.name handlerRepoName,
|
|
9396
|
+
handler_repo.package_name handlerPackageName
|
|
9397
|
+
FROM graph_edges ge
|
|
9398
|
+
LEFT JOIN outbound_calls subscribe
|
|
9399
|
+
ON subscribe.id=CAST(json_extract(ge.evidence_json,'$.subscribeCallId') AS INTEGER)
|
|
9400
|
+
LEFT JOIN repositories subscription_repo
|
|
9401
|
+
ON subscription_repo.id=subscribe.repo_id
|
|
9402
|
+
AND subscription_repo.workspace_id=ge.workspace_id
|
|
9403
|
+
LEFT JOIN symbols handler
|
|
9404
|
+
ON ge.to_kind='symbol' AND handler.id=CAST(ge.to_id AS INTEGER)
|
|
9405
|
+
LEFT JOIN repositories handler_repo
|
|
9406
|
+
ON handler_repo.id=handler.repo_id AND handler_repo.workspace_id=ge.workspace_id
|
|
9407
|
+
WHERE ge.workspace_id=? AND ge.generation=?
|
|
9408
|
+
AND ge.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY' AND ge.from_kind='event'
|
|
9409
|
+
AND ge.from_id COLLATE BINARY=? COLLATE BINARY
|
|
9410
|
+
ORDER BY COALESCE(subscription_repo.name,'') COLLATE BINARY,
|
|
9411
|
+
COALESCE(subscription_repo.id,0),COALESCE(subscribe.source_file,'') COLLATE BINARY,
|
|
9412
|
+
subscribe.call_site_start_offset,subscribe.call_site_end_offset,ge.id`).all(
|
|
9413
|
+
query.workspaceId,
|
|
9414
|
+
query.graphGeneration,
|
|
9415
|
+
query.eventName
|
|
8528
9416
|
);
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
9417
|
+
return rows2.flatMap((row) => {
|
|
9418
|
+
const transition = transitionFromRow(row);
|
|
9419
|
+
return transition ? [transition] : [];
|
|
9420
|
+
});
|
|
9421
|
+
}
|
|
9422
|
+
function transitionFromRow(row) {
|
|
9423
|
+
const graphEdgeId = numberValue10(row.graphEdgeId);
|
|
9424
|
+
const graphGeneration2 = numberValue10(row.graphGeneration);
|
|
9425
|
+
const eventName = stringValue13(row.eventName);
|
|
9426
|
+
const targetId = stringValue13(row.targetId);
|
|
9427
|
+
if (graphEdgeId === void 0 || graphGeneration2 === void 0 || eventName === void 0 || targetId === void 0) return void 0;
|
|
9428
|
+
const evidence = parseEvidence(row.evidenceJson);
|
|
9429
|
+
const handler = symbolTarget(row);
|
|
9430
|
+
const status = transitionStatus(stringValue13(row.status), handler);
|
|
9431
|
+
const reasonCode = status === "resolved" ? void 0 : stringValue13(evidence.reasonCode) ?? missingTargetReason(row, handler);
|
|
9432
|
+
return {
|
|
9433
|
+
graphEdgeId,
|
|
9434
|
+
graphGeneration: graphGeneration2,
|
|
9435
|
+
eventName,
|
|
9436
|
+
status,
|
|
9437
|
+
targetKind: targetKind(row.targetKind),
|
|
9438
|
+
targetId,
|
|
9439
|
+
confidence: numberValue10(row.confidence) ?? 0,
|
|
9440
|
+
unresolvedReason: status === "resolved" ? void 0 : stringValue13(row.unresolvedReason) ?? reasonCode,
|
|
9441
|
+
reasonCode,
|
|
9442
|
+
...associationEvidence(row, evidence),
|
|
9443
|
+
handler
|
|
9444
|
+
};
|
|
9445
|
+
}
|
|
9446
|
+
function associationEvidence(row, evidence) {
|
|
9447
|
+
const symbolCallUnresolvedReason = stringValue13(
|
|
9448
|
+
evidence.symbolCallUnresolvedReason
|
|
8535
9449
|
);
|
|
8536
|
-
if (start.servicePath && !start.operation && !start.operationPath && !start.handler)
|
|
8537
|
-
return { repo, selectorMatched: false };
|
|
8538
9450
|
return {
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
|
|
9451
|
+
subscribeCallId: numberValue10(row.subscribeCallId),
|
|
9452
|
+
symbolCallId: numberValue10(evidence.symbolCallId),
|
|
9453
|
+
roleSiteMatchCount: nonNegativeCount(evidence.roleSiteMatchCount),
|
|
9454
|
+
callRole: stringValue13(evidence.callRole),
|
|
9455
|
+
factOrigin: stringValue13(evidence.factOrigin),
|
|
9456
|
+
associationBasis: stringValue13(evidence.associationBasis),
|
|
9457
|
+
dispatchScope: stringValue13(evidence.dispatchScope),
|
|
9458
|
+
subscriptionRepoId: numberValue10(row.subscriptionRepoId),
|
|
9459
|
+
subscriptionRepoName: stringValue13(row.subscriptionRepoName),
|
|
9460
|
+
sourceFile: stringValue13(row.sourceFile),
|
|
9461
|
+
sourceLine: numberValue10(row.sourceLine),
|
|
9462
|
+
callSiteStartOffset: numberValue10(row.callSiteStartOffset),
|
|
9463
|
+
callSiteEndOffset: numberValue10(row.callSiteEndOffset),
|
|
9464
|
+
wrapperFunction: stringValue13(evidence.wrapperFunction),
|
|
9465
|
+
resolutionStrategy: stringValue13(evidence.resolutionStrategy),
|
|
9466
|
+
associationStatus: stringValue13(evidence.associationStatus),
|
|
9467
|
+
symbolCallResolutionStatus: stringValue13(evidence.symbolCallResolutionStatus),
|
|
9468
|
+
candidateCount: nonNegativeCount(evidence.candidateCount),
|
|
9469
|
+
symbolCallUnresolvedReason,
|
|
9470
|
+
omittedSymbolCallUnresolvedReasonCharacterCount: symbolCallUnresolvedReason ? nonNegativeCount(evidence.omittedSymbolCallUnresolvedReasonCharacterCount) : void 0
|
|
9471
|
+
};
|
|
9472
|
+
}
|
|
9473
|
+
function symbolTarget(row) {
|
|
9474
|
+
const symbolId = numberValue10(row.handlerSymbolId);
|
|
9475
|
+
const repoId = numberValue10(row.handlerRepoId);
|
|
9476
|
+
const repoName = stringValue13(row.handlerRepoName);
|
|
9477
|
+
const sourceFile = stringValue13(row.handlerSourceFile);
|
|
9478
|
+
const sourceLine2 = numberValue10(row.handlerSourceLine);
|
|
9479
|
+
const endLine = numberValue10(row.handlerEndLine);
|
|
9480
|
+
const kind = stringValue13(row.handlerKind);
|
|
9481
|
+
const qualifiedName = stringValue13(row.handlerQualifiedName);
|
|
9482
|
+
if (symbolId === void 0 || repoId === void 0 || !repoName || !sourceFile || sourceLine2 === void 0 || endLine === void 0 || !kind || !qualifiedName)
|
|
9483
|
+
return void 0;
|
|
9484
|
+
return {
|
|
9485
|
+
symbolId,
|
|
9486
|
+
repoId,
|
|
9487
|
+
repoName,
|
|
9488
|
+
sourceFile,
|
|
9489
|
+
sourceLine: sourceLine2,
|
|
9490
|
+
endLine,
|
|
9491
|
+
kind,
|
|
9492
|
+
qualifiedName,
|
|
9493
|
+
packageName: stringValue13(row.handlerPackageName),
|
|
9494
|
+
startOffset: numberValue10(row.handlerStartOffset),
|
|
9495
|
+
endOffset: numberValue10(row.handlerEndOffset)
|
|
8546
9496
|
};
|
|
8547
9497
|
}
|
|
8548
|
-
function
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?`
|
|
8552
|
-
).get(operationId);
|
|
8553
|
-
if (!op) return /* @__PURE__ */ new Set();
|
|
8554
|
-
const operation = normalizeOperation2(op.operationPath ?? op.operationName);
|
|
8555
|
-
const rows2 = db.prepare(
|
|
8556
|
-
`SELECT DISTINCT hc.source_file sourceFile,sym.id symbolId FROM handler_classes hc
|
|
8557
|
-
JOIN handler_methods hm ON hm.handler_class_id=hc.id
|
|
8558
|
-
LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
|
|
8559
|
-
AND sym.source_file=hc.source_file
|
|
8560
|
-
AND sym.qualified_name=hc.class_name || '.' || hm.method_name
|
|
8561
|
-
AND sym.start_line=hm.source_line
|
|
8562
|
-
WHERE hc.repo_id=?
|
|
8563
|
-
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
|
|
8564
|
-
CASE WHEN hm.decorator_kind='Event' THEN 'event'
|
|
8565
|
-
WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
|
|
8566
|
-
ELSE 'unsupported' END)='operation'
|
|
8567
|
-
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
|
|
8568
|
-
CASE WHEN hm.decorator_kind IN ('Action','Func','On')
|
|
8569
|
-
THEN 1 ELSE 0 END)=1
|
|
8570
|
-
AND (hm.decorator_value=? OR hm.method_name=? OR hm.decorator_value=?)`
|
|
8571
|
-
).all(op.repoId, operation, operation, op.operationName);
|
|
8572
|
-
return new Set(rows2.map((row) => row.sourceFile).filter(Boolean));
|
|
9498
|
+
function transitionStatus(value, handler) {
|
|
9499
|
+
if (value === "resolved" && handler) return "resolved";
|
|
9500
|
+
return value === "ambiguous" ? "ambiguous" : "unresolved";
|
|
8573
9501
|
}
|
|
8574
|
-
function
|
|
8575
|
-
return
|
|
9502
|
+
function missingTargetReason(row, handler) {
|
|
9503
|
+
return stringValue13(row.status) === "resolved" && !handler ? "subscription_handler_target_missing" : void 0;
|
|
8576
9504
|
}
|
|
8577
|
-
function
|
|
8578
|
-
|
|
8579
|
-
if (!edge || edge.status !== "resolved") return { files: /* @__PURE__ */ new Set(), edge };
|
|
8580
|
-
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);
|
|
8581
|
-
if (!row || typeof row.symbolId !== "number")
|
|
8582
|
-
return { repoId: row?.repoId, files: /* @__PURE__ */ new Set(), edge };
|
|
8583
|
-
return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []), symbolId: row?.symbolId, edge };
|
|
9505
|
+
function targetKind(value) {
|
|
9506
|
+
return value === "symbol" || value === "symbol_reference" || value === "subscription_handler" ? value : "subscription_handler";
|
|
8584
9507
|
}
|
|
8585
|
-
function
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
9508
|
+
function parseEvidence(value) {
|
|
9509
|
+
try {
|
|
9510
|
+
const parsed = JSON.parse(String(value ?? "{}"));
|
|
9511
|
+
return isRecord9(parsed) ? parsed : {};
|
|
9512
|
+
} catch {
|
|
9513
|
+
return {};
|
|
9514
|
+
}
|
|
8589
9515
|
}
|
|
8590
|
-
function
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
return String(call.call_type);
|
|
9516
|
+
function nonNegativeCount(value) {
|
|
9517
|
+
const count2 = numberValue10(value);
|
|
9518
|
+
return count2 === void 0 ? 0 : Math.max(0, Math.floor(count2));
|
|
8594
9519
|
}
|
|
8595
|
-
function
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
return
|
|
9520
|
+
function numberValue10(value) {
|
|
9521
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
9522
|
+
}
|
|
9523
|
+
function stringValue13(value) {
|
|
9524
|
+
return typeof value === "string" ? value : void 0;
|
|
8600
9525
|
}
|
|
9526
|
+
function isRecord9(value) {
|
|
9527
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
9528
|
+
}
|
|
9529
|
+
|
|
9530
|
+
// src/trace/012-trace-graph-lookups.ts
|
|
8601
9531
|
function graphForCalls(db, callIds) {
|
|
8602
9532
|
const map = /* @__PURE__ */ new Map();
|
|
8603
9533
|
if (callIds.length === 0) return map;
|
|
8604
|
-
const rows2 = db.prepare(
|
|
8605
|
-
|
|
8606
|
-
|
|
9534
|
+
const rows2 = db.prepare(`SELECT * FROM graph_edges
|
|
9535
|
+
WHERE from_kind='call'
|
|
9536
|
+
AND from_id IN (${callIds.map(() => "?").join(",")})
|
|
9537
|
+
ORDER BY id`).all(
|
|
9538
|
+
...callIds.map(String)
|
|
9539
|
+
);
|
|
8607
9540
|
for (const row of rows2) {
|
|
8608
9541
|
const id = Number(row.from_id);
|
|
8609
9542
|
map.set(id, [...map.get(id) ?? [], row]);
|
|
@@ -8611,92 +9544,487 @@ function graphForCalls(db, callIds) {
|
|
|
8611
9544
|
return map;
|
|
8612
9545
|
}
|
|
8613
9546
|
function symbolNode(db, symbolId) {
|
|
8614
|
-
const row = db.prepare(`SELECT s.id symbolId,s.name symbolName,
|
|
9547
|
+
const row = db.prepare(`SELECT s.id symbolId,s.name symbolName,
|
|
9548
|
+
s.qualified_name qualifiedName,s.source_file sourceFile,
|
|
9549
|
+
s.start_line startLine,s.end_line endLine,r.name repoName,r.id repoId
|
|
9550
|
+
FROM symbols s JOIN repositories r ON r.id=s.repo_id
|
|
9551
|
+
WHERE s.id=?`).get(symbolId);
|
|
8615
9552
|
if (!row) return void 0;
|
|
8616
|
-
const
|
|
8617
|
-
|
|
9553
|
+
const sourceFile = String(row.sourceFile ?? "");
|
|
9554
|
+
const fileName = sourceFile.split("/").at(-1) ?? sourceFile;
|
|
9555
|
+
return {
|
|
9556
|
+
id: `symbol:${symbolId}`,
|
|
9557
|
+
kind: "symbol",
|
|
9558
|
+
label: `${fileName}:${String(row.qualifiedName ?? row.symbolName)}`,
|
|
9559
|
+
...row
|
|
9560
|
+
};
|
|
8618
9561
|
}
|
|
8619
9562
|
function operationNode(db, operationId) {
|
|
8620
|
-
const row = db.prepare(`SELECT o.id operationId,o.operation_name operationName,
|
|
9563
|
+
const row = db.prepare(`SELECT o.id operationId,o.operation_name operationName,
|
|
9564
|
+
o.operation_type operationType,o.operation_path operationPath,
|
|
9565
|
+
o.source_file sourceFile,o.source_line sourceLine,s.id serviceId,
|
|
9566
|
+
s.service_name serviceName,s.qualified_name qualifiedName,
|
|
9567
|
+
s.service_path servicePath,r.id repoId,r.name repoName
|
|
9568
|
+
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id
|
|
9569
|
+
JOIN repositories r ON r.id=s.repo_id WHERE o.id=?`).get(
|
|
9570
|
+
operationId
|
|
9571
|
+
);
|
|
8621
9572
|
if (!row) return void 0;
|
|
8622
|
-
return {
|
|
9573
|
+
return {
|
|
9574
|
+
id: `operation:${operationId}`,
|
|
9575
|
+
kind: "operation",
|
|
9576
|
+
label: `${String(row.repoName)}:${String(row.servicePath)}${String(row.operationPath)}`,
|
|
9577
|
+
...row
|
|
9578
|
+
};
|
|
9579
|
+
}
|
|
9580
|
+
|
|
9581
|
+
// src/trace/013-trace-root-scopes.ts
|
|
9582
|
+
function createTraceRootPlan(db, scheduler, scope, requestedWorkspaceId, includeAsync) {
|
|
9583
|
+
const workspaceId = resolveTraversalWorkspaceId(
|
|
9584
|
+
db,
|
|
9585
|
+
requestedWorkspaceId,
|
|
9586
|
+
scope.repoId
|
|
9587
|
+
);
|
|
9588
|
+
if (workspaceId !== void 0) {
|
|
9589
|
+
const lifecycle = currentFactLifecycleDiagnostic(db, workspaceId);
|
|
9590
|
+
if (lifecycle) return {
|
|
9591
|
+
workspaceId,
|
|
9592
|
+
queue: [],
|
|
9593
|
+
pendingRoots: [],
|
|
9594
|
+
diagnostic: lifecycle
|
|
9595
|
+
};
|
|
9596
|
+
}
|
|
9597
|
+
if (!scope.selectorMatched)
|
|
9598
|
+
return { workspaceId, queue: [], pendingRoots: [] };
|
|
9599
|
+
if (workspaceId === void 0) return {
|
|
9600
|
+
workspaceId,
|
|
9601
|
+
queue: [],
|
|
9602
|
+
pendingRoots: [],
|
|
9603
|
+
diagnostic: workspaceAmbiguityDiagnostic(db)
|
|
9604
|
+
};
|
|
9605
|
+
const pendingRoots = includeAsync ? rootScopes(db, workspaceId, scope) : void 0;
|
|
9606
|
+
if (pendingRoots)
|
|
9607
|
+
return { workspaceId, queue: [], pendingRoots };
|
|
9608
|
+
return {
|
|
9609
|
+
workspaceId,
|
|
9610
|
+
queue: initialQueue(scheduler, workspaceId, scope),
|
|
9611
|
+
pendingRoots: []
|
|
9612
|
+
};
|
|
8623
9613
|
}
|
|
8624
|
-
function
|
|
8625
|
-
|
|
9614
|
+
function nextPendingRoot(pendingRoots, scheduler, workspaceId) {
|
|
9615
|
+
while (pendingRoots.length > 0) {
|
|
9616
|
+
const root = pendingRoots.shift();
|
|
9617
|
+
if (!root) return void 0;
|
|
9618
|
+
const context = /* @__PURE__ */ new Map();
|
|
9619
|
+
const scheduled = scheduler.schedule({
|
|
9620
|
+
workspaceId,
|
|
9621
|
+
repoId: root.repoId,
|
|
9622
|
+
files: root.files,
|
|
9623
|
+
symbolIds: root.symbolIds,
|
|
9624
|
+
context
|
|
9625
|
+
});
|
|
9626
|
+
if (scheduled.kind !== "scheduled") continue;
|
|
9627
|
+
return { ...root, depth: 1, context, state: scheduled.state };
|
|
9628
|
+
}
|
|
9629
|
+
return void 0;
|
|
8626
9630
|
}
|
|
8627
|
-
function
|
|
9631
|
+
function claimPendingRoot(pendingRoots, target) {
|
|
9632
|
+
const index = pendingRoots.findIndex((root) => target.repoId === root.repoId && !root.unownedOnly && setsEqual(root.files, target.files) && setsEqual(root.symbolIds, target.symbolIds));
|
|
9633
|
+
if (index < 0) return false;
|
|
9634
|
+
pendingRoots.splice(index, 1);
|
|
9635
|
+
return true;
|
|
9636
|
+
}
|
|
9637
|
+
function enqueueCausalScope(queue, pendingRoots, scope) {
|
|
9638
|
+
if (scope.context.size === 0 && scope.files && scope.symbolIds)
|
|
9639
|
+
claimPendingRoot(pendingRoots, {
|
|
9640
|
+
repoId: scope.repoId,
|
|
9641
|
+
files: scope.files,
|
|
9642
|
+
symbolIds: scope.symbolIds
|
|
9643
|
+
});
|
|
9644
|
+
queue.push(scope);
|
|
9645
|
+
}
|
|
9646
|
+
function initialQueue(scheduler, workspaceId, scope) {
|
|
9647
|
+
const context = /* @__PURE__ */ new Map();
|
|
9648
|
+
const scheduled = scheduler.schedule({
|
|
9649
|
+
workspaceId,
|
|
9650
|
+
repoId: scope.repoId,
|
|
9651
|
+
files: scope.files,
|
|
9652
|
+
symbolIds: scope.symbolIds,
|
|
9653
|
+
context
|
|
9654
|
+
});
|
|
9655
|
+
return scheduled.kind === "scheduled" ? [{
|
|
9656
|
+
repoId: scope.repoId,
|
|
9657
|
+
files: scope.files,
|
|
9658
|
+
symbolIds: scope.symbolIds,
|
|
9659
|
+
depth: 1,
|
|
9660
|
+
context,
|
|
9661
|
+
state: scheduled.state
|
|
9662
|
+
}] : [];
|
|
9663
|
+
}
|
|
9664
|
+
function rootScopes(db, workspaceId, scope) {
|
|
9665
|
+
if (scope.symbolIds && scope.symbolIds.size === 1) return void 0;
|
|
9666
|
+
const calls = scopedRootCalls(db, workspaceId, scope);
|
|
9667
|
+
if (!hasExactDispatch(db, calls)) return void 0;
|
|
9668
|
+
if (scope.symbolIds && scope.symbolIds.size > 1)
|
|
9669
|
+
return selectedSymbolRoots(db, workspaceId, scope.symbolIds);
|
|
9670
|
+
return callOwnerRoots(calls);
|
|
9671
|
+
}
|
|
9672
|
+
function scopedRootCalls(db, workspaceId, scope) {
|
|
9673
|
+
return rootCalls(db, workspaceId, scope.repoId, scope.files).filter((call) => !scope.symbolIds || typeof call.sourceSymbolId === "number" && scope.symbolIds.has(call.sourceSymbolId));
|
|
9674
|
+
}
|
|
9675
|
+
function callOwnerRoots(calls) {
|
|
9676
|
+
const roots = /* @__PURE__ */ new Map();
|
|
9677
|
+
for (const call of calls) {
|
|
9678
|
+
const [key, root] = callOwnerRoot(call);
|
|
9679
|
+
if (roots.has(key)) continue;
|
|
9680
|
+
roots.set(key, root);
|
|
9681
|
+
}
|
|
9682
|
+
return roots.size > 0 ? [...roots.values()] : void 0;
|
|
9683
|
+
}
|
|
9684
|
+
function callOwnerRoot(call) {
|
|
9685
|
+
const symbolId = call.sourceSymbolId;
|
|
9686
|
+
const owned = typeof symbolId === "number";
|
|
9687
|
+
const key = owned ? `symbol:${symbolId}` : `unowned:${call.repoId}:${call.sourceFile}`;
|
|
9688
|
+
return [key, {
|
|
9689
|
+
repoId: call.repoId,
|
|
9690
|
+
files: /* @__PURE__ */ new Set([call.sourceFile]),
|
|
9691
|
+
symbolIds: owned ? /* @__PURE__ */ new Set([symbolId]) : /* @__PURE__ */ new Set(),
|
|
9692
|
+
unownedOnly: !owned,
|
|
9693
|
+
rootObservationOnly: true
|
|
9694
|
+
}];
|
|
9695
|
+
}
|
|
9696
|
+
function selectedSymbolRoots(db, workspaceId, symbolIds) {
|
|
9697
|
+
const ids = [...symbolIds];
|
|
9698
|
+
const rows2 = db.prepare(`SELECT s.id,s.repo_id repoId,s.source_file sourceFile
|
|
9699
|
+
FROM symbols s JOIN repositories r ON r.id=s.repo_id
|
|
9700
|
+
WHERE r.workspace_id=? AND s.id IN (${ids.map(() => "?").join(",")})
|
|
9701
|
+
ORDER BY r.name COLLATE BINARY,r.id,s.source_file COLLATE BINARY,
|
|
9702
|
+
s.start_offset,s.end_offset,s.id`).all(workspaceId, ...ids);
|
|
9703
|
+
return rows2.flatMap((row) => typeof row.id === "number" && typeof row.repoId === "number" && typeof row.sourceFile === "string" ? [{
|
|
9704
|
+
repoId: row.repoId,
|
|
9705
|
+
files: /* @__PURE__ */ new Set([row.sourceFile]),
|
|
9706
|
+
symbolIds: /* @__PURE__ */ new Set([row.id]),
|
|
9707
|
+
unownedOnly: false,
|
|
9708
|
+
rootObservationOnly: false
|
|
9709
|
+
}] : []);
|
|
9710
|
+
}
|
|
9711
|
+
function setsEqual(left, right) {
|
|
9712
|
+
if (left.size !== right.size) return false;
|
|
9713
|
+
return [...left].every((value) => right.has(value));
|
|
9714
|
+
}
|
|
9715
|
+
function rootCalls(db, workspaceId, repoId, files) {
|
|
9716
|
+
const rows2 = db.prepare(`SELECT c.id,c.repo_id repoId,r.name repoName,
|
|
9717
|
+
r.workspace_id workspaceId,r.graph_generation graphGeneration,
|
|
9718
|
+
c.source_symbol_id sourceSymbolId,c.source_file sourceFile,
|
|
9719
|
+
c.call_type callType,c.event_name_expr eventName
|
|
9720
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
9721
|
+
WHERE r.workspace_id=? AND (? IS NULL OR c.repo_id=?)
|
|
9722
|
+
ORDER BY r.name COLLATE BINARY,r.id,c.source_file COLLATE BINARY,
|
|
9723
|
+
c.call_site_start_offset,c.call_site_end_offset,c.source_line,c.id`).all(
|
|
9724
|
+
workspaceId,
|
|
9725
|
+
repoId,
|
|
9726
|
+
repoId
|
|
9727
|
+
);
|
|
9728
|
+
return files ? rows2.filter((row) => files.has(row.sourceFile)) : rows2;
|
|
9729
|
+
}
|
|
9730
|
+
function hasExactDispatch(db, calls) {
|
|
9731
|
+
const match = db.prepare(`SELECT 1 matched FROM graph_edges emitted
|
|
9732
|
+
WHERE emitted.workspace_id=? AND emitted.generation=?
|
|
9733
|
+
AND emitted.edge_type='HANDLER_EMITS_EVENT'
|
|
9734
|
+
AND emitted.from_kind='call' AND emitted.from_id=?
|
|
9735
|
+
AND EXISTS (SELECT 1 FROM graph_edges subscriber
|
|
9736
|
+
WHERE subscriber.workspace_id=emitted.workspace_id
|
|
9737
|
+
AND subscriber.generation=emitted.generation
|
|
9738
|
+
AND subscriber.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
|
|
9739
|
+
AND subscriber.from_kind='event'
|
|
9740
|
+
AND subscriber.from_id COLLATE BINARY=? COLLATE BINARY)
|
|
9741
|
+
LIMIT 1`);
|
|
9742
|
+
return calls.some((call) => call.callType === "async_emit" && typeof call.eventName === "string" && Boolean(match.get(
|
|
9743
|
+
call.workspaceId,
|
|
9744
|
+
call.graphGeneration,
|
|
9745
|
+
String(call.id),
|
|
9746
|
+
call.eventName
|
|
9747
|
+
)));
|
|
9748
|
+
}
|
|
9749
|
+
function workspaceAmbiguityDiagnostic(db) {
|
|
9750
|
+
const total = Number(db.prepare(`SELECT COUNT(DISTINCT w.id) count
|
|
9751
|
+
FROM workspaces w JOIN repositories r ON r.workspace_id=w.id`).get()?.count ?? 0);
|
|
9752
|
+
const workspaceIds = db.prepare(`SELECT DISTINCT w.id FROM workspaces w
|
|
9753
|
+
JOIN repositories r ON r.workspace_id=w.id ORDER BY w.id LIMIT 5`).all().flatMap((row) => typeof row.id === "number" ? [row.id] : []);
|
|
9754
|
+
return {
|
|
9755
|
+
severity: "error",
|
|
9756
|
+
code: "trace_workspace_ambiguous",
|
|
9757
|
+
message: total > 1 ? "Trace spans multiple indexed workspaces; provide a workspace identity." : "No indexed workspace could be selected for this trace.",
|
|
9758
|
+
workspaceCount: total,
|
|
9759
|
+
workspaceIds,
|
|
9760
|
+
omittedWorkspaceCount: Math.max(0, total - workspaceIds.length),
|
|
9761
|
+
remediation: "Pass TraceOptions.workspaceId or select a repository in one workspace."
|
|
9762
|
+
};
|
|
9763
|
+
}
|
|
9764
|
+
|
|
9765
|
+
// src/trace/017-trace-context.ts
|
|
9766
|
+
function parseTraceEvidence(value) {
|
|
8628
9767
|
try {
|
|
8629
9768
|
const parsed = JSON.parse(String(value || "{}"));
|
|
8630
|
-
return
|
|
9769
|
+
return isRecord10(parsed) ? boundCandidateLikeEvidence(parsed) : {};
|
|
8631
9770
|
} catch {
|
|
8632
9771
|
return {};
|
|
8633
9772
|
}
|
|
8634
9773
|
}
|
|
8635
|
-
function
|
|
8636
|
-
|
|
8637
|
-
}
|
|
8638
|
-
function receiverFromEvidence(value) {
|
|
8639
|
-
const evidence = parseEvidence(value);
|
|
9774
|
+
function receiverFromTraceEvidence(value) {
|
|
9775
|
+
const evidence = parseTraceEvidence(value);
|
|
8640
9776
|
return typeof evidence.receiver === "string" ? evidence.receiver : void 0;
|
|
8641
9777
|
}
|
|
8642
|
-
function hasDynamicPlaceholder(value) {
|
|
8643
|
-
return extractPlaceholders(value).length > 0;
|
|
8644
|
-
}
|
|
8645
|
-
function enrichBinding(row) {
|
|
8646
|
-
const effectiveServicePath = row.servicePathExpr && !hasDynamicPlaceholder(row.servicePathExpr) ? row.servicePathExpr : !row.servicePathExpr ? row.requireServicePath : void 0;
|
|
8647
|
-
const effectiveDestination = row.destinationExpr && !hasDynamicPlaceholder(row.destinationExpr) ? row.destinationExpr : !row.destinationExpr ? row.requireDestination : void 0;
|
|
8648
|
-
return { ...row, effectiveServicePath, effectiveDestination };
|
|
8649
|
-
}
|
|
8650
9778
|
function knownBindingsForCalls(db, calls) {
|
|
8651
9779
|
const map = /* @__PURE__ */ new Map();
|
|
8652
|
-
for (const call of calls)
|
|
8653
|
-
const receiver = receiverFromEvidence(call.evidence_json);
|
|
8654
|
-
const bindingId = Number(call.service_binding_id ?? 0);
|
|
8655
|
-
if (!receiver || !bindingId) continue;
|
|
8656
|
-
const row = db.prepare(`SELECT b.id,b.alias,b.alias_expr aliasExpr,b.destination_expr destinationExpr,b.service_path_expr servicePathExpr,b.source_file sourceFile,b.source_line sourceLine,req.service_path requireServicePath,req.destination requireDestination
|
|
8657
|
-
FROM service_bindings b LEFT JOIN cds_requires req ON req.repo_id=b.repo_id AND req.alias=b.alias
|
|
8658
|
-
WHERE b.id=?`).get(bindingId);
|
|
8659
|
-
if (row) map.set(receiver, enrichBinding({ ...row, bindingId, source: "local_service_binding", calleeReceiver: receiver }));
|
|
8660
|
-
}
|
|
9780
|
+
for (const call of calls) addCallBinding(db, map, call);
|
|
8661
9781
|
return map;
|
|
8662
9782
|
}
|
|
8663
9783
|
function knownBindingsForScope(db, repoId, symbolIds, files) {
|
|
8664
9784
|
const map = /* @__PURE__ */ new Map();
|
|
8665
9785
|
if (repoId === void 0) return map;
|
|
8666
|
-
const rows2 = db.prepare(`SELECT b.id,b.symbol_id symbolId,
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
9786
|
+
const rows2 = db.prepare(`SELECT b.id,b.symbol_id symbolId,
|
|
9787
|
+
b.variable_name variableName,b.alias,b.alias_expr aliasExpr,
|
|
9788
|
+
b.destination_expr destinationExpr,b.service_path_expr servicePathExpr,
|
|
9789
|
+
b.source_file sourceFile,b.source_line sourceLine,
|
|
9790
|
+
req.service_path requireServicePath,req.destination requireDestination
|
|
9791
|
+
FROM service_bindings b LEFT JOIN cds_requires req
|
|
9792
|
+
ON req.repo_id=b.repo_id AND req.alias=b.alias
|
|
9793
|
+
WHERE b.repo_id=?
|
|
9794
|
+
ORDER BY b.source_file COLLATE BINARY,b.source_line,b.id`).all(
|
|
9795
|
+
repoId
|
|
9796
|
+
);
|
|
9797
|
+
for (const row of rows2) addScopeBinding(map, row, symbolIds, files);
|
|
9798
|
+
return map;
|
|
9799
|
+
}
|
|
9800
|
+
function contextForSymbolCall(db, symbolCall, callerBindings) {
|
|
9801
|
+
const next = /* @__PURE__ */ new Map();
|
|
9802
|
+
if (callerBindings.size === 0) return next;
|
|
9803
|
+
const context = symbolCallContext(db, symbolCall);
|
|
9804
|
+
context.args.forEach((argument, index) => addArgumentBindings(
|
|
9805
|
+
next,
|
|
9806
|
+
callerBindings,
|
|
9807
|
+
context,
|
|
9808
|
+
argument,
|
|
9809
|
+
index
|
|
9810
|
+
));
|
|
9811
|
+
return next;
|
|
9812
|
+
}
|
|
9813
|
+
function addCallBinding(db, map, call) {
|
|
9814
|
+
const receiver = receiverFromTraceEvidence(call.evidence_json);
|
|
9815
|
+
const bindingId = Number(call.service_binding_id ?? 0);
|
|
9816
|
+
if (!receiver || !bindingId) return;
|
|
9817
|
+
const row = db.prepare(`SELECT b.id,b.alias,b.alias_expr aliasExpr,
|
|
9818
|
+
b.destination_expr destinationExpr,b.service_path_expr servicePathExpr,
|
|
9819
|
+
b.source_file sourceFile,b.source_line sourceLine,
|
|
9820
|
+
req.service_path requireServicePath,req.destination requireDestination
|
|
9821
|
+
FROM service_bindings b LEFT JOIN cds_requires req
|
|
9822
|
+
ON req.repo_id=b.repo_id AND req.alias=b.alias WHERE b.id=?`).get(
|
|
9823
|
+
bindingId
|
|
9824
|
+
);
|
|
9825
|
+
if (row) map.set(receiver, enrichBinding({
|
|
9826
|
+
...row,
|
|
9827
|
+
bindingId,
|
|
9828
|
+
source: "local_service_binding",
|
|
9829
|
+
calleeReceiver: receiver
|
|
9830
|
+
}));
|
|
9831
|
+
}
|
|
9832
|
+
function addScopeBinding(map, row, symbolIds, files) {
|
|
9833
|
+
if (!row.variableName) return;
|
|
9834
|
+
if (files && !files.has(String(row.sourceFile))) return;
|
|
9835
|
+
if (symbolIds?.size && row.symbolId != null && !symbolIds.has(Number(row.symbolId))) return;
|
|
9836
|
+
const candidate = enrichBinding({
|
|
9837
|
+
...row,
|
|
9838
|
+
bindingId: Number(row.id),
|
|
9839
|
+
source: "local_service_binding",
|
|
9840
|
+
calleeReceiver: row.variableName,
|
|
9841
|
+
resolutionStatus: "selected"
|
|
9842
|
+
});
|
|
9843
|
+
const existing = map.get(row.variableName);
|
|
9844
|
+
if (!existing) {
|
|
9845
|
+
map.set(row.variableName, candidate);
|
|
9846
|
+
return;
|
|
9847
|
+
}
|
|
9848
|
+
map.set(row.variableName, ambiguousBinding(existing, candidate));
|
|
9849
|
+
}
|
|
9850
|
+
function ambiguousBinding(existing, candidate) {
|
|
9851
|
+
const bindingCandidates2 = uniqueBindingCandidates([
|
|
9852
|
+
...existing.bindingCandidates ?? [bindingEvidence3(existing)],
|
|
9853
|
+
bindingEvidence3(candidate)
|
|
9854
|
+
]);
|
|
9855
|
+
return {
|
|
9856
|
+
...candidate,
|
|
9857
|
+
bindingId: void 0,
|
|
9858
|
+
source: "ambiguous_local_service_bindings",
|
|
9859
|
+
resolutionStatus: "ambiguous",
|
|
9860
|
+
bindingCandidates: bindingCandidates2
|
|
9861
|
+
};
|
|
9862
|
+
}
|
|
9863
|
+
function symbolCallContext(db, symbolCall) {
|
|
9864
|
+
const callEvidence = parseTraceEvidence(symbolCall.evidence_json);
|
|
9865
|
+
const callee = db.prepare(`SELECT evidence_json evidenceJson,
|
|
9866
|
+
source_file sourceFile,start_line startLine FROM symbols WHERE id=?`).get(
|
|
9867
|
+
symbolCall.callee_symbol_id
|
|
9868
|
+
);
|
|
9869
|
+
const evidence = parseTraceEvidence(callee?.evidenceJson);
|
|
9870
|
+
return {
|
|
9871
|
+
args: recordArray6(callEvidence.callArguments),
|
|
9872
|
+
params: stringArray6(evidence.parameters),
|
|
9873
|
+
parameterBindings: recordArray6(evidence.parameterBindings),
|
|
9874
|
+
parameterPropertyAliases: recordArray6(evidence.parameterPropertyAliases),
|
|
9875
|
+
provenance: {
|
|
9876
|
+
callerSite: {
|
|
9877
|
+
sourceFile: String(symbolCall.source_file ?? ""),
|
|
9878
|
+
sourceLine: Number(symbolCall.source_line ?? 0)
|
|
9879
|
+
},
|
|
9880
|
+
calleeSite: { sourceFile: callee?.sourceFile, sourceLine: callee?.startLine }
|
|
8684
9881
|
}
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
9882
|
+
};
|
|
9883
|
+
}
|
|
9884
|
+
function addArgumentBindings(next, caller, context, argument, index) {
|
|
9885
|
+
const parameterBinding = context.parameterBindings.find(
|
|
9886
|
+
(binding) => binding.index === index
|
|
9887
|
+
);
|
|
9888
|
+
const parameter = parameterBinding?.kind === "identifier" && typeof parameterBinding.name === "string" ? parameterBinding.name : context.params[index];
|
|
9889
|
+
addIdentifierBinding(next, caller, context.provenance, argument, parameter);
|
|
9890
|
+
addObjectBindings(next, caller, context, argument, parameterBinding, parameter, index);
|
|
9891
|
+
addArrayBindings(next, caller, context.provenance, argument, parameterBinding, index);
|
|
9892
|
+
}
|
|
9893
|
+
function addIdentifierBinding(next, caller, provenance, argument, parameter) {
|
|
9894
|
+
if (argument.kind !== "identifier" || typeof argument.name !== "string" || !parameter) return;
|
|
9895
|
+
const binding = caller.get(argument.name);
|
|
9896
|
+
if (binding) next.set(parameter, {
|
|
9897
|
+
...binding,
|
|
9898
|
+
...provenance,
|
|
9899
|
+
source: "local_symbol_argument",
|
|
9900
|
+
callerArgument: argument.name,
|
|
9901
|
+
calleeParameter: parameter,
|
|
9902
|
+
calleeReceiver: parameter
|
|
9903
|
+
});
|
|
9904
|
+
}
|
|
9905
|
+
function addObjectBindings(next, caller, context, argument, parameterBinding, parameter, index) {
|
|
9906
|
+
if (argument.kind !== "object_literal" || !Array.isArray(argument.properties)) return;
|
|
9907
|
+
for (const property of recordArray6(argument.properties)) addObjectPropertyBinding(
|
|
9908
|
+
next,
|
|
9909
|
+
caller,
|
|
9910
|
+
context,
|
|
9911
|
+
property,
|
|
9912
|
+
parameterBinding,
|
|
9913
|
+
parameter,
|
|
9914
|
+
index
|
|
9915
|
+
);
|
|
9916
|
+
}
|
|
9917
|
+
function addObjectPropertyBinding(next, caller, context, property, parameterBinding, parameter, index) {
|
|
9918
|
+
if (typeof property.property !== "string" || typeof property.argument !== "string") return;
|
|
9919
|
+
const binding = caller.get(property.argument);
|
|
9920
|
+
if (!binding) return;
|
|
9921
|
+
const local = objectPatternLocal(parameterBinding, property.property);
|
|
9922
|
+
if (local) {
|
|
9923
|
+
next.set(local, objectBinding(
|
|
9924
|
+
binding,
|
|
9925
|
+
context.provenance,
|
|
9926
|
+
property.property,
|
|
9927
|
+
property.argument,
|
|
9928
|
+
String(index),
|
|
9929
|
+
local,
|
|
9930
|
+
"local_symbol_destructured_object_argument"
|
|
9931
|
+
));
|
|
9932
|
+
return;
|
|
9933
|
+
}
|
|
9934
|
+
if (!parameter) return;
|
|
9935
|
+
const receiver = `${parameter}.${property.property}`;
|
|
9936
|
+
next.set(receiver, objectBinding(
|
|
9937
|
+
binding,
|
|
9938
|
+
context.provenance,
|
|
9939
|
+
property.property,
|
|
9940
|
+
property.argument,
|
|
9941
|
+
parameter,
|
|
9942
|
+
receiver,
|
|
9943
|
+
"local_symbol_object_argument"
|
|
9944
|
+
));
|
|
9945
|
+
addObjectAliases(next, binding, context, property, parameter, receiver);
|
|
9946
|
+
}
|
|
9947
|
+
function addObjectAliases(next, binding, context, property, parameter, receiver) {
|
|
9948
|
+
for (const alias of context.parameterPropertyAliases) {
|
|
9949
|
+
if (alias.parameter !== parameter || alias.property !== property.property || typeof alias.local !== "string") continue;
|
|
9950
|
+
next.set(alias.local, {
|
|
9951
|
+
...objectBinding(
|
|
9952
|
+
binding,
|
|
9953
|
+
context.provenance,
|
|
9954
|
+
String(property.property),
|
|
9955
|
+
String(property.argument),
|
|
9956
|
+
parameter,
|
|
9957
|
+
alias.local,
|
|
9958
|
+
"local_symbol_object_parameter_destructure"
|
|
9959
|
+
),
|
|
9960
|
+
calleeObjectProperty: receiver,
|
|
9961
|
+
calleeLocalDestructuredIdentifier: alias.local,
|
|
9962
|
+
parameterPropertyAliasKind: alias.kind,
|
|
9963
|
+
parameterPropertyAliasLine: alias.line
|
|
8695
9964
|
});
|
|
8696
9965
|
}
|
|
8697
|
-
return map;
|
|
8698
9966
|
}
|
|
8699
|
-
function
|
|
9967
|
+
function addArrayBindings(next, caller, provenance, argument, parameterBinding, index) {
|
|
9968
|
+
const arrays = arrayPattern(argument, parameterBinding);
|
|
9969
|
+
if (!arrays) return;
|
|
9970
|
+
for (const element of arrays.elements)
|
|
9971
|
+
addArrayElement(next, caller, provenance, element, arrays.targets, index);
|
|
9972
|
+
}
|
|
9973
|
+
function arrayPattern(argument, binding) {
|
|
9974
|
+
if (argument.kind !== "array_literal") return void 0;
|
|
9975
|
+
if (!Array.isArray(argument.elements)) return void 0;
|
|
9976
|
+
if (binding?.kind !== "array_pattern") return void 0;
|
|
9977
|
+
if (!Array.isArray(binding.elements)) return void 0;
|
|
9978
|
+
return {
|
|
9979
|
+
elements: recordArray6(argument.elements),
|
|
9980
|
+
targets: recordArray6(binding.elements)
|
|
9981
|
+
};
|
|
9982
|
+
}
|
|
9983
|
+
function addArrayElement(next, caller, provenance, element, targets, index) {
|
|
9984
|
+
if (element.kind !== "identifier" || typeof element.name !== "string") return;
|
|
9985
|
+
const target = targets.find((item) => item.index === element.index);
|
|
9986
|
+
if (typeof target?.local !== "string") return;
|
|
9987
|
+
const binding = caller.get(element.name);
|
|
9988
|
+
if (binding) next.set(target.local, {
|
|
9989
|
+
...binding,
|
|
9990
|
+
...provenance,
|
|
9991
|
+
source: "local_symbol_destructured_array_argument",
|
|
9992
|
+
callerArgument: element.name,
|
|
9993
|
+
calleeParameter: String(index),
|
|
9994
|
+
calleeReceiver: target.local
|
|
9995
|
+
});
|
|
9996
|
+
}
|
|
9997
|
+
function objectPatternLocal(binding, property) {
|
|
9998
|
+
if (binding?.kind !== "object_pattern" || !Array.isArray(binding.properties)) return void 0;
|
|
9999
|
+
const match = recordArray6(binding.properties).find(
|
|
10000
|
+
(item) => item.property === property && typeof item.local === "string"
|
|
10001
|
+
);
|
|
10002
|
+
return typeof match?.local === "string" ? match.local : void 0;
|
|
10003
|
+
}
|
|
10004
|
+
function objectBinding(binding, provenance, callerProperty, callerArgument, parameter, receiver, source) {
|
|
10005
|
+
return {
|
|
10006
|
+
...binding,
|
|
10007
|
+
...provenance,
|
|
10008
|
+
source,
|
|
10009
|
+
callerProperty,
|
|
10010
|
+
callerArgument,
|
|
10011
|
+
calleeParameter: parameter,
|
|
10012
|
+
calleeReceiver: receiver
|
|
10013
|
+
};
|
|
10014
|
+
}
|
|
10015
|
+
function enrichBinding(row) {
|
|
10016
|
+
const servicePath = row.servicePathExpr && !hasPlaceholder(row.servicePathExpr) ? row.servicePathExpr : !row.servicePathExpr ? row.requireServicePath : void 0;
|
|
10017
|
+
const destination = row.destinationExpr && !hasPlaceholder(row.destinationExpr) ? row.destinationExpr : !row.destinationExpr ? row.requireDestination : void 0;
|
|
10018
|
+
return {
|
|
10019
|
+
...row,
|
|
10020
|
+
effectiveServicePath: servicePath,
|
|
10021
|
+
effectiveDestination: destination
|
|
10022
|
+
};
|
|
10023
|
+
}
|
|
10024
|
+
function hasPlaceholder(value) {
|
|
10025
|
+
return extractPlaceholders(value).length > 0;
|
|
10026
|
+
}
|
|
10027
|
+
function bindingEvidence3(binding) {
|
|
8700
10028
|
return {
|
|
8701
10029
|
bindingId: binding.bindingId,
|
|
8702
10030
|
sourceFile: binding.sourceFile,
|
|
@@ -8707,74 +10035,672 @@ function bindingCandidateEvidence(binding) {
|
|
|
8707
10035
|
servicePathExpr: binding.servicePathExpr
|
|
8708
10036
|
};
|
|
8709
10037
|
}
|
|
8710
|
-
function uniqueBindingCandidates(candidates2) {
|
|
8711
|
-
const seen = /* @__PURE__ */ new Set();
|
|
8712
|
-
return candidates2.filter((candidate) => {
|
|
8713
|
-
const key = JSON.stringify(candidate);
|
|
8714
|
-
if (seen.has(key)) return false;
|
|
8715
|
-
seen.add(key);
|
|
8716
|
-
return true;
|
|
8717
|
-
});
|
|
10038
|
+
function uniqueBindingCandidates(candidates2) {
|
|
10039
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10040
|
+
return candidates2.filter((candidate) => {
|
|
10041
|
+
const key = JSON.stringify(candidate);
|
|
10042
|
+
if (seen.has(key)) return false;
|
|
10043
|
+
seen.add(key);
|
|
10044
|
+
return true;
|
|
10045
|
+
});
|
|
10046
|
+
}
|
|
10047
|
+
function recordArray6(value) {
|
|
10048
|
+
return Array.isArray(value) ? value.filter(isRecord10) : [];
|
|
10049
|
+
}
|
|
10050
|
+
function stringArray6(value) {
|
|
10051
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
10052
|
+
}
|
|
10053
|
+
function isRecord10(value) {
|
|
10054
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
10055
|
+
}
|
|
10056
|
+
|
|
10057
|
+
// src/trace/015-trace-edge-recorder.ts
|
|
10058
|
+
var nonResolvedGraphStatuses = {
|
|
10059
|
+
ambiguous: "ambiguous",
|
|
10060
|
+
dynamic: "dynamic",
|
|
10061
|
+
terminal: "terminal"
|
|
10062
|
+
};
|
|
10063
|
+
var TraceEdgeRecorder = class {
|
|
10064
|
+
constructor(edges, observer) {
|
|
10065
|
+
this.edges = edges;
|
|
10066
|
+
this.observer = observer;
|
|
10067
|
+
}
|
|
10068
|
+
edges;
|
|
10069
|
+
observer;
|
|
10070
|
+
record(edge, semantics) {
|
|
10071
|
+
const ordinal = this.edges.length;
|
|
10072
|
+
this.edges.push(edge);
|
|
10073
|
+
this.observer?.record(observation(ordinal, edge, semantics));
|
|
10074
|
+
return ordinal;
|
|
10075
|
+
}
|
|
10076
|
+
unavailable(side, endpointKind) {
|
|
10077
|
+
return {
|
|
10078
|
+
kind: "unavailable",
|
|
10079
|
+
side,
|
|
10080
|
+
endpointKind,
|
|
10081
|
+
detailedEdgeIndex: this.edges.length
|
|
10082
|
+
};
|
|
10083
|
+
}
|
|
10084
|
+
};
|
|
10085
|
+
function semanticCallSource(call, workspaceId) {
|
|
10086
|
+
const symbolId = positiveNumber(call.source_symbol_id);
|
|
10087
|
+
if (symbolId !== void 0) return { kind: "symbol", symbolId };
|
|
10088
|
+
return {
|
|
10089
|
+
kind: "call_site",
|
|
10090
|
+
workspaceId,
|
|
10091
|
+
repositoryId: call.repo_id,
|
|
10092
|
+
repositoryName: call.repoName,
|
|
10093
|
+
sourceFile: call.source_file,
|
|
10094
|
+
sourceLine: call.source_line,
|
|
10095
|
+
startOffset: finiteNumber(call.call_site_start_offset),
|
|
10096
|
+
endOffset: finiteNumber(call.call_site_end_offset),
|
|
10097
|
+
callId: call.id
|
|
10098
|
+
};
|
|
10099
|
+
}
|
|
10100
|
+
function semanticOperation(value, unavailable) {
|
|
10101
|
+
const operationId = positiveNumber(value);
|
|
10102
|
+
return operationId === void 0 ? unavailable() : { kind: "operation", operationId };
|
|
10103
|
+
}
|
|
10104
|
+
function semanticSymbol(value, unavailable) {
|
|
10105
|
+
const symbolId = positiveNumber(value);
|
|
10106
|
+
return symbolId === void 0 ? unavailable() : { kind: "symbol", symbolId };
|
|
10107
|
+
}
|
|
10108
|
+
function semanticHandler(methodIdValue, symbolIdValue, unavailable) {
|
|
10109
|
+
const symbolId = positiveNumber(symbolIdValue);
|
|
10110
|
+
if (symbolId !== void 0) return { kind: "symbol", symbolId };
|
|
10111
|
+
const handlerMethodId = positiveNumber(methodIdValue);
|
|
10112
|
+
return handlerMethodId === void 0 ? unavailable() : { kind: "handler_method", handlerMethodId };
|
|
10113
|
+
}
|
|
10114
|
+
function semanticGraphTarget(row, call, workspaceId, unavailable) {
|
|
10115
|
+
if (row.to_kind === "event") return {
|
|
10116
|
+
kind: "event",
|
|
10117
|
+
workspaceId,
|
|
10118
|
+
eventName: typeof call.event_name_expr === "string" ? call.event_name_expr : row.to_id
|
|
10119
|
+
};
|
|
10120
|
+
const id = positiveNumber(row.to_id);
|
|
10121
|
+
if (row.to_kind === "operation")
|
|
10122
|
+
return id === void 0 ? unavailable() : { kind: "operation", operationId: id };
|
|
10123
|
+
if (row.to_kind === "symbol")
|
|
10124
|
+
return id === void 0 ? unavailable() : { kind: "symbol", symbolId: id };
|
|
10125
|
+
if (row.to_kind === "handler_method") return id === void 0 ? unavailable() : { kind: "handler_method", handlerMethodId: id };
|
|
10126
|
+
return {
|
|
10127
|
+
kind: "target",
|
|
10128
|
+
workspaceId,
|
|
10129
|
+
repositoryId: positiveNumber(call.repo_id),
|
|
10130
|
+
targetKind: row.to_kind,
|
|
10131
|
+
targetId: row.to_id
|
|
10132
|
+
};
|
|
10133
|
+
}
|
|
10134
|
+
function semanticScopeTarget(workspaceId, repositoryId, sourceFiles, symbolIds, structuralKey) {
|
|
10135
|
+
return {
|
|
10136
|
+
kind: "scope",
|
|
10137
|
+
workspaceId,
|
|
10138
|
+
repositoryId,
|
|
10139
|
+
sourceFiles: [...sourceFiles ?? []],
|
|
10140
|
+
symbolIds: [...symbolIds ?? []],
|
|
10141
|
+
structuralKey
|
|
10142
|
+
};
|
|
10143
|
+
}
|
|
10144
|
+
function compactGraphStatus(row, evidence, unresolvedReason, dynamicMode2) {
|
|
10145
|
+
const effective = recordValue2(evidence.effectiveResolution);
|
|
10146
|
+
const status = stringValue14(effective.status) ?? row.status ?? "unresolved";
|
|
10147
|
+
if (status !== "resolved")
|
|
10148
|
+
return nonResolvedGraphStatuses[status] ?? "unresolved";
|
|
10149
|
+
if (unresolvedReason) return "unresolved";
|
|
10150
|
+
const inference = recordValue2(evidence.dynamicTargetInference);
|
|
10151
|
+
if (isDynamicInference(dynamicMode2, inference)) return "dynamic";
|
|
10152
|
+
return traversableTargetKind(row.to_kind) ? "resolved" : "terminal";
|
|
10153
|
+
}
|
|
10154
|
+
function isDynamicInference(mode, evidence) {
|
|
10155
|
+
return mode === "infer" && evidence.status === "resolved";
|
|
10156
|
+
}
|
|
10157
|
+
function traversableTargetKind(kind) {
|
|
10158
|
+
return ["operation", "symbol", "handler_method"].includes(kind);
|
|
10159
|
+
}
|
|
10160
|
+
function compactResolutionStatus(status, unresolvedReason) {
|
|
10161
|
+
if (status === "ambiguous") return "ambiguous";
|
|
10162
|
+
if (status === "dynamic") return "dynamic";
|
|
10163
|
+
return status === "resolved" && !unresolvedReason ? "resolved" : "unresolved";
|
|
10164
|
+
}
|
|
10165
|
+
function compactEventStatus(status, bodyExpansion) {
|
|
10166
|
+
if (bodyExpansion === "cycle_blocked") return "cycle";
|
|
10167
|
+
if (status === "ambiguous") return "ambiguous";
|
|
10168
|
+
return status === "resolved" ? "inferred" : "unresolved";
|
|
10169
|
+
}
|
|
10170
|
+
function compactDecisionFromEvidence(evidence, overrides = {}) {
|
|
10171
|
+
const effective = recordValue2(evidence.effectiveResolution);
|
|
10172
|
+
const persisted = recordValue2(evidence.persistedResolution);
|
|
10173
|
+
const dynamic = recordValue2(evidence.dynamicTargetExploration);
|
|
10174
|
+
const implementation = recordValue2(evidence.implementationSelection);
|
|
10175
|
+
const missing = stringArray7(dynamic.missingVariables ?? evidence.missingRuntimeVariables);
|
|
10176
|
+
const authoritativeMissingCount = finiteNumber(
|
|
10177
|
+
dynamic.missingVariableCount ?? evidence.missingVariableCount
|
|
10178
|
+
);
|
|
10179
|
+
return {
|
|
10180
|
+
effectiveResolutionStatus: stringValue14(effective.status),
|
|
10181
|
+
effectiveTarget: targetSummary(effective),
|
|
10182
|
+
persistedResolutionStatus: stringValue14(persisted.status),
|
|
10183
|
+
persistedTarget: targetSummary(persisted),
|
|
10184
|
+
missingVariableNames: missing.length > 0 ? missing : void 0,
|
|
10185
|
+
missingVariableCount: authoritativeMissingCount ?? (missing.length > 0 ? missing.length : void 0),
|
|
10186
|
+
dynamicMode: dynamicMode(dynamic.mode),
|
|
10187
|
+
candidateCount: firstNumber(
|
|
10188
|
+
dynamic.candidateCount,
|
|
10189
|
+
implementation.candidateCount,
|
|
10190
|
+
implementation.candidateScoreCount,
|
|
10191
|
+
evidence.candidateCount,
|
|
10192
|
+
evidence.persistedCandidateCount
|
|
10193
|
+
),
|
|
10194
|
+
viableCandidateCount: finiteNumber(dynamic.viableCandidateCount),
|
|
10195
|
+
rejectedCandidateCount: finiteNumber(dynamic.rejectedCandidateCount),
|
|
10196
|
+
omittedCandidateCount: finiteNumber(dynamic.omittedCandidateCount),
|
|
10197
|
+
implementationStrategy: stringValue14(implementation.strategy),
|
|
10198
|
+
implementationGuided: booleanValue(implementation.guided),
|
|
10199
|
+
implementationContextual: booleanValue(
|
|
10200
|
+
evidence.contextualImplementationSelected
|
|
10201
|
+
),
|
|
10202
|
+
reasonCode: stringValue14(evidence.reasonCode ?? evidence.cycleReason),
|
|
10203
|
+
eventMatchStrategy: stringValue14(evidence.matchStrategy),
|
|
10204
|
+
dispatchCertainty: stringValue14(evidence.dispatchCertainty),
|
|
10205
|
+
associationStatus: stringValue14(evidence.associationStatus),
|
|
10206
|
+
associationBasis: stringValue14(evidence.associationBasis),
|
|
10207
|
+
eventScope: stringValue14(evidence.dispatchScope),
|
|
10208
|
+
callRole: stringValue14(evidence.callRole),
|
|
10209
|
+
factOrigin: stringValue14(evidence.factOrigin),
|
|
10210
|
+
roleSiteMatchCount: finiteNumber(evidence.roleSiteMatchCount),
|
|
10211
|
+
bodyExpansion: stringValue14(evidence.bodyExpansion),
|
|
10212
|
+
...overrides
|
|
10213
|
+
};
|
|
10214
|
+
}
|
|
10215
|
+
function compactRefs(values) {
|
|
10216
|
+
return {
|
|
10217
|
+
graphEdgeIds: reference(values.graphEdgeId),
|
|
10218
|
+
outboundCallIds: reference(values.outboundCallId),
|
|
10219
|
+
subscribeCallIds: reference(values.subscribeCallId),
|
|
10220
|
+
symbolCallIds: reference(values.symbolCallId),
|
|
10221
|
+
operationIds: reference(values.operationId),
|
|
10222
|
+
symbolIds: reference(values.symbolId),
|
|
10223
|
+
handlerMethodIds: reference(values.handlerMethodId)
|
|
10224
|
+
};
|
|
10225
|
+
}
|
|
10226
|
+
function compactSite(values) {
|
|
10227
|
+
return {
|
|
10228
|
+
repository: stringValue14(values.repository ?? values.repoName),
|
|
10229
|
+
sourceFile: stringValue14(values.sourceFile ?? values.source_file),
|
|
10230
|
+
sourceLine: finiteNumber(values.sourceLine ?? values.source_line),
|
|
10231
|
+
startOffset: finiteNumber(values.startOffset ?? values.call_site_start_offset),
|
|
10232
|
+
endOffset: finiteNumber(values.endOffset ?? values.call_site_end_offset)
|
|
10233
|
+
};
|
|
10234
|
+
}
|
|
10235
|
+
function observation(ordinal, edge, semantics) {
|
|
10236
|
+
return {
|
|
10237
|
+
ordinal,
|
|
10238
|
+
step: edge.step,
|
|
10239
|
+
type: edge.type,
|
|
10240
|
+
source: semantics.source,
|
|
10241
|
+
target: semantics.target,
|
|
10242
|
+
status: semantics.status,
|
|
10243
|
+
confidence: edge.confidence,
|
|
10244
|
+
decision: semantics.decision,
|
|
10245
|
+
refs: semantics.refs,
|
|
10246
|
+
site: semantics.site
|
|
10247
|
+
};
|
|
10248
|
+
}
|
|
10249
|
+
function targetSummary(value) {
|
|
10250
|
+
const kind = stringValue14(value.targetKind);
|
|
10251
|
+
const id = stringValue14(value.targetId);
|
|
10252
|
+
return kind && id ? { kind, id } : void 0;
|
|
10253
|
+
}
|
|
10254
|
+
function reference(value) {
|
|
10255
|
+
if (typeof value === "string" && value.length > 0) {
|
|
10256
|
+
const numeric2 = Number(value);
|
|
10257
|
+
if (Number.isSafeInteger(numeric2)) return numeric2 > 0 ? [numeric2] : void 0;
|
|
10258
|
+
return [value];
|
|
10259
|
+
}
|
|
10260
|
+
const number = finiteNumber(value);
|
|
10261
|
+
return number === void 0 || number <= 0 ? void 0 : [number];
|
|
10262
|
+
}
|
|
10263
|
+
function dynamicMode(value) {
|
|
10264
|
+
return value === "strict" || value === "candidates" || value === "infer" ? value : void 0;
|
|
10265
|
+
}
|
|
10266
|
+
function recordValue2(value) {
|
|
10267
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
10268
|
+
}
|
|
10269
|
+
function stringArray7(value) {
|
|
10270
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
10271
|
+
}
|
|
10272
|
+
function stringValue14(value) {
|
|
10273
|
+
return typeof value === "string" ? value : void 0;
|
|
10274
|
+
}
|
|
10275
|
+
function finiteNumber(value) {
|
|
10276
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
10277
|
+
if (typeof value !== "string" || value.trim() === "") return void 0;
|
|
10278
|
+
const parsed = Number(value);
|
|
10279
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
10280
|
+
}
|
|
10281
|
+
function positiveNumber(value) {
|
|
10282
|
+
const number = finiteNumber(value);
|
|
10283
|
+
return number !== void 0 && number > 0 ? number : void 0;
|
|
10284
|
+
}
|
|
10285
|
+
function booleanValue(value) {
|
|
10286
|
+
return typeof value === "boolean" ? value : void 0;
|
|
10287
|
+
}
|
|
10288
|
+
function firstNumber(...values) {
|
|
10289
|
+
for (const value of values) {
|
|
10290
|
+
const numeric2 = finiteNumber(value);
|
|
10291
|
+
if (numeric2 !== void 0) return numeric2;
|
|
10292
|
+
}
|
|
10293
|
+
return void 0;
|
|
10294
|
+
}
|
|
10295
|
+
|
|
10296
|
+
// src/trace/019-trace-edge-semantics.ts
|
|
10297
|
+
function recordImplementationObservation(recorder, edge, input) {
|
|
10298
|
+
const target = semanticHandler(
|
|
10299
|
+
input.handlerMethodId,
|
|
10300
|
+
input.handlerSymbolId,
|
|
10301
|
+
() => recorder.unavailable("target", "selected_handler")
|
|
10302
|
+
);
|
|
10303
|
+
recorder.record(edge, {
|
|
10304
|
+
source: semanticOperation(
|
|
10305
|
+
input.operationId,
|
|
10306
|
+
() => recorder.unavailable("source", "operation")
|
|
10307
|
+
),
|
|
10308
|
+
target,
|
|
10309
|
+
status: compactResolutionStatus(input.effectiveStatus, input.unresolvedReason),
|
|
10310
|
+
decision: compactDecisionFromEvidence(input.evidence, {
|
|
10311
|
+
effectiveResolutionStatus: input.unresolvedReason ? "unresolved" : input.effectiveStatus,
|
|
10312
|
+
effectiveTarget: decisionTarget(target),
|
|
10313
|
+
persistedResolutionStatus: input.persistedStatus,
|
|
10314
|
+
persistedTarget: input.persistedTargetKind && input.persistedTargetId ? { kind: input.persistedTargetKind, id: input.persistedTargetId } : void 0,
|
|
10315
|
+
implementationStrategy: input.strategy,
|
|
10316
|
+
implementationGuided: input.guided,
|
|
10317
|
+
implementationContextual: input.contextual,
|
|
10318
|
+
reasonCode: input.unresolvedReason ? "selected_handler_unavailable" : void 0
|
|
10319
|
+
}),
|
|
10320
|
+
refs: compactRefs({
|
|
10321
|
+
graphEdgeId: input.graphEdgeId,
|
|
10322
|
+
operationId: input.operationId,
|
|
10323
|
+
handlerMethodId: input.handlerMethodId,
|
|
10324
|
+
symbolId: input.handlerSymbolId
|
|
10325
|
+
}),
|
|
10326
|
+
site: compactSite(input.site)
|
|
10327
|
+
});
|
|
10328
|
+
}
|
|
10329
|
+
function recordLocalCallObservation(recorder, edge, input) {
|
|
10330
|
+
const source = semanticSymbol(
|
|
10331
|
+
input.symbolCall.caller_symbol_id,
|
|
10332
|
+
() => recorder.unavailable("source", "caller_symbol")
|
|
10333
|
+
);
|
|
10334
|
+
const target = semanticSymbol(
|
|
10335
|
+
input.symbolCall.callee_symbol_id,
|
|
10336
|
+
() => recorder.unavailable("target", "callee_symbol")
|
|
10337
|
+
);
|
|
10338
|
+
recorder.record(edge, {
|
|
10339
|
+
source,
|
|
10340
|
+
target,
|
|
10341
|
+
status: compactResolutionStatus(
|
|
10342
|
+
input.symbolCall.status,
|
|
10343
|
+
input.unresolvedReason
|
|
10344
|
+
),
|
|
10345
|
+
decision: compactDecisionFromEvidence(input.evidence, {
|
|
10346
|
+
effectiveResolutionStatus: String(input.symbolCall.status),
|
|
10347
|
+
reasonCode: input.unresolvedReason ? "symbol_call_unresolved" : void 0
|
|
10348
|
+
}),
|
|
10349
|
+
refs: {
|
|
10350
|
+
symbolCallIds: idArray(input.symbolCall.id),
|
|
10351
|
+
symbolIds: idArray(
|
|
10352
|
+
input.symbolCall.caller_symbol_id,
|
|
10353
|
+
input.symbolCall.callee_symbol_id
|
|
10354
|
+
)
|
|
10355
|
+
},
|
|
10356
|
+
site: compactSite(input.symbolCall)
|
|
10357
|
+
});
|
|
10358
|
+
return target;
|
|
10359
|
+
}
|
|
10360
|
+
function recordCycleObservation(recorder, edge, source, scope, refs, site) {
|
|
10361
|
+
recorder.record(edge, {
|
|
10362
|
+
source,
|
|
10363
|
+
target: scope.workspaceId === void 0 ? recorder.unavailable("target", "cycle_scope") : semanticScopeTarget(
|
|
10364
|
+
scope.workspaceId,
|
|
10365
|
+
scope.repositoryId,
|
|
10366
|
+
scope.sourceFiles,
|
|
10367
|
+
scope.symbolIds,
|
|
10368
|
+
scope.structuralKey
|
|
10369
|
+
),
|
|
10370
|
+
status: "cycle",
|
|
10371
|
+
decision: compactDecisionFromEvidence(edge.evidence, {
|
|
10372
|
+
reasonCode: "structural_ancestry_cycle"
|
|
10373
|
+
}),
|
|
10374
|
+
refs: compactRefs(refs),
|
|
10375
|
+
site: compactSite(site)
|
|
10376
|
+
});
|
|
10377
|
+
}
|
|
10378
|
+
function recordOutboundObservation(recorder, edge, input) {
|
|
10379
|
+
const source = semanticCallSource(input.call, input.workspaceId);
|
|
10380
|
+
const target = semanticGraphTarget(
|
|
10381
|
+
input.row,
|
|
10382
|
+
input.call,
|
|
10383
|
+
input.workspaceId,
|
|
10384
|
+
() => recorder.unavailable("target", input.row.to_kind)
|
|
10385
|
+
);
|
|
10386
|
+
recorder.record(edge, {
|
|
10387
|
+
source,
|
|
10388
|
+
target,
|
|
10389
|
+
status: compactGraphStatus(
|
|
10390
|
+
input.row,
|
|
10391
|
+
input.evidence,
|
|
10392
|
+
input.unresolvedReason,
|
|
10393
|
+
input.dynamicMode
|
|
10394
|
+
),
|
|
10395
|
+
decision: compactDecisionFromEvidence(input.evidence, {
|
|
10396
|
+
reasonCode: input.unresolvedReason ? safeReasonCode(input.evidence.reasonCode, "outbound_target_unresolved") : void 0
|
|
10397
|
+
}),
|
|
10398
|
+
refs: compactRefs({
|
|
10399
|
+
graphEdgeId: positiveId(input.evidence.persistedGraphEdgeId),
|
|
10400
|
+
outboundCallId: input.call.id,
|
|
10401
|
+
operationId: input.row.to_kind === "operation" ? input.row.to_id : void 0,
|
|
10402
|
+
symbolId: input.call.source_symbol_id
|
|
10403
|
+
}),
|
|
10404
|
+
site: compactSite(input.call)
|
|
10405
|
+
});
|
|
10406
|
+
return { source, target };
|
|
10407
|
+
}
|
|
10408
|
+
function recordEventBridgeObservation(recorder, edge, plan, workspaceId, subscriptionCount) {
|
|
10409
|
+
const handler = plan.transition.handler;
|
|
10410
|
+
const target = handler ? { kind: "symbol", symbolId: handler.symbolId } : {
|
|
10411
|
+
kind: "target",
|
|
10412
|
+
workspaceId,
|
|
10413
|
+
repositoryId: plan.transition.subscriptionRepoId,
|
|
10414
|
+
targetKind: plan.transition.targetKind,
|
|
10415
|
+
targetId: plan.transition.targetId
|
|
10416
|
+
};
|
|
10417
|
+
recorder.record(edge, {
|
|
10418
|
+
source: {
|
|
10419
|
+
kind: "event",
|
|
10420
|
+
workspaceId,
|
|
10421
|
+
eventName: plan.transition.eventName
|
|
10422
|
+
},
|
|
10423
|
+
target,
|
|
10424
|
+
status: compactEventStatus(plan.transition.status, plan.bodyExpansion),
|
|
10425
|
+
decision: compactDecisionFromEvidence(plan.evidence, {
|
|
10426
|
+
eventSubscriptionCount: subscriptionCount,
|
|
10427
|
+
reasonCode: plan.transition.reasonCode
|
|
10428
|
+
}),
|
|
10429
|
+
refs: eventReferences(plan),
|
|
10430
|
+
site: eventSite(plan)
|
|
10431
|
+
});
|
|
10432
|
+
return target;
|
|
10433
|
+
}
|
|
10434
|
+
function recordEventCycleObservation(recorder, edge, plan, source, workspaceId) {
|
|
10435
|
+
const handler = plan.transition.handler;
|
|
10436
|
+
if (!plan.state) return;
|
|
10437
|
+
recorder.record(edge, {
|
|
10438
|
+
source,
|
|
10439
|
+
target: semanticScopeTarget(
|
|
10440
|
+
workspaceId,
|
|
10441
|
+
handler?.repoId,
|
|
10442
|
+
handler ? /* @__PURE__ */ new Set([handler.sourceFile]) : void 0,
|
|
10443
|
+
handler ? /* @__PURE__ */ new Set([handler.symbolId]) : void 0,
|
|
10444
|
+
plan.state.structuralKey
|
|
10445
|
+
),
|
|
10446
|
+
status: "cycle",
|
|
10447
|
+
decision: compactDecisionFromEvidence(edge.evidence, {
|
|
10448
|
+
reasonCode: "structural_ancestry_cycle",
|
|
10449
|
+
dispatchCertainty: "static_name_only"
|
|
10450
|
+
}),
|
|
10451
|
+
refs: eventReferences(plan),
|
|
10452
|
+
site: eventSite(plan)
|
|
10453
|
+
});
|
|
10454
|
+
}
|
|
10455
|
+
function recordDynamicBranchObservation(recorder, branch, call, source, evidence, workspaceId) {
|
|
10456
|
+
const target = dynamicBranchTarget(recorder, branch, workspaceId);
|
|
10457
|
+
recorder.record(branch.edge, {
|
|
10458
|
+
source,
|
|
10459
|
+
target,
|
|
10460
|
+
status: "dynamic",
|
|
10461
|
+
decision: compactDecisionFromEvidence(evidence, {
|
|
10462
|
+
dynamicMode: "candidates",
|
|
10463
|
+
effectiveTarget: branch.operationId === void 0 ? void 0 : { kind: "operation", id: String(branch.operationId) },
|
|
10464
|
+
remediationCode: "provide_runtime_variables"
|
|
10465
|
+
}),
|
|
10466
|
+
refs: compactRefs({
|
|
10467
|
+
graphEdgeId: positiveId(evidence.persistedGraphEdgeId),
|
|
10468
|
+
outboundCallId: call.id,
|
|
10469
|
+
operationId: branch.operationId,
|
|
10470
|
+
symbolId: call.source_symbol_id
|
|
10471
|
+
}),
|
|
10472
|
+
site: compactSite(call)
|
|
10473
|
+
});
|
|
10474
|
+
}
|
|
10475
|
+
function dynamicBranchTarget(recorder, branch, workspaceId) {
|
|
10476
|
+
if (branch.operationId !== void 0)
|
|
10477
|
+
return { kind: "operation", operationId: branch.operationId };
|
|
10478
|
+
if (branch.servicePath && branch.operationPath) return {
|
|
10479
|
+
kind: "target",
|
|
10480
|
+
workspaceId,
|
|
10481
|
+
repositoryId: branch.repositoryId,
|
|
10482
|
+
targetKind: "dynamic_operation_candidate",
|
|
10483
|
+
targetId: JSON.stringify([branch.servicePath, branch.operationPath])
|
|
10484
|
+
};
|
|
10485
|
+
return recorder.unavailable("target", "dynamic_operation_candidate");
|
|
10486
|
+
}
|
|
10487
|
+
function eventReferences(plan) {
|
|
10488
|
+
return compactRefs({
|
|
10489
|
+
graphEdgeId: plan.transition.graphEdgeId,
|
|
10490
|
+
subscribeCallId: plan.transition.subscribeCallId,
|
|
10491
|
+
symbolCallId: plan.transition.symbolCallId,
|
|
10492
|
+
symbolId: plan.transition.handler?.symbolId
|
|
10493
|
+
});
|
|
10494
|
+
}
|
|
10495
|
+
function eventSite(plan) {
|
|
10496
|
+
return compactSite({
|
|
10497
|
+
repository: plan.transition.subscriptionRepoName,
|
|
10498
|
+
sourceFile: plan.transition.sourceFile,
|
|
10499
|
+
sourceLine: plan.transition.sourceLine,
|
|
10500
|
+
startOffset: plan.transition.callSiteStartOffset,
|
|
10501
|
+
endOffset: plan.transition.callSiteEndOffset
|
|
10502
|
+
});
|
|
10503
|
+
}
|
|
10504
|
+
function safeReasonCode(value, fallback) {
|
|
10505
|
+
return typeof value === "string" && /^[a-z][a-z0-9_.-]{0,79}$/.test(value) ? value : fallback;
|
|
10506
|
+
}
|
|
10507
|
+
function positiveId(value) {
|
|
10508
|
+
if (typeof value === "number") return Number.isFinite(value) && value > 0 ? value : void 0;
|
|
10509
|
+
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
10510
|
+
const numeric2 = Number(value);
|
|
10511
|
+
if (Number.isSafeInteger(numeric2)) return numeric2 > 0 ? numeric2 : void 0;
|
|
10512
|
+
return value;
|
|
10513
|
+
}
|
|
10514
|
+
function idArray(...values) {
|
|
10515
|
+
const ids = values.flatMap((value) => {
|
|
10516
|
+
const id = positiveId(value);
|
|
10517
|
+
return id === void 0 ? [] : [id];
|
|
10518
|
+
});
|
|
10519
|
+
return ids.length > 0 ? ids : void 0;
|
|
10520
|
+
}
|
|
10521
|
+
function decisionTarget(endpoint) {
|
|
10522
|
+
if (endpoint.kind === "symbol")
|
|
10523
|
+
return { kind: "symbol", id: String(endpoint.symbolId) };
|
|
10524
|
+
if (endpoint.kind === "handler_method")
|
|
10525
|
+
return { kind: "handler_method", id: String(endpoint.handlerMethodId) };
|
|
10526
|
+
return void 0;
|
|
10527
|
+
}
|
|
10528
|
+
|
|
10529
|
+
// src/trace/trace-engine.ts
|
|
10530
|
+
var compactObserverKey = /* @__PURE__ */ Symbol("service-flow.compact-trace-observer");
|
|
10531
|
+
function compactObserver(options) {
|
|
10532
|
+
const observed = options;
|
|
10533
|
+
return observed[compactObserverKey];
|
|
10534
|
+
}
|
|
10535
|
+
function normalizeOperation2(value) {
|
|
10536
|
+
if (!value) return void 0;
|
|
10537
|
+
return value.startsWith("/") ? value.slice(1) : value;
|
|
10538
|
+
}
|
|
10539
|
+
function positiveDepth(value) {
|
|
10540
|
+
return Number.isFinite(value) && value > 0 ? Math.floor(value) : 25;
|
|
10541
|
+
}
|
|
10542
|
+
function operationStartScope(db, repoId, start, hintOptions, workspaceId) {
|
|
10543
|
+
const requested = normalizeOperation2(start.operationPath ?? start.operation);
|
|
10544
|
+
if (!requested) return void 0;
|
|
10545
|
+
const rows2 = 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
|
|
10546
|
+
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
|
|
10547
|
+
WHERE (? IS NULL OR r.workspace_id=?) AND (? IS NULL OR r.id=?)
|
|
10548
|
+
AND (? IS NULL OR s.service_path=?) AND (o.operation_name=? OR o.operation_path=? OR o.operation_path=?)
|
|
10549
|
+
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}`);
|
|
10550
|
+
if (rows2.length === 0) return void 0;
|
|
10551
|
+
const repoCount = new Set(rows2.map((row) => String(row.repoName))).size;
|
|
10552
|
+
const serviceCount = new Set(rows2.map((row) => `${String(row.repoName)}:${String(row.servicePath)}`)).size;
|
|
10553
|
+
if (!repoId && repoCount > 1)
|
|
10554
|
+
return { diagnostics: [ambiguousStartDiagnostic(requested, rows2, "Operation trace start matched multiple repositories; add --repo to disambiguate")] };
|
|
10555
|
+
if (!start.servicePath && serviceCount > 1)
|
|
10556
|
+
return { diagnostics: [ambiguousStartDiagnostic(requested, rows2, "Operation trace start matched multiple services; add --service to disambiguate")] };
|
|
10557
|
+
if (rows2.length !== 1)
|
|
10558
|
+
return { diagnostics: [ambiguousStartDiagnostic(requested, rows2, "Operation trace start matched multiple indexed operations")] };
|
|
10559
|
+
const operationId = String(rows2[0]?.operationId);
|
|
10560
|
+
const impl = implementationScope(db, operationId);
|
|
10561
|
+
if (impl.edge?.status === "resolved" && impl.files.size > 0) return { files: impl.files, symbols: impl.symbolId ? /* @__PURE__ */ new Set([impl.symbolId]) : void 0, repoId: impl.repoId, operationId, diagnostics: [] };
|
|
10562
|
+
const hinted = hintedImplementationSelection(
|
|
10563
|
+
db,
|
|
10564
|
+
impl.edge,
|
|
10565
|
+
operationId,
|
|
10566
|
+
hintOptions
|
|
10567
|
+
);
|
|
10568
|
+
if (hinted.methodId) {
|
|
10569
|
+
const hintedScope = handlerScope(db, hinted.methodId);
|
|
10570
|
+
if (hintedScope?.files.size) return { files: hintedScope.files, symbols: hintedScope.symbolId ? /* @__PURE__ */ new Set([hintedScope.symbolId]) : void 0, repoId: hintedScope.repoId, operationId, diagnostics: [] };
|
|
10571
|
+
}
|
|
10572
|
+
if (impl.edge) {
|
|
10573
|
+
const evidence = parseTraceEvidence(impl.edge.evidence_json);
|
|
10574
|
+
const hintDiagnostic = implementationHintDiagnostic(hinted, evidence);
|
|
10575
|
+
const diagnostics = [implementationStartDiagnostic(impl.edge, evidence)];
|
|
10576
|
+
return { operationId, diagnostics: hintDiagnostic ? [hintDiagnostic, ...diagnostics] : diagnostics };
|
|
10577
|
+
}
|
|
10578
|
+
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" }] };
|
|
10579
|
+
}
|
|
10580
|
+
function sourceFilesForStart(db, repoId, start, workspaceId) {
|
|
10581
|
+
return sourceScopeForSelector(db, repoId, start, workspaceId);
|
|
10582
|
+
}
|
|
10583
|
+
function startScope(db, start, hintOptions, workspaceId) {
|
|
10584
|
+
const repos = start.repo ? reposByName(db, start.repo, workspaceId).map((row) => ({
|
|
10585
|
+
id: row.id,
|
|
10586
|
+
name: row.name,
|
|
10587
|
+
packageName: row.package_name ?? void 0
|
|
10588
|
+
})) : [];
|
|
10589
|
+
if (start.repo && repos.length === 0) return {
|
|
10590
|
+
selectorMatched: false,
|
|
10591
|
+
startDiagnostics: [selectorRepoNotFoundDiagnostic(start.repo)]
|
|
10592
|
+
};
|
|
10593
|
+
if (start.repo && repos.length > 1) return {
|
|
10594
|
+
selectorMatched: false,
|
|
10595
|
+
startDiagnostics: [selectorRepoAmbiguousDiagnostic(start.repo, repos)]
|
|
10596
|
+
};
|
|
10597
|
+
const repo = repos[0];
|
|
10598
|
+
const operationScope = operationStartScope(
|
|
10599
|
+
db,
|
|
10600
|
+
repo?.id,
|
|
10601
|
+
start,
|
|
10602
|
+
hintOptions,
|
|
10603
|
+
workspaceId
|
|
10604
|
+
);
|
|
10605
|
+
const terminalOperationScope = operationScope && !operationScope.files && (operationScope.diagnostics ?? []).some((d) => d.resolutionStage === "operation" || d.resolutionStage === "implementation");
|
|
10606
|
+
const sourceScope = operationScope?.files || terminalOperationScope ? operationScope : sourceFilesForStart(db, repo?.id, start, workspaceId);
|
|
10607
|
+
const terminalSelectorScope = Boolean(sourceScope?.diagnostics?.length && !sourceScope.files);
|
|
10608
|
+
const sourceFiles = sourceScope?.files;
|
|
10609
|
+
const hasSelector = Boolean(
|
|
10610
|
+
start.handler ?? start.operation ?? start.operationPath ?? start.servicePath
|
|
10611
|
+
);
|
|
10612
|
+
if (start.servicePath && !start.operation && !start.operationPath && !start.handler)
|
|
10613
|
+
return { repo, selectorMatched: false };
|
|
10614
|
+
return {
|
|
10615
|
+
repo,
|
|
10616
|
+
executionRepoId: sourceScope?.repoId ?? repo?.id,
|
|
10617
|
+
sourceFiles,
|
|
10618
|
+
symbolIds: sourceScope?.symbols,
|
|
10619
|
+
selectorMatched: !terminalOperationScope && !terminalSelectorScope && (!hasSelector || sourceFiles !== void 0),
|
|
10620
|
+
startOperationId: operationScope?.operationId,
|
|
10621
|
+
startDiagnostics: operationScope?.diagnostics?.length ? operationScope.diagnostics : sourceScope?.diagnostics
|
|
10622
|
+
};
|
|
10623
|
+
}
|
|
10624
|
+
function handlerFilesForOperation(db, operationId) {
|
|
10625
|
+
const op = db.prepare(
|
|
10626
|
+
`SELECT o.operation_name operationName,o.operation_path operationPath,s.repo_id repoId
|
|
10627
|
+
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?`
|
|
10628
|
+
).get(operationId);
|
|
10629
|
+
if (!op) return /* @__PURE__ */ new Set();
|
|
10630
|
+
const operation = normalizeOperation2(op.operationPath ?? op.operationName);
|
|
10631
|
+
const rows2 = db.prepare(
|
|
10632
|
+
`SELECT DISTINCT hc.source_file sourceFile,sym.id symbolId FROM handler_classes hc
|
|
10633
|
+
JOIN handler_methods hm ON hm.handler_class_id=hc.id
|
|
10634
|
+
LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id
|
|
10635
|
+
AND sym.source_file=hc.source_file
|
|
10636
|
+
AND sym.qualified_name=hc.class_name || '.' || hm.method_name
|
|
10637
|
+
AND sym.start_line=hm.source_line
|
|
10638
|
+
WHERE hc.repo_id=?
|
|
10639
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
|
|
10640
|
+
CASE WHEN hm.decorator_kind='Event' THEN 'event'
|
|
10641
|
+
WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
|
|
10642
|
+
ELSE 'unsupported' END)='operation'
|
|
10643
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
|
|
10644
|
+
CASE WHEN hm.decorator_kind IN ('Action','Func','On')
|
|
10645
|
+
THEN 1 ELSE 0 END)=1
|
|
10646
|
+
AND (hm.decorator_value=? OR hm.method_name=? OR hm.decorator_value=?)`
|
|
10647
|
+
).all(op.repoId, operation, operation, op.operationName);
|
|
10648
|
+
return new Set(rows2.map((row) => row.sourceFile).filter(Boolean));
|
|
10649
|
+
}
|
|
10650
|
+
function implementationEdge(db, operationId) {
|
|
10651
|
+
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);
|
|
10652
|
+
}
|
|
10653
|
+
function implementationScope(db, operationId) {
|
|
10654
|
+
const edge = implementationEdge(db, operationId);
|
|
10655
|
+
if (!edge || edge.status !== "resolved") return { files: /* @__PURE__ */ new Set(), edge };
|
|
10656
|
+
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);
|
|
10657
|
+
if (!row || typeof row.symbolId !== "number")
|
|
10658
|
+
return { repoId: row?.repoId, files: /* @__PURE__ */ new Set(), edge };
|
|
10659
|
+
return { repoId: row?.repoId, files: new Set(row?.sourceFile ? [row.sourceFile] : []), symbolId: row?.symbolId, edge };
|
|
10660
|
+
}
|
|
10661
|
+
function handlerScope(db, methodId) {
|
|
10662
|
+
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);
|
|
10663
|
+
if (!row || typeof row.symbolId !== "number") return void 0;
|
|
10664
|
+
return { repoId: row.repoId, files: new Set(row.sourceFile ? [row.sourceFile] : []), symbolId: row.symbolId };
|
|
8718
10665
|
}
|
|
8719
|
-
function
|
|
8720
|
-
|
|
8721
|
-
if (
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
8726
|
-
|
|
8727
|
-
|
|
8728
|
-
|
|
8729
|
-
const provenance = {
|
|
8730
|
-
callerSite: { sourceFile: String(symbolCall.source_file ?? ""), sourceLine: Number(symbolCall.source_line ?? 0) },
|
|
8731
|
-
calleeSite: { sourceFile: callee?.sourceFile, sourceLine: callee?.startLine }
|
|
8732
|
-
};
|
|
8733
|
-
args.forEach((arg, index) => {
|
|
8734
|
-
const paramBinding = parameterBindings.find((binding) => binding.index === index);
|
|
8735
|
-
const param = paramBinding?.kind === "identifier" && typeof paramBinding.name === "string" ? paramBinding.name : params[index];
|
|
8736
|
-
if (arg.kind === "identifier" && typeof arg.name === "string") {
|
|
8737
|
-
const binding = callerBindings.get(arg.name);
|
|
8738
|
-
if (binding && param) next.set(param, { ...binding, ...provenance, source: "local_symbol_argument", callerArgument: arg.name, calleeParameter: param, calleeReceiver: param });
|
|
8739
|
-
}
|
|
8740
|
-
if (arg.kind === "object_literal" && Array.isArray(arg.properties)) {
|
|
8741
|
-
for (const prop of arg.properties) {
|
|
8742
|
-
if (typeof prop.property !== "string" || typeof prop.argument !== "string") continue;
|
|
8743
|
-
const binding = callerBindings.get(prop.argument);
|
|
8744
|
-
if (!binding) continue;
|
|
8745
|
-
const destructured = paramBinding?.kind === "object_pattern" && Array.isArray(paramBinding.properties) ? paramBinding.properties.find((item) => item.property === prop.property && typeof item.local === "string") : void 0;
|
|
8746
|
-
if (destructured && typeof destructured.local === "string") next.set(destructured.local, { ...binding, ...provenance, source: "local_symbol_destructured_object_argument", callerProperty: prop.property, callerArgument: prop.argument, calleeParameter: String(index), calleeReceiver: destructured.local });
|
|
8747
|
-
else if (param) {
|
|
8748
|
-
next.set(`${param}.${prop.property}`, { ...binding, ...provenance, source: "local_symbol_object_argument", callerProperty: prop.property, callerArgument: prop.argument, calleeParameter: param, calleeReceiver: `${param}.${prop.property}` });
|
|
8749
|
-
for (const alias of parameterPropertyAliases) {
|
|
8750
|
-
if (alias.parameter === param && alias.property === prop.property && typeof alias.local === "string") next.set(alias.local, { ...binding, ...provenance, source: "local_symbol_object_parameter_destructure", callerProperty: prop.property, callerArgument: prop.argument, calleeParameter: param, calleeObjectProperty: `${param}.${prop.property}`, calleeReceiver: alias.local, calleeLocalDestructuredIdentifier: alias.local, parameterPropertyAliasKind: alias.kind, parameterPropertyAliasLine: alias.line });
|
|
8751
|
-
}
|
|
8752
|
-
}
|
|
8753
|
-
}
|
|
8754
|
-
}
|
|
8755
|
-
if (arg.kind === "array_literal" && Array.isArray(arg.elements) && paramBinding?.kind === "array_pattern" && Array.isArray(paramBinding.elements)) {
|
|
8756
|
-
for (const element of arg.elements) {
|
|
8757
|
-
const target = paramBinding.elements.find((item) => item.index === element.index);
|
|
8758
|
-
if (element.kind !== "identifier" || typeof element.name !== "string" || typeof target?.local !== "string") continue;
|
|
8759
|
-
const binding = callerBindings.get(element.name);
|
|
8760
|
-
if (binding) next.set(target.local, { ...binding, ...provenance, source: "local_symbol_destructured_array_argument", callerArgument: element.name, calleeParameter: String(index), calleeReceiver: target.local });
|
|
8761
|
-
}
|
|
8762
|
-
}
|
|
8763
|
-
});
|
|
8764
|
-
return next;
|
|
10666
|
+
function traceEdgeType(call, row) {
|
|
10667
|
+
if (row.to_kind === "operation" && row.edge_type === "REMOTE_CALL_RESOLVES_TO_OPERATION") return "remote_action";
|
|
10668
|
+
if (row.to_kind === "operation" && row.edge_type === "LOCAL_CALL_RESOLVES_TO_OPERATION") return "local_service_call";
|
|
10669
|
+
return String(call.call_type);
|
|
10670
|
+
}
|
|
10671
|
+
function includeCall(type, options) {
|
|
10672
|
+
if (!options.includeDb && type === "local_db_query") return false;
|
|
10673
|
+
if (!options.includeExternal && type === "external_http") return false;
|
|
10674
|
+
if (!options.includeAsync && type.startsWith("async_")) return false;
|
|
10675
|
+
return true;
|
|
8765
10676
|
}
|
|
8766
10677
|
function trace(db, start, options) {
|
|
10678
|
+
const observer = compactObserver(options);
|
|
10679
|
+
const schemaLifecycle = schemaLifecycleDiagnostic(db);
|
|
10680
|
+
if (schemaLifecycle)
|
|
10681
|
+
return { start, nodes: [], edges: [], diagnostics: [schemaLifecycle] };
|
|
8767
10682
|
const hintOptions = { implementationRepo: options.implementationRepo, implementationHints: options.implementationHints };
|
|
8768
10683
|
const scope = startScope(db, start, hintOptions, options.workspaceId);
|
|
8769
10684
|
const hasSelector = Boolean(start.repo || start.handler || start.operation || start.operationPath || start.servicePath);
|
|
8770
10685
|
const diagnosticRepoId = scope.executionRepoId ?? scope.repo?.id;
|
|
10686
|
+
const scheduler = new TraversalScopeScheduler();
|
|
10687
|
+
const roots = createTraceRootPlan(db, scheduler, {
|
|
10688
|
+
repoId: diagnosticRepoId,
|
|
10689
|
+
files: scope.sourceFiles,
|
|
10690
|
+
symbolIds: scope.symbolIds,
|
|
10691
|
+
selectorMatched: scope.selectorMatched
|
|
10692
|
+
}, options.workspaceId, Boolean(options.includeAsync));
|
|
10693
|
+
observer?.setWorkspaceId?.(roots.workspaceId);
|
|
10694
|
+
if (roots.diagnostic)
|
|
10695
|
+
return { start, nodes: [], edges: [], diagnostics: [roots.diagnostic] };
|
|
10696
|
+
const { workspaceId, queue, pendingRoots } = roots;
|
|
8771
10697
|
const diagnostics = loadTraceDiagnostics(
|
|
8772
10698
|
db,
|
|
8773
10699
|
diagnosticRepoId,
|
|
8774
10700
|
!hasSelector,
|
|
8775
|
-
|
|
10701
|
+
workspaceId
|
|
8776
10702
|
);
|
|
8777
|
-
const stale = diagnosticRepoId !== void 0 || !hasSelector ? 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,
|
|
10703
|
+
const stale = diagnosticRepoId !== void 0 || !hasSelector ? 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) : [];
|
|
8778
10704
|
for (const row of stale)
|
|
8779
10705
|
prependTraceDiagnostic(diagnostics, { severity: "warning", code: "graph_stale", message: `Graph is stale for ${row.name ?? "repository"}: ${row.reason ?? "facts_changed"}. Run service-flow link.` });
|
|
8780
10706
|
for (const diagnostic of scope.startDiagnostics ?? [])
|
|
@@ -8783,9 +10709,8 @@ function trace(db, start, options) {
|
|
|
8783
10709
|
prependTraceDiagnostic(diagnostics, selectorNotFoundDiagnostic(start));
|
|
8784
10710
|
const maxDepth = positiveDepth(options.depth);
|
|
8785
10711
|
const edges = [];
|
|
10712
|
+
const recorder = new TraceEdgeRecorder(edges, observer);
|
|
8786
10713
|
const nodes = /* @__PURE__ */ new Map();
|
|
8787
|
-
const seenEdges = /* @__PURE__ */ new Set();
|
|
8788
|
-
const queue = scope.selectorMatched ? [{ repoId: scope.executionRepoId, files: scope.sourceFiles, symbolIds: scope.symbolIds, depth: 1, context: /* @__PURE__ */ new Map() }] : [];
|
|
8789
10714
|
if (scope.startOperationId && scope.selectorMatched) {
|
|
8790
10715
|
const op = operationNode(db, scope.startOperationId);
|
|
8791
10716
|
const impl = implementationScope(db, scope.startOperationId);
|
|
@@ -8799,7 +10724,7 @@ function trace(db, start, options) {
|
|
|
8799
10724
|
if (impl.edge && (impl.edge.status === "resolved" || startSelection.methodId)) {
|
|
8800
10725
|
const selectedMethodId = impl.edge.status === "resolved" ? impl.edge.to_id : startSelection.methodId;
|
|
8801
10726
|
const implEvidence = {
|
|
8802
|
-
...
|
|
10727
|
+
...parseTraceEvidence(impl.edge.evidence_json),
|
|
8803
10728
|
startResolution: {
|
|
8804
10729
|
strategy: "indexed_operation_graph",
|
|
8805
10730
|
matchedOperationId: scope.startOperationId,
|
|
@@ -8816,39 +10741,109 @@ function trace(db, start, options) {
|
|
|
8816
10741
|
) : { evidence: implEvidence };
|
|
8817
10742
|
if (selected.diagnostic) prependTraceDiagnostic(diagnostics, selected.diagnostic);
|
|
8818
10743
|
if (selected.handler) nodes.set(String(selected.handler.id), selected.handler);
|
|
8819
|
-
|
|
8820
|
-
|
|
10744
|
+
const unresolvedReason = selected.unresolvedReason ?? (impl.edge.status === "resolved" || startSelection.methodId ? void 0 : String(impl.edge.unresolved_reason ?? impl.edge.status));
|
|
10745
|
+
const selectedScope = selectedMethodId ? handlerScope(db, selectedMethodId) : void 0;
|
|
10746
|
+
const edge = { 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 };
|
|
10747
|
+
recordImplementationObservation(recorder, edge, {
|
|
10748
|
+
operationId: scope.startOperationId,
|
|
10749
|
+
handlerMethodId: selectedMethodId,
|
|
10750
|
+
handlerSymbolId: selectedScope?.symbolId,
|
|
10751
|
+
graphEdgeId: impl.edge.id,
|
|
10752
|
+
persistedStatus: impl.edge.status,
|
|
10753
|
+
persistedTargetKind: impl.edge.to_kind,
|
|
10754
|
+
persistedTargetId: impl.edge.to_id,
|
|
10755
|
+
effectiveStatus: startSelection.methodId ? "resolved" : String(impl.edge.status ?? "unresolved"),
|
|
10756
|
+
strategy: String(startSelection.evidence.strategy ?? "indexed_operation_graph"),
|
|
10757
|
+
guided: startSelection.evidence.guided === true,
|
|
10758
|
+
unresolvedReason,
|
|
10759
|
+
evidence: selected.evidence,
|
|
10760
|
+
site: op ?? {}
|
|
10761
|
+
});
|
|
8821
10762
|
}
|
|
8822
10763
|
}
|
|
8823
|
-
|
|
8824
|
-
|
|
10764
|
+
while (queue.length > 0 || pendingRoots.length > 0) {
|
|
10765
|
+
if ((queue[0]?.depth ?? Number.POSITIVE_INFINITY) > 1 && workspaceId !== void 0) {
|
|
10766
|
+
const root = nextPendingRoot(pendingRoots, scheduler, workspaceId);
|
|
10767
|
+
if (root) queue.unshift(root);
|
|
10768
|
+
}
|
|
8825
10769
|
const current = queue.shift();
|
|
8826
10770
|
if (!current || current.depth > maxDepth) continue;
|
|
8827
|
-
|
|
8828
|
-
const key = `${current.repoId ?? "*"}:${[...current.symbolIds ?? /* @__PURE__ */ new Set(["*"])].sort().join(",")}:${[...current.files ?? /* @__PURE__ */ new Set(["*"])].sort().join(",")}:${contextKey}`;
|
|
8829
|
-
if (seenScopes.has(key)) continue;
|
|
8830
|
-
seenScopes.add(key);
|
|
10771
|
+
if (!scheduler.markExpanded(current.state)) continue;
|
|
8831
10772
|
const calls = db.prepare(
|
|
8832
|
-
`SELECT c.*,r.name repoName
|
|
8833
|
-
|
|
10773
|
+
`SELECT c.*,r.name repoName,r.workspace_id workspaceId,
|
|
10774
|
+
r.graph_generation graphGeneration
|
|
10775
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
10776
|
+
WHERE (? IS NULL OR c.repo_id=?) AND (? IS NULL OR r.workspace_id=?)
|
|
10777
|
+
ORDER BY c.source_file COLLATE BINARY,c.call_site_start_offset,
|
|
10778
|
+
c.call_site_end_offset,c.source_line,c.id`
|
|
10779
|
+
).all(current.repoId, current.repoId, workspaceId, workspaceId);
|
|
8834
10780
|
const filtered = calls.filter(
|
|
8835
|
-
(c) => (!current.symbolIds || current.symbolIds.has(Number(c.source_symbol_id))) && (!current.files || current.files.has(String(c.source_file))) && includeCall(String(c.call_type), options)
|
|
10781
|
+
(c) => (current.unownedOnly ? c.source_symbol_id == null : !current.symbolIds || current.symbolIds.has(Number(c.source_symbol_id))) && (!current.files || current.files.has(String(c.source_file))) && includeCall(String(c.call_type), options)
|
|
8836
10782
|
);
|
|
8837
|
-
const callerBindings = new Map([...current.context
|
|
8838
|
-
if (current.symbolIds && current.symbolIds.size > 0 && current.depth < maxDepth) {
|
|
8839
|
-
const symbolRows = db.prepare(`SELECT sc.*,s.repo_id calleeRepoId,
|
|
10783
|
+
const callerBindings = new Map([...current.context, ...knownBindingsForScope(db, current.repoId, current.symbolIds, current.files), ...knownBindingsForCalls(db, filtered)]);
|
|
10784
|
+
if (!current.rootObservationOnly && current.symbolIds && current.symbolIds.size > 0 && current.depth < maxDepth) {
|
|
10785
|
+
const symbolRows = db.prepare(`SELECT sc.*,s.repo_id calleeRepoId,
|
|
10786
|
+
s.source_file calleeFile FROM symbol_calls sc
|
|
10787
|
+
LEFT JOIN symbols s ON s.id=sc.callee_symbol_id
|
|
10788
|
+
WHERE sc.call_role='ordinary_call'
|
|
10789
|
+
AND sc.caller_symbol_id IN (${[...current.symbolIds].map(() => "?").join(",")})
|
|
10790
|
+
ORDER BY sc.source_file COLLATE BINARY,sc.call_site_start_offset,
|
|
10791
|
+
sc.call_site_end_offset,sc.source_line,sc.id`).all(
|
|
10792
|
+
...current.symbolIds
|
|
10793
|
+
);
|
|
8840
10794
|
for (const symbolCall of symbolRows) {
|
|
8841
10795
|
if (!symbolCall.callee_symbol_id) continue;
|
|
8842
10796
|
const nextSymbols = /* @__PURE__ */ new Set([Number(symbolCall.callee_symbol_id)]);
|
|
8843
10797
|
const nextFiles = /* @__PURE__ */ new Set([String(symbolCall.calleeFile)]);
|
|
8844
10798
|
const nextRepoId = Number(symbolCall.calleeRepoId);
|
|
8845
|
-
const
|
|
10799
|
+
const nextContext = contextForSymbolCall(db, symbolCall, callerBindings);
|
|
10800
|
+
const scheduling = scheduler.schedule({
|
|
10801
|
+
workspaceId,
|
|
10802
|
+
repoId: nextRepoId,
|
|
10803
|
+
files: nextFiles,
|
|
10804
|
+
symbolIds: nextSymbols,
|
|
10805
|
+
context: nextContext
|
|
10806
|
+
}, current.state);
|
|
8846
10807
|
const calleeNode = symbolNode(db, Number(symbolCall.callee_symbol_id));
|
|
8847
10808
|
if (calleeNode) nodes.set(String(calleeNode.id), calleeNode);
|
|
8848
|
-
const evidence = { ...
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
10809
|
+
const evidence = { ...parseTraceEvidence(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 };
|
|
10810
|
+
const unresolvedReason = String(symbolCall.status) === "resolved" ? void 0 : symbolCall.unresolved_reason ? String(symbolCall.unresolved_reason) : void 0;
|
|
10811
|
+
const edge = { 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 };
|
|
10812
|
+
const target = recordLocalCallObservation(recorder, edge, {
|
|
10813
|
+
symbolCall,
|
|
10814
|
+
evidence,
|
|
10815
|
+
unresolvedReason
|
|
10816
|
+
});
|
|
10817
|
+
if (scheduling.kind === "cycle") {
|
|
10818
|
+
const cycleEvidence = {
|
|
10819
|
+
cycle: true,
|
|
10820
|
+
cycleReason: "structural_ancestry_cycle",
|
|
10821
|
+
symbolCallId: symbolCall.id
|
|
10822
|
+
};
|
|
10823
|
+
const cycleEdge = { 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" };
|
|
10824
|
+
recordCycleObservation(recorder, cycleEdge, target, {
|
|
10825
|
+
workspaceId,
|
|
10826
|
+
repositoryId: nextRepoId,
|
|
10827
|
+
sourceFiles: nextFiles,
|
|
10828
|
+
symbolIds: nextSymbols,
|
|
10829
|
+
structuralKey: scheduling.state.structuralKey
|
|
10830
|
+
}, {
|
|
10831
|
+
symbolCallId: symbolCall.id,
|
|
10832
|
+
symbolId: symbolCall.callee_symbol_id
|
|
10833
|
+
}, symbolCall);
|
|
10834
|
+
}
|
|
10835
|
+
if (scheduling.kind === "scheduled") enqueueCausalScope(
|
|
10836
|
+
queue,
|
|
10837
|
+
pendingRoots,
|
|
10838
|
+
{
|
|
10839
|
+
repoId: nextRepoId,
|
|
10840
|
+
files: nextFiles,
|
|
10841
|
+
symbolIds: nextSymbols,
|
|
10842
|
+
depth: current.depth + 1,
|
|
10843
|
+
context: nextContext,
|
|
10844
|
+
state: scheduling.state
|
|
10845
|
+
}
|
|
10846
|
+
);
|
|
8852
10847
|
}
|
|
8853
10848
|
}
|
|
8854
10849
|
const graph = graphForCalls(
|
|
@@ -8866,29 +10861,27 @@ function trace(db, start, options) {
|
|
|
8866
10861
|
callType: call.call_type
|
|
8867
10862
|
});
|
|
8868
10863
|
const persistedRowsForCall = graph.get(Number(call.id)) ?? [];
|
|
8869
|
-
const contextual = contextualRuntimeResolution(db, call, callerBindings.get(
|
|
10864
|
+
const contextual = contextualRuntimeResolution(db, call, callerBindings.get(receiverFromTraceEvidence(call.evidence_json) ?? ""), call.workspaceId, persistedRowsForCall);
|
|
8870
10865
|
const graphRows = contextual.row ? [contextual.row] : persistedRowsForCall;
|
|
8871
10866
|
for (const row of graphRows) {
|
|
8872
|
-
|
|
8873
|
-
seenEdges.add(Number(row.id));
|
|
8874
|
-
const persistedEvidence = parseEvidence(row.evidence_json);
|
|
10867
|
+
const persistedEvidence = parseTraceEvidence(row.evidence_json);
|
|
8875
10868
|
const rawEvidence = baseTraceEvidence(row, call, persistedEvidence, contextual.evidence);
|
|
8876
10869
|
const effective = runtimeResolution(db, row, rawEvidence, {
|
|
8877
10870
|
vars: options.vars,
|
|
8878
10871
|
dynamicMode: options.dynamicMode ?? "strict",
|
|
8879
10872
|
maxDynamicCandidates: options.maxDynamicCandidates
|
|
8880
|
-
},
|
|
10873
|
+
}, call.workspaceId, contextual.state);
|
|
8881
10874
|
const evidence = effective.evidence;
|
|
8882
10875
|
const effectiveRow = effective.row;
|
|
8883
|
-
const
|
|
10876
|
+
const targetNode2 = `${effectiveRow.to_kind}:${effectiveRow.to_id}`;
|
|
8884
10877
|
const opNode = effectiveRow.to_kind === "operation" ? operationNode(db, effectiveRow.to_id) : void 0;
|
|
8885
|
-
nodes.set(
|
|
8886
|
-
id:
|
|
10878
|
+
nodes.set(targetNode2, opNode ?? {
|
|
10879
|
+
id: targetNode2,
|
|
8887
10880
|
kind: effectiveRow.to_kind,
|
|
8888
10881
|
label: effectiveRow.to_kind === "db_entity" ? `Entity: ${effectiveRow.to_id || "unknown"}` : effectiveRow.to_id
|
|
8889
10882
|
});
|
|
8890
10883
|
const to = edgeTarget(effectiveRow, evidence);
|
|
8891
|
-
|
|
10884
|
+
const edge = {
|
|
8892
10885
|
step: current.depth,
|
|
8893
10886
|
type: traceEdgeType(call, effectiveRow),
|
|
8894
10887
|
from: `${call.repoName}:${call.source_file}:${call.source_line}`,
|
|
@@ -8896,9 +10889,88 @@ function trace(db, start, options) {
|
|
|
8896
10889
|
evidence,
|
|
8897
10890
|
confidence: Number(effectiveRow.confidence ?? call.confidence),
|
|
8898
10891
|
unresolvedReason: effective.unresolvedReason
|
|
10892
|
+
};
|
|
10893
|
+
const semanticWorkspaceId = workspaceId ?? call.workspaceId;
|
|
10894
|
+
const semantic = recordOutboundObservation(recorder, edge, {
|
|
10895
|
+
call,
|
|
10896
|
+
row: effectiveRow,
|
|
10897
|
+
evidence,
|
|
10898
|
+
workspaceId: semanticWorkspaceId,
|
|
10899
|
+
dynamicMode: options.dynamicMode,
|
|
10900
|
+
unresolvedReason: effective.unresolvedReason
|
|
8899
10901
|
});
|
|
8900
|
-
if (
|
|
8901
|
-
|
|
10902
|
+
if (options.includeAsync && call.call_type === "async_emit" && effectiveRow.edge_type === "HANDLER_EMITS_EVENT" && typeof call.event_name_expr === "string") {
|
|
10903
|
+
const plans = planEventSubscriberTransitions(db, {
|
|
10904
|
+
workspaceId: workspaceId ?? call.workspaceId,
|
|
10905
|
+
graphGeneration: call.graphGeneration,
|
|
10906
|
+
eventName: call.event_name_expr
|
|
10907
|
+
}, scheduler, current.state, current.depth, maxDepth);
|
|
10908
|
+
for (const plan of plans) {
|
|
10909
|
+
const nodeId = String(plan.node.id);
|
|
10910
|
+
const targetLabel = String(plan.node.label ?? nodeId);
|
|
10911
|
+
nodes.set(nodeId, plan.node);
|
|
10912
|
+
const bridgeEdge = {
|
|
10913
|
+
step: current.depth,
|
|
10914
|
+
type: "event_name_matches_subscription_handler",
|
|
10915
|
+
from: plan.transition.eventName,
|
|
10916
|
+
to: targetLabel,
|
|
10917
|
+
evidence: plan.evidence,
|
|
10918
|
+
confidence: plan.transition.confidence,
|
|
10919
|
+
unresolvedReason: plan.transition.unresolvedReason
|
|
10920
|
+
};
|
|
10921
|
+
const handler = plan.transition.handler;
|
|
10922
|
+
const bridgeTarget = recordEventBridgeObservation(
|
|
10923
|
+
recorder,
|
|
10924
|
+
bridgeEdge,
|
|
10925
|
+
plan,
|
|
10926
|
+
semanticWorkspaceId,
|
|
10927
|
+
plans.length
|
|
10928
|
+
);
|
|
10929
|
+
if (plan.bodyExpansion === "cycle_blocked" && plan.state) {
|
|
10930
|
+
const cycleEvidence = {
|
|
10931
|
+
cycle: true,
|
|
10932
|
+
cycleReason: "structural_ancestry_cycle",
|
|
10933
|
+
graphEdgeId: plan.transition.graphEdgeId
|
|
10934
|
+
};
|
|
10935
|
+
const cycleEdge = { 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" };
|
|
10936
|
+
recordEventCycleObservation(
|
|
10937
|
+
recorder,
|
|
10938
|
+
cycleEdge,
|
|
10939
|
+
plan,
|
|
10940
|
+
bridgeTarget,
|
|
10941
|
+
semanticWorkspaceId
|
|
10942
|
+
);
|
|
10943
|
+
}
|
|
10944
|
+
if (plan.bodyExpansion === "scheduled" && plan.state && handler) {
|
|
10945
|
+
const files = /* @__PURE__ */ new Set([handler.sourceFile]);
|
|
10946
|
+
const symbolIds = /* @__PURE__ */ new Set([handler.symbolId]);
|
|
10947
|
+
enqueueCausalScope(queue, pendingRoots, {
|
|
10948
|
+
repoId: handler.repoId,
|
|
10949
|
+
files,
|
|
10950
|
+
symbolIds,
|
|
10951
|
+
depth: current.depth + 1,
|
|
10952
|
+
context: /* @__PURE__ */ new Map(),
|
|
10953
|
+
state: plan.state
|
|
10954
|
+
});
|
|
10955
|
+
}
|
|
10956
|
+
}
|
|
10957
|
+
}
|
|
10958
|
+
if ((options.dynamicMode ?? "strict") === "candidates" && effectiveRow.status !== "resolved") {
|
|
10959
|
+
for (const branch of dynamicCandidateBranches(
|
|
10960
|
+
current.depth,
|
|
10961
|
+
call,
|
|
10962
|
+
evidence
|
|
10963
|
+
)) {
|
|
10964
|
+
recordDynamicBranchObservation(
|
|
10965
|
+
recorder,
|
|
10966
|
+
branch,
|
|
10967
|
+
call,
|
|
10968
|
+
semantic.source,
|
|
10969
|
+
evidence,
|
|
10970
|
+
semanticWorkspaceId
|
|
10971
|
+
);
|
|
10972
|
+
}
|
|
10973
|
+
}
|
|
8902
10974
|
if (effectiveRow.to_kind === "operation") {
|
|
8903
10975
|
const implementation = implementationScope(db, effectiveRow.to_id);
|
|
8904
10976
|
const contextSelection = contextualImplementationSelection(
|
|
@@ -8912,7 +10984,7 @@ function trace(db, start, options) {
|
|
|
8912
10984
|
const contextMethodId = contextSelection.methodId;
|
|
8913
10985
|
let selectedHandlerAvailable = true;
|
|
8914
10986
|
if (implementation.edge) {
|
|
8915
|
-
const implEvidence =
|
|
10987
|
+
const implEvidence = parseTraceEvidence(implementation.edge.evidence_json);
|
|
8916
10988
|
const hintDiagnostic = implementationHintDiagnostic(contextSelection, implEvidence);
|
|
8917
10989
|
if (hintDiagnostic) prependTraceDiagnostic(diagnostics, hintDiagnostic);
|
|
8918
10990
|
const selectedMethodId = implementation.edge.status === "resolved" ? implementation.edge.to_id : contextMethodId;
|
|
@@ -8935,14 +11007,32 @@ function trace(db, start, options) {
|
|
|
8935
11007
|
if (selected.diagnostic) prependTraceDiagnostic(diagnostics, selected.diagnostic);
|
|
8936
11008
|
const implTo = selected.handler?.label ? String(selected.handler.label) : `${implementation.edge.to_kind}:${implementation.edge.to_id}`;
|
|
8937
11009
|
if (selected.handler) nodes.set(String(selected.handler.id), selected.handler);
|
|
8938
|
-
|
|
11010
|
+
const unresolvedReason = selected.unresolvedReason ?? (implementation.edge.status === "resolved" || contextMethodId ? void 0 : String(implementation.edge.unresolved_reason ?? implementation.edge.status));
|
|
11011
|
+
const implementationTraceEdge = {
|
|
8939
11012
|
step: current.depth,
|
|
8940
11013
|
type: "operation_implemented_by_handler",
|
|
8941
11014
|
from: to,
|
|
8942
11015
|
to: implTo,
|
|
8943
11016
|
evidence: selected.evidence,
|
|
8944
11017
|
confidence: Number(implementation.edge.confidence ?? 0),
|
|
8945
|
-
unresolvedReason
|
|
11018
|
+
unresolvedReason
|
|
11019
|
+
};
|
|
11020
|
+
const selectedScope = selectedMethodId ? handlerScope(db, selectedMethodId) : void 0;
|
|
11021
|
+
recordImplementationObservation(recorder, implementationTraceEdge, {
|
|
11022
|
+
operationId: effectiveRow.to_id,
|
|
11023
|
+
handlerMethodId: selectedMethodId,
|
|
11024
|
+
handlerSymbolId: selectedScope?.symbolId,
|
|
11025
|
+
graphEdgeId: implementation.edge.id,
|
|
11026
|
+
persistedStatus: implementation.edge.status,
|
|
11027
|
+
persistedTargetKind: implementation.edge.to_kind,
|
|
11028
|
+
persistedTargetId: implementation.edge.to_id,
|
|
11029
|
+
effectiveStatus: contextMethodId ? "resolved" : String(implementation.edge.status),
|
|
11030
|
+
strategy: String(contextSelection.evidence.strategy ?? (contextMethodId ? "contextual_implementation_selection" : "indexed_operation_graph")),
|
|
11031
|
+
guided: contextSelection.evidence.guided === true,
|
|
11032
|
+
contextual: Boolean(contextMethodId && contextSelection.evidence.strategy !== "implementation_repo_hint"),
|
|
11033
|
+
unresolvedReason,
|
|
11034
|
+
evidence: selected.evidence,
|
|
11035
|
+
site: call
|
|
8946
11036
|
});
|
|
8947
11037
|
}
|
|
8948
11038
|
if (current.depth >= maxDepth) continue;
|
|
@@ -8953,24 +11043,53 @@ function trace(db, start, options) {
|
|
|
8953
11043
|
const targetRepoId = contextScope?.repoId ?? implementation.repoId ?? db.prepare(
|
|
8954
11044
|
"SELECT s.repo_id repoId FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.id=?"
|
|
8955
11045
|
).get(effectiveRow.to_id)?.repoId;
|
|
8956
|
-
const
|
|
8957
|
-
|
|
8958
|
-
|
|
11046
|
+
const nextContext = /* @__PURE__ */ new Map();
|
|
11047
|
+
const scheduling = scheduler.schedule({
|
|
11048
|
+
workspaceId: workspaceId ?? call.workspaceId,
|
|
11049
|
+
repoId: targetRepoId,
|
|
11050
|
+
files,
|
|
11051
|
+
symbolIds,
|
|
11052
|
+
context: nextContext
|
|
11053
|
+
}, current.state);
|
|
11054
|
+
if (scheduling.kind === "cycle") {
|
|
11055
|
+
const cycleEvidence = {
|
|
11056
|
+
...evidence,
|
|
11057
|
+
cycle: true,
|
|
11058
|
+
cycleReason: "structural_ancestry_cycle"
|
|
11059
|
+
};
|
|
11060
|
+
const cycleEdge = {
|
|
8959
11061
|
step: current.depth,
|
|
8960
11062
|
type: "cycle",
|
|
8961
11063
|
from: to,
|
|
8962
|
-
to:
|
|
8963
|
-
evidence:
|
|
11064
|
+
to: scheduling.state.structuralKey,
|
|
11065
|
+
evidence: cycleEvidence,
|
|
8964
11066
|
confidence: 1,
|
|
8965
|
-
unresolvedReason: "Cycle detected; downstream scope
|
|
8966
|
-
}
|
|
8967
|
-
|
|
8968
|
-
|
|
11067
|
+
unresolvedReason: "Cycle detected in structural ancestry; downstream scope was not expanded"
|
|
11068
|
+
};
|
|
11069
|
+
recordCycleObservation(recorder, cycleEdge, semantic.target, {
|
|
11070
|
+
workspaceId: semanticWorkspaceId,
|
|
11071
|
+
repositoryId: targetRepoId,
|
|
11072
|
+
sourceFiles: files,
|
|
11073
|
+
symbolIds,
|
|
11074
|
+
structuralKey: scheduling.state.structuralKey
|
|
11075
|
+
}, {
|
|
11076
|
+
graphEdgeId: evidence.persistedGraphEdgeId,
|
|
11077
|
+
outboundCallId: call.id,
|
|
11078
|
+
operationId: effectiveRow.to_id
|
|
11079
|
+
}, call);
|
|
11080
|
+
}
|
|
11081
|
+
if (scheduling.kind === "scheduled") enqueueCausalScope(
|
|
11082
|
+
queue,
|
|
11083
|
+
pendingRoots,
|
|
11084
|
+
{
|
|
8969
11085
|
repoId: targetRepoId,
|
|
8970
11086
|
files,
|
|
8971
11087
|
symbolIds,
|
|
8972
|
-
depth: current.depth + 1
|
|
8973
|
-
|
|
11088
|
+
depth: current.depth + 1,
|
|
11089
|
+
context: nextContext,
|
|
11090
|
+
state: scheduling.state
|
|
11091
|
+
}
|
|
11092
|
+
);
|
|
8974
11093
|
}
|
|
8975
11094
|
}
|
|
8976
11095
|
}
|
|
@@ -8982,9 +11101,1010 @@ function trace(db, start, options) {
|
|
|
8982
11101
|
prependTraceDiagnostic(diagnostics, diagnostic);
|
|
8983
11102
|
return { start, nodes: [...nodes.values()], edges, diagnostics };
|
|
8984
11103
|
}
|
|
11104
|
+
function traceWithObserver(db, start, options, observer) {
|
|
11105
|
+
const observed = {
|
|
11106
|
+
...options,
|
|
11107
|
+
[compactObserverKey]: observer
|
|
11108
|
+
};
|
|
11109
|
+
return trace(db, start, observed);
|
|
11110
|
+
}
|
|
11111
|
+
|
|
11112
|
+
// src/trace/014-compact-contract.ts
|
|
11113
|
+
var CompactObservationCollector = class {
|
|
11114
|
+
observations = [];
|
|
11115
|
+
workspaceId;
|
|
11116
|
+
record(observation2) {
|
|
11117
|
+
this.observations.push(observation2);
|
|
11118
|
+
}
|
|
11119
|
+
setWorkspaceId(workspaceId) {
|
|
11120
|
+
this.workspaceId = workspaceId;
|
|
11121
|
+
}
|
|
11122
|
+
};
|
|
11123
|
+
|
|
11124
|
+
// src/trace/020-compact-field-projection.ts
|
|
11125
|
+
var compactNameLimit = 8;
|
|
11126
|
+
var compactDiagnosticMessages = {
|
|
11127
|
+
schema_upgrade_required: "The database schema must be upgraded before tracing.",
|
|
11128
|
+
reindex_required: "Current analyzer facts are required before tracing.",
|
|
11129
|
+
trace_workspace_ambiguous: "The trace workspace is ambiguous.",
|
|
11130
|
+
trace_runtime_variables_missing: "Runtime variable names are required to resolve a branch.",
|
|
11131
|
+
implementation_hint_mismatch: "The implementation hint did not select one implementation.",
|
|
11132
|
+
selected_handler_provenance_mismatch: "Selected handler provenance did not match its graph target.",
|
|
11133
|
+
selected_handler_target_not_found: "The selected handler target is not indexed.",
|
|
11134
|
+
trace_start_implementation_unresolved: "The trace start implementation is unresolved."
|
|
11135
|
+
};
|
|
11136
|
+
function projectCompactDecision(input) {
|
|
11137
|
+
if (!input) return {};
|
|
11138
|
+
const out = resolutionDecision(input);
|
|
11139
|
+
addNameDecision(out, input);
|
|
11140
|
+
addDynamicDecision(out, input);
|
|
11141
|
+
addImplementationDecision(out, input);
|
|
11142
|
+
addEventDecision(out, input);
|
|
11143
|
+
const reasonCode = compactSafeCode(input.reasonCode);
|
|
11144
|
+
if (reasonCode) out.reasonCode = reasonCode;
|
|
11145
|
+
addRemediationDecision(out, input);
|
|
11146
|
+
return out;
|
|
11147
|
+
}
|
|
11148
|
+
function resolutionDecision(input) {
|
|
11149
|
+
const out = {};
|
|
11150
|
+
const status = compactSafeCode(input.effectiveResolutionStatus);
|
|
11151
|
+
if (status) out.effectiveResolutionStatus = status;
|
|
11152
|
+
if (!persistedResolutionDiffers(input)) return out;
|
|
11153
|
+
const persistedStatus = compactSafeCode(input.persistedResolutionStatus);
|
|
11154
|
+
if (persistedStatus) out.persistedResolutionStatus = persistedStatus;
|
|
11155
|
+
return out;
|
|
11156
|
+
}
|
|
11157
|
+
function addNameDecision(out, input) {
|
|
11158
|
+
const allNames = safeVariableNames(input.missingVariableNames);
|
|
11159
|
+
const names = allNames.slice(0, compactNameLimit);
|
|
11160
|
+
const total = Math.max(compactCount(input.missingVariableCount), allNames.length);
|
|
11161
|
+
if (names.length > 0) out.missingVariableNames = names;
|
|
11162
|
+
if (total === 0) return;
|
|
11163
|
+
out.missingVariableCount = total;
|
|
11164
|
+
out.shownMissingVariableCount = names.length;
|
|
11165
|
+
out.omittedMissingVariableCount = Math.max(0, total - names.length);
|
|
11166
|
+
}
|
|
11167
|
+
function addDynamicDecision(out, input) {
|
|
11168
|
+
if (input.dynamicMode) out.dynamicMode = input.dynamicMode;
|
|
11169
|
+
if (input.candidateCount !== void 0) out.candidateCount = compactCount(input.candidateCount);
|
|
11170
|
+
if (input.viableCandidateCount !== void 0)
|
|
11171
|
+
out.viableCandidateCount = compactCount(input.viableCandidateCount);
|
|
11172
|
+
if (input.rejectedCandidateCount !== void 0)
|
|
11173
|
+
out.rejectedCandidateCount = compactCount(input.rejectedCandidateCount);
|
|
11174
|
+
if (input.omittedCandidateCount !== void 0)
|
|
11175
|
+
out.omittedCandidateCount = compactCount(input.omittedCandidateCount);
|
|
11176
|
+
}
|
|
11177
|
+
function addImplementationDecision(out, input) {
|
|
11178
|
+
const strategy = compactSafeCode(input.implementationStrategy);
|
|
11179
|
+
if (strategy) out.implementationStrategy = strategy;
|
|
11180
|
+
if (input.implementationGuided !== void 0)
|
|
11181
|
+
out.implementationGuided = input.implementationGuided;
|
|
11182
|
+
if (input.implementationContextual !== void 0)
|
|
11183
|
+
out.implementationContextual = input.implementationContextual;
|
|
11184
|
+
}
|
|
11185
|
+
function addEventDecision(out, input) {
|
|
11186
|
+
addEventCodes(out, input);
|
|
11187
|
+
if (input.eventSubscriptionCount !== void 0)
|
|
11188
|
+
out.eventSubscriptionCount = compactCount(input.eventSubscriptionCount);
|
|
11189
|
+
if (input.roleSiteMatchCount !== void 0)
|
|
11190
|
+
out.roleSiteMatchCount = compactCount(input.roleSiteMatchCount);
|
|
11191
|
+
}
|
|
11192
|
+
function addEventCodes(out, input) {
|
|
11193
|
+
const values = [
|
|
11194
|
+
["eventMatchStrategy", compactSafeCode(input.eventMatchStrategy)],
|
|
11195
|
+
["dispatchCertainty", compactSafeCode(input.dispatchCertainty)],
|
|
11196
|
+
["associationStatus", compactSafeCode(input.associationStatus)],
|
|
11197
|
+
["associationBasis", compactSafeCode(input.associationBasis)],
|
|
11198
|
+
["eventScope", compactSafeCode(input.eventScope)],
|
|
11199
|
+
["callRole", compactSafeCode(input.callRole)],
|
|
11200
|
+
["factOrigin", compactSafeCode(input.factOrigin)],
|
|
11201
|
+
["bodyExpansion", compactSafeCode(input.bodyExpansion)]
|
|
11202
|
+
];
|
|
11203
|
+
for (const [key, value] of values) {
|
|
11204
|
+
if (value) Object.assign(out, { [key]: value });
|
|
11205
|
+
}
|
|
11206
|
+
}
|
|
11207
|
+
function addRemediationDecision(out, input) {
|
|
11208
|
+
const hint = input.remediationCode ? compactRemediationHint(input.remediationCode) : void 0;
|
|
11209
|
+
if (!hint) return;
|
|
11210
|
+
out.remediationHint = hint;
|
|
11211
|
+
const total = Math.max(1, compactCount(input.remediationHintCount));
|
|
11212
|
+
out.omittedRemediationHintCount = Math.max(0, total - 1);
|
|
11213
|
+
}
|
|
11214
|
+
function projectCompactDiagnostics(values) {
|
|
11215
|
+
return values.map((value, index) => compactDiagnostic(value, index)).sort(compareCompactDiagnostic);
|
|
11216
|
+
}
|
|
11217
|
+
function projectCompactStart(start) {
|
|
11218
|
+
return {
|
|
11219
|
+
repo: start.repo ?? null,
|
|
11220
|
+
servicePath: start.servicePath ?? null,
|
|
11221
|
+
operation: start.operation ?? null,
|
|
11222
|
+
operationPath: start.operationPath ?? null,
|
|
11223
|
+
handler: start.handler ?? null
|
|
11224
|
+
};
|
|
11225
|
+
}
|
|
11226
|
+
function projectCompactQuery(options) {
|
|
11227
|
+
const hints = (options.implementationHints ?? []).map(projectCompactHint).sort((left, right) => compareBinary(
|
|
11228
|
+
JSON.stringify(left),
|
|
11229
|
+
JSON.stringify(right)
|
|
11230
|
+
));
|
|
11231
|
+
return {
|
|
11232
|
+
depth: compactPositiveInteger(options.depth) ?? 25,
|
|
11233
|
+
includeAsync: Boolean(options.includeAsync),
|
|
11234
|
+
includeDb: Boolean(options.includeDb),
|
|
11235
|
+
includeExternal: Boolean(options.includeExternal),
|
|
11236
|
+
dynamicMode: options.dynamicMode ?? "strict",
|
|
11237
|
+
maxDynamicCandidates: compactPositiveInteger(options.maxDynamicCandidates) ?? 5,
|
|
11238
|
+
suppliedVariableNames: compactSortedUnique(Object.keys(options.vars ?? {})),
|
|
11239
|
+
runtimeValuesOmitted: true,
|
|
11240
|
+
implementationRepo: options.implementationRepo ?? null,
|
|
11241
|
+
implementationHints: hints
|
|
11242
|
+
};
|
|
11243
|
+
}
|
|
11244
|
+
function projectCompactHint(hint) {
|
|
11245
|
+
return {
|
|
11246
|
+
servicePath: hint.servicePath ?? null,
|
|
11247
|
+
operationPath: hint.operationPath ?? null,
|
|
11248
|
+
packageName: hint.packageName ?? null,
|
|
11249
|
+
repositoryName: hint.repositoryName ?? null,
|
|
11250
|
+
candidateFamily: hint.candidateFamily ?? null,
|
|
11251
|
+
implementationRepo: hint.implementationRepo ?? null
|
|
11252
|
+
};
|
|
11253
|
+
}
|
|
11254
|
+
function compactStatusCounts(values) {
|
|
11255
|
+
const counts = {
|
|
11256
|
+
resolved: 0,
|
|
11257
|
+
terminal: 0,
|
|
11258
|
+
inferred: 0,
|
|
11259
|
+
dynamic: 0,
|
|
11260
|
+
ambiguous: 0,
|
|
11261
|
+
unresolved: 0,
|
|
11262
|
+
cycle: 0
|
|
11263
|
+
};
|
|
11264
|
+
for (const value of values) counts[value.status] += 1;
|
|
11265
|
+
return counts;
|
|
11266
|
+
}
|
|
11267
|
+
function compactCompleteness(counts, diagnostics) {
|
|
11268
|
+
const total = compactStatusTotal(counts);
|
|
11269
|
+
if (total === 0 && diagnostics.some(compactBlockingDiagnostic)) return "blocked";
|
|
11270
|
+
if (counts.dynamic + counts.ambiguous + counts.unresolved > 0) return "partial";
|
|
11271
|
+
if (diagnostics.some((item) => item[1] === "error" || item[1] === "warning"))
|
|
11272
|
+
return "partial";
|
|
11273
|
+
return "complete";
|
|
11274
|
+
}
|
|
11275
|
+
function compactStatusTotal(counts) {
|
|
11276
|
+
return counts.resolved + counts.terminal + counts.inferred + counts.dynamic + counts.ambiguous + counts.unresolved + counts.cycle;
|
|
11277
|
+
}
|
|
11278
|
+
function removeEquivalentCompactPersistedDecision(decision) {
|
|
11279
|
+
if (decision.persistedResolutionStatus !== decision.effectiveResolutionStatus) return;
|
|
11280
|
+
if (!decision.persistedTarget || decision.persistedTarget !== decision.effectiveTarget) return;
|
|
11281
|
+
delete decision.persistedResolutionStatus;
|
|
11282
|
+
delete decision.persistedTarget;
|
|
11283
|
+
}
|
|
11284
|
+
function compactBlockingDiagnostic(item) {
|
|
11285
|
+
if (item[1] === "error") return true;
|
|
11286
|
+
return item[2] === "schema_upgrade_required" || item[2] === "reindex_required" || item[2] === "trace_workspace_ambiguous" || item[2].startsWith("selector_") || item[2].startsWith("trace_start_");
|
|
11287
|
+
}
|
|
11288
|
+
function compactDiagnostic(value, index) {
|
|
11289
|
+
const code = compactSafeCode(value.code) ?? "unknown_diagnostic";
|
|
11290
|
+
const details = compactDiagnosticDetails(value, code);
|
|
11291
|
+
return {
|
|
11292
|
+
index,
|
|
11293
|
+
severity: compactDiagnosticSeverity(value.severity),
|
|
11294
|
+
code,
|
|
11295
|
+
message: compactDiagnosticMessages[code] ?? `See detailed diagnostic at index ${index}.`,
|
|
11296
|
+
file: compactSafeSourceFile(value.sourceFile) ?? compactSafeSourceFile(value.file),
|
|
11297
|
+
line: compactPositiveInteger(value.sourceLine) ?? compactPositiveInteger(value.line),
|
|
11298
|
+
details: Object.keys(details).length > 0 ? details : void 0
|
|
11299
|
+
};
|
|
11300
|
+
}
|
|
11301
|
+
function compactDiagnosticDetails(value, code) {
|
|
11302
|
+
const out = {};
|
|
11303
|
+
const reasonCode = compactSafeCode(value.reasonCode);
|
|
11304
|
+
if (reasonCode) out.reasonCode = reasonCode;
|
|
11305
|
+
addDiagnosticNames(out, value);
|
|
11306
|
+
addDiagnosticCounts(out, value);
|
|
11307
|
+
const hint = compactDiagnosticRemediation(code);
|
|
11308
|
+
if (hint) {
|
|
11309
|
+
out.remediationHint = hint;
|
|
11310
|
+
out.omittedHintCount = Math.max(
|
|
11311
|
+
0,
|
|
11312
|
+
compactDiagnosticHintCount(value, code, out) - 1
|
|
11313
|
+
);
|
|
11314
|
+
}
|
|
11315
|
+
return out;
|
|
11316
|
+
}
|
|
11317
|
+
function addDiagnosticNames(out, value) {
|
|
11318
|
+
const allNames = safeVariableNames(compactStringArray(value.missingVariables));
|
|
11319
|
+
const names = allNames.slice(0, compactNameLimit);
|
|
11320
|
+
const total = Math.max(allNames.length, compactCount(value.missingVariableCount));
|
|
11321
|
+
if (names.length > 0) out.missingVariableNames = names;
|
|
11322
|
+
if (total === 0) return;
|
|
11323
|
+
out.missingVariableCount = total;
|
|
11324
|
+
out.shownMissingVariableCount = names.length;
|
|
11325
|
+
out.omittedMissingVariableCount = Math.max(0, total - names.length);
|
|
11326
|
+
}
|
|
11327
|
+
function addDiagnosticCounts(out, value) {
|
|
11328
|
+
if (value.candidateCount !== void 0)
|
|
11329
|
+
out.candidateCount = compactCount(value.candidateCount);
|
|
11330
|
+
if (value.viableCandidateCount !== void 0)
|
|
11331
|
+
out.viableCandidateCount = compactCount(value.viableCandidateCount);
|
|
11332
|
+
if (value.rejectedCandidateCount !== void 0)
|
|
11333
|
+
out.rejectedCandidateCount = compactCount(value.rejectedCandidateCount);
|
|
11334
|
+
}
|
|
11335
|
+
function compareCompactDiagnostic(left, right) {
|
|
11336
|
+
const ranks = { error: 0, warning: 1, info: 2 };
|
|
11337
|
+
return ranks[left.severity] - ranks[right.severity] || compareBinary(left.code, right.code) || compareBinary(left.file ?? "", right.file ?? "") || (left.line ?? Number.MAX_SAFE_INTEGER) - (right.line ?? Number.MAX_SAFE_INTEGER) || compareBinary(left.message, right.message) || left.index - right.index;
|
|
11338
|
+
}
|
|
11339
|
+
function persistedResolutionDiffers(input) {
|
|
11340
|
+
return input.persistedResolutionStatus !== void 0 && (input.persistedResolutionStatus !== input.effectiveResolutionStatus || JSON.stringify(input.persistedTarget) !== JSON.stringify(input.effectiveTarget));
|
|
11341
|
+
}
|
|
11342
|
+
function compactDiagnosticSeverity(value) {
|
|
11343
|
+
return value === "error" || value === "warning" ? value : "info";
|
|
11344
|
+
}
|
|
11345
|
+
function compactDiagnosticRemediation(code) {
|
|
11346
|
+
if (code === "schema_upgrade_required" || code === "reindex_required")
|
|
11347
|
+
return compactRemediationHint("reindex_and_link");
|
|
11348
|
+
if (code === "trace_runtime_variables_missing")
|
|
11349
|
+
return compactRemediationHint("provide_runtime_variables");
|
|
11350
|
+
if (code === "implementation_hint_mismatch")
|
|
11351
|
+
return compactRemediationHint("select_implementation");
|
|
11352
|
+
return void 0;
|
|
11353
|
+
}
|
|
11354
|
+
function compactDiagnosticHintCount(value, code, details) {
|
|
11355
|
+
const missing = code === "trace_runtime_variables_missing" ? details.missingVariableCount ?? 0 : 0;
|
|
11356
|
+
return Math.max(
|
|
11357
|
+
1,
|
|
11358
|
+
missing,
|
|
11359
|
+
compactArrayLength(value.suggestions),
|
|
11360
|
+
compactArrayLength(value.implementationHintSuggestions),
|
|
11361
|
+
compactArrayLength(value.copyableExamples),
|
|
11362
|
+
compactCount(value.suggestionCount),
|
|
11363
|
+
compactCount(value.implementationHintSuggestionCount),
|
|
11364
|
+
compactCount(value.copyableExampleCount)
|
|
11365
|
+
);
|
|
11366
|
+
}
|
|
11367
|
+
function compactRemediationHint(code) {
|
|
11368
|
+
if (code === "provide_runtime_variables") return "Provide the missing variable names listed in details.";
|
|
11369
|
+
if (code === "select_implementation") return "Select one implementation with a scoped implementation hint.";
|
|
11370
|
+
if (code === "reindex_and_link") return "Force reindex, then force relink the workspace.";
|
|
11371
|
+
if (code === "inspect_detailed_edge") return "Inspect the correlated detailed trace edge.";
|
|
11372
|
+
return void 0;
|
|
11373
|
+
}
|
|
11374
|
+
function compactSafeCode(value) {
|
|
11375
|
+
return typeof value === "string" && /^[a-z][a-z0-9_.-]{0,79}$/.test(value) ? value : void 0;
|
|
11376
|
+
}
|
|
11377
|
+
function projectCompactDecisionTarget(kindValue, id) {
|
|
11378
|
+
const kind = compactSafeCode(kindValue);
|
|
11379
|
+
if (!kind || id.length === 0 || id.length > 240 || /[\r\n]/.test(id)) return void 0;
|
|
11380
|
+
if (/^[a-z]+:\/\//i.test(id) || /\b(?:bearer|token|secret|password|credential|authorization)\b/i.test(id))
|
|
11381
|
+
return void 0;
|
|
11382
|
+
const redacted = redactText(id);
|
|
11383
|
+
return redacted === id ? `${kind}:${redacted}` : void 0;
|
|
11384
|
+
}
|
|
11385
|
+
function compactSafeSourceFile(value) {
|
|
11386
|
+
return typeof value === "string" && value.length > 0 && value.length <= 512 && !/^[a-z]+:\/\//i.test(value) && !/[\r\n]/.test(value) ? value : void 0;
|
|
11387
|
+
}
|
|
11388
|
+
function safeVariableNames(values) {
|
|
11389
|
+
return compactSortedUnique((values ?? []).filter((value) => /^[A-Za-z_$][\w$]*$/.test(value)));
|
|
11390
|
+
}
|
|
11391
|
+
function compactCount(value) {
|
|
11392
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
11393
|
+
}
|
|
11394
|
+
function compactPositiveInteger(value) {
|
|
11395
|
+
const normalized = compactCount(value);
|
|
11396
|
+
return normalized > 0 ? normalized : void 0;
|
|
11397
|
+
}
|
|
11398
|
+
function compactStringArray(value) {
|
|
11399
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
11400
|
+
}
|
|
11401
|
+
function compactArrayLength(value) {
|
|
11402
|
+
return Array.isArray(value) ? value.length : 0;
|
|
11403
|
+
}
|
|
11404
|
+
function compactSortedUnique(values) {
|
|
11405
|
+
return [...new Set(values)].sort(compareBinary);
|
|
11406
|
+
}
|
|
11407
|
+
|
|
11408
|
+
// src/trace/016-compact-projector.ts
|
|
11409
|
+
var REFERENCE_LIMIT = 5;
|
|
11410
|
+
function projectCompactGraph(input) {
|
|
11411
|
+
validateObservationOrdinals(input.observations, input.trace.edges.length);
|
|
11412
|
+
const resolved3 = input.observations.map((item) => resolveObservation(input.db, item));
|
|
11413
|
+
const diagnostics = projectCompactDiagnostics(input.trace.diagnostics);
|
|
11414
|
+
const aggregates = aggregateObservations(resolved3);
|
|
11415
|
+
const nodes = canonicalNodes(resolved3);
|
|
11416
|
+
const repos = sortedUnique(nodes.flatMap((node) => node.repo ? [node.repo] : []));
|
|
11417
|
+
const files = sortedUnique([
|
|
11418
|
+
...nodes.flatMap((node) => node.file ? [node.file] : []),
|
|
11419
|
+
...diagnostics.flatMap((item) => item.file ? [item.file] : [])
|
|
11420
|
+
]);
|
|
11421
|
+
const nodeRows = compactNodeRows(nodes, repos, files);
|
|
11422
|
+
const edgeRows = compactEdgeRows(aggregates, nodes);
|
|
11423
|
+
const diagnosticRows = compactDiagnosticRows(diagnostics, files);
|
|
11424
|
+
const result = compactResult(input, nodes, nodeRows, edgeRows, diagnosticRows, repos, files);
|
|
11425
|
+
validateCompactResult(result);
|
|
11426
|
+
return result;
|
|
11427
|
+
}
|
|
11428
|
+
function resolveObservation(db, input) {
|
|
11429
|
+
const target = resolveEndpoint(db, input.target, input.ordinal, "target", input.site);
|
|
11430
|
+
return {
|
|
11431
|
+
input,
|
|
11432
|
+
source: resolveEndpoint(db, input.source, input.ordinal, "source", input.site),
|
|
11433
|
+
target,
|
|
11434
|
+
decision: observationDecision(db, input, target)
|
|
11435
|
+
};
|
|
11436
|
+
}
|
|
11437
|
+
function observationDecision(db, input, target) {
|
|
11438
|
+
const decision = projectCompactDecision(input.decision);
|
|
11439
|
+
if (decision.effectiveResolutionStatus && target.decisionTarget)
|
|
11440
|
+
decision.effectiveTarget = target.decisionTarget;
|
|
11441
|
+
if (decision.persistedResolutionStatus && input.decision?.persistedTarget) {
|
|
11442
|
+
const persisted = persistedDecisionTarget(db, input.decision.persistedTarget);
|
|
11443
|
+
if (persisted) decision.persistedTarget = persisted;
|
|
11444
|
+
}
|
|
11445
|
+
removeEquivalentCompactPersistedDecision(decision);
|
|
11446
|
+
return decision;
|
|
11447
|
+
}
|
|
11448
|
+
function persistedDecisionTarget(db, target) {
|
|
11449
|
+
const numeric2 = numericId(target.id);
|
|
11450
|
+
if (target.kind === "operation" && numeric2 !== void 0)
|
|
11451
|
+
return operationNode2(db, numeric2)?.decisionTarget;
|
|
11452
|
+
if (target.kind === "symbol" && numeric2 !== void 0)
|
|
11453
|
+
return symbolNode2(db, numeric2)?.decisionTarget;
|
|
11454
|
+
if (target.kind === "handler_method" && numeric2 !== void 0)
|
|
11455
|
+
return handlerNode(db, numeric2)?.decisionTarget;
|
|
11456
|
+
return projectCompactDecisionTarget(target.kind, target.id);
|
|
11457
|
+
}
|
|
11458
|
+
function resolveEndpoint(db, endpoint, ordinal, side, site) {
|
|
11459
|
+
if (endpoint.kind === "operation")
|
|
11460
|
+
return resolvedOrUnavailable(
|
|
11461
|
+
operationNode2(db, endpoint.operationId),
|
|
11462
|
+
side,
|
|
11463
|
+
"operation",
|
|
11464
|
+
ordinal,
|
|
11465
|
+
site
|
|
11466
|
+
);
|
|
11467
|
+
if (endpoint.kind === "symbol")
|
|
11468
|
+
return resolvedOrUnavailable(
|
|
11469
|
+
symbolNode2(db, endpoint.symbolId),
|
|
11470
|
+
side,
|
|
11471
|
+
"symbol",
|
|
11472
|
+
ordinal,
|
|
11473
|
+
site
|
|
11474
|
+
);
|
|
11475
|
+
if (endpoint.kind === "handler_method")
|
|
11476
|
+
return resolvedOrUnavailable(
|
|
11477
|
+
handlerNode(db, endpoint.handlerMethodId),
|
|
11478
|
+
side,
|
|
11479
|
+
"handler_method",
|
|
11480
|
+
ordinal,
|
|
11481
|
+
site
|
|
11482
|
+
);
|
|
11483
|
+
if (endpoint.kind === "event") return eventNode(endpoint.workspaceId, endpoint.eventName);
|
|
11484
|
+
if (endpoint.kind === "target") return targetNode(db, endpoint, ordinal, side, site);
|
|
11485
|
+
if (endpoint.kind === "call_site") return callSiteNode(db, endpoint);
|
|
11486
|
+
if (endpoint.kind === "scope") return scopeNode(db, endpoint);
|
|
11487
|
+
return unavailableNode(
|
|
11488
|
+
endpoint.side,
|
|
11489
|
+
endpoint.endpointKind,
|
|
11490
|
+
endpoint.detailedEdgeIndex,
|
|
11491
|
+
endpoint.site ?? site
|
|
11492
|
+
);
|
|
11493
|
+
}
|
|
11494
|
+
function resolvedOrUnavailable(node, side, kind, ordinal, site) {
|
|
11495
|
+
return node ?? unavailableNode(side, kind, ordinal, site);
|
|
11496
|
+
}
|
|
11497
|
+
function operationNode2(db, operationId) {
|
|
11498
|
+
const row = db.prepare(`SELECT o.id,o.operation_name operationName,
|
|
11499
|
+
o.operation_path operationPath,o.source_file sourceFile,o.source_line sourceLine,
|
|
11500
|
+
s.service_path servicePath,r.workspace_id workspaceId,r.relative_path relativePath,
|
|
11501
|
+
r.name repoName
|
|
11502
|
+
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id
|
|
11503
|
+
JOIN repositories r ON r.id=s.repo_id WHERE o.id=?`).get(operationId);
|
|
11504
|
+
if (!row) return void 0;
|
|
11505
|
+
const operationPath = stringValue15(row.operationPath);
|
|
11506
|
+
const servicePath = stringValue15(row.servicePath);
|
|
11507
|
+
const repo = repositoryLabel(row);
|
|
11508
|
+
const workspaceId = numberValue11(row.workspaceId);
|
|
11509
|
+
if (!operationPath) return void 0;
|
|
11510
|
+
if (!servicePath) return void 0;
|
|
11511
|
+
if (!repo) return void 0;
|
|
11512
|
+
if (workspaceId === void 0) return void 0;
|
|
11513
|
+
const operationName = stringValue15(row.operationName);
|
|
11514
|
+
return {
|
|
11515
|
+
key: canonicalKey("operation", workspaceId, repo, servicePath, operationPath),
|
|
11516
|
+
kind: "operation",
|
|
11517
|
+
label: `${servicePath}:${operationName || operationPath}`,
|
|
11518
|
+
repo,
|
|
11519
|
+
file: stringValue15(row.sourceFile),
|
|
11520
|
+
line: numberValue11(row.sourceLine),
|
|
11521
|
+
synthetic: false,
|
|
11522
|
+
decisionTarget: projectCompactDecisionTarget(
|
|
11523
|
+
"operation",
|
|
11524
|
+
`${repo}:${servicePath}:${operationPath}`
|
|
11525
|
+
)
|
|
11526
|
+
};
|
|
11527
|
+
}
|
|
11528
|
+
function symbolNode2(db, symbolId) {
|
|
11529
|
+
const row = db.prepare(`SELECT s.id,s.kind,s.qualified_name qualifiedName,
|
|
11530
|
+
s.source_file sourceFile,s.start_line startLine,s.start_offset startOffset,
|
|
11531
|
+
s.end_offset endOffset,r.workspace_id workspaceId,r.relative_path relativePath,
|
|
11532
|
+
r.name repoName FROM symbols s JOIN repositories r ON r.id=s.repo_id
|
|
11533
|
+
WHERE s.id=?`).get(symbolId);
|
|
11534
|
+
return symbolNodeFromRow(row);
|
|
11535
|
+
}
|
|
11536
|
+
function symbolNodeFromRow(row) {
|
|
11537
|
+
if (!row) return void 0;
|
|
11538
|
+
const name = stringValue15(row.qualifiedName);
|
|
11539
|
+
const repo = repositoryLabel(row);
|
|
11540
|
+
const file = stringValue15(row.sourceFile);
|
|
11541
|
+
const workspaceId = numberValue11(row.workspaceId);
|
|
11542
|
+
if (!name) return void 0;
|
|
11543
|
+
if (!repo) return void 0;
|
|
11544
|
+
if (!file) return void 0;
|
|
11545
|
+
if (workspaceId === void 0) return void 0;
|
|
11546
|
+
const startOffset = numberValue11(row.startOffset);
|
|
11547
|
+
const endOffset = numberValue11(row.endOffset);
|
|
11548
|
+
return {
|
|
11549
|
+
key: canonicalKey(
|
|
11550
|
+
"symbol",
|
|
11551
|
+
workspaceId,
|
|
11552
|
+
repo,
|
|
11553
|
+
file,
|
|
11554
|
+
startOffset,
|
|
11555
|
+
endOffset,
|
|
11556
|
+
name
|
|
11557
|
+
),
|
|
11558
|
+
kind: "symbol",
|
|
11559
|
+
label: name,
|
|
11560
|
+
repo,
|
|
11561
|
+
file,
|
|
11562
|
+
line: numberValue11(row.startLine),
|
|
11563
|
+
synthetic: false,
|
|
11564
|
+
decisionTarget: projectCompactDecisionTarget(
|
|
11565
|
+
"symbol",
|
|
11566
|
+
`${repo}:${file}:${startOffset ?? ""}:${endOffset ?? ""}:${name}`
|
|
11567
|
+
)
|
|
11568
|
+
};
|
|
11569
|
+
}
|
|
11570
|
+
function handlerNode(db, handlerMethodId) {
|
|
11571
|
+
const symbol = db.prepare(`SELECT s.kind,s.qualified_name qualifiedName,
|
|
11572
|
+
s.source_file sourceFile,s.start_line startLine,s.start_offset startOffset,
|
|
11573
|
+
s.end_offset endOffset,r.workspace_id workspaceId,r.relative_path relativePath,
|
|
11574
|
+
r.name repoName
|
|
11575
|
+
FROM handler_methods hm JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
11576
|
+
JOIN repositories r ON r.id=hc.repo_id LEFT JOIN symbols s
|
|
11577
|
+
ON s.repo_id=hc.repo_id AND s.source_file=hc.source_file
|
|
11578
|
+
AND s.qualified_name=hc.class_name || '.' || hm.method_name
|
|
11579
|
+
AND s.start_line=hm.source_line WHERE hm.id=?
|
|
11580
|
+
ORDER BY s.id LIMIT 1`).get(handlerMethodId);
|
|
11581
|
+
const resolved3 = symbolNodeFromRow(symbol);
|
|
11582
|
+
return resolved3 ?? standaloneHandlerNode(db, handlerMethodId);
|
|
11583
|
+
}
|
|
11584
|
+
function standaloneHandlerNode(db, handlerMethodId) {
|
|
11585
|
+
const row = db.prepare(`SELECT hm.method_name methodName,hm.source_file sourceFile,
|
|
11586
|
+
hm.source_line sourceLine,hc.class_name className,r.workspace_id workspaceId,
|
|
11587
|
+
r.relative_path relativePath,r.name repoName FROM handler_methods hm
|
|
11588
|
+
JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
11589
|
+
JOIN repositories r ON r.id=hc.repo_id WHERE hm.id=?`).get(handlerMethodId);
|
|
11590
|
+
if (!row) return void 0;
|
|
11591
|
+
const methodName2 = stringValue15(row.methodName);
|
|
11592
|
+
const className = stringValue15(row.className);
|
|
11593
|
+
const repo = repositoryLabel(row);
|
|
11594
|
+
const file = stringValue15(row.sourceFile);
|
|
11595
|
+
const workspaceId = numberValue11(row.workspaceId);
|
|
11596
|
+
if (!methodName2) return void 0;
|
|
11597
|
+
if (!className) return void 0;
|
|
11598
|
+
if (!repo) return void 0;
|
|
11599
|
+
if (!file) return void 0;
|
|
11600
|
+
if (workspaceId === void 0) return void 0;
|
|
11601
|
+
const sourceLine2 = numberValue11(row.sourceLine);
|
|
11602
|
+
return {
|
|
11603
|
+
key: canonicalKey(
|
|
11604
|
+
"handler_method",
|
|
11605
|
+
workspaceId,
|
|
11606
|
+
repo,
|
|
11607
|
+
file,
|
|
11608
|
+
sourceLine2,
|
|
11609
|
+
className,
|
|
11610
|
+
methodName2
|
|
11611
|
+
),
|
|
11612
|
+
kind: "handler_method",
|
|
11613
|
+
label: `${className}.${methodName2}`,
|
|
11614
|
+
repo,
|
|
11615
|
+
file,
|
|
11616
|
+
line: sourceLine2,
|
|
11617
|
+
synthetic: false,
|
|
11618
|
+
decisionTarget: projectCompactDecisionTarget(
|
|
11619
|
+
"handler_method",
|
|
11620
|
+
`${repo}:${file}:${sourceLine2 ?? ""}:${className}.${methodName2}`
|
|
11621
|
+
)
|
|
11622
|
+
};
|
|
11623
|
+
}
|
|
11624
|
+
function eventNode(workspaceId, eventName) {
|
|
11625
|
+
return {
|
|
11626
|
+
key: canonicalKey("event", workspaceId, eventName),
|
|
11627
|
+
kind: "event",
|
|
11628
|
+
label: eventName,
|
|
11629
|
+
synthetic: false,
|
|
11630
|
+
decisionTarget: projectCompactDecisionTarget("event", eventName)
|
|
11631
|
+
};
|
|
11632
|
+
}
|
|
11633
|
+
function targetNode(db, endpoint, ordinal, side, site) {
|
|
11634
|
+
const linked = linkedTargetNode(db, endpoint.targetKind, endpoint.targetId);
|
|
11635
|
+
if (linked !== void 0)
|
|
11636
|
+
return linked ?? unavailableNode(side, endpoint.targetKind, ordinal, site);
|
|
11637
|
+
const repo = endpoint.repositoryId === void 0 ? void 0 : repositoryById(db, endpoint.repositoryId);
|
|
11638
|
+
return {
|
|
11639
|
+
key: canonicalKey(
|
|
11640
|
+
"target",
|
|
11641
|
+
endpoint.workspaceId,
|
|
11642
|
+
repo,
|
|
11643
|
+
endpoint.targetKind,
|
|
11644
|
+
endpoint.targetId
|
|
11645
|
+
),
|
|
11646
|
+
kind: compactTargetKind(endpoint.targetKind),
|
|
11647
|
+
label: endpoint.targetId || endpoint.targetKind,
|
|
11648
|
+
repo,
|
|
11649
|
+
synthetic: false,
|
|
11650
|
+
decisionTarget: projectCompactDecisionTarget(endpoint.targetKind, endpoint.targetId)
|
|
11651
|
+
};
|
|
11652
|
+
}
|
|
11653
|
+
function linkedTargetNode(db, kind, id) {
|
|
11654
|
+
const numeric2 = numericId(id);
|
|
11655
|
+
if (kind === "operation") {
|
|
11656
|
+
if (numeric2 === void 0) return null;
|
|
11657
|
+
return operationNode2(db, numeric2) ?? null;
|
|
11658
|
+
}
|
|
11659
|
+
if (kind === "symbol") {
|
|
11660
|
+
if (numeric2 === void 0) return null;
|
|
11661
|
+
return symbolNode2(db, numeric2) ?? null;
|
|
11662
|
+
}
|
|
11663
|
+
if (kind === "handler_method") {
|
|
11664
|
+
if (numeric2 === void 0) return null;
|
|
11665
|
+
return handlerNode(db, numeric2) ?? null;
|
|
11666
|
+
}
|
|
11667
|
+
return void 0;
|
|
11668
|
+
}
|
|
11669
|
+
function callSiteNode(db, endpoint) {
|
|
11670
|
+
const repo = repositoryById(db, endpoint.repositoryId) ?? endpoint.repositoryName;
|
|
11671
|
+
const span = endpoint.startOffset === void 0 || endpoint.endOffset === void 0 ? ["line", endpoint.sourceLine] : [endpoint.startOffset, endpoint.endOffset];
|
|
11672
|
+
return {
|
|
11673
|
+
key: canonicalKey(
|
|
11674
|
+
"call_site",
|
|
11675
|
+
endpoint.workspaceId,
|
|
11676
|
+
repo,
|
|
11677
|
+
endpoint.sourceFile,
|
|
11678
|
+
...span,
|
|
11679
|
+
endpoint.callId
|
|
11680
|
+
),
|
|
11681
|
+
kind: "call_site",
|
|
11682
|
+
label: `${repo}:${endpoint.sourceFile}:${endpoint.sourceLine}`,
|
|
11683
|
+
repo,
|
|
11684
|
+
file: endpoint.sourceFile,
|
|
11685
|
+
line: endpoint.sourceLine,
|
|
11686
|
+
synthetic: false,
|
|
11687
|
+
decisionTarget: projectCompactDecisionTarget(
|
|
11688
|
+
"call_site",
|
|
11689
|
+
`${repo}:${endpoint.sourceFile}:${span.join(":")}`
|
|
11690
|
+
)
|
|
11691
|
+
};
|
|
11692
|
+
}
|
|
11693
|
+
function scopeNode(db, endpoint) {
|
|
11694
|
+
const repo = endpoint.repositoryId === void 0 ? void 0 : repositoryById(db, endpoint.repositoryId);
|
|
11695
|
+
const files = sortedUnique(endpoint.sourceFiles);
|
|
11696
|
+
const symbols = sortedUnique(endpoint.symbolIds.flatMap((id) => {
|
|
11697
|
+
const key = symbolNode2(db, id)?.key;
|
|
11698
|
+
return key ? [key] : [];
|
|
11699
|
+
}));
|
|
11700
|
+
const identity = canonicalKey("scope", endpoint.workspaceId, repo, files, symbols);
|
|
11701
|
+
return {
|
|
11702
|
+
key: symbols.length > 0 || files.length > 0 ? identity : canonicalKey("scope", endpoint.workspaceId, repo, endpoint.structuralKey),
|
|
11703
|
+
kind: "scope",
|
|
11704
|
+
label: repo ? `scope:${repo}` : "scope:workspace",
|
|
11705
|
+
repo,
|
|
11706
|
+
file: files.length === 1 ? files[0] : void 0,
|
|
11707
|
+
synthetic: false,
|
|
11708
|
+
decisionTarget: projectCompactDecisionTarget("scope", identity)
|
|
11709
|
+
};
|
|
11710
|
+
}
|
|
11711
|
+
function unavailableNode(side, endpointKind, ordinal, site) {
|
|
11712
|
+
return {
|
|
11713
|
+
key: canonicalKey(
|
|
11714
|
+
"unavailable",
|
|
11715
|
+
side,
|
|
11716
|
+
endpointKind,
|
|
11717
|
+
site?.repository,
|
|
11718
|
+
site?.sourceFile,
|
|
11719
|
+
site?.startOffset,
|
|
11720
|
+
site?.endOffset,
|
|
11721
|
+
site?.sourceLine,
|
|
11722
|
+
ordinal
|
|
11723
|
+
),
|
|
11724
|
+
kind: "synthetic",
|
|
11725
|
+
label: `${side}:${compactSafeCode(endpointKind) ?? "unavailable"}`,
|
|
11726
|
+
repo: site?.repository,
|
|
11727
|
+
file: site?.sourceFile,
|
|
11728
|
+
line: site?.sourceLine,
|
|
11729
|
+
synthetic: true
|
|
11730
|
+
};
|
|
11731
|
+
}
|
|
11732
|
+
function aggregateObservations(items) {
|
|
11733
|
+
const groups = /* @__PURE__ */ new Map();
|
|
11734
|
+
for (const item of items) {
|
|
11735
|
+
const key = aggregationKey(item);
|
|
11736
|
+
const current = groups.get(key);
|
|
11737
|
+
if (current) appendAggregate(current, item);
|
|
11738
|
+
else groups.set(key, createAggregate(item));
|
|
11739
|
+
}
|
|
11740
|
+
return [...groups.values()].map(finalizeAggregate);
|
|
11741
|
+
}
|
|
11742
|
+
function aggregationKey(item) {
|
|
11743
|
+
return JSON.stringify([
|
|
11744
|
+
item.input.step,
|
|
11745
|
+
item.input.type,
|
|
11746
|
+
item.source.key,
|
|
11747
|
+
item.target.key,
|
|
11748
|
+
item.input.status,
|
|
11749
|
+
normalizedConfidence(item.input.confidence),
|
|
11750
|
+
item.decision
|
|
11751
|
+
]);
|
|
11752
|
+
}
|
|
11753
|
+
function createAggregate(item) {
|
|
11754
|
+
return {
|
|
11755
|
+
source: item.source,
|
|
11756
|
+
target: item.target,
|
|
11757
|
+
step: item.input.step,
|
|
11758
|
+
type: item.input.type,
|
|
11759
|
+
status: item.input.status,
|
|
11760
|
+
confidence: normalizedConfidence(item.input.confidence),
|
|
11761
|
+
decision: item.decision,
|
|
11762
|
+
ordinals: [item.input.ordinal],
|
|
11763
|
+
refs: item.input.refs ? [item.input.refs] : [],
|
|
11764
|
+
site: item.input.site
|
|
11765
|
+
};
|
|
11766
|
+
}
|
|
11767
|
+
function appendAggregate(group, item) {
|
|
11768
|
+
group.ordinals.push(item.input.ordinal);
|
|
11769
|
+
if (item.input.refs) group.refs.push(item.input.refs);
|
|
11770
|
+
if (compareSite(item.input.site, group.site) < 0) group.site = item.input.site;
|
|
11771
|
+
}
|
|
11772
|
+
function finalizeAggregate(group) {
|
|
11773
|
+
group.ordinals.sort((left, right) => left - right);
|
|
11774
|
+
return group;
|
|
11775
|
+
}
|
|
11776
|
+
function canonicalNodes(items) {
|
|
11777
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
11778
|
+
for (const node of items.flatMap((item) => [item.source, item.target])) {
|
|
11779
|
+
const existing = nodes.get(node.key);
|
|
11780
|
+
if (!existing || compareNodeBody(node, existing) < 0) nodes.set(node.key, node);
|
|
11781
|
+
}
|
|
11782
|
+
return [...nodes.values()].sort((left, right) => compareBinary(left.key, right.key));
|
|
11783
|
+
}
|
|
11784
|
+
function compactNodeRows(nodes, repos, files) {
|
|
11785
|
+
const repoIndexes = indexMap(repos);
|
|
11786
|
+
const fileIndexes = indexMap(files);
|
|
11787
|
+
return nodes.map((node, index) => [
|
|
11788
|
+
`n${index}`,
|
|
11789
|
+
node.kind,
|
|
11790
|
+
redactText(node.label),
|
|
11791
|
+
node.repo === void 0 ? null : repoIndexes.get(node.repo) ?? null,
|
|
11792
|
+
node.file === void 0 ? null : fileIndexes.get(node.file) ?? null,
|
|
11793
|
+
node.line ?? null
|
|
11794
|
+
]);
|
|
11795
|
+
}
|
|
11796
|
+
function compactEdgeRows(groups, nodes) {
|
|
11797
|
+
const nodeIndexes = new Map(nodes.map((node, index) => [node.key, index]));
|
|
11798
|
+
const sorted = [...groups].sort((left, right) => compareAggregate(left, right, nodeIndexes));
|
|
11799
|
+
return sorted.map((group, index) => edgeRow(group, index, nodeIndexes));
|
|
11800
|
+
}
|
|
11801
|
+
function edgeRow(group, index, nodeIndexes) {
|
|
11802
|
+
const source = nodeIndexes.get(group.source.key);
|
|
11803
|
+
const target = nodeIndexes.get(group.target.key);
|
|
11804
|
+
if (source === void 0 || target === void 0) throw compactError("edge_node_missing");
|
|
11805
|
+
return [
|
|
11806
|
+
`e${index}`,
|
|
11807
|
+
group.ordinals,
|
|
11808
|
+
group.step,
|
|
11809
|
+
group.type,
|
|
11810
|
+
`n${source}`,
|
|
11811
|
+
`n${target}`,
|
|
11812
|
+
group.status,
|
|
11813
|
+
group.confidence,
|
|
11814
|
+
group.ordinals.length,
|
|
11815
|
+
edgeDetails(group)
|
|
11816
|
+
];
|
|
11817
|
+
}
|
|
11818
|
+
function edgeDetails(group) {
|
|
11819
|
+
const refs = projectReferences(group.refs);
|
|
11820
|
+
if (Object.keys(group.decision).length === 0 && Object.keys(refs).length === 0) return null;
|
|
11821
|
+
return { decision: group.decision, refs };
|
|
11822
|
+
}
|
|
11823
|
+
function projectReferences(values) {
|
|
11824
|
+
const out = {};
|
|
11825
|
+
setReference(out, "graphEdgeIds", values.flatMap((item) => item.graphEdgeIds ?? []));
|
|
11826
|
+
setReference(out, "outboundCallIds", values.flatMap((item) => item.outboundCallIds ?? []));
|
|
11827
|
+
setReference(out, "subscribeCallIds", values.flatMap((item) => item.subscribeCallIds ?? []));
|
|
11828
|
+
setReference(out, "symbolCallIds", values.flatMap((item) => item.symbolCallIds ?? []));
|
|
11829
|
+
setReference(out, "operationIds", values.flatMap((item) => item.operationIds ?? []));
|
|
11830
|
+
setReference(out, "symbolIds", values.flatMap((item) => item.symbolIds ?? []));
|
|
11831
|
+
setReference(out, "handlerMethodIds", values.flatMap((item) => item.handlerMethodIds ?? []));
|
|
11832
|
+
return out;
|
|
11833
|
+
}
|
|
11834
|
+
function setReference(out, key, values) {
|
|
11835
|
+
const unique3 = uniqueReferences(values);
|
|
11836
|
+
if (unique3.length === 0) return;
|
|
11837
|
+
const shown = unique3.slice(0, REFERENCE_LIMIT);
|
|
11838
|
+
out[key] = {
|
|
11839
|
+
values: shown,
|
|
11840
|
+
total: unique3.length,
|
|
11841
|
+
shown: shown.length,
|
|
11842
|
+
omitted: unique3.length - shown.length
|
|
11843
|
+
};
|
|
11844
|
+
}
|
|
11845
|
+
function compactDiagnosticRows(diagnostics, files) {
|
|
11846
|
+
const fileIndexes = indexMap(files);
|
|
11847
|
+
return diagnostics.map((item) => [
|
|
11848
|
+
item.index,
|
|
11849
|
+
item.severity,
|
|
11850
|
+
item.code,
|
|
11851
|
+
item.message,
|
|
11852
|
+
item.file === void 0 ? null : fileIndexes.get(item.file) ?? null,
|
|
11853
|
+
item.line ?? null,
|
|
11854
|
+
item.details ?? null
|
|
11855
|
+
]);
|
|
11856
|
+
}
|
|
11857
|
+
function compactResult(input, resolvedNodes, nodes, edges, diagnostics, repos, files) {
|
|
11858
|
+
const statusCounts = compactStatusCounts(input.observations);
|
|
11859
|
+
return {
|
|
11860
|
+
schema: "service-flow/compact-graph@1",
|
|
11861
|
+
start: projectCompactStart(input.start),
|
|
11862
|
+
query: projectCompactQuery(input.options),
|
|
11863
|
+
source: input.source,
|
|
11864
|
+
summary: {
|
|
11865
|
+
completeness: compactCompleteness(statusCounts, diagnostics),
|
|
11866
|
+
fullTraceNodes: input.trace.nodes.length,
|
|
11867
|
+
fullTraceEdges: input.trace.edges.length,
|
|
11868
|
+
fullTraceDiagnostics: input.trace.diagnostics.length,
|
|
11869
|
+
nodes: nodes.length,
|
|
11870
|
+
edges: edges.length,
|
|
11871
|
+
collapsedEdges: input.trace.edges.length - edges.length,
|
|
11872
|
+
statusCounts,
|
|
11873
|
+
projection: {
|
|
11874
|
+
evidence: "summary-only",
|
|
11875
|
+
syntheticEndpoints: resolvedNodes.filter((node) => node.synthetic).length,
|
|
11876
|
+
omittedUnreferencedFullNodes: omittedDetailedNodeCount(input)
|
|
11877
|
+
}
|
|
11878
|
+
},
|
|
11879
|
+
repos,
|
|
11880
|
+
files,
|
|
11881
|
+
nodeColumns: ["id", "kind", "label", "repo", "file", "line"],
|
|
11882
|
+
nodes,
|
|
11883
|
+
edgeColumns: [
|
|
11884
|
+
"id",
|
|
11885
|
+
"traceOrdinals",
|
|
11886
|
+
"step",
|
|
11887
|
+
"type",
|
|
11888
|
+
"from",
|
|
11889
|
+
"to",
|
|
11890
|
+
"status",
|
|
11891
|
+
"confidence",
|
|
11892
|
+
"count",
|
|
11893
|
+
"details"
|
|
11894
|
+
],
|
|
11895
|
+
edges,
|
|
11896
|
+
diagnosticColumns: [
|
|
11897
|
+
"fullDiagnosticIndex",
|
|
11898
|
+
"severity",
|
|
11899
|
+
"code",
|
|
11900
|
+
"message",
|
|
11901
|
+
"file",
|
|
11902
|
+
"line",
|
|
11903
|
+
"details"
|
|
11904
|
+
],
|
|
11905
|
+
diagnostics
|
|
11906
|
+
};
|
|
11907
|
+
}
|
|
11908
|
+
function omittedDetailedNodeCount(input) {
|
|
11909
|
+
const referenced = new Set(input.observations.flatMap((item) => [
|
|
11910
|
+
...detailedNodeIds(item.source),
|
|
11911
|
+
...detailedNodeIds(item.target)
|
|
11912
|
+
]));
|
|
11913
|
+
return input.trace.nodes.filter((node) => {
|
|
11914
|
+
const id = typeof node.id === "string" ? node.id : void 0;
|
|
11915
|
+
return id === void 0 || !referenced.has(id);
|
|
11916
|
+
}).length;
|
|
11917
|
+
}
|
|
11918
|
+
function detailedNodeIds(endpoint) {
|
|
11919
|
+
if (endpoint.kind === "operation") return [`operation:${endpoint.operationId}`];
|
|
11920
|
+
if (endpoint.kind === "symbol") return [`symbol:${endpoint.symbolId}`];
|
|
11921
|
+
if (endpoint.kind === "handler_method")
|
|
11922
|
+
return [`handler_method:${endpoint.handlerMethodId}`];
|
|
11923
|
+
if (endpoint.kind === "event") return [`event:${endpoint.eventName}`];
|
|
11924
|
+
if (endpoint.kind === "target") return [`${endpoint.targetKind}:${endpoint.targetId}`];
|
|
11925
|
+
if (endpoint.kind === "call_site") return [`call:${endpoint.callId}`];
|
|
11926
|
+
if (endpoint.kind === "scope")
|
|
11927
|
+
return endpoint.symbolIds.map((symbolId) => `symbol:${symbolId}`);
|
|
11928
|
+
return [];
|
|
11929
|
+
}
|
|
11930
|
+
function validateObservationOrdinals(observations, fullEdgeCount) {
|
|
11931
|
+
const ordinals = observations.map((item) => item.ordinal).sort((left, right) => left - right);
|
|
11932
|
+
if (ordinals.length !== fullEdgeCount) throw compactError("observation_count_mismatch");
|
|
11933
|
+
if (ordinals.some((value, index) => value !== index))
|
|
11934
|
+
throw compactError("trace_ordinal_partition_invalid");
|
|
11935
|
+
}
|
|
11936
|
+
function validateCompactResult(result) {
|
|
11937
|
+
if (result.summary.nodes !== result.nodes.length) throw compactError("node_count_mismatch");
|
|
11938
|
+
if (result.summary.edges !== result.edges.length) throw compactError("edge_count_mismatch");
|
|
11939
|
+
const statusTotal = compactStatusTotal(result.summary.statusCounts);
|
|
11940
|
+
if (statusTotal !== result.summary.fullTraceEdges) throw compactError("status_count_mismatch");
|
|
11941
|
+
const edgeTotal = result.edges.reduce((sum, edge) => sum + edge[8], 0);
|
|
11942
|
+
if (edgeTotal !== result.summary.fullTraceEdges) throw compactError("edge_member_count_mismatch");
|
|
11943
|
+
if (result.edges.some((edge) => edge.length !== 10 || edge[8] !== edge[1].length))
|
|
11944
|
+
throw compactError("edge_tuple_invalid");
|
|
11945
|
+
if (result.nodes.some((node) => node.length !== 6)) throw compactError("node_tuple_invalid");
|
|
11946
|
+
if (result.diagnostics.some((item) => item.length !== 7)) throw compactError("diagnostic_tuple_invalid");
|
|
11947
|
+
validateResultOrdinals(result);
|
|
11948
|
+
}
|
|
11949
|
+
function validateResultOrdinals(result) {
|
|
11950
|
+
const ordinals = result.edges.flatMap((edge) => edge[1]).sort((left, right) => left - right);
|
|
11951
|
+
if (ordinals.some((value, index) => value !== index) || ordinals.length !== result.summary.fullTraceEdges)
|
|
11952
|
+
throw compactError("output_trace_ordinal_partition_invalid");
|
|
11953
|
+
if (result.summary.collapsedEdges !== result.summary.fullTraceEdges - result.summary.edges)
|
|
11954
|
+
throw compactError("collapsed_edge_count_mismatch");
|
|
11955
|
+
}
|
|
11956
|
+
function compareAggregate(left, right, nodeIndexes) {
|
|
11957
|
+
return left.step - right.step || indexFor(nodeIndexes, left.source.key) - indexFor(nodeIndexes, right.source.key) || indexFor(nodeIndexes, left.target.key) - indexFor(nodeIndexes, right.target.key) || compareBinary(left.type, right.type) || compareBinary(left.status, right.status) || compareSite(left.site, right.site) || (left.ordinals[0] ?? 0) - (right.ordinals[0] ?? 0);
|
|
11958
|
+
}
|
|
11959
|
+
function compareSite(left, right) {
|
|
11960
|
+
return compareBinary(siteSortKey(left), siteSortKey(right));
|
|
11961
|
+
}
|
|
11962
|
+
function siteSortKey(site) {
|
|
11963
|
+
return JSON.stringify([
|
|
11964
|
+
site?.repository ?? "",
|
|
11965
|
+
site?.sourceFile ?? "",
|
|
11966
|
+
sortableNumber(site?.startOffset),
|
|
11967
|
+
sortableNumber(site?.endOffset),
|
|
11968
|
+
sortableNumber(site?.sourceLine)
|
|
11969
|
+
]);
|
|
11970
|
+
}
|
|
11971
|
+
function sortableNumber(value) {
|
|
11972
|
+
return value === void 0 ? "z" : `n${String(value).padStart(16, "0")}`;
|
|
11973
|
+
}
|
|
11974
|
+
function compareNodeBody(left, right) {
|
|
11975
|
+
return compareBinary(JSON.stringify([
|
|
11976
|
+
left.kind,
|
|
11977
|
+
left.label,
|
|
11978
|
+
left.repo,
|
|
11979
|
+
left.file,
|
|
11980
|
+
left.line
|
|
11981
|
+
]), JSON.stringify([
|
|
11982
|
+
right.kind,
|
|
11983
|
+
right.label,
|
|
11984
|
+
right.repo,
|
|
11985
|
+
right.file,
|
|
11986
|
+
right.line
|
|
11987
|
+
]));
|
|
11988
|
+
}
|
|
11989
|
+
function compactTargetKind(kind) {
|
|
11990
|
+
if (kind === "db_entity") return "database_entity";
|
|
11991
|
+
if (kind === "operation_candidate") return "dynamic_target";
|
|
11992
|
+
if (kind === "symbol_reference" || kind === "subscription_handler")
|
|
11993
|
+
return "unresolved_target";
|
|
11994
|
+
return compactSafeCode(kind) ?? "target";
|
|
11995
|
+
}
|
|
11996
|
+
function repositoryById(db, repositoryId) {
|
|
11997
|
+
const row = db.prepare("SELECT relative_path relativePath,name repoName FROM repositories WHERE id=?").get(repositoryId);
|
|
11998
|
+
return repositoryLabel(row);
|
|
11999
|
+
}
|
|
12000
|
+
function repositoryLabel(row) {
|
|
12001
|
+
return stringValue15(row?.relativePath) ?? stringValue15(row?.repoName);
|
|
12002
|
+
}
|
|
12003
|
+
function normalizedConfidence(value) {
|
|
12004
|
+
return Number.isFinite(value) ? Math.max(0, Math.min(1, value)) : 0;
|
|
12005
|
+
}
|
|
12006
|
+
function numericId(value) {
|
|
12007
|
+
return /^\d+$/.test(value) ? Number(value) : void 0;
|
|
12008
|
+
}
|
|
12009
|
+
function numberValue11(value) {
|
|
12010
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
12011
|
+
}
|
|
12012
|
+
function stringValue15(value) {
|
|
12013
|
+
return typeof value === "string" ? value : void 0;
|
|
12014
|
+
}
|
|
12015
|
+
function uniqueReferences(values) {
|
|
12016
|
+
const unique3 = new Map(values.map((value) => [`${typeof value}:${String(value)}`, value]));
|
|
12017
|
+
return [...unique3.values()].sort((left, right) => {
|
|
12018
|
+
if (typeof left === "number" && typeof right === "number") return left - right;
|
|
12019
|
+
return compareBinary(`${typeof left}:${String(left)}`, `${typeof right}:${String(right)}`);
|
|
12020
|
+
});
|
|
12021
|
+
}
|
|
12022
|
+
function sortedUnique(values) {
|
|
12023
|
+
return [...new Set(values)].sort(compareBinary);
|
|
12024
|
+
}
|
|
12025
|
+
function canonicalKey(...parts) {
|
|
12026
|
+
return JSON.stringify(parts);
|
|
12027
|
+
}
|
|
12028
|
+
function indexMap(values) {
|
|
12029
|
+
return new Map(values.map((value, index) => [value, index]));
|
|
12030
|
+
}
|
|
12031
|
+
function indexFor(values, key) {
|
|
12032
|
+
const value = values.get(key);
|
|
12033
|
+
if (value === void 0) throw compactError("canonical_node_index_missing");
|
|
12034
|
+
return value;
|
|
12035
|
+
}
|
|
12036
|
+
function compactError(code) {
|
|
12037
|
+
return new Error(`compact_graph_invariant:${code}`);
|
|
12038
|
+
}
|
|
12039
|
+
|
|
12040
|
+
// src/trace/018-compact-trace.ts
|
|
12041
|
+
function compactTrace(db, start, options) {
|
|
12042
|
+
return traceAndCompact(db, start, options).compact;
|
|
12043
|
+
}
|
|
12044
|
+
function traceAndCompact(db, start, options) {
|
|
12045
|
+
const collector = new CompactObservationCollector();
|
|
12046
|
+
const trace2 = traceWithObserver(db, start, options, collector);
|
|
12047
|
+
const source = compactSourceContext(db, options, collector.workspaceId);
|
|
12048
|
+
const compact = projectCompactGraph({
|
|
12049
|
+
db,
|
|
12050
|
+
start,
|
|
12051
|
+
options,
|
|
12052
|
+
source,
|
|
12053
|
+
trace: trace2,
|
|
12054
|
+
observations: collector.observations
|
|
12055
|
+
});
|
|
12056
|
+
return { trace: trace2, compact };
|
|
12057
|
+
}
|
|
12058
|
+
function compactSourceContext(db, options, traversalWorkspaceId) {
|
|
12059
|
+
return {
|
|
12060
|
+
schemaVersion: schemaVersion2(db),
|
|
12061
|
+
analyzerVersion: sourceAnalyzerVersion(
|
|
12062
|
+
db,
|
|
12063
|
+
traversalWorkspaceId ?? options.workspaceId
|
|
12064
|
+
),
|
|
12065
|
+
graphGeneration: graphGeneration(
|
|
12066
|
+
db,
|
|
12067
|
+
traversalWorkspaceId ?? options.workspaceId
|
|
12068
|
+
)
|
|
12069
|
+
};
|
|
12070
|
+
}
|
|
12071
|
+
function sourceAnalyzerVersion(db, workspaceId) {
|
|
12072
|
+
const rows2 = db.prepare(`SELECT DISTINCT
|
|
12073
|
+
COALESCE(fact_analyzer_version,'legacy_unknown') analyzerVersion
|
|
12074
|
+
FROM repositories WHERE (? IS NULL OR workspace_id=?)
|
|
12075
|
+
ORDER BY analyzerVersion COLLATE BINARY LIMIT 2`).all(
|
|
12076
|
+
workspaceId,
|
|
12077
|
+
workspaceId
|
|
12078
|
+
);
|
|
12079
|
+
if (rows2.length === 0) return "none";
|
|
12080
|
+
if (rows2.length > 1) return "mixed";
|
|
12081
|
+
return stringValue16(rows2[0]?.analyzerVersion) ?? "legacy_unknown";
|
|
12082
|
+
}
|
|
12083
|
+
function graphGeneration(db, workspaceId) {
|
|
12084
|
+
if (workspaceId === void 0) return 0;
|
|
12085
|
+
const rows2 = db.prepare(`SELECT DISTINCT graph_generation generation
|
|
12086
|
+
FROM repositories WHERE workspace_id=?
|
|
12087
|
+
ORDER BY graph_generation LIMIT 2`).all(workspaceId);
|
|
12088
|
+
return rows2.length === 1 ? numberValue12(rows2[0]?.generation) ?? 0 : 0;
|
|
12089
|
+
}
|
|
12090
|
+
function schemaVersion2(db) {
|
|
12091
|
+
const row = db.pragma("user_version")[0];
|
|
12092
|
+
return numberValue12(row?.user_version) ?? CURRENT_SCHEMA_VERSION;
|
|
12093
|
+
}
|
|
12094
|
+
function numberValue12(value) {
|
|
12095
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
12096
|
+
}
|
|
12097
|
+
function stringValue16(value) {
|
|
12098
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
12099
|
+
}
|
|
8985
12100
|
|
|
8986
12101
|
export {
|
|
12102
|
+
migrate,
|
|
12103
|
+
VERSION,
|
|
12104
|
+
ANALYZER_VERSION,
|
|
8987
12105
|
projectBoundedInOrder,
|
|
12106
|
+
insertSymbolCalls,
|
|
12107
|
+
insertCalls,
|
|
8988
12108
|
upsertWorkspace,
|
|
8989
12109
|
getWorkspace,
|
|
8990
12110
|
upsertRepository,
|
|
@@ -8998,8 +12118,6 @@ export {
|
|
|
8998
12118
|
insertRegistrations,
|
|
8999
12119
|
insertBindings,
|
|
9000
12120
|
insertExecutableSymbols,
|
|
9001
|
-
insertSymbolCalls,
|
|
9002
|
-
insertCalls,
|
|
9003
12121
|
normalizePath,
|
|
9004
12122
|
stripQuotes,
|
|
9005
12123
|
discoverRepositories,
|
|
@@ -9014,6 +12132,7 @@ export {
|
|
|
9014
12132
|
normalizeODataOperationInvocationPath,
|
|
9015
12133
|
classifyODataPathIntent,
|
|
9016
12134
|
parseServiceBindings,
|
|
12135
|
+
classifyOutboundCallsInSource,
|
|
9017
12136
|
containsSupportedOutboundCall,
|
|
9018
12137
|
parseOutboundCalls,
|
|
9019
12138
|
applyVariables,
|
|
@@ -9021,9 +12140,12 @@ export {
|
|
|
9021
12140
|
substituteVariables,
|
|
9022
12141
|
parseImplementationHint,
|
|
9023
12142
|
implementationHintSuggestions,
|
|
12143
|
+
factLifecycleDiagnostic,
|
|
9024
12144
|
linkWorkspace,
|
|
9025
12145
|
parseVars,
|
|
9026
12146
|
selectorRepoAmbiguousDiagnostic,
|
|
9027
|
-
trace
|
|
12147
|
+
trace,
|
|
12148
|
+
compactTrace,
|
|
12149
|
+
traceAndCompact
|
|
9028
12150
|
};
|
|
9029
|
-
//# sourceMappingURL=chunk-
|
|
12151
|
+
//# sourceMappingURL=chunk-TOVX4WYH.js.map
|