@neat.is/core 0.2.5

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.
Files changed (44) hide show
  1. package/compat.json +120 -0
  2. package/dist/chunk-6JT6L2OV.js +164 -0
  3. package/dist/chunk-6JT6L2OV.js.map +1 -0
  4. package/dist/chunk-6SFEITLJ.js +3371 -0
  5. package/dist/chunk-6SFEITLJ.js.map +1 -0
  6. package/dist/chunk-I5IMCXRO.js +325 -0
  7. package/dist/chunk-I5IMCXRO.js.map +1 -0
  8. package/dist/chunk-T2U4U256.js +462 -0
  9. package/dist/chunk-T2U4U256.js.map +1 -0
  10. package/dist/chunk-WX55TLUT.js +184 -0
  11. package/dist/chunk-WX55TLUT.js.map +1 -0
  12. package/dist/chunk-XOOCA5T7.js +290 -0
  13. package/dist/chunk-XOOCA5T7.js.map +1 -0
  14. package/dist/cli.cjs +5754 -0
  15. package/dist/cli.cjs.map +1 -0
  16. package/dist/cli.d.cts +36 -0
  17. package/dist/cli.d.ts +36 -0
  18. package/dist/cli.js +1175 -0
  19. package/dist/cli.js.map +1 -0
  20. package/dist/index.cjs +4552 -0
  21. package/dist/index.cjs.map +1 -0
  22. package/dist/index.d.cts +408 -0
  23. package/dist/index.d.ts +408 -0
  24. package/dist/index.js +93 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/neatd.cjs +3070 -0
  27. package/dist/neatd.cjs.map +1 -0
  28. package/dist/neatd.d.cts +1 -0
  29. package/dist/neatd.d.ts +1 -0
  30. package/dist/neatd.js +114 -0
  31. package/dist/neatd.js.map +1 -0
  32. package/dist/otel-grpc-B4XBSI4W.js +9 -0
  33. package/dist/otel-grpc-B4XBSI4W.js.map +1 -0
  34. package/dist/server.cjs +4499 -0
  35. package/dist/server.cjs.map +1 -0
  36. package/dist/server.d.cts +2 -0
  37. package/dist/server.d.ts +2 -0
  38. package/dist/server.js +97 -0
  39. package/dist/server.js.map +1 -0
  40. package/package.json +77 -0
  41. package/proto/opentelemetry/proto/collector/trace/v1/trace_service.proto +31 -0
  42. package/proto/opentelemetry/proto/common/v1/common.proto +46 -0
  43. package/proto/opentelemetry/proto/resource/v1/resource.proto +19 -0
  44. package/proto/opentelemetry/proto/trace/v1/trace.proto +93 -0
