@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
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
startPersistLoop,
|
|
12
12
|
touchLastSeen,
|
|
13
13
|
writeAtomically
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-JMLZOKCS.js";
|
|
15
15
|
|
|
16
16
|
// src/daemon.ts
|
|
17
17
|
import { promises as fs } from "fs";
|
|
@@ -159,4 +159,4 @@ export {
|
|
|
159
159
|
routeSpanToProject,
|
|
160
160
|
startDaemon
|
|
161
161
|
};
|
|
162
|
-
//# sourceMappingURL=chunk-
|
|
162
|
+
//# sourceMappingURL=chunk-5BQWBQJR.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
__require
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=chunk-DGUM43GV.js.map
|
|
@@ -3721,8 +3721,10 @@ export {
|
|
|
3721
3721
|
getGraph,
|
|
3722
3722
|
resetGraph,
|
|
3723
3723
|
checkCompatibility,
|
|
3724
|
+
checkDeprecatedApi,
|
|
3724
3725
|
ensureCompatLoaded,
|
|
3725
3726
|
compatPairs,
|
|
3727
|
+
deprecatedApis,
|
|
3726
3728
|
EVENT_BUS_CHANNEL,
|
|
3727
3729
|
eventBus,
|
|
3728
3730
|
emitNeatEvent,
|
|
@@ -3773,4 +3775,4 @@ export {
|
|
|
3773
3775
|
touchLastSeen,
|
|
3774
3776
|
removeProject
|
|
3775
3777
|
};
|
|
3776
|
-
//# sourceMappingURL=chunk-
|
|
3778
|
+
//# sourceMappingURL=chunk-JMLZOKCS.js.map
|
|
@@ -115,7 +115,7 @@ function loadProtobufDecoder() {
|
|
|
115
115
|
async function decodeProtobufBody(buf) {
|
|
116
116
|
const Type = loadProtobufDecoder();
|
|
117
117
|
const decoded = Type.decode(buf).toJSON();
|
|
118
|
-
const { reshapeGrpcRequest: reshapeGrpcRequest2 } = await import("./otel-grpc-
|
|
118
|
+
const { reshapeGrpcRequest: reshapeGrpcRequest2 } = await import("./otel-grpc-USFL3OI3.js");
|
|
119
119
|
return reshapeGrpcRequest2(decoded);
|
|
120
120
|
}
|
|
121
121
|
async function buildOtelReceiver(opts) {
|
|
@@ -322,4 +322,4 @@ export {
|
|
|
322
322
|
buildOtelReceiver,
|
|
323
323
|
logSpanHandler
|
|
324
324
|
};
|
|
325
|
-
//# sourceMappingURL=chunk-
|
|
325
|
+
//# sourceMappingURL=chunk-QYUB3FQL.js.map
|
|
@@ -5,6 +5,11 @@ import {
|
|
|
5
5
|
Projects,
|
|
6
6
|
TRANSITIVE_DEPENDENCIES_DEFAULT_DEPTH,
|
|
7
7
|
TRANSITIVE_DEPENDENCIES_MAX_DEPTH,
|
|
8
|
+
checkCompatibility,
|
|
9
|
+
checkDeprecatedApi,
|
|
10
|
+
compatPairs,
|
|
11
|
+
confidenceForEdge,
|
|
12
|
+
deprecatedApis,
|
|
8
13
|
evaluateAllPolicies,
|
|
9
14
|
eventBus,
|
|
10
15
|
extractFromDirectory,
|
|
@@ -16,7 +21,7 @@ import {
|
|
|
16
21
|
pathsForProject,
|
|
17
22
|
readErrorEvents,
|
|
18
23
|
readStaleEvents
|
|
19
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-JMLZOKCS.js";
|
|
20
25
|
|
|
21
26
|
// src/diff.ts
|
|
22
27
|
import { promises as fs } from "fs";
|
|
@@ -97,7 +102,249 @@ function canonicalJson(value) {
|
|
|
97
102
|
// src/api.ts
|
|
98
103
|
import Fastify from "fastify";
|
|
99
104
|
import cors from "@fastify/cors";
|
|
100
|
-
import { PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
|
|
105
|
+
import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
|
|
106
|
+
|
|
107
|
+
// src/divergences.ts
|
|
108
|
+
import {
|
|
109
|
+
DivergenceResultSchema,
|
|
110
|
+
EdgeType,
|
|
111
|
+
NodeType,
|
|
112
|
+
parseEdgeId,
|
|
113
|
+
Provenance
|
|
114
|
+
} from "@neat.is/types";
|
|
115
|
+
function bucketKey(source, target, type) {
|
|
116
|
+
return `${type}|${source}|${target}`;
|
|
117
|
+
}
|
|
118
|
+
function bucketEdges(graph) {
|
|
119
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
120
|
+
graph.forEachEdge((id, attrs) => {
|
|
121
|
+
const e = attrs;
|
|
122
|
+
const parsed = parseEdgeId(id);
|
|
123
|
+
const provenance = parsed?.provenance ?? e.provenance;
|
|
124
|
+
const key = bucketKey(e.source, e.target, e.type);
|
|
125
|
+
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
126
|
+
switch (provenance) {
|
|
127
|
+
case Provenance.EXTRACTED:
|
|
128
|
+
cur.extracted = e;
|
|
129
|
+
break;
|
|
130
|
+
case Provenance.OBSERVED:
|
|
131
|
+
cur.observed = e;
|
|
132
|
+
break;
|
|
133
|
+
case Provenance.INFERRED:
|
|
134
|
+
cur.inferred = e;
|
|
135
|
+
break;
|
|
136
|
+
case Provenance.FRONTIER:
|
|
137
|
+
cur.frontier = e;
|
|
138
|
+
break;
|
|
139
|
+
default:
|
|
140
|
+
if (e.provenance === Provenance.STALE) cur.stale = e;
|
|
141
|
+
}
|
|
142
|
+
buckets.set(key, cur);
|
|
143
|
+
});
|
|
144
|
+
return buckets;
|
|
145
|
+
}
|
|
146
|
+
function nodeIsFrontier(graph, nodeId) {
|
|
147
|
+
if (!graph.hasNode(nodeId)) return false;
|
|
148
|
+
const attrs = graph.getNodeAttributes(nodeId);
|
|
149
|
+
return attrs.type === NodeType.FrontierNode;
|
|
150
|
+
}
|
|
151
|
+
function hasAnyObservedFromSource(graph, sourceId) {
|
|
152
|
+
if (!graph.hasNode(sourceId)) return false;
|
|
153
|
+
for (const edgeId of graph.outboundEdges(sourceId)) {
|
|
154
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
155
|
+
if (e.provenance === Provenance.OBSERVED) return true;
|
|
156
|
+
}
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
function clampConfidence(n) {
|
|
160
|
+
if (!Number.isFinite(n)) return 0;
|
|
161
|
+
return Math.max(0, Math.min(1, n));
|
|
162
|
+
}
|
|
163
|
+
function reasonForMissingObserved(source, target, type) {
|
|
164
|
+
return `Code declares ${source} \u2192 ${target} (${type}) but no production traffic has been observed for this edge.`;
|
|
165
|
+
}
|
|
166
|
+
function reasonForMissingExtracted(source, target, type) {
|
|
167
|
+
return `Production observed ${source} \u2192 ${target} (${type}) but static analysis did not surface this edge.`;
|
|
168
|
+
}
|
|
169
|
+
var RECOMMENDATION_MISSING_OBSERVED = "Verify the code path is exercised in production; check feature flags or conditional branches that might gate the call.";
|
|
170
|
+
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.";
|
|
171
|
+
var RECOMMENDATION_HOST_MISMATCH = "Check environment-specific config overrides \u2014 the runtime host differs from what static configuration declares.";
|
|
172
|
+
function detectMissingDivergences(graph, bucket) {
|
|
173
|
+
const out = [];
|
|
174
|
+
if (bucket.extracted && !bucket.observed) {
|
|
175
|
+
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
176
|
+
const sourceHasTraffic = hasAnyObservedFromSource(graph, bucket.source);
|
|
177
|
+
out.push({
|
|
178
|
+
type: "missing-observed",
|
|
179
|
+
source: bucket.source,
|
|
180
|
+
target: bucket.target,
|
|
181
|
+
edgeType: bucket.type,
|
|
182
|
+
extracted: bucket.extracted,
|
|
183
|
+
confidence: sourceHasTraffic ? 1 : 0.5,
|
|
184
|
+
reason: reasonForMissingObserved(bucket.source, bucket.target, bucket.type),
|
|
185
|
+
recommendation: RECOMMENDATION_MISSING_OBSERVED
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (bucket.observed && !bucket.extracted) {
|
|
190
|
+
const cascaded = clampConfidence(confidenceForEdge(bucket.observed));
|
|
191
|
+
out.push({
|
|
192
|
+
type: "missing-extracted",
|
|
193
|
+
source: bucket.source,
|
|
194
|
+
target: bucket.target,
|
|
195
|
+
edgeType: bucket.type,
|
|
196
|
+
observed: bucket.observed,
|
|
197
|
+
confidence: cascaded,
|
|
198
|
+
reason: reasonForMissingExtracted(bucket.source, bucket.target, bucket.type),
|
|
199
|
+
recommendation: RECOMMENDATION_MISSING_EXTRACTED
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
204
|
+
function declaredHostFor(svc) {
|
|
205
|
+
const raw = svc.dbConnectionTarget?.trim();
|
|
206
|
+
if (!raw) return null;
|
|
207
|
+
const colon = raw.lastIndexOf(":");
|
|
208
|
+
if (colon === -1) return raw;
|
|
209
|
+
const port = raw.slice(colon + 1);
|
|
210
|
+
if (/^\d+$/.test(port)) return raw.slice(0, colon);
|
|
211
|
+
return raw;
|
|
212
|
+
}
|
|
213
|
+
function hasExtractedConfiguredBy(graph, svcId) {
|
|
214
|
+
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
215
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
216
|
+
if (e.type === EdgeType.CONFIGURED_BY && e.provenance === Provenance.EXTRACTED) {
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
function detectHostMismatch(graph, svcId, svc) {
|
|
223
|
+
const declaredHost = declaredHostFor(svc);
|
|
224
|
+
if (!declaredHost) return [];
|
|
225
|
+
if (!hasExtractedConfiguredBy(graph, svcId)) return [];
|
|
226
|
+
const out = [];
|
|
227
|
+
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
228
|
+
const edge = graph.getEdgeAttributes(edgeId);
|
|
229
|
+
if (edge.type !== EdgeType.CONNECTS_TO) continue;
|
|
230
|
+
if (edge.provenance !== Provenance.OBSERVED) continue;
|
|
231
|
+
const target = graph.getNodeAttributes(edge.target);
|
|
232
|
+
if (target.type !== NodeType.DatabaseNode) continue;
|
|
233
|
+
const observedHost = target.host?.trim();
|
|
234
|
+
if (!observedHost) continue;
|
|
235
|
+
if (observedHost === declaredHost) continue;
|
|
236
|
+
out.push({
|
|
237
|
+
type: "host-mismatch",
|
|
238
|
+
source: svcId,
|
|
239
|
+
target: edge.target,
|
|
240
|
+
extractedHost: declaredHost,
|
|
241
|
+
observedHost,
|
|
242
|
+
confidence: clampConfidence(confidenceForEdge(edge)),
|
|
243
|
+
reason: `Config declares ${svcId} connects to ${declaredHost}; production connects to ${observedHost}.`,
|
|
244
|
+
recommendation: RECOMMENDATION_HOST_MISMATCH
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
249
|
+
function detectCompatDivergences(graph, svcId, svc) {
|
|
250
|
+
const out = [];
|
|
251
|
+
const deps = svc.dependencies ?? {};
|
|
252
|
+
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
253
|
+
const edge = graph.getEdgeAttributes(edgeId);
|
|
254
|
+
if (edge.type !== EdgeType.CONNECTS_TO) continue;
|
|
255
|
+
if (edge.provenance !== Provenance.OBSERVED) continue;
|
|
256
|
+
const target = graph.getNodeAttributes(edge.target);
|
|
257
|
+
if (target.type !== NodeType.DatabaseNode) continue;
|
|
258
|
+
for (const pair of compatPairs()) {
|
|
259
|
+
if (pair.engine !== target.engine) continue;
|
|
260
|
+
const declared = deps[pair.driver];
|
|
261
|
+
if (!declared) continue;
|
|
262
|
+
const result = checkCompatibility(
|
|
263
|
+
pair.driver,
|
|
264
|
+
declared,
|
|
265
|
+
target.engine,
|
|
266
|
+
target.engineVersion
|
|
267
|
+
);
|
|
268
|
+
if (!result.compatible && result.reason) {
|
|
269
|
+
out.push({
|
|
270
|
+
type: "version-mismatch",
|
|
271
|
+
source: svcId,
|
|
272
|
+
target: edge.target,
|
|
273
|
+
extractedVersion: declared,
|
|
274
|
+
observedVersion: target.engineVersion,
|
|
275
|
+
compatibility: "incompatible",
|
|
276
|
+
confidence: 1,
|
|
277
|
+
reason: result.reason,
|
|
278
|
+
recommendation: result.minDriverVersion ? `Upgrade ${pair.driver} to >= ${result.minDriverVersion}.` : `Update the ${pair.driver} driver to a version compatible with ${target.engine} ${target.engineVersion}.`
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
for (const rule of deprecatedApis()) {
|
|
283
|
+
const declared = deps[rule.package];
|
|
284
|
+
if (!declared) continue;
|
|
285
|
+
const result = checkDeprecatedApi(rule, declared);
|
|
286
|
+
if (!result.compatible && result.reason) {
|
|
287
|
+
const ruleRef = {
|
|
288
|
+
kind: rule.kind ?? "deprecated-api",
|
|
289
|
+
reason: result.reason,
|
|
290
|
+
package: rule.package
|
|
291
|
+
};
|
|
292
|
+
out.push({
|
|
293
|
+
type: "compat-violation",
|
|
294
|
+
source: svcId,
|
|
295
|
+
target: edge.target,
|
|
296
|
+
rule: ruleRef,
|
|
297
|
+
observed: edge,
|
|
298
|
+
confidence: 1,
|
|
299
|
+
reason: result.reason,
|
|
300
|
+
recommendation: `Replace deprecated ${rule.package}@${declared} with a supported version.`
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return out;
|
|
306
|
+
}
|
|
307
|
+
function involvesNode(d, nodeId) {
|
|
308
|
+
return d.source === nodeId || d.target === nodeId;
|
|
309
|
+
}
|
|
310
|
+
function computeDivergences(graph, opts = {}) {
|
|
311
|
+
const all = [];
|
|
312
|
+
const buckets = bucketEdges(graph);
|
|
313
|
+
for (const bucket of buckets.values()) {
|
|
314
|
+
for (const d of detectMissingDivergences(graph, bucket)) all.push(d);
|
|
315
|
+
}
|
|
316
|
+
graph.forEachNode((nodeId, attrs) => {
|
|
317
|
+
const n = attrs;
|
|
318
|
+
if (n.type !== NodeType.ServiceNode) return;
|
|
319
|
+
const svc = n;
|
|
320
|
+
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
321
|
+
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
322
|
+
});
|
|
323
|
+
let filtered = all;
|
|
324
|
+
if (opts.type) {
|
|
325
|
+
const allowed = opts.type;
|
|
326
|
+
filtered = filtered.filter((d) => allowed.has(d.type));
|
|
327
|
+
}
|
|
328
|
+
if (opts.minConfidence !== void 0) {
|
|
329
|
+
const threshold = opts.minConfidence;
|
|
330
|
+
filtered = filtered.filter((d) => d.confidence >= threshold);
|
|
331
|
+
}
|
|
332
|
+
if (opts.node) {
|
|
333
|
+
const target = opts.node;
|
|
334
|
+
filtered = filtered.filter((d) => involvesNode(d, target));
|
|
335
|
+
}
|
|
336
|
+
filtered.sort((a, b) => {
|
|
337
|
+
if (b.confidence !== a.confidence) return b.confidence - a.confidence;
|
|
338
|
+
if (a.type !== b.type) return a.type.localeCompare(b.type);
|
|
339
|
+
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
340
|
+
return a.target.localeCompare(b.target);
|
|
341
|
+
});
|
|
342
|
+
return DivergenceResultSchema.parse({
|
|
343
|
+
divergences: filtered,
|
|
344
|
+
totalAffected: filtered.length,
|
|
345
|
+
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
346
|
+
});
|
|
347
|
+
}
|
|
101
348
|
|
|
102
349
|
// src/streaming.ts
|
|
103
350
|
var SSE_HEARTBEAT_MS = 3e4;
|
|
@@ -263,6 +510,41 @@ function registerRoutes(scope, ctx) {
|
|
|
263
510
|
}
|
|
264
511
|
return getTransitiveDependencies(proj.graph, id, depth);
|
|
265
512
|
});
|
|
513
|
+
scope.get("/graph/divergences", async (req, reply) => {
|
|
514
|
+
const proj = resolveProject(registry, req, reply);
|
|
515
|
+
if (!proj) return;
|
|
516
|
+
let typeFilter;
|
|
517
|
+
if (req.query.type) {
|
|
518
|
+
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
519
|
+
const parsed = [];
|
|
520
|
+
for (const c of candidates) {
|
|
521
|
+
const r = DivergenceTypeSchema.safeParse(c);
|
|
522
|
+
if (!r.success) {
|
|
523
|
+
return reply.code(400).send({
|
|
524
|
+
error: `unknown divergence type "${c}"`,
|
|
525
|
+
allowed: DivergenceTypeSchema.options
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
parsed.push(r.data);
|
|
529
|
+
}
|
|
530
|
+
typeFilter = new Set(parsed);
|
|
531
|
+
}
|
|
532
|
+
let minConfidence;
|
|
533
|
+
if (req.query.minConfidence !== void 0) {
|
|
534
|
+
const n = Number(req.query.minConfidence);
|
|
535
|
+
if (!Number.isFinite(n) || n < 0 || n > 1) {
|
|
536
|
+
return reply.code(400).send({
|
|
537
|
+
error: "minConfidence must be a number in [0, 1]"
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
minConfidence = n;
|
|
541
|
+
}
|
|
542
|
+
return computeDivergences(proj.graph, {
|
|
543
|
+
...typeFilter ? { type: typeFilter } : {},
|
|
544
|
+
...minConfidence !== void 0 ? { minConfidence } : {},
|
|
545
|
+
...req.query.node ? { node: req.query.node } : {}
|
|
546
|
+
});
|
|
547
|
+
});
|
|
266
548
|
scope.get("/incidents", async (req, reply) => {
|
|
267
549
|
const proj = resolveProject(registry, req, reply);
|
|
268
550
|
if (!proj) return;
|
|
@@ -522,4 +804,4 @@ export {
|
|
|
522
804
|
computeGraphDiff,
|
|
523
805
|
buildApi
|
|
524
806
|
};
|
|
525
|
-
//# sourceMappingURL=chunk-
|
|
807
|
+
//# sourceMappingURL=chunk-W7ZYJZC7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/diff.ts","../src/api.ts","../src/divergences.ts","../src/streaming.ts"],"sourcesContent":["import { promises as fs } from 'node:fs'\nimport type { GraphEdge, GraphNode } from '@neat.is/types'\nimport type { NeatGraph } from './graph.js'\n\n// Diff a snapshot on disk (or fetched over HTTP) against the live in-memory\n// graph. The \"base\" is the snapshot you supplied; the \"current\" is whatever\n// the server has loaded right now. Symmetric in shape, but consumers usually\n// want to read it as \"what changed since base.\"\n//\n// The shape mirrors the issue's spec (#77):\n// { added: { nodes, edges }, removed: { nodes, edges },\n// changed: { nodes: [{id, before, after}], edges: [{id, before, after}] } }\n//\n// Both timestamps are echoed back so the caller doesn't have to track them\n// separately.\n\ninterface PersistedNodeEntry {\n key?: string\n attributes?: Record<string, unknown>\n}\n\ninterface PersistedEdgeEntry {\n key?: string\n source?: string\n target?: string\n attributes?: Record<string, unknown>\n}\n\nexport interface PersistedSnapshot {\n schemaVersion?: number\n exportedAt?: string\n graph?: {\n nodes?: PersistedNodeEntry[]\n edges?: PersistedEdgeEntry[]\n }\n}\n\nexport interface GraphDiff {\n base: { exportedAt?: string }\n current: { exportedAt: string }\n added: { nodes: GraphNode[]; edges: GraphEdge[] }\n removed: { nodes: GraphNode[]; edges: GraphEdge[] }\n changed: {\n nodes: { id: string; before: GraphNode; after: GraphNode }[]\n edges: { id: string; before: GraphEdge; after: GraphEdge }[]\n }\n}\n\nexport async function loadSnapshotForDiff(target: string): Promise<PersistedSnapshot> {\n if (/^https?:\\/\\//i.test(target)) {\n const res = await fetch(target)\n if (!res.ok) {\n throw new Error(`fetch ${target} failed: ${res.status} ${res.statusText}`)\n }\n return (await res.json()) as PersistedSnapshot\n }\n const raw = await fs.readFile(target, 'utf8')\n return JSON.parse(raw) as PersistedSnapshot\n}\n\nfunction indexEntries<T>(\n entries: { key?: string; attributes?: Record<string, unknown> }[] | undefined,\n): Map<string, T> {\n const m = new Map<string, T>()\n if (!entries) return m\n for (const entry of entries) {\n const id = (entry.attributes?.id as string | undefined) ?? entry.key\n if (!id) continue\n m.set(id, entry.attributes as T)\n }\n return m\n}\n\nexport function computeGraphDiff(\n liveGraph: NeatGraph,\n baseSnapshot: PersistedSnapshot,\n currentExportedAt: string = new Date().toISOString(),\n): GraphDiff {\n const baseNodes = indexEntries<GraphNode>(baseSnapshot.graph?.nodes)\n const baseEdges = indexEntries<GraphEdge>(baseSnapshot.graph?.edges)\n\n const liveNodes = new Map<string, GraphNode>()\n liveGraph.forEachNode((id, attrs) => liveNodes.set(id, attrs as GraphNode))\n const liveEdges = new Map<string, GraphEdge>()\n liveGraph.forEachEdge((id, attrs) => liveEdges.set(id, attrs as GraphEdge))\n\n const result: GraphDiff = {\n base: { exportedAt: baseSnapshot.exportedAt },\n current: { exportedAt: currentExportedAt },\n added: { nodes: [], edges: [] },\n removed: { nodes: [], edges: [] },\n changed: { nodes: [], edges: [] },\n }\n\n for (const [id, after] of liveNodes) {\n const before = baseNodes.get(id)\n if (!before) {\n result.added.nodes.push(after)\n } else if (!shallowEqual(before, after)) {\n result.changed.nodes.push({ id, before, after })\n }\n }\n for (const [id, before] of baseNodes) {\n if (!liveNodes.has(id)) result.removed.nodes.push(before)\n }\n for (const [id, after] of liveEdges) {\n const before = baseEdges.get(id)\n if (!before) {\n result.added.edges.push(after)\n } else if (!shallowEqual(before, after)) {\n result.changed.edges.push({ id, before, after })\n }\n }\n for (const [id, before] of baseEdges) {\n if (!liveEdges.has(id)) result.removed.edges.push(before)\n }\n\n return result\n}\n\n// Stable JSON comparison. Snapshot order isn't guaranteed, so canonicalising\n// keys before stringify keeps the comparison robust against re-ordered fields.\nfunction shallowEqual(a: unknown, b: unknown): boolean {\n return canonicalJson(a) === canonicalJson(b)\n}\n\nfunction canonicalJson(value: unknown): string {\n return JSON.stringify(value, (_key, v) => {\n if (v && typeof v === 'object' && !Array.isArray(v)) {\n return Object.keys(v as Record<string, unknown>)\n .sort()\n .reduce<Record<string, unknown>>((acc, k) => {\n acc[k] = (v as Record<string, unknown>)[k]\n return acc\n }, {})\n }\n return v\n })\n}\n","import Fastify, {\n type FastifyInstance,\n type FastifyReply,\n type FastifyRequest,\n} from 'fastify'\nimport cors from '@fastify/cors'\nimport type {\n ErrorEvent,\n GraphEdge,\n GraphNode,\n Policy,\n PolicyViolation,\n} from '@neat.is/types'\nimport { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from '@neat.is/types'\nimport type { DivergenceType } from '@neat.is/types'\nimport { computeDivergences } from './divergences.js'\nimport {\n evaluateAllPolicies,\n loadPolicyFile,\n PolicyViolationsLog,\n} from './policy.js'\nimport type { NeatGraph } from './graph.js'\nimport { DEFAULT_PROJECT } from './graph.js'\nimport { extractFromDirectory } from './extract.js'\nimport { readErrorEvents, readStaleEvents } from './ingest.js'\nimport {\n getBlastRadius,\n getRootCause,\n getTransitiveDependencies,\n TRANSITIVE_DEPENDENCIES_DEFAULT_DEPTH,\n TRANSITIVE_DEPENDENCIES_MAX_DEPTH,\n} from './traverse.js'\nimport { computeGraphDiff, loadSnapshotForDiff } from './diff.js'\nimport type { SearchIndex } from './search.js'\nimport type { Projects, ProjectContext } from './projects.js'\nimport { Projects as ProjectsClass, pathsForProject } from './projects.js'\nimport { listProjects as listRegistryProjects } from './registry.js'\nimport { handleSse } from './streaming.js'\n\nexport interface BuildApiOptions {\n // Multi-project shape. Optional — when absent we synthesise a single-\n // project registry from the legacy fields below so existing callers\n // (mainly tests) keep working unchanged.\n projects?: Projects\n startedAt?: number\n\n // Legacy single-project shape. Mapped to project=`default` if `projects`\n // isn't provided.\n graph?: NeatGraph\n scanPath?: string\n errorsPath?: string\n staleEventsPath?: string\n searchIndex?: SearchIndex\n}\n\ninterface SerializedGraph {\n nodes: GraphNode[]\n edges: GraphEdge[]\n}\n\nfunction serializeGraph(graph: NeatGraph): SerializedGraph {\n const nodes: GraphNode[] = []\n graph.forEachNode((_id, attrs) => {\n nodes.push(attrs)\n })\n const edges: GraphEdge[] = []\n graph.forEachEdge((_id, attrs) => {\n edges.push(attrs)\n })\n return { nodes, edges }\n}\n\nfunction projectFromReq(req: FastifyRequest): string {\n // `:project` is optional in the URL — the request hits either\n // /projects/:project/X or /X (which means default). Coerce the missing\n // param to DEFAULT_PROJECT here so handlers don't repeat the fallback.\n const params = req.params as { project?: string }\n return params.project ?? DEFAULT_PROJECT\n}\n\nfunction resolveProject(\n registry: Projects,\n req: FastifyRequest,\n reply: FastifyReply,\n): ProjectContext | null {\n const name = projectFromReq(req)\n const ctx = registry.get(name)\n if (!ctx) {\n void reply.code(404).send({ error: 'project not found', project: name })\n return null\n }\n return ctx\n}\n\nfunction buildLegacyRegistry(opts: BuildApiOptions): Projects {\n if (opts.projects) return opts.projects\n if (!opts.graph) {\n throw new Error('buildApi: either `projects` or `graph` must be provided')\n }\n const registry = new ProjectsClass()\n // pathsForProject only matters here for the snapshot/embeddings paths\n // routes never read; the ingest paths come from explicit options below.\n const paths = pathsForProject(DEFAULT_PROJECT, '')\n registry.set(DEFAULT_PROJECT, {\n graph: opts.graph,\n scanPath: opts.scanPath,\n paths: {\n snapshotPath: paths.snapshotPath,\n errorsPath: opts.errorsPath ?? paths.errorsPath,\n staleEventsPath: opts.staleEventsPath ?? paths.staleEventsPath,\n embeddingsCachePath: paths.embeddingsCachePath,\n policyViolationsPath: paths.policyViolationsPath,\n },\n searchIndex: opts.searchIndex,\n })\n return registry\n}\n\ninterface RouteContext {\n registry: Projects\n startedAt: number\n // Legacy callers passed `errorsPath`/`staleEventsPath` explicitly and\n // expected absent values to disable the read. Track that intent so the\n // /incidents handlers don't accidentally read a phantom file.\n errorsPathFor: (ctx: ProjectContext) => string | undefined\n staleEventsPathFor: (ctx: ProjectContext) => string | undefined\n // policy.json lives at the project root (per ADR-042 §File location), not\n // under neat-out/. Routes that read it map a project context to the path.\n policyFilePathFor: (ctx: ProjectContext) => string | undefined\n}\n\n// Registers every project-scoped route on `scope`. Called twice from\n// buildApi: once on the root app (so /graph etc. land at default), once\n// inside a `register(_, { prefix: '/projects/:project' })` plugin so the\n// same handlers run when the URL names a project explicitly.\nfunction registerRoutes(scope: FastifyInstance, ctx: RouteContext): void {\n const { registry, startedAt, errorsPathFor, staleEventsPathFor } = ctx\n\n // SSE event stream (ADR-051 #1). Dual-mounted: hits /events for default\n // project and /projects/:project/events when scoped. The handler keeps\n // the connection open and writes one frame per bus envelope whose\n // `project` matches.\n scope.get<{ Params: { project?: string } }>('/events', (req, reply) => {\n const proj = resolveProject(registry, req, reply)\n if (!proj) return\n handleSse(req, reply, { project: proj.name })\n })\n\n scope.get<{ Params: { project?: string } }>('/health', async (req, reply) => {\n const proj = resolveProject(registry, req, reply)\n if (!proj) return\n return {\n uptime: Math.floor((Date.now() - startedAt) / 1000),\n project: proj.name,\n nodeCount: proj.graph.order,\n edgeCount: proj.graph.size,\n lastUpdated: new Date().toISOString(),\n }\n })\n\n scope.get<{ Params: { project?: string } }>('/graph', async (req, reply) => {\n const proj = resolveProject(registry, req, reply)\n if (!proj) return\n return serializeGraph(proj.graph)\n })\n\n scope.get<{ Params: { project?: string; id: string } }>(\n '/graph/node/:id',\n async (req, reply) => {\n const proj = resolveProject(registry, req, reply)\n if (!proj) return\n const { id } = req.params\n if (!proj.graph.hasNode(id)) {\n return reply.code(404).send({ error: 'node not found', id })\n }\n return proj.graph.getNodeAttributes(id) as GraphNode\n },\n )\n\n scope.get<{ Params: { project?: string; id: string } }>(\n '/graph/edges/:id',\n async (req, reply) => {\n const proj = resolveProject(registry, req, reply)\n if (!proj) return\n const { id } = req.params\n if (!proj.graph.hasNode(id)) {\n return reply.code(404).send({ error: 'node not found', id })\n }\n const inbound = proj.graph\n .inboundEdges(id)\n .map((e) => proj.graph.getEdgeAttributes(e) as GraphEdge)\n const outbound = proj.graph\n .outboundEdges(id)\n .map((e) => proj.graph.getEdgeAttributes(e) as GraphEdge)\n return { inbound, outbound }\n },\n )\n\n // Transitive dependencies (issue #144). BFS outbound to depth N, returning\n // a flat list with distance + edgeType + provenance per dependency.\n // Default depth 3, max 10. The MCP get_dependencies tool calls this.\n scope.get<{\n Params: { project?: string; id: string }\n Querystring: { depth?: string }\n }>('/graph/node/:id/dependencies', async (req, reply) => {\n const proj = resolveProject(registry, req, reply)\n if (!proj) return\n const { id } = req.params\n if (!proj.graph.hasNode(id)) {\n return reply.code(404).send({ error: 'node not found', id })\n }\n const depth = req.query.depth ? Number(req.query.depth) : TRANSITIVE_DEPENDENCIES_DEFAULT_DEPTH\n if (!Number.isFinite(depth) || depth < 1 || depth > TRANSITIVE_DEPENDENCIES_MAX_DEPTH) {\n return reply.code(400).send({\n error: `depth must be an integer in [1, ${TRANSITIVE_DEPENDENCIES_MAX_DEPTH}]`,\n })\n }\n return getTransitiveDependencies(proj.graph, id, depth)\n })\n\n // Divergence query — the thesis surface (ADR-060). Read-only, derived,\n // dual-mounted via registerRoutes. Query params filter the result set;\n // body shape is DivergenceResult.\n scope.get<{\n Params: { project?: string }\n Querystring: { type?: string; minConfidence?: string; node?: string }\n }>('/graph/divergences', async (req, reply) => {\n const proj = resolveProject(registry, req, reply)\n if (!proj) return\n let typeFilter: Set<DivergenceType> | undefined\n if (req.query.type) {\n const candidates = req.query.type\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s.length > 0)\n const parsed: DivergenceType[] = []\n for (const c of candidates) {\n const r = DivergenceTypeSchema.safeParse(c)\n if (!r.success) {\n return reply.code(400).send({\n error: `unknown divergence type \"${c}\"`,\n allowed: DivergenceTypeSchema.options,\n })\n }\n parsed.push(r.data)\n }\n typeFilter = new Set(parsed)\n }\n let minConfidence: number | undefined\n if (req.query.minConfidence !== undefined) {\n const n = Number(req.query.minConfidence)\n if (!Number.isFinite(n) || n < 0 || n > 1) {\n return reply.code(400).send({\n error: 'minConfidence must be a number in [0, 1]',\n })\n }\n minConfidence = n\n }\n return computeDivergences(proj.graph, {\n ...(typeFilter ? { type: typeFilter } : {}),\n ...(minConfidence !== undefined ? { minConfidence } : {}),\n ...(req.query.node ? { node: req.query.node } : {}),\n })\n })\n\n scope.get<{ Params: { project?: string } }>('/incidents', async (req, reply) => {\n const proj = resolveProject(registry, req, reply)\n if (!proj) return\n const epath = errorsPathFor(proj)\n if (!epath) return []\n return readErrorEvents(epath)\n })\n\n scope.get<{\n Params: { project?: string }\n Querystring: { limit?: string; edgeType?: string }\n }>('/incidents/stale', async (req, reply) => {\n const proj = resolveProject(registry, req, reply)\n if (!proj) return\n const spath = staleEventsPathFor(proj)\n if (!spath) return []\n const events = await readStaleEvents(spath)\n const filtered = req.query.edgeType\n ? events.filter((e) => e.edgeType === req.query.edgeType)\n : events\n const ordered = [...filtered].reverse()\n const limit = req.query.limit ? Number(req.query.limit) : 50\n return ordered.slice(0, Number.isFinite(limit) && limit > 0 ? limit : 50)\n })\n\n scope.get<{ Params: { project?: string; nodeId: string } }>(\n '/incidents/:nodeId',\n async (req, reply) => {\n const proj = resolveProject(registry, req, reply)\n if (!proj) return\n const { nodeId } = req.params\n if (!proj.graph.hasNode(nodeId)) {\n return reply.code(404).send({ error: 'node not found', id: nodeId })\n }\n const epath = errorsPathFor(proj)\n if (!epath) return []\n const events = await readErrorEvents(epath)\n return events.filter(\n (e) =>\n e.affectedNode === nodeId || e.service === nodeId.replace(/^service:/, ''),\n )\n },\n )\n\n scope.get<{\n Params: { project?: string; nodeId: string }\n Querystring: { errorId?: string }\n }>('/traverse/root-cause/:nodeId', async (req, reply) => {\n const proj = resolveProject(registry, req, reply)\n if (!proj) return\n const { nodeId } = req.params\n if (!proj.graph.hasNode(nodeId)) {\n return reply.code(404).send({ error: 'node not found', id: nodeId })\n }\n let errorEvent: ErrorEvent | undefined\n const epath = errorsPathFor(proj)\n if (req.query.errorId && epath) {\n const events = await readErrorEvents(epath)\n errorEvent = events.find((e) => e.id === req.query.errorId)\n if (!errorEvent) {\n return reply\n .code(404)\n .send({ error: 'error event not found', id: req.query.errorId })\n }\n }\n const result = getRootCause(proj.graph, nodeId, errorEvent)\n if (!result) return reply.code(404).send({ error: 'no root cause found', id: nodeId })\n return result\n })\n\n scope.get<{\n Params: { project?: string; nodeId: string }\n Querystring: { depth?: string }\n }>('/traverse/blast-radius/:nodeId', async (req, reply) => {\n const proj = resolveProject(registry, req, reply)\n if (!proj) return\n const { nodeId } = req.params\n if (!proj.graph.hasNode(nodeId)) {\n return reply.code(404).send({ error: 'node not found', id: nodeId })\n }\n const depth = req.query.depth ? Number(req.query.depth) : undefined\n if (depth !== undefined && (!Number.isFinite(depth) || depth < 0)) {\n return reply.code(400).send({ error: 'depth must be a non-negative number' })\n }\n return getBlastRadius(proj.graph, nodeId, depth)\n })\n\n scope.get<{\n Params: { project?: string }\n Querystring: { q?: string; limit?: string }\n }>('/search', async (req, reply) => {\n const proj = resolveProject(registry, req, reply)\n if (!proj) return\n const raw = (req.query.q ?? '').trim()\n if (!raw) return reply.code(400).send({ error: 'query parameter `q` is required' })\n const limit = req.query.limit ? Number(req.query.limit) : undefined\n const safeLimit =\n limit !== undefined && Number.isFinite(limit) && limit > 0 ? limit : undefined\n if (proj.searchIndex) {\n const result = await proj.searchIndex.search(raw, safeLimit)\n return {\n query: result.query,\n provider: result.provider,\n matches: result.matches.map((m) => ({ ...m.node, score: m.score })),\n }\n }\n const q = raw.toLowerCase()\n const matches: (GraphNode & { score: number })[] = []\n proj.graph.forEachNode((id, attrs) => {\n const name = (attrs as { name?: string }).name ?? ''\n if (id.toLowerCase().includes(q) || name.toLowerCase().includes(q)) {\n matches.push({ ...(attrs as GraphNode), score: 1 })\n }\n })\n return {\n query: q,\n provider: 'substring' as const,\n matches: matches.slice(0, safeLimit),\n }\n })\n\n scope.get<{ Params: { project?: string }; Querystring: { against?: string } }>(\n '/graph/diff',\n async (req, reply) => {\n const proj = resolveProject(registry, req, reply)\n if (!proj) return\n const against = req.query.against\n if (!against) {\n return reply.code(400).send({ error: 'query parameter `against` is required' })\n }\n try {\n const snapshot = await loadSnapshotForDiff(against)\n return computeGraphDiff(proj.graph, snapshot)\n } catch (err) {\n return reply\n .code(400)\n .send({ error: 'failed to load snapshot', against, detail: (err as Error).message })\n }\n },\n )\n\n scope.post<{ Params: { project?: string } }>('/graph/scan', async (req, reply) => {\n const proj = resolveProject(registry, req, reply)\n if (!proj) return\n if (!proj.scanPath) {\n return reply\n .code(409)\n .send({ error: 'scan path not configured for this project', project: proj.name })\n }\n const result = await extractFromDirectory(proj.graph, proj.scanPath)\n return {\n project: proj.name,\n scanned: proj.scanPath,\n nodesAdded: result.nodesAdded,\n edgesAdded: result.edgesAdded,\n nodeCount: proj.graph.order,\n edgeCount: proj.graph.size,\n }\n })\n\n // Policy surface (ADR-045 / contract #18). /policies returns the parsed\n // policy.json; /policies/violations is the persistent log; /policies/check\n // is dry-run evaluation. All dual-mounted via registerRoutes.\n scope.get<{ Params: { project?: string } }>('/policies', async (req, reply) => {\n const proj = resolveProject(registry, req, reply)\n if (!proj) return\n const policyPath = ctx.policyFilePathFor(proj)\n if (!policyPath) {\n // No policy file configured for this project — return the empty file\n // shape so consumers don't have to special-case \"no policies yet.\"\n return { version: 1, policies: [] }\n }\n try {\n const policies = await loadPolicyFile(policyPath)\n return { version: 1, policies }\n } catch (err) {\n return reply.code(400).send({\n error: 'policy.json failed to parse',\n details: (err as Error).message,\n })\n }\n })\n\n scope.get<{\n Params: { project?: string }\n Querystring: { severity?: string; policyId?: string }\n }>('/policies/violations', async (req, reply) => {\n const proj = resolveProject(registry, req, reply)\n if (!proj) return\n const log = new PolicyViolationsLog(proj.paths.policyViolationsPath)\n let violations = await log.readAll()\n if (req.query.severity) {\n const sev = PolicySeveritySchema.safeParse(req.query.severity)\n if (!sev.success) {\n return reply.code(400).send({\n error: 'invalid severity',\n details: sev.error.format(),\n })\n }\n violations = violations.filter((v) => v.severity === sev.data)\n }\n if (req.query.policyId) {\n violations = violations.filter((v) => v.policyId === req.query.policyId)\n }\n return violations\n })\n\n scope.post<{\n Params: { project?: string }\n Body: { hypotheticalAction?: unknown }\n }>('/policies/check', async (req, reply) => {\n const proj = resolveProject(registry, req, reply)\n if (!proj) return\n const parsed = PoliciesCheckBodySchema.safeParse(req.body ?? {})\n if (!parsed.success) {\n return reply.code(400).send({\n error: 'invalid /policies/check body',\n details: parsed.error.format(),\n })\n }\n\n const policyPath = ctx.policyFilePathFor(proj)\n let policies: Policy[] = []\n if (policyPath) {\n try {\n policies = await loadPolicyFile(policyPath)\n } catch (err) {\n return reply.code(400).send({\n error: 'policy.json failed to parse',\n details: (err as Error).message,\n })\n }\n }\n\n // No hypothetical → return current violations against the live graph.\n // With a hypothetical → simulate the action against a deep-copy graph\n // (avoids mutation authority concerns), evaluate, return the delta.\n const evalCtx = { now: () => Date.now() }\n if (!parsed.data.hypotheticalAction) {\n const violations = evaluateAllPolicies(proj.graph, policies, evalCtx)\n const blocking = violations.filter((v) => v.onViolation === 'block')\n return { allowed: blocking.length === 0, violations }\n }\n\n // For now the dry-run simulation re-uses evaluateAllPolicies on the\n // current graph. Full hypothetical simulation (e.g. \"what if I added\n // this OBSERVED edge?\") is the v0.2.4-δ scope; #117 ships the surface,\n // #118 fills in the action shapes' simulation logic.\n const violations = evaluateAllPolicies(proj.graph, policies, evalCtx)\n const blocking = violations.filter((v) => v.onViolation === 'block')\n return {\n allowed: blocking.length === 0,\n hypotheticalAction: parsed.data.hypotheticalAction,\n violations,\n } as { allowed: boolean; hypotheticalAction: unknown; violations: PolicyViolation[] }\n })\n}\n\nexport async function buildApi(opts: BuildApiOptions): Promise<FastifyInstance> {\n const app = Fastify({ logger: false })\n await app.register(cors, { origin: true })\n\n const startedAt = opts.startedAt ?? Date.now()\n const registry = buildLegacyRegistry(opts)\n\n const legacyErrorsExplicit = !opts.projects && opts.errorsPath !== undefined\n const legacyStaleExplicit = !opts.projects && opts.staleEventsPath !== undefined\n\n const errorsPathFor = (proj: ProjectContext): string | undefined => {\n if (proj.name === DEFAULT_PROJECT && !opts.projects) {\n return legacyErrorsExplicit ? opts.errorsPath : undefined\n }\n return proj.paths.errorsPath\n }\n const staleEventsPathFor = (proj: ProjectContext): string | undefined => {\n if (proj.name === DEFAULT_PROJECT && !opts.projects) {\n return legacyStaleExplicit ? opts.staleEventsPath : undefined\n }\n return proj.paths.staleEventsPath\n }\n\n // policy.json lives at the project's scanPath root per ADR-042. Without a\n // scanPath we have nowhere to read it from — those projects show as\n // \"no policies configured\" via the empty-file response.\n const policyFilePathFor = (proj: ProjectContext): string | undefined => {\n if (!proj.scanPath) return undefined\n return `${proj.scanPath}/policy.json`\n }\n\n const routeCtx: RouteContext = {\n registry,\n startedAt,\n errorsPathFor,\n staleEventsPathFor,\n policyFilePathFor,\n }\n\n // Multi-project switcher (ADR-051 #4). Direct passthrough of the\n // machine-level registry from registry.ts (ADR-048) — distinct from the\n // dual-mount routing in ADR-026, which exposes per-project endpoints.\n // Returns Array<{ name, path, status, registeredAt, lastSeenAt?, languages }>.\n app.get('/projects', async (_req, reply) => {\n try {\n return await listRegistryProjects()\n } catch (err) {\n return reply.code(500).send({\n error: 'failed to read project registry',\n details: (err as Error).message,\n })\n }\n })\n\n // Default mount: /health, /graph, /incidents, etc. all hit project=default.\n registerRoutes(app, routeCtx)\n\n // Project-scoped mount: same handlers, URL params include `:project`.\n await app.register(\n async (scope) => {\n registerRoutes(scope, routeCtx)\n },\n { prefix: '/projects/:project' },\n )\n\n return app\n}\n","// computeDivergences — the thesis surface, derived (ADR-060).\n//\n// Walks the live graph and surfaces the five locked divergence shapes:\n// missing-observed, missing-extracted, version-mismatch, host-mismatch,\n// and compat-violation. Pure: no I/O, no mutation, no async. The function\n// operates on a NeatGraph reference and returns a fresh DivergenceResult\n// each call — there is no persistence (binding rule 2).\n//\n// Mutation authority (ADR-030 / contract #3) is locked to ingest.ts and\n// extract/*; this module reads only. The contract test\n// `packages/core/test/audits/contracts.test.ts` enforces it.\n\nimport type {\n CompatRuleRef,\n Divergence,\n DivergenceResult,\n DivergenceType,\n GraphEdge,\n GraphNode,\n ServiceNode,\n} from '@neat.is/types'\nimport {\n DivergenceResultSchema,\n EdgeType,\n NodeType,\n parseEdgeId,\n Provenance,\n} from '@neat.is/types'\nimport type { NeatGraph } from './graph.js'\nimport {\n checkCompatibility,\n checkDeprecatedApi,\n compatPairs,\n deprecatedApis,\n} from './compat.js'\nimport { confidenceForEdge } from './traverse.js'\n\nexport interface DivergenceQueryOpts {\n // Filter the result to a subset of divergence types. Undefined keeps all\n // five. Empty set returns nothing.\n type?: ReadonlySet<DivergenceType>\n // Drop divergences below this confidence threshold. Undefined keeps all.\n minConfidence?: number\n // Scope to divergences that involve this node (as source or target).\n node?: string\n}\n\n// (source, target, type) → which provenance variants are present. Each\n// bucket is the unit the missing-observed / missing-extracted detectors\n// operate over.\ninterface EdgeBucket {\n source: string\n target: string\n type: GraphEdge['type']\n extracted?: GraphEdge\n observed?: GraphEdge\n inferred?: GraphEdge\n stale?: GraphEdge\n frontier?: GraphEdge\n}\n\nfunction bucketKey(source: string, target: string, type: string): string {\n return `${type}|${source}|${target}`\n}\n\nfunction bucketEdges(graph: NeatGraph): Map<string, EdgeBucket> {\n const buckets = new Map<string, EdgeBucket>()\n graph.forEachEdge((id, attrs) => {\n const e = attrs as GraphEdge\n const parsed = parseEdgeId(id)\n // parseEdgeId can fall through to EXTRACTED for unknown shapes — fall\n // back to the edge's own provenance when the id doesn't parse cleanly.\n const provenance = parsed?.provenance ?? e.provenance\n const key = bucketKey(e.source, e.target, e.type)\n const cur =\n buckets.get(key) ?? { source: e.source, target: e.target, type: e.type }\n switch (provenance) {\n case Provenance.EXTRACTED:\n cur.extracted = e\n break\n case Provenance.OBSERVED:\n cur.observed = e\n break\n case Provenance.INFERRED:\n cur.inferred = e\n break\n case Provenance.FRONTIER:\n cur.frontier = e\n break\n default:\n // STALE rides on what used to be an OBSERVED edge — the id format\n // stays OBSERVED per identity.ts, so this branch is mostly defensive.\n if (e.provenance === Provenance.STALE) cur.stale = e\n }\n buckets.set(key, cur)\n })\n return buckets\n}\n\nfunction nodeIsFrontier(graph: NeatGraph, nodeId: string): boolean {\n if (!graph.hasNode(nodeId)) return false\n const attrs = graph.getNodeAttributes(nodeId) as GraphNode\n return attrs.type === NodeType.FrontierNode\n}\n\nfunction hasAnyObservedFromSource(graph: NeatGraph, sourceId: string): boolean {\n if (!graph.hasNode(sourceId)) return false\n for (const edgeId of graph.outboundEdges(sourceId)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (e.provenance === Provenance.OBSERVED) return true\n }\n return false\n}\n\nfunction clampConfidence(n: number): number {\n if (!Number.isFinite(n)) return 0\n return Math.max(0, Math.min(1, n))\n}\n\nfunction reasonForMissingObserved(source: string, target: string, type: string): string {\n return `Code declares ${source} → ${target} (${type}) but no production traffic has been observed for this edge.`\n}\n\nfunction reasonForMissingExtracted(source: string, target: string, type: string): string {\n return `Production observed ${source} → ${target} (${type}) but static analysis did not surface this edge.`\n}\n\nconst RECOMMENDATION_MISSING_OBSERVED =\n 'Verify the code path is exercised in production; check feature flags or conditional branches that might gate the call.'\nconst RECOMMENDATION_MISSING_EXTRACTED =\n '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.'\nconst RECOMMENDATION_HOST_MISMATCH =\n 'Check environment-specific config overrides — the runtime host differs from what static configuration declares.'\n\nfunction detectMissingDivergences(\n graph: NeatGraph,\n bucket: EdgeBucket,\n): Divergence[] {\n const out: Divergence[] = []\n\n if (bucket.extracted && !bucket.observed) {\n // Skip when the would-be target is a FrontierNode — those represent\n // unresolved span peers, not real entities we expect OBSERVED traffic\n // to. The coexistence contract is between EXTRACTED and OBSERVED on\n // real nodes; FRONTIER is unknown territory.\n if (!nodeIsFrontier(graph, bucket.target)) {\n const sourceHasTraffic = hasAnyObservedFromSource(graph, bucket.source)\n out.push({\n type: 'missing-observed',\n source: bucket.source,\n target: bucket.target,\n edgeType: bucket.type,\n extracted: bucket.extracted,\n confidence: sourceHasTraffic ? 1.0 : 0.5,\n reason: reasonForMissingObserved(bucket.source, bucket.target, bucket.type),\n recommendation: RECOMMENDATION_MISSING_OBSERVED,\n })\n }\n }\n\n if (bucket.observed && !bucket.extracted) {\n const cascaded = clampConfidence(confidenceForEdge(bucket.observed))\n out.push({\n type: 'missing-extracted',\n source: bucket.source,\n target: bucket.target,\n edgeType: bucket.type,\n observed: bucket.observed,\n confidence: cascaded,\n reason: reasonForMissingExtracted(bucket.source, bucket.target, bucket.type),\n recommendation: RECOMMENDATION_MISSING_EXTRACTED,\n })\n }\n\n return out\n}\n\n// Returns the declared host of the service's static DB target, when\n// recoverable. ServiceNode.dbConnectionTarget is the static-extraction\n// surface for \"this service connects to X\" — `X` is host[:port] or a\n// docker-compose-style service name. Empty / undefined means we have no\n// EXTRACTED host to compare against and host-mismatch can't fire.\nfunction declaredHostFor(svc: ServiceNode): string | null {\n const raw = svc.dbConnectionTarget?.trim()\n if (!raw) return null\n // Strip a trailing port if present so it lines up with DatabaseNode.host\n // (ADR-028 §6 — DatabaseNode id excludes port).\n const colon = raw.lastIndexOf(':')\n if (colon === -1) return raw\n const port = raw.slice(colon + 1)\n if (/^\\d+$/.test(port)) return raw.slice(0, colon)\n return raw\n}\n\nfunction hasExtractedConfiguredBy(graph: NeatGraph, svcId: string): boolean {\n for (const edgeId of graph.outboundEdges(svcId)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (e.type === EdgeType.CONFIGURED_BY && e.provenance === Provenance.EXTRACTED) {\n return true\n }\n }\n return false\n}\n\nfunction detectHostMismatch(\n graph: NeatGraph,\n svcId: string,\n svc: ServiceNode,\n): Divergence[] {\n const declaredHost = declaredHostFor(svc)\n if (!declaredHost) return []\n if (!hasExtractedConfiguredBy(graph, svcId)) return []\n\n const out: Divergence[] = []\n for (const edgeId of graph.outboundEdges(svcId)) {\n const edge = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (edge.type !== EdgeType.CONNECTS_TO) continue\n if (edge.provenance !== Provenance.OBSERVED) continue\n const target = graph.getNodeAttributes(edge.target) as GraphNode\n if (target.type !== NodeType.DatabaseNode) continue\n const observedHost = target.host?.trim()\n if (!observedHost) continue\n if (observedHost === declaredHost) continue\n\n out.push({\n type: 'host-mismatch',\n source: svcId,\n target: edge.target,\n extractedHost: declaredHost,\n observedHost,\n confidence: clampConfidence(confidenceForEdge(edge)),\n reason: `Config declares ${svcId} connects to ${declaredHost}; production connects to ${observedHost}.`,\n recommendation: RECOMMENDATION_HOST_MISMATCH,\n })\n }\n return out\n}\n\nfunction detectCompatDivergences(\n graph: NeatGraph,\n svcId: string,\n svc: ServiceNode,\n): Divergence[] {\n const out: Divergence[] = []\n const deps = svc.dependencies ?? {}\n\n for (const edgeId of graph.outboundEdges(svcId)) {\n const edge = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (edge.type !== EdgeType.CONNECTS_TO) continue\n if (edge.provenance !== Provenance.OBSERVED) continue\n const target = graph.getNodeAttributes(edge.target) as GraphNode\n if (target.type !== NodeType.DatabaseNode) continue\n\n // Driver-engine compat. Definitive — when a rule fires it's a\n // version-mismatch with confidence 1.0.\n for (const pair of compatPairs()) {\n if (pair.engine !== target.engine) continue\n const declared = deps[pair.driver]\n if (!declared) continue\n const result = checkCompatibility(\n pair.driver,\n declared,\n target.engine,\n target.engineVersion,\n )\n if (!result.compatible && result.reason) {\n out.push({\n type: 'version-mismatch',\n source: svcId,\n target: edge.target,\n extractedVersion: declared,\n observedVersion: target.engineVersion,\n compatibility: 'incompatible',\n confidence: 1.0,\n reason: result.reason,\n recommendation: result.minDriverVersion\n ? `Upgrade ${pair.driver} to >= ${result.minDriverVersion}.`\n : `Update the ${pair.driver} driver to a version compatible with ${target.engine} ${target.engineVersion}.`,\n })\n }\n }\n\n // Deprecated-api compat. Broader than version-mismatch — surfaces as\n // compat-violation. Driver-engine rules above already covered the\n // \"version is too low\" shape; deprecated covers \"version is too high\n // / no longer supported.\"\n for (const rule of deprecatedApis()) {\n const declared = deps[rule.package]\n if (!declared) continue\n const result = checkDeprecatedApi(rule, declared)\n if (!result.compatible && result.reason) {\n const ruleRef: CompatRuleRef = {\n kind: rule.kind ?? 'deprecated-api',\n reason: result.reason,\n package: rule.package,\n }\n out.push({\n type: 'compat-violation',\n source: svcId,\n target: edge.target,\n rule: ruleRef,\n observed: edge,\n confidence: 1.0,\n reason: result.reason,\n recommendation: `Replace deprecated ${rule.package}@${declared} with a supported version.`,\n })\n }\n }\n }\n return out\n}\n\nfunction involvesNode(d: Divergence, nodeId: string): boolean {\n return d.source === nodeId || d.target === nodeId\n}\n\nexport function computeDivergences(\n graph: NeatGraph,\n opts: DivergenceQueryOpts = {},\n): DivergenceResult {\n const all: Divergence[] = []\n\n // Pass 1 — bucket every edge and emit missing-observed / missing-extracted.\n const buckets = bucketEdges(graph)\n for (const bucket of buckets.values()) {\n for (const d of detectMissingDivergences(graph, bucket)) all.push(d)\n }\n\n // Pass 2 — per-service host + compat rules.\n graph.forEachNode((nodeId, attrs) => {\n const n = attrs as GraphNode\n if (n.type !== NodeType.ServiceNode) return\n const svc = n as ServiceNode\n for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d)\n for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d)\n })\n\n // Filter + sort. Higher confidence first; within the same confidence,\n // stable on (type, source, target) so callers see deterministic output.\n let filtered = all\n if (opts.type) {\n const allowed = opts.type\n filtered = filtered.filter((d) => allowed.has(d.type))\n }\n if (opts.minConfidence !== undefined) {\n const threshold = opts.minConfidence\n filtered = filtered.filter((d) => d.confidence >= threshold)\n }\n if (opts.node) {\n const target = opts.node\n filtered = filtered.filter((d) => involvesNode(d, target))\n }\n\n filtered.sort((a, b) => {\n if (b.confidence !== a.confidence) return b.confidence - a.confidence\n if (a.type !== b.type) return a.type.localeCompare(b.type)\n if (a.source !== b.source) return a.source.localeCompare(b.source)\n return a.target.localeCompare(b.target)\n })\n\n return DivergenceResultSchema.parse({\n divergences: filtered,\n totalAffected: filtered.length,\n computedAt: new Date().toISOString(),\n })\n}\n","// SSE handler for the frontend-facing event stream (ADR-051 #1).\n// Subscribes to the bus in events.ts, filters by project, writes\n// `event: <type>\\ndata: <json>\\n\\n` frames to the client.\n//\n// Backpressure: per-connection queue cap of 1000 outstanding writes; once\n// hit, the connection is dropped with `event: error data: { reason:\n// 'backpressure' }` per ADR-051 #8. Heartbeat: comment line every 30s\n// keeps proxies from idle-timing out (ADR-051 #3).\n\nimport type { FastifyReply, FastifyRequest } from 'fastify'\nimport {\n EVENT_BUS_CHANNEL,\n eventBus,\n type NeatEventEnvelope,\n} from './events.js'\n\nexport const SSE_HEARTBEAT_MS = 30_000\nexport const SSE_BACKPRESSURE_CAP = 1000\n\nexport interface HandleSseOptions {\n project: string\n heartbeatMs?: number\n backpressureCap?: number\n}\n\nexport function handleSse(\n req: FastifyRequest,\n reply: FastifyReply,\n opts: HandleSseOptions,\n): void {\n const heartbeatMs = opts.heartbeatMs ?? SSE_HEARTBEAT_MS\n const backpressureCap = opts.backpressureCap ?? SSE_BACKPRESSURE_CAP\n\n reply.raw.setHeader('Content-Type', 'text/event-stream')\n reply.raw.setHeader('Cache-Control', 'no-cache, no-transform')\n reply.raw.setHeader('Connection', 'keep-alive')\n reply.raw.setHeader('X-Accel-Buffering', 'no')\n reply.raw.flushHeaders?.()\n\n let pending = 0\n let dropped = false\n\n const closeConnection = (): void => {\n if (dropped) return\n dropped = true\n eventBus.off(EVENT_BUS_CHANNEL, listener)\n clearInterval(heartbeat)\n if (!reply.raw.writableEnded) reply.raw.end()\n }\n\n const writeFrame = (frame: string): void => {\n if (dropped) return\n if (pending >= backpressureCap) {\n // Past the cap — emit one final error frame and drop. Don't try to\n // gracefully drain; a slow consumer that's already 1000 frames behind\n // is not going to catch up.\n const errFrame = `event: error\\ndata: ${JSON.stringify({ reason: 'backpressure' })}\\n\\n`\n reply.raw.write(errFrame)\n closeConnection()\n return\n }\n pending++\n reply.raw.write(frame, () => {\n pending = Math.max(0, pending - 1)\n })\n }\n\n const listener = (envelope: NeatEventEnvelope): void => {\n if (envelope.project !== opts.project) return\n writeFrame(`event: ${envelope.type}\\ndata: ${JSON.stringify(envelope.payload)}\\n\\n`)\n }\n\n eventBus.on(EVENT_BUS_CHANNEL, listener)\n\n const heartbeat = setInterval(() => {\n if (dropped) return\n reply.raw.write(':heartbeat\\n\\n')\n }, heartbeatMs)\n if (typeof heartbeat.unref === 'function') heartbeat.unref()\n\n req.raw.on('close', closeConnection)\n reply.raw.on('close', closeConnection)\n reply.raw.on('error', closeConnection)\n}\n"],"mappings":"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","names":["violations","blocking"]}
|