@neat.is/core 0.4.28-dev.20260712 → 0.4.28-dev.20260714

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 path50 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
- if (exactUnauthPaths.has(path50) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path50)) {
64
+ const path53 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
+ if (exactUnauthPaths.has(path53) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path53)) {
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_path43.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
198
- return import_node_path43.default.resolve(here, "..", "proto");
197
+ const here = import_node_path47.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
198
+ return import_node_path47.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_path43, import_node_crypto2, grpc, protoLoader;
266
+ var import_node_url, import_node_path47, 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_path43 = __toESM(require("path"), 1);
272
+ import_node_path47 = __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 path50 = q === -1 ? v : v.slice(0, q);
366
- if (path50.length > 0) return path50;
365
+ const path53 = q === -1 ? v : v.slice(0, q);
366
+ if (path53.length > 0) return path53;
367
367
  }
368
368
  }
369
369
  return void 0;
@@ -417,10 +417,10 @@ function parseOtlpRequest(body) {
417
417
  return out;
418
418
  }
419
419
  function loadProtoRoot() {
420
- const here = import_node_path44.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
421
- const protoRoot = import_node_path44.default.resolve(here, "..", "proto");
420
+ const here = import_node_path48.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
421
+ const protoRoot = import_node_path48.default.resolve(here, "..", "proto");
422
422
  const root = new import_protobufjs.default.Root();
423
- root.resolvePath = (_origin, target) => import_node_path44.default.resolve(protoRoot, target);
423
+ root.resolvePath = (_origin, target) => import_node_path48.default.resolve(protoRoot, target);
424
424
  root.loadSync(
425
425
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
426
426
  { keepCase: true }
@@ -648,12 +648,12 @@ async function listenSteppingOtlp(app, requestedPort, host) {
648
648
  }
649
649
  }
650
650
  }
