@neat.is/core 0.4.28-dev.20260707 → 0.4.28-dev.20260709

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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  mountBearerAuth,
3
3
  readAuthEnv
4
- } from "./chunk-A3322JYS.js";
4
+ } from "./chunk-CFDPIMRP.js";
5
5
 
6
6
  // src/graph.ts
7
7
  import GraphDefault from "graphology";
@@ -456,19 +456,19 @@ function confidenceFromMix(edges, now = Date.now()) {
456
456
  function longestIncomingWalk(graph, start, maxDepth) {
457
457
  let best = { path: [start], edges: [] };
458
458
  const visited = /* @__PURE__ */ new Set([start]);
459
- function step(node, path42, edges) {
460
- if (path42.length > best.path.length) {
461
- best = { path: [...path42], edges: [...edges] };
459
+ function step(node, path43, edges) {
460
+ if (path43.length > best.path.length) {
461
+ best = { path: [...path43], edges: [...edges] };
462
462
  }
463
- if (path42.length - 1 >= maxDepth) return;
463
+ if (path43.length - 1 >= maxDepth) return;
464
464
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
465
465
  for (const [srcId, edge] of incoming) {
466
466
  if (visited.has(srcId)) continue;
467
467
  visited.add(srcId);
468
- path42.push(srcId);
468
+ path43.push(srcId);
469
469
  edges.push(edge);
470
- step(srcId, path42, edges);
471
- path42.pop();
470
+ step(srcId, path43, edges);
471
+ path43.pop();
472
472
  edges.pop();
473
473
  visited.delete(srcId);
474
474
  }
@@ -662,26 +662,26 @@ function dominantFailingCall(graph, serviceId4, visited) {
662
662
  return best;
663
663
  }
664
664
  function followFailingCallChain(graph, originServiceId, maxDepth) {
665
- const path42 = [originServiceId];
665
+ const path43 = [originServiceId];
666
666
  const edges = [];
667
667
  const visited = /* @__PURE__ */ new Set([originServiceId]);
668
668
  let current = originServiceId;
669
669
  for (let depth = 0; depth < maxDepth; depth++) {
670
670
  const hop = dominantFailingCall(graph, current, visited);
671
671
  if (!hop) break;
672
- path42.push(hop.nextService);
672
+ path43.push(hop.nextService);
673
673
  edges.push(hop.edge);
674
674
  visited.add(hop.nextService);
675
675
  current = hop.nextService;
676
676
  }
677
677
  if (edges.length === 0) return null;
678
- return { path: path42, edges, culprit: current };
678
+ return { path: path43, edges, culprit: current };
679
679
  }
680
680
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
681
681
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
682
682
  if (!chain) return null;
683
683
  const culprit = chain.culprit;
684
- const path42 = [...chain.path];
684
+ const path43 = [...chain.path];
685
685
  const edgeProvenances = chain.edges.map((e) => e.provenance);
686
686
  const baseConfidence = confidenceFromMix(chain.edges);
687
687
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -689,14 +689,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
689
689
  if (loc) {
690
690
  let rootCauseNode = culprit;
691
691
  if (loc.fileNode) {
692
- path42.push(loc.fileNode);
692
+ path43.push(loc.fileNode);
693
693
  edgeProvenances.push(Provenance.OBSERVED);
694
694
  rootCauseNode = loc.fileNode;
695
695
  }
696
696
  return RootCauseResultSchema.parse({
697
697
  rootCauseNode,
698
698
  rootCauseReason: loc.rootCauseReason,
699
- traversalPath: path42,
699
+ traversalPath: path43,
700
700
  edgeProvenances,
701
701
  confidence,
702
702
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -708,7 +708,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
708
708
  return RootCauseResultSchema.parse({
709
709
  rootCauseNode: culprit,
710
710
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
711
- traversalPath: path42,
711
+ traversalPath: path43,
712
712
  edgeProvenances,
713
713
  confidence,
714
714
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -1373,6 +1373,8 @@ var PolicyViolationsLog = class {
1373
1373
  // src/ingest.ts
1374
1374
  import {
1375
1375
  EdgeType as EdgeType3,
1376
+ GraphEdgeSchema,
1377
+ GraphNodeSchema,
1376
1378
  NodeType as NodeType3,
1377
1379
  Provenance as Provenance2,
1378
1380
  confidenceForObservedSignal,
@@ -1877,6 +1879,19 @@ function ensureServiceNode(graph, serviceName, env) {
1877
1879
  graph.addNode(id, node);
1878
1880
  return id;
1879
1881
  }
1882
+ function ensureInfraNode(graph, kind, name, provider) {
1883
+ const id = infraId(kind, name);
1884
+ if (graph.hasNode(id)) return id;
1885
+ const node = {
1886
+ id,
1887
+ type: NodeType3.InfraNode,
1888
+ name,
1889
+ provider,
1890
+ kind
1891
+ };
1892
+ graph.addNode(id, node);
1893
+ return id;
1894
+ }
1880
1895
  function ensureDatabaseNode(graph, host, engine) {
1881
1896
  const id = databaseId(host);
1882
1897
  if (graph.hasNode(id)) return id;
@@ -2529,24 +2544,58 @@ function dedupeIncidents(events) {
2529
2544
  return !hasRealFailure.has(groupKey(ev));
2530
2545
  });
2531
2546
  }
2547
+ var SnapshotValidationError = class extends Error {
2548
+ constructor(issues) {
2549
+ super(`snapshot failed validation (${issues.length} invalid ${issues.length === 1 ? "entry" : "entries"})`);
2550
+ this.issues = issues;
2551
+ this.name = "SnapshotValidationError";
2552
+ }
2553
+ issues;
2554
+ };
2555
+ function describeZodIssues(error) {
2556
+ return error.issues.map((issue) => issue.path.length > 0 ? `${issue.path.join(".")}: ${issue.message}` : issue.message).join("; ");
2557
+ }
2532
2558
  function mergeSnapshot(graph, snapshot) {
2533
2559
  const exported = snapshot.graph;
2560
+ const incomingNodes = Array.isArray(exported.nodes) ? exported.nodes : [];
2561
+ const incomingEdges = Array.isArray(exported.edges) ? exported.edges : [];
2562
+ const issues = [];
2563
+ const validNodes = [];
2564
+ const validEdges = [];
2565
+ for (const node of incomingNodes) {
2566
+ if (node.attributes === void 0) continue;
2567
+ const parsed = GraphNodeSchema.safeParse(node.attributes);
2568
+ if (!parsed.success) {
2569
+ issues.push(`node "${node.key}": ${describeZodIssues(parsed.error)}`);
2570
+ continue;
2571
+ }
2572
+ validNodes.push({ key: node.key, attributes: parsed.data });
2573
+ }
2574
+ for (const edge of incomingEdges) {
2575
+ if (edge.attributes === void 0) continue;
2576
+ const parsed = GraphEdgeSchema.safeParse(edge.attributes);
2577
+ if (!parsed.success) {
2578
+ const label = edge.key ?? `${edge.source}->${edge.target}`;
2579
+ issues.push(`edge "${label}": ${describeZodIssues(parsed.error)}`);
2580
+ continue;
2581
+ }
2582
+ const id = edge.key ?? parsed.data.id;
2583
+ validEdges.push({ key: id, source: edge.source, target: edge.target, attributes: parsed.data });
2584
+ }
2585
+ if (issues.length > 0) {
2586
+ throw new SnapshotValidationError(issues);
2587
+ }
2534
2588
  let nodesAdded = 0;
2535
2589
  let edgesAdded = 0;
2536
- for (const node of exported.nodes ?? []) {
2590
+ for (const node of validNodes) {
2537
2591
  if (graph.hasNode(node.key)) continue;
2538
- if (!node.attributes) continue;
2539
2592
  graph.addNode(node.key, node.attributes);
2540
2593
  nodesAdded++;
2541
2594
  }
2542
- for (const edge of exported.edges ?? []) {
2543
- const attrs = edge.attributes;
2544
- if (!attrs) continue;
2545
- const id = edge.key ?? attrs.id;
2546
- if (!id) continue;
2547
- if (graph.hasEdge(id)) continue;
2595
+ for (const edge of validEdges) {
2596
+ if (graph.hasEdge(edge.key)) continue;
2548
2597
  if (!graph.hasNode(edge.source) || !graph.hasNode(edge.target)) continue;
2549
- graph.addEdgeWithKey(id, edge.source, edge.target, attrs);
2598
+ graph.addEdgeWithKey(edge.key, edge.source, edge.target, edge.attributes);
2550
2599
  edgesAdded++;
2551
2600
  }
2552
2601
  return { nodesAdded, edgesAdded };
@@ -4622,10 +4671,10 @@ function fastifyRouteMethods(objNode) {
4622
4671
  }
4623
4672
  return [];
4624
4673
  }
4625
- function serverRoutesFromSource(source, parser, hasExpress, hasFastify) {
4674
+ function serverRoutesFromSource(source, parser, hasExpress, hasFastify, hasHono = false) {
4626
4675
  const tree = parseSource2(parser, source);
4627
4676
  const out = [];
4628
- const framework = hasExpress ? "express" : "fastify";
4677
+ const framework = hasExpress ? "express" : hasFastify ? "fastify" : hasHono ? "hono" : "unknown";
4629
4678
  walk(tree.rootNode, (node) => {
4630
4679
  if (node.type !== "call_expression") return;
4631
4680
  const fn = node.childForFieldName("function");
@@ -4775,8 +4824,9 @@ async function addRoutes(graph, services) {
4775
4824
  };
4776
4825
  const hasExpress = deps["express"] !== void 0;
4777
4826
  const hasFastify = deps["fastify"] !== void 0;
4827
+ const hasHono = deps["hono"] !== void 0;
4778
4828
  const hasNext = deps["next"] !== void 0;
4779
- if (!hasExpress && !hasFastify && !hasNext) continue;
4829
+ if (!hasExpress && !hasFastify && !hasHono && !hasNext) continue;
4780
4830
  const files = await loadSourceFiles(service.dir);
4781
4831
  for (const file of files) {
4782
4832
  if (isTestPath(file.path)) continue;
@@ -4786,8 +4836,8 @@ async function addRoutes(graph, services) {
4786
4836
  try {
4787
4837
  if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
4788
4838
  routes = nextRoutesFromFile(file.content, relFile, jsParser);
4789
- } else if (hasExpress || hasFastify) {
4790
- routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify);
4839
+ } else if (hasExpress || hasFastify || hasHono) {
4840
+ routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify, hasHono);
4791
4841
  } else {
4792
4842
  routes = [];
4793
4843
  }
@@ -6119,25 +6169,258 @@ async function addK8sResources(graph, scanPath) {
6119
6169
  return { nodesAdded, edgesAdded: 0 };
6120
6170
  }
6121
6171
 
6172
+ // src/extract/infra/cloudflare.ts
6173
+ import { promises as fs19 } from "fs";
6174
+ import path35 from "path";
6175
+ import { parse as parseToml2 } from "smol-toml";
6176
+ import { EdgeType as EdgeType16, Provenance as Provenance15, confidenceForExtracted as confidenceForExtracted13 } from "@neat.is/types";
6177
+ var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
6178
+ async function readWranglerConfig(dir) {
6179
+ for (const filename of WRANGLER_FILENAMES) {
6180
+ const abs = path35.join(dir, filename);
6181
+ if (!await exists(abs)) continue;
6182
+ const raw = await fs19.readFile(abs, "utf8");
6183
+ const config = filename === "wrangler.toml" ? parseToml2(raw) : JSON.parse(maskCommentsInSource(raw));
6184
+ return { config, relFile: filename, raw };
6185
+ }
6186
+ return null;
6187
+ }
6188
+ function lineContaining(raw, needle) {
6189
+ if (!needle) return void 0;
6190
+ const idx = raw.indexOf(needle);
6191
+ if (idx === -1) return void 0;
6192
+ let line = 1;
6193
+ for (let i = 0; i < idx; i++) if (raw[i] === "\n") line++;
6194
+ return line;
6195
+ }
6196
+ function normalizeRoutes(config) {
6197
+ const out = [];
6198
+ const pushOne = (r) => {
6199
+ if (typeof r === "string") out.push(r);
6200
+ else if (r && typeof r === "object" && typeof r.pattern === "string") {
6201
+ out.push(r.pattern);
6202
+ }
6203
+ };
6204
+ if (Array.isArray(config.routes)) config.routes.forEach(pushOne);
6205
+ else if (config.route) pushOne(config.route);
6206
+ return out;
6207
+ }
6208
+ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, line) {
6209
+ let nodesAdded = 0;
6210
+ let edgesAdded = 0;
6211
+ const node = makeInfraNode(kind, name, "cloudflare");
6212
+ if (!graph.hasNode(node.id)) {
6213
+ graph.addNode(node.id, node);
6214
+ nodesAdded++;
6215
+ }
6216
+ if (node.id === anchorId) return { nodesAdded, edgesAdded };
6217
+ const edgeId = extractedEdgeId2(anchorId, node.id, edgeType);
6218
+ if (!graph.hasEdge(edgeId)) {
6219
+ const edge = {
6220
+ id: edgeId,
6221
+ source: anchorId,
6222
+ target: node.id,
6223
+ type: edgeType,
6224
+ provenance: Provenance15.EXTRACTED,
6225
+ confidence: confidenceForExtracted13("structural"),
6226
+ evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
6227
+ };
6228
+ graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
6229
+ edgesAdded++;
6230
+ }
6231
+ return { nodesAdded, edgesAdded };
6232
+ }
6233
+ async function addCloudflareWorkers(graph, services, scanPath) {
6234
+ let nodesAdded = 0;
6235
+ let edgesAdded = 0;
6236
+ const discovered = [];
6237
+ const workerIndex = /* @__PURE__ */ new Map();
6238
+ for (const service of services) {
6239
+ let read;
6240
+ try {
6241
+ read = await readWranglerConfig(service.dir);
6242
+ } catch (err) {
6243
+ recordExtractionError("infra cloudflare", path35.relative(scanPath, service.dir), err);
6244
+ continue;
6245
+ }
6246
+ if (!read || !read.config.name) continue;
6247
+ const evidenceFile = toPosix2(path35.relative(scanPath, path35.join(service.dir, read.relFile)));
6248
+ discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
6249
+ }
6250
+ for (const worker of discovered) {
6251
+ const { service, config } = worker;
6252
+ const serviceNode = graph.getNodeAttributes(service.node.id);
6253
+ if (serviceNode.platform !== "cloudflare") {
6254
+ const updated = { ...serviceNode, platform: "cloudflare" };
6255
+ graph.replaceNodeAttributes(service.node.id, updated);
6256
+ }
6257
+ let anchorId = service.node.id;
6258
+ if (config.main) {
6259
+ const entryRelPath = toPosix2(path35.normalize(config.main));
6260
+ const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
6261
+ graph,
6262
+ service.pkg.name,
6263
+ service.node.id,
6264
+ entryRelPath
6265
+ );
6266
+ nodesAdded += fn;
6267
+ edgesAdded += fe;
6268
+ const fileNode = graph.getNodeAttributes(fileNodeId);
6269
+ if (fileNode.platform !== "cloudflare" || fileNode.platformName !== config.name) {
6270
+ const updated = { ...fileNode, platform: "cloudflare", platformName: config.name };
6271
+ graph.replaceNodeAttributes(fileNodeId, updated);
6272
+ }
6273
+ anchorId = fileNodeId;
6274
+ }
6275
+ workerIndex.set(config.name, { anchorId });
6276
+ }
6277
+ for (const worker of discovered) {
6278
+ const { config, evidenceFile, raw } = worker;
6279
+ const anchorId = workerIndex.get(config.name).anchorId;
6280
+ const runtimeNode = makeInfraNode("workerd", "cloudflare", "cloudflare");
6281
+ if (!graph.hasNode(runtimeNode.id)) {
6282
+ graph.addNode(runtimeNode.id, runtimeNode);
6283
+ nodesAdded++;
6284
+ }
6285
+ if (runtimeNode.id !== anchorId) {
6286
+ const runsOnId = extractedEdgeId2(anchorId, runtimeNode.id, EdgeType16.RUNS_ON);
6287
+ if (!graph.hasEdge(runsOnId)) {
6288
+ const edge = {
6289
+ id: runsOnId,
6290
+ source: anchorId,
6291
+ target: runtimeNode.id,
6292
+ type: EdgeType16.RUNS_ON,
6293
+ provenance: Provenance15.EXTRACTED,
6294
+ confidence: confidenceForExtracted13("structural"),
6295
+ evidence: {
6296
+ file: evidenceFile,
6297
+ ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
6298
+ }
6299
+ };
6300
+ graph.addEdgeWithKey(runsOnId, edge.source, edge.target, edge);
6301
+ edgesAdded++;
6302
+ }
6303
+ }
6304
+ for (const route of normalizeRoutes(config)) {
6305
+ const result = addResourceEdge(
6306
+ graph,
6307
+ anchorId,
6308
+ EdgeType16.CONNECTS_TO,
6309
+ "cloudflare-route",
6310
+ route,
6311
+ evidenceFile,
6312
+ lineContaining(raw, route)
6313
+ );
6314
+ nodesAdded += result.nodesAdded;
6315
+ edgesAdded += result.edgesAdded;
6316
+ }
6317
+ const bindingGroups = [
6318
+ { kind: "cloudflare-kv", entries: config.kv_namespaces ?? [], nameOf: (b) => b.binding },
6319
+ { kind: "cloudflare-d1", entries: config.d1_databases ?? [], nameOf: (b) => b.binding },
6320
+ { kind: "cloudflare-r2", entries: config.r2_buckets ?? [], nameOf: (b) => b.binding },
6321
+ { kind: "cloudflare-durable-object", entries: config.durable_objects?.bindings ?? [], nameOf: (b) => b.name },
6322
+ { kind: "cloudflare-queue", entries: config.queues?.producers ?? [], nameOf: (b) => b.queue },
6323
+ { kind: "cloudflare-queue", entries: config.queues?.consumers ?? [], nameOf: (b) => b.queue }
6324
+ ];
6325
+ for (const group of bindingGroups) {
6326
+ for (const entry of group.entries) {
6327
+ const name = group.nameOf(entry);
6328
+ if (!name) continue;
6329
+ const result = addResourceEdge(
6330
+ graph,
6331
+ anchorId,
6332
+ EdgeType16.DEPENDS_ON,
6333
+ group.kind,
6334
+ name,
6335
+ evidenceFile,
6336
+ lineContaining(raw, name)
6337
+ );
6338
+ nodesAdded += result.nodesAdded;
6339
+ edgesAdded += result.edgesAdded;
6340
+ }
6341
+ }
6342
+ for (const cron of config.triggers?.crons ?? []) {
6343
+ const result = addResourceEdge(
6344
+ graph,
6345
+ anchorId,
6346
+ EdgeType16.DEPENDS_ON,
6347
+ "cloudflare-cron",
6348
+ cron,
6349
+ evidenceFile,
6350
+ lineContaining(raw, cron)
6351
+ );
6352
+ nodesAdded += result.nodesAdded;
6353
+ edgesAdded += result.edgesAdded;
6354
+ }
6355
+ for (const varName of Object.keys(config.vars ?? {})) {
6356
+ const result = addResourceEdge(
6357
+ graph,
6358
+ anchorId,
6359
+ EdgeType16.DEPENDS_ON,
6360
+ "cloudflare-env-var",
6361
+ varName,
6362
+ evidenceFile,
6363
+ lineContaining(raw, varName)
6364
+ );
6365
+ nodesAdded += result.nodesAdded;
6366
+ edgesAdded += result.edgesAdded;
6367
+ }
6368
+ for (const svc of config.services ?? []) {
6369
+ if (!svc.service) continue;
6370
+ const target = workerIndex.get(svc.service);
6371
+ if (target && target.anchorId !== anchorId) {
6372
+ const edgeId = extractedEdgeId2(anchorId, target.anchorId, EdgeType16.CALLS);
6373
+ if (!graph.hasEdge(edgeId)) {
6374
+ const edge = {
6375
+ id: edgeId,
6376
+ source: anchorId,
6377
+ target: target.anchorId,
6378
+ type: EdgeType16.CALLS,
6379
+ provenance: Provenance15.EXTRACTED,
6380
+ confidence: confidenceForExtracted13("structural"),
6381
+ evidence: { file: evidenceFile, line: lineContaining(raw, svc.service) }
6382
+ };
6383
+ graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
6384
+ edgesAdded++;
6385
+ }
6386
+ continue;
6387
+ }
6388
+ const result = addResourceEdge(
6389
+ graph,
6390
+ anchorId,
6391
+ EdgeType16.DEPENDS_ON,
6392
+ "cloudflare-service-binding",
6393
+ svc.service,
6394
+ evidenceFile,
6395
+ lineContaining(raw, svc.service)
6396
+ );
6397
+ nodesAdded += result.nodesAdded;
6398
+ edgesAdded += result.edgesAdded;
6399
+ }
6400
+ }
6401
+ return { nodesAdded, edgesAdded };
6402
+ }
6403
+
6122
6404
  // src/extract/infra/index.ts
6123
6405
  async function addInfra(graph, scanPath, services) {
6124
6406
  const compose = await addComposeInfra(graph, scanPath, services);
6125
6407
  const dockerfile = await addDockerfileRuntimes(graph, services, scanPath);
6126
6408
  const terraform = await addTerraformResources(graph, scanPath);
6127
6409
  const k8s = await addK8sResources(graph, scanPath);
6410
+ const cloudflare = await addCloudflareWorkers(graph, services, scanPath);
6128
6411
  return {
6129
- nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded,
6130
- edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded
6412
+ nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded,
6413
+ edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded
6131
6414
  };
6132
6415
  }
6133
6416
 
6134
6417
  // src/extract/index.ts
6135
- import path36 from "path";
6418
+ import path37 from "path";
6136
6419
 
6137
6420
  // src/extract/retire.ts
6138
6421
  import { existsSync as existsSync2 } from "fs";
6139
- import path35 from "path";
6140
- import { NodeType as NodeType14, Provenance as Provenance15 } from "@neat.is/types";
6422
+ import path36 from "path";
6423
+ import { NodeType as NodeType14, Provenance as Provenance16 } from "@neat.is/types";
6141
6424
  function dropOrphanedFileNodes(graph) {
6142
6425
  const orphans = [];
6143
6426
  graph.forEachNode((id, attrs) => {
@@ -6154,7 +6437,7 @@ function retireEdgesByFile(graph, file) {
6154
6437
  const toDrop = [];
6155
6438
  graph.forEachEdge((id, attrs) => {
6156
6439
  const edge = attrs;
6157
- if (edge.provenance !== Provenance15.EXTRACTED) return;
6440
+ if (edge.provenance !== Provenance16.EXTRACTED) return;
6158
6441
  if (!edge.evidence?.file) return;
6159
6442
  if (edge.evidence.file === normalized) toDrop.push(id);
6160
6443
  });
@@ -6167,14 +6450,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
6167
6450
  const bases = [scanPath, ...serviceDirs];
6168
6451
  graph.forEachEdge((id, attrs) => {
6169
6452
  const edge = attrs;
6170
- if (edge.provenance !== Provenance15.EXTRACTED) return;
6453
+ if (edge.provenance !== Provenance16.EXTRACTED) return;
6171
6454
  const evidenceFile = edge.evidence?.file;
6172
6455
  if (!evidenceFile) return;
6173
- if (path35.isAbsolute(evidenceFile)) {
6456
+ if (path36.isAbsolute(evidenceFile)) {
6174
6457
  if (!existsSync2(evidenceFile)) toDrop.push(id);
6175
6458
  return;
6176
6459
  }
6177
- const found = bases.some((base) => existsSync2(path35.join(base, evidenceFile)));
6460
+ const found = bases.some((base) => existsSync2(path36.join(base, evidenceFile)));
6178
6461
  if (!found) toDrop.push(id);
6179
6462
  });
6180
6463
  for (const id of toDrop) graph.dropEdge(id);
@@ -6216,7 +6499,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6216
6499
  }
6217
6500
  const droppedEntries = drainDroppedExtracted();
6218
6501
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
6219
- const rejectedPath = path36.join(path36.dirname(opts.errorsPath), "rejected.ndjson");
6502
+ const rejectedPath = path37.join(path37.dirname(opts.errorsPath), "rejected.ndjson");
6220
6503
  try {
6221
6504
  await writeRejectedExtracted(droppedEntries, rejectedPath);
6222
6505
  } catch (err) {
@@ -6252,10 +6535,10 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6252
6535
  import {
6253
6536
  databaseId as databaseId3,
6254
6537
  DivergenceResultSchema,
6255
- EdgeType as EdgeType16,
6538
+ EdgeType as EdgeType17,
6256
6539
  NodeType as NodeType15,
6257
6540
  parseEdgeId,
6258
- Provenance as Provenance16
6541
+ Provenance as Provenance17
6259
6542
  } from "@neat.is/types";
6260
6543
  function bucketKey(source, target, type) {
6261
6544
  return `${type}|${source}|${target}`;
@@ -6269,17 +6552,17 @@ function bucketEdges(graph) {
6269
6552
  const key = bucketKey(e.source, e.target, e.type);
6270
6553
  const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
6271
6554
  switch (provenance) {
6272
- case Provenance16.EXTRACTED:
6555
+ case Provenance17.EXTRACTED:
6273
6556
  cur.extracted = e;
6274
6557
  break;
6275
- case Provenance16.OBSERVED:
6558
+ case Provenance17.OBSERVED:
6276
6559
  cur.observed = e;
6277
6560
  break;
6278
- case Provenance16.INFERRED:
6561
+ case Provenance17.INFERRED:
6279
6562
  cur.inferred = e;
6280
6563
  break;
6281
6564
  default:
6282
- if (e.provenance === Provenance16.STALE) cur.stale = e;
6565
+ if (e.provenance === Provenance17.STALE) cur.stale = e;
6283
6566
  }
6284
6567
  buckets.set(key, cur);
6285
6568
  });
@@ -6313,14 +6596,14 @@ function gradedConfidence(edge) {
6313
6596
  return clampConfidence(confidenceForEdge(edge));
6314
6597
  }
6315
6598
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
6316
- EdgeType16.CALLS,
6317
- EdgeType16.CONNECTS_TO,
6318
- EdgeType16.PUBLISHES_TO,
6319
- EdgeType16.CONSUMES_FROM
6599
+ EdgeType17.CALLS,
6600
+ EdgeType17.CONNECTS_TO,
6601
+ EdgeType17.PUBLISHES_TO,
6602
+ EdgeType17.CONSUMES_FROM
6320
6603
  ]);
6321
6604
  function detectMissingDivergences(graph, bucket) {
6322
6605
  const out = [];
6323
- if (bucket.type === EdgeType16.CONTAINS) return out;
6606
+ if (bucket.type === EdgeType17.CONTAINS) return out;
6324
6607
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
6325
6608
  if (!nodeIsFrontier(graph, bucket.target)) {
6326
6609
  out.push({
@@ -6361,7 +6644,7 @@ function declaredHostFor(svc) {
6361
6644
  function hasExtractedConfiguredBy(graph, svcId) {
6362
6645
  for (const edgeId of graph.outboundEdges(svcId)) {
6363
6646
  const e = graph.getEdgeAttributes(edgeId);
6364
- if (e.type === EdgeType16.CONFIGURED_BY && e.provenance === Provenance16.EXTRACTED) {
6647
+ if (e.type === EdgeType17.CONFIGURED_BY && e.provenance === Provenance17.EXTRACTED) {
6365
6648
  return true;
6366
6649
  }
6367
6650
  }
@@ -6374,8 +6657,8 @@ function detectHostMismatch(graph, svcId, svc) {
6374
6657
  const out = [];
6375
6658
  for (const edgeId of graph.outboundEdges(svcId)) {
6376
6659
  const edge = graph.getEdgeAttributes(edgeId);
6377
- if (edge.type !== EdgeType16.CONNECTS_TO) continue;
6378
- if (edge.provenance !== Provenance16.OBSERVED) continue;
6660
+ if (edge.type !== EdgeType17.CONNECTS_TO) continue;
6661
+ if (edge.provenance !== Provenance17.OBSERVED) continue;
6379
6662
  const target = graph.getNodeAttributes(edge.target);
6380
6663
  if (target.type !== NodeType15.DatabaseNode) continue;
6381
6664
  const observedHost = target.host?.trim();
@@ -6399,8 +6682,8 @@ function detectCompatDivergences(graph, svcId, svc) {
6399
6682
  const deps = svc.dependencies ?? {};
6400
6683
  for (const edgeId of graph.outboundEdges(svcId)) {
6401
6684
  const edge = graph.getEdgeAttributes(edgeId);
6402
- if (edge.type !== EdgeType16.CONNECTS_TO) continue;
6403
- if (edge.provenance !== Provenance16.OBSERVED) continue;
6685
+ if (edge.type !== EdgeType17.CONNECTS_TO) continue;
6686
+ if (edge.provenance !== Provenance17.OBSERVED) continue;
6404
6687
  const target = graph.getNodeAttributes(edge.target);
6405
6688
  if (target.type !== NodeType15.DatabaseNode) continue;
6406
6689
  for (const pair of compatPairs()) {
@@ -6522,9 +6805,9 @@ function computeDivergences(graph, opts = {}) {
6522
6805
  }
6523
6806
 
6524
6807
  // src/persist.ts
6525
- import { promises as fs19 } from "fs";
6526
- import path37 from "path";
6527
- import { Provenance as Provenance17, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
6808
+ import { promises as fs20 } from "fs";
6809
+ import path38 from "path";
6810
+ import { Provenance as Provenance18, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
6528
6811
  var SCHEMA_VERSION = 4;
6529
6812
  function migrateV1ToV2(payload) {
6530
6813
  const nodes = payload.graph.nodes;
@@ -6546,7 +6829,7 @@ function migrateV2ToV3(payload) {
6546
6829
  for (const edge of edges) {
6547
6830
  const attrs = edge.attributes;
6548
6831
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
6549
- attrs.provenance = Provenance17.OBSERVED;
6832
+ attrs.provenance = Provenance18.OBSERVED;
6550
6833
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
6551
6834
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
6552
6835
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
@@ -6560,7 +6843,7 @@ function migrateV2ToV3(payload) {
6560
6843
  return { ...payload, schemaVersion: 3 };
6561
6844
  }
6562
6845
  async function ensureDir(filePath) {
6563
- await fs19.mkdir(path37.dirname(filePath), { recursive: true });
6846
+ await fs20.mkdir(path38.dirname(filePath), { recursive: true });
6564
6847
  }
6565
6848
  async function saveGraphToDisk(graph, outPath) {
6566
6849
  await ensureDir(outPath);
@@ -6570,13 +6853,13 @@ async function saveGraphToDisk(graph, outPath) {
6570
6853
  graph: graph.export()
6571
6854
  };
6572
6855
  const tmp = `${outPath}.tmp`;
6573
- await fs19.writeFile(tmp, JSON.stringify(payload), "utf8");
6574
- await fs19.rename(tmp, outPath);
6856
+ await fs20.writeFile(tmp, JSON.stringify(payload), "utf8");
6857
+ await fs20.rename(tmp, outPath);
6575
6858
  }
6576
6859
  async function loadGraphFromDisk(graph, outPath) {
6577
6860
  let raw;
6578
6861
  try {
6579
- raw = await fs19.readFile(outPath, "utf8");
6862
+ raw = await fs20.readFile(outPath, "utf8");
6580
6863
  } catch (err) {
6581
6864
  if (err.code === "ENOENT") return;
6582
6865
  throw err;
@@ -6640,7 +6923,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
6640
6923
  }
6641
6924
 
6642
6925
  // src/diff.ts
6643
- import { promises as fs20 } from "fs";
6926
+ import { promises as fs21 } from "fs";
6644
6927
  async function loadSnapshotForDiff(target) {
6645
6928
  if (/^https?:\/\//i.test(target)) {
6646
6929
  const res = await fetch(target);
@@ -6649,7 +6932,7 @@ async function loadSnapshotForDiff(target) {
6649
6932
  }
6650
6933
  return await res.json();
6651
6934
  }
6652
- const raw = await fs20.readFile(target, "utf8");
6935
+ const raw = await fs21.readFile(target, "utf8");
6653
6936
  return JSON.parse(raw);
6654
6937
  }
6655
6938
  function indexEntries(entries) {
@@ -6716,23 +6999,23 @@ function canonicalJson(value) {
6716
6999
  }
6717
7000
 
6718
7001
  // src/projects.ts
6719
- import path38 from "path";
7002
+ import path39 from "path";
6720
7003
  function pathsForProject(project, baseDir) {
6721
7004
  if (project === DEFAULT_PROJECT) {
6722
7005
  return {
6723
- snapshotPath: path38.join(baseDir, "graph.json"),
6724
- errorsPath: path38.join(baseDir, "errors.ndjson"),
6725
- staleEventsPath: path38.join(baseDir, "stale-events.ndjson"),
6726
- embeddingsCachePath: path38.join(baseDir, "embeddings.json"),
6727
- policyViolationsPath: path38.join(baseDir, "policy-violations.ndjson")
7006
+ snapshotPath: path39.join(baseDir, "graph.json"),
7007
+ errorsPath: path39.join(baseDir, "errors.ndjson"),
7008
+ staleEventsPath: path39.join(baseDir, "stale-events.ndjson"),
7009
+ embeddingsCachePath: path39.join(baseDir, "embeddings.json"),
7010
+ policyViolationsPath: path39.join(baseDir, "policy-violations.ndjson")
6728
7011
  };
6729
7012
  }
6730
7013
  return {
6731
- snapshotPath: path38.join(baseDir, `${project}.json`),
6732
- errorsPath: path38.join(baseDir, `errors.${project}.ndjson`),
6733
- staleEventsPath: path38.join(baseDir, `stale-events.${project}.ndjson`),
6734
- embeddingsCachePath: path38.join(baseDir, `embeddings.${project}.json`),
6735
- policyViolationsPath: path38.join(baseDir, `policy-violations.${project}.ndjson`)
7014
+ snapshotPath: path39.join(baseDir, `${project}.json`),
7015
+ errorsPath: path39.join(baseDir, `errors.${project}.ndjson`),
7016
+ staleEventsPath: path39.join(baseDir, `stale-events.${project}.ndjson`),
7017
+ embeddingsCachePath: path39.join(baseDir, `embeddings.${project}.json`),
7018
+ policyViolationsPath: path39.join(baseDir, `policy-violations.${project}.ndjson`)
6736
7019
  };
6737
7020
  }
6738
7021
  var Projects = class {
@@ -6771,9 +7054,9 @@ function parseExtraProjects(raw) {
6771
7054
  }
6772
7055
 
6773
7056
  // src/registry.ts
6774
- import { promises as fs21 } from "fs";
7057
+ import { promises as fs22 } from "fs";
6775
7058
  import os2 from "os";
6776
- import path39 from "path";
7059
+ import path40 from "path";
6777
7060
  import {
6778
7061
  RegistryFileSchema
6779
7062
  } from "@neat.is/types";
@@ -6781,20 +7064,20 @@ var LOCK_TIMEOUT_MS = 5e3;
6781
7064
  var LOCK_RETRY_MS = 50;
6782
7065
  function neatHome() {
6783
7066
  const override = process.env.NEAT_HOME;
6784
- if (override && override.length > 0) return path39.resolve(override);
6785
- return path39.join(os2.homedir(), ".neat");
7067
+ if (override && override.length > 0) return path40.resolve(override);
7068
+ return path40.join(os2.homedir(), ".neat");
6786
7069
  }
6787
7070
  function registryPath() {
6788
- return path39.join(neatHome(), "projects.json");
7071
+ return path40.join(neatHome(), "projects.json");
6789
7072
  }
6790
7073
  function registryLockPath() {
6791
- return path39.join(neatHome(), "projects.json.lock");
7074
+ return path40.join(neatHome(), "projects.json.lock");
6792
7075
  }
6793
7076
  function daemonPidPath() {
6794
- return path39.join(neatHome(), "neatd.pid");
7077
+ return path40.join(neatHome(), "neatd.pid");
6795
7078
  }
6796
7079
  function daemonsDir() {
6797
- return path39.join(neatHome(), "daemons");
7080
+ return path40.join(neatHome(), "daemons");
6798
7081
  }
6799
7082
  function isFiniteInt(v) {
6800
7083
  return typeof v === "number" && Number.isFinite(v);
@@ -6827,7 +7110,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
6827
7110
  const dir = daemonsDir();
6828
7111
  let names;
6829
7112
  try {
6830
- names = await fs21.readdir(dir);
7113
+ names = await fs22.readdir(dir);
6831
7114
  } catch (err) {
6832
7115
  if (err.code === "ENOENT") return [];
6833
7116
  throw err;
@@ -6835,10 +7118,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
6835
7118
  const out = [];
6836
7119
  for (const name of names) {
6837
7120
  if (!name.endsWith(".json")) continue;
6838
- const file = path39.join(dir, name);
7121
+ const file = path40.join(dir, name);
6839
7122
  let raw;
6840
7123
  try {
6841
- raw = await fs21.readFile(file, "utf8");
7124
+ raw = await fs22.readFile(file, "utf8");
6842
7125
  } catch {
6843
7126
  continue;
6844
7127
  }
@@ -6851,7 +7134,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
6851
7134
  return out;
6852
7135
  }
6853
7136
  async function removeDaemonRecord(source) {
6854
- await fs21.unlink(source).catch(() => {
7137
+ await fs22.unlink(source).catch(() => {
6855
7138
  });
6856
7139
  }
6857
7140
  async function listMachineProjects(probe = defaultDiscoveryProbe) {
@@ -6908,7 +7191,7 @@ function isPidAliveDefault(pid) {
6908
7191
  }
6909
7192
  async function readPidFile(file) {
6910
7193
  try {
6911
- const raw = await fs21.readFile(file, "utf8");
7194
+ const raw = await fs22.readFile(file, "utf8");
6912
7195
  const pid = Number.parseInt(raw.trim(), 10);
6913
7196
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
6914
7197
  } catch {
@@ -6956,32 +7239,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
6956
7239
  }
6957
7240
  }
6958
7241
  async function normalizeProjectPath(input) {
6959
- const resolved = path39.resolve(input);
7242
+ const resolved = path40.resolve(input);
6960
7243
  try {
6961
- return await fs21.realpath(resolved);
7244
+ return await fs22.realpath(resolved);
6962
7245
  } catch {
6963
7246
  return resolved;
6964
7247
  }
6965
7248
  }
6966
7249
  async function writeAtomically(target, contents) {
6967
- await fs21.mkdir(path39.dirname(target), { recursive: true });
7250
+ await fs22.mkdir(path40.dirname(target), { recursive: true });
6968
7251
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
6969
- const fd = await fs21.open(tmp, "w");
7252
+ const fd = await fs22.open(tmp, "w");
6970
7253
  try {
6971
7254
  await fd.writeFile(contents, "utf8");
6972
7255
  await fd.sync();
6973
7256
  } finally {
6974
7257
  await fd.close();
6975
7258
  }
6976
- await fs21.rename(tmp, target);
7259
+ await fs22.rename(tmp, target);
6977
7260
  }
6978
7261
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
6979
7262
  const deadline = Date.now() + timeoutMs;
6980
- await fs21.mkdir(path39.dirname(lockPath), { recursive: true });
7263
+ await fs22.mkdir(path40.dirname(lockPath), { recursive: true });
6981
7264
  let probedHolder = false;
6982
7265
  while (true) {
6983
7266
  try {
6984
- const fd = await fs21.open(lockPath, "wx");
7267
+ const fd = await fs22.open(lockPath, "wx");
6985
7268
  try {
6986
7269
  await fd.writeFile(`${process.pid}
6987
7270
  `, "utf8");
@@ -7006,7 +7289,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
7006
7289
  }
7007
7290
  }
7008
7291
  async function releaseLock(lockPath) {
7009
- await fs21.unlink(lockPath).catch(() => {
7292
+ await fs22.unlink(lockPath).catch(() => {
7010
7293
  });
7011
7294
  }
7012
7295
  async function withLock(fn) {
@@ -7022,7 +7305,7 @@ async function readRegistry() {
7022
7305
  const file = registryPath();
7023
7306
  let raw;
7024
7307
  try {
7025
- raw = await fs21.readFile(file, "utf8");
7308
+ raw = await fs22.readFile(file, "utf8");
7026
7309
  } catch (err) {
7027
7310
  if (err.code === "ENOENT") {
7028
7311
  return { version: 1, projects: [] };
@@ -7127,7 +7410,7 @@ function pruneTtlMs() {
7127
7410
  }
7128
7411
  async function statPathStatus(p) {
7129
7412
  try {
7130
- const stat = await fs21.stat(p);
7413
+ const stat = await fs22.stat(p);
7131
7414
  return stat.isDirectory() ? "present" : "unknown";
7132
7415
  } catch (err) {
7133
7416
  return err.code === "ENOENT" ? "gone" : "unknown";
@@ -7172,14 +7455,14 @@ import cors from "@fastify/cors";
7172
7455
  import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
7173
7456
 
7174
7457
  // src/extend/index.ts
7175
- import { promises as fs23 } from "fs";
7176
- import path41 from "path";
7458
+ import { promises as fs24 } from "fs";
7459
+ import path42 from "path";
7177
7460
  import os3 from "os";
7178
7461
  import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
7179
7462
 
7180
7463
  // src/installers/package-manager.ts
7181
- import { promises as fs22 } from "fs";
7182
- import path40 from "path";
7464
+ import { promises as fs23 } from "fs";
7465
+ import path41 from "path";
7183
7466
  import { spawn } from "child_process";
7184
7467
  var LOCKFILE_PRIORITY = [
7185
7468
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -7194,29 +7477,29 @@ var LOCKFILE_PRIORITY = [
7194
7477
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
7195
7478
  async function exists2(p) {
7196
7479
  try {
7197
- await fs22.access(p);
7480
+ await fs23.access(p);
7198
7481
  return true;
7199
7482
  } catch {
7200
7483
  return false;
7201
7484
  }
7202
7485
  }
7203
7486
  async function detectPackageManager(serviceDir) {
7204
- let dir = path40.resolve(serviceDir);
7487
+ let dir = path41.resolve(serviceDir);
7205
7488
  const stops = /* @__PURE__ */ new Set();
7206
7489
  for (let i = 0; i < 64; i++) {
7207
7490
  if (stops.has(dir)) break;
7208
7491
  stops.add(dir);
7209
7492
  for (const candidate of LOCKFILE_PRIORITY) {
7210
- const lockPath = path40.join(dir, candidate.lockfile);
7493
+ const lockPath = path41.join(dir, candidate.lockfile);
7211
7494
  if (await exists2(lockPath)) {
7212
7495
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
7213
7496
  }
7214
7497
  }
7215
- const parent = path40.dirname(dir);
7498
+ const parent = path41.dirname(dir);
7216
7499
  if (parent === dir) break;
7217
7500
  dir = parent;
7218
7501
  }
7219
- return { pm: "npm", cwd: path40.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7502
+ return { pm: "npm", cwd: path41.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7220
7503
  }
7221
7504
  async function runPackageManagerInstall(cmd) {
7222
7505
  return new Promise((resolve) => {
@@ -7258,30 +7541,30 @@ ${err.message}`
7258
7541
  // src/extend/index.ts
7259
7542
  async function fileExists2(p) {
7260
7543
  try {
7261
- await fs23.access(p);
7544
+ await fs24.access(p);
7262
7545
  return true;
7263
7546
  } catch {
7264
7547
  return false;
7265
7548
  }
7266
7549
  }
7267
7550
  async function readPackageJson(scanPath) {
7268
- const pkgPath = path41.join(scanPath, "package.json");
7269
- const raw = await fs23.readFile(pkgPath, "utf8");
7551
+ const pkgPath = path42.join(scanPath, "package.json");
7552
+ const raw = await fs24.readFile(pkgPath, "utf8");
7270
7553
  return JSON.parse(raw);
7271
7554
  }
7272
7555
  async function findHookFiles(scanPath) {
7273
- const entries = await fs23.readdir(scanPath);
7556
+ const entries = await fs24.readdir(scanPath);
7274
7557
  return entries.filter(
7275
7558
  (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
7276
7559
  ).sort();
7277
7560
  }
7278
7561
  function extendLogPath() {
7279
- return process.env.NEAT_EXTEND_LOG ?? path41.join(os3.homedir(), ".neat", "extend-log.ndjson");
7562
+ return process.env.NEAT_EXTEND_LOG ?? path42.join(os3.homedir(), ".neat", "extend-log.ndjson");
7280
7563
  }
7281
7564
  async function appendExtendLog(entry) {
7282
7565
  const logPath = extendLogPath();
7283
- await fs23.mkdir(path41.dirname(logPath), { recursive: true });
7284
- await fs23.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
7566
+ await fs24.mkdir(path42.dirname(logPath), { recursive: true });
7567
+ await fs24.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
7285
7568
  }
7286
7569
  function splicedContent(fileContent, snippet2) {
7287
7570
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -7339,7 +7622,7 @@ function lookupInstrumentation(library, installedVersion) {
7339
7622
  }
7340
7623
  async function describeProjectInstrumentation(ctx) {
7341
7624
  const hookFiles = await findHookFiles(ctx.scanPath);
7342
- const envNeat = await fileExists2(path41.join(ctx.scanPath, ".env.neat"));
7625
+ const envNeat = await fileExists2(path42.join(ctx.scanPath, ".env.neat"));
7343
7626
  const registryInstrPackages = new Set(
7344
7627
  registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
7345
7628
  );
@@ -7361,31 +7644,31 @@ async function applyExtension(ctx, args, options) {
7361
7644
  );
7362
7645
  }
7363
7646
  for (const file of hookFiles) {
7364
- const content = await fs23.readFile(path41.join(ctx.scanPath, file), "utf8");
7647
+ const content = await fs24.readFile(path42.join(ctx.scanPath, file), "utf8");
7365
7648
  if (content.includes(args.registration_snippet)) {
7366
7649
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
7367
7650
  }
7368
7651
  }
7369
7652
  const primaryFile = hookFiles[0];
7370
- const primaryPath = path41.join(ctx.scanPath, primaryFile);
7653
+ const primaryPath = path42.join(ctx.scanPath, primaryFile);
7371
7654
  const filesTouched = [];
7372
7655
  const depsAdded = [];
7373
- const pkgPath = path41.join(ctx.scanPath, "package.json");
7656
+ const pkgPath = path42.join(ctx.scanPath, "package.json");
7374
7657
  const pkg = await readPackageJson(ctx.scanPath);
7375
7658
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
7376
7659
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
7377
- await fs23.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7660
+ await fs24.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7378
7661
  filesTouched.push("package.json");
7379
7662
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
7380
7663
  }
7381
- const hookContent = await fs23.readFile(primaryPath, "utf8");
7664
+ const hookContent = await fs24.readFile(primaryPath, "utf8");
7382
7665
  const patched = splicedContent(hookContent, args.registration_snippet);
7383
7666
  if (!patched) {
7384
7667
  throw new Error(
7385
7668
  `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
7386
7669
  );
7387
7670
  }
7388
- await fs23.writeFile(primaryPath, patched, "utf8");
7671
+ await fs24.writeFile(primaryPath, patched, "utf8");
7389
7672
  filesTouched.push(primaryFile);
7390
7673
  const cmd = await detectPackageManager(ctx.scanPath);
7391
7674
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -7416,7 +7699,7 @@ async function dryRunExtension(ctx, args) {
7416
7699
  };
7417
7700
  }
7418
7701
  for (const file of hookFiles) {
7419
- const content = await fs23.readFile(path41.join(ctx.scanPath, file), "utf8");
7702
+ const content = await fs24.readFile(path42.join(ctx.scanPath, file), "utf8");
7420
7703
  if (content.includes(args.registration_snippet)) {
7421
7704
  return {
7422
7705
  library: args.library,
@@ -7438,7 +7721,7 @@ async function dryRunExtension(ctx, args) {
7438
7721
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
7439
7722
  filesTouched.push("package.json");
7440
7723
  }
7441
- const hookContent = await fs23.readFile(path41.join(ctx.scanPath, primaryFile), "utf8");
7724
+ const hookContent = await fs24.readFile(path42.join(ctx.scanPath, primaryFile), "utf8");
7442
7725
  const patched = splicedContent(hookContent, args.registration_snippet);
7443
7726
  if (patched) {
7444
7727
  filesTouched.push(primaryFile);
@@ -7453,28 +7736,28 @@ async function rollbackExtension(ctx, args) {
7453
7736
  if (!await fileExists2(logPath)) {
7454
7737
  return { undone: false, message: "no apply found for library" };
7455
7738
  }
7456
- const raw = await fs23.readFile(logPath, "utf8");
7739
+ const raw = await fs24.readFile(logPath, "utf8");
7457
7740
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
7458
7741
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
7459
7742
  if (!match) {
7460
7743
  return { undone: false, message: "no apply found for library" };
7461
7744
  }
7462
- const pkgPath = path41.join(ctx.scanPath, "package.json");
7745
+ const pkgPath = path42.join(ctx.scanPath, "package.json");
7463
7746
  if (await fileExists2(pkgPath)) {
7464
7747
  const pkg = await readPackageJson(ctx.scanPath);
7465
7748
  if (pkg.dependencies?.[match.instrumentation_package]) {
7466
7749
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
7467
7750
  pkg.dependencies = rest;
7468
- await fs23.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7751
+ await fs24.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7469
7752
  }
7470
7753
  }
7471
7754
  const hookFiles = await findHookFiles(ctx.scanPath);
7472
7755
  for (const file of hookFiles) {
7473
- const filePath = path41.join(ctx.scanPath, file);
7474
- const content = await fs23.readFile(filePath, "utf8");
7756
+ const filePath = path42.join(ctx.scanPath, file);
7757
+ const content = await fs24.readFile(filePath, "utf8");
7475
7758
  if (content.includes(match.registration_snippet)) {
7476
7759
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
7477
- await fs23.writeFile(filePath, filtered, "utf8");
7760
+ await fs24.writeFile(filePath, filtered, "utf8");
7478
7761
  break;
7479
7762
  }
7480
7763
  }
@@ -7484,6 +7767,50 @@ async function rollbackExtension(ctx, args) {
7484
7767
  };
7485
7768
  }
7486
7769
 
7770
+ // src/logs-store.ts
7771
+ var LOGS_STORE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
7772
+ var LOGS_QUERY_DEFAULT_LIMIT = 100;
7773
+ var LOGS_QUERY_MAX_LIMIT = 1e3;
7774
+ var buffers = /* @__PURE__ */ new Map();
7775
+ var KEY_SEP = "::";
7776
+ function bufferKey(projectName, source) {
7777
+ return `${projectName}${KEY_SEP}${source}`;
7778
+ }
7779
+ function sourcesForProject(projectName) {
7780
+ const prefix = `${projectName}${KEY_SEP}`;
7781
+ const sources = [];
7782
+ for (const key of buffers.keys()) {
7783
+ if (key.startsWith(prefix)) sources.push(key.slice(prefix.length));
7784
+ }
7785
+ return sources;
7786
+ }
7787
+ function queryLogEntries(opts) {
7788
+ const { projectName, source, service, since, limit } = opts;
7789
+ const sources = source && source.length > 0 ? source : sourcesForProject(projectName);
7790
+ let merged = [];
7791
+ for (const src of sources) {
7792
+ const buf = buffers.get(bufferKey(projectName, src));
7793
+ if (buf) merged = merged.concat(buf);
7794
+ }
7795
+ if (service) {
7796
+ merged = merged.filter((e) => e.serviceName === service);
7797
+ }
7798
+ if (since) {
7799
+ const sinceMs = Date.parse(since);
7800
+ if (!Number.isNaN(sinceMs)) {
7801
+ merged = merged.filter((e) => {
7802
+ const t = Date.parse(e.timestamp);
7803
+ return Number.isNaN(t) || t >= sinceMs;
7804
+ });
7805
+ }
7806
+ }
7807
+ merged.sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
7808
+ if (typeof limit === "number" && Number.isFinite(limit) && limit >= 0) {
7809
+ return merged.slice(0, limit);
7810
+ }
7811
+ return merged;
7812
+ }
7813
+
7487
7814
  // src/streaming.ts
7488
7815
  var SSE_HEARTBEAT_MS = 3e4;
7489
7816
  var SSE_BACKPRESSURE_CAP = 1e3;
@@ -7742,6 +8069,23 @@ function registerRoutes(scope, ctx) {
7742
8069
  const sliced = ordered.slice(0, Number.isFinite(limit) && limit > 0 ? limit : 50);
7743
8070
  return { count: sliced.length, total, events: sliced };
7744
8071
  });
8072
+ scope.get("/logs", async (req, reply) => {
8073
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
8074
+ if (!proj) return;
8075
+ const rawSource = req.query.source;
8076
+ const sources = rawSource === void 0 ? void 0 : Array.isArray(rawSource) ? rawSource : [rawSource];
8077
+ const filtered = queryLogEntries({
8078
+ projectName: proj.name,
8079
+ ...sources && sources.length > 0 ? { source: sources } : {},
8080
+ ...req.query.service ? { service: req.query.service } : {},
8081
+ ...req.query.since ? { since: req.query.since } : {}
8082
+ });
8083
+ const total = filtered.length;
8084
+ const limit = req.query.limit ? Number(req.query.limit) : LOGS_QUERY_DEFAULT_LIMIT;
8085
+ const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.min(limit, LOGS_QUERY_MAX_LIMIT) : LOGS_QUERY_DEFAULT_LIMIT;
8086
+ const sliced = filtered.slice(0, safeLimit);
8087
+ return { count: sliced.length, total, logs: sliced };
8088
+ });
7745
8089
  const incidentHistoryHandler = async (req, reply) => {
7746
8090
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
7747
8091
  if (!proj) return;
@@ -7837,8 +8181,16 @@ function registerRoutes(scope, ctx) {
7837
8181
  if (!against) {
7838
8182
  return reply.code(400).send({ error: "query parameter `against` is required" });
7839
8183
  }
8184
+ const targetProjectName = against === "self" ? proj.name : against;
8185
+ const targetProject = registry.get(targetProjectName);
8186
+ if (!targetProject) {
8187
+ return reply.code(400).send({
8188
+ error: "unknown snapshot id \u2014 `against` must be `self` or the name of a project this server has a snapshot for",
8189
+ against
8190
+ });
8191
+ }
7840
8192
  try {
7841
- const snapshot = await loadSnapshotForDiff(against);
8193
+ const snapshot = await loadSnapshotForDiff(targetProject.paths.snapshotPath);
7842
8194
  return computeGraphDiff(proj.graph, snapshot);
7843
8195
  } catch (err) {
7844
8196
  return reply.code(400).send({ error: "failed to load snapshot", against, detail: err.message });
@@ -7868,6 +8220,13 @@ function registerRoutes(scope, ctx) {
7868
8220
  edgeCount: proj.graph.size
7869
8221
  };
7870
8222
  } catch (err) {
8223
+ if (err instanceof SnapshotValidationError) {
8224
+ return reply.code(400).send({
8225
+ error: "snapshot merge failed",
8226
+ details: err.message,
8227
+ issues: err.issues
8228
+ });
8229
+ }
7871
8230
  return reply.code(400).send({
7872
8231
  error: "snapshot merge failed",
7873
8232
  details: err.message
@@ -8227,6 +8586,7 @@ export {
8227
8586
  reconcileObservedRelPath,
8228
8587
  ensureObservedFileNode,
8229
8588
  ensureServiceNode,
8589
+ ensureInfraNode,
8230
8590
  upsertObservedEdge,
8231
8591
  stitchTrace,
8232
8592
  makeErrorSpanWriter,
@@ -8247,6 +8607,7 @@ export {
8247
8607
  addImports,
8248
8608
  addDatabasesAndCompat,
8249
8609
  addConfigNodes,
8610
+ normalizePathTemplate,
8250
8611
  addCallEdges,
8251
8612
  addInfra,
8252
8613
  retireEdgesByFile,
@@ -8282,4 +8643,4 @@ export {
8282
8643
  pruneRegistry,
8283
8644
  buildApi
8284
8645
  };
8285
- //# sourceMappingURL=chunk-QM6BMPVJ.js.map
8646
+ //# sourceMappingURL=chunk-5T6J3WKC.js.map