@neat.is/core 0.3.3 → 0.3.5
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/dist/{chunk-VABXPLDT.js → chunk-BUB3ASD5.js} +268 -142
- package/dist/chunk-BUB3ASD5.js.map +1 -0
- package/dist/{chunk-ICFH326H.js → chunk-KCEZSFU2.js} +2 -2
- package/dist/cli.cjs +309 -204
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +244 -144
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +251 -151
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +213 -115
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-VABXPLDT.js.map +0 -1
- /package/dist/{chunk-ICFH326H.js.map → chunk-KCEZSFU2.js.map} +0 -0
|
@@ -339,17 +339,21 @@ import {
|
|
|
339
339
|
BlastRadiusResultSchema,
|
|
340
340
|
NodeType,
|
|
341
341
|
PROV_RANK,
|
|
342
|
-
Provenance,
|
|
343
342
|
RootCauseResultSchema,
|
|
344
343
|
TransitiveDependenciesResultSchema
|
|
345
344
|
} from "@neat.is/types";
|
|
346
345
|
var ROOT_CAUSE_MAX_DEPTH = 5;
|
|
347
346
|
var BLAST_RADIUS_DEFAULT_DEPTH = 10;
|
|
347
|
+
function isFrontierNode(graph, nodeId) {
|
|
348
|
+
if (!graph.hasNode(nodeId)) return false;
|
|
349
|
+
const attrs = graph.getNodeAttributes(nodeId);
|
|
350
|
+
return attrs.type === NodeType.FrontierNode;
|
|
351
|
+
}
|
|
348
352
|
function bestEdgeBySource(graph, edgeIds) {
|
|
349
353
|
const best = /* @__PURE__ */ new Map();
|
|
350
354
|
for (const id of edgeIds) {
|
|
351
355
|
const e = graph.getEdgeAttributes(id);
|
|
352
|
-
if (e.
|
|
356
|
+
if (isFrontierNode(graph, e.source)) continue;
|
|
353
357
|
const cur = best.get(e.source);
|
|
354
358
|
if (!cur || PROV_RANK[e.provenance] > PROV_RANK[cur.provenance]) {
|
|
355
359
|
best.set(e.source, e);
|
|
@@ -361,7 +365,7 @@ function bestEdgeByTarget(graph, edgeIds) {
|
|
|
361
365
|
const best = /* @__PURE__ */ new Map();
|
|
362
366
|
for (const id of edgeIds) {
|
|
363
367
|
const e = graph.getEdgeAttributes(id);
|
|
364
|
-
if (e.
|
|
368
|
+
if (isFrontierNode(graph, e.target)) continue;
|
|
365
369
|
const cur = best.get(e.target);
|
|
366
370
|
if (!cur || PROV_RANK[e.provenance] > PROV_RANK[cur.provenance]) {
|
|
367
371
|
best.set(e.target, e);
|
|
@@ -373,8 +377,7 @@ var PROVENANCE_CEILING = {
|
|
|
373
377
|
OBSERVED: 1,
|
|
374
378
|
INFERRED: 0.7,
|
|
375
379
|
EXTRACTED: 0.5,
|
|
376
|
-
STALE: 0.3
|
|
377
|
-
FRONTIER: 0.3
|
|
380
|
+
STALE: 0.3
|
|
378
381
|
};
|
|
379
382
|
function volumeWeight(spanCount) {
|
|
380
383
|
if (!spanCount || spanCount <= 0) return 0.5;
|
|
@@ -427,19 +430,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
427
430
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
428
431
|
let best = { path: [start], edges: [] };
|
|
429
432
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
430
|
-
function step(node,
|
|
431
|
-
if (
|
|
432
|
-
best = { path: [...
|
|
433
|
+
function step(node, path34, edges) {
|
|
434
|
+
if (path34.length > best.path.length) {
|
|
435
|
+
best = { path: [...path34], edges: [...edges] };
|
|
433
436
|
}
|
|
434
|
-
if (
|
|
437
|
+
if (path34.length - 1 >= maxDepth) return;
|
|
435
438
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
436
439
|
for (const [srcId, edge] of incoming) {
|
|
437
440
|
if (visited.has(srcId)) continue;
|
|
438
441
|
visited.add(srcId);
|
|
439
|
-
|
|
442
|
+
path34.push(srcId);
|
|
440
443
|
edges.push(edge);
|
|
441
|
-
step(srcId,
|
|
442
|
-
|
|
444
|
+
step(srcId, path34, edges);
|
|
445
|
+
path34.pop();
|
|
443
446
|
edges.pop();
|
|
444
447
|
visited.delete(srcId);
|
|
445
448
|
}
|
|
@@ -631,8 +634,7 @@ import path2 from "path";
|
|
|
631
634
|
import {
|
|
632
635
|
EdgeType,
|
|
633
636
|
NodeType as NodeType2,
|
|
634
|
-
PolicyFileSchema
|
|
635
|
-
Provenance as Provenance2
|
|
637
|
+
PolicyFileSchema
|
|
636
638
|
} from "@neat.is/types";
|
|
637
639
|
|
|
638
640
|
// src/events.ts
|
|
@@ -733,8 +735,8 @@ var evaluateStructural = ({
|
|
|
733
735
|
for (const edgeId of graph.outboundEdges(id)) {
|
|
734
736
|
const e = graph.getEdgeAttributes(edgeId);
|
|
735
737
|
if (e.type !== rule.edgeType) continue;
|
|
736
|
-
if (e.provenance === Provenance2.FRONTIER) continue;
|
|
737
738
|
const target = graph.getNodeAttributes(e.target);
|
|
739
|
+
if (target.type === NodeType2.FrontierNode) continue;
|
|
738
740
|
if (target.type === rule.toNodeType) {
|
|
739
741
|
satisfied = true;
|
|
740
742
|
break;
|
|
@@ -853,8 +855,8 @@ var evaluateCompatibility = ({
|
|
|
853
855
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
854
856
|
const e = graph.getEdgeAttributes(edgeId);
|
|
855
857
|
if (e.type !== EdgeType.CONNECTS_TO) continue;
|
|
856
|
-
if (e.provenance === Provenance2.FRONTIER) continue;
|
|
857
858
|
const dbAttrs = graph.getNodeAttributes(e.target);
|
|
859
|
+
if (dbAttrs.type === NodeType2.FrontierNode) continue;
|
|
858
860
|
if (dbAttrs.type !== NodeType2.DatabaseNode) continue;
|
|
859
861
|
const db = dbAttrs;
|
|
860
862
|
for (const pair of compatPairs()) {
|
|
@@ -1017,10 +1019,10 @@ var PolicyViolationsLog = class {
|
|
|
1017
1019
|
import {
|
|
1018
1020
|
EdgeType as EdgeType2,
|
|
1019
1021
|
NodeType as NodeType3,
|
|
1020
|
-
Provenance
|
|
1022
|
+
Provenance,
|
|
1023
|
+
confidenceForObservedSignal,
|
|
1021
1024
|
databaseId,
|
|
1022
1025
|
extractedEdgeId,
|
|
1023
|
-
frontierEdgeId,
|
|
1024
1026
|
frontierId,
|
|
1025
1027
|
inferredEdgeId,
|
|
1026
1028
|
observedEdgeId,
|
|
@@ -1182,31 +1184,6 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
1182
1184
|
graph.addNode(id, node);
|
|
1183
1185
|
return id;
|
|
1184
1186
|
}
|
|
1185
|
-
function upsertFrontierEdge(graph, type, source, target, ts) {
|
|
1186
|
-
const id = frontierEdgeId(source, target, type);
|
|
1187
|
-
if (graph.hasEdge(id)) {
|
|
1188
|
-
const existing = graph.getEdgeAttributes(id);
|
|
1189
|
-
const updated = {
|
|
1190
|
-
...existing,
|
|
1191
|
-
provenance: Provenance3.FRONTIER,
|
|
1192
|
-
lastObserved: ts,
|
|
1193
|
-
callCount: (existing.callCount ?? 0) + 1
|
|
1194
|
-
};
|
|
1195
|
-
graph.replaceEdgeAttributes(id, updated);
|
|
1196
|
-
return;
|
|
1197
|
-
}
|
|
1198
|
-
const edge = {
|
|
1199
|
-
id,
|
|
1200
|
-
source,
|
|
1201
|
-
target,
|
|
1202
|
-
type,
|
|
1203
|
-
provenance: Provenance3.FRONTIER,
|
|
1204
|
-
confidence: 1,
|
|
1205
|
-
lastObserved: ts,
|
|
1206
|
-
callCount: 1
|
|
1207
|
-
};
|
|
1208
|
-
graph.addEdgeWithKey(id, source, target, edge);
|
|
1209
|
-
}
|
|
1210
1187
|
function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
1211
1188
|
if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
|
|
1212
1189
|
const id = makeObservedEdgeId(type, source, target);
|
|
@@ -1214,35 +1191,37 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
|
1214
1191
|
const existing = graph.getEdgeAttributes(id);
|
|
1215
1192
|
const newSpanCount = (existing.signal?.spanCount ?? existing.callCount ?? 0) + 1;
|
|
1216
1193
|
const newErrorCount = (existing.signal?.errorCount ?? 0) + (isError ? 1 : 0);
|
|
1194
|
+
const newSignal = {
|
|
1195
|
+
spanCount: newSpanCount,
|
|
1196
|
+
errorCount: newErrorCount,
|
|
1197
|
+
lastObservedAgeMs: 0
|
|
1198
|
+
};
|
|
1217
1199
|
const updated = {
|
|
1218
1200
|
...existing,
|
|
1219
|
-
provenance:
|
|
1201
|
+
provenance: Provenance.OBSERVED,
|
|
1220
1202
|
lastObserved: ts,
|
|
1221
1203
|
callCount: newSpanCount,
|
|
1222
|
-
signal:
|
|
1223
|
-
|
|
1224
|
-
errorCount: newErrorCount,
|
|
1225
|
-
lastObservedAgeMs: 0
|
|
1226
|
-
},
|
|
1227
|
-
confidence: 1
|
|
1204
|
+
signal: newSignal,
|
|
1205
|
+
confidence: confidenceForObservedSignal(newSignal)
|
|
1228
1206
|
};
|
|
1229
1207
|
graph.replaceEdgeAttributes(id, updated);
|
|
1230
1208
|
return { edge: updated, created: false };
|
|
1231
1209
|
}
|
|
1210
|
+
const signal = {
|
|
1211
|
+
spanCount: 1,
|
|
1212
|
+
errorCount: isError ? 1 : 0,
|
|
1213
|
+
lastObservedAgeMs: 0
|
|
1214
|
+
};
|
|
1232
1215
|
const edge = {
|
|
1233
1216
|
id,
|
|
1234
1217
|
source,
|
|
1235
1218
|
target,
|
|
1236
1219
|
type,
|
|
1237
|
-
provenance:
|
|
1238
|
-
confidence:
|
|
1220
|
+
provenance: Provenance.OBSERVED,
|
|
1221
|
+
confidence: confidenceForObservedSignal(signal),
|
|
1239
1222
|
lastObserved: ts,
|
|
1240
1223
|
callCount: 1,
|
|
1241
|
-
signal
|
|
1242
|
-
spanCount: 1,
|
|
1243
|
-
errorCount: isError ? 1 : 0,
|
|
1244
|
-
lastObservedAgeMs: 0
|
|
1245
|
-
}
|
|
1224
|
+
signal
|
|
1246
1225
|
};
|
|
1247
1226
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
1248
1227
|
return { edge, created: true };
|
|
@@ -1257,7 +1236,7 @@ function stitchTrace(graph, sourceServiceId, ts) {
|
|
|
1257
1236
|
const outbound = graph.outboundEdges(nodeId);
|
|
1258
1237
|
for (const edgeId of outbound) {
|
|
1259
1238
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
1260
|
-
if (edge.provenance !==
|
|
1239
|
+
if (edge.provenance !== Provenance.EXTRACTED) continue;
|
|
1261
1240
|
if (graph.hasEdge(observedEdgeId(edge.source, edge.target, edge.type))) continue;
|
|
1262
1241
|
upsertInferredEdge(graph, edge.type, edge.source, edge.target, ts);
|
|
1263
1242
|
if (!visited.has(edge.target)) {
|
|
@@ -1280,7 +1259,7 @@ function upsertInferredEdge(graph, type, source, target, ts) {
|
|
|
1280
1259
|
source,
|
|
1281
1260
|
target,
|
|
1282
1261
|
type,
|
|
1283
|
-
provenance:
|
|
1262
|
+
provenance: Provenance.INFERRED,
|
|
1284
1263
|
confidence: INFERRED_CONFIDENCE,
|
|
1285
1264
|
lastObserved: ts
|
|
1286
1265
|
};
|
|
@@ -1290,18 +1269,28 @@ async function appendErrorEvent(ctx, ev) {
|
|
|
1290
1269
|
await fs3.mkdir(path3.dirname(ctx.errorsPath), { recursive: true });
|
|
1291
1270
|
await fs3.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
1292
1271
|
}
|
|
1272
|
+
function sanitizeAttributes(attrs) {
|
|
1273
|
+
const out = {};
|
|
1274
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
1275
|
+
if (typeof v === "bigint") out[k] = v.toString();
|
|
1276
|
+
else out[k] = v;
|
|
1277
|
+
}
|
|
1278
|
+
return out;
|
|
1279
|
+
}
|
|
1293
1280
|
function buildErrorEventForReceiver(span) {
|
|
1294
1281
|
if (span.statusCode !== 2) return null;
|
|
1295
1282
|
const ts = span.startTimeIso ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1283
|
+
const attrs = sanitizeAttributes(span.attributes);
|
|
1296
1284
|
return {
|
|
1297
1285
|
id: `${span.traceId}:${span.spanId}`,
|
|
1298
1286
|
timestamp: ts,
|
|
1299
1287
|
service: span.service,
|
|
1300
1288
|
traceId: span.traceId,
|
|
1301
1289
|
spanId: span.spanId,
|
|
1302
|
-
errorMessage: span.exception?.message ?? span.
|
|
1290
|
+
errorMessage: span.exception?.message ?? span.name ?? "unknown error",
|
|
1303
1291
|
...span.exception?.type ? { exceptionType: span.exception.type } : {},
|
|
1304
1292
|
...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
|
|
1293
|
+
...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
|
|
1305
1294
|
affectedNode: serviceId(span.service)
|
|
1306
1295
|
};
|
|
1307
1296
|
}
|
|
@@ -1352,11 +1341,16 @@ async function handleSpan(ctx, span) {
|
|
|
1352
1341
|
affectedNode = targetId;
|
|
1353
1342
|
resolvedViaAddress = true;
|
|
1354
1343
|
} else if (!targetId) {
|
|
1355
|
-
const
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1344
|
+
const frontierNodeId = ensureFrontierNode(ctx.graph, host, ts);
|
|
1345
|
+
upsertObservedEdge(
|
|
1346
|
+
ctx.graph,
|
|
1347
|
+
EdgeType2.CALLS,
|
|
1348
|
+
sourceId,
|
|
1349
|
+
frontierNodeId,
|
|
1350
|
+
ts,
|
|
1351
|
+
isError
|
|
1352
|
+
);
|
|
1353
|
+
affectedNode = frontierNodeId;
|
|
1360
1354
|
resolvedViaAddress = true;
|
|
1361
1355
|
}
|
|
1362
1356
|
}
|
|
@@ -1378,15 +1372,17 @@ async function handleSpan(ctx, span) {
|
|
|
1378
1372
|
if (span.statusCode === 2) {
|
|
1379
1373
|
stitchTrace(ctx.graph, sourceId, ts);
|
|
1380
1374
|
if (ctx.writeErrorEventInline !== false) {
|
|
1375
|
+
const attrs = sanitizeAttributes(span.attributes);
|
|
1381
1376
|
const ev = {
|
|
1382
1377
|
id: `${span.traceId}:${span.spanId}`,
|
|
1383
1378
|
timestamp: ts,
|
|
1384
1379
|
service: span.service,
|
|
1385
1380
|
traceId: span.traceId,
|
|
1386
1381
|
spanId: span.spanId,
|
|
1387
|
-
errorMessage: span.exception?.message ?? span.
|
|
1382
|
+
errorMessage: span.exception?.message ?? span.name ?? "unknown error",
|
|
1388
1383
|
...span.exception?.type ? { exceptionType: span.exception.type } : {},
|
|
1389
1384
|
...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
|
|
1385
|
+
...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
|
|
1390
1386
|
affectedNode
|
|
1391
1387
|
};
|
|
1392
1388
|
await appendErrorEvent(ctx, ev);
|
|
@@ -1442,8 +1438,7 @@ function rewireFrontierEdges(graph, frontierId2, serviceId3) {
|
|
|
1442
1438
|
}
|
|
1443
1439
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
1444
1440
|
graph.dropEdge(oldEdgeId);
|
|
1445
|
-
const
|
|
1446
|
-
const newId = promotedProvenance === Provenance3.OBSERVED ? observedEdgeId(newSource, newTarget, edge.type) : promotedProvenance === Provenance3.INFERRED ? inferredEdgeId(newSource, newTarget, edge.type) : promotedProvenance === Provenance3.EXTRACTED ? extractedEdgeId(newSource, newTarget, edge.type) : frontierEdgeId(newSource, newTarget, edge.type);
|
|
1441
|
+
const newId = edge.provenance === Provenance.OBSERVED ? observedEdgeId(newSource, newTarget, edge.type) : edge.provenance === Provenance.INFERRED ? inferredEdgeId(newSource, newTarget, edge.type) : extractedEdgeId(newSource, newTarget, edge.type);
|
|
1447
1442
|
if (graph.hasEdge(newId)) {
|
|
1448
1443
|
const existing = graph.getEdgeAttributes(newId);
|
|
1449
1444
|
const merged = {
|
|
@@ -1458,8 +1453,7 @@ function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
|
1458
1453
|
...edge,
|
|
1459
1454
|
id: newId,
|
|
1460
1455
|
source: newSource,
|
|
1461
|
-
target: newTarget
|
|
1462
|
-
provenance: promotedProvenance
|
|
1456
|
+
target: newTarget
|
|
1463
1457
|
};
|
|
1464
1458
|
graph.addEdgeWithKey(newId, newSource, newTarget, rebuilt);
|
|
1465
1459
|
}
|
|
@@ -1478,12 +1472,12 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
1478
1472
|
const project = options.project ?? DEFAULT_PROJECT;
|
|
1479
1473
|
graph.forEachEdge((id, attrs) => {
|
|
1480
1474
|
const e = attrs;
|
|
1481
|
-
if (e.provenance !==
|
|
1475
|
+
if (e.provenance !== Provenance.OBSERVED) return;
|
|
1482
1476
|
if (!e.lastObserved) return;
|
|
1483
1477
|
const threshold = thresholdForEdgeType(e.type, thresholds);
|
|
1484
1478
|
const age = now - new Date(e.lastObserved).getTime();
|
|
1485
1479
|
if (age > threshold) {
|
|
1486
|
-
const updated = { ...e, provenance:
|
|
1480
|
+
const updated = { ...e, provenance: Provenance.STALE, confidence: 0.3 };
|
|
1487
1481
|
graph.replaceEdgeAttributes(id, updated);
|
|
1488
1482
|
events.push({
|
|
1489
1483
|
edgeId: id,
|
|
@@ -1500,8 +1494,8 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
1500
1494
|
project,
|
|
1501
1495
|
payload: {
|
|
1502
1496
|
edgeId: id,
|
|
1503
|
-
from:
|
|
1504
|
-
to:
|
|
1497
|
+
from: Provenance.OBSERVED,
|
|
1498
|
+
to: Provenance.STALE
|
|
1505
1499
|
}
|
|
1506
1500
|
});
|
|
1507
1501
|
}
|
|
@@ -1593,6 +1587,27 @@ function formatExtractionBanner(count) {
|
|
|
1593
1587
|
if (count === 1) return `[neat] 1 file skipped due to parse errors`;
|
|
1594
1588
|
return `[neat] ${count} files skipped due to parse errors`;
|
|
1595
1589
|
}
|
|
1590
|
+
var droppedSink = [];
|
|
1591
|
+
function noteExtractedDropped(edge) {
|
|
1592
|
+
droppedSink.push(edge);
|
|
1593
|
+
}
|
|
1594
|
+
function drainDroppedExtracted() {
|
|
1595
|
+
return droppedSink.splice(0, droppedSink.length);
|
|
1596
|
+
}
|
|
1597
|
+
function isRejectedLogEnabled() {
|
|
1598
|
+
const raw = process.env.NEAT_EXTRACTED_REJECTED_LOG;
|
|
1599
|
+
return raw === "1" || raw === "true";
|
|
1600
|
+
}
|
|
1601
|
+
async function writeRejectedExtracted(drops, rejectedPath) {
|
|
1602
|
+
if (drops.length === 0) return;
|
|
1603
|
+
await fs4.mkdir(path4.dirname(rejectedPath), { recursive: true });
|
|
1604
|
+
const lines = drops.map((d) => JSON.stringify({ ...d, ts: (/* @__PURE__ */ new Date()).toISOString() })).join("\n") + "\n";
|
|
1605
|
+
await fs4.appendFile(rejectedPath, lines, "utf8");
|
|
1606
|
+
}
|
|
1607
|
+
function formatPrecisionFloorBanner(count) {
|
|
1608
|
+
if (count === 1) return `[neat] 1 extracted edge dropped below precision floor`;
|
|
1609
|
+
return `[neat] ${count} extracted edges dropped below precision floor`;
|
|
1610
|
+
}
|
|
1596
1611
|
|
|
1597
1612
|
// src/extract/services.ts
|
|
1598
1613
|
import { promises as fs8 } from "fs";
|
|
@@ -2223,7 +2238,13 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
2223
2238
|
|
|
2224
2239
|
// src/extract/databases/index.ts
|
|
2225
2240
|
import path17 from "path";
|
|
2226
|
-
import {
|
|
2241
|
+
import {
|
|
2242
|
+
EdgeType as EdgeType3,
|
|
2243
|
+
NodeType as NodeType6,
|
|
2244
|
+
Provenance as Provenance2,
|
|
2245
|
+
databaseId as databaseId2,
|
|
2246
|
+
confidenceForExtracted
|
|
2247
|
+
} from "@neat.is/types";
|
|
2227
2248
|
|
|
2228
2249
|
// src/extract/databases/db-config-yaml.ts
|
|
2229
2250
|
import path10 from "path";
|
|
@@ -2797,7 +2818,8 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
2797
2818
|
source: service.node.id,
|
|
2798
2819
|
target: dbNode.id,
|
|
2799
2820
|
type: EdgeType3.CONNECTS_TO,
|
|
2800
|
-
provenance:
|
|
2821
|
+
provenance: Provenance2.EXTRACTED,
|
|
2822
|
+
confidence: confidenceForExtracted("structural"),
|
|
2801
2823
|
// ADR-032 / #140 — every EXTRACTED edge carries evidence.file.
|
|
2802
2824
|
// Ghost-edge cleanup keys retirement on this; the conditional
|
|
2803
2825
|
// sourceFile spread that used to live here was a v0.1.x leftover.
|
|
@@ -2830,7 +2852,13 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
2830
2852
|
// src/extract/configs.ts
|
|
2831
2853
|
import { promises as fs12 } from "fs";
|
|
2832
2854
|
import path18 from "path";
|
|
2833
|
-
import {
|
|
2855
|
+
import {
|
|
2856
|
+
EdgeType as EdgeType4,
|
|
2857
|
+
NodeType as NodeType7,
|
|
2858
|
+
Provenance as Provenance3,
|
|
2859
|
+
configId,
|
|
2860
|
+
confidenceForExtracted as confidenceForExtracted2
|
|
2861
|
+
} from "@neat.is/types";
|
|
2834
2862
|
async function walkConfigFiles(dir) {
|
|
2835
2863
|
const out = [];
|
|
2836
2864
|
async function walk(current) {
|
|
@@ -2870,7 +2898,8 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
2870
2898
|
source: service.node.id,
|
|
2871
2899
|
target: node.id,
|
|
2872
2900
|
type: EdgeType4.CONFIGURED_BY,
|
|
2873
|
-
provenance:
|
|
2901
|
+
provenance: Provenance3.EXTRACTED,
|
|
2902
|
+
confidence: confidenceForExtracted2("structural"),
|
|
2874
2903
|
evidence: { file: relPath.split(path18.sep).join("/") }
|
|
2875
2904
|
};
|
|
2876
2905
|
if (!graph.hasEdge(edge.id)) {
|
|
@@ -2883,14 +2912,25 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
2883
2912
|
}
|
|
2884
2913
|
|
|
2885
2914
|
// src/extract/calls/index.ts
|
|
2886
|
-
import {
|
|
2915
|
+
import {
|
|
2916
|
+
EdgeType as EdgeType6,
|
|
2917
|
+
NodeType as NodeType8,
|
|
2918
|
+
Provenance as Provenance5,
|
|
2919
|
+
confidenceForExtracted as confidenceForExtracted4,
|
|
2920
|
+
passesExtractedFloor as passesExtractedFloor2
|
|
2921
|
+
} from "@neat.is/types";
|
|
2887
2922
|
|
|
2888
2923
|
// src/extract/calls/http.ts
|
|
2889
2924
|
import path20 from "path";
|
|
2890
2925
|
import Parser from "tree-sitter";
|
|
2891
2926
|
import JavaScript from "tree-sitter-javascript";
|
|
2892
2927
|
import Python from "tree-sitter-python";
|
|
2893
|
-
import {
|
|
2928
|
+
import {
|
|
2929
|
+
EdgeType as EdgeType5,
|
|
2930
|
+
Provenance as Provenance4,
|
|
2931
|
+
confidenceForExtracted as confidenceForExtracted3,
|
|
2932
|
+
passesExtractedFloor
|
|
2933
|
+
} from "@neat.is/types";
|
|
2894
2934
|
|
|
2895
2935
|
// src/extract/calls/shared.ts
|
|
2896
2936
|
import { promises as fs13 } from "fs";
|
|
@@ -3022,17 +3062,32 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3022
3062
|
for (const [targetId, evidenceFile] of seenTargets) {
|
|
3023
3063
|
const fileContent = files.find((f) => f.path === evidenceFile.file)?.content ?? "";
|
|
3024
3064
|
const line = lineOf(fileContent, `//${evidenceFile.host}`);
|
|
3065
|
+
const confidence = confidenceForExtracted3("hostname-shape-match");
|
|
3066
|
+
const ev = {
|
|
3067
|
+
file: path20.relative(service.dir, evidenceFile.file),
|
|
3068
|
+
line,
|
|
3069
|
+
snippet: snippet(fileContent, line)
|
|
3070
|
+
};
|
|
3071
|
+
const edgeId = extractedEdgeId2(service.node.id, targetId, EdgeType5.CALLS);
|
|
3072
|
+
if (!passesExtractedFloor(confidence)) {
|
|
3073
|
+
noteExtractedDropped({
|
|
3074
|
+
source: service.node.id,
|
|
3075
|
+
target: targetId,
|
|
3076
|
+
type: EdgeType5.CALLS,
|
|
3077
|
+
confidence,
|
|
3078
|
+
confidenceKind: "hostname-shape-match",
|
|
3079
|
+
evidence: ev
|
|
3080
|
+
});
|
|
3081
|
+
continue;
|
|
3082
|
+
}
|
|
3025
3083
|
const edge = {
|
|
3026
|
-
id:
|
|
3084
|
+
id: edgeId,
|
|
3027
3085
|
source: service.node.id,
|
|
3028
3086
|
target: targetId,
|
|
3029
3087
|
type: EdgeType5.CALLS,
|
|
3030
|
-
provenance:
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
line,
|
|
3034
|
-
snippet: snippet(fileContent, line)
|
|
3035
|
-
}
|
|
3088
|
+
provenance: Provenance4.EXTRACTED,
|
|
3089
|
+
confidence,
|
|
3090
|
+
evidence: ev
|
|
3036
3091
|
};
|
|
3037
3092
|
if (!graph.hasEdge(edge.id)) {
|
|
3038
3093
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3070,6 +3125,10 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3070
3125
|
name: topic,
|
|
3071
3126
|
kind: "kafka-topic",
|
|
3072
3127
|
edgeType,
|
|
3128
|
+
// `producer.send({topic: 'x'})` / `consumer.subscribe({topic: 'x'})` —
|
|
3129
|
+
// framework-aware (kafkajs / node-rdkafka shape). Verified-call-site
|
|
3130
|
+
// tier (ADR-066).
|
|
3131
|
+
confidenceKind: "verified-call-site",
|
|
3073
3132
|
evidence: {
|
|
3074
3133
|
file: path21.relative(serviceDir, file.path),
|
|
3075
3134
|
line,
|
|
@@ -3101,6 +3160,10 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3101
3160
|
name: host,
|
|
3102
3161
|
kind: "redis",
|
|
3103
3162
|
edgeType: "CALLS",
|
|
3163
|
+
// `redis://host` URL literal — the scheme is structural support, but no
|
|
3164
|
+
// call expression is verified to wire it through. URL-with-structural-
|
|
3165
|
+
// support tier (ADR-066).
|
|
3166
|
+
confidenceKind: "url-with-structural-support",
|
|
3104
3167
|
evidence: {
|
|
3105
3168
|
file: path22.relative(serviceDir, file.path),
|
|
3106
3169
|
line,
|
|
@@ -3141,6 +3204,10 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3141
3204
|
name,
|
|
3142
3205
|
kind,
|
|
3143
3206
|
edgeType: "CALLS",
|
|
3207
|
+
// SDK marker (S3Client, GetCommand, etc.) plus a Bucket/TableName
|
|
3208
|
+
// literal — framework-aware recognizer, verified-call-site tier
|
|
3209
|
+
// (ADR-066).
|
|
3210
|
+
confidenceKind: "verified-call-site",
|
|
3144
3211
|
evidence: {
|
|
3145
3212
|
file: path23.relative(serviceDir, file.path),
|
|
3146
3213
|
line,
|
|
@@ -3219,6 +3286,10 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3219
3286
|
name,
|
|
3220
3287
|
kind,
|
|
3221
3288
|
edgeType: "CALLS",
|
|
3289
|
+
// `new <Name>Client(...)` with @aws-sdk/* or @grpc/grpc-js import
|
|
3290
|
+
// context — import-aware classification per #238. Verified-call-site
|
|
3291
|
+
// tier (ADR-066).
|
|
3292
|
+
confidenceKind: "verified-call-site",
|
|
3222
3293
|
evidence: {
|
|
3223
3294
|
file: path24.relative(serviceDir, file.path),
|
|
3224
3295
|
line,
|
|
@@ -3279,13 +3350,26 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3279
3350
|
const edgeId = extractedEdgeId2(service.node.id, ep.infraId, edgeType);
|
|
3280
3351
|
if (seenEdges.has(edgeId)) continue;
|
|
3281
3352
|
seenEdges.add(edgeId);
|
|
3353
|
+
const confidence = confidenceForExtracted4(ep.confidenceKind);
|
|
3354
|
+
if (!passesExtractedFloor2(confidence)) {
|
|
3355
|
+
noteExtractedDropped({
|
|
3356
|
+
source: service.node.id,
|
|
3357
|
+
target: ep.infraId,
|
|
3358
|
+
type: edgeType,
|
|
3359
|
+
confidence,
|
|
3360
|
+
confidenceKind: ep.confidenceKind,
|
|
3361
|
+
evidence: ep.evidence
|
|
3362
|
+
});
|
|
3363
|
+
continue;
|
|
3364
|
+
}
|
|
3282
3365
|
if (!graph.hasEdge(edgeId)) {
|
|
3283
3366
|
const edge = {
|
|
3284
3367
|
id: edgeId,
|
|
3285
3368
|
source: service.node.id,
|
|
3286
3369
|
target: ep.infraId,
|
|
3287
3370
|
type: edgeType,
|
|
3288
|
-
provenance:
|
|
3371
|
+
provenance: Provenance5.EXTRACTED,
|
|
3372
|
+
confidence,
|
|
3289
3373
|
evidence: ep.evidence
|
|
3290
3374
|
};
|
|
3291
3375
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3306,7 +3390,7 @@ async function addCallEdges(graph, services) {
|
|
|
3306
3390
|
|
|
3307
3391
|
// src/extract/infra/docker-compose.ts
|
|
3308
3392
|
import path25 from "path";
|
|
3309
|
-
import { EdgeType as EdgeType7, Provenance as
|
|
3393
|
+
import { EdgeType as EdgeType7, Provenance as Provenance6, confidenceForExtracted as confidenceForExtracted5 } from "@neat.is/types";
|
|
3310
3394
|
|
|
3311
3395
|
// src/extract/infra/shared.ts
|
|
3312
3396
|
import { NodeType as NodeType9, infraId as infraId5 } from "@neat.is/types";
|
|
@@ -3399,7 +3483,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3399
3483
|
source: sourceId,
|
|
3400
3484
|
target: targetId,
|
|
3401
3485
|
type: EdgeType7.DEPENDS_ON,
|
|
3402
|
-
provenance:
|
|
3486
|
+
provenance: Provenance6.EXTRACTED,
|
|
3487
|
+
confidence: confidenceForExtracted5("structural"),
|
|
3403
3488
|
evidence: { file: evidenceFile }
|
|
3404
3489
|
};
|
|
3405
3490
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3412,7 +3497,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3412
3497
|
// src/extract/infra/dockerfile.ts
|
|
3413
3498
|
import path26 from "path";
|
|
3414
3499
|
import { promises as fs14 } from "fs";
|
|
3415
|
-
import { EdgeType as EdgeType8, Provenance as
|
|
3500
|
+
import { EdgeType as EdgeType8, Provenance as Provenance7, confidenceForExtracted as confidenceForExtracted6 } from "@neat.is/types";
|
|
3416
3501
|
function runtimeImage(content) {
|
|
3417
3502
|
const lines = content.split("\n");
|
|
3418
3503
|
let last = null;
|
|
@@ -3458,7 +3543,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3458
3543
|
source: service.node.id,
|
|
3459
3544
|
target: node.id,
|
|
3460
3545
|
type: EdgeType8.RUNS_ON,
|
|
3461
|
-
provenance:
|
|
3546
|
+
provenance: Provenance7.EXTRACTED,
|
|
3547
|
+
confidence: confidenceForExtracted6("structural"),
|
|
3462
3548
|
evidence: {
|
|
3463
3549
|
file: path26.relative(scanPath, dockerfilePath).split(path26.sep).join("/")
|
|
3464
3550
|
}
|
|
@@ -3573,16 +3659,19 @@ async function addInfra(graph, scanPath, services) {
|
|
|
3573
3659
|
};
|
|
3574
3660
|
}
|
|
3575
3661
|
|
|
3662
|
+
// src/extract/index.ts
|
|
3663
|
+
import path30 from "path";
|
|
3664
|
+
|
|
3576
3665
|
// src/extract/retire.ts
|
|
3577
3666
|
import { existsSync } from "fs";
|
|
3578
3667
|
import path29 from "path";
|
|
3579
|
-
import { Provenance as
|
|
3668
|
+
import { Provenance as Provenance8 } from "@neat.is/types";
|
|
3580
3669
|
function retireEdgesByFile(graph, file) {
|
|
3581
3670
|
const normalized = file.split("\\").join("/");
|
|
3582
3671
|
const toDrop = [];
|
|
3583
3672
|
graph.forEachEdge((id, attrs) => {
|
|
3584
3673
|
const edge = attrs;
|
|
3585
|
-
if (edge.provenance !==
|
|
3674
|
+
if (edge.provenance !== Provenance8.EXTRACTED) return;
|
|
3586
3675
|
if (!edge.evidence?.file) return;
|
|
3587
3676
|
if (edge.evidence.file === normalized) toDrop.push(id);
|
|
3588
3677
|
});
|
|
@@ -3594,7 +3683,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
3594
3683
|
const bases = [scanPath, ...serviceDirs];
|
|
3595
3684
|
graph.forEachEdge((id, attrs) => {
|
|
3596
3685
|
const edge = attrs;
|
|
3597
|
-
if (edge.provenance !==
|
|
3686
|
+
if (edge.provenance !== Provenance8.EXTRACTED) return;
|
|
3598
3687
|
const evidenceFile = edge.evidence?.file;
|
|
3599
3688
|
if (!evidenceFile) return;
|
|
3600
3689
|
if (path29.isAbsolute(evidenceFile)) {
|
|
@@ -3636,13 +3725,26 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
3636
3725
|
);
|
|
3637
3726
|
}
|
|
3638
3727
|
}
|
|
3728
|
+
const droppedEntries = drainDroppedExtracted();
|
|
3729
|
+
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
3730
|
+
const rejectedPath = path30.join(path30.dirname(opts.errorsPath), "rejected.ndjson");
|
|
3731
|
+
try {
|
|
3732
|
+
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
3733
|
+
} catch (err) {
|
|
3734
|
+
console.warn(
|
|
3735
|
+
`[neat] failed to write rejected extracted edges to ${rejectedPath}: ${err.message}`
|
|
3736
|
+
);
|
|
3737
|
+
}
|
|
3738
|
+
}
|
|
3639
3739
|
const result = {
|
|
3640
3740
|
nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
3641
3741
|
edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
3642
3742
|
frontiersPromoted,
|
|
3643
3743
|
extractionErrors: errorEntries.length,
|
|
3644
3744
|
errorEntries,
|
|
3645
|
-
ghostsRetired
|
|
3745
|
+
ghostsRetired,
|
|
3746
|
+
extractedDropped: droppedEntries.length,
|
|
3747
|
+
droppedEntries
|
|
3646
3748
|
};
|
|
3647
3749
|
emitNeatEvent({
|
|
3648
3750
|
type: "extraction-complete",
|
|
@@ -3659,8 +3761,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
3659
3761
|
|
|
3660
3762
|
// src/persist.ts
|
|
3661
3763
|
import { promises as fs17 } from "fs";
|
|
3662
|
-
import
|
|
3663
|
-
|
|
3764
|
+
import path31 from "path";
|
|
3765
|
+
import { Provenance as Provenance9, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
|
|
3766
|
+
var SCHEMA_VERSION = 3;
|
|
3664
3767
|
function migrateV1ToV2(payload) {
|
|
3665
3768
|
const nodes = payload.graph.nodes;
|
|
3666
3769
|
if (Array.isArray(nodes)) {
|
|
@@ -3672,8 +3775,27 @@ function migrateV1ToV2(payload) {
|
|
|
3672
3775
|
}
|
|
3673
3776
|
return { ...payload, schemaVersion: 2 };
|
|
3674
3777
|
}
|
|
3778
|
+
function migrateV2ToV3(payload) {
|
|
3779
|
+
const edges = payload.graph.edges;
|
|
3780
|
+
if (Array.isArray(edges)) {
|
|
3781
|
+
for (const edge of edges) {
|
|
3782
|
+
const attrs = edge.attributes;
|
|
3783
|
+
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
3784
|
+
attrs.provenance = Provenance9.OBSERVED;
|
|
3785
|
+
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
3786
|
+
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
3787
|
+
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
3788
|
+
if (type && source && target) {
|
|
3789
|
+
const newId = observedEdgeId2(source, target, type);
|
|
3790
|
+
attrs.id = newId;
|
|
3791
|
+
if (edge.key) edge.key = newId;
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
return { ...payload, schemaVersion: 3 };
|
|
3796
|
+
}
|
|
3675
3797
|
async function ensureDir(filePath) {
|
|
3676
|
-
await fs17.mkdir(
|
|
3798
|
+
await fs17.mkdir(path31.dirname(filePath), { recursive: true });
|
|
3677
3799
|
}
|
|
3678
3800
|
async function saveGraphToDisk(graph, outPath) {
|
|
3679
3801
|
await ensureDir(outPath);
|
|
@@ -3698,6 +3820,9 @@ async function loadGraphFromDisk(graph, outPath) {
|
|
|
3698
3820
|
if (payload.schemaVersion === 1) {
|
|
3699
3821
|
payload = migrateV1ToV2(payload);
|
|
3700
3822
|
}
|
|
3823
|
+
if (payload.schemaVersion === 2) {
|
|
3824
|
+
payload = migrateV2ToV3(payload);
|
|
3825
|
+
}
|
|
3701
3826
|
if (payload.schemaVersion !== SCHEMA_VERSION) {
|
|
3702
3827
|
throw new Error(
|
|
3703
3828
|
`persist: unsupported snapshot schemaVersion ${payload.schemaVersion} (expected ${SCHEMA_VERSION})`
|
|
@@ -3817,23 +3942,23 @@ function canonicalJson(value) {
|
|
|
3817
3942
|
}
|
|
3818
3943
|
|
|
3819
3944
|
// src/projects.ts
|
|
3820
|
-
import
|
|
3945
|
+
import path32 from "path";
|
|
3821
3946
|
function pathsForProject(project, baseDir) {
|
|
3822
3947
|
if (project === DEFAULT_PROJECT) {
|
|
3823
3948
|
return {
|
|
3824
|
-
snapshotPath:
|
|
3825
|
-
errorsPath:
|
|
3826
|
-
staleEventsPath:
|
|
3827
|
-
embeddingsCachePath:
|
|
3828
|
-
policyViolationsPath:
|
|
3949
|
+
snapshotPath: path32.join(baseDir, "graph.json"),
|
|
3950
|
+
errorsPath: path32.join(baseDir, "errors.ndjson"),
|
|
3951
|
+
staleEventsPath: path32.join(baseDir, "stale-events.ndjson"),
|
|
3952
|
+
embeddingsCachePath: path32.join(baseDir, "embeddings.json"),
|
|
3953
|
+
policyViolationsPath: path32.join(baseDir, "policy-violations.ndjson")
|
|
3829
3954
|
};
|
|
3830
3955
|
}
|
|
3831
3956
|
return {
|
|
3832
|
-
snapshotPath:
|
|
3833
|
-
errorsPath:
|
|
3834
|
-
staleEventsPath:
|
|
3835
|
-
embeddingsCachePath:
|
|
3836
|
-
policyViolationsPath:
|
|
3957
|
+
snapshotPath: path32.join(baseDir, `${project}.json`),
|
|
3958
|
+
errorsPath: path32.join(baseDir, `errors.${project}.ndjson`),
|
|
3959
|
+
staleEventsPath: path32.join(baseDir, `stale-events.${project}.ndjson`),
|
|
3960
|
+
embeddingsCachePath: path32.join(baseDir, `embeddings.${project}.json`),
|
|
3961
|
+
policyViolationsPath: path32.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
3837
3962
|
};
|
|
3838
3963
|
}
|
|
3839
3964
|
var Projects = class {
|
|
@@ -3874,7 +3999,7 @@ function parseExtraProjects(raw) {
|
|
|
3874
3999
|
// src/registry.ts
|
|
3875
4000
|
import { promises as fs19 } from "fs";
|
|
3876
4001
|
import os2 from "os";
|
|
3877
|
-
import
|
|
4002
|
+
import path33 from "path";
|
|
3878
4003
|
import {
|
|
3879
4004
|
RegistryFileSchema
|
|
3880
4005
|
} from "@neat.is/types";
|
|
@@ -3882,17 +4007,17 @@ var LOCK_TIMEOUT_MS = 5e3;
|
|
|
3882
4007
|
var LOCK_RETRY_MS = 50;
|
|
3883
4008
|
function neatHome() {
|
|
3884
4009
|
const override = process.env.NEAT_HOME;
|
|
3885
|
-
if (override && override.length > 0) return
|
|
3886
|
-
return
|
|
4010
|
+
if (override && override.length > 0) return path33.resolve(override);
|
|
4011
|
+
return path33.join(os2.homedir(), ".neat");
|
|
3887
4012
|
}
|
|
3888
4013
|
function registryPath() {
|
|
3889
|
-
return
|
|
4014
|
+
return path33.join(neatHome(), "projects.json");
|
|
3890
4015
|
}
|
|
3891
4016
|
function registryLockPath() {
|
|
3892
|
-
return
|
|
4017
|
+
return path33.join(neatHome(), "projects.json.lock");
|
|
3893
4018
|
}
|
|
3894
4019
|
async function normalizeProjectPath(input) {
|
|
3895
|
-
const resolved =
|
|
4020
|
+
const resolved = path33.resolve(input);
|
|
3896
4021
|
try {
|
|
3897
4022
|
return await fs19.realpath(resolved);
|
|
3898
4023
|
} catch {
|
|
@@ -3900,7 +4025,7 @@ async function normalizeProjectPath(input) {
|
|
|
3900
4025
|
}
|
|
3901
4026
|
}
|
|
3902
4027
|
async function writeAtomically(target, contents) {
|
|
3903
|
-
await fs19.mkdir(
|
|
4028
|
+
await fs19.mkdir(path33.dirname(target), { recursive: true });
|
|
3904
4029
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
3905
4030
|
const fd = await fs19.open(tmp, "w");
|
|
3906
4031
|
try {
|
|
@@ -3913,7 +4038,7 @@ async function writeAtomically(target, contents) {
|
|
|
3913
4038
|
}
|
|
3914
4039
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
3915
4040
|
const deadline = Date.now() + timeoutMs;
|
|
3916
|
-
await fs19.mkdir(
|
|
4041
|
+
await fs19.mkdir(path33.dirname(lockPath), { recursive: true });
|
|
3917
4042
|
while (true) {
|
|
3918
4043
|
try {
|
|
3919
4044
|
const fd = await fs19.open(lockPath, "wx");
|
|
@@ -4051,7 +4176,7 @@ import {
|
|
|
4051
4176
|
EdgeType as EdgeType9,
|
|
4052
4177
|
NodeType as NodeType10,
|
|
4053
4178
|
parseEdgeId,
|
|
4054
|
-
Provenance as
|
|
4179
|
+
Provenance as Provenance10
|
|
4055
4180
|
} from "@neat.is/types";
|
|
4056
4181
|
function bucketKey(source, target, type) {
|
|
4057
4182
|
return `${type}|${source}|${target}`;
|
|
@@ -4065,20 +4190,17 @@ function bucketEdges(graph) {
|
|
|
4065
4190
|
const key = bucketKey(e.source, e.target, e.type);
|
|
4066
4191
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
4067
4192
|
switch (provenance) {
|
|
4068
|
-
case
|
|
4193
|
+
case Provenance10.EXTRACTED:
|
|
4069
4194
|
cur.extracted = e;
|
|
4070
4195
|
break;
|
|
4071
|
-
case
|
|
4196
|
+
case Provenance10.OBSERVED:
|
|
4072
4197
|
cur.observed = e;
|
|
4073
4198
|
break;
|
|
4074
|
-
case
|
|
4199
|
+
case Provenance10.INFERRED:
|
|
4075
4200
|
cur.inferred = e;
|
|
4076
4201
|
break;
|
|
4077
|
-
case Provenance11.FRONTIER:
|
|
4078
|
-
cur.frontier = e;
|
|
4079
|
-
break;
|
|
4080
4202
|
default:
|
|
4081
|
-
if (e.provenance ===
|
|
4203
|
+
if (e.provenance === Provenance10.STALE) cur.stale = e;
|
|
4082
4204
|
}
|
|
4083
4205
|
buckets.set(key, cur);
|
|
4084
4206
|
});
|
|
@@ -4089,14 +4211,6 @@ function nodeIsFrontier(graph, nodeId) {
|
|
|
4089
4211
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
4090
4212
|
return attrs.type === NodeType10.FrontierNode;
|
|
4091
4213
|
}
|
|
4092
|
-
function hasAnyObservedFromSource(graph, sourceId) {
|
|
4093
|
-
if (!graph.hasNode(sourceId)) return false;
|
|
4094
|
-
for (const edgeId of graph.outboundEdges(sourceId)) {
|
|
4095
|
-
const e = graph.getEdgeAttributes(edgeId);
|
|
4096
|
-
if (e.provenance === Provenance11.OBSERVED) return true;
|
|
4097
|
-
}
|
|
4098
|
-
return false;
|
|
4099
|
-
}
|
|
4100
4214
|
function clampConfidence(n) {
|
|
4101
4215
|
if (!Number.isFinite(n)) return 0;
|
|
4102
4216
|
return Math.max(0, Math.min(1, n));
|
|
@@ -4110,32 +4224,34 @@ function reasonForMissingExtracted(source, target, type) {
|
|
|
4110
4224
|
var RECOMMENDATION_MISSING_OBSERVED = "Verify the code path is exercised in production; check feature flags or conditional branches that might gate the call.";
|
|
4111
4225
|
var RECOMMENDATION_MISSING_EXTRACTED = "Likely dynamic dispatch, reflection, or a coverage gap in tree-sitter extraction. Consider an `aliases` entry on the source service or file an extractor issue.";
|
|
4112
4226
|
var RECOMMENDATION_HOST_MISMATCH = "Check environment-specific config overrides \u2014 the runtime host differs from what static configuration declares.";
|
|
4227
|
+
function gradedConfidence(edge) {
|
|
4228
|
+
if (typeof edge.confidence === "number") return clampConfidence(edge.confidence);
|
|
4229
|
+
return clampConfidence(confidenceForEdge(edge));
|
|
4230
|
+
}
|
|
4113
4231
|
function detectMissingDivergences(graph, bucket) {
|
|
4114
4232
|
const out = [];
|
|
4115
4233
|
if (bucket.extracted && !bucket.observed) {
|
|
4116
4234
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
4117
|
-
const sourceHasTraffic = hasAnyObservedFromSource(graph, bucket.source);
|
|
4118
4235
|
out.push({
|
|
4119
4236
|
type: "missing-observed",
|
|
4120
4237
|
source: bucket.source,
|
|
4121
4238
|
target: bucket.target,
|
|
4122
4239
|
edgeType: bucket.type,
|
|
4123
4240
|
extracted: bucket.extracted,
|
|
4124
|
-
confidence:
|
|
4241
|
+
confidence: gradedConfidence(bucket.extracted),
|
|
4125
4242
|
reason: reasonForMissingObserved(bucket.source, bucket.target, bucket.type),
|
|
4126
4243
|
recommendation: RECOMMENDATION_MISSING_OBSERVED
|
|
4127
4244
|
});
|
|
4128
4245
|
}
|
|
4129
4246
|
}
|
|
4130
4247
|
if (bucket.observed && !bucket.extracted) {
|
|
4131
|
-
const cascaded = clampConfidence(confidenceForEdge(bucket.observed));
|
|
4132
4248
|
out.push({
|
|
4133
4249
|
type: "missing-extracted",
|
|
4134
4250
|
source: bucket.source,
|
|
4135
4251
|
target: bucket.target,
|
|
4136
4252
|
edgeType: bucket.type,
|
|
4137
4253
|
observed: bucket.observed,
|
|
4138
|
-
confidence:
|
|
4254
|
+
confidence: gradedConfidence(bucket.observed),
|
|
4139
4255
|
reason: reasonForMissingExtracted(bucket.source, bucket.target, bucket.type),
|
|
4140
4256
|
recommendation: RECOMMENDATION_MISSING_EXTRACTED
|
|
4141
4257
|
});
|
|
@@ -4154,7 +4270,7 @@ function declaredHostFor(svc) {
|
|
|
4154
4270
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
4155
4271
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4156
4272
|
const e = graph.getEdgeAttributes(edgeId);
|
|
4157
|
-
if (e.type === EdgeType9.CONFIGURED_BY && e.provenance ===
|
|
4273
|
+
if (e.type === EdgeType9.CONFIGURED_BY && e.provenance === Provenance10.EXTRACTED) {
|
|
4158
4274
|
return true;
|
|
4159
4275
|
}
|
|
4160
4276
|
}
|
|
@@ -4168,7 +4284,7 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
4168
4284
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4169
4285
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4170
4286
|
if (edge.type !== EdgeType9.CONNECTS_TO) continue;
|
|
4171
|
-
if (edge.provenance !==
|
|
4287
|
+
if (edge.provenance !== Provenance10.OBSERVED) continue;
|
|
4172
4288
|
const target = graph.getNodeAttributes(edge.target);
|
|
4173
4289
|
if (target.type !== NodeType10.DatabaseNode) continue;
|
|
4174
4290
|
const observedHost = target.host?.trim();
|
|
@@ -4193,7 +4309,7 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
4193
4309
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4194
4310
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4195
4311
|
if (edge.type !== EdgeType9.CONNECTS_TO) continue;
|
|
4196
|
-
if (edge.provenance !==
|
|
4312
|
+
if (edge.provenance !== Provenance10.OBSERVED) continue;
|
|
4197
4313
|
const target = graph.getNodeAttributes(edge.target);
|
|
4198
4314
|
if (target.type !== NodeType10.DatabaseNode) continue;
|
|
4199
4315
|
for (const pair of compatPairs()) {
|
|
@@ -4274,8 +4390,17 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4274
4390
|
const target = opts.node;
|
|
4275
4391
|
filtered = filtered.filter((d) => involvesNode(d, target));
|
|
4276
4392
|
}
|
|
4393
|
+
const TYPE_LEADERSHIP = {
|
|
4394
|
+
"missing-extracted": 0,
|
|
4395
|
+
"missing-observed": 1,
|
|
4396
|
+
"version-mismatch": 2,
|
|
4397
|
+
"host-mismatch": 3,
|
|
4398
|
+
"compat-violation": 4
|
|
4399
|
+
};
|
|
4277
4400
|
filtered.sort((a, b) => {
|
|
4278
4401
|
if (b.confidence !== a.confidence) return b.confidence - a.confidence;
|
|
4402
|
+
const lead = TYPE_LEADERSHIP[a.type] - TYPE_LEADERSHIP[b.type];
|
|
4403
|
+
if (lead !== 0) return lead;
|
|
4279
4404
|
if (a.type !== b.type) return a.type.localeCompare(b.type);
|
|
4280
4405
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
4281
4406
|
return a.target.localeCompare(b.target);
|
|
@@ -4795,6 +4920,7 @@ export {
|
|
|
4795
4920
|
readErrorEvents,
|
|
4796
4921
|
isStrictExtractionEnabled,
|
|
4797
4922
|
formatExtractionBanner,
|
|
4923
|
+
formatPrecisionFloorBanner,
|
|
4798
4924
|
discoverServices,
|
|
4799
4925
|
addServiceNodes,
|
|
4800
4926
|
addServiceAliases,
|
|
@@ -4826,4 +4952,4 @@ export {
|
|
|
4826
4952
|
removeProject,
|
|
4827
4953
|
buildApi
|
|
4828
4954
|
};
|
|
4829
|
-
//# sourceMappingURL=chunk-
|
|
4955
|
+
//# sourceMappingURL=chunk-BUB3ASD5.js.map
|