651
- var import_node_path44, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
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
652
  var init_otel = __esm({
653
653
  "src/otel.ts"() {
654
654
  "use strict";
655
655
  init_cjs_shims();
656
- import_node_path44 = __toESM(require("path"), 1);
656
+ import_node_path48 = __toESM(require("path"), 1);
657
657
  import_node_url2 = require("url");
658
658
  import_fastify2 = __toESM(require("fastify"), 1);
659
659
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -676,14 +676,14 @@ __export(neatd_exports, {
676
676
  });
677
677
  module.exports = __toCommonJS(neatd_exports);
678
678
  init_cjs_shims();
679
- var import_node_fs30 = require("fs");
680
- var import_node_path49 = __toESM(require("path"), 1);
679
+ var import_node_fs33 = require("fs");
680
+ var import_node_path52 = __toESM(require("path"), 1);
681
681
  var import_node_module2 = require("module");
682
682
 
683
683
  // src/daemon.ts
684
684
  init_cjs_shims();
685
- var import_node_fs28 = require("fs");
686
- var import_node_path47 = __toESM(require("path"), 1);
685
+ var import_node_fs31 = require("fs");
686
+ var import_node_path50 = __toESM(require("path"), 1);
687
687
  var import_node_module = require("module");
688
688
 
689
689
  // src/graph.ts
@@ -1213,19 +1213,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1213
1213
  function longestIncomingWalk(graph, start, maxDepth) {
1214
1214
  let best = { path: [start], edges: [] };
1215
1215
  const visited = /* @__PURE__ */ new Set([start]);
1216
- function step(node, path50, edges) {
1217
- if (path50.length > best.path.length) {
1218
- best = { path: [...path50], edges: [...edges] };
1216
+ function step(node, path53, edges) {
1217
+ if (path53.length > best.path.length) {
1218
+ best = { path: [...path53], edges: [...edges] };
1219
1219
  }
1220
- if (path50.length - 1 >= maxDepth) return;
1220
+ if (path53.length - 1 >= maxDepth) return;
1221
1221
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1222
1222
  for (const [srcId, edge] of incoming) {
1223
1223
  if (visited.has(srcId)) continue;
1224
1224
  visited.add(srcId);
1225
- path50.push(srcId);
1225
+ path53.push(srcId);
1226
1226
  edges.push(edge);
1227
- step(srcId, path50, edges);
1228
- path50.pop();
1227
+ step(srcId, path53, edges);
1228
+ path53.pop();
1229
1229
  edges.pop();
1230
1230
  visited.delete(srcId);
1231
1231
  }
@@ -1419,26 +1419,26 @@ function dominantFailingCall(graph, serviceId5, visited) {
1419
1419
  return best;
1420
1420
  }
1421
1421
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1422
- const path50 = [originServiceId];
1422
+ const path53 = [originServiceId];
1423
1423
  const edges = [];
1424
1424
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1425
1425
  let current = originServiceId;
1426
1426
  for (let depth = 0; depth < maxDepth; depth++) {
1427
1427
  const hop = dominantFailingCall(graph, current, visited);
1428
1428
  if (!hop) break;
1429
- path50.push(hop.nextService);
1429
+ path53.push(hop.nextService);
1430
1430
  edges.push(hop.edge);
1431
1431
  visited.add(hop.nextService);
1432
1432
  current = hop.nextService;
1433
1433
  }
1434
1434
  if (edges.length === 0) return null;
1435
- return { path: path50, edges, culprit: current };
1435
+ return { path: path53, edges, culprit: current };
1436
1436
  }
1437
1437
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1438
1438
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1439
1439
  if (!chain) return null;
1440
1440
  const culprit = chain.culprit;
1441
- const path50 = [...chain.path];
1441
+ const path53 = [...chain.path];
1442
1442
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1443
1443
  const baseConfidence = confidenceFromMix(chain.edges);
1444
1444
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1446,14 +1446,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1446
1446
  if (loc) {
1447
1447
  let rootCauseNode = culprit;
1448
1448
  if (loc.fileNode) {
1449
- path50.push(loc.fileNode);
1449
+ path53.push(loc.fileNode);
1450
1450
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1451
1451
  rootCauseNode = loc.fileNode;
1452
1452
  }
1453
1453
  return import_types.RootCauseResultSchema.parse({
1454
1454
  rootCauseNode,
1455
1455
  rootCauseReason: loc.rootCauseReason,
1456
- traversalPath: path50,
1456
+ traversalPath: path53,
1457
1457
  edgeProvenances,
1458
1458
  confidence,
1459
1459
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1465,7 +1465,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1465
1465
  return import_types.RootCauseResultSchema.parse({
1466
1466
  rootCauseNode: culprit,
1467
1467
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1468
- traversalPath: path50,
1468
+ traversalPath: path53,
1469
1469
  edgeProvenances,
1470
1470
  confidence,
1471
1471
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -6458,6 +6458,39 @@ function classifyImage(image) {
6458
6458
  if (last.startsWith("memcached")) return "memcached";
6459
6459
  return "container";
6460
6460
  }
6461
+ function lineContaining(raw, needle) {
6462
+ if (!needle) return void 0;
6463
+ const idx = raw.indexOf(needle);
6464
+ if (idx === -1) return void 0;
6465
+ let line = 1;
6466
+ for (let i = 0; i < idx; i++) if (raw[i] === "\n") line++;
6467
+ return line;
6468
+ }
6469
+ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provider, evidenceFile, line) {
6470
+ let nodesAdded = 0;
6471
+ let edgesAdded = 0;
6472
+ const node = makeInfraNode(kind, name, provider);
6473
+ if (!graph.hasNode(node.id)) {
6474
+ graph.addNode(node.id, node);
6475
+ nodesAdded++;
6476
+ }
6477
+ if (node.id === anchorId) return { nodesAdded, edgesAdded };
6478
+ const edgeId = (0, import_types4.extractedEdgeId)(anchorId, node.id, edgeType);
6479
+ if (!graph.hasEdge(edgeId)) {
6480
+ const edge = {
6481
+ id: edgeId,
6482
+ source: anchorId,
6483
+ target: node.id,
6484
+ type: edgeType,
6485
+ provenance: import_types21.Provenance.EXTRACTED,
6486
+ confidence: (0, import_types21.confidenceForExtracted)("structural"),
6487
+ evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
6488
+ };
6489
+ graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
6490
+ edgesAdded++;
6491
+ }
6492
+ return { nodesAdded, edgesAdded };
6493
+ }
6461
6494
 
6462
6495
  // src/extract/infra/docker-compose.ts
6463
6496
  function dependsOnList(value) {
@@ -6817,7 +6850,7 @@ async function readWranglerConfig(dir) {
6817
6850
  }
6818
6851
  return null;
6819
6852
  }
6820
- function lineContaining(raw, needle) {
6853
+ function lineContaining2(raw, needle) {
6821
6854
  if (!needle) return void 0;
6822
6855
  const idx = raw.indexOf(needle);
6823
6856
  if (idx === -1) return void 0;
@@ -6941,7 +6974,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
6941
6974
  "cloudflare-route",
6942
6975
  route,
6943
6976
  evidenceFile,
6944
- lineContaining(raw, route)
6977
+ lineContaining2(raw, route)
6945
6978
  );
6946
6979
  nodesAdded += result.nodesAdded;
6947
6980
  edgesAdded += result.edgesAdded;
@@ -6965,7 +6998,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
6965
6998
  group.kind,
6966
6999
  name,
6967
7000
  evidenceFile,
6968
- lineContaining(raw, name)
7001
+ lineContaining2(raw, name)
6969
7002
  );
6970
7003
  nodesAdded += result.nodesAdded;
6971
7004
  edgesAdded += result.edgesAdded;
@@ -6979,7 +7012,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
6979
7012
  "cloudflare-cron",
6980
7013
  cron,
6981
7014
  evidenceFile,
6982
- lineContaining(raw, cron)
7015
+ lineContaining2(raw, cron)
6983
7016
  );
6984
7017
  nodesAdded += result.nodesAdded;
6985
7018
  edgesAdded += result.edgesAdded;
@@ -6992,7 +7025,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
6992
7025
  "cloudflare-env-var",
6993
7026
  varName,
6994
7027
  evidenceFile,
6995
- lineContaining(raw, varName)
7028
+ lineContaining2(raw, varName)
6996
7029
  );
6997
7030
  nodesAdded += result.nodesAdded;
6998
7031
  edgesAdded += result.edgesAdded;
@@ -7010,7 +7043,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7010
7043
  type: import_types25.EdgeType.CALLS,
7011
7044
  provenance: import_types25.Provenance.EXTRACTED,
7012
7045
  confidence: (0, import_types25.confidenceForExtracted)("structural"),
7013
- evidence: { file: evidenceFile, line: lineContaining(raw, svc.service) }
7046
+ evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
7014
7047
  };
7015
7048
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
7016
7049
  edgesAdded++;
@@ -7024,11 +7057,208 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7024
7057
  "cloudflare-service-binding",
7025
7058
  svc.service,
7026
7059
  evidenceFile,
7027
- lineContaining(raw, svc.service)
7060
+ lineContaining2(raw, svc.service)
7061
+ );
7062
+ nodesAdded += result.nodesAdded;
7063
+ edgesAdded += result.edgesAdded;
7064
+ }
7065
+ }
7066
+ return { nodesAdded, edgesAdded };
7067
+ }
7068
+
7069
+ // src/extract/infra/vercel.ts
7070
+ init_cjs_shims();
7071
+ var import_node_fs20 = require("fs");
7072
+ var import_node_path36 = __toESM(require("path"), 1);
7073
+ var import_types26 = require("@neat.is/types");
7074
+ var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
7075
+ async function readVercelConfig(dir) {
7076
+ for (const filename of VERCEL_CONFIG_FILENAMES) {
7077
+ const abs = import_node_path36.default.join(dir, filename);
7078
+ if (!await exists(abs)) continue;
7079
+ const raw = await import_node_fs20.promises.readFile(abs, "utf8");
7080
+ const config = JSON.parse(maskCommentsInSource(raw));
7081
+ return { config, relFile: filename, raw };
7082
+ }
7083
+ return null;
7084
+ }
7085
+ async function readLinkedProjectName(dir) {
7086
+ const abs = import_node_path36.default.join(dir, ".vercel", "project.json");
7087
+ if (!await exists(abs)) return void 0;
7088
+ const parsed = JSON.parse(await import_node_fs20.promises.readFile(abs, "utf8"));
7089
+ return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
7090
+ }
7091
+ function routeSource(route) {
7092
+ return route.source ?? route.src;
7093
+ }
7094
+ async function addVercelServices(graph, services, scanPath) {
7095
+ let nodesAdded = 0;
7096
+ let edgesAdded = 0;
7097
+ for (const service of services) {
7098
+ let read = null;
7099
+ let projectName;
7100
+ try {
7101
+ read = await readVercelConfig(service.dir);
7102
+ projectName = await readLinkedProjectName(service.dir);
7103
+ } catch (err) {
7104
+ recordExtractionError("infra vercel", import_node_path36.default.relative(scanPath, service.dir), err);
7105
+ continue;
7106
+ }
7107
+ if (!read && !projectName) continue;
7108
+ const serviceNode = graph.getNodeAttributes(service.node.id);
7109
+ if (serviceNode.platform !== "vercel" || projectName && serviceNode.platformName !== projectName) {
7110
+ const updated = {
7111
+ ...serviceNode,
7112
+ platform: "vercel",
7113
+ ...projectName ? { platformName: projectName } : {}
7114
+ };
7115
+ graph.replaceNodeAttributes(service.node.id, updated);
7116
+ }
7117
+ const anchorId = service.node.id;
7118
+ if (!read) continue;
7119
+ const { config, relFile, raw } = read;
7120
+ const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, import_node_path36.default.join(service.dir, relFile)));
7121
+ const add = (edgeType, kind, name) => {
7122
+ if (!name) return;
7123
+ const result = emitPlatformResourceEdge(
7124
+ graph,
7125
+ anchorId,
7126
+ edgeType,
7127
+ kind,
7128
+ name,
7129
+ "vercel",
7130
+ evidenceFile,
7131
+ lineContaining(raw, name)
7132
+ );
7133
+ nodesAdded += result.nodesAdded;
7134
+ edgesAdded += result.edgesAdded;
7135
+ };
7136
+ add(import_types26.EdgeType.RUNS_ON, "vercel", "vercel");
7137
+ for (const cron of config.crons ?? []) add(import_types26.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7138
+ for (const varName of Object.keys(config.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7139
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7140
+ for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
7141
+ add(import_types26.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7142
+ }
7143
+ }
7144
+ return { nodesAdded, edgesAdded };
7145
+ }
7146
+
7147
+ // src/extract/infra/railway.ts
7148
+ init_cjs_shims();
7149
+ var import_node_fs21 = require("fs");
7150
+ var import_node_path37 = __toESM(require("path"), 1);
7151
+ var import_smol_toml3 = require("smol-toml");
7152
+ var import_types27 = require("@neat.is/types");
7153
+ var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
7154
+ async function readRailwayConfig(dir) {
7155
+ for (const filename of RAILWAY_FILENAMES) {
7156
+ const abs = import_node_path37.default.join(dir, filename);
7157
+ if (!await exists(abs)) continue;
7158
+ const raw = await import_node_fs21.promises.readFile(abs, "utf8");
7159
+ const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
7160
+ return { config, relFile: filename, raw };
7161
+ }
7162
+ return null;
7163
+ }
7164
+ async function addRailwayServices(graph, services, scanPath) {
7165
+ let nodesAdded = 0;
7166
+ let edgesAdded = 0;
7167
+ for (const service of services) {
7168
+ let read = null;
7169
+ try {
7170
+ read = await readRailwayConfig(service.dir);
7171
+ } catch (err) {
7172
+ recordExtractionError("infra railway", import_node_path37.default.relative(scanPath, service.dir), err);
7173
+ continue;
7174
+ }
7175
+ if (!read) continue;
7176
+ const serviceNode = graph.getNodeAttributes(service.node.id);
7177
+ if (serviceNode.platform !== "railway") {
7178
+ graph.replaceNodeAttributes(service.node.id, { ...serviceNode, platform: "railway" });
7179
+ }
7180
+ const anchorId = service.node.id;
7181
+ const { config, relFile, raw } = read;
7182
+ const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
7183
+ const add = (edgeType, kind, name) => {
7184
+ if (!name) return;
7185
+ const result = emitPlatformResourceEdge(
7186
+ graph,
7187
+ anchorId,
7188
+ edgeType,
7189
+ kind,
7190
+ name,
7191
+ "railway",
7192
+ evidenceFile,
7193
+ lineContaining(raw, name)
7028
7194
  );
7029
7195
  nodesAdded += result.nodesAdded;
7030
7196
  edgesAdded += result.edgesAdded;
7197
+ };
7198
+ add(import_types27.EdgeType.RUNS_ON, "railway", "railway");
7199
+ add(import_types27.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7200
+ add(import_types27.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7201
+ }
7202
+ return { nodesAdded, edgesAdded };
7203
+ }
7204
+
7205
+ // src/extract/infra/supabase.ts
7206
+ init_cjs_shims();
7207
+ var import_node_fs22 = require("fs");
7208
+ var import_node_path38 = __toESM(require("path"), 1);
7209
+ var import_smol_toml4 = require("smol-toml");
7210
+ var import_types28 = require("@neat.is/types");
7211
+ async function readSupabaseConfig(dir) {
7212
+ const relFile = import_node_path38.default.join("supabase", "config.toml");
7213
+ const abs = import_node_path38.default.join(dir, relFile);
7214
+ if (!await exists(abs)) return null;
7215
+ const raw = await import_node_fs22.promises.readFile(abs, "utf8");
7216
+ const config = (0, import_smol_toml4.parse)(raw);
7217
+ return { config, relFile, raw };
7218
+ }
7219
+ async function addSupabaseProjects(graph, services, scanPath) {
7220
+ let nodesAdded = 0;
7221
+ let edgesAdded = 0;
7222
+ for (const service of services) {
7223
+ let read = null;
7224
+ try {
7225
+ read = await readSupabaseConfig(service.dir);
7226
+ } catch (err) {
7227
+ recordExtractionError("infra supabase", import_node_path38.default.relative(scanPath, service.dir), err);
7228
+ continue;
7229
+ }
7230
+ if (!read) continue;
7231
+ const { config, relFile, raw } = read;
7232
+ const projectId = typeof config.project_id === "string" ? config.project_id : void 0;
7233
+ const serviceNode = graph.getNodeAttributes(service.node.id);
7234
+ if (serviceNode.platform !== "supabase" || projectId && serviceNode.platformName !== projectId) {
7235
+ graph.replaceNodeAttributes(service.node.id, {
7236
+ ...serviceNode,
7237
+ platform: "supabase",
7238
+ ...projectId ? { platformName: projectId } : {}
7239
+ });
7031
7240
  }
7241
+ const anchorId = service.node.id;
7242
+ const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7243
+ const add = (edgeType, kind, name) => {
7244
+ if (!name) return;
7245
+ const result = emitPlatformResourceEdge(
7246
+ graph,
7247
+ anchorId,
7248
+ edgeType,
7249
+ kind,
7250
+ name,
7251
+ "supabase",
7252
+ evidenceFile,
7253
+ lineContaining(raw, name)
7254
+ );
7255
+ nodesAdded += result.nodesAdded;
7256
+ edgesAdded += result.edgesAdded;
7257
+ };
7258
+ add(import_types28.EdgeType.RUNS_ON, "supabase", "supabase");
7259
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "supabase-function", fn);
7260
+ if (config.storage) add(import_types28.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7261
+ if (config.auth) add(import_types28.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7032
7262
  }
7033
7263
  return { nodesAdded, edgesAdded };
7034
7264
  }
@@ -7040,24 +7270,27 @@ async function addInfra(graph, scanPath, services) {
7040
7270
  const terraform = await addTerraformResources(graph, scanPath);
7041
7271
  const k8s = await addK8sResources(graph, scanPath);
7042
7272
  const cloudflare = await addCloudflareWorkers(graph, services, scanPath);
7273
+ const vercel = await addVercelServices(graph, services, scanPath);
7274
+ const railway = await addRailwayServices(graph, services, scanPath);
7275
+ const supabase = await addSupabaseProjects(graph, services, scanPath);
7043
7276
  return {
7044
- nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded,
7045
- edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded
7277
+ nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded + vercel.nodesAdded + railway.nodesAdded + supabase.nodesAdded,
7278
+ edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded + vercel.edgesAdded + railway.edgesAdded + supabase.edgesAdded
7046
7279
  };
7047
7280
  }
7048
7281
 
7049
7282
  // src/extract/index.ts
7050
- var import_node_path37 = __toESM(require("path"), 1);
7283
+ var import_node_path40 = __toESM(require("path"), 1);
7051
7284
 
7052
7285
  // src/extract/retire.ts
7053
7286
  init_cjs_shims();
7054
- var import_node_fs20 = require("fs");
7055
- var import_node_path36 = __toESM(require("path"), 1);
7056
- var import_types26 = require("@neat.is/types");
7287
+ var import_node_fs23 = require("fs");
7288
+ var import_node_path39 = __toESM(require("path"), 1);
7289
+ var import_types29 = require("@neat.is/types");
7057
7290
  function dropOrphanedFileNodes(graph) {
7058
7291
  const orphans = [];
7059
7292
  graph.forEachNode((id, attrs) => {
7060
- if (attrs.type !== import_types26.NodeType.FileNode) return;
7293
+ if (attrs.type !== import_types29.NodeType.FileNode) return;
7061
7294
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
7062
7295
  orphans.push(id);
7063
7296
  }
@@ -7070,14 +7303,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
7070
7303
  const bases = [scanPath, ...serviceDirs];
7071
7304
  graph.forEachEdge((id, attrs) => {
7072
7305
  const edge = attrs;
7073
- if (edge.provenance !== import_types26.Provenance.EXTRACTED) return;
7306
+ if (edge.provenance !== import_types29.Provenance.EXTRACTED) return;
7074
7307
  const evidenceFile = edge.evidence?.file;
7075
7308
  if (!evidenceFile) return;
7076
- if (import_node_path36.default.isAbsolute(evidenceFile)) {
7077
- if (!(0, import_node_fs20.existsSync)(evidenceFile)) toDrop.push(id);
7309
+ if (import_node_path39.default.isAbsolute(evidenceFile)) {
7310
+ if (!(0, import_node_fs23.existsSync)(evidenceFile)) toDrop.push(id);
7078
7311
  return;
7079
7312
  }
7080
- const found = bases.some((base) => (0, import_node_fs20.existsSync)(import_node_path36.default.join(base, evidenceFile)));
7313
+ const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path39.default.join(base, evidenceFile)));
7081
7314
  if (!found) toDrop.push(id);
7082
7315
  });
7083
7316
  for (const id of toDrop) graph.dropEdge(id);
@@ -7119,7 +7352,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7119
7352
  }
7120
7353
  const droppedEntries = drainDroppedExtracted();
7121
7354
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
7122
- const rejectedPath = import_node_path37.default.join(import_node_path37.default.dirname(opts.errorsPath), "rejected.ndjson");
7355
+ const rejectedPath = import_node_path40.default.join(import_node_path40.default.dirname(opts.errorsPath), "rejected.ndjson");
7123
7356
  try {
7124
7357
  await writeRejectedExtracted(droppedEntries, rejectedPath);
7125
7358
  } catch (err) {
@@ -7153,9 +7386,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7153
7386
 
7154
7387
  // src/persist.ts
7155
7388
  init_cjs_shims();
7156
- var import_node_fs21 = require("fs");
7157
- var import_node_path38 = __toESM(require("path"), 1);
7158
- var import_types27 = require("@neat.is/types");
7389
+ var import_node_fs24 = require("fs");
7390
+ var import_node_path41 = __toESM(require("path"), 1);
7391
+ var import_types30 = require("@neat.is/types");
7159
7392
  var SCHEMA_VERSION = 4;
7160
7393
  function migrateV1ToV2(payload) {
7161
7394
  const nodes = payload.graph.nodes;
@@ -7177,12 +7410,12 @@ function migrateV2ToV3(payload) {
7177
7410
  for (const edge of edges) {
7178
7411
  const attrs = edge.attributes;
7179
7412
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
7180
- attrs.provenance = import_types27.Provenance.OBSERVED;
7413
+ attrs.provenance = import_types30.Provenance.OBSERVED;
7181
7414
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
7182
7415
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
7183
7416
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
7184
7417
  if (type && source && target) {
7185
- const newId = (0, import_types27.observedEdgeId)(source, target, type);
7418
+ const newId = (0, import_types30.observedEdgeId)(source, target, type);
7186
7419
  attrs.id = newId;
7187
7420
  if (edge.key) edge.key = newId;
7188
7421
  }
@@ -7191,7 +7424,7 @@ function migrateV2ToV3(payload) {
7191
7424
  return { ...payload, schemaVersion: 3 };
7192
7425
  }
7193
7426
  async function ensureDir(filePath) {
7194
- await import_node_fs21.promises.mkdir(import_node_path38.default.dirname(filePath), { recursive: true });
7427
+ await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(filePath), { recursive: true });
7195
7428
  }
7196
7429
  async function saveGraphToDisk(graph, outPath) {
7197
7430
  await ensureDir(outPath);
@@ -7201,13 +7434,13 @@ async function saveGraphToDisk(graph, outPath) {
7201
7434
  graph: graph.export()
7202
7435
  };
7203
7436
  const tmp = `${outPath}.tmp`;
7204
- await import_node_fs21.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
7205
- await import_node_fs21.promises.rename(tmp, outPath);
7437
+ await import_node_fs24.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
7438
+ await import_node_fs24.promises.rename(tmp, outPath);
7206
7439
  }
7207
7440
  async function loadGraphFromDisk(graph, outPath) {
7208
7441
  let raw;
7209
7442
  try {
7210
- raw = await import_node_fs21.promises.readFile(outPath, "utf8");
7443
+ raw = await import_node_fs24.promises.readFile(outPath, "utf8");
7211
7444
  } catch (err) {
7212
7445
  if (err.code === "ENOENT") return;
7213
7446
  throw err;
@@ -7272,23 +7505,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
7272
7505
 
7273
7506
  // src/projects.ts
7274
7507
  init_cjs_shims();
7275
- var import_node_path39 = __toESM(require("path"), 1);
7508
+ var import_node_path42 = __toESM(require("path"), 1);
7276
7509
  function pathsForProject(project, baseDir) {
7277
7510
  if (project === DEFAULT_PROJECT) {
7278
7511
  return {
7279
- snapshotPath: import_node_path39.default.join(baseDir, "graph.json"),
7280
- errorsPath: import_node_path39.default.join(baseDir, "errors.ndjson"),
7281
- staleEventsPath: import_node_path39.default.join(baseDir, "stale-events.ndjson"),
7282
- embeddingsCachePath: import_node_path39.default.join(baseDir, "embeddings.json"),
7283
- policyViolationsPath: import_node_path39.default.join(baseDir, "policy-violations.ndjson")
7512
+ snapshotPath: import_node_path42.default.join(baseDir, "graph.json"),
7513
+ errorsPath: import_node_path42.default.join(baseDir, "errors.ndjson"),
7514
+ staleEventsPath: import_node_path42.default.join(baseDir, "stale-events.ndjson"),
7515
+ embeddingsCachePath: import_node_path42.default.join(baseDir, "embeddings.json"),
7516
+ policyViolationsPath: import_node_path42.default.join(baseDir, "policy-violations.ndjson")
7284
7517
  };
7285
7518
  }
7286
7519
  return {
7287
- snapshotPath: import_node_path39.default.join(baseDir, `${project}.json`),
7288
- errorsPath: import_node_path39.default.join(baseDir, `errors.${project}.ndjson`),
7289
- staleEventsPath: import_node_path39.default.join(baseDir, `stale-events.${project}.ndjson`),
7290
- embeddingsCachePath: import_node_path39.default.join(baseDir, `embeddings.${project}.json`),
7291
- policyViolationsPath: import_node_path39.default.join(baseDir, `policy-violations.${project}.ndjson`)
7520
+ snapshotPath: import_node_path42.default.join(baseDir, `${project}.json`),
7521
+ errorsPath: import_node_path42.default.join(baseDir, `errors.${project}.ndjson`),
7522
+ staleEventsPath: import_node_path42.default.join(baseDir, `stale-events.${project}.ndjson`),
7523
+ embeddingsCachePath: import_node_path42.default.join(baseDir, `embeddings.${project}.json`),
7524
+ policyViolationsPath: import_node_path42.default.join(baseDir, `policy-violations.${project}.ndjson`)
7292
7525
  };
7293
7526
  }
7294
7527
  var Projects = class {
@@ -7326,19 +7559,19 @@ var Projects = class {
7326
7559
  init_cjs_shims();
7327
7560
  var import_fastify = __toESM(require("fastify"), 1);
7328
7561
  var import_cors = __toESM(require("@fastify/cors"), 1);
7329
- var import_types30 = require("@neat.is/types");
7562
+ var import_types33 = require("@neat.is/types");
7330
7563
 
7331
7564
  // src/extend/index.ts
7332
7565
  init_cjs_shims();
7333
- var import_node_fs23 = require("fs");
7334
- var import_node_path41 = __toESM(require("path"), 1);
7566
+ var import_node_fs26 = require("fs");
7567
+ var import_node_path44 = __toESM(require("path"), 1);
7335
7568
  var import_node_os2 = __toESM(require("os"), 1);
7336
7569
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
7337
7570
 
7338
7571
  // src/installers/package-manager.ts
7339
7572
  init_cjs_shims();
7340
- var import_node_fs22 = require("fs");
7341
- var import_node_path40 = __toESM(require("path"), 1);
7573
+ var import_node_fs25 = require("fs");
7574
+ var import_node_path43 = __toESM(require("path"), 1);
7342
7575
  var import_node_child_process = require("child_process");
7343
7576
  var LOCKFILE_PRIORITY = [
7344
7577
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -7353,29 +7586,29 @@ var LOCKFILE_PRIORITY = [
7353
7586
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
7354
7587
  async function exists2(p) {
7355
7588
  try {
7356
- await import_node_fs22.promises.access(p);
7589
+ await import_node_fs25.promises.access(p);
7357
7590
  return true;
7358
7591
  } catch {
7359
7592
  return false;
7360
7593
  }
7361
7594
  }
7362
7595
  async function detectPackageManager(serviceDir) {
7363
- let dir = import_node_path40.default.resolve(serviceDir);
7596
+ let dir = import_node_path43.default.resolve(serviceDir);
7364
7597
  const stops = /* @__PURE__ */ new Set();
7365
7598
  for (let i = 0; i < 64; i++) {
7366
7599
  if (stops.has(dir)) break;
7367
7600
  stops.add(dir);
7368
7601
  for (const candidate of LOCKFILE_PRIORITY) {
7369
- const lockPath = import_node_path40.default.join(dir, candidate.lockfile);
7602
+ const lockPath = import_node_path43.default.join(dir, candidate.lockfile);
7370
7603
  if (await exists2(lockPath)) {
7371
7604
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
7372
7605
  }
7373
7606
  }
7374
- const parent = import_node_path40.default.dirname(dir);
7607
+ const parent = import_node_path43.default.dirname(dir);
7375
7608
  if (parent === dir) break;
7376
7609
  dir = parent;
7377
7610
  }
7378
- return { pm: "npm", cwd: import_node_path40.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7611
+ return { pm: "npm", cwd: import_node_path43.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7379
7612
  }
7380
7613
  async function runPackageManagerInstall(cmd) {
7381
7614
  return new Promise((resolve) => {
@@ -7417,30 +7650,30 @@ ${err.message}`
7417
7650
  // src/extend/index.ts
7418
7651
  async function fileExists2(p) {
7419
7652
  try {
7420
- await import_node_fs23.promises.access(p);
7653
+ await import_node_fs26.promises.access(p);
7421
7654
  return true;
7422
7655
  } catch {
7423
7656
  return false;
7424
7657
  }
7425
7658
  }
7426
7659
  async function readPackageJson(scanPath) {
7427
- const pkgPath = import_node_path41.default.join(scanPath, "package.json");
7428
- const raw = await import_node_fs23.promises.readFile(pkgPath, "utf8");
7660
+ const pkgPath = import_node_path44.default.join(scanPath, "package.json");
7661
+ const raw = await import_node_fs26.promises.readFile(pkgPath, "utf8");
7429
7662
  return JSON.parse(raw);
7430
7663
  }
7431
7664
  async function findHookFiles(scanPath) {
7432
- const entries = await import_node_fs23.promises.readdir(scanPath);
7665
+ const entries = await import_node_fs26.promises.readdir(scanPath);
7433
7666
  return entries.filter(
7434
7667
  (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
7435
7668
  ).sort();
7436
7669
  }
7437
7670
  function extendLogPath() {
7438
- return process.env.NEAT_EXTEND_LOG ?? import_node_path41.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
7671
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path44.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
7439
7672
  }
7440
7673
  async function appendExtendLog(entry2) {
7441
7674
  const logPath = extendLogPath();
7442
- await import_node_fs23.promises.mkdir(import_node_path41.default.dirname(logPath), { recursive: true });
7443
- await import_node_fs23.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
7675
+ await import_node_fs26.promises.mkdir(import_node_path44.default.dirname(logPath), { recursive: true });
7676
+ await import_node_fs26.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
7444
7677
  }
7445
7678
  function splicedContent(fileContent, snippet2) {
7446
7679
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -7498,7 +7731,7 @@ function lookupInstrumentation(library, installedVersion) {
7498
7731
  }
7499
7732
  async function describeProjectInstrumentation(ctx) {
7500
7733
  const hookFiles = await findHookFiles(ctx.scanPath);
7501
- const envNeat = await fileExists2(import_node_path41.default.join(ctx.scanPath, ".env.neat"));
7734
+ const envNeat = await fileExists2(import_node_path44.default.join(ctx.scanPath, ".env.neat"));
7502
7735
  const registryInstrPackages = new Set(
7503
7736
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
7504
7737
  );
@@ -7520,31 +7753,31 @@ async function applyExtension(ctx, args, options) {
7520
7753
  );
7521
7754
  }
7522
7755
  for (const file of hookFiles) {
7523
- const content = await import_node_fs23.promises.readFile(import_node_path41.default.join(ctx.scanPath, file), "utf8");
7756
+ const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
7524
7757
  if (content.includes(args.registration_snippet)) {
7525
7758
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
7526
7759
  }
7527
7760
  }
7528
7761
  const primaryFile = hookFiles[0];
7529
- const primaryPath = import_node_path41.default.join(ctx.scanPath, primaryFile);
7762
+ const primaryPath = import_node_path44.default.join(ctx.scanPath, primaryFile);
7530
7763
  const filesTouched = [];
7531
7764
  const depsAdded = [];
7532
- const pkgPath = import_node_path41.default.join(ctx.scanPath, "package.json");
7765
+ const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
7533
7766
  const pkg = await readPackageJson(ctx.scanPath);
7534
7767
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
7535
7768
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
7536
- await import_node_fs23.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7769
+ await import_node_fs26.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7537
7770
  filesTouched.push("package.json");
7538
7771
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
7539
7772
  }
7540
- const hookContent = await import_node_fs23.promises.readFile(primaryPath, "utf8");
7773
+ const hookContent = await import_node_fs26.promises.readFile(primaryPath, "utf8");
7541
7774
  const patched = splicedContent(hookContent, args.registration_snippet);
7542
7775
  if (!patched) {
7543
7776
  throw new Error(
7544
7777
  `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
7545
7778
  );
7546
7779
  }
7547
- await import_node_fs23.promises.writeFile(primaryPath, patched, "utf8");
7780
+ await import_node_fs26.promises.writeFile(primaryPath, patched, "utf8");
7548
7781
  filesTouched.push(primaryFile);
7549
7782
  const cmd = await detectPackageManager(ctx.scanPath);
7550
7783
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -7575,7 +7808,7 @@ async function dryRunExtension(ctx, args) {
7575
7808
  };
7576
7809
  }
7577
7810
  for (const file of hookFiles) {
7578
- const content = await import_node_fs23.promises.readFile(import_node_path41.default.join(ctx.scanPath, file), "utf8");
7811
+ const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
7579
7812
  if (content.includes(args.registration_snippet)) {
7580
7813
  return {
7581
7814
  library: args.library,
@@ -7597,7 +7830,7 @@ async function dryRunExtension(ctx, args) {
7597
7830
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
7598
7831
  filesTouched.push("package.json");
7599
7832
  }
7600
- const hookContent = await import_node_fs23.promises.readFile(import_node_path41.default.join(ctx.scanPath, primaryFile), "utf8");
7833
+ const hookContent = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, primaryFile), "utf8");
7601
7834
  const patched = splicedContent(hookContent, args.registration_snippet);
7602
7835
  if (patched) {
7603
7836
  filesTouched.push(primaryFile);
@@ -7612,28 +7845,28 @@ async function rollbackExtension(ctx, args) {
7612
7845
  if (!await fileExists2(logPath)) {
7613
7846
  return { undone: false, message: "no apply found for library" };
7614
7847
  }
7615
- const raw = await import_node_fs23.promises.readFile(logPath, "utf8");
7848
+ const raw = await import_node_fs26.promises.readFile(logPath, "utf8");
7616
7849
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
7617
7850
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
7618
7851
  if (!match) {
7619
7852
  return { undone: false, message: "no apply found for library" };
7620
7853
  }
7621
- const pkgPath = import_node_path41.default.join(ctx.scanPath, "package.json");
7854
+ const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
7622
7855
  if (await fileExists2(pkgPath)) {
7623
7856
  const pkg = await readPackageJson(ctx.scanPath);
7624
7857
  if (pkg.dependencies?.[match.instrumentation_package]) {
7625
7858
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
7626
7859
  pkg.dependencies = rest;
7627
- await import_node_fs23.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7860
+ await import_node_fs26.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7628
7861
  }
7629
7862
  }
7630
7863
  const hookFiles = await findHookFiles(ctx.scanPath);
7631
7864
  for (const file of hookFiles) {
7632
- const filePath = import_node_path41.default.join(ctx.scanPath, file);
7633
- const content = await import_node_fs23.promises.readFile(filePath, "utf8");
7865
+ const filePath = import_node_path44.default.join(ctx.scanPath, file);
7866
+ const content = await import_node_fs26.promises.readFile(filePath, "utf8");
7634
7867
  if (content.includes(match.registration_snippet)) {
7635
7868
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
7636
- await import_node_fs23.promises.writeFile(filePath, filtered, "utf8");
7869
+ await import_node_fs26.promises.writeFile(filePath, filtered, "utf8");
7637
7870
  break;
7638
7871
  }
7639
7872
  }
@@ -7645,7 +7878,7 @@ async function rollbackExtension(ctx, args) {
7645
7878
 
7646
7879
  // src/divergences.ts
7647
7880
  init_cjs_shims();
7648
- var import_types28 = require("@neat.is/types");
7881
+ var import_types31 = require("@neat.is/types");
7649
7882
  function bucketKey(source, target, type) {
7650
7883
  return `${type}|${source}|${target}`;
7651
7884
  }
@@ -7653,22 +7886,22 @@ function bucketEdges(graph) {
7653
7886
  const buckets2 = /* @__PURE__ */ new Map();
7654
7887
  graph.forEachEdge((id, attrs) => {
7655
7888
  const e = attrs;
7656
- const parsed = (0, import_types28.parseEdgeId)(id);
7889
+ const parsed = (0, import_types31.parseEdgeId)(id);
7657
7890
  const provenance = parsed?.provenance ?? e.provenance;
7658
7891
  const key = bucketKey(e.source, e.target, e.type);
7659
7892
  const cur = buckets2.get(key) ?? { source: e.source, target: e.target, type: e.type };
7660
7893
  switch (provenance) {
7661
- case import_types28.Provenance.EXTRACTED:
7894
+ case import_types31.Provenance.EXTRACTED:
7662
7895
  cur.extracted = e;
7663
7896
  break;
7664
- case import_types28.Provenance.OBSERVED:
7897
+ case import_types31.Provenance.OBSERVED:
7665
7898
  cur.observed = e;
7666
7899
  break;
7667
- case import_types28.Provenance.INFERRED:
7900
+ case import_types31.Provenance.INFERRED:
7668
7901
  cur.inferred = e;
7669
7902
  break;
7670
7903
  default:
7671
- if (e.provenance === import_types28.Provenance.STALE) cur.stale = e;
7904
+ if (e.provenance === import_types31.Provenance.STALE) cur.stale = e;
7672
7905
  }
7673
7906
  buckets2.set(key, cur);
7674
7907
  });
@@ -7677,12 +7910,12 @@ function bucketEdges(graph) {
7677
7910
  function nodeIsFrontier(graph, nodeId) {
7678
7911
  if (!graph.hasNode(nodeId)) return false;
7679
7912
  const attrs = graph.getNodeAttributes(nodeId);
7680
- return attrs.type === import_types28.NodeType.FrontierNode;
7913
+ return attrs.type === import_types31.NodeType.FrontierNode;
7681
7914
  }
7682
7915
  function nodeIsWebsocketChannel(graph, nodeId) {
7683
7916
  if (!graph.hasNode(nodeId)) return false;
7684
7917
  const attrs = graph.getNodeAttributes(nodeId);
7685
- return attrs.type === import_types28.NodeType.WebSocketChannelNode;
7918
+ return attrs.type === import_types31.NodeType.WebSocketChannelNode;
7686
7919
  }
7687
7920
  function clampConfidence(n) {
7688
7921
  if (!Number.isFinite(n)) return 0;
@@ -7702,14 +7935,14 @@ function gradedConfidence(edge) {
7702
7935
  return clampConfidence(confidenceForEdge(edge));
7703
7936
  }
7704
7937
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
7705
- import_types28.EdgeType.CALLS,
7706
- import_types28.EdgeType.CONNECTS_TO,
7707
- import_types28.EdgeType.PUBLISHES_TO,
7708
- import_types28.EdgeType.CONSUMES_FROM
7938
+ import_types31.EdgeType.CALLS,
7939
+ import_types31.EdgeType.CONNECTS_TO,
7940
+ import_types31.EdgeType.PUBLISHES_TO,
7941
+ import_types31.EdgeType.CONSUMES_FROM
7709
7942
  ]);
7710
7943
  function detectMissingDivergences(graph, bucket) {
7711
7944
  const out = [];
7712
- if (bucket.type === import_types28.EdgeType.CONTAINS) return out;
7945
+ if (bucket.type === import_types31.EdgeType.CONTAINS) return out;
7713
7946
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
7714
7947
  if (!nodeIsFrontier(graph, bucket.target)) {
7715
7948
  out.push({
@@ -7750,7 +7983,7 @@ function declaredHostFor(svc) {
7750
7983
  function hasExtractedConfiguredBy(graph, svcId) {
7751
7984
  for (const edgeId of graph.outboundEdges(svcId)) {
7752
7985
  const e = graph.getEdgeAttributes(edgeId);
7753
- if (e.type === import_types28.EdgeType.CONFIGURED_BY && e.provenance === import_types28.Provenance.EXTRACTED) {
7986
+ if (e.type === import_types31.EdgeType.CONFIGURED_BY && e.provenance === import_types31.Provenance.EXTRACTED) {
7754
7987
  return true;
7755
7988
  }
7756
7989
  }
@@ -7763,10 +7996,10 @@ function detectHostMismatch(graph, svcId, svc) {
7763
7996
  const out = [];
7764
7997
  for (const edgeId of graph.outboundEdges(svcId)) {
7765
7998
  const edge = graph.getEdgeAttributes(edgeId);
7766
- if (edge.type !== import_types28.EdgeType.CONNECTS_TO) continue;
7767
- if (edge.provenance !== import_types28.Provenance.OBSERVED) continue;
7999
+ if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8000
+ if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
7768
8001
  const target = graph.getNodeAttributes(edge.target);
7769
- if (target.type !== import_types28.NodeType.DatabaseNode) continue;
8002
+ if (target.type !== import_types31.NodeType.DatabaseNode) continue;
7770
8003
  const observedHost = target.host?.trim();
7771
8004
  if (!observedHost) continue;
7772
8005
  if (observedHost === declaredHost) continue;
@@ -7788,10 +8021,10 @@ function detectCompatDivergences(graph, svcId, svc) {
7788
8021
  const deps = svc.dependencies ?? {};
7789
8022
  for (const edgeId of graph.outboundEdges(svcId)) {
7790
8023
  const edge = graph.getEdgeAttributes(edgeId);
7791
- if (edge.type !== import_types28.EdgeType.CONNECTS_TO) continue;
7792
- if (edge.provenance !== import_types28.Provenance.OBSERVED) continue;
8024
+ if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8025
+ if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
7793
8026
  const target = graph.getNodeAttributes(edge.target);
7794
- if (target.type !== import_types28.NodeType.DatabaseNode) continue;
8027
+ if (target.type !== import_types31.NodeType.DatabaseNode) continue;
7795
8028
  for (const pair of compatPairs()) {
7796
8029
  if (pair.engine !== target.engine) continue;
7797
8030
  const declared = deps[pair.driver];
@@ -7850,7 +8083,7 @@ function suppressHostMismatchHalves(all) {
7850
8083
  for (const d of all) {
7851
8084
  if (d.type !== "host-mismatch") continue;
7852
8085
  observedHalf.add(`${d.source}->${d.target}`);
7853
- declaredHalf.add((0, import_types28.databaseId)(d.extractedHost));
8086
+ declaredHalf.add((0, import_types31.databaseId)(d.extractedHost));
7854
8087
  }
7855
8088
  if (observedHalf.size === 0) return all;
7856
8089
  return all.filter((d) => {
@@ -7869,7 +8102,7 @@ function computeDivergences(graph, opts = {}) {
7869
8102
  }
7870
8103
  graph.forEachNode((nodeId, attrs) => {
7871
8104
  const n = attrs;
7872
- if (n.type !== import_types28.NodeType.ServiceNode) return;
8105
+ if (n.type !== import_types31.NodeType.ServiceNode) return;
7873
8106
  const svc = n;
7874
8107
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
7875
8108
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -7903,7 +8136,7 @@ function computeDivergences(graph, opts = {}) {
7903
8136
  if (a.source !== b.source) return a.source.localeCompare(b.source);
7904
8137
  return a.target.localeCompare(b.target);
7905
8138
  });
7906
- return import_types28.DivergenceResultSchema.parse({
8139
+ return import_types31.DivergenceResultSchema.parse({
7907
8140
  divergences: filtered,
7908
8141
  totalAffected: filtered.length,
7909
8142
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -7957,7 +8190,7 @@ function queryLogEntries(opts) {
7957
8190
 
7958
8191
  // src/diff.ts
7959
8192
  init_cjs_shims();
7960
- var import_node_fs24 = require("fs");
8193
+ var import_node_fs27 = require("fs");
7961
8194
  async function loadSnapshotForDiff(target) {
7962
8195
  if (/^https?:\/\//i.test(target)) {
7963
8196
  const res = await fetch(target);
@@ -7966,7 +8199,7 @@ async function loadSnapshotForDiff(target) {
7966
8199
  }
7967
8200
  return await res.json();
7968
8201
  }
7969
- const raw = await import_node_fs24.promises.readFile(target, "utf8");
8202
+ const raw = await import_node_fs27.promises.readFile(target, "utf8");
7970
8203
  return JSON.parse(raw);
7971
8204
  }
7972
8205
  function indexEntries(entries) {
@@ -8034,25 +8267,25 @@ function canonicalJson(value) {
8034
8267
 
8035
8268
  // src/registry.ts
8036
8269
  init_cjs_shims();
8037
- var import_node_fs25 = require("fs");
8270
+ var import_node_fs28 = require("fs");
8038
8271
  var import_node_os3 = __toESM(require("os"), 1);
8039
- var import_node_path42 = __toESM(require("path"), 1);
8040
- var import_types29 = require("@neat.is/types");
8272
+ var import_node_path45 = __toESM(require("path"), 1);
8273
+ var import_types32 = require("@neat.is/types");
8041
8274
  var LOCK_TIMEOUT_MS = 5e3;
8042
8275
  var LOCK_RETRY_MS = 50;
8043
8276
  function neatHome() {
8044
8277
  const override = process.env.NEAT_HOME;
8045
- if (override && override.length > 0) return import_node_path42.default.resolve(override);
8046
- return import_node_path42.default.join(import_node_os3.default.homedir(), ".neat");
8278
+ if (override && override.length > 0) return import_node_path45.default.resolve(override);
8279
+ return import_node_path45.default.join(import_node_os3.default.homedir(), ".neat");
8047
8280
  }
8048
8281
  function registryPath() {
8049
- return import_node_path42.default.join(neatHome(), "projects.json");
8282
+ return import_node_path45.default.join(neatHome(), "projects.json");
8050
8283
  }
8051
8284
  function registryLockPath() {
8052
- return import_node_path42.default.join(neatHome(), "projects.json.lock");
8285
+ return import_node_path45.default.join(neatHome(), "projects.json.lock");
8053
8286
  }
8054
8287
  function daemonPidPath() {
8055
- return import_node_path42.default.join(neatHome(), "neatd.pid");
8288
+ return import_node_path45.default.join(neatHome(), "neatd.pid");
8056
8289
  }
8057
8290
  function isPidAliveDefault(pid) {
8058
8291
  try {
@@ -8064,7 +8297,7 @@ function isPidAliveDefault(pid) {
8064
8297
  }
8065
8298
  async function readPidFile(file) {
8066
8299
  try {
8067
- const raw = await import_node_fs25.promises.readFile(file, "utf8");
8300
+ const raw = await import_node_fs28.promises.readFile(file, "utf8");
8068
8301
  const pid = Number.parseInt(raw.trim(), 10);
8069
8302
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
8070
8303
  } catch {
@@ -8112,24 +8345,24 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
8112
8345
  }
8113
8346
  }
8114
8347
  async function writeAtomically(target, contents) {
8115
- await import_node_fs25.promises.mkdir(import_node_path42.default.dirname(target), { recursive: true });
8348
+ await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(target), { recursive: true });
8116
8349
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
8117
- const fd = await import_node_fs25.promises.open(tmp, "w");
8350
+ const fd = await import_node_fs28.promises.open(tmp, "w");
8118
8351
  try {
8119
8352
  await fd.writeFile(contents, "utf8");
8120
8353
  await fd.sync();
8121
8354
  } finally {
8122
8355
  await fd.close();
8123
8356
  }
8124
- await import_node_fs25.promises.rename(tmp, target);
8357
+ await import_node_fs28.promises.rename(tmp, target);
8125
8358
  }
8126
8359
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
8127
8360
  const deadline = Date.now() + timeoutMs;
8128
- await import_node_fs25.promises.mkdir(import_node_path42.default.dirname(lockPath), { recursive: true });
8361
+ await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(lockPath), { recursive: true });
8129
8362
  let probedHolder = false;
8130
8363
  while (true) {
8131
8364
  try {
8132
- const fd = await import_node_fs25.promises.open(lockPath, "wx");
8365
+ const fd = await import_node_fs28.promises.open(lockPath, "wx");
8133
8366
  try {
8134
8367
  await fd.writeFile(`${process.pid}
8135
8368
  `, "utf8");
@@ -8154,7 +8387,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
8154
8387
  }
8155
8388
  }
8156
8389
  async function releaseLock(lockPath) {
8157
- await import_node_fs25.promises.unlink(lockPath).catch(() => {
8390
+ await import_node_fs28.promises.unlink(lockPath).catch(() => {
8158
8391
  });
8159
8392
  }
8160
8393
  async function withLock(fn) {
@@ -8170,7 +8403,7 @@ async function readRegistry() {
8170
8403
  const file = registryPath();
8171
8404
  let raw;
8172
8405
  try {
8173
- raw = await import_node_fs25.promises.readFile(file, "utf8");
8406
+ raw = await import_node_fs28.promises.readFile(file, "utf8");
8174
8407
  } catch (err) {
8175
8408
  if (err.code === "ENOENT") {
8176
8409
  return { version: 1, projects: [] };
@@ -8178,10 +8411,10 @@ async function readRegistry() {
8178
8411
  throw err;
8179
8412
  }
8180
8413
  const parsed = JSON.parse(raw);
8181
- return import_types29.RegistryFileSchema.parse(parsed);
8414
+ return import_types32.RegistryFileSchema.parse(parsed);
8182
8415
  }
8183
8416
  async function writeRegistry(reg) {
8184
- const validated = import_types29.RegistryFileSchema.parse(reg);
8417
+ const validated = import_types32.RegistryFileSchema.parse(reg);
8185
8418
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
8186
8419
  }
8187
8420
  async function getProject(name) {
@@ -8225,7 +8458,7 @@ function pruneTtlMs() {
8225
8458
  }
8226
8459
  async function statPathStatus(p) {
8227
8460
  try {
8228
- const stat = await import_node_fs25.promises.stat(p);
8461
+ const stat = await import_node_fs28.promises.stat(p);
8229
8462
  return stat.isDirectory() ? "present" : "unknown";
8230
8463
  } catch (err) {
8231
8464
  return err.code === "ENOENT" ? "gone" : "unknown";
@@ -8322,65 +8555,274 @@ data: ${JSON.stringify(envelope.payload)}
8322
8555
 
8323
8556
  // src/api.ts
8324
8557
  init_auth();
8325
- function serializeGraph(graph) {
8326
- const nodes = [];
8327
- graph.forEachNode((_id, attrs) => {
8328
- nodes.push(attrs);
8329
- });
8330
- const edges = [];
8331
- graph.forEachEdge((_id, attrs) => {
8332
- edges.push(attrs);
8333
- });
8334
- return { nodes, edges };
8335
- }
8336
- function projectFromReq(req2, singleProject) {
8337
- const params = req2.params;
8338
- const named = params.project;
8339
- if (singleProject) {
8340
- return named === void 0 || named === DEFAULT_PROJECT ? singleProject : named;
8558
+
8559
+ // src/connectors-config.ts
8560
+ init_cjs_shims();
8561
+ var import_node_os4 = __toESM(require("os"), 1);
8562
+ var import_node_path46 = __toESM(require("path"), 1);
8563
+ var import_node_fs29 = require("fs");
8564
+ var CONNECTORS_CONFIG_VERSION = 1;
8565
+ var EnvRefUnsetError = class extends Error {
8566
+ ref;
8567
+ varName;
8568
+ constructor(ref, varName) {
8569
+ super(`${ref} is unset`);
8570
+ this.name = "EnvRefUnsetError";
8571
+ this.ref = ref;
8572
+ this.varName = varName;
8341
8573
  }
8342
- return named ?? DEFAULT_PROJECT;
8574
+ };
8575
+ function neatHome2() {
8576
+ const override = process.env.NEAT_HOME;
8577
+ if (override && override.length > 0) return import_node_path46.default.resolve(override);
8578
+ return import_node_path46.default.join(import_node_os4.default.homedir(), ".neat");
8343
8579
  }
8344
- function resolveProject(registry, req2, reply, bootstrap, singleProject) {
8345
- const name = projectFromReq(req2, singleProject);
8346
- const ctx = registry.get(name);
8347
- if (!ctx) {
8348
- const phase = bootstrap?.status(name);
8349
- if (phase === "bootstrapping") {
8350
- void reply.code(503).send({ ready: false, project: name, status: "bootstrapping" });
8351
- return null;
8352
- }
8353
- if (phase === "broken") {
8354
- void reply.code(503).send({ ready: false, project: name, status: "broken" });
8355
- return null;
8580
+ function connectorsConfigPath(home = neatHome2()) {
8581
+ return import_node_path46.default.join(home, "connectors.json");
8582
+ }
8583
+ var MODE_MASK_LOOSER_THAN_0600 = 63;
8584
+ async function warnIfModeLooserThan0600(file) {
8585
+ if (process.platform === "win32") return;
8586
+ try {
8587
+ const stat = await import_node_fs29.promises.stat(file);
8588
+ if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
8589
+ const mode = (stat.mode & 511).toString(8).padStart(3, "0");
8590
+ console.warn(
8591
+ `[neat] ${file} is mode 0${mode}, looser than the 0600 this file's secrets call for \u2014 run \`chmod 600 ${file}\``
8592
+ );
8356
8593
  }
8357
- void reply.code(404).send({ error: "project not found", project: name });
8358
- return null;
8594
+ } catch {
8359
8595
  }
8360
- return ctx;
8361
8596
  }
8362
- function buildLegacyRegistry(opts) {
8363
- if (opts.projects) return opts.projects;
8364
- if (!opts.graph) {
8365
- throw new Error("buildApi: either `projects` or `graph` must be provided");
8597
+ async function readConnectorsConfig(home = neatHome2()) {
8598
+ const file = connectorsConfigPath(home);
8599
+ let raw;
8600
+ try {
8601
+ raw = await import_node_fs29.promises.readFile(file, "utf8");
8602
+ } catch (err) {
8603
+ if (err.code === "ENOENT") {
8604
+ return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
8605
+ }
8606
+ throw err;
8366
8607
  }
8367
- const registry = new Projects();
8368
- const paths = pathsForProject(DEFAULT_PROJECT, "");
8369
- registry.set(DEFAULT_PROJECT, {
8370
- graph: opts.graph,
8371
- scanPath: opts.scanPath,
8372
- paths: {
8373
- snapshotPath: paths.snapshotPath,
8374
- errorsPath: opts.errorsPath ?? paths.errorsPath,
8375
- staleEventsPath: opts.staleEventsPath ?? paths.staleEventsPath,
8376
- embeddingsCachePath: paths.embeddingsCachePath,
8377
- policyViolationsPath: paths.policyViolationsPath
8378
- },
8379
- searchIndex: opts.searchIndex
8380
- });
8381
- return registry;
8608
+ await warnIfModeLooserThan0600(file);
8609
+ let parsed;
8610
+ try {
8611
+ parsed = JSON.parse(raw);
8612
+ } catch (err) {
8613
+ throw new Error(`${file} is not valid JSON: ${err.message}`);
8614
+ }
8615
+ return validateConfig(parsed, file);
8382
8616
  }
8383
- function registerRoutes(scope, ctx) {
8617
+ function validateConfig(parsed, file) {
8618
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
8619
+ throw new Error(`${file} must be a JSON object with a "connectors" array`);
8620
+ }
8621
+ const obj = parsed;
8622
+ const version = obj.version === void 0 ? CONNECTORS_CONFIG_VERSION : obj.version;
8623
+ if (typeof version !== "number" || !Number.isInteger(version)) {
8624
+ throw new Error(`${file}: "version" must be an integer`);
8625
+ }
8626
+ const rawConnectors = obj.connectors;
8627
+ if (!Array.isArray(rawConnectors)) {
8628
+ throw new Error(`${file}: "connectors" must be an array`);
8629
+ }
8630
+ const connectors = rawConnectors.map((entry2, i) => validateEntry(entry2, i, file));
8631
+ return { version, connectors };
8632
+ }
8633
+ function validateEntry(entry2, index, file) {
8634
+ const where = `${file}: connectors[${index}]`;
8635
+ if (typeof entry2 !== "object" || entry2 === null || Array.isArray(entry2)) {
8636
+ throw new Error(`${where} must be an object`);
8637
+ }
8638
+ const e = entry2;
8639
+ const id = e.id;
8640
+ if (typeof id !== "string" || id.length === 0) {
8641
+ throw new Error(`${where}.id must be a non-empty string`);
8642
+ }
8643
+ const provider = e.provider;
8644
+ if (typeof provider !== "string" || provider.length === 0) {
8645
+ throw new Error(`${where}.provider must be a non-empty string`);
8646
+ }
8647
+ if (e.project !== void 0 && typeof e.project !== "string") {
8648
+ throw new Error(`${where}.project must be a string when present`);
8649
+ }
8650
+ const credential = validateCredentialRef(e.credential, `${where}.credential`);
8651
+ let options;
8652
+ if (e.options !== void 0) {
8653
+ if (typeof e.options !== "object" || e.options === null || Array.isArray(e.options)) {
8654
+ throw new Error(`${where}.options must be an object when present`);
8655
+ }
8656
+ options = e.options;
8657
+ }
8658
+ return {
8659
+ id,
8660
+ provider,
8661
+ ...typeof e.project === "string" ? { project: e.project } : {},
8662
+ credential,
8663
+ ...options ? { options } : {}
8664
+ };
8665
+ }
8666
+ function validateCredentialRef(raw, where) {
8667
+ if (typeof raw === "string") {
8668
+ if (raw.length === 0) throw new Error(`${where} must be a non-empty string`);
8669
+ return raw;
8670
+ }
8671
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
8672
+ const fields = raw;
8673
+ const keys = Object.keys(fields);
8674
+ if (keys.length === 0) throw new Error(`${where} object must have at least one field`);
8675
+ for (const key of keys) {
8676
+ const v = fields[key];
8677
+ if (typeof v !== "string" || v.length === 0) {
8678
+ throw new Error(`${where}.${key} must be a non-empty string`);
8679
+ }
8680
+ }
8681
+ return fields;
8682
+ }
8683
+ throw new Error(`${where} must be a string or an object of strings`);
8684
+ }
8685
+ function resolveCredential(ref, env = process.env) {
8686
+ if (typeof ref === "string") {
8687
+ return { kind: "single", value: resolveRef(ref, env) };
8688
+ }
8689
+ const fields = {};
8690
+ for (const [key, value] of Object.entries(ref)) {
8691
+ fields[key] = resolveRef(value, env);
8692
+ }
8693
+ return { kind: "fields", fields };
8694
+ }
8695
+ function resolveRef(value, env) {
8696
+ if (value.length > 1 && value.startsWith("$")) {
8697
+ const varName = value.slice(1);
8698
+ const resolved = env[varName];
8699
+ if (resolved === void 0 || resolved.length === 0) {
8700
+ throw new EnvRefUnsetError(value, varName);
8701
+ }
8702
+ return resolved;
8703
+ }
8704
+ return value;
8705
+ }
8706
+ function connectorMatchesProject(entry2, project) {
8707
+ return entry2.project === void 0 || entry2.project === project;
8708
+ }
8709
+ function isEnvRef(value) {
8710
+ return value.length > 1 && value.startsWith("$");
8711
+ }
8712
+ function redactCredentialRef(ref) {
8713
+ const one = (value) => isEnvRef(value) ? value : "****";
8714
+ if (typeof ref === "string") return one(ref);
8715
+ const out = {};
8716
+ for (const [key, value] of Object.entries(ref)) out[key] = one(value);
8717
+ return out;
8718
+ }
8719
+
8720
+ // src/connectors/status.ts
8721
+ init_cjs_shims();
8722
+ var CONNECTOR_STALE_THRESHOLD_MS = 5 * 6e4;
8723
+ var records = /* @__PURE__ */ new Map();
8724
+ var MAX_ERROR_LEN = 200;
8725
+ function sanitizePollError(err) {
8726
+ const msg = err instanceof Error ? err.message : String(err);
8727
+ const collapsed = msg.replace(/\s+/g, " ").trim();
8728
+ return collapsed.length > MAX_ERROR_LEN ? `${collapsed.slice(0, MAX_ERROR_LEN - 1)}\u2026` : collapsed;
8729
+ }
8730
+ function recordConnectorPoll(id, tick) {
8731
+ const prev = records.get(id);
8732
+ records.set(id, {
8733
+ lastPollAt: tick.at,
8734
+ lastOutcome: tick.outcome,
8735
+ lastError: tick.outcome === "error" ? tick.error ?? null : null,
8736
+ signalsLastPoll: tick.signalsLastPoll ?? 0,
8737
+ lastOkAt: tick.outcome === "ok" ? tick.at : prev?.lastOkAt ?? null
8738
+ });
8739
+ }
8740
+ function deriveState(rec, now, thresholdMs) {
8741
+ if (rec.lastOutcome === "error") return "error";
8742
+ const okAt = rec.lastOkAt ? Date.parse(rec.lastOkAt) : NaN;
8743
+ if (!Number.isNaN(okAt) && now - okAt > thresholdMs) return "stale";
8744
+ return "healthy";
8745
+ }
8746
+ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_THRESHOLD_MS) {
8747
+ const rec = records.get(id);
8748
+ if (!rec) {
8749
+ return {
8750
+ state: "idle",
8751
+ lastPollAt: null,
8752
+ lastOutcome: null,
8753
+ lastError: null,
8754
+ signalsLastPoll: 0
8755
+ };
8756
+ }
8757
+ return {
8758
+ state: deriveState(rec, now, thresholdMs),
8759
+ lastPollAt: rec.lastPollAt,
8760
+ lastOutcome: rec.lastOutcome,
8761
+ lastError: rec.lastError,
8762
+ signalsLastPoll: rec.signalsLastPoll
8763
+ };
8764
+ }
8765
+
8766
+ // src/api.ts
8767
+ function serializeGraph(graph) {
8768
+ const nodes = [];
8769
+ graph.forEachNode((_id, attrs) => {
8770
+ nodes.push(attrs);
8771
+ });
8772
+ const edges = [];
8773
+ graph.forEachEdge((_id, attrs) => {
8774
+ edges.push(attrs);
8775
+ });
8776
+ return { nodes, edges };
8777
+ }
8778
+ function projectFromReq(req2, singleProject) {
8779
+ const params = req2.params;
8780
+ const named = params.project;
8781
+ if (singleProject) {
8782
+ return named === void 0 || named === DEFAULT_PROJECT ? singleProject : named;
8783
+ }
8784
+ return named ?? DEFAULT_PROJECT;
8785
+ }
8786
+ function resolveProject(registry, req2, reply, bootstrap, singleProject) {
8787
+ const name = projectFromReq(req2, singleProject);
8788
+ const ctx = registry.get(name);
8789
+ if (!ctx) {
8790
+ const phase = bootstrap?.status(name);
8791
+ if (phase === "bootstrapping") {
8792
+ void reply.code(503).send({ ready: false, project: name, status: "bootstrapping" });
8793
+ return null;
8794
+ }
8795
+ if (phase === "broken") {
8796
+ void reply.code(503).send({ ready: false, project: name, status: "broken" });
8797
+ return null;
8798
+ }
8799
+ void reply.code(404).send({ error: "project not found", project: name });
8800
+ return null;
8801
+ }
8802
+ return ctx;
8803
+ }
8804
+ function buildLegacyRegistry(opts) {
8805
+ if (opts.projects) return opts.projects;
8806
+ if (!opts.graph) {
8807
+ throw new Error("buildApi: either `projects` or `graph` must be provided");
8808
+ }
8809
+ const registry = new Projects();
8810
+ const paths = pathsForProject(DEFAULT_PROJECT, "");
8811
+ registry.set(DEFAULT_PROJECT, {
8812
+ graph: opts.graph,
8813
+ scanPath: opts.scanPath,
8814
+ paths: {
8815
+ snapshotPath: paths.snapshotPath,
8816
+ errorsPath: opts.errorsPath ?? paths.errorsPath,
8817
+ staleEventsPath: opts.staleEventsPath ?? paths.staleEventsPath,
8818
+ embeddingsCachePath: paths.embeddingsCachePath,
8819
+ policyViolationsPath: paths.policyViolationsPath
8820
+ },
8821
+ searchIndex: opts.searchIndex
8822
+ });
8823
+ return registry;
8824
+ }
8825
+ function registerRoutes(scope, ctx) {
8384
8826
  const { registry, startedAt, errorsPathFor, staleEventsPathFor } = ctx;
8385
8827
  scope.get("/events", (req2, reply) => {
8386
8828
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
@@ -8472,11 +8914,11 @@ function registerRoutes(scope, ctx) {
8472
8914
  const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
8473
8915
  const parsed = [];
8474
8916
  for (const c of candidates) {
8475
- const r = import_types30.DivergenceTypeSchema.safeParse(c);
8917
+ const r = import_types33.DivergenceTypeSchema.safeParse(c);
8476
8918
  if (!r.success) {
8477
8919
  return reply.code(400).send({
8478
8920
  error: `unknown divergence type "${c}"`,
8479
- allowed: import_types30.DivergenceTypeSchema.options
8921
+ allowed: import_types33.DivergenceTypeSchema.options
8480
8922
  });
8481
8923
  }
8482
8924
  parsed.push(r.data);
@@ -8541,6 +8983,26 @@ function registerRoutes(scope, ctx) {
8541
8983
  const sliced = filtered.slice(0, safeLimit);
8542
8984
  return { count: sliced.length, total, logs: sliced };
8543
8985
  });
8986
+ scope.get("/connectors", async (req2, reply) => {
8987
+ const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
8988
+ if (!proj) return;
8989
+ let entries;
8990
+ try {
8991
+ entries = (await readConnectorsConfig(ctx.connectorsHome)).connectors;
8992
+ } catch (err) {
8993
+ return reply.code(500).send({
8994
+ error: "connectors.json failed to read",
8995
+ details: err.message
8996
+ });
8997
+ }
8998
+ const connectors = entries.filter((entry2) => connectorMatchesProject(entry2, proj.name)).map((entry2) => ({
8999
+ id: entry2.id,
9000
+ provider: entry2.provider,
9001
+ credentialRef: redactCredentialRef(entry2.credential),
9002
+ status: getConnectorStatus(entry2.id)
9003
+ }));
9004
+ return { connectors };
9005
+ });
8544
9006
  const incidentHistoryHandler = async (req2, reply) => {
8545
9007
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
8546
9008
  if (!proj) return;
@@ -8727,7 +9189,7 @@ function registerRoutes(scope, ctx) {
8727
9189
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
8728
9190
  let violations = await log.readAll();
8729
9191
  if (req2.query.severity) {
8730
- const sev = import_types30.PolicySeveritySchema.safeParse(req2.query.severity);
9192
+ const sev = import_types33.PolicySeveritySchema.safeParse(req2.query.severity);
8731
9193
  if (!sev.success) {
8732
9194
  return reply.code(400).send({
8733
9195
  error: "invalid severity",
@@ -8766,7 +9228,7 @@ function registerRoutes(scope, ctx) {
8766
9228
  scope.post("/policies/check", async (req2, reply) => {
8767
9229
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
8768
9230
  if (!proj) return;
8769
- const parsed = import_types30.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
9231
+ const parsed = import_types33.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
8770
9232
  if (!parsed.success) {
8771
9233
  return reply.code(400).send({
8772
9234
  error: "invalid /policies/check body",
@@ -8911,9 +9373,11 @@ async function buildApi(opts) {
8911
9373
  trustProxy,
8912
9374
  publicRead
8913
9375
  });
9376
+ const requiresAuth = authToken !== void 0 && authToken.length > 0 && trustProxy !== true;
8914
9377
  app.get("/api/config", async () => ({
8915
9378
  publicRead: publicRead === true,
8916
- authProxy: trustProxy === true
9379
+ authProxy: trustProxy === true,
9380
+ requiresAuth
8917
9381
  }));
8918
9382
  const startedAt = opts.startedAt ?? Date.now();
8919
9383
  const registry = buildLegacyRegistry(opts);
@@ -8943,7 +9407,8 @@ async function buildApi(opts) {
8943
9407
  staleEventsPathFor,
8944
9408
  policyFilePathFor,
8945
9409
  bootstrap: opts.bootstrap,
8946
- singleProject: opts.singleProject?.name
9410
+ singleProject: opts.singleProject?.name,
9411
+ connectorsHome: opts.connectorsHome
8947
9412
  };
8948
9413
  app.get("/health", async () => {
8949
9414
  const uptimeMs = Date.now() - startedAt;
@@ -9085,16 +9550,31 @@ function startConnectorPollLoop(connector, ctx, graph, resolveTarget, options =
9085
9550
  let stopped = false;
9086
9551
  let since = ctx.since;
9087
9552
  const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
9553
+ const connectorId = options.connectorId;
9088
9554
  const onError = options.onError ?? ((err) => console.error(`[neatd] connector poll failed (${connector.provider})`, err));
9089
9555
  const tick = () => {
9090
9556
  if (stopped) return;
9091
9557
  void (async () => {
9092
9558
  const tickStartedAt = (/* @__PURE__ */ new Date()).toISOString();
9093
9559
  try {
9094
- await runConnectorPoll(connector, { ...ctx, since }, graph, resolveTarget);
9560
+ const result = await runConnectorPoll(connector, { ...ctx, since }, graph, resolveTarget);
9095
9561
  since = tickStartedAt;
9562
+ if (connectorId) {
9563
+ recordConnectorPoll(connectorId, {
9564
+ outcome: "ok",
9565
+ at: tickStartedAt,
9566
+ signalsLastPoll: result.signalCount
9567
+ });
9568
+ }
9096
9569
  } catch (err) {
9097
9570
  onError(err);
9571
+ if (connectorId) {
9572
+ recordConnectorPoll(connectorId, {
9573
+ outcome: "error",
9574
+ at: tickStartedAt,
9575
+ error: sanitizePollError(err)
9576
+ });
9577
+ }
9098
9578
  }
9099
9579
  })();
9100
9580
  };
@@ -9393,6 +9873,30 @@ function buildEdgeLogsQuery(limit) {
9393
9873
  `limit ${safeLimit}`
9394
9874
  ].join("\n");
9395
9875
  }
9876
+ function logsAllHttpFailureMessage(status2) {
9877
+ if (status2 === 401 || status2 === 403) {
9878
+ return `supabase connector: logs.all request rejected (HTTP ${status2}). Check the Management API token, its analytics read scope, and --api-project-ref.`;
9879
+ }
9880
+ if (status2 === 404) {
9881
+ return "supabase connector: logs.all project not found (HTTP 404). Check --api-project-ref.";
9882
+ }
9883
+ if (status2 === 429) {
9884
+ return "supabase connector: logs.all rate-limited (HTTP 429). The connector will retry on the next poll; reduce poll cadence if this persists.";
9885
+ }
9886
+ if (status2 === 400) {
9887
+ return "supabase connector: logs.all query rejected (HTTP 400). Provider details redacted; confirm the live log-query dialect before shipping this connector.";
9888
+ }
9889
+ return `supabase connector: logs.all request failed (HTTP ${status2}); provider details redacted.`;
9890
+ }
9891
+ function providerErrorDetails(error) {
9892
+ if (!error || typeof error === "string") return "";
9893
+ const parts = [];
9894
+ if (typeof error.code === "number") parts.push(`code ${error.code}`);
9895
+ if (typeof error.status === "string" && /^[A-Z0-9_.-]+$/.test(error.status)) {
9896
+ parts.push(`status ${error.status}`);
9897
+ }
9898
+ return parts.length > 0 ? ` (${parts.join(", ")})` : "";
9899
+ }
9396
9900
  async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl = fetch) {
9397
9901
  const baseUrl = config.managementApiUrl ?? DEFAULT_SUPABASE_MANAGEMENT_API_URL;
9398
9902
  const url = new URL(`${baseUrl}/v1/projects/${config.apiProjectRef}/analytics/endpoints/logs.all`);
@@ -9407,12 +9911,18 @@ async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl
9407
9911
  { provider: "supabase", accountKey: config.apiProjectRef, fetchImpl }
9408
9912
  );
9409
9913
  if (!res.ok) {
9410
- throw new Error(`supabase connector: logs.all request failed (${res.status} ${res.statusText})`);
9914
+ throw new Error(logsAllHttpFailureMessage(res.status));
9915
+ }
9916
+ let body;
9917
+ try {
9918
+ body = await res.json();
9919
+ } catch {
9920
+ throw new Error("supabase connector: logs.all returned invalid JSON; provider details redacted.");
9411
9921
  }
9412
- const body = await res.json();
9413
9922
  if (body.error) {
9414
- const message = typeof body.error === "string" ? body.error : body.error.message;
9415
- throw new Error(`supabase connector: logs.all returned an error (${message})`);
9923
+ throw new Error(
9924
+ `supabase connector: logs.all returned a provider error${providerErrorDetails(body.error)}; provider message redacted.`
9925
+ );
9416
9926
  }
9417
9927
  return body.result ?? [];
9418
9928
  }
@@ -9444,10 +9954,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
9444
9954
  // src/connectors/supabase/map.ts
9445
9955
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
9446
9956
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
9447
- function targetFromRestPath(path50) {
9448
- const rpcMatch = REST_RPC_PATH_RE.exec(path50);
9957
+ function targetFromRestPath(path53) {
9958
+ const rpcMatch = REST_RPC_PATH_RE.exec(path53);
9449
9959
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
9450
- const tableMatch = REST_TABLE_PATH_RE.exec(path50);
9960
+ const tableMatch = REST_TABLE_PATH_RE.exec(path53);
9451
9961
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
9452
9962
  return null;
9453
9963
  }
@@ -9550,19 +10060,19 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
9550
10060
 
9551
10061
  // src/connectors/supabase/resolve.ts
9552
10062
  init_cjs_shims();
9553
- var import_types32 = require("@neat.is/types");
10063
+ var import_types35 = require("@neat.is/types");
9554
10064
  function createSupabaseResolveTarget(graph, config) {
9555
10065
  return (signal, _ctx) => {
9556
10066
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
9557
10067
  return null;
9558
10068
  }
9559
- const subResourceId = (0, import_types32.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10069
+ const subResourceId = (0, import_types35.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
9560
10070
  if (graph.hasNode(subResourceId)) {
9561
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types32.EdgeType.CALLS };
10071
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types35.EdgeType.CALLS };
9562
10072
  }
9563
- const projectLevelId = (0, import_types32.infraId)("supabase", config.nodeRef);
10073
+ const projectLevelId = (0, import_types35.infraId)("supabase", config.nodeRef);
9564
10074
  if (graph.hasNode(projectLevelId)) {
9565
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types32.EdgeType.CALLS };
10075
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types35.EdgeType.CALLS };
9566
10076
  }
9567
10077
  return null;
9568
10078
  };
@@ -9570,6 +10080,35 @@ function createSupabaseResolveTarget(graph, config) {
9570
10080
 
9571
10081
  // src/connectors/supabase/index.ts
9572
10082
  var DEFAULT_MAX_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
10083
+ function errorCode(err) {
10084
+ const code = err?.code;
10085
+ return typeof code === "string" && code.length > 0 ? code : void 0;
10086
+ }
10087
+ function describeSupabasePostgresSurfaceFailure(projectRef, err) {
10088
+ const code = errorCode(err);
10089
+ let reason;
10090
+ switch (code) {
10091
+ case "42501":
10092
+ reason = "permission denied; grant pg_read_all_stats to the configured Postgres role";
10093
+ break;
10094
+ case "42P01":
10095
+ case "42704":
10096
+ reason = "pg_stat_statements is not enabled or visible to the configured Postgres role";
10097
+ break;
10098
+ case "28P01":
10099
+ case "28000":
10100
+ reason = "Postgres credential rejected";
10101
+ break;
10102
+ case "3D000":
10103
+ reason = "database not found";
10104
+ break;
10105
+ default: {
10106
+ const name = err instanceof Error && err.name ? err.name : "Error";
10107
+ reason = code ? `${name} ${code}` : name;
10108
+ }
10109
+ }
10110
+ return `supabase connector: pg_stat_statements surface unavailable for project ${projectRef} (${reason}); continuing with Management API log surface.`;
10111
+ }
9573
10112
  var SupabaseConnector = class {
9574
10113
  // `deps.fetchPgStatStatements` defaults to the real Postgres-backed
9575
10114
  // implementation; tests override it to exercise the "both surfaces
@@ -9601,12 +10140,18 @@ var SupabaseConnector = class {
9601
10140
  const signals = mapEdgeLogRowsToSignals(logRows);
9602
10141
  if (creds.postgresConnectionString) {
9603
10142
  const fetchStatements = this.deps.fetchPgStatStatements ?? fetchPgStatStatements;
9604
- const statementRows = await fetchStatements(
9605
- creds.postgresConnectionString,
9606
- this.config.statementLimit ?? DEFAULT_STATEMENT_LIMIT,
9607
- this.config.apiProjectRef
9608
- );
9609
- signals.push(...diffPgStatStatementsToSignals(statementRows, this.statementBaselines, now.toISOString()));
10143
+ try {
10144
+ const statementRows = await fetchStatements(
10145
+ creds.postgresConnectionString,
10146
+ this.config.statementLimit ?? DEFAULT_STATEMENT_LIMIT,
10147
+ this.config.apiProjectRef
10148
+ );
10149
+ signals.push(...diffPgStatStatementsToSignals(statementRows, this.statementBaselines, now.toISOString()));
10150
+ } catch (err) {
10151
+ const summary = describeSupabasePostgresSurfaceFailure(this.config.apiProjectRef, err);
10152
+ if (this.deps.onPostgresSurfaceError) this.deps.onPostgresSurfaceError(err, summary);
10153
+ else console.warn(summary);
10154
+ }
9610
10155
  }
9611
10156
  return signals;
9612
10157
  }
@@ -9620,7 +10165,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
9620
10165
 
9621
10166
  // src/connectors/railway/index.ts
9622
10167
  init_cjs_shims();
9623
- var import_types36 = require("@neat.is/types");
10168
+ var import_types39 = require("@neat.is/types");
9624
10169
 
9625
10170
  // src/connectors/railway/client.ts
9626
10171
  init_cjs_shims();
@@ -9765,7 +10310,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
9765
10310
  const out = [];
9766
10311
  graph.forEachNode((_id, attrs) => {
9767
10312
  const node = attrs;
9768
- if (node.type !== import_types36.NodeType.RouteNode) return;
10313
+ if (node.type !== import_types39.NodeType.RouteNode) return;
9769
10314
  const route = attrs;
9770
10315
  if (route.service !== serviceName) return;
9771
10316
  out.push({
@@ -9861,12 +10406,12 @@ function createRailwayResolveTarget(config) {
9861
10406
  const serviceName = config.serviceNameById[config.serviceId];
9862
10407
  if (!serviceName) return null;
9863
10408
  if (signal.targetKind === ROUTE_TARGET_KIND) {
9864
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types36.EdgeType.CALLS };
10409
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types39.EdgeType.CALLS };
9865
10410
  }
9866
10411
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
9867
10412
  const peerName = config.serviceNameById[signal.targetName];
9868
10413
  if (!peerName) return null;
9869
- return { targetNodeId: (0, import_types36.serviceId)(peerName), serviceName, edgeType: import_types36.EdgeType.CONNECTS_TO };
10414
+ return { targetNodeId: (0, import_types39.serviceId)(peerName), serviceName, edgeType: import_types39.EdgeType.CONNECTS_TO };
9870
10415
  }
9871
10416
  return null;
9872
10417
  };
@@ -9981,9 +10526,9 @@ function parseFirebaseTargetName(targetName) {
9981
10526
  const secondSep = rest.indexOf(FIELD_SEP);
9982
10527
  if (secondSep === -1) return null;
9983
10528
  const method = rest.slice(0, secondSep);
9984
- const path50 = rest.slice(secondSep + 1);
9985
- if (!resourceName || !method || !path50) return null;
9986
- return { resourceName, method, path: path50 };
10529
+ const path53 = rest.slice(secondSep + 1);
10530
+ if (!resourceName || !method || !path53) return null;
10531
+ return { resourceName, method, path: path53 };
9987
10532
  }
9988
10533
  function resourceNameFor(type, labels) {
9989
10534
  if (!labels) return null;
@@ -10020,14 +10565,14 @@ function mapLogEntryToSignal(entry2) {
10020
10565
  if (!req2) return null;
10021
10566
  if (!req2.requestMethod) return null;
10022
10567
  const method = req2.requestMethod.toUpperCase();
10023
- const path50 = pathFromRequestUrl(req2.requestUrl);
10024
- if (path50 === null) return null;
10568
+ const path53 = pathFromRequestUrl(req2.requestUrl);
10569
+ if (path53 === null) return null;
10025
10570
  const timestamp = entry2.timestamp;
10026
10571
  if (!timestamp) return null;
10027
10572
  const isError = typeof req2.status === "number" && req2.status >= ERROR_STATUS_THRESHOLD2;
10028
10573
  return {
10029
10574
  targetKind: resourceType,
10030
- targetName: packFirebaseTargetName({ resourceName, method, path: path50 }),
10575
+ targetName: packFirebaseTargetName({ resourceName, method, path: path53 }),
10031
10576
  callCount: 1,
10032
10577
  errorCount: isError ? 1 : 0,
10033
10578
  lastObservedIso: timestamp
@@ -10044,7 +10589,7 @@ function mapLogEntriesToSignals(entries) {
10044
10589
 
10045
10590
  // src/connectors/firebase/resolve.ts
10046
10591
  init_cjs_shims();
10047
- var import_types37 = require("@neat.is/types");
10592
+ var import_types40 = require("@neat.is/types");
10048
10593
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
10049
10594
  switch (resourceType) {
10050
10595
  case "cloud_function":
@@ -10059,7 +10604,7 @@ function routeEntriesFor(graph, serviceName) {
10059
10604
  const entries = [];
10060
10605
  graph.forEachNode((_id, attrs) => {
10061
10606
  const node = attrs;
10062
- if (node.type !== import_types37.NodeType.RouteNode) return;
10607
+ if (node.type !== import_types40.NodeType.RouteNode) return;
10063
10608
  const route = attrs;
10064
10609
  if (route.service !== serviceName) return;
10065
10610
  entries.push({
@@ -10091,7 +10636,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
10091
10636
  return {
10092
10637
  targetNodeId: match.routeNodeId,
10093
10638
  serviceName,
10094
- edgeType: import_types37.EdgeType.CALLS
10639
+ edgeType: import_types40.EdgeType.CALLS
10095
10640
  };
10096
10641
  };
10097
10642
  }
@@ -10118,7 +10663,7 @@ init_cjs_shims();
10118
10663
 
10119
10664
  // src/connectors/cloudflare/connector.ts
10120
10665
  init_cjs_shims();
10121
- var import_types39 = require("@neat.is/types");
10666
+ var import_types42 = require("@neat.is/types");
10122
10667
 
10123
10668
  // src/connectors/cloudflare/client.ts
10124
10669
  init_cjs_shims();
@@ -10225,7 +10770,7 @@ function mapEventToSignal(event) {
10225
10770
  if (typeof timestampMs !== "number" || !Number.isFinite(timestampMs)) return null;
10226
10771
  const statusCode = metadata?.statusCode;
10227
10772
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
10228
- const path50 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
10773
+ const path53 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
10229
10774
  return {
10230
10775
  targetKind: CLOUDFLARE_TARGET_KIND,
10231
10776
  targetName: scriptName,
@@ -10233,7 +10778,7 @@ function mapEventToSignal(event) {
10233
10778
  errorCount: isError ? 1 : 0,
10234
10779
  lastObservedIso: new Date(timestampMs).toISOString(),
10235
10780
  method,
10236
- ...path50 ? { path: path50 } : {},
10781
+ ...path53 ? { path: path53 } : {},
10237
10782
  ...typeof statusCode === "number" ? { statusCode } : {},
10238
10783
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
10239
10784
  };
@@ -10273,19 +10818,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
10273
10818
  graph.forEachNode((id, attrs) => {
10274
10819
  if (found) return;
10275
10820
  const a = attrs;
10276
- if (a.type === import_types39.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
10821
+ if (a.type === import_types42.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
10277
10822
  found = id;
10278
10823
  }
10279
10824
  });
10280
10825
  return found;
10281
10826
  }
10282
- function findMatchingRouteNode(graph, serviceName, method, path50) {
10283
- const normalizedPath = normalizePathTemplate(path50);
10827
+ function findMatchingRouteNode(graph, serviceName, method, path53) {
10828
+ const normalizedPath = normalizePathTemplate(path53);
10284
10829
  let found = null;
10285
10830
  graph.forEachNode((id, attrs) => {
10286
10831
  if (found) return;
10287
10832
  const a = attrs;
10288
- if (a.type !== import_types39.NodeType.RouteNode || a.service !== serviceName) return;
10833
+ if (a.type !== import_types42.NodeType.RouteNode || a.service !== serviceName) return;
10289
10834
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
10290
10835
  const routeMethod = (a.method ?? "").toUpperCase();
10291
10836
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -10297,18 +10842,18 @@ function createCloudflareResolveTarget(config, graph) {
10297
10842
  return (signal) => {
10298
10843
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
10299
10844
  const scriptName = signal.targetName;
10300
- const { method, path: path50 } = signal;
10845
+ const { method, path: path53 } = signal;
10301
10846
  const resolveRouteGrain = (serviceName, wholeFileId) => {
10302
- if (!method || !path50) return wholeFileId;
10303
- return findMatchingRouteNode(graph, serviceName, method, path50) ?? wholeFileId;
10847
+ if (!method || !path53) return wholeFileId;
10848
+ return findMatchingRouteNode(graph, serviceName, method, path53) ?? wholeFileId;
10304
10849
  };
10305
10850
  const mapping = config.workers?.[scriptName];
10306
10851
  if (mapping) {
10307
- const wholeFileId = (0, import_types39.fileId)(mapping.service, mapping.entryFile);
10852
+ const wholeFileId = (0, import_types42.fileId)(mapping.service, mapping.entryFile);
10308
10853
  return {
10309
10854
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
10310
10855
  serviceName: mapping.service,
10311
- edgeType: import_types39.EdgeType.CALLS
10856
+ edgeType: import_types42.EdgeType.CALLS
10312
10857
  };
10313
10858
  }
10314
10859
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -10317,169 +10862,18 @@ function createCloudflareResolveTarget(config, graph) {
10317
10862
  return {
10318
10863
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
10319
10864
  serviceName: fileNode.service,
10320
- edgeType: import_types39.EdgeType.CALLS
10865
+ edgeType: import_types42.EdgeType.CALLS
10321
10866
  };
10322
10867
  }
10323
10868
  return {
10324
- targetNodeId: (0, import_types39.infraId)("cloudflare-worker", scriptName),
10869
+ targetNodeId: (0, import_types42.infraId)("cloudflare-worker", scriptName),
10325
10870
  serviceName: scriptName,
10326
- edgeType: import_types39.EdgeType.CALLS,
10871
+ edgeType: import_types42.EdgeType.CALLS,
10327
10872
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
10328
10873
  };
10329
10874
  };
10330
10875
  }
10331
10876
 
10332
- // src/connectors-config.ts
10333
- init_cjs_shims();
10334
- var import_node_os4 = __toESM(require("os"), 1);
10335
- var import_node_path45 = __toESM(require("path"), 1);
10336
- var import_node_fs26 = require("fs");
10337
- var CONNECTORS_CONFIG_VERSION = 1;
10338
- var EnvRefUnsetError = class extends Error {
10339
- ref;
10340
- varName;
10341
- constructor(ref, varName) {
10342
- super(`${ref} is unset`);
10343
- this.name = "EnvRefUnsetError";
10344
- this.ref = ref;
10345
- this.varName = varName;
10346
- }
10347
- };
10348
- function neatHome2() {
10349
- const override = process.env.NEAT_HOME;
10350
- if (override && override.length > 0) return import_node_path45.default.resolve(override);
10351
- return import_node_path45.default.join(import_node_os4.default.homedir(), ".neat");
10352
- }
10353
- function connectorsConfigPath(home = neatHome2()) {
10354
- return import_node_path45.default.join(home, "connectors.json");
10355
- }
10356
- var MODE_MASK_LOOSER_THAN_0600 = 63;
10357
- async function warnIfModeLooserThan0600(file) {
10358
- if (process.platform === "win32") return;
10359
- try {
10360
- const stat = await import_node_fs26.promises.stat(file);
10361
- if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
10362
- const mode = (stat.mode & 511).toString(8).padStart(3, "0");
10363
- console.warn(
10364
- `[neat] ${file} is mode 0${mode}, looser than the 0600 this file's secrets call for \u2014 run \`chmod 600 ${file}\``
10365
- );
10366
- }
10367
- } catch {
10368
- }
10369
- }
10370
- async function readConnectorsConfig(home = neatHome2()) {
10371
- const file = connectorsConfigPath(home);
10372
- let raw;
10373
- try {
10374
- raw = await import_node_fs26.promises.readFile(file, "utf8");
10375
- } catch (err) {
10376
- if (err.code === "ENOENT") {
10377
- return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
10378
- }
10379
- throw err;
10380
- }
10381
- await warnIfModeLooserThan0600(file);
10382
- let parsed;
10383
- try {
10384
- parsed = JSON.parse(raw);
10385
- } catch (err) {
10386
- throw new Error(`${file} is not valid JSON: ${err.message}`);
10387
- }
10388
- return validateConfig(parsed, file);
10389
- }
10390
- function validateConfig(parsed, file) {
10391
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
10392
- throw new Error(`${file} must be a JSON object with a "connectors" array`);
10393
- }
10394
- const obj = parsed;
10395
- const version = obj.version === void 0 ? CONNECTORS_CONFIG_VERSION : obj.version;
10396
- if (typeof version !== "number" || !Number.isInteger(version)) {
10397
- throw new Error(`${file}: "version" must be an integer`);
10398
- }
10399
- const rawConnectors = obj.connectors;
10400
- if (!Array.isArray(rawConnectors)) {
10401
- throw new Error(`${file}: "connectors" must be an array`);
10402
- }
10403
- const connectors = rawConnectors.map((entry2, i) => validateEntry(entry2, i, file));
10404
- return { version, connectors };
10405
- }
10406
- function validateEntry(entry2, index, file) {
10407
- const where = `${file}: connectors[${index}]`;
10408
- if (typeof entry2 !== "object" || entry2 === null || Array.isArray(entry2)) {
10409
- throw new Error(`${where} must be an object`);
10410
- }
10411
- const e = entry2;
10412
- const id = e.id;
10413
- if (typeof id !== "string" || id.length === 0) {
10414
- throw new Error(`${where}.id must be a non-empty string`);
10415
- }
10416
- const provider = e.provider;
10417
- if (typeof provider !== "string" || provider.length === 0) {
10418
- throw new Error(`${where}.provider must be a non-empty string`);
10419
- }
10420
- if (e.project !== void 0 && typeof e.project !== "string") {
10421
- throw new Error(`${where}.project must be a string when present`);
10422
- }
10423
- const credential = validateCredentialRef(e.credential, `${where}.credential`);
10424
- let options;
10425
- if (e.options !== void 0) {
10426
- if (typeof e.options !== "object" || e.options === null || Array.isArray(e.options)) {
10427
- throw new Error(`${where}.options must be an object when present`);
10428
- }
10429
- options = e.options;
10430
- }
10431
- return {
10432
- id,
10433
- provider,
10434
- ...typeof e.project === "string" ? { project: e.project } : {},
10435
- credential,
10436
- ...options ? { options } : {}
10437
- };
10438
- }
10439
- function validateCredentialRef(raw, where) {
10440
- if (typeof raw === "string") {
10441
- if (raw.length === 0) throw new Error(`${where} must be a non-empty string`);
10442
- return raw;
10443
- }
10444
- if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
10445
- const fields = raw;
10446
- const keys = Object.keys(fields);
10447
- if (keys.length === 0) throw new Error(`${where} object must have at least one field`);
10448
- for (const key of keys) {
10449
- const v = fields[key];
10450
- if (typeof v !== "string" || v.length === 0) {
10451
- throw new Error(`${where}.${key} must be a non-empty string`);
10452
- }
10453
- }
10454
- return fields;
10455
- }
10456
- throw new Error(`${where} must be a string or an object of strings`);
10457
- }
10458
- function resolveCredential(ref, env = process.env) {
10459
- if (typeof ref === "string") {
10460
- return { kind: "single", value: resolveRef(ref, env) };
10461
- }
10462
- const fields = {};
10463
- for (const [key, value] of Object.entries(ref)) {
10464
- fields[key] = resolveRef(value, env);
10465
- }
10466
- return { kind: "fields", fields };
10467
- }
10468
- function resolveRef(value, env) {
10469
- if (value.length > 1 && value.startsWith("$")) {
10470
- const varName = value.slice(1);
10471
- const resolved = env[varName];
10472
- if (resolved === void 0 || resolved.length === 0) {
10473
- throw new EnvRefUnsetError(value, varName);
10474
- }
10475
- return resolved;
10476
- }
10477
- return value;
10478
- }
10479
- function connectorMatchesProject(entry2, project) {
10480
- return entry2.project === void 0 || entry2.project === project;
10481
- }
10482
-
10483
10877
  // src/connectors/registry.ts
10484
10878
  var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
10485
10879
  async function authProbe(input) {
@@ -10665,6 +11059,9 @@ function buildRegistration(entry2, graph, env = process.env) {
10665
11059
  return {
10666
11060
  ok: true,
10667
11061
  registration: {
11062
+ // Carry the entry id so the daemon can key this connector's poll-status
11063
+ // records to it (ADR-136).
11064
+ id: entry2.id,
10668
11065
  connector: built.connector,
10669
11066
  credentials,
10670
11067
  resolveTarget: built.resolveTarget,
@@ -10699,8 +11096,8 @@ init_auth();
10699
11096
 
10700
11097
  // src/unrouted.ts
10701
11098
  init_cjs_shims();
10702
- var import_node_fs27 = require("fs");
10703
- var import_node_path46 = __toESM(require("path"), 1);
11099
+ var import_node_fs30 = require("fs");
11100
+ var import_node_path49 = __toESM(require("path"), 1);
10704
11101
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
10705
11102
  return {
10706
11103
  timestamp: now.toISOString(),
@@ -10710,34 +11107,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
10710
11107
  };
10711
11108
  }
10712
11109
  async function appendUnroutedSpan(neatHome4, record) {
10713
- const target = import_node_path46.default.join(neatHome4, "errors.ndjson");
10714
- await import_node_fs27.promises.mkdir(neatHome4, { recursive: true });
10715
- await import_node_fs27.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
11110
+ const target = import_node_path49.default.join(neatHome4, "errors.ndjson");
11111
+ await import_node_fs30.promises.mkdir(neatHome4, { recursive: true });
11112
+ await import_node_fs30.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
10716
11113
  }
10717
11114
  function unroutedErrorsPath(neatHome4) {
10718
- return import_node_path46.default.join(neatHome4, "errors.ndjson");
11115
+ return import_node_path49.default.join(neatHome4, "errors.ndjson");
10719
11116
  }
10720
11117
 
10721
11118
  // src/daemon.ts
10722
- var import_types42 = require("@neat.is/types");
11119
+ var import_types45 = require("@neat.is/types");
10723
11120
  function daemonJsonPath(scanPath) {
10724
- return import_node_path47.default.join(scanPath, "neat-out", "daemon.json");
11121
+ return import_node_path50.default.join(scanPath, "neat-out", "daemon.json");
10725
11122
  }
10726
11123
  function daemonsDiscoveryDir(home) {
10727
11124
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
10728
- return import_node_path47.default.join(base, "daemons");
11125
+ return import_node_path50.default.join(base, "daemons");
10729
11126
  }
10730
11127
  function daemonDiscoveryPath(project, home) {
10731
- return import_node_path47.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
11128
+ return import_node_path50.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
10732
11129
  }
10733
11130
  function sanitizeDiscoveryName(project) {
10734
11131
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
10735
11132
  }
10736
11133
  function neatHomeFromEnv() {
10737
11134
  const env = process.env.NEAT_HOME;
10738
- if (env && env.length > 0) return import_node_path47.default.resolve(env);
11135
+ if (env && env.length > 0) return import_node_path50.default.resolve(env);
10739
11136
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
10740
- return import_node_path47.default.join(home, ".neat");
11137
+ return import_node_path50.default.join(home, ".neat");
10741
11138
  }
10742
11139
  function resolveNeatVersion() {
10743
11140
  if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
@@ -10769,7 +11166,7 @@ async function clearDaemonRecord(record, home) {
10769
11166
  } catch {
10770
11167
  }
10771
11168
  try {
10772
- await import_node_fs28.promises.unlink(daemonDiscoveryPath(record.project, home));
11169
+ await import_node_fs31.promises.unlink(daemonDiscoveryPath(record.project, home));
10773
11170
  } catch {
10774
11171
  }
10775
11172
  }
@@ -10778,12 +11175,12 @@ function reconcileDaemonRecordSync(record, home) {
10778
11175
  const stopped = { ...record, status: "stopped" };
10779
11176
  const target = daemonJsonPath(record.projectPath);
10780
11177
  const tmp = `${target}.${process.pid}.tmp`;
10781
- (0, import_node_fs28.writeFileSync)(tmp, JSON.stringify(stopped, null, 2) + "\n");
10782
- (0, import_node_fs28.renameSync)(tmp, target);
11178
+ (0, import_node_fs31.writeFileSync)(tmp, JSON.stringify(stopped, null, 2) + "\n");
11179
+ (0, import_node_fs31.renameSync)(tmp, target);
10783
11180
  } catch {
10784
11181
  }
10785
11182
  try {
10786
- (0, import_node_fs28.unlinkSync)(daemonDiscoveryPath(record.project, home));
11183
+ (0, import_node_fs31.unlinkSync)(daemonDiscoveryPath(record.project, home));
10787
11184
  } catch {
10788
11185
  }
10789
11186
  }
@@ -10806,11 +11203,11 @@ function teardownSlot(slot) {
10806
11203
  }
10807
11204
  }
10808
11205
  function neatHomeFor(opts) {
10809
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path47.default.resolve(opts.neatHome);
11206
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path50.default.resolve(opts.neatHome);
10810
11207
  const env = process.env.NEAT_HOME;
10811
- if (env && env.length > 0) return import_node_path47.default.resolve(env);
11208
+ if (env && env.length > 0) return import_node_path50.default.resolve(env);
10812
11209
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
10813
- return import_node_path47.default.join(home, ".neat");
11210
+ return import_node_path50.default.join(home, ".neat");
10814
11211
  }
10815
11212
  function routeSpanToProject(serviceName, projects) {
10816
11213
  if (!serviceName) return DEFAULT_PROJECT;
@@ -10854,13 +11251,13 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
10854
11251
  if (!serviceName) return true;
10855
11252
  if (serviceNameMatchesProject(serviceName, project)) return true;
10856
11253
  return graph.someNode(
10857
- (_id, attrs) => attrs.type === import_types42.NodeType.ServiceNode && attrs.name === serviceName
11254
+ (_id, attrs) => attrs.type === import_types45.NodeType.ServiceNode && attrs.name === serviceName
10858
11255
  );
10859
11256
  }
10860
11257
  async function bootstrapProject(entry2, connectors = [], neatHome4) {
10861
- const paths = pathsForProject(entry2.name, import_node_path47.default.join(entry2.path, "neat-out"));
11258
+ const paths = pathsForProject(entry2.name, import_node_path50.default.join(entry2.path, "neat-out"));
10862
11259
  try {
10863
- const stat = await import_node_fs28.promises.stat(entry2.path);
11260
+ const stat = await import_node_fs31.promises.stat(entry2.path);
10864
11261
  if (!stat.isDirectory()) {
10865
11262
  throw new Error(`registered path ${entry2.path} is not a directory`);
10866
11263
  }
@@ -10913,7 +11310,11 @@ async function bootstrapProject(entry2, connectors = [], neatHome4) {
10913
11310
  { projectDir: entry2.path, credentials: registration.credentials },
10914
11311
  graph,
10915
11312
  registration.resolveTarget,
10916
- { intervalMs: registration.intervalMs }
11313
+ // `connectorId` is threaded through so every tick lands in the
11314
+ // in-process status tracker the connector-status endpoint reads
11315
+ // (ADR-136). Undefined for a programmatic registration, which records
11316
+ // nothing.
11317
+ { intervalMs: registration.intervalMs, connectorId: registration.id }
10917
11318
  )
10918
11319
  );
10919
11320
  const stopConnectors = () => {
@@ -10985,7 +11386,7 @@ async function startDaemon(opts = {}) {
10985
11386
  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;
10986
11387
  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;
10987
11388
  const singleProject = projectArg;
10988
- const singleProjectPath = singleProject && projectPathArg ? import_node_path47.default.resolve(projectPathArg) : null;
11389
+ const singleProjectPath = singleProject && projectPathArg ? import_node_path50.default.resolve(projectPathArg) : null;
10989
11390
  if (singleProject && !singleProjectPath) {
10990
11391
  throw new Error(
10991
11392
  `neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
@@ -10993,14 +11394,14 @@ async function startDaemon(opts = {}) {
10993
11394
  }
10994
11395
  if (!singleProject) {
10995
11396
  try {
10996
- await import_node_fs28.promises.access(regPath);
11397
+ await import_node_fs31.promises.access(regPath);
10997
11398
  } catch {
10998
11399
  throw new Error(
10999
11400
  `neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
11000
11401
  );
11001
11402
  }
11002
11403
  }
11003
- const pidPath = import_node_path47.default.join(home, "neatd.pid");
11404
+ const pidPath = import_node_path50.default.join(home, "neatd.pid");
11004
11405
  await writeAtomically(pidPath, `${process.pid}
11005
11406
  `);
11006
11407
  const slots = /* @__PURE__ */ new Map();
@@ -11182,7 +11583,12 @@ async function startDaemon(opts = {}) {
11182
11583
  // only this project (the dashboard pins to it), and the daemon-wide
11183
11584
  // `/health` carries it at the top level for the spawn-reuse identity
11184
11585
  // check. Absent for the legacy multi-project daemon.
11185
- singleProject: singleProject && singleProjectPath ? { name: singleProject, path: singleProjectPath } : void 0
11586
+ singleProject: singleProject && singleProjectPath ? { name: singleProject, path: singleProjectPath } : void 0,
11587
+ // ADR-136 — the connector-status endpoint reads ~/.neat/connectors.json
11588
+ // through the same resolved home the slot bootstrap read it from, so a
11589
+ // daemon given an explicit NEAT_HOME serves status for the same file it
11590
+ // polls.
11591
+ connectorsHome: home
11186
11592
  });
11187
11593
  restAddress = await restApp.listen({ port: restPort, host });
11188
11594
  console.log(
@@ -11194,7 +11600,7 @@ async function startDaemon(opts = {}) {
11194
11600
  }
11195
11601
  if (restApp) await restApp.close().catch(() => {
11196
11602
  });
11197
- await import_node_fs28.promises.unlink(pidPath).catch(() => {
11603
+ await import_node_fs31.promises.unlink(pidPath).catch(() => {
11198
11604
  });
11199
11605
  throw new Error(
11200
11606
  `neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
@@ -11320,7 +11726,7 @@ async function startDaemon(opts = {}) {
11320
11726
  });
11321
11727
  if (otlpApp) await otlpApp.close().catch(() => {
11322
11728
  });
11323
- await import_node_fs28.promises.unlink(pidPath).catch(() => {
11729
+ await import_node_fs31.promises.unlink(pidPath).catch(() => {
11324
11730
  });
11325
11731
  throw new Error(
11326
11732
  `neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
@@ -11355,7 +11761,7 @@ async function startDaemon(opts = {}) {
11355
11761
  });
11356
11762
  if (otlpApp) await otlpApp.close().catch(() => {
11357
11763
  });
11358
- await import_node_fs28.promises.unlink(pidPath).catch(() => {
11764
+ await import_node_fs31.promises.unlink(pidPath).catch(() => {
11359
11765
  });
11360
11766
  throw new Error(
11361
11767
  `neatd: failed to write daemon.json for "${singleProject}" \u2014 ${err.message}`
@@ -11402,9 +11808,9 @@ async function startDaemon(opts = {}) {
11402
11808
  let registryWatcher = null;
11403
11809
  let reloadTimer = null;
11404
11810
  if (!singleProject) try {
11405
- const regDir = import_node_path47.default.dirname(regPath);
11406
- const regBase = import_node_path47.default.basename(regPath);
11407
- registryWatcher = (0, import_node_fs28.watch)(regDir, (_eventType, filename) => {
11811
+ const regDir = import_node_path50.default.dirname(regPath);
11812
+ const regBase = import_node_path50.default.basename(regPath);
11813
+ registryWatcher = (0, import_node_fs31.watch)(regDir, (_eventType, filename) => {
11408
11814
  if (filename !== null && filename !== regBase) return;
11409
11815
  if (reloadTimer) clearTimeout(reloadTimer);
11410
11816
  reloadTimer = setTimeout(() => {
@@ -11453,7 +11859,7 @@ async function startDaemon(opts = {}) {
11453
11859
  if (daemonRecord) {
11454
11860
  await clearDaemonRecord(daemonRecord, home);
11455
11861
  }
11456
- await import_node_fs28.promises.unlink(pidPath).catch(() => {
11862
+ await import_node_fs31.promises.unlink(pidPath).catch(() => {
11457
11863
  });
11458
11864
  };
11459
11865
  return {
@@ -11476,9 +11882,9 @@ init_auth();
11476
11882
  // src/web-spawn.ts
11477
11883
  init_cjs_shims();
11478
11884
  var import_node_child_process2 = require("child_process");
11479
- var import_node_fs29 = require("fs");
11885
+ var import_node_fs32 = require("fs");
11480
11886
  var import_node_net = __toESM(require("net"), 1);
11481
- var import_node_path48 = __toESM(require("path"), 1);
11887
+ var import_node_path51 = __toESM(require("path"), 1);
11482
11888
  var DEFAULT_WEB_PORT = 6328;
11483
11889
  var DEFAULT_REST_PORT = 8080;
11484
11890
  function asValidPort(value) {
@@ -11487,11 +11893,11 @@ function asValidPort(value) {
11487
11893
  }
11488
11894
  function projectRoot() {
11489
11895
  const fromEnv = process.env.NEAT_SCAN_PATH;
11490
- return import_node_path48.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
11896
+ return import_node_path51.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
11491
11897
  }
11492
11898
  async function readDaemonPorts(root) {
11493
11899
  try {
11494
- const raw = await import_node_fs29.promises.readFile(import_node_path48.default.join(root, "neat-out", "daemon.json"), "utf8");
11900
+ const raw = await import_node_fs32.promises.readFile(import_node_path51.default.join(root, "neat-out", "daemon.json"), "utf8");
11495
11901
  const parsed = JSON.parse(raw);
11496
11902
  const ports = parsed?.ports ?? {};
11497
11903
  return { web: asValidPort(ports.web), rest: asValidPort(ports.rest) };
@@ -11536,10 +11942,10 @@ function resolveWebPackageDir() {
11536
11942
  eval("require")
11537
11943
  );
11538
11944
  const pkgJsonPath = req.resolve("@neat.is/web/package.json");
11539
- return import_node_path48.default.dirname(pkgJsonPath);
11945
+ return import_node_path51.default.dirname(pkgJsonPath);
11540
11946
  }
11541
11947
  function resolveStandaloneServerEntry(webDir) {
11542
- return import_node_path48.default.join(webDir, ".next/standalone/packages/web/server.js");
11948
+ return import_node_path51.default.join(webDir, ".next/standalone/packages/web/server.js");
11543
11949
  }
11544
11950
  async function pickInternalPort() {
11545
11951
  return new Promise((resolve, reject) => {
@@ -11592,7 +11998,7 @@ async function spawnWebUI(restPort, opts = {}) {
11592
11998
  NEAT_API_URL: apiUrl
11593
11999
  };
11594
12000
  child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
11595
- cwd: import_node_path48.default.dirname(serverEntry),
12001
+ cwd: import_node_path51.default.dirname(serverEntry),
11596
12002
  env,
11597
12003
  stdio: ["ignore", "inherit", "inherit"],
11598
12004
  detached: false
@@ -11768,14 +12174,14 @@ function localVersion() {
11768
12174
  }
11769
12175
  function neatHome3() {
11770
12176
  if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
11771
- return import_node_path49.default.resolve(process.env.NEAT_HOME);
12177
+ return import_node_path52.default.resolve(process.env.NEAT_HOME);
11772
12178
  }
11773
12179
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11774
- return import_node_path49.default.join(home, ".neat");
12180
+ return import_node_path52.default.join(home, ".neat");
11775
12181
  }
11776
12182
  async function readPid() {
11777
12183
  try {
11778
- const raw = await import_node_fs30.promises.readFile(import_node_path49.default.join(neatHome3(), "neatd.pid"), "utf8");
12184
+ const raw = await import_node_fs33.promises.readFile(import_node_path52.default.join(neatHome3(), "neatd.pid"), "utf8");
11779
12185
  const n = Number.parseInt(raw.trim(), 10);
11780
12186
  return Number.isFinite(n) ? n : null;
11781
12187
  } catch {