@neat.is/core 0.5.3 → 0.6.0

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 path51 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
64
- if (exactUnauthPaths.has(path51) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path51)) {
63
+ const path52 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
64
+ if (exactUnauthPaths.has(path52) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path52)) {
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_path47.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
197
- return import_node_path47.default.resolve(here, "..", "proto");
196
+ const here = import_node_path48.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
197
+ return import_node_path48.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_path47, import_node_crypto2, grpc, protoLoader;
265
+ var import_node_url, import_node_path48, 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_path47 = __toESM(require("path"), 1);
271
+ import_node_path48 = __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);
@@ -341,6 +341,11 @@ function pickEnv(spanAttrs, resourceAttrs) {
341
341
  }
342
342
  return ENV_FALLBACK;
343
343
  }
344
+ function normalizeDbSystem(attrs) {
345
+ const raw = attrs["db.system"];
346
+ if (typeof raw !== "string") return void 0;
347
+ return raw === "mongoose" ? "mongodb" : raw;
348
+ }
344
349
  function messagingDestinationOf(attrs) {
345
350
  for (const key of ["messaging.destination.name", "messaging.destination"]) {
346
351
  const v = attrs[key];
@@ -361,8 +366,8 @@ function websocketChannelPathOf(attrs) {
361
366
  const v = attrs[key];
362
367
  if (typeof v === "string" && v.length > 0) {
363
368
  const q = v.indexOf("?");
364
- const path51 = q === -1 ? v : v.slice(0, q);
365
- if (path51.length > 0) return path51;
369
+ const path52 = q === -1 ? v : v.slice(0, q);
370
+ if (path52.length > 0) return path52;
366
371
  }
367
372
  }
368
373
  return void 0;
@@ -395,8 +400,9 @@ function parseOtlpRequest(body) {
395
400
  durationNanos: durationNanos(span.startTimeUnixNano, span.endTimeUnixNano),
396
401
  env: pickEnv(attrs, resourceAttrs),
397
402
  attributes: attrs,
398
- dbSystem: typeof attrs["db.system"] === "string" ? attrs["db.system"] : void 0,
403
+ dbSystem: normalizeDbSystem(attrs),
399
404
  dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
405
+ dbCollection: typeof attrs["db.collection.name"] === "string" ? attrs["db.collection.name"] : typeof attrs["db.mongodb.collection"] === "string" ? attrs["db.mongodb.collection"] : void 0,
400
406
  messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
401
407
  messagingDestination: messagingDestinationOf(attrs),
402
408
  graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
@@ -416,10 +422,10 @@ function parseOtlpRequest(body) {
416
422
  return out;
417
423
  }
418
424
  function loadProtoRoot() {
419
- const here = import_node_path48.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
420
- const protoRoot = import_node_path48.default.resolve(here, "..", "proto");
425
+ const here = import_node_path49.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
426
+ const protoRoot = import_node_path49.default.resolve(here, "..", "proto");
421
427
  const root = new import_protobufjs.default.Root();
422
- root.resolvePath = (_origin, target) => import_node_path48.default.resolve(protoRoot, target);
428
+ root.resolvePath = (_origin, target) => import_node_path49.default.resolve(protoRoot, target);
423
429
  root.loadSync(
424
430
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
425
431
  { keepCase: true }
@@ -655,12 +661,12 @@ function logSpanHandler(span) {
655
661
  `otel: ${span.service} ${span.name} parent=${parent} status=${status2}${db}`
656
662
  );
657
663
  }
658
- var import_node_path48, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
664
+ var import_node_path49, 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
665
  var init_otel = __esm({
660
666
  "src/otel.ts"() {
661
667
  "use strict";
662
668
  init_cjs_shims();
663
- import_node_path48 = __toESM(require("path"), 1);
669
+ import_node_path49 = __toESM(require("path"), 1);
664
670
  import_node_url2 = require("url");
665
671
  import_fastify2 = __toESM(require("fastify"), 1);
666
672
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -1250,19 +1256,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1250
1256
  function longestIncomingWalk(graph, start, maxDepth) {
1251
1257
  let best = { path: [start], edges: [] };
1252
1258
  const visited = /* @__PURE__ */ new Set([start]);
1253
- function step(node, path51, edges) {
1254
- if (path51.length > best.path.length) {
1255
- best = { path: [...path51], edges: [...edges] };
1259
+ function step(node, path52, edges) {
1260
+ if (path52.length > best.path.length) {
1261
+ best = { path: [...path52], edges: [...edges] };
1256
1262
  }
1257
- if (path51.length - 1 >= maxDepth) return;
1263
+ if (path52.length - 1 >= maxDepth) return;
1258
1264
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1259
1265
  for (const [srcId, edge] of incoming) {
1260
1266
  if (visited.has(srcId)) continue;
1261
1267
  visited.add(srcId);
1262
- path51.push(srcId);
1268
+ path52.push(srcId);
1263
1269
  edges.push(edge);
1264
- step(srcId, path51, edges);
1265
- path51.pop();
1270
+ step(srcId, path52, edges);
1271
+ path52.pop();
1266
1272
  edges.pop();
1267
1273
  visited.delete(srcId);
1268
1274
  }
@@ -1456,26 +1462,26 @@ function dominantFailingCall(graph, serviceId6, visited) {
1456
1462
  return best;
1457
1463
  }
1458
1464
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1459
- const path51 = [originServiceId];
1465
+ const path52 = [originServiceId];
1460
1466
  const edges = [];
1461
1467
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1462
1468
  let current = originServiceId;
1463
1469
  for (let depth = 0; depth < maxDepth; depth++) {
1464
1470
  const hop = dominantFailingCall(graph, current, visited);
1465
1471
  if (!hop) break;
1466
- path51.push(hop.nextService);
1472
+ path52.push(hop.nextService);
1467
1473
  edges.push(hop.edge);
1468
1474
  visited.add(hop.nextService);
1469
1475
  current = hop.nextService;
1470
1476
  }
1471
1477
  if (edges.length === 0) return null;
1472
- return { path: path51, edges, culprit: current };
1478
+ return { path: path52, edges, culprit: current };
1473
1479
  }
1474
1480
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1475
1481
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1476
1482
  if (!chain) return null;
1477
1483
  const culprit = chain.culprit;
1478
- const path51 = [...chain.path];
1484
+ const path52 = [...chain.path];
1479
1485
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1480
1486
  const baseConfidence = confidenceFromMix(chain.edges);
1481
1487
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1483,14 +1489,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1483
1489
  if (loc) {
1484
1490
  let rootCauseNode = culprit;
1485
1491
  if (loc.fileNode) {
1486
- path51.push(loc.fileNode);
1492
+ path52.push(loc.fileNode);
1487
1493
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1488
1494
  rootCauseNode = loc.fileNode;
1489
1495
  }
1490
1496
  return import_types.RootCauseResultSchema.parse({
1491
1497
  rootCauseNode,
1492
1498
  rootCauseReason: loc.rootCauseReason,
1493
- traversalPath: path51,
1499
+ traversalPath: path52,
1494
1500
  edgeProvenances,
1495
1501
  confidence,
1496
1502
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1502,7 +1508,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1502
1508
  return import_types.RootCauseResultSchema.parse({
1503
1509
  rootCauseNode: culprit,
1504
1510
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1505
- traversalPath: path51,
1511
+ traversalPath: path52,
1506
1512
  edgeProvenances,
1507
1513
  confidence,
1508
1514
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2929,6 +2935,18 @@ async function handleSpan(ctx, span) {
2929
2935
  callSiteEvidence
2930
2936
  );
2931
2937
  if (result) affectedNode = targetId;
2938
+ if (span.dbSystem === "mongodb" && span.dbCollection) {
2939
+ const collectionId = ensureInfraNode(ctx.graph, "mongodb-collection", span.dbCollection, "self");
2940
+ upsertObservedEdge(
2941
+ ctx.graph,
2942
+ import_types3.EdgeType.CALLS,
2943
+ observedSource(),
2944
+ collectionId,
2945
+ ts,
2946
+ isError,
2947
+ callSiteEvidence
2948
+ );
2949
+ }
2932
2950
  }
2933
2951
  } else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
2934
2952
  const targetId = ensureMessagingDestinationNode(
@@ -5776,7 +5794,7 @@ async function addGrpcMethods(graph, services) {
5776
5794
 
5777
5795
  // src/extract/calls/index.ts
5778
5796
  init_cjs_shims();
5779
- var import_types20 = require("@neat.is/types");
5797
+ var import_types21 = require("@neat.is/types");
5780
5798
 
5781
5799
  // src/extract/calls/http.ts
5782
5800
  init_cjs_shims();
@@ -6489,15 +6507,311 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6489
6507
  return out;
6490
6508
  }
6491
6509
 
6510
+ // src/extract/calls/mongoose.ts
6511
+ init_cjs_shims();
6512
+ var import_node_path31 = __toESM(require("path"), 1);
6513
+ var import_types20 = require("@neat.is/types");
6514
+ var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
6515
+ var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
6516
+ var PLURALIZE_DISABLED_RE = /\bpluralize\s*\(\s*(?:null|false)\s*\)/;
6517
+ var UNCOUNTABLES = /* @__PURE__ */ new Set([
6518
+ "advice",
6519
+ "energy",
6520
+ "excretion",
6521
+ "digestion",
6522
+ "cooperation",
6523
+ "health",
6524
+ "justice",
6525
+ "labour",
6526
+ "machinery",
6527
+ "equipment",
6528
+ "information",
6529
+ "pollution",
6530
+ "sewage",
6531
+ "paper",
6532
+ "money",
6533
+ "species",
6534
+ "series",
6535
+ "rain",
6536
+ "rice",
6537
+ "fish",
6538
+ "sheep",
6539
+ "moose",
6540
+ "deer",
6541
+ "news",
6542
+ "expertise",
6543
+ "status",
6544
+ "media"
6545
+ ]);
6546
+ var PLURALIZE_RULES = [
6547
+ [/human$/gi, "humans"],
6548
+ [/(m)an$/gi, "$1en"],
6549
+ [/(pe)rson$/gi, "$1ople"],
6550
+ [/(child)$/gi, "$1ren"],
6551
+ [/^(ox)$/gi, "$1en"],
6552
+ [/(ax|test)is$/gi, "$1es"],
6553
+ [/(octop|vir)us$/gi, "$1i"],
6554
+ [/(alias|status)$/gi, "$1es"],
6555
+ [/(bu)s$/gi, "$1ses"],
6556
+ [/(buffal|tomat|potat)o$/gi, "$1oes"],
6557
+ [/([ti])um$/gi, "$1a"],
6558
+ [/sis$/gi, "ses"],
6559
+ [/(?:([^f])fe|([lr])f)$/gi, "$1$2ves"],
6560
+ [/(hive)$/gi, "$1s"],
6561
+ [/([^aeiouy]|qu)y$/gi, "$1ies"],
6562
+ [/(x|ch|ss|sh)$/gi, "$1es"],
6563
+ [/(matr|vert|ind)ix|ex$/gi, "$1ices"],
6564
+ [/([m|l])ouse$/gi, "$1ice"],
6565
+ [/(kn|w|l)ife$/gi, "$1ives"],
6566
+ [/(quiz)$/gi, "$1zes"],
6567
+ [/s$/gi, "s"],
6568
+ [/([^a-z])$/, "$1"],
6569
+ [/$/gi, "s"]
6570
+ ];
6571
+ function pluralizeCollection(name) {
6572
+ const str = name.toLowerCase();
6573
+ if (UNCOUNTABLES.has(str)) return str;
6574
+ for (const [re, repl] of PLURALIZE_RULES) {
6575
+ if (str.match(re)) return str.replace(re, repl);
6576
+ }
6577
+ return str;
6578
+ }
6579
+ var MODEL_METHODS = [
6580
+ "find",
6581
+ "findOne",
6582
+ "findById",
6583
+ "findByIdAndUpdate",
6584
+ "findByIdAndDelete",
6585
+ "findOneAndUpdate",
6586
+ "findOneAndDelete",
6587
+ "findOneAndReplace",
6588
+ "countDocuments",
6589
+ "estimatedDocumentCount",
6590
+ "distinct",
6591
+ "aggregate",
6592
+ "exists",
6593
+ "where",
6594
+ "populate",
6595
+ "watch",
6596
+ "hydrate",
6597
+ "create",
6598
+ "insertMany",
6599
+ "insertOne",
6600
+ "updateOne",
6601
+ "updateMany",
6602
+ "replaceOne",
6603
+ "deleteOne",
6604
+ "deleteMany",
6605
+ "bulkWrite",
6606
+ "bulkSave",
6607
+ "save",
6608
+ // pre-v7, removed in current mongoose but present in older codebases:
6609
+ "count",
6610
+ "update",
6611
+ "remove",
6612
+ "findOneAndRemove",
6613
+ "findByIdAndRemove"
6614
+ ];
6615
+ var MODEL_METHODS_ALT = MODEL_METHODS.join("|");
6616
+ function schemaCollectionVars(content) {
6617
+ const out = /* @__PURE__ */ new Map();
6618
+ const re = /(?:const|let|var)\s+(\w+)\s*=\s*new\s+(?:mongoose\s*\.\s*)?Schema\s*\(/g;
6619
+ let m;
6620
+ while ((m = re.exec(content)) !== null) {
6621
+ const windowText = content.slice(m.index, m.index + 2e3);
6622
+ const opt = /\bcollection\s*:\s*['"`]([\w.-]+)['"`]/.exec(windowText);
6623
+ if (opt) out.set(m[1], opt[1]);
6624
+ }
6625
+ return out;
6626
+ }
6627
+ function resolveModel(modelName, rest, schemaColl, pluralizeOn) {
6628
+ const trimmed = rest.trim();
6629
+ const literalThird = /,\s*['"`]([\w.-]+)['"`]\s*$/.exec(trimmed);
6630
+ if (literalThird) return { name: literalThird[1], kind: "mongodb-collection" };
6631
+ const firstIdent = /^([A-Za-z_$][\w$]*)/.exec(trimmed)?.[1];
6632
+ if (firstIdent && schemaColl.has(firstIdent)) {
6633
+ return { name: schemaColl.get(firstIdent), kind: "mongodb-collection" };
6634
+ }
6635
+ if (/,\s*[A-Za-z_$][\w$]*\s*$/.test(trimmed)) {
6636
+ return { name: modelName, kind: "mongodb-model" };
6637
+ }
6638
+ return { name: pluralizeOn ? pluralizeCollection(modelName) : modelName, kind: "mongodb-collection" };
6639
+ }
6640
+ function collectModelDefs(content, pluralizeOn) {
6641
+ const schemaColl = schemaCollectionVars(content);
6642
+ const out = [];
6643
+ const re = /(?:(?:const|let|var)\s+(\w+)\s*=\s*)?(?:await\s+)?(?:\b\w+\s*\.\s*)?\bmodel\s*\(\s*['"`]([\w$]+)['"`]\s*(?:,\s*([\s\S]{0,300}?))?\)/g;
6644
+ let m;
6645
+ while ((m = re.exec(content)) !== null) {
6646
+ out.push({
6647
+ modelName: m[2],
6648
+ varName: m[1] ?? null,
6649
+ resolved: resolveModel(m[2], m[3] ?? "", schemaColl, pluralizeOn),
6650
+ matchText: m[0]
6651
+ });
6652
+ }
6653
+ return out;
6654
+ }
6655
+ function endpoint(r, file, serviceDir, matchText) {
6656
+ const line = lineOf(file.content, matchText);
6657
+ return {
6658
+ infraId: (0, import_types20.infraId)(r.kind, r.name),
6659
+ name: r.name,
6660
+ kind: r.kind,
6661
+ edgeType: "CALLS",
6662
+ confidenceKind: "verified-call-site",
6663
+ evidence: { file: import_node_path31.default.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
6664
+ };
6665
+ }
6666
+ function mongooseEndpointsFromFile(file, serviceDir) {
6667
+ const hasMongoose = MONGOOSE_IMPORT_RE.test(file.content);
6668
+ const hasMongodb = MONGODB_IMPORT_RE.test(file.content);
6669
+ if (!hasMongoose && !hasMongodb) return [];
6670
+ const content = file.content;
6671
+ const out = [];
6672
+ const seen = /* @__PURE__ */ new Set();
6673
+ const push = (r, matchText) => {
6674
+ const key = `${r.kind}/${r.name}`;
6675
+ if (seen.has(key)) return;
6676
+ seen.add(key);
6677
+ out.push(endpoint(r, file, serviceDir, matchText));
6678
+ };
6679
+ if (hasMongoose) {
6680
+ const pluralizeOn = !PLURALIZE_DISABLED_RE.test(content);
6681
+ for (const def of collectModelDefs(content, pluralizeOn)) push(def.resolved, def.matchText);
6682
+ }
6683
+ const collRe = /\.\s*collection\s*\(\s*['"`]([\w.$-]+)['"`]\s*\)/g;
6684
+ let c;
6685
+ while ((c = collRe.exec(content)) !== null) {
6686
+ push({ name: c[1], kind: "mongodb-collection" }, c[0]);
6687
+ }
6688
+ return out;
6689
+ }
6690
+ function parseDestructure(inner, specifier, out) {
6691
+ for (const raw of inner.split(",")) {
6692
+ const part = raw.trim();
6693
+ if (!part) continue;
6694
+ const asM = /^(\w+)\s+as\s+(\w+)$/.exec(part);
6695
+ const colonM = /^(\w+)\s*:\s*(\w+)$/.exec(part);
6696
+ if (asM) out.push({ local: asM[2], kind: "named", exportName: asM[1], specifier });
6697
+ else if (colonM) out.push({ local: colonM[2], kind: "named", exportName: colonM[1], specifier });
6698
+ else {
6699
+ const id = /^(\w+)$/.exec(part);
6700
+ if (id) out.push({ local: id[1], kind: "named", exportName: id[1], specifier });
6701
+ }
6702
+ }
6703
+ }
6704
+ function parseImportBindings(content) {
6705
+ const out = [];
6706
+ const esm = /import\s+([^;'"`\n]+?)\s+from\s*['"`]([^'"`]+)['"`]/g;
6707
+ let m;
6708
+ while ((m = esm.exec(content)) !== null) {
6709
+ const clause = m[1].trim();
6710
+ const spec = m[2];
6711
+ const ns = /^\*\s+as\s+(\w+)$/.exec(clause);
6712
+ if (ns) {
6713
+ out.push({ local: ns[1], kind: "namespace", specifier: spec });
6714
+ continue;
6715
+ }
6716
+ const named = /\{([^}]*)\}/.exec(clause);
6717
+ if (named) parseDestructure(named[1], spec, out);
6718
+ const def = /^(\w+)\s*(?:,|$)/.exec(clause);
6719
+ if (def && !clause.startsWith("{")) out.push({ local: def[1], kind: "default", specifier: spec });
6720
+ }
6721
+ const cjs = /(?:const|let|var)\s+(\{[^}]*\}|\w+)\s*=\s*require\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
6722
+ while ((m = cjs.exec(content)) !== null) {
6723
+ const lhs = m[1];
6724
+ const spec = m[2];
6725
+ if (lhs.startsWith("{")) parseDestructure(lhs.slice(1, -1), spec, out);
6726
+ else out.push({ local: lhs, kind: "whole", specifier: spec });
6727
+ }
6728
+ return out;
6729
+ }
6730
+ function fileExportsOf(content, pluralizeOn) {
6731
+ const defs = collectModelDefs(content, pluralizeOn).filter((d) => d.resolved.kind === "mongodb-collection");
6732
+ if (defs.length === 0) return null;
6733
+ const byVar = /* @__PURE__ */ new Map();
6734
+ for (const d of defs) if (d.varName) byVar.set(d.varName, d.resolved.name);
6735
+ const byName = new Map(byVar);
6736
+ let def;
6737
+ const named = /(?:module\.exports|export\s+default)\s*=\s*(\w+)\b/.exec(content);
6738
+ if (named && byVar.has(named[1])) def = byVar.get(named[1]);
6739
+ if (!def) {
6740
+ const inline = /(?:module\.exports|export\s+default)\s*=\s*(?:await\s+)?(?:\w+\s*\.\s*)?model\s*\(\s*['"`]([\w$]+)['"`]/.exec(content);
6741
+ if (inline) def = pluralizeOn ? pluralizeCollection(inline[1]) : inline[1];
6742
+ }
6743
+ if (!def && byVar.size === 1) def = [...byVar.values()][0];
6744
+ return { byName, ...def ? { default: def } : {} };
6745
+ }
6746
+ async function mongooseCrossFileEndpoints(files, serviceDir) {
6747
+ const mongooseFiles = files.filter((f) => MONGOOSE_IMPORT_RE.test(f.content));
6748
+ if (mongooseFiles.length === 0) return [];
6749
+ const pluralizeOn = !files.some((f) => PLURALIZE_DISABLED_RE.test(f.content));
6750
+ const registry = /* @__PURE__ */ new Map();
6751
+ for (const f of mongooseFiles) {
6752
+ const fx = fileExportsOf(f.content, pluralizeOn);
6753
+ if (fx) registry.set(toPosix2(import_node_path31.default.relative(serviceDir, f.path)), fx);
6754
+ }
6755
+ if (registry.size === 0) return [];
6756
+ const out = [];
6757
+ const seen = /* @__PURE__ */ new Set();
6758
+ for (const f of files) {
6759
+ const bindings = parseImportBindings(f.content);
6760
+ if (bindings.length === 0) continue;
6761
+ const directColl = /* @__PURE__ */ new Map();
6762
+ const nsExports = /* @__PURE__ */ new Map();
6763
+ for (const b of bindings) {
6764
+ const resolvedRel = await resolveJsImport(b.specifier, import_node_path31.default.dirname(f.path), serviceDir, null);
6765
+ if (!resolvedRel) continue;
6766
+ const fx = registry.get(resolvedRel);
6767
+ if (!fx) continue;
6768
+ if (b.kind === "named" && b.exportName) {
6769
+ const coll = fx.byName.get(b.exportName);
6770
+ if (coll) directColl.set(b.local, coll);
6771
+ } else if (b.kind === "default") {
6772
+ if (fx.default) directColl.set(b.local, fx.default);
6773
+ } else {
6774
+ if (fx.default) directColl.set(b.local, fx.default);
6775
+ nsExports.set(b.local, fx);
6776
+ }
6777
+ }
6778
+ if (directColl.size === 0 && nsExports.size === 0) continue;
6779
+ const localDefs = new Set(
6780
+ collectModelDefs(f.content, pluralizeOn).map((d) => d.varName).filter((v) => v !== null)
6781
+ );
6782
+ const emit = (collection, matchText) => {
6783
+ const key = `${f.path}::${collection}`;
6784
+ if (seen.has(key)) return;
6785
+ seen.add(key);
6786
+ out.push(endpoint({ name: collection, kind: "mongodb-collection" }, f, serviceDir, matchText));
6787
+ };
6788
+ for (const [local, collection] of directColl) {
6789
+ if (localDefs.has(local)) continue;
6790
+ const qre = new RegExp(`\\b${local}\\s*\\.\\s*(?:${MODEL_METHODS_ALT})\\s*\\(`, "g");
6791
+ let qm;
6792
+ while ((qm = qre.exec(f.content)) !== null) emit(collection, qm[0]);
6793
+ }
6794
+ for (const [local, fx] of nsExports) {
6795
+ const qre = new RegExp(`\\b${local}\\s*\\.\\s*(\\w+)\\s*\\.\\s*(?:${MODEL_METHODS_ALT})\\s*\\(`, "g");
6796
+ let qm;
6797
+ while ((qm = qre.exec(f.content)) !== null) {
6798
+ const coll = fx.byName.get(qm[1]);
6799
+ if (coll) emit(coll, qm[0]);
6800
+ }
6801
+ }
6802
+ }
6803
+ return out;
6804
+ }
6805
+
6492
6806
  // src/extract/calls/index.ts
6493
6807
  function edgeTypeFromEndpoint(ep) {
6494
6808
  switch (ep.edgeType) {
6495
6809
  case "PUBLISHES_TO":
6496
- return import_types20.EdgeType.PUBLISHES_TO;
6810
+ return import_types21.EdgeType.PUBLISHES_TO;
6497
6811
  case "CONSUMES_FROM":
6498
- return import_types20.EdgeType.CONSUMES_FROM;
6812
+ return import_types21.EdgeType.CONSUMES_FROM;
6499
6813
  default:
6500
- return import_types20.EdgeType.CALLS;
6814
+ return import_types21.EdgeType.CALLS;
6501
6815
  }
6502
6816
  }
6503
6817
  function isAwsKind(kind) {
@@ -6509,23 +6823,27 @@ async function addExternalEndpointEdges(graph, services) {
6509
6823
  for (const service of services) {
6510
6824
  const files = await loadSourceFiles(service.dir);
6511
6825
  const endpoints = [];
6826
+ const maskedFiles = [];
6512
6827
  for (const file of files) {
6513
6828
  if (isTestPath(file.path)) continue;
6514
6829
  const masked = maskCommentsInSource(file.content);
6515
6830
  const maskedFile = { path: file.path, content: masked };
6831
+ maskedFiles.push(maskedFile);
6516
6832
  endpoints.push(...kafkaEndpointsFromFile(maskedFile, service.dir));
6517
6833
  endpoints.push(...redisEndpointsFromFile(maskedFile, service.dir));
6518
6834
  endpoints.push(...awsEndpointsFromFile(maskedFile, service.dir));
6519
6835
  endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
6520
6836
  endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir));
6837
+ endpoints.push(...mongooseEndpointsFromFile(maskedFile, service.dir));
6521
6838
  }
6839
+ endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
6522
6840
  if (endpoints.length === 0) continue;
6523
6841
  const seenEdges = /* @__PURE__ */ new Set();
6524
6842
  for (const ep of endpoints) {
6525
6843
  if (!graph.hasNode(ep.infraId)) {
6526
6844
  const node = {
6527
6845
  id: ep.infraId,
6528
- type: import_types20.NodeType.InfraNode,
6846
+ type: import_types21.NodeType.InfraNode,
6529
6847
  name: ep.name,
6530
6848
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
6531
6849
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -6537,7 +6855,7 @@ async function addExternalEndpointEdges(graph, services) {
6537
6855
  nodesAdded++;
6538
6856
  }
6539
6857
  const edgeType = edgeTypeFromEndpoint(ep);
6540
- const confidence = (0, import_types20.confidenceForExtracted)(ep.confidenceKind);
6858
+ const confidence = (0, import_types21.confidenceForExtracted)(ep.confidenceKind);
6541
6859
  const relFile = toPosix2(ep.evidence.file);
6542
6860
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
6543
6861
  graph,
@@ -6547,7 +6865,7 @@ async function addExternalEndpointEdges(graph, services) {
6547
6865
  );
6548
6866
  nodesAdded += n;
6549
6867
  edgesAdded += e;
6550
- if (!(0, import_types20.passesExtractedFloor)(confidence)) {
6868
+ if (!(0, import_types21.passesExtractedFloor)(confidence)) {
6551
6869
  noteExtractedDropped({
6552
6870
  source: fileNodeId,
6553
6871
  target: ep.infraId,
@@ -6567,7 +6885,7 @@ async function addExternalEndpointEdges(graph, services) {
6567
6885
  source: fileNodeId,
6568
6886
  target: ep.infraId,
6569
6887
  type: edgeType,
6570
- provenance: import_types20.Provenance.EXTRACTED,
6888
+ provenance: import_types21.Provenance.EXTRACTED,
6571
6889
  confidence,
6572
6890
  evidence: ep.evidence
6573
6891
  };
@@ -6593,16 +6911,16 @@ init_cjs_shims();
6593
6911
 
6594
6912
  // src/extract/infra/docker-compose.ts
6595
6913
  init_cjs_shims();
6596
- var import_node_path31 = __toESM(require("path"), 1);
6597
- var import_types22 = require("@neat.is/types");
6914
+ var import_node_path32 = __toESM(require("path"), 1);
6915
+ var import_types23 = require("@neat.is/types");
6598
6916
 
6599
6917
  // src/extract/infra/shared.ts
6600
6918
  init_cjs_shims();
6601
- var import_types21 = require("@neat.is/types");
6919
+ var import_types22 = require("@neat.is/types");
6602
6920
  function makeInfraNode(kind, name, provider = "self", extras) {
6603
6921
  return {
6604
- id: (0, import_types21.infraId)(kind, name),
6605
- type: import_types21.NodeType.InfraNode,
6922
+ id: (0, import_types22.infraId)(kind, name),
6923
+ type: import_types22.NodeType.InfraNode,
6606
6924
  name,
6607
6925
  provider,
6608
6926
  kind,
@@ -6646,8 +6964,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
6646
6964
  source: anchorId,
6647
6965
  target: node.id,
6648
6966
  type: edgeType,
6649
- provenance: import_types21.Provenance.EXTRACTED,
6650
- confidence: (0, import_types21.confidenceForExtracted)("structural"),
6967
+ provenance: import_types22.Provenance.EXTRACTED,
6968
+ confidence: (0, import_types22.confidenceForExtracted)("structural"),
6651
6969
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
6652
6970
  };
6653
6971
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6664,7 +6982,7 @@ function dependsOnList(value) {
6664
6982
  }
6665
6983
  function serviceNameToServiceNode(name, services) {
6666
6984
  for (const s of services) {
6667
- if (s.node.name === name || import_node_path31.default.basename(s.dir) === name) return s.node.id;
6985
+ if (s.node.name === name || import_node_path32.default.basename(s.dir) === name) return s.node.id;
6668
6986
  }
6669
6987
  return null;
6670
6988
  }
@@ -6673,7 +6991,7 @@ async function addComposeInfra(graph, scanPath, services) {
6673
6991
  let edgesAdded = 0;
6674
6992
  let composePath = null;
6675
6993
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
6676
- const abs = import_node_path31.default.join(scanPath, name);
6994
+ const abs = import_node_path32.default.join(scanPath, name);
6677
6995
  if (await exists(abs)) {
6678
6996
  composePath = abs;
6679
6997
  break;
@@ -6686,13 +7004,13 @@ async function addComposeInfra(graph, scanPath, services) {
6686
7004
  } catch (err) {
6687
7005
  recordExtractionError(
6688
7006
  "infra docker-compose",
6689
- import_node_path31.default.relative(scanPath, composePath),
7007
+ import_node_path32.default.relative(scanPath, composePath),
6690
7008
  err
6691
7009
  );
6692
7010
  return { nodesAdded, edgesAdded };
6693
7011
  }
6694
7012
  if (!compose?.services) return { nodesAdded, edgesAdded };
6695
- const evidenceFile = import_node_path31.default.relative(scanPath, composePath).split(import_node_path31.default.sep).join("/");
7013
+ const evidenceFile = import_node_path32.default.relative(scanPath, composePath).split(import_node_path32.default.sep).join("/");
6696
7014
  const composeNameToNodeId = /* @__PURE__ */ new Map();
6697
7015
  for (const [composeName, svc] of Object.entries(compose.services)) {
6698
7016
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -6714,15 +7032,15 @@ async function addComposeInfra(graph, scanPath, services) {
6714
7032
  for (const dep of dependsOnList(svc.depends_on)) {
6715
7033
  const targetId = composeNameToNodeId.get(dep);
6716
7034
  if (!targetId) continue;
6717
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types22.EdgeType.DEPENDS_ON);
7035
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types23.EdgeType.DEPENDS_ON);
6718
7036
  if (graph.hasEdge(edgeId)) continue;
6719
7037
  const edge = {
6720
7038
  id: edgeId,
6721
7039
  source: sourceId,
6722
7040
  target: targetId,
6723
- type: import_types22.EdgeType.DEPENDS_ON,
6724
- provenance: import_types22.Provenance.EXTRACTED,
6725
- confidence: (0, import_types22.confidenceForExtracted)("structural"),
7041
+ type: import_types23.EdgeType.DEPENDS_ON,
7042
+ provenance: import_types23.Provenance.EXTRACTED,
7043
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
6726
7044
  evidence: { file: evidenceFile }
6727
7045
  };
6728
7046
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6734,9 +7052,9 @@ async function addComposeInfra(graph, scanPath, services) {
6734
7052
 
6735
7053
  // src/extract/infra/dockerfile.ts
6736
7054
  init_cjs_shims();
6737
- var import_node_path32 = __toESM(require("path"), 1);
7055
+ var import_node_path33 = __toESM(require("path"), 1);
6738
7056
  var import_node_fs16 = require("fs");
6739
- var import_types23 = require("@neat.is/types");
7057
+ var import_types24 = require("@neat.is/types");
6740
7058
  function readDockerfile(content) {
6741
7059
  let image = null;
6742
7060
  const ports = [];
@@ -6765,7 +7083,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6765
7083
  let nodesAdded = 0;
6766
7084
  let edgesAdded = 0;
6767
7085
  for (const service of services) {
6768
- const dockerfilePath = import_node_path32.default.join(service.dir, "Dockerfile");
7086
+ const dockerfilePath = import_node_path33.default.join(service.dir, "Dockerfile");
6769
7087
  if (!await exists(dockerfilePath)) continue;
6770
7088
  let content;
6771
7089
  try {
@@ -6773,7 +7091,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6773
7091
  } catch (err) {
6774
7092
  recordExtractionError(
6775
7093
  "infra dockerfile",
6776
- import_node_path32.default.relative(scanPath, dockerfilePath),
7094
+ import_node_path33.default.relative(scanPath, dockerfilePath),
6777
7095
  err
6778
7096
  );
6779
7097
  continue;
@@ -6785,8 +7103,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6785
7103
  graph.addNode(node.id, node);
6786
7104
  nodesAdded++;
6787
7105
  }
6788
- const relDockerfile = toPosix2(import_node_path32.default.relative(service.dir, dockerfilePath));
6789
- const evidenceFile = toPosix2(import_node_path32.default.relative(scanPath, dockerfilePath));
7106
+ const relDockerfile = toPosix2(import_node_path33.default.relative(service.dir, dockerfilePath));
7107
+ const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, dockerfilePath));
6790
7108
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
6791
7109
  graph,
6792
7110
  service.pkg.name,
@@ -6795,15 +7113,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6795
7113
  );
6796
7114
  nodesAdded += fn;
6797
7115
  edgesAdded += fe;
6798
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types23.EdgeType.RUNS_ON);
7116
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types24.EdgeType.RUNS_ON);
6799
7117
  if (!graph.hasEdge(edgeId)) {
6800
7118
  const edge = {
6801
7119
  id: edgeId,
6802
7120
  source: fileNodeId,
6803
7121
  target: node.id,
6804
- type: import_types23.EdgeType.RUNS_ON,
6805
- provenance: import_types23.Provenance.EXTRACTED,
6806
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7122
+ type: import_types24.EdgeType.RUNS_ON,
7123
+ provenance: import_types24.Provenance.EXTRACTED,
7124
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
6807
7125
  evidence: {
6808
7126
  file: evidenceFile,
6809
7127
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -6818,15 +7136,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6818
7136
  graph.addNode(portNode.id, portNode);
6819
7137
  nodesAdded++;
6820
7138
  }
6821
- const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types23.EdgeType.CONNECTS_TO);
7139
+ const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types24.EdgeType.CONNECTS_TO);
6822
7140
  if (graph.hasEdge(portEdgeId)) continue;
6823
7141
  const portEdge = {
6824
7142
  id: portEdgeId,
6825
7143
  source: fileNodeId,
6826
7144
  target: portNode.id,
6827
- type: import_types23.EdgeType.CONNECTS_TO,
6828
- provenance: import_types23.Provenance.EXTRACTED,
6829
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7145
+ type: import_types24.EdgeType.CONNECTS_TO,
7146
+ provenance: import_types24.Provenance.EXTRACTED,
7147
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
6830
7148
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
6831
7149
  };
6832
7150
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -6839,8 +7157,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6839
7157
  // src/extract/infra/terraform.ts
6840
7158
  init_cjs_shims();
6841
7159
  var import_node_fs17 = require("fs");
6842
- var import_node_path33 = __toESM(require("path"), 1);
6843
- var import_types24 = require("@neat.is/types");
7160
+ var import_node_path34 = __toESM(require("path"), 1);
7161
+ var import_types25 = require("@neat.is/types");
6844
7162
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
6845
7163
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
6846
7164
  async function walkTfFiles(start, depth = 0, max = 5) {
@@ -6850,11 +7168,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
6850
7168
  for (const entry of entries) {
6851
7169
  if (entry.isDirectory()) {
6852
7170
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
6853
- const child = import_node_path33.default.join(start, entry.name);
7171
+ const child = import_node_path34.default.join(start, entry.name);
6854
7172
  if (await isPythonVenvDir(child)) continue;
6855
7173
  out.push(...await walkTfFiles(child, depth + 1, max));
6856
7174
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
6857
- out.push(import_node_path33.default.join(start, entry.name));
7175
+ out.push(import_node_path34.default.join(start, entry.name));
6858
7176
  }
6859
7177
  }
6860
7178
  return out;
@@ -6886,7 +7204,7 @@ async function addTerraformResources(graph, scanPath) {
6886
7204
  const files = await walkTfFiles(scanPath);
6887
7205
  for (const file of files) {
6888
7206
  const content = await import_node_fs17.promises.readFile(file, "utf8");
6889
- const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, file));
7207
+ const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, file));
6890
7208
  const resources = [];
6891
7209
  const byKey = /* @__PURE__ */ new Map();
6892
7210
  RESOURCE_RE.lastIndex = 0;
@@ -6921,16 +7239,16 @@ async function addTerraformResources(graph, scanPath) {
6921
7239
  if (!target) continue;
6922
7240
  if (seen.has(target.nodeId)) continue;
6923
7241
  seen.add(target.nodeId);
6924
- const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types24.EdgeType.DEPENDS_ON);
7242
+ const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types25.EdgeType.DEPENDS_ON);
6925
7243
  if (graph.hasEdge(edgeId)) continue;
6926
7244
  const line = lineAt2(content, resource.bodyOffset + ref.index);
6927
7245
  const edge = {
6928
7246
  id: edgeId,
6929
7247
  source: resource.nodeId,
6930
7248
  target: target.nodeId,
6931
- type: import_types24.EdgeType.DEPENDS_ON,
6932
- provenance: import_types24.Provenance.EXTRACTED,
6933
- confidence: (0, import_types24.confidenceForExtracted)("structural"),
7249
+ type: import_types25.EdgeType.DEPENDS_ON,
7250
+ provenance: import_types25.Provenance.EXTRACTED,
7251
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
6934
7252
  evidence: { file: evidenceFile, line, snippet: key }
6935
7253
  };
6936
7254
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6944,7 +7262,7 @@ async function addTerraformResources(graph, scanPath) {
6944
7262
  // src/extract/infra/k8s.ts
6945
7263
  init_cjs_shims();
6946
7264
  var import_node_fs18 = require("fs");
6947
- var import_node_path34 = __toESM(require("path"), 1);
7265
+ var import_node_path35 = __toESM(require("path"), 1);
6948
7266
  var import_yaml3 = require("yaml");
6949
7267
  var K8S_KIND_TO_INFRA_KIND = {
6950
7268
  Service: "k8s-service",
@@ -6962,11 +7280,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
6962
7280
  for (const entry of entries) {
6963
7281
  if (entry.isDirectory()) {
6964
7282
  if (IGNORED_DIRS.has(entry.name)) continue;
6965
- const child = import_node_path34.default.join(start, entry.name);
7283
+ const child = import_node_path35.default.join(start, entry.name);
6966
7284
  if (await isPythonVenvDir(child)) continue;
6967
7285
  out.push(...await walkYamlFiles2(child, depth + 1, max));
6968
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path34.default.extname(entry.name))) {
6969
- out.push(import_node_path34.default.join(start, entry.name));
7286
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path35.default.extname(entry.name))) {
7287
+ out.push(import_node_path35.default.join(start, entry.name));
6970
7288
  }
6971
7289
  }
6972
7290
  return out;
@@ -7000,13 +7318,13 @@ async function addK8sResources(graph, scanPath) {
7000
7318
  // src/extract/infra/cloudflare.ts
7001
7319
  init_cjs_shims();
7002
7320
  var import_node_fs19 = require("fs");
7003
- var import_node_path35 = __toESM(require("path"), 1);
7321
+ var import_node_path36 = __toESM(require("path"), 1);
7004
7322
  var import_smol_toml2 = require("smol-toml");
7005
- var import_types25 = require("@neat.is/types");
7323
+ var import_types26 = require("@neat.is/types");
7006
7324
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
7007
7325
  async function readWranglerConfig(dir) {
7008
7326
  for (const filename of WRANGLER_FILENAMES) {
7009
- const abs = import_node_path35.default.join(dir, filename);
7327
+ const abs = import_node_path36.default.join(dir, filename);
7010
7328
  if (!await exists(abs)) continue;
7011
7329
  const raw = await import_node_fs19.promises.readFile(abs, "utf8");
7012
7330
  const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7050,8 +7368,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
7050
7368
  source: anchorId,
7051
7369
  target: node.id,
7052
7370
  type: edgeType,
7053
- provenance: import_types25.Provenance.EXTRACTED,
7054
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7371
+ provenance: import_types26.Provenance.EXTRACTED,
7372
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7055
7373
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
7056
7374
  };
7057
7375
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7069,11 +7387,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7069
7387
  try {
7070
7388
  read = await readWranglerConfig(service.dir);
7071
7389
  } catch (err) {
7072
- recordExtractionError("infra cloudflare", import_node_path35.default.relative(scanPath, service.dir), err);
7390
+ recordExtractionError("infra cloudflare", import_node_path36.default.relative(scanPath, service.dir), err);
7073
7391
  continue;
7074
7392
  }
7075
7393
  if (!read || !read.config.name) continue;
7076
- const evidenceFile = toPosix2(import_node_path35.default.relative(scanPath, import_node_path35.default.join(service.dir, read.relFile)));
7394
+ const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, import_node_path36.default.join(service.dir, read.relFile)));
7077
7395
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
7078
7396
  }
7079
7397
  for (const worker of discovered) {
@@ -7085,7 +7403,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7085
7403
  }
7086
7404
  let anchorId = service.node.id;
7087
7405
  if (config.main) {
7088
- const entryRelPath = toPosix2(import_node_path35.default.normalize(config.main));
7406
+ const entryRelPath = toPosix2(import_node_path36.default.normalize(config.main));
7089
7407
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7090
7408
  graph,
7091
7409
  service.pkg.name,
@@ -7112,15 +7430,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7112
7430
  nodesAdded++;
7113
7431
  }
7114
7432
  if (runtimeNode.id !== anchorId) {
7115
- const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types25.EdgeType.RUNS_ON);
7433
+ const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types26.EdgeType.RUNS_ON);
7116
7434
  if (!graph.hasEdge(runsOnId)) {
7117
7435
  const edge = {
7118
7436
  id: runsOnId,
7119
7437
  source: anchorId,
7120
7438
  target: runtimeNode.id,
7121
- type: import_types25.EdgeType.RUNS_ON,
7122
- provenance: import_types25.Provenance.EXTRACTED,
7123
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7439
+ type: import_types26.EdgeType.RUNS_ON,
7440
+ provenance: import_types26.Provenance.EXTRACTED,
7441
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7124
7442
  evidence: {
7125
7443
  file: evidenceFile,
7126
7444
  ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
@@ -7134,7 +7452,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7134
7452
  const result = addResourceEdge(
7135
7453
  graph,
7136
7454
  anchorId,
7137
- import_types25.EdgeType.CONNECTS_TO,
7455
+ import_types26.EdgeType.CONNECTS_TO,
7138
7456
  "cloudflare-route",
7139
7457
  route,
7140
7458
  evidenceFile,
@@ -7158,7 +7476,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7158
7476
  const result = addResourceEdge(
7159
7477
  graph,
7160
7478
  anchorId,
7161
- import_types25.EdgeType.DEPENDS_ON,
7479
+ import_types26.EdgeType.DEPENDS_ON,
7162
7480
  group.kind,
7163
7481
  name,
7164
7482
  evidenceFile,
@@ -7172,7 +7490,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7172
7490
  const result = addResourceEdge(
7173
7491
  graph,
7174
7492
  anchorId,
7175
- import_types25.EdgeType.DEPENDS_ON,
7493
+ import_types26.EdgeType.DEPENDS_ON,
7176
7494
  "cloudflare-cron",
7177
7495
  cron,
7178
7496
  evidenceFile,
@@ -7185,7 +7503,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7185
7503
  const result = addResourceEdge(
7186
7504
  graph,
7187
7505
  anchorId,
7188
- import_types25.EdgeType.DEPENDS_ON,
7506
+ import_types26.EdgeType.DEPENDS_ON,
7189
7507
  "cloudflare-env-var",
7190
7508
  varName,
7191
7509
  evidenceFile,
@@ -7198,15 +7516,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7198
7516
  if (!svc.service) continue;
7199
7517
  const target = workerIndex.get(svc.service);
7200
7518
  if (target && target.anchorId !== anchorId) {
7201
- const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types25.EdgeType.CALLS);
7519
+ const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types26.EdgeType.CALLS);
7202
7520
  if (!graph.hasEdge(edgeId)) {
7203
7521
  const edge = {
7204
7522
  id: edgeId,
7205
7523
  source: anchorId,
7206
7524
  target: target.anchorId,
7207
- type: import_types25.EdgeType.CALLS,
7208
- provenance: import_types25.Provenance.EXTRACTED,
7209
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7525
+ type: import_types26.EdgeType.CALLS,
7526
+ provenance: import_types26.Provenance.EXTRACTED,
7527
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7210
7528
  evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
7211
7529
  };
7212
7530
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7217,7 +7535,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7217
7535
  const result = addResourceEdge(
7218
7536
  graph,
7219
7537
  anchorId,
7220
- import_types25.EdgeType.DEPENDS_ON,
7538
+ import_types26.EdgeType.DEPENDS_ON,
7221
7539
  "cloudflare-service-binding",
7222
7540
  svc.service,
7223
7541
  evidenceFile,
@@ -7233,12 +7551,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7233
7551
  // src/extract/infra/vercel.ts
7234
7552
  init_cjs_shims();
7235
7553
  var import_node_fs20 = require("fs");
7236
- var import_node_path36 = __toESM(require("path"), 1);
7237
- var import_types26 = require("@neat.is/types");
7554
+ var import_node_path37 = __toESM(require("path"), 1);
7555
+ var import_types27 = require("@neat.is/types");
7238
7556
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
7239
7557
  async function readVercelConfig(dir) {
7240
7558
  for (const filename of VERCEL_CONFIG_FILENAMES) {
7241
- const abs = import_node_path36.default.join(dir, filename);
7559
+ const abs = import_node_path37.default.join(dir, filename);
7242
7560
  if (!await exists(abs)) continue;
7243
7561
  const raw = await import_node_fs20.promises.readFile(abs, "utf8");
7244
7562
  const config = JSON.parse(maskCommentsInSource(raw));
@@ -7247,7 +7565,7 @@ async function readVercelConfig(dir) {
7247
7565
  return null;
7248
7566
  }
7249
7567
  async function readLinkedProjectName(dir) {
7250
- const abs = import_node_path36.default.join(dir, ".vercel", "project.json");
7568
+ const abs = import_node_path37.default.join(dir, ".vercel", "project.json");
7251
7569
  if (!await exists(abs)) return void 0;
7252
7570
  const parsed = JSON.parse(await import_node_fs20.promises.readFile(abs, "utf8"));
7253
7571
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
@@ -7265,7 +7583,7 @@ async function addVercelServices(graph, services, scanPath) {
7265
7583
  read = await readVercelConfig(service.dir);
7266
7584
  projectName = await readLinkedProjectName(service.dir);
7267
7585
  } catch (err) {
7268
- recordExtractionError("infra vercel", import_node_path36.default.relative(scanPath, service.dir), err);
7586
+ recordExtractionError("infra vercel", import_node_path37.default.relative(scanPath, service.dir), err);
7269
7587
  continue;
7270
7588
  }
7271
7589
  if (!read && !projectName) continue;
@@ -7281,7 +7599,7 @@ async function addVercelServices(graph, services, scanPath) {
7281
7599
  const anchorId = service.node.id;
7282
7600
  if (!read) continue;
7283
7601
  const { config, relFile, raw } = read;
7284
- const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, import_node_path36.default.join(service.dir, relFile)));
7602
+ const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
7285
7603
  const add = (edgeType, kind, name) => {
7286
7604
  if (!name) return;
7287
7605
  const result = emitPlatformResourceEdge(
@@ -7297,12 +7615,12 @@ async function addVercelServices(graph, services, scanPath) {
7297
7615
  nodesAdded += result.nodesAdded;
7298
7616
  edgesAdded += result.edgesAdded;
7299
7617
  };
7300
- add(import_types26.EdgeType.RUNS_ON, "vercel", "vercel");
7301
- for (const cron of config.crons ?? []) add(import_types26.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7302
- for (const varName of Object.keys(config.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7303
- for (const varName of Object.keys(config.build?.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7618
+ add(import_types27.EdgeType.RUNS_ON, "vercel", "vercel");
7619
+ for (const cron of config.crons ?? []) add(import_types27.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7620
+ for (const varName of Object.keys(config.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7621
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7304
7622
  for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
7305
- add(import_types26.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7623
+ add(import_types27.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7306
7624
  }
7307
7625
  }
7308
7626
  return { nodesAdded, edgesAdded };
@@ -7311,13 +7629,13 @@ async function addVercelServices(graph, services, scanPath) {
7311
7629
  // src/extract/infra/railway.ts
7312
7630
  init_cjs_shims();
7313
7631
  var import_node_fs21 = require("fs");
7314
- var import_node_path37 = __toESM(require("path"), 1);
7632
+ var import_node_path38 = __toESM(require("path"), 1);
7315
7633
  var import_smol_toml3 = require("smol-toml");
7316
- var import_types27 = require("@neat.is/types");
7634
+ var import_types28 = require("@neat.is/types");
7317
7635
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
7318
7636
  async function readRailwayConfig(dir) {
7319
7637
  for (const filename of RAILWAY_FILENAMES) {
7320
- const abs = import_node_path37.default.join(dir, filename);
7638
+ const abs = import_node_path38.default.join(dir, filename);
7321
7639
  if (!await exists(abs)) continue;
7322
7640
  const raw = await import_node_fs21.promises.readFile(abs, "utf8");
7323
7641
  const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7333,7 +7651,7 @@ async function addRailwayServices(graph, services, scanPath) {
7333
7651
  try {
7334
7652
  read = await readRailwayConfig(service.dir);
7335
7653
  } catch (err) {
7336
- recordExtractionError("infra railway", import_node_path37.default.relative(scanPath, service.dir), err);
7654
+ recordExtractionError("infra railway", import_node_path38.default.relative(scanPath, service.dir), err);
7337
7655
  continue;
7338
7656
  }
7339
7657
  if (!read) continue;
@@ -7343,7 +7661,7 @@ async function addRailwayServices(graph, services, scanPath) {
7343
7661
  }
7344
7662
  const anchorId = service.node.id;
7345
7663
  const { config, relFile, raw } = read;
7346
- const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
7664
+ const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7347
7665
  const add = (edgeType, kind, name) => {
7348
7666
  if (!name) return;
7349
7667
  const result = emitPlatformResourceEdge(
@@ -7359,9 +7677,9 @@ async function addRailwayServices(graph, services, scanPath) {
7359
7677
  nodesAdded += result.nodesAdded;
7360
7678
  edgesAdded += result.edgesAdded;
7361
7679
  };
7362
- add(import_types27.EdgeType.RUNS_ON, "railway", "railway");
7363
- add(import_types27.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7364
- add(import_types27.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7680
+ add(import_types28.EdgeType.RUNS_ON, "railway", "railway");
7681
+ add(import_types28.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7682
+ add(import_types28.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7365
7683
  }
7366
7684
  return { nodesAdded, edgesAdded };
7367
7685
  }
@@ -7369,12 +7687,12 @@ async function addRailwayServices(graph, services, scanPath) {
7369
7687
  // src/extract/infra/supabase.ts
7370
7688
  init_cjs_shims();
7371
7689
  var import_node_fs22 = require("fs");
7372
- var import_node_path38 = __toESM(require("path"), 1);
7690
+ var import_node_path39 = __toESM(require("path"), 1);
7373
7691
  var import_smol_toml4 = require("smol-toml");
7374
- var import_types28 = require("@neat.is/types");
7692
+ var import_types29 = require("@neat.is/types");
7375
7693
  async function readSupabaseConfig(dir) {
7376
- const relFile = import_node_path38.default.join("supabase", "config.toml");
7377
- const abs = import_node_path38.default.join(dir, relFile);
7694
+ const relFile = import_node_path39.default.join("supabase", "config.toml");
7695
+ const abs = import_node_path39.default.join(dir, relFile);
7378
7696
  if (!await exists(abs)) return null;
7379
7697
  const raw = await import_node_fs22.promises.readFile(abs, "utf8");
7380
7698
  const config = (0, import_smol_toml4.parse)(raw);
@@ -7388,7 +7706,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7388
7706
  try {
7389
7707
  read = await readSupabaseConfig(service.dir);
7390
7708
  } catch (err) {
7391
- recordExtractionError("infra supabase", import_node_path38.default.relative(scanPath, service.dir), err);
7709
+ recordExtractionError("infra supabase", import_node_path39.default.relative(scanPath, service.dir), err);
7392
7710
  continue;
7393
7711
  }
7394
7712
  if (!read) continue;
@@ -7403,7 +7721,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7403
7721
  });
7404
7722
  }
7405
7723
  const anchorId = service.node.id;
7406
- const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7724
+ const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
7407
7725
  const add = (edgeType, kind, name) => {
7408
7726
  if (!name) return;
7409
7727
  const result = emitPlatformResourceEdge(
@@ -7419,10 +7737,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
7419
7737
  nodesAdded += result.nodesAdded;
7420
7738
  edgesAdded += result.edgesAdded;
7421
7739
  };
7422
- add(import_types28.EdgeType.RUNS_ON, "supabase", "supabase");
7423
- for (const fn of Object.keys(config.functions ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "supabase-function", fn);
7424
- if (config.storage) add(import_types28.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7425
- if (config.auth) add(import_types28.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7740
+ add(import_types29.EdgeType.RUNS_ON, "supabase", "supabase");
7741
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types29.EdgeType.DEPENDS_ON, "supabase-function", fn);
7742
+ if (config.storage) add(import_types29.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7743
+ if (config.auth) add(import_types29.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7426
7744
  }
7427
7745
  return { nodesAdded, edgesAdded };
7428
7746
  }
@@ -7444,17 +7762,17 @@ async function addInfra(graph, scanPath, services) {
7444
7762
  }
7445
7763
 
7446
7764
  // src/extract/index.ts
7447
- var import_node_path40 = __toESM(require("path"), 1);
7765
+ var import_node_path41 = __toESM(require("path"), 1);
7448
7766
 
7449
7767
  // src/extract/retire.ts
7450
7768
  init_cjs_shims();
7451
7769
  var import_node_fs23 = require("fs");
7452
- var import_node_path39 = __toESM(require("path"), 1);
7453
- var import_types29 = require("@neat.is/types");
7770
+ var import_node_path40 = __toESM(require("path"), 1);
7771
+ var import_types30 = require("@neat.is/types");
7454
7772
  function dropOrphanedFileNodes(graph) {
7455
7773
  const orphans = [];
7456
7774
  graph.forEachNode((id, attrs) => {
7457
- if (attrs.type !== import_types29.NodeType.FileNode) return;
7775
+ if (attrs.type !== import_types30.NodeType.FileNode) return;
7458
7776
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
7459
7777
  orphans.push(id);
7460
7778
  }
@@ -7467,14 +7785,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
7467
7785
  const bases = [scanPath, ...serviceDirs];
7468
7786
  graph.forEachEdge((id, attrs) => {
7469
7787
  const edge = attrs;
7470
- if (edge.provenance !== import_types29.Provenance.EXTRACTED) return;
7788
+ if (edge.provenance !== import_types30.Provenance.EXTRACTED) return;
7471
7789
  const evidenceFile = edge.evidence?.file;
7472
7790
  if (!evidenceFile) return;
7473
- if (import_node_path39.default.isAbsolute(evidenceFile)) {
7791
+ if (import_node_path40.default.isAbsolute(evidenceFile)) {
7474
7792
  if (!(0, import_node_fs23.existsSync)(evidenceFile)) toDrop.push(id);
7475
7793
  return;
7476
7794
  }
7477
- const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path39.default.join(base, evidenceFile)));
7795
+ const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path40.default.join(base, evidenceFile)));
7478
7796
  if (!found) toDrop.push(id);
7479
7797
  });
7480
7798
  for (const id of toDrop) graph.dropEdge(id);
@@ -7516,7 +7834,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7516
7834
  }
7517
7835
  const droppedEntries = drainDroppedExtracted();
7518
7836
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
7519
- const rejectedPath = import_node_path40.default.join(import_node_path40.default.dirname(opts.errorsPath), "rejected.ndjson");
7837
+ const rejectedPath = import_node_path41.default.join(import_node_path41.default.dirname(opts.errorsPath), "rejected.ndjson");
7520
7838
  try {
7521
7839
  await writeRejectedExtracted(droppedEntries, rejectedPath);
7522
7840
  } catch (err) {
@@ -7551,8 +7869,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7551
7869
  // src/persist.ts
7552
7870
  init_cjs_shims();
7553
7871
  var import_node_fs24 = require("fs");
7554
- var import_node_path41 = __toESM(require("path"), 1);
7555
- var import_types30 = require("@neat.is/types");
7872
+ var import_node_path42 = __toESM(require("path"), 1);
7873
+ var import_types31 = require("@neat.is/types");
7556
7874
  var SCHEMA_VERSION = 4;
7557
7875
  function migrateV1ToV2(payload) {
7558
7876
  const nodes = payload.graph.nodes;
@@ -7574,12 +7892,12 @@ function migrateV2ToV3(payload) {
7574
7892
  for (const edge of edges) {
7575
7893
  const attrs = edge.attributes;
7576
7894
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
7577
- attrs.provenance = import_types30.Provenance.OBSERVED;
7895
+ attrs.provenance = import_types31.Provenance.OBSERVED;
7578
7896
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
7579
7897
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
7580
7898
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
7581
7899
  if (type && source && target) {
7582
- const newId = (0, import_types30.observedEdgeId)(source, target, type);
7900
+ const newId = (0, import_types31.observedEdgeId)(source, target, type);
7583
7901
  attrs.id = newId;
7584
7902
  if (edge.key) edge.key = newId;
7585
7903
  }
@@ -7588,7 +7906,7 @@ function migrateV2ToV3(payload) {
7588
7906
  return { ...payload, schemaVersion: 3 };
7589
7907
  }
7590
7908
  async function ensureDir(filePath) {
7591
- await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(filePath), { recursive: true });
7909
+ await import_node_fs24.promises.mkdir(import_node_path42.default.dirname(filePath), { recursive: true });
7592
7910
  }
7593
7911
  async function saveGraphToDisk(graph, outPath) {
7594
7912
  await ensureDir(outPath);
@@ -7671,19 +7989,19 @@ function startPersistLoop(graph, outPath, opts = {}) {
7671
7989
  init_cjs_shims();
7672
7990
  var import_fastify = __toESM(require("fastify"), 1);
7673
7991
  var import_cors = __toESM(require("@fastify/cors"), 1);
7674
- var import_types33 = require("@neat.is/types");
7992
+ var import_types34 = require("@neat.is/types");
7675
7993
 
7676
7994
  // src/extend/index.ts
7677
7995
  init_cjs_shims();
7678
7996
  var import_node_fs26 = require("fs");
7679
- var import_node_path43 = __toESM(require("path"), 1);
7997
+ var import_node_path44 = __toESM(require("path"), 1);
7680
7998
  var import_node_os2 = __toESM(require("os"), 1);
7681
7999
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
7682
8000
 
7683
8001
  // src/installers/package-manager.ts
7684
8002
  init_cjs_shims();
7685
8003
  var import_node_fs25 = require("fs");
7686
- var import_node_path42 = __toESM(require("path"), 1);
8004
+ var import_node_path43 = __toESM(require("path"), 1);
7687
8005
  var import_node_child_process = require("child_process");
7688
8006
  var LOCKFILE_PRIORITY = [
7689
8007
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -7705,22 +8023,22 @@ async function exists2(p) {
7705
8023
  }
7706
8024
  }
7707
8025
  async function detectPackageManager(serviceDir) {
7708
- let dir = import_node_path42.default.resolve(serviceDir);
8026
+ let dir = import_node_path43.default.resolve(serviceDir);
7709
8027
  const stops = /* @__PURE__ */ new Set();
7710
8028
  for (let i = 0; i < 64; i++) {
7711
8029
  if (stops.has(dir)) break;
7712
8030
  stops.add(dir);
7713
8031
  for (const candidate of LOCKFILE_PRIORITY) {
7714
- const lockPath = import_node_path42.default.join(dir, candidate.lockfile);
8032
+ const lockPath = import_node_path43.default.join(dir, candidate.lockfile);
7715
8033
  if (await exists2(lockPath)) {
7716
8034
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
7717
8035
  }
7718
8036
  }
7719
- const parent = import_node_path42.default.dirname(dir);
8037
+ const parent = import_node_path43.default.dirname(dir);
7720
8038
  if (parent === dir) break;
7721
8039
  dir = parent;
7722
8040
  }
7723
- return { pm: "npm", cwd: import_node_path42.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8041
+ return { pm: "npm", cwd: import_node_path43.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7724
8042
  }
7725
8043
  async function runPackageManagerInstall(cmd) {
7726
8044
  return new Promise((resolve) => {
@@ -7769,7 +8087,7 @@ async function fileExists2(p) {
7769
8087
  }
7770
8088
  }
7771
8089
  async function readPackageJson(scanPath) {
7772
- const pkgPath = import_node_path43.default.join(scanPath, "package.json");
8090
+ const pkgPath = import_node_path44.default.join(scanPath, "package.json");
7773
8091
  const raw = await import_node_fs26.promises.readFile(pkgPath, "utf8");
7774
8092
  return JSON.parse(raw);
7775
8093
  }
@@ -7788,11 +8106,11 @@ async function findHookFiles(scanPath) {
7788
8106
  for (const entry of entries) {
7789
8107
  if (entry.isDirectory()) {
7790
8108
  if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
7791
- await walk3(import_node_path43.default.join(dir, entry.name));
8109
+ await walk3(import_node_path44.default.join(dir, entry.name));
7792
8110
  } else if (entry.isFile()) {
7793
8111
  if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
7794
- const rel = import_node_path43.default.relative(scanPath, import_node_path43.default.join(dir, entry.name));
7795
- found.push(rel.split(import_node_path43.default.sep).join("/"));
8112
+ const rel = import_node_path44.default.relative(scanPath, import_node_path44.default.join(dir, entry.name));
8113
+ found.push(rel.split(import_node_path44.default.sep).join("/"));
7796
8114
  }
7797
8115
  }
7798
8116
  }
@@ -7803,7 +8121,7 @@ async function findHookFiles(scanPath) {
7803
8121
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
7804
8122
  let fallback = null;
7805
8123
  for (const file of hookFiles) {
7806
- const content = await import_node_fs26.promises.readFile(import_node_path43.default.join(scanPath, file), "utf8");
8124
+ const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(scanPath, file), "utf8");
7807
8125
  const patched = splicedContent(content, snippet2);
7808
8126
  if (patched !== null) return { file, content, patched };
7809
8127
  if (fallback === null) fallback = { file, content };
@@ -7811,11 +8129,11 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
7811
8129
  return { file: fallback.file, content: fallback.content, patched: null };
7812
8130
  }
7813
8131
  function extendLogPath() {
7814
- return process.env.NEAT_EXTEND_LOG ?? import_node_path43.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
8132
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path44.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
7815
8133
  }
7816
8134
  async function appendExtendLog(entry) {
7817
8135
  const logPath = extendLogPath();
7818
- await import_node_fs26.promises.mkdir(import_node_path43.default.dirname(logPath), { recursive: true });
8136
+ await import_node_fs26.promises.mkdir(import_node_path44.default.dirname(logPath), { recursive: true });
7819
8137
  await import_node_fs26.promises.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
7820
8138
  }
7821
8139
  function splicedContent(fileContent, snippet2) {
@@ -7874,7 +8192,7 @@ function lookupInstrumentation(library, installedVersion) {
7874
8192
  }
7875
8193
  async function describeProjectInstrumentation(ctx) {
7876
8194
  const hookFiles = await findHookFiles(ctx.scanPath);
7877
- const envNeat = await fileExists2(import_node_path43.default.join(ctx.scanPath, ".env.neat"));
8195
+ const envNeat = await fileExists2(import_node_path44.default.join(ctx.scanPath, ".env.neat"));
7878
8196
  const registryInstrPackages = new Set(
7879
8197
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
7880
8198
  );
@@ -7896,7 +8214,7 @@ async function applyExtension(ctx, args, options) {
7896
8214
  );
7897
8215
  }
7898
8216
  for (const file of hookFiles) {
7899
- const content = await import_node_fs26.promises.readFile(import_node_path43.default.join(ctx.scanPath, file), "utf8");
8217
+ const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
7900
8218
  if (content.includes(args.registration_snippet)) {
7901
8219
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
7902
8220
  }
@@ -7908,10 +8226,10 @@ async function applyExtension(ctx, args, options) {
7908
8226
  );
7909
8227
  }
7910
8228
  const primaryFile = primary.file;
7911
- const primaryPath = import_node_path43.default.join(ctx.scanPath, primaryFile);
8229
+ const primaryPath = import_node_path44.default.join(ctx.scanPath, primaryFile);
7912
8230
  const filesTouched = [];
7913
8231
  const depsAdded = [];
7914
- const pkgPath = import_node_path43.default.join(ctx.scanPath, "package.json");
8232
+ const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
7915
8233
  const pkg = await readPackageJson(ctx.scanPath);
7916
8234
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
7917
8235
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
@@ -7950,7 +8268,7 @@ async function dryRunExtension(ctx, args) {
7950
8268
  };
7951
8269
  }
7952
8270
  for (const file of hookFiles) {
7953
- const content = await import_node_fs26.promises.readFile(import_node_path43.default.join(ctx.scanPath, file), "utf8");
8271
+ const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
7954
8272
  if (content.includes(args.registration_snippet)) {
7955
8273
  return {
7956
8274
  library: args.library,
@@ -7991,7 +8309,7 @@ async function rollbackExtension(ctx, args) {
7991
8309
  if (!match) {
7992
8310
  return { undone: false, message: "no apply found for library" };
7993
8311
  }
7994
- const pkgPath = import_node_path43.default.join(ctx.scanPath, "package.json");
8312
+ const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
7995
8313
  if (await fileExists2(pkgPath)) {
7996
8314
  const pkg = await readPackageJson(ctx.scanPath);
7997
8315
  if (pkg.dependencies?.[match.instrumentation_package]) {
@@ -8002,7 +8320,7 @@ async function rollbackExtension(ctx, args) {
8002
8320
  }
8003
8321
  const hookFiles = await findHookFiles(ctx.scanPath);
8004
8322
  for (const file of hookFiles) {
8005
- const filePath = import_node_path43.default.join(ctx.scanPath, file);
8323
+ const filePath = import_node_path44.default.join(ctx.scanPath, file);
8006
8324
  const content = await import_node_fs26.promises.readFile(filePath, "utf8");
8007
8325
  if (content.includes(match.registration_snippet)) {
8008
8326
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
@@ -8018,39 +8336,39 @@ async function rollbackExtension(ctx, args) {
8018
8336
 
8019
8337
  // src/divergences.ts
8020
8338
  init_cjs_shims();
8021
- var import_types31 = require("@neat.is/types");
8339
+ var import_types32 = require("@neat.is/types");
8022
8340
  function bucketKey(source, target, type) {
8023
8341
  return `${type}|${source}|${target}`;
8024
8342
  }
8025
8343
  function bucketSourceFor(graph, edge) {
8026
- if (edge.type !== import_types31.EdgeType.CONNECTS_TO) return edge.source;
8027
- const parsed = (0, import_types31.parseFileId)(edge.source);
8344
+ if (edge.type !== import_types32.EdgeType.CONNECTS_TO) return edge.source;
8345
+ const parsed = (0, import_types32.parseFileId)(edge.source);
8028
8346
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
8029
8347
  const target = graph.getNodeAttributes(edge.target);
8030
- if (target.type !== import_types31.NodeType.DatabaseNode) return edge.source;
8031
- return (0, import_types31.serviceId)(parsed.service);
8348
+ if (target.type !== import_types32.NodeType.DatabaseNode) return edge.source;
8349
+ return (0, import_types32.serviceId)(parsed.service);
8032
8350
  }
8033
8351
  function bucketEdges(graph) {
8034
8352
  const buckets2 = /* @__PURE__ */ new Map();
8035
8353
  graph.forEachEdge((id, attrs) => {
8036
8354
  const e = attrs;
8037
- const parsed = (0, import_types31.parseEdgeId)(id);
8355
+ const parsed = (0, import_types32.parseEdgeId)(id);
8038
8356
  const provenance = parsed?.provenance ?? e.provenance;
8039
8357
  const source = bucketSourceFor(graph, e);
8040
8358
  const key = bucketKey(source, e.target, e.type);
8041
8359
  const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
8042
8360
  switch (provenance) {
8043
- case import_types31.Provenance.EXTRACTED:
8361
+ case import_types32.Provenance.EXTRACTED:
8044
8362
  cur.extracted = e;
8045
8363
  break;
8046
- case import_types31.Provenance.OBSERVED:
8364
+ case import_types32.Provenance.OBSERVED:
8047
8365
  cur.observed = e;
8048
8366
  break;
8049
- case import_types31.Provenance.INFERRED:
8367
+ case import_types32.Provenance.INFERRED:
8050
8368
  cur.inferred = e;
8051
8369
  break;
8052
8370
  default:
8053
- if (e.provenance === import_types31.Provenance.STALE) cur.stale = e;
8371
+ if (e.provenance === import_types32.Provenance.STALE) cur.stale = e;
8054
8372
  }
8055
8373
  buckets2.set(key, cur);
8056
8374
  });
@@ -8059,12 +8377,12 @@ function bucketEdges(graph) {
8059
8377
  function nodeIsFrontier(graph, nodeId) {
8060
8378
  if (!graph.hasNode(nodeId)) return false;
8061
8379
  const attrs = graph.getNodeAttributes(nodeId);
8062
- return attrs.type === import_types31.NodeType.FrontierNode;
8380
+ return attrs.type === import_types32.NodeType.FrontierNode;
8063
8381
  }
8064
8382
  function nodeIsWebsocketChannel(graph, nodeId) {
8065
8383
  if (!graph.hasNode(nodeId)) return false;
8066
8384
  const attrs = graph.getNodeAttributes(nodeId);
8067
- return attrs.type === import_types31.NodeType.WebSocketChannelNode;
8385
+ return attrs.type === import_types32.NodeType.WebSocketChannelNode;
8068
8386
  }
8069
8387
  function clampConfidence(n) {
8070
8388
  if (!Number.isFinite(n)) return 0;
@@ -8084,14 +8402,14 @@ function gradedConfidence(edge) {
8084
8402
  return clampConfidence(confidenceForEdge(edge));
8085
8403
  }
8086
8404
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
8087
- import_types31.EdgeType.CALLS,
8088
- import_types31.EdgeType.CONNECTS_TO,
8089
- import_types31.EdgeType.PUBLISHES_TO,
8090
- import_types31.EdgeType.CONSUMES_FROM
8405
+ import_types32.EdgeType.CALLS,
8406
+ import_types32.EdgeType.CONNECTS_TO,
8407
+ import_types32.EdgeType.PUBLISHES_TO,
8408
+ import_types32.EdgeType.CONSUMES_FROM
8091
8409
  ]);
8092
8410
  function detectMissingDivergences(graph, bucket) {
8093
8411
  const out = [];
8094
- if (bucket.type === import_types31.EdgeType.CONTAINS) return out;
8412
+ if (bucket.type === import_types32.EdgeType.CONTAINS) return out;
8095
8413
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
8096
8414
  if (!nodeIsFrontier(graph, bucket.target)) {
8097
8415
  out.push({
@@ -8132,7 +8450,7 @@ function declaredHostFor(svc) {
8132
8450
  function hasExtractedConfiguredBy(graph, svcId) {
8133
8451
  for (const edgeId of graph.outboundEdges(svcId)) {
8134
8452
  const e = graph.getEdgeAttributes(edgeId);
8135
- if (e.type === import_types31.EdgeType.CONFIGURED_BY && e.provenance === import_types31.Provenance.EXTRACTED) {
8453
+ if (e.type === import_types32.EdgeType.CONFIGURED_BY && e.provenance === import_types32.Provenance.EXTRACTED) {
8136
8454
  return true;
8137
8455
  }
8138
8456
  }
@@ -8145,10 +8463,10 @@ function detectHostMismatch(graph, svcId, svc) {
8145
8463
  const out = [];
8146
8464
  for (const edgeId of graph.outboundEdges(svcId)) {
8147
8465
  const edge = graph.getEdgeAttributes(edgeId);
8148
- if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8149
- if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
8466
+ if (edge.type !== import_types32.EdgeType.CONNECTS_TO) continue;
8467
+ if (edge.provenance !== import_types32.Provenance.OBSERVED) continue;
8150
8468
  const target = graph.getNodeAttributes(edge.target);
8151
- if (target.type !== import_types31.NodeType.DatabaseNode) continue;
8469
+ if (target.type !== import_types32.NodeType.DatabaseNode) continue;
8152
8470
  const observedHost = target.host?.trim();
8153
8471
  if (!observedHost) continue;
8154
8472
  if (observedHost === declaredHost) continue;
@@ -8170,10 +8488,10 @@ function detectCompatDivergences(graph, svcId, svc) {
8170
8488
  const deps = svc.dependencies ?? {};
8171
8489
  for (const edgeId of graph.outboundEdges(svcId)) {
8172
8490
  const edge = graph.getEdgeAttributes(edgeId);
8173
- if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8174
- if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
8491
+ if (edge.type !== import_types32.EdgeType.CONNECTS_TO) continue;
8492
+ if (edge.provenance !== import_types32.Provenance.OBSERVED) continue;
8175
8493
  const target = graph.getNodeAttributes(edge.target);
8176
- if (target.type !== import_types31.NodeType.DatabaseNode) continue;
8494
+ if (target.type !== import_types32.NodeType.DatabaseNode) continue;
8177
8495
  for (const pair of compatPairs()) {
8178
8496
  if (pair.engine !== target.engine) continue;
8179
8497
  const declared = deps[pair.driver];
@@ -8232,7 +8550,7 @@ function suppressHostMismatchHalves(all) {
8232
8550
  for (const d of all) {
8233
8551
  if (d.type !== "host-mismatch") continue;
8234
8552
  observedHalf.add(`${d.source}->${d.target}`);
8235
- declaredHalf.add((0, import_types31.databaseId)(d.extractedHost));
8553
+ declaredHalf.add((0, import_types32.databaseId)(d.extractedHost));
8236
8554
  }
8237
8555
  if (observedHalf.size === 0) return all;
8238
8556
  return all.filter((d) => {
@@ -8251,7 +8569,7 @@ function computeDivergences(graph, opts = {}) {
8251
8569
  }
8252
8570
  graph.forEachNode((nodeId, attrs) => {
8253
8571
  const n = attrs;
8254
- if (n.type !== import_types31.NodeType.ServiceNode) return;
8572
+ if (n.type !== import_types32.NodeType.ServiceNode) return;
8255
8573
  const svc = n;
8256
8574
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
8257
8575
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -8285,7 +8603,7 @@ function computeDivergences(graph, opts = {}) {
8285
8603
  if (a.source !== b.source) return a.source.localeCompare(b.source);
8286
8604
  return a.target.localeCompare(b.target);
8287
8605
  });
8288
- return import_types31.DivergenceResultSchema.parse({
8606
+ return import_types32.DivergenceResultSchema.parse({
8289
8607
  divergences: filtered,
8290
8608
  totalAffected: filtered.length,
8291
8609
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -8416,23 +8734,23 @@ function canonicalJson(value) {
8416
8734
 
8417
8735
  // src/projects.ts
8418
8736
  init_cjs_shims();
8419
- var import_node_path44 = __toESM(require("path"), 1);
8737
+ var import_node_path45 = __toESM(require("path"), 1);
8420
8738
  function pathsForProject(project, baseDir) {
8421
8739
  if (project === DEFAULT_PROJECT) {
8422
8740
  return {
8423
- snapshotPath: import_node_path44.default.join(baseDir, "graph.json"),
8424
- errorsPath: import_node_path44.default.join(baseDir, "errors.ndjson"),
8425
- staleEventsPath: import_node_path44.default.join(baseDir, "stale-events.ndjson"),
8426
- embeddingsCachePath: import_node_path44.default.join(baseDir, "embeddings.json"),
8427
- policyViolationsPath: import_node_path44.default.join(baseDir, "policy-violations.ndjson")
8741
+ snapshotPath: import_node_path45.default.join(baseDir, "graph.json"),
8742
+ errorsPath: import_node_path45.default.join(baseDir, "errors.ndjson"),
8743
+ staleEventsPath: import_node_path45.default.join(baseDir, "stale-events.ndjson"),
8744
+ embeddingsCachePath: import_node_path45.default.join(baseDir, "embeddings.json"),
8745
+ policyViolationsPath: import_node_path45.default.join(baseDir, "policy-violations.ndjson")
8428
8746
  };
8429
8747
  }
8430
8748
  return {
8431
- snapshotPath: import_node_path44.default.join(baseDir, `${project}.json`),
8432
- errorsPath: import_node_path44.default.join(baseDir, `errors.${project}.ndjson`),
8433
- staleEventsPath: import_node_path44.default.join(baseDir, `stale-events.${project}.ndjson`),
8434
- embeddingsCachePath: import_node_path44.default.join(baseDir, `embeddings.${project}.json`),
8435
- policyViolationsPath: import_node_path44.default.join(baseDir, `policy-violations.${project}.ndjson`)
8749
+ snapshotPath: import_node_path45.default.join(baseDir, `${project}.json`),
8750
+ errorsPath: import_node_path45.default.join(baseDir, `errors.${project}.ndjson`),
8751
+ staleEventsPath: import_node_path45.default.join(baseDir, `stale-events.${project}.ndjson`),
8752
+ embeddingsCachePath: import_node_path45.default.join(baseDir, `embeddings.${project}.json`),
8753
+ policyViolationsPath: import_node_path45.default.join(baseDir, `policy-violations.${project}.ndjson`)
8436
8754
  };
8437
8755
  }
8438
8756
  var Projects = class {
@@ -8470,23 +8788,23 @@ var Projects = class {
8470
8788
  init_cjs_shims();
8471
8789
  var import_node_fs28 = require("fs");
8472
8790
  var import_node_os3 = __toESM(require("os"), 1);
8473
- var import_node_path45 = __toESM(require("path"), 1);
8474
- var import_types32 = require("@neat.is/types");
8791
+ var import_node_path46 = __toESM(require("path"), 1);
8792
+ var import_types33 = require("@neat.is/types");
8475
8793
  var LOCK_TIMEOUT_MS = 5e3;
8476
8794
  var LOCK_RETRY_MS = 50;
8477
8795
  function neatHome() {
8478
8796
  const override = process.env.NEAT_HOME;
8479
- if (override && override.length > 0) return import_node_path45.default.resolve(override);
8480
- return import_node_path45.default.join(import_node_os3.default.homedir(), ".neat");
8797
+ if (override && override.length > 0) return import_node_path46.default.resolve(override);
8798
+ return import_node_path46.default.join(import_node_os3.default.homedir(), ".neat");
8481
8799
  }
8482
8800
  function registryPath() {
8483
- return import_node_path45.default.join(neatHome(), "projects.json");
8801
+ return import_node_path46.default.join(neatHome(), "projects.json");
8484
8802
  }
8485
8803
  function registryLockPath() {
8486
- return import_node_path45.default.join(neatHome(), "projects.json.lock");
8804
+ return import_node_path46.default.join(neatHome(), "projects.json.lock");
8487
8805
  }
8488
8806
  function daemonPidPath() {
8489
- return import_node_path45.default.join(neatHome(), "neatd.pid");
8807
+ return import_node_path46.default.join(neatHome(), "neatd.pid");
8490
8808
  }
8491
8809
  function isPidAliveDefault(pid) {
8492
8810
  try {
@@ -8546,7 +8864,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
8546
8864
  }
8547
8865
  }
8548
8866
  async function normalizeProjectPath(input) {
8549
- const resolved = import_node_path45.default.resolve(input);
8867
+ const resolved = import_node_path46.default.resolve(input);
8550
8868
  try {
8551
8869
  return await import_node_fs28.promises.realpath(resolved);
8552
8870
  } catch {
@@ -8554,7 +8872,7 @@ async function normalizeProjectPath(input) {
8554
8872
  }
8555
8873
  }
8556
8874
  async function writeAtomically(target, contents) {
8557
- await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(target), { recursive: true });
8875
+ await import_node_fs28.promises.mkdir(import_node_path46.default.dirname(target), { recursive: true });
8558
8876
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
8559
8877
  const fd = await import_node_fs28.promises.open(tmp, "w");
8560
8878
  try {
@@ -8567,7 +8885,7 @@ async function writeAtomically(target, contents) {
8567
8885
  }
8568
8886
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
8569
8887
  const deadline = Date.now() + timeoutMs;
8570
- await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(lockPath), { recursive: true });
8888
+ await import_node_fs28.promises.mkdir(import_node_path46.default.dirname(lockPath), { recursive: true });
8571
8889
  let probedHolder = false;
8572
8890
  while (true) {
8573
8891
  try {
@@ -8620,10 +8938,10 @@ async function readRegistry() {
8620
8938
  throw err;
8621
8939
  }
8622
8940
  const parsed = JSON.parse(raw);
8623
- return import_types32.RegistryFileSchema.parse(parsed);
8941
+ return import_types33.RegistryFileSchema.parse(parsed);
8624
8942
  }
8625
8943
  async function writeRegistry(reg) {
8626
- const validated = import_types32.RegistryFileSchema.parse(reg);
8944
+ const validated = import_types33.RegistryFileSchema.parse(reg);
8627
8945
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
8628
8946
  }
8629
8947
  var ProjectNameCollisionError = class extends Error {
@@ -8818,7 +9136,7 @@ init_auth();
8818
9136
  // src/connectors-config.ts
8819
9137
  init_cjs_shims();
8820
9138
  var import_node_os4 = __toESM(require("os"), 1);
8821
- var import_node_path46 = __toESM(require("path"), 1);
9139
+ var import_node_path47 = __toESM(require("path"), 1);
8822
9140
  var import_node_fs29 = require("fs");
8823
9141
  var CONNECTORS_CONFIG_VERSION = 1;
8824
9142
  var EnvRefUnsetError = class extends Error {
@@ -8833,11 +9151,11 @@ var EnvRefUnsetError = class extends Error {
8833
9151
  };
8834
9152
  function neatHome2() {
8835
9153
  const override = process.env.NEAT_HOME;
8836
- if (override && override.length > 0) return import_node_path46.default.resolve(override);
8837
- return import_node_path46.default.join(import_node_os4.default.homedir(), ".neat");
9154
+ if (override && override.length > 0) return import_node_path47.default.resolve(override);
9155
+ return import_node_path47.default.join(import_node_os4.default.homedir(), ".neat");
8838
9156
  }
8839
9157
  function connectorsConfigPath(home = neatHome2()) {
8840
- return import_node_path46.default.join(home, "connectors.json");
9158
+ return import_node_path47.default.join(home, "connectors.json");
8841
9159
  }
8842
9160
  var MODE_MASK_LOOSER_THAN_0600 = 63;
8843
9161
  async function warnIfModeLooserThan0600(file) {
@@ -9173,11 +9491,11 @@ function registerRoutes(scope, ctx) {
9173
9491
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
9174
9492
  const parsed = [];
9175
9493
  for (const c of candidates) {
9176
- const r = import_types33.DivergenceTypeSchema.safeParse(c);
9494
+ const r = import_types34.DivergenceTypeSchema.safeParse(c);
9177
9495
  if (!r.success) {
9178
9496
  return reply.code(400).send({
9179
9497
  error: `unknown divergence type "${c}"`,
9180
- allowed: import_types33.DivergenceTypeSchema.options
9498
+ allowed: import_types34.DivergenceTypeSchema.options
9181
9499
  });
9182
9500
  }
9183
9501
  parsed.push(r.data);
@@ -9448,7 +9766,7 @@ function registerRoutes(scope, ctx) {
9448
9766
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
9449
9767
  let violations = await log.readAll();
9450
9768
  if (req.query.severity) {
9451
- const sev = import_types33.PolicySeveritySchema.safeParse(req.query.severity);
9769
+ const sev = import_types34.PolicySeveritySchema.safeParse(req.query.severity);
9452
9770
  if (!sev.success) {
9453
9771
  return reply.code(400).send({
9454
9772
  error: "invalid severity",
@@ -9487,7 +9805,7 @@ function registerRoutes(scope, ctx) {
9487
9805
  scope.post("/policies/check", async (req, reply) => {
9488
9806
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
9489
9807
  if (!proj) return;
9490
- const parsed = import_types33.PoliciesCheckBodySchema.safeParse(req.body ?? {});
9808
+ const parsed = import_types34.PoliciesCheckBodySchema.safeParse(req.body ?? {});
9491
9809
  if (!parsed.success) {
9492
9810
  return reply.code(400).send({
9493
9811
  error: "invalid /policies/check body",
@@ -9559,6 +9877,42 @@ function registerRoutes(scope, ctx) {
9559
9877
  return reply.code(500).send({ error: err.message });
9560
9878
  }
9561
9879
  });
9880
+ scope.get("/instrumentation", async (req, reply) => {
9881
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
9882
+ if (!proj) return;
9883
+ if (!proj.scanPath) {
9884
+ return { engaged: null };
9885
+ }
9886
+ try {
9887
+ const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
9888
+ const uninstrumented = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
9889
+ if (state.hookFiles.length === 0) {
9890
+ return {
9891
+ engaged: false,
9892
+ diagnosis: {
9893
+ reason: "No instrumented entry point found \u2014 NEAT hasn't written an OTel init hook for this project.",
9894
+ fixCommand: "neat init",
9895
+ detail: "Run `neat init` so NEAT writes the instrumentation hook next to your entry point, then run your app."
9896
+ }
9897
+ };
9898
+ }
9899
+ if (uninstrumented.length > 0) {
9900
+ const names = uninstrumented.map((u) => u.library);
9901
+ const more = names.length > 1 ? ` (and ${names.length - 1} more)` : "";
9902
+ return {
9903
+ engaged: false,
9904
+ diagnosis: {
9905
+ reason: `\`${names[0]}\`${more} isn't in the auto-instrumentation set, so spans from it won't reach the graph.`,
9906
+ fixCommand: "neat extend",
9907
+ detail: `Uninstrumented on your hot path: ${names.join(", ")}. Run \`neat extend\` to wire the missing instrumentation.`
9908
+ }
9909
+ };
9910
+ }
9911
+ return { engaged: true };
9912
+ } catch {
9913
+ return { engaged: null };
9914
+ }
9915
+ });
9562
9916
  scope.post("/extend/apply", async (req, reply) => {
9563
9917
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
9564
9918
  if (!proj) return;
@@ -9753,21 +10107,21 @@ init_otel_grpc();
9753
10107
  // src/daemon.ts
9754
10108
  init_cjs_shims();
9755
10109
  var import_node_fs31 = require("fs");
9756
- var import_node_path50 = __toESM(require("path"), 1);
10110
+ var import_node_path51 = __toESM(require("path"), 1);
9757
10111
  var import_node_module = require("module");
9758
10112
  init_otel();
9759
10113
 
9760
10114
  // src/connectors/index.ts
9761
10115
  init_cjs_shims();
9762
- var import_types34 = require("@neat.is/types");
10116
+ var import_types35 = require("@neat.is/types");
9763
10117
  var NO_ENV = "unknown";
9764
10118
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
9765
10119
  if (!graph.hasNode(targetNodeId)) return void 0;
9766
10120
  const sites = [];
9767
10121
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
9768
10122
  const edge = graph.getEdgeAttributes(edgeId);
9769
- if (edge.provenance !== import_types34.Provenance.EXTRACTED) continue;
9770
- const parsed = (0, import_types34.parseFileId)(edge.source);
10123
+ if (edge.provenance !== import_types35.Provenance.EXTRACTED) continue;
10124
+ const parsed = (0, import_types35.parseFileId)(edge.source);
9771
10125
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
9772
10126
  const site = { relPath: edge.evidence.file };
9773
10127
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -9778,7 +10132,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
9778
10132
  function routeCallSiteFor(graph, targetNodeId) {
9779
10133
  if (!graph.hasNode(targetNodeId)) return void 0;
9780
10134
  const attrs = graph.getNodeAttributes(targetNodeId);
9781
- if (attrs.type !== import_types34.NodeType.RouteNode || !attrs.path) return void 0;
10135
+ if (attrs.type !== import_types35.NodeType.RouteNode || !attrs.path) return void 0;
9782
10136
  const site = { relPath: attrs.path };
9783
10137
  if (attrs.line !== void 0) site.line = attrs.line;
9784
10138
  return site;
@@ -9952,7 +10306,12 @@ var JUNCTION_DEFAULT_RATE_LIMITS = {
9952
10306
  supabase: { capacity: 30, refillMs: 1e4 },
9953
10307
  // Not a documented API limit at all — a self-imposed ceiling on the raw
9954
10308
  // pg_stat_statements connection (see module header above).
9955
- "supabase-postgres": { capacity: 20, refillMs: 3e3 }
10309
+ "supabase-postgres": { capacity: 20, refillMs: 3e3 },
10310
+ // Push provider (ADR-146): the Drains REST API is touched only by `neat
10311
+ // connector add/remove/test` (provision/deprovision/validate), never a poll
10312
+ // loop, so this bucket is exercised a handful of times per command. Kept
10313
+ // conservative pending a documented Drains-API rate limit.
10314
+ vercel: { capacity: 20, refillMs: 5e3 }
9956
10315
  };
9957
10316
  var JUNCTION_GENERIC_RATE_LIMIT = { capacity: 20, refillMs: 5e3 };
9958
10317
  function defaultRateLimitFor(provider) {
@@ -10244,10 +10603,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
10244
10603
  // src/connectors/supabase/map.ts
10245
10604
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
10246
10605
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
10247
- function targetFromRestPath(path51) {
10248
- const rpcMatch = REST_RPC_PATH_RE.exec(path51);
10606
+ function targetFromRestPath(path52) {
10607
+ const rpcMatch = REST_RPC_PATH_RE.exec(path52);
10249
10608
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
10250
- const tableMatch = REST_TABLE_PATH_RE.exec(path51);
10609
+ const tableMatch = REST_TABLE_PATH_RE.exec(path52);
10251
10610
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
10252
10611
  return null;
10253
10612
  }
@@ -10356,23 +10715,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
10356
10715
 
10357
10716
  // src/connectors/supabase/resolve.ts
10358
10717
  init_cjs_shims();
10359
- var import_types36 = require("@neat.is/types");
10718
+ var import_types37 = require("@neat.is/types");
10360
10719
  function createSupabaseResolveTarget(graph, config) {
10361
10720
  return (signal, _ctx) => {
10362
10721
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
10363
10722
  return null;
10364
10723
  }
10365
- const subResourceId = (0, import_types36.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10724
+ const subResourceId = (0, import_types37.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10366
10725
  if (graph.hasNode(subResourceId)) {
10367
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
10726
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10368
10727
  }
10369
- const bareResourceId = (0, import_types36.infraId)(signal.targetKind, signal.targetName);
10728
+ const bareResourceId = (0, import_types37.infraId)(signal.targetKind, signal.targetName);
10370
10729
  if (graph.hasNode(bareResourceId)) {
10371
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
10730
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10372
10731
  }
10373
- const projectLevelId = (0, import_types36.infraId)("supabase", config.nodeRef);
10732
+ const projectLevelId = (0, import_types37.infraId)("supabase", config.nodeRef);
10374
10733
  if (graph.hasNode(projectLevelId)) {
10375
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
10734
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10376
10735
  }
10377
10736
  return null;
10378
10737
  };
@@ -10465,7 +10824,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
10465
10824
 
10466
10825
  // src/connectors/railway/index.ts
10467
10826
  init_cjs_shims();
10468
- var import_types40 = require("@neat.is/types");
10827
+ var import_types41 = require("@neat.is/types");
10469
10828
 
10470
10829
  // src/connectors/railway/client.ts
10471
10830
  init_cjs_shims();
@@ -10591,7 +10950,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
10591
10950
  const out = [];
10592
10951
  graph.forEachNode((_id, attrs) => {
10593
10952
  const node = attrs;
10594
- if (node.type !== import_types40.NodeType.RouteNode) return;
10953
+ if (node.type !== import_types41.NodeType.RouteNode) return;
10595
10954
  const route = attrs;
10596
10955
  if (route.service !== serviceName) return;
10597
10956
  out.push({
@@ -10695,12 +11054,12 @@ function createRailwayResolveTarget(config) {
10695
11054
  const serviceName = config.serviceNameById[config.serviceId];
10696
11055
  if (!serviceName) return null;
10697
11056
  if (signal.targetKind === ROUTE_TARGET_KIND) {
10698
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types40.EdgeType.CALLS };
11057
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types41.EdgeType.CALLS };
10699
11058
  }
10700
11059
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
10701
11060
  const peerName = config.serviceNameById[signal.targetName];
10702
11061
  if (!peerName) return null;
10703
- return { targetNodeId: (0, import_types40.serviceId)(peerName), serviceName, edgeType: import_types40.EdgeType.CONNECTS_TO };
11062
+ return { targetNodeId: (0, import_types41.serviceId)(peerName), serviceName, edgeType: import_types41.EdgeType.CONNECTS_TO };
10704
11063
  }
10705
11064
  return null;
10706
11065
  };
@@ -10824,9 +11183,9 @@ function parseFirebaseTargetName(targetName) {
10824
11183
  const secondSep = rest.indexOf(FIELD_SEP);
10825
11184
  if (secondSep === -1) return null;
10826
11185
  const method = rest.slice(0, secondSep);
10827
- const path51 = rest.slice(secondSep + 1);
10828
- if (!resourceName || !method || !path51) return null;
10829
- return { resourceName, method, path: path51 };
11186
+ const path52 = rest.slice(secondSep + 1);
11187
+ if (!resourceName || !method || !path52) return null;
11188
+ return { resourceName, method, path: path52 };
10830
11189
  }
10831
11190
  function resourceNameFor(type, labels) {
10832
11191
  if (!labels) return null;
@@ -10864,14 +11223,14 @@ function mapLogEntryToSignal(entry) {
10864
11223
  if (!req) return null;
10865
11224
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
10866
11225
  const method = req.requestMethod.toUpperCase();
10867
- const path51 = pathFromRequestUrl(req.requestUrl);
10868
- if (path51 === null) return null;
11226
+ const path52 = pathFromRequestUrl(req.requestUrl);
11227
+ if (path52 === null) return null;
10869
11228
  const timestamp = entry.timestamp;
10870
11229
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
10871
11230
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
10872
11231
  return {
10873
11232
  targetKind: resourceType,
10874
- targetName: packFirebaseTargetName({ resourceName, method, path: path51 }),
11233
+ targetName: packFirebaseTargetName({ resourceName, method, path: path52 }),
10875
11234
  callCount: 1,
10876
11235
  errorCount: isError ? 1 : 0,
10877
11236
  lastObservedIso: timestamp
@@ -10888,7 +11247,7 @@ function mapLogEntriesToSignals(entries) {
10888
11247
 
10889
11248
  // src/connectors/firebase/resolve.ts
10890
11249
  init_cjs_shims();
10891
- var import_types41 = require("@neat.is/types");
11250
+ var import_types42 = require("@neat.is/types");
10892
11251
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
10893
11252
  switch (resourceType) {
10894
11253
  case "cloud_function":
@@ -10903,7 +11262,7 @@ function routeEntriesFor(graph, serviceName) {
10903
11262
  const entries = [];
10904
11263
  graph.forEachNode((_id, attrs) => {
10905
11264
  const node = attrs;
10906
- if (node.type !== import_types41.NodeType.RouteNode) return;
11265
+ if (node.type !== import_types42.NodeType.RouteNode) return;
10907
11266
  const route = attrs;
10908
11267
  if (route.service !== serviceName) return;
10909
11268
  entries.push({
@@ -10935,7 +11294,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
10935
11294
  return {
10936
11295
  targetNodeId: match.routeNodeId,
10937
11296
  serviceName,
10938
- edgeType: import_types41.EdgeType.CALLS
11297
+ edgeType: import_types42.EdgeType.CALLS
10939
11298
  };
10940
11299
  };
10941
11300
  }
@@ -10962,7 +11321,7 @@ init_cjs_shims();
10962
11321
 
10963
11322
  // src/connectors/cloudflare/connector.ts
10964
11323
  init_cjs_shims();
10965
- var import_types43 = require("@neat.is/types");
11324
+ var import_types44 = require("@neat.is/types");
10966
11325
 
10967
11326
  // src/connectors/cloudflare/client.ts
10968
11327
  init_cjs_shims();
@@ -11078,7 +11437,7 @@ function mapEventToSignal(event) {
11078
11437
  if (Number.isNaN(observedAt.getTime())) return null;
11079
11438
  const statusCode = metadata?.statusCode;
11080
11439
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
11081
- const path51 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
11440
+ const path52 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
11082
11441
  return {
11083
11442
  targetKind: CLOUDFLARE_TARGET_KIND,
11084
11443
  targetName: scriptName,
@@ -11086,7 +11445,7 @@ function mapEventToSignal(event) {
11086
11445
  errorCount: isError ? 1 : 0,
11087
11446
  lastObservedIso: observedAt.toISOString(),
11088
11447
  method,
11089
- ...path51 ? { path: path51 } : {},
11448
+ ...path52 ? { path: path52 } : {},
11090
11449
  ...typeof statusCode === "number" ? { statusCode } : {},
11091
11450
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
11092
11451
  };
@@ -11126,19 +11485,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
11126
11485
  graph.forEachNode((id, attrs) => {
11127
11486
  if (found) return;
11128
11487
  const a = attrs;
11129
- if (a.type === import_types43.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11488
+ if (a.type === import_types44.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11130
11489
  found = id;
11131
11490
  }
11132
11491
  });
11133
11492
  return found;
11134
11493
  }
11135
- function findMatchingRouteNode(graph, serviceName, method, path51) {
11136
- const normalizedPath = normalizePathTemplate(path51);
11494
+ function findMatchingRouteNode(graph, serviceName, method, path52) {
11495
+ const normalizedPath = normalizePathTemplate(path52);
11137
11496
  let found = null;
11138
11497
  graph.forEachNode((id, attrs) => {
11139
11498
  if (found) return;
11140
11499
  const a = attrs;
11141
- if (a.type !== import_types43.NodeType.RouteNode || a.service !== serviceName) return;
11500
+ if (a.type !== import_types44.NodeType.RouteNode || a.service !== serviceName) return;
11142
11501
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
11143
11502
  const routeMethod = (a.method ?? "").toUpperCase();
11144
11503
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -11150,18 +11509,18 @@ function createCloudflareResolveTarget(config, graph) {
11150
11509
  return (signal) => {
11151
11510
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
11152
11511
  const scriptName = signal.targetName;
11153
- const { method, path: path51 } = signal;
11512
+ const { method, path: path52 } = signal;
11154
11513
  const resolveRouteGrain = (serviceName, wholeFileId) => {
11155
- if (!method || !path51) return wholeFileId;
11156
- return findMatchingRouteNode(graph, serviceName, method, path51) ?? wholeFileId;
11514
+ if (!method || !path52) return wholeFileId;
11515
+ return findMatchingRouteNode(graph, serviceName, method, path52) ?? wholeFileId;
11157
11516
  };
11158
11517
  const mapping = config.workers?.[scriptName];
11159
11518
  if (mapping) {
11160
- const wholeFileId = (0, import_types43.fileId)(mapping.service, mapping.entryFile);
11519
+ const wholeFileId = (0, import_types44.fileId)(mapping.service, mapping.entryFile);
11161
11520
  return {
11162
11521
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
11163
11522
  serviceName: mapping.service,
11164
- edgeType: import_types43.EdgeType.CALLS
11523
+ edgeType: import_types44.EdgeType.CALLS
11165
11524
  };
11166
11525
  }
11167
11526
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -11170,18 +11529,124 @@ function createCloudflareResolveTarget(config, graph) {
11170
11529
  return {
11171
11530
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
11172
11531
  serviceName: fileNode.service,
11173
- edgeType: import_types43.EdgeType.CALLS
11532
+ edgeType: import_types44.EdgeType.CALLS
11174
11533
  };
11175
11534
  }
11176
11535
  return {
11177
- targetNodeId: (0, import_types43.infraId)("cloudflare-worker", scriptName),
11536
+ targetNodeId: (0, import_types44.infraId)("cloudflare-worker", scriptName),
11178
11537
  serviceName: scriptName,
11179
- edgeType: import_types43.EdgeType.CALLS,
11538
+ edgeType: import_types44.EdgeType.CALLS,
11180
11539
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
11181
11540
  };
11182
11541
  };
11183
11542
  }
11184
11543
 
11544
+ // src/connectors/vercel/index.ts
11545
+ init_cjs_shims();
11546
+
11547
+ // src/connectors/vercel/client.ts
11548
+ init_cjs_shims();
11549
+ var DEFAULT_API_BASE_URL = "https://api.vercel.com";
11550
+ var DEFAULT_DRAIN_NAME = "neat-otlp";
11551
+ var TRACE_SCHEMAS = { trace: { version: "v1" } };
11552
+ function apiBase(config) {
11553
+ return config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
11554
+ }
11555
+ function teamQuery(config) {
11556
+ return `?teamId=${encodeURIComponent(config.teamId)}`;
11557
+ }
11558
+ function drainDelivery(config, otelToken) {
11559
+ return {
11560
+ type: "http",
11561
+ endpoint: config.endpoint,
11562
+ encoding: "json",
11563
+ headers: bearerAuthHeader(otelToken),
11564
+ ...config.secret ? { secret: config.secret } : {}
11565
+ };
11566
+ }
11567
+ async function describeError(res) {
11568
+ try {
11569
+ const data = await res.json();
11570
+ const message = data?.error?.message;
11571
+ return typeof message === "string" && message.length > 0 ? ` \u2014 ${message}` : "";
11572
+ } catch {
11573
+ return "";
11574
+ }
11575
+ }
11576
+ async function createVercelDrain(config, credentials, fetchImpl = fetch) {
11577
+ const projectIds = config.projectIds ?? [];
11578
+ const body = {
11579
+ name: config.drainName ?? DEFAULT_DRAIN_NAME,
11580
+ projects: projectIds.length > 0 ? "some" : "all",
11581
+ ...projectIds.length > 0 ? { projectIds } : {},
11582
+ schemas: TRACE_SCHEMAS,
11583
+ delivery: drainDelivery(config, credentials.otelToken),
11584
+ source: { kind: "self-served" }
11585
+ };
11586
+ const res = await junctionFetch(
11587
+ `${apiBase(config)}/v1/drains${teamQuery(config)}`,
11588
+ {
11589
+ method: "POST",
11590
+ headers: { "Content-Type": "application/json", ...bearerAuthHeader(credentials.token) },
11591
+ body: JSON.stringify(body)
11592
+ },
11593
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11594
+ );
11595
+ if (!res.ok) {
11596
+ throw new Error(
11597
+ `vercel connector: create drain failed (${res.status} ${res.statusText}${await describeError(res)})`
11598
+ );
11599
+ }
11600
+ const payload = await res.json().catch(() => null);
11601
+ if (!payload || typeof payload.id !== "string" || payload.id.length === 0) {
11602
+ throw new Error(
11603
+ "vercel connector: create drain returned no drain id \u2014 the Drains API response shape may have changed"
11604
+ );
11605
+ }
11606
+ return {
11607
+ id: payload.id,
11608
+ ...payload.status ? { status: payload.status } : {},
11609
+ ...payload.disabledReason ? { disabledReason: payload.disabledReason } : {}
11610
+ };
11611
+ }
11612
+ async function deleteVercelDrain(config, drainId, credentials, fetchImpl = fetch) {
11613
+ const res = await junctionFetch(
11614
+ `${apiBase(config)}/v1/drains/${encodeURIComponent(drainId)}${teamQuery(config)}`,
11615
+ { method: "DELETE", headers: { ...bearerAuthHeader(credentials.token) } },
11616
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11617
+ );
11618
+ if (res.ok || res.status === 404) return;
11619
+ throw new Error(
11620
+ `vercel connector: delete drain failed (${res.status} ${res.statusText}${await describeError(res)})`
11621
+ );
11622
+ }
11623
+ async function testVercelDrainDelivery(config, credentials, fetchImpl = fetch) {
11624
+ const res = await junctionFetch(
11625
+ `${apiBase(config)}/v1/drains/test${teamQuery(config)}`,
11626
+ {
11627
+ method: "POST",
11628
+ headers: { "Content-Type": "application/json", ...bearerAuthHeader(credentials.token) },
11629
+ body: JSON.stringify({ schemas: TRACE_SCHEMAS, delivery: drainDelivery(config, credentials.otelToken) })
11630
+ },
11631
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11632
+ );
11633
+ if (res.status === 401 || res.status === 403) {
11634
+ return { status: "failure", error: `vercel rejected the API token (HTTP ${res.status})` };
11635
+ }
11636
+ if (!res.ok) {
11637
+ return {
11638
+ status: "failure",
11639
+ error: `vercel drain validation failed (${res.status} ${res.statusText}${await describeError(res)})`
11640
+ };
11641
+ }
11642
+ const payload = await res.json().catch(() => null);
11643
+ return {
11644
+ ...payload?.status ? { status: payload.status } : {},
11645
+ ...payload?.error ? { error: payload.error } : {},
11646
+ ...payload?.endpoint ? { endpoint: payload.endpoint } : {}
11647
+ };
11648
+ }
11649
+
11185
11650
  // src/connectors/registry.ts
11186
11651
  var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
11187
11652
  async function authProbe(input) {
@@ -11318,21 +11783,112 @@ var PROVIDER_DISPATCH = {
11318
11783
  resolveTarget: createCloudflareResolveTarget(config, graph)
11319
11784
  };
11320
11785
  },
11321
- // GET /user/tokens/verify — Cloudflare's own purpose-built "is this API
11322
- // token live" endpoint. 200 on a valid token, 401 on an invalid one.
11786
+ // GET /accounts/{accountId}/tokens/verify — the *account-scoped* token-verify
11787
+ // endpoint. A Workers connector token is scoped to the account, and the
11788
+ // user-level `GET /user/tokens/verify` returns 401 "Invalid API Token" for
11789
+ // such a token even though it authenticates fine against the account's own
11790
+ // resources (confirmed live). Probing the account-scoped verify endpoint —
11791
+ // `accountId` is already required for this provider — returns 200
11792
+ // `{status:"active"}` for a working token and 401 for a bad one, so a valid
11793
+ // Workers token is no longer falsely rejected at `neat connector add`.
11323
11794
  validate({ credentials, options, fetchImpl }) {
11324
11795
  const cfg = options;
11325
11796
  const baseUrl = cfg.baseUrl ?? CLOUDFLARE_API_BASE_URL;
11326
11797
  return authProbe({
11327
11798
  provider: "cloudflare",
11328
11799
  accountKey: cfg.accountId ?? "validate",
11329
- url: `${baseUrl}/user/tokens/verify`,
11800
+ url: `${baseUrl}/accounts/${cfg.accountId ?? ""}/tokens/verify`,
11330
11801
  token: String(credentials.apiToken ?? ""),
11331
11802
  ...fetchImpl ? { fetchImpl } : {}
11332
11803
  });
11333
11804
  }
11334
11805
  }
11335
11806
  };
11807
+ function vercelCredsFrom(credentials) {
11808
+ return { token: String(credentials.token ?? ""), otelToken: String(credentials.otelToken ?? "") };
11809
+ }
11810
+ function vercelConfigFromOptions(options) {
11811
+ const raw = options.projectIds;
11812
+ let projectIds;
11813
+ if (Array.isArray(raw)) projectIds = raw.filter((p) => typeof p === "string");
11814
+ else if (typeof raw === "string" && raw.trim().length > 0) {
11815
+ projectIds = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
11816
+ }
11817
+ return {
11818
+ teamId: String(options.teamId ?? ""),
11819
+ endpoint: String(options.endpoint ?? ""),
11820
+ ...projectIds && projectIds.length > 0 ? { projectIds } : {},
11821
+ ...typeof options.drainId === "string" ? { drainId: options.drainId } : {},
11822
+ ...typeof options.drainName === "string" ? { drainName: options.drainName } : {},
11823
+ ...typeof options.apiBaseUrl === "string" ? { apiBaseUrl: options.apiBaseUrl } : {},
11824
+ ...typeof options.secret === "string" ? { secret: options.secret } : {}
11825
+ };
11826
+ }
11827
+ var PUSH_PROVIDER_DISPATCH = {
11828
+ vercel: {
11829
+ provider: "vercel",
11830
+ // The Vercel access token is the "primary" secret a single `--token`
11831
+ // populates; `otelToken` (the daemon's OTLP bearer) is the second field.
11832
+ primaryCredentialKey: "token",
11833
+ requiredCredentialFields: ["token", "otelToken"],
11834
+ // teamId scopes every Drains call; endpoint is where the drain delivers.
11835
+ // projectIds is optional (absent → the drain covers the whole team).
11836
+ requiredOptionFields: ["teamId", "endpoint"],
11837
+ // POST /v1/drains/test — authenticates the token and pings the endpoint
11838
+ // with a sample event, so `success` means the credential is live *and* the
11839
+ // daemon's OTLP endpoint is reachable and accepted the drain's bearer.
11840
+ async validate({ credentials, options, fetchImpl }) {
11841
+ const result = await testVercelDrainDelivery(
11842
+ vercelConfigFromOptions(options),
11843
+ vercelCredsFrom(credentials),
11844
+ fetchImpl
11845
+ );
11846
+ if (result.status === "success") return { ok: true };
11847
+ return {
11848
+ ok: false,
11849
+ reason: result.error ?? `vercel drain delivery test returned "${result.status ?? "no status"}"`
11850
+ };
11851
+ },
11852
+ // POST /v1/drains — creates the trace drain, returns its id to store in
11853
+ // `options.drainId`. A created-but-not-enabled drain is surfaced as a note,
11854
+ // not a failure (the entry still points at a real drain).
11855
+ async provision({ credentials, options, fetchImpl }) {
11856
+ try {
11857
+ const created = await createVercelDrain(
11858
+ vercelConfigFromOptions(options),
11859
+ vercelCredsFrom(credentials),
11860
+ fetchImpl
11861
+ );
11862
+ const note = created.status && created.status !== "enabled" ? `the drain was created but its status is "${created.status}"${created.disabledReason ? ` (${created.disabledReason})` : ""} \u2014 check the Vercel dashboard` : void 0;
11863
+ return { ok: true, options: { drainId: created.id }, ...note ? { note } : {} };
11864
+ } catch (err) {
11865
+ return { ok: false, reason: err.message };
11866
+ }
11867
+ },
11868
+ // DELETE /v1/drains/{id} — idempotent (deleteVercelDrain treats 404 as
11869
+ // success). No recorded drainId → nothing to delete, still a success.
11870
+ async deprovision({ credentials, options, fetchImpl }) {
11871
+ const drainId = typeof options.drainId === "string" ? options.drainId : "";
11872
+ if (!drainId) {
11873
+ return { ok: true, note: "no drain id was recorded \u2014 nothing to delete on the Vercel side" };
11874
+ }
11875
+ try {
11876
+ await deleteVercelDrain(
11877
+ vercelConfigFromOptions(options),
11878
+ drainId,
11879
+ vercelCredsFrom(credentials),
11880
+ fetchImpl
11881
+ );
11882
+ return { ok: true };
11883
+ } catch (err) {
11884
+ return { ok: false, reason: err.message };
11885
+ }
11886
+ }
11887
+ }
11888
+ };
11889
+ function isPushProvider(provider) {
11890
+ return provider in PUSH_PROVIDER_DISPATCH;
11891
+ }
11336
11892
  function resolveEntryCredentials(dispatch, entry, env) {
11337
11893
  let credentials;
11338
11894
  try {
@@ -11355,6 +11911,13 @@ function resolveEntryCredentials(dispatch, entry, env) {
11355
11911
  function buildRegistration(entry, graph, env = process.env) {
11356
11912
  const dispatch = PROVIDER_DISPATCH[entry.provider];
11357
11913
  if (!dispatch) {
11914
+ if (isPushProvider(entry.provider)) {
11915
+ return {
11916
+ ok: false,
11917
+ push: true,
11918
+ reason: `push provider "${entry.provider}" ingests via the OTLP receiver \u2014 nothing to poll`
11919
+ };
11920
+ }
11358
11921
  return { ok: false, reason: `unknown provider "${entry.provider}"` };
11359
11922
  }
11360
11923
  const creds = resolveEntryCredentials(dispatch, entry, env);
@@ -11405,7 +11968,7 @@ async function loadConnectorRegistrations(input) {
11405
11968
  if (!connectorMatchesProject(entry, project)) continue;
11406
11969
  const result = buildRegistration(entry, graph, env);
11407
11970
  if (result.ok) registrations.push(result.registration);
11408
- else onSkip?.(entry, result.reason);
11971
+ else if (!result.push) onSkip?.(entry, result.reason);
11409
11972
  }
11410
11973
  return registrations;
11411
11974
  }
@@ -11416,7 +11979,7 @@ init_auth();
11416
11979
  // src/unrouted.ts
11417
11980
  init_cjs_shims();
11418
11981
  var import_node_fs30 = require("fs");
11419
- var import_node_path49 = __toESM(require("path"), 1);
11982
+ var import_node_path50 = __toESM(require("path"), 1);
11420
11983
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
11421
11984
  return {
11422
11985
  timestamp: now.toISOString(),
@@ -11426,34 +11989,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
11426
11989
  };
11427
11990
  }
11428
11991
  async function appendUnroutedSpan(neatHome3, record) {
11429
- const target = import_node_path49.default.join(neatHome3, "errors.ndjson");
11992
+ const target = import_node_path50.default.join(neatHome3, "errors.ndjson");
11430
11993
  await import_node_fs30.promises.mkdir(neatHome3, { recursive: true });
11431
11994
  await import_node_fs30.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
11432
11995
  }
11433
11996
  function unroutedErrorsPath(neatHome3) {
11434
- return import_node_path49.default.join(neatHome3, "errors.ndjson");
11997
+ return import_node_path50.default.join(neatHome3, "errors.ndjson");
11435
11998
  }
11436
11999
 
11437
12000
  // src/daemon.ts
11438
- var import_types46 = require("@neat.is/types");
12001
+ var import_types47 = require("@neat.is/types");
11439
12002
  function daemonJsonPath(scanPath) {
11440
- return import_node_path50.default.join(scanPath, "neat-out", "daemon.json");
12003
+ return import_node_path51.default.join(scanPath, "neat-out", "daemon.json");
11441
12004
  }
11442
12005
  function daemonsDiscoveryDir(home) {
11443
12006
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
11444
- return import_node_path50.default.join(base, "daemons");
12007
+ return import_node_path51.default.join(base, "daemons");
11445
12008
  }
11446
12009
  function daemonDiscoveryPath(project, home) {
11447
- return import_node_path50.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
12010
+ return import_node_path51.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
11448
12011
  }
11449
12012
  function sanitizeDiscoveryName(project) {
11450
12013
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
11451
12014
  }
11452
12015
  function neatHomeFromEnv() {
11453
12016
  const env = process.env.NEAT_HOME;
11454
- if (env && env.length > 0) return import_node_path50.default.resolve(env);
12017
+ if (env && env.length > 0) return import_node_path51.default.resolve(env);
11455
12018
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11456
- return import_node_path50.default.join(home, ".neat");
12019
+ return import_node_path51.default.join(home, ".neat");
11457
12020
  }
11458
12021
  function resolveNeatVersion() {
11459
12022
  if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
@@ -11508,11 +12071,11 @@ function teardownSlot(slot) {
11508
12071
  }
11509
12072
  }
11510
12073
  function neatHomeFor(opts) {
11511
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path50.default.resolve(opts.neatHome);
12074
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path51.default.resolve(opts.neatHome);
11512
12075
  const env = process.env.NEAT_HOME;
11513
- if (env && env.length > 0) return import_node_path50.default.resolve(env);
12076
+ if (env && env.length > 0) return import_node_path51.default.resolve(env);
11514
12077
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11515
- return import_node_path50.default.join(home, ".neat");
12078
+ return import_node_path51.default.join(home, ".neat");
11516
12079
  }
11517
12080
  function routeSpanToProject(serviceName, projects) {
11518
12081
  if (!serviceName) return DEFAULT_PROJECT;
@@ -11556,11 +12119,11 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
11556
12119
  if (!serviceName) return true;
11557
12120
  if (serviceNameMatchesProject(serviceName, project)) return true;
11558
12121
  return graph.someNode(
11559
- (_id, attrs) => attrs.type === import_types46.NodeType.ServiceNode && attrs.name === serviceName
12122
+ (_id, attrs) => attrs.type === import_types47.NodeType.ServiceNode && attrs.name === serviceName
11560
12123
  );
11561
12124
  }
11562
12125
  async function bootstrapProject(entry, connectors = [], neatHome3) {
11563
- const paths = pathsForProject(entry.name, import_node_path50.default.join(entry.path, "neat-out"));
12126
+ const paths = pathsForProject(entry.name, import_node_path51.default.join(entry.path, "neat-out"));
11564
12127
  try {
11565
12128
  const stat = await import_node_fs31.promises.stat(entry.path);
11566
12129
  if (!stat.isDirectory()) {
@@ -11691,7 +12254,7 @@ async function startDaemon(opts = {}) {
11691
12254
  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;
11692
12255
  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;
11693
12256
  const singleProject = projectArg;
11694
- const singleProjectPath = singleProject && projectPathArg ? import_node_path50.default.resolve(projectPathArg) : null;
12257
+ const singleProjectPath = singleProject && projectPathArg ? import_node_path51.default.resolve(projectPathArg) : null;
11695
12258
  if (singleProject && !singleProjectPath) {
11696
12259
  throw new Error(
11697
12260
  `neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
@@ -11706,7 +12269,7 @@ async function startDaemon(opts = {}) {
11706
12269
  );
11707
12270
  }
11708
12271
  }
11709
- const pidPath = import_node_path50.default.join(home, "neatd.pid");
12272
+ const pidPath = import_node_path51.default.join(home, "neatd.pid");
11710
12273
  await writeAtomically(pidPath, `${process.pid}
11711
12274
  `);
11712
12275
  const slots = /* @__PURE__ */ new Map();
@@ -12113,8 +12676,8 @@ async function startDaemon(opts = {}) {
12113
12676
  let registryWatcher = null;
12114
12677
  let reloadTimer = null;
12115
12678
  if (!singleProject) try {
12116
- const regDir = import_node_path50.default.dirname(regPath);
12117
- const regBase = import_node_path50.default.basename(regPath);
12679
+ const regDir = import_node_path51.default.dirname(regPath);
12680
+ const regBase = import_node_path51.default.basename(regPath);
12118
12681
  registryWatcher = (0, import_node_fs31.watch)(regDir, (_eventType, filename) => {
12119
12682
  if (filename !== null && filename !== regBase) return;
12120
12683
  if (reloadTimer) clearTimeout(reloadTimer);