@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
package/dist/neatd.cjs
CHANGED
|
@@ -973,6 +973,24 @@ function isFrontierNode(graph, nodeId) {
|
|
|
973
973
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
974
974
|
return attrs.type === import_types.NodeType.FrontierNode;
|
|
975
975
|
}
|
|
976
|
+
function resolveOwningService(graph, nodeId) {
|
|
977
|
+
if (!graph.hasNode(nodeId)) return null;
|
|
978
|
+
const attrs = graph.getNodeAttributes(nodeId);
|
|
979
|
+
if (attrs.type === import_types.NodeType.ServiceNode) {
|
|
980
|
+
return { id: nodeId, svc: attrs };
|
|
981
|
+
}
|
|
982
|
+
if (attrs.type === import_types.NodeType.FileNode) {
|
|
983
|
+
for (const edgeId of graph.inboundEdges(nodeId)) {
|
|
984
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
985
|
+
if (e.type !== import_types.EdgeType.CONTAINS) continue;
|
|
986
|
+
const owner = graph.getNodeAttributes(e.source);
|
|
987
|
+
if (owner.type === import_types.NodeType.ServiceNode) {
|
|
988
|
+
return { id: e.source, svc: owner };
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
return null;
|
|
993
|
+
}
|
|
976
994
|
function bestEdgeBySource(graph, edgeIds) {
|
|
977
995
|
const best = /* @__PURE__ */ new Map();
|
|
978
996
|
for (const id of edgeIds) {
|
|
@@ -1079,9 +1097,9 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1079
1097
|
const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
|
|
1080
1098
|
if (candidatePairs.length === 0) return null;
|
|
1081
1099
|
for (const id of walk.path) {
|
|
1082
|
-
const
|
|
1083
|
-
if (
|
|
1084
|
-
const svc =
|
|
1100
|
+
const owner = resolveOwningService(graph, id);
|
|
1101
|
+
if (!owner) continue;
|
|
1102
|
+
const { id: serviceId3, svc } = owner;
|
|
1085
1103
|
const deps = svc.dependencies ?? {};
|
|
1086
1104
|
for (const pair of candidatePairs) {
|
|
1087
1105
|
const declared = deps[pair.driver];
|
|
@@ -1094,7 +1112,7 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1094
1112
|
);
|
|
1095
1113
|
if (!result.compatible) {
|
|
1096
1114
|
return {
|
|
1097
|
-
rootCauseNode:
|
|
1115
|
+
rootCauseNode: serviceId3,
|
|
1098
1116
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
1099
1117
|
...result.minDriverVersion ? {
|
|
1100
1118
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -1107,9 +1125,9 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1107
1125
|
}
|
|
1108
1126
|
function serviceRootCauseShape(graph, _origin, walk) {
|
|
1109
1127
|
for (const id of walk.path) {
|
|
1110
|
-
const
|
|
1111
|
-
if (
|
|
1112
|
-
const svc =
|
|
1128
|
+
const owner = resolveOwningService(graph, id);
|
|
1129
|
+
if (!owner) continue;
|
|
1130
|
+
const { id: serviceId3, svc } = owner;
|
|
1113
1131
|
const deps = svc.dependencies ?? {};
|
|
1114
1132
|
const serviceNodeEngine = svc.nodeEngine;
|
|
1115
1133
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -1118,7 +1136,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1118
1136
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
1119
1137
|
if (!result.compatible && result.reason) {
|
|
1120
1138
|
return {
|
|
1121
|
-
rootCauseNode:
|
|
1139
|
+
rootCauseNode: serviceId3,
|
|
1122
1140
|
rootCauseReason: result.reason,
|
|
1123
1141
|
...result.requiredNodeVersion ? {
|
|
1124
1142
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -1133,7 +1151,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1133
1151
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
1134
1152
|
if (!result.compatible && result.reason) {
|
|
1135
1153
|
return {
|
|
1136
|
-
rootCauseNode:
|
|
1154
|
+
rootCauseNode: serviceId3,
|
|
1137
1155
|
rootCauseReason: result.reason,
|
|
1138
1156
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
1139
1157
|
};
|
|
@@ -1142,9 +1160,15 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1142
1160
|
}
|
|
1143
1161
|
return null;
|
|
1144
1162
|
}
|
|
1163
|
+
function fileRootCauseShape(graph, origin, walk) {
|
|
1164
|
+
const owner = resolveOwningService(graph, origin.id);
|
|
1165
|
+
if (!owner) return null;
|
|
1166
|
+
return serviceRootCauseShape(graph, owner.svc, walk);
|
|
1167
|
+
}
|
|
1145
1168
|
var rootCauseShapes = {
|
|
1146
1169
|
[import_types.NodeType.DatabaseNode]: databaseRootCauseShape,
|
|
1147
|
-
[import_types.NodeType.ServiceNode]: serviceRootCauseShape
|
|
1170
|
+
[import_types.NodeType.ServiceNode]: serviceRootCauseShape,
|
|
1171
|
+
[import_types.NodeType.FileNode]: fileRootCauseShape
|
|
1148
1172
|
};
|
|
1149
1173
|
function getRootCause(graph, errorNodeId, errorEvent) {
|
|
1150
1174
|
if (!graph.hasNode(errorNodeId)) return null;
|
|
@@ -1607,6 +1631,86 @@ function hostFromUrl(u) {
|
|
|
1607
1631
|
function pickAddress(span) {
|
|
1608
1632
|
return pickAttr(span, "server.address", "net.peer.name", "net.host.name") ?? hostFromUrl(pickAttr(span, "url.full", "http.url"));
|
|
1609
1633
|
}
|
|
1634
|
+
var CODE_FILEPATH_ATTR = "code.filepath";
|
|
1635
|
+
var CODE_LINENO_ATTR = "code.lineno";
|
|
1636
|
+
var CODE_FUNCTION_ATTR = "code.function";
|
|
1637
|
+
function toPosix(p) {
|
|
1638
|
+
return p.split("\\").join("/");
|
|
1639
|
+
}
|
|
1640
|
+
function languageForExt(relPath) {
|
|
1641
|
+
const dot = relPath.lastIndexOf(".");
|
|
1642
|
+
if (dot === -1) return void 0;
|
|
1643
|
+
switch (relPath.slice(dot).toLowerCase()) {
|
|
1644
|
+
case ".py":
|
|
1645
|
+
return "python";
|
|
1646
|
+
case ".ts":
|
|
1647
|
+
case ".tsx":
|
|
1648
|
+
return "typescript";
|
|
1649
|
+
case ".js":
|
|
1650
|
+
case ".jsx":
|
|
1651
|
+
case ".mjs":
|
|
1652
|
+
case ".cjs":
|
|
1653
|
+
return "javascript";
|
|
1654
|
+
default:
|
|
1655
|
+
return void 0;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
function relPathForRuntimeFile(filepath, serviceNode) {
|
|
1659
|
+
let p = toPosix(filepath).replace(/^file:\/\//, "");
|
|
1660
|
+
const root = serviceNode?.repoPath;
|
|
1661
|
+
if (root && root !== "." && root.length > 0) {
|
|
1662
|
+
const rootPosix = toPosix(root);
|
|
1663
|
+
const anchor = `/${rootPosix}/`;
|
|
1664
|
+
const idx = p.lastIndexOf(anchor);
|
|
1665
|
+
if (idx !== -1) return p.slice(idx + anchor.length);
|
|
1666
|
+
const base = rootPosix.split("/").filter(Boolean).pop();
|
|
1667
|
+
if (base) {
|
|
1668
|
+
const baseAnchor = `/${base}/`;
|
|
1669
|
+
const bidx = p.lastIndexOf(baseAnchor);
|
|
1670
|
+
if (bidx !== -1) return p.slice(bidx + baseAnchor.length);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
p = p.replace(/^[A-Za-z]:/, "").replace(/^\/+/, "");
|
|
1674
|
+
return p.length > 0 ? p : null;
|
|
1675
|
+
}
|
|
1676
|
+
function callSiteFromSpan(span, serviceNode) {
|
|
1677
|
+
const filepath = span.attributes[CODE_FILEPATH_ATTR];
|
|
1678
|
+
if (typeof filepath !== "string" || filepath.length === 0) return null;
|
|
1679
|
+
const relPath = relPathForRuntimeFile(filepath, serviceNode);
|
|
1680
|
+
if (!relPath) return null;
|
|
1681
|
+
const linenoRaw = span.attributes[CODE_LINENO_ATTR];
|
|
1682
|
+
const line = typeof linenoRaw === "number" && Number.isFinite(linenoRaw) ? linenoRaw : void 0;
|
|
1683
|
+
const fnRaw = span.attributes[CODE_FUNCTION_ATTR];
|
|
1684
|
+
const fn = typeof fnRaw === "string" && fnRaw.length > 0 ? fnRaw : void 0;
|
|
1685
|
+
return { relPath, ...line !== void 0 ? { line } : {}, ...fn ? { fn } : {} };
|
|
1686
|
+
}
|
|
1687
|
+
function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
1688
|
+
const fileNodeId = (0, import_types3.fileId)(serviceName, callSite.relPath);
|
|
1689
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
1690
|
+
const language = languageForExt(callSite.relPath);
|
|
1691
|
+
const node = {
|
|
1692
|
+
id: fileNodeId,
|
|
1693
|
+
type: import_types3.NodeType.FileNode,
|
|
1694
|
+
service: serviceName,
|
|
1695
|
+
path: callSite.relPath,
|
|
1696
|
+
...language ? { language } : {},
|
|
1697
|
+
discoveredVia: "otel"
|
|
1698
|
+
};
|
|
1699
|
+
graph.addNode(fileNodeId, node);
|
|
1700
|
+
}
|
|
1701
|
+
const containsId = makeObservedEdgeId(import_types3.EdgeType.CONTAINS, serviceNodeId, fileNodeId);
|
|
1702
|
+
if (!graph.hasEdge(containsId)) {
|
|
1703
|
+
const edge = {
|
|
1704
|
+
id: containsId,
|
|
1705
|
+
source: serviceNodeId,
|
|
1706
|
+
target: fileNodeId,
|
|
1707
|
+
type: import_types3.EdgeType.CONTAINS,
|
|
1708
|
+
provenance: import_types3.Provenance.OBSERVED
|
|
1709
|
+
};
|
|
1710
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
1711
|
+
}
|
|
1712
|
+
return fileNodeId;
|
|
1713
|
+
}
|
|
1610
1714
|
function makeObservedEdgeId(type, source, target) {
|
|
1611
1715
|
return (0, import_types3.observedEdgeId)(source, target, type);
|
|
1612
1716
|
}
|
|
@@ -1849,6 +1953,9 @@ async function handleSpan(ctx, span) {
|
|
|
1849
1953
|
const sourceId = ensureServiceNode(ctx.graph, span.service, env);
|
|
1850
1954
|
const isError = span.statusCode === 2;
|
|
1851
1955
|
cacheSpanService(span, nowMs);
|
|
1956
|
+
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
1957
|
+
const callSite = callSiteFromSpan(span, sourceServiceNode);
|
|
1958
|
+
const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
|
|
1852
1959
|
let affectedNode = sourceId;
|
|
1853
1960
|
if (span.dbSystem) {
|
|
1854
1961
|
const host = pickAddress(span);
|
|
@@ -1858,7 +1965,7 @@ async function handleSpan(ctx, span) {
|
|
|
1858
1965
|
const result = upsertObservedEdge(
|
|
1859
1966
|
ctx.graph,
|
|
1860
1967
|
import_types3.EdgeType.CONNECTS_TO,
|
|
1861
|
-
|
|
1968
|
+
observedSource(),
|
|
1862
1969
|
targetId,
|
|
1863
1970
|
ts,
|
|
1864
1971
|
isError
|
|
@@ -1874,7 +1981,7 @@ async function handleSpan(ctx, span) {
|
|
|
1874
1981
|
upsertObservedEdge(
|
|
1875
1982
|
ctx.graph,
|
|
1876
1983
|
import_types3.EdgeType.CALLS,
|
|
1877
|
-
|
|
1984
|
+
observedSource(),
|
|
1878
1985
|
targetId,
|
|
1879
1986
|
ts,
|
|
1880
1987
|
isError
|
|
@@ -1886,7 +1993,7 @@ async function handleSpan(ctx, span) {
|
|
|
1886
1993
|
upsertObservedEdge(
|
|
1887
1994
|
ctx.graph,
|
|
1888
1995
|
import_types3.EdgeType.CALLS,
|
|
1889
|
-
|
|
1996
|
+
observedSource(),
|
|
1890
1997
|
frontierNodeId,
|
|
1891
1998
|
ts,
|
|
1892
1999
|
isError
|
|
@@ -3444,7 +3551,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3444
3551
|
|
|
3445
3552
|
// src/extract/calls/index.ts
|
|
3446
3553
|
init_cjs_shims();
|
|
3447
|
-
var
|
|
3554
|
+
var import_types15 = require("@neat.is/types");
|
|
3448
3555
|
|
|
3449
3556
|
// src/extract/calls/http.ts
|
|
3450
3557
|
init_cjs_shims();
|
|
@@ -3452,12 +3559,13 @@ var import_node_path20 = __toESM(require("path"), 1);
|
|
|
3452
3559
|
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
3453
3560
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3454
3561
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
3455
|
-
var
|
|
3562
|
+
var import_types10 = require("@neat.is/types");
|
|
3456
3563
|
|
|
3457
3564
|
// src/extract/calls/shared.ts
|
|
3458
3565
|
init_cjs_shims();
|
|
3459
3566
|
var import_node_fs13 = require("fs");
|
|
3460
3567
|
var import_node_path19 = __toESM(require("path"), 1);
|
|
3568
|
+
var import_types9 = require("@neat.is/types");
|
|
3461
3569
|
async function walkSourceFiles(dir) {
|
|
3462
3570
|
const out = [];
|
|
3463
3571
|
async function walk(current) {
|
|
@@ -3497,6 +3605,58 @@ function snippet(text, line) {
|
|
|
3497
3605
|
const lines = text.split("\n");
|
|
3498
3606
|
return (lines[line - 1] ?? "").trim();
|
|
3499
3607
|
}
|
|
3608
|
+
function toPosix2(p) {
|
|
3609
|
+
return p.split("\\").join("/");
|
|
3610
|
+
}
|
|
3611
|
+
function languageForPath(relPath) {
|
|
3612
|
+
switch (import_node_path19.default.extname(relPath).toLowerCase()) {
|
|
3613
|
+
case ".py":
|
|
3614
|
+
return "python";
|
|
3615
|
+
case ".ts":
|
|
3616
|
+
case ".tsx":
|
|
3617
|
+
return "typescript";
|
|
3618
|
+
case ".js":
|
|
3619
|
+
case ".jsx":
|
|
3620
|
+
case ".mjs":
|
|
3621
|
+
case ".cjs":
|
|
3622
|
+
return "javascript";
|
|
3623
|
+
default:
|
|
3624
|
+
return void 0;
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3628
|
+
let nodesAdded = 0;
|
|
3629
|
+
let edgesAdded = 0;
|
|
3630
|
+
const fileNodeId = (0, import_types9.fileId)(serviceName, relPath);
|
|
3631
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
3632
|
+
const language = languageForPath(relPath);
|
|
3633
|
+
const node = {
|
|
3634
|
+
id: fileNodeId,
|
|
3635
|
+
type: import_types9.NodeType.FileNode,
|
|
3636
|
+
service: serviceName,
|
|
3637
|
+
path: relPath,
|
|
3638
|
+
...language ? { language } : {},
|
|
3639
|
+
discoveredVia: "static"
|
|
3640
|
+
};
|
|
3641
|
+
graph.addNode(fileNodeId, node);
|
|
3642
|
+
nodesAdded++;
|
|
3643
|
+
}
|
|
3644
|
+
const containsId = (0, import_types9.extractedEdgeId)(serviceNodeId, fileNodeId, import_types9.EdgeType.CONTAINS);
|
|
3645
|
+
if (!graph.hasEdge(containsId)) {
|
|
3646
|
+
const edge = {
|
|
3647
|
+
id: containsId,
|
|
3648
|
+
source: serviceNodeId,
|
|
3649
|
+
target: fileNodeId,
|
|
3650
|
+
type: import_types9.EdgeType.CONTAINS,
|
|
3651
|
+
provenance: import_types9.Provenance.EXTRACTED,
|
|
3652
|
+
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
3653
|
+
evidence: { file: relPath }
|
|
3654
|
+
};
|
|
3655
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3656
|
+
edgesAdded++;
|
|
3657
|
+
}
|
|
3658
|
+
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3659
|
+
}
|
|
3500
3660
|
|
|
3501
3661
|
// src/extract/calls/http.ts
|
|
3502
3662
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
@@ -3530,16 +3690,16 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
3530
3690
|
const tree = parser.parse(source);
|
|
3531
3691
|
const literals = [];
|
|
3532
3692
|
collectStringLiterals(tree.rootNode, literals);
|
|
3533
|
-
const
|
|
3693
|
+
const out = [];
|
|
3534
3694
|
for (const lit of literals) {
|
|
3535
3695
|
if (isInsideJsxExternalLink(lit.node)) continue;
|
|
3536
3696
|
for (const host of knownHosts) {
|
|
3537
3697
|
if (urlMatchesHost(lit.text, host)) {
|
|
3538
|
-
|
|
3698
|
+
out.push({ host, line: lit.node.startPosition.row + 1 });
|
|
3539
3699
|
}
|
|
3540
3700
|
}
|
|
3541
3701
|
}
|
|
3542
|
-
return
|
|
3702
|
+
return out;
|
|
3543
3703
|
}
|
|
3544
3704
|
function makeJsParser() {
|
|
3545
3705
|
const p = new import_tree_sitter.default();
|
|
@@ -3562,71 +3722,78 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3562
3722
|
hostToNodeId.set(import_node_path20.default.basename(service.dir), service.node.id);
|
|
3563
3723
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
3564
3724
|
}
|
|
3725
|
+
let nodesAdded = 0;
|
|
3565
3726
|
let edgesAdded = 0;
|
|
3566
3727
|
for (const service of services) {
|
|
3567
3728
|
const files = await loadSourceFiles(service.dir);
|
|
3568
|
-
const
|
|
3729
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3569
3730
|
for (const file of files) {
|
|
3570
3731
|
if (isTestPath(file.path)) continue;
|
|
3571
3732
|
const parser = import_node_path20.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
3572
|
-
let
|
|
3733
|
+
let sites;
|
|
3573
3734
|
try {
|
|
3574
|
-
|
|
3735
|
+
sites = callsFromSource(file.content, parser, knownHosts);
|
|
3575
3736
|
} catch (err) {
|
|
3576
3737
|
recordExtractionError("http call extraction", file.path, err);
|
|
3577
3738
|
continue;
|
|
3578
3739
|
}
|
|
3579
|
-
|
|
3580
|
-
|
|
3740
|
+
if (sites.length === 0) continue;
|
|
3741
|
+
const relFile = toPosix2(import_node_path20.default.relative(service.dir, file.path));
|
|
3742
|
+
for (const site of sites) {
|
|
3743
|
+
const targetId = hostToNodeId.get(site.host);
|
|
3581
3744
|
if (!targetId || targetId === service.node.id) continue;
|
|
3582
|
-
|
|
3583
|
-
|
|
3745
|
+
const dedupKey = `${relFile}|${targetId}`;
|
|
3746
|
+
if (seen.has(dedupKey)) continue;
|
|
3747
|
+
seen.add(dedupKey);
|
|
3748
|
+
const confidence = (0, import_types10.confidenceForExtracted)("hostname-shape-match");
|
|
3749
|
+
const ev = {
|
|
3750
|
+
file: relFile,
|
|
3751
|
+
line: site.line,
|
|
3752
|
+
snippet: snippet(file.content, site.line)
|
|
3753
|
+
};
|
|
3754
|
+
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
3755
|
+
graph,
|
|
3756
|
+
service.pkg.name,
|
|
3757
|
+
service.node.id,
|
|
3758
|
+
relFile
|
|
3759
|
+
);
|
|
3760
|
+
nodesAdded += n;
|
|
3761
|
+
edgesAdded += e;
|
|
3762
|
+
if (!(0, import_types10.passesExtractedFloor)(confidence)) {
|
|
3763
|
+
noteExtractedDropped({
|
|
3764
|
+
source: fileNodeId,
|
|
3765
|
+
target: targetId,
|
|
3766
|
+
type: import_types10.EdgeType.CALLS,
|
|
3767
|
+
confidence,
|
|
3768
|
+
confidenceKind: "hostname-shape-match",
|
|
3769
|
+
evidence: ev
|
|
3770
|
+
});
|
|
3771
|
+
continue;
|
|
3772
|
+
}
|
|
3773
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types10.EdgeType.CALLS);
|
|
3774
|
+
if (!graph.hasEdge(edgeId)) {
|
|
3775
|
+
const edge = {
|
|
3776
|
+
id: edgeId,
|
|
3777
|
+
source: fileNodeId,
|
|
3778
|
+
target: targetId,
|
|
3779
|
+
type: import_types10.EdgeType.CALLS,
|
|
3780
|
+
provenance: import_types10.Provenance.EXTRACTED,
|
|
3781
|
+
confidence,
|
|
3782
|
+
evidence: ev
|
|
3783
|
+
};
|
|
3784
|
+
graph.addEdgeWithKey(edgeId, fileNodeId, targetId, edge);
|
|
3785
|
+
edgesAdded++;
|
|
3584
3786
|
}
|
|
3585
|
-
}
|
|
3586
|
-
}
|
|
3587
|
-
for (const [targetId, evidenceFile] of seenTargets) {
|
|
3588
|
-
const fileContent = files.find((f) => f.path === evidenceFile.file)?.content ?? "";
|
|
3589
|
-
const line = lineOf(fileContent, `//${evidenceFile.host}`);
|
|
3590
|
-
const confidence = (0, import_types9.confidenceForExtracted)("hostname-shape-match");
|
|
3591
|
-
const ev = {
|
|
3592
|
-
file: import_node_path20.default.relative(service.dir, evidenceFile.file),
|
|
3593
|
-
line,
|
|
3594
|
-
snippet: snippet(fileContent, line)
|
|
3595
|
-
};
|
|
3596
|
-
const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, targetId, import_types9.EdgeType.CALLS);
|
|
3597
|
-
if (!(0, import_types9.passesExtractedFloor)(confidence)) {
|
|
3598
|
-
noteExtractedDropped({
|
|
3599
|
-
source: service.node.id,
|
|
3600
|
-
target: targetId,
|
|
3601
|
-
type: import_types9.EdgeType.CALLS,
|
|
3602
|
-
confidence,
|
|
3603
|
-
confidenceKind: "hostname-shape-match",
|
|
3604
|
-
evidence: ev
|
|
3605
|
-
});
|
|
3606
|
-
continue;
|
|
3607
|
-
}
|
|
3608
|
-
const edge = {
|
|
3609
|
-
id: edgeId,
|
|
3610
|
-
source: service.node.id,
|
|
3611
|
-
target: targetId,
|
|
3612
|
-
type: import_types9.EdgeType.CALLS,
|
|
3613
|
-
provenance: import_types9.Provenance.EXTRACTED,
|
|
3614
|
-
confidence,
|
|
3615
|
-
evidence: ev
|
|
3616
|
-
};
|
|
3617
|
-
if (!graph.hasEdge(edge.id)) {
|
|
3618
|
-
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
3619
|
-
edgesAdded++;
|
|
3620
3787
|
}
|
|
3621
3788
|
}
|
|
3622
3789
|
}
|
|
3623
|
-
return edgesAdded;
|
|
3790
|
+
return { nodesAdded, edgesAdded };
|
|
3624
3791
|
}
|
|
3625
3792
|
|
|
3626
3793
|
// src/extract/calls/kafka.ts
|
|
3627
3794
|
init_cjs_shims();
|
|
3628
3795
|
var import_node_path21 = __toESM(require("path"), 1);
|
|
3629
|
-
var
|
|
3796
|
+
var import_types11 = require("@neat.is/types");
|
|
3630
3797
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3631
3798
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
3632
3799
|
function findAll(re, text) {
|
|
@@ -3647,7 +3814,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3647
3814
|
seen.add(key);
|
|
3648
3815
|
const line = lineOf(file.content, topic);
|
|
3649
3816
|
out.push({
|
|
3650
|
-
infraId: (0,
|
|
3817
|
+
infraId: (0, import_types11.infraId)("kafka-topic", topic),
|
|
3651
3818
|
name: topic,
|
|
3652
3819
|
kind: "kafka-topic",
|
|
3653
3820
|
edgeType,
|
|
@@ -3670,7 +3837,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3670
3837
|
// src/extract/calls/redis.ts
|
|
3671
3838
|
init_cjs_shims();
|
|
3672
3839
|
var import_node_path22 = __toESM(require("path"), 1);
|
|
3673
|
-
var
|
|
3840
|
+
var import_types12 = require("@neat.is/types");
|
|
3674
3841
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
3675
3842
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
3676
3843
|
const out = [];
|
|
@@ -3683,7 +3850,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3683
3850
|
seen.add(host);
|
|
3684
3851
|
const line = lineOf(file.content, host);
|
|
3685
3852
|
out.push({
|
|
3686
|
-
infraId: (0,
|
|
3853
|
+
infraId: (0, import_types12.infraId)("redis", host),
|
|
3687
3854
|
name: host,
|
|
3688
3855
|
kind: "redis",
|
|
3689
3856
|
edgeType: "CALLS",
|
|
@@ -3704,7 +3871,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3704
3871
|
// src/extract/calls/aws.ts
|
|
3705
3872
|
init_cjs_shims();
|
|
3706
3873
|
var import_node_path23 = __toESM(require("path"), 1);
|
|
3707
|
-
var
|
|
3874
|
+
var import_types13 = require("@neat.is/types");
|
|
3708
3875
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3709
3876
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3710
3877
|
function hasMarker(text, markers) {
|
|
@@ -3728,7 +3895,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3728
3895
|
seen.add(key);
|
|
3729
3896
|
const line = lineOf(file.content, name);
|
|
3730
3897
|
out.push({
|
|
3731
|
-
infraId: (0,
|
|
3898
|
+
infraId: (0, import_types13.infraId)(kind, name),
|
|
3732
3899
|
name,
|
|
3733
3900
|
kind,
|
|
3734
3901
|
edgeType: "CALLS",
|
|
@@ -3763,7 +3930,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3763
3930
|
// src/extract/calls/grpc.ts
|
|
3764
3931
|
init_cjs_shims();
|
|
3765
3932
|
var import_node_path24 = __toESM(require("path"), 1);
|
|
3766
|
-
var
|
|
3933
|
+
var import_types14 = require("@neat.is/types");
|
|
3767
3934
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
3768
3935
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
3769
3936
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
@@ -3811,7 +3978,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3811
3978
|
const { kind } = classified;
|
|
3812
3979
|
const line = lineOf(file.content, m[0]);
|
|
3813
3980
|
out.push({
|
|
3814
|
-
infraId: (0,
|
|
3981
|
+
infraId: (0, import_types14.infraId)(kind, name),
|
|
3815
3982
|
name,
|
|
3816
3983
|
kind,
|
|
3817
3984
|
edgeType: "CALLS",
|
|
@@ -3833,11 +4000,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3833
4000
|
function edgeTypeFromEndpoint(ep) {
|
|
3834
4001
|
switch (ep.edgeType) {
|
|
3835
4002
|
case "PUBLISHES_TO":
|
|
3836
|
-
return
|
|
4003
|
+
return import_types15.EdgeType.PUBLISHES_TO;
|
|
3837
4004
|
case "CONSUMES_FROM":
|
|
3838
|
-
return
|
|
4005
|
+
return import_types15.EdgeType.CONSUMES_FROM;
|
|
3839
4006
|
default:
|
|
3840
|
-
return
|
|
4007
|
+
return import_types15.EdgeType.CALLS;
|
|
3841
4008
|
}
|
|
3842
4009
|
}
|
|
3843
4010
|
function isAwsKind(kind) {
|
|
@@ -3864,7 +4031,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3864
4031
|
if (!graph.hasNode(ep.infraId)) {
|
|
3865
4032
|
const node = {
|
|
3866
4033
|
id: ep.infraId,
|
|
3867
|
-
type:
|
|
4034
|
+
type: import_types15.NodeType.InfraNode,
|
|
3868
4035
|
name: ep.name,
|
|
3869
4036
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
3870
4037
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -3876,13 +4043,19 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3876
4043
|
nodesAdded++;
|
|
3877
4044
|
}
|
|
3878
4045
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
3879
|
-
const
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
4046
|
+
const confidence = (0, import_types15.confidenceForExtracted)(ep.confidenceKind);
|
|
4047
|
+
const relFile = toPosix2(ep.evidence.file);
|
|
4048
|
+
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
4049
|
+
graph,
|
|
4050
|
+
service.pkg.name,
|
|
4051
|
+
service.node.id,
|
|
4052
|
+
relFile
|
|
4053
|
+
);
|
|
4054
|
+
nodesAdded += n;
|
|
4055
|
+
edgesAdded += e;
|
|
4056
|
+
if (!(0, import_types15.passesExtractedFloor)(confidence)) {
|
|
3884
4057
|
noteExtractedDropped({
|
|
3885
|
-
source:
|
|
4058
|
+
source: fileNodeId,
|
|
3886
4059
|
target: ep.infraId,
|
|
3887
4060
|
type: edgeType,
|
|
3888
4061
|
confidence,
|
|
@@ -3891,13 +4064,16 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3891
4064
|
});
|
|
3892
4065
|
continue;
|
|
3893
4066
|
}
|
|
4067
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, ep.infraId, edgeType);
|
|
4068
|
+
if (seenEdges.has(edgeId)) continue;
|
|
4069
|
+
seenEdges.add(edgeId);
|
|
3894
4070
|
if (!graph.hasEdge(edgeId)) {
|
|
3895
4071
|
const edge = {
|
|
3896
4072
|
id: edgeId,
|
|
3897
|
-
source:
|
|
4073
|
+
source: fileNodeId,
|
|
3898
4074
|
target: ep.infraId,
|
|
3899
4075
|
type: edgeType,
|
|
3900
|
-
provenance:
|
|
4076
|
+
provenance: import_types15.Provenance.EXTRACTED,
|
|
3901
4077
|
confidence,
|
|
3902
4078
|
evidence: ep.evidence
|
|
3903
4079
|
};
|
|
@@ -3909,11 +4085,11 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3909
4085
|
return { nodesAdded, edgesAdded };
|
|
3910
4086
|
}
|
|
3911
4087
|
async function addCallEdges(graph, services) {
|
|
3912
|
-
const
|
|
4088
|
+
const http = await addHttpCallEdges(graph, services);
|
|
3913
4089
|
const ext = await addExternalEndpointEdges(graph, services);
|
|
3914
4090
|
return {
|
|
3915
|
-
nodesAdded: ext.nodesAdded,
|
|
3916
|
-
edgesAdded:
|
|
4091
|
+
nodesAdded: http.nodesAdded + ext.nodesAdded,
|
|
4092
|
+
edgesAdded: http.edgesAdded + ext.edgesAdded
|
|
3917
4093
|
};
|
|
3918
4094
|
}
|
|
3919
4095
|
|
|
@@ -3923,15 +4099,15 @@ init_cjs_shims();
|
|
|
3923
4099
|
// src/extract/infra/docker-compose.ts
|
|
3924
4100
|
init_cjs_shims();
|
|
3925
4101
|
var import_node_path25 = __toESM(require("path"), 1);
|
|
3926
|
-
var
|
|
4102
|
+
var import_types17 = require("@neat.is/types");
|
|
3927
4103
|
|
|
3928
4104
|
// src/extract/infra/shared.ts
|
|
3929
4105
|
init_cjs_shims();
|
|
3930
|
-
var
|
|
4106
|
+
var import_types16 = require("@neat.is/types");
|
|
3931
4107
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
3932
4108
|
return {
|
|
3933
|
-
id: (0,
|
|
3934
|
-
type:
|
|
4109
|
+
id: (0, import_types16.infraId)(kind, name),
|
|
4110
|
+
type: import_types16.NodeType.InfraNode,
|
|
3935
4111
|
name,
|
|
3936
4112
|
provider,
|
|
3937
4113
|
kind,
|
|
@@ -4010,15 +4186,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4010
4186
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
4011
4187
|
const targetId = composeNameToNodeId.get(dep);
|
|
4012
4188
|
if (!targetId) continue;
|
|
4013
|
-
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId,
|
|
4189
|
+
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types17.EdgeType.DEPENDS_ON);
|
|
4014
4190
|
if (graph.hasEdge(edgeId)) continue;
|
|
4015
4191
|
const edge = {
|
|
4016
4192
|
id: edgeId,
|
|
4017
4193
|
source: sourceId,
|
|
4018
4194
|
target: targetId,
|
|
4019
|
-
type:
|
|
4020
|
-
provenance:
|
|
4021
|
-
confidence: (0,
|
|
4195
|
+
type: import_types17.EdgeType.DEPENDS_ON,
|
|
4196
|
+
provenance: import_types17.Provenance.EXTRACTED,
|
|
4197
|
+
confidence: (0, import_types17.confidenceForExtracted)("structural"),
|
|
4022
4198
|
evidence: { file: evidenceFile }
|
|
4023
4199
|
};
|
|
4024
4200
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4032,7 +4208,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4032
4208
|
init_cjs_shims();
|
|
4033
4209
|
var import_node_path26 = __toESM(require("path"), 1);
|
|
4034
4210
|
var import_node_fs14 = require("fs");
|
|
4035
|
-
var
|
|
4211
|
+
var import_types18 = require("@neat.is/types");
|
|
4036
4212
|
function runtimeImage(content) {
|
|
4037
4213
|
const lines = content.split("\n");
|
|
4038
4214
|
let last = null;
|
|
@@ -4071,15 +4247,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4071
4247
|
graph.addNode(node.id, node);
|
|
4072
4248
|
nodesAdded++;
|
|
4073
4249
|
}
|
|
4074
|
-
const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, node.id,
|
|
4250
|
+
const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, node.id, import_types18.EdgeType.RUNS_ON);
|
|
4075
4251
|
if (!graph.hasEdge(edgeId)) {
|
|
4076
4252
|
const edge = {
|
|
4077
4253
|
id: edgeId,
|
|
4078
4254
|
source: service.node.id,
|
|
4079
4255
|
target: node.id,
|
|
4080
|
-
type:
|
|
4081
|
-
provenance:
|
|
4082
|
-
confidence: (0,
|
|
4256
|
+
type: import_types18.EdgeType.RUNS_ON,
|
|
4257
|
+
provenance: import_types18.Provenance.EXTRACTED,
|
|
4258
|
+
confidence: (0, import_types18.confidenceForExtracted)("structural"),
|
|
4083
4259
|
evidence: {
|
|
4084
4260
|
file: import_node_path26.default.relative(scanPath, dockerfilePath).split(import_node_path26.default.sep).join("/")
|
|
4085
4261
|
}
|
|
@@ -4207,13 +4383,24 @@ var import_node_path30 = __toESM(require("path"), 1);
|
|
|
4207
4383
|
init_cjs_shims();
|
|
4208
4384
|
var import_node_fs17 = require("fs");
|
|
4209
4385
|
var import_node_path29 = __toESM(require("path"), 1);
|
|
4210
|
-
var
|
|
4386
|
+
var import_types19 = require("@neat.is/types");
|
|
4387
|
+
function dropOrphanedFileNodes(graph) {
|
|
4388
|
+
const orphans = [];
|
|
4389
|
+
graph.forEachNode((id, attrs) => {
|
|
4390
|
+
if (attrs.type !== import_types19.NodeType.FileNode) return;
|
|
4391
|
+
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
4392
|
+
orphans.push(id);
|
|
4393
|
+
}
|
|
4394
|
+
});
|
|
4395
|
+
for (const id of orphans) graph.dropNode(id);
|
|
4396
|
+
return orphans.length;
|
|
4397
|
+
}
|
|
4211
4398
|
function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
4212
4399
|
const toDrop = [];
|
|
4213
4400
|
const bases = [scanPath, ...serviceDirs];
|
|
4214
4401
|
graph.forEachEdge((id, attrs) => {
|
|
4215
4402
|
const edge = attrs;
|
|
4216
|
-
if (edge.provenance !==
|
|
4403
|
+
if (edge.provenance !== import_types19.Provenance.EXTRACTED) return;
|
|
4217
4404
|
const evidenceFile = edge.evidence?.file;
|
|
4218
4405
|
if (!evidenceFile) return;
|
|
4219
4406
|
if (import_node_path29.default.isAbsolute(evidenceFile)) {
|
|
@@ -4224,6 +4411,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
4224
4411
|
if (!found) toDrop.push(id);
|
|
4225
4412
|
});
|
|
4226
4413
|
for (const id of toDrop) graph.dropEdge(id);
|
|
4414
|
+
dropOrphanedFileNodes(graph);
|
|
4227
4415
|
return toDrop.length;
|
|
4228
4416
|
}
|
|
4229
4417
|
|
|
@@ -4293,7 +4481,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4293
4481
|
init_cjs_shims();
|
|
4294
4482
|
var import_node_fs18 = require("fs");
|
|
4295
4483
|
var import_node_path31 = __toESM(require("path"), 1);
|
|
4296
|
-
var
|
|
4484
|
+
var import_types20 = require("@neat.is/types");
|
|
4297
4485
|
var SCHEMA_VERSION = 4;
|
|
4298
4486
|
function migrateV1ToV2(payload) {
|
|
4299
4487
|
const nodes = payload.graph.nodes;
|
|
@@ -4315,12 +4503,12 @@ function migrateV2ToV3(payload) {
|
|
|
4315
4503
|
for (const edge of edges) {
|
|
4316
4504
|
const attrs = edge.attributes;
|
|
4317
4505
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
4318
|
-
attrs.provenance =
|
|
4506
|
+
attrs.provenance = import_types20.Provenance.OBSERVED;
|
|
4319
4507
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
4320
4508
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
4321
4509
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
4322
4510
|
if (type && source && target) {
|
|
4323
|
-
const newId = (0,
|
|
4511
|
+
const newId = (0, import_types20.observedEdgeId)(source, target, type);
|
|
4324
4512
|
attrs.id = newId;
|
|
4325
4513
|
if (edge.key) edge.key = newId;
|
|
4326
4514
|
}
|
|
@@ -4458,11 +4646,11 @@ var Projects = class {
|
|
|
4458
4646
|
init_cjs_shims();
|
|
4459
4647
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
4460
4648
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
4461
|
-
var
|
|
4649
|
+
var import_types23 = require("@neat.is/types");
|
|
4462
4650
|
|
|
4463
4651
|
// src/divergences.ts
|
|
4464
4652
|
init_cjs_shims();
|
|
4465
|
-
var
|
|
4653
|
+
var import_types21 = require("@neat.is/types");
|
|
4466
4654
|
function bucketKey(source, target, type) {
|
|
4467
4655
|
return `${type}|${source}|${target}`;
|
|
4468
4656
|
}
|
|
@@ -4470,22 +4658,22 @@ function bucketEdges(graph) {
|
|
|
4470
4658
|
const buckets = /* @__PURE__ */ new Map();
|
|
4471
4659
|
graph.forEachEdge((id, attrs) => {
|
|
4472
4660
|
const e = attrs;
|
|
4473
|
-
const parsed = (0,
|
|
4661
|
+
const parsed = (0, import_types21.parseEdgeId)(id);
|
|
4474
4662
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
4475
4663
|
const key = bucketKey(e.source, e.target, e.type);
|
|
4476
4664
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
4477
4665
|
switch (provenance) {
|
|
4478
|
-
case
|
|
4666
|
+
case import_types21.Provenance.EXTRACTED:
|
|
4479
4667
|
cur.extracted = e;
|
|
4480
4668
|
break;
|
|
4481
|
-
case
|
|
4669
|
+
case import_types21.Provenance.OBSERVED:
|
|
4482
4670
|
cur.observed = e;
|
|
4483
4671
|
break;
|
|
4484
|
-
case
|
|
4672
|
+
case import_types21.Provenance.INFERRED:
|
|
4485
4673
|
cur.inferred = e;
|
|
4486
4674
|
break;
|
|
4487
4675
|
default:
|
|
4488
|
-
if (e.provenance ===
|
|
4676
|
+
if (e.provenance === import_types21.Provenance.STALE) cur.stale = e;
|
|
4489
4677
|
}
|
|
4490
4678
|
buckets.set(key, cur);
|
|
4491
4679
|
});
|
|
@@ -4494,7 +4682,7 @@ function bucketEdges(graph) {
|
|
|
4494
4682
|
function nodeIsFrontier(graph, nodeId) {
|
|
4495
4683
|
if (!graph.hasNode(nodeId)) return false;
|
|
4496
4684
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
4497
|
-
return attrs.type ===
|
|
4685
|
+
return attrs.type === import_types21.NodeType.FrontierNode;
|
|
4498
4686
|
}
|
|
4499
4687
|
function clampConfidence(n) {
|
|
4500
4688
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -4515,6 +4703,7 @@ function gradedConfidence(edge) {
|
|
|
4515
4703
|
}
|
|
4516
4704
|
function detectMissingDivergences(graph, bucket) {
|
|
4517
4705
|
const out = [];
|
|
4706
|
+
if (bucket.type === import_types21.EdgeType.CONTAINS) return out;
|
|
4518
4707
|
if (bucket.extracted && !bucket.observed) {
|
|
4519
4708
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
4520
4709
|
out.push({
|
|
@@ -4555,7 +4744,7 @@ function declaredHostFor(svc) {
|
|
|
4555
4744
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
4556
4745
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4557
4746
|
const e = graph.getEdgeAttributes(edgeId);
|
|
4558
|
-
if (e.type ===
|
|
4747
|
+
if (e.type === import_types21.EdgeType.CONFIGURED_BY && e.provenance === import_types21.Provenance.EXTRACTED) {
|
|
4559
4748
|
return true;
|
|
4560
4749
|
}
|
|
4561
4750
|
}
|
|
@@ -4568,10 +4757,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
4568
4757
|
const out = [];
|
|
4569
4758
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4570
4759
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4571
|
-
if (edge.type !==
|
|
4572
|
-
if (edge.provenance !==
|
|
4760
|
+
if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
|
|
4761
|
+
if (edge.provenance !== import_types21.Provenance.OBSERVED) continue;
|
|
4573
4762
|
const target = graph.getNodeAttributes(edge.target);
|
|
4574
|
-
if (target.type !==
|
|
4763
|
+
if (target.type !== import_types21.NodeType.DatabaseNode) continue;
|
|
4575
4764
|
const observedHost = target.host?.trim();
|
|
4576
4765
|
if (!observedHost) continue;
|
|
4577
4766
|
if (observedHost === declaredHost) continue;
|
|
@@ -4593,10 +4782,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
4593
4782
|
const deps = svc.dependencies ?? {};
|
|
4594
4783
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4595
4784
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4596
|
-
if (edge.type !==
|
|
4597
|
-
if (edge.provenance !==
|
|
4785
|
+
if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
|
|
4786
|
+
if (edge.provenance !== import_types21.Provenance.OBSERVED) continue;
|
|
4598
4787
|
const target = graph.getNodeAttributes(edge.target);
|
|
4599
|
-
if (target.type !==
|
|
4788
|
+
if (target.type !== import_types21.NodeType.DatabaseNode) continue;
|
|
4600
4789
|
for (const pair of compatPairs()) {
|
|
4601
4790
|
if (pair.engine !== target.engine) continue;
|
|
4602
4791
|
const declared = deps[pair.driver];
|
|
@@ -4657,7 +4846,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4657
4846
|
}
|
|
4658
4847
|
graph.forEachNode((nodeId, attrs) => {
|
|
4659
4848
|
const n = attrs;
|
|
4660
|
-
if (n.type !==
|
|
4849
|
+
if (n.type !== import_types21.NodeType.ServiceNode) return;
|
|
4661
4850
|
const svc = n;
|
|
4662
4851
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
4663
4852
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -4690,7 +4879,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4690
4879
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
4691
4880
|
return a.target.localeCompare(b.target);
|
|
4692
4881
|
});
|
|
4693
|
-
return
|
|
4882
|
+
return import_types21.DivergenceResultSchema.parse({
|
|
4694
4883
|
divergences: filtered,
|
|
4695
4884
|
totalAffected: filtered.length,
|
|
4696
4885
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4779,7 +4968,7 @@ init_cjs_shims();
|
|
|
4779
4968
|
var import_node_fs20 = require("fs");
|
|
4780
4969
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
4781
4970
|
var import_node_path33 = __toESM(require("path"), 1);
|
|
4782
|
-
var
|
|
4971
|
+
var import_types22 = require("@neat.is/types");
|
|
4783
4972
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
4784
4973
|
var LOCK_RETRY_MS = 50;
|
|
4785
4974
|
function neatHome() {
|
|
@@ -4850,10 +5039,10 @@ async function readRegistry() {
|
|
|
4850
5039
|
throw err;
|
|
4851
5040
|
}
|
|
4852
5041
|
const parsed = JSON.parse(raw);
|
|
4853
|
-
return
|
|
5042
|
+
return import_types22.RegistryFileSchema.parse(parsed);
|
|
4854
5043
|
}
|
|
4855
5044
|
async function writeRegistry(reg) {
|
|
4856
|
-
const validated =
|
|
5045
|
+
const validated = import_types22.RegistryFileSchema.parse(reg);
|
|
4857
5046
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
4858
5047
|
}
|
|
4859
5048
|
async function getProject(name) {
|
|
@@ -5075,11 +5264,11 @@ function registerRoutes(scope, ctx) {
|
|
|
5075
5264
|
const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
5076
5265
|
const parsed = [];
|
|
5077
5266
|
for (const c of candidates) {
|
|
5078
|
-
const r =
|
|
5267
|
+
const r = import_types23.DivergenceTypeSchema.safeParse(c);
|
|
5079
5268
|
if (!r.success) {
|
|
5080
5269
|
return reply.code(400).send({
|
|
5081
5270
|
error: `unknown divergence type "${c}"`,
|
|
5082
|
-
allowed:
|
|
5271
|
+
allowed: import_types23.DivergenceTypeSchema.options
|
|
5083
5272
|
});
|
|
5084
5273
|
}
|
|
5085
5274
|
parsed.push(r.data);
|
|
@@ -5292,7 +5481,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5292
5481
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
5293
5482
|
let violations = await log.readAll();
|
|
5294
5483
|
if (req2.query.severity) {
|
|
5295
|
-
const sev =
|
|
5484
|
+
const sev = import_types23.PolicySeveritySchema.safeParse(req2.query.severity);
|
|
5296
5485
|
if (!sev.success) {
|
|
5297
5486
|
return reply.code(400).send({
|
|
5298
5487
|
error: "invalid severity",
|
|
@@ -5309,7 +5498,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5309
5498
|
scope.post("/policies/check", async (req2, reply) => {
|
|
5310
5499
|
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
5311
5500
|
if (!proj) return;
|
|
5312
|
-
const parsed =
|
|
5501
|
+
const parsed = import_types23.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
|
|
5313
5502
|
if (!parsed.success) {
|
|
5314
5503
|
return reply.code(400).send({
|
|
5315
5504
|
error: "invalid /policies/check body",
|
|
@@ -5346,14 +5535,18 @@ function registerRoutes(scope, ctx) {
|
|
|
5346
5535
|
async function buildApi(opts) {
|
|
5347
5536
|
const app = (0, import_fastify.default)({ logger: false });
|
|
5348
5537
|
await app.register(import_cors.default, { origin: true });
|
|
5538
|
+
const env = readAuthEnv();
|
|
5539
|
+
const authToken = opts.authToken ?? env.authToken;
|
|
5540
|
+
const trustProxy = opts.trustProxy ?? env.trustProxy;
|
|
5541
|
+
const publicRead = opts.publicRead ?? env.publicRead;
|
|
5349
5542
|
mountBearerAuth(app, {
|
|
5350
|
-
token:
|
|
5351
|
-
trustProxy
|
|
5352
|
-
publicRead
|
|
5543
|
+
token: authToken,
|
|
5544
|
+
trustProxy,
|
|
5545
|
+
publicRead
|
|
5353
5546
|
});
|
|
5354
5547
|
app.get("/api/config", async () => ({
|
|
5355
|
-
publicRead:
|
|
5356
|
-
authProxy:
|
|
5548
|
+
publicRead: publicRead === true,
|
|
5549
|
+
authProxy: trustProxy === true
|
|
5357
5550
|
}));
|
|
5358
5551
|
const startedAt = opts.startedAt ?? Date.now();
|
|
5359
5552
|
const registry = buildLegacyRegistry(opts);
|