@@ -0,0 +1,462 @@
1
+ import {
2
+ DEFAULT_PROJECT,
3
+ PolicyViolationsLog,
4
+ Projects,
5
+ TRANSITIVE_DEPENDENCIES_DEFAULT_DEPTH,
6
+ TRANSITIVE_DEPENDENCIES_MAX_DEPTH,
7
+ evaluateAllPolicies,
8
+ extractFromDirectory,
9
+ getBlastRadius,
10
+ getRootCause,
11
+ getTransitiveDependencies,
12
+ loadPolicyFile,
13
+ pathsForProject,
14
+ readErrorEvents,
15
+ readStaleEvents
16
+ } from "./chunk-6SFEITLJ.js";
17
+
18
+ // src/diff.ts
19
+ import { promises as fs } from "fs";
20
+ async function loadSnapshotForDiff(target) {
21
+ if (/^https?:\/\//i.test(target)) {
22
+ const res = await fetch(target);
23
+ if (!res.ok) {
24
+ throw new Error(`fetch ${target} failed: ${res.status} ${res.statusText}`);
25
+ }
26
+ return await res.json();
27
+ }
28
+ const raw = await fs.readFile(target, "utf8");
29
+ return JSON.parse(raw);
30
+ }
31
+ function indexEntries(entries) {
32
+ const m = /* @__PURE__ */ new Map();
33
+ if (!entries) return m;
34
+ for (const entry of entries) {
35
+ const id = entry.attributes?.id ?? entry.key;
36
+ if (!id) continue;
37
+ m.set(id, entry.attributes);
38
+ }
39
+ return m;
40
+ }
41
+ function computeGraphDiff(liveGraph, baseSnapshot, currentExportedAt = (/* @__PURE__ */ new Date()).toISOString()) {
42
+ const baseNodes = indexEntries(baseSnapshot.graph?.nodes);
43
+ const baseEdges = indexEntries(baseSnapshot.graph?.edges);
44
+ const liveNodes = /* @__PURE__ */ new Map();
45
+ liveGraph.forEachNode((id, attrs) => liveNodes.set(id, attrs));
46
+ const liveEdges = /* @__PURE__ */ new Map();
47
+ liveGraph.forEachEdge((id, attrs) => liveEdges.set(id, attrs));
48
+ const result = {
49
+ base: { exportedAt: baseSnapshot.exportedAt },
50
+ current: { exportedAt: currentExportedAt },
51
+ added: { nodes: [], edges: [] },
52
+ removed: { nodes: [], edges: [] },
53
+ changed: { nodes: [], edges: [] }
54
+ };
55
+ for (const [id, after] of liveNodes) {
56
+ const before = baseNodes.get(id);
57
+ if (!before) {
58
+ result.added.nodes.push(after);
59
+ } else if (!shallowEqual(before, after)) {
60
+ result.changed.nodes.push({ id, before, after });
61
+ }
62
+ }
63
+ for (const [id, before] of baseNodes) {
64
+ if (!liveNodes.has(id)) result.removed.nodes.push(before);
65
+ }
66
+ for (const [id, after] of liveEdges) {
67
+ const before = baseEdges.get(id);
68
+ if (!before) {
69
+ result.added.edges.push(after);
70
+ } else if (!shallowEqual(before, after)) {
71
+ result.changed.edges.push({ id, before, after });
72
+ }
73
+ }
74
+ for (const [id, before] of baseEdges) {
75
+ if (!liveEdges.has(id)) result.removed.edges.push(before);
76
+ }
77
+ return result;
78
+ }
79
+ function shallowEqual(a, b) {
80
+ return canonicalJson(a) === canonicalJson(b);
81
+ }
82
+ function canonicalJson(value) {
83
+ return JSON.stringify(value, (_key, v) => {
84
+ if (v && typeof v === "object" && !Array.isArray(v)) {
85
+ return Object.keys(v).sort().reduce((acc, k) => {
86
+ acc[k] = v[k];
87
+ return acc;
88
+ }, {});
89
+ }
90
+ return v;
91
+ });
92
+ }
93
+
94
+ // src/api.ts
95
+ import Fastify from "fastify";
96
+ import cors from "@fastify/cors";
97
+ import { PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
98
+ function serializeGraph(graph) {
99
+ const nodes = [];
100
+ graph.forEachNode((_id, attrs) => {
101
+ nodes.push(attrs);
102
+ });
103
+ const edges = [];
104
+ graph.forEachEdge((_id, attrs) => {
105
+ edges.push(attrs);
106
+ });
107
+ return { nodes, edges };
108
+ }
109
+ function projectFromReq(req) {
110
+ const params = req.params;
111
+ return params.project ?? DEFAULT_PROJECT;
112
+ }
113
+ function resolveProject(registry, req, reply) {
114
+ const name = projectFromReq(req);
115
+ const ctx = registry.get(name);
116
+ if (!ctx) {
117
+ void reply.code(404).send({ error: "project not found", project: name });
118
+ return null;
119
+ }
120
+ return ctx;
121
+ }
122
+ function buildLegacyRegistry(opts) {
123
+ if (opts.projects) return opts.projects;
124
+ if (!opts.graph) {
125
+ throw new Error("buildApi: either `projects` or `graph` must be provided");
126
+ }
127
+ const registry = new Projects();
128
+ const paths = pathsForProject(DEFAULT_PROJECT, "");
129
+ registry.set(DEFAULT_PROJECT, {
130
+ graph: opts.graph,
131
+ scanPath: opts.scanPath,
132
+ paths: {
133
+ snapshotPath: paths.snapshotPath,
134
+ errorsPath: opts.errorsPath ?? paths.errorsPath,
135
+ staleEventsPath: opts.staleEventsPath ?? paths.staleEventsPath,
136
+ embeddingsCachePath: paths.embeddingsCachePath,
137
+ policyViolationsPath: paths.policyViolationsPath
138
+ },
139
+ searchIndex: opts.searchIndex
140
+ });
141
+ return registry;
142
+ }
143
+ function registerRoutes(scope, ctx) {
144
+ const { registry, startedAt, errorsPathFor, staleEventsPathFor } = ctx;
145
+ scope.get("/health", async (req, reply) => {
146
+ const proj = resolveProject(registry, req, reply);
147
+ if (!proj) return;
148
+ return {
149
+ uptime: Math.floor((Date.now() - startedAt) / 1e3),
150
+ project: proj.name,
151
+ nodeCount: proj.graph.order,
152
+ edgeCount: proj.graph.size,
153
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
154
+ };
155
+ });
156
+ scope.get("/graph", async (req, reply) => {
157
+ const proj = resolveProject(registry, req, reply);
158
+ if (!proj) return;
159
+ return serializeGraph(proj.graph);
160
+ });
161
+ scope.get(
162
+ "/graph/node/:id",
163
+ async (req, reply) => {
164
+ const proj = resolveProject(registry, req, reply);
165
+ if (!proj) return;
166
+ const { id } = req.params;
167
+ if (!proj.graph.hasNode(id)) {
168
+ return reply.code(404).send({ error: "node not found", id });
169
+ }
170
+ return proj.graph.getNodeAttributes(id);
171
+ }
172
+ );
173
+ scope.get(
174
+ "/graph/edges/:id",
175
+ async (req, reply) => {
176
+ const proj = resolveProject(registry, req, reply);
177
+ if (!proj) return;
178
+ const { id } = req.params;
179
+ if (!proj.graph.hasNode(id)) {
180
+ return reply.code(404).send({ error: "node not found", id });
181
+ }
182
+ const inbound = proj.graph.inboundEdges(id).map((e) => proj.graph.getEdgeAttributes(e));
183
+ const outbound = proj.graph.outboundEdges(id).map((e) => proj.graph.getEdgeAttributes(e));
184
+ return { inbound, outbound };
185
+ }
186
+ );
187
+ scope.get("/graph/node/:id/dependencies", async (req, reply) => {
188
+ const proj = resolveProject(registry, req, reply);
189
+ if (!proj) return;
190
+ const { id } = req.params;
191
+ if (!proj.graph.hasNode(id)) {
192
+ return reply.code(404).send({ error: "node not found", id });
193
+ }
194
+ const depth = req.query.depth ? Number(req.query.depth) : TRANSITIVE_DEPENDENCIES_DEFAULT_DEPTH;
195
+ if (!Number.isFinite(depth) || depth < 1 || depth > TRANSITIVE_DEPENDENCIES_MAX_DEPTH) {
196
+ return reply.code(400).send({
197
+ error: `depth must be an integer in [1, ${TRANSITIVE_DEPENDENCIES_MAX_DEPTH}]`
198
+ });
199
+ }
200
+ return getTransitiveDependencies(proj.graph, id, depth);
201
+ });
202
+ scope.get("/incidents", async (req, reply) => {
203
+ const proj = resolveProject(registry, req, reply);
204
+ if (!proj) return;
205
+ const epath = errorsPathFor(proj);
206
+ if (!epath) return [];
207
+ return readErrorEvents(epath);
208
+ });
209
+ scope.get("/incidents/stale", async (req, reply) => {
210
+ const proj = resolveProject(registry, req, reply);
211
+ if (!proj) return;
212
+ const spath = staleEventsPathFor(proj);
213
+ if (!spath) return [];
214
+ const events = await readStaleEvents(spath);
215
+ const filtered = req.query.edgeType ? events.filter((e) => e.edgeType === req.query.edgeType) : events;
216
+ const ordered = [...filtered].reverse();
217
+ const limit = req.query.limit ? Number(req.query.limit) : 50;
218
+ return ordered.slice(0, Number.isFinite(limit) && limit > 0 ? limit : 50);
219
+ });
220
+ scope.get(
221
+ "/incidents/:nodeId",
222
+ async (req, reply) => {
223
+ const proj = resolveProject(registry, req, reply);
224
+ if (!proj) return;
225
+ const { nodeId } = req.params;
226
+ if (!proj.graph.hasNode(nodeId)) {
227
+ return reply.code(404).send({ error: "node not found", id: nodeId });
228
+ }
229
+ const epath = errorsPathFor(proj);
230
+ if (!epath) return [];
231
+ const events = await readErrorEvents(epath);
232
+ return events.filter(
233
+ (e) => e.affectedNode === nodeId || e.service === nodeId.replace(/^service:/, "")
234
+ );
235
+ }
236
+ );
237
+ scope.get("/traverse/root-cause/:nodeId", async (req, reply) => {
238
+ const proj = resolveProject(registry, req, reply);
239
+ if (!proj) return;
240
+ const { nodeId } = req.params;
241
+ if (!proj.graph.hasNode(nodeId)) {
242
+ return reply.code(404).send({ error: "node not found", id: nodeId });
243
+ }
244
+ let errorEvent;
245
+ const epath = errorsPathFor(proj);
246
+ if (req.query.errorId && epath) {
247
+ const events = await readErrorEvents(epath);
248
+ errorEvent = events.find((e) => e.id === req.query.errorId);
249
+ if (!errorEvent) {
250
+ return reply.code(404).send({ error: "error event not found", id: req.query.errorId });
251
+ }
252
+ }
253
+ const result = getRootCause(proj.graph, nodeId, errorEvent);
254
+ if (!result) return reply.code(404).send({ error: "no root cause found", id: nodeId });
255
+ return result;
256
+ });
257
+ scope.get("/traverse/blast-radius/:nodeId", async (req, reply) => {
258
+ const proj = resolveProject(registry, req, reply);
259
+ if (!proj) return;
260
+ const { nodeId } = req.params;
261
+ if (!proj.graph.hasNode(nodeId)) {
262
+ return reply.code(404).send({ error: "node not found", id: nodeId });
263
+ }
264
+ const depth = req.query.depth ? Number(req.query.depth) : void 0;
265
+ if (depth !== void 0 && (!Number.isFinite(depth) || depth < 0)) {
266
+ return reply.code(400).send({ error: "depth must be a non-negative number" });
267
+ }
268
+ return getBlastRadius(proj.graph, nodeId, depth);
269
+ });
270
+ scope.get("/search", async (req, reply) => {
271
+ const proj = resolveProject(registry, req, reply);
272
+ if (!proj) return;
273
+ const raw = (req.query.q ?? "").trim();
274
+ if (!raw) return reply.code(400).send({ error: "query parameter `q` is required" });
275
+ const limit = req.query.limit ? Number(req.query.limit) : void 0;
276
+ const safeLimit = limit !== void 0 && Number.isFinite(limit) && limit > 0 ? limit : void 0;
277
+ if (proj.searchIndex) {
278
+ const result = await proj.searchIndex.search(raw, safeLimit);
279
+ return {
280
+ query: result.query,
281
+ provider: result.provider,
282
+ matches: result.matches.map((m) => ({ ...m.node, score: m.score }))
283
+ };
284
+ }
285
+ const q = raw.toLowerCase();
286
+ const matches = [];
287
+ proj.graph.forEachNode((id, attrs) => {
288
+ const name = attrs.name ?? "";
289
+ if (id.toLowerCase().includes(q) || name.toLowerCase().includes(q)) {
290
+ matches.push({ ...attrs, score: 1 });
291
+ }
292
+ });
293
+ return {
294
+ query: q,
295
+ provider: "substring",
296
+ matches: matches.slice(0, safeLimit)
297
+ };
298
+ });
299
+ scope.get(
300
+ "/graph/diff",
301
+ async (req, reply) => {
302
+ const proj = resolveProject(registry, req, reply);
303
+ if (!proj) return;
304
+ const against = req.query.against;
305
+ if (!against) {
306
+ return reply.code(400).send({ error: "query parameter `against` is required" });
307
+ }
308
+ try {
309
+ const snapshot = await loadSnapshotForDiff(against);
310
+ return computeGraphDiff(proj.graph, snapshot);
311
+ } catch (err) {
312
+ return reply.code(400).send({ error: "failed to load snapshot", against, detail: err.message });
313
+ }
314
+ }
315
+ );
316
+ scope.post("/graph/scan", async (req, reply) => {
317
+ const proj = resolveProject(registry, req, reply);
318
+ if (!proj) return;
319
+ if (!proj.scanPath) {
320
+ return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
321
+ }
322
+ const result = await extractFromDirectory(proj.graph, proj.scanPath);
323
+ return {
324
+ project: proj.name,
325
+ scanned: proj.scanPath,
326
+ nodesAdded: result.nodesAdded,
327
+ edgesAdded: result.edgesAdded,
328
+ nodeCount: proj.graph.order,
329
+ edgeCount: proj.graph.size
330
+ };
331
+ });
332
+ scope.get("/policies", async (req, reply) => {
333
+ const proj = resolveProject(registry, req, reply);
334
+ if (!proj) return;
335
+ const policyPath = ctx.policyFilePathFor(proj);
336
+ if (!policyPath) {
337
+ return { version: 1, policies: [] };
338
+ }
339
+ try {
340
+ const policies = await loadPolicyFile(policyPath);
341
+ return { version: 1, policies };
342
+ } catch (err) {
343
+ return reply.code(400).send({
344
+ error: "policy.json failed to parse",
345
+ details: err.message
346
+ });
347
+ }
348
+ });
349
+ scope.get("/policies/violations", async (req, reply) => {
350
+ const proj = resolveProject(registry, req, reply);
351
+ if (!proj) return;
352
+ const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
353
+ let violations = await log.readAll();
354
+ if (req.query.severity) {
355
+ const sev = PolicySeveritySchema.safeParse(req.query.severity);
356
+ if (!sev.success) {
357
+ return reply.code(400).send({
358
+ error: "invalid severity",
359
+ details: sev.error.format()
360
+ });
361
+ }
362
+ violations = violations.filter((v) => v.severity === sev.data);
363
+ }
364
+ if (req.query.policyId) {
365
+ violations = violations.filter((v) => v.policyId === req.query.policyId);
366
+ }
367
+ return violations;
368
+ });
369
+ scope.post("/policies/check", async (req, reply) => {
370
+ const proj = resolveProject(registry, req, reply);
371
+ if (!proj) return;
372
+ const parsed = PoliciesCheckBodySchema.safeParse(req.body ?? {});
373
+ if (!parsed.success) {
374
+ return reply.code(400).send({
375
+ error: "invalid /policies/check body",
376
+ details: parsed.error.format()
377
+ });
378
+ }
379
+ const policyPath = ctx.policyFilePathFor(proj);
380
+ let policies = [];
381
+ if (policyPath) {
382
+ try {
383
+ policies = await loadPolicyFile(policyPath);
384
+ } catch (err) {
385
+ return reply.code(400).send({
386
+ error: "policy.json failed to parse",
387
+ details: err.message
388
+ });
389
+ }
390
+ }
391
+ const evalCtx = { now: () => Date.now() };
392
+ if (!parsed.data.hypotheticalAction) {
393
+ const violations2 = evaluateAllPolicies(proj.graph, policies, evalCtx);
394
+ const blocking2 = violations2.filter((v) => v.onViolation === "block");
395
+ return { allowed: blocking2.length === 0, violations: violations2 };
396
+ }
397
+ const violations = evaluateAllPolicies(proj.graph, policies, evalCtx);
398
+ const blocking = violations.filter((v) => v.onViolation === "block");
399
+ return {
400
+ allowed: blocking.length === 0,
401
+ hypotheticalAction: parsed.data.hypotheticalAction,
402
+ violations
403
+ };
404
+ });
405
+ }
406
+ async function buildApi(opts) {
407
+ const app = Fastify({ logger: false });
408
+ await app.register(cors, { origin: true });
409
+ const startedAt = opts.startedAt ?? Date.now();
410
+ const registry = buildLegacyRegistry(opts);
411
+ const legacyErrorsExplicit = !opts.projects && opts.errorsPath !== void 0;
412
+ const legacyStaleExplicit = !opts.projects && opts.staleEventsPath !== void 0;
413
+ const errorsPathFor = (proj) => {
414
+ if (proj.name === DEFAULT_PROJECT && !opts.projects) {
415
+ return legacyErrorsExplicit ? opts.errorsPath : void 0;
416
+ }
417
+ return proj.paths.errorsPath;
418
+ };
419
+ const staleEventsPathFor = (proj) => {
420
+ if (proj.name === DEFAULT_PROJECT && !opts.projects) {
421
+ return legacyStaleExplicit ? opts.staleEventsPath : void 0;
422
+ }
423
+ return proj.paths.staleEventsPath;
424
+ };
425
+ const policyFilePathFor = (proj) => {
426
+ if (!proj.scanPath) return void 0;
427
+ return `${proj.scanPath}/policy.json`;
428
+ };
429
+ const routeCtx = {
430
+ registry,
431
+ startedAt,
432
+ errorsPathFor,
433
+ staleEventsPathFor,
434
+ policyFilePathFor
435
+ };
436
+ app.get("/projects", async () => ({
437
+ projects: registry.list().map((name) => {
438
+ const proj = registry.get(name);
439
+ return {
440
+ name,
441
+ nodeCount: proj.graph.order,
442
+ edgeCount: proj.graph.size,
443
+ scanPath: proj.scanPath
444
+ };
445
+ })
446
+ }));
447
+ registerRoutes(app, routeCtx);
448
+ await app.register(
449
+ async (scope) => {
450
+ registerRoutes(scope, routeCtx);
451
+ },
452
+ { prefix: "/projects/:project" }
453
+ );
454
+ return app;
455
+ }
456
+
457
+ export {
458
+ loadSnapshotForDiff,
459
+ computeGraphDiff,
460
+ buildApi
461
+ };
462
+ //# sourceMappingURL=chunk-T2U4U256.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/diff.ts","../src/api.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 { PoliciesCheckBodySchema, PolicySeveritySchema } from '@neat.is/types'\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'\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 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 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 // Top-level discovery — only meaningful at the root.\n app.get('/projects', async () => ({\n projects: registry.list().map((name) => {\n const proj = registry.get(name) as ProjectContext\n return {\n name,\n nodeCount: proj.graph.order,\n edgeCount: proj.graph.size,\n scanPath: proj.scanPath,\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"],"mappings":"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","names":["violations","blocking"]}