@neat.is/core 0.4.28-dev.20260708 → 0.4.28-dev.20260710

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/index.cjs CHANGED
@@ -60,8 +60,8 @@ function mountBearerAuth(app, opts) {
60
60
  ]);
61
61
  const publicRead = opts.publicRead === true;
62
62
  app.addHook("preHandler", (req, reply, done) => {
63
- const path47 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
64
- if (exactUnauthPaths.has(path47) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path47)) {
63
+ const path48 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
64
+ if (exactUnauthPaths.has(path48) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path48)) {
65
65
  done();
66
66
  return;
67
67
  }
@@ -193,8 +193,8 @@ function reshapeGrpcRequest(req) {
193
193
  };
194
194
  }
195
195
  function resolveProtoRoot() {
196
- const here = import_node_path42.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
197
- return import_node_path42.default.resolve(here, "..", "proto");
196
+ const here = import_node_path43.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
197
+ return import_node_path43.default.resolve(here, "..", "proto");
198
198
  }
199
199
  function loadTraceService() {
200
200
  const protoRoot = resolveProtoRoot();
@@ -262,13 +262,13 @@ async function startOtelGrpcReceiver(opts) {
262
262
  })
263
263
  };
264
264
  }
265
- var import_node_url, import_node_path42, import_node_crypto2, grpc, protoLoader;
265
+ var import_node_url, import_node_path43, import_node_crypto2, grpc, protoLoader;
266
266
  var init_otel_grpc = __esm({
267
267
  "src/otel-grpc.ts"() {
268
268
  "use strict";
269
269
  init_cjs_shims();
270
270
  import_node_url = require("url");
271
- import_node_path42 = __toESM(require("path"), 1);
271
+ import_node_path43 = __toESM(require("path"), 1);
272
272
  import_node_crypto2 = require("crypto");
273
273
  grpc = __toESM(require("@grpc/grpc-js"), 1);
274
274
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -361,8 +361,8 @@ function websocketChannelPathOf(attrs) {
361
361
  const v = attrs[key];
362
362
  if (typeof v === "string" && v.length > 0) {
363
363
  const q = v.indexOf("?");
364
- const path47 = q === -1 ? v : v.slice(0, q);
365
- if (path47.length > 0) return path47;
364
+ const path48 = q === -1 ? v : v.slice(0, q);
365
+ if (path48.length > 0) return path48;
366
366
  }
367
367
  }
368
368
  return void 0;
@@ -416,10 +416,10 @@ function parseOtlpRequest(body) {
416
416
  return out;
417
417
  }
418
418
  function loadProtoRoot() {
419
- const here = import_node_path43.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
420
- const protoRoot = import_node_path43.default.resolve(here, "..", "proto");
419
+ const here = import_node_path44.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
420
+ const protoRoot = import_node_path44.default.resolve(here, "..", "proto");
421
421
  const root = new import_protobufjs.default.Root();
422
- root.resolvePath = (_origin, target) => import_node_path43.default.resolve(protoRoot, target);
422
+ root.resolvePath = (_origin, target) => import_node_path44.default.resolve(protoRoot, target);
423
423
  root.loadSync(
424
424
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
425
425
  { keepCase: true }
@@ -655,12 +655,12 @@ function logSpanHandler(span) {
655
655
  `otel: ${span.service} ${span.name} parent=${parent} status=${status2}${db}`
656
656
  );
657
657
  }
658
- var import_node_path43, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
658
+ var import_node_path44, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
659
659
  var init_otel = __esm({
660
660
  "src/otel.ts"() {
661
661
  "use strict";
662
662
  init_cjs_shims();
663
- import_node_path43 = __toESM(require("path"), 1);
663
+ import_node_path44 = __toESM(require("path"), 1);
664
664
  import_node_url2 = require("url");
665
665
  import_fastify2 = __toESM(require("fastify"), 1);
666
666
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -1250,19 +1250,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1250
1250
  function longestIncomingWalk(graph, start, maxDepth) {
1251
1251
  let best = { path: [start], edges: [] };
1252
1252
  const visited = /* @__PURE__ */ new Set([start]);
1253
- function step(node, path47, edges) {
1254
- if (path47.length > best.path.length) {
1255
- best = { path: [...path47], edges: [...edges] };
1253
+ function step(node, path48, edges) {
1254
+ if (path48.length > best.path.length) {
1255
+ best = { path: [...path48], edges: [...edges] };
1256
1256
  }
1257
- if (path47.length - 1 >= maxDepth) return;
1257
+ if (path48.length - 1 >= maxDepth) return;
1258
1258
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1259
1259
  for (const [srcId, edge] of incoming) {
1260
1260
  if (visited.has(srcId)) continue;
1261
1261
  visited.add(srcId);
1262
- path47.push(srcId);
1262
+ path48.push(srcId);
1263
1263
  edges.push(edge);
1264
- step(srcId, path47, edges);
1265
- path47.pop();
1264
+ step(srcId, path48, edges);
1265
+ path48.pop();
1266
1266
  edges.pop();
1267
1267
  visited.delete(srcId);
1268
1268
  }
@@ -1456,26 +1456,26 @@ function dominantFailingCall(graph, serviceId5, visited) {
1456
1456
  return best;
1457
1457
  }
1458
1458
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1459
- const path47 = [originServiceId];
1459
+ const path48 = [originServiceId];
1460
1460
  const edges = [];
1461
1461
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1462
1462
  let current = originServiceId;
1463
1463
  for (let depth = 0; depth < maxDepth; depth++) {
1464
1464
  const hop = dominantFailingCall(graph, current, visited);
1465
1465
  if (!hop) break;
1466
- path47.push(hop.nextService);
1466
+ path48.push(hop.nextService);
1467
1467
  edges.push(hop.edge);
1468
1468
  visited.add(hop.nextService);
1469
1469
  current = hop.nextService;
1470
1470
  }
1471
1471
  if (edges.length === 0) return null;
1472
- return { path: path47, edges, culprit: current };
1472
+ return { path: path48, edges, culprit: current };
1473
1473
  }
1474
1474
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1475
1475
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1476
1476
  if (!chain) return null;
1477
1477
  const culprit = chain.culprit;
1478
- const path47 = [...chain.path];
1478
+ const path48 = [...chain.path];
1479
1479
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1480
1480
  const baseConfidence = confidenceFromMix(chain.edges);
1481
1481
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1483,14 +1483,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1483
1483
  if (loc) {
1484
1484
  let rootCauseNode = culprit;
1485
1485
  if (loc.fileNode) {
1486
- path47.push(loc.fileNode);
1486
+ path48.push(loc.fileNode);
1487
1487
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1488
1488
  rootCauseNode = loc.fileNode;
1489
1489
  }
1490
1490
  return import_types.RootCauseResultSchema.parse({
1491
1491
  rootCauseNode,
1492
1492
  rootCauseReason: loc.rootCauseReason,
1493
- traversalPath: path47,
1493
+ traversalPath: path48,
1494
1494
  edgeProvenances,
1495
1495
  confidence,
1496
1496
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1502,7 +1502,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1502
1502
  return import_types.RootCauseResultSchema.parse({
1503
1503
  rootCauseNode: culprit,
1504
1504
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1505
- traversalPath: path47,
1505
+ traversalPath: path48,
1506
1506
  edgeProvenances,
1507
1507
  confidence,
1508
1508
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2579,6 +2579,19 @@ function ensureServiceNode(graph, serviceName, env) {
2579
2579
  graph.addNode(id, node);
2580
2580
  return id;
2581
2581
  }
2582
+ function ensureInfraNode(graph, kind, name, provider) {
2583
+ const id = (0, import_types3.infraId)(kind, name);
2584
+ if (graph.hasNode(id)) return id;
2585
+ const node = {
2586
+ id,
2587
+ type: import_types3.NodeType.InfraNode,
2588
+ name,
2589
+ provider,
2590
+ kind
2591
+ };
2592
+ graph.addNode(id, node);
2593
+ return id;
2594
+ }
2582
2595
  function ensureDatabaseNode(graph, host, engine) {
2583
2596
  const id = (0, import_types3.databaseId)(host);
2584
2597
  if (graph.hasNode(id)) return id;
@@ -5337,10 +5350,10 @@ function fastifyRouteMethods(objNode) {
5337
5350
  }
5338
5351
  return [];
5339
5352
  }
5340
- function serverRoutesFromSource(source, parser, hasExpress, hasFastify) {
5353
+ function serverRoutesFromSource(source, parser, hasExpress, hasFastify, hasHono = false) {
5341
5354
  const tree = parseSource2(parser, source);
5342
5355
  const out = [];
5343
- const framework = hasExpress ? "express" : "fastify";
5356
+ const framework = hasExpress ? "express" : hasFastify ? "fastify" : hasHono ? "hono" : "unknown";
5344
5357
  walk(tree.rootNode, (node) => {
5345
5358
  if (node.type !== "call_expression") return;
5346
5359
  const fn = node.childForFieldName("function");
@@ -5490,8 +5503,9 @@ async function addRoutes(graph, services) {
5490
5503
  };
5491
5504
  const hasExpress = deps["express"] !== void 0;
5492
5505
  const hasFastify = deps["fastify"] !== void 0;
5506
+ const hasHono = deps["hono"] !== void 0;
5493
5507
  const hasNext = deps["next"] !== void 0;
5494
- if (!hasExpress && !hasFastify && !hasNext) continue;
5508
+ if (!hasExpress && !hasFastify && !hasHono && !hasNext) continue;
5495
5509
  const files = await loadSourceFiles(service.dir);
5496
5510
  for (const file of files) {
5497
5511
  if (isTestPath(file.path)) continue;
@@ -5501,8 +5515,8 @@ async function addRoutes(graph, services) {
5501
5515
  try {
5502
5516
  if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
5503
5517
  routes = nextRoutesFromFile(file.content, relFile, jsParser);
5504
- } else if (hasExpress || hasFastify) {
5505
- routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify);
5518
+ } else if (hasExpress || hasFastify || hasHono) {
5519
+ routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify, hasHono);
5506
5520
  } else {
5507
5521
  routes = [];
5508
5522
  }
@@ -6826,30 +6840,264 @@ async function addK8sResources(graph, scanPath) {
6826
6840
  return { nodesAdded, edgesAdded: 0 };
6827
6841
  }
6828
6842
 
6843
+ // src/extract/infra/cloudflare.ts
6844
+ init_cjs_shims();
6845
+ var import_node_fs19 = require("fs");
6846
+ var import_node_path35 = __toESM(require("path"), 1);
6847
+ var import_smol_toml2 = require("smol-toml");
6848
+ var import_types25 = require("@neat.is/types");
6849
+ var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
6850
+ async function readWranglerConfig(dir) {
6851
+ for (const filename of WRANGLER_FILENAMES) {
6852
+ const abs = import_node_path35.default.join(dir, filename);
6853
+ if (!await exists(abs)) continue;
6854
+ const raw = await import_node_fs19.promises.readFile(abs, "utf8");
6855
+ const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
6856
+ return { config, relFile: filename, raw };
6857
+ }
6858
+ return null;
6859
+ }
6860
+ function lineContaining(raw, needle) {
6861
+ if (!needle) return void 0;
6862
+ const idx = raw.indexOf(needle);
6863
+ if (idx === -1) return void 0;
6864
+ let line = 1;
6865
+ for (let i = 0; i < idx; i++) if (raw[i] === "\n") line++;
6866
+ return line;
6867
+ }
6868
+ function normalizeRoutes(config) {
6869
+ const out = [];
6870
+ const pushOne = (r) => {
6871
+ if (typeof r === "string") out.push(r);
6872
+ else if (r && typeof r === "object" && typeof r.pattern === "string") {
6873
+ out.push(r.pattern);
6874
+ }
6875
+ };
6876
+ if (Array.isArray(config.routes)) config.routes.forEach(pushOne);
6877
+ else if (config.route) pushOne(config.route);
6878
+ return out;
6879
+ }
6880
+ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, line) {
6881
+ let nodesAdded = 0;
6882
+ let edgesAdded = 0;
6883
+ const node = makeInfraNode(kind, name, "cloudflare");
6884
+ if (!graph.hasNode(node.id)) {
6885
+ graph.addNode(node.id, node);
6886
+ nodesAdded++;
6887
+ }
6888
+ if (node.id === anchorId) return { nodesAdded, edgesAdded };
6889
+ const edgeId = (0, import_types4.extractedEdgeId)(anchorId, node.id, edgeType);
6890
+ if (!graph.hasEdge(edgeId)) {
6891
+ const edge = {
6892
+ id: edgeId,
6893
+ source: anchorId,
6894
+ target: node.id,
6895
+ type: edgeType,
6896
+ provenance: import_types25.Provenance.EXTRACTED,
6897
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
6898
+ evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
6899
+ };
6900
+ graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
6901
+ edgesAdded++;
6902
+ }
6903
+ return { nodesAdded, edgesAdded };
6904
+ }
6905
+ async function addCloudflareWorkers(graph, services, scanPath) {
6906
+ let nodesAdded = 0;
6907
+ let edgesAdded = 0;
6908
+ const discovered = [];
6909
+ const workerIndex = /* @__PURE__ */ new Map();
6910
+ for (const service of services) {
6911
+ let read;
6912
+ try {
6913
+ read = await readWranglerConfig(service.dir);
6914
+ } catch (err) {
6915
+ recordExtractionError("infra cloudflare", import_node_path35.default.relative(scanPath, service.dir), err);
6916
+ continue;
6917
+ }
6918
+ if (!read || !read.config.name) continue;
6919
+ const evidenceFile = toPosix2(import_node_path35.default.relative(scanPath, import_node_path35.default.join(service.dir, read.relFile)));
6920
+ discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
6921
+ }
6922
+ for (const worker of discovered) {
6923
+ const { service, config } = worker;
6924
+ const serviceNode = graph.getNodeAttributes(service.node.id);
6925
+ if (serviceNode.platform !== "cloudflare") {
6926
+ const updated = { ...serviceNode, platform: "cloudflare" };
6927
+ graph.replaceNodeAttributes(service.node.id, updated);
6928
+ }
6929
+ let anchorId = service.node.id;
6930
+ if (config.main) {
6931
+ const entryRelPath = toPosix2(import_node_path35.default.normalize(config.main));
6932
+ const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
6933
+ graph,
6934
+ service.pkg.name,
6935
+ service.node.id,
6936
+ entryRelPath
6937
+ );
6938
+ nodesAdded += fn;
6939
+ edgesAdded += fe;
6940
+ const fileNode = graph.getNodeAttributes(fileNodeId);
6941
+ if (fileNode.platform !== "cloudflare" || fileNode.platformName !== config.name) {
6942
+ const updated = { ...fileNode, platform: "cloudflare", platformName: config.name };
6943
+ graph.replaceNodeAttributes(fileNodeId, updated);
6944
+ }
6945
+ anchorId = fileNodeId;
6946
+ }
6947
+ workerIndex.set(config.name, { anchorId });
6948
+ }
6949
+ for (const worker of discovered) {
6950
+ const { config, evidenceFile, raw } = worker;
6951
+ const anchorId = workerIndex.get(config.name).anchorId;
6952
+ const runtimeNode = makeInfraNode("workerd", "cloudflare", "cloudflare");
6953
+ if (!graph.hasNode(runtimeNode.id)) {
6954
+ graph.addNode(runtimeNode.id, runtimeNode);
6955
+ nodesAdded++;
6956
+ }
6957
+ if (runtimeNode.id !== anchorId) {
6958
+ const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types25.EdgeType.RUNS_ON);
6959
+ if (!graph.hasEdge(runsOnId)) {
6960
+ const edge = {
6961
+ id: runsOnId,
6962
+ source: anchorId,
6963
+ target: runtimeNode.id,
6964
+ type: import_types25.EdgeType.RUNS_ON,
6965
+ provenance: import_types25.Provenance.EXTRACTED,
6966
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
6967
+ evidence: {
6968
+ file: evidenceFile,
6969
+ ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
6970
+ }
6971
+ };
6972
+ graph.addEdgeWithKey(runsOnId, edge.source, edge.target, edge);
6973
+ edgesAdded++;
6974
+ }
6975
+ }
6976
+ for (const route of normalizeRoutes(config)) {
6977
+ const result = addResourceEdge(
6978
+ graph,
6979
+ anchorId,
6980
+ import_types25.EdgeType.CONNECTS_TO,
6981
+ "cloudflare-route",
6982
+ route,
6983
+ evidenceFile,
6984
+ lineContaining(raw, route)
6985
+ );
6986
+ nodesAdded += result.nodesAdded;
6987
+ edgesAdded += result.edgesAdded;
6988
+ }
6989
+ const bindingGroups = [
6990
+ { kind: "cloudflare-kv", entries: config.kv_namespaces ?? [], nameOf: (b) => b.binding },
6991
+ { kind: "cloudflare-d1", entries: config.d1_databases ?? [], nameOf: (b) => b.binding },
6992
+ { kind: "cloudflare-r2", entries: config.r2_buckets ?? [], nameOf: (b) => b.binding },
6993
+ { kind: "cloudflare-durable-object", entries: config.durable_objects?.bindings ?? [], nameOf: (b) => b.name },
6994
+ { kind: "cloudflare-queue", entries: config.queues?.producers ?? [], nameOf: (b) => b.queue },
6995
+ { kind: "cloudflare-queue", entries: config.queues?.consumers ?? [], nameOf: (b) => b.queue }
6996
+ ];
6997
+ for (const group of bindingGroups) {
6998
+ for (const entry of group.entries) {
6999
+ const name = group.nameOf(entry);
7000
+ if (!name) continue;
7001
+ const result = addResourceEdge(
7002
+ graph,
7003
+ anchorId,
7004
+ import_types25.EdgeType.DEPENDS_ON,
7005
+ group.kind,
7006
+ name,
7007
+ evidenceFile,
7008
+ lineContaining(raw, name)
7009
+ );
7010
+ nodesAdded += result.nodesAdded;
7011
+ edgesAdded += result.edgesAdded;
7012
+ }
7013
+ }
7014
+ for (const cron of config.triggers?.crons ?? []) {
7015
+ const result = addResourceEdge(
7016
+ graph,
7017
+ anchorId,
7018
+ import_types25.EdgeType.DEPENDS_ON,
7019
+ "cloudflare-cron",
7020
+ cron,
7021
+ evidenceFile,
7022
+ lineContaining(raw, cron)
7023
+ );
7024
+ nodesAdded += result.nodesAdded;
7025
+ edgesAdded += result.edgesAdded;
7026
+ }
7027
+ for (const varName of Object.keys(config.vars ?? {})) {
7028
+ const result = addResourceEdge(
7029
+ graph,
7030
+ anchorId,
7031
+ import_types25.EdgeType.DEPENDS_ON,
7032
+ "cloudflare-env-var",
7033
+ varName,
7034
+ evidenceFile,
7035
+ lineContaining(raw, varName)
7036
+ );
7037
+ nodesAdded += result.nodesAdded;
7038
+ edgesAdded += result.edgesAdded;
7039
+ }
7040
+ for (const svc of config.services ?? []) {
7041
+ if (!svc.service) continue;
7042
+ const target = workerIndex.get(svc.service);
7043
+ if (target && target.anchorId !== anchorId) {
7044
+ const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types25.EdgeType.CALLS);
7045
+ if (!graph.hasEdge(edgeId)) {
7046
+ const edge = {
7047
+ id: edgeId,
7048
+ source: anchorId,
7049
+ target: target.anchorId,
7050
+ type: import_types25.EdgeType.CALLS,
7051
+ provenance: import_types25.Provenance.EXTRACTED,
7052
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
7053
+ evidence: { file: evidenceFile, line: lineContaining(raw, svc.service) }
7054
+ };
7055
+ graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
7056
+ edgesAdded++;
7057
+ }
7058
+ continue;
7059
+ }
7060
+ const result = addResourceEdge(
7061
+ graph,
7062
+ anchorId,
7063
+ import_types25.EdgeType.DEPENDS_ON,
7064
+ "cloudflare-service-binding",
7065
+ svc.service,
7066
+ evidenceFile,
7067
+ lineContaining(raw, svc.service)
7068
+ );
7069
+ nodesAdded += result.nodesAdded;
7070
+ edgesAdded += result.edgesAdded;
7071
+ }
7072
+ }
7073
+ return { nodesAdded, edgesAdded };
7074
+ }
7075
+
6829
7076
  // src/extract/infra/index.ts
6830
7077
  async function addInfra(graph, scanPath, services) {
6831
7078
  const compose = await addComposeInfra(graph, scanPath, services);
6832
7079
  const dockerfile = await addDockerfileRuntimes(graph, services, scanPath);
6833
7080
  const terraform = await addTerraformResources(graph, scanPath);
6834
7081
  const k8s = await addK8sResources(graph, scanPath);
7082
+ const cloudflare = await addCloudflareWorkers(graph, services, scanPath);
6835
7083
  return {
6836
- nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded,
6837
- edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded
7084
+ nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded,
7085
+ edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded
6838
7086
  };
6839
7087
  }
6840
7088
 
6841
7089
  // src/extract/index.ts
6842
- var import_node_path36 = __toESM(require("path"), 1);
7090
+ var import_node_path37 = __toESM(require("path"), 1);
6843
7091
 
6844
7092
  // src/extract/retire.ts
6845
7093
  init_cjs_shims();
6846
- var import_node_fs19 = require("fs");
6847
- var import_node_path35 = __toESM(require("path"), 1);
6848
- var import_types25 = require("@neat.is/types");
7094
+ var import_node_fs20 = require("fs");
7095
+ var import_node_path36 = __toESM(require("path"), 1);
7096
+ var import_types26 = require("@neat.is/types");
6849
7097
  function dropOrphanedFileNodes(graph) {
6850
7098
  const orphans = [];
6851
7099
  graph.forEachNode((id, attrs) => {
6852
- if (attrs.type !== import_types25.NodeType.FileNode) return;
7100
+ if (attrs.type !== import_types26.NodeType.FileNode) return;
6853
7101
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
6854
7102
  orphans.push(id);
6855
7103
  }
@@ -6862,14 +7110,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
6862
7110
  const bases = [scanPath, ...serviceDirs];
6863
7111
  graph.forEachEdge((id, attrs) => {
6864
7112
  const edge = attrs;
6865
- if (edge.provenance !== import_types25.Provenance.EXTRACTED) return;
7113
+ if (edge.provenance !== import_types26.Provenance.EXTRACTED) return;
6866
7114
  const evidenceFile = edge.evidence?.file;
6867
7115
  if (!evidenceFile) return;
6868
- if (import_node_path35.default.isAbsolute(evidenceFile)) {
6869
- if (!(0, import_node_fs19.existsSync)(evidenceFile)) toDrop.push(id);
7116
+ if (import_node_path36.default.isAbsolute(evidenceFile)) {
7117
+ if (!(0, import_node_fs20.existsSync)(evidenceFile)) toDrop.push(id);
6870
7118
  return;
6871
7119
  }
6872
- const found = bases.some((base) => (0, import_node_fs19.existsSync)(import_node_path35.default.join(base, evidenceFile)));
7120
+ const found = bases.some((base) => (0, import_node_fs20.existsSync)(import_node_path36.default.join(base, evidenceFile)));
6873
7121
  if (!found) toDrop.push(id);
6874
7122
  });
6875
7123
  for (const id of toDrop) graph.dropEdge(id);
@@ -6911,7 +7159,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6911
7159
  }
6912
7160
  const droppedEntries = drainDroppedExtracted();
6913
7161
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
6914
- const rejectedPath = import_node_path36.default.join(import_node_path36.default.dirname(opts.errorsPath), "rejected.ndjson");
7162
+ const rejectedPath = import_node_path37.default.join(import_node_path37.default.dirname(opts.errorsPath), "rejected.ndjson");
6915
7163
  try {
6916
7164
  await writeRejectedExtracted(droppedEntries, rejectedPath);
6917
7165
  } catch (err) {
@@ -6945,9 +7193,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
6945
7193
 
6946
7194
  // src/persist.ts
6947
7195
  init_cjs_shims();
6948
- var import_node_fs20 = require("fs");
6949
- var import_node_path37 = __toESM(require("path"), 1);
6950
- var import_types26 = require("@neat.is/types");
7196
+ var import_node_fs21 = require("fs");
7197
+ var import_node_path38 = __toESM(require("path"), 1);
7198
+ var import_types27 = require("@neat.is/types");
6951
7199
  var SCHEMA_VERSION = 4;
6952
7200
  function migrateV1ToV2(payload) {
6953
7201
  const nodes = payload.graph.nodes;
@@ -6969,12 +7217,12 @@ function migrateV2ToV3(payload) {
6969
7217
  for (const edge of edges) {
6970
7218
  const attrs = edge.attributes;
6971
7219
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
6972
- attrs.provenance = import_types26.Provenance.OBSERVED;
7220
+ attrs.provenance = import_types27.Provenance.OBSERVED;
6973
7221
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
6974
7222
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
6975
7223
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
6976
7224
  if (type && source && target) {
6977
- const newId = (0, import_types26.observedEdgeId)(source, target, type);
7225
+ const newId = (0, import_types27.observedEdgeId)(source, target, type);
6978
7226
  attrs.id = newId;
6979
7227
  if (edge.key) edge.key = newId;
6980
7228
  }
@@ -6983,7 +7231,7 @@ function migrateV2ToV3(payload) {
6983
7231
  return { ...payload, schemaVersion: 3 };
6984
7232
  }
6985
7233
  async function ensureDir(filePath) {
6986
- await import_node_fs20.promises.mkdir(import_node_path37.default.dirname(filePath), { recursive: true });
7234
+ await import_node_fs21.promises.mkdir(import_node_path38.default.dirname(filePath), { recursive: true });
6987
7235
  }
6988
7236
  async function saveGraphToDisk(graph, outPath) {
6989
7237
  await ensureDir(outPath);
@@ -6993,13 +7241,13 @@ async function saveGraphToDisk(graph, outPath) {
6993
7241
  graph: graph.export()
6994
7242
  };
6995
7243
  const tmp = `${outPath}.tmp`;
6996
- await import_node_fs20.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
6997
- await import_node_fs20.promises.rename(tmp, outPath);
7244
+ await import_node_fs21.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
7245
+ await import_node_fs21.promises.rename(tmp, outPath);
6998
7246
  }
6999
7247
  async function loadGraphFromDisk(graph, outPath) {
7000
7248
  let raw;
7001
7249
  try {
7002
- raw = await import_node_fs20.promises.readFile(outPath, "utf8");
7250
+ raw = await import_node_fs21.promises.readFile(outPath, "utf8");
7003
7251
  } catch (err) {
7004
7252
  if (err.code === "ENOENT") return;
7005
7253
  throw err;
@@ -7066,19 +7314,19 @@ function startPersistLoop(graph, outPath, opts = {}) {
7066
7314
  init_cjs_shims();
7067
7315
  var import_fastify = __toESM(require("fastify"), 1);
7068
7316
  var import_cors = __toESM(require("@fastify/cors"), 1);
7069
- var import_types29 = require("@neat.is/types");
7317
+ var import_types30 = require("@neat.is/types");
7070
7318
 
7071
7319
  // src/extend/index.ts
7072
7320
  init_cjs_shims();
7073
- var import_node_fs22 = require("fs");
7074
- var import_node_path39 = __toESM(require("path"), 1);
7321
+ var import_node_fs23 = require("fs");
7322
+ var import_node_path40 = __toESM(require("path"), 1);
7075
7323
  var import_node_os2 = __toESM(require("os"), 1);
7076
7324
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
7077
7325
 
7078
7326
  // src/installers/package-manager.ts
7079
7327
  init_cjs_shims();
7080
- var import_node_fs21 = require("fs");
7081
- var import_node_path38 = __toESM(require("path"), 1);
7328
+ var import_node_fs22 = require("fs");
7329
+ var import_node_path39 = __toESM(require("path"), 1);
7082
7330
  var import_node_child_process = require("child_process");
7083
7331
  var LOCKFILE_PRIORITY = [
7084
7332
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -7093,29 +7341,29 @@ var LOCKFILE_PRIORITY = [
7093
7341
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
7094
7342
  async function exists2(p) {
7095
7343
  try {
7096
- await import_node_fs21.promises.access(p);
7344
+ await import_node_fs22.promises.access(p);
7097
7345
  return true;
7098
7346
  } catch {
7099
7347
  return false;
7100
7348
  }
7101
7349
  }
7102
7350
  async function detectPackageManager(serviceDir) {
7103
- let dir = import_node_path38.default.resolve(serviceDir);
7351
+ let dir = import_node_path39.default.resolve(serviceDir);
7104
7352
  const stops = /* @__PURE__ */ new Set();
7105
7353
  for (let i = 0; i < 64; i++) {
7106
7354
  if (stops.has(dir)) break;
7107
7355
  stops.add(dir);
7108
7356
  for (const candidate of LOCKFILE_PRIORITY) {
7109
- const lockPath = import_node_path38.default.join(dir, candidate.lockfile);
7357
+ const lockPath = import_node_path39.default.join(dir, candidate.lockfile);
7110
7358
  if (await exists2(lockPath)) {
7111
7359
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
7112
7360
  }
7113
7361
  }
7114
- const parent = import_node_path38.default.dirname(dir);
7362
+ const parent = import_node_path39.default.dirname(dir);
7115
7363
  if (parent === dir) break;
7116
7364
  dir = parent;
7117
7365
  }
7118
- return { pm: "npm", cwd: import_node_path38.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7366
+ return { pm: "npm", cwd: import_node_path39.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7119
7367
  }
7120
7368
  async function runPackageManagerInstall(cmd) {
7121
7369
  return new Promise((resolve) => {
@@ -7157,30 +7405,30 @@ ${err.message}`
7157
7405
  // src/extend/index.ts
7158
7406
  async function fileExists2(p) {
7159
7407
  try {
7160
- await import_node_fs22.promises.access(p);
7408
+ await import_node_fs23.promises.access(p);
7161
7409
  return true;
7162
7410
  } catch {
7163
7411
  return false;
7164
7412
  }
7165
7413
  }
7166
7414
  async function readPackageJson(scanPath) {
7167
- const pkgPath = import_node_path39.default.join(scanPath, "package.json");
7168
- const raw = await import_node_fs22.promises.readFile(pkgPath, "utf8");
7415
+ const pkgPath = import_node_path40.default.join(scanPath, "package.json");
7416
+ const raw = await import_node_fs23.promises.readFile(pkgPath, "utf8");
7169
7417
  return JSON.parse(raw);
7170
7418
  }
7171
7419
  async function findHookFiles(scanPath) {
7172
- const entries = await import_node_fs22.promises.readdir(scanPath);
7420
+ const entries = await import_node_fs23.promises.readdir(scanPath);
7173
7421
  return entries.filter(
7174
7422
  (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
7175
7423
  ).sort();
7176
7424
  }
7177
7425
  function extendLogPath() {
7178
- return process.env.NEAT_EXTEND_LOG ?? import_node_path39.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
7426
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path40.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
7179
7427
  }
7180
7428
  async function appendExtendLog(entry) {
7181
7429
  const logPath = extendLogPath();
7182
- await import_node_fs22.promises.mkdir(import_node_path39.default.dirname(logPath), { recursive: true });
7183
- await import_node_fs22.promises.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
7430
+ await import_node_fs23.promises.mkdir(import_node_path40.default.dirname(logPath), { recursive: true });
7431
+ await import_node_fs23.promises.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
7184
7432
  }
7185
7433
  function splicedContent(fileContent, snippet2) {
7186
7434
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -7238,7 +7486,7 @@ function lookupInstrumentation(library, installedVersion) {
7238
7486
  }
7239
7487
  async function describeProjectInstrumentation(ctx) {
7240
7488
  const hookFiles = await findHookFiles(ctx.scanPath);
7241
- const envNeat = await fileExists2(import_node_path39.default.join(ctx.scanPath, ".env.neat"));
7489
+ const envNeat = await fileExists2(import_node_path40.default.join(ctx.scanPath, ".env.neat"));
7242
7490
  const registryInstrPackages = new Set(
7243
7491
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
7244
7492
  );
@@ -7260,31 +7508,31 @@ async function applyExtension(ctx, args, options) {
7260
7508
  );
7261
7509
  }
7262
7510
  for (const file of hookFiles) {
7263
- const content = await import_node_fs22.promises.readFile(import_node_path39.default.join(ctx.scanPath, file), "utf8");
7511
+ const content = await import_node_fs23.promises.readFile(import_node_path40.default.join(ctx.scanPath, file), "utf8");
7264
7512
  if (content.includes(args.registration_snippet)) {
7265
7513
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
7266
7514
  }
7267
7515
  }
7268
7516
  const primaryFile = hookFiles[0];
7269
- const primaryPath = import_node_path39.default.join(ctx.scanPath, primaryFile);
7517
+ const primaryPath = import_node_path40.default.join(ctx.scanPath, primaryFile);
7270
7518
  const filesTouched = [];
7271
7519
  const depsAdded = [];
7272
- const pkgPath = import_node_path39.default.join(ctx.scanPath, "package.json");
7520
+ const pkgPath = import_node_path40.default.join(ctx.scanPath, "package.json");
7273
7521
  const pkg = await readPackageJson(ctx.scanPath);
7274
7522
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
7275
7523
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
7276
- await import_node_fs22.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7524
+ await import_node_fs23.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7277
7525
  filesTouched.push("package.json");
7278
7526
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
7279
7527
  }
7280
- const hookContent = await import_node_fs22.promises.readFile(primaryPath, "utf8");
7528
+ const hookContent = await import_node_fs23.promises.readFile(primaryPath, "utf8");
7281
7529
  const patched = splicedContent(hookContent, args.registration_snippet);
7282
7530
  if (!patched) {
7283
7531
  throw new Error(
7284
7532
  `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
7285
7533
  );
7286
7534
  }
7287
- await import_node_fs22.promises.writeFile(primaryPath, patched, "utf8");
7535
+ await import_node_fs23.promises.writeFile(primaryPath, patched, "utf8");
7288
7536
  filesTouched.push(primaryFile);
7289
7537
  const cmd = await detectPackageManager(ctx.scanPath);
7290
7538
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -7315,7 +7563,7 @@ async function dryRunExtension(ctx, args) {
7315
7563
  };
7316
7564
  }
7317
7565
  for (const file of hookFiles) {
7318
- const content = await import_node_fs22.promises.readFile(import_node_path39.default.join(ctx.scanPath, file), "utf8");
7566
+ const content = await import_node_fs23.promises.readFile(import_node_path40.default.join(ctx.scanPath, file), "utf8");
7319
7567
  if (content.includes(args.registration_snippet)) {
7320
7568
  return {
7321
7569
  library: args.library,
@@ -7337,7 +7585,7 @@ async function dryRunExtension(ctx, args) {
7337
7585
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
7338
7586
  filesTouched.push("package.json");
7339
7587
  }
7340
- const hookContent = await import_node_fs22.promises.readFile(import_node_path39.default.join(ctx.scanPath, primaryFile), "utf8");
7588
+ const hookContent = await import_node_fs23.promises.readFile(import_node_path40.default.join(ctx.scanPath, primaryFile), "utf8");
7341
7589
  const patched = splicedContent(hookContent, args.registration_snippet);
7342
7590
  if (patched) {
7343
7591
  filesTouched.push(primaryFile);
@@ -7352,28 +7600,28 @@ async function rollbackExtension(ctx, args) {
7352
7600
  if (!await fileExists2(logPath)) {
7353
7601
  return { undone: false, message: "no apply found for library" };
7354
7602
  }
7355
- const raw = await import_node_fs22.promises.readFile(logPath, "utf8");
7603
+ const raw = await import_node_fs23.promises.readFile(logPath, "utf8");
7356
7604
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
7357
7605
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
7358
7606
  if (!match) {
7359
7607
  return { undone: false, message: "no apply found for library" };
7360
7608
  }
7361
- const pkgPath = import_node_path39.default.join(ctx.scanPath, "package.json");
7609
+ const pkgPath = import_node_path40.default.join(ctx.scanPath, "package.json");
7362
7610
  if (await fileExists2(pkgPath)) {
7363
7611
  const pkg = await readPackageJson(ctx.scanPath);
7364
7612
  if (pkg.dependencies?.[match.instrumentation_package]) {
7365
7613
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
7366
7614
  pkg.dependencies = rest;
7367
- await import_node_fs22.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7615
+ await import_node_fs23.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7368
7616
  }
7369
7617
  }
7370
7618
  const hookFiles = await findHookFiles(ctx.scanPath);
7371
7619
  for (const file of hookFiles) {
7372
- const filePath = import_node_path39.default.join(ctx.scanPath, file);
7373
- const content = await import_node_fs22.promises.readFile(filePath, "utf8");
7620
+ const filePath = import_node_path40.default.join(ctx.scanPath, file);
7621
+ const content = await import_node_fs23.promises.readFile(filePath, "utf8");
7374
7622
  if (content.includes(match.registration_snippet)) {
7375
7623
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
7376
- await import_node_fs22.promises.writeFile(filePath, filtered, "utf8");
7624
+ await import_node_fs23.promises.writeFile(filePath, filtered, "utf8");
7377
7625
  break;
7378
7626
  }
7379
7627
  }
@@ -7385,7 +7633,7 @@ async function rollbackExtension(ctx, args) {
7385
7633
 
7386
7634
  // src/divergences.ts
7387
7635
  init_cjs_shims();
7388
- var import_types27 = require("@neat.is/types");
7636
+ var import_types28 = require("@neat.is/types");
7389
7637
  function bucketKey(source, target, type) {
7390
7638
  return `${type}|${source}|${target}`;
7391
7639
  }
@@ -7393,22 +7641,22 @@ function bucketEdges(graph) {
7393
7641
  const buckets2 = /* @__PURE__ */ new Map();
7394
7642
  graph.forEachEdge((id, attrs) => {
7395
7643
  const e = attrs;
7396
- const parsed = (0, import_types27.parseEdgeId)(id);
7644
+ const parsed = (0, import_types28.parseEdgeId)(id);
7397
7645
  const provenance = parsed?.provenance ?? e.provenance;
7398
7646
  const key = bucketKey(e.source, e.target, e.type);
7399
7647
  const cur = buckets2.get(key) ?? { source: e.source, target: e.target, type: e.type };
7400
7648
  switch (provenance) {
7401
- case import_types27.Provenance.EXTRACTED:
7649
+ case import_types28.Provenance.EXTRACTED:
7402
7650
  cur.extracted = e;
7403
7651
  break;
7404
- case import_types27.Provenance.OBSERVED:
7652
+ case import_types28.Provenance.OBSERVED:
7405
7653
  cur.observed = e;
7406
7654
  break;
7407
- case import_types27.Provenance.INFERRED:
7655
+ case import_types28.Provenance.INFERRED:
7408
7656
  cur.inferred = e;
7409
7657
  break;
7410
7658
  default:
7411
- if (e.provenance === import_types27.Provenance.STALE) cur.stale = e;
7659
+ if (e.provenance === import_types28.Provenance.STALE) cur.stale = e;
7412
7660
  }
7413
7661
  buckets2.set(key, cur);
7414
7662
  });
@@ -7417,12 +7665,12 @@ function bucketEdges(graph) {
7417
7665
  function nodeIsFrontier(graph, nodeId) {
7418
7666
  if (!graph.hasNode(nodeId)) return false;
7419
7667
  const attrs = graph.getNodeAttributes(nodeId);
7420
- return attrs.type === import_types27.NodeType.FrontierNode;
7668
+ return attrs.type === import_types28.NodeType.FrontierNode;
7421
7669
  }
7422
7670
  function nodeIsWebsocketChannel(graph, nodeId) {
7423
7671
  if (!graph.hasNode(nodeId)) return false;
7424
7672
  const attrs = graph.getNodeAttributes(nodeId);
7425
- return attrs.type === import_types27.NodeType.WebSocketChannelNode;
7673
+ return attrs.type === import_types28.NodeType.WebSocketChannelNode;
7426
7674
  }
7427
7675
  function clampConfidence(n) {
7428
7676
  if (!Number.isFinite(n)) return 0;
@@ -7442,14 +7690,14 @@ function gradedConfidence(edge) {
7442
7690
  return clampConfidence(confidenceForEdge(edge));
7443
7691
  }
7444
7692
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
7445
- import_types27.EdgeType.CALLS,
7446
- import_types27.EdgeType.CONNECTS_TO,
7447
- import_types27.EdgeType.PUBLISHES_TO,
7448
- import_types27.EdgeType.CONSUMES_FROM
7693
+ import_types28.EdgeType.CALLS,
7694
+ import_types28.EdgeType.CONNECTS_TO,
7695
+ import_types28.EdgeType.PUBLISHES_TO,
7696
+ import_types28.EdgeType.CONSUMES_FROM
7449
7697
  ]);
7450
7698
  function detectMissingDivergences(graph, bucket) {
7451
7699
  const out = [];
7452
- if (bucket.type === import_types27.EdgeType.CONTAINS) return out;
7700
+ if (bucket.type === import_types28.EdgeType.CONTAINS) return out;
7453
7701
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
7454
7702
  if (!nodeIsFrontier(graph, bucket.target)) {
7455
7703
  out.push({
@@ -7490,7 +7738,7 @@ function declaredHostFor(svc) {
7490
7738
  function hasExtractedConfiguredBy(graph, svcId) {
7491
7739
  for (const edgeId of graph.outboundEdges(svcId)) {
7492
7740
  const e = graph.getEdgeAttributes(edgeId);
7493
- if (e.type === import_types27.EdgeType.CONFIGURED_BY && e.provenance === import_types27.Provenance.EXTRACTED) {
7741
+ if (e.type === import_types28.EdgeType.CONFIGURED_BY && e.provenance === import_types28.Provenance.EXTRACTED) {
7494
7742
  return true;
7495
7743
  }
7496
7744
  }
@@ -7503,10 +7751,10 @@ function detectHostMismatch(graph, svcId, svc) {
7503
7751
  const out = [];
7504
7752
  for (const edgeId of graph.outboundEdges(svcId)) {
7505
7753
  const edge = graph.getEdgeAttributes(edgeId);
7506
- if (edge.type !== import_types27.EdgeType.CONNECTS_TO) continue;
7507
- if (edge.provenance !== import_types27.Provenance.OBSERVED) continue;
7754
+ if (edge.type !== import_types28.EdgeType.CONNECTS_TO) continue;
7755
+ if (edge.provenance !== import_types28.Provenance.OBSERVED) continue;
7508
7756
  const target = graph.getNodeAttributes(edge.target);
7509
- if (target.type !== import_types27.NodeType.DatabaseNode) continue;
7757
+ if (target.type !== import_types28.NodeType.DatabaseNode) continue;
7510
7758
  const observedHost = target.host?.trim();
7511
7759
  if (!observedHost) continue;
7512
7760
  if (observedHost === declaredHost) continue;
@@ -7528,10 +7776,10 @@ function detectCompatDivergences(graph, svcId, svc) {
7528
7776
  const deps = svc.dependencies ?? {};
7529
7777
  for (const edgeId of graph.outboundEdges(svcId)) {
7530
7778
  const edge = graph.getEdgeAttributes(edgeId);
7531
- if (edge.type !== import_types27.EdgeType.CONNECTS_TO) continue;
7532
- if (edge.provenance !== import_types27.Provenance.OBSERVED) continue;
7779
+ if (edge.type !== import_types28.EdgeType.CONNECTS_TO) continue;
7780
+ if (edge.provenance !== import_types28.Provenance.OBSERVED) continue;
7533
7781
  const target = graph.getNodeAttributes(edge.target);
7534
- if (target.type !== import_types27.NodeType.DatabaseNode) continue;
7782
+ if (target.type !== import_types28.NodeType.DatabaseNode) continue;
7535
7783
  for (const pair of compatPairs()) {
7536
7784
  if (pair.engine !== target.engine) continue;
7537
7785
  const declared = deps[pair.driver];
@@ -7590,7 +7838,7 @@ function suppressHostMismatchHalves(all) {
7590
7838
  for (const d of all) {
7591
7839
  if (d.type !== "host-mismatch") continue;
7592
7840
  observedHalf.add(`${d.source}->${d.target}`);
7593
- declaredHalf.add((0, import_types27.databaseId)(d.extractedHost));
7841
+ declaredHalf.add((0, import_types28.databaseId)(d.extractedHost));
7594
7842
  }
7595
7843
  if (observedHalf.size === 0) return all;
7596
7844
  return all.filter((d) => {
@@ -7609,7 +7857,7 @@ function computeDivergences(graph, opts = {}) {
7609
7857
  }
7610
7858
  graph.forEachNode((nodeId, attrs) => {
7611
7859
  const n = attrs;
7612
- if (n.type !== import_types27.NodeType.ServiceNode) return;
7860
+ if (n.type !== import_types28.NodeType.ServiceNode) return;
7613
7861
  const svc = n;
7614
7862
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
7615
7863
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -7643,7 +7891,7 @@ function computeDivergences(graph, opts = {}) {
7643
7891
  if (a.source !== b.source) return a.source.localeCompare(b.source);
7644
7892
  return a.target.localeCompare(b.target);
7645
7893
  });
7646
- return import_types27.DivergenceResultSchema.parse({
7894
+ return import_types28.DivergenceResultSchema.parse({
7647
7895
  divergences: filtered,
7648
7896
  totalAffected: filtered.length,
7649
7897
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -7697,7 +7945,7 @@ function queryLogEntries(opts) {
7697
7945
 
7698
7946
  // src/diff.ts
7699
7947
  init_cjs_shims();
7700
- var import_node_fs23 = require("fs");
7948
+ var import_node_fs24 = require("fs");
7701
7949
  async function loadSnapshotForDiff(target) {
7702
7950
  if (/^https?:\/\//i.test(target)) {
7703
7951
  const res = await fetch(target);
@@ -7706,7 +7954,7 @@ async function loadSnapshotForDiff(target) {
7706
7954
  }
7707
7955
  return await res.json();
7708
7956
  }
7709
- const raw = await import_node_fs23.promises.readFile(target, "utf8");
7957
+ const raw = await import_node_fs24.promises.readFile(target, "utf8");
7710
7958
  return JSON.parse(raw);
7711
7959
  }
7712
7960
  function indexEntries(entries) {
@@ -7774,23 +8022,23 @@ function canonicalJson(value) {
7774
8022
 
7775
8023
  // src/projects.ts
7776
8024
  init_cjs_shims();
7777
- var import_node_path40 = __toESM(require("path"), 1);
8025
+ var import_node_path41 = __toESM(require("path"), 1);
7778
8026
  function pathsForProject(project, baseDir) {
7779
8027
  if (project === DEFAULT_PROJECT) {
7780
8028
  return {
7781
- snapshotPath: import_node_path40.default.join(baseDir, "graph.json"),
7782
- errorsPath: import_node_path40.default.join(baseDir, "errors.ndjson"),
7783
- staleEventsPath: import_node_path40.default.join(baseDir, "stale-events.ndjson"),
7784
- embeddingsCachePath: import_node_path40.default.join(baseDir, "embeddings.json"),
7785
- policyViolationsPath: import_node_path40.default.join(baseDir, "policy-violations.ndjson")
8029
+ snapshotPath: import_node_path41.default.join(baseDir, "graph.json"),
8030
+ errorsPath: import_node_path41.default.join(baseDir, "errors.ndjson"),
8031
+ staleEventsPath: import_node_path41.default.join(baseDir, "stale-events.ndjson"),
8032
+ embeddingsCachePath: import_node_path41.default.join(baseDir, "embeddings.json"),
8033
+ policyViolationsPath: import_node_path41.default.join(baseDir, "policy-violations.ndjson")
7786
8034
  };
7787
8035
  }
7788
8036
  return {
7789
- snapshotPath: import_node_path40.default.join(baseDir, `${project}.json`),
7790
- errorsPath: import_node_path40.default.join(baseDir, `errors.${project}.ndjson`),
7791
- staleEventsPath: import_node_path40.default.join(baseDir, `stale-events.${project}.ndjson`),
7792
- embeddingsCachePath: import_node_path40.default.join(baseDir, `embeddings.${project}.json`),
7793
- policyViolationsPath: import_node_path40.default.join(baseDir, `policy-violations.${project}.ndjson`)
8037
+ snapshotPath: import_node_path41.default.join(baseDir, `${project}.json`),
8038
+ errorsPath: import_node_path41.default.join(baseDir, `errors.${project}.ndjson`),
8039
+ staleEventsPath: import_node_path41.default.join(baseDir, `stale-events.${project}.ndjson`),
8040
+ embeddingsCachePath: import_node_path41.default.join(baseDir, `embeddings.${project}.json`),
8041
+ policyViolationsPath: import_node_path41.default.join(baseDir, `policy-violations.${project}.ndjson`)
7794
8042
  };
7795
8043
  }
7796
8044
  var Projects = class {
@@ -7826,25 +8074,25 @@ var Projects = class {
7826
8074
 
7827
8075
  // src/registry.ts
7828
8076
  init_cjs_shims();
7829
- var import_node_fs24 = require("fs");
8077
+ var import_node_fs25 = require("fs");
7830
8078
  var import_node_os3 = __toESM(require("os"), 1);
7831
- var import_node_path41 = __toESM(require("path"), 1);
7832
- var import_types28 = require("@neat.is/types");
8079
+ var import_node_path42 = __toESM(require("path"), 1);
8080
+ var import_types29 = require("@neat.is/types");
7833
8081
  var LOCK_TIMEOUT_MS = 5e3;
7834
8082
  var LOCK_RETRY_MS = 50;
7835
8083
  function neatHome() {
7836
8084
  const override = process.env.NEAT_HOME;
7837
- if (override && override.length > 0) return import_node_path41.default.resolve(override);
7838
- return import_node_path41.default.join(import_node_os3.default.homedir(), ".neat");
8085
+ if (override && override.length > 0) return import_node_path42.default.resolve(override);
8086
+ return import_node_path42.default.join(import_node_os3.default.homedir(), ".neat");
7839
8087
  }
7840
8088
  function registryPath() {
7841
- return import_node_path41.default.join(neatHome(), "projects.json");
8089
+ return import_node_path42.default.join(neatHome(), "projects.json");
7842
8090
  }
7843
8091
  function registryLockPath() {
7844
- return import_node_path41.default.join(neatHome(), "projects.json.lock");
8092
+ return import_node_path42.default.join(neatHome(), "projects.json.lock");
7845
8093
  }
7846
8094
  function daemonPidPath() {
7847
- return import_node_path41.default.join(neatHome(), "neatd.pid");
8095
+ return import_node_path42.default.join(neatHome(), "neatd.pid");
7848
8096
  }
7849
8097
  function isPidAliveDefault(pid) {
7850
8098
  try {
@@ -7856,7 +8104,7 @@ function isPidAliveDefault(pid) {
7856
8104
  }
7857
8105
  async function readPidFile(file) {
7858
8106
  try {
7859
- const raw = await import_node_fs24.promises.readFile(file, "utf8");
8107
+ const raw = await import_node_fs25.promises.readFile(file, "utf8");
7860
8108
  const pid = Number.parseInt(raw.trim(), 10);
7861
8109
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
7862
8110
  } catch {
@@ -7904,32 +8152,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
7904
8152
  }
7905
8153
  }
7906
8154
  async function normalizeProjectPath(input) {
7907
- const resolved = import_node_path41.default.resolve(input);
8155
+ const resolved = import_node_path42.default.resolve(input);
7908
8156
  try {
7909
- return await import_node_fs24.promises.realpath(resolved);
8157
+ return await import_node_fs25.promises.realpath(resolved);
7910
8158
  } catch {
7911
8159
  return resolved;
7912
8160
  }
7913
8161
  }
7914
8162
  async function writeAtomically(target, contents) {
7915
- await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(target), { recursive: true });
8163
+ await import_node_fs25.promises.mkdir(import_node_path42.default.dirname(target), { recursive: true });
7916
8164
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
7917
- const fd = await import_node_fs24.promises.open(tmp, "w");
8165
+ const fd = await import_node_fs25.promises.open(tmp, "w");
7918
8166
  try {
7919
8167
  await fd.writeFile(contents, "utf8");
7920
8168
  await fd.sync();
7921
8169
  } finally {
7922
8170
  await fd.close();
7923
8171
  }
7924
- await import_node_fs24.promises.rename(tmp, target);
8172
+ await import_node_fs25.promises.rename(tmp, target);
7925
8173
  }
7926
8174
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
7927
8175
  const deadline = Date.now() + timeoutMs;
7928
- await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(lockPath), { recursive: true });
8176
+ await import_node_fs25.promises.mkdir(import_node_path42.default.dirname(lockPath), { recursive: true });
7929
8177
  let probedHolder = false;
7930
8178
  while (true) {
7931
8179
  try {
7932
- const fd = await import_node_fs24.promises.open(lockPath, "wx");
8180
+ const fd = await import_node_fs25.promises.open(lockPath, "wx");
7933
8181
  try {
7934
8182
  await fd.writeFile(`${process.pid}
7935
8183
  `, "utf8");
@@ -7954,7 +8202,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
7954
8202
  }
7955
8203
  }
7956
8204
  async function releaseLock(lockPath) {
7957
- await import_node_fs24.promises.unlink(lockPath).catch(() => {
8205
+ await import_node_fs25.promises.unlink(lockPath).catch(() => {
7958
8206
  });
7959
8207
  }
7960
8208
  async function withLock(fn) {
@@ -7970,7 +8218,7 @@ async function readRegistry() {
7970
8218
  const file = registryPath();
7971
8219
  let raw;
7972
8220
  try {
7973
- raw = await import_node_fs24.promises.readFile(file, "utf8");
8221
+ raw = await import_node_fs25.promises.readFile(file, "utf8");
7974
8222
  } catch (err) {
7975
8223
  if (err.code === "ENOENT") {
7976
8224
  return { version: 1, projects: [] };
@@ -7978,10 +8226,10 @@ async function readRegistry() {
7978
8226
  throw err;
7979
8227
  }
7980
8228
  const parsed = JSON.parse(raw);
7981
- return import_types28.RegistryFileSchema.parse(parsed);
8229
+ return import_types29.RegistryFileSchema.parse(parsed);
7982
8230
  }
7983
8231
  async function writeRegistry(reg) {
7984
- const validated = import_types28.RegistryFileSchema.parse(reg);
8232
+ const validated = import_types29.RegistryFileSchema.parse(reg);
7985
8233
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
7986
8234
  }
7987
8235
  var ProjectNameCollisionError = class extends Error {
@@ -8075,7 +8323,7 @@ function pruneTtlMs() {
8075
8323
  }
8076
8324
  async function statPathStatus(p) {
8077
8325
  try {
8078
- const stat = await import_node_fs24.promises.stat(p);
8326
+ const stat = await import_node_fs25.promises.stat(p);
8079
8327
  return stat.isDirectory() ? "present" : "unknown";
8080
8328
  } catch (err) {
8081
8329
  return err.code === "ENOENT" ? "gone" : "unknown";
@@ -8322,11 +8570,11 @@ function registerRoutes(scope, ctx) {
8322
8570
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
8323
8571
  const parsed = [];
8324
8572
  for (const c of candidates) {
8325
- const r = import_types29.DivergenceTypeSchema.safeParse(c);
8573
+ const r = import_types30.DivergenceTypeSchema.safeParse(c);
8326
8574
  if (!r.success) {
8327
8575
  return reply.code(400).send({
8328
8576
  error: `unknown divergence type "${c}"`,
8329
- allowed: import_types29.DivergenceTypeSchema.options
8577
+ allowed: import_types30.DivergenceTypeSchema.options
8330
8578
  });
8331
8579
  }
8332
8580
  parsed.push(r.data);
@@ -8577,7 +8825,7 @@ function registerRoutes(scope, ctx) {
8577
8825
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
8578
8826
  let violations = await log.readAll();
8579
8827
  if (req.query.severity) {
8580
- const sev = import_types29.PolicySeveritySchema.safeParse(req.query.severity);
8828
+ const sev = import_types30.PolicySeveritySchema.safeParse(req.query.severity);
8581
8829
  if (!sev.success) {
8582
8830
  return reply.code(400).send({
8583
8831
  error: "invalid severity",
@@ -8616,7 +8864,7 @@ function registerRoutes(scope, ctx) {
8616
8864
  scope.post("/policies/check", async (req, reply) => {
8617
8865
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
8618
8866
  if (!proj) return;
8619
- const parsed = import_types29.PoliciesCheckBodySchema.safeParse(req.body ?? {});
8867
+ const parsed = import_types30.PoliciesCheckBodySchema.safeParse(req.body ?? {});
8620
8868
  if (!parsed.success) {
8621
8869
  return reply.code(400).send({
8622
8870
  error: "invalid /policies/check body",
@@ -8878,8 +9126,8 @@ init_otel_grpc();
8878
9126
 
8879
9127
  // src/daemon.ts
8880
9128
  init_cjs_shims();
8881
- var import_node_fs27 = require("fs");
8882
- var import_node_path46 = __toESM(require("path"), 1);
9129
+ var import_node_fs28 = require("fs");
9130
+ var import_node_path47 = __toESM(require("path"), 1);
8883
9131
  var import_node_module = require("module");
8884
9132
  init_otel();
8885
9133
 
@@ -8897,6 +9145,10 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
8897
9145
  unresolved++;
8898
9146
  continue;
8899
9147
  }
9148
+ if (resolved.ensureInfraNode) {
9149
+ const { kind, name, provider } = resolved.ensureInfraNode;
9150
+ ensureInfraNode(graph, kind, name, provider);
9151
+ }
8900
9152
  const serviceNodeId = ensureServiceNode(graph, resolved.serviceName, NO_ENV);
8901
9153
  const callSite = signal.callSite ? { relPath: signal.callSite.file, line: signal.callSite.line } : void 0;
8902
9154
  const sourceId = callSite ? ensureObservedFileNode(graph, resolved.serviceName, serviceNodeId, callSite) : serviceNodeId;
@@ -9298,10 +9550,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
9298
9550
  // src/connectors/supabase/map.ts
9299
9551
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
9300
9552
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
9301
- function targetFromRestPath(path47) {
9302
- const rpcMatch = REST_RPC_PATH_RE.exec(path47);
9553
+ function targetFromRestPath(path48) {
9554
+ const rpcMatch = REST_RPC_PATH_RE.exec(path48);
9303
9555
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
9304
- const tableMatch = REST_TABLE_PATH_RE.exec(path47);
9556
+ const tableMatch = REST_TABLE_PATH_RE.exec(path48);
9305
9557
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
9306
9558
  return null;
9307
9559
  }
@@ -9404,19 +9656,19 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
9404
9656
 
9405
9657
  // src/connectors/supabase/resolve.ts
9406
9658
  init_cjs_shims();
9407
- var import_types31 = require("@neat.is/types");
9659
+ var import_types32 = require("@neat.is/types");
9408
9660
  function createSupabaseResolveTarget(graph, config) {
9409
9661
  return (signal, _ctx) => {
9410
9662
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
9411
9663
  return null;
9412
9664
  }
9413
- const subResourceId = (0, import_types31.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
9665
+ const subResourceId = (0, import_types32.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
9414
9666
  if (graph.hasNode(subResourceId)) {
9415
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types31.EdgeType.CALLS };
9667
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types32.EdgeType.CALLS };
9416
9668
  }
9417
- const projectLevelId = (0, import_types31.infraId)("supabase", config.nodeRef);
9669
+ const projectLevelId = (0, import_types32.infraId)("supabase", config.nodeRef);
9418
9670
  if (graph.hasNode(projectLevelId)) {
9419
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types31.EdgeType.CALLS };
9671
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types32.EdgeType.CALLS };
9420
9672
  }
9421
9673
  return null;
9422
9674
  };
@@ -9474,7 +9726,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
9474
9726
 
9475
9727
  // src/connectors/railway/index.ts
9476
9728
  init_cjs_shims();
9477
- var import_types35 = require("@neat.is/types");
9729
+ var import_types36 = require("@neat.is/types");
9478
9730
 
9479
9731
  // src/connectors/railway/client.ts
9480
9732
  init_cjs_shims();
@@ -9619,7 +9871,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
9619
9871
  const out = [];
9620
9872
  graph.forEachNode((_id, attrs) => {
9621
9873
  const node = attrs;
9622
- if (node.type !== import_types35.NodeType.RouteNode) return;
9874
+ if (node.type !== import_types36.NodeType.RouteNode) return;
9623
9875
  const route = attrs;
9624
9876
  if (route.service !== serviceName) return;
9625
9877
  out.push({
@@ -9715,12 +9967,12 @@ function createRailwayResolveTarget(config) {
9715
9967
  const serviceName = config.serviceNameById[config.serviceId];
9716
9968
  if (!serviceName) return null;
9717
9969
  if (signal.targetKind === ROUTE_TARGET_KIND) {
9718
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types35.EdgeType.CALLS };
9970
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types36.EdgeType.CALLS };
9719
9971
  }
9720
9972
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
9721
9973
  const peerName = config.serviceNameById[signal.targetName];
9722
9974
  if (!peerName) return null;
9723
- return { targetNodeId: (0, import_types35.serviceId)(peerName), serviceName, edgeType: import_types35.EdgeType.CONNECTS_TO };
9975
+ return { targetNodeId: (0, import_types36.serviceId)(peerName), serviceName, edgeType: import_types36.EdgeType.CONNECTS_TO };
9724
9976
  }
9725
9977
  return null;
9726
9978
  };
@@ -9835,9 +10087,9 @@ function parseFirebaseTargetName(targetName) {
9835
10087
  const secondSep = rest.indexOf(FIELD_SEP);
9836
10088
  if (secondSep === -1) return null;
9837
10089
  const method = rest.slice(0, secondSep);
9838
- const path47 = rest.slice(secondSep + 1);
9839
- if (!resourceName || !method || !path47) return null;
9840
- return { resourceName, method, path: path47 };
10090
+ const path48 = rest.slice(secondSep + 1);
10091
+ if (!resourceName || !method || !path48) return null;
10092
+ return { resourceName, method, path: path48 };
9841
10093
  }
9842
10094
  function resourceNameFor(type, labels) {
9843
10095
  if (!labels) return null;
@@ -9874,14 +10126,14 @@ function mapLogEntryToSignal(entry) {
9874
10126
  if (!req) return null;
9875
10127
  if (!req.requestMethod) return null;
9876
10128
  const method = req.requestMethod.toUpperCase();
9877
- const path47 = pathFromRequestUrl(req.requestUrl);
9878
- if (path47 === null) return null;
10129
+ const path48 = pathFromRequestUrl(req.requestUrl);
10130
+ if (path48 === null) return null;
9879
10131
  const timestamp = entry.timestamp;
9880
10132
  if (!timestamp) return null;
9881
10133
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
9882
10134
  return {
9883
10135
  targetKind: resourceType,
9884
- targetName: packFirebaseTargetName({ resourceName, method, path: path47 }),
10136
+ targetName: packFirebaseTargetName({ resourceName, method, path: path48 }),
9885
10137
  callCount: 1,
9886
10138
  errorCount: isError ? 1 : 0,
9887
10139
  lastObservedIso: timestamp
@@ -9898,7 +10150,7 @@ function mapLogEntriesToSignals(entries) {
9898
10150
 
9899
10151
  // src/connectors/firebase/resolve.ts
9900
10152
  init_cjs_shims();
9901
- var import_types36 = require("@neat.is/types");
10153
+ var import_types37 = require("@neat.is/types");
9902
10154
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
9903
10155
  switch (resourceType) {
9904
10156
  case "cloud_function":
@@ -9913,7 +10165,7 @@ function routeEntriesFor(graph, serviceName) {
9913
10165
  const entries = [];
9914
10166
  graph.forEachNode((_id, attrs) => {
9915
10167
  const node = attrs;
9916
- if (node.type !== import_types36.NodeType.RouteNode) return;
10168
+ if (node.type !== import_types37.NodeType.RouteNode) return;
9917
10169
  const route = attrs;
9918
10170
  if (route.service !== serviceName) return;
9919
10171
  entries.push({
@@ -9945,7 +10197,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
9945
10197
  return {
9946
10198
  targetNodeId: match.routeNodeId,
9947
10199
  serviceName,
9948
- edgeType: import_types36.EdgeType.CALLS
10200
+ edgeType: import_types37.EdgeType.CALLS
9949
10201
  };
9950
10202
  };
9951
10203
  }
@@ -9972,7 +10224,7 @@ init_cjs_shims();
9972
10224
 
9973
10225
  // src/connectors/cloudflare/connector.ts
9974
10226
  init_cjs_shims();
9975
- var import_types38 = require("@neat.is/types");
10227
+ var import_types39 = require("@neat.is/types");
9976
10228
 
9977
10229
  // src/connectors/cloudflare/client.ts
9978
10230
  init_cjs_shims();
@@ -10022,7 +10274,14 @@ async function queryWorkerInvocations(ctx, config, window, fetchImpl = fetch) {
10022
10274
  const message = payload.errors?.map((e) => e.message).join("; ") || "unknown error";
10023
10275
  throw new Error(`cloudflare connector: telemetry query returned an error (${message})`);
10024
10276
  }
10025
- return payload.result?.events?.events ?? [];
10277
+ const events = payload.result?.events?.events;
10278
+ if (events === void 0) {
10279
+ console.warn(
10280
+ "[neat connector] cloudflare: telemetry query returned success:true but no result.events.events array \u2014 the response shape may have changed; treating as zero events this tick"
10281
+ );
10282
+ return [];
10283
+ }
10284
+ return events;
10026
10285
  }
10027
10286
 
10028
10287
  // src/connectors/cloudflare/map.ts
@@ -10053,6 +10312,13 @@ function parseHttpMethodFromTrigger(trigger) {
10053
10312
  const method = token.toUpperCase();
10054
10313
  return HTTP_METHODS.has(method) ? method : null;
10055
10314
  }
10315
+ function parsePathFromTrigger(trigger) {
10316
+ const trimmed = trigger.trim();
10317
+ const spaceIdx = trimmed.indexOf(" ");
10318
+ if (spaceIdx === -1) return void 0;
10319
+ const rest = trimmed.slice(spaceIdx + 1).trim();
10320
+ return rest.length > 0 ? rest : void 0;
10321
+ }
10056
10322
  var ERROR_STATUS_THRESHOLD3 = 500;
10057
10323
  function mapEventToSignal(event) {
10058
10324
  const metadata = event.$metadata;
@@ -10065,6 +10331,7 @@ function mapEventToSignal(event) {
10065
10331
  if (typeof timestampMs !== "number" || !Number.isFinite(timestampMs)) return null;
10066
10332
  const statusCode = metadata?.statusCode;
10067
10333
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
10334
+ const path48 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
10068
10335
  return {
10069
10336
  targetKind: CLOUDFLARE_TARGET_KIND,
10070
10337
  targetName: scriptName,
@@ -10072,6 +10339,7 @@ function mapEventToSignal(event) {
10072
10339
  errorCount: isError ? 1 : 0,
10073
10340
  lastObservedIso: new Date(timestampMs).toISOString(),
10074
10341
  method,
10342
+ ...path48 ? { path: path48 } : {},
10075
10343
  ...typeof statusCode === "number" ? { statusCode } : {},
10076
10344
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
10077
10345
  };
@@ -10106,15 +10374,63 @@ var CloudflareConnector = class {
10106
10374
  return signals;
10107
10375
  }
10108
10376
  };
10109
- function createCloudflareResolveTarget(config) {
10377
+ function findTaggedWorkerFileNode(graph, workerName) {
10378
+ let found = null;
10379
+ graph.forEachNode((id, attrs) => {
10380
+ if (found) return;
10381
+ const a = attrs;
10382
+ if (a.type === import_types39.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
10383
+ found = id;
10384
+ }
10385
+ });
10386
+ return found;
10387
+ }
10388
+ function findMatchingRouteNode(graph, serviceName, method, path48) {
10389
+ const normalizedPath = normalizePathTemplate(path48);
10390
+ let found = null;
10391
+ graph.forEachNode((id, attrs) => {
10392
+ if (found) return;
10393
+ const a = attrs;
10394
+ if (a.type !== import_types39.NodeType.RouteNode || a.service !== serviceName) return;
10395
+ if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
10396
+ const routeMethod = (a.method ?? "").toUpperCase();
10397
+ if (routeMethod !== "ALL" && routeMethod !== method) return;
10398
+ found = id;
10399
+ });
10400
+ return found;
10401
+ }
10402
+ function createCloudflareResolveTarget(config, graph) {
10110
10403
  return (signal) => {
10111
10404
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
10112
- const mapping = config.workers[signal.targetName];
10113
- if (!mapping) return null;
10405
+ const scriptName = signal.targetName;
10406
+ const { method, path: path48 } = signal;
10407
+ const resolveRouteGrain = (serviceName, wholeFileId) => {
10408
+ if (!method || !path48) return wholeFileId;
10409
+ return findMatchingRouteNode(graph, serviceName, method, path48) ?? wholeFileId;
10410
+ };
10411
+ const mapping = config.workers?.[scriptName];
10412
+ if (mapping) {
10413
+ const wholeFileId = (0, import_types39.fileId)(mapping.service, mapping.entryFile);
10414
+ return {
10415
+ targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
10416
+ serviceName: mapping.service,
10417
+ edgeType: import_types39.EdgeType.CALLS
10418
+ };
10419
+ }
10420
+ const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
10421
+ if (taggedFileId) {
10422
+ const fileNode = graph.getNodeAttributes(taggedFileId);
10423
+ return {
10424
+ targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
10425
+ serviceName: fileNode.service,
10426
+ edgeType: import_types39.EdgeType.CALLS
10427
+ };
10428
+ }
10114
10429
  return {
10115
- targetNodeId: (0, import_types38.fileId)(mapping.service, mapping.entryFile),
10116
- serviceName: mapping.service,
10117
- edgeType: import_types38.EdgeType.CALLS
10430
+ targetNodeId: (0, import_types39.infraId)("cloudflare-worker", scriptName),
10431
+ serviceName: scriptName,
10432
+ edgeType: import_types39.EdgeType.CALLS,
10433
+ ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
10118
10434
  };
10119
10435
  };
10120
10436
  }
@@ -10122,8 +10438,8 @@ function createCloudflareResolveTarget(config) {
10122
10438
  // src/connectors-config.ts
10123
10439
  init_cjs_shims();
10124
10440
  var import_node_os4 = __toESM(require("os"), 1);
10125
- var import_node_path44 = __toESM(require("path"), 1);
10126
- var import_node_fs25 = require("fs");
10441
+ var import_node_path45 = __toESM(require("path"), 1);
10442
+ var import_node_fs26 = require("fs");
10127
10443
  var CONNECTORS_CONFIG_VERSION = 1;
10128
10444
  var EnvRefUnsetError = class extends Error {
10129
10445
  ref;
@@ -10137,17 +10453,17 @@ var EnvRefUnsetError = class extends Error {
10137
10453
  };
10138
10454
  function neatHome2() {
10139
10455
  const override = process.env.NEAT_HOME;
10140
- if (override && override.length > 0) return import_node_path44.default.resolve(override);
10141
- return import_node_path44.default.join(import_node_os4.default.homedir(), ".neat");
10456
+ if (override && override.length > 0) return import_node_path45.default.resolve(override);
10457
+ return import_node_path45.default.join(import_node_os4.default.homedir(), ".neat");
10142
10458
  }
10143
10459
  function connectorsConfigPath(home = neatHome2()) {
10144
- return import_node_path44.default.join(home, "connectors.json");
10460
+ return import_node_path45.default.join(home, "connectors.json");
10145
10461
  }
10146
10462
  var MODE_MASK_LOOSER_THAN_0600 = 63;
10147
10463
  async function warnIfModeLooserThan0600(file) {
10148
10464
  if (process.platform === "win32") return;
10149
10465
  try {
10150
- const stat = await import_node_fs25.promises.stat(file);
10466
+ const stat = await import_node_fs26.promises.stat(file);
10151
10467
  if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
10152
10468
  const mode = (stat.mode & 511).toString(8).padStart(3, "0");
10153
10469
  console.warn(
@@ -10161,7 +10477,7 @@ async function readConnectorsConfig(home = neatHome2()) {
10161
10477
  const file = connectorsConfigPath(home);
10162
10478
  let raw;
10163
10479
  try {
10164
- raw = await import_node_fs25.promises.readFile(file, "utf8");
10480
+ raw = await import_node_fs26.promises.readFile(file, "utf8");
10165
10481
  } catch (err) {
10166
10482
  if (err.code === "ENOENT") {
10167
10483
  return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
@@ -10383,12 +10699,16 @@ var PROVIDER_DISPATCH = {
10383
10699
  provider: "cloudflare",
10384
10700
  primaryCredentialKey: "apiToken",
10385
10701
  requiredCredentialFields: ["apiToken"],
10386
- requiredOptionFields: ["accountId", "workers"],
10387
- build(_graph, options) {
10702
+ // `workers` dropped as a required field (ADR-133) — the mapping is now
10703
+ // derived from the extracted graph's platform tag; an `options.workers`
10704
+ // entry still works as an explicit override
10705
+ // (CloudflareConnectorConfig.workers).
10706
+ requiredOptionFields: ["accountId"],
10707
+ build(graph, options) {
10388
10708
  const config = options;
10389
10709
  return {
10390
10710
  connector: new CloudflareConnector(config),
10391
- resolveTarget: createCloudflareResolveTarget(config)
10711
+ resolveTarget: createCloudflareResolveTarget(config, graph)
10392
10712
  };
10393
10713
  },
10394
10714
  // GET /user/tokens/verify — Cloudflare's own purpose-built "is this API
@@ -10485,8 +10805,8 @@ init_auth();
10485
10805
 
10486
10806
  // src/unrouted.ts
10487
10807
  init_cjs_shims();
10488
- var import_node_fs26 = require("fs");
10489
- var import_node_path45 = __toESM(require("path"), 1);
10808
+ var import_node_fs27 = require("fs");
10809
+ var import_node_path46 = __toESM(require("path"), 1);
10490
10810
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
10491
10811
  return {
10492
10812
  timestamp: now.toISOString(),
@@ -10496,34 +10816,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
10496
10816
  };
10497
10817
  }
10498
10818
  async function appendUnroutedSpan(neatHome3, record) {
10499
- const target = import_node_path45.default.join(neatHome3, "errors.ndjson");
10500
- await import_node_fs26.promises.mkdir(neatHome3, { recursive: true });
10501
- await import_node_fs26.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
10819
+ const target = import_node_path46.default.join(neatHome3, "errors.ndjson");
10820
+ await import_node_fs27.promises.mkdir(neatHome3, { recursive: true });
10821
+ await import_node_fs27.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
10502
10822
  }
10503
10823
  function unroutedErrorsPath(neatHome3) {
10504
- return import_node_path45.default.join(neatHome3, "errors.ndjson");
10824
+ return import_node_path46.default.join(neatHome3, "errors.ndjson");
10505
10825
  }
10506
10826
 
10507
10827
  // src/daemon.ts
10508
- var import_types41 = require("@neat.is/types");
10828
+ var import_types42 = require("@neat.is/types");
10509
10829
  function daemonJsonPath(scanPath) {
10510
- return import_node_path46.default.join(scanPath, "neat-out", "daemon.json");
10830
+ return import_node_path47.default.join(scanPath, "neat-out", "daemon.json");
10511
10831
  }
10512
10832
  function daemonsDiscoveryDir(home) {
10513
10833
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
10514
- return import_node_path46.default.join(base, "daemons");
10834
+ return import_node_path47.default.join(base, "daemons");
10515
10835
  }
10516
10836
  function daemonDiscoveryPath(project, home) {
10517
- return import_node_path46.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
10837
+ return import_node_path47.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
10518
10838
  }
10519
10839
  function sanitizeDiscoveryName(project) {
10520
10840
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
10521
10841
  }
10522
10842
  function neatHomeFromEnv() {
10523
10843
  const env = process.env.NEAT_HOME;
10524
- if (env && env.length > 0) return import_node_path46.default.resolve(env);
10844
+ if (env && env.length > 0) return import_node_path47.default.resolve(env);
10525
10845
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
10526
- return import_node_path46.default.join(home, ".neat");
10846
+ return import_node_path47.default.join(home, ".neat");
10527
10847
  }
10528
10848
  function resolveNeatVersion() {
10529
10849
  if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
@@ -10555,7 +10875,7 @@ async function clearDaemonRecord(record, home) {
10555
10875
  } catch {
10556
10876
  }
10557
10877
  try {
10558
- await import_node_fs27.promises.unlink(daemonDiscoveryPath(record.project, home));
10878
+ await import_node_fs28.promises.unlink(daemonDiscoveryPath(record.project, home));
10559
10879
  } catch {
10560
10880
  }
10561
10881
  }
@@ -10578,11 +10898,11 @@ function teardownSlot(slot) {
10578
10898
  }
10579
10899
  }
10580
10900
  function neatHomeFor(opts) {
10581
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path46.default.resolve(opts.neatHome);
10901
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path47.default.resolve(opts.neatHome);
10582
10902
  const env = process.env.NEAT_HOME;
10583
- if (env && env.length > 0) return import_node_path46.default.resolve(env);
10903
+ if (env && env.length > 0) return import_node_path47.default.resolve(env);
10584
10904
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
10585
- return import_node_path46.default.join(home, ".neat");
10905
+ return import_node_path47.default.join(home, ".neat");
10586
10906
  }
10587
10907
  function routeSpanToProject(serviceName, projects) {
10588
10908
  if (!serviceName) return DEFAULT_PROJECT;
@@ -10626,13 +10946,13 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
10626
10946
  if (!serviceName) return true;
10627
10947
  if (serviceNameMatchesProject(serviceName, project)) return true;
10628
10948
  return graph.someNode(
10629
- (_id, attrs) => attrs.type === import_types41.NodeType.ServiceNode && attrs.name === serviceName
10949
+ (_id, attrs) => attrs.type === import_types42.NodeType.ServiceNode && attrs.name === serviceName
10630
10950
  );
10631
10951
  }
10632
10952
  async function bootstrapProject(entry, connectors = [], neatHome3) {
10633
- const paths = pathsForProject(entry.name, import_node_path46.default.join(entry.path, "neat-out"));
10953
+ const paths = pathsForProject(entry.name, import_node_path47.default.join(entry.path, "neat-out"));
10634
10954
  try {
10635
- const stat = await import_node_fs27.promises.stat(entry.path);
10955
+ const stat = await import_node_fs28.promises.stat(entry.path);
10636
10956
  if (!stat.isDirectory()) {
10637
10957
  throw new Error(`registered path ${entry.path} is not a directory`);
10638
10958
  }
@@ -10757,7 +11077,7 @@ async function startDaemon(opts = {}) {
10757
11077
  const projectArg = typeof opts.project === "string" && opts.project.length > 0 ? opts.project : process.env.NEAT_PROJECT && process.env.NEAT_PROJECT.length > 0 ? process.env.NEAT_PROJECT : null;
10758
11078
  const projectPathArg = opts.projectPath && opts.projectPath.length > 0 ? opts.projectPath : process.env.NEAT_PROJECT_PATH && process.env.NEAT_PROJECT_PATH.length > 0 ? process.env.NEAT_PROJECT_PATH : null;
10759
11079
  const singleProject = projectArg;
10760
- const singleProjectPath = singleProject && projectPathArg ? import_node_path46.default.resolve(projectPathArg) : null;
11080
+ const singleProjectPath = singleProject && projectPathArg ? import_node_path47.default.resolve(projectPathArg) : null;
10761
11081
  if (singleProject && !singleProjectPath) {
10762
11082
  throw new Error(
10763
11083
  `neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
@@ -10765,14 +11085,14 @@ async function startDaemon(opts = {}) {
10765
11085
  }
10766
11086
  if (!singleProject) {
10767
11087
  try {
10768
- await import_node_fs27.promises.access(regPath);
11088
+ await import_node_fs28.promises.access(regPath);
10769
11089
  } catch {
10770
11090
  throw new Error(
10771
11091
  `neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
10772
11092
  );
10773
11093
  }
10774
11094
  }
10775
- const pidPath = import_node_path46.default.join(home, "neatd.pid");
11095
+ const pidPath = import_node_path47.default.join(home, "neatd.pid");
10776
11096
  await writeAtomically(pidPath, `${process.pid}
10777
11097
  `);
10778
11098
  const slots = /* @__PURE__ */ new Map();
@@ -10966,7 +11286,7 @@ async function startDaemon(opts = {}) {
10966
11286
  }
10967
11287
  if (restApp) await restApp.close().catch(() => {
10968
11288
  });
10969
- await import_node_fs27.promises.unlink(pidPath).catch(() => {
11289
+ await import_node_fs28.promises.unlink(pidPath).catch(() => {
10970
11290
  });
10971
11291
  throw new Error(
10972
11292
  `neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
@@ -11092,7 +11412,7 @@ async function startDaemon(opts = {}) {
11092
11412
  });
11093
11413
  if (otlpApp) await otlpApp.close().catch(() => {
11094
11414
  });
11095
- await import_node_fs27.promises.unlink(pidPath).catch(() => {
11415
+ await import_node_fs28.promises.unlink(pidPath).catch(() => {
11096
11416
  });
11097
11417
  throw new Error(
11098
11418
  `neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
@@ -11127,7 +11447,7 @@ async function startDaemon(opts = {}) {
11127
11447
  });
11128
11448
  if (otlpApp) await otlpApp.close().catch(() => {
11129
11449
  });
11130
- await import_node_fs27.promises.unlink(pidPath).catch(() => {
11450
+ await import_node_fs28.promises.unlink(pidPath).catch(() => {
11131
11451
  });
11132
11452
  throw new Error(
11133
11453
  `neatd: failed to write daemon.json for "${singleProject}" \u2014 ${err.message}`
@@ -11174,9 +11494,9 @@ async function startDaemon(opts = {}) {
11174
11494
  let registryWatcher = null;
11175
11495
  let reloadTimer = null;
11176
11496
  if (!singleProject) try {
11177
- const regDir = import_node_path46.default.dirname(regPath);
11178
- const regBase = import_node_path46.default.basename(regPath);
11179
- registryWatcher = (0, import_node_fs27.watch)(regDir, (_eventType, filename) => {
11497
+ const regDir = import_node_path47.default.dirname(regPath);
11498
+ const regBase = import_node_path47.default.basename(regPath);
11499
+ registryWatcher = (0, import_node_fs28.watch)(regDir, (_eventType, filename) => {
11180
11500
  if (filename !== null && filename !== regBase) return;
11181
11501
  if (reloadTimer) clearTimeout(reloadTimer);
11182
11502
  reloadTimer = setTimeout(() => {
@@ -11225,7 +11545,7 @@ async function startDaemon(opts = {}) {
11225
11545
  if (daemonRecord) {
11226
11546
  await clearDaemonRecord(daemonRecord, home);
11227
11547
  }
11228
- await import_node_fs27.promises.unlink(pidPath).catch(() => {
11548
+ await import_node_fs28.promises.unlink(pidPath).catch(() => {
11229
11549
  });
11230
11550
  };
11231
11551
  return {