@neat.is/core 0.5.3 → 0.5.4-dev.20260721

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);
@@ -361,8 +361,8 @@ function websocketChannelPathOf(attrs) {
361
361
  const v = attrs[key];
362
362
  if (typeof v === "string" && v.length > 0) {
363
363
  const q = v.indexOf("?");
364
- const path51 = q === -1 ? v : v.slice(0, q);
365
- if (path51.length > 0) return path51;
364
+ const path52 = q === -1 ? v : v.slice(0, q);
365
+ if (path52.length > 0) return path52;
366
366
  }
367
367
  }
368
368
  return void 0;
@@ -397,6 +397,7 @@ function parseOtlpRequest(body) {
397
397
  attributes: attrs,
398
398
  dbSystem: typeof attrs["db.system"] === "string" ? attrs["db.system"] : void 0,
399
399
  dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
400
+ dbCollection: typeof attrs["db.collection.name"] === "string" ? attrs["db.collection.name"] : typeof attrs["db.mongodb.collection"] === "string" ? attrs["db.mongodb.collection"] : void 0,
400
401
  messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
401
402
  messagingDestination: messagingDestinationOf(attrs),
402
403
  graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
@@ -416,10 +417,10 @@ function parseOtlpRequest(body) {
416
417
  return out;
417
418
  }
418
419
  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");
420
+ const here = import_node_path49.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
421
+ const protoRoot = import_node_path49.default.resolve(here, "..", "proto");
421
422
  const root = new import_protobufjs.default.Root();
422
- root.resolvePath = (_origin, target) => import_node_path48.default.resolve(protoRoot, target);
423
+ root.resolvePath = (_origin, target) => import_node_path49.default.resolve(protoRoot, target);
423
424
  root.loadSync(
424
425
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
425
426
  { keepCase: true }
@@ -655,12 +656,12 @@ function logSpanHandler(span) {
655
656
  `otel: ${span.service} ${span.name} parent=${parent} status=${status2}${db}`
656
657
  );
657
658
  }
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;
659
+ 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
660
  var init_otel = __esm({
660
661
  "src/otel.ts"() {
661
662
  "use strict";
662
663
  init_cjs_shims();
663
- import_node_path48 = __toESM(require("path"), 1);
664
+ import_node_path49 = __toESM(require("path"), 1);
664
665
  import_node_url2 = require("url");
665
666
  import_fastify2 = __toESM(require("fastify"), 1);
666
667
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -1250,19 +1251,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1250
1251
  function longestIncomingWalk(graph, start, maxDepth) {
1251
1252
  let best = { path: [start], edges: [] };
1252
1253
  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] };
1254
+ function step(node, path52, edges) {
1255
+ if (path52.length > best.path.length) {
1256
+ best = { path: [...path52], edges: [...edges] };
1256
1257
  }
1257
- if (path51.length - 1 >= maxDepth) return;
1258
+ if (path52.length - 1 >= maxDepth) return;
1258
1259
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1259
1260
  for (const [srcId, edge] of incoming) {
1260
1261
  if (visited.has(srcId)) continue;
1261
1262
  visited.add(srcId);
1262
- path51.push(srcId);
1263
+ path52.push(srcId);
1263
1264
  edges.push(edge);
1264
- step(srcId, path51, edges);
1265
- path51.pop();
1265
+ step(srcId, path52, edges);
1266
+ path52.pop();
1266
1267
  edges.pop();
1267
1268
  visited.delete(srcId);
1268
1269
  }
@@ -1456,26 +1457,26 @@ function dominantFailingCall(graph, serviceId6, visited) {
1456
1457
  return best;
1457
1458
  }
1458
1459
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1459
- const path51 = [originServiceId];
1460
+ const path52 = [originServiceId];
1460
1461
  const edges = [];
1461
1462
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1462
1463
  let current = originServiceId;
1463
1464
  for (let depth = 0; depth < maxDepth; depth++) {
1464
1465
  const hop = dominantFailingCall(graph, current, visited);
1465
1466
  if (!hop) break;
1466
- path51.push(hop.nextService);
1467
+ path52.push(hop.nextService);
1467
1468
  edges.push(hop.edge);
1468
1469
  visited.add(hop.nextService);
1469
1470
  current = hop.nextService;
1470
1471
  }
1471
1472
  if (edges.length === 0) return null;
1472
- return { path: path51, edges, culprit: current };
1473
+ return { path: path52, edges, culprit: current };
1473
1474
  }
1474
1475
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1475
1476
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1476
1477
  if (!chain) return null;
1477
1478
  const culprit = chain.culprit;
1478
- const path51 = [...chain.path];
1479
+ const path52 = [...chain.path];
1479
1480
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1480
1481
  const baseConfidence = confidenceFromMix(chain.edges);
1481
1482
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1483,14 +1484,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1483
1484
  if (loc) {
1484
1485
  let rootCauseNode = culprit;
1485
1486
  if (loc.fileNode) {
1486
- path51.push(loc.fileNode);
1487
+ path52.push(loc.fileNode);
1487
1488
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1488
1489
  rootCauseNode = loc.fileNode;
1489
1490
  }
1490
1491
  return import_types.RootCauseResultSchema.parse({
1491
1492
  rootCauseNode,
1492
1493
  rootCauseReason: loc.rootCauseReason,
1493
- traversalPath: path51,
1494
+ traversalPath: path52,
1494
1495
  edgeProvenances,
1495
1496
  confidence,
1496
1497
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1502,7 +1503,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1502
1503
  return import_types.RootCauseResultSchema.parse({
1503
1504
  rootCauseNode: culprit,
1504
1505
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1505
- traversalPath: path51,
1506
+ traversalPath: path52,
1506
1507
  edgeProvenances,
1507
1508
  confidence,
1508
1509
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2929,6 +2930,18 @@ async function handleSpan(ctx, span) {
2929
2930
  callSiteEvidence
2930
2931
  );
2931
2932
  if (result) affectedNode = targetId;
2933
+ if (span.dbSystem === "mongodb" && span.dbCollection) {
2934
+ const collectionId = ensureInfraNode(ctx.graph, "mongodb-collection", span.dbCollection, "self");
2935
+ upsertObservedEdge(
2936
+ ctx.graph,
2937
+ import_types3.EdgeType.CALLS,
2938
+ observedSource(),
2939
+ collectionId,
2940
+ ts,
2941
+ isError,
2942
+ callSiteEvidence
2943
+ );
2944
+ }
2932
2945
  }
2933
2946
  } else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
2934
2947
  const targetId = ensureMessagingDestinationNode(
@@ -5776,7 +5789,7 @@ async function addGrpcMethods(graph, services) {
5776
5789
 
5777
5790
  // src/extract/calls/index.ts
5778
5791
  init_cjs_shims();
5779
- var import_types20 = require("@neat.is/types");
5792
+ var import_types21 = require("@neat.is/types");
5780
5793
 
5781
5794
  // src/extract/calls/http.ts
5782
5795
  init_cjs_shims();
@@ -6489,15 +6502,311 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6489
6502
  return out;
6490
6503
  }
6491
6504
 
6505
+ // src/extract/calls/mongoose.ts
6506
+ init_cjs_shims();
6507
+ var import_node_path31 = __toESM(require("path"), 1);
6508
+ var import_types20 = require("@neat.is/types");
6509
+ var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
6510
+ var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
6511
+ var PLURALIZE_DISABLED_RE = /\bpluralize\s*\(\s*(?:null|false)\s*\)/;
6512
+ var UNCOUNTABLES = /* @__PURE__ */ new Set([
6513
+ "advice",
6514
+ "energy",
6515
+ "excretion",
6516
+ "digestion",
6517
+ "cooperation",
6518
+ "health",
6519
+ "justice",
6520
+ "labour",
6521
+ "machinery",
6522
+ "equipment",
6523
+ "information",
6524
+ "pollution",
6525
+ "sewage",
6526
+ "paper",
6527
+ "money",
6528
+ "species",
6529
+ "series",
6530
+ "rain",
6531
+ "rice",
6532
+ "fish",
6533
+ "sheep",
6534
+ "moose",
6535
+ "deer",
6536
+ "news",
6537
+ "expertise",
6538
+ "status",
6539
+ "media"
6540
+ ]);
6541
+ var PLURALIZE_RULES = [
6542
+ [/human$/gi, "humans"],
6543
+ [/(m)an$/gi, "$1en"],
6544
+ [/(pe)rson$/gi, "$1ople"],
6545
+ [/(child)$/gi, "$1ren"],
6546
+ [/^(ox)$/gi, "$1en"],
6547
+ [/(ax|test)is$/gi, "$1es"],
6548
+ [/(octop|vir)us$/gi, "$1i"],
6549
+ [/(alias|status)$/gi, "$1es"],
6550
+ [/(bu)s$/gi, "$1ses"],
6551
+ [/(buffal|tomat|potat)o$/gi, "$1oes"],
6552
+ [/([ti])um$/gi, "$1a"],
6553
+ [/sis$/gi, "ses"],
6554
+ [/(?:([^f])fe|([lr])f)$/gi, "$1$2ves"],
6555
+ [/(hive)$/gi, "$1s"],
6556
+ [/([^aeiouy]|qu)y$/gi, "$1ies"],
6557
+ [/(x|ch|ss|sh)$/gi, "$1es"],
6558
+ [/(matr|vert|ind)ix|ex$/gi, "$1ices"],
6559
+ [/([m|l])ouse$/gi, "$1ice"],
6560
+ [/(kn|w|l)ife$/gi, "$1ives"],
6561
+ [/(quiz)$/gi, "$1zes"],
6562
+ [/s$/gi, "s"],
6563
+ [/([^a-z])$/, "$1"],
6564
+ [/$/gi, "s"]
6565
+ ];
6566
+ function pluralizeCollection(name) {
6567
+ const str = name.toLowerCase();
6568
+ if (UNCOUNTABLES.has(str)) return str;
6569
+ for (const [re, repl] of PLURALIZE_RULES) {
6570
+ if (str.match(re)) return str.replace(re, repl);
6571
+ }
6572
+ return str;
6573
+ }
6574
+ var MODEL_METHODS = [
6575
+ "find",
6576
+ "findOne",
6577
+ "findById",
6578
+ "findByIdAndUpdate",
6579
+ "findByIdAndDelete",
6580
+ "findOneAndUpdate",
6581
+ "findOneAndDelete",
6582
+ "findOneAndReplace",
6583
+ "countDocuments",
6584
+ "estimatedDocumentCount",
6585
+ "distinct",
6586
+ "aggregate",
6587
+ "exists",
6588
+ "where",
6589
+ "populate",
6590
+ "watch",
6591
+ "hydrate",
6592
+ "create",
6593
+ "insertMany",
6594
+ "insertOne",
6595
+ "updateOne",
6596
+ "updateMany",
6597
+ "replaceOne",
6598
+ "deleteOne",
6599
+ "deleteMany",
6600
+ "bulkWrite",
6601
+ "bulkSave",
6602
+ "save",
6603
+ // pre-v7, removed in current mongoose but present in older codebases:
6604
+ "count",
6605
+ "update",
6606
+ "remove",
6607
+ "findOneAndRemove",
6608
+ "findByIdAndRemove"
6609
+ ];
6610
+ var MODEL_METHODS_ALT = MODEL_METHODS.join("|");
6611
+ function schemaCollectionVars(content) {
6612
+ const out = /* @__PURE__ */ new Map();
6613
+ const re = /(?:const|let|var)\s+(\w+)\s*=\s*new\s+(?:mongoose\s*\.\s*)?Schema\s*\(/g;
6614
+ let m;
6615
+ while ((m = re.exec(content)) !== null) {
6616
+ const windowText = content.slice(m.index, m.index + 2e3);
6617
+ const opt = /\bcollection\s*:\s*['"`]([\w.-]+)['"`]/.exec(windowText);
6618
+ if (opt) out.set(m[1], opt[1]);
6619
+ }
6620
+ return out;
6621
+ }
6622
+ function resolveModel(modelName, rest, schemaColl, pluralizeOn) {
6623
+ const trimmed = rest.trim();
6624
+ const literalThird = /,\s*['"`]([\w.-]+)['"`]\s*$/.exec(trimmed);
6625
+ if (literalThird) return { name: literalThird[1], kind: "mongodb-collection" };
6626
+ const firstIdent = /^([A-Za-z_$][\w$]*)/.exec(trimmed)?.[1];
6627
+ if (firstIdent && schemaColl.has(firstIdent)) {
6628
+ return { name: schemaColl.get(firstIdent), kind: "mongodb-collection" };
6629
+ }
6630
+ if (/,\s*[A-Za-z_$][\w$]*\s*$/.test(trimmed)) {
6631
+ return { name: modelName, kind: "mongodb-model" };
6632
+ }
6633
+ return { name: pluralizeOn ? pluralizeCollection(modelName) : modelName, kind: "mongodb-collection" };
6634
+ }
6635
+ function collectModelDefs(content, pluralizeOn) {
6636
+ const schemaColl = schemaCollectionVars(content);
6637
+ const out = [];
6638
+ 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;
6639
+ let m;
6640
+ while ((m = re.exec(content)) !== null) {
6641
+ out.push({
6642
+ modelName: m[2],
6643
+ varName: m[1] ?? null,
6644
+ resolved: resolveModel(m[2], m[3] ?? "", schemaColl, pluralizeOn),
6645
+ matchText: m[0]
6646
+ });
6647
+ }
6648
+ return out;
6649
+ }
6650
+ function endpoint(r, file, serviceDir, matchText) {
6651
+ const line = lineOf(file.content, matchText);
6652
+ return {
6653
+ infraId: (0, import_types20.infraId)(r.kind, r.name),
6654
+ name: r.name,
6655
+ kind: r.kind,
6656
+ edgeType: "CALLS",
6657
+ confidenceKind: "verified-call-site",
6658
+ evidence: { file: import_node_path31.default.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
6659
+ };
6660
+ }
6661
+ function mongooseEndpointsFromFile(file, serviceDir) {
6662
+ const hasMongoose = MONGOOSE_IMPORT_RE.test(file.content);
6663
+ const hasMongodb = MONGODB_IMPORT_RE.test(file.content);
6664
+ if (!hasMongoose && !hasMongodb) return [];
6665
+ const content = file.content;
6666
+ const out = [];
6667
+ const seen = /* @__PURE__ */ new Set();
6668
+ const push = (r, matchText) => {
6669
+ const key = `${r.kind}/${r.name}`;
6670
+ if (seen.has(key)) return;
6671
+ seen.add(key);
6672
+ out.push(endpoint(r, file, serviceDir, matchText));
6673
+ };
6674
+ if (hasMongoose) {
6675
+ const pluralizeOn = !PLURALIZE_DISABLED_RE.test(content);
6676
+ for (const def of collectModelDefs(content, pluralizeOn)) push(def.resolved, def.matchText);
6677
+ }
6678
+ const collRe = /\.\s*collection\s*\(\s*['"`]([\w.$-]+)['"`]\s*\)/g;
6679
+ let c;
6680
+ while ((c = collRe.exec(content)) !== null) {
6681
+ push({ name: c[1], kind: "mongodb-collection" }, c[0]);
6682
+ }
6683
+ return out;
6684
+ }
6685
+ function parseDestructure(inner, specifier, out) {
6686
+ for (const raw of inner.split(",")) {
6687
+ const part = raw.trim();
6688
+ if (!part) continue;
6689
+ const asM = /^(\w+)\s+as\s+(\w+)$/.exec(part);
6690
+ const colonM = /^(\w+)\s*:\s*(\w+)$/.exec(part);
6691
+ if (asM) out.push({ local: asM[2], kind: "named", exportName: asM[1], specifier });
6692
+ else if (colonM) out.push({ local: colonM[2], kind: "named", exportName: colonM[1], specifier });
6693
+ else {
6694
+ const id = /^(\w+)$/.exec(part);
6695
+ if (id) out.push({ local: id[1], kind: "named", exportName: id[1], specifier });
6696
+ }
6697
+ }
6698
+ }
6699
+ function parseImportBindings(content) {
6700
+ const out = [];
6701
+ const esm = /import\s+([^;'"`\n]+?)\s+from\s*['"`]([^'"`]+)['"`]/g;
6702
+ let m;
6703
+ while ((m = esm.exec(content)) !== null) {
6704
+ const clause = m[1].trim();
6705
+ const spec = m[2];
6706
+ const ns = /^\*\s+as\s+(\w+)$/.exec(clause);
6707
+ if (ns) {
6708
+ out.push({ local: ns[1], kind: "namespace", specifier: spec });
6709
+ continue;
6710
+ }
6711
+ const named = /\{([^}]*)\}/.exec(clause);
6712
+ if (named) parseDestructure(named[1], spec, out);
6713
+ const def = /^(\w+)\s*(?:,|$)/.exec(clause);
6714
+ if (def && !clause.startsWith("{")) out.push({ local: def[1], kind: "default", specifier: spec });
6715
+ }
6716
+ const cjs = /(?:const|let|var)\s+(\{[^}]*\}|\w+)\s*=\s*require\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
6717
+ while ((m = cjs.exec(content)) !== null) {
6718
+ const lhs = m[1];
6719
+ const spec = m[2];
6720
+ if (lhs.startsWith("{")) parseDestructure(lhs.slice(1, -1), spec, out);
6721
+ else out.push({ local: lhs, kind: "whole", specifier: spec });
6722
+ }
6723
+ return out;
6724
+ }
6725
+ function fileExportsOf(content, pluralizeOn) {
6726
+ const defs = collectModelDefs(content, pluralizeOn).filter((d) => d.resolved.kind === "mongodb-collection");
6727
+ if (defs.length === 0) return null;
6728
+ const byVar = /* @__PURE__ */ new Map();
6729
+ for (const d of defs) if (d.varName) byVar.set(d.varName, d.resolved.name);
6730
+ const byName = new Map(byVar);
6731
+ let def;
6732
+ const named = /(?:module\.exports|export\s+default)\s*=\s*(\w+)\b/.exec(content);
6733
+ if (named && byVar.has(named[1])) def = byVar.get(named[1]);
6734
+ if (!def) {
6735
+ const inline = /(?:module\.exports|export\s+default)\s*=\s*(?:await\s+)?(?:\w+\s*\.\s*)?model\s*\(\s*['"`]([\w$]+)['"`]/.exec(content);
6736
+ if (inline) def = pluralizeOn ? pluralizeCollection(inline[1]) : inline[1];
6737
+ }
6738
+ if (!def && byVar.size === 1) def = [...byVar.values()][0];
6739
+ return { byName, ...def ? { default: def } : {} };
6740
+ }
6741
+ async function mongooseCrossFileEndpoints(files, serviceDir) {
6742
+ const mongooseFiles = files.filter((f) => MONGOOSE_IMPORT_RE.test(f.content));
6743
+ if (mongooseFiles.length === 0) return [];
6744
+ const pluralizeOn = !files.some((f) => PLURALIZE_DISABLED_RE.test(f.content));
6745
+ const registry = /* @__PURE__ */ new Map();
6746
+ for (const f of mongooseFiles) {
6747
+ const fx = fileExportsOf(f.content, pluralizeOn);
6748
+ if (fx) registry.set(toPosix2(import_node_path31.default.relative(serviceDir, f.path)), fx);
6749
+ }
6750
+ if (registry.size === 0) return [];
6751
+ const out = [];
6752
+ const seen = /* @__PURE__ */ new Set();
6753
+ for (const f of files) {
6754
+ const bindings = parseImportBindings(f.content);
6755
+ if (bindings.length === 0) continue;
6756
+ const directColl = /* @__PURE__ */ new Map();
6757
+ const nsExports = /* @__PURE__ */ new Map();
6758
+ for (const b of bindings) {
6759
+ const resolvedRel = await resolveJsImport(b.specifier, import_node_path31.default.dirname(f.path), serviceDir, null);
6760
+ if (!resolvedRel) continue;
6761
+ const fx = registry.get(resolvedRel);
6762
+ if (!fx) continue;
6763
+ if (b.kind === "named" && b.exportName) {
6764
+ const coll = fx.byName.get(b.exportName);
6765
+ if (coll) directColl.set(b.local, coll);
6766
+ } else if (b.kind === "default") {
6767
+ if (fx.default) directColl.set(b.local, fx.default);
6768
+ } else {
6769
+ if (fx.default) directColl.set(b.local, fx.default);
6770
+ nsExports.set(b.local, fx);
6771
+ }
6772
+ }
6773
+ if (directColl.size === 0 && nsExports.size === 0) continue;
6774
+ const localDefs = new Set(
6775
+ collectModelDefs(f.content, pluralizeOn).map((d) => d.varName).filter((v) => v !== null)
6776
+ );
6777
+ const emit = (collection, matchText) => {
6778
+ const key = `${f.path}::${collection}`;
6779
+ if (seen.has(key)) return;
6780
+ seen.add(key);
6781
+ out.push(endpoint({ name: collection, kind: "mongodb-collection" }, f, serviceDir, matchText));
6782
+ };
6783
+ for (const [local, collection] of directColl) {
6784
+ if (localDefs.has(local)) continue;
6785
+ const qre = new RegExp(`\\b${local}\\s*\\.\\s*(?:${MODEL_METHODS_ALT})\\s*\\(`, "g");
6786
+ let qm;
6787
+ while ((qm = qre.exec(f.content)) !== null) emit(collection, qm[0]);
6788
+ }
6789
+ for (const [local, fx] of nsExports) {
6790
+ const qre = new RegExp(`\\b${local}\\s*\\.\\s*(\\w+)\\s*\\.\\s*(?:${MODEL_METHODS_ALT})\\s*\\(`, "g");
6791
+ let qm;
6792
+ while ((qm = qre.exec(f.content)) !== null) {
6793
+ const coll = fx.byName.get(qm[1]);
6794
+ if (coll) emit(coll, qm[0]);
6795
+ }
6796
+ }
6797
+ }
6798
+ return out;
6799
+ }
6800
+
6492
6801
  // src/extract/calls/index.ts
6493
6802
  function edgeTypeFromEndpoint(ep) {
6494
6803
  switch (ep.edgeType) {
6495
6804
  case "PUBLISHES_TO":
6496
- return import_types20.EdgeType.PUBLISHES_TO;
6805
+ return import_types21.EdgeType.PUBLISHES_TO;
6497
6806
  case "CONSUMES_FROM":
6498
- return import_types20.EdgeType.CONSUMES_FROM;
6807
+ return import_types21.EdgeType.CONSUMES_FROM;
6499
6808
  default:
6500
- return import_types20.EdgeType.CALLS;
6809
+ return import_types21.EdgeType.CALLS;
6501
6810
  }
6502
6811
  }
6503
6812
  function isAwsKind(kind) {
@@ -6509,23 +6818,27 @@ async function addExternalEndpointEdges(graph, services) {
6509
6818
  for (const service of services) {
6510
6819
  const files = await loadSourceFiles(service.dir);
6511
6820
  const endpoints = [];
6821
+ const maskedFiles = [];
6512
6822
  for (const file of files) {
6513
6823
  if (isTestPath(file.path)) continue;
6514
6824
  const masked = maskCommentsInSource(file.content);
6515
6825
  const maskedFile = { path: file.path, content: masked };
6826
+ maskedFiles.push(maskedFile);
6516
6827
  endpoints.push(...kafkaEndpointsFromFile(maskedFile, service.dir));
6517
6828
  endpoints.push(...redisEndpointsFromFile(maskedFile, service.dir));
6518
6829
  endpoints.push(...awsEndpointsFromFile(maskedFile, service.dir));
6519
6830
  endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
6520
6831
  endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir));
6832
+ endpoints.push(...mongooseEndpointsFromFile(maskedFile, service.dir));
6521
6833
  }
6834
+ endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
6522
6835
  if (endpoints.length === 0) continue;
6523
6836
  const seenEdges = /* @__PURE__ */ new Set();
6524
6837
  for (const ep of endpoints) {
6525
6838
  if (!graph.hasNode(ep.infraId)) {
6526
6839
  const node = {
6527
6840
  id: ep.infraId,
6528
- type: import_types20.NodeType.InfraNode,
6841
+ type: import_types21.NodeType.InfraNode,
6529
6842
  name: ep.name,
6530
6843
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
6531
6844
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -6537,7 +6850,7 @@ async function addExternalEndpointEdges(graph, services) {
6537
6850
  nodesAdded++;
6538
6851
  }
6539
6852
  const edgeType = edgeTypeFromEndpoint(ep);
6540
- const confidence = (0, import_types20.confidenceForExtracted)(ep.confidenceKind);
6853
+ const confidence = (0, import_types21.confidenceForExtracted)(ep.confidenceKind);
6541
6854
  const relFile = toPosix2(ep.evidence.file);
6542
6855
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
6543
6856
  graph,
@@ -6547,7 +6860,7 @@ async function addExternalEndpointEdges(graph, services) {
6547
6860
  );
6548
6861
  nodesAdded += n;
6549
6862
  edgesAdded += e;
6550
- if (!(0, import_types20.passesExtractedFloor)(confidence)) {
6863
+ if (!(0, import_types21.passesExtractedFloor)(confidence)) {
6551
6864
  noteExtractedDropped({
6552
6865
  source: fileNodeId,
6553
6866
  target: ep.infraId,
@@ -6567,7 +6880,7 @@ async function addExternalEndpointEdges(graph, services) {
6567
6880
  source: fileNodeId,
6568
6881
  target: ep.infraId,
6569
6882
  type: edgeType,
6570
- provenance: import_types20.Provenance.EXTRACTED,
6883
+ provenance: import_types21.Provenance.EXTRACTED,
6571
6884
  confidence,
6572
6885
  evidence: ep.evidence
6573
6886
  };
@@ -6593,16 +6906,16 @@ init_cjs_shims();
6593
6906
 
6594
6907
  // src/extract/infra/docker-compose.ts
6595
6908
  init_cjs_shims();
6596
- var import_node_path31 = __toESM(require("path"), 1);
6597
- var import_types22 = require("@neat.is/types");
6909
+ var import_node_path32 = __toESM(require("path"), 1);
6910
+ var import_types23 = require("@neat.is/types");
6598
6911
 
6599
6912
  // src/extract/infra/shared.ts
6600
6913
  init_cjs_shims();
6601
- var import_types21 = require("@neat.is/types");
6914
+ var import_types22 = require("@neat.is/types");
6602
6915
  function makeInfraNode(kind, name, provider = "self", extras) {
6603
6916
  return {
6604
- id: (0, import_types21.infraId)(kind, name),
6605
- type: import_types21.NodeType.InfraNode,
6917
+ id: (0, import_types22.infraId)(kind, name),
6918
+ type: import_types22.NodeType.InfraNode,
6606
6919
  name,
6607
6920
  provider,
6608
6921
  kind,
@@ -6646,8 +6959,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
6646
6959
  source: anchorId,
6647
6960
  target: node.id,
6648
6961
  type: edgeType,
6649
- provenance: import_types21.Provenance.EXTRACTED,
6650
- confidence: (0, import_types21.confidenceForExtracted)("structural"),
6962
+ provenance: import_types22.Provenance.EXTRACTED,
6963
+ confidence: (0, import_types22.confidenceForExtracted)("structural"),
6651
6964
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
6652
6965
  };
6653
6966
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6664,7 +6977,7 @@ function dependsOnList(value) {
6664
6977
  }
6665
6978
  function serviceNameToServiceNode(name, services) {
6666
6979
  for (const s of services) {
6667
- if (s.node.name === name || import_node_path31.default.basename(s.dir) === name) return s.node.id;
6980
+ if (s.node.name === name || import_node_path32.default.basename(s.dir) === name) return s.node.id;
6668
6981
  }
6669
6982
  return null;
6670
6983
  }
@@ -6673,7 +6986,7 @@ async function addComposeInfra(graph, scanPath, services) {
6673
6986
  let edgesAdded = 0;
6674
6987
  let composePath = null;
6675
6988
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
6676
- const abs = import_node_path31.default.join(scanPath, name);
6989
+ const abs = import_node_path32.default.join(scanPath, name);
6677
6990
  if (await exists(abs)) {
6678
6991
  composePath = abs;
6679
6992
  break;
@@ -6686,13 +6999,13 @@ async function addComposeInfra(graph, scanPath, services) {
6686
6999
  } catch (err) {
6687
7000
  recordExtractionError(
6688
7001
  "infra docker-compose",
6689
- import_node_path31.default.relative(scanPath, composePath),
7002
+ import_node_path32.default.relative(scanPath, composePath),
6690
7003
  err
6691
7004
  );
6692
7005
  return { nodesAdded, edgesAdded };
6693
7006
  }
6694
7007
  if (!compose?.services) return { nodesAdded, edgesAdded };
6695
- const evidenceFile = import_node_path31.default.relative(scanPath, composePath).split(import_node_path31.default.sep).join("/");
7008
+ const evidenceFile = import_node_path32.default.relative(scanPath, composePath).split(import_node_path32.default.sep).join("/");
6696
7009
  const composeNameToNodeId = /* @__PURE__ */ new Map();
6697
7010
  for (const [composeName, svc] of Object.entries(compose.services)) {
6698
7011
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -6714,15 +7027,15 @@ async function addComposeInfra(graph, scanPath, services) {
6714
7027
  for (const dep of dependsOnList(svc.depends_on)) {
6715
7028
  const targetId = composeNameToNodeId.get(dep);
6716
7029
  if (!targetId) continue;
6717
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types22.EdgeType.DEPENDS_ON);
7030
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types23.EdgeType.DEPENDS_ON);
6718
7031
  if (graph.hasEdge(edgeId)) continue;
6719
7032
  const edge = {
6720
7033
  id: edgeId,
6721
7034
  source: sourceId,
6722
7035
  target: targetId,
6723
- type: import_types22.EdgeType.DEPENDS_ON,
6724
- provenance: import_types22.Provenance.EXTRACTED,
6725
- confidence: (0, import_types22.confidenceForExtracted)("structural"),
7036
+ type: import_types23.EdgeType.DEPENDS_ON,
7037
+ provenance: import_types23.Provenance.EXTRACTED,
7038
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
6726
7039
  evidence: { file: evidenceFile }
6727
7040
  };
6728
7041
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6734,9 +7047,9 @@ async function addComposeInfra(graph, scanPath, services) {
6734
7047
 
6735
7048
  // src/extract/infra/dockerfile.ts
6736
7049
  init_cjs_shims();
6737
- var import_node_path32 = __toESM(require("path"), 1);
7050
+ var import_node_path33 = __toESM(require("path"), 1);
6738
7051
  var import_node_fs16 = require("fs");
6739
- var import_types23 = require("@neat.is/types");
7052
+ var import_types24 = require("@neat.is/types");
6740
7053
  function readDockerfile(content) {
6741
7054
  let image = null;
6742
7055
  const ports = [];
@@ -6765,7 +7078,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6765
7078
  let nodesAdded = 0;
6766
7079
  let edgesAdded = 0;
6767
7080
  for (const service of services) {
6768
- const dockerfilePath = import_node_path32.default.join(service.dir, "Dockerfile");
7081
+ const dockerfilePath = import_node_path33.default.join(service.dir, "Dockerfile");
6769
7082
  if (!await exists(dockerfilePath)) continue;
6770
7083
  let content;
6771
7084
  try {
@@ -6773,7 +7086,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6773
7086
  } catch (err) {
6774
7087
  recordExtractionError(
6775
7088
  "infra dockerfile",
6776
- import_node_path32.default.relative(scanPath, dockerfilePath),
7089
+ import_node_path33.default.relative(scanPath, dockerfilePath),
6777
7090
  err
6778
7091
  );
6779
7092
  continue;
@@ -6785,8 +7098,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6785
7098
  graph.addNode(node.id, node);
6786
7099
  nodesAdded++;
6787
7100
  }
6788
- const relDockerfile = toPosix2(import_node_path32.default.relative(service.dir, dockerfilePath));
6789
- const evidenceFile = toPosix2(import_node_path32.default.relative(scanPath, dockerfilePath));
7101
+ const relDockerfile = toPosix2(import_node_path33.default.relative(service.dir, dockerfilePath));
7102
+ const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, dockerfilePath));
6790
7103
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
6791
7104
  graph,
6792
7105
  service.pkg.name,
@@ -6795,15 +7108,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6795
7108
  );
6796
7109
  nodesAdded += fn;
6797
7110
  edgesAdded += fe;
6798
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types23.EdgeType.RUNS_ON);
7111
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types24.EdgeType.RUNS_ON);
6799
7112
  if (!graph.hasEdge(edgeId)) {
6800
7113
  const edge = {
6801
7114
  id: edgeId,
6802
7115
  source: fileNodeId,
6803
7116
  target: node.id,
6804
- type: import_types23.EdgeType.RUNS_ON,
6805
- provenance: import_types23.Provenance.EXTRACTED,
6806
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7117
+ type: import_types24.EdgeType.RUNS_ON,
7118
+ provenance: import_types24.Provenance.EXTRACTED,
7119
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
6807
7120
  evidence: {
6808
7121
  file: evidenceFile,
6809
7122
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -6818,15 +7131,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6818
7131
  graph.addNode(portNode.id, portNode);
6819
7132
  nodesAdded++;
6820
7133
  }
6821
- const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types23.EdgeType.CONNECTS_TO);
7134
+ const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types24.EdgeType.CONNECTS_TO);
6822
7135
  if (graph.hasEdge(portEdgeId)) continue;
6823
7136
  const portEdge = {
6824
7137
  id: portEdgeId,
6825
7138
  source: fileNodeId,
6826
7139
  target: portNode.id,
6827
- type: import_types23.EdgeType.CONNECTS_TO,
6828
- provenance: import_types23.Provenance.EXTRACTED,
6829
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7140
+ type: import_types24.EdgeType.CONNECTS_TO,
7141
+ provenance: import_types24.Provenance.EXTRACTED,
7142
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
6830
7143
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
6831
7144
  };
6832
7145
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -6839,8 +7152,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6839
7152
  // src/extract/infra/terraform.ts
6840
7153
  init_cjs_shims();
6841
7154
  var import_node_fs17 = require("fs");
6842
- var import_node_path33 = __toESM(require("path"), 1);
6843
- var import_types24 = require("@neat.is/types");
7155
+ var import_node_path34 = __toESM(require("path"), 1);
7156
+ var import_types25 = require("@neat.is/types");
6844
7157
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
6845
7158
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
6846
7159
  async function walkTfFiles(start, depth = 0, max = 5) {
@@ -6850,11 +7163,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
6850
7163
  for (const entry of entries) {
6851
7164
  if (entry.isDirectory()) {
6852
7165
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
6853
- const child = import_node_path33.default.join(start, entry.name);
7166
+ const child = import_node_path34.default.join(start, entry.name);
6854
7167
  if (await isPythonVenvDir(child)) continue;
6855
7168
  out.push(...await walkTfFiles(child, depth + 1, max));
6856
7169
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
6857
- out.push(import_node_path33.default.join(start, entry.name));
7170
+ out.push(import_node_path34.default.join(start, entry.name));
6858
7171
  }
6859
7172
  }
6860
7173
  return out;
@@ -6886,7 +7199,7 @@ async function addTerraformResources(graph, scanPath) {
6886
7199
  const files = await walkTfFiles(scanPath);
6887
7200
  for (const file of files) {
6888
7201
  const content = await import_node_fs17.promises.readFile(file, "utf8");
6889
- const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, file));
7202
+ const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, file));
6890
7203
  const resources = [];
6891
7204
  const byKey = /* @__PURE__ */ new Map();
6892
7205
  RESOURCE_RE.lastIndex = 0;
@@ -6921,16 +7234,16 @@ async function addTerraformResources(graph, scanPath) {
6921
7234
  if (!target) continue;
6922
7235
  if (seen.has(target.nodeId)) continue;
6923
7236
  seen.add(target.nodeId);
6924
- const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types24.EdgeType.DEPENDS_ON);
7237
+ const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types25.EdgeType.DEPENDS_ON);
6925
7238
  if (graph.hasEdge(edgeId)) continue;
6926
7239
  const line = lineAt2(content, resource.bodyOffset + ref.index);
6927
7240
  const edge = {
6928
7241
  id: edgeId,
6929
7242
  source: resource.nodeId,
6930
7243
  target: target.nodeId,
6931
- type: import_types24.EdgeType.DEPENDS_ON,
6932
- provenance: import_types24.Provenance.EXTRACTED,
6933
- confidence: (0, import_types24.confidenceForExtracted)("structural"),
7244
+ type: import_types25.EdgeType.DEPENDS_ON,
7245
+ provenance: import_types25.Provenance.EXTRACTED,
7246
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
6934
7247
  evidence: { file: evidenceFile, line, snippet: key }
6935
7248
  };
6936
7249
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6944,7 +7257,7 @@ async function addTerraformResources(graph, scanPath) {
6944
7257
  // src/extract/infra/k8s.ts
6945
7258
  init_cjs_shims();
6946
7259
  var import_node_fs18 = require("fs");
6947
- var import_node_path34 = __toESM(require("path"), 1);
7260
+ var import_node_path35 = __toESM(require("path"), 1);
6948
7261
  var import_yaml3 = require("yaml");
6949
7262
  var K8S_KIND_TO_INFRA_KIND = {
6950
7263
  Service: "k8s-service",
@@ -6962,11 +7275,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
6962
7275
  for (const entry of entries) {
6963
7276
  if (entry.isDirectory()) {
6964
7277
  if (IGNORED_DIRS.has(entry.name)) continue;
6965
- const child = import_node_path34.default.join(start, entry.name);
7278
+ const child = import_node_path35.default.join(start, entry.name);
6966
7279
  if (await isPythonVenvDir(child)) continue;
6967
7280
  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));
7281
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path35.default.extname(entry.name))) {
7282
+ out.push(import_node_path35.default.join(start, entry.name));
6970
7283
  }
6971
7284
  }
6972
7285
  return out;
@@ -7000,13 +7313,13 @@ async function addK8sResources(graph, scanPath) {
7000
7313
  // src/extract/infra/cloudflare.ts
7001
7314
  init_cjs_shims();
7002
7315
  var import_node_fs19 = require("fs");
7003
- var import_node_path35 = __toESM(require("path"), 1);
7316
+ var import_node_path36 = __toESM(require("path"), 1);
7004
7317
  var import_smol_toml2 = require("smol-toml");
7005
- var import_types25 = require("@neat.is/types");
7318
+ var import_types26 = require("@neat.is/types");
7006
7319
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
7007
7320
  async function readWranglerConfig(dir) {
7008
7321
  for (const filename of WRANGLER_FILENAMES) {
7009
- const abs = import_node_path35.default.join(dir, filename);
7322
+ const abs = import_node_path36.default.join(dir, filename);
7010
7323
  if (!await exists(abs)) continue;
7011
7324
  const raw = await import_node_fs19.promises.readFile(abs, "utf8");
7012
7325
  const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7050,8 +7363,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
7050
7363
  source: anchorId,
7051
7364
  target: node.id,
7052
7365
  type: edgeType,
7053
- provenance: import_types25.Provenance.EXTRACTED,
7054
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7366
+ provenance: import_types26.Provenance.EXTRACTED,
7367
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7055
7368
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
7056
7369
  };
7057
7370
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7069,11 +7382,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7069
7382
  try {
7070
7383
  read = await readWranglerConfig(service.dir);
7071
7384
  } catch (err) {
7072
- recordExtractionError("infra cloudflare", import_node_path35.default.relative(scanPath, service.dir), err);
7385
+ recordExtractionError("infra cloudflare", import_node_path36.default.relative(scanPath, service.dir), err);
7073
7386
  continue;
7074
7387
  }
7075
7388
  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)));
7389
+ const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, import_node_path36.default.join(service.dir, read.relFile)));
7077
7390
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
7078
7391
  }
7079
7392
  for (const worker of discovered) {
@@ -7085,7 +7398,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7085
7398
  }
7086
7399
  let anchorId = service.node.id;
7087
7400
  if (config.main) {
7088
- const entryRelPath = toPosix2(import_node_path35.default.normalize(config.main));
7401
+ const entryRelPath = toPosix2(import_node_path36.default.normalize(config.main));
7089
7402
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7090
7403
  graph,
7091
7404
  service.pkg.name,
@@ -7112,15 +7425,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7112
7425
  nodesAdded++;
7113
7426
  }
7114
7427
  if (runtimeNode.id !== anchorId) {
7115
- const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types25.EdgeType.RUNS_ON);
7428
+ const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types26.EdgeType.RUNS_ON);
7116
7429
  if (!graph.hasEdge(runsOnId)) {
7117
7430
  const edge = {
7118
7431
  id: runsOnId,
7119
7432
  source: anchorId,
7120
7433
  target: runtimeNode.id,
7121
- type: import_types25.EdgeType.RUNS_ON,
7122
- provenance: import_types25.Provenance.EXTRACTED,
7123
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7434
+ type: import_types26.EdgeType.RUNS_ON,
7435
+ provenance: import_types26.Provenance.EXTRACTED,
7436
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7124
7437
  evidence: {
7125
7438
  file: evidenceFile,
7126
7439
  ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
@@ -7134,7 +7447,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7134
7447
  const result = addResourceEdge(
7135
7448
  graph,
7136
7449
  anchorId,
7137
- import_types25.EdgeType.CONNECTS_TO,
7450
+ import_types26.EdgeType.CONNECTS_TO,
7138
7451
  "cloudflare-route",
7139
7452
  route,
7140
7453
  evidenceFile,
@@ -7158,7 +7471,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7158
7471
  const result = addResourceEdge(
7159
7472
  graph,
7160
7473
  anchorId,
7161
- import_types25.EdgeType.DEPENDS_ON,
7474
+ import_types26.EdgeType.DEPENDS_ON,
7162
7475
  group.kind,
7163
7476
  name,
7164
7477
  evidenceFile,
@@ -7172,7 +7485,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7172
7485
  const result = addResourceEdge(
7173
7486
  graph,
7174
7487
  anchorId,
7175
- import_types25.EdgeType.DEPENDS_ON,
7488
+ import_types26.EdgeType.DEPENDS_ON,
7176
7489
  "cloudflare-cron",
7177
7490
  cron,
7178
7491
  evidenceFile,
@@ -7185,7 +7498,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7185
7498
  const result = addResourceEdge(
7186
7499
  graph,
7187
7500
  anchorId,
7188
- import_types25.EdgeType.DEPENDS_ON,
7501
+ import_types26.EdgeType.DEPENDS_ON,
7189
7502
  "cloudflare-env-var",
7190
7503
  varName,
7191
7504
  evidenceFile,
@@ -7198,15 +7511,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7198
7511
  if (!svc.service) continue;
7199
7512
  const target = workerIndex.get(svc.service);
7200
7513
  if (target && target.anchorId !== anchorId) {
7201
- const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types25.EdgeType.CALLS);
7514
+ const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types26.EdgeType.CALLS);
7202
7515
  if (!graph.hasEdge(edgeId)) {
7203
7516
  const edge = {
7204
7517
  id: edgeId,
7205
7518
  source: anchorId,
7206
7519
  target: target.anchorId,
7207
- type: import_types25.EdgeType.CALLS,
7208
- provenance: import_types25.Provenance.EXTRACTED,
7209
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7520
+ type: import_types26.EdgeType.CALLS,
7521
+ provenance: import_types26.Provenance.EXTRACTED,
7522
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7210
7523
  evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
7211
7524
  };
7212
7525
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7217,7 +7530,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7217
7530
  const result = addResourceEdge(
7218
7531
  graph,
7219
7532
  anchorId,
7220
- import_types25.EdgeType.DEPENDS_ON,
7533
+ import_types26.EdgeType.DEPENDS_ON,
7221
7534
  "cloudflare-service-binding",
7222
7535
  svc.service,
7223
7536
  evidenceFile,
@@ -7233,12 +7546,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7233
7546
  // src/extract/infra/vercel.ts
7234
7547
  init_cjs_shims();
7235
7548
  var import_node_fs20 = require("fs");
7236
- var import_node_path36 = __toESM(require("path"), 1);
7237
- var import_types26 = require("@neat.is/types");
7549
+ var import_node_path37 = __toESM(require("path"), 1);
7550
+ var import_types27 = require("@neat.is/types");
7238
7551
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
7239
7552
  async function readVercelConfig(dir) {
7240
7553
  for (const filename of VERCEL_CONFIG_FILENAMES) {
7241
- const abs = import_node_path36.default.join(dir, filename);
7554
+ const abs = import_node_path37.default.join(dir, filename);
7242
7555
  if (!await exists(abs)) continue;
7243
7556
  const raw = await import_node_fs20.promises.readFile(abs, "utf8");
7244
7557
  const config = JSON.parse(maskCommentsInSource(raw));
@@ -7247,7 +7560,7 @@ async function readVercelConfig(dir) {
7247
7560
  return null;
7248
7561
  }
7249
7562
  async function readLinkedProjectName(dir) {
7250
- const abs = import_node_path36.default.join(dir, ".vercel", "project.json");
7563
+ const abs = import_node_path37.default.join(dir, ".vercel", "project.json");
7251
7564
  if (!await exists(abs)) return void 0;
7252
7565
  const parsed = JSON.parse(await import_node_fs20.promises.readFile(abs, "utf8"));
7253
7566
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
@@ -7265,7 +7578,7 @@ async function addVercelServices(graph, services, scanPath) {
7265
7578
  read = await readVercelConfig(service.dir);
7266
7579
  projectName = await readLinkedProjectName(service.dir);
7267
7580
  } catch (err) {
7268
- recordExtractionError("infra vercel", import_node_path36.default.relative(scanPath, service.dir), err);
7581
+ recordExtractionError("infra vercel", import_node_path37.default.relative(scanPath, service.dir), err);
7269
7582
  continue;
7270
7583
  }
7271
7584
  if (!read && !projectName) continue;
@@ -7281,7 +7594,7 @@ async function addVercelServices(graph, services, scanPath) {
7281
7594
  const anchorId = service.node.id;
7282
7595
  if (!read) continue;
7283
7596
  const { config, relFile, raw } = read;
7284
- const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, import_node_path36.default.join(service.dir, relFile)));
7597
+ const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
7285
7598
  const add = (edgeType, kind, name) => {
7286
7599
  if (!name) return;
7287
7600
  const result = emitPlatformResourceEdge(
@@ -7297,12 +7610,12 @@ async function addVercelServices(graph, services, scanPath) {
7297
7610
  nodesAdded += result.nodesAdded;
7298
7611
  edgesAdded += result.edgesAdded;
7299
7612
  };
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);
7613
+ add(import_types27.EdgeType.RUNS_ON, "vercel", "vercel");
7614
+ for (const cron of config.crons ?? []) add(import_types27.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7615
+ for (const varName of Object.keys(config.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7616
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7304
7617
  for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
7305
- add(import_types26.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7618
+ add(import_types27.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7306
7619
  }
7307
7620
  }
7308
7621
  return { nodesAdded, edgesAdded };
@@ -7311,13 +7624,13 @@ async function addVercelServices(graph, services, scanPath) {
7311
7624
  // src/extract/infra/railway.ts
7312
7625
  init_cjs_shims();
7313
7626
  var import_node_fs21 = require("fs");
7314
- var import_node_path37 = __toESM(require("path"), 1);
7627
+ var import_node_path38 = __toESM(require("path"), 1);
7315
7628
  var import_smol_toml3 = require("smol-toml");
7316
- var import_types27 = require("@neat.is/types");
7629
+ var import_types28 = require("@neat.is/types");
7317
7630
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
7318
7631
  async function readRailwayConfig(dir) {
7319
7632
  for (const filename of RAILWAY_FILENAMES) {
7320
- const abs = import_node_path37.default.join(dir, filename);
7633
+ const abs = import_node_path38.default.join(dir, filename);
7321
7634
  if (!await exists(abs)) continue;
7322
7635
  const raw = await import_node_fs21.promises.readFile(abs, "utf8");
7323
7636
  const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7333,7 +7646,7 @@ async function addRailwayServices(graph, services, scanPath) {
7333
7646
  try {
7334
7647
  read = await readRailwayConfig(service.dir);
7335
7648
  } catch (err) {
7336
- recordExtractionError("infra railway", import_node_path37.default.relative(scanPath, service.dir), err);
7649
+ recordExtractionError("infra railway", import_node_path38.default.relative(scanPath, service.dir), err);
7337
7650
  continue;
7338
7651
  }
7339
7652
  if (!read) continue;
@@ -7343,7 +7656,7 @@ async function addRailwayServices(graph, services, scanPath) {
7343
7656
  }
7344
7657
  const anchorId = service.node.id;
7345
7658
  const { config, relFile, raw } = read;
7346
- const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
7659
+ const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7347
7660
  const add = (edgeType, kind, name) => {
7348
7661
  if (!name) return;
7349
7662
  const result = emitPlatformResourceEdge(
@@ -7359,9 +7672,9 @@ async function addRailwayServices(graph, services, scanPath) {
7359
7672
  nodesAdded += result.nodesAdded;
7360
7673
  edgesAdded += result.edgesAdded;
7361
7674
  };
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);
7675
+ add(import_types28.EdgeType.RUNS_ON, "railway", "railway");
7676
+ add(import_types28.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7677
+ add(import_types28.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7365
7678
  }
7366
7679
  return { nodesAdded, edgesAdded };
7367
7680
  }
@@ -7369,12 +7682,12 @@ async function addRailwayServices(graph, services, scanPath) {
7369
7682
  // src/extract/infra/supabase.ts
7370
7683
  init_cjs_shims();
7371
7684
  var import_node_fs22 = require("fs");
7372
- var import_node_path38 = __toESM(require("path"), 1);
7685
+ var import_node_path39 = __toESM(require("path"), 1);
7373
7686
  var import_smol_toml4 = require("smol-toml");
7374
- var import_types28 = require("@neat.is/types");
7687
+ var import_types29 = require("@neat.is/types");
7375
7688
  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);
7689
+ const relFile = import_node_path39.default.join("supabase", "config.toml");
7690
+ const abs = import_node_path39.default.join(dir, relFile);
7378
7691
  if (!await exists(abs)) return null;
7379
7692
  const raw = await import_node_fs22.promises.readFile(abs, "utf8");
7380
7693
  const config = (0, import_smol_toml4.parse)(raw);
@@ -7388,7 +7701,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7388
7701
  try {
7389
7702
  read = await readSupabaseConfig(service.dir);
7390
7703
  } catch (err) {
7391
- recordExtractionError("infra supabase", import_node_path38.default.relative(scanPath, service.dir), err);
7704
+ recordExtractionError("infra supabase", import_node_path39.default.relative(scanPath, service.dir), err);
7392
7705
  continue;
7393
7706
  }
7394
7707
  if (!read) continue;
@@ -7403,7 +7716,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7403
7716
  });
7404
7717
  }
7405
7718
  const anchorId = service.node.id;
7406
- const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7719
+ const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
7407
7720
  const add = (edgeType, kind, name) => {
7408
7721
  if (!name) return;
7409
7722
  const result = emitPlatformResourceEdge(
@@ -7419,10 +7732,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
7419
7732
  nodesAdded += result.nodesAdded;
7420
7733
  edgesAdded += result.edgesAdded;
7421
7734
  };
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");
7735
+ add(import_types29.EdgeType.RUNS_ON, "supabase", "supabase");
7736
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types29.EdgeType.DEPENDS_ON, "supabase-function", fn);
7737
+ if (config.storage) add(import_types29.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7738
+ if (config.auth) add(import_types29.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7426
7739
  }
7427
7740
  return { nodesAdded, edgesAdded };
7428
7741
  }
@@ -7444,17 +7757,17 @@ async function addInfra(graph, scanPath, services) {
7444
7757
  }
7445
7758
 
7446
7759
  // src/extract/index.ts
7447
- var import_node_path40 = __toESM(require("path"), 1);
7760
+ var import_node_path41 = __toESM(require("path"), 1);
7448
7761
 
7449
7762
  // src/extract/retire.ts
7450
7763
  init_cjs_shims();
7451
7764
  var import_node_fs23 = require("fs");
7452
- var import_node_path39 = __toESM(require("path"), 1);
7453
- var import_types29 = require("@neat.is/types");
7765
+ var import_node_path40 = __toESM(require("path"), 1);
7766
+ var import_types30 = require("@neat.is/types");
7454
7767
  function dropOrphanedFileNodes(graph) {
7455
7768
  const orphans = [];
7456
7769
  graph.forEachNode((id, attrs) => {
7457
- if (attrs.type !== import_types29.NodeType.FileNode) return;
7770
+ if (attrs.type !== import_types30.NodeType.FileNode) return;
7458
7771
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
7459
7772
  orphans.push(id);
7460
7773
  }
@@ -7467,14 +7780,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
7467
7780
  const bases = [scanPath, ...serviceDirs];
7468
7781
  graph.forEachEdge((id, attrs) => {
7469
7782
  const edge = attrs;
7470
- if (edge.provenance !== import_types29.Provenance.EXTRACTED) return;
7783
+ if (edge.provenance !== import_types30.Provenance.EXTRACTED) return;
7471
7784
  const evidenceFile = edge.evidence?.file;
7472
7785
  if (!evidenceFile) return;
7473
- if (import_node_path39.default.isAbsolute(evidenceFile)) {
7786
+ if (import_node_path40.default.isAbsolute(evidenceFile)) {
7474
7787
  if (!(0, import_node_fs23.existsSync)(evidenceFile)) toDrop.push(id);
7475
7788
  return;
7476
7789
  }
7477
- const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path39.default.join(base, evidenceFile)));
7790
+ const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path40.default.join(base, evidenceFile)));
7478
7791
  if (!found) toDrop.push(id);
7479
7792
  });
7480
7793
  for (const id of toDrop) graph.dropEdge(id);
@@ -7516,7 +7829,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7516
7829
  }
7517
7830
  const droppedEntries = drainDroppedExtracted();
7518
7831
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
7519
- const rejectedPath = import_node_path40.default.join(import_node_path40.default.dirname(opts.errorsPath), "rejected.ndjson");
7832
+ const rejectedPath = import_node_path41.default.join(import_node_path41.default.dirname(opts.errorsPath), "rejected.ndjson");
7520
7833
  try {
7521
7834
  await writeRejectedExtracted(droppedEntries, rejectedPath);
7522
7835
  } catch (err) {
@@ -7551,8 +7864,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7551
7864
  // src/persist.ts
7552
7865
  init_cjs_shims();
7553
7866
  var import_node_fs24 = require("fs");
7554
- var import_node_path41 = __toESM(require("path"), 1);
7555
- var import_types30 = require("@neat.is/types");
7867
+ var import_node_path42 = __toESM(require("path"), 1);
7868
+ var import_types31 = require("@neat.is/types");
7556
7869
  var SCHEMA_VERSION = 4;
7557
7870
  function migrateV1ToV2(payload) {
7558
7871
  const nodes = payload.graph.nodes;
@@ -7574,12 +7887,12 @@ function migrateV2ToV3(payload) {
7574
7887
  for (const edge of edges) {
7575
7888
  const attrs = edge.attributes;
7576
7889
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
7577
- attrs.provenance = import_types30.Provenance.OBSERVED;
7890
+ attrs.provenance = import_types31.Provenance.OBSERVED;
7578
7891
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
7579
7892
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
7580
7893
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
7581
7894
  if (type && source && target) {
7582
- const newId = (0, import_types30.observedEdgeId)(source, target, type);
7895
+ const newId = (0, import_types31.observedEdgeId)(source, target, type);
7583
7896
  attrs.id = newId;
7584
7897
  if (edge.key) edge.key = newId;
7585
7898
  }
@@ -7588,7 +7901,7 @@ function migrateV2ToV3(payload) {
7588
7901
  return { ...payload, schemaVersion: 3 };
7589
7902
  }
7590
7903
  async function ensureDir(filePath) {
7591
- await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(filePath), { recursive: true });
7904
+ await import_node_fs24.promises.mkdir(import_node_path42.default.dirname(filePath), { recursive: true });
7592
7905
  }
7593
7906
  async function saveGraphToDisk(graph, outPath) {
7594
7907
  await ensureDir(outPath);
@@ -7671,19 +7984,19 @@ function startPersistLoop(graph, outPath, opts = {}) {
7671
7984
  init_cjs_shims();
7672
7985
  var import_fastify = __toESM(require("fastify"), 1);
7673
7986
  var import_cors = __toESM(require("@fastify/cors"), 1);
7674
- var import_types33 = require("@neat.is/types");
7987
+ var import_types34 = require("@neat.is/types");
7675
7988
 
7676
7989
  // src/extend/index.ts
7677
7990
  init_cjs_shims();
7678
7991
  var import_node_fs26 = require("fs");
7679
- var import_node_path43 = __toESM(require("path"), 1);
7992
+ var import_node_path44 = __toESM(require("path"), 1);
7680
7993
  var import_node_os2 = __toESM(require("os"), 1);
7681
7994
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
7682
7995
 
7683
7996
  // src/installers/package-manager.ts
7684
7997
  init_cjs_shims();
7685
7998
  var import_node_fs25 = require("fs");
7686
- var import_node_path42 = __toESM(require("path"), 1);
7999
+ var import_node_path43 = __toESM(require("path"), 1);
7687
8000
  var import_node_child_process = require("child_process");
7688
8001
  var LOCKFILE_PRIORITY = [
7689
8002
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -7705,22 +8018,22 @@ async function exists2(p) {
7705
8018
  }
7706
8019
  }
7707
8020
  async function detectPackageManager(serviceDir) {
7708
- let dir = import_node_path42.default.resolve(serviceDir);
8021
+ let dir = import_node_path43.default.resolve(serviceDir);
7709
8022
  const stops = /* @__PURE__ */ new Set();
7710
8023
  for (let i = 0; i < 64; i++) {
7711
8024
  if (stops.has(dir)) break;
7712
8025
  stops.add(dir);
7713
8026
  for (const candidate of LOCKFILE_PRIORITY) {
7714
- const lockPath = import_node_path42.default.join(dir, candidate.lockfile);
8027
+ const lockPath = import_node_path43.default.join(dir, candidate.lockfile);
7715
8028
  if (await exists2(lockPath)) {
7716
8029
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
7717
8030
  }
7718
8031
  }
7719
- const parent = import_node_path42.default.dirname(dir);
8032
+ const parent = import_node_path43.default.dirname(dir);
7720
8033
  if (parent === dir) break;
7721
8034
  dir = parent;
7722
8035
  }
7723
- return { pm: "npm", cwd: import_node_path42.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8036
+ return { pm: "npm", cwd: import_node_path43.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7724
8037
  }
7725
8038
  async function runPackageManagerInstall(cmd) {
7726
8039
  return new Promise((resolve) => {
@@ -7769,7 +8082,7 @@ async function fileExists2(p) {
7769
8082
  }
7770
8083
  }
7771
8084
  async function readPackageJson(scanPath) {
7772
- const pkgPath = import_node_path43.default.join(scanPath, "package.json");
8085
+ const pkgPath = import_node_path44.default.join(scanPath, "package.json");
7773
8086
  const raw = await import_node_fs26.promises.readFile(pkgPath, "utf8");
7774
8087
  return JSON.parse(raw);
7775
8088
  }
@@ -7788,11 +8101,11 @@ async function findHookFiles(scanPath) {
7788
8101
  for (const entry of entries) {
7789
8102
  if (entry.isDirectory()) {
7790
8103
  if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
7791
- await walk3(import_node_path43.default.join(dir, entry.name));
8104
+ await walk3(import_node_path44.default.join(dir, entry.name));
7792
8105
  } else if (entry.isFile()) {
7793
8106
  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("/"));
8107
+ const rel = import_node_path44.default.relative(scanPath, import_node_path44.default.join(dir, entry.name));
8108
+ found.push(rel.split(import_node_path44.default.sep).join("/"));
7796
8109
  }
7797
8110
  }
7798
8111
  }
@@ -7803,7 +8116,7 @@ async function findHookFiles(scanPath) {
7803
8116
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
7804
8117
  let fallback = null;
7805
8118
  for (const file of hookFiles) {
7806
- const content = await import_node_fs26.promises.readFile(import_node_path43.default.join(scanPath, file), "utf8");
8119
+ const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(scanPath, file), "utf8");
7807
8120
  const patched = splicedContent(content, snippet2);
7808
8121
  if (patched !== null) return { file, content, patched };
7809
8122
  if (fallback === null) fallback = { file, content };
@@ -7811,11 +8124,11 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
7811
8124
  return { file: fallback.file, content: fallback.content, patched: null };
7812
8125
  }
7813
8126
  function extendLogPath() {
7814
- return process.env.NEAT_EXTEND_LOG ?? import_node_path43.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
8127
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path44.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
7815
8128
  }
7816
8129
  async function appendExtendLog(entry) {
7817
8130
  const logPath = extendLogPath();
7818
- await import_node_fs26.promises.mkdir(import_node_path43.default.dirname(logPath), { recursive: true });
8131
+ await import_node_fs26.promises.mkdir(import_node_path44.default.dirname(logPath), { recursive: true });
7819
8132
  await import_node_fs26.promises.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
7820
8133
  }
7821
8134
  function splicedContent(fileContent, snippet2) {
@@ -7874,7 +8187,7 @@ function lookupInstrumentation(library, installedVersion) {
7874
8187
  }
7875
8188
  async function describeProjectInstrumentation(ctx) {
7876
8189
  const hookFiles = await findHookFiles(ctx.scanPath);
7877
- const envNeat = await fileExists2(import_node_path43.default.join(ctx.scanPath, ".env.neat"));
8190
+ const envNeat = await fileExists2(import_node_path44.default.join(ctx.scanPath, ".env.neat"));
7878
8191
  const registryInstrPackages = new Set(
7879
8192
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
7880
8193
  );
@@ -7896,7 +8209,7 @@ async function applyExtension(ctx, args, options) {
7896
8209
  );
7897
8210
  }
7898
8211
  for (const file of hookFiles) {
7899
- const content = await import_node_fs26.promises.readFile(import_node_path43.default.join(ctx.scanPath, file), "utf8");
8212
+ const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
7900
8213
  if (content.includes(args.registration_snippet)) {
7901
8214
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
7902
8215
  }
@@ -7908,10 +8221,10 @@ async function applyExtension(ctx, args, options) {
7908
8221
  );
7909
8222
  }
7910
8223
  const primaryFile = primary.file;
7911
- const primaryPath = import_node_path43.default.join(ctx.scanPath, primaryFile);
8224
+ const primaryPath = import_node_path44.default.join(ctx.scanPath, primaryFile);
7912
8225
  const filesTouched = [];
7913
8226
  const depsAdded = [];
7914
- const pkgPath = import_node_path43.default.join(ctx.scanPath, "package.json");
8227
+ const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
7915
8228
  const pkg = await readPackageJson(ctx.scanPath);
7916
8229
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
7917
8230
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
@@ -7950,7 +8263,7 @@ async function dryRunExtension(ctx, args) {
7950
8263
  };
7951
8264
  }
7952
8265
  for (const file of hookFiles) {
7953
- const content = await import_node_fs26.promises.readFile(import_node_path43.default.join(ctx.scanPath, file), "utf8");
8266
+ const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
7954
8267
  if (content.includes(args.registration_snippet)) {
7955
8268
  return {
7956
8269
  library: args.library,
@@ -7991,7 +8304,7 @@ async function rollbackExtension(ctx, args) {
7991
8304
  if (!match) {
7992
8305
  return { undone: false, message: "no apply found for library" };
7993
8306
  }
7994
- const pkgPath = import_node_path43.default.join(ctx.scanPath, "package.json");
8307
+ const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
7995
8308
  if (await fileExists2(pkgPath)) {
7996
8309
  const pkg = await readPackageJson(ctx.scanPath);
7997
8310
  if (pkg.dependencies?.[match.instrumentation_package]) {
@@ -8002,7 +8315,7 @@ async function rollbackExtension(ctx, args) {
8002
8315
  }
8003
8316
  const hookFiles = await findHookFiles(ctx.scanPath);
8004
8317
  for (const file of hookFiles) {
8005
- const filePath = import_node_path43.default.join(ctx.scanPath, file);
8318
+ const filePath = import_node_path44.default.join(ctx.scanPath, file);
8006
8319
  const content = await import_node_fs26.promises.readFile(filePath, "utf8");
8007
8320
  if (content.includes(match.registration_snippet)) {
8008
8321
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
@@ -8018,39 +8331,39 @@ async function rollbackExtension(ctx, args) {
8018
8331
 
8019
8332
  // src/divergences.ts
8020
8333
  init_cjs_shims();
8021
- var import_types31 = require("@neat.is/types");
8334
+ var import_types32 = require("@neat.is/types");
8022
8335
  function bucketKey(source, target, type) {
8023
8336
  return `${type}|${source}|${target}`;
8024
8337
  }
8025
8338
  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);
8339
+ if (edge.type !== import_types32.EdgeType.CONNECTS_TO) return edge.source;
8340
+ const parsed = (0, import_types32.parseFileId)(edge.source);
8028
8341
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
8029
8342
  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);
8343
+ if (target.type !== import_types32.NodeType.DatabaseNode) return edge.source;
8344
+ return (0, import_types32.serviceId)(parsed.service);
8032
8345
  }
8033
8346
  function bucketEdges(graph) {
8034
8347
  const buckets2 = /* @__PURE__ */ new Map();
8035
8348
  graph.forEachEdge((id, attrs) => {
8036
8349
  const e = attrs;
8037
- const parsed = (0, import_types31.parseEdgeId)(id);
8350
+ const parsed = (0, import_types32.parseEdgeId)(id);
8038
8351
  const provenance = parsed?.provenance ?? e.provenance;
8039
8352
  const source = bucketSourceFor(graph, e);
8040
8353
  const key = bucketKey(source, e.target, e.type);
8041
8354
  const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
8042
8355
  switch (provenance) {
8043
- case import_types31.Provenance.EXTRACTED:
8356
+ case import_types32.Provenance.EXTRACTED:
8044
8357
  cur.extracted = e;
8045
8358
  break;
8046
- case import_types31.Provenance.OBSERVED:
8359
+ case import_types32.Provenance.OBSERVED:
8047
8360
  cur.observed = e;
8048
8361
  break;
8049
- case import_types31.Provenance.INFERRED:
8362
+ case import_types32.Provenance.INFERRED:
8050
8363
  cur.inferred = e;
8051
8364
  break;
8052
8365
  default:
8053
- if (e.provenance === import_types31.Provenance.STALE) cur.stale = e;
8366
+ if (e.provenance === import_types32.Provenance.STALE) cur.stale = e;
8054
8367
  }
8055
8368
  buckets2.set(key, cur);
8056
8369
  });
@@ -8059,12 +8372,12 @@ function bucketEdges(graph) {
8059
8372
  function nodeIsFrontier(graph, nodeId) {
8060
8373
  if (!graph.hasNode(nodeId)) return false;
8061
8374
  const attrs = graph.getNodeAttributes(nodeId);
8062
- return attrs.type === import_types31.NodeType.FrontierNode;
8375
+ return attrs.type === import_types32.NodeType.FrontierNode;
8063
8376
  }
8064
8377
  function nodeIsWebsocketChannel(graph, nodeId) {
8065
8378
  if (!graph.hasNode(nodeId)) return false;
8066
8379
  const attrs = graph.getNodeAttributes(nodeId);
8067
- return attrs.type === import_types31.NodeType.WebSocketChannelNode;
8380
+ return attrs.type === import_types32.NodeType.WebSocketChannelNode;
8068
8381
  }
8069
8382
  function clampConfidence(n) {
8070
8383
  if (!Number.isFinite(n)) return 0;
@@ -8084,14 +8397,14 @@ function gradedConfidence(edge) {
8084
8397
  return clampConfidence(confidenceForEdge(edge));
8085
8398
  }
8086
8399
  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
8400
+ import_types32.EdgeType.CALLS,
8401
+ import_types32.EdgeType.CONNECTS_TO,
8402
+ import_types32.EdgeType.PUBLISHES_TO,
8403
+ import_types32.EdgeType.CONSUMES_FROM
8091
8404
  ]);
8092
8405
  function detectMissingDivergences(graph, bucket) {
8093
8406
  const out = [];
8094
- if (bucket.type === import_types31.EdgeType.CONTAINS) return out;
8407
+ if (bucket.type === import_types32.EdgeType.CONTAINS) return out;
8095
8408
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
8096
8409
  if (!nodeIsFrontier(graph, bucket.target)) {
8097
8410
  out.push({
@@ -8132,7 +8445,7 @@ function declaredHostFor(svc) {
8132
8445
  function hasExtractedConfiguredBy(graph, svcId) {
8133
8446
  for (const edgeId of graph.outboundEdges(svcId)) {
8134
8447
  const e = graph.getEdgeAttributes(edgeId);
8135
- if (e.type === import_types31.EdgeType.CONFIGURED_BY && e.provenance === import_types31.Provenance.EXTRACTED) {
8448
+ if (e.type === import_types32.EdgeType.CONFIGURED_BY && e.provenance === import_types32.Provenance.EXTRACTED) {
8136
8449
  return true;
8137
8450
  }
8138
8451
  }
@@ -8145,10 +8458,10 @@ function detectHostMismatch(graph, svcId, svc) {
8145
8458
  const out = [];
8146
8459
  for (const edgeId of graph.outboundEdges(svcId)) {
8147
8460
  const edge = graph.getEdgeAttributes(edgeId);
8148
- if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8149
- if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
8461
+ if (edge.type !== import_types32.EdgeType.CONNECTS_TO) continue;
8462
+ if (edge.provenance !== import_types32.Provenance.OBSERVED) continue;
8150
8463
  const target = graph.getNodeAttributes(edge.target);
8151
- if (target.type !== import_types31.NodeType.DatabaseNode) continue;
8464
+ if (target.type !== import_types32.NodeType.DatabaseNode) continue;
8152
8465
  const observedHost = target.host?.trim();
8153
8466
  if (!observedHost) continue;
8154
8467
  if (observedHost === declaredHost) continue;
@@ -8170,10 +8483,10 @@ function detectCompatDivergences(graph, svcId, svc) {
8170
8483
  const deps = svc.dependencies ?? {};
8171
8484
  for (const edgeId of graph.outboundEdges(svcId)) {
8172
8485
  const edge = graph.getEdgeAttributes(edgeId);
8173
- if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8174
- if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
8486
+ if (edge.type !== import_types32.EdgeType.CONNECTS_TO) continue;
8487
+ if (edge.provenance !== import_types32.Provenance.OBSERVED) continue;
8175
8488
  const target = graph.getNodeAttributes(edge.target);
8176
- if (target.type !== import_types31.NodeType.DatabaseNode) continue;
8489
+ if (target.type !== import_types32.NodeType.DatabaseNode) continue;
8177
8490
  for (const pair of compatPairs()) {
8178
8491
  if (pair.engine !== target.engine) continue;
8179
8492
  const declared = deps[pair.driver];
@@ -8232,7 +8545,7 @@ function suppressHostMismatchHalves(all) {
8232
8545
  for (const d of all) {
8233
8546
  if (d.type !== "host-mismatch") continue;
8234
8547
  observedHalf.add(`${d.source}->${d.target}`);
8235
- declaredHalf.add((0, import_types31.databaseId)(d.extractedHost));
8548
+ declaredHalf.add((0, import_types32.databaseId)(d.extractedHost));
8236
8549
  }
8237
8550
  if (observedHalf.size === 0) return all;
8238
8551
  return all.filter((d) => {
@@ -8251,7 +8564,7 @@ function computeDivergences(graph, opts = {}) {
8251
8564
  }
8252
8565
  graph.forEachNode((nodeId, attrs) => {
8253
8566
  const n = attrs;
8254
- if (n.type !== import_types31.NodeType.ServiceNode) return;
8567
+ if (n.type !== import_types32.NodeType.ServiceNode) return;
8255
8568
  const svc = n;
8256
8569
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
8257
8570
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -8285,7 +8598,7 @@ function computeDivergences(graph, opts = {}) {
8285
8598
  if (a.source !== b.source) return a.source.localeCompare(b.source);
8286
8599
  return a.target.localeCompare(b.target);
8287
8600
  });
8288
- return import_types31.DivergenceResultSchema.parse({
8601
+ return import_types32.DivergenceResultSchema.parse({
8289
8602
  divergences: filtered,
8290
8603
  totalAffected: filtered.length,
8291
8604
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -8416,23 +8729,23 @@ function canonicalJson(value) {
8416
8729
 
8417
8730
  // src/projects.ts
8418
8731
  init_cjs_shims();
8419
- var import_node_path44 = __toESM(require("path"), 1);
8732
+ var import_node_path45 = __toESM(require("path"), 1);
8420
8733
  function pathsForProject(project, baseDir) {
8421
8734
  if (project === DEFAULT_PROJECT) {
8422
8735
  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")
8736
+ snapshotPath: import_node_path45.default.join(baseDir, "graph.json"),
8737
+ errorsPath: import_node_path45.default.join(baseDir, "errors.ndjson"),
8738
+ staleEventsPath: import_node_path45.default.join(baseDir, "stale-events.ndjson"),
8739
+ embeddingsCachePath: import_node_path45.default.join(baseDir, "embeddings.json"),
8740
+ policyViolationsPath: import_node_path45.default.join(baseDir, "policy-violations.ndjson")
8428
8741
  };
8429
8742
  }
8430
8743
  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`)
8744
+ snapshotPath: import_node_path45.default.join(baseDir, `${project}.json`),
8745
+ errorsPath: import_node_path45.default.join(baseDir, `errors.${project}.ndjson`),
8746
+ staleEventsPath: import_node_path45.default.join(baseDir, `stale-events.${project}.ndjson`),
8747
+ embeddingsCachePath: import_node_path45.default.join(baseDir, `embeddings.${project}.json`),
8748
+ policyViolationsPath: import_node_path45.default.join(baseDir, `policy-violations.${project}.ndjson`)
8436
8749
  };
8437
8750
  }
8438
8751
  var Projects = class {
@@ -8470,23 +8783,23 @@ var Projects = class {
8470
8783
  init_cjs_shims();
8471
8784
  var import_node_fs28 = require("fs");
8472
8785
  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");
8786
+ var import_node_path46 = __toESM(require("path"), 1);
8787
+ var import_types33 = require("@neat.is/types");
8475
8788
  var LOCK_TIMEOUT_MS = 5e3;
8476
8789
  var LOCK_RETRY_MS = 50;
8477
8790
  function neatHome() {
8478
8791
  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");
8792
+ if (override && override.length > 0) return import_node_path46.default.resolve(override);
8793
+ return import_node_path46.default.join(import_node_os3.default.homedir(), ".neat");
8481
8794
  }
8482
8795
  function registryPath() {
8483
- return import_node_path45.default.join(neatHome(), "projects.json");
8796
+ return import_node_path46.default.join(neatHome(), "projects.json");
8484
8797
  }
8485
8798
  function registryLockPath() {
8486
- return import_node_path45.default.join(neatHome(), "projects.json.lock");
8799
+ return import_node_path46.default.join(neatHome(), "projects.json.lock");
8487
8800
  }
8488
8801
  function daemonPidPath() {
8489
- return import_node_path45.default.join(neatHome(), "neatd.pid");
8802
+ return import_node_path46.default.join(neatHome(), "neatd.pid");
8490
8803
  }
8491
8804
  function isPidAliveDefault(pid) {
8492
8805
  try {
@@ -8546,7 +8859,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
8546
8859
  }
8547
8860
  }
8548
8861
  async function normalizeProjectPath(input) {
8549
- const resolved = import_node_path45.default.resolve(input);
8862
+ const resolved = import_node_path46.default.resolve(input);
8550
8863
  try {
8551
8864
  return await import_node_fs28.promises.realpath(resolved);
8552
8865
  } catch {
@@ -8554,7 +8867,7 @@ async function normalizeProjectPath(input) {
8554
8867
  }
8555
8868
  }
8556
8869
  async function writeAtomically(target, contents) {
8557
- await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(target), { recursive: true });
8870
+ await import_node_fs28.promises.mkdir(import_node_path46.default.dirname(target), { recursive: true });
8558
8871
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
8559
8872
  const fd = await import_node_fs28.promises.open(tmp, "w");
8560
8873
  try {
@@ -8567,7 +8880,7 @@ async function writeAtomically(target, contents) {
8567
8880
  }
8568
8881
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
8569
8882
  const deadline = Date.now() + timeoutMs;
8570
- await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(lockPath), { recursive: true });
8883
+ await import_node_fs28.promises.mkdir(import_node_path46.default.dirname(lockPath), { recursive: true });
8571
8884
  let probedHolder = false;
8572
8885
  while (true) {
8573
8886
  try {
@@ -8620,10 +8933,10 @@ async function readRegistry() {
8620
8933
  throw err;
8621
8934
  }
8622
8935
  const parsed = JSON.parse(raw);
8623
- return import_types32.RegistryFileSchema.parse(parsed);
8936
+ return import_types33.RegistryFileSchema.parse(parsed);
8624
8937
  }
8625
8938
  async function writeRegistry(reg) {
8626
- const validated = import_types32.RegistryFileSchema.parse(reg);
8939
+ const validated = import_types33.RegistryFileSchema.parse(reg);
8627
8940
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
8628
8941
  }
8629
8942
  var ProjectNameCollisionError = class extends Error {
@@ -8818,7 +9131,7 @@ init_auth();
8818
9131
  // src/connectors-config.ts
8819
9132
  init_cjs_shims();
8820
9133
  var import_node_os4 = __toESM(require("os"), 1);
8821
- var import_node_path46 = __toESM(require("path"), 1);
9134
+ var import_node_path47 = __toESM(require("path"), 1);
8822
9135
  var import_node_fs29 = require("fs");
8823
9136
  var CONNECTORS_CONFIG_VERSION = 1;
8824
9137
  var EnvRefUnsetError = class extends Error {
@@ -8833,11 +9146,11 @@ var EnvRefUnsetError = class extends Error {
8833
9146
  };
8834
9147
  function neatHome2() {
8835
9148
  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");
9149
+ if (override && override.length > 0) return import_node_path47.default.resolve(override);
9150
+ return import_node_path47.default.join(import_node_os4.default.homedir(), ".neat");
8838
9151
  }
8839
9152
  function connectorsConfigPath(home = neatHome2()) {
8840
- return import_node_path46.default.join(home, "connectors.json");
9153
+ return import_node_path47.default.join(home, "connectors.json");
8841
9154
  }
8842
9155
  var MODE_MASK_LOOSER_THAN_0600 = 63;
8843
9156
  async function warnIfModeLooserThan0600(file) {
@@ -9173,11 +9486,11 @@ function registerRoutes(scope, ctx) {
9173
9486
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
9174
9487
  const parsed = [];
9175
9488
  for (const c of candidates) {
9176
- const r = import_types33.DivergenceTypeSchema.safeParse(c);
9489
+ const r = import_types34.DivergenceTypeSchema.safeParse(c);
9177
9490
  if (!r.success) {
9178
9491
  return reply.code(400).send({
9179
9492
  error: `unknown divergence type "${c}"`,
9180
- allowed: import_types33.DivergenceTypeSchema.options
9493
+ allowed: import_types34.DivergenceTypeSchema.options
9181
9494
  });
9182
9495
  }
9183
9496
  parsed.push(r.data);
@@ -9448,7 +9761,7 @@ function registerRoutes(scope, ctx) {
9448
9761
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
9449
9762
  let violations = await log.readAll();
9450
9763
  if (req.query.severity) {
9451
- const sev = import_types33.PolicySeveritySchema.safeParse(req.query.severity);
9764
+ const sev = import_types34.PolicySeveritySchema.safeParse(req.query.severity);
9452
9765
  if (!sev.success) {
9453
9766
  return reply.code(400).send({
9454
9767
  error: "invalid severity",
@@ -9487,7 +9800,7 @@ function registerRoutes(scope, ctx) {
9487
9800
  scope.post("/policies/check", async (req, reply) => {
9488
9801
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
9489
9802
  if (!proj) return;
9490
- const parsed = import_types33.PoliciesCheckBodySchema.safeParse(req.body ?? {});
9803
+ const parsed = import_types34.PoliciesCheckBodySchema.safeParse(req.body ?? {});
9491
9804
  if (!parsed.success) {
9492
9805
  return reply.code(400).send({
9493
9806
  error: "invalid /policies/check body",
@@ -9753,21 +10066,21 @@ init_otel_grpc();
9753
10066
  // src/daemon.ts
9754
10067
  init_cjs_shims();
9755
10068
  var import_node_fs31 = require("fs");
9756
- var import_node_path50 = __toESM(require("path"), 1);
10069
+ var import_node_path51 = __toESM(require("path"), 1);
9757
10070
  var import_node_module = require("module");
9758
10071
  init_otel();
9759
10072
 
9760
10073
  // src/connectors/index.ts
9761
10074
  init_cjs_shims();
9762
- var import_types34 = require("@neat.is/types");
10075
+ var import_types35 = require("@neat.is/types");
9763
10076
  var NO_ENV = "unknown";
9764
10077
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
9765
10078
  if (!graph.hasNode(targetNodeId)) return void 0;
9766
10079
  const sites = [];
9767
10080
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
9768
10081
  const edge = graph.getEdgeAttributes(edgeId);
9769
- if (edge.provenance !== import_types34.Provenance.EXTRACTED) continue;
9770
- const parsed = (0, import_types34.parseFileId)(edge.source);
10082
+ if (edge.provenance !== import_types35.Provenance.EXTRACTED) continue;
10083
+ const parsed = (0, import_types35.parseFileId)(edge.source);
9771
10084
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
9772
10085
  const site = { relPath: edge.evidence.file };
9773
10086
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -9778,7 +10091,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
9778
10091
  function routeCallSiteFor(graph, targetNodeId) {
9779
10092
  if (!graph.hasNode(targetNodeId)) return void 0;
9780
10093
  const attrs = graph.getNodeAttributes(targetNodeId);
9781
- if (attrs.type !== import_types34.NodeType.RouteNode || !attrs.path) return void 0;
10094
+ if (attrs.type !== import_types35.NodeType.RouteNode || !attrs.path) return void 0;
9782
10095
  const site = { relPath: attrs.path };
9783
10096
  if (attrs.line !== void 0) site.line = attrs.line;
9784
10097
  return site;
@@ -9952,7 +10265,12 @@ var JUNCTION_DEFAULT_RATE_LIMITS = {
9952
10265
  supabase: { capacity: 30, refillMs: 1e4 },
9953
10266
  // Not a documented API limit at all — a self-imposed ceiling on the raw
9954
10267
  // pg_stat_statements connection (see module header above).
9955
- "supabase-postgres": { capacity: 20, refillMs: 3e3 }
10268
+ "supabase-postgres": { capacity: 20, refillMs: 3e3 },
10269
+ // Push provider (ADR-146): the Drains REST API is touched only by `neat
10270
+ // connector add/remove/test` (provision/deprovision/validate), never a poll
10271
+ // loop, so this bucket is exercised a handful of times per command. Kept
10272
+ // conservative pending a documented Drains-API rate limit.
10273
+ vercel: { capacity: 20, refillMs: 5e3 }
9956
10274
  };
9957
10275
  var JUNCTION_GENERIC_RATE_LIMIT = { capacity: 20, refillMs: 5e3 };
9958
10276
  function defaultRateLimitFor(provider) {
@@ -10244,10 +10562,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
10244
10562
  // src/connectors/supabase/map.ts
10245
10563
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
10246
10564
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
10247
- function targetFromRestPath(path51) {
10248
- const rpcMatch = REST_RPC_PATH_RE.exec(path51);
10565
+ function targetFromRestPath(path52) {
10566
+ const rpcMatch = REST_RPC_PATH_RE.exec(path52);
10249
10567
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
10250
- const tableMatch = REST_TABLE_PATH_RE.exec(path51);
10568
+ const tableMatch = REST_TABLE_PATH_RE.exec(path52);
10251
10569
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
10252
10570
  return null;
10253
10571
  }
@@ -10356,23 +10674,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
10356
10674
 
10357
10675
  // src/connectors/supabase/resolve.ts
10358
10676
  init_cjs_shims();
10359
- var import_types36 = require("@neat.is/types");
10677
+ var import_types37 = require("@neat.is/types");
10360
10678
  function createSupabaseResolveTarget(graph, config) {
10361
10679
  return (signal, _ctx) => {
10362
10680
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
10363
10681
  return null;
10364
10682
  }
10365
- const subResourceId = (0, import_types36.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10683
+ const subResourceId = (0, import_types37.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10366
10684
  if (graph.hasNode(subResourceId)) {
10367
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
10685
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10368
10686
  }
10369
- const bareResourceId = (0, import_types36.infraId)(signal.targetKind, signal.targetName);
10687
+ const bareResourceId = (0, import_types37.infraId)(signal.targetKind, signal.targetName);
10370
10688
  if (graph.hasNode(bareResourceId)) {
10371
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
10689
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10372
10690
  }
10373
- const projectLevelId = (0, import_types36.infraId)("supabase", config.nodeRef);
10691
+ const projectLevelId = (0, import_types37.infraId)("supabase", config.nodeRef);
10374
10692
  if (graph.hasNode(projectLevelId)) {
10375
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
10693
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10376
10694
  }
10377
10695
  return null;
10378
10696
  };
@@ -10465,7 +10783,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
10465
10783
 
10466
10784
  // src/connectors/railway/index.ts
10467
10785
  init_cjs_shims();
10468
- var import_types40 = require("@neat.is/types");
10786
+ var import_types41 = require("@neat.is/types");
10469
10787
 
10470
10788
  // src/connectors/railway/client.ts
10471
10789
  init_cjs_shims();
@@ -10591,7 +10909,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
10591
10909
  const out = [];
10592
10910
  graph.forEachNode((_id, attrs) => {
10593
10911
  const node = attrs;
10594
- if (node.type !== import_types40.NodeType.RouteNode) return;
10912
+ if (node.type !== import_types41.NodeType.RouteNode) return;
10595
10913
  const route = attrs;
10596
10914
  if (route.service !== serviceName) return;
10597
10915
  out.push({
@@ -10695,12 +11013,12 @@ function createRailwayResolveTarget(config) {
10695
11013
  const serviceName = config.serviceNameById[config.serviceId];
10696
11014
  if (!serviceName) return null;
10697
11015
  if (signal.targetKind === ROUTE_TARGET_KIND) {
10698
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types40.EdgeType.CALLS };
11016
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types41.EdgeType.CALLS };
10699
11017
  }
10700
11018
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
10701
11019
  const peerName = config.serviceNameById[signal.targetName];
10702
11020
  if (!peerName) return null;
10703
- return { targetNodeId: (0, import_types40.serviceId)(peerName), serviceName, edgeType: import_types40.EdgeType.CONNECTS_TO };
11021
+ return { targetNodeId: (0, import_types41.serviceId)(peerName), serviceName, edgeType: import_types41.EdgeType.CONNECTS_TO };
10704
11022
  }
10705
11023
  return null;
10706
11024
  };
@@ -10824,9 +11142,9 @@ function parseFirebaseTargetName(targetName) {
10824
11142
  const secondSep = rest.indexOf(FIELD_SEP);
10825
11143
  if (secondSep === -1) return null;
10826
11144
  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 };
11145
+ const path52 = rest.slice(secondSep + 1);
11146
+ if (!resourceName || !method || !path52) return null;
11147
+ return { resourceName, method, path: path52 };
10830
11148
  }
10831
11149
  function resourceNameFor(type, labels) {
10832
11150
  if (!labels) return null;
@@ -10864,14 +11182,14 @@ function mapLogEntryToSignal(entry) {
10864
11182
  if (!req) return null;
10865
11183
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
10866
11184
  const method = req.requestMethod.toUpperCase();
10867
- const path51 = pathFromRequestUrl(req.requestUrl);
10868
- if (path51 === null) return null;
11185
+ const path52 = pathFromRequestUrl(req.requestUrl);
11186
+ if (path52 === null) return null;
10869
11187
  const timestamp = entry.timestamp;
10870
11188
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
10871
11189
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
10872
11190
  return {
10873
11191
  targetKind: resourceType,
10874
- targetName: packFirebaseTargetName({ resourceName, method, path: path51 }),
11192
+ targetName: packFirebaseTargetName({ resourceName, method, path: path52 }),
10875
11193
  callCount: 1,
10876
11194
  errorCount: isError ? 1 : 0,
10877
11195
  lastObservedIso: timestamp
@@ -10888,7 +11206,7 @@ function mapLogEntriesToSignals(entries) {
10888
11206
 
10889
11207
  // src/connectors/firebase/resolve.ts
10890
11208
  init_cjs_shims();
10891
- var import_types41 = require("@neat.is/types");
11209
+ var import_types42 = require("@neat.is/types");
10892
11210
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
10893
11211
  switch (resourceType) {
10894
11212
  case "cloud_function":
@@ -10903,7 +11221,7 @@ function routeEntriesFor(graph, serviceName) {
10903
11221
  const entries = [];
10904
11222
  graph.forEachNode((_id, attrs) => {
10905
11223
  const node = attrs;
10906
- if (node.type !== import_types41.NodeType.RouteNode) return;
11224
+ if (node.type !== import_types42.NodeType.RouteNode) return;
10907
11225
  const route = attrs;
10908
11226
  if (route.service !== serviceName) return;
10909
11227
  entries.push({
@@ -10935,7 +11253,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
10935
11253
  return {
10936
11254
  targetNodeId: match.routeNodeId,
10937
11255
  serviceName,
10938
- edgeType: import_types41.EdgeType.CALLS
11256
+ edgeType: import_types42.EdgeType.CALLS
10939
11257
  };
10940
11258
  };
10941
11259
  }
@@ -10962,7 +11280,7 @@ init_cjs_shims();
10962
11280
 
10963
11281
  // src/connectors/cloudflare/connector.ts
10964
11282
  init_cjs_shims();
10965
- var import_types43 = require("@neat.is/types");
11283
+ var import_types44 = require("@neat.is/types");
10966
11284
 
10967
11285
  // src/connectors/cloudflare/client.ts
10968
11286
  init_cjs_shims();
@@ -11078,7 +11396,7 @@ function mapEventToSignal(event) {
11078
11396
  if (Number.isNaN(observedAt.getTime())) return null;
11079
11397
  const statusCode = metadata?.statusCode;
11080
11398
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
11081
- const path51 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
11399
+ const path52 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
11082
11400
  return {
11083
11401
  targetKind: CLOUDFLARE_TARGET_KIND,
11084
11402
  targetName: scriptName,
@@ -11086,7 +11404,7 @@ function mapEventToSignal(event) {
11086
11404
  errorCount: isError ? 1 : 0,
11087
11405
  lastObservedIso: observedAt.toISOString(),
11088
11406
  method,
11089
- ...path51 ? { path: path51 } : {},
11407
+ ...path52 ? { path: path52 } : {},
11090
11408
  ...typeof statusCode === "number" ? { statusCode } : {},
11091
11409
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
11092
11410
  };
@@ -11126,19 +11444,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
11126
11444
  graph.forEachNode((id, attrs) => {
11127
11445
  if (found) return;
11128
11446
  const a = attrs;
11129
- if (a.type === import_types43.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11447
+ if (a.type === import_types44.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11130
11448
  found = id;
11131
11449
  }
11132
11450
  });
11133
11451
  return found;
11134
11452
  }
11135
- function findMatchingRouteNode(graph, serviceName, method, path51) {
11136
- const normalizedPath = normalizePathTemplate(path51);
11453
+ function findMatchingRouteNode(graph, serviceName, method, path52) {
11454
+ const normalizedPath = normalizePathTemplate(path52);
11137
11455
  let found = null;
11138
11456
  graph.forEachNode((id, attrs) => {
11139
11457
  if (found) return;
11140
11458
  const a = attrs;
11141
- if (a.type !== import_types43.NodeType.RouteNode || a.service !== serviceName) return;
11459
+ if (a.type !== import_types44.NodeType.RouteNode || a.service !== serviceName) return;
11142
11460
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
11143
11461
  const routeMethod = (a.method ?? "").toUpperCase();
11144
11462
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -11150,18 +11468,18 @@ function createCloudflareResolveTarget(config, graph) {
11150
11468
  return (signal) => {
11151
11469
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
11152
11470
  const scriptName = signal.targetName;
11153
- const { method, path: path51 } = signal;
11471
+ const { method, path: path52 } = signal;
11154
11472
  const resolveRouteGrain = (serviceName, wholeFileId) => {
11155
- if (!method || !path51) return wholeFileId;
11156
- return findMatchingRouteNode(graph, serviceName, method, path51) ?? wholeFileId;
11473
+ if (!method || !path52) return wholeFileId;
11474
+ return findMatchingRouteNode(graph, serviceName, method, path52) ?? wholeFileId;
11157
11475
  };
11158
11476
  const mapping = config.workers?.[scriptName];
11159
11477
  if (mapping) {
11160
- const wholeFileId = (0, import_types43.fileId)(mapping.service, mapping.entryFile);
11478
+ const wholeFileId = (0, import_types44.fileId)(mapping.service, mapping.entryFile);
11161
11479
  return {
11162
11480
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
11163
11481
  serviceName: mapping.service,
11164
- edgeType: import_types43.EdgeType.CALLS
11482
+ edgeType: import_types44.EdgeType.CALLS
11165
11483
  };
11166
11484
  }
11167
11485
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -11170,18 +11488,124 @@ function createCloudflareResolveTarget(config, graph) {
11170
11488
  return {
11171
11489
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
11172
11490
  serviceName: fileNode.service,
11173
- edgeType: import_types43.EdgeType.CALLS
11491
+ edgeType: import_types44.EdgeType.CALLS
11174
11492
  };
11175
11493
  }
11176
11494
  return {
11177
- targetNodeId: (0, import_types43.infraId)("cloudflare-worker", scriptName),
11495
+ targetNodeId: (0, import_types44.infraId)("cloudflare-worker", scriptName),
11178
11496
  serviceName: scriptName,
11179
- edgeType: import_types43.EdgeType.CALLS,
11497
+ edgeType: import_types44.EdgeType.CALLS,
11180
11498
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
11181
11499
  };
11182
11500
  };
11183
11501
  }
11184
11502
 
11503
+ // src/connectors/vercel/index.ts
11504
+ init_cjs_shims();
11505
+
11506
+ // src/connectors/vercel/client.ts
11507
+ init_cjs_shims();
11508
+ var DEFAULT_API_BASE_URL = "https://api.vercel.com";
11509
+ var DEFAULT_DRAIN_NAME = "neat-otlp";
11510
+ var TRACE_SCHEMAS = { trace: { version: "v1" } };
11511
+ function apiBase(config) {
11512
+ return config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
11513
+ }
11514
+ function teamQuery(config) {
11515
+ return `?teamId=${encodeURIComponent(config.teamId)}`;
11516
+ }
11517
+ function drainDelivery(config, otelToken) {
11518
+ return {
11519
+ type: "http",
11520
+ endpoint: config.endpoint,
11521
+ encoding: "json",
11522
+ headers: bearerAuthHeader(otelToken),
11523
+ ...config.secret ? { secret: config.secret } : {}
11524
+ };
11525
+ }
11526
+ async function describeError(res) {
11527
+ try {
11528
+ const data = await res.json();
11529
+ const message = data?.error?.message;
11530
+ return typeof message === "string" && message.length > 0 ? ` \u2014 ${message}` : "";
11531
+ } catch {
11532
+ return "";
11533
+ }
11534
+ }
11535
+ async function createVercelDrain(config, credentials, fetchImpl = fetch) {
11536
+ const projectIds = config.projectIds ?? [];
11537
+ const body = {
11538
+ name: config.drainName ?? DEFAULT_DRAIN_NAME,
11539
+ projects: projectIds.length > 0 ? "some" : "all",
11540
+ ...projectIds.length > 0 ? { projectIds } : {},
11541
+ schemas: TRACE_SCHEMAS,
11542
+ delivery: drainDelivery(config, credentials.otelToken),
11543
+ source: { kind: "self-served" }
11544
+ };
11545
+ const res = await junctionFetch(
11546
+ `${apiBase(config)}/v1/drains${teamQuery(config)}`,
11547
+ {
11548
+ method: "POST",
11549
+ headers: { "Content-Type": "application/json", ...bearerAuthHeader(credentials.token) },
11550
+ body: JSON.stringify(body)
11551
+ },
11552
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11553
+ );
11554
+ if (!res.ok) {
11555
+ throw new Error(
11556
+ `vercel connector: create drain failed (${res.status} ${res.statusText}${await describeError(res)})`
11557
+ );
11558
+ }
11559
+ const payload = await res.json().catch(() => null);
11560
+ if (!payload || typeof payload.id !== "string" || payload.id.length === 0) {
11561
+ throw new Error(
11562
+ "vercel connector: create drain returned no drain id \u2014 the Drains API response shape may have changed"
11563
+ );
11564
+ }
11565
+ return {
11566
+ id: payload.id,
11567
+ ...payload.status ? { status: payload.status } : {},
11568
+ ...payload.disabledReason ? { disabledReason: payload.disabledReason } : {}
11569
+ };
11570
+ }
11571
+ async function deleteVercelDrain(config, drainId, credentials, fetchImpl = fetch) {
11572
+ const res = await junctionFetch(
11573
+ `${apiBase(config)}/v1/drains/${encodeURIComponent(drainId)}${teamQuery(config)}`,
11574
+ { method: "DELETE", headers: { ...bearerAuthHeader(credentials.token) } },
11575
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11576
+ );
11577
+ if (res.ok || res.status === 404) return;
11578
+ throw new Error(
11579
+ `vercel connector: delete drain failed (${res.status} ${res.statusText}${await describeError(res)})`
11580
+ );
11581
+ }
11582
+ async function testVercelDrainDelivery(config, credentials, fetchImpl = fetch) {
11583
+ const res = await junctionFetch(
11584
+ `${apiBase(config)}/v1/drains/test${teamQuery(config)}`,
11585
+ {
11586
+ method: "POST",
11587
+ headers: { "Content-Type": "application/json", ...bearerAuthHeader(credentials.token) },
11588
+ body: JSON.stringify({ schemas: TRACE_SCHEMAS, delivery: drainDelivery(config, credentials.otelToken) })
11589
+ },
11590
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11591
+ );
11592
+ if (res.status === 401 || res.status === 403) {
11593
+ return { status: "failure", error: `vercel rejected the API token (HTTP ${res.status})` };
11594
+ }
11595
+ if (!res.ok) {
11596
+ return {
11597
+ status: "failure",
11598
+ error: `vercel drain validation failed (${res.status} ${res.statusText}${await describeError(res)})`
11599
+ };
11600
+ }
11601
+ const payload = await res.json().catch(() => null);
11602
+ return {
11603
+ ...payload?.status ? { status: payload.status } : {},
11604
+ ...payload?.error ? { error: payload.error } : {},
11605
+ ...payload?.endpoint ? { endpoint: payload.endpoint } : {}
11606
+ };
11607
+ }
11608
+
11185
11609
  // src/connectors/registry.ts
11186
11610
  var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
11187
11611
  async function authProbe(input) {
@@ -11333,6 +11757,91 @@ var PROVIDER_DISPATCH = {
11333
11757
  }
11334
11758
  }
11335
11759
  };
11760
+ function vercelCredsFrom(credentials) {
11761
+ return { token: String(credentials.token ?? ""), otelToken: String(credentials.otelToken ?? "") };
11762
+ }
11763
+ function vercelConfigFromOptions(options) {
11764
+ const raw = options.projectIds;
11765
+ let projectIds;
11766
+ if (Array.isArray(raw)) projectIds = raw.filter((p) => typeof p === "string");
11767
+ else if (typeof raw === "string" && raw.trim().length > 0) {
11768
+ projectIds = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
11769
+ }
11770
+ return {
11771
+ teamId: String(options.teamId ?? ""),
11772
+ endpoint: String(options.endpoint ?? ""),
11773
+ ...projectIds && projectIds.length > 0 ? { projectIds } : {},
11774
+ ...typeof options.drainId === "string" ? { drainId: options.drainId } : {},
11775
+ ...typeof options.drainName === "string" ? { drainName: options.drainName } : {},
11776
+ ...typeof options.apiBaseUrl === "string" ? { apiBaseUrl: options.apiBaseUrl } : {},
11777
+ ...typeof options.secret === "string" ? { secret: options.secret } : {}
11778
+ };
11779
+ }
11780
+ var PUSH_PROVIDER_DISPATCH = {
11781
+ vercel: {
11782
+ provider: "vercel",
11783
+ // The Vercel access token is the "primary" secret a single `--token`
11784
+ // populates; `otelToken` (the daemon's OTLP bearer) is the second field.
11785
+ primaryCredentialKey: "token",
11786
+ requiredCredentialFields: ["token", "otelToken"],
11787
+ // teamId scopes every Drains call; endpoint is where the drain delivers.
11788
+ // projectIds is optional (absent → the drain covers the whole team).
11789
+ requiredOptionFields: ["teamId", "endpoint"],
11790
+ // POST /v1/drains/test — authenticates the token and pings the endpoint
11791
+ // with a sample event, so `success` means the credential is live *and* the
11792
+ // daemon's OTLP endpoint is reachable and accepted the drain's bearer.
11793
+ async validate({ credentials, options, fetchImpl }) {
11794
+ const result = await testVercelDrainDelivery(
11795
+ vercelConfigFromOptions(options),
11796
+ vercelCredsFrom(credentials),
11797
+ fetchImpl
11798
+ );
11799
+ if (result.status === "success") return { ok: true };
11800
+ return {
11801
+ ok: false,
11802
+ reason: result.error ?? `vercel drain delivery test returned "${result.status ?? "no status"}"`
11803
+ };
11804
+ },
11805
+ // POST /v1/drains — creates the trace drain, returns its id to store in
11806
+ // `options.drainId`. A created-but-not-enabled drain is surfaced as a note,
11807
+ // not a failure (the entry still points at a real drain).
11808
+ async provision({ credentials, options, fetchImpl }) {
11809
+ try {
11810
+ const created = await createVercelDrain(
11811
+ vercelConfigFromOptions(options),
11812
+ vercelCredsFrom(credentials),
11813
+ fetchImpl
11814
+ );
11815
+ 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;
11816
+ return { ok: true, options: { drainId: created.id }, ...note ? { note } : {} };
11817
+ } catch (err) {
11818
+ return { ok: false, reason: err.message };
11819
+ }
11820
+ },
11821
+ // DELETE /v1/drains/{id} — idempotent (deleteVercelDrain treats 404 as
11822
+ // success). No recorded drainId → nothing to delete, still a success.
11823
+ async deprovision({ credentials, options, fetchImpl }) {
11824
+ const drainId = typeof options.drainId === "string" ? options.drainId : "";
11825
+ if (!drainId) {
11826
+ return { ok: true, note: "no drain id was recorded \u2014 nothing to delete on the Vercel side" };
11827
+ }
11828
+ try {
11829
+ await deleteVercelDrain(
11830
+ vercelConfigFromOptions(options),
11831
+ drainId,
11832
+ vercelCredsFrom(credentials),
11833
+ fetchImpl
11834
+ );
11835
+ return { ok: true };
11836
+ } catch (err) {
11837
+ return { ok: false, reason: err.message };
11838
+ }
11839
+ }
11840
+ }
11841
+ };
11842
+ function isPushProvider(provider) {
11843
+ return provider in PUSH_PROVIDER_DISPATCH;
11844
+ }
11336
11845
  function resolveEntryCredentials(dispatch, entry, env) {
11337
11846
  let credentials;
11338
11847
  try {
@@ -11355,6 +11864,13 @@ function resolveEntryCredentials(dispatch, entry, env) {
11355
11864
  function buildRegistration(entry, graph, env = process.env) {
11356
11865
  const dispatch = PROVIDER_DISPATCH[entry.provider];
11357
11866
  if (!dispatch) {
11867
+ if (isPushProvider(entry.provider)) {
11868
+ return {
11869
+ ok: false,
11870
+ push: true,
11871
+ reason: `push provider "${entry.provider}" ingests via the OTLP receiver \u2014 nothing to poll`
11872
+ };
11873
+ }
11358
11874
  return { ok: false, reason: `unknown provider "${entry.provider}"` };
11359
11875
  }
11360
11876
  const creds = resolveEntryCredentials(dispatch, entry, env);
@@ -11405,7 +11921,7 @@ async function loadConnectorRegistrations(input) {
11405
11921
  if (!connectorMatchesProject(entry, project)) continue;
11406
11922
  const result = buildRegistration(entry, graph, env);
11407
11923
  if (result.ok) registrations.push(result.registration);
11408
- else onSkip?.(entry, result.reason);
11924
+ else if (!result.push) onSkip?.(entry, result.reason);
11409
11925
  }
11410
11926
  return registrations;
11411
11927
  }
@@ -11416,7 +11932,7 @@ init_auth();
11416
11932
  // src/unrouted.ts
11417
11933
  init_cjs_shims();
11418
11934
  var import_node_fs30 = require("fs");
11419
- var import_node_path49 = __toESM(require("path"), 1);
11935
+ var import_node_path50 = __toESM(require("path"), 1);
11420
11936
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
11421
11937
  return {
11422
11938
  timestamp: now.toISOString(),
@@ -11426,34 +11942,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
11426
11942
  };
11427
11943
  }
11428
11944
  async function appendUnroutedSpan(neatHome3, record) {
11429
- const target = import_node_path49.default.join(neatHome3, "errors.ndjson");
11945
+ const target = import_node_path50.default.join(neatHome3, "errors.ndjson");
11430
11946
  await import_node_fs30.promises.mkdir(neatHome3, { recursive: true });
11431
11947
  await import_node_fs30.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
11432
11948
  }
11433
11949
  function unroutedErrorsPath(neatHome3) {
11434
- return import_node_path49.default.join(neatHome3, "errors.ndjson");
11950
+ return import_node_path50.default.join(neatHome3, "errors.ndjson");
11435
11951
  }
11436
11952
 
11437
11953
  // src/daemon.ts
11438
- var import_types46 = require("@neat.is/types");
11954
+ var import_types47 = require("@neat.is/types");
11439
11955
  function daemonJsonPath(scanPath) {
11440
- return import_node_path50.default.join(scanPath, "neat-out", "daemon.json");
11956
+ return import_node_path51.default.join(scanPath, "neat-out", "daemon.json");
11441
11957
  }
11442
11958
  function daemonsDiscoveryDir(home) {
11443
11959
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
11444
- return import_node_path50.default.join(base, "daemons");
11960
+ return import_node_path51.default.join(base, "daemons");
11445
11961
  }
11446
11962
  function daemonDiscoveryPath(project, home) {
11447
- return import_node_path50.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
11963
+ return import_node_path51.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
11448
11964
  }
11449
11965
  function sanitizeDiscoveryName(project) {
11450
11966
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
11451
11967
  }
11452
11968
  function neatHomeFromEnv() {
11453
11969
  const env = process.env.NEAT_HOME;
11454
- if (env && env.length > 0) return import_node_path50.default.resolve(env);
11970
+ if (env && env.length > 0) return import_node_path51.default.resolve(env);
11455
11971
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11456
- return import_node_path50.default.join(home, ".neat");
11972
+ return import_node_path51.default.join(home, ".neat");
11457
11973
  }
11458
11974
  function resolveNeatVersion() {
11459
11975
  if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
@@ -11508,11 +12024,11 @@ function teardownSlot(slot) {
11508
12024
  }
11509
12025
  }
11510
12026
  function neatHomeFor(opts) {
11511
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path50.default.resolve(opts.neatHome);
12027
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path51.default.resolve(opts.neatHome);
11512
12028
  const env = process.env.NEAT_HOME;
11513
- if (env && env.length > 0) return import_node_path50.default.resolve(env);
12029
+ if (env && env.length > 0) return import_node_path51.default.resolve(env);
11514
12030
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11515
- return import_node_path50.default.join(home, ".neat");
12031
+ return import_node_path51.default.join(home, ".neat");
11516
12032
  }
11517
12033
  function routeSpanToProject(serviceName, projects) {
11518
12034
  if (!serviceName) return DEFAULT_PROJECT;
@@ -11556,11 +12072,11 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
11556
12072
  if (!serviceName) return true;
11557
12073
  if (serviceNameMatchesProject(serviceName, project)) return true;
11558
12074
  return graph.someNode(
11559
- (_id, attrs) => attrs.type === import_types46.NodeType.ServiceNode && attrs.name === serviceName
12075
+ (_id, attrs) => attrs.type === import_types47.NodeType.ServiceNode && attrs.name === serviceName
11560
12076
  );
11561
12077
  }
11562
12078
  async function bootstrapProject(entry, connectors = [], neatHome3) {
11563
- const paths = pathsForProject(entry.name, import_node_path50.default.join(entry.path, "neat-out"));
12079
+ const paths = pathsForProject(entry.name, import_node_path51.default.join(entry.path, "neat-out"));
11564
12080
  try {
11565
12081
  const stat = await import_node_fs31.promises.stat(entry.path);
11566
12082
  if (!stat.isDirectory()) {
@@ -11691,7 +12207,7 @@ async function startDaemon(opts = {}) {
11691
12207
  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
12208
  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
12209
  const singleProject = projectArg;
11694
- const singleProjectPath = singleProject && projectPathArg ? import_node_path50.default.resolve(projectPathArg) : null;
12210
+ const singleProjectPath = singleProject && projectPathArg ? import_node_path51.default.resolve(projectPathArg) : null;
11695
12211
  if (singleProject && !singleProjectPath) {
11696
12212
  throw new Error(
11697
12213
  `neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
@@ -11706,7 +12222,7 @@ async function startDaemon(opts = {}) {
11706
12222
  );
11707
12223
  }
11708
12224
  }
11709
- const pidPath = import_node_path50.default.join(home, "neatd.pid");
12225
+ const pidPath = import_node_path51.default.join(home, "neatd.pid");
11710
12226
  await writeAtomically(pidPath, `${process.pid}
11711
12227
  `);
11712
12228
  const slots = /* @__PURE__ */ new Map();
@@ -12113,8 +12629,8 @@ async function startDaemon(opts = {}) {
12113
12629
  let registryWatcher = null;
12114
12630
  let reloadTimer = null;
12115
12631
  if (!singleProject) try {
12116
- const regDir = import_node_path50.default.dirname(regPath);
12117
- const regBase = import_node_path50.default.basename(regPath);
12632
+ const regDir = import_node_path51.default.dirname(regPath);
12633
+ const regBase = import_node_path51.default.basename(regPath);
12118
12634
  registryWatcher = (0, import_node_fs31.watch)(regDir, (_eventType, filename) => {
12119
12635
  if (filename !== null && filename !== regBase) return;
12120
12636
  if (reloadTimer) clearTimeout(reloadTimer);