@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/neatd.cjs CHANGED
@@ -61,8 +61,8 @@ function mountBearerAuth(app, opts) {
61
61
  ]);
62
62
  const publicRead = opts.publicRead === true;
63
63
  app.addHook("preHandler", (req2, reply, done) => {
64
- const path53 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
- if (exactUnauthPaths.has(path53) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path53)) {
64
+ const path54 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
+ if (exactUnauthPaths.has(path54) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path54)) {
66
66
  done();
67
67
  return;
68
68
  }
@@ -194,8 +194,8 @@ function reshapeGrpcRequest(req2) {
194
194
  };
195
195
  }
196
196
  function resolveProtoRoot() {
197
- const here = import_node_path47.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
198
- return import_node_path47.default.resolve(here, "..", "proto");
197
+ const here = import_node_path48.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
198
+ return import_node_path48.default.resolve(here, "..", "proto");
199
199
  }
200
200
  function loadTraceService() {
201
201
  const protoRoot = resolveProtoRoot();
@@ -263,13 +263,13 @@ async function startOtelGrpcReceiver(opts) {
263
263
  })
264
264
  };
265
265
  }
266
- var import_node_url, import_node_path47, import_node_crypto2, grpc, protoLoader;
266
+ var import_node_url, import_node_path48, import_node_crypto2, grpc, protoLoader;
267
267
  var init_otel_grpc = __esm({
268
268
  "src/otel-grpc.ts"() {
269
269
  "use strict";
270
270
  init_cjs_shims();
271
271
  import_node_url = require("url");
272
- import_node_path47 = __toESM(require("path"), 1);
272
+ import_node_path48 = __toESM(require("path"), 1);
273
273
  import_node_crypto2 = require("crypto");
274
274
  grpc = __toESM(require("@grpc/grpc-js"), 1);
275
275
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -362,8 +362,8 @@ function websocketChannelPathOf(attrs) {
362
362
  const v = attrs[key];
363
363
  if (typeof v === "string" && v.length > 0) {
364
364
  const q = v.indexOf("?");
365
- const path53 = q === -1 ? v : v.slice(0, q);
366
- if (path53.length > 0) return path53;
365
+ const path54 = q === -1 ? v : v.slice(0, q);
366
+ if (path54.length > 0) return path54;
367
367
  }
368
368
  }
369
369
  return void 0;
@@ -398,6 +398,7 @@ function parseOtlpRequest(body) {
398
398
  attributes: attrs,
399
399
  dbSystem: typeof attrs["db.system"] === "string" ? attrs["db.system"] : void 0,
400
400
  dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
401
+ dbCollection: typeof attrs["db.collection.name"] === "string" ? attrs["db.collection.name"] : typeof attrs["db.mongodb.collection"] === "string" ? attrs["db.mongodb.collection"] : void 0,
401
402
  messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
402
403
  messagingDestination: messagingDestinationOf(attrs),
403
404
  graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
@@ -417,10 +418,10 @@ function parseOtlpRequest(body) {
417
418
  return out;
418
419
  }
419
420
  function loadProtoRoot() {
420
- const here = import_node_path48.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
421
- const protoRoot = import_node_path48.default.resolve(here, "..", "proto");
421
+ const here = import_node_path49.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
422
+ const protoRoot = import_node_path49.default.resolve(here, "..", "proto");
422
423
  const root = new import_protobufjs.default.Root();
423
- root.resolvePath = (_origin, target) => import_node_path48.default.resolve(protoRoot, target);
424
+ root.resolvePath = (_origin, target) => import_node_path49.default.resolve(protoRoot, target);
424
425
  root.loadSync(
425
426
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
426
427
  { keepCase: true }
@@ -648,12 +649,12 @@ async function listenSteppingOtlp(app, requestedPort, host) {
648
649
  }
649
650
  }
650
651
  }
651
- 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;
652
+ 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;
652
653
  var init_otel = __esm({
653
654
  "src/otel.ts"() {
654
655
  "use strict";
655
656
  init_cjs_shims();
656
- import_node_path48 = __toESM(require("path"), 1);
657
+ import_node_path49 = __toESM(require("path"), 1);
657
658
  import_node_url2 = require("url");
658
659
  import_fastify2 = __toESM(require("fastify"), 1);
659
660
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -677,13 +678,13 @@ __export(neatd_exports, {
677
678
  module.exports = __toCommonJS(neatd_exports);
678
679
  init_cjs_shims();
679
680
  var import_node_fs33 = require("fs");
680
- var import_node_path52 = __toESM(require("path"), 1);
681
+ var import_node_path53 = __toESM(require("path"), 1);
681
682
  var import_node_module2 = require("module");
682
683
 
683
684
  // src/daemon.ts
684
685
  init_cjs_shims();
685
686
  var import_node_fs31 = require("fs");
686
- var import_node_path50 = __toESM(require("path"), 1);
687
+ var import_node_path51 = __toESM(require("path"), 1);
687
688
  var import_node_module = require("module");
688
689
 
689
690
  // src/graph.ts
@@ -1213,19 +1214,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1213
1214
  function longestIncomingWalk(graph, start, maxDepth) {
1214
1215
  let best = { path: [start], edges: [] };
1215
1216
  const visited = /* @__PURE__ */ new Set([start]);
1216
- function step(node, path53, edges) {
1217
- if (path53.length > best.path.length) {
1218
- best = { path: [...path53], edges: [...edges] };
1217
+ function step(node, path54, edges) {
1218
+ if (path54.length > best.path.length) {
1219
+ best = { path: [...path54], edges: [...edges] };
1219
1220
  }
1220
- if (path53.length - 1 >= maxDepth) return;
1221
+ if (path54.length - 1 >= maxDepth) return;
1221
1222
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1222
1223
  for (const [srcId, edge] of incoming) {
1223
1224
  if (visited.has(srcId)) continue;
1224
1225
  visited.add(srcId);
1225
- path53.push(srcId);
1226
+ path54.push(srcId);
1226
1227
  edges.push(edge);
1227
- step(srcId, path53, edges);
1228
- path53.pop();
1228
+ step(srcId, path54, edges);
1229
+ path54.pop();
1229
1230
  edges.pop();
1230
1231
  visited.delete(srcId);
1231
1232
  }
@@ -1419,26 +1420,26 @@ function dominantFailingCall(graph, serviceId6, visited) {
1419
1420
  return best;
1420
1421
  }
1421
1422
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1422
- const path53 = [originServiceId];
1423
+ const path54 = [originServiceId];
1423
1424
  const edges = [];
1424
1425
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1425
1426
  let current = originServiceId;
1426
1427
  for (let depth = 0; depth < maxDepth; depth++) {
1427
1428
  const hop = dominantFailingCall(graph, current, visited);
1428
1429
  if (!hop) break;
1429
- path53.push(hop.nextService);
1430
+ path54.push(hop.nextService);
1430
1431
  edges.push(hop.edge);
1431
1432
  visited.add(hop.nextService);
1432
1433
  current = hop.nextService;
1433
1434
  }
1434
1435
  if (edges.length === 0) return null;
1435
- return { path: path53, edges, culprit: current };
1436
+ return { path: path54, edges, culprit: current };
1436
1437
  }
1437
1438
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1438
1439
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1439
1440
  if (!chain) return null;
1440
1441
  const culprit = chain.culprit;
1441
- const path53 = [...chain.path];
1442
+ const path54 = [...chain.path];
1442
1443
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1443
1444
  const baseConfidence = confidenceFromMix(chain.edges);
1444
1445
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1446,14 +1447,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1446
1447
  if (loc) {
1447
1448
  let rootCauseNode = culprit;
1448
1449
  if (loc.fileNode) {
1449
- path53.push(loc.fileNode);
1450
+ path54.push(loc.fileNode);
1450
1451
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1451
1452
  rootCauseNode = loc.fileNode;
1452
1453
  }
1453
1454
  return import_types.RootCauseResultSchema.parse({
1454
1455
  rootCauseNode,
1455
1456
  rootCauseReason: loc.rootCauseReason,
1456
- traversalPath: path53,
1457
+ traversalPath: path54,
1457
1458
  edgeProvenances,
1458
1459
  confidence,
1459
1460
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1465,7 +1466,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1465
1466
  return import_types.RootCauseResultSchema.parse({
1466
1467
  rootCauseNode: culprit,
1467
1468
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1468
- traversalPath: path53,
1469
+ traversalPath: path54,
1469
1470
  edgeProvenances,
1470
1471
  confidence,
1471
1472
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2892,6 +2893,18 @@ async function handleSpan(ctx, span) {
2892
2893
  callSiteEvidence
2893
2894
  );
2894
2895
  if (result) affectedNode = targetId;
2896
+ if (span.dbSystem === "mongodb" && span.dbCollection) {
2897
+ const collectionId = ensureInfraNode(ctx.graph, "mongodb-collection", span.dbCollection, "self");
2898
+ upsertObservedEdge(
2899
+ ctx.graph,
2900
+ import_types3.EdgeType.CALLS,
2901
+ observedSource(),
2902
+ collectionId,
2903
+ ts,
2904
+ isError,
2905
+ callSiteEvidence
2906
+ );
2907
+ }
2895
2908
  }
2896
2909
  } else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
2897
2910
  const targetId = ensureMessagingDestinationNode(
@@ -5736,7 +5749,7 @@ async function addGrpcMethods(graph, services) {
5736
5749
 
5737
5750
  // src/extract/calls/index.ts
5738
5751
  init_cjs_shims();
5739
- var import_types20 = require("@neat.is/types");
5752
+ var import_types21 = require("@neat.is/types");
5740
5753
 
5741
5754
  // src/extract/calls/http.ts
5742
5755
  init_cjs_shims();
@@ -6449,15 +6462,311 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6449
6462
  return out;
6450
6463
  }
6451
6464
 
6465
+ // src/extract/calls/mongoose.ts
6466
+ init_cjs_shims();
6467
+ var import_node_path31 = __toESM(require("path"), 1);
6468
+ var import_types20 = require("@neat.is/types");
6469
+ var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
6470
+ var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
6471
+ var PLURALIZE_DISABLED_RE = /\bpluralize\s*\(\s*(?:null|false)\s*\)/;
6472
+ var UNCOUNTABLES = /* @__PURE__ */ new Set([
6473
+ "advice",
6474
+ "energy",
6475
+ "excretion",
6476
+ "digestion",
6477
+ "cooperation",
6478
+ "health",
6479
+ "justice",
6480
+ "labour",
6481
+ "machinery",
6482
+ "equipment",
6483
+ "information",
6484
+ "pollution",
6485
+ "sewage",
6486
+ "paper",
6487
+ "money",
6488
+ "species",
6489
+ "series",
6490
+ "rain",
6491
+ "rice",
6492
+ "fish",
6493
+ "sheep",
6494
+ "moose",
6495
+ "deer",
6496
+ "news",
6497
+ "expertise",
6498
+ "status",
6499
+ "media"
6500
+ ]);
6501
+ var PLURALIZE_RULES = [
6502
+ [/human$/gi, "humans"],
6503
+ [/(m)an$/gi, "$1en"],
6504
+ [/(pe)rson$/gi, "$1ople"],
6505
+ [/(child)$/gi, "$1ren"],
6506
+ [/^(ox)$/gi, "$1en"],
6507
+ [/(ax|test)is$/gi, "$1es"],
6508
+ [/(octop|vir)us$/gi, "$1i"],
6509
+ [/(alias|status)$/gi, "$1es"],
6510
+ [/(bu)s$/gi, "$1ses"],
6511
+ [/(buffal|tomat|potat)o$/gi, "$1oes"],
6512
+ [/([ti])um$/gi, "$1a"],
6513
+ [/sis$/gi, "ses"],
6514
+ [/(?:([^f])fe|([lr])f)$/gi, "$1$2ves"],
6515
+ [/(hive)$/gi, "$1s"],
6516
+ [/([^aeiouy]|qu)y$/gi, "$1ies"],
6517
+ [/(x|ch|ss|sh)$/gi, "$1es"],
6518
+ [/(matr|vert|ind)ix|ex$/gi, "$1ices"],
6519
+ [/([m|l])ouse$/gi, "$1ice"],
6520
+ [/(kn|w|l)ife$/gi, "$1ives"],
6521
+ [/(quiz)$/gi, "$1zes"],
6522
+ [/s$/gi, "s"],
6523
+ [/([^a-z])$/, "$1"],
6524
+ [/$/gi, "s"]
6525
+ ];
6526
+ function pluralizeCollection(name) {
6527
+ const str = name.toLowerCase();
6528
+ if (UNCOUNTABLES.has(str)) return str;
6529
+ for (const [re, repl] of PLURALIZE_RULES) {
6530
+ if (str.match(re)) return str.replace(re, repl);
6531
+ }
6532
+ return str;
6533
+ }
6534
+ var MODEL_METHODS = [
6535
+ "find",
6536
+ "findOne",
6537
+ "findById",
6538
+ "findByIdAndUpdate",
6539
+ "findByIdAndDelete",
6540
+ "findOneAndUpdate",
6541
+ "findOneAndDelete",
6542
+ "findOneAndReplace",
6543
+ "countDocuments",
6544
+ "estimatedDocumentCount",
6545
+ "distinct",
6546
+ "aggregate",
6547
+ "exists",
6548
+ "where",
6549
+ "populate",
6550
+ "watch",
6551
+ "hydrate",
6552
+ "create",
6553
+ "insertMany",
6554
+ "insertOne",
6555
+ "updateOne",
6556
+ "updateMany",
6557
+ "replaceOne",
6558
+ "deleteOne",
6559
+ "deleteMany",
6560
+ "bulkWrite",
6561
+ "bulkSave",
6562
+ "save",
6563
+ // pre-v7, removed in current mongoose but present in older codebases:
6564
+ "count",
6565
+ "update",
6566
+ "remove",
6567
+ "findOneAndRemove",
6568
+ "findByIdAndRemove"
6569
+ ];
6570
+ var MODEL_METHODS_ALT = MODEL_METHODS.join("|");
6571
+ function schemaCollectionVars(content) {
6572
+ const out = /* @__PURE__ */ new Map();
6573
+ const re = /(?:const|let|var)\s+(\w+)\s*=\s*new\s+(?:mongoose\s*\.\s*)?Schema\s*\(/g;
6574
+ let m;
6575
+ while ((m = re.exec(content)) !== null) {
6576
+ const windowText = content.slice(m.index, m.index + 2e3);
6577
+ const opt = /\bcollection\s*:\s*['"`]([\w.-]+)['"`]/.exec(windowText);
6578
+ if (opt) out.set(m[1], opt[1]);
6579
+ }
6580
+ return out;
6581
+ }
6582
+ function resolveModel(modelName, rest, schemaColl, pluralizeOn) {
6583
+ const trimmed = rest.trim();
6584
+ const literalThird = /,\s*['"`]([\w.-]+)['"`]\s*$/.exec(trimmed);
6585
+ if (literalThird) return { name: literalThird[1], kind: "mongodb-collection" };
6586
+ const firstIdent = /^([A-Za-z_$][\w$]*)/.exec(trimmed)?.[1];
6587
+ if (firstIdent && schemaColl.has(firstIdent)) {
6588
+ return { name: schemaColl.get(firstIdent), kind: "mongodb-collection" };
6589
+ }
6590
+ if (/,\s*[A-Za-z_$][\w$]*\s*$/.test(trimmed)) {
6591
+ return { name: modelName, kind: "mongodb-model" };
6592
+ }
6593
+ return { name: pluralizeOn ? pluralizeCollection(modelName) : modelName, kind: "mongodb-collection" };
6594
+ }
6595
+ function collectModelDefs(content, pluralizeOn) {
6596
+ const schemaColl = schemaCollectionVars(content);
6597
+ const out = [];
6598
+ 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;
6599
+ let m;
6600
+ while ((m = re.exec(content)) !== null) {
6601
+ out.push({
6602
+ modelName: m[2],
6603
+ varName: m[1] ?? null,
6604
+ resolved: resolveModel(m[2], m[3] ?? "", schemaColl, pluralizeOn),
6605
+ matchText: m[0]
6606
+ });
6607
+ }
6608
+ return out;
6609
+ }
6610
+ function endpoint(r, file, serviceDir, matchText) {
6611
+ const line = lineOf(file.content, matchText);
6612
+ return {
6613
+ infraId: (0, import_types20.infraId)(r.kind, r.name),
6614
+ name: r.name,
6615
+ kind: r.kind,
6616
+ edgeType: "CALLS",
6617
+ confidenceKind: "verified-call-site",
6618
+ evidence: { file: import_node_path31.default.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
6619
+ };
6620
+ }
6621
+ function mongooseEndpointsFromFile(file, serviceDir) {
6622
+ const hasMongoose = MONGOOSE_IMPORT_RE.test(file.content);
6623
+ const hasMongodb = MONGODB_IMPORT_RE.test(file.content);
6624
+ if (!hasMongoose && !hasMongodb) return [];
6625
+ const content = file.content;
6626
+ const out = [];
6627
+ const seen = /* @__PURE__ */ new Set();
6628
+ const push = (r, matchText) => {
6629
+ const key = `${r.kind}/${r.name}`;
6630
+ if (seen.has(key)) return;
6631
+ seen.add(key);
6632
+ out.push(endpoint(r, file, serviceDir, matchText));
6633
+ };
6634
+ if (hasMongoose) {
6635
+ const pluralizeOn = !PLURALIZE_DISABLED_RE.test(content);
6636
+ for (const def of collectModelDefs(content, pluralizeOn)) push(def.resolved, def.matchText);
6637
+ }
6638
+ const collRe = /\.\s*collection\s*\(\s*['"`]([\w.$-]+)['"`]\s*\)/g;
6639
+ let c;
6640
+ while ((c = collRe.exec(content)) !== null) {
6641
+ push({ name: c[1], kind: "mongodb-collection" }, c[0]);
6642
+ }
6643
+ return out;
6644
+ }
6645
+ function parseDestructure(inner, specifier, out) {
6646
+ for (const raw of inner.split(",")) {
6647
+ const part = raw.trim();
6648
+ if (!part) continue;
6649
+ const asM = /^(\w+)\s+as\s+(\w+)$/.exec(part);
6650
+ const colonM = /^(\w+)\s*:\s*(\w+)$/.exec(part);
6651
+ if (asM) out.push({ local: asM[2], kind: "named", exportName: asM[1], specifier });
6652
+ else if (colonM) out.push({ local: colonM[2], kind: "named", exportName: colonM[1], specifier });
6653
+ else {
6654
+ const id = /^(\w+)$/.exec(part);
6655
+ if (id) out.push({ local: id[1], kind: "named", exportName: id[1], specifier });
6656
+ }
6657
+ }
6658
+ }
6659
+ function parseImportBindings(content) {
6660
+ const out = [];
6661
+ const esm = /import\s+([^;'"`\n]+?)\s+from\s*['"`]([^'"`]+)['"`]/g;
6662
+ let m;
6663
+ while ((m = esm.exec(content)) !== null) {
6664
+ const clause = m[1].trim();
6665
+ const spec = m[2];
6666
+ const ns = /^\*\s+as\s+(\w+)$/.exec(clause);
6667
+ if (ns) {
6668
+ out.push({ local: ns[1], kind: "namespace", specifier: spec });
6669
+ continue;
6670
+ }
6671
+ const named = /\{([^}]*)\}/.exec(clause);
6672
+ if (named) parseDestructure(named[1], spec, out);
6673
+ const def = /^(\w+)\s*(?:,|$)/.exec(clause);
6674
+ if (def && !clause.startsWith("{")) out.push({ local: def[1], kind: "default", specifier: spec });
6675
+ }
6676
+ const cjs = /(?:const|let|var)\s+(\{[^}]*\}|\w+)\s*=\s*require\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
6677
+ while ((m = cjs.exec(content)) !== null) {
6678
+ const lhs = m[1];
6679
+ const spec = m[2];
6680
+ if (lhs.startsWith("{")) parseDestructure(lhs.slice(1, -1), spec, out);
6681
+ else out.push({ local: lhs, kind: "whole", specifier: spec });
6682
+ }
6683
+ return out;
6684
+ }
6685
+ function fileExportsOf(content, pluralizeOn) {
6686
+ const defs = collectModelDefs(content, pluralizeOn).filter((d) => d.resolved.kind === "mongodb-collection");
6687
+ if (defs.length === 0) return null;
6688
+ const byVar = /* @__PURE__ */ new Map();
6689
+ for (const d of defs) if (d.varName) byVar.set(d.varName, d.resolved.name);
6690
+ const byName = new Map(byVar);
6691
+ let def;
6692
+ const named = /(?:module\.exports|export\s+default)\s*=\s*(\w+)\b/.exec(content);
6693
+ if (named && byVar.has(named[1])) def = byVar.get(named[1]);
6694
+ if (!def) {
6695
+ const inline = /(?:module\.exports|export\s+default)\s*=\s*(?:await\s+)?(?:\w+\s*\.\s*)?model\s*\(\s*['"`]([\w$]+)['"`]/.exec(content);
6696
+ if (inline) def = pluralizeOn ? pluralizeCollection(inline[1]) : inline[1];
6697
+ }
6698
+ if (!def && byVar.size === 1) def = [...byVar.values()][0];
6699
+ return { byName, ...def ? { default: def } : {} };
6700
+ }
6701
+ async function mongooseCrossFileEndpoints(files, serviceDir) {
6702
+ const mongooseFiles = files.filter((f) => MONGOOSE_IMPORT_RE.test(f.content));
6703
+ if (mongooseFiles.length === 0) return [];
6704
+ const pluralizeOn = !files.some((f) => PLURALIZE_DISABLED_RE.test(f.content));
6705
+ const registry = /* @__PURE__ */ new Map();
6706
+ for (const f of mongooseFiles) {
6707
+ const fx = fileExportsOf(f.content, pluralizeOn);
6708
+ if (fx) registry.set(toPosix2(import_node_path31.default.relative(serviceDir, f.path)), fx);
6709
+ }
6710
+ if (registry.size === 0) return [];
6711
+ const out = [];
6712
+ const seen = /* @__PURE__ */ new Set();
6713
+ for (const f of files) {
6714
+ const bindings = parseImportBindings(f.content);
6715
+ if (bindings.length === 0) continue;
6716
+ const directColl = /* @__PURE__ */ new Map();
6717
+ const nsExports = /* @__PURE__ */ new Map();
6718
+ for (const b of bindings) {
6719
+ const resolvedRel = await resolveJsImport(b.specifier, import_node_path31.default.dirname(f.path), serviceDir, null);
6720
+ if (!resolvedRel) continue;
6721
+ const fx = registry.get(resolvedRel);
6722
+ if (!fx) continue;
6723
+ if (b.kind === "named" && b.exportName) {
6724
+ const coll = fx.byName.get(b.exportName);
6725
+ if (coll) directColl.set(b.local, coll);
6726
+ } else if (b.kind === "default") {
6727
+ if (fx.default) directColl.set(b.local, fx.default);
6728
+ } else {
6729
+ if (fx.default) directColl.set(b.local, fx.default);
6730
+ nsExports.set(b.local, fx);
6731
+ }
6732
+ }
6733
+ if (directColl.size === 0 && nsExports.size === 0) continue;
6734
+ const localDefs = new Set(
6735
+ collectModelDefs(f.content, pluralizeOn).map((d) => d.varName).filter((v) => v !== null)
6736
+ );
6737
+ const emit = (collection, matchText) => {
6738
+ const key = `${f.path}::${collection}`;
6739
+ if (seen.has(key)) return;
6740
+ seen.add(key);
6741
+ out.push(endpoint({ name: collection, kind: "mongodb-collection" }, f, serviceDir, matchText));
6742
+ };
6743
+ for (const [local, collection] of directColl) {
6744
+ if (localDefs.has(local)) continue;
6745
+ const qre = new RegExp(`\\b${local}\\s*\\.\\s*(?:${MODEL_METHODS_ALT})\\s*\\(`, "g");
6746
+ let qm;
6747
+ while ((qm = qre.exec(f.content)) !== null) emit(collection, qm[0]);
6748
+ }
6749
+ for (const [local, fx] of nsExports) {
6750
+ const qre = new RegExp(`\\b${local}\\s*\\.\\s*(\\w+)\\s*\\.\\s*(?:${MODEL_METHODS_ALT})\\s*\\(`, "g");
6751
+ let qm;
6752
+ while ((qm = qre.exec(f.content)) !== null) {
6753
+ const coll = fx.byName.get(qm[1]);
6754
+ if (coll) emit(coll, qm[0]);
6755
+ }
6756
+ }
6757
+ }
6758
+ return out;
6759
+ }
6760
+
6452
6761
  // src/extract/calls/index.ts
6453
6762
  function edgeTypeFromEndpoint(ep) {
6454
6763
  switch (ep.edgeType) {
6455
6764
  case "PUBLISHES_TO":
6456
- return import_types20.EdgeType.PUBLISHES_TO;
6765
+ return import_types21.EdgeType.PUBLISHES_TO;
6457
6766
  case "CONSUMES_FROM":
6458
- return import_types20.EdgeType.CONSUMES_FROM;
6767
+ return import_types21.EdgeType.CONSUMES_FROM;
6459
6768
  default:
6460
- return import_types20.EdgeType.CALLS;
6769
+ return import_types21.EdgeType.CALLS;
6461
6770
  }
6462
6771
  }
6463
6772
  function isAwsKind(kind) {
@@ -6469,23 +6778,27 @@ async function addExternalEndpointEdges(graph, services) {
6469
6778
  for (const service of services) {
6470
6779
  const files = await loadSourceFiles(service.dir);
6471
6780
  const endpoints = [];
6781
+ const maskedFiles = [];
6472
6782
  for (const file of files) {
6473
6783
  if (isTestPath(file.path)) continue;
6474
6784
  const masked = maskCommentsInSource(file.content);
6475
6785
  const maskedFile = { path: file.path, content: masked };
6786
+ maskedFiles.push(maskedFile);
6476
6787
  endpoints.push(...kafkaEndpointsFromFile(maskedFile, service.dir));
6477
6788
  endpoints.push(...redisEndpointsFromFile(maskedFile, service.dir));
6478
6789
  endpoints.push(...awsEndpointsFromFile(maskedFile, service.dir));
6479
6790
  endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
6480
6791
  endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir));
6792
+ endpoints.push(...mongooseEndpointsFromFile(maskedFile, service.dir));
6481
6793
  }
6794
+ endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
6482
6795
  if (endpoints.length === 0) continue;
6483
6796
  const seenEdges = /* @__PURE__ */ new Set();
6484
6797
  for (const ep of endpoints) {
6485
6798
  if (!graph.hasNode(ep.infraId)) {
6486
6799
  const node = {
6487
6800
  id: ep.infraId,
6488
- type: import_types20.NodeType.InfraNode,
6801
+ type: import_types21.NodeType.InfraNode,
6489
6802
  name: ep.name,
6490
6803
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
6491
6804
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -6497,7 +6810,7 @@ async function addExternalEndpointEdges(graph, services) {
6497
6810
  nodesAdded++;
6498
6811
  }
6499
6812
  const edgeType = edgeTypeFromEndpoint(ep);
6500
- const confidence = (0, import_types20.confidenceForExtracted)(ep.confidenceKind);
6813
+ const confidence = (0, import_types21.confidenceForExtracted)(ep.confidenceKind);
6501
6814
  const relFile = toPosix2(ep.evidence.file);
6502
6815
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
6503
6816
  graph,
@@ -6507,7 +6820,7 @@ async function addExternalEndpointEdges(graph, services) {
6507
6820
  );
6508
6821
  nodesAdded += n;
6509
6822
  edgesAdded += e;
6510
- if (!(0, import_types20.passesExtractedFloor)(confidence)) {
6823
+ if (!(0, import_types21.passesExtractedFloor)(confidence)) {
6511
6824
  noteExtractedDropped({
6512
6825
  source: fileNodeId,
6513
6826
  target: ep.infraId,
@@ -6527,7 +6840,7 @@ async function addExternalEndpointEdges(graph, services) {
6527
6840
  source: fileNodeId,
6528
6841
  target: ep.infraId,
6529
6842
  type: edgeType,
6530
- provenance: import_types20.Provenance.EXTRACTED,
6843
+ provenance: import_types21.Provenance.EXTRACTED,
6531
6844
  confidence,
6532
6845
  evidence: ep.evidence
6533
6846
  };
@@ -6553,16 +6866,16 @@ init_cjs_shims();
6553
6866
 
6554
6867
  // src/extract/infra/docker-compose.ts
6555
6868
  init_cjs_shims();
6556
- var import_node_path31 = __toESM(require("path"), 1);
6557
- var import_types22 = require("@neat.is/types");
6869
+ var import_node_path32 = __toESM(require("path"), 1);
6870
+ var import_types23 = require("@neat.is/types");
6558
6871
 
6559
6872
  // src/extract/infra/shared.ts
6560
6873
  init_cjs_shims();
6561
- var import_types21 = require("@neat.is/types");
6874
+ var import_types22 = require("@neat.is/types");
6562
6875
  function makeInfraNode(kind, name, provider = "self", extras) {
6563
6876
  return {
6564
- id: (0, import_types21.infraId)(kind, name),
6565
- type: import_types21.NodeType.InfraNode,
6877
+ id: (0, import_types22.infraId)(kind, name),
6878
+ type: import_types22.NodeType.InfraNode,
6566
6879
  name,
6567
6880
  provider,
6568
6881
  kind,
@@ -6606,8 +6919,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
6606
6919
  source: anchorId,
6607
6920
  target: node.id,
6608
6921
  type: edgeType,
6609
- provenance: import_types21.Provenance.EXTRACTED,
6610
- confidence: (0, import_types21.confidenceForExtracted)("structural"),
6922
+ provenance: import_types22.Provenance.EXTRACTED,
6923
+ confidence: (0, import_types22.confidenceForExtracted)("structural"),
6611
6924
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
6612
6925
  };
6613
6926
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6624,7 +6937,7 @@ function dependsOnList(value) {
6624
6937
  }
6625
6938
  function serviceNameToServiceNode(name, services) {
6626
6939
  for (const s of services) {
6627
- if (s.node.name === name || import_node_path31.default.basename(s.dir) === name) return s.node.id;
6940
+ if (s.node.name === name || import_node_path32.default.basename(s.dir) === name) return s.node.id;
6628
6941
  }
6629
6942
  return null;
6630
6943
  }
@@ -6633,7 +6946,7 @@ async function addComposeInfra(graph, scanPath, services) {
6633
6946
  let edgesAdded = 0;
6634
6947
  let composePath = null;
6635
6948
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
6636
- const abs = import_node_path31.default.join(scanPath, name);
6949
+ const abs = import_node_path32.default.join(scanPath, name);
6637
6950
  if (await exists(abs)) {
6638
6951
  composePath = abs;
6639
6952
  break;
@@ -6646,13 +6959,13 @@ async function addComposeInfra(graph, scanPath, services) {
6646
6959
  } catch (err) {
6647
6960
  recordExtractionError(
6648
6961
  "infra docker-compose",
6649
- import_node_path31.default.relative(scanPath, composePath),
6962
+ import_node_path32.default.relative(scanPath, composePath),
6650
6963
  err
6651
6964
  );
6652
6965
  return { nodesAdded, edgesAdded };
6653
6966
  }
6654
6967
  if (!compose?.services) return { nodesAdded, edgesAdded };
6655
- const evidenceFile = import_node_path31.default.relative(scanPath, composePath).split(import_node_path31.default.sep).join("/");
6968
+ const evidenceFile = import_node_path32.default.relative(scanPath, composePath).split(import_node_path32.default.sep).join("/");
6656
6969
  const composeNameToNodeId = /* @__PURE__ */ new Map();
6657
6970
  for (const [composeName, svc] of Object.entries(compose.services)) {
6658
6971
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -6674,15 +6987,15 @@ async function addComposeInfra(graph, scanPath, services) {
6674
6987
  for (const dep of dependsOnList(svc.depends_on)) {
6675
6988
  const targetId = composeNameToNodeId.get(dep);
6676
6989
  if (!targetId) continue;
6677
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types22.EdgeType.DEPENDS_ON);
6990
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types23.EdgeType.DEPENDS_ON);
6678
6991
  if (graph.hasEdge(edgeId)) continue;
6679
6992
  const edge = {
6680
6993
  id: edgeId,
6681
6994
  source: sourceId,
6682
6995
  target: targetId,
6683
- type: import_types22.EdgeType.DEPENDS_ON,
6684
- provenance: import_types22.Provenance.EXTRACTED,
6685
- confidence: (0, import_types22.confidenceForExtracted)("structural"),
6996
+ type: import_types23.EdgeType.DEPENDS_ON,
6997
+ provenance: import_types23.Provenance.EXTRACTED,
6998
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
6686
6999
  evidence: { file: evidenceFile }
6687
7000
  };
6688
7001
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6694,9 +7007,9 @@ async function addComposeInfra(graph, scanPath, services) {
6694
7007
 
6695
7008
  // src/extract/infra/dockerfile.ts
6696
7009
  init_cjs_shims();
6697
- var import_node_path32 = __toESM(require("path"), 1);
7010
+ var import_node_path33 = __toESM(require("path"), 1);
6698
7011
  var import_node_fs16 = require("fs");
6699
- var import_types23 = require("@neat.is/types");
7012
+ var import_types24 = require("@neat.is/types");
6700
7013
  function readDockerfile(content) {
6701
7014
  let image = null;
6702
7015
  const ports = [];
@@ -6725,7 +7038,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6725
7038
  let nodesAdded = 0;
6726
7039
  let edgesAdded = 0;
6727
7040
  for (const service of services) {
6728
- const dockerfilePath = import_node_path32.default.join(service.dir, "Dockerfile");
7041
+ const dockerfilePath = import_node_path33.default.join(service.dir, "Dockerfile");
6729
7042
  if (!await exists(dockerfilePath)) continue;
6730
7043
  let content;
6731
7044
  try {
@@ -6733,7 +7046,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6733
7046
  } catch (err) {
6734
7047
  recordExtractionError(
6735
7048
  "infra dockerfile",
6736
- import_node_path32.default.relative(scanPath, dockerfilePath),
7049
+ import_node_path33.default.relative(scanPath, dockerfilePath),
6737
7050
  err
6738
7051
  );
6739
7052
  continue;
@@ -6745,8 +7058,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6745
7058
  graph.addNode(node.id, node);
6746
7059
  nodesAdded++;
6747
7060
  }
6748
- const relDockerfile = toPosix2(import_node_path32.default.relative(service.dir, dockerfilePath));
6749
- const evidenceFile = toPosix2(import_node_path32.default.relative(scanPath, dockerfilePath));
7061
+ const relDockerfile = toPosix2(import_node_path33.default.relative(service.dir, dockerfilePath));
7062
+ const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, dockerfilePath));
6750
7063
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
6751
7064
  graph,
6752
7065
  service.pkg.name,
@@ -6755,15 +7068,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6755
7068
  );
6756
7069
  nodesAdded += fn;
6757
7070
  edgesAdded += fe;
6758
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types23.EdgeType.RUNS_ON);
7071
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types24.EdgeType.RUNS_ON);
6759
7072
  if (!graph.hasEdge(edgeId)) {
6760
7073
  const edge = {
6761
7074
  id: edgeId,
6762
7075
  source: fileNodeId,
6763
7076
  target: node.id,
6764
- type: import_types23.EdgeType.RUNS_ON,
6765
- provenance: import_types23.Provenance.EXTRACTED,
6766
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7077
+ type: import_types24.EdgeType.RUNS_ON,
7078
+ provenance: import_types24.Provenance.EXTRACTED,
7079
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
6767
7080
  evidence: {
6768
7081
  file: evidenceFile,
6769
7082
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -6778,15 +7091,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6778
7091
  graph.addNode(portNode.id, portNode);
6779
7092
  nodesAdded++;
6780
7093
  }
6781
- const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types23.EdgeType.CONNECTS_TO);
7094
+ const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types24.EdgeType.CONNECTS_TO);
6782
7095
  if (graph.hasEdge(portEdgeId)) continue;
6783
7096
  const portEdge = {
6784
7097
  id: portEdgeId,
6785
7098
  source: fileNodeId,
6786
7099
  target: portNode.id,
6787
- type: import_types23.EdgeType.CONNECTS_TO,
6788
- provenance: import_types23.Provenance.EXTRACTED,
6789
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7100
+ type: import_types24.EdgeType.CONNECTS_TO,
7101
+ provenance: import_types24.Provenance.EXTRACTED,
7102
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
6790
7103
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
6791
7104
  };
6792
7105
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -6799,8 +7112,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6799
7112
  // src/extract/infra/terraform.ts
6800
7113
  init_cjs_shims();
6801
7114
  var import_node_fs17 = require("fs");
6802
- var import_node_path33 = __toESM(require("path"), 1);
6803
- var import_types24 = require("@neat.is/types");
7115
+ var import_node_path34 = __toESM(require("path"), 1);
7116
+ var import_types25 = require("@neat.is/types");
6804
7117
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
6805
7118
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
6806
7119
  async function walkTfFiles(start, depth = 0, max = 5) {
@@ -6810,11 +7123,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
6810
7123
  for (const entry2 of entries) {
6811
7124
  if (entry2.isDirectory()) {
6812
7125
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
6813
- const child = import_node_path33.default.join(start, entry2.name);
7126
+ const child = import_node_path34.default.join(start, entry2.name);
6814
7127
  if (await isPythonVenvDir(child)) continue;
6815
7128
  out.push(...await walkTfFiles(child, depth + 1, max));
6816
7129
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
6817
- out.push(import_node_path33.default.join(start, entry2.name));
7130
+ out.push(import_node_path34.default.join(start, entry2.name));
6818
7131
  }
6819
7132
  }
6820
7133
  return out;
@@ -6846,7 +7159,7 @@ async function addTerraformResources(graph, scanPath) {
6846
7159
  const files = await walkTfFiles(scanPath);
6847
7160
  for (const file of files) {
6848
7161
  const content = await import_node_fs17.promises.readFile(file, "utf8");
6849
- const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, file));
7162
+ const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, file));
6850
7163
  const resources = [];
6851
7164
  const byKey = /* @__PURE__ */ new Map();
6852
7165
  RESOURCE_RE.lastIndex = 0;
@@ -6881,16 +7194,16 @@ async function addTerraformResources(graph, scanPath) {
6881
7194
  if (!target) continue;
6882
7195
  if (seen.has(target.nodeId)) continue;
6883
7196
  seen.add(target.nodeId);
6884
- const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types24.EdgeType.DEPENDS_ON);
7197
+ const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types25.EdgeType.DEPENDS_ON);
6885
7198
  if (graph.hasEdge(edgeId)) continue;
6886
7199
  const line = lineAt2(content, resource.bodyOffset + ref.index);
6887
7200
  const edge = {
6888
7201
  id: edgeId,
6889
7202
  source: resource.nodeId,
6890
7203
  target: target.nodeId,
6891
- type: import_types24.EdgeType.DEPENDS_ON,
6892
- provenance: import_types24.Provenance.EXTRACTED,
6893
- confidence: (0, import_types24.confidenceForExtracted)("structural"),
7204
+ type: import_types25.EdgeType.DEPENDS_ON,
7205
+ provenance: import_types25.Provenance.EXTRACTED,
7206
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
6894
7207
  evidence: { file: evidenceFile, line, snippet: key }
6895
7208
  };
6896
7209
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6904,7 +7217,7 @@ async function addTerraformResources(graph, scanPath) {
6904
7217
  // src/extract/infra/k8s.ts
6905
7218
  init_cjs_shims();
6906
7219
  var import_node_fs18 = require("fs");
6907
- var import_node_path34 = __toESM(require("path"), 1);
7220
+ var import_node_path35 = __toESM(require("path"), 1);
6908
7221
  var import_yaml3 = require("yaml");
6909
7222
  var K8S_KIND_TO_INFRA_KIND = {
6910
7223
  Service: "k8s-service",
@@ -6922,11 +7235,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
6922
7235
  for (const entry2 of entries) {
6923
7236
  if (entry2.isDirectory()) {
6924
7237
  if (IGNORED_DIRS.has(entry2.name)) continue;
6925
- const child = import_node_path34.default.join(start, entry2.name);
7238
+ const child = import_node_path35.default.join(start, entry2.name);
6926
7239
  if (await isPythonVenvDir(child)) continue;
6927
7240
  out.push(...await walkYamlFiles2(child, depth + 1, max));
6928
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path34.default.extname(entry2.name))) {
6929
- out.push(import_node_path34.default.join(start, entry2.name));
7241
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path35.default.extname(entry2.name))) {
7242
+ out.push(import_node_path35.default.join(start, entry2.name));
6930
7243
  }
6931
7244
  }
6932
7245
  return out;
@@ -6960,13 +7273,13 @@ async function addK8sResources(graph, scanPath) {
6960
7273
  // src/extract/infra/cloudflare.ts
6961
7274
  init_cjs_shims();
6962
7275
  var import_node_fs19 = require("fs");
6963
- var import_node_path35 = __toESM(require("path"), 1);
7276
+ var import_node_path36 = __toESM(require("path"), 1);
6964
7277
  var import_smol_toml2 = require("smol-toml");
6965
- var import_types25 = require("@neat.is/types");
7278
+ var import_types26 = require("@neat.is/types");
6966
7279
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
6967
7280
  async function readWranglerConfig(dir) {
6968
7281
  for (const filename of WRANGLER_FILENAMES) {
6969
- const abs = import_node_path35.default.join(dir, filename);
7282
+ const abs = import_node_path36.default.join(dir, filename);
6970
7283
  if (!await exists(abs)) continue;
6971
7284
  const raw = await import_node_fs19.promises.readFile(abs, "utf8");
6972
7285
  const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7010,8 +7323,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
7010
7323
  source: anchorId,
7011
7324
  target: node.id,
7012
7325
  type: edgeType,
7013
- provenance: import_types25.Provenance.EXTRACTED,
7014
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7326
+ provenance: import_types26.Provenance.EXTRACTED,
7327
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7015
7328
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
7016
7329
  };
7017
7330
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7029,11 +7342,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7029
7342
  try {
7030
7343
  read = await readWranglerConfig(service.dir);
7031
7344
  } catch (err) {
7032
- recordExtractionError("infra cloudflare", import_node_path35.default.relative(scanPath, service.dir), err);
7345
+ recordExtractionError("infra cloudflare", import_node_path36.default.relative(scanPath, service.dir), err);
7033
7346
  continue;
7034
7347
  }
7035
7348
  if (!read || !read.config.name) continue;
7036
- const evidenceFile = toPosix2(import_node_path35.default.relative(scanPath, import_node_path35.default.join(service.dir, read.relFile)));
7349
+ const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, import_node_path36.default.join(service.dir, read.relFile)));
7037
7350
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
7038
7351
  }
7039
7352
  for (const worker of discovered) {
@@ -7045,7 +7358,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7045
7358
  }
7046
7359
  let anchorId = service.node.id;
7047
7360
  if (config.main) {
7048
- const entryRelPath = toPosix2(import_node_path35.default.normalize(config.main));
7361
+ const entryRelPath = toPosix2(import_node_path36.default.normalize(config.main));
7049
7362
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7050
7363
  graph,
7051
7364
  service.pkg.name,
@@ -7072,15 +7385,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7072
7385
  nodesAdded++;
7073
7386
  }
7074
7387
  if (runtimeNode.id !== anchorId) {
7075
- const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types25.EdgeType.RUNS_ON);
7388
+ const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types26.EdgeType.RUNS_ON);
7076
7389
  if (!graph.hasEdge(runsOnId)) {
7077
7390
  const edge = {
7078
7391
  id: runsOnId,
7079
7392
  source: anchorId,
7080
7393
  target: runtimeNode.id,
7081
- type: import_types25.EdgeType.RUNS_ON,
7082
- provenance: import_types25.Provenance.EXTRACTED,
7083
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7394
+ type: import_types26.EdgeType.RUNS_ON,
7395
+ provenance: import_types26.Provenance.EXTRACTED,
7396
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7084
7397
  evidence: {
7085
7398
  file: evidenceFile,
7086
7399
  ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
@@ -7094,7 +7407,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7094
7407
  const result = addResourceEdge(
7095
7408
  graph,
7096
7409
  anchorId,
7097
- import_types25.EdgeType.CONNECTS_TO,
7410
+ import_types26.EdgeType.CONNECTS_TO,
7098
7411
  "cloudflare-route",
7099
7412
  route,
7100
7413
  evidenceFile,
@@ -7118,7 +7431,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7118
7431
  const result = addResourceEdge(
7119
7432
  graph,
7120
7433
  anchorId,
7121
- import_types25.EdgeType.DEPENDS_ON,
7434
+ import_types26.EdgeType.DEPENDS_ON,
7122
7435
  group.kind,
7123
7436
  name,
7124
7437
  evidenceFile,
@@ -7132,7 +7445,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7132
7445
  const result = addResourceEdge(
7133
7446
  graph,
7134
7447
  anchorId,
7135
- import_types25.EdgeType.DEPENDS_ON,
7448
+ import_types26.EdgeType.DEPENDS_ON,
7136
7449
  "cloudflare-cron",
7137
7450
  cron,
7138
7451
  evidenceFile,
@@ -7145,7 +7458,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7145
7458
  const result = addResourceEdge(
7146
7459
  graph,
7147
7460
  anchorId,
7148
- import_types25.EdgeType.DEPENDS_ON,
7461
+ import_types26.EdgeType.DEPENDS_ON,
7149
7462
  "cloudflare-env-var",
7150
7463
  varName,
7151
7464
  evidenceFile,
@@ -7158,15 +7471,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7158
7471
  if (!svc.service) continue;
7159
7472
  const target = workerIndex.get(svc.service);
7160
7473
  if (target && target.anchorId !== anchorId) {
7161
- const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types25.EdgeType.CALLS);
7474
+ const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types26.EdgeType.CALLS);
7162
7475
  if (!graph.hasEdge(edgeId)) {
7163
7476
  const edge = {
7164
7477
  id: edgeId,
7165
7478
  source: anchorId,
7166
7479
  target: target.anchorId,
7167
- type: import_types25.EdgeType.CALLS,
7168
- provenance: import_types25.Provenance.EXTRACTED,
7169
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7480
+ type: import_types26.EdgeType.CALLS,
7481
+ provenance: import_types26.Provenance.EXTRACTED,
7482
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7170
7483
  evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
7171
7484
  };
7172
7485
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7177,7 +7490,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7177
7490
  const result = addResourceEdge(
7178
7491
  graph,
7179
7492
  anchorId,
7180
- import_types25.EdgeType.DEPENDS_ON,
7493
+ import_types26.EdgeType.DEPENDS_ON,
7181
7494
  "cloudflare-service-binding",
7182
7495
  svc.service,
7183
7496
  evidenceFile,
@@ -7193,12 +7506,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7193
7506
  // src/extract/infra/vercel.ts
7194
7507
  init_cjs_shims();
7195
7508
  var import_node_fs20 = require("fs");
7196
- var import_node_path36 = __toESM(require("path"), 1);
7197
- var import_types26 = require("@neat.is/types");
7509
+ var import_node_path37 = __toESM(require("path"), 1);
7510
+ var import_types27 = require("@neat.is/types");
7198
7511
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
7199
7512
  async function readVercelConfig(dir) {
7200
7513
  for (const filename of VERCEL_CONFIG_FILENAMES) {
7201
- const abs = import_node_path36.default.join(dir, filename);
7514
+ const abs = import_node_path37.default.join(dir, filename);
7202
7515
  if (!await exists(abs)) continue;
7203
7516
  const raw = await import_node_fs20.promises.readFile(abs, "utf8");
7204
7517
  const config = JSON.parse(maskCommentsInSource(raw));
@@ -7207,7 +7520,7 @@ async function readVercelConfig(dir) {
7207
7520
  return null;
7208
7521
  }
7209
7522
  async function readLinkedProjectName(dir) {
7210
- const abs = import_node_path36.default.join(dir, ".vercel", "project.json");
7523
+ const abs = import_node_path37.default.join(dir, ".vercel", "project.json");
7211
7524
  if (!await exists(abs)) return void 0;
7212
7525
  const parsed = JSON.parse(await import_node_fs20.promises.readFile(abs, "utf8"));
7213
7526
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
@@ -7225,7 +7538,7 @@ async function addVercelServices(graph, services, scanPath) {
7225
7538
  read = await readVercelConfig(service.dir);
7226
7539
  projectName = await readLinkedProjectName(service.dir);
7227
7540
  } catch (err) {
7228
- recordExtractionError("infra vercel", import_node_path36.default.relative(scanPath, service.dir), err);
7541
+ recordExtractionError("infra vercel", import_node_path37.default.relative(scanPath, service.dir), err);
7229
7542
  continue;
7230
7543
  }
7231
7544
  if (!read && !projectName) continue;
@@ -7241,7 +7554,7 @@ async function addVercelServices(graph, services, scanPath) {
7241
7554
  const anchorId = service.node.id;
7242
7555
  if (!read) continue;
7243
7556
  const { config, relFile, raw } = read;
7244
- const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, import_node_path36.default.join(service.dir, relFile)));
7557
+ const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
7245
7558
  const add = (edgeType, kind, name) => {
7246
7559
  if (!name) return;
7247
7560
  const result = emitPlatformResourceEdge(
@@ -7257,12 +7570,12 @@ async function addVercelServices(graph, services, scanPath) {
7257
7570
  nodesAdded += result.nodesAdded;
7258
7571
  edgesAdded += result.edgesAdded;
7259
7572
  };
7260
- add(import_types26.EdgeType.RUNS_ON, "vercel", "vercel");
7261
- for (const cron of config.crons ?? []) add(import_types26.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7262
- for (const varName of Object.keys(config.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7263
- for (const varName of Object.keys(config.build?.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7573
+ add(import_types27.EdgeType.RUNS_ON, "vercel", "vercel");
7574
+ for (const cron of config.crons ?? []) add(import_types27.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7575
+ for (const varName of Object.keys(config.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7576
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7264
7577
  for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
7265
- add(import_types26.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7578
+ add(import_types27.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7266
7579
  }
7267
7580
  }
7268
7581
  return { nodesAdded, edgesAdded };
@@ -7271,13 +7584,13 @@ async function addVercelServices(graph, services, scanPath) {
7271
7584
  // src/extract/infra/railway.ts
7272
7585
  init_cjs_shims();
7273
7586
  var import_node_fs21 = require("fs");
7274
- var import_node_path37 = __toESM(require("path"), 1);
7587
+ var import_node_path38 = __toESM(require("path"), 1);
7275
7588
  var import_smol_toml3 = require("smol-toml");
7276
- var import_types27 = require("@neat.is/types");
7589
+ var import_types28 = require("@neat.is/types");
7277
7590
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
7278
7591
  async function readRailwayConfig(dir) {
7279
7592
  for (const filename of RAILWAY_FILENAMES) {
7280
- const abs = import_node_path37.default.join(dir, filename);
7593
+ const abs = import_node_path38.default.join(dir, filename);
7281
7594
  if (!await exists(abs)) continue;
7282
7595
  const raw = await import_node_fs21.promises.readFile(abs, "utf8");
7283
7596
  const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7293,7 +7606,7 @@ async function addRailwayServices(graph, services, scanPath) {
7293
7606
  try {
7294
7607
  read = await readRailwayConfig(service.dir);
7295
7608
  } catch (err) {
7296
- recordExtractionError("infra railway", import_node_path37.default.relative(scanPath, service.dir), err);
7609
+ recordExtractionError("infra railway", import_node_path38.default.relative(scanPath, service.dir), err);
7297
7610
  continue;
7298
7611
  }
7299
7612
  if (!read) continue;
@@ -7303,7 +7616,7 @@ async function addRailwayServices(graph, services, scanPath) {
7303
7616
  }
7304
7617
  const anchorId = service.node.id;
7305
7618
  const { config, relFile, raw } = read;
7306
- const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
7619
+ const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7307
7620
  const add = (edgeType, kind, name) => {
7308
7621
  if (!name) return;
7309
7622
  const result = emitPlatformResourceEdge(
@@ -7319,9 +7632,9 @@ async function addRailwayServices(graph, services, scanPath) {
7319
7632
  nodesAdded += result.nodesAdded;
7320
7633
  edgesAdded += result.edgesAdded;
7321
7634
  };
7322
- add(import_types27.EdgeType.RUNS_ON, "railway", "railway");
7323
- add(import_types27.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7324
- add(import_types27.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7635
+ add(import_types28.EdgeType.RUNS_ON, "railway", "railway");
7636
+ add(import_types28.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7637
+ add(import_types28.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7325
7638
  }
7326
7639
  return { nodesAdded, edgesAdded };
7327
7640
  }
@@ -7329,12 +7642,12 @@ async function addRailwayServices(graph, services, scanPath) {
7329
7642
  // src/extract/infra/supabase.ts
7330
7643
  init_cjs_shims();
7331
7644
  var import_node_fs22 = require("fs");
7332
- var import_node_path38 = __toESM(require("path"), 1);
7645
+ var import_node_path39 = __toESM(require("path"), 1);
7333
7646
  var import_smol_toml4 = require("smol-toml");
7334
- var import_types28 = require("@neat.is/types");
7647
+ var import_types29 = require("@neat.is/types");
7335
7648
  async function readSupabaseConfig(dir) {
7336
- const relFile = import_node_path38.default.join("supabase", "config.toml");
7337
- const abs = import_node_path38.default.join(dir, relFile);
7649
+ const relFile = import_node_path39.default.join("supabase", "config.toml");
7650
+ const abs = import_node_path39.default.join(dir, relFile);
7338
7651
  if (!await exists(abs)) return null;
7339
7652
  const raw = await import_node_fs22.promises.readFile(abs, "utf8");
7340
7653
  const config = (0, import_smol_toml4.parse)(raw);
@@ -7348,7 +7661,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7348
7661
  try {
7349
7662
  read = await readSupabaseConfig(service.dir);
7350
7663
  } catch (err) {
7351
- recordExtractionError("infra supabase", import_node_path38.default.relative(scanPath, service.dir), err);
7664
+ recordExtractionError("infra supabase", import_node_path39.default.relative(scanPath, service.dir), err);
7352
7665
  continue;
7353
7666
  }
7354
7667
  if (!read) continue;
@@ -7363,7 +7676,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7363
7676
  });
7364
7677
  }
7365
7678
  const anchorId = service.node.id;
7366
- const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7679
+ const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
7367
7680
  const add = (edgeType, kind, name) => {
7368
7681
  if (!name) return;
7369
7682
  const result = emitPlatformResourceEdge(
@@ -7379,10 +7692,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
7379
7692
  nodesAdded += result.nodesAdded;
7380
7693
  edgesAdded += result.edgesAdded;
7381
7694
  };
7382
- add(import_types28.EdgeType.RUNS_ON, "supabase", "supabase");
7383
- for (const fn of Object.keys(config.functions ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "supabase-function", fn);
7384
- if (config.storage) add(import_types28.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7385
- if (config.auth) add(import_types28.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7695
+ add(import_types29.EdgeType.RUNS_ON, "supabase", "supabase");
7696
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types29.EdgeType.DEPENDS_ON, "supabase-function", fn);
7697
+ if (config.storage) add(import_types29.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7698
+ if (config.auth) add(import_types29.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7386
7699
  }
7387
7700
  return { nodesAdded, edgesAdded };
7388
7701
  }
@@ -7404,17 +7717,17 @@ async function addInfra(graph, scanPath, services) {
7404
7717
  }
7405
7718
 
7406
7719
  // src/extract/index.ts
7407
- var import_node_path40 = __toESM(require("path"), 1);
7720
+ var import_node_path41 = __toESM(require("path"), 1);
7408
7721
 
7409
7722
  // src/extract/retire.ts
7410
7723
  init_cjs_shims();
7411
7724
  var import_node_fs23 = require("fs");
7412
- var import_node_path39 = __toESM(require("path"), 1);
7413
- var import_types29 = require("@neat.is/types");
7725
+ var import_node_path40 = __toESM(require("path"), 1);
7726
+ var import_types30 = require("@neat.is/types");
7414
7727
  function dropOrphanedFileNodes(graph) {
7415
7728
  const orphans = [];
7416
7729
  graph.forEachNode((id, attrs) => {
7417
- if (attrs.type !== import_types29.NodeType.FileNode) return;
7730
+ if (attrs.type !== import_types30.NodeType.FileNode) return;
7418
7731
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
7419
7732
  orphans.push(id);
7420
7733
  }
@@ -7427,14 +7740,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
7427
7740
  const bases = [scanPath, ...serviceDirs];
7428
7741
  graph.forEachEdge((id, attrs) => {
7429
7742
  const edge = attrs;
7430
- if (edge.provenance !== import_types29.Provenance.EXTRACTED) return;
7743
+ if (edge.provenance !== import_types30.Provenance.EXTRACTED) return;
7431
7744
  const evidenceFile = edge.evidence?.file;
7432
7745
  if (!evidenceFile) return;
7433
- if (import_node_path39.default.isAbsolute(evidenceFile)) {
7746
+ if (import_node_path40.default.isAbsolute(evidenceFile)) {
7434
7747
  if (!(0, import_node_fs23.existsSync)(evidenceFile)) toDrop.push(id);
7435
7748
  return;
7436
7749
  }
7437
- const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path39.default.join(base, evidenceFile)));
7750
+ const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path40.default.join(base, evidenceFile)));
7438
7751
  if (!found) toDrop.push(id);
7439
7752
  });
7440
7753
  for (const id of toDrop) graph.dropEdge(id);
@@ -7476,7 +7789,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7476
7789
  }
7477
7790
  const droppedEntries = drainDroppedExtracted();
7478
7791
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
7479
- const rejectedPath = import_node_path40.default.join(import_node_path40.default.dirname(opts.errorsPath), "rejected.ndjson");
7792
+ const rejectedPath = import_node_path41.default.join(import_node_path41.default.dirname(opts.errorsPath), "rejected.ndjson");
7480
7793
  try {
7481
7794
  await writeRejectedExtracted(droppedEntries, rejectedPath);
7482
7795
  } catch (err) {
@@ -7511,8 +7824,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7511
7824
  // src/persist.ts
7512
7825
  init_cjs_shims();
7513
7826
  var import_node_fs24 = require("fs");
7514
- var import_node_path41 = __toESM(require("path"), 1);
7515
- var import_types30 = require("@neat.is/types");
7827
+ var import_node_path42 = __toESM(require("path"), 1);
7828
+ var import_types31 = require("@neat.is/types");
7516
7829
  var SCHEMA_VERSION = 4;
7517
7830
  function migrateV1ToV2(payload) {
7518
7831
  const nodes = payload.graph.nodes;
@@ -7534,12 +7847,12 @@ function migrateV2ToV3(payload) {
7534
7847
  for (const edge of edges) {
7535
7848
  const attrs = edge.attributes;
7536
7849
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
7537
- attrs.provenance = import_types30.Provenance.OBSERVED;
7850
+ attrs.provenance = import_types31.Provenance.OBSERVED;
7538
7851
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
7539
7852
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
7540
7853
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
7541
7854
  if (type && source && target) {
7542
- const newId = (0, import_types30.observedEdgeId)(source, target, type);
7855
+ const newId = (0, import_types31.observedEdgeId)(source, target, type);
7543
7856
  attrs.id = newId;
7544
7857
  if (edge.key) edge.key = newId;
7545
7858
  }
@@ -7548,7 +7861,7 @@ function migrateV2ToV3(payload) {
7548
7861
  return { ...payload, schemaVersion: 3 };
7549
7862
  }
7550
7863
  async function ensureDir(filePath) {
7551
- await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(filePath), { recursive: true });
7864
+ await import_node_fs24.promises.mkdir(import_node_path42.default.dirname(filePath), { recursive: true });
7552
7865
  }
7553
7866
  async function saveGraphToDisk(graph, outPath) {
7554
7867
  await ensureDir(outPath);
@@ -7629,23 +7942,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
7629
7942
 
7630
7943
  // src/projects.ts
7631
7944
  init_cjs_shims();
7632
- var import_node_path42 = __toESM(require("path"), 1);
7945
+ var import_node_path43 = __toESM(require("path"), 1);
7633
7946
  function pathsForProject(project, baseDir) {
7634
7947
  if (project === DEFAULT_PROJECT) {
7635
7948
  return {
7636
- snapshotPath: import_node_path42.default.join(baseDir, "graph.json"),
7637
- errorsPath: import_node_path42.default.join(baseDir, "errors.ndjson"),
7638
- staleEventsPath: import_node_path42.default.join(baseDir, "stale-events.ndjson"),
7639
- embeddingsCachePath: import_node_path42.default.join(baseDir, "embeddings.json"),
7640
- policyViolationsPath: import_node_path42.default.join(baseDir, "policy-violations.ndjson")
7949
+ snapshotPath: import_node_path43.default.join(baseDir, "graph.json"),
7950
+ errorsPath: import_node_path43.default.join(baseDir, "errors.ndjson"),
7951
+ staleEventsPath: import_node_path43.default.join(baseDir, "stale-events.ndjson"),
7952
+ embeddingsCachePath: import_node_path43.default.join(baseDir, "embeddings.json"),
7953
+ policyViolationsPath: import_node_path43.default.join(baseDir, "policy-violations.ndjson")
7641
7954
  };
7642
7955
  }
7643
7956
  return {
7644
- snapshotPath: import_node_path42.default.join(baseDir, `${project}.json`),
7645
- errorsPath: import_node_path42.default.join(baseDir, `errors.${project}.ndjson`),
7646
- staleEventsPath: import_node_path42.default.join(baseDir, `stale-events.${project}.ndjson`),
7647
- embeddingsCachePath: import_node_path42.default.join(baseDir, `embeddings.${project}.json`),
7648
- policyViolationsPath: import_node_path42.default.join(baseDir, `policy-violations.${project}.ndjson`)
7957
+ snapshotPath: import_node_path43.default.join(baseDir, `${project}.json`),
7958
+ errorsPath: import_node_path43.default.join(baseDir, `errors.${project}.ndjson`),
7959
+ staleEventsPath: import_node_path43.default.join(baseDir, `stale-events.${project}.ndjson`),
7960
+ embeddingsCachePath: import_node_path43.default.join(baseDir, `embeddings.${project}.json`),
7961
+ policyViolationsPath: import_node_path43.default.join(baseDir, `policy-violations.${project}.ndjson`)
7649
7962
  };
7650
7963
  }
7651
7964
  var Projects = class {
@@ -7683,19 +7996,19 @@ var Projects = class {
7683
7996
  init_cjs_shims();
7684
7997
  var import_fastify = __toESM(require("fastify"), 1);
7685
7998
  var import_cors = __toESM(require("@fastify/cors"), 1);
7686
- var import_types33 = require("@neat.is/types");
7999
+ var import_types34 = require("@neat.is/types");
7687
8000
 
7688
8001
  // src/extend/index.ts
7689
8002
  init_cjs_shims();
7690
8003
  var import_node_fs26 = require("fs");
7691
- var import_node_path44 = __toESM(require("path"), 1);
8004
+ var import_node_path45 = __toESM(require("path"), 1);
7692
8005
  var import_node_os2 = __toESM(require("os"), 1);
7693
8006
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
7694
8007
 
7695
8008
  // src/installers/package-manager.ts
7696
8009
  init_cjs_shims();
7697
8010
  var import_node_fs25 = require("fs");
7698
- var import_node_path43 = __toESM(require("path"), 1);
8011
+ var import_node_path44 = __toESM(require("path"), 1);
7699
8012
  var import_node_child_process = require("child_process");
7700
8013
  var LOCKFILE_PRIORITY = [
7701
8014
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -7717,22 +8030,22 @@ async function exists2(p) {
7717
8030
  }
7718
8031
  }
7719
8032
  async function detectPackageManager(serviceDir) {
7720
- let dir = import_node_path43.default.resolve(serviceDir);
8033
+ let dir = import_node_path44.default.resolve(serviceDir);
7721
8034
  const stops = /* @__PURE__ */ new Set();
7722
8035
  for (let i = 0; i < 64; i++) {
7723
8036
  if (stops.has(dir)) break;
7724
8037
  stops.add(dir);
7725
8038
  for (const candidate of LOCKFILE_PRIORITY) {
7726
- const lockPath = import_node_path43.default.join(dir, candidate.lockfile);
8039
+ const lockPath = import_node_path44.default.join(dir, candidate.lockfile);
7727
8040
  if (await exists2(lockPath)) {
7728
8041
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
7729
8042
  }
7730
8043
  }
7731
- const parent = import_node_path43.default.dirname(dir);
8044
+ const parent = import_node_path44.default.dirname(dir);
7732
8045
  if (parent === dir) break;
7733
8046
  dir = parent;
7734
8047
  }
7735
- return { pm: "npm", cwd: import_node_path43.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8048
+ return { pm: "npm", cwd: import_node_path44.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7736
8049
  }
7737
8050
  async function runPackageManagerInstall(cmd) {
7738
8051
  return new Promise((resolve) => {
@@ -7781,7 +8094,7 @@ async function fileExists2(p) {
7781
8094
  }
7782
8095
  }
7783
8096
  async function readPackageJson(scanPath) {
7784
- const pkgPath = import_node_path44.default.join(scanPath, "package.json");
8097
+ const pkgPath = import_node_path45.default.join(scanPath, "package.json");
7785
8098
  const raw = await import_node_fs26.promises.readFile(pkgPath, "utf8");
7786
8099
  return JSON.parse(raw);
7787
8100
  }
@@ -7800,11 +8113,11 @@ async function findHookFiles(scanPath) {
7800
8113
  for (const entry2 of entries) {
7801
8114
  if (entry2.isDirectory()) {
7802
8115
  if (entry2.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry2.name)) continue;
7803
- await walk3(import_node_path44.default.join(dir, entry2.name));
8116
+ await walk3(import_node_path45.default.join(dir, entry2.name));
7804
8117
  } else if (entry2.isFile()) {
7805
8118
  if ((entry2.name.startsWith("instrumentation") || entry2.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry2.name)) {
7806
- const rel = import_node_path44.default.relative(scanPath, import_node_path44.default.join(dir, entry2.name));
7807
- found.push(rel.split(import_node_path44.default.sep).join("/"));
8119
+ const rel = import_node_path45.default.relative(scanPath, import_node_path45.default.join(dir, entry2.name));
8120
+ found.push(rel.split(import_node_path45.default.sep).join("/"));
7808
8121
  }
7809
8122
  }
7810
8123
  }
@@ -7815,7 +8128,7 @@ async function findHookFiles(scanPath) {
7815
8128
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
7816
8129
  let fallback = null;
7817
8130
  for (const file of hookFiles) {
7818
- const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(scanPath, file), "utf8");
8131
+ const content = await import_node_fs26.promises.readFile(import_node_path45.default.join(scanPath, file), "utf8");
7819
8132
  const patched = splicedContent(content, snippet2);
7820
8133
  if (patched !== null) return { file, content, patched };
7821
8134
  if (fallback === null) fallback = { file, content };
@@ -7823,11 +8136,11 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
7823
8136
  return { file: fallback.file, content: fallback.content, patched: null };
7824
8137
  }
7825
8138
  function extendLogPath() {
7826
- return process.env.NEAT_EXTEND_LOG ?? import_node_path44.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
8139
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path45.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
7827
8140
  }
7828
8141
  async function appendExtendLog(entry2) {
7829
8142
  const logPath = extendLogPath();
7830
- await import_node_fs26.promises.mkdir(import_node_path44.default.dirname(logPath), { recursive: true });
8143
+ await import_node_fs26.promises.mkdir(import_node_path45.default.dirname(logPath), { recursive: true });
7831
8144
  await import_node_fs26.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
7832
8145
  }
7833
8146
  function splicedContent(fileContent, snippet2) {
@@ -7886,7 +8199,7 @@ function lookupInstrumentation(library, installedVersion) {
7886
8199
  }
7887
8200
  async function describeProjectInstrumentation(ctx) {
7888
8201
  const hookFiles = await findHookFiles(ctx.scanPath);
7889
- const envNeat = await fileExists2(import_node_path44.default.join(ctx.scanPath, ".env.neat"));
8202
+ const envNeat = await fileExists2(import_node_path45.default.join(ctx.scanPath, ".env.neat"));
7890
8203
  const registryInstrPackages = new Set(
7891
8204
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
7892
8205
  );
@@ -7908,7 +8221,7 @@ async function applyExtension(ctx, args, options) {
7908
8221
  );
7909
8222
  }
7910
8223
  for (const file of hookFiles) {
7911
- const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
8224
+ const content = await import_node_fs26.promises.readFile(import_node_path45.default.join(ctx.scanPath, file), "utf8");
7912
8225
  if (content.includes(args.registration_snippet)) {
7913
8226
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
7914
8227
  }
@@ -7920,10 +8233,10 @@ async function applyExtension(ctx, args, options) {
7920
8233
  );
7921
8234
  }
7922
8235
  const primaryFile = primary.file;
7923
- const primaryPath = import_node_path44.default.join(ctx.scanPath, primaryFile);
8236
+ const primaryPath = import_node_path45.default.join(ctx.scanPath, primaryFile);
7924
8237
  const filesTouched = [];
7925
8238
  const depsAdded = [];
7926
- const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
8239
+ const pkgPath = import_node_path45.default.join(ctx.scanPath, "package.json");
7927
8240
  const pkg = await readPackageJson(ctx.scanPath);
7928
8241
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
7929
8242
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
@@ -7962,7 +8275,7 @@ async function dryRunExtension(ctx, args) {
7962
8275
  };
7963
8276
  }
7964
8277
  for (const file of hookFiles) {
7965
- const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
8278
+ const content = await import_node_fs26.promises.readFile(import_node_path45.default.join(ctx.scanPath, file), "utf8");
7966
8279
  if (content.includes(args.registration_snippet)) {
7967
8280
  return {
7968
8281
  library: args.library,
@@ -8003,7 +8316,7 @@ async function rollbackExtension(ctx, args) {
8003
8316
  if (!match) {
8004
8317
  return { undone: false, message: "no apply found for library" };
8005
8318
  }
8006
- const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
8319
+ const pkgPath = import_node_path45.default.join(ctx.scanPath, "package.json");
8007
8320
  if (await fileExists2(pkgPath)) {
8008
8321
  const pkg = await readPackageJson(ctx.scanPath);
8009
8322
  if (pkg.dependencies?.[match.instrumentation_package]) {
@@ -8014,7 +8327,7 @@ async function rollbackExtension(ctx, args) {
8014
8327
  }
8015
8328
  const hookFiles = await findHookFiles(ctx.scanPath);
8016
8329
  for (const file of hookFiles) {
8017
- const filePath = import_node_path44.default.join(ctx.scanPath, file);
8330
+ const filePath = import_node_path45.default.join(ctx.scanPath, file);
8018
8331
  const content = await import_node_fs26.promises.readFile(filePath, "utf8");
8019
8332
  if (content.includes(match.registration_snippet)) {
8020
8333
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
@@ -8030,39 +8343,39 @@ async function rollbackExtension(ctx, args) {
8030
8343
 
8031
8344
  // src/divergences.ts
8032
8345
  init_cjs_shims();
8033
- var import_types31 = require("@neat.is/types");
8346
+ var import_types32 = require("@neat.is/types");
8034
8347
  function bucketKey(source, target, type) {
8035
8348
  return `${type}|${source}|${target}`;
8036
8349
  }
8037
8350
  function bucketSourceFor(graph, edge) {
8038
- if (edge.type !== import_types31.EdgeType.CONNECTS_TO) return edge.source;
8039
- const parsed = (0, import_types31.parseFileId)(edge.source);
8351
+ if (edge.type !== import_types32.EdgeType.CONNECTS_TO) return edge.source;
8352
+ const parsed = (0, import_types32.parseFileId)(edge.source);
8040
8353
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
8041
8354
  const target = graph.getNodeAttributes(edge.target);
8042
- if (target.type !== import_types31.NodeType.DatabaseNode) return edge.source;
8043
- return (0, import_types31.serviceId)(parsed.service);
8355
+ if (target.type !== import_types32.NodeType.DatabaseNode) return edge.source;
8356
+ return (0, import_types32.serviceId)(parsed.service);
8044
8357
  }
8045
8358
  function bucketEdges(graph) {
8046
8359
  const buckets2 = /* @__PURE__ */ new Map();
8047
8360
  graph.forEachEdge((id, attrs) => {
8048
8361
  const e = attrs;
8049
- const parsed = (0, import_types31.parseEdgeId)(id);
8362
+ const parsed = (0, import_types32.parseEdgeId)(id);
8050
8363
  const provenance = parsed?.provenance ?? e.provenance;
8051
8364
  const source = bucketSourceFor(graph, e);
8052
8365
  const key = bucketKey(source, e.target, e.type);
8053
8366
  const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
8054
8367
  switch (provenance) {
8055
- case import_types31.Provenance.EXTRACTED:
8368
+ case import_types32.Provenance.EXTRACTED:
8056
8369
  cur.extracted = e;
8057
8370
  break;
8058
- case import_types31.Provenance.OBSERVED:
8371
+ case import_types32.Provenance.OBSERVED:
8059
8372
  cur.observed = e;
8060
8373
  break;
8061
- case import_types31.Provenance.INFERRED:
8374
+ case import_types32.Provenance.INFERRED:
8062
8375
  cur.inferred = e;
8063
8376
  break;
8064
8377
  default:
8065
- if (e.provenance === import_types31.Provenance.STALE) cur.stale = e;
8378
+ if (e.provenance === import_types32.Provenance.STALE) cur.stale = e;
8066
8379
  }
8067
8380
  buckets2.set(key, cur);
8068
8381
  });
@@ -8071,12 +8384,12 @@ function bucketEdges(graph) {
8071
8384
  function nodeIsFrontier(graph, nodeId) {
8072
8385
  if (!graph.hasNode(nodeId)) return false;
8073
8386
  const attrs = graph.getNodeAttributes(nodeId);
8074
- return attrs.type === import_types31.NodeType.FrontierNode;
8387
+ return attrs.type === import_types32.NodeType.FrontierNode;
8075
8388
  }
8076
8389
  function nodeIsWebsocketChannel(graph, nodeId) {
8077
8390
  if (!graph.hasNode(nodeId)) return false;
8078
8391
  const attrs = graph.getNodeAttributes(nodeId);
8079
- return attrs.type === import_types31.NodeType.WebSocketChannelNode;
8392
+ return attrs.type === import_types32.NodeType.WebSocketChannelNode;
8080
8393
  }
8081
8394
  function clampConfidence(n) {
8082
8395
  if (!Number.isFinite(n)) return 0;
@@ -8096,14 +8409,14 @@ function gradedConfidence(edge) {
8096
8409
  return clampConfidence(confidenceForEdge(edge));
8097
8410
  }
8098
8411
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
8099
- import_types31.EdgeType.CALLS,
8100
- import_types31.EdgeType.CONNECTS_TO,
8101
- import_types31.EdgeType.PUBLISHES_TO,
8102
- import_types31.EdgeType.CONSUMES_FROM
8412
+ import_types32.EdgeType.CALLS,
8413
+ import_types32.EdgeType.CONNECTS_TO,
8414
+ import_types32.EdgeType.PUBLISHES_TO,
8415
+ import_types32.EdgeType.CONSUMES_FROM
8103
8416
  ]);
8104
8417
  function detectMissingDivergences(graph, bucket) {
8105
8418
  const out = [];
8106
- if (bucket.type === import_types31.EdgeType.CONTAINS) return out;
8419
+ if (bucket.type === import_types32.EdgeType.CONTAINS) return out;
8107
8420
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
8108
8421
  if (!nodeIsFrontier(graph, bucket.target)) {
8109
8422
  out.push({
@@ -8144,7 +8457,7 @@ function declaredHostFor(svc) {
8144
8457
  function hasExtractedConfiguredBy(graph, svcId) {
8145
8458
  for (const edgeId of graph.outboundEdges(svcId)) {
8146
8459
  const e = graph.getEdgeAttributes(edgeId);
8147
- if (e.type === import_types31.EdgeType.CONFIGURED_BY && e.provenance === import_types31.Provenance.EXTRACTED) {
8460
+ if (e.type === import_types32.EdgeType.CONFIGURED_BY && e.provenance === import_types32.Provenance.EXTRACTED) {
8148
8461
  return true;
8149
8462
  }
8150
8463
  }
@@ -8157,10 +8470,10 @@ function detectHostMismatch(graph, svcId, svc) {
8157
8470
  const out = [];
8158
8471
  for (const edgeId of graph.outboundEdges(svcId)) {
8159
8472
  const edge = graph.getEdgeAttributes(edgeId);
8160
- if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8161
- if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
8473
+ if (edge.type !== import_types32.EdgeType.CONNECTS_TO) continue;
8474
+ if (edge.provenance !== import_types32.Provenance.OBSERVED) continue;
8162
8475
  const target = graph.getNodeAttributes(edge.target);
8163
- if (target.type !== import_types31.NodeType.DatabaseNode) continue;
8476
+ if (target.type !== import_types32.NodeType.DatabaseNode) continue;
8164
8477
  const observedHost = target.host?.trim();
8165
8478
  if (!observedHost) continue;
8166
8479
  if (observedHost === declaredHost) continue;
@@ -8182,10 +8495,10 @@ function detectCompatDivergences(graph, svcId, svc) {
8182
8495
  const deps = svc.dependencies ?? {};
8183
8496
  for (const edgeId of graph.outboundEdges(svcId)) {
8184
8497
  const edge = graph.getEdgeAttributes(edgeId);
8185
- if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8186
- if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
8498
+ if (edge.type !== import_types32.EdgeType.CONNECTS_TO) continue;
8499
+ if (edge.provenance !== import_types32.Provenance.OBSERVED) continue;
8187
8500
  const target = graph.getNodeAttributes(edge.target);
8188
- if (target.type !== import_types31.NodeType.DatabaseNode) continue;
8501
+ if (target.type !== import_types32.NodeType.DatabaseNode) continue;
8189
8502
  for (const pair of compatPairs()) {
8190
8503
  if (pair.engine !== target.engine) continue;
8191
8504
  const declared = deps[pair.driver];
@@ -8244,7 +8557,7 @@ function suppressHostMismatchHalves(all) {
8244
8557
  for (const d of all) {
8245
8558
  if (d.type !== "host-mismatch") continue;
8246
8559
  observedHalf.add(`${d.source}->${d.target}`);
8247
- declaredHalf.add((0, import_types31.databaseId)(d.extractedHost));
8560
+ declaredHalf.add((0, import_types32.databaseId)(d.extractedHost));
8248
8561
  }
8249
8562
  if (observedHalf.size === 0) return all;
8250
8563
  return all.filter((d) => {
@@ -8263,7 +8576,7 @@ function computeDivergences(graph, opts = {}) {
8263
8576
  }
8264
8577
  graph.forEachNode((nodeId, attrs) => {
8265
8578
  const n = attrs;
8266
- if (n.type !== import_types31.NodeType.ServiceNode) return;
8579
+ if (n.type !== import_types32.NodeType.ServiceNode) return;
8267
8580
  const svc = n;
8268
8581
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
8269
8582
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -8297,7 +8610,7 @@ function computeDivergences(graph, opts = {}) {
8297
8610
  if (a.source !== b.source) return a.source.localeCompare(b.source);
8298
8611
  return a.target.localeCompare(b.target);
8299
8612
  });
8300
- return import_types31.DivergenceResultSchema.parse({
8613
+ return import_types32.DivergenceResultSchema.parse({
8301
8614
  divergences: filtered,
8302
8615
  totalAffected: filtered.length,
8303
8616
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -8430,23 +8743,23 @@ function canonicalJson(value) {
8430
8743
  init_cjs_shims();
8431
8744
  var import_node_fs28 = require("fs");
8432
8745
  var import_node_os3 = __toESM(require("os"), 1);
8433
- var import_node_path45 = __toESM(require("path"), 1);
8434
- var import_types32 = require("@neat.is/types");
8746
+ var import_node_path46 = __toESM(require("path"), 1);
8747
+ var import_types33 = require("@neat.is/types");
8435
8748
  var LOCK_TIMEOUT_MS = 5e3;
8436
8749
  var LOCK_RETRY_MS = 50;
8437
8750
  function neatHome() {
8438
8751
  const override = process.env.NEAT_HOME;
8439
- if (override && override.length > 0) return import_node_path45.default.resolve(override);
8440
- return import_node_path45.default.join(import_node_os3.default.homedir(), ".neat");
8752
+ if (override && override.length > 0) return import_node_path46.default.resolve(override);
8753
+ return import_node_path46.default.join(import_node_os3.default.homedir(), ".neat");
8441
8754
  }
8442
8755
  function registryPath() {
8443
- return import_node_path45.default.join(neatHome(), "projects.json");
8756
+ return import_node_path46.default.join(neatHome(), "projects.json");
8444
8757
  }
8445
8758
  function registryLockPath() {
8446
- return import_node_path45.default.join(neatHome(), "projects.json.lock");
8759
+ return import_node_path46.default.join(neatHome(), "projects.json.lock");
8447
8760
  }
8448
8761
  function daemonPidPath() {
8449
- return import_node_path45.default.join(neatHome(), "neatd.pid");
8762
+ return import_node_path46.default.join(neatHome(), "neatd.pid");
8450
8763
  }
8451
8764
  function isPidAliveDefault(pid) {
8452
8765
  try {
@@ -8506,7 +8819,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
8506
8819
  }
8507
8820
  }
8508
8821
  async function writeAtomically(target, contents) {
8509
- await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(target), { recursive: true });
8822
+ await import_node_fs28.promises.mkdir(import_node_path46.default.dirname(target), { recursive: true });
8510
8823
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
8511
8824
  const fd = await import_node_fs28.promises.open(tmp, "w");
8512
8825
  try {
@@ -8519,7 +8832,7 @@ async function writeAtomically(target, contents) {
8519
8832
  }
8520
8833
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
8521
8834
  const deadline = Date.now() + timeoutMs;
8522
- await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(lockPath), { recursive: true });
8835
+ await import_node_fs28.promises.mkdir(import_node_path46.default.dirname(lockPath), { recursive: true });
8523
8836
  let probedHolder = false;
8524
8837
  while (true) {
8525
8838
  try {
@@ -8572,10 +8885,10 @@ async function readRegistry() {
8572
8885
  throw err;
8573
8886
  }
8574
8887
  const parsed = JSON.parse(raw);
8575
- return import_types32.RegistryFileSchema.parse(parsed);
8888
+ return import_types33.RegistryFileSchema.parse(parsed);
8576
8889
  }
8577
8890
  async function writeRegistry(reg) {
8578
- const validated = import_types32.RegistryFileSchema.parse(reg);
8891
+ const validated = import_types33.RegistryFileSchema.parse(reg);
8579
8892
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
8580
8893
  }
8581
8894
  async function getProject(name) {
@@ -8720,7 +9033,7 @@ init_auth();
8720
9033
  // src/connectors-config.ts
8721
9034
  init_cjs_shims();
8722
9035
  var import_node_os4 = __toESM(require("os"), 1);
8723
- var import_node_path46 = __toESM(require("path"), 1);
9036
+ var import_node_path47 = __toESM(require("path"), 1);
8724
9037
  var import_node_fs29 = require("fs");
8725
9038
  var CONNECTORS_CONFIG_VERSION = 1;
8726
9039
  var EnvRefUnsetError = class extends Error {
@@ -8735,11 +9048,11 @@ var EnvRefUnsetError = class extends Error {
8735
9048
  };
8736
9049
  function neatHome2() {
8737
9050
  const override = process.env.NEAT_HOME;
8738
- if (override && override.length > 0) return import_node_path46.default.resolve(override);
8739
- return import_node_path46.default.join(import_node_os4.default.homedir(), ".neat");
9051
+ if (override && override.length > 0) return import_node_path47.default.resolve(override);
9052
+ return import_node_path47.default.join(import_node_os4.default.homedir(), ".neat");
8740
9053
  }
8741
9054
  function connectorsConfigPath(home = neatHome2()) {
8742
- return import_node_path46.default.join(home, "connectors.json");
9055
+ return import_node_path47.default.join(home, "connectors.json");
8743
9056
  }
8744
9057
  var MODE_MASK_LOOSER_THAN_0600 = 63;
8745
9058
  async function warnIfModeLooserThan0600(file) {
@@ -9075,11 +9388,11 @@ function registerRoutes(scope, ctx) {
9075
9388
  const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
9076
9389
  const parsed = [];
9077
9390
  for (const c of candidates) {
9078
- const r = import_types33.DivergenceTypeSchema.safeParse(c);
9391
+ const r = import_types34.DivergenceTypeSchema.safeParse(c);
9079
9392
  if (!r.success) {
9080
9393
  return reply.code(400).send({
9081
9394
  error: `unknown divergence type "${c}"`,
9082
- allowed: import_types33.DivergenceTypeSchema.options
9395
+ allowed: import_types34.DivergenceTypeSchema.options
9083
9396
  });
9084
9397
  }
9085
9398
  parsed.push(r.data);
@@ -9350,7 +9663,7 @@ function registerRoutes(scope, ctx) {
9350
9663
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
9351
9664
  let violations = await log.readAll();
9352
9665
  if (req2.query.severity) {
9353
- const sev = import_types33.PolicySeveritySchema.safeParse(req2.query.severity);
9666
+ const sev = import_types34.PolicySeveritySchema.safeParse(req2.query.severity);
9354
9667
  if (!sev.success) {
9355
9668
  return reply.code(400).send({
9356
9669
  error: "invalid severity",
@@ -9389,7 +9702,7 @@ function registerRoutes(scope, ctx) {
9389
9702
  scope.post("/policies/check", async (req2, reply) => {
9390
9703
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
9391
9704
  if (!proj) return;
9392
- const parsed = import_types33.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
9705
+ const parsed = import_types34.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
9393
9706
  if (!parsed.success) {
9394
9707
  return reply.code(400).send({
9395
9708
  error: "invalid /policies/check body",
@@ -9653,15 +9966,15 @@ init_otel();
9653
9966
 
9654
9967
  // src/connectors/index.ts
9655
9968
  init_cjs_shims();
9656
- var import_types34 = require("@neat.is/types");
9969
+ var import_types35 = require("@neat.is/types");
9657
9970
  var NO_ENV = "unknown";
9658
9971
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
9659
9972
  if (!graph.hasNode(targetNodeId)) return void 0;
9660
9973
  const sites = [];
9661
9974
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
9662
9975
  const edge = graph.getEdgeAttributes(edgeId);
9663
- if (edge.provenance !== import_types34.Provenance.EXTRACTED) continue;
9664
- const parsed = (0, import_types34.parseFileId)(edge.source);
9976
+ if (edge.provenance !== import_types35.Provenance.EXTRACTED) continue;
9977
+ const parsed = (0, import_types35.parseFileId)(edge.source);
9665
9978
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
9666
9979
  const site = { relPath: edge.evidence.file };
9667
9980
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -9672,7 +9985,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
9672
9985
  function routeCallSiteFor(graph, targetNodeId) {
9673
9986
  if (!graph.hasNode(targetNodeId)) return void 0;
9674
9987
  const attrs = graph.getNodeAttributes(targetNodeId);
9675
- if (attrs.type !== import_types34.NodeType.RouteNode || !attrs.path) return void 0;
9988
+ if (attrs.type !== import_types35.NodeType.RouteNode || !attrs.path) return void 0;
9676
9989
  const site = { relPath: attrs.path };
9677
9990
  if (attrs.line !== void 0) site.line = attrs.line;
9678
9991
  return site;
@@ -9846,7 +10159,12 @@ var JUNCTION_DEFAULT_RATE_LIMITS = {
9846
10159
  supabase: { capacity: 30, refillMs: 1e4 },
9847
10160
  // Not a documented API limit at all — a self-imposed ceiling on the raw
9848
10161
  // pg_stat_statements connection (see module header above).
9849
- "supabase-postgres": { capacity: 20, refillMs: 3e3 }
10162
+ "supabase-postgres": { capacity: 20, refillMs: 3e3 },
10163
+ // Push provider (ADR-146): the Drains REST API is touched only by `neat
10164
+ // connector add/remove/test` (provision/deprovision/validate), never a poll
10165
+ // loop, so this bucket is exercised a handful of times per command. Kept
10166
+ // conservative pending a documented Drains-API rate limit.
10167
+ vercel: { capacity: 20, refillMs: 5e3 }
9850
10168
  };
9851
10169
  var JUNCTION_GENERIC_RATE_LIMIT = { capacity: 20, refillMs: 5e3 };
9852
10170
  function defaultRateLimitFor(provider) {
@@ -10138,10 +10456,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
10138
10456
  // src/connectors/supabase/map.ts
10139
10457
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
10140
10458
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
10141
- function targetFromRestPath(path53) {
10142
- const rpcMatch = REST_RPC_PATH_RE.exec(path53);
10459
+ function targetFromRestPath(path54) {
10460
+ const rpcMatch = REST_RPC_PATH_RE.exec(path54);
10143
10461
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
10144
- const tableMatch = REST_TABLE_PATH_RE.exec(path53);
10462
+ const tableMatch = REST_TABLE_PATH_RE.exec(path54);
10145
10463
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
10146
10464
  return null;
10147
10465
  }
@@ -10250,23 +10568,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
10250
10568
 
10251
10569
  // src/connectors/supabase/resolve.ts
10252
10570
  init_cjs_shims();
10253
- var import_types36 = require("@neat.is/types");
10571
+ var import_types37 = require("@neat.is/types");
10254
10572
  function createSupabaseResolveTarget(graph, config) {
10255
10573
  return (signal, _ctx) => {
10256
10574
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
10257
10575
  return null;
10258
10576
  }
10259
- const subResourceId = (0, import_types36.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10577
+ const subResourceId = (0, import_types37.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10260
10578
  if (graph.hasNode(subResourceId)) {
10261
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
10579
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10262
10580
  }
10263
- const bareResourceId = (0, import_types36.infraId)(signal.targetKind, signal.targetName);
10581
+ const bareResourceId = (0, import_types37.infraId)(signal.targetKind, signal.targetName);
10264
10582
  if (graph.hasNode(bareResourceId)) {
10265
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
10583
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10266
10584
  }
10267
- const projectLevelId = (0, import_types36.infraId)("supabase", config.nodeRef);
10585
+ const projectLevelId = (0, import_types37.infraId)("supabase", config.nodeRef);
10268
10586
  if (graph.hasNode(projectLevelId)) {
10269
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
10587
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10270
10588
  }
10271
10589
  return null;
10272
10590
  };
@@ -10359,7 +10677,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
10359
10677
 
10360
10678
  // src/connectors/railway/index.ts
10361
10679
  init_cjs_shims();
10362
- var import_types40 = require("@neat.is/types");
10680
+ var import_types41 = require("@neat.is/types");
10363
10681
 
10364
10682
  // src/connectors/railway/client.ts
10365
10683
  init_cjs_shims();
@@ -10485,7 +10803,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
10485
10803
  const out = [];
10486
10804
  graph.forEachNode((_id, attrs) => {
10487
10805
  const node = attrs;
10488
- if (node.type !== import_types40.NodeType.RouteNode) return;
10806
+ if (node.type !== import_types41.NodeType.RouteNode) return;
10489
10807
  const route = attrs;
10490
10808
  if (route.service !== serviceName) return;
10491
10809
  out.push({
@@ -10589,12 +10907,12 @@ function createRailwayResolveTarget(config) {
10589
10907
  const serviceName = config.serviceNameById[config.serviceId];
10590
10908
  if (!serviceName) return null;
10591
10909
  if (signal.targetKind === ROUTE_TARGET_KIND) {
10592
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types40.EdgeType.CALLS };
10910
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types41.EdgeType.CALLS };
10593
10911
  }
10594
10912
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
10595
10913
  const peerName = config.serviceNameById[signal.targetName];
10596
10914
  if (!peerName) return null;
10597
- return { targetNodeId: (0, import_types40.serviceId)(peerName), serviceName, edgeType: import_types40.EdgeType.CONNECTS_TO };
10915
+ return { targetNodeId: (0, import_types41.serviceId)(peerName), serviceName, edgeType: import_types41.EdgeType.CONNECTS_TO };
10598
10916
  }
10599
10917
  return null;
10600
10918
  };
@@ -10718,9 +11036,9 @@ function parseFirebaseTargetName(targetName) {
10718
11036
  const secondSep = rest.indexOf(FIELD_SEP);
10719
11037
  if (secondSep === -1) return null;
10720
11038
  const method = rest.slice(0, secondSep);
10721
- const path53 = rest.slice(secondSep + 1);
10722
- if (!resourceName || !method || !path53) return null;
10723
- return { resourceName, method, path: path53 };
11039
+ const path54 = rest.slice(secondSep + 1);
11040
+ if (!resourceName || !method || !path54) return null;
11041
+ return { resourceName, method, path: path54 };
10724
11042
  }
10725
11043
  function resourceNameFor(type, labels) {
10726
11044
  if (!labels) return null;
@@ -10758,14 +11076,14 @@ function mapLogEntryToSignal(entry2) {
10758
11076
  if (!req2) return null;
10759
11077
  if (typeof req2.requestMethod !== "string" || req2.requestMethod.length === 0) return null;
10760
11078
  const method = req2.requestMethod.toUpperCase();
10761
- const path53 = pathFromRequestUrl(req2.requestUrl);
10762
- if (path53 === null) return null;
11079
+ const path54 = pathFromRequestUrl(req2.requestUrl);
11080
+ if (path54 === null) return null;
10763
11081
  const timestamp = entry2.timestamp;
10764
11082
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
10765
11083
  const isError = typeof req2.status === "number" && req2.status >= ERROR_STATUS_THRESHOLD2;
10766
11084
  return {
10767
11085
  targetKind: resourceType,
10768
- targetName: packFirebaseTargetName({ resourceName, method, path: path53 }),
11086
+ targetName: packFirebaseTargetName({ resourceName, method, path: path54 }),
10769
11087
  callCount: 1,
10770
11088
  errorCount: isError ? 1 : 0,
10771
11089
  lastObservedIso: timestamp
@@ -10782,7 +11100,7 @@ function mapLogEntriesToSignals(entries) {
10782
11100
 
10783
11101
  // src/connectors/firebase/resolve.ts
10784
11102
  init_cjs_shims();
10785
- var import_types41 = require("@neat.is/types");
11103
+ var import_types42 = require("@neat.is/types");
10786
11104
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
10787
11105
  switch (resourceType) {
10788
11106
  case "cloud_function":
@@ -10797,7 +11115,7 @@ function routeEntriesFor(graph, serviceName) {
10797
11115
  const entries = [];
10798
11116
  graph.forEachNode((_id, attrs) => {
10799
11117
  const node = attrs;
10800
- if (node.type !== import_types41.NodeType.RouteNode) return;
11118
+ if (node.type !== import_types42.NodeType.RouteNode) return;
10801
11119
  const route = attrs;
10802
11120
  if (route.service !== serviceName) return;
10803
11121
  entries.push({
@@ -10829,7 +11147,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
10829
11147
  return {
10830
11148
  targetNodeId: match.routeNodeId,
10831
11149
  serviceName,
10832
- edgeType: import_types41.EdgeType.CALLS
11150
+ edgeType: import_types42.EdgeType.CALLS
10833
11151
  };
10834
11152
  };
10835
11153
  }
@@ -10856,7 +11174,7 @@ init_cjs_shims();
10856
11174
 
10857
11175
  // src/connectors/cloudflare/connector.ts
10858
11176
  init_cjs_shims();
10859
- var import_types43 = require("@neat.is/types");
11177
+ var import_types44 = require("@neat.is/types");
10860
11178
 
10861
11179
  // src/connectors/cloudflare/client.ts
10862
11180
  init_cjs_shims();
@@ -10972,7 +11290,7 @@ function mapEventToSignal(event) {
10972
11290
  if (Number.isNaN(observedAt.getTime())) return null;
10973
11291
  const statusCode = metadata?.statusCode;
10974
11292
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
10975
- const path53 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
11293
+ const path54 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
10976
11294
  return {
10977
11295
  targetKind: CLOUDFLARE_TARGET_KIND,
10978
11296
  targetName: scriptName,
@@ -10980,7 +11298,7 @@ function mapEventToSignal(event) {
10980
11298
  errorCount: isError ? 1 : 0,
10981
11299
  lastObservedIso: observedAt.toISOString(),
10982
11300
  method,
10983
- ...path53 ? { path: path53 } : {},
11301
+ ...path54 ? { path: path54 } : {},
10984
11302
  ...typeof statusCode === "number" ? { statusCode } : {},
10985
11303
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
10986
11304
  };
@@ -11020,19 +11338,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
11020
11338
  graph.forEachNode((id, attrs) => {
11021
11339
  if (found) return;
11022
11340
  const a = attrs;
11023
- if (a.type === import_types43.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11341
+ if (a.type === import_types44.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11024
11342
  found = id;
11025
11343
  }
11026
11344
  });
11027
11345
  return found;
11028
11346
  }
11029
- function findMatchingRouteNode(graph, serviceName, method, path53) {
11030
- const normalizedPath = normalizePathTemplate(path53);
11347
+ function findMatchingRouteNode(graph, serviceName, method, path54) {
11348
+ const normalizedPath = normalizePathTemplate(path54);
11031
11349
  let found = null;
11032
11350
  graph.forEachNode((id, attrs) => {
11033
11351
  if (found) return;
11034
11352
  const a = attrs;
11035
- if (a.type !== import_types43.NodeType.RouteNode || a.service !== serviceName) return;
11353
+ if (a.type !== import_types44.NodeType.RouteNode || a.service !== serviceName) return;
11036
11354
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
11037
11355
  const routeMethod = (a.method ?? "").toUpperCase();
11038
11356
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -11044,18 +11362,18 @@ function createCloudflareResolveTarget(config, graph) {
11044
11362
  return (signal) => {
11045
11363
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
11046
11364
  const scriptName = signal.targetName;
11047
- const { method, path: path53 } = signal;
11365
+ const { method, path: path54 } = signal;
11048
11366
  const resolveRouteGrain = (serviceName, wholeFileId) => {
11049
- if (!method || !path53) return wholeFileId;
11050
- return findMatchingRouteNode(graph, serviceName, method, path53) ?? wholeFileId;
11367
+ if (!method || !path54) return wholeFileId;
11368
+ return findMatchingRouteNode(graph, serviceName, method, path54) ?? wholeFileId;
11051
11369
  };
11052
11370
  const mapping = config.workers?.[scriptName];
11053
11371
  if (mapping) {
11054
- const wholeFileId = (0, import_types43.fileId)(mapping.service, mapping.entryFile);
11372
+ const wholeFileId = (0, import_types44.fileId)(mapping.service, mapping.entryFile);
11055
11373
  return {
11056
11374
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
11057
11375
  serviceName: mapping.service,
11058
- edgeType: import_types43.EdgeType.CALLS
11376
+ edgeType: import_types44.EdgeType.CALLS
11059
11377
  };
11060
11378
  }
11061
11379
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -11064,18 +11382,124 @@ function createCloudflareResolveTarget(config, graph) {
11064
11382
  return {
11065
11383
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
11066
11384
  serviceName: fileNode.service,
11067
- edgeType: import_types43.EdgeType.CALLS
11385
+ edgeType: import_types44.EdgeType.CALLS
11068
11386
  };
11069
11387
  }
11070
11388
  return {
11071
- targetNodeId: (0, import_types43.infraId)("cloudflare-worker", scriptName),
11389
+ targetNodeId: (0, import_types44.infraId)("cloudflare-worker", scriptName),
11072
11390
  serviceName: scriptName,
11073
- edgeType: import_types43.EdgeType.CALLS,
11391
+ edgeType: import_types44.EdgeType.CALLS,
11074
11392
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
11075
11393
  };
11076
11394
  };
11077
11395
  }
11078
11396
 
11397
+ // src/connectors/vercel/index.ts
11398
+ init_cjs_shims();
11399
+
11400
+ // src/connectors/vercel/client.ts
11401
+ init_cjs_shims();
11402
+ var DEFAULT_API_BASE_URL = "https://api.vercel.com";
11403
+ var DEFAULT_DRAIN_NAME = "neat-otlp";
11404
+ var TRACE_SCHEMAS = { trace: { version: "v1" } };
11405
+ function apiBase(config) {
11406
+ return config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
11407
+ }
11408
+ function teamQuery(config) {
11409
+ return `?teamId=${encodeURIComponent(config.teamId)}`;
11410
+ }
11411
+ function drainDelivery(config, otelToken) {
11412
+ return {
11413
+ type: "http",
11414
+ endpoint: config.endpoint,
11415
+ encoding: "json",
11416
+ headers: bearerAuthHeader(otelToken),
11417
+ ...config.secret ? { secret: config.secret } : {}
11418
+ };
11419
+ }
11420
+ async function describeError(res) {
11421
+ try {
11422
+ const data = await res.json();
11423
+ const message = data?.error?.message;
11424
+ return typeof message === "string" && message.length > 0 ? ` \u2014 ${message}` : "";
11425
+ } catch {
11426
+ return "";
11427
+ }
11428
+ }
11429
+ async function createVercelDrain(config, credentials, fetchImpl = fetch) {
11430
+ const projectIds = config.projectIds ?? [];
11431
+ const body = {
11432
+ name: config.drainName ?? DEFAULT_DRAIN_NAME,
11433
+ projects: projectIds.length > 0 ? "some" : "all",
11434
+ ...projectIds.length > 0 ? { projectIds } : {},
11435
+ schemas: TRACE_SCHEMAS,
11436
+ delivery: drainDelivery(config, credentials.otelToken),
11437
+ source: { kind: "self-served" }
11438
+ };
11439
+ const res = await junctionFetch(
11440
+ `${apiBase(config)}/v1/drains${teamQuery(config)}`,
11441
+ {
11442
+ method: "POST",
11443
+ headers: { "Content-Type": "application/json", ...bearerAuthHeader(credentials.token) },
11444
+ body: JSON.stringify(body)
11445
+ },
11446
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11447
+ );
11448
+ if (!res.ok) {
11449
+ throw new Error(
11450
+ `vercel connector: create drain failed (${res.status} ${res.statusText}${await describeError(res)})`
11451
+ );
11452
+ }
11453
+ const payload = await res.json().catch(() => null);
11454
+ if (!payload || typeof payload.id !== "string" || payload.id.length === 0) {
11455
+ throw new Error(
11456
+ "vercel connector: create drain returned no drain id \u2014 the Drains API response shape may have changed"
11457
+ );
11458
+ }
11459
+ return {
11460
+ id: payload.id,
11461
+ ...payload.status ? { status: payload.status } : {},
11462
+ ...payload.disabledReason ? { disabledReason: payload.disabledReason } : {}
11463
+ };
11464
+ }
11465
+ async function deleteVercelDrain(config, drainId, credentials, fetchImpl = fetch) {
11466
+ const res = await junctionFetch(
11467
+ `${apiBase(config)}/v1/drains/${encodeURIComponent(drainId)}${teamQuery(config)}`,
11468
+ { method: "DELETE", headers: { ...bearerAuthHeader(credentials.token) } },
11469
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11470
+ );
11471
+ if (res.ok || res.status === 404) return;
11472
+ throw new Error(
11473
+ `vercel connector: delete drain failed (${res.status} ${res.statusText}${await describeError(res)})`
11474
+ );
11475
+ }
11476
+ async function testVercelDrainDelivery(config, credentials, fetchImpl = fetch) {
11477
+ const res = await junctionFetch(
11478
+ `${apiBase(config)}/v1/drains/test${teamQuery(config)}`,
11479
+ {
11480
+ method: "POST",
11481
+ headers: { "Content-Type": "application/json", ...bearerAuthHeader(credentials.token) },
11482
+ body: JSON.stringify({ schemas: TRACE_SCHEMAS, delivery: drainDelivery(config, credentials.otelToken) })
11483
+ },
11484
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11485
+ );
11486
+ if (res.status === 401 || res.status === 403) {
11487
+ return { status: "failure", error: `vercel rejected the API token (HTTP ${res.status})` };
11488
+ }
11489
+ if (!res.ok) {
11490
+ return {
11491
+ status: "failure",
11492
+ error: `vercel drain validation failed (${res.status} ${res.statusText}${await describeError(res)})`
11493
+ };
11494
+ }
11495
+ const payload = await res.json().catch(() => null);
11496
+ return {
11497
+ ...payload?.status ? { status: payload.status } : {},
11498
+ ...payload?.error ? { error: payload.error } : {},
11499
+ ...payload?.endpoint ? { endpoint: payload.endpoint } : {}
11500
+ };
11501
+ }
11502
+
11079
11503
  // src/connectors/registry.ts
11080
11504
  var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
11081
11505
  async function authProbe(input) {
@@ -11227,6 +11651,91 @@ var PROVIDER_DISPATCH = {
11227
11651
  }
11228
11652
  }
11229
11653
  };
11654
+ function vercelCredsFrom(credentials) {
11655
+ return { token: String(credentials.token ?? ""), otelToken: String(credentials.otelToken ?? "") };
11656
+ }
11657
+ function vercelConfigFromOptions(options) {
11658
+ const raw = options.projectIds;
11659
+ let projectIds;
11660
+ if (Array.isArray(raw)) projectIds = raw.filter((p) => typeof p === "string");
11661
+ else if (typeof raw === "string" && raw.trim().length > 0) {
11662
+ projectIds = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
11663
+ }
11664
+ return {
11665
+ teamId: String(options.teamId ?? ""),
11666
+ endpoint: String(options.endpoint ?? ""),
11667
+ ...projectIds && projectIds.length > 0 ? { projectIds } : {},
11668
+ ...typeof options.drainId === "string" ? { drainId: options.drainId } : {},
11669
+ ...typeof options.drainName === "string" ? { drainName: options.drainName } : {},
11670
+ ...typeof options.apiBaseUrl === "string" ? { apiBaseUrl: options.apiBaseUrl } : {},
11671
+ ...typeof options.secret === "string" ? { secret: options.secret } : {}
11672
+ };
11673
+ }
11674
+ var PUSH_PROVIDER_DISPATCH = {
11675
+ vercel: {
11676
+ provider: "vercel",
11677
+ // The Vercel access token is the "primary" secret a single `--token`
11678
+ // populates; `otelToken` (the daemon's OTLP bearer) is the second field.
11679
+ primaryCredentialKey: "token",
11680
+ requiredCredentialFields: ["token", "otelToken"],
11681
+ // teamId scopes every Drains call; endpoint is where the drain delivers.
11682
+ // projectIds is optional (absent → the drain covers the whole team).
11683
+ requiredOptionFields: ["teamId", "endpoint"],
11684
+ // POST /v1/drains/test — authenticates the token and pings the endpoint
11685
+ // with a sample event, so `success` means the credential is live *and* the
11686
+ // daemon's OTLP endpoint is reachable and accepted the drain's bearer.
11687
+ async validate({ credentials, options, fetchImpl }) {
11688
+ const result = await testVercelDrainDelivery(
11689
+ vercelConfigFromOptions(options),
11690
+ vercelCredsFrom(credentials),
11691
+ fetchImpl
11692
+ );
11693
+ if (result.status === "success") return { ok: true };
11694
+ return {
11695
+ ok: false,
11696
+ reason: result.error ?? `vercel drain delivery test returned "${result.status ?? "no status"}"`
11697
+ };
11698
+ },
11699
+ // POST /v1/drains — creates the trace drain, returns its id to store in
11700
+ // `options.drainId`. A created-but-not-enabled drain is surfaced as a note,
11701
+ // not a failure (the entry still points at a real drain).
11702
+ async provision({ credentials, options, fetchImpl }) {
11703
+ try {
11704
+ const created = await createVercelDrain(
11705
+ vercelConfigFromOptions(options),
11706
+ vercelCredsFrom(credentials),
11707
+ fetchImpl
11708
+ );
11709
+ 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;
11710
+ return { ok: true, options: { drainId: created.id }, ...note ? { note } : {} };
11711
+ } catch (err) {
11712
+ return { ok: false, reason: err.message };
11713
+ }
11714
+ },
11715
+ // DELETE /v1/drains/{id} — idempotent (deleteVercelDrain treats 404 as
11716
+ // success). No recorded drainId → nothing to delete, still a success.
11717
+ async deprovision({ credentials, options, fetchImpl }) {
11718
+ const drainId = typeof options.drainId === "string" ? options.drainId : "";
11719
+ if (!drainId) {
11720
+ return { ok: true, note: "no drain id was recorded \u2014 nothing to delete on the Vercel side" };
11721
+ }
11722
+ try {
11723
+ await deleteVercelDrain(
11724
+ vercelConfigFromOptions(options),
11725
+ drainId,
11726
+ vercelCredsFrom(credentials),
11727
+ fetchImpl
11728
+ );
11729
+ return { ok: true };
11730
+ } catch (err) {
11731
+ return { ok: false, reason: err.message };
11732
+ }
11733
+ }
11734
+ }
11735
+ };
11736
+ function isPushProvider(provider) {
11737
+ return provider in PUSH_PROVIDER_DISPATCH;
11738
+ }
11230
11739
  function resolveEntryCredentials(dispatch, entry2, env) {
11231
11740
  let credentials;
11232
11741
  try {
@@ -11249,6 +11758,13 @@ function resolveEntryCredentials(dispatch, entry2, env) {
11249
11758
  function buildRegistration(entry2, graph, env = process.env) {
11250
11759
  const dispatch = PROVIDER_DISPATCH[entry2.provider];
11251
11760
  if (!dispatch) {
11761
+ if (isPushProvider(entry2.provider)) {
11762
+ return {
11763
+ ok: false,
11764
+ push: true,
11765
+ reason: `push provider "${entry2.provider}" ingests via the OTLP receiver \u2014 nothing to poll`
11766
+ };
11767
+ }
11252
11768
  return { ok: false, reason: `unknown provider "${entry2.provider}"` };
11253
11769
  }
11254
11770
  const creds = resolveEntryCredentials(dispatch, entry2, env);
@@ -11299,7 +11815,7 @@ async function loadConnectorRegistrations(input) {
11299
11815
  if (!connectorMatchesProject(entry2, project)) continue;
11300
11816
  const result = buildRegistration(entry2, graph, env);
11301
11817
  if (result.ok) registrations.push(result.registration);
11302
- else onSkip?.(entry2, result.reason);
11818
+ else if (!result.push) onSkip?.(entry2, result.reason);
11303
11819
  }
11304
11820
  return registrations;
11305
11821
  }
@@ -11310,7 +11826,7 @@ init_auth();
11310
11826
  // src/unrouted.ts
11311
11827
  init_cjs_shims();
11312
11828
  var import_node_fs30 = require("fs");
11313
- var import_node_path49 = __toESM(require("path"), 1);
11829
+ var import_node_path50 = __toESM(require("path"), 1);
11314
11830
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
11315
11831
  return {
11316
11832
  timestamp: now.toISOString(),
@@ -11320,34 +11836,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
11320
11836
  };
11321
11837
  }
11322
11838
  async function appendUnroutedSpan(neatHome4, record) {
11323
- const target = import_node_path49.default.join(neatHome4, "errors.ndjson");
11839
+ const target = import_node_path50.default.join(neatHome4, "errors.ndjson");
11324
11840
  await import_node_fs30.promises.mkdir(neatHome4, { recursive: true });
11325
11841
  await import_node_fs30.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
11326
11842
  }
11327
11843
  function unroutedErrorsPath(neatHome4) {
11328
- return import_node_path49.default.join(neatHome4, "errors.ndjson");
11844
+ return import_node_path50.default.join(neatHome4, "errors.ndjson");
11329
11845
  }
11330
11846
 
11331
11847
  // src/daemon.ts
11332
- var import_types46 = require("@neat.is/types");
11848
+ var import_types47 = require("@neat.is/types");
11333
11849
  function daemonJsonPath(scanPath) {
11334
- return import_node_path50.default.join(scanPath, "neat-out", "daemon.json");
11850
+ return import_node_path51.default.join(scanPath, "neat-out", "daemon.json");
11335
11851
  }
11336
11852
  function daemonsDiscoveryDir(home) {
11337
11853
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
11338
- return import_node_path50.default.join(base, "daemons");
11854
+ return import_node_path51.default.join(base, "daemons");
11339
11855
  }
11340
11856
  function daemonDiscoveryPath(project, home) {
11341
- return import_node_path50.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
11857
+ return import_node_path51.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
11342
11858
  }
11343
11859
  function sanitizeDiscoveryName(project) {
11344
11860
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
11345
11861
  }
11346
11862
  function neatHomeFromEnv() {
11347
11863
  const env = process.env.NEAT_HOME;
11348
- if (env && env.length > 0) return import_node_path50.default.resolve(env);
11864
+ if (env && env.length > 0) return import_node_path51.default.resolve(env);
11349
11865
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11350
- return import_node_path50.default.join(home, ".neat");
11866
+ return import_node_path51.default.join(home, ".neat");
11351
11867
  }
11352
11868
  function resolveNeatVersion() {
11353
11869
  if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
@@ -11416,11 +11932,11 @@ function teardownSlot(slot) {
11416
11932
  }
11417
11933
  }
11418
11934
  function neatHomeFor(opts) {
11419
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path50.default.resolve(opts.neatHome);
11935
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path51.default.resolve(opts.neatHome);
11420
11936
  const env = process.env.NEAT_HOME;
11421
- if (env && env.length > 0) return import_node_path50.default.resolve(env);
11937
+ if (env && env.length > 0) return import_node_path51.default.resolve(env);
11422
11938
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11423
- return import_node_path50.default.join(home, ".neat");
11939
+ return import_node_path51.default.join(home, ".neat");
11424
11940
  }
11425
11941
  function routeSpanToProject(serviceName, projects) {
11426
11942
  if (!serviceName) return DEFAULT_PROJECT;
@@ -11464,11 +11980,11 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
11464
11980
  if (!serviceName) return true;
11465
11981
  if (serviceNameMatchesProject(serviceName, project)) return true;
11466
11982
  return graph.someNode(
11467
- (_id, attrs) => attrs.type === import_types46.NodeType.ServiceNode && attrs.name === serviceName
11983
+ (_id, attrs) => attrs.type === import_types47.NodeType.ServiceNode && attrs.name === serviceName
11468
11984
  );
11469
11985
  }
11470
11986
  async function bootstrapProject(entry2, connectors = [], neatHome4) {
11471
- const paths = pathsForProject(entry2.name, import_node_path50.default.join(entry2.path, "neat-out"));
11987
+ const paths = pathsForProject(entry2.name, import_node_path51.default.join(entry2.path, "neat-out"));
11472
11988
  try {
11473
11989
  const stat = await import_node_fs31.promises.stat(entry2.path);
11474
11990
  if (!stat.isDirectory()) {
@@ -11599,7 +12115,7 @@ async function startDaemon(opts = {}) {
11599
12115
  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;
11600
12116
  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;
11601
12117
  const singleProject = projectArg;
11602
- const singleProjectPath = singleProject && projectPathArg ? import_node_path50.default.resolve(projectPathArg) : null;
12118
+ const singleProjectPath = singleProject && projectPathArg ? import_node_path51.default.resolve(projectPathArg) : null;
11603
12119
  if (singleProject && !singleProjectPath) {
11604
12120
  throw new Error(
11605
12121
  `neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
@@ -11614,7 +12130,7 @@ async function startDaemon(opts = {}) {
11614
12130
  );
11615
12131
  }
11616
12132
  }
11617
- const pidPath = import_node_path50.default.join(home, "neatd.pid");
12133
+ const pidPath = import_node_path51.default.join(home, "neatd.pid");
11618
12134
  await writeAtomically(pidPath, `${process.pid}
11619
12135
  `);
11620
12136
  const slots = /* @__PURE__ */ new Map();
@@ -12021,8 +12537,8 @@ async function startDaemon(opts = {}) {
12021
12537
  let registryWatcher = null;
12022
12538
  let reloadTimer = null;
12023
12539
  if (!singleProject) try {
12024
- const regDir = import_node_path50.default.dirname(regPath);
12025
- const regBase = import_node_path50.default.basename(regPath);
12540
+ const regDir = import_node_path51.default.dirname(regPath);
12541
+ const regBase = import_node_path51.default.basename(regPath);
12026
12542
  registryWatcher = (0, import_node_fs31.watch)(regDir, (_eventType, filename) => {
12027
12543
  if (filename !== null && filename !== regBase) return;
12028
12544
  if (reloadTimer) clearTimeout(reloadTimer);
@@ -12097,7 +12613,7 @@ init_cjs_shims();
12097
12613
  var import_node_child_process2 = require("child_process");
12098
12614
  var import_node_fs32 = require("fs");
12099
12615
  var import_node_net = __toESM(require("net"), 1);
12100
- var import_node_path51 = __toESM(require("path"), 1);
12616
+ var import_node_path52 = __toESM(require("path"), 1);
12101
12617
  var DEFAULT_WEB_PORT = 6328;
12102
12618
  var DEFAULT_REST_PORT = 8080;
12103
12619
  function asValidPort(value) {
@@ -12106,11 +12622,11 @@ function asValidPort(value) {
12106
12622
  }
12107
12623
  function projectRoot() {
12108
12624
  const fromEnv = process.env.NEAT_SCAN_PATH;
12109
- return import_node_path51.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
12625
+ return import_node_path52.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
12110
12626
  }
12111
12627
  async function readDaemonPorts(root) {
12112
12628
  try {
12113
- const raw = await import_node_fs32.promises.readFile(import_node_path51.default.join(root, "neat-out", "daemon.json"), "utf8");
12629
+ const raw = await import_node_fs32.promises.readFile(import_node_path52.default.join(root, "neat-out", "daemon.json"), "utf8");
12114
12630
  const parsed = JSON.parse(raw);
12115
12631
  const ports = parsed?.ports ?? {};
12116
12632
  return { web: asValidPort(ports.web), rest: asValidPort(ports.rest) };
@@ -12155,10 +12671,10 @@ function resolveWebPackageDir() {
12155
12671
  eval("require")
12156
12672
  );
12157
12673
  const pkgJsonPath = req.resolve("@neat.is/web/package.json");
12158
- return import_node_path51.default.dirname(pkgJsonPath);
12674
+ return import_node_path52.default.dirname(pkgJsonPath);
12159
12675
  }
12160
12676
  function resolveStandaloneServerEntry(webDir) {
12161
- return import_node_path51.default.join(webDir, ".next/standalone/packages/web/server.js");
12677
+ return import_node_path52.default.join(webDir, ".next/standalone/packages/web/server.js");
12162
12678
  }
12163
12679
  async function pickInternalPort() {
12164
12680
  return new Promise((resolve, reject) => {
@@ -12211,7 +12727,7 @@ async function spawnWebUI(restPort, opts = {}) {
12211
12727
  NEAT_API_URL: apiUrl
12212
12728
  };
12213
12729
  child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
12214
- cwd: import_node_path51.default.dirname(serverEntry),
12730
+ cwd: import_node_path52.default.dirname(serverEntry),
12215
12731
  env,
12216
12732
  stdio: ["ignore", "inherit", "inherit"],
12217
12733
  detached: false
@@ -12387,14 +12903,14 @@ function localVersion() {
12387
12903
  }
12388
12904
  function neatHome3() {
12389
12905
  if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
12390
- return import_node_path52.default.resolve(process.env.NEAT_HOME);
12906
+ return import_node_path53.default.resolve(process.env.NEAT_HOME);
12391
12907
  }
12392
12908
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
12393
- return import_node_path52.default.join(home, ".neat");
12909
+ return import_node_path53.default.join(home, ".neat");
12394
12910
  }
12395
12911
  async function readPid() {
12396
12912
  try {
12397
- const raw = await import_node_fs33.promises.readFile(import_node_path52.default.join(neatHome3(), "neatd.pid"), "utf8");
12913
+ const raw = await import_node_fs33.promises.readFile(import_node_path53.default.join(neatHome3(), "neatd.pid"), "utf8");
12398
12914
  const n = Number.parseInt(raw.trim(), 10);
12399
12915
  return Number.isFinite(n) ? n : null;
12400
12916
  } catch {