@neat.is/core 0.2.9 → 0.2.10
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-BVF7MVR5.js → chunk-5BQWBQJR.js} +2 -2
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/{chunk-IRPH6KL4.js → chunk-JMLZOKCS.js} +3 -1
- package/dist/{chunk-I5IMCXRO.js → chunk-QYUB3FQL.js} +2 -2
- package/dist/{chunk-5KX7EI4F.js → chunk-W7ZYJZC7.js} +285 -3
- package/dist/chunk-W7ZYJZC7.js.map +1 -0
- package/dist/cli.cjs +383 -17
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +100 -5
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +278 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -4
- package/dist/neatd.cjs +106 -5
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +112 -6
- package/dist/neatd.js.map +1 -1
- package/dist/{otel-grpc-B4XBSI4W.js → otel-grpc-USFL3OI3.js} +3 -2
- package/dist/otel-grpc-USFL3OI3.js.map +1 -0
- package/dist/server.cjs +382 -108
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +4 -3
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-5KX7EI4F.js.map +0 -1
- /package/dist/{chunk-BVF7MVR5.js.map → chunk-5BQWBQJR.js.map} +0 -0
- /package/dist/{otel-grpc-B4XBSI4W.js.map → chunk-DGUM43GV.js.map} +0 -0
- /package/dist/{chunk-IRPH6KL4.js.map → chunk-JMLZOKCS.js.map} +0 -0
- /package/dist/{chunk-I5IMCXRO.js.map → chunk-QYUB3FQL.js.map} +0 -0
package/dist/server.cjs
CHANGED
|
@@ -392,12 +392,10 @@ function getGraph(project = DEFAULT_PROJECT) {
|
|
|
392
392
|
init_cjs_shims();
|
|
393
393
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
394
394
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
395
|
-
var
|
|
395
|
+
var import_types20 = require("@neat.is/types");
|
|
396
396
|
|
|
397
|
-
// src/
|
|
397
|
+
// src/divergences.ts
|
|
398
398
|
init_cjs_shims();
|
|
399
|
-
var import_node_fs2 = require("fs");
|
|
400
|
-
var import_node_path2 = __toESM(require("path"), 1);
|
|
401
399
|
var import_types2 = require("@neat.is/types");
|
|
402
400
|
|
|
403
401
|
// src/compat.ts
|
|
@@ -713,18 +711,6 @@ function deprecatedApis() {
|
|
|
713
711
|
return currentMatrix().deprecatedApis ?? [];
|
|
714
712
|
}
|
|
715
713
|
|
|
716
|
-
// src/events.ts
|
|
717
|
-
init_cjs_shims();
|
|
718
|
-
var import_node_events = require("events");
|
|
719
|
-
var EVENT_BUS_CHANNEL = "event";
|
|
720
|
-
var NeatEventBus = class extends import_node_events.EventEmitter {
|
|
721
|
-
};
|
|
722
|
-
var eventBus = new NeatEventBus();
|
|
723
|
-
eventBus.setMaxListeners(0);
|
|
724
|
-
function emitNeatEvent(envelope) {
|
|
725
|
-
eventBus.emit(EVENT_BUS_CHANNEL, envelope);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
714
|
// src/traverse.ts
|
|
729
715
|
init_cjs_shims();
|
|
730
716
|
var import_types = require("@neat.is/types");
|
|
@@ -1006,6 +992,259 @@ function getTransitiveDependencies(graph, nodeId, depth = TRANSITIVE_DEPENDENCIE
|
|
|
1006
992
|
});
|
|
1007
993
|
}
|
|
1008
994
|
|
|
995
|
+
// src/divergences.ts
|
|
996
|
+
function bucketKey(source, target, type) {
|
|
997
|
+
return `${type}|${source}|${target}`;
|
|
998
|
+
}
|
|
999
|
+
function bucketEdges(graph) {
|
|
1000
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
1001
|
+
graph.forEachEdge((id, attrs) => {
|
|
1002
|
+
const e = attrs;
|
|
1003
|
+
const parsed = (0, import_types2.parseEdgeId)(id);
|
|
1004
|
+
const provenance = parsed?.provenance ?? e.provenance;
|
|
1005
|
+
const key = bucketKey(e.source, e.target, e.type);
|
|
1006
|
+
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
1007
|
+
switch (provenance) {
|
|
1008
|
+
case import_types2.Provenance.EXTRACTED:
|
|
1009
|
+
cur.extracted = e;
|
|
1010
|
+
break;
|
|
1011
|
+
case import_types2.Provenance.OBSERVED:
|
|
1012
|
+
cur.observed = e;
|
|
1013
|
+
break;
|
|
1014
|
+
case import_types2.Provenance.INFERRED:
|
|
1015
|
+
cur.inferred = e;
|
|
1016
|
+
break;
|
|
1017
|
+
case import_types2.Provenance.FRONTIER:
|
|
1018
|
+
cur.frontier = e;
|
|
1019
|
+
break;
|
|
1020
|
+
default:
|
|
1021
|
+
if (e.provenance === import_types2.Provenance.STALE) cur.stale = e;
|
|
1022
|
+
}
|
|
1023
|
+
buckets.set(key, cur);
|
|
1024
|
+
});
|
|
1025
|
+
return buckets;
|
|
1026
|
+
}
|
|
1027
|
+
function nodeIsFrontier(graph, nodeId) {
|
|
1028
|
+
if (!graph.hasNode(nodeId)) return false;
|
|
1029
|
+
const attrs = graph.getNodeAttributes(nodeId);
|
|
1030
|
+
return attrs.type === import_types2.NodeType.FrontierNode;
|
|
1031
|
+
}
|
|
1032
|
+
function hasAnyObservedFromSource(graph, sourceId) {
|
|
1033
|
+
if (!graph.hasNode(sourceId)) return false;
|
|
1034
|
+
for (const edgeId of graph.outboundEdges(sourceId)) {
|
|
1035
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
1036
|
+
if (e.provenance === import_types2.Provenance.OBSERVED) return true;
|
|
1037
|
+
}
|
|
1038
|
+
return false;
|
|
1039
|
+
}
|
|
1040
|
+
function clampConfidence(n) {
|
|
1041
|
+
if (!Number.isFinite(n)) return 0;
|
|
1042
|
+
return Math.max(0, Math.min(1, n));
|
|
1043
|
+
}
|
|
1044
|
+
function reasonForMissingObserved(source, target, type) {
|
|
1045
|
+
return `Code declares ${source} \u2192 ${target} (${type}) but no production traffic has been observed for this edge.`;
|
|
1046
|
+
}
|
|
1047
|
+
function reasonForMissingExtracted(source, target, type) {
|
|
1048
|
+
return `Production observed ${source} \u2192 ${target} (${type}) but static analysis did not surface this edge.`;
|
|
1049
|
+
}
|
|
1050
|
+
var RECOMMENDATION_MISSING_OBSERVED = "Verify the code path is exercised in production; check feature flags or conditional branches that might gate the call.";
|
|
1051
|
+
var RECOMMENDATION_MISSING_EXTRACTED = "Likely dynamic dispatch, reflection, or a coverage gap in tree-sitter extraction. Consider an `aliases` entry on the source service or file an extractor issue.";
|
|
1052
|
+
var RECOMMENDATION_HOST_MISMATCH = "Check environment-specific config overrides \u2014 the runtime host differs from what static configuration declares.";
|
|
1053
|
+
function detectMissingDivergences(graph, bucket) {
|
|
1054
|
+
const out = [];
|
|
1055
|
+
if (bucket.extracted && !bucket.observed) {
|
|
1056
|
+
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
1057
|
+
const sourceHasTraffic = hasAnyObservedFromSource(graph, bucket.source);
|
|
1058
|
+
out.push({
|
|
1059
|
+
type: "missing-observed",
|
|
1060
|
+
source: bucket.source,
|
|
1061
|
+
target: bucket.target,
|
|
1062
|
+
edgeType: bucket.type,
|
|
1063
|
+
extracted: bucket.extracted,
|
|
1064
|
+
confidence: sourceHasTraffic ? 1 : 0.5,
|
|
1065
|
+
reason: reasonForMissingObserved(bucket.source, bucket.target, bucket.type),
|
|
1066
|
+
recommendation: RECOMMENDATION_MISSING_OBSERVED
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
if (bucket.observed && !bucket.extracted) {
|
|
1071
|
+
const cascaded = clampConfidence(confidenceForEdge(bucket.observed));
|
|
1072
|
+
out.push({
|
|
1073
|
+
type: "missing-extracted",
|
|
1074
|
+
source: bucket.source,
|
|
1075
|
+
target: bucket.target,
|
|
1076
|
+
edgeType: bucket.type,
|
|
1077
|
+
observed: bucket.observed,
|
|
1078
|
+
confidence: cascaded,
|
|
1079
|
+
reason: reasonForMissingExtracted(bucket.source, bucket.target, bucket.type),
|
|
1080
|
+
recommendation: RECOMMENDATION_MISSING_EXTRACTED
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
return out;
|
|
1084
|
+
}
|
|
1085
|
+
function declaredHostFor(svc) {
|
|
1086
|
+
const raw = svc.dbConnectionTarget?.trim();
|
|
1087
|
+
if (!raw) return null;
|
|
1088
|
+
const colon = raw.lastIndexOf(":");
|
|
1089
|
+
if (colon === -1) return raw;
|
|
1090
|
+
const port = raw.slice(colon + 1);
|
|
1091
|
+
if (/^\d+$/.test(port)) return raw.slice(0, colon);
|
|
1092
|
+
return raw;
|
|
1093
|
+
}
|
|
1094
|
+
function hasExtractedConfiguredBy(graph, svcId) {
|
|
1095
|
+
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
1096
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
1097
|
+
if (e.type === import_types2.EdgeType.CONFIGURED_BY && e.provenance === import_types2.Provenance.EXTRACTED) {
|
|
1098
|
+
return true;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
return false;
|
|
1102
|
+
}
|
|
1103
|
+
function detectHostMismatch(graph, svcId, svc) {
|
|
1104
|
+
const declaredHost = declaredHostFor(svc);
|
|
1105
|
+
if (!declaredHost) return [];
|
|
1106
|
+
if (!hasExtractedConfiguredBy(graph, svcId)) return [];
|
|
1107
|
+
const out = [];
|
|
1108
|
+
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
1109
|
+
const edge = graph.getEdgeAttributes(edgeId);
|
|
1110
|
+
if (edge.type !== import_types2.EdgeType.CONNECTS_TO) continue;
|
|
1111
|
+
if (edge.provenance !== import_types2.Provenance.OBSERVED) continue;
|
|
1112
|
+
const target = graph.getNodeAttributes(edge.target);
|
|
1113
|
+
if (target.type !== import_types2.NodeType.DatabaseNode) continue;
|
|
1114
|
+
const observedHost = target.host?.trim();
|
|
1115
|
+
if (!observedHost) continue;
|
|
1116
|
+
if (observedHost === declaredHost) continue;
|
|
1117
|
+
out.push({
|
|
1118
|
+
type: "host-mismatch",
|
|
1119
|
+
source: svcId,
|
|
1120
|
+
target: edge.target,
|
|
1121
|
+
extractedHost: declaredHost,
|
|
1122
|
+
observedHost,
|
|
1123
|
+
confidence: clampConfidence(confidenceForEdge(edge)),
|
|
1124
|
+
reason: `Config declares ${svcId} connects to ${declaredHost}; production connects to ${observedHost}.`,
|
|
1125
|
+
recommendation: RECOMMENDATION_HOST_MISMATCH
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
return out;
|
|
1129
|
+
}
|
|
1130
|
+
function detectCompatDivergences(graph, svcId, svc) {
|
|
1131
|
+
const out = [];
|
|
1132
|
+
const deps = svc.dependencies ?? {};
|
|
1133
|
+
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
1134
|
+
const edge = graph.getEdgeAttributes(edgeId);
|
|
1135
|
+
if (edge.type !== import_types2.EdgeType.CONNECTS_TO) continue;
|
|
1136
|
+
if (edge.provenance !== import_types2.Provenance.OBSERVED) continue;
|
|
1137
|
+
const target = graph.getNodeAttributes(edge.target);
|
|
1138
|
+
if (target.type !== import_types2.NodeType.DatabaseNode) continue;
|
|
1139
|
+
for (const pair of compatPairs()) {
|
|
1140
|
+
if (pair.engine !== target.engine) continue;
|
|
1141
|
+
const declared = deps[pair.driver];
|
|
1142
|
+
if (!declared) continue;
|
|
1143
|
+
const result = checkCompatibility(
|
|
1144
|
+
pair.driver,
|
|
1145
|
+
declared,
|
|
1146
|
+
target.engine,
|
|
1147
|
+
target.engineVersion
|
|
1148
|
+
);
|
|
1149
|
+
if (!result.compatible && result.reason) {
|
|
1150
|
+
out.push({
|
|
1151
|
+
type: "version-mismatch",
|
|
1152
|
+
source: svcId,
|
|
1153
|
+
target: edge.target,
|
|
1154
|
+
extractedVersion: declared,
|
|
1155
|
+
observedVersion: target.engineVersion,
|
|
1156
|
+
compatibility: "incompatible",
|
|
1157
|
+
confidence: 1,
|
|
1158
|
+
reason: result.reason,
|
|
1159
|
+
recommendation: result.minDriverVersion ? `Upgrade ${pair.driver} to >= ${result.minDriverVersion}.` : `Update the ${pair.driver} driver to a version compatible with ${target.engine} ${target.engineVersion}.`
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
for (const rule of deprecatedApis()) {
|
|
1164
|
+
const declared = deps[rule.package];
|
|
1165
|
+
if (!declared) continue;
|
|
1166
|
+
const result = checkDeprecatedApi(rule, declared);
|
|
1167
|
+
if (!result.compatible && result.reason) {
|
|
1168
|
+
const ruleRef = {
|
|
1169
|
+
kind: rule.kind ?? "deprecated-api",
|
|
1170
|
+
reason: result.reason,
|
|
1171
|
+
package: rule.package
|
|
1172
|
+
};
|
|
1173
|
+
out.push({
|
|
1174
|
+
type: "compat-violation",
|
|
1175
|
+
source: svcId,
|
|
1176
|
+
target: edge.target,
|
|
1177
|
+
rule: ruleRef,
|
|
1178
|
+
observed: edge,
|
|
1179
|
+
confidence: 1,
|
|
1180
|
+
reason: result.reason,
|
|
1181
|
+
recommendation: `Replace deprecated ${rule.package}@${declared} with a supported version.`
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
return out;
|
|
1187
|
+
}
|
|
1188
|
+
function involvesNode(d, nodeId) {
|
|
1189
|
+
return d.source === nodeId || d.target === nodeId;
|
|
1190
|
+
}
|
|
1191
|
+
function computeDivergences(graph, opts = {}) {
|
|
1192
|
+
const all = [];
|
|
1193
|
+
const buckets = bucketEdges(graph);
|
|
1194
|
+
for (const bucket of buckets.values()) {
|
|
1195
|
+
for (const d of detectMissingDivergences(graph, bucket)) all.push(d);
|
|
1196
|
+
}
|
|
1197
|
+
graph.forEachNode((nodeId, attrs) => {
|
|
1198
|
+
const n = attrs;
|
|
1199
|
+
if (n.type !== import_types2.NodeType.ServiceNode) return;
|
|
1200
|
+
const svc = n;
|
|
1201
|
+
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
1202
|
+
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
1203
|
+
});
|
|
1204
|
+
let filtered = all;
|
|
1205
|
+
if (opts.type) {
|
|
1206
|
+
const allowed = opts.type;
|
|
1207
|
+
filtered = filtered.filter((d) => allowed.has(d.type));
|
|
1208
|
+
}
|
|
1209
|
+
if (opts.minConfidence !== void 0) {
|
|
1210
|
+
const threshold = opts.minConfidence;
|
|
1211
|
+
filtered = filtered.filter((d) => d.confidence >= threshold);
|
|
1212
|
+
}
|
|
1213
|
+
if (opts.node) {
|
|
1214
|
+
const target = opts.node;
|
|
1215
|
+
filtered = filtered.filter((d) => involvesNode(d, target));
|
|
1216
|
+
}
|
|
1217
|
+
filtered.sort((a, b) => {
|
|
1218
|
+
if (b.confidence !== a.confidence) return b.confidence - a.confidence;
|
|
1219
|
+
if (a.type !== b.type) return a.type.localeCompare(b.type);
|
|
1220
|
+
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
1221
|
+
return a.target.localeCompare(b.target);
|
|
1222
|
+
});
|
|
1223
|
+
return import_types2.DivergenceResultSchema.parse({
|
|
1224
|
+
divergences: filtered,
|
|
1225
|
+
totalAffected: filtered.length,
|
|
1226
|
+
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// src/policy.ts
|
|
1231
|
+
init_cjs_shims();
|
|
1232
|
+
var import_node_fs2 = require("fs");
|
|
1233
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
1234
|
+
var import_types3 = require("@neat.is/types");
|
|
1235
|
+
|
|
1236
|
+
// src/events.ts
|
|
1237
|
+
init_cjs_shims();
|
|
1238
|
+
var import_node_events = require("events");
|
|
1239
|
+
var EVENT_BUS_CHANNEL = "event";
|
|
1240
|
+
var NeatEventBus = class extends import_node_events.EventEmitter {
|
|
1241
|
+
};
|
|
1242
|
+
var eventBus = new NeatEventBus();
|
|
1243
|
+
eventBus.setMaxListeners(0);
|
|
1244
|
+
function emitNeatEvent(envelope) {
|
|
1245
|
+
eventBus.emit(EVENT_BUS_CHANNEL, envelope);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1009
1248
|
// src/policy.ts
|
|
1010
1249
|
var DEFAULT_ACTION_BY_SEVERITY = {
|
|
1011
1250
|
info: "log",
|
|
@@ -1043,7 +1282,7 @@ var evaluateStructural = ({
|
|
|
1043
1282
|
for (const edgeId of graph.outboundEdges(id)) {
|
|
1044
1283
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1045
1284
|
if (e.type !== rule.edgeType) continue;
|
|
1046
|
-
if (e.provenance ===
|
|
1285
|
+
if (e.provenance === import_types3.Provenance.FRONTIER) continue;
|
|
1047
1286
|
const target = graph.getNodeAttributes(e.target);
|
|
1048
1287
|
if (target.type === rule.toNodeType) {
|
|
1049
1288
|
satisfied = true;
|
|
@@ -1156,16 +1395,16 @@ var evaluateCompatibility = ({
|
|
|
1156
1395
|
const wantsKind = (kind) => rule.kind === void 0 || rule.kind === kind;
|
|
1157
1396
|
graph.forEachNode((svcId, attrs) => {
|
|
1158
1397
|
const a = attrs;
|
|
1159
|
-
if (a.type !==
|
|
1398
|
+
if (a.type !== import_types3.NodeType.ServiceNode) return;
|
|
1160
1399
|
const svc = a;
|
|
1161
1400
|
const deps = svc.dependencies ?? {};
|
|
1162
1401
|
if (wantsKind("driver-engine")) {
|
|
1163
1402
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
1164
1403
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1165
|
-
if (e.type !==
|
|
1166
|
-
if (e.provenance ===
|
|
1404
|
+
if (e.type !== import_types3.EdgeType.CONNECTS_TO) continue;
|
|
1405
|
+
if (e.provenance === import_types3.Provenance.FRONTIER) continue;
|
|
1167
1406
|
const dbAttrs = graph.getNodeAttributes(e.target);
|
|
1168
|
-
if (dbAttrs.type !==
|
|
1407
|
+
if (dbAttrs.type !== import_types3.NodeType.DatabaseNode) continue;
|
|
1169
1408
|
const db = dbAttrs;
|
|
1170
1409
|
for (const pair of compatPairs()) {
|
|
1171
1410
|
if (pair.engine !== db.engine) continue;
|
|
@@ -1283,7 +1522,7 @@ async function loadPolicyFile(policyPath) {
|
|
|
1283
1522
|
throw err;
|
|
1284
1523
|
}
|
|
1285
1524
|
const json = JSON.parse(raw);
|
|
1286
|
-
const file =
|
|
1525
|
+
const file = import_types3.PolicyFileSchema.parse(json);
|
|
1287
1526
|
return file.policies;
|
|
1288
1527
|
}
|
|
1289
1528
|
var PolicyViolationsLog = class {
|
|
@@ -1333,7 +1572,7 @@ init_cjs_shims();
|
|
|
1333
1572
|
init_cjs_shims();
|
|
1334
1573
|
var import_node_fs3 = require("fs");
|
|
1335
1574
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
1336
|
-
var
|
|
1575
|
+
var import_types4 = require("@neat.is/types");
|
|
1337
1576
|
var HOUR_MS = 60 * 60 * 1e3;
|
|
1338
1577
|
var DAY_MS = 24 * HOUR_MS;
|
|
1339
1578
|
var DEFAULT_STALE_THRESHOLDS = {
|
|
@@ -1389,10 +1628,10 @@ function pickAddress(span) {
|
|
|
1389
1628
|
return pickAttr(span, "server.address", "net.peer.name", "net.host.name") ?? hostFromUrl(pickAttr(span, "url.full", "http.url"));
|
|
1390
1629
|
}
|
|
1391
1630
|
function makeObservedEdgeId(type, source, target) {
|
|
1392
|
-
return (0,
|
|
1631
|
+
return (0, import_types4.observedEdgeId)(source, target, type);
|
|
1393
1632
|
}
|
|
1394
1633
|
function makeInferredEdgeId(type, source, target) {
|
|
1395
|
-
return (0,
|
|
1634
|
+
return (0, import_types4.inferredEdgeId)(source, target, type);
|
|
1396
1635
|
}
|
|
1397
1636
|
var INFERRED_CONFIDENCE = 0.6;
|
|
1398
1637
|
var STITCH_MAX_DEPTH = 2;
|
|
@@ -1423,13 +1662,13 @@ function lookupParentSpanService(traceId, parentSpanId, now) {
|
|
|
1423
1662
|
return entry.service;
|
|
1424
1663
|
}
|
|
1425
1664
|
function resolveServiceId(graph, host) {
|
|
1426
|
-
const direct = (0,
|
|
1665
|
+
const direct = (0, import_types4.serviceId)(host);
|
|
1427
1666
|
if (graph.hasNode(direct)) return direct;
|
|
1428
1667
|
let found = null;
|
|
1429
1668
|
graph.forEachNode((id, attrs) => {
|
|
1430
1669
|
if (found) return;
|
|
1431
1670
|
const a = attrs;
|
|
1432
|
-
if (a.type !==
|
|
1671
|
+
if (a.type !== import_types4.NodeType.ServiceNode) return;
|
|
1433
1672
|
if (a.name === host) {
|
|
1434
1673
|
found = id;
|
|
1435
1674
|
return;
|
|
@@ -1441,14 +1680,14 @@ function resolveServiceId(graph, host) {
|
|
|
1441
1680
|
return found;
|
|
1442
1681
|
}
|
|
1443
1682
|
function frontierIdFor(host) {
|
|
1444
|
-
return (0,
|
|
1683
|
+
return (0, import_types4.frontierId)(host);
|
|
1445
1684
|
}
|
|
1446
1685
|
function ensureServiceNode(graph, serviceName) {
|
|
1447
|
-
const id = (0,
|
|
1686
|
+
const id = (0, import_types4.serviceId)(serviceName);
|
|
1448
1687
|
if (graph.hasNode(id)) return id;
|
|
1449
1688
|
const node = {
|
|
1450
1689
|
id,
|
|
1451
|
-
type:
|
|
1690
|
+
type: import_types4.NodeType.ServiceNode,
|
|
1452
1691
|
name: serviceName,
|
|
1453
1692
|
language: "unknown",
|
|
1454
1693
|
discoveredVia: "otel"
|
|
@@ -1457,11 +1696,11 @@ function ensureServiceNode(graph, serviceName) {
|
|
|
1457
1696
|
return id;
|
|
1458
1697
|
}
|
|
1459
1698
|
function ensureDatabaseNode(graph, host, engine) {
|
|
1460
|
-
const id = (0,
|
|
1699
|
+
const id = (0, import_types4.databaseId)(host);
|
|
1461
1700
|
if (graph.hasNode(id)) return id;
|
|
1462
1701
|
const node = {
|
|
1463
1702
|
id,
|
|
1464
|
-
type:
|
|
1703
|
+
type: import_types4.NodeType.DatabaseNode,
|
|
1465
1704
|
name: host,
|
|
1466
1705
|
engine,
|
|
1467
1706
|
engineVersion: "unknown",
|
|
@@ -1481,7 +1720,7 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
1481
1720
|
}
|
|
1482
1721
|
const node = {
|
|
1483
1722
|
id,
|
|
1484
|
-
type:
|
|
1723
|
+
type: import_types4.NodeType.FrontierNode,
|
|
1485
1724
|
name: host,
|
|
1486
1725
|
host,
|
|
1487
1726
|
firstObserved: ts,
|
|
@@ -1491,12 +1730,12 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
1491
1730
|
return id;
|
|
1492
1731
|
}
|
|
1493
1732
|
function upsertFrontierEdge(graph, type, source, target, ts) {
|
|
1494
|
-
const id = (0,
|
|
1733
|
+
const id = (0, import_types4.frontierEdgeId)(source, target, type);
|
|
1495
1734
|
if (graph.hasEdge(id)) {
|
|
1496
1735
|
const existing = graph.getEdgeAttributes(id);
|
|
1497
1736
|
const updated = {
|
|
1498
1737
|
...existing,
|
|
1499
|
-
provenance:
|
|
1738
|
+
provenance: import_types4.Provenance.FRONTIER,
|
|
1500
1739
|
lastObserved: ts,
|
|
1501
1740
|
callCount: (existing.callCount ?? 0) + 1
|
|
1502
1741
|
};
|
|
@@ -1508,7 +1747,7 @@ function upsertFrontierEdge(graph, type, source, target, ts) {
|
|
|
1508
1747
|
source,
|
|
1509
1748
|
target,
|
|
1510
1749
|
type,
|
|
1511
|
-
provenance:
|
|
1750
|
+
provenance: import_types4.Provenance.FRONTIER,
|
|
1512
1751
|
confidence: 1,
|
|
1513
1752
|
lastObserved: ts,
|
|
1514
1753
|
callCount: 1
|
|
@@ -1524,7 +1763,7 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
|
1524
1763
|
const newErrorCount = (existing.signal?.errorCount ?? 0) + (isError ? 1 : 0);
|
|
1525
1764
|
const updated = {
|
|
1526
1765
|
...existing,
|
|
1527
|
-
provenance:
|
|
1766
|
+
provenance: import_types4.Provenance.OBSERVED,
|
|
1528
1767
|
lastObserved: ts,
|
|
1529
1768
|
callCount: newSpanCount,
|
|
1530
1769
|
signal: {
|
|
@@ -1542,7 +1781,7 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
|
1542
1781
|
source,
|
|
1543
1782
|
target,
|
|
1544
1783
|
type,
|
|
1545
|
-
provenance:
|
|
1784
|
+
provenance: import_types4.Provenance.OBSERVED,
|
|
1546
1785
|
confidence: 1,
|
|
1547
1786
|
lastObserved: ts,
|
|
1548
1787
|
callCount: 1,
|
|
@@ -1565,8 +1804,8 @@ function stitchTrace(graph, sourceServiceId, ts) {
|
|
|
1565
1804
|
const outbound = graph.outboundEdges(nodeId);
|
|
1566
1805
|
for (const edgeId of outbound) {
|
|
1567
1806
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
1568
|
-
if (edge.provenance !==
|
|
1569
|
-
if (graph.hasEdge((0,
|
|
1807
|
+
if (edge.provenance !== import_types4.Provenance.EXTRACTED) continue;
|
|
1808
|
+
if (graph.hasEdge((0, import_types4.observedEdgeId)(edge.source, edge.target, edge.type))) continue;
|
|
1570
1809
|
upsertInferredEdge(graph, edge.type, edge.source, edge.target, ts);
|
|
1571
1810
|
if (!visited.has(edge.target)) {
|
|
1572
1811
|
visited.add(edge.target);
|
|
@@ -1588,7 +1827,7 @@ function upsertInferredEdge(graph, type, source, target, ts) {
|
|
|
1588
1827
|
source,
|
|
1589
1828
|
target,
|
|
1590
1829
|
type,
|
|
1591
|
-
provenance:
|
|
1830
|
+
provenance: import_types4.Provenance.INFERRED,
|
|
1592
1831
|
confidence: INFERRED_CONFIDENCE,
|
|
1593
1832
|
lastObserved: ts
|
|
1594
1833
|
};
|
|
@@ -1609,10 +1848,10 @@ async function handleSpan(ctx, span) {
|
|
|
1609
1848
|
const host = pickAddress(span);
|
|
1610
1849
|
if (host) {
|
|
1611
1850
|
ensureDatabaseNode(ctx.graph, host, span.dbSystem);
|
|
1612
|
-
const targetId = (0,
|
|
1851
|
+
const targetId = (0, import_types4.databaseId)(host);
|
|
1613
1852
|
const result = upsertObservedEdge(
|
|
1614
1853
|
ctx.graph,
|
|
1615
|
-
|
|
1854
|
+
import_types4.EdgeType.CONNECTS_TO,
|
|
1616
1855
|
sourceId,
|
|
1617
1856
|
targetId,
|
|
1618
1857
|
ts,
|
|
@@ -1628,7 +1867,7 @@ async function handleSpan(ctx, span) {
|
|
|
1628
1867
|
if (targetId && targetId !== sourceId) {
|
|
1629
1868
|
upsertObservedEdge(
|
|
1630
1869
|
ctx.graph,
|
|
1631
|
-
|
|
1870
|
+
import_types4.EdgeType.CALLS,
|
|
1632
1871
|
sourceId,
|
|
1633
1872
|
targetId,
|
|
1634
1873
|
ts,
|
|
@@ -1639,7 +1878,7 @@ async function handleSpan(ctx, span) {
|
|
|
1639
1878
|
} else if (!targetId) {
|
|
1640
1879
|
const frontierId2 = ensureFrontierNode(ctx.graph, host, ts);
|
|
1641
1880
|
if (ctx.graph.hasNode(sourceId)) {
|
|
1642
|
-
upsertFrontierEdge(ctx.graph,
|
|
1881
|
+
upsertFrontierEdge(ctx.graph, import_types4.EdgeType.CALLS, sourceId, frontierId2, ts);
|
|
1643
1882
|
}
|
|
1644
1883
|
affectedNode = frontierId2;
|
|
1645
1884
|
resolvedViaAddress = true;
|
|
@@ -1651,7 +1890,7 @@ async function handleSpan(ctx, span) {
|
|
|
1651
1890
|
const parentId = ensureServiceNode(ctx.graph, parentService);
|
|
1652
1891
|
upsertObservedEdge(
|
|
1653
1892
|
ctx.graph,
|
|
1654
|
-
|
|
1893
|
+
import_types4.EdgeType.CALLS,
|
|
1655
1894
|
parentId,
|
|
1656
1895
|
sourceId,
|
|
1657
1896
|
ts,
|
|
@@ -1684,7 +1923,7 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
1684
1923
|
const aliasIndex = /* @__PURE__ */ new Map();
|
|
1685
1924
|
graph.forEachNode((id, attrs) => {
|
|
1686
1925
|
const a = attrs;
|
|
1687
|
-
if (a.type !==
|
|
1926
|
+
if (a.type !== import_types4.NodeType.ServiceNode) return;
|
|
1688
1927
|
aliasIndex.set(a.name, id);
|
|
1689
1928
|
if (a.aliases) {
|
|
1690
1929
|
for (const alias of a.aliases) aliasIndex.set(alias, id);
|
|
@@ -1693,7 +1932,7 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
1693
1932
|
const toPromote = [];
|
|
1694
1933
|
graph.forEachNode((id, attrs) => {
|
|
1695
1934
|
const a = attrs;
|
|
1696
|
-
if (a.type !==
|
|
1935
|
+
if (a.type !== import_types4.NodeType.FrontierNode) return;
|
|
1697
1936
|
const target = aliasIndex.get(a.host);
|
|
1698
1937
|
if (!target) return;
|
|
1699
1938
|
if (target === id) return;
|
|
@@ -1727,8 +1966,8 @@ function rewireFrontierEdges(graph, frontierId2, serviceId3) {
|
|
|
1727
1966
|
}
|
|
1728
1967
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
1729
1968
|
graph.dropEdge(oldEdgeId);
|
|
1730
|
-
const promotedProvenance = edge.provenance ===
|
|
1731
|
-
const newId = promotedProvenance ===
|
|
1969
|
+
const promotedProvenance = edge.provenance === import_types4.Provenance.FRONTIER ? import_types4.Provenance.OBSERVED : edge.provenance;
|
|
1970
|
+
const newId = promotedProvenance === import_types4.Provenance.OBSERVED ? (0, import_types4.observedEdgeId)(newSource, newTarget, edge.type) : promotedProvenance === import_types4.Provenance.INFERRED ? (0, import_types4.inferredEdgeId)(newSource, newTarget, edge.type) : promotedProvenance === import_types4.Provenance.EXTRACTED ? (0, import_types4.extractedEdgeId)(newSource, newTarget, edge.type) : (0, import_types4.frontierEdgeId)(newSource, newTarget, edge.type);
|
|
1732
1971
|
if (graph.hasEdge(newId)) {
|
|
1733
1972
|
const existing = graph.getEdgeAttributes(newId);
|
|
1734
1973
|
const merged = {
|
|
@@ -1763,12 +2002,12 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
1763
2002
|
const project = options.project ?? DEFAULT_PROJECT;
|
|
1764
2003
|
graph.forEachEdge((id, attrs) => {
|
|
1765
2004
|
const e = attrs;
|
|
1766
|
-
if (e.provenance !==
|
|
2005
|
+
if (e.provenance !== import_types4.Provenance.OBSERVED) return;
|
|
1767
2006
|
if (!e.lastObserved) return;
|
|
1768
2007
|
const threshold = thresholdForEdgeType(e.type, thresholds);
|
|
1769
2008
|
const age = now - new Date(e.lastObserved).getTime();
|
|
1770
2009
|
if (age > threshold) {
|
|
1771
|
-
const updated = { ...e, provenance:
|
|
2010
|
+
const updated = { ...e, provenance: import_types4.Provenance.STALE, confidence: 0.3 };
|
|
1772
2011
|
graph.replaceEdgeAttributes(id, updated);
|
|
1773
2012
|
events.push({
|
|
1774
2013
|
edgeId: id,
|
|
@@ -1785,8 +2024,8 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
1785
2024
|
project,
|
|
1786
2025
|
payload: {
|
|
1787
2026
|
edgeId: id,
|
|
1788
|
-
from:
|
|
1789
|
-
to:
|
|
2027
|
+
from: import_types4.Provenance.OBSERVED,
|
|
2028
|
+
to: import_types4.Provenance.STALE
|
|
1790
2029
|
}
|
|
1791
2030
|
});
|
|
1792
2031
|
}
|
|
@@ -1851,14 +2090,14 @@ var import_node_fs7 = require("fs");
|
|
|
1851
2090
|
var import_node_path7 = __toESM(require("path"), 1);
|
|
1852
2091
|
var import_ignore = __toESM(require("ignore"), 1);
|
|
1853
2092
|
var import_minimatch2 = require("minimatch");
|
|
1854
|
-
var
|
|
2093
|
+
var import_types6 = require("@neat.is/types");
|
|
1855
2094
|
|
|
1856
2095
|
// src/extract/shared.ts
|
|
1857
2096
|
init_cjs_shims();
|
|
1858
2097
|
var import_node_fs4 = require("fs");
|
|
1859
2098
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
1860
2099
|
var import_yaml = require("yaml");
|
|
1861
|
-
var
|
|
2100
|
+
var import_types5 = require("@neat.is/types");
|
|
1862
2101
|
var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py"]);
|
|
1863
2102
|
var CONFIG_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml"]);
|
|
1864
2103
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -2112,8 +2351,8 @@ async function discoverNodeService(scanPath, dir) {
|
|
|
2112
2351
|
}
|
|
2113
2352
|
if (!pkg.name) return null;
|
|
2114
2353
|
const node = {
|
|
2115
|
-
id: (0,
|
|
2116
|
-
type:
|
|
2354
|
+
id: (0, import_types6.serviceId)(pkg.name),
|
|
2355
|
+
type: import_types6.NodeType.ServiceNode,
|
|
2117
2356
|
name: pkg.name,
|
|
2118
2357
|
language: "javascript",
|
|
2119
2358
|
version: pkg.version,
|
|
@@ -2128,8 +2367,8 @@ async function discoverPyService(scanPath, dir) {
|
|
|
2128
2367
|
if (!py) return null;
|
|
2129
2368
|
const pkg = pythonToPackage(py);
|
|
2130
2369
|
const node = {
|
|
2131
|
-
id: (0,
|
|
2132
|
-
type:
|
|
2370
|
+
id: (0, import_types6.serviceId)(py.name),
|
|
2371
|
+
type: import_types6.NodeType.ServiceNode,
|
|
2133
2372
|
name: py.name,
|
|
2134
2373
|
language: "python",
|
|
2135
2374
|
version: py.version,
|
|
@@ -2219,7 +2458,7 @@ init_cjs_shims();
|
|
|
2219
2458
|
var import_node_path8 = __toESM(require("path"), 1);
|
|
2220
2459
|
var import_node_fs8 = require("fs");
|
|
2221
2460
|
var import_yaml2 = require("yaml");
|
|
2222
|
-
var
|
|
2461
|
+
var import_types7 = require("@neat.is/types");
|
|
2223
2462
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
2224
2463
|
"Service",
|
|
2225
2464
|
"Deployment",
|
|
@@ -2229,7 +2468,7 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
|
2229
2468
|
function addAliases(graph, serviceId3, candidates) {
|
|
2230
2469
|
if (!graph.hasNode(serviceId3)) return;
|
|
2231
2470
|
const node = graph.getNodeAttributes(serviceId3);
|
|
2232
|
-
if (node.type !==
|
|
2471
|
+
if (node.type !== import_types7.NodeType.ServiceNode) return;
|
|
2233
2472
|
const set = new Set(node.aliases ?? []);
|
|
2234
2473
|
for (const c of candidates) {
|
|
2235
2474
|
if (!c) continue;
|
|
@@ -2382,7 +2621,7 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
2382
2621
|
// src/extract/databases/index.ts
|
|
2383
2622
|
init_cjs_shims();
|
|
2384
2623
|
var import_node_path16 = __toESM(require("path"), 1);
|
|
2385
|
-
var
|
|
2624
|
+
var import_types8 = require("@neat.is/types");
|
|
2386
2625
|
|
|
2387
2626
|
// src/extract/databases/db-config-yaml.ts
|
|
2388
2627
|
init_cjs_shims();
|
|
@@ -2830,8 +3069,8 @@ function compatibleDriversFor(engine) {
|
|
|
2830
3069
|
}
|
|
2831
3070
|
function toDatabaseNode(config) {
|
|
2832
3071
|
return {
|
|
2833
|
-
id: (0,
|
|
2834
|
-
type:
|
|
3072
|
+
id: (0, import_types8.databaseId)(config.host),
|
|
3073
|
+
type: import_types8.NodeType.DatabaseNode,
|
|
2835
3074
|
name: config.database || config.host,
|
|
2836
3075
|
engine: config.engine,
|
|
2837
3076
|
engineVersion: config.engineVersion,
|
|
@@ -2962,11 +3201,11 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
2962
3201
|
});
|
|
2963
3202
|
}
|
|
2964
3203
|
const edge = {
|
|
2965
|
-
id: (0,
|
|
3204
|
+
id: (0, import_types5.extractedEdgeId)(service.node.id, dbNode.id, import_types8.EdgeType.CONNECTS_TO),
|
|
2966
3205
|
source: service.node.id,
|
|
2967
3206
|
target: dbNode.id,
|
|
2968
|
-
type:
|
|
2969
|
-
provenance:
|
|
3207
|
+
type: import_types8.EdgeType.CONNECTS_TO,
|
|
3208
|
+
provenance: import_types8.Provenance.EXTRACTED,
|
|
2970
3209
|
...config.sourceFile ? {
|
|
2971
3210
|
evidence: {
|
|
2972
3211
|
file: import_node_path16.default.relative(scanPath, config.sourceFile).split(import_node_path16.default.sep).join("/")
|
|
@@ -2999,7 +3238,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
2999
3238
|
init_cjs_shims();
|
|
3000
3239
|
var import_node_fs11 = require("fs");
|
|
3001
3240
|
var import_node_path17 = __toESM(require("path"), 1);
|
|
3002
|
-
var
|
|
3241
|
+
var import_types9 = require("@neat.is/types");
|
|
3003
3242
|
async function walkConfigFiles(dir) {
|
|
3004
3243
|
const out = [];
|
|
3005
3244
|
async function walk(current) {
|
|
@@ -3024,8 +3263,8 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3024
3263
|
for (const file of configFiles) {
|
|
3025
3264
|
const relPath = import_node_path17.default.relative(scanPath, file);
|
|
3026
3265
|
const node = {
|
|
3027
|
-
id: (0,
|
|
3028
|
-
type:
|
|
3266
|
+
id: (0, import_types9.configId)(relPath),
|
|
3267
|
+
type: import_types9.NodeType.ConfigNode,
|
|
3029
3268
|
name: import_node_path17.default.basename(file),
|
|
3030
3269
|
path: relPath,
|
|
3031
3270
|
fileType: isConfigFile(import_node_path17.default.basename(file)).fileType
|
|
@@ -3035,11 +3274,11 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3035
3274
|
nodesAdded++;
|
|
3036
3275
|
}
|
|
3037
3276
|
const edge = {
|
|
3038
|
-
id: (0,
|
|
3277
|
+
id: (0, import_types5.extractedEdgeId)(service.node.id, node.id, import_types9.EdgeType.CONFIGURED_BY),
|
|
3039
3278
|
source: service.node.id,
|
|
3040
3279
|
target: node.id,
|
|
3041
|
-
type:
|
|
3042
|
-
provenance:
|
|
3280
|
+
type: import_types9.EdgeType.CONFIGURED_BY,
|
|
3281
|
+
provenance: import_types9.Provenance.EXTRACTED,
|
|
3043
3282
|
evidence: { file: relPath.split(import_node_path17.default.sep).join("/") }
|
|
3044
3283
|
};
|
|
3045
3284
|
if (!graph.hasEdge(edge.id)) {
|
|
@@ -3053,7 +3292,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3053
3292
|
|
|
3054
3293
|
// src/extract/calls/index.ts
|
|
3055
3294
|
init_cjs_shims();
|
|
3056
|
-
var
|
|
3295
|
+
var import_types15 = require("@neat.is/types");
|
|
3057
3296
|
|
|
3058
3297
|
// src/extract/calls/http.ts
|
|
3059
3298
|
init_cjs_shims();
|
|
@@ -3061,7 +3300,7 @@ var import_node_path19 = __toESM(require("path"), 1);
|
|
|
3061
3300
|
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
3062
3301
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3063
3302
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
3064
|
-
var
|
|
3303
|
+
var import_types10 = require("@neat.is/types");
|
|
3065
3304
|
|
|
3066
3305
|
// src/extract/calls/shared.ts
|
|
3067
3306
|
init_cjs_shims();
|
|
@@ -3176,11 +3415,11 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3176
3415
|
const fileContent = files.find((f) => f.path === evidenceFile.file)?.content ?? "";
|
|
3177
3416
|
const line = lineOf(fileContent, `//${evidenceFile.host}`);
|
|
3178
3417
|
const edge = {
|
|
3179
|
-
id: (0,
|
|
3418
|
+
id: (0, import_types5.extractedEdgeId)(service.node.id, targetId, import_types10.EdgeType.CALLS),
|
|
3180
3419
|
source: service.node.id,
|
|
3181
3420
|
target: targetId,
|
|
3182
|
-
type:
|
|
3183
|
-
provenance:
|
|
3421
|
+
type: import_types10.EdgeType.CALLS,
|
|
3422
|
+
provenance: import_types10.Provenance.EXTRACTED,
|
|
3184
3423
|
evidence: {
|
|
3185
3424
|
file: import_node_path19.default.relative(service.dir, evidenceFile.file),
|
|
3186
3425
|
line,
|
|
@@ -3199,7 +3438,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3199
3438
|
// src/extract/calls/kafka.ts
|
|
3200
3439
|
init_cjs_shims();
|
|
3201
3440
|
var import_node_path20 = __toESM(require("path"), 1);
|
|
3202
|
-
var
|
|
3441
|
+
var import_types11 = require("@neat.is/types");
|
|
3203
3442
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3204
3443
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
3205
3444
|
function findAll(re, text) {
|
|
@@ -3220,7 +3459,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3220
3459
|
seen.add(key);
|
|
3221
3460
|
const line = lineOf(file.content, topic);
|
|
3222
3461
|
out.push({
|
|
3223
|
-
infraId: (0,
|
|
3462
|
+
infraId: (0, import_types11.infraId)("kafka-topic", topic),
|
|
3224
3463
|
name: topic,
|
|
3225
3464
|
kind: "kafka-topic",
|
|
3226
3465
|
edgeType,
|
|
@@ -3239,7 +3478,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3239
3478
|
// src/extract/calls/redis.ts
|
|
3240
3479
|
init_cjs_shims();
|
|
3241
3480
|
var import_node_path21 = __toESM(require("path"), 1);
|
|
3242
|
-
var
|
|
3481
|
+
var import_types12 = require("@neat.is/types");
|
|
3243
3482
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
3244
3483
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
3245
3484
|
const out = [];
|
|
@@ -3252,7 +3491,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3252
3491
|
seen.add(host);
|
|
3253
3492
|
const line = lineOf(file.content, host);
|
|
3254
3493
|
out.push({
|
|
3255
|
-
infraId: (0,
|
|
3494
|
+
infraId: (0, import_types12.infraId)("redis", host),
|
|
3256
3495
|
name: host,
|
|
3257
3496
|
kind: "redis",
|
|
3258
3497
|
edgeType: "CALLS",
|
|
@@ -3269,7 +3508,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3269
3508
|
// src/extract/calls/aws.ts
|
|
3270
3509
|
init_cjs_shims();
|
|
3271
3510
|
var import_node_path22 = __toESM(require("path"), 1);
|
|
3272
|
-
var
|
|
3511
|
+
var import_types13 = require("@neat.is/types");
|
|
3273
3512
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3274
3513
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3275
3514
|
function hasMarker(text, markers) {
|
|
@@ -3293,7 +3532,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3293
3532
|
seen.add(key);
|
|
3294
3533
|
const line = lineOf(file.content, name);
|
|
3295
3534
|
out.push({
|
|
3296
|
-
infraId: (0,
|
|
3535
|
+
infraId: (0, import_types13.infraId)(kind, name),
|
|
3297
3536
|
name,
|
|
3298
3537
|
kind,
|
|
3299
3538
|
edgeType: "CALLS",
|
|
@@ -3324,7 +3563,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3324
3563
|
// src/extract/calls/grpc.ts
|
|
3325
3564
|
init_cjs_shims();
|
|
3326
3565
|
var import_node_path23 = __toESM(require("path"), 1);
|
|
3327
|
-
var
|
|
3566
|
+
var import_types14 = require("@neat.is/types");
|
|
3328
3567
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
3329
3568
|
function isLikelyAddress(value) {
|
|
3330
3569
|
if (!value) return false;
|
|
@@ -3343,7 +3582,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3343
3582
|
seen.add(name);
|
|
3344
3583
|
const line = lineOf(file.content, m[0]);
|
|
3345
3584
|
out.push({
|
|
3346
|
-
infraId: (0,
|
|
3585
|
+
infraId: (0, import_types14.infraId)("grpc-service", name),
|
|
3347
3586
|
name,
|
|
3348
3587
|
kind: "grpc-service",
|
|
3349
3588
|
edgeType: "CALLS",
|
|
@@ -3361,11 +3600,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3361
3600
|
function edgeTypeFromEndpoint(ep) {
|
|
3362
3601
|
switch (ep.edgeType) {
|
|
3363
3602
|
case "PUBLISHES_TO":
|
|
3364
|
-
return
|
|
3603
|
+
return import_types15.EdgeType.PUBLISHES_TO;
|
|
3365
3604
|
case "CONSUMES_FROM":
|
|
3366
|
-
return
|
|
3605
|
+
return import_types15.EdgeType.CONSUMES_FROM;
|
|
3367
3606
|
default:
|
|
3368
|
-
return
|
|
3607
|
+
return import_types15.EdgeType.CALLS;
|
|
3369
3608
|
}
|
|
3370
3609
|
}
|
|
3371
3610
|
async function addExternalEndpointEdges(graph, services) {
|
|
@@ -3386,7 +3625,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3386
3625
|
if (!graph.hasNode(ep.infraId)) {
|
|
3387
3626
|
const node = {
|
|
3388
3627
|
id: ep.infraId,
|
|
3389
|
-
type:
|
|
3628
|
+
type: import_types15.NodeType.InfraNode,
|
|
3390
3629
|
name: ep.name,
|
|
3391
3630
|
provider: ep.kind.startsWith("s3") || ep.kind.startsWith("dynamodb") ? "aws" : "self",
|
|
3392
3631
|
kind: ep.kind
|
|
@@ -3395,7 +3634,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3395
3634
|
nodesAdded++;
|
|
3396
3635
|
}
|
|
3397
3636
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
3398
|
-
const edgeId = (0,
|
|
3637
|
+
const edgeId = (0, import_types5.extractedEdgeId)(service.node.id, ep.infraId, edgeType);
|
|
3399
3638
|
if (seenEdges.has(edgeId)) continue;
|
|
3400
3639
|
seenEdges.add(edgeId);
|
|
3401
3640
|
if (!graph.hasEdge(edgeId)) {
|
|
@@ -3404,7 +3643,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3404
3643
|
source: service.node.id,
|
|
3405
3644
|
target: ep.infraId,
|
|
3406
3645
|
type: edgeType,
|
|
3407
|
-
provenance:
|
|
3646
|
+
provenance: import_types15.Provenance.EXTRACTED,
|
|
3408
3647
|
evidence: ep.evidence
|
|
3409
3648
|
};
|
|
3410
3649
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3429,15 +3668,15 @@ init_cjs_shims();
|
|
|
3429
3668
|
// src/extract/infra/docker-compose.ts
|
|
3430
3669
|
init_cjs_shims();
|
|
3431
3670
|
var import_node_path24 = __toESM(require("path"), 1);
|
|
3432
|
-
var
|
|
3671
|
+
var import_types17 = require("@neat.is/types");
|
|
3433
3672
|
|
|
3434
3673
|
// src/extract/infra/shared.ts
|
|
3435
3674
|
init_cjs_shims();
|
|
3436
|
-
var
|
|
3675
|
+
var import_types16 = require("@neat.is/types");
|
|
3437
3676
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
3438
3677
|
return {
|
|
3439
|
-
id: (0,
|
|
3440
|
-
type:
|
|
3678
|
+
id: (0, import_types16.infraId)(kind, name),
|
|
3679
|
+
type: import_types16.NodeType.InfraNode,
|
|
3441
3680
|
name,
|
|
3442
3681
|
provider,
|
|
3443
3682
|
kind,
|
|
@@ -3514,14 +3753,14 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3514
3753
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
3515
3754
|
const targetId = composeNameToNodeId.get(dep);
|
|
3516
3755
|
if (!targetId) continue;
|
|
3517
|
-
const edgeId = (0,
|
|
3756
|
+
const edgeId = (0, import_types5.extractedEdgeId)(sourceId, targetId, import_types17.EdgeType.DEPENDS_ON);
|
|
3518
3757
|
if (graph.hasEdge(edgeId)) continue;
|
|
3519
3758
|
const edge = {
|
|
3520
3759
|
id: edgeId,
|
|
3521
3760
|
source: sourceId,
|
|
3522
3761
|
target: targetId,
|
|
3523
|
-
type:
|
|
3524
|
-
provenance:
|
|
3762
|
+
type: import_types17.EdgeType.DEPENDS_ON,
|
|
3763
|
+
provenance: import_types17.Provenance.EXTRACTED,
|
|
3525
3764
|
evidence: { file: evidenceFile }
|
|
3526
3765
|
};
|
|
3527
3766
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3535,7 +3774,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3535
3774
|
init_cjs_shims();
|
|
3536
3775
|
var import_node_path25 = __toESM(require("path"), 1);
|
|
3537
3776
|
var import_node_fs13 = require("fs");
|
|
3538
|
-
var
|
|
3777
|
+
var import_types18 = require("@neat.is/types");
|
|
3539
3778
|
function runtimeImage(content) {
|
|
3540
3779
|
const lines = content.split("\n");
|
|
3541
3780
|
let last = null;
|
|
@@ -3572,14 +3811,14 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3572
3811
|
graph.addNode(node.id, node);
|
|
3573
3812
|
nodesAdded++;
|
|
3574
3813
|
}
|
|
3575
|
-
const edgeId = (0,
|
|
3814
|
+
const edgeId = (0, import_types5.extractedEdgeId)(service.node.id, node.id, import_types18.EdgeType.RUNS_ON);
|
|
3576
3815
|
if (!graph.hasEdge(edgeId)) {
|
|
3577
3816
|
const edge = {
|
|
3578
3817
|
id: edgeId,
|
|
3579
3818
|
source: service.node.id,
|
|
3580
3819
|
target: node.id,
|
|
3581
|
-
type:
|
|
3582
|
-
provenance:
|
|
3820
|
+
type: import_types18.EdgeType.RUNS_ON,
|
|
3821
|
+
provenance: import_types18.Provenance.EXTRACTED,
|
|
3583
3822
|
evidence: {
|
|
3584
3823
|
file: import_node_path25.default.relative(scanPath, dockerfilePath).split(import_node_path25.default.sep).join("/")
|
|
3585
3824
|
}
|
|
@@ -3864,7 +4103,7 @@ init_cjs_shims();
|
|
|
3864
4103
|
var import_node_fs17 = require("fs");
|
|
3865
4104
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
3866
4105
|
var import_node_path29 = __toESM(require("path"), 1);
|
|
3867
|
-
var
|
|
4106
|
+
var import_types19 = require("@neat.is/types");
|
|
3868
4107
|
function neatHome() {
|
|
3869
4108
|
const override = process.env.NEAT_HOME;
|
|
3870
4109
|
if (override && override.length > 0) return import_node_path29.default.resolve(override);
|
|
@@ -3885,7 +4124,7 @@ async function readRegistry() {
|
|
|
3885
4124
|
throw err;
|
|
3886
4125
|
}
|
|
3887
4126
|
const parsed = JSON.parse(raw);
|
|
3888
|
-
return
|
|
4127
|
+
return import_types19.RegistryFileSchema.parse(parsed);
|
|
3889
4128
|
}
|
|
3890
4129
|
async function listProjects() {
|
|
3891
4130
|
const reg = await readRegistry();
|
|
@@ -4057,6 +4296,41 @@ function registerRoutes(scope, ctx) {
|
|
|
4057
4296
|
}
|
|
4058
4297
|
return getTransitiveDependencies(proj.graph, id, depth);
|
|
4059
4298
|
});
|
|
4299
|
+
scope.get("/graph/divergences", async (req, reply) => {
|
|
4300
|
+
const proj = resolveProject(registry, req, reply);
|
|
4301
|
+
if (!proj) return;
|
|
4302
|
+
let typeFilter;
|
|
4303
|
+
if (req.query.type) {
|
|
4304
|
+
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
4305
|
+
const parsed = [];
|
|
4306
|
+
for (const c of candidates) {
|
|
4307
|
+
const r = import_types20.DivergenceTypeSchema.safeParse(c);
|
|
4308
|
+
if (!r.success) {
|
|
4309
|
+
return reply.code(400).send({
|
|
4310
|
+
error: `unknown divergence type "${c}"`,
|
|
4311
|
+
allowed: import_types20.DivergenceTypeSchema.options
|
|
4312
|
+
});
|
|
4313
|
+
}
|
|
4314
|
+
parsed.push(r.data);
|
|
4315
|
+
}
|
|
4316
|
+
typeFilter = new Set(parsed);
|
|
4317
|
+
}
|
|
4318
|
+
let minConfidence;
|
|
4319
|
+
if (req.query.minConfidence !== void 0) {
|
|
4320
|
+
const n = Number(req.query.minConfidence);
|
|
4321
|
+
if (!Number.isFinite(n) || n < 0 || n > 1) {
|
|
4322
|
+
return reply.code(400).send({
|
|
4323
|
+
error: "minConfidence must be a number in [0, 1]"
|
|
4324
|
+
});
|
|
4325
|
+
}
|
|
4326
|
+
minConfidence = n;
|
|
4327
|
+
}
|
|
4328
|
+
return computeDivergences(proj.graph, {
|
|
4329
|
+
...typeFilter ? { type: typeFilter } : {},
|
|
4330
|
+
...minConfidence !== void 0 ? { minConfidence } : {},
|
|
4331
|
+
...req.query.node ? { node: req.query.node } : {}
|
|
4332
|
+
});
|
|
4333
|
+
});
|
|
4060
4334
|
scope.get("/incidents", async (req, reply) => {
|
|
4061
4335
|
const proj = resolveProject(registry, req, reply);
|
|
4062
4336
|
if (!proj) return;
|
|
@@ -4210,7 +4484,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4210
4484
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
4211
4485
|
let violations = await log.readAll();
|
|
4212
4486
|
if (req.query.severity) {
|
|
4213
|
-
const sev =
|
|
4487
|
+
const sev = import_types20.PolicySeveritySchema.safeParse(req.query.severity);
|
|
4214
4488
|
if (!sev.success) {
|
|
4215
4489
|
return reply.code(400).send({
|
|
4216
4490
|
error: "invalid severity",
|
|
@@ -4227,7 +4501,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4227
4501
|
scope.post("/policies/check", async (req, reply) => {
|
|
4228
4502
|
const proj = resolveProject(registry, req, reply);
|
|
4229
4503
|
if (!proj) return;
|
|
4230
|
-
const parsed =
|
|
4504
|
+
const parsed = import_types20.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
4231
4505
|
if (!parsed.success) {
|
|
4232
4506
|
return reply.code(400).send({
|
|
4233
4507
|
error: "invalid /policies/check body",
|