@neat.is/core 0.3.4 → 0.3.6
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-T3A2GK3X.js → chunk-YHQYHFI3.js} +2 -2
- package/dist/{chunk-33ZZ2ZID.js → chunk-ZU2RQRCN.js} +94 -83
- package/dist/chunk-ZU2RQRCN.js.map +1 -0
- package/dist/cli.cjs +433 -139
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +339 -60
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +84 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +165 -70
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +82 -2
- package/dist/neatd.js.map +1 -1
- package/dist/server.cjs +57 -45
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-33ZZ2ZID.js.map +0 -1
- /package/dist/{chunk-T3A2GK3X.js.map → chunk-YHQYHFI3.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
routeSpanToProject,
|
|
3
3
|
startDaemon
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-YHQYHFI3.js";
|
|
5
5
|
import {
|
|
6
6
|
ProjectNameCollisionError,
|
|
7
7
|
addProject,
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
thresholdForEdgeType,
|
|
38
38
|
touchLastSeen,
|
|
39
39
|
writeAtomically
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-ZU2RQRCN.js";
|
|
41
41
|
import {
|
|
42
42
|
startOtelGrpcReceiver
|
|
43
43
|
} from "./chunk-G3PDTGOW.js";
|
package/dist/neatd.cjs
CHANGED
|
@@ -371,6 +371,7 @@ var init_otel = __esm({
|
|
|
371
371
|
init_cjs_shims();
|
|
372
372
|
var import_node_fs22 = require("fs");
|
|
373
373
|
var import_node_path38 = __toESM(require("path"), 1);
|
|
374
|
+
var import_node_module = require("module");
|
|
374
375
|
|
|
375
376
|
// src/daemon.ts
|
|
376
377
|
init_cjs_shims();
|
|
@@ -749,11 +750,16 @@ init_cjs_shims();
|
|
|
749
750
|
var import_types = require("@neat.is/types");
|
|
750
751
|
var ROOT_CAUSE_MAX_DEPTH = 5;
|
|
751
752
|
var BLAST_RADIUS_DEFAULT_DEPTH = 10;
|
|
753
|
+
function isFrontierNode(graph, nodeId) {
|
|
754
|
+
if (!graph.hasNode(nodeId)) return false;
|
|
755
|
+
const attrs = graph.getNodeAttributes(nodeId);
|
|
756
|
+
return attrs.type === import_types.NodeType.FrontierNode;
|
|
757
|
+
}
|
|
752
758
|
function bestEdgeBySource(graph, edgeIds) {
|
|
753
759
|
const best = /* @__PURE__ */ new Map();
|
|
754
760
|
for (const id of edgeIds) {
|
|
755
761
|
const e = graph.getEdgeAttributes(id);
|
|
756
|
-
if (e.
|
|
762
|
+
if (isFrontierNode(graph, e.source)) continue;
|
|
757
763
|
const cur = best.get(e.source);
|
|
758
764
|
if (!cur || import_types.PROV_RANK[e.provenance] > import_types.PROV_RANK[cur.provenance]) {
|
|
759
765
|
best.set(e.source, e);
|
|
@@ -765,7 +771,7 @@ function bestEdgeByTarget(graph, edgeIds) {
|
|
|
765
771
|
const best = /* @__PURE__ */ new Map();
|
|
766
772
|
for (const id of edgeIds) {
|
|
767
773
|
const e = graph.getEdgeAttributes(id);
|
|
768
|
-
if (e.
|
|
774
|
+
if (isFrontierNode(graph, e.target)) continue;
|
|
769
775
|
const cur = best.get(e.target);
|
|
770
776
|
if (!cur || import_types.PROV_RANK[e.provenance] > import_types.PROV_RANK[cur.provenance]) {
|
|
771
777
|
best.set(e.target, e);
|
|
@@ -777,8 +783,7 @@ var PROVENANCE_CEILING = {
|
|
|
777
783
|
OBSERVED: 1,
|
|
778
784
|
INFERRED: 0.7,
|
|
779
785
|
EXTRACTED: 0.5,
|
|
780
|
-
STALE: 0.3
|
|
781
|
-
FRONTIER: 0.3
|
|
786
|
+
STALE: 0.3
|
|
782
787
|
};
|
|
783
788
|
function volumeWeight(spanCount) {
|
|
784
789
|
if (!spanCount || spanCount <= 0) return 0.5;
|
|
@@ -1062,8 +1067,8 @@ var evaluateStructural = ({
|
|
|
1062
1067
|
for (const edgeId of graph.outboundEdges(id)) {
|
|
1063
1068
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1064
1069
|
if (e.type !== rule.edgeType) continue;
|
|
1065
|
-
if (e.provenance === import_types2.Provenance.FRONTIER) continue;
|
|
1066
1070
|
const target = graph.getNodeAttributes(e.target);
|
|
1071
|
+
if (target.type === import_types2.NodeType.FrontierNode) continue;
|
|
1067
1072
|
if (target.type === rule.toNodeType) {
|
|
1068
1073
|
satisfied = true;
|
|
1069
1074
|
break;
|
|
@@ -1182,8 +1187,8 @@ var evaluateCompatibility = ({
|
|
|
1182
1187
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
1183
1188
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1184
1189
|
if (e.type !== import_types2.EdgeType.CONNECTS_TO) continue;
|
|
1185
|
-
if (e.provenance === import_types2.Provenance.FRONTIER) continue;
|
|
1186
1190
|
const dbAttrs = graph.getNodeAttributes(e.target);
|
|
1191
|
+
if (dbAttrs.type === import_types2.NodeType.FrontierNode) continue;
|
|
1187
1192
|
if (dbAttrs.type !== import_types2.NodeType.DatabaseNode) continue;
|
|
1188
1193
|
const db = dbAttrs;
|
|
1189
1194
|
for (const pair of compatPairs()) {
|
|
@@ -1478,31 +1483,6 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
1478
1483
|
graph.addNode(id, node);
|
|
1479
1484
|
return id;
|
|
1480
1485
|
}
|
|
1481
|
-
function upsertFrontierEdge(graph, type, source, target, ts) {
|
|
1482
|
-
const id = (0, import_types3.frontierEdgeId)(source, target, type);
|
|
1483
|
-
if (graph.hasEdge(id)) {
|
|
1484
|
-
const existing = graph.getEdgeAttributes(id);
|
|
1485
|
-
const updated = {
|
|
1486
|
-
...existing,
|
|
1487
|
-
provenance: import_types3.Provenance.FRONTIER,
|
|
1488
|
-
lastObserved: ts,
|
|
1489
|
-
callCount: (existing.callCount ?? 0) + 1
|
|
1490
|
-
};
|
|
1491
|
-
graph.replaceEdgeAttributes(id, updated);
|
|
1492
|
-
return;
|
|
1493
|
-
}
|
|
1494
|
-
const edge = {
|
|
1495
|
-
id,
|
|
1496
|
-
source,
|
|
1497
|
-
target,
|
|
1498
|
-
type,
|
|
1499
|
-
provenance: import_types3.Provenance.FRONTIER,
|
|
1500
|
-
confidence: 1,
|
|
1501
|
-
lastObserved: ts,
|
|
1502
|
-
callCount: 1
|
|
1503
|
-
};
|
|
1504
|
-
graph.addEdgeWithKey(id, source, target, edge);
|
|
1505
|
-
}
|
|
1506
1486
|
function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
1507
1487
|
if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
|
|
1508
1488
|
const id = makeObservedEdgeId(type, source, target);
|
|
@@ -1588,18 +1568,28 @@ async function appendErrorEvent(ctx, ev) {
|
|
|
1588
1568
|
await import_node_fs3.promises.mkdir(import_node_path3.default.dirname(ctx.errorsPath), { recursive: true });
|
|
1589
1569
|
await import_node_fs3.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
1590
1570
|
}
|
|
1571
|
+
function sanitizeAttributes(attrs) {
|
|
1572
|
+
const out = {};
|
|
1573
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
1574
|
+
if (typeof v === "bigint") out[k] = v.toString();
|
|
1575
|
+
else out[k] = v;
|
|
1576
|
+
}
|
|
1577
|
+
return out;
|
|
1578
|
+
}
|
|
1591
1579
|
function buildErrorEventForReceiver(span) {
|
|
1592
1580
|
if (span.statusCode !== 2) return null;
|
|
1593
1581
|
const ts = span.startTimeIso ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1582
|
+
const attrs = sanitizeAttributes(span.attributes);
|
|
1594
1583
|
return {
|
|
1595
1584
|
id: `${span.traceId}:${span.spanId}`,
|
|
1596
1585
|
timestamp: ts,
|
|
1597
1586
|
service: span.service,
|
|
1598
1587
|
traceId: span.traceId,
|
|
1599
1588
|
spanId: span.spanId,
|
|
1600
|
-
errorMessage: span.exception?.message ??
|
|
1589
|
+
errorMessage: span.exception?.message ?? "unknown error",
|
|
1601
1590
|
...span.exception?.type ? { exceptionType: span.exception.type } : {},
|
|
1602
1591
|
...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
|
|
1592
|
+
...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
|
|
1603
1593
|
affectedNode: (0, import_types3.serviceId)(span.service)
|
|
1604
1594
|
};
|
|
1605
1595
|
}
|
|
@@ -1650,11 +1640,16 @@ async function handleSpan(ctx, span) {
|
|
|
1650
1640
|
affectedNode = targetId;
|
|
1651
1641
|
resolvedViaAddress = true;
|
|
1652
1642
|
} else if (!targetId) {
|
|
1653
|
-
const
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1643
|
+
const frontierNodeId = ensureFrontierNode(ctx.graph, host, ts);
|
|
1644
|
+
upsertObservedEdge(
|
|
1645
|
+
ctx.graph,
|
|
1646
|
+
import_types3.EdgeType.CALLS,
|
|
1647
|
+
sourceId,
|
|
1648
|
+
frontierNodeId,
|
|
1649
|
+
ts,
|
|
1650
|
+
isError
|
|
1651
|
+
);
|
|
1652
|
+
affectedNode = frontierNodeId;
|
|
1658
1653
|
resolvedViaAddress = true;
|
|
1659
1654
|
}
|
|
1660
1655
|
}
|
|
@@ -1676,15 +1671,17 @@ async function handleSpan(ctx, span) {
|
|
|
1676
1671
|
if (span.statusCode === 2) {
|
|
1677
1672
|
stitchTrace(ctx.graph, sourceId, ts);
|
|
1678
1673
|
if (ctx.writeErrorEventInline !== false) {
|
|
1674
|
+
const attrs = sanitizeAttributes(span.attributes);
|
|
1679
1675
|
const ev = {
|
|
1680
1676
|
id: `${span.traceId}:${span.spanId}`,
|
|
1681
1677
|
timestamp: ts,
|
|
1682
1678
|
service: span.service,
|
|
1683
1679
|
traceId: span.traceId,
|
|
1684
1680
|
spanId: span.spanId,
|
|
1685
|
-
errorMessage: span.exception?.message ??
|
|
1681
|
+
errorMessage: span.exception?.message ?? "unknown error",
|
|
1686
1682
|
...span.exception?.type ? { exceptionType: span.exception.type } : {},
|
|
1687
1683
|
...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
|
|
1684
|
+
...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
|
|
1688
1685
|
affectedNode
|
|
1689
1686
|
};
|
|
1690
1687
|
await appendErrorEvent(ctx, ev);
|
|
@@ -1740,8 +1737,7 @@ function rewireFrontierEdges(graph, frontierId2, serviceId3) {
|
|
|
1740
1737
|
}
|
|
1741
1738
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
1742
1739
|
graph.dropEdge(oldEdgeId);
|
|
1743
|
-
const
|
|
1744
|
-
const newId = promotedProvenance === import_types3.Provenance.OBSERVED ? (0, import_types3.observedEdgeId)(newSource, newTarget, edge.type) : promotedProvenance === import_types3.Provenance.INFERRED ? (0, import_types3.inferredEdgeId)(newSource, newTarget, edge.type) : promotedProvenance === import_types3.Provenance.EXTRACTED ? (0, import_types3.extractedEdgeId)(newSource, newTarget, edge.type) : (0, import_types3.frontierEdgeId)(newSource, newTarget, edge.type);
|
|
1740
|
+
const newId = edge.provenance === import_types3.Provenance.OBSERVED ? (0, import_types3.observedEdgeId)(newSource, newTarget, edge.type) : edge.provenance === import_types3.Provenance.INFERRED ? (0, import_types3.inferredEdgeId)(newSource, newTarget, edge.type) : (0, import_types3.extractedEdgeId)(newSource, newTarget, edge.type);
|
|
1745
1741
|
if (graph.hasEdge(newId)) {
|
|
1746
1742
|
const existing = graph.getEdgeAttributes(newId);
|
|
1747
1743
|
const merged = {
|
|
@@ -1756,8 +1752,7 @@ function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
|
1756
1752
|
...edge,
|
|
1757
1753
|
id: newId,
|
|
1758
1754
|
source: newSource,
|
|
1759
|
-
target: newTarget
|
|
1760
|
-
provenance: promotedProvenance
|
|
1755
|
+
target: newTarget
|
|
1761
1756
|
};
|
|
1762
1757
|
graph.addEdgeWithKey(newId, newSource, newTarget, rebuilt);
|
|
1763
1758
|
}
|
|
@@ -3981,7 +3976,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
3981
3976
|
init_cjs_shims();
|
|
3982
3977
|
var import_node_fs18 = require("fs");
|
|
3983
3978
|
var import_node_path31 = __toESM(require("path"), 1);
|
|
3984
|
-
var
|
|
3979
|
+
var import_types19 = require("@neat.is/types");
|
|
3980
|
+
var SCHEMA_VERSION = 3;
|
|
3985
3981
|
function migrateV1ToV2(payload) {
|
|
3986
3982
|
const nodes = payload.graph.nodes;
|
|
3987
3983
|
if (Array.isArray(nodes)) {
|
|
@@ -3993,6 +3989,25 @@ function migrateV1ToV2(payload) {
|
|
|
3993
3989
|
}
|
|
3994
3990
|
return { ...payload, schemaVersion: 2 };
|
|
3995
3991
|
}
|
|
3992
|
+
function migrateV2ToV3(payload) {
|
|
3993
|
+
const edges = payload.graph.edges;
|
|
3994
|
+
if (Array.isArray(edges)) {
|
|
3995
|
+
for (const edge of edges) {
|
|
3996
|
+
const attrs = edge.attributes;
|
|
3997
|
+
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
3998
|
+
attrs.provenance = import_types19.Provenance.OBSERVED;
|
|
3999
|
+
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
4000
|
+
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
4001
|
+
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
4002
|
+
if (type && source && target) {
|
|
4003
|
+
const newId = (0, import_types19.observedEdgeId)(source, target, type);
|
|
4004
|
+
attrs.id = newId;
|
|
4005
|
+
if (edge.key) edge.key = newId;
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
return { ...payload, schemaVersion: 3 };
|
|
4010
|
+
}
|
|
3996
4011
|
async function ensureDir(filePath) {
|
|
3997
4012
|
await import_node_fs18.promises.mkdir(import_node_path31.default.dirname(filePath), { recursive: true });
|
|
3998
4013
|
}
|
|
@@ -4019,6 +4034,9 @@ async function loadGraphFromDisk(graph, outPath) {
|
|
|
4019
4034
|
if (payload.schemaVersion === 1) {
|
|
4020
4035
|
payload = migrateV1ToV2(payload);
|
|
4021
4036
|
}
|
|
4037
|
+
if (payload.schemaVersion === 2) {
|
|
4038
|
+
payload = migrateV2ToV3(payload);
|
|
4039
|
+
}
|
|
4022
4040
|
if (payload.schemaVersion !== SCHEMA_VERSION) {
|
|
4023
4041
|
throw new Error(
|
|
4024
4042
|
`persist: unsupported snapshot schemaVersion ${payload.schemaVersion} (expected ${SCHEMA_VERSION})`
|
|
@@ -4117,11 +4135,11 @@ var Projects = class {
|
|
|
4117
4135
|
init_cjs_shims();
|
|
4118
4136
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
4119
4137
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
4120
|
-
var
|
|
4138
|
+
var import_types22 = require("@neat.is/types");
|
|
4121
4139
|
|
|
4122
4140
|
// src/divergences.ts
|
|
4123
4141
|
init_cjs_shims();
|
|
4124
|
-
var
|
|
4142
|
+
var import_types20 = require("@neat.is/types");
|
|
4125
4143
|
function bucketKey(source, target, type) {
|
|
4126
4144
|
return `${type}|${source}|${target}`;
|
|
4127
4145
|
}
|
|
@@ -4129,25 +4147,22 @@ function bucketEdges(graph) {
|
|
|
4129
4147
|
const buckets = /* @__PURE__ */ new Map();
|
|
4130
4148
|
graph.forEachEdge((id, attrs) => {
|
|
4131
4149
|
const e = attrs;
|
|
4132
|
-
const parsed = (0,
|
|
4150
|
+
const parsed = (0, import_types20.parseEdgeId)(id);
|
|
4133
4151
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
4134
4152
|
const key = bucketKey(e.source, e.target, e.type);
|
|
4135
4153
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
4136
4154
|
switch (provenance) {
|
|
4137
|
-
case
|
|
4155
|
+
case import_types20.Provenance.EXTRACTED:
|
|
4138
4156
|
cur.extracted = e;
|
|
4139
4157
|
break;
|
|
4140
|
-
case
|
|
4158
|
+
case import_types20.Provenance.OBSERVED:
|
|
4141
4159
|
cur.observed = e;
|
|
4142
4160
|
break;
|
|
4143
|
-
case
|
|
4161
|
+
case import_types20.Provenance.INFERRED:
|
|
4144
4162
|
cur.inferred = e;
|
|
4145
4163
|
break;
|
|
4146
|
-
case import_types19.Provenance.FRONTIER:
|
|
4147
|
-
cur.frontier = e;
|
|
4148
|
-
break;
|
|
4149
4164
|
default:
|
|
4150
|
-
if (e.provenance ===
|
|
4165
|
+
if (e.provenance === import_types20.Provenance.STALE) cur.stale = e;
|
|
4151
4166
|
}
|
|
4152
4167
|
buckets.set(key, cur);
|
|
4153
4168
|
});
|
|
@@ -4156,7 +4171,7 @@ function bucketEdges(graph) {
|
|
|
4156
4171
|
function nodeIsFrontier(graph, nodeId) {
|
|
4157
4172
|
if (!graph.hasNode(nodeId)) return false;
|
|
4158
4173
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
4159
|
-
return attrs.type ===
|
|
4174
|
+
return attrs.type === import_types20.NodeType.FrontierNode;
|
|
4160
4175
|
}
|
|
4161
4176
|
function clampConfidence(n) {
|
|
4162
4177
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -4217,7 +4232,7 @@ function declaredHostFor(svc) {
|
|
|
4217
4232
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
4218
4233
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4219
4234
|
const e = graph.getEdgeAttributes(edgeId);
|
|
4220
|
-
if (e.type ===
|
|
4235
|
+
if (e.type === import_types20.EdgeType.CONFIGURED_BY && e.provenance === import_types20.Provenance.EXTRACTED) {
|
|
4221
4236
|
return true;
|
|
4222
4237
|
}
|
|
4223
4238
|
}
|
|
@@ -4230,10 +4245,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
4230
4245
|
const out = [];
|
|
4231
4246
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4232
4247
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4233
|
-
if (edge.type !==
|
|
4234
|
-
if (edge.provenance !==
|
|
4248
|
+
if (edge.type !== import_types20.EdgeType.CONNECTS_TO) continue;
|
|
4249
|
+
if (edge.provenance !== import_types20.Provenance.OBSERVED) continue;
|
|
4235
4250
|
const target = graph.getNodeAttributes(edge.target);
|
|
4236
|
-
if (target.type !==
|
|
4251
|
+
if (target.type !== import_types20.NodeType.DatabaseNode) continue;
|
|
4237
4252
|
const observedHost = target.host?.trim();
|
|
4238
4253
|
if (!observedHost) continue;
|
|
4239
4254
|
if (observedHost === declaredHost) continue;
|
|
@@ -4255,10 +4270,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
4255
4270
|
const deps = svc.dependencies ?? {};
|
|
4256
4271
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4257
4272
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4258
|
-
if (edge.type !==
|
|
4259
|
-
if (edge.provenance !==
|
|
4273
|
+
if (edge.type !== import_types20.EdgeType.CONNECTS_TO) continue;
|
|
4274
|
+
if (edge.provenance !== import_types20.Provenance.OBSERVED) continue;
|
|
4260
4275
|
const target = graph.getNodeAttributes(edge.target);
|
|
4261
|
-
if (target.type !==
|
|
4276
|
+
if (target.type !== import_types20.NodeType.DatabaseNode) continue;
|
|
4262
4277
|
for (const pair of compatPairs()) {
|
|
4263
4278
|
if (pair.engine !== target.engine) continue;
|
|
4264
4279
|
const declared = deps[pair.driver];
|
|
@@ -4319,7 +4334,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4319
4334
|
}
|
|
4320
4335
|
graph.forEachNode((nodeId, attrs) => {
|
|
4321
4336
|
const n = attrs;
|
|
4322
|
-
if (n.type !==
|
|
4337
|
+
if (n.type !== import_types20.NodeType.ServiceNode) return;
|
|
4323
4338
|
const svc = n;
|
|
4324
4339
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
4325
4340
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -4352,7 +4367,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4352
4367
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
4353
4368
|
return a.target.localeCompare(b.target);
|
|
4354
4369
|
});
|
|
4355
|
-
return
|
|
4370
|
+
return import_types20.DivergenceResultSchema.parse({
|
|
4356
4371
|
divergences: filtered,
|
|
4357
4372
|
totalAffected: filtered.length,
|
|
4358
4373
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4441,7 +4456,7 @@ init_cjs_shims();
|
|
|
4441
4456
|
var import_node_fs20 = require("fs");
|
|
4442
4457
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
4443
4458
|
var import_node_path33 = __toESM(require("path"), 1);
|
|
4444
|
-
var
|
|
4459
|
+
var import_types21 = require("@neat.is/types");
|
|
4445
4460
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
4446
4461
|
var LOCK_RETRY_MS = 50;
|
|
4447
4462
|
function neatHome() {
|
|
@@ -4512,10 +4527,10 @@ async function readRegistry() {
|
|
|
4512
4527
|
throw err;
|
|
4513
4528
|
}
|
|
4514
4529
|
const parsed = JSON.parse(raw);
|
|
4515
|
-
return
|
|
4530
|
+
return import_types21.RegistryFileSchema.parse(parsed);
|
|
4516
4531
|
}
|
|
4517
4532
|
async function writeRegistry(reg) {
|
|
4518
|
-
const validated =
|
|
4533
|
+
const validated = import_types21.RegistryFileSchema.parse(reg);
|
|
4519
4534
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
4520
4535
|
}
|
|
4521
4536
|
async function getProject(name) {
|
|
@@ -4725,11 +4740,11 @@ function registerRoutes(scope, ctx) {
|
|
|
4725
4740
|
const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
4726
4741
|
const parsed = [];
|
|
4727
4742
|
for (const c of candidates) {
|
|
4728
|
-
const r =
|
|
4743
|
+
const r = import_types22.DivergenceTypeSchema.safeParse(c);
|
|
4729
4744
|
if (!r.success) {
|
|
4730
4745
|
return reply.code(400).send({
|
|
4731
4746
|
error: `unknown divergence type "${c}"`,
|
|
4732
|
-
allowed:
|
|
4747
|
+
allowed: import_types22.DivergenceTypeSchema.options
|
|
4733
4748
|
});
|
|
4734
4749
|
}
|
|
4735
4750
|
parsed.push(r.data);
|
|
@@ -4913,7 +4928,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4913
4928
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
4914
4929
|
let violations = await log.readAll();
|
|
4915
4930
|
if (req2.query.severity) {
|
|
4916
|
-
const sev =
|
|
4931
|
+
const sev = import_types22.PolicySeveritySchema.safeParse(req2.query.severity);
|
|
4917
4932
|
if (!sev.success) {
|
|
4918
4933
|
return reply.code(400).send({
|
|
4919
4934
|
error: "invalid severity",
|
|
@@ -4930,7 +4945,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4930
4945
|
scope.post("/policies/check", async (req2, reply) => {
|
|
4931
4946
|
const proj = resolveProject(registry, req2, reply);
|
|
4932
4947
|
if (!proj) return;
|
|
4933
|
-
const parsed =
|
|
4948
|
+
const parsed = import_types22.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
|
|
4934
4949
|
if (!parsed.success) {
|
|
4935
4950
|
return reply.code(400).send({
|
|
4936
4951
|
error: "invalid /policies/check body",
|
|
@@ -5375,7 +5390,83 @@ async function spawnWebUI(restPort) {
|
|
|
5375
5390
|
return { child, port, stop };
|
|
5376
5391
|
}
|
|
5377
5392
|
|
|
5393
|
+
// src/version-skew.ts
|
|
5394
|
+
init_cjs_shims();
|
|
5395
|
+
var import_promises = require("timers/promises");
|
|
5396
|
+
var NPM_REGISTRY_URL = "https://registry.npmjs.org/neat.is/latest";
|
|
5397
|
+
var DEFAULT_TIMEOUT_MS = 2e3;
|
|
5398
|
+
async function fetchRegistryVersion(url, timeoutMs, fetchImpl) {
|
|
5399
|
+
const controller = new AbortController();
|
|
5400
|
+
let timedOut = false;
|
|
5401
|
+
const timer = (0, import_promises.setTimeout)(timeoutMs).then(() => {
|
|
5402
|
+
timedOut = true;
|
|
5403
|
+
controller.abort();
|
|
5404
|
+
});
|
|
5405
|
+
try {
|
|
5406
|
+
const res = await Promise.race([
|
|
5407
|
+
fetchImpl(url, { signal: controller.signal, headers: { accept: "application/json" } }),
|
|
5408
|
+
timer.then(() => null)
|
|
5409
|
+
]);
|
|
5410
|
+
if (timedOut || !res) return null;
|
|
5411
|
+
if (!res.ok) return null;
|
|
5412
|
+
const body = await res.json();
|
|
5413
|
+
if (typeof body.version !== "string" || body.version.length === 0) return null;
|
|
5414
|
+
return body.version;
|
|
5415
|
+
} catch {
|
|
5416
|
+
return null;
|
|
5417
|
+
} finally {
|
|
5418
|
+
if (!timedOut) controller.abort();
|
|
5419
|
+
}
|
|
5420
|
+
}
|
|
5421
|
+
function isLocalBehind(local, remote) {
|
|
5422
|
+
if (local === remote) return false;
|
|
5423
|
+
const parse10 = (v) => {
|
|
5424
|
+
const core = v.split(/[-+]/)[0] ?? v;
|
|
5425
|
+
return core.split(".").map((s) => {
|
|
5426
|
+
const n = Number.parseInt(s, 10);
|
|
5427
|
+
return Number.isFinite(n) ? n : 0;
|
|
5428
|
+
});
|
|
5429
|
+
};
|
|
5430
|
+
const a = parse10(local);
|
|
5431
|
+
const b = parse10(remote);
|
|
5432
|
+
const len = Math.max(a.length, b.length);
|
|
5433
|
+
for (let i = 0; i < len; i++) {
|
|
5434
|
+
const av = a[i] ?? 0;
|
|
5435
|
+
const bv = b[i] ?? 0;
|
|
5436
|
+
if (av < bv) return true;
|
|
5437
|
+
if (av > bv) return false;
|
|
5438
|
+
}
|
|
5439
|
+
return false;
|
|
5440
|
+
}
|
|
5441
|
+
async function checkVersionSkew(opts) {
|
|
5442
|
+
const url = opts.registryUrl ?? NPM_REGISTRY_URL;
|
|
5443
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
5444
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
5445
|
+
const warn = opts.warn ?? ((m) => console.warn(m));
|
|
5446
|
+
const remote = await fetchRegistryVersion(url, timeoutMs, fetchImpl);
|
|
5447
|
+
if (!remote) return { remoteVersion: null, skewed: false, warned: false };
|
|
5448
|
+
if (!isLocalBehind(opts.localVersion, remote)) {
|
|
5449
|
+
return { remoteVersion: remote, skewed: false, warned: false };
|
|
5450
|
+
}
|
|
5451
|
+
warn(
|
|
5452
|
+
`[neatd] running neat.is@${opts.localVersion} but neat.is@${remote} is on npm \u2014 run \`npm install -g neat.is@latest\` to update`
|
|
5453
|
+
);
|
|
5454
|
+
return { remoteVersion: remote, skewed: true, warned: true };
|
|
5455
|
+
}
|
|
5456
|
+
|
|
5378
5457
|
// src/neatd.ts
|
|
5458
|
+
function localVersion() {
|
|
5459
|
+
if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
|
|
5460
|
+
return process.env.NEAT_LOCAL_VERSION;
|
|
5461
|
+
}
|
|
5462
|
+
try {
|
|
5463
|
+
const req2 = (0, import_node_module.createRequire)(importMetaUrl);
|
|
5464
|
+
const pkg = req2("../package.json");
|
|
5465
|
+
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
5466
|
+
} catch {
|
|
5467
|
+
return "0.0.0";
|
|
5468
|
+
}
|
|
5469
|
+
}
|
|
5379
5470
|
function neatHome2() {
|
|
5380
5471
|
if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
|
|
5381
5472
|
return import_node_path38.default.resolve(process.env.NEAT_HOME);
|
|
@@ -5405,6 +5496,10 @@ async function cmdStart() {
|
|
|
5405
5496
|
console.log(`neatd: registry at ${registryPath()}`);
|
|
5406
5497
|
if (handle.restAddress) console.log(`neatd: REST \u2192 ${handle.restAddress}`);
|
|
5407
5498
|
if (handle.otlpAddress) console.log(`neatd: OTLP \u2192 ${handle.otlpAddress}`);
|
|
5499
|
+
if (process.env.NEAT_DISABLE_VERSION_CHECK !== "1") {
|
|
5500
|
+
void checkVersionSkew({ localVersion: localVersion() }).catch(() => {
|
|
5501
|
+
});
|
|
5502
|
+
}
|
|
5408
5503
|
const skipWeb = process.env.NEAT_WEB_DISABLED === "1";
|
|
5409
5504
|
let web = null;
|
|
5410
5505
|
if (!skipWeb) {
|