@neat.is/core 0.4.6 → 0.4.8
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-RBWL4HRB.js → chunk-J5CEKCTR.js} +337 -133
- package/dist/chunk-J5CEKCTR.js.map +1 -0
- package/dist/{chunk-NON5AXOR.js → chunk-RC3CIDZO.js} +2 -2
- package/dist/cli.cjs +501 -164
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +2 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +125 -19
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +324 -131
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +324 -131
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +307 -114
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-RBWL4HRB.js.map +0 -1
- /package/dist/{chunk-NON5AXOR.js.map → chunk-RC3CIDZO.js.map} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
mountBearerAuth
|
|
2
|
+
mountBearerAuth,
|
|
3
|
+
readAuthEnv
|
|
3
4
|
} from "./chunk-HVF4S7J3.js";
|
|
4
5
|
|
|
5
6
|
// src/graph.ts
|
|
@@ -341,6 +342,7 @@ function deprecatedApis() {
|
|
|
341
342
|
// src/traverse.ts
|
|
342
343
|
import {
|
|
343
344
|
BlastRadiusResultSchema,
|
|
345
|
+
EdgeType,
|
|
344
346
|
NodeType,
|
|
345
347
|
PROV_RANK,
|
|
346
348
|
RootCauseResultSchema,
|
|
@@ -353,6 +355,24 @@ function isFrontierNode(graph, nodeId) {
|
|
|
353
355
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
354
356
|
return attrs.type === NodeType.FrontierNode;
|
|
355
357
|
}
|
|
358
|
+
function resolveOwningService(graph, nodeId) {
|
|
359
|
+
if (!graph.hasNode(nodeId)) return null;
|
|
360
|
+
const attrs = graph.getNodeAttributes(nodeId);
|
|
361
|
+
if (attrs.type === NodeType.ServiceNode) {
|
|
362
|
+
return { id: nodeId, svc: attrs };
|
|
363
|
+
}
|
|
364
|
+
if (attrs.type === NodeType.FileNode) {
|
|
365
|
+
for (const edgeId of graph.inboundEdges(nodeId)) {
|
|
366
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
367
|
+
if (e.type !== EdgeType.CONTAINS) continue;
|
|
368
|
+
const owner = graph.getNodeAttributes(e.source);
|
|
369
|
+
if (owner.type === NodeType.ServiceNode) {
|
|
370
|
+
return { id: e.source, svc: owner };
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
356
376
|
function bestEdgeBySource(graph, edgeIds) {
|
|
357
377
|
const best = /* @__PURE__ */ new Map();
|
|
358
378
|
for (const id of edgeIds) {
|
|
@@ -459,9 +479,9 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
459
479
|
const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
|
|
460
480
|
if (candidatePairs.length === 0) return null;
|
|
461
481
|
for (const id of walk.path) {
|
|
462
|
-
const
|
|
463
|
-
if (
|
|
464
|
-
const svc =
|
|
482
|
+
const owner = resolveOwningService(graph, id);
|
|
483
|
+
if (!owner) continue;
|
|
484
|
+
const { id: serviceId3, svc } = owner;
|
|
465
485
|
const deps = svc.dependencies ?? {};
|
|
466
486
|
for (const pair of candidatePairs) {
|
|
467
487
|
const declared = deps[pair.driver];
|
|
@@ -474,7 +494,7 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
474
494
|
);
|
|
475
495
|
if (!result.compatible) {
|
|
476
496
|
return {
|
|
477
|
-
rootCauseNode:
|
|
497
|
+
rootCauseNode: serviceId3,
|
|
478
498
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
479
499
|
...result.minDriverVersion ? {
|
|
480
500
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -487,9 +507,9 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
487
507
|
}
|
|
488
508
|
function serviceRootCauseShape(graph, _origin, walk) {
|
|
489
509
|
for (const id of walk.path) {
|
|
490
|
-
const
|
|
491
|
-
if (
|
|
492
|
-
const svc =
|
|
510
|
+
const owner = resolveOwningService(graph, id);
|
|
511
|
+
if (!owner) continue;
|
|
512
|
+
const { id: serviceId3, svc } = owner;
|
|
493
513
|
const deps = svc.dependencies ?? {};
|
|
494
514
|
const serviceNodeEngine = svc.nodeEngine;
|
|
495
515
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -498,7 +518,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
498
518
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
499
519
|
if (!result.compatible && result.reason) {
|
|
500
520
|
return {
|
|
501
|
-
rootCauseNode:
|
|
521
|
+
rootCauseNode: serviceId3,
|
|
502
522
|
rootCauseReason: result.reason,
|
|
503
523
|
...result.requiredNodeVersion ? {
|
|
504
524
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -513,7 +533,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
513
533
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
514
534
|
if (!result.compatible && result.reason) {
|
|
515
535
|
return {
|
|
516
|
-
rootCauseNode:
|
|
536
|
+
rootCauseNode: serviceId3,
|
|
517
537
|
rootCauseReason: result.reason,
|
|
518
538
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
519
539
|
};
|
|
@@ -522,9 +542,15 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
522
542
|
}
|
|
523
543
|
return null;
|
|
524
544
|
}
|
|
545
|
+
function fileRootCauseShape(graph, origin, walk) {
|
|
546
|
+
const owner = resolveOwningService(graph, origin.id);
|
|
547
|
+
if (!owner) return null;
|
|
548
|
+
return serviceRootCauseShape(graph, owner.svc, walk);
|
|
549
|
+
}
|
|
525
550
|
var rootCauseShapes = {
|
|
526
551
|
[NodeType.DatabaseNode]: databaseRootCauseShape,
|
|
527
|
-
[NodeType.ServiceNode]: serviceRootCauseShape
|
|
552
|
+
[NodeType.ServiceNode]: serviceRootCauseShape,
|
|
553
|
+
[NodeType.FileNode]: fileRootCauseShape
|
|
528
554
|
};
|
|
529
555
|
function getRootCause(graph, errorNodeId, errorEvent) {
|
|
530
556
|
if (!graph.hasNode(errorNodeId)) return null;
|
|
@@ -636,7 +662,7 @@ import path3 from "path";
|
|
|
636
662
|
import { promises as fs2 } from "fs";
|
|
637
663
|
import path2 from "path";
|
|
638
664
|
import {
|
|
639
|
-
EdgeType,
|
|
665
|
+
EdgeType as EdgeType2,
|
|
640
666
|
NodeType as NodeType2,
|
|
641
667
|
PolicyFileSchema
|
|
642
668
|
} from "@neat.is/types";
|
|
@@ -858,7 +884,7 @@ var evaluateCompatibility = ({
|
|
|
858
884
|
if (wantsKind("driver-engine")) {
|
|
859
885
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
860
886
|
const e = graph.getEdgeAttributes(edgeId);
|
|
861
|
-
if (e.type !==
|
|
887
|
+
if (e.type !== EdgeType2.CONNECTS_TO) continue;
|
|
862
888
|
const dbAttrs = graph.getNodeAttributes(e.target);
|
|
863
889
|
if (dbAttrs.type === NodeType2.FrontierNode) continue;
|
|
864
890
|
if (dbAttrs.type !== NodeType2.DatabaseNode) continue;
|
|
@@ -1021,12 +1047,13 @@ var PolicyViolationsLog = class {
|
|
|
1021
1047
|
|
|
1022
1048
|
// src/ingest.ts
|
|
1023
1049
|
import {
|
|
1024
|
-
EdgeType as
|
|
1050
|
+
EdgeType as EdgeType3,
|
|
1025
1051
|
NodeType as NodeType3,
|
|
1026
1052
|
Provenance,
|
|
1027
1053
|
confidenceForObservedSignal,
|
|
1028
1054
|
databaseId,
|
|
1029
1055
|
extractedEdgeId,
|
|
1056
|
+
fileId,
|
|
1030
1057
|
frontierId,
|
|
1031
1058
|
inferredEdgeId,
|
|
1032
1059
|
observedEdgeId,
|
|
@@ -1094,6 +1121,86 @@ function hostFromUrl(u) {
|
|
|
1094
1121
|
function pickAddress(span) {
|
|
1095
1122
|
return pickAttr(span, "server.address", "net.peer.name", "net.host.name") ?? hostFromUrl(pickAttr(span, "url.full", "http.url"));
|
|
1096
1123
|
}
|
|
1124
|
+
var CODE_FILEPATH_ATTR = "code.filepath";
|
|
1125
|
+
var CODE_LINENO_ATTR = "code.lineno";
|
|
1126
|
+
var CODE_FUNCTION_ATTR = "code.function";
|
|
1127
|
+
function toPosix(p) {
|
|
1128
|
+
return p.split("\\").join("/");
|
|
1129
|
+
}
|
|
1130
|
+
function languageForExt(relPath) {
|
|
1131
|
+
const dot = relPath.lastIndexOf(".");
|
|
1132
|
+
if (dot === -1) return void 0;
|
|
1133
|
+
switch (relPath.slice(dot).toLowerCase()) {
|
|
1134
|
+
case ".py":
|
|
1135
|
+
return "python";
|
|
1136
|
+
case ".ts":
|
|
1137
|
+
case ".tsx":
|
|
1138
|
+
return "typescript";
|
|
1139
|
+
case ".js":
|
|
1140
|
+
case ".jsx":
|
|
1141
|
+
case ".mjs":
|
|
1142
|
+
case ".cjs":
|
|
1143
|
+
return "javascript";
|
|
1144
|
+
default:
|
|
1145
|
+
return void 0;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
function relPathForRuntimeFile(filepath, serviceNode) {
|
|
1149
|
+
let p = toPosix(filepath).replace(/^file:\/\//, "");
|
|
1150
|
+
const root = serviceNode?.repoPath;
|
|
1151
|
+
if (root && root !== "." && root.length > 0) {
|
|
1152
|
+
const rootPosix = toPosix(root);
|
|
1153
|
+
const anchor = `/${rootPosix}/`;
|
|
1154
|
+
const idx = p.lastIndexOf(anchor);
|
|
1155
|
+
if (idx !== -1) return p.slice(idx + anchor.length);
|
|
1156
|
+
const base = rootPosix.split("/").filter(Boolean).pop();
|
|
1157
|
+
if (base) {
|
|
1158
|
+
const baseAnchor = `/${base}/`;
|
|
1159
|
+
const bidx = p.lastIndexOf(baseAnchor);
|
|
1160
|
+
if (bidx !== -1) return p.slice(bidx + baseAnchor.length);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
p = p.replace(/^[A-Za-z]:/, "").replace(/^\/+/, "");
|
|
1164
|
+
return p.length > 0 ? p : null;
|
|
1165
|
+
}
|
|
1166
|
+
function callSiteFromSpan(span, serviceNode) {
|
|
1167
|
+
const filepath = span.attributes[CODE_FILEPATH_ATTR];
|
|
1168
|
+
if (typeof filepath !== "string" || filepath.length === 0) return null;
|
|
1169
|
+
const relPath = relPathForRuntimeFile(filepath, serviceNode);
|
|
1170
|
+
if (!relPath) return null;
|
|
1171
|
+
const linenoRaw = span.attributes[CODE_LINENO_ATTR];
|
|
1172
|
+
const line = typeof linenoRaw === "number" && Number.isFinite(linenoRaw) ? linenoRaw : void 0;
|
|
1173
|
+
const fnRaw = span.attributes[CODE_FUNCTION_ATTR];
|
|
1174
|
+
const fn = typeof fnRaw === "string" && fnRaw.length > 0 ? fnRaw : void 0;
|
|
1175
|
+
return { relPath, ...line !== void 0 ? { line } : {}, ...fn ? { fn } : {} };
|
|
1176
|
+
}
|
|
1177
|
+
function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
1178
|
+
const fileNodeId = fileId(serviceName, callSite.relPath);
|
|
1179
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
1180
|
+
const language = languageForExt(callSite.relPath);
|
|
1181
|
+
const node = {
|
|
1182
|
+
id: fileNodeId,
|
|
1183
|
+
type: NodeType3.FileNode,
|
|
1184
|
+
service: serviceName,
|
|
1185
|
+
path: callSite.relPath,
|
|
1186
|
+
...language ? { language } : {},
|
|
1187
|
+
discoveredVia: "otel"
|
|
1188
|
+
};
|
|
1189
|
+
graph.addNode(fileNodeId, node);
|
|
1190
|
+
}
|
|
1191
|
+
const containsId = makeObservedEdgeId(EdgeType3.CONTAINS, serviceNodeId, fileNodeId);
|
|
1192
|
+
if (!graph.hasEdge(containsId)) {
|
|
1193
|
+
const edge = {
|
|
1194
|
+
id: containsId,
|
|
1195
|
+
source: serviceNodeId,
|
|
1196
|
+
target: fileNodeId,
|
|
1197
|
+
type: EdgeType3.CONTAINS,
|
|
1198
|
+
provenance: Provenance.OBSERVED
|
|
1199
|
+
};
|
|
1200
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
1201
|
+
}
|
|
1202
|
+
return fileNodeId;
|
|
1203
|
+
}
|
|
1097
1204
|
function makeObservedEdgeId(type, source, target) {
|
|
1098
1205
|
return observedEdgeId(source, target, type);
|
|
1099
1206
|
}
|
|
@@ -1336,6 +1443,9 @@ async function handleSpan(ctx, span) {
|
|
|
1336
1443
|
const sourceId = ensureServiceNode(ctx.graph, span.service, env);
|
|
1337
1444
|
const isError = span.statusCode === 2;
|
|
1338
1445
|
cacheSpanService(span, nowMs);
|
|
1446
|
+
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
1447
|
+
const callSite = callSiteFromSpan(span, sourceServiceNode);
|
|
1448
|
+
const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
|
|
1339
1449
|
let affectedNode = sourceId;
|
|
1340
1450
|
if (span.dbSystem) {
|
|
1341
1451
|
const host = pickAddress(span);
|
|
@@ -1344,8 +1454,8 @@ async function handleSpan(ctx, span) {
|
|
|
1344
1454
|
const targetId = databaseId(host);
|
|
1345
1455
|
const result = upsertObservedEdge(
|
|
1346
1456
|
ctx.graph,
|
|
1347
|
-
|
|
1348
|
-
|
|
1457
|
+
EdgeType3.CONNECTS_TO,
|
|
1458
|
+
observedSource(),
|
|
1349
1459
|
targetId,
|
|
1350
1460
|
ts,
|
|
1351
1461
|
isError
|
|
@@ -1360,8 +1470,8 @@ async function handleSpan(ctx, span) {
|
|
|
1360
1470
|
if (targetId && targetId !== sourceId) {
|
|
1361
1471
|
upsertObservedEdge(
|
|
1362
1472
|
ctx.graph,
|
|
1363
|
-
|
|
1364
|
-
|
|
1473
|
+
EdgeType3.CALLS,
|
|
1474
|
+
observedSource(),
|
|
1365
1475
|
targetId,
|
|
1366
1476
|
ts,
|
|
1367
1477
|
isError
|
|
@@ -1372,8 +1482,8 @@ async function handleSpan(ctx, span) {
|
|
|
1372
1482
|
const frontierNodeId = ensureFrontierNode(ctx.graph, host, ts);
|
|
1373
1483
|
upsertObservedEdge(
|
|
1374
1484
|
ctx.graph,
|
|
1375
|
-
|
|
1376
|
-
|
|
1485
|
+
EdgeType3.CALLS,
|
|
1486
|
+
observedSource(),
|
|
1377
1487
|
frontierNodeId,
|
|
1378
1488
|
ts,
|
|
1379
1489
|
isError
|
|
@@ -1388,7 +1498,7 @@ async function handleSpan(ctx, span) {
|
|
|
1388
1498
|
const parentId = ensureServiceNode(ctx.graph, parent.service, parent.env);
|
|
1389
1499
|
upsertObservedEdge(
|
|
1390
1500
|
ctx.graph,
|
|
1391
|
-
|
|
1501
|
+
EdgeType3.CALLS,
|
|
1392
1502
|
parentId,
|
|
1393
1503
|
sourceId,
|
|
1394
1504
|
ts,
|
|
@@ -2334,7 +2444,7 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
2334
2444
|
// src/extract/databases/index.ts
|
|
2335
2445
|
import path17 from "path";
|
|
2336
2446
|
import {
|
|
2337
|
-
EdgeType as
|
|
2447
|
+
EdgeType as EdgeType4,
|
|
2338
2448
|
NodeType as NodeType6,
|
|
2339
2449
|
Provenance as Provenance2,
|
|
2340
2450
|
databaseId as databaseId2,
|
|
@@ -2909,10 +3019,10 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
2909
3019
|
});
|
|
2910
3020
|
}
|
|
2911
3021
|
const edge = {
|
|
2912
|
-
id: extractedEdgeId2(service.node.id, dbNode.id,
|
|
3022
|
+
id: extractedEdgeId2(service.node.id, dbNode.id, EdgeType4.CONNECTS_TO),
|
|
2913
3023
|
source: service.node.id,
|
|
2914
3024
|
target: dbNode.id,
|
|
2915
|
-
type:
|
|
3025
|
+
type: EdgeType4.CONNECTS_TO,
|
|
2916
3026
|
provenance: Provenance2.EXTRACTED,
|
|
2917
3027
|
confidence: confidenceForExtracted("structural"),
|
|
2918
3028
|
// ADR-032 / #140 — every EXTRACTED edge carries evidence.file.
|
|
@@ -2948,7 +3058,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
2948
3058
|
import { promises as fs12 } from "fs";
|
|
2949
3059
|
import path18 from "path";
|
|
2950
3060
|
import {
|
|
2951
|
-
EdgeType as
|
|
3061
|
+
EdgeType as EdgeType5,
|
|
2952
3062
|
NodeType as NodeType7,
|
|
2953
3063
|
Provenance as Provenance3,
|
|
2954
3064
|
configId,
|
|
@@ -2991,10 +3101,10 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
2991
3101
|
nodesAdded++;
|
|
2992
3102
|
}
|
|
2993
3103
|
const edge = {
|
|
2994
|
-
id: extractedEdgeId2(service.node.id, node.id,
|
|
3104
|
+
id: extractedEdgeId2(service.node.id, node.id, EdgeType5.CONFIGURED_BY),
|
|
2995
3105
|
source: service.node.id,
|
|
2996
3106
|
target: node.id,
|
|
2997
|
-
type:
|
|
3107
|
+
type: EdgeType5.CONFIGURED_BY,
|
|
2998
3108
|
provenance: Provenance3.EXTRACTED,
|
|
2999
3109
|
confidence: confidenceForExtracted2("structural"),
|
|
3000
3110
|
evidence: { file: relPath.split(path18.sep).join("/") }
|
|
@@ -3010,10 +3120,10 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3010
3120
|
|
|
3011
3121
|
// src/extract/calls/index.ts
|
|
3012
3122
|
import {
|
|
3013
|
-
EdgeType as
|
|
3014
|
-
NodeType as
|
|
3015
|
-
Provenance as
|
|
3016
|
-
confidenceForExtracted as
|
|
3123
|
+
EdgeType as EdgeType8,
|
|
3124
|
+
NodeType as NodeType9,
|
|
3125
|
+
Provenance as Provenance6,
|
|
3126
|
+
confidenceForExtracted as confidenceForExtracted5,
|
|
3017
3127
|
passesExtractedFloor as passesExtractedFloor2
|
|
3018
3128
|
} from "@neat.is/types";
|
|
3019
3129
|
|
|
@@ -3023,15 +3133,23 @@ import Parser from "tree-sitter";
|
|
|
3023
3133
|
import JavaScript from "tree-sitter-javascript";
|
|
3024
3134
|
import Python from "tree-sitter-python";
|
|
3025
3135
|
import {
|
|
3026
|
-
EdgeType as
|
|
3027
|
-
Provenance as
|
|
3028
|
-
confidenceForExtracted as
|
|
3136
|
+
EdgeType as EdgeType7,
|
|
3137
|
+
Provenance as Provenance5,
|
|
3138
|
+
confidenceForExtracted as confidenceForExtracted4,
|
|
3029
3139
|
passesExtractedFloor
|
|
3030
3140
|
} from "@neat.is/types";
|
|
3031
3141
|
|
|
3032
3142
|
// src/extract/calls/shared.ts
|
|
3033
3143
|
import { promises as fs13 } from "fs";
|
|
3034
3144
|
import path19 from "path";
|
|
3145
|
+
import {
|
|
3146
|
+
EdgeType as EdgeType6,
|
|
3147
|
+
NodeType as NodeType8,
|
|
3148
|
+
Provenance as Provenance4,
|
|
3149
|
+
confidenceForExtracted as confidenceForExtracted3,
|
|
3150
|
+
extractedEdgeId as extractedEdgeId3,
|
|
3151
|
+
fileId as fileId2
|
|
3152
|
+
} from "@neat.is/types";
|
|
3035
3153
|
async function walkSourceFiles(dir) {
|
|
3036
3154
|
const out = [];
|
|
3037
3155
|
async function walk(current) {
|
|
@@ -3071,6 +3189,58 @@ function snippet(text, line) {
|
|
|
3071
3189
|
const lines = text.split("\n");
|
|
3072
3190
|
return (lines[line - 1] ?? "").trim();
|
|
3073
3191
|
}
|
|
3192
|
+
function toPosix2(p) {
|
|
3193
|
+
return p.split("\\").join("/");
|
|
3194
|
+
}
|
|
3195
|
+
function languageForPath(relPath) {
|
|
3196
|
+
switch (path19.extname(relPath).toLowerCase()) {
|
|
3197
|
+
case ".py":
|
|
3198
|
+
return "python";
|
|
3199
|
+
case ".ts":
|
|
3200
|
+
case ".tsx":
|
|
3201
|
+
return "typescript";
|
|
3202
|
+
case ".js":
|
|
3203
|
+
case ".jsx":
|
|
3204
|
+
case ".mjs":
|
|
3205
|
+
case ".cjs":
|
|
3206
|
+
return "javascript";
|
|
3207
|
+
default:
|
|
3208
|
+
return void 0;
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3212
|
+
let nodesAdded = 0;
|
|
3213
|
+
let edgesAdded = 0;
|
|
3214
|
+
const fileNodeId = fileId2(serviceName, relPath);
|
|
3215
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
3216
|
+
const language = languageForPath(relPath);
|
|
3217
|
+
const node = {
|
|
3218
|
+
id: fileNodeId,
|
|
3219
|
+
type: NodeType8.FileNode,
|
|
3220
|
+
service: serviceName,
|
|
3221
|
+
path: relPath,
|
|
3222
|
+
...language ? { language } : {},
|
|
3223
|
+
discoveredVia: "static"
|
|
3224
|
+
};
|
|
3225
|
+
graph.addNode(fileNodeId, node);
|
|
3226
|
+
nodesAdded++;
|
|
3227
|
+
}
|
|
3228
|
+
const containsId = extractedEdgeId3(serviceNodeId, fileNodeId, EdgeType6.CONTAINS);
|
|
3229
|
+
if (!graph.hasEdge(containsId)) {
|
|
3230
|
+
const edge = {
|
|
3231
|
+
id: containsId,
|
|
3232
|
+
source: serviceNodeId,
|
|
3233
|
+
target: fileNodeId,
|
|
3234
|
+
type: EdgeType6.CONTAINS,
|
|
3235
|
+
provenance: Provenance4.EXTRACTED,
|
|
3236
|
+
confidence: confidenceForExtracted3("structural"),
|
|
3237
|
+
evidence: { file: relPath }
|
|
3238
|
+
};
|
|
3239
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3240
|
+
edgesAdded++;
|
|
3241
|
+
}
|
|
3242
|
+
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3243
|
+
}
|
|
3074
3244
|
|
|
3075
3245
|
// src/extract/calls/http.ts
|
|
3076
3246
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
@@ -3104,16 +3274,16 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
3104
3274
|
const tree = parser.parse(source);
|
|
3105
3275
|
const literals = [];
|
|
3106
3276
|
collectStringLiterals(tree.rootNode, literals);
|
|
3107
|
-
const
|
|
3277
|
+
const out = [];
|
|
3108
3278
|
for (const lit of literals) {
|
|
3109
3279
|
if (isInsideJsxExternalLink(lit.node)) continue;
|
|
3110
3280
|
for (const host of knownHosts) {
|
|
3111
3281
|
if (urlMatchesHost(lit.text, host)) {
|
|
3112
|
-
|
|
3282
|
+
out.push({ host, line: lit.node.startPosition.row + 1 });
|
|
3113
3283
|
}
|
|
3114
3284
|
}
|
|
3115
3285
|
}
|
|
3116
|
-
return
|
|
3286
|
+
return out;
|
|
3117
3287
|
}
|
|
3118
3288
|
function makeJsParser() {
|
|
3119
3289
|
const p = new Parser();
|
|
@@ -3136,65 +3306,72 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3136
3306
|
hostToNodeId.set(path20.basename(service.dir), service.node.id);
|
|
3137
3307
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
3138
3308
|
}
|
|
3309
|
+
let nodesAdded = 0;
|
|
3139
3310
|
let edgesAdded = 0;
|
|
3140
3311
|
for (const service of services) {
|
|
3141
3312
|
const files = await loadSourceFiles(service.dir);
|
|
3142
|
-
const
|
|
3313
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3143
3314
|
for (const file of files) {
|
|
3144
3315
|
if (isTestPath(file.path)) continue;
|
|
3145
3316
|
const parser = path20.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
3146
|
-
let
|
|
3317
|
+
let sites;
|
|
3147
3318
|
try {
|
|
3148
|
-
|
|
3319
|
+
sites = callsFromSource(file.content, parser, knownHosts);
|
|
3149
3320
|
} catch (err) {
|
|
3150
3321
|
recordExtractionError("http call extraction", file.path, err);
|
|
3151
3322
|
continue;
|
|
3152
3323
|
}
|
|
3153
|
-
|
|
3154
|
-
|
|
3324
|
+
if (sites.length === 0) continue;
|
|
3325
|
+
const relFile = toPosix2(path20.relative(service.dir, file.path));
|
|
3326
|
+
for (const site of sites) {
|
|
3327
|
+
const targetId = hostToNodeId.get(site.host);
|
|
3155
3328
|
if (!targetId || targetId === service.node.id) continue;
|
|
3156
|
-
|
|
3157
|
-
|
|
3329
|
+
const dedupKey = `${relFile}|${targetId}`;
|
|
3330
|
+
if (seen.has(dedupKey)) continue;
|
|
3331
|
+
seen.add(dedupKey);
|
|
3332
|
+
const confidence = confidenceForExtracted4("hostname-shape-match");
|
|
3333
|
+
const ev = {
|
|
3334
|
+
file: relFile,
|
|
3335
|
+
line: site.line,
|
|
3336
|
+
snippet: snippet(file.content, site.line)
|
|
3337
|
+
};
|
|
3338
|
+
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
3339
|
+
graph,
|
|
3340
|
+
service.pkg.name,
|
|
3341
|
+
service.node.id,
|
|
3342
|
+
relFile
|
|
3343
|
+
);
|
|
3344
|
+
nodesAdded += n;
|
|
3345
|
+
edgesAdded += e;
|
|
3346
|
+
if (!passesExtractedFloor(confidence)) {
|
|
3347
|
+
noteExtractedDropped({
|
|
3348
|
+
source: fileNodeId,
|
|
3349
|
+
target: targetId,
|
|
3350
|
+
type: EdgeType7.CALLS,
|
|
3351
|
+
confidence,
|
|
3352
|
+
confidenceKind: "hostname-shape-match",
|
|
3353
|
+
evidence: ev
|
|
3354
|
+
});
|
|
3355
|
+
continue;
|
|
3356
|
+
}
|
|
3357
|
+
const edgeId = extractedEdgeId2(fileNodeId, targetId, EdgeType7.CALLS);
|
|
3358
|
+
if (!graph.hasEdge(edgeId)) {
|
|
3359
|
+
const edge = {
|
|
3360
|
+
id: edgeId,
|
|
3361
|
+
source: fileNodeId,
|
|
3362
|
+
target: targetId,
|
|
3363
|
+
type: EdgeType7.CALLS,
|
|
3364
|
+
provenance: Provenance5.EXTRACTED,
|
|
3365
|
+
confidence,
|
|
3366
|
+
evidence: ev
|
|
3367
|
+
};
|
|
3368
|
+
graph.addEdgeWithKey(edgeId, fileNodeId, targetId, edge);
|
|
3369
|
+
edgesAdded++;
|
|
3158
3370
|
}
|
|
3159
|
-
}
|
|
3160
|
-
}
|
|
3161
|
-
for (const [targetId, evidenceFile] of seenTargets) {
|
|
3162
|
-
const fileContent = files.find((f) => f.path === evidenceFile.file)?.content ?? "";
|
|
3163
|
-
const line = lineOf(fileContent, `//${evidenceFile.host}`);
|
|
3164
|
-
const confidence = confidenceForExtracted3("hostname-shape-match");
|
|
3165
|
-
const ev = {
|
|
3166
|
-
file: path20.relative(service.dir, evidenceFile.file),
|
|
3167
|
-
line,
|
|
3168
|
-
snippet: snippet(fileContent, line)
|
|
3169
|
-
};
|
|
3170
|
-
const edgeId = extractedEdgeId2(service.node.id, targetId, EdgeType5.CALLS);
|
|
3171
|
-
if (!passesExtractedFloor(confidence)) {
|
|
3172
|
-
noteExtractedDropped({
|
|
3173
|
-
source: service.node.id,
|
|
3174
|
-
target: targetId,
|
|
3175
|
-
type: EdgeType5.CALLS,
|
|
3176
|
-
confidence,
|
|
3177
|
-
confidenceKind: "hostname-shape-match",
|
|
3178
|
-
evidence: ev
|
|
3179
|
-
});
|
|
3180
|
-
continue;
|
|
3181
|
-
}
|
|
3182
|
-
const edge = {
|
|
3183
|
-
id: edgeId,
|
|
3184
|
-
source: service.node.id,
|
|
3185
|
-
target: targetId,
|
|
3186
|
-
type: EdgeType5.CALLS,
|
|
3187
|
-
provenance: Provenance4.EXTRACTED,
|
|
3188
|
-
confidence,
|
|
3189
|
-
evidence: ev
|
|
3190
|
-
};
|
|
3191
|
-
if (!graph.hasEdge(edge.id)) {
|
|
3192
|
-
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
3193
|
-
edgesAdded++;
|
|
3194
3371
|
}
|
|
3195
3372
|
}
|
|
3196
3373
|
}
|
|
3197
|
-
return edgesAdded;
|
|
3374
|
+
return { nodesAdded, edgesAdded };
|
|
3198
3375
|
}
|
|
3199
3376
|
|
|
3200
3377
|
// src/extract/calls/kafka.ts
|
|
@@ -3403,11 +3580,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3403
3580
|
function edgeTypeFromEndpoint(ep) {
|
|
3404
3581
|
switch (ep.edgeType) {
|
|
3405
3582
|
case "PUBLISHES_TO":
|
|
3406
|
-
return
|
|
3583
|
+
return EdgeType8.PUBLISHES_TO;
|
|
3407
3584
|
case "CONSUMES_FROM":
|
|
3408
|
-
return
|
|
3585
|
+
return EdgeType8.CONSUMES_FROM;
|
|
3409
3586
|
default:
|
|
3410
|
-
return
|
|
3587
|
+
return EdgeType8.CALLS;
|
|
3411
3588
|
}
|
|
3412
3589
|
}
|
|
3413
3590
|
function isAwsKind(kind) {
|
|
@@ -3434,7 +3611,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3434
3611
|
if (!graph.hasNode(ep.infraId)) {
|
|
3435
3612
|
const node = {
|
|
3436
3613
|
id: ep.infraId,
|
|
3437
|
-
type:
|
|
3614
|
+
type: NodeType9.InfraNode,
|
|
3438
3615
|
name: ep.name,
|
|
3439
3616
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
3440
3617
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -3446,13 +3623,19 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3446
3623
|
nodesAdded++;
|
|
3447
3624
|
}
|
|
3448
3625
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
3449
|
-
const
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3626
|
+
const confidence = confidenceForExtracted5(ep.confidenceKind);
|
|
3627
|
+
const relFile = toPosix2(ep.evidence.file);
|
|
3628
|
+
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
3629
|
+
graph,
|
|
3630
|
+
service.pkg.name,
|
|
3631
|
+
service.node.id,
|
|
3632
|
+
relFile
|
|
3633
|
+
);
|
|
3634
|
+
nodesAdded += n;
|
|
3635
|
+
edgesAdded += e;
|
|
3453
3636
|
if (!passesExtractedFloor2(confidence)) {
|
|
3454
3637
|
noteExtractedDropped({
|
|
3455
|
-
source:
|
|
3638
|
+
source: fileNodeId,
|
|
3456
3639
|
target: ep.infraId,
|
|
3457
3640
|
type: edgeType,
|
|
3458
3641
|
confidence,
|
|
@@ -3461,13 +3644,16 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3461
3644
|
});
|
|
3462
3645
|
continue;
|
|
3463
3646
|
}
|
|
3647
|
+
const edgeId = extractedEdgeId2(fileNodeId, ep.infraId, edgeType);
|
|
3648
|
+
if (seenEdges.has(edgeId)) continue;
|
|
3649
|
+
seenEdges.add(edgeId);
|
|
3464
3650
|
if (!graph.hasEdge(edgeId)) {
|
|
3465
3651
|
const edge = {
|
|
3466
3652
|
id: edgeId,
|
|
3467
|
-
source:
|
|
3653
|
+
source: fileNodeId,
|
|
3468
3654
|
target: ep.infraId,
|
|
3469
3655
|
type: edgeType,
|
|
3470
|
-
provenance:
|
|
3656
|
+
provenance: Provenance6.EXTRACTED,
|
|
3471
3657
|
confidence,
|
|
3472
3658
|
evidence: ep.evidence
|
|
3473
3659
|
};
|
|
@@ -3479,24 +3665,24 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3479
3665
|
return { nodesAdded, edgesAdded };
|
|
3480
3666
|
}
|
|
3481
3667
|
async function addCallEdges(graph, services) {
|
|
3482
|
-
const
|
|
3668
|
+
const http = await addHttpCallEdges(graph, services);
|
|
3483
3669
|
const ext = await addExternalEndpointEdges(graph, services);
|
|
3484
3670
|
return {
|
|
3485
|
-
nodesAdded: ext.nodesAdded,
|
|
3486
|
-
edgesAdded:
|
|
3671
|
+
nodesAdded: http.nodesAdded + ext.nodesAdded,
|
|
3672
|
+
edgesAdded: http.edgesAdded + ext.edgesAdded
|
|
3487
3673
|
};
|
|
3488
3674
|
}
|
|
3489
3675
|
|
|
3490
3676
|
// src/extract/infra/docker-compose.ts
|
|
3491
3677
|
import path25 from "path";
|
|
3492
|
-
import { EdgeType as
|
|
3678
|
+
import { EdgeType as EdgeType9, Provenance as Provenance7, confidenceForExtracted as confidenceForExtracted6 } from "@neat.is/types";
|
|
3493
3679
|
|
|
3494
3680
|
// src/extract/infra/shared.ts
|
|
3495
|
-
import { NodeType as
|
|
3681
|
+
import { NodeType as NodeType10, infraId as infraId5 } from "@neat.is/types";
|
|
3496
3682
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
3497
3683
|
return {
|
|
3498
3684
|
id: infraId5(kind, name),
|
|
3499
|
-
type:
|
|
3685
|
+
type: NodeType10.InfraNode,
|
|
3500
3686
|
name,
|
|
3501
3687
|
provider,
|
|
3502
3688
|
kind,
|
|
@@ -3575,15 +3761,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3575
3761
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
3576
3762
|
const targetId = composeNameToNodeId.get(dep);
|
|
3577
3763
|
if (!targetId) continue;
|
|
3578
|
-
const edgeId = extractedEdgeId2(sourceId, targetId,
|
|
3764
|
+
const edgeId = extractedEdgeId2(sourceId, targetId, EdgeType9.DEPENDS_ON);
|
|
3579
3765
|
if (graph.hasEdge(edgeId)) continue;
|
|
3580
3766
|
const edge = {
|
|
3581
3767
|
id: edgeId,
|
|
3582
3768
|
source: sourceId,
|
|
3583
3769
|
target: targetId,
|
|
3584
|
-
type:
|
|
3585
|
-
provenance:
|
|
3586
|
-
confidence:
|
|
3770
|
+
type: EdgeType9.DEPENDS_ON,
|
|
3771
|
+
provenance: Provenance7.EXTRACTED,
|
|
3772
|
+
confidence: confidenceForExtracted6("structural"),
|
|
3587
3773
|
evidence: { file: evidenceFile }
|
|
3588
3774
|
};
|
|
3589
3775
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3596,7 +3782,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3596
3782
|
// src/extract/infra/dockerfile.ts
|
|
3597
3783
|
import path26 from "path";
|
|
3598
3784
|
import { promises as fs14 } from "fs";
|
|
3599
|
-
import { EdgeType as
|
|
3785
|
+
import { EdgeType as EdgeType10, Provenance as Provenance8, confidenceForExtracted as confidenceForExtracted7 } from "@neat.is/types";
|
|
3600
3786
|
function runtimeImage(content) {
|
|
3601
3787
|
const lines = content.split("\n");
|
|
3602
3788
|
let last = null;
|
|
@@ -3635,15 +3821,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3635
3821
|
graph.addNode(node.id, node);
|
|
3636
3822
|
nodesAdded++;
|
|
3637
3823
|
}
|
|
3638
|
-
const edgeId = extractedEdgeId2(service.node.id, node.id,
|
|
3824
|
+
const edgeId = extractedEdgeId2(service.node.id, node.id, EdgeType10.RUNS_ON);
|
|
3639
3825
|
if (!graph.hasEdge(edgeId)) {
|
|
3640
3826
|
const edge = {
|
|
3641
3827
|
id: edgeId,
|
|
3642
3828
|
source: service.node.id,
|
|
3643
3829
|
target: node.id,
|
|
3644
|
-
type:
|
|
3645
|
-
provenance:
|
|
3646
|
-
confidence:
|
|
3830
|
+
type: EdgeType10.RUNS_ON,
|
|
3831
|
+
provenance: Provenance8.EXTRACTED,
|
|
3832
|
+
confidence: confidenceForExtracted7("structural"),
|
|
3647
3833
|
evidence: {
|
|
3648
3834
|
file: path26.relative(scanPath, dockerfilePath).split(path26.sep).join("/")
|
|
3649
3835
|
}
|
|
@@ -3768,17 +3954,29 @@ import path30 from "path";
|
|
|
3768
3954
|
// src/extract/retire.ts
|
|
3769
3955
|
import { existsSync } from "fs";
|
|
3770
3956
|
import path29 from "path";
|
|
3771
|
-
import { Provenance as
|
|
3957
|
+
import { NodeType as NodeType11, Provenance as Provenance9 } from "@neat.is/types";
|
|
3958
|
+
function dropOrphanedFileNodes(graph) {
|
|
3959
|
+
const orphans = [];
|
|
3960
|
+
graph.forEachNode((id, attrs) => {
|
|
3961
|
+
if (attrs.type !== NodeType11.FileNode) return;
|
|
3962
|
+
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
3963
|
+
orphans.push(id);
|
|
3964
|
+
}
|
|
3965
|
+
});
|
|
3966
|
+
for (const id of orphans) graph.dropNode(id);
|
|
3967
|
+
return orphans.length;
|
|
3968
|
+
}
|
|
3772
3969
|
function retireEdgesByFile(graph, file) {
|
|
3773
3970
|
const normalized = file.split("\\").join("/");
|
|
3774
3971
|
const toDrop = [];
|
|
3775
3972
|
graph.forEachEdge((id, attrs) => {
|
|
3776
3973
|
const edge = attrs;
|
|
3777
|
-
if (edge.provenance !==
|
|
3974
|
+
if (edge.provenance !== Provenance9.EXTRACTED) return;
|
|
3778
3975
|
if (!edge.evidence?.file) return;
|
|
3779
3976
|
if (edge.evidence.file === normalized) toDrop.push(id);
|
|
3780
3977
|
});
|
|
3781
3978
|
for (const id of toDrop) graph.dropEdge(id);
|
|
3979
|
+
dropOrphanedFileNodes(graph);
|
|
3782
3980
|
return toDrop.length;
|
|
3783
3981
|
}
|
|
3784
3982
|
function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
@@ -3786,7 +3984,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
3786
3984
|
const bases = [scanPath, ...serviceDirs];
|
|
3787
3985
|
graph.forEachEdge((id, attrs) => {
|
|
3788
3986
|
const edge = attrs;
|
|
3789
|
-
if (edge.provenance !==
|
|
3987
|
+
if (edge.provenance !== Provenance9.EXTRACTED) return;
|
|
3790
3988
|
const evidenceFile = edge.evidence?.file;
|
|
3791
3989
|
if (!evidenceFile) return;
|
|
3792
3990
|
if (path29.isAbsolute(evidenceFile)) {
|
|
@@ -3797,6 +3995,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
3797
3995
|
if (!found) toDrop.push(id);
|
|
3798
3996
|
});
|
|
3799
3997
|
for (const id of toDrop) graph.dropEdge(id);
|
|
3998
|
+
dropOrphanedFileNodes(graph);
|
|
3800
3999
|
return toDrop.length;
|
|
3801
4000
|
}
|
|
3802
4001
|
|
|
@@ -3865,10 +4064,10 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
3865
4064
|
// src/divergences.ts
|
|
3866
4065
|
import {
|
|
3867
4066
|
DivergenceResultSchema,
|
|
3868
|
-
EdgeType as
|
|
3869
|
-
NodeType as
|
|
4067
|
+
EdgeType as EdgeType11,
|
|
4068
|
+
NodeType as NodeType12,
|
|
3870
4069
|
parseEdgeId,
|
|
3871
|
-
Provenance as
|
|
4070
|
+
Provenance as Provenance10
|
|
3872
4071
|
} from "@neat.is/types";
|
|
3873
4072
|
function bucketKey(source, target, type) {
|
|
3874
4073
|
return `${type}|${source}|${target}`;
|
|
@@ -3882,17 +4081,17 @@ function bucketEdges(graph) {
|
|
|
3882
4081
|
const key = bucketKey(e.source, e.target, e.type);
|
|
3883
4082
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
3884
4083
|
switch (provenance) {
|
|
3885
|
-
case
|
|
4084
|
+
case Provenance10.EXTRACTED:
|
|
3886
4085
|
cur.extracted = e;
|
|
3887
4086
|
break;
|
|
3888
|
-
case
|
|
4087
|
+
case Provenance10.OBSERVED:
|
|
3889
4088
|
cur.observed = e;
|
|
3890
4089
|
break;
|
|
3891
|
-
case
|
|
4090
|
+
case Provenance10.INFERRED:
|
|
3892
4091
|
cur.inferred = e;
|
|
3893
4092
|
break;
|
|
3894
4093
|
default:
|
|
3895
|
-
if (e.provenance ===
|
|
4094
|
+
if (e.provenance === Provenance10.STALE) cur.stale = e;
|
|
3896
4095
|
}
|
|
3897
4096
|
buckets.set(key, cur);
|
|
3898
4097
|
});
|
|
@@ -3901,7 +4100,7 @@ function bucketEdges(graph) {
|
|
|
3901
4100
|
function nodeIsFrontier(graph, nodeId) {
|
|
3902
4101
|
if (!graph.hasNode(nodeId)) return false;
|
|
3903
4102
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
3904
|
-
return attrs.type ===
|
|
4103
|
+
return attrs.type === NodeType12.FrontierNode;
|
|
3905
4104
|
}
|
|
3906
4105
|
function clampConfidence(n) {
|
|
3907
4106
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -3922,6 +4121,7 @@ function gradedConfidence(edge) {
|
|
|
3922
4121
|
}
|
|
3923
4122
|
function detectMissingDivergences(graph, bucket) {
|
|
3924
4123
|
const out = [];
|
|
4124
|
+
if (bucket.type === EdgeType11.CONTAINS) return out;
|
|
3925
4125
|
if (bucket.extracted && !bucket.observed) {
|
|
3926
4126
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
3927
4127
|
out.push({
|
|
@@ -3962,7 +4162,7 @@ function declaredHostFor(svc) {
|
|
|
3962
4162
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
3963
4163
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
3964
4164
|
const e = graph.getEdgeAttributes(edgeId);
|
|
3965
|
-
if (e.type ===
|
|
4165
|
+
if (e.type === EdgeType11.CONFIGURED_BY && e.provenance === Provenance10.EXTRACTED) {
|
|
3966
4166
|
return true;
|
|
3967
4167
|
}
|
|
3968
4168
|
}
|
|
@@ -3975,10 +4175,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
3975
4175
|
const out = [];
|
|
3976
4176
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
3977
4177
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
3978
|
-
if (edge.type !==
|
|
3979
|
-
if (edge.provenance !==
|
|
4178
|
+
if (edge.type !== EdgeType11.CONNECTS_TO) continue;
|
|
4179
|
+
if (edge.provenance !== Provenance10.OBSERVED) continue;
|
|
3980
4180
|
const target = graph.getNodeAttributes(edge.target);
|
|
3981
|
-
if (target.type !==
|
|
4181
|
+
if (target.type !== NodeType12.DatabaseNode) continue;
|
|
3982
4182
|
const observedHost = target.host?.trim();
|
|
3983
4183
|
if (!observedHost) continue;
|
|
3984
4184
|
if (observedHost === declaredHost) continue;
|
|
@@ -4000,10 +4200,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
4000
4200
|
const deps = svc.dependencies ?? {};
|
|
4001
4201
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4002
4202
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4003
|
-
if (edge.type !==
|
|
4004
|
-
if (edge.provenance !==
|
|
4203
|
+
if (edge.type !== EdgeType11.CONNECTS_TO) continue;
|
|
4204
|
+
if (edge.provenance !== Provenance10.OBSERVED) continue;
|
|
4005
4205
|
const target = graph.getNodeAttributes(edge.target);
|
|
4006
|
-
if (target.type !==
|
|
4206
|
+
if (target.type !== NodeType12.DatabaseNode) continue;
|
|
4007
4207
|
for (const pair of compatPairs()) {
|
|
4008
4208
|
if (pair.engine !== target.engine) continue;
|
|
4009
4209
|
const declared = deps[pair.driver];
|
|
@@ -4064,7 +4264,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4064
4264
|
}
|
|
4065
4265
|
graph.forEachNode((nodeId, attrs) => {
|
|
4066
4266
|
const n = attrs;
|
|
4067
|
-
if (n.type !==
|
|
4267
|
+
if (n.type !== NodeType12.ServiceNode) return;
|
|
4068
4268
|
const svc = n;
|
|
4069
4269
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
4070
4270
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -4107,7 +4307,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4107
4307
|
// src/persist.ts
|
|
4108
4308
|
import { promises as fs17 } from "fs";
|
|
4109
4309
|
import path31 from "path";
|
|
4110
|
-
import { Provenance as
|
|
4310
|
+
import { Provenance as Provenance11, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
|
|
4111
4311
|
var SCHEMA_VERSION = 4;
|
|
4112
4312
|
function migrateV1ToV2(payload) {
|
|
4113
4313
|
const nodes = payload.graph.nodes;
|
|
@@ -4129,7 +4329,7 @@ function migrateV2ToV3(payload) {
|
|
|
4129
4329
|
for (const edge of edges) {
|
|
4130
4330
|
const attrs = edge.attributes;
|
|
4131
4331
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
4132
|
-
attrs.provenance =
|
|
4332
|
+
attrs.provenance = Provenance11.OBSERVED;
|
|
4133
4333
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
4134
4334
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
4135
4335
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
@@ -4981,14 +5181,18 @@ function registerRoutes(scope, ctx) {
|
|
|
4981
5181
|
async function buildApi(opts) {
|
|
4982
5182
|
const app = Fastify({ logger: false });
|
|
4983
5183
|
await app.register(cors, { origin: true });
|
|
5184
|
+
const env = readAuthEnv();
|
|
5185
|
+
const authToken = opts.authToken ?? env.authToken;
|
|
5186
|
+
const trustProxy = opts.trustProxy ?? env.trustProxy;
|
|
5187
|
+
const publicRead = opts.publicRead ?? env.publicRead;
|
|
4984
5188
|
mountBearerAuth(app, {
|
|
4985
|
-
token:
|
|
4986
|
-
trustProxy
|
|
4987
|
-
publicRead
|
|
5189
|
+
token: authToken,
|
|
5190
|
+
trustProxy,
|
|
5191
|
+
publicRead
|
|
4988
5192
|
});
|
|
4989
5193
|
app.get("/api/config", async () => ({
|
|
4990
|
-
publicRead:
|
|
4991
|
-
authProxy:
|
|
5194
|
+
publicRead: publicRead === true,
|
|
5195
|
+
authProxy: trustProxy === true
|
|
4992
5196
|
}));
|
|
4993
5197
|
const startedAt = opts.startedAt ?? Date.now();
|
|
4994
5198
|
const registry = buildLegacyRegistry(opts);
|
|
@@ -5137,4 +5341,4 @@ export {
|
|
|
5137
5341
|
removeProject,
|
|
5138
5342
|
buildApi
|
|
5139
5343
|
};
|
|
5140
|
-
//# sourceMappingURL=chunk-
|
|
5344
|
+
//# sourceMappingURL=chunk-J5CEKCTR.js.map
|