@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/index.cjs
CHANGED
|
@@ -1017,6 +1017,24 @@ function isFrontierNode(graph, nodeId) {
|
|
|
1017
1017
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
1018
1018
|
return attrs.type === import_types.NodeType.FrontierNode;
|
|
1019
1019
|
}
|
|
1020
|
+
function resolveOwningService(graph, nodeId) {
|
|
1021
|
+
if (!graph.hasNode(nodeId)) return null;
|
|
1022
|
+
const attrs = graph.getNodeAttributes(nodeId);
|
|
1023
|
+
if (attrs.type === import_types.NodeType.ServiceNode) {
|
|
1024
|
+
return { id: nodeId, svc: attrs };
|
|
1025
|
+
}
|
|
1026
|
+
if (attrs.type === import_types.NodeType.FileNode) {
|
|
1027
|
+
for (const edgeId of graph.inboundEdges(nodeId)) {
|
|
1028
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
1029
|
+
if (e.type !== import_types.EdgeType.CONTAINS) continue;
|
|
1030
|
+
const owner = graph.getNodeAttributes(e.source);
|
|
1031
|
+
if (owner.type === import_types.NodeType.ServiceNode) {
|
|
1032
|
+
return { id: e.source, svc: owner };
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
return null;
|
|
1037
|
+
}
|
|
1020
1038
|
function bestEdgeBySource(graph, edgeIds) {
|
|
1021
1039
|
const best = /* @__PURE__ */ new Map();
|
|
1022
1040
|
for (const id of edgeIds) {
|
|
@@ -1123,9 +1141,9 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1123
1141
|
const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
|
|
1124
1142
|
if (candidatePairs.length === 0) return null;
|
|
1125
1143
|
for (const id of walk.path) {
|
|
1126
|
-
const
|
|
1127
|
-
if (
|
|
1128
|
-
const svc =
|
|
1144
|
+
const owner = resolveOwningService(graph, id);
|
|
1145
|
+
if (!owner) continue;
|
|
1146
|
+
const { id: serviceId3, svc } = owner;
|
|
1129
1147
|
const deps = svc.dependencies ?? {};
|
|
1130
1148
|
for (const pair of candidatePairs) {
|
|
1131
1149
|
const declared = deps[pair.driver];
|
|
@@ -1138,7 +1156,7 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1138
1156
|
);
|
|
1139
1157
|
if (!result.compatible) {
|
|
1140
1158
|
return {
|
|
1141
|
-
rootCauseNode:
|
|
1159
|
+
rootCauseNode: serviceId3,
|
|
1142
1160
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
1143
1161
|
...result.minDriverVersion ? {
|
|
1144
1162
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -1151,9 +1169,9 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1151
1169
|
}
|
|
1152
1170
|
function serviceRootCauseShape(graph, _origin, walk) {
|
|
1153
1171
|
for (const id of walk.path) {
|
|
1154
|
-
const
|
|
1155
|
-
if (
|
|
1156
|
-
const svc =
|
|
1172
|
+
const owner = resolveOwningService(graph, id);
|
|
1173
|
+
if (!owner) continue;
|
|
1174
|
+
const { id: serviceId3, svc } = owner;
|
|
1157
1175
|
const deps = svc.dependencies ?? {};
|
|
1158
1176
|
const serviceNodeEngine = svc.nodeEngine;
|
|
1159
1177
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -1162,7 +1180,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1162
1180
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
1163
1181
|
if (!result.compatible && result.reason) {
|
|
1164
1182
|
return {
|
|
1165
|
-
rootCauseNode:
|
|
1183
|
+
rootCauseNode: serviceId3,
|
|
1166
1184
|
rootCauseReason: result.reason,
|
|
1167
1185
|
...result.requiredNodeVersion ? {
|
|
1168
1186
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -1177,7 +1195,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1177
1195
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
1178
1196
|
if (!result.compatible && result.reason) {
|
|
1179
1197
|
return {
|
|
1180
|
-
rootCauseNode:
|
|
1198
|
+
rootCauseNode: serviceId3,
|
|
1181
1199
|
rootCauseReason: result.reason,
|
|
1182
1200
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
1183
1201
|
};
|
|
@@ -1186,9 +1204,15 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1186
1204
|
}
|
|
1187
1205
|
return null;
|
|
1188
1206
|
}
|
|
1207
|
+
function fileRootCauseShape(graph, origin, walk) {
|
|
1208
|
+
const owner = resolveOwningService(graph, origin.id);
|
|
1209
|
+
if (!owner) return null;
|
|
1210
|
+
return serviceRootCauseShape(graph, owner.svc, walk);
|
|
1211
|
+
}
|
|
1189
1212
|
var rootCauseShapes = {
|
|
1190
1213
|
[import_types.NodeType.DatabaseNode]: databaseRootCauseShape,
|
|
1191
|
-
[import_types.NodeType.ServiceNode]: serviceRootCauseShape
|
|
1214
|
+
[import_types.NodeType.ServiceNode]: serviceRootCauseShape,
|
|
1215
|
+
[import_types.NodeType.FileNode]: fileRootCauseShape
|
|
1192
1216
|
};
|
|
1193
1217
|
function getRootCause(graph, errorNodeId, errorEvent) {
|
|
1194
1218
|
if (!graph.hasNode(errorNodeId)) return null;
|
|
@@ -1673,6 +1697,86 @@ function hostFromUrl(u) {
|
|
|
1673
1697
|
function pickAddress(span) {
|
|
1674
1698
|
return pickAttr(span, "server.address", "net.peer.name", "net.host.name") ?? hostFromUrl(pickAttr(span, "url.full", "http.url"));
|
|
1675
1699
|
}
|
|
1700
|
+
var CODE_FILEPATH_ATTR = "code.filepath";
|
|
1701
|
+
var CODE_LINENO_ATTR = "code.lineno";
|
|
1702
|
+
var CODE_FUNCTION_ATTR = "code.function";
|
|
1703
|
+
function toPosix(p) {
|
|
1704
|
+
return p.split("\\").join("/");
|
|
1705
|
+
}
|
|
1706
|
+
function languageForExt(relPath) {
|
|
1707
|
+
const dot = relPath.lastIndexOf(".");
|
|
1708
|
+
if (dot === -1) return void 0;
|
|
1709
|
+
switch (relPath.slice(dot).toLowerCase()) {
|
|
1710
|
+
case ".py":
|
|
1711
|
+
return "python";
|
|
1712
|
+
case ".ts":
|
|
1713
|
+
case ".tsx":
|
|
1714
|
+
return "typescript";
|
|
1715
|
+
case ".js":
|
|
1716
|
+
case ".jsx":
|
|
1717
|
+
case ".mjs":
|
|
1718
|
+
case ".cjs":
|
|
1719
|
+
return "javascript";
|
|
1720
|
+
default:
|
|
1721
|
+
return void 0;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
function relPathForRuntimeFile(filepath, serviceNode) {
|
|
1725
|
+
let p = toPosix(filepath).replace(/^file:\/\//, "");
|
|
1726
|
+
const root = serviceNode?.repoPath;
|
|
1727
|
+
if (root && root !== "." && root.length > 0) {
|
|
1728
|
+
const rootPosix = toPosix(root);
|
|
1729
|
+
const anchor = `/${rootPosix}/`;
|
|
1730
|
+
const idx = p.lastIndexOf(anchor);
|
|
1731
|
+
if (idx !== -1) return p.slice(idx + anchor.length);
|
|
1732
|
+
const base = rootPosix.split("/").filter(Boolean).pop();
|
|
1733
|
+
if (base) {
|
|
1734
|
+
const baseAnchor = `/${base}/`;
|
|
1735
|
+
const bidx = p.lastIndexOf(baseAnchor);
|
|
1736
|
+
if (bidx !== -1) return p.slice(bidx + baseAnchor.length);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
p = p.replace(/^[A-Za-z]:/, "").replace(/^\/+/, "");
|
|
1740
|
+
return p.length > 0 ? p : null;
|
|
1741
|
+
}
|
|
1742
|
+
function callSiteFromSpan(span, serviceNode) {
|
|
1743
|
+
const filepath = span.attributes[CODE_FILEPATH_ATTR];
|
|
1744
|
+
if (typeof filepath !== "string" || filepath.length === 0) return null;
|
|
1745
|
+
const relPath = relPathForRuntimeFile(filepath, serviceNode);
|
|
1746
|
+
if (!relPath) return null;
|
|
1747
|
+
const linenoRaw = span.attributes[CODE_LINENO_ATTR];
|
|
1748
|
+
const line = typeof linenoRaw === "number" && Number.isFinite(linenoRaw) ? linenoRaw : void 0;
|
|
1749
|
+
const fnRaw = span.attributes[CODE_FUNCTION_ATTR];
|
|
1750
|
+
const fn = typeof fnRaw === "string" && fnRaw.length > 0 ? fnRaw : void 0;
|
|
1751
|
+
return { relPath, ...line !== void 0 ? { line } : {}, ...fn ? { fn } : {} };
|
|
1752
|
+
}
|
|
1753
|
+
function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
1754
|
+
const fileNodeId = (0, import_types3.fileId)(serviceName, callSite.relPath);
|
|
1755
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
1756
|
+
const language = languageForExt(callSite.relPath);
|
|
1757
|
+
const node = {
|
|
1758
|
+
id: fileNodeId,
|
|
1759
|
+
type: import_types3.NodeType.FileNode,
|
|
1760
|
+
service: serviceName,
|
|
1761
|
+
path: callSite.relPath,
|
|
1762
|
+
...language ? { language } : {},
|
|
1763
|
+
discoveredVia: "otel"
|
|
1764
|
+
};
|
|
1765
|
+
graph.addNode(fileNodeId, node);
|
|
1766
|
+
}
|
|
1767
|
+
const containsId = makeObservedEdgeId(import_types3.EdgeType.CONTAINS, serviceNodeId, fileNodeId);
|
|
1768
|
+
if (!graph.hasEdge(containsId)) {
|
|
1769
|
+
const edge = {
|
|
1770
|
+
id: containsId,
|
|
1771
|
+
source: serviceNodeId,
|
|
1772
|
+
target: fileNodeId,
|
|
1773
|
+
type: import_types3.EdgeType.CONTAINS,
|
|
1774
|
+
provenance: import_types3.Provenance.OBSERVED
|
|
1775
|
+
};
|
|
1776
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
1777
|
+
}
|
|
1778
|
+
return fileNodeId;
|
|
1779
|
+
}
|
|
1676
1780
|
function makeObservedEdgeId(type, source, target) {
|
|
1677
1781
|
return (0, import_types3.observedEdgeId)(source, target, type);
|
|
1678
1782
|
}
|
|
@@ -1915,6 +2019,9 @@ async function handleSpan(ctx, span) {
|
|
|
1915
2019
|
const sourceId = ensureServiceNode(ctx.graph, span.service, env);
|
|
1916
2020
|
const isError = span.statusCode === 2;
|
|
1917
2021
|
cacheSpanService(span, nowMs);
|
|
2022
|
+
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
2023
|
+
const callSite = callSiteFromSpan(span, sourceServiceNode);
|
|
2024
|
+
const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
|
|
1918
2025
|
let affectedNode = sourceId;
|
|
1919
2026
|
if (span.dbSystem) {
|
|
1920
2027
|
const host = pickAddress(span);
|
|
@@ -1924,7 +2031,7 @@ async function handleSpan(ctx, span) {
|
|
|
1924
2031
|
const result = upsertObservedEdge(
|
|
1925
2032
|
ctx.graph,
|
|
1926
2033
|
import_types3.EdgeType.CONNECTS_TO,
|
|
1927
|
-
|
|
2034
|
+
observedSource(),
|
|
1928
2035
|
targetId,
|
|
1929
2036
|
ts,
|
|
1930
2037
|
isError
|
|
@@ -1940,7 +2047,7 @@ async function handleSpan(ctx, span) {
|
|
|
1940
2047
|
upsertObservedEdge(
|
|
1941
2048
|
ctx.graph,
|
|
1942
2049
|
import_types3.EdgeType.CALLS,
|
|
1943
|
-
|
|
2050
|
+
observedSource(),
|
|
1944
2051
|
targetId,
|
|
1945
2052
|
ts,
|
|
1946
2053
|
isError
|
|
@@ -1952,7 +2059,7 @@ async function handleSpan(ctx, span) {
|
|
|
1952
2059
|
upsertObservedEdge(
|
|
1953
2060
|
ctx.graph,
|
|
1954
2061
|
import_types3.EdgeType.CALLS,
|
|
1955
|
-
|
|
2062
|
+
observedSource(),
|
|
1956
2063
|
frontierNodeId,
|
|
1957
2064
|
ts,
|
|
1958
2065
|
isError
|
|
@@ -3583,7 +3690,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3583
3690
|
|
|
3584
3691
|
// src/extract/calls/index.ts
|
|
3585
3692
|
init_cjs_shims();
|
|
3586
|
-
var
|
|
3693
|
+
var import_types15 = require("@neat.is/types");
|
|
3587
3694
|
|
|
3588
3695
|
// src/extract/calls/http.ts
|
|
3589
3696
|
init_cjs_shims();
|
|
@@ -3591,12 +3698,13 @@ var import_node_path20 = __toESM(require("path"), 1);
|
|
|
3591
3698
|
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
3592
3699
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3593
3700
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
3594
|
-
var
|
|
3701
|
+
var import_types10 = require("@neat.is/types");
|
|
3595
3702
|
|
|
3596
3703
|
// src/extract/calls/shared.ts
|
|
3597
3704
|
init_cjs_shims();
|
|
3598
3705
|
var import_node_fs13 = require("fs");
|
|
3599
3706
|
var import_node_path19 = __toESM(require("path"), 1);
|
|
3707
|
+
var import_types9 = require("@neat.is/types");
|
|
3600
3708
|
async function walkSourceFiles(dir) {
|
|
3601
3709
|
const out = [];
|
|
3602
3710
|
async function walk(current) {
|
|
@@ -3636,6 +3744,58 @@ function snippet(text, line) {
|
|
|
3636
3744
|
const lines = text.split("\n");
|
|
3637
3745
|
return (lines[line - 1] ?? "").trim();
|
|
3638
3746
|
}
|
|
3747
|
+
function toPosix2(p) {
|
|
3748
|
+
return p.split("\\").join("/");
|
|
3749
|
+
}
|
|
3750
|
+
function languageForPath(relPath) {
|
|
3751
|
+
switch (import_node_path19.default.extname(relPath).toLowerCase()) {
|
|
3752
|
+
case ".py":
|
|
3753
|
+
return "python";
|
|
3754
|
+
case ".ts":
|
|
3755
|
+
case ".tsx":
|
|
3756
|
+
return "typescript";
|
|
3757
|
+
case ".js":
|
|
3758
|
+
case ".jsx":
|
|
3759
|
+
case ".mjs":
|
|
3760
|
+
case ".cjs":
|
|
3761
|
+
return "javascript";
|
|
3762
|
+
default:
|
|
3763
|
+
return void 0;
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3767
|
+
let nodesAdded = 0;
|
|
3768
|
+
let edgesAdded = 0;
|
|
3769
|
+
const fileNodeId = (0, import_types9.fileId)(serviceName, relPath);
|
|
3770
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
3771
|
+
const language = languageForPath(relPath);
|
|
3772
|
+
const node = {
|
|
3773
|
+
id: fileNodeId,
|
|
3774
|
+
type: import_types9.NodeType.FileNode,
|
|
3775
|
+
service: serviceName,
|
|
3776
|
+
path: relPath,
|
|
3777
|
+
...language ? { language } : {},
|
|
3778
|
+
discoveredVia: "static"
|
|
3779
|
+
};
|
|
3780
|
+
graph.addNode(fileNodeId, node);
|
|
3781
|
+
nodesAdded++;
|
|
3782
|
+
}
|
|
3783
|
+
const containsId = (0, import_types9.extractedEdgeId)(serviceNodeId, fileNodeId, import_types9.EdgeType.CONTAINS);
|
|
3784
|
+
if (!graph.hasEdge(containsId)) {
|
|
3785
|
+
const edge = {
|
|
3786
|
+
id: containsId,
|
|
3787
|
+
source: serviceNodeId,
|
|
3788
|
+
target: fileNodeId,
|
|
3789
|
+
type: import_types9.EdgeType.CONTAINS,
|
|
3790
|
+
provenance: import_types9.Provenance.EXTRACTED,
|
|
3791
|
+
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
3792
|
+
evidence: { file: relPath }
|
|
3793
|
+
};
|
|
3794
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3795
|
+
edgesAdded++;
|
|
3796
|
+
}
|
|
3797
|
+
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3798
|
+
}
|
|
3639
3799
|
|
|
3640
3800
|
// src/extract/calls/http.ts
|
|
3641
3801
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
@@ -3669,16 +3829,16 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
3669
3829
|
const tree = parser.parse(source);
|
|
3670
3830
|
const literals = [];
|
|
3671
3831
|
collectStringLiterals(tree.rootNode, literals);
|
|
3672
|
-
const
|
|
3832
|
+
const out = [];
|
|
3673
3833
|
for (const lit of literals) {
|
|
3674
3834
|
if (isInsideJsxExternalLink(lit.node)) continue;
|
|
3675
3835
|
for (const host of knownHosts) {
|
|
3676
3836
|
if (urlMatchesHost(lit.text, host)) {
|
|
3677
|
-
|
|
3837
|
+
out.push({ host, line: lit.node.startPosition.row + 1 });
|
|
3678
3838
|
}
|
|
3679
3839
|
}
|
|
3680
3840
|
}
|
|
3681
|
-
return
|
|
3841
|
+
return out;
|
|
3682
3842
|
}
|
|
3683
3843
|
function makeJsParser() {
|
|
3684
3844
|
const p = new import_tree_sitter.default();
|
|
@@ -3701,71 +3861,78 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3701
3861
|
hostToNodeId.set(import_node_path20.default.basename(service.dir), service.node.id);
|
|
3702
3862
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
3703
3863
|
}
|
|
3864
|
+
let nodesAdded = 0;
|
|
3704
3865
|
let edgesAdded = 0;
|
|
3705
3866
|
for (const service of services) {
|
|
3706
3867
|
const files = await loadSourceFiles(service.dir);
|
|
3707
|
-
const
|
|
3868
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3708
3869
|
for (const file of files) {
|
|
3709
3870
|
if (isTestPath(file.path)) continue;
|
|
3710
3871
|
const parser = import_node_path20.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
3711
|
-
let
|
|
3872
|
+
let sites;
|
|
3712
3873
|
try {
|
|
3713
|
-
|
|
3874
|
+
sites = callsFromSource(file.content, parser, knownHosts);
|
|
3714
3875
|
} catch (err) {
|
|
3715
3876
|
recordExtractionError("http call extraction", file.path, err);
|
|
3716
3877
|
continue;
|
|
3717
3878
|
}
|
|
3718
|
-
|
|
3719
|
-
|
|
3879
|
+
if (sites.length === 0) continue;
|
|
3880
|
+
const relFile = toPosix2(import_node_path20.default.relative(service.dir, file.path));
|
|
3881
|
+
for (const site of sites) {
|
|
3882
|
+
const targetId = hostToNodeId.get(site.host);
|
|
3720
3883
|
if (!targetId || targetId === service.node.id) continue;
|
|
3721
|
-
|
|
3722
|
-
|
|
3884
|
+
const dedupKey = `${relFile}|${targetId}`;
|
|
3885
|
+
if (seen.has(dedupKey)) continue;
|
|
3886
|
+
seen.add(dedupKey);
|
|
3887
|
+
const confidence = (0, import_types10.confidenceForExtracted)("hostname-shape-match");
|
|
3888
|
+
const ev = {
|
|
3889
|
+
file: relFile,
|
|
3890
|
+
line: site.line,
|
|
3891
|
+
snippet: snippet(file.content, site.line)
|
|
3892
|
+
};
|
|
3893
|
+
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
3894
|
+
graph,
|
|
3895
|
+
service.pkg.name,
|
|
3896
|
+
service.node.id,
|
|
3897
|
+
relFile
|
|
3898
|
+
);
|
|
3899
|
+
nodesAdded += n;
|
|
3900
|
+
edgesAdded += e;
|
|
3901
|
+
if (!(0, import_types10.passesExtractedFloor)(confidence)) {
|
|
3902
|
+
noteExtractedDropped({
|
|
3903
|
+
source: fileNodeId,
|
|
3904
|
+
target: targetId,
|
|
3905
|
+
type: import_types10.EdgeType.CALLS,
|
|
3906
|
+
confidence,
|
|
3907
|
+
confidenceKind: "hostname-shape-match",
|
|
3908
|
+
evidence: ev
|
|
3909
|
+
});
|
|
3910
|
+
continue;
|
|
3911
|
+
}
|
|
3912
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types10.EdgeType.CALLS);
|
|
3913
|
+
if (!graph.hasEdge(edgeId)) {
|
|
3914
|
+
const edge = {
|
|
3915
|
+
id: edgeId,
|
|
3916
|
+
source: fileNodeId,
|
|
3917
|
+
target: targetId,
|
|
3918
|
+
type: import_types10.EdgeType.CALLS,
|
|
3919
|
+
provenance: import_types10.Provenance.EXTRACTED,
|
|
3920
|
+
confidence,
|
|
3921
|
+
evidence: ev
|
|
3922
|
+
};
|
|
3923
|
+
graph.addEdgeWithKey(edgeId, fileNodeId, targetId, edge);
|
|
3924
|
+
edgesAdded++;
|
|
3723
3925
|
}
|
|
3724
|
-
}
|
|
3725
|
-
}
|
|
3726
|
-
for (const [targetId, evidenceFile] of seenTargets) {
|
|
3727
|
-
const fileContent = files.find((f) => f.path === evidenceFile.file)?.content ?? "";
|
|
3728
|
-
const line = lineOf(fileContent, `//${evidenceFile.host}`);
|
|
3729
|
-
const confidence = (0, import_types9.confidenceForExtracted)("hostname-shape-match");
|
|
3730
|
-
const ev = {
|
|
3731
|
-
file: import_node_path20.default.relative(service.dir, evidenceFile.file),
|
|
3732
|
-
line,
|
|
3733
|
-
snippet: snippet(fileContent, line)
|
|
3734
|
-
};
|
|
3735
|
-
const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, targetId, import_types9.EdgeType.CALLS);
|
|
3736
|
-
if (!(0, import_types9.passesExtractedFloor)(confidence)) {
|
|
3737
|
-
noteExtractedDropped({
|
|
3738
|
-
source: service.node.id,
|
|
3739
|
-
target: targetId,
|
|
3740
|
-
type: import_types9.EdgeType.CALLS,
|
|
3741
|
-
confidence,
|
|
3742
|
-
confidenceKind: "hostname-shape-match",
|
|
3743
|
-
evidence: ev
|
|
3744
|
-
});
|
|
3745
|
-
continue;
|
|
3746
|
-
}
|
|
3747
|
-
const edge = {
|
|
3748
|
-
id: edgeId,
|
|
3749
|
-
source: service.node.id,
|
|
3750
|
-
target: targetId,
|
|
3751
|
-
type: import_types9.EdgeType.CALLS,
|
|
3752
|
-
provenance: import_types9.Provenance.EXTRACTED,
|
|
3753
|
-
confidence,
|
|
3754
|
-
evidence: ev
|
|
3755
|
-
};
|
|
3756
|
-
if (!graph.hasEdge(edge.id)) {
|
|
3757
|
-
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
3758
|
-
edgesAdded++;
|
|
3759
3926
|
}
|
|
3760
3927
|
}
|
|
3761
3928
|
}
|
|
3762
|
-
return edgesAdded;
|
|
3929
|
+
return { nodesAdded, edgesAdded };
|
|
3763
3930
|
}
|
|
3764
3931
|
|
|
3765
3932
|
// src/extract/calls/kafka.ts
|
|
3766
3933
|
init_cjs_shims();
|
|
3767
3934
|
var import_node_path21 = __toESM(require("path"), 1);
|
|
3768
|
-
var
|
|
3935
|
+
var import_types11 = require("@neat.is/types");
|
|
3769
3936
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3770
3937
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
3771
3938
|
function findAll(re, text) {
|
|
@@ -3786,7 +3953,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3786
3953
|
seen.add(key);
|
|
3787
3954
|
const line = lineOf(file.content, topic);
|
|
3788
3955
|
out.push({
|
|
3789
|
-
infraId: (0,
|
|
3956
|
+
infraId: (0, import_types11.infraId)("kafka-topic", topic),
|
|
3790
3957
|
name: topic,
|
|
3791
3958
|
kind: "kafka-topic",
|
|
3792
3959
|
edgeType,
|
|
@@ -3809,7 +3976,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3809
3976
|
// src/extract/calls/redis.ts
|
|
3810
3977
|
init_cjs_shims();
|
|
3811
3978
|
var import_node_path22 = __toESM(require("path"), 1);
|
|
3812
|
-
var
|
|
3979
|
+
var import_types12 = require("@neat.is/types");
|
|
3813
3980
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
3814
3981
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
3815
3982
|
const out = [];
|
|
@@ -3822,7 +3989,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3822
3989
|
seen.add(host);
|
|
3823
3990
|
const line = lineOf(file.content, host);
|
|
3824
3991
|
out.push({
|
|
3825
|
-
infraId: (0,
|
|
3992
|
+
infraId: (0, import_types12.infraId)("redis", host),
|
|
3826
3993
|
name: host,
|
|
3827
3994
|
kind: "redis",
|
|
3828
3995
|
edgeType: "CALLS",
|
|
@@ -3843,7 +4010,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3843
4010
|
// src/extract/calls/aws.ts
|
|
3844
4011
|
init_cjs_shims();
|
|
3845
4012
|
var import_node_path23 = __toESM(require("path"), 1);
|
|
3846
|
-
var
|
|
4013
|
+
var import_types13 = require("@neat.is/types");
|
|
3847
4014
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3848
4015
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3849
4016
|
function hasMarker(text, markers) {
|
|
@@ -3867,7 +4034,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3867
4034
|
seen.add(key);
|
|
3868
4035
|
const line = lineOf(file.content, name);
|
|
3869
4036
|
out.push({
|
|
3870
|
-
infraId: (0,
|
|
4037
|
+
infraId: (0, import_types13.infraId)(kind, name),
|
|
3871
4038
|
name,
|
|
3872
4039
|
kind,
|
|
3873
4040
|
edgeType: "CALLS",
|
|
@@ -3902,7 +4069,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3902
4069
|
// src/extract/calls/grpc.ts
|
|
3903
4070
|
init_cjs_shims();
|
|
3904
4071
|
var import_node_path24 = __toESM(require("path"), 1);
|
|
3905
|
-
var
|
|
4072
|
+
var import_types14 = require("@neat.is/types");
|
|
3906
4073
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
3907
4074
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
3908
4075
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
@@ -3950,7 +4117,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3950
4117
|
const { kind } = classified;
|
|
3951
4118
|
const line = lineOf(file.content, m[0]);
|
|
3952
4119
|
out.push({
|
|
3953
|
-
infraId: (0,
|
|
4120
|
+
infraId: (0, import_types14.infraId)(kind, name),
|
|
3954
4121
|
name,
|
|
3955
4122
|
kind,
|
|
3956
4123
|
edgeType: "CALLS",
|
|
@@ -3972,11 +4139,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3972
4139
|
function edgeTypeFromEndpoint(ep) {
|
|
3973
4140
|
switch (ep.edgeType) {
|
|
3974
4141
|
case "PUBLISHES_TO":
|
|
3975
|
-
return
|
|
4142
|
+
return import_types15.EdgeType.PUBLISHES_TO;
|
|
3976
4143
|
case "CONSUMES_FROM":
|
|
3977
|
-
return
|
|
4144
|
+
return import_types15.EdgeType.CONSUMES_FROM;
|
|
3978
4145
|
default:
|
|
3979
|
-
return
|
|
4146
|
+
return import_types15.EdgeType.CALLS;
|
|
3980
4147
|
}
|
|
3981
4148
|
}
|
|
3982
4149
|
function isAwsKind(kind) {
|
|
@@ -4003,7 +4170,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4003
4170
|
if (!graph.hasNode(ep.infraId)) {
|
|
4004
4171
|
const node = {
|
|
4005
4172
|
id: ep.infraId,
|
|
4006
|
-
type:
|
|
4173
|
+
type: import_types15.NodeType.InfraNode,
|
|
4007
4174
|
name: ep.name,
|
|
4008
4175
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
4009
4176
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -4015,13 +4182,19 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4015
4182
|
nodesAdded++;
|
|
4016
4183
|
}
|
|
4017
4184
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
4018
|
-
const
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4185
|
+
const confidence = (0, import_types15.confidenceForExtracted)(ep.confidenceKind);
|
|
4186
|
+
const relFile = toPosix2(ep.evidence.file);
|
|
4187
|
+
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
4188
|
+
graph,
|
|
4189
|
+
service.pkg.name,
|
|
4190
|
+
service.node.id,
|
|
4191
|
+
relFile
|
|
4192
|
+
);
|
|
4193
|
+
nodesAdded += n;
|
|
4194
|
+
edgesAdded += e;
|
|
4195
|
+
if (!(0, import_types15.passesExtractedFloor)(confidence)) {
|
|
4023
4196
|
noteExtractedDropped({
|
|
4024
|
-
source:
|
|
4197
|
+
source: fileNodeId,
|
|
4025
4198
|
target: ep.infraId,
|
|
4026
4199
|
type: edgeType,
|
|
4027
4200
|
confidence,
|
|
@@ -4030,13 +4203,16 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4030
4203
|
});
|
|
4031
4204
|
continue;
|
|
4032
4205
|
}
|
|
4206
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, ep.infraId, edgeType);
|
|
4207
|
+
if (seenEdges.has(edgeId)) continue;
|
|
4208
|
+
seenEdges.add(edgeId);
|
|
4033
4209
|
if (!graph.hasEdge(edgeId)) {
|
|
4034
4210
|
const edge = {
|
|
4035
4211
|
id: edgeId,
|
|
4036
|
-
source:
|
|
4212
|
+
source: fileNodeId,
|
|
4037
4213
|
target: ep.infraId,
|
|
4038
4214
|
type: edgeType,
|
|
4039
|
-
provenance:
|
|
4215
|
+
provenance: import_types15.Provenance.EXTRACTED,
|
|
4040
4216
|
confidence,
|
|
4041
4217
|
evidence: ep.evidence
|
|
4042
4218
|
};
|
|
@@ -4048,11 +4224,11 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4048
4224
|
return { nodesAdded, edgesAdded };
|
|
4049
4225
|
}
|
|
4050
4226
|
async function addCallEdges(graph, services) {
|
|
4051
|
-
const
|
|
4227
|
+
const http = await addHttpCallEdges(graph, services);
|
|
4052
4228
|
const ext = await addExternalEndpointEdges(graph, services);
|
|
4053
4229
|
return {
|
|
4054
|
-
nodesAdded: ext.nodesAdded,
|
|
4055
|
-
edgesAdded:
|
|
4230
|
+
nodesAdded: http.nodesAdded + ext.nodesAdded,
|
|
4231
|
+
edgesAdded: http.edgesAdded + ext.edgesAdded
|
|
4056
4232
|
};
|
|
4057
4233
|
}
|
|
4058
4234
|
|
|
@@ -4062,15 +4238,15 @@ init_cjs_shims();
|
|
|
4062
4238
|
// src/extract/infra/docker-compose.ts
|
|
4063
4239
|
init_cjs_shims();
|
|
4064
4240
|
var import_node_path25 = __toESM(require("path"), 1);
|
|
4065
|
-
var
|
|
4241
|
+
var import_types17 = require("@neat.is/types");
|
|
4066
4242
|
|
|
4067
4243
|
// src/extract/infra/shared.ts
|
|
4068
4244
|
init_cjs_shims();
|
|
4069
|
-
var
|
|
4245
|
+
var import_types16 = require("@neat.is/types");
|
|
4070
4246
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
4071
4247
|
return {
|
|
4072
|
-
id: (0,
|
|
4073
|
-
type:
|
|
4248
|
+
id: (0, import_types16.infraId)(kind, name),
|
|
4249
|
+
type: import_types16.NodeType.InfraNode,
|
|
4074
4250
|
name,
|
|
4075
4251
|
provider,
|
|
4076
4252
|
kind,
|
|
@@ -4149,15 +4325,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4149
4325
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
4150
4326
|
const targetId = composeNameToNodeId.get(dep);
|
|
4151
4327
|
if (!targetId) continue;
|
|
4152
|
-
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId,
|
|
4328
|
+
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types17.EdgeType.DEPENDS_ON);
|
|
4153
4329
|
if (graph.hasEdge(edgeId)) continue;
|
|
4154
4330
|
const edge = {
|
|
4155
4331
|
id: edgeId,
|
|
4156
4332
|
source: sourceId,
|
|
4157
4333
|
target: targetId,
|
|
4158
|
-
type:
|
|
4159
|
-
provenance:
|
|
4160
|
-
confidence: (0,
|
|
4334
|
+
type: import_types17.EdgeType.DEPENDS_ON,
|
|
4335
|
+
provenance: import_types17.Provenance.EXTRACTED,
|
|
4336
|
+
confidence: (0, import_types17.confidenceForExtracted)("structural"),
|
|
4161
4337
|
evidence: { file: evidenceFile }
|
|
4162
4338
|
};
|
|
4163
4339
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4171,7 +4347,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4171
4347
|
init_cjs_shims();
|
|
4172
4348
|
var import_node_path26 = __toESM(require("path"), 1);
|
|
4173
4349
|
var import_node_fs14 = require("fs");
|
|
4174
|
-
var
|
|
4350
|
+
var import_types18 = require("@neat.is/types");
|
|
4175
4351
|
function runtimeImage(content) {
|
|
4176
4352
|
const lines = content.split("\n");
|
|
4177
4353
|
let last = null;
|
|
@@ -4210,15 +4386,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4210
4386
|
graph.addNode(node.id, node);
|
|
4211
4387
|
nodesAdded++;
|
|
4212
4388
|
}
|
|
4213
|
-
const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, node.id,
|
|
4389
|
+
const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, node.id, import_types18.EdgeType.RUNS_ON);
|
|
4214
4390
|
if (!graph.hasEdge(edgeId)) {
|
|
4215
4391
|
const edge = {
|
|
4216
4392
|
id: edgeId,
|
|
4217
4393
|
source: service.node.id,
|
|
4218
4394
|
target: node.id,
|
|
4219
|
-
type:
|
|
4220
|
-
provenance:
|
|
4221
|
-
confidence: (0,
|
|
4395
|
+
type: import_types18.EdgeType.RUNS_ON,
|
|
4396
|
+
provenance: import_types18.Provenance.EXTRACTED,
|
|
4397
|
+
confidence: (0, import_types18.confidenceForExtracted)("structural"),
|
|
4222
4398
|
evidence: {
|
|
4223
4399
|
file: import_node_path26.default.relative(scanPath, dockerfilePath).split(import_node_path26.default.sep).join("/")
|
|
4224
4400
|
}
|
|
@@ -4346,13 +4522,24 @@ var import_node_path30 = __toESM(require("path"), 1);
|
|
|
4346
4522
|
init_cjs_shims();
|
|
4347
4523
|
var import_node_fs17 = require("fs");
|
|
4348
4524
|
var import_node_path29 = __toESM(require("path"), 1);
|
|
4349
|
-
var
|
|
4525
|
+
var import_types19 = require("@neat.is/types");
|
|
4526
|
+
function dropOrphanedFileNodes(graph) {
|
|
4527
|
+
const orphans = [];
|
|
4528
|
+
graph.forEachNode((id, attrs) => {
|
|
4529
|
+
if (attrs.type !== import_types19.NodeType.FileNode) return;
|
|
4530
|
+
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
4531
|
+
orphans.push(id);
|
|
4532
|
+
}
|
|
4533
|
+
});
|
|
4534
|
+
for (const id of orphans) graph.dropNode(id);
|
|
4535
|
+
return orphans.length;
|
|
4536
|
+
}
|
|
4350
4537
|
function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
4351
4538
|
const toDrop = [];
|
|
4352
4539
|
const bases = [scanPath, ...serviceDirs];
|
|
4353
4540
|
graph.forEachEdge((id, attrs) => {
|
|
4354
4541
|
const edge = attrs;
|
|
4355
|
-
if (edge.provenance !==
|
|
4542
|
+
if (edge.provenance !== import_types19.Provenance.EXTRACTED) return;
|
|
4356
4543
|
const evidenceFile = edge.evidence?.file;
|
|
4357
4544
|
if (!evidenceFile) return;
|
|
4358
4545
|
if (import_node_path29.default.isAbsolute(evidenceFile)) {
|
|
@@ -4363,6 +4550,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
4363
4550
|
if (!found) toDrop.push(id);
|
|
4364
4551
|
});
|
|
4365
4552
|
for (const id of toDrop) graph.dropEdge(id);
|
|
4553
|
+
dropOrphanedFileNodes(graph);
|
|
4366
4554
|
return toDrop.length;
|
|
4367
4555
|
}
|
|
4368
4556
|
|
|
@@ -4432,7 +4620,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4432
4620
|
init_cjs_shims();
|
|
4433
4621
|
var import_node_fs18 = require("fs");
|
|
4434
4622
|
var import_node_path31 = __toESM(require("path"), 1);
|
|
4435
|
-
var
|
|
4623
|
+
var import_types20 = require("@neat.is/types");
|
|
4436
4624
|
var SCHEMA_VERSION = 4;
|
|
4437
4625
|
function migrateV1ToV2(payload) {
|
|
4438
4626
|
const nodes = payload.graph.nodes;
|
|
@@ -4454,12 +4642,12 @@ function migrateV2ToV3(payload) {
|
|
|
4454
4642
|
for (const edge of edges) {
|
|
4455
4643
|
const attrs = edge.attributes;
|
|
4456
4644
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
4457
|
-
attrs.provenance =
|
|
4645
|
+
attrs.provenance = import_types20.Provenance.OBSERVED;
|
|
4458
4646
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
4459
4647
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
4460
4648
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
4461
4649
|
if (type && source && target) {
|
|
4462
|
-
const newId = (0,
|
|
4650
|
+
const newId = (0, import_types20.observedEdgeId)(source, target, type);
|
|
4463
4651
|
attrs.id = newId;
|
|
4464
4652
|
if (edge.key) edge.key = newId;
|
|
4465
4653
|
}
|
|
@@ -4545,11 +4733,11 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
|
|
|
4545
4733
|
init_cjs_shims();
|
|
4546
4734
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
4547
4735
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
4548
|
-
var
|
|
4736
|
+
var import_types23 = require("@neat.is/types");
|
|
4549
4737
|
|
|
4550
4738
|
// src/divergences.ts
|
|
4551
4739
|
init_cjs_shims();
|
|
4552
|
-
var
|
|
4740
|
+
var import_types21 = require("@neat.is/types");
|
|
4553
4741
|
function bucketKey(source, target, type) {
|
|
4554
4742
|
return `${type}|${source}|${target}`;
|
|
4555
4743
|
}
|
|
@@ -4557,22 +4745,22 @@ function bucketEdges(graph) {
|
|
|
4557
4745
|
const buckets = /* @__PURE__ */ new Map();
|
|
4558
4746
|
graph.forEachEdge((id, attrs) => {
|
|
4559
4747
|
const e = attrs;
|
|
4560
|
-
const parsed = (0,
|
|
4748
|
+
const parsed = (0, import_types21.parseEdgeId)(id);
|
|
4561
4749
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
4562
4750
|
const key = bucketKey(e.source, e.target, e.type);
|
|
4563
4751
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
4564
4752
|
switch (provenance) {
|
|
4565
|
-
case
|
|
4753
|
+
case import_types21.Provenance.EXTRACTED:
|
|
4566
4754
|
cur.extracted = e;
|
|
4567
4755
|
break;
|
|
4568
|
-
case
|
|
4756
|
+
case import_types21.Provenance.OBSERVED:
|
|
4569
4757
|
cur.observed = e;
|
|
4570
4758
|
break;
|
|
4571
|
-
case
|
|
4759
|
+
case import_types21.Provenance.INFERRED:
|
|
4572
4760
|
cur.inferred = e;
|
|
4573
4761
|
break;
|
|
4574
4762
|
default:
|
|
4575
|
-
if (e.provenance ===
|
|
4763
|
+
if (e.provenance === import_types21.Provenance.STALE) cur.stale = e;
|
|
4576
4764
|
}
|
|
4577
4765
|
buckets.set(key, cur);
|
|
4578
4766
|
});
|
|
@@ -4581,7 +4769,7 @@ function bucketEdges(graph) {
|
|
|
4581
4769
|
function nodeIsFrontier(graph, nodeId) {
|
|
4582
4770
|
if (!graph.hasNode(nodeId)) return false;
|
|
4583
4771
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
4584
|
-
return attrs.type ===
|
|
4772
|
+
return attrs.type === import_types21.NodeType.FrontierNode;
|
|
4585
4773
|
}
|
|
4586
4774
|
function clampConfidence(n) {
|
|
4587
4775
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -4602,6 +4790,7 @@ function gradedConfidence(edge) {
|
|
|
4602
4790
|
}
|
|
4603
4791
|
function detectMissingDivergences(graph, bucket) {
|
|
4604
4792
|
const out = [];
|
|
4793
|
+
if (bucket.type === import_types21.EdgeType.CONTAINS) return out;
|
|
4605
4794
|
if (bucket.extracted && !bucket.observed) {
|
|
4606
4795
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
4607
4796
|
out.push({
|
|
@@ -4642,7 +4831,7 @@ function declaredHostFor(svc) {
|
|
|
4642
4831
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
4643
4832
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4644
4833
|
const e = graph.getEdgeAttributes(edgeId);
|
|
4645
|
-
if (e.type ===
|
|
4834
|
+
if (e.type === import_types21.EdgeType.CONFIGURED_BY && e.provenance === import_types21.Provenance.EXTRACTED) {
|
|
4646
4835
|
return true;
|
|
4647
4836
|
}
|
|
4648
4837
|
}
|
|
@@ -4655,10 +4844,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
4655
4844
|
const out = [];
|
|
4656
4845
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4657
4846
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4658
|
-
if (edge.type !==
|
|
4659
|
-
if (edge.provenance !==
|
|
4847
|
+
if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
|
|
4848
|
+
if (edge.provenance !== import_types21.Provenance.OBSERVED) continue;
|
|
4660
4849
|
const target = graph.getNodeAttributes(edge.target);
|
|
4661
|
-
if (target.type !==
|
|
4850
|
+
if (target.type !== import_types21.NodeType.DatabaseNode) continue;
|
|
4662
4851
|
const observedHost = target.host?.trim();
|
|
4663
4852
|
if (!observedHost) continue;
|
|
4664
4853
|
if (observedHost === declaredHost) continue;
|
|
@@ -4680,10 +4869,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
4680
4869
|
const deps = svc.dependencies ?? {};
|
|
4681
4870
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4682
4871
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4683
|
-
if (edge.type !==
|
|
4684
|
-
if (edge.provenance !==
|
|
4872
|
+
if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
|
|
4873
|
+
if (edge.provenance !== import_types21.Provenance.OBSERVED) continue;
|
|
4685
4874
|
const target = graph.getNodeAttributes(edge.target);
|
|
4686
|
-
if (target.type !==
|
|
4875
|
+
if (target.type !== import_types21.NodeType.DatabaseNode) continue;
|
|
4687
4876
|
for (const pair of compatPairs()) {
|
|
4688
4877
|
if (pair.engine !== target.engine) continue;
|
|
4689
4878
|
const declared = deps[pair.driver];
|
|
@@ -4744,7 +4933,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4744
4933
|
}
|
|
4745
4934
|
graph.forEachNode((nodeId, attrs) => {
|
|
4746
4935
|
const n = attrs;
|
|
4747
|
-
if (n.type !==
|
|
4936
|
+
if (n.type !== import_types21.NodeType.ServiceNode) return;
|
|
4748
4937
|
const svc = n;
|
|
4749
4938
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
4750
4939
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -4777,7 +4966,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4777
4966
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
4778
4967
|
return a.target.localeCompare(b.target);
|
|
4779
4968
|
});
|
|
4780
|
-
return
|
|
4969
|
+
return import_types21.DivergenceResultSchema.parse({
|
|
4781
4970
|
divergences: filtered,
|
|
4782
4971
|
totalAffected: filtered.length,
|
|
4783
4972
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4918,7 +5107,7 @@ init_cjs_shims();
|
|
|
4918
5107
|
var import_node_fs20 = require("fs");
|
|
4919
5108
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
4920
5109
|
var import_node_path33 = __toESM(require("path"), 1);
|
|
4921
|
-
var
|
|
5110
|
+
var import_types22 = require("@neat.is/types");
|
|
4922
5111
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
4923
5112
|
var LOCK_RETRY_MS = 50;
|
|
4924
5113
|
function neatHome() {
|
|
@@ -4997,10 +5186,10 @@ async function readRegistry() {
|
|
|
4997
5186
|
throw err;
|
|
4998
5187
|
}
|
|
4999
5188
|
const parsed = JSON.parse(raw);
|
|
5000
|
-
return
|
|
5189
|
+
return import_types22.RegistryFileSchema.parse(parsed);
|
|
5001
5190
|
}
|
|
5002
5191
|
async function writeRegistry(reg) {
|
|
5003
|
-
const validated =
|
|
5192
|
+
const validated = import_types22.RegistryFileSchema.parse(reg);
|
|
5004
5193
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
5005
5194
|
}
|
|
5006
5195
|
var ProjectNameCollisionError = class extends Error {
|
|
@@ -5272,11 +5461,11 @@ function registerRoutes(scope, ctx) {
|
|
|
5272
5461
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
5273
5462
|
const parsed = [];
|
|
5274
5463
|
for (const c of candidates) {
|
|
5275
|
-
const r =
|
|
5464
|
+
const r = import_types23.DivergenceTypeSchema.safeParse(c);
|
|
5276
5465
|
if (!r.success) {
|
|
5277
5466
|
return reply.code(400).send({
|
|
5278
5467
|
error: `unknown divergence type "${c}"`,
|
|
5279
|
-
allowed:
|
|
5468
|
+
allowed: import_types23.DivergenceTypeSchema.options
|
|
5280
5469
|
});
|
|
5281
5470
|
}
|
|
5282
5471
|
parsed.push(r.data);
|
|
@@ -5489,7 +5678,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5489
5678
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
5490
5679
|
let violations = await log.readAll();
|
|
5491
5680
|
if (req.query.severity) {
|
|
5492
|
-
const sev =
|
|
5681
|
+
const sev = import_types23.PolicySeveritySchema.safeParse(req.query.severity);
|
|
5493
5682
|
if (!sev.success) {
|
|
5494
5683
|
return reply.code(400).send({
|
|
5495
5684
|
error: "invalid severity",
|
|
@@ -5506,7 +5695,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5506
5695
|
scope.post("/policies/check", async (req, reply) => {
|
|
5507
5696
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5508
5697
|
if (!proj) return;
|
|
5509
|
-
const parsed =
|
|
5698
|
+
const parsed = import_types23.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
5510
5699
|
if (!parsed.success) {
|
|
5511
5700
|
return reply.code(400).send({
|
|
5512
5701
|
error: "invalid /policies/check body",
|
|
@@ -5543,14 +5732,18 @@ function registerRoutes(scope, ctx) {
|
|
|
5543
5732
|
async function buildApi(opts) {
|
|
5544
5733
|
const app = (0, import_fastify.default)({ logger: false });
|
|
5545
5734
|
await app.register(import_cors.default, { origin: true });
|
|
5735
|
+
const env = readAuthEnv();
|
|
5736
|
+
const authToken = opts.authToken ?? env.authToken;
|
|
5737
|
+
const trustProxy = opts.trustProxy ?? env.trustProxy;
|
|
5738
|
+
const publicRead = opts.publicRead ?? env.publicRead;
|
|
5546
5739
|
mountBearerAuth(app, {
|
|
5547
|
-
token:
|
|
5548
|
-
trustProxy
|
|
5549
|
-
publicRead
|
|
5740
|
+
token: authToken,
|
|
5741
|
+
trustProxy,
|
|
5742
|
+
publicRead
|
|
5550
5743
|
});
|
|
5551
5744
|
app.get("/api/config", async () => ({
|
|
5552
|
-
publicRead:
|
|
5553
|
-
authProxy:
|
|
5745
|
+
publicRead: publicRead === true,
|
|
5746
|
+
authProxy: trustProxy === true
|
|
5554
5747
|
}));
|
|
5555
5748
|
const startedAt = opts.startedAt ?? Date.now();
|
|
5556
5749
|
const registry = buildLegacyRegistry(opts);
|