@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.
package/dist/server.cjs CHANGED
@@ -53,15 +53,16 @@ function mountBearerAuth(app, opts) {
53
53
  if (!opts.token || opts.token.length === 0) return;
54
54
  if (opts.trustProxy) return;
55
55
  const expected = Buffer.from(opts.token, "utf8");
56
- const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
56
+ const exactUnauthPaths = /* @__PURE__ */ new Set([
57
+ ...DEFAULT_UNAUTH_PATHS,
58
+ ...opts.extraUnauthenticatedSuffixes ?? []
59
+ ]);
57
60
  const publicRead = opts.publicRead === true;
58
61
  app.addHook("preHandler", (req, reply, done) => {
59
- const path46 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
60
- for (const suffix of suffixes) {
61
- if (path46 === suffix || path46.endsWith(suffix)) {
62
- done();
63
- return;
64
- }
62
+ const path47 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
63
+ if (exactUnauthPaths.has(path47) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path47)) {
64
+ done();
65
+ return;
65
66
  }
66
67
  if (publicRead && PUBLIC_READ_METHODS.has(req.method)) {
67
68
  done();
@@ -96,7 +97,7 @@ function readAuthEnv(env = process.env) {
96
97
  publicRead: parseBoolEnv(env.NEAT_PUBLIC_READ)
97
98
  };
98
99
  }
99
- var import_node_crypto, LOOPBACK_HOSTS, BindAuthorityError, PUBLIC_READ_METHODS, DEFAULT_UNAUTH_SUFFIXES;
100
+ var import_node_crypto, LOOPBACK_HOSTS, BindAuthorityError, PUBLIC_READ_METHODS, DEFAULT_UNAUTH_PATHS, PROJECT_SCOPED_UNAUTH_PATTERN;
100
101
  var init_auth = __esm({
101
102
  "src/auth.ts"() {
102
103
  "use strict";
@@ -117,12 +118,15 @@ var init_auth = __esm({
117
118
  }
118
119
  };
119
120
  PUBLIC_READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
120
- DEFAULT_UNAUTH_SUFFIXES = [
121
+ DEFAULT_UNAUTH_PATHS = [
121
122
  "/health",
122
123
  "/healthz",
123
124
  "/readyz",
124
125
  "/api/config"
125
126
  ];
127
+ PROJECT_SCOPED_UNAUTH_PATTERN = new RegExp(
128
+ `^/projects/[^/]+/(?:${DEFAULT_UNAUTH_PATHS.map((p) => p.slice(1)).join("|")})$`
129
+ );
126
130
  }
127
131
  });
128
132
 
@@ -188,8 +192,8 @@ function reshapeGrpcRequest(req) {
188
192
  };
189
193
  }
190
194
  function resolveProtoRoot() {
191
- const here = import_node_path42.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
192
- return import_node_path42.default.resolve(here, "..", "proto");
195
+ const here = import_node_path43.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
196
+ return import_node_path43.default.resolve(here, "..", "proto");
193
197
  }
194
198
  function loadTraceService() {
195
199
  const protoRoot = resolveProtoRoot();
@@ -257,13 +261,13 @@ async function startOtelGrpcReceiver(opts) {
257
261
  })
258
262
  };
259
263
  }
260
- var import_node_url, import_node_path42, import_node_crypto2, grpc, protoLoader;
264
+ var import_node_url, import_node_path43, import_node_crypto2, grpc, protoLoader;
261
265
  var init_otel_grpc = __esm({
262
266
  "src/otel-grpc.ts"() {
263
267
  "use strict";
264
268
  init_cjs_shims();
265
269
  import_node_url = require("url");
266
- import_node_path42 = __toESM(require("path"), 1);
270
+ import_node_path43 = __toESM(require("path"), 1);
267
271
  import_node_crypto2 = require("crypto");
268
272
  grpc = __toESM(require("@grpc/grpc-js"), 1);
269
273
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -356,8 +360,8 @@ function websocketChannelPathOf(attrs) {
356
360
  const v = attrs[key];
357
361
  if (typeof v === "string" && v.length > 0) {
358
362
  const q = v.indexOf("?");
359
- const path46 = q === -1 ? v : v.slice(0, q);
360
- if (path46.length > 0) return path46;
363
+ const path47 = q === -1 ? v : v.slice(0, q);
364
+ if (path47.length > 0) return path47;
361
365
  }
362
366
  }
363
367
  return void 0;
@@ -411,10 +415,10 @@ function parseOtlpRequest(body) {
411
415
  return out;
412
416
  }
413
417
  function loadProtoRoot() {
414
- const here = import_node_path43.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
415
- const protoRoot = import_node_path43.default.resolve(here, "..", "proto");
418
+ const here = import_node_path44.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
419
+ const protoRoot = import_node_path44.default.resolve(here, "..", "proto");
416
420
  const root = new import_protobufjs.default.Root();
417
- root.resolvePath = (_origin, target) => import_node_path43.default.resolve(protoRoot, target);
421
+ root.resolvePath = (_origin, target) => import_node_path44.default.resolve(protoRoot, target);
418
422
  root.loadSync(
419
423
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
420
424
  { keepCase: true }
@@ -626,12 +630,12 @@ async function buildOtelReceiver(opts) {
626
630
  };
627
631
  return decorated;
628
632
  }
629
- var import_node_path43, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
633
+ var import_node_path44, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
630
634
  var init_otel = __esm({
631
635
  "src/otel.ts"() {
632
636
  "use strict";
633
637
  init_cjs_shims();
634
- import_node_path43 = __toESM(require("path"), 1);
638
+ import_node_path44 = __toESM(require("path"), 1);
635
639
  import_node_url2 = require("url");
636
640
  import_fastify2 = __toESM(require("fastify"), 1);
637
641
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -647,7 +651,7 @@ var init_otel = __esm({
647
651
 
648
652
  // src/server.ts
649
653
  init_cjs_shims();
650
- var import_node_path45 = __toESM(require("path"), 1);
654
+ var import_node_path46 = __toESM(require("path"), 1);
651
655
 
652
656
  // src/graph.ts
653
657
  init_cjs_shims();
@@ -671,7 +675,7 @@ function getGraph(project = DEFAULT_PROJECT) {
671
675
  init_cjs_shims();
672
676
  var import_fastify = __toESM(require("fastify"), 1);
673
677
  var import_cors = __toESM(require("@fastify/cors"), 1);
674
- var import_types29 = require("@neat.is/types");
678
+ var import_types30 = require("@neat.is/types");
675
679
 
676
680
  // src/extend/index.ts
677
681
  init_cjs_shims();
@@ -1414,19 +1418,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1414
1418
  function longestIncomingWalk(graph, start, maxDepth) {
1415
1419
  let best = { path: [start], edges: [] };
1416
1420
  const visited = /* @__PURE__ */ new Set([start]);
1417
- function step(node, path46, edges) {
1418
- if (path46.length > best.path.length) {
1419
- best = { path: [...path46], edges: [...edges] };
1421
+ function step(node, path47, edges) {
1422
+ if (path47.length > best.path.length) {
1423
+ best = { path: [...path47], edges: [...edges] };
1420
1424
  }
1421
- if (path46.length - 1 >= maxDepth) return;
1425
+ if (path47.length - 1 >= maxDepth) return;
1422
1426
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1423
1427
  for (const [srcId, edge] of incoming) {
1424
1428
  if (visited.has(srcId)) continue;
1425
1429
  visited.add(srcId);
1426
- path46.push(srcId);
1430
+ path47.push(srcId);
1427
1431
  edges.push(edge);
1428
- step(srcId, path46, edges);
1429
- path46.pop();
1432
+ step(srcId, path47, edges);
1433
+ path47.pop();
1430
1434
  edges.pop();
1431
1435
  visited.delete(srcId);
1432
1436
  }
@@ -1620,26 +1624,26 @@ function dominantFailingCall(graph, serviceId4, visited) {
1620
1624
  return best;
1621
1625
  }
1622
1626
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1623
- const path46 = [originServiceId];
1627
+ const path47 = [originServiceId];
1624
1628
  const edges = [];
1625
1629
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1626
1630
  let current = originServiceId;
1627
1631
  for (let depth = 0; depth < maxDepth; depth++) {
1628
1632
  const hop = dominantFailingCall(graph, current, visited);
1629
1633
  if (!hop) break;
1630
- path46.push(hop.nextService);
1634
+ path47.push(hop.nextService);
1631
1635
  edges.push(hop.edge);
1632
1636
  visited.add(hop.nextService);
1633
1637
  current = hop.nextService;
1634
1638
  }
1635
1639
  if (edges.length === 0) return null;
1636
- return { path: path46, edges, culprit: current };
1640
+ return { path: path47, edges, culprit: current };
1637
1641
  }
1638
1642
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1639
1643
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1640
1644
  if (!chain) return null;
1641
1645
  const culprit = chain.culprit;
1642
- const path46 = [...chain.path];
1646
+ const path47 = [...chain.path];
1643
1647
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1644
1648
  const baseConfidence = confidenceFromMix(chain.edges);
1645
1649
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1647,14 +1651,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1647
1651
  if (loc) {
1648
1652
  let rootCauseNode = culprit;
1649
1653
  if (loc.fileNode) {
1650
- path46.push(loc.fileNode);
1654
+ path47.push(loc.fileNode);
1651
1655
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1652
1656
  rootCauseNode = loc.fileNode;
1653
1657
  }
1654
1658
  return import_types.RootCauseResultSchema.parse({
1655
1659
  rootCauseNode,
1656
1660
  rootCauseReason: loc.rootCauseReason,
1657
- traversalPath: path46,
1661
+ traversalPath: path47,
1658
1662
  edgeProvenances,
1659
1663
  confidence,
1660
1664
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1666,7 +1670,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1666
1670
  return import_types.RootCauseResultSchema.parse({
1667
1671
  rootCauseNode: culprit,
1668
1672
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1669
- traversalPath: path46,
1673
+ traversalPath: path47,
1670
1674
  edgeProvenances,
1671
1675
  confidence,
1672
1676
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2077,6 +2081,51 @@ function computeDivergences(graph, opts = {}) {
2077
2081
  });
2078
2082
  }
2079
2083
 
2084
+ // src/logs-store.ts
2085
+ init_cjs_shims();
2086
+ var LOGS_STORE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
2087
+ var LOGS_QUERY_DEFAULT_LIMIT = 100;
2088
+ var LOGS_QUERY_MAX_LIMIT = 1e3;
2089
+ var buffers = /* @__PURE__ */ new Map();
2090
+ var KEY_SEP = "::";
2091
+ function bufferKey(projectName, source) {
2092
+ return `${projectName}${KEY_SEP}${source}`;
2093
+ }
2094
+ function sourcesForProject(projectName) {
2095
+ const prefix = `${projectName}${KEY_SEP}`;
2096
+ const sources = [];
2097
+ for (const key of buffers.keys()) {
2098
+ if (key.startsWith(prefix)) sources.push(key.slice(prefix.length));
2099
+ }
2100
+ return sources;
2101
+ }
2102
+ function queryLogEntries(opts) {
2103
+ const { projectName, source, service, since, limit } = opts;
2104
+ const sources = source && source.length > 0 ? source : sourcesForProject(projectName);
2105
+ let merged = [];
2106
+ for (const src of sources) {
2107
+ const buf = buffers.get(bufferKey(projectName, src));
2108
+ if (buf) merged = merged.concat(buf);
2109
+ }
2110
+ if (service) {
2111
+ merged = merged.filter((e) => e.serviceName === service);
2112
+ }
2113
+ if (since) {
2114
+ const sinceMs = Date.parse(since);
2115
+ if (!Number.isNaN(sinceMs)) {
2116
+ merged = merged.filter((e) => {
2117
+ const t = Date.parse(e.timestamp);
2118
+ return Number.isNaN(t) || t >= sinceMs;
2119
+ });
2120
+ }
2121
+ }
2122
+ merged.sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
2123
+ if (typeof limit === "number" && Number.isFinite(limit) && limit >= 0) {
2124
+ return merged.slice(0, limit);
2125
+ }
2126
+ return merged;
2127
+ }
2128
+
2080
2129
  // src/policy.ts
2081
2130
  init_cjs_shims();
2082
2131
  var import_node_fs4 = require("fs");
@@ -3663,24 +3712,58 @@ function dedupeIncidents(events) {
3663
3712
  return !hasRealFailure.has(groupKey(ev));
3664
3713
  });
3665
3714
  }
3715
+ var SnapshotValidationError = class extends Error {
3716
+ constructor(issues) {
3717
+ super(`snapshot failed validation (${issues.length} invalid ${issues.length === 1 ? "entry" : "entries"})`);
3718
+ this.issues = issues;
3719
+ this.name = "SnapshotValidationError";
3720
+ }
3721
+ issues;
3722
+ };
3723
+ function describeZodIssues(error) {
3724
+ return error.issues.map((issue) => issue.path.length > 0 ? `${issue.path.join(".")}: ${issue.message}` : issue.message).join("; ");
3725
+ }
3666
3726
  function mergeSnapshot(graph, snapshot) {
3667
3727
  const exported = snapshot.graph;
3728
+ const incomingNodes = Array.isArray(exported.nodes) ? exported.nodes : [];
3729
+ const incomingEdges = Array.isArray(exported.edges) ? exported.edges : [];
3730
+ const issues = [];
3731
+ const validNodes = [];
3732
+ const validEdges = [];
3733
+ for (const node of incomingNodes) {
3734
+ if (node.attributes === void 0) continue;
3735
+ const parsed = import_types4.GraphNodeSchema.safeParse(node.attributes);
3736
+ if (!parsed.success) {
3737
+ issues.push(`node "${node.key}": ${describeZodIssues(parsed.error)}`);
3738
+ continue;
3739
+ }
3740
+ validNodes.push({ key: node.key, attributes: parsed.data });
3741
+ }
3742
+ for (const edge of incomingEdges) {
3743
+ if (edge.attributes === void 0) continue;
3744
+ const parsed = import_types4.GraphEdgeSchema.safeParse(edge.attributes);
3745
+ if (!parsed.success) {
3746
+ const label = edge.key ?? `${edge.source}->${edge.target}`;
3747
+ issues.push(`edge "${label}": ${describeZodIssues(parsed.error)}`);
3748
+ continue;
3749
+ }
3750
+ const id = edge.key ?? parsed.data.id;
3751
+ validEdges.push({ key: id, source: edge.source, target: edge.target, attributes: parsed.data });
3752
+ }
3753
+ if (issues.length > 0) {
3754
+ throw new SnapshotValidationError(issues);
3755
+ }
3668
3756
  let nodesAdded = 0;
3669
3757
  let edgesAdded = 0;
3670
- for (const node of exported.nodes ?? []) {
3758
+ for (const node of validNodes) {
3671
3759
  if (graph.hasNode(node.key)) continue;
3672
- if (!node.attributes) continue;
3673
3760
  graph.addNode(node.key, node.attributes);
3674
3761
  nodesAdded++;
3675
3762
  }
3676
- for (const edge of exported.edges ?? []) {
3677
- const attrs = edge.attributes;
3678
- if (!attrs) continue;
3679
- const id = edge.key ?? attrs.id;
3680
- if (!id) continue;
3681
- if (graph.hasEdge(id)) continue;
3763
+ for (const edge of validEdges) {
3764
+ if (graph.hasEdge(edge.key)) continue;
3682
3765
  if (!graph.hasNode(edge.source) || !graph.hasNode(edge.target)) continue;
3683
- graph.addEdgeWithKey(id, edge.source, edge.target, attrs);
3766
+ graph.addEdgeWithKey(edge.key, edge.source, edge.target, edge.attributes);
3684
3767
  edgesAdded++;
3685
3768
  }
3686
3769
  return { nodesAdded, edgesAdded };
@@ -5735,10 +5818,10 @@ function fastifyRouteMethods(objNode) {
5735
5818
  }
5736
5819
  return [];
5737
5820
  }
5738
- function serverRoutesFromSource(source, parser, hasExpress, hasFastify) {
5821
+ function serverRoutesFromSource(source, parser, hasExpress, hasFastify, hasHono = false) {
5739
5822
  const tree = parseSource2(parser, source);
5740
5823
  const out = [];
5741
- const framework = hasExpress ? "express" : "fastify";
5824
+ const framework = hasExpress ? "express" : hasFastify ? "fastify" : hasHono ? "hono" : "unknown";
5742
5825
  walk(tree.rootNode, (node) => {
5743
5826
  if (node.type !== "call_expression") return;
5744
5827
  const fn = node.childForFieldName("function");
@@ -5888,8 +5971,9 @@ async function addRoutes(graph, services) {
5888
5971
  };
5889
5972
  const hasExpress = deps["express"] !== void 0;
5890
5973
  const hasFastify = deps["fastify"] !== void 0;
5974
+ const hasHono = deps["hono"] !== void 0;
5891
5975
  const hasNext = deps["next"] !== void 0;
5892
- if (!hasExpress && !hasFastify && !hasNext) continue;
5976
+ if (!hasExpress && !hasFastify && !hasHono && !hasNext) continue;
5893
5977
  const files = await loadSourceFiles(service.dir);
5894
5978
  for (const file of files) {
5895
5979
  if (isTestPath(file.path)) continue;
@@ -5899,8 +5983,8 @@ async function addRoutes(graph, services) {
5899
5983
  try {
5900
5984
  if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
5901
5985
  routes = nextRoutesFromFile(file.content, relFile, jsParser);
5902
- } else if (hasExpress || hasFastify) {
5903
- routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify);
5986
+ } else if (hasExpress || hasFastify || hasHono) {
5987
+ routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify, hasHono);
5904
5988
  } else {
5905
5989
  routes = [];
5906
5990
  }
@@ -7224,30 +7308,264 @@ async function addK8sResources(graph, scanPath) {
7224
7308
  return { nodesAdded, edgesAdded: 0 };
7225
7309
  }
7226
7310
 
7311
+ // src/extract/infra/cloudflare.ts
7312
+ init_cjs_shims();
7313
+ var import_node_fs21 = require("fs");
7314
+ var import_node_path37 = __toESM(require("path"), 1);
7315
+ var import_smol_toml2 = require("smol-toml");
7316
+ var import_types26 = require("@neat.is/types");
7317
+ var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
7318
+ async function readWranglerConfig(dir) {
7319
+ for (const filename of WRANGLER_FILENAMES) {
7320
+ const abs = import_node_path37.default.join(dir, filename);
7321
+ if (!await exists2(abs)) continue;
7322
+ const raw = await import_node_fs21.promises.readFile(abs, "utf8");
7323
+ const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
7324
+ return { config, relFile: filename, raw };
7325
+ }
7326
+ return null;
7327
+ }
7328
+ function lineContaining(raw, needle) {
7329
+ if (!needle) return void 0;
7330
+ const idx = raw.indexOf(needle);
7331
+ if (idx === -1) return void 0;
7332
+ let line = 1;
7333
+ for (let i = 0; i < idx; i++) if (raw[i] === "\n") line++;
7334
+ return line;
7335
+ }
7336
+ function normalizeRoutes(config) {
7337
+ const out = [];
7338
+ const pushOne = (r) => {
7339
+ if (typeof r === "string") out.push(r);
7340
+ else if (r && typeof r === "object" && typeof r.pattern === "string") {
7341
+ out.push(r.pattern);
7342
+ }
7343
+ };
7344
+ if (Array.isArray(config.routes)) config.routes.forEach(pushOne);
7345
+ else if (config.route) pushOne(config.route);
7346
+ return out;
7347
+ }
7348
+ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, line) {
7349
+ let nodesAdded = 0;
7350
+ let edgesAdded = 0;
7351
+ const node = makeInfraNode(kind, name, "cloudflare");
7352
+ if (!graph.hasNode(node.id)) {
7353
+ graph.addNode(node.id, node);
7354
+ nodesAdded++;
7355
+ }
7356
+ if (node.id === anchorId) return { nodesAdded, edgesAdded };
7357
+ const edgeId = (0, import_types5.extractedEdgeId)(anchorId, node.id, edgeType);
7358
+ if (!graph.hasEdge(edgeId)) {
7359
+ const edge = {
7360
+ id: edgeId,
7361
+ source: anchorId,
7362
+ target: node.id,
7363
+ type: edgeType,
7364
+ provenance: import_types26.Provenance.EXTRACTED,
7365
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7366
+ evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
7367
+ };
7368
+ graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
7369
+ edgesAdded++;
7370
+ }
7371
+ return { nodesAdded, edgesAdded };
7372
+ }
7373
+ async function addCloudflareWorkers(graph, services, scanPath) {
7374
+ let nodesAdded = 0;
7375
+ let edgesAdded = 0;
7376
+ const discovered = [];
7377
+ const workerIndex = /* @__PURE__ */ new Map();
7378
+ for (const service of services) {
7379
+ let read;
7380
+ try {
7381
+ read = await readWranglerConfig(service.dir);
7382
+ } catch (err) {
7383
+ recordExtractionError("infra cloudflare", import_node_path37.default.relative(scanPath, service.dir), err);
7384
+ continue;
7385
+ }
7386
+ if (!read || !read.config.name) continue;
7387
+ const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, read.relFile)));
7388
+ discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
7389
+ }
7390
+ for (const worker of discovered) {
7391
+ const { service, config } = worker;
7392
+ const serviceNode = graph.getNodeAttributes(service.node.id);
7393
+ if (serviceNode.platform !== "cloudflare") {
7394
+ const updated = { ...serviceNode, platform: "cloudflare" };
7395
+ graph.replaceNodeAttributes(service.node.id, updated);
7396
+ }
7397
+ let anchorId = service.node.id;
7398
+ if (config.main) {
7399
+ const entryRelPath = toPosix2(import_node_path37.default.normalize(config.main));
7400
+ const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7401
+ graph,
7402
+ service.pkg.name,
7403
+ service.node.id,
7404
+ entryRelPath
7405
+ );
7406
+ nodesAdded += fn;
7407
+ edgesAdded += fe;
7408
+ const fileNode = graph.getNodeAttributes(fileNodeId);
7409
+ if (fileNode.platform !== "cloudflare" || fileNode.platformName !== config.name) {
7410
+ const updated = { ...fileNode, platform: "cloudflare", platformName: config.name };
7411
+ graph.replaceNodeAttributes(fileNodeId, updated);
7412
+ }
7413
+ anchorId = fileNodeId;
7414
+ }
7415
+ workerIndex.set(config.name, { anchorId });
7416
+ }
7417
+ for (const worker of discovered) {
7418
+ const { config, evidenceFile, raw } = worker;
7419
+ const anchorId = workerIndex.get(config.name).anchorId;
7420
+ const runtimeNode = makeInfraNode("workerd", "cloudflare", "cloudflare");
7421
+ if (!graph.hasNode(runtimeNode.id)) {
7422
+ graph.addNode(runtimeNode.id, runtimeNode);
7423
+ nodesAdded++;
7424
+ }
7425
+ if (runtimeNode.id !== anchorId) {
7426
+ const runsOnId = (0, import_types5.extractedEdgeId)(anchorId, runtimeNode.id, import_types26.EdgeType.RUNS_ON);
7427
+ if (!graph.hasEdge(runsOnId)) {
7428
+ const edge = {
7429
+ id: runsOnId,
7430
+ source: anchorId,
7431
+ target: runtimeNode.id,
7432
+ type: import_types26.EdgeType.RUNS_ON,
7433
+ provenance: import_types26.Provenance.EXTRACTED,
7434
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7435
+ evidence: {
7436
+ file: evidenceFile,
7437
+ ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
7438
+ }
7439
+ };
7440
+ graph.addEdgeWithKey(runsOnId, edge.source, edge.target, edge);
7441
+ edgesAdded++;
7442
+ }
7443
+ }
7444
+ for (const route of normalizeRoutes(config)) {
7445
+ const result = addResourceEdge(
7446
+ graph,
7447
+ anchorId,
7448
+ import_types26.EdgeType.CONNECTS_TO,
7449
+ "cloudflare-route",
7450
+ route,
7451
+ evidenceFile,
7452
+ lineContaining(raw, route)
7453
+ );
7454
+ nodesAdded += result.nodesAdded;
7455
+ edgesAdded += result.edgesAdded;
7456
+ }
7457
+ const bindingGroups = [
7458
+ { kind: "cloudflare-kv", entries: config.kv_namespaces ?? [], nameOf: (b) => b.binding },
7459
+ { kind: "cloudflare-d1", entries: config.d1_databases ?? [], nameOf: (b) => b.binding },
7460
+ { kind: "cloudflare-r2", entries: config.r2_buckets ?? [], nameOf: (b) => b.binding },
7461
+ { kind: "cloudflare-durable-object", entries: config.durable_objects?.bindings ?? [], nameOf: (b) => b.name },
7462
+ { kind: "cloudflare-queue", entries: config.queues?.producers ?? [], nameOf: (b) => b.queue },
7463
+ { kind: "cloudflare-queue", entries: config.queues?.consumers ?? [], nameOf: (b) => b.queue }
7464
+ ];
7465
+ for (const group of bindingGroups) {
7466
+ for (const entry of group.entries) {
7467
+ const name = group.nameOf(entry);
7468
+ if (!name) continue;
7469
+ const result = addResourceEdge(
7470
+ graph,
7471
+ anchorId,
7472
+ import_types26.EdgeType.DEPENDS_ON,
7473
+ group.kind,
7474
+ name,
7475
+ evidenceFile,
7476
+ lineContaining(raw, name)
7477
+ );
7478
+ nodesAdded += result.nodesAdded;
7479
+ edgesAdded += result.edgesAdded;
7480
+ }
7481
+ }
7482
+ for (const cron of config.triggers?.crons ?? []) {
7483
+ const result = addResourceEdge(
7484
+ graph,
7485
+ anchorId,
7486
+ import_types26.EdgeType.DEPENDS_ON,
7487
+ "cloudflare-cron",
7488
+ cron,
7489
+ evidenceFile,
7490
+ lineContaining(raw, cron)
7491
+ );
7492
+ nodesAdded += result.nodesAdded;
7493
+ edgesAdded += result.edgesAdded;
7494
+ }
7495
+ for (const varName of Object.keys(config.vars ?? {})) {
7496
+ const result = addResourceEdge(
7497
+ graph,
7498
+ anchorId,
7499
+ import_types26.EdgeType.DEPENDS_ON,
7500
+ "cloudflare-env-var",
7501
+ varName,
7502
+ evidenceFile,
7503
+ lineContaining(raw, varName)
7504
+ );
7505
+ nodesAdded += result.nodesAdded;
7506
+ edgesAdded += result.edgesAdded;
7507
+ }
7508
+ for (const svc of config.services ?? []) {
7509
+ if (!svc.service) continue;
7510
+ const target = workerIndex.get(svc.service);
7511
+ if (target && target.anchorId !== anchorId) {
7512
+ const edgeId = (0, import_types5.extractedEdgeId)(anchorId, target.anchorId, import_types26.EdgeType.CALLS);
7513
+ if (!graph.hasEdge(edgeId)) {
7514
+ const edge = {
7515
+ id: edgeId,
7516
+ source: anchorId,
7517
+ target: target.anchorId,
7518
+ type: import_types26.EdgeType.CALLS,
7519
+ provenance: import_types26.Provenance.EXTRACTED,
7520
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7521
+ evidence: { file: evidenceFile, line: lineContaining(raw, svc.service) }
7522
+ };
7523
+ graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
7524
+ edgesAdded++;
7525
+ }
7526
+ continue;
7527
+ }
7528
+ const result = addResourceEdge(
7529
+ graph,
7530
+ anchorId,
7531
+ import_types26.EdgeType.DEPENDS_ON,
7532
+ "cloudflare-service-binding",
7533
+ svc.service,
7534
+ evidenceFile,
7535
+ lineContaining(raw, svc.service)
7536
+ );
7537
+ nodesAdded += result.nodesAdded;
7538
+ edgesAdded += result.edgesAdded;
7539
+ }
7540
+ }
7541
+ return { nodesAdded, edgesAdded };
7542
+ }
7543
+
7227
7544
  // src/extract/infra/index.ts
7228
7545
  async function addInfra(graph, scanPath, services) {
7229
7546
  const compose = await addComposeInfra(graph, scanPath, services);
7230
7547
  const dockerfile = await addDockerfileRuntimes(graph, services, scanPath);
7231
7548
  const terraform = await addTerraformResources(graph, scanPath);
7232
7549
  const k8s = await addK8sResources(graph, scanPath);
7550
+ const cloudflare = await addCloudflareWorkers(graph, services, scanPath);
7233
7551
  return {
7234
- nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded,
7235
- edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded
7552
+ nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded,
7553
+ edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded
7236
7554
  };
7237
7555
  }
7238
7556
 
7239
7557
  // src/extract/index.ts
7240
- var import_node_path38 = __toESM(require("path"), 1);
7558
+ var import_node_path39 = __toESM(require("path"), 1);
7241
7559
 
7242
7560
  // src/extract/retire.ts
7243
7561
  init_cjs_shims();
7244
- var import_node_fs21 = require("fs");
7245
- var import_node_path37 = __toESM(require("path"), 1);
7246
- var import_types26 = require("@neat.is/types");
7562
+ var import_node_fs22 = require("fs");
7563
+ var import_node_path38 = __toESM(require("path"), 1);
7564
+ var import_types27 = require("@neat.is/types");
7247
7565
  function dropOrphanedFileNodes(graph) {
7248
7566
  const orphans = [];
7249
7567
  graph.forEachNode((id, attrs) => {
7250
- if (attrs.type !== import_types26.NodeType.FileNode) return;
7568
+ if (attrs.type !== import_types27.NodeType.FileNode) return;
7251
7569
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
7252
7570
  orphans.push(id);
7253
7571
  }
@@ -7260,14 +7578,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
7260
7578
  const bases = [scanPath, ...serviceDirs];
7261
7579
  graph.forEachEdge((id, attrs) => {
7262
7580
  const edge = attrs;
7263
- if (edge.provenance !== import_types26.Provenance.EXTRACTED) return;
7581
+ if (edge.provenance !== import_types27.Provenance.EXTRACTED) return;
7264
7582
  const evidenceFile = edge.evidence?.file;
7265
7583
  if (!evidenceFile) return;
7266
- if (import_node_path37.default.isAbsolute(evidenceFile)) {
7267
- if (!(0, import_node_fs21.existsSync)(evidenceFile)) toDrop.push(id);
7584
+ if (import_node_path38.default.isAbsolute(evidenceFile)) {
7585
+ if (!(0, import_node_fs22.existsSync)(evidenceFile)) toDrop.push(id);
7268
7586
  return;
7269
7587
  }
7270
- const found = bases.some((base) => (0, import_node_fs21.existsSync)(import_node_path37.default.join(base, evidenceFile)));
7588
+ const found = bases.some((base) => (0, import_node_fs22.existsSync)(import_node_path38.default.join(base, evidenceFile)));
7271
7589
  if (!found) toDrop.push(id);
7272
7590
  });
7273
7591
  for (const id of toDrop) graph.dropEdge(id);
@@ -7309,7 +7627,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7309
7627
  }
7310
7628
  const droppedEntries = drainDroppedExtracted();
7311
7629
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
7312
- const rejectedPath = import_node_path38.default.join(import_node_path38.default.dirname(opts.errorsPath), "rejected.ndjson");
7630
+ const rejectedPath = import_node_path39.default.join(import_node_path39.default.dirname(opts.errorsPath), "rejected.ndjson");
7313
7631
  try {
7314
7632
  await writeRejectedExtracted(droppedEntries, rejectedPath);
7315
7633
  } catch (err) {
@@ -7343,7 +7661,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7343
7661
 
7344
7662
  // src/diff.ts
7345
7663
  init_cjs_shims();
7346
- var import_node_fs22 = require("fs");
7664
+ var import_node_fs23 = require("fs");
7347
7665
  async function loadSnapshotForDiff(target) {
7348
7666
  if (/^https?:\/\//i.test(target)) {
7349
7667
  const res = await fetch(target);
@@ -7352,7 +7670,7 @@ async function loadSnapshotForDiff(target) {
7352
7670
  }
7353
7671
  return await res.json();
7354
7672
  }
7355
- const raw = await import_node_fs22.promises.readFile(target, "utf8");
7673
+ const raw = await import_node_fs23.promises.readFile(target, "utf8");
7356
7674
  return JSON.parse(raw);
7357
7675
  }
7358
7676
  function indexEntries(entries) {
@@ -7420,9 +7738,9 @@ function canonicalJson(value) {
7420
7738
 
7421
7739
  // src/persist.ts
7422
7740
  init_cjs_shims();
7423
- var import_node_fs23 = require("fs");
7424
- var import_node_path39 = __toESM(require("path"), 1);
7425
- var import_types27 = require("@neat.is/types");
7741
+ var import_node_fs24 = require("fs");
7742
+ var import_node_path40 = __toESM(require("path"), 1);
7743
+ var import_types28 = require("@neat.is/types");
7426
7744
  var SCHEMA_VERSION = 4;
7427
7745
  function migrateV1ToV2(payload) {
7428
7746
  const nodes = payload.graph.nodes;
@@ -7444,12 +7762,12 @@ function migrateV2ToV3(payload) {
7444
7762
  for (const edge of edges) {
7445
7763
  const attrs = edge.attributes;
7446
7764
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
7447
- attrs.provenance = import_types27.Provenance.OBSERVED;
7765
+ attrs.provenance = import_types28.Provenance.OBSERVED;
7448
7766
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
7449
7767
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
7450
7768
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
7451
7769
  if (type && source && target) {
7452
- const newId = (0, import_types27.observedEdgeId)(source, target, type);
7770
+ const newId = (0, import_types28.observedEdgeId)(source, target, type);
7453
7771
  attrs.id = newId;
7454
7772
  if (edge.key) edge.key = newId;
7455
7773
  }
@@ -7458,7 +7776,7 @@ function migrateV2ToV3(payload) {
7458
7776
  return { ...payload, schemaVersion: 3 };
7459
7777
  }
7460
7778
  async function ensureDir(filePath) {
7461
- await import_node_fs23.promises.mkdir(import_node_path39.default.dirname(filePath), { recursive: true });
7779
+ await import_node_fs24.promises.mkdir(import_node_path40.default.dirname(filePath), { recursive: true });
7462
7780
  }
7463
7781
  async function saveGraphToDisk(graph, outPath) {
7464
7782
  await ensureDir(outPath);
@@ -7468,13 +7786,13 @@ async function saveGraphToDisk(graph, outPath) {
7468
7786
  graph: graph.export()
7469
7787
  };
7470
7788
  const tmp = `${outPath}.tmp`;
7471
- await import_node_fs23.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
7472
- await import_node_fs23.promises.rename(tmp, outPath);
7789
+ await import_node_fs24.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
7790
+ await import_node_fs24.promises.rename(tmp, outPath);
7473
7791
  }
7474
7792
  async function loadGraphFromDisk(graph, outPath) {
7475
7793
  let raw;
7476
7794
  try {
7477
- raw = await import_node_fs23.promises.readFile(outPath, "utf8");
7795
+ raw = await import_node_fs24.promises.readFile(outPath, "utf8");
7478
7796
  } catch (err) {
7479
7797
  if (err.code === "ENOENT") return;
7480
7798
  throw err;
@@ -7539,23 +7857,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
7539
7857
 
7540
7858
  // src/projects.ts
7541
7859
  init_cjs_shims();
7542
- var import_node_path40 = __toESM(require("path"), 1);
7860
+ var import_node_path41 = __toESM(require("path"), 1);
7543
7861
  function pathsForProject(project, baseDir) {
7544
7862
  if (project === DEFAULT_PROJECT) {
7545
7863
  return {
7546
- snapshotPath: import_node_path40.default.join(baseDir, "graph.json"),
7547
- errorsPath: import_node_path40.default.join(baseDir, "errors.ndjson"),
7548
- staleEventsPath: import_node_path40.default.join(baseDir, "stale-events.ndjson"),
7549
- embeddingsCachePath: import_node_path40.default.join(baseDir, "embeddings.json"),
7550
- policyViolationsPath: import_node_path40.default.join(baseDir, "policy-violations.ndjson")
7864
+ snapshotPath: import_node_path41.default.join(baseDir, "graph.json"),
7865
+ errorsPath: import_node_path41.default.join(baseDir, "errors.ndjson"),
7866
+ staleEventsPath: import_node_path41.default.join(baseDir, "stale-events.ndjson"),
7867
+ embeddingsCachePath: import_node_path41.default.join(baseDir, "embeddings.json"),
7868
+ policyViolationsPath: import_node_path41.default.join(baseDir, "policy-violations.ndjson")
7551
7869
  };
7552
7870
  }
7553
7871
  return {
7554
- snapshotPath: import_node_path40.default.join(baseDir, `${project}.json`),
7555
- errorsPath: import_node_path40.default.join(baseDir, `errors.${project}.ndjson`),
7556
- staleEventsPath: import_node_path40.default.join(baseDir, `stale-events.${project}.ndjson`),
7557
- embeddingsCachePath: import_node_path40.default.join(baseDir, `embeddings.${project}.json`),
7558
- policyViolationsPath: import_node_path40.default.join(baseDir, `policy-violations.${project}.ndjson`)
7872
+ snapshotPath: import_node_path41.default.join(baseDir, `${project}.json`),
7873
+ errorsPath: import_node_path41.default.join(baseDir, `errors.${project}.ndjson`),
7874
+ staleEventsPath: import_node_path41.default.join(baseDir, `stale-events.${project}.ndjson`),
7875
+ embeddingsCachePath: import_node_path41.default.join(baseDir, `embeddings.${project}.json`),
7876
+ policyViolationsPath: import_node_path41.default.join(baseDir, `policy-violations.${project}.ndjson`)
7559
7877
  };
7560
7878
  }
7561
7879
  var Projects = class {
@@ -7595,23 +7913,23 @@ function parseExtraProjects(raw) {
7595
7913
 
7596
7914
  // src/registry.ts
7597
7915
  init_cjs_shims();
7598
- var import_node_fs24 = require("fs");
7916
+ var import_node_fs25 = require("fs");
7599
7917
  var import_node_os3 = __toESM(require("os"), 1);
7600
- var import_node_path41 = __toESM(require("path"), 1);
7601
- var import_types28 = require("@neat.is/types");
7918
+ var import_node_path42 = __toESM(require("path"), 1);
7919
+ var import_types29 = require("@neat.is/types");
7602
7920
  function neatHome() {
7603
7921
  const override = process.env.NEAT_HOME;
7604
- if (override && override.length > 0) return import_node_path41.default.resolve(override);
7605
- return import_node_path41.default.join(import_node_os3.default.homedir(), ".neat");
7922
+ if (override && override.length > 0) return import_node_path42.default.resolve(override);
7923
+ return import_node_path42.default.join(import_node_os3.default.homedir(), ".neat");
7606
7924
  }
7607
7925
  function registryPath() {
7608
- return import_node_path41.default.join(neatHome(), "projects.json");
7926
+ return import_node_path42.default.join(neatHome(), "projects.json");
7609
7927
  }
7610
7928
  async function readRegistry() {
7611
7929
  const file = registryPath();
7612
7930
  let raw;
7613
7931
  try {
7614
- raw = await import_node_fs24.promises.readFile(file, "utf8");
7932
+ raw = await import_node_fs25.promises.readFile(file, "utf8");
7615
7933
  } catch (err) {
7616
7934
  if (err.code === "ENOENT") {
7617
7935
  return { version: 1, projects: [] };
@@ -7619,7 +7937,7 @@ async function readRegistry() {
7619
7937
  throw err;
7620
7938
  }
7621
7939
  const parsed = JSON.parse(raw);
7622
- return import_types28.RegistryFileSchema.parse(parsed);
7940
+ return import_types29.RegistryFileSchema.parse(parsed);
7623
7941
  }
7624
7942
  async function getProject(name) {
7625
7943
  const reg = await readRegistry();
@@ -7840,11 +8158,11 @@ function registerRoutes(scope, ctx) {
7840
8158
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
7841
8159
  const parsed = [];
7842
8160
  for (const c of candidates) {
7843
- const r = import_types29.DivergenceTypeSchema.safeParse(c);
8161
+ const r = import_types30.DivergenceTypeSchema.safeParse(c);
7844
8162
  if (!r.success) {
7845
8163
  return reply.code(400).send({
7846
8164
  error: `unknown divergence type "${c}"`,
7847
- allowed: import_types29.DivergenceTypeSchema.options
8165
+ allowed: import_types30.DivergenceTypeSchema.options
7848
8166
  });
7849
8167
  }
7850
8168
  parsed.push(r.data);
@@ -7892,6 +8210,23 @@ function registerRoutes(scope, ctx) {
7892
8210
  const sliced = ordered.slice(0, Number.isFinite(limit) && limit > 0 ? limit : 50);
7893
8211
  return { count: sliced.length, total, events: sliced };
7894
8212
  });
8213
+ scope.get("/logs", async (req, reply) => {
8214
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
8215
+ if (!proj) return;
8216
+ const rawSource = req.query.source;
8217
+ const sources = rawSource === void 0 ? void 0 : Array.isArray(rawSource) ? rawSource : [rawSource];
8218
+ const filtered = queryLogEntries({
8219
+ projectName: proj.name,
8220
+ ...sources && sources.length > 0 ? { source: sources } : {},
8221
+ ...req.query.service ? { service: req.query.service } : {},
8222
+ ...req.query.since ? { since: req.query.since } : {}
8223
+ });
8224
+ const total = filtered.length;
8225
+ const limit = req.query.limit ? Number(req.query.limit) : LOGS_QUERY_DEFAULT_LIMIT;
8226
+ const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.min(limit, LOGS_QUERY_MAX_LIMIT) : LOGS_QUERY_DEFAULT_LIMIT;
8227
+ const sliced = filtered.slice(0, safeLimit);
8228
+ return { count: sliced.length, total, logs: sliced };
8229
+ });
7895
8230
  const incidentHistoryHandler = async (req, reply) => {
7896
8231
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
7897
8232
  if (!proj) return;
@@ -7987,8 +8322,16 @@ function registerRoutes(scope, ctx) {
7987
8322
  if (!against) {
7988
8323
  return reply.code(400).send({ error: "query parameter `against` is required" });
7989
8324
  }
8325
+ const targetProjectName = against === "self" ? proj.name : against;
8326
+ const targetProject = registry.get(targetProjectName);
8327
+ if (!targetProject) {
8328
+ return reply.code(400).send({
8329
+ error: "unknown snapshot id \u2014 `against` must be `self` or the name of a project this server has a snapshot for",
8330
+ against
8331
+ });
8332
+ }
7990
8333
  try {
7991
- const snapshot = await loadSnapshotForDiff(against);
8334
+ const snapshot = await loadSnapshotForDiff(targetProject.paths.snapshotPath);
7992
8335
  return computeGraphDiff(proj.graph, snapshot);
7993
8336
  } catch (err) {
7994
8337
  return reply.code(400).send({ error: "failed to load snapshot", against, detail: err.message });
@@ -8018,6 +8361,13 @@ function registerRoutes(scope, ctx) {
8018
8361
  edgeCount: proj.graph.size
8019
8362
  };
8020
8363
  } catch (err) {
8364
+ if (err instanceof SnapshotValidationError) {
8365
+ return reply.code(400).send({
8366
+ error: "snapshot merge failed",
8367
+ details: err.message,
8368
+ issues: err.issues
8369
+ });
8370
+ }
8021
8371
  return reply.code(400).send({
8022
8372
  error: "snapshot merge failed",
8023
8373
  details: err.message
@@ -8063,7 +8413,7 @@ function registerRoutes(scope, ctx) {
8063
8413
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
8064
8414
  let violations = await log.readAll();
8065
8415
  if (req.query.severity) {
8066
- const sev = import_types29.PolicySeveritySchema.safeParse(req.query.severity);
8416
+ const sev = import_types30.PolicySeveritySchema.safeParse(req.query.severity);
8067
8417
  if (!sev.success) {
8068
8418
  return reply.code(400).send({
8069
8419
  error: "invalid severity",
@@ -8102,7 +8452,7 @@ function registerRoutes(scope, ctx) {
8102
8452
  scope.post("/policies/check", async (req, reply) => {
8103
8453
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
8104
8454
  if (!proj) return;
8105
- const parsed = import_types29.PoliciesCheckBodySchema.safeParse(req.body ?? {});
8455
+ const parsed = import_types30.PoliciesCheckBodySchema.safeParse(req.body ?? {});
8106
8456
  if (!parsed.success) {
8107
8457
  return reply.code(400).send({
8108
8458
  error: "invalid /policies/check body",
@@ -8364,8 +8714,8 @@ init_otel_grpc();
8364
8714
 
8365
8715
  // src/search.ts
8366
8716
  init_cjs_shims();
8367
- var import_node_fs25 = require("fs");
8368
- var import_node_path44 = __toESM(require("path"), 1);
8717
+ var import_node_fs26 = require("fs");
8718
+ var import_node_path45 = __toESM(require("path"), 1);
8369
8719
  var import_node_crypto3 = require("crypto");
8370
8720
  var DEFAULT_LIMIT = 10;
8371
8721
  var NOMIC_DIM = 768;
@@ -8519,7 +8869,7 @@ async function pickEmbedder() {
8519
8869
  }
8520
8870
  async function readCache(cachePath) {
8521
8871
  try {
8522
- const raw = await import_node_fs25.promises.readFile(cachePath, "utf8");
8872
+ const raw = await import_node_fs26.promises.readFile(cachePath, "utf8");
8523
8873
  const parsed = JSON.parse(raw);
8524
8874
  if (parsed.version !== 1) return null;
8525
8875
  return parsed;
@@ -8528,8 +8878,8 @@ async function readCache(cachePath) {
8528
8878
  }
8529
8879
  }
8530
8880
  async function writeCache(cachePath, cache) {
8531
- await import_node_fs25.promises.mkdir(import_node_path44.default.dirname(cachePath), { recursive: true });
8532
- await import_node_fs25.promises.writeFile(cachePath, JSON.stringify(cache));
8881
+ await import_node_fs26.promises.mkdir(import_node_path45.default.dirname(cachePath), { recursive: true });
8882
+ await import_node_fs26.promises.writeFile(cachePath, JSON.stringify(cache));
8533
8883
  }
8534
8884
  var VectorIndex = class {
8535
8885
  constructor(embedder, cachePath) {
@@ -8709,14 +9059,14 @@ async function bootProject(registry, name, scanPath, baseDir) {
8709
9059
  async function main() {
8710
9060
  const baseDirEnv = process.env.NEAT_OUT_DIR;
8711
9061
  const legacyOutPath = process.env.NEAT_OUT_PATH;
8712
- const baseDir = baseDirEnv ? import_node_path45.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path45.default.resolve(import_node_path45.default.dirname(legacyOutPath)) : import_node_path45.default.resolve("./neat-out");
8713
- const defaultScanPath = import_node_path45.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
9062
+ const baseDir = baseDirEnv ? import_node_path46.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path46.default.resolve(import_node_path46.default.dirname(legacyOutPath)) : import_node_path46.default.resolve("./neat-out");
9063
+ const defaultScanPath = import_node_path46.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
8714
9064
  const registry = new Projects();
8715
9065
  await bootProject(registry, DEFAULT_PROJECT, defaultScanPath, baseDir);
8716
9066
  for (const name of parseExtraProjects(process.env.NEAT_PROJECTS)) {
8717
9067
  const envKey = `NEAT_PROJECT_SCAN_PATH_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
8718
9068
  const projectScan = process.env[envKey];
8719
- await bootProject(registry, name, projectScan ? import_node_path45.default.resolve(projectScan) : void 0, baseDir);
9069
+ await bootProject(registry, name, projectScan ? import_node_path46.default.resolve(projectScan) : void 0, baseDir);
8720
9070
  }
8721
9071
  const host = process.env.HOST ?? "0.0.0.0";
8722
9072
  const port = Number(process.env.PORT ?? 8